xref: /rk3399_rockchip-uboot/drivers/cpu/rockchip_amp.c (revision 5a94b26492fd3ad20c580976e18e101b67d14e6e)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2021 Rockchip Electronics Co., Ltd
4  */
5 
6 #include <common.h>
7 #include <amp.h>
8 #include <bidram.h>
9 #include <boot_rkimg.h>
10 #include <config.h>
11 #include <sysmem.h>
12 #include <asm/gic.h>
13 #include <asm/io.h>
14 #include <asm/arch/rockchip_smccc.h>
15 
16 /*
17  * [Design Principles]
18  *
19  * [amp.img]
20  *	The amp image with FIT format which consists of non-linux firmwares.
21  *	Please refer to: driver/cpu/amp.its.
22  *
23  *	amp.img generation: ./tools/mkimage -f drivers/cpu/amp.its -E -p 0xe00 amp.img
24  *
25  * [linux]
26  *	We still use the traditional solution for a better compatibility:
27  *	boot.img/recovery.img with FIT format or Android format.
28  *
29  *	The developer need add "/configurations/conf/linux" node to configure:
30  *	description, arch, cpu, thumb, hyp, udelay(optional) properties.
31  *	The addresses depend on U-Boot: kernel_addr_r, fdt_addr_r and
32  *	ramdisk_addr_r. Please refer to: driver/cpu/amp.its.
33  *
34  * [memory management]
35  *	U-Boot is not responsible for memory distribution/fixup any more, please
36  *	handle this on kernel dts "/memory".
37  *
38  * [trust]
39  *	The AMP feature requires trust support.
40  */
41 
42 #define AMP_PART	"amp"
43 #define gicd_readl(offset)	readl((void *)GICD_BASE + (offset))
44 #define gicd_writel(v, offset)	writel(v, (void *)GICD_BASE + (offset))
45 
46 typedef struct boot_args {
47 	ulong arg0;
48 	ulong arg1;
49 	ulong arg2;
50 	ulong arg3;
51 } boot_args_t;
52 
53 typedef struct boot_cpu {
54 	u32 arch;
55 	u32 state;
56 	u32 entry;
57 	u32 linux_os;
58 } boot_cpu_t;
59 
60 static boot_cpu_t g_bootcpu;
61 
62 static u32 fit_get_u32_default(const void *fit, int noffset,
63 			       const char *prop, u32 def)
64 {
65 	const fdt32_t *val;
66 
67 	val = fdt_getprop(fit, noffset, prop, NULL);
68 	if (!val)
69 		return def;
70 
71 	return fdt32_to_cpu(*val);
72 }
73 
74 static int load_linux_for_nonboot_cpu(u32 cpu, u32 aarch64, u32 load,
75 				      u32 *entry, boot_args_t *args)
76 {
77 	static const char *boot_cmd[] = {
78 		"boot_fit", "boot_android ${devtype} ${devnum}" };
79 	int i, ret;
80 
81 	env_set_hex("bootm_states_unmask", BOOTM_STATE_OS_GO);
82 	for (i = 0; i < ARRAY_SIZE(boot_cmd); i++) {
83 		ret = run_command(boot_cmd[i], 0);
84 		if (!ret)
85 			break;
86 	}
87 	env_set("bootm_states_unmask", NULL);
88 	if (ret) {
89 		AMP_E("Load linux failed, ret=%d\n", ret);
90 		return ret;
91 	}
92 
93 	/* linux boot args */
94 	if (aarch64) {
95 		args->arg0 = (ulong)gd->fdt_blob;
96 		args->arg1 = 0;
97 		args->arg2 = 0;
98 	} else {
99 		args->arg0 = 0;
100 		args->arg1 = 0;
101 		args->arg2 = (ulong)gd->fdt_blob;
102 	}
103 
104 	/* don't need call cleanup_before_linux() as this nonboot cpu is clean */
105 	board_quiesce_devices(&images);
106 	flush_dcache_all();
107 
108 	/* fixup: ramdisk/fdt/entry depend on U-Boot */
109 	*entry = env_get_ulong("kernel_addr_r", 16, 0);
110 
111 	return 0;
112 }
113 
114 static int is_default_pe_state(u32 pe_state)
115 {
116 #ifdef CONFIG_ARM64
117 	return (pe_state == PE_STATE(1, 1, 0, 0));
118 #else
119 	return (pe_state == PE_STATE(0, 0, 0, 0));
120 #endif
121 }
122 
123 static void setup_sync_bits_for_linux(void)
124 {
125 	u32 val, num_irq, offset;
126 
127 	val = gicd_readl(GICD_CTLR);
128 	val &= ~0x3;
129 	gicd_writel(val, GICD_CTLR);
130 
131 	num_irq = 32 * ((gicd_readl(GICD_TYPER) & 0x1F) + 1);
132 	offset = ((num_irq - 1) / 4) * 4;
133 	gicd_writel(0x0, GICD_IPRIORITYRn + offset);
134 }
135 
136 static int smc_cpu_on(u32 cpu, u32 pe_state, u32 entry,
137 		      boot_args_t *args, bool is_linux)
138 {
139 	int ret;
140 
141 	AMP_I("Brought up cpu[%x] with state 0x%x, entry 0x%08x ...",
142 	      cpu, pe_state, entry);
143 
144 	/* if target pe state is default arch state, power up cpu directly */
145 	if (is_default_pe_state(pe_state))
146 		goto finish;
147 
148 	ret = sip_smc_amp_cfg(AMP_PE_STATE, cpu, pe_state, 0);
149 	if (ret) {
150 		AMP_E("smc pe-state, ret=%d\n", ret);
151 		return ret;
152 	}
153 
154 	/* only linux needs boot args */
155 	if (!is_linux)
156 		goto finish;
157 
158 	ret = sip_smc_amp_cfg(AMP_BOOT_ARG01, cpu, args->arg0, args->arg1);
159 	if (ret) {
160 		AMP_E("smc boot arg01, ret=%d\n", ret);
161 		return ret;
162 	}
163 
164 	ret = sip_smc_amp_cfg(AMP_BOOT_ARG23, cpu, args->arg2, args->arg3);
165 	if (ret) {
166 		AMP_E("smc boot arg23, ret=%d\n", ret);
167 		return ret;
168 	}
169 
170 finish:
171 	ret = psci_cpu_on(cpu, entry);
172 	if (ret) {
173 		printf("cpu up failed, ret=%d\n", ret);
174 		return ret;
175 	}
176 	printf("OK\n");
177 
178 	return 0;
179 }
180 
181 static int brought_up_amp(void *fit, int noffset,
182 			  boot_cpu_t *bootcpu, int is_linux)
183 {
184 	const char *desc;
185 	boot_args_t args;
186 	u32 cpu, aarch64, hyp;
187 	u32 load, thumb, us;
188 	u32 pe_state, entry;
189 	int data_size;
190 	int ret;
191 	u8  arch = -ENODATA;
192 
193 	desc = fdt_getprop(fit, noffset, "description", NULL);
194 	cpu = fit_get_u32_default(fit, noffset, "cpu", -ENODATA);
195 	hyp = fit_get_u32_default(fit, noffset, "hyp", 0);
196 	thumb = fit_get_u32_default(fit, noffset, "thumb", 0);
197 	load = fit_get_u32_default(fit, noffset, "load", -ENODATA);
198 	us = fit_get_u32_default(fit, noffset, "udelay", 0);
199 	fit_image_get_arch(fit, noffset, &arch);
200 	fit_image_get_data_size(fit, noffset, &data_size);
201 	memset(&args, 0, sizeof(args));
202 
203 	if (!desc || cpu == -ENODATA || arch == -ENODATA ||
204 	    (load == -ENODATA && !is_linux)) {
205 		AMP_E("Property missing!\n");
206 		return -EINVAL;
207 	}
208 	aarch64 = (arch == IH_ARCH_ARM) ? 0 : 1;
209 	pe_state = PE_STATE(aarch64, hyp, thumb, 0);
210 	entry = load;
211 
212 #ifdef DEBUG
213 	AMP_I("       desc: %s\n", desc);
214 	AMP_I("        cpu: 0x%x\n", cpu);
215 	AMP_I("    aarch64: %d\n", aarch64);
216 	AMP_I("        hyp: %d\n", hyp);
217 	AMP_I("      thumb: %d\n", thumb);
218 	AMP_I("      entry: 0x%08x\n", entry);
219 	AMP_I("   pe_state: 0x%08x\n", pe_state);
220 	AMP_I("   linux-os: %d\n\n", is_linux);
221 #endif
222 
223 	if ((read_mpidr() & 0x0fff) == cpu) {
224 		bootcpu->arch = arch;
225 		bootcpu->entry = entry;
226 		bootcpu->state = pe_state;
227 		bootcpu->linux_os = is_linux;
228 		return 0;
229 	}
230 
231 	/* === only nonboot cpu can reach here === */
232 
233 	/* load or check */
234 	if (is_linux) {
235 		ret = load_linux_for_nonboot_cpu(cpu,
236 				aarch64, load, &entry, &args);
237 		if (ret)
238 			return ret;
239 		/*
240 		 * Must setup before jump to linux.
241 		 * This is an appointment on RK amp solution to handle
242 		 * GIC configure competition.
243 		 */
244 		setup_sync_bits_for_linux();
245 	} else {
246 		if (!sysmem_alloc_base_by_name(desc,
247 				(phys_addr_t)load, data_size))
248 			return -ENXIO;
249 	}
250 
251 	/* wakeup */
252 	ret = smc_cpu_on(cpu, pe_state, entry, &args, is_linux);
253 	if (ret)
254 		return ret;
255 
256 	if (us)
257 		udelay(us);
258 
259 	return 0;
260 }
261 
262 static int brought_up_all_amp(void *fit, const char *fit_uname_cfg)
263 {
264 	int loadables_index;
265 	int linux_noffset;
266 	int conf_noffset;
267 	int cpu_noffset;
268 	int ret;
269 	const char *uname;
270 
271 	conf_noffset = fit_conf_get_node(fit, fit_uname_cfg);
272 	if (conf_noffset < 0)
273 		return conf_noffset;
274 
275 	linux_noffset = fdt_subnode_offset(fit, conf_noffset, "linux");
276 	if (linux_noffset > 0) {
277 		ret = brought_up_amp(fit, linux_noffset, &g_bootcpu, 1);
278 		if (ret)
279 			return ret;
280 	}
281 
282 	for (loadables_index = 0;
283 	     uname = fdt_stringlist_get(fit, conf_noffset,
284 			FIT_LOADABLE_PROP, loadables_index, NULL), uname;
285 	     loadables_index++) {
286 		cpu_noffset = fit_image_get_node(fit, uname);
287 		if (cpu_noffset < 0)
288 			return cpu_noffset;
289 
290 		ret = brought_up_amp(fit, cpu_noffset, &g_bootcpu, 0);
291 		if (ret)
292 			return ret;
293 	}
294 
295 	/* === only boot cpu can reach here === */
296 
297 	if (!g_bootcpu.linux_os) {
298 		flush_dcache_all();
299 		AMP_I("Brought up cpu[%x, self] with state 0x%x, entry 0x%08x ...",
300 		      (u32)read_mpidr() & 0x0fff, g_bootcpu.state, g_bootcpu.entry);
301 		cleanup_before_linux();
302 		printf("OK\n");
303 		armv8_switch_to_el2(0, 0, 0, g_bootcpu.state, (u64)g_bootcpu.entry,
304 		     g_bootcpu.arch == IH_ARCH_ARM ? ES_TO_AARCH32 : ES_TO_AARCH64);
305 	}
306 
307 	/* return: boot cpu continue to boot linux */
308 	return 0;
309 }
310 
311 int amp_cpus_on(void)
312 {
313 	struct blk_desc *dev_desc;
314 	bootm_headers_t images;
315 	disk_partition_t part;
316 	void *fit;
317 	int ret = 0;
318 
319 	dev_desc = rockchip_get_bootdev();
320 	if (!dev_desc)
321 		return -EIO;
322 
323 	if (part_get_info_by_name(dev_desc, AMP_PART, &part) < 0)
324 		return -ENODEV;
325 
326 	fit = malloc(part.size * part.blksz);
327 	if (!fit) {
328 		AMP_E("No memory, please increase CONFIG_SYS_MALLOC_LEN\n");
329 		return -ENOMEM;
330 	}
331 
332 	if (blk_dread(dev_desc, part.start, part.size, fit) != part.size) {
333 		ret = -EIO;
334 		goto out;
335 	}
336 
337 	if (fdt_check_header(fit)) {
338 		AMP_E("Not fit\n");
339 		ret = -EINVAL;
340 		goto out;
341 	}
342 
343 	/* Load loadables */
344 	memset(&images, 0, sizeof(images));
345 	images.fit_uname_cfg = "conf";
346 	images.fit_hdr_os = fit;
347 	images.verify = 1;
348 	ret = boot_get_loadable(0, NULL, &images, IH_ARCH_DEFAULT, NULL, NULL);
349 	if (ret) {
350 		AMP_E("Load loadables, ret=%d\n", ret);
351 		goto out;
352 	}
353 	flush_dcache_all();
354 
355 	/* Wakeup */
356 	ret = brought_up_all_amp(images.fit_hdr_os, images.fit_uname_cfg);
357 	if (ret)
358 		AMP_E("Brought up amps, ret=%d\n", ret);
359 out:
360 	free(fit);
361 
362 	return ret;
363 }
364 
365 int arm64_switch_amp_pe(bootm_headers_t *images)
366 {
367 	images->os.arch = g_bootcpu.arch;
368 	return g_bootcpu.state;
369 }
370 
371