blob: 4b2d45b062e5ba8021781f1d6776c0a46918dece [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* andes_pmu.c - Andes PMU device callbacks and platform overrides
*
* Copyright (C) 2023 Andes Technology Corporation
*/
#include <andes/andes45.h>
#include <andes/andes_hpm.h>
#include <andes/andes_pmu.h>
#include <sbi/sbi_bitops.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_pmu.h>
#include <libfdt.h>
static void andes_hw_counter_enable_irq(uint32_t ctr_idx)
{
unsigned long mip_val;
if (ctr_idx >= SBI_PMU_HW_CTR_MAX)
return;
mip_val = csr_read(CSR_MIP);
if (!(mip_val & MIP_PMOVI))
csr_clear(CSR_MCOUNTEROVF, BIT(ctr_idx));
csr_set(CSR_MCOUNTERINTEN, BIT(ctr_idx));
}
static void andes_hw_counter_disable_irq(uint32_t ctr_idx)
{
csr_clear(CSR_MCOUNTERINTEN, BIT(ctr_idx));
}
static void andes_hw_counter_filter_mode(unsigned long flags, int ctr_idx)
{
if (flags & SBI_PMU_CFG_FLAG_SET_UINH)
csr_set(CSR_MCOUNTERMASK_U, BIT(ctr_idx));
else
csr_clear(CSR_MCOUNTERMASK_U, BIT(ctr_idx));
if (flags & SBI_PMU_CFG_FLAG_SET_SINH)
csr_set(CSR_MCOUNTERMASK_S, BIT(ctr_idx));
else
csr_clear(CSR_MCOUNTERMASK_S, BIT(ctr_idx));
}
static struct sbi_pmu_device andes_pmu = {
.name = "andes_pmu",
.hw_counter_enable_irq = andes_hw_counter_enable_irq,
.hw_counter_disable_irq = andes_hw_counter_disable_irq,
/*
* We set delegation of supervisor local interrupts via
* 18th bit on mslideleg instead of mideleg, so leave
* hw_counter_irq_bit() callback unimplemented.
*/
.hw_counter_irq_bit = NULL,
.hw_counter_filter_mode = andes_hw_counter_filter_mode
};
int andes_pmu_extensions_init(const struct fdt_match *match,
struct sbi_hart_features *hfeatures)
{
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
if (!has_andes_pmu())
return 0;
/*
* Don't expect both Andes PMU and standard Sscofpmf/Smcntrpmf,
* are supported as they serve the same purpose.
*/
if (sbi_hart_has_extension(scratch, SBI_HART_EXT_SSCOFPMF) ||
sbi_hart_has_extension(scratch, SBI_HART_EXT_SMCNTRPMF))
return SBI_EINVAL;
sbi_hart_update_extension(scratch, SBI_HART_EXT_XANDESPMU, true);
/* Inhibit all HPM counters in M-mode */
csr_write(CSR_MCOUNTERMASK_M, 0xfffffffd);
/* Delegate counter overflow interrupt to S-mode */
csr_write(CSR_MSLIDELEG, MIP_PMOVI);
return 0;
}
int andes_pmu_init(const struct fdt_match *match)
{
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
void *fdt = fdt_get_address();
int pmu_offset;
if (sbi_hart_has_extension(scratch, SBI_HART_EXT_XANDESPMU))
sbi_pmu_set_device(&andes_pmu);
/*
* Populate default mappings if device-tree doesn't
* provide a valid pmu node.
*/
pmu_offset = fdt_node_offset_by_compatible(fdt, -1, "riscv,pmu");
if (pmu_offset < 0)
return (pmu_offset == -FDT_ERR_NOTFOUND) ? andes_pmu_setup()
: SBI_EFAIL;
return 0;
}