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