1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Implement CPPC FFH helper routines for RISC-V.
4  *
5  * Copyright (C) 2024 Ventana Micro Systems Inc.
6  */
7 
8 #include <acpi/cppc_acpi.h>
9 #include <asm/csr.h>
10 #include <asm/sbi.h>
11 
12 #define SBI_EXT_CPPC 0x43505043
13 
14 /* CPPC interfaces defined in SBI spec */
15 #define SBI_CPPC_PROBE			0x0
16 #define SBI_CPPC_READ			0x1
17 #define SBI_CPPC_READ_HI		0x2
18 #define SBI_CPPC_WRITE			0x3
19 
20 /* RISC-V FFH definitions from RISC-V FFH spec */
21 #define FFH_CPPC_TYPE(r)		(((r) & GENMASK_ULL(63, 60)) >> 60)
22 #define FFH_CPPC_SBI_REG(r)		((r) & GENMASK(31, 0))
23 #define FFH_CPPC_CSR_NUM(r)		((r) & GENMASK(11, 0))
24 
25 #define FFH_CPPC_SBI			0x1
26 #define FFH_CPPC_CSR			0x2
27 
28 struct sbi_cppc_data {
29 	u64 val;
30 	u32 reg;
31 	struct sbiret ret;
32 };
33 
34 static bool cppc_ext_present;
35 
sbi_cppc_init(void)36 static int __init sbi_cppc_init(void)
37 {
38 	if (sbi_spec_version >= sbi_mk_version(2, 0) &&
39 	    sbi_probe_extension(SBI_EXT_CPPC) > 0) {
40 		pr_info("SBI CPPC extension detected\n");
41 		cppc_ext_present = true;
42 	} else {
43 		pr_info("SBI CPPC extension NOT detected!!\n");
44 		cppc_ext_present = false;
45 	}
46 
47 	return 0;
48 }
49 device_initcall(sbi_cppc_init);
50 
sbi_cppc_read(void * read_data)51 static void sbi_cppc_read(void *read_data)
52 {
53 	struct sbi_cppc_data *data = (struct sbi_cppc_data *)read_data;
54 
55 	data->ret = sbi_ecall(SBI_EXT_CPPC, SBI_CPPC_READ,
56 			      data->reg, 0, 0, 0, 0, 0);
57 }
58 
sbi_cppc_write(void * write_data)59 static void sbi_cppc_write(void *write_data)
60 {
61 	struct sbi_cppc_data *data = (struct sbi_cppc_data *)write_data;
62 
63 	data->ret = sbi_ecall(SBI_EXT_CPPC, SBI_CPPC_WRITE,
64 			      data->reg, data->val, 0, 0, 0, 0);
65 }
66 
cppc_ffh_csr_read(void * read_data)67 static void cppc_ffh_csr_read(void *read_data)
68 {
69 	struct sbi_cppc_data *data = (struct sbi_cppc_data *)read_data;
70 
71 	switch (data->reg) {
72 	/* Support only TIME CSR for now */
73 	case CSR_TIME:
74 		data->ret.value = csr_read(CSR_TIME);
75 		data->ret.error = 0;
76 		break;
77 	default:
78 		data->ret.error = -EINVAL;
79 		break;
80 	}
81 }
82 
cppc_ffh_csr_write(void * write_data)83 static void cppc_ffh_csr_write(void *write_data)
84 {
85 	struct sbi_cppc_data *data = (struct sbi_cppc_data *)write_data;
86 
87 	data->ret.error = -EINVAL;
88 }
89 
90 /*
91  * Refer to drivers/acpi/cppc_acpi.c for the description of the functions
92  * below.
93  */
cpc_ffh_supported(void)94 bool cpc_ffh_supported(void)
95 {
96 	return true;
97 }
98 
cpc_read_ffh(int cpu,struct cpc_reg * reg,u64 * val)99 int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val)
100 {
101 	struct sbi_cppc_data data;
102 
103 	if (WARN_ON_ONCE(irqs_disabled()))
104 		return -EPERM;
105 
106 	if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_SBI) {
107 		if (!cppc_ext_present)
108 			return -EINVAL;
109 
110 		data.reg = FFH_CPPC_SBI_REG(reg->address);
111 
112 		smp_call_function_single(cpu, sbi_cppc_read, &data, 1);
113 
114 		*val = data.ret.value;
115 
116 		return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
117 	} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
118 		data.reg = FFH_CPPC_CSR_NUM(reg->address);
119 
120 		smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1);
121 
122 		*val = data.ret.value;
123 
124 		return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
125 	}
126 
127 	return -EINVAL;
128 }
129 
cpc_write_ffh(int cpu,struct cpc_reg * reg,u64 val)130 int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val)
131 {
132 	struct sbi_cppc_data data;
133 
134 	if (WARN_ON_ONCE(irqs_disabled()))
135 		return -EPERM;
136 
137 	if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_SBI) {
138 		if (!cppc_ext_present)
139 			return -EINVAL;
140 
141 		data.reg = FFH_CPPC_SBI_REG(reg->address);
142 		data.val = val;
143 
144 		smp_call_function_single(cpu, sbi_cppc_write, &data, 1);
145 
146 		return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
147 	} else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) {
148 		data.reg = FFH_CPPC_CSR_NUM(reg->address);
149 		data.val = val;
150 
151 		smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1);
152 
153 		return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0;
154 	}
155 
156 	return -EINVAL;
157 }
158