xref: /OK3568_Linux_fs/kernel/drivers/soc/rockchip/rockchip_amp.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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