1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Rockchip AMP support.
4 *
5 * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
6 * Author: Tony Xie <tony.xie@rock-chips.com>
7 */
8
9 #include <linux/clk.h>
10 #include <linux/module.h>
11 #include <linux/of.h>
12 #include <linux/platform_device.h>
13 #include <linux/pm_domain.h>
14 #include <linux/pm_runtime.h>
15 #include <linux/rockchip/rockchip_sip.h>
16
17 #define RK_CPU_STATUS_OFF 0
18 #define RK_CPU_STATUS_ON 1
19 #define RK_CPU_STATUS_BUSY -1
20
21 enum amp_cpu_ctrl_status {
22 AMP_CPU_STATUS_AMP_DIS = 0,
23 AMP_CPU_STATUS_EN,
24 AMP_CPU_STATUS_ON,
25 AMP_CPU_STATUS_OFF,
26 };
27
28 #define AMP_FLAG_CPU_ARM64 BIT(1)
29 #define AMP_FLAG_CPU_EL2_HYP BIT(2)
30 #define AMP_FLAG_CPU_ARM32_T BIT(3)
31
32 struct rkamp_device {
33 struct device *dev;
34 struct clk_bulk_data *clks;
35 int num_clks;
36 struct device **pd_dev;
37 int num_pds;
38 };
39
40 static struct {
41 u32 en;
42 u32 mode;
43 u64 entry;
44 u64 cpu_id;
45 } cpu_boot_info[CONFIG_NR_CPUS];
46
get_cpu_boot_info_idx(unsigned long cpu_id)47 static int get_cpu_boot_info_idx(unsigned long cpu_id)
48 {
49 int i;
50
51 for (i = 0; i < CONFIG_NR_CPUS; i++) {
52 if (cpu_boot_info[i].cpu_id == cpu_id)
53 return i;
54 }
55
56 return -EINVAL;
57 }
58
boot_cpu_show(struct device * dev,struct device_attribute * attr,char * buf)59 static ssize_t boot_cpu_show(struct device *dev,
60 struct device_attribute *attr,
61 char *buf)
62 {
63 char *str = buf;
64
65 str += sprintf(str, "cpu on/off:\n");
66 str += sprintf(str,
67 " echo on/off [cpu id] > /sys/rk_amp/boot_cpu\n");
68 str += sprintf(str, "get cpu on/off status:\n");
69 str += sprintf(str,
70 " echo status [cpu id] > /sys/rk_amp/boot_cpu\n");
71 if (str != buf)
72 *(str - 1) = '\n';
73
74 return (str - buf);
75 }
76
cpu_status_print(unsigned long cpu_id,struct arm_smccc_res * res)77 static void cpu_status_print(unsigned long cpu_id, struct arm_smccc_res *res)
78 {
79 if (res->a0) {
80 pr_info("get cpu-0x%lx status(%lx) error!\n", cpu_id, res->a0);
81 return;
82 }
83
84 if (res->a1 == AMP_CPU_STATUS_AMP_DIS)
85 pr_info("cpu-0x%lx amp is disable (%ld)\n", cpu_id, res->a1);
86 else if (res->a1 == AMP_CPU_STATUS_EN)
87 pr_info("cpu-0x%lx amp is enable (%ld)\n", cpu_id, res->a1);
88 else if (res->a1 == AMP_CPU_STATUS_ON)
89 pr_info("cpu-0x%lx amp: cpu is on (%ld)\n", cpu_id, res->a1);
90 else if (res->a1 == AMP_CPU_STATUS_OFF)
91 pr_info("cpu-0x%lx amp: cpu is off(%ld)\n", cpu_id, res->a1);
92 else
93 pr_info("cpu-0x%lx status(%ld) is error\n", cpu_id, res->a1);
94
95 if (res->a2 == RK_CPU_STATUS_OFF)
96 pr_info("cpu-0x%lx status(%ld) is off\n", cpu_id, res->a2);
97 else if (res->a2 == RK_CPU_STATUS_ON)
98 pr_info("cpu-0x%lx status(%ld) is on\n", cpu_id, res->a2);
99 else if (res->a2 == RK_CPU_STATUS_BUSY)
100 pr_info("cpu-0x%lx status(%ld) is busy\n", cpu_id, res->a2);
101 else
102 pr_info("cpu-0x%lx status(%ld) is error\n", cpu_id, res->a2);
103 }
104
boot_cpu_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)105 static ssize_t boot_cpu_store(struct device *dev,
106 struct device_attribute *attr,
107 const char *buf,
108 size_t count)
109 {
110 char cmd[10];
111 unsigned long cpu_id;
112 struct arm_smccc_res res = {0};
113 int ret, idx;
114
115 ret = sscanf(buf, "%s", cmd);
116 if (ret != 1) {
117 pr_info("Use on/off [cpu id] or status [cpu id]\n");
118 return -EINVAL;
119 }
120
121 if (!strncmp(cmd, "status", strlen("status"))) {
122 ret = sscanf(buf, "%s %lx", cmd, &cpu_id);
123
124 if (ret != 2)
125 return -EINVAL;
126
127 res = sip_smc_get_amp_info(RK_AMP_SUB_FUNC_GET_CPU_STATUS,
128 cpu_id);
129 cpu_status_print(cpu_id, &res);
130 } else if (!strncmp(cmd, "off", strlen("off"))) {
131 ret = sscanf(buf, "%s %lx", cmd, &cpu_id);
132 if (ret != 2)
133 return -EINVAL;
134
135 idx = get_cpu_boot_info_idx(cpu_id);
136
137 if (idx >= 0 && cpu_boot_info[idx].en) {
138 ret = sip_smc_amp_config(RK_AMP_SUB_FUNC_REQ_CPU_OFF,
139 cpu_id, 0, 0);
140 if (ret)
141 pr_info("requesting a cpu off is error(%d)!\n",
142 ret);
143 }
144 } else if (!strncmp(cmd, "on", strlen("on"))) {
145 ret = sscanf(buf, "%s %lx", cmd, &cpu_id);
146
147 if (ret != 2)
148 return -EINVAL;
149
150 idx = get_cpu_boot_info_idx(cpu_id);
151 if (idx >= 0 && cpu_boot_info[idx].en) {
152 ret = sip_smc_amp_config(RK_AMP_SUB_FUNC_CPU_ON,
153 cpu_id,
154 cpu_boot_info[idx].entry,
155 0);
156 if (ret)
157 pr_info("booting up a cpu is error(%d)!\n",
158 ret);
159 }
160 } else {
161 pr_info("unsupported cmd(%s)\n", cmd);
162 }
163
164 return count;
165 }
166
167 static struct kobject *rk_amp_kobj;
168 static struct device_attribute rk_amp_attrs[] = {
169 __ATTR(boot_cpu, 0664, boot_cpu_show, boot_cpu_store),
170 };
171
rockchip_amp_boot_cpus(struct device * dev,struct device_node * cpu_node,int idx)172 static int rockchip_amp_boot_cpus(struct device *dev,
173 struct device_node *cpu_node, int idx)
174 {
175 u64 cpu_entry, cpu_id;
176 u32 cpu_mode;
177 int ret;
178
179 if (idx >= CONFIG_NR_CPUS)
180 return -1;
181
182 if (of_property_read_u64_array(cpu_node, "entry", &cpu_entry, 1)) {
183 dev_warn(dev, "can not get the entry\n");
184 return -1;
185 }
186
187 if (!cpu_entry) {
188 dev_warn(dev, "cpu-entry is 0\n");
189 return -1;
190 }
191
192 if (of_property_read_u64_array(cpu_node, "id", &cpu_id, 1)) {
193 dev_warn(dev, "can not get the cpu id\n");
194 return -1;
195 }
196
197 if (of_property_read_u32_array(cpu_node, "mode", &cpu_mode, 1)) {
198 dev_warn(dev, "can not get the cpu mode\n");
199 return -1;
200 }
201
202 cpu_boot_info[idx].entry = cpu_entry;
203 cpu_boot_info[idx].mode = cpu_mode;
204 cpu_boot_info[idx].cpu_id = cpu_id;
205
206 ret = sip_smc_amp_config(RK_AMP_SUB_FUNC_CFG_MODE, cpu_id, cpu_mode, 0);
207 if (ret) {
208 dev_warn(dev, "setting cpu mode is error(%d)!\n", ret);
209 return ret;
210 }
211
212 ret = sip_smc_amp_config(RK_AMP_SUB_FUNC_CPU_ON, cpu_id, cpu_entry, 0);
213 if (ret) {
214 dev_warn(dev, "booting up a cpu is error(%d)!\n", ret);
215 return ret;
216 }
217
218 cpu_boot_info[idx].en = 1;
219
220 return 0;
221 }
222
rockchip_amp_probe(struct platform_device * pdev)223 static int rockchip_amp_probe(struct platform_device *pdev)
224 {
225 struct rkamp_device *rkamp_dev = NULL;
226 int ret, i, idx = 0;
227 struct device_node *cpus_node, *cpu_node;
228
229 rkamp_dev = devm_kzalloc(&pdev->dev, sizeof(*rkamp_dev), GFP_KERNEL);
230 if (!rkamp_dev)
231 return -ENOMEM;
232
233 rkamp_dev->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rkamp_dev->clks);
234 if (rkamp_dev->num_clks < 0)
235 return -ENODEV;
236 ret = clk_bulk_prepare_enable(rkamp_dev->num_clks, rkamp_dev->clks);
237 if (ret)
238 return dev_err_probe(&pdev->dev, ret, "failed to prepare enable clks: %d\n", ret);
239
240 pm_runtime_enable(&pdev->dev);
241
242 rkamp_dev->num_pds = of_count_phandle_with_args(pdev->dev.of_node, "power-domains",
243 "#power-domain-cells");
244
245 if (rkamp_dev->num_pds > 0) {
246 rkamp_dev->pd_dev = devm_kmalloc_array(&pdev->dev, rkamp_dev->num_pds,
247 sizeof(*rkamp_dev->pd_dev), GFP_KERNEL);
248 if (!rkamp_dev->pd_dev)
249 return -ENOMEM;
250
251 if (rkamp_dev->num_pds == 1) {
252 ret = pm_runtime_resume_and_get(&pdev->dev);
253 if (ret < 0)
254 return dev_err_probe(&pdev->dev, ret,
255 "failed to get power-domain\n");
256 } else {
257 for (i = 0; i < rkamp_dev->num_pds; i++) {
258 rkamp_dev->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);
259 ret = pm_runtime_resume_and_get(rkamp_dev->pd_dev[i]);
260 if (ret < 0)
261 return dev_err_probe(&pdev->dev, ret,
262 "failed to get pd_dev[%d]\n", i);
263 }
264 }
265 }
266
267 cpus_node = of_get_child_by_name(pdev->dev.of_node, "amp-cpus");
268
269 if (cpus_node) {
270 for_each_available_child_of_node(cpus_node, cpu_node) {
271 if (!rockchip_amp_boot_cpus(&pdev->dev, cpu_node,
272 idx)) {
273 idx++;
274 }
275 }
276 }
277
278 rk_amp_kobj = kobject_create_and_add("rk_amp", NULL);
279 if (!rk_amp_kobj)
280 return -ENOMEM;
281
282 for (i = 0; i < ARRAY_SIZE(rk_amp_attrs); i++) {
283 ret = sysfs_create_file(rk_amp_kobj, &rk_amp_attrs[i].attr);
284 if (ret)
285 return dev_err_probe(&pdev->dev, ret, "create file index %d error\n", i);
286 }
287
288 return 0;
289 }
290
rockchip_amp_remove(struct platform_device * pdev)291 static int rockchip_amp_remove(struct platform_device *pdev)
292 {
293 int i;
294 struct rkamp_device *rkamp_dev = platform_get_drvdata(pdev);
295
296 clk_bulk_disable_unprepare(rkamp_dev->num_clks, rkamp_dev->clks);
297
298 if (rkamp_dev->num_pds == 1) {
299 pm_runtime_put_sync(&pdev->dev);
300 } else if (rkamp_dev->num_pds > 1) {
301 for (i = 0; i < rkamp_dev->num_pds; i++) {
302 pm_runtime_put_sync(rkamp_dev->pd_dev[i]);
303 dev_pm_domain_detach(rkamp_dev->pd_dev[i], true);
304 rkamp_dev->pd_dev[i] = NULL;
305 }
306 }
307
308 pm_runtime_disable(&pdev->dev);
309
310 for (i = 0; i < ARRAY_SIZE(rk_amp_attrs); i++)
311 sysfs_remove_file(rk_amp_kobj, &rk_amp_attrs[i].attr);
312
313 kobject_put(rk_amp_kobj);
314
315 return 0;
316 }
317
318 static const struct of_device_id rockchip_amp_match[] = {
319 { .compatible = "rockchip,amp" },
320 { .compatible = "rockchip,mcu-amp" },
321 { .compatible = "rockchip,rk3568-amp" },
322 { /* sentinel */ },
323 };
324
325 MODULE_DEVICE_TABLE(of, rockchip_amp_match);
326
327 static struct platform_driver rockchip_amp_driver = {
328 .probe = rockchip_amp_probe,
329 .remove = rockchip_amp_remove,
330 .driver = {
331 .name = "rockchip-amp",
332 .of_match_table = rockchip_amp_match,
333 },
334 };
335 module_platform_driver(rockchip_amp_driver);
336
337 MODULE_DESCRIPTION("Rockchip AMP driver");
338 MODULE_AUTHOR("Tony xie<tony.xie@rock-chips.com>");
339 MODULE_LICENSE("GPL");
340