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