// SPDX-License-Identifier: GPL-2.0-only /* * Implement CPPC FFH helper routines for RISC-V. * * Copyright (C) 2024 Ventana Micro Systems Inc. */ #include #include #include #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; }