30f3ffbee8
Add cpufreq driver based on ACPI CPPC for RISC-V. The driver uses either SBI CPPC interfaces or the CSRs to access the CPPC registers as defined by the RISC-V FFH spec. Signed-off-by: Sunil V L <sunilvl@ventanamicro.com> Reviewed-by: Pierre Gondois <pierre.gondois@arm.com> Acked-by: Rafael J. Wysocki <rafael@kernel.org> Acked-by: Sudeep Holla <sudeep.holla@arm.com> Link: https://lore.kernel.org/r/20240208034414.22579-2-sunilvl@ventanamicro.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
158 lines
3.5 KiB
C
158 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Implement CPPC FFH helper routines for RISC-V.
|
|
*
|
|
* Copyright (C) 2024 Ventana Micro Systems Inc.
|
|
*/
|
|
|
|
#include <acpi/cppc_acpi.h>
|
|
#include <asm/csr.h>
|
|
#include <asm/sbi.h>
|
|
|
|
#define SBI_EXT_CPPC 0x43505043
|
|
|
|
/* CPPC interfaces defined in SBI spec */
|
|
#define SBI_CPPC_PROBE 0x0
|
|
#define SBI_CPPC_READ 0x1
|
|
#define SBI_CPPC_READ_HI 0x2
|
|
#define SBI_CPPC_WRITE 0x3
|
|
|
|
/* RISC-V FFH definitions from RISC-V FFH spec */
|
|
#define FFH_CPPC_TYPE(r) (((r) & GENMASK_ULL(63, 60)) >> 60)
|
|
#define FFH_CPPC_SBI_REG(r) ((r) & GENMASK(31, 0))
|
|
#define FFH_CPPC_CSR_NUM(r) ((r) & GENMASK(11, 0))
|
|
|
|
#define FFH_CPPC_SBI 0x1
|
|
#define FFH_CPPC_CSR 0x2
|
|
|
|
struct sbi_cppc_data {
|
|
u64 val;
|
|
u32 reg;
|
|
struct sbiret ret;
|
|
};
|
|
|
|
static bool cppc_ext_present;
|
|
|
|
static int __init sbi_cppc_init(void)
|
|
{
|
|
if (sbi_spec_version >= sbi_mk_version(2, 0) &&
|
|
sbi_probe_extension(SBI_EXT_CPPC) > 0) {
|
|
pr_info("SBI CPPC extension detected\n");
|
|
cppc_ext_present = true;
|
|
} else {
|
|
pr_info("SBI CPPC extension NOT detected!!\n");
|
|
cppc_ext_present = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
device_initcall(sbi_cppc_init);
|
|
|
|
static void sbi_cppc_read(void *read_data)
|
|
{
|
|
struct sbi_cppc_data *data = (struct sbi_cppc_data *)read_data;
|
|
|
|
data->ret = sbi_ecall(SBI_EXT_CPPC, SBI_CPPC_READ,
|
|
data->reg, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
static void sbi_cppc_write(void *write_data)
|
|
{
|
|
struct sbi_cppc_data *data = (struct sbi_cppc_data *)write_data;
|
|
|
|
data->ret = sbi_ecall(SBI_EXT_CPPC, SBI_CPPC_WRITE,
|
|
data->reg, data->val, 0, 0, 0, 0);
|
|
}
|
|
|
|
static void cppc_ffh_csr_read(void *read_data)
|
|
{
|
|
struct sbi_cppc_data *data = (struct sbi_cppc_data *)read_data;
|
|
|
|
switch (data->reg) {
|
|
/* Support only TIME CSR for now */
|
|
case CSR_TIME:
|
|
data->ret.value = csr_read(CSR_TIME);
|
|
data->ret.error = 0;
|
|
break;
|
|
default:
|
|
data->ret.error = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cppc_ffh_csr_write(void *write_data)
|
|
{
|
|
struct sbi_cppc_data *data = (struct sbi_cppc_data *)write_data;
|
|
|
|
data->ret.error = -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Refer to drivers/acpi/cppc_acpi.c for the description of the functions
|
|
* below.
|
|
*/
|
|
bool cpc_ffh_supported(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
|
|
{
|
|
struct sbi_cppc_data data;
|
|
|
|
if (WARN_ON_ONCE(irqs_disabled()))
|
|
return -EPERM;
|
|
|
|
if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_SBI) {
|
|
if (!cppc_ext_present)
|
|
return -EINVAL;
|
|
|
|
data.reg = FFH_CPPC_SBI_REG(reg->address);
|
|
|
|
smp_call_function_single(cpu, sbi_cppc_read, &data, 1);
|
|
|
|
*val = data.ret.value;
|
|
|
|
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
|
|
} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
|
|
data.reg = FFH_CPPC_CSR_NUM(reg->address);
|
|
|
|
smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1);
|
|
|
|
*val = data.ret.value;
|
|
|
|
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val)
|
|
{
|
|
struct sbi_cppc_data data;
|
|
|
|
if (WARN_ON_ONCE(irqs_disabled()))
|
|
return -EPERM;
|
|
|
|
if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_SBI) {
|
|
if (!cppc_ext_present)
|
|
return -EINVAL;
|
|
|
|
data.reg = FFH_CPPC_SBI_REG(reg->address);
|
|
data.val = val;
|
|
|
|
smp_call_function_single(cpu, sbi_cppc_write, &data, 1);
|
|
|
|
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
|
|
} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
|
|
data.reg = FFH_CPPC_CSR_NUM(reg->address);
|
|
data.val = val;
|
|
|
|
smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1);
|
|
|
|
return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|