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
28struct sbi_cppc_data {
29	u64 val;
30	u32 reg;
31	struct sbiret ret;
32};
33
34static bool cppc_ext_present;
35
36static 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}
49device_initcall(sbi_cppc_init);
50
51static 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
59static 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
67static 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
83static 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 */
94bool cpc_ffh_supported(void)
95{
96	return true;
97}
98
99int 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
130int 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