]> Git Repo - J-u-boot.git/blob - arch/arm/mach-snapdragon/board.c
mach-snapdragon: parse memory ourselves
[J-u-boot.git] / arch / arm / mach-snapdragon / board.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Common initialisation for Qualcomm Snapdragon boards.
4  *
5  * Copyright (c) 2024 Linaro Ltd.
6  * Author: Caleb Connolly <[email protected]>
7  */
8
9 #include "time.h"
10 #include <asm/armv8/mmu.h>
11 #include <asm/gpio.h>
12 #include <asm/io.h>
13 #include <asm/psci.h>
14 #include <asm/system.h>
15 #include <dm/device.h>
16 #include <dm/pinctrl.h>
17 #include <dm/uclass-internal.h>
18 #include <dm/read.h>
19 #include <power/regulator.h>
20 #include <env.h>
21 #include <fdt_support.h>
22 #include <init.h>
23 #include <linux/arm-smccc.h>
24 #include <linux/bug.h>
25 #include <linux/psci.h>
26 #include <linux/sizes.h>
27 #include <lmb.h>
28 #include <malloc.h>
29 #include <fdt_support.h>
30 #include <usb.h>
31 #include <sort.h>
32
33 #include "qcom-priv.h"
34
35 DECLARE_GLOBAL_DATA_PTR;
36
37 static struct mm_region rbx_mem_map[CONFIG_NR_DRAM_BANKS + 2] = { { 0 } };
38
39 struct mm_region *mem_map = rbx_mem_map;
40
41 static struct {
42         phys_addr_t start;
43         phys_size_t size;
44 } prevbl_ddr_banks[CONFIG_NR_DRAM_BANKS] __section(".data") = { 0 };
45
46 int dram_init(void)
47 {
48         /*
49          * gd->ram_base / ram_size have been setup already
50          * in qcom_parse_memory().
51          */
52         return 0;
53 }
54
55 static int ddr_bank_cmp(const void *v1, const void *v2)
56 {
57         const struct {
58                 phys_addr_t start;
59                 phys_size_t size;
60         } *res1 = v1, *res2 = v2;
61
62         if (!res1->size)
63                 return 1;
64         if (!res2->size)
65                 return -1;
66
67         return (res1->start >> 24) - (res2->start >> 24);
68 }
69
70 /* This has to be done post-relocation since gd->bd isn't preserved */
71 static void qcom_configure_bi_dram(void)
72 {
73         int i;
74
75         for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
76                 gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start;
77                 gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size;
78         }
79 }
80
81 int dram_init_banksize(void)
82 {
83         qcom_configure_bi_dram();
84
85         return 0;
86 }
87
88 static void qcom_parse_memory(void)
89 {
90         ofnode node;
91         const fdt64_t *memory;
92         int memsize;
93         phys_addr_t ram_end = 0;
94         int i, j, banks;
95
96         node = ofnode_path("/memory");
97         if (!ofnode_valid(node)) {
98                 log_err("No memory node found in device tree!\n");
99                 return;
100         }
101         memory = ofnode_read_prop(node, "reg", &memsize);
102         if (!memory) {
103                 log_err("No memory configuration was provided by the previous bootloader!\n");
104                 return;
105         }
106
107         banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS);
108
109         if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2)
110                 log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS);
111
112         if (banks > CONFIG_NR_DRAM_BANKS)
113                 log_err("Provided more memory banks than we can handle\n");
114
115         for (i = 0, j = 0; i < banks * 2; i += 2, j++) {
116                 prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]);
117                 prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]);
118                 /* SM8650 boards sometimes have empty regions! */
119                 if (!prevbl_ddr_banks[j].size) {
120                         j--;
121                         continue;
122                 }
123                 ram_end = max(ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size);
124         }
125
126         /* Sort our RAM banks -_- */
127         qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp);
128
129         gd->ram_base = prevbl_ddr_banks[0].start;
130         gd->ram_size = ram_end - gd->ram_base;
131         debug("ram_base = %#011lx, ram_size = %#011llx, ram_end = %#011llx\n",
132               gd->ram_base, gd->ram_size, ram_end);
133 }
134
135 static void show_psci_version(void)
136 {
137         struct arm_smccc_res res;
138
139         arm_smccc_smc(ARM_PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0, 0, 0, 0, 0, &res);
140
141         debug("PSCI:  v%ld.%ld\n",
142               PSCI_VERSION_MAJOR(res.a0),
143               PSCI_VERSION_MINOR(res.a0));
144 }
145
146 /* We support booting U-Boot with an internal DT when running as a first-stage bootloader
147  * or for supporting quirky devices where it's easier to leave the downstream DT in place
148  * to improve ABL compatibility. Otherwise, we use the DT provided by ABL.
149  */
150 void *board_fdt_blob_setup(int *err)
151 {
152         struct fdt_header *fdt;
153         bool internal_valid, external_valid;
154
155         *err = 0;
156         fdt = (struct fdt_header *)get_prev_bl_fdt_addr();
157         external_valid = fdt && !fdt_check_header(fdt);
158         internal_valid = !fdt_check_header(gd->fdt_blob);
159
160         /*
161          * There is no point returning an error here, U-Boot can't do anything useful in this situation.
162          * Bail out while we can still print a useful error message.
163          */
164         if (!internal_valid && !external_valid)
165                 panic("Internal FDT is invalid and no external FDT was provided! (fdt=%#llx)\n",
166                       (phys_addr_t)fdt);
167
168         if (internal_valid) {
169                 debug("Using built in FDT\n");
170         } else {
171                 debug("Using external FDT\n");
172                 /* So we can use it before returning */
173                 gd->fdt_blob = fdt;
174         }
175
176         /*
177          * Parse the /memory node while we're here,
178          * this makes it easy to do other things early.
179          */
180         qcom_parse_memory();
181
182         return (void *)gd->fdt_blob;
183 }
184
185 void reset_cpu(void)
186 {
187         psci_system_reset();
188 }
189
190 /*
191  * Some Qualcomm boards require GPIO configuration when switching USB modes.
192  * Support setting this configuration via pinctrl state.
193  */
194 int board_usb_init(int index, enum usb_init_type init)
195 {
196         struct udevice *usb;
197         int ret = 0;
198
199         /* USB device */
200         ret = uclass_find_device_by_seq(UCLASS_USB, index, &usb);
201         if (ret) {
202                 printf("Cannot find USB device\n");
203                 return ret;
204         }
205
206         ret = dev_read_stringlist_search(usb, "pinctrl-names",
207                                          "device");
208         /* No "device" pinctrl state, so just bail */
209         if (ret < 0)
210                 return 0;
211
212         /* Select "default" or "device" pinctrl */
213         switch (init) {
214         case USB_INIT_HOST:
215                 pinctrl_select_state(usb, "default");
216                 break;
217         case USB_INIT_DEVICE:
218                 pinctrl_select_state(usb, "device");
219                 break;
220         default:
221                 debug("Unknown usb_init_type %d\n", init);
222                 break;
223         }
224
225         return 0;
226 }
227
228 /*
229  * Some boards still need board specific init code, they can implement that by
230  * overriding this function.
231  *
232  * FIXME: get rid of board specific init code
233  */
234 void __weak qcom_board_init(void)
235 {
236 }
237
238 int board_init(void)
239 {
240         regulators_enable_boot_on(false);
241         show_psci_version();
242         qcom_of_fixup_nodes();
243         qcom_board_init();
244         return 0;
245 }
246
247 /* Sets up the "board", and "soc" environment variables as well as constructing the devicetree
248  * path, with a few quirks to handle non-standard dtb filenames. This is not meant to be a
249  * comprehensive solution to automatically picking the DTB, but aims to be correct for the
250  * majority case. For most devices it should be possible to make this algorithm work by
251  * adjusting the root compatible property in the U-Boot DTS. Handling devices with multiple
252  * variants that are all supported by a single U-Boot image will require implementing device-
253  * specific detection.
254  */
255 static void configure_env(void)
256 {
257         const char *first_compat, *last_compat;
258         char *tmp;
259         char buf[32] = { 0 };
260         /*
261          * Most DTB filenames follow the scheme: qcom/<soc>-[vendor]-<board>.dtb
262          * The vendor is skipped when it's a Qualcomm reference board, or the
263          * db845c.
264          */
265         char dt_path[64] = { 0 };
266         int compat_count, ret;
267         ofnode root;
268
269         root = ofnode_root();
270         /* This is almost always 2, but be explicit that we want the first and last compatibles
271          * not the first and second.
272          */
273         compat_count = ofnode_read_string_count(root, "compatible");
274         if (compat_count < 2) {
275                 log_warning("%s: only one root compatible bailing!\n", __func__);
276                 return;
277         }
278
279         /* The most specific device compatible (e.g. "thundercomm,db845c") */
280         ret = ofnode_read_string_index(root, "compatible", 0, &first_compat);
281         if (ret < 0) {
282                 log_warning("Can't read first compatible\n");
283                 return;
284         }
285
286         /* The last compatible is always the SoC compatible */
287         ret = ofnode_read_string_index(root, "compatible", compat_count - 1, &last_compat);
288         if (ret < 0) {
289                 log_warning("Can't read second compatible\n");
290                 return;
291         }
292
293         /* Copy the second compat (e.g. "qcom,sdm845") into buf */
294         strlcpy(buf, last_compat, sizeof(buf) - 1);
295         tmp = buf;
296
297         /* strsep() is destructive, it replaces the comma with a \0 */
298         if (!strsep(&tmp, ",")) {
299                 log_warning("second compatible '%s' has no ','\n", buf);
300                 return;
301         }
302
303         /* tmp now points to just the "sdm845" part of the string */
304         env_set("soc", tmp);
305
306         /* Now figure out the "board" part from the first compatible */
307         memset(buf, 0, sizeof(buf));
308         strlcpy(buf, first_compat, sizeof(buf) - 1);
309         tmp = buf;
310
311         /* The Qualcomm reference boards (RBx, HDK, etc)  */
312         if (!strncmp("qcom", buf, strlen("qcom"))) {
313                 /*
314                  * They all have the first compatible as "qcom,<soc>-<board>"
315                  * (e.g. "qcom,qrb5165-rb5"). We extract just the part after
316                  * the dash.
317                  */
318                 if (!strsep(&tmp, "-")) {
319                         log_warning("compatible '%s' has no '-'\n", buf);
320                         return;
321                 }
322                 /* tmp is now "rb5" */
323                 env_set("board", tmp);
324         } else {
325                 if (!strsep(&tmp, ",")) {
326                         log_warning("compatible '%s' has no ','\n", buf);
327                         return;
328                 }
329                 /* for thundercomm we just want the bit after the comma (e.g. "db845c"),
330                  * for all other boards we replace the comma with a '-' and take both
331                  * (e.g. "oneplus-enchilada")
332                  */
333                 if (!strncmp("thundercomm", buf, strlen("thundercomm"))) {
334                         env_set("board", tmp);
335                 } else {
336                         *(tmp - 1) = '-';
337                         env_set("board", buf);
338                 }
339         }
340
341         /* Now build the full path name */
342         snprintf(dt_path, sizeof(dt_path), "qcom/%s-%s.dtb",
343                  env_get("soc"), env_get("board"));
344         env_set("fdtfile", dt_path);
345 }
346
347 void __weak qcom_late_init(void)
348 {
349 }
350
351 #define KERNEL_COMP_SIZE        SZ_64M
352
353 #define addr_alloc(size) lmb_alloc(size, SZ_2M)
354
355 /* Stolen from arch/arm/mach-apple/board.c */
356 int board_late_init(void)
357 {
358         u32 status = 0;
359
360         /* We need to be fairly conservative here as we support boards with just 1G of TOTAL RAM */
361         status |= env_set_hex("kernel_addr_r", addr_alloc(SZ_128M));
362         status |= env_set_hex("ramdisk_addr_r", addr_alloc(SZ_128M));
363         status |= env_set_hex("kernel_comp_addr_r", addr_alloc(KERNEL_COMP_SIZE));
364         status |= env_set_hex("kernel_comp_size", KERNEL_COMP_SIZE);
365         status |= env_set_hex("scriptaddr", addr_alloc(SZ_4M));
366         status |= env_set_hex("pxefile_addr_r", addr_alloc(SZ_4M));
367         status |= env_set_hex("fdt_addr_r", addr_alloc(SZ_2M));
368
369         if (status)
370                 log_warning("%s: Failed to set run time variables\n", __func__);
371
372         configure_env();
373         qcom_late_init();
374
375         return 0;
376 }
377
378 static void build_mem_map(void)
379 {
380         int i, j;
381
382         /*
383          * Ensure the peripheral block is sized to correctly cover the address range
384          * up to the first memory bank.
385          * Don't map the first page to ensure that we actually trigger an abort on a
386          * null pointer access rather than just hanging.
387          * FIXME: we should probably split this into more precise regions
388          */
389         mem_map[0].phys = 0x1000;
390         mem_map[0].virt = mem_map[0].phys;
391         mem_map[0].size = gd->bd->bi_dram[0].start - mem_map[0].phys;
392         mem_map[0].attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
393                          PTE_BLOCK_NON_SHARE |
394                          PTE_BLOCK_PXN | PTE_BLOCK_UXN;
395
396         for (i = 1, j = 0; i < ARRAY_SIZE(rbx_mem_map) - 1 && gd->bd->bi_dram[j].size; i++, j++) {
397                 mem_map[i].phys = gd->bd->bi_dram[j].start;
398                 mem_map[i].virt = mem_map[i].phys;
399                 mem_map[i].size = gd->bd->bi_dram[j].size;
400                 mem_map[i].attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | \
401                                    PTE_BLOCK_INNER_SHARE;
402         }
403
404         mem_map[i].phys = UINT64_MAX;
405         mem_map[i].size = 0;
406
407 #ifdef DEBUG
408         debug("Configured memory map:\n");
409         for (i = 0; mem_map[i].size; i++)
410                 debug("  0x%016llx - 0x%016llx: entry %d\n",
411                       mem_map[i].phys, mem_map[i].phys + mem_map[i].size, i);
412 #endif
413 }
414
415 u64 get_page_table_size(void)
416 {
417         return SZ_64K;
418 }
419
420 static int fdt_cmp_res(const void *v1, const void *v2)
421 {
422         const struct fdt_resource *res1 = v1, *res2 = v2;
423
424         return res1->start - res2->start;
425 }
426
427 #define N_RESERVED_REGIONS 32
428
429 /* Mark all no-map regions as PTE_TYPE_FAULT to prevent speculative access.
430  * On some platforms this is enough to trigger a security violation and trap
431  * to EL3.
432  */
433 static void carve_out_reserved_memory(void)
434 {
435         static struct fdt_resource res[N_RESERVED_REGIONS] = { 0 };
436         int parent, rmem, count, i = 0;
437         phys_addr_t start;
438         size_t size;
439
440         /* Some reserved nodes must be carved out, as the cache-prefetcher may otherwise
441          * attempt to access them, causing a security exception.
442          */
443         parent = fdt_path_offset(gd->fdt_blob, "/reserved-memory");
444         if (parent <= 0) {
445                 log_err("No reserved memory regions found\n");
446                 return;
447         }
448
449         /* Collect the reserved memory regions */
450         fdt_for_each_subnode(rmem, gd->fdt_blob, parent) {
451                 const fdt32_t *ptr;
452                 int len;
453                 if (!fdt_getprop(gd->fdt_blob, rmem, "no-map", NULL))
454                         continue;
455
456                 if (i == N_RESERVED_REGIONS) {
457                         log_err("Too many reserved regions!\n");
458                         break;
459                 }
460
461                 /* Read the address and size out from the reg property. Doing this "properly" with
462                  * fdt_get_resource() takes ~70ms on SDM845, but open-coding the happy path here
463                  * takes <1ms... Oh the woes of no dcache.
464                  */
465                 ptr = fdt_getprop(gd->fdt_blob, rmem, "reg", &len);
466                 if (ptr) {
467                         /* Qualcomm devices use #address/size-cells = <2> but all reserved regions are within
468                          * the 32-bit address space. So we can cheat here for speed.
469                          */
470                         res[i].start = fdt32_to_cpu(ptr[1]);
471                         res[i].end = res[i].start + fdt32_to_cpu(ptr[3]);
472                         i++;
473                 }
474         }
475
476         /* Sort the reserved memory regions by address */
477         count = i;
478         qsort(res, count, sizeof(struct fdt_resource), fdt_cmp_res);
479
480         /* Now set the right attributes for them. Often a lot of the regions are tightly packed together
481          * so we can optimise the number of calls to mmu_change_region_attr() by combining adjacent
482          * regions.
483          */
484         start = ALIGN_DOWN(res[0].start, SZ_2M);
485         size = ALIGN(res[0].end - start, SZ_2M);
486         for (i = 1; i <= count; i++) {
487                 /* We ideally want to 2M align everything for more efficient pagetables, but we must avoid
488                  * overwriting reserved memory regions which shouldn't be mapped as FAULT (like those with
489                  * compatible properties).
490                  * If within 2M of the previous region, bump the size to include this region. Otherwise
491                  * start a new region.
492                  */
493                 if (i == count || start + size < res[i].start - SZ_2M) {
494                         debug("  0x%016llx - 0x%016llx: reserved\n",
495                               start, start + size);
496                         mmu_change_region_attr(start, size, PTE_TYPE_FAULT);
497                         /* If this is the final region then quit here before we index
498                          * out of bounds...
499                          */
500                         if (i == count)
501                                 break;
502                         start = ALIGN_DOWN(res[i].start, SZ_2M);
503                         size = ALIGN(res[i].end - start, SZ_2M);
504                 } else {
505                         /* Bump size if this region is immediately after the previous one */
506                         size = ALIGN(res[i].end - start, SZ_2M);
507                 }
508         }
509 }
510
511 /* This function open-codes setup_all_pgtables() so that we can
512  * insert additional mappings *before* turning on the MMU.
513  */
514 void enable_caches(void)
515 {
516         u64 tlb_addr = gd->arch.tlb_addr;
517         u64 tlb_size = gd->arch.tlb_size;
518         u64 pt_size;
519         ulong carveout_start;
520
521         gd->arch.tlb_fillptr = tlb_addr;
522
523         build_mem_map();
524
525         icache_enable();
526
527         /* Create normal system page tables */
528         setup_pgtables();
529
530         pt_size = (uintptr_t)gd->arch.tlb_fillptr -
531                   (uintptr_t)gd->arch.tlb_addr;
532         debug("Primary pagetable size: %lluKiB\n", pt_size / 1024);
533
534         /* Create emergency page tables */
535         gd->arch.tlb_size -= pt_size;
536         gd->arch.tlb_addr = gd->arch.tlb_fillptr;
537         setup_pgtables();
538         gd->arch.tlb_emerg = gd->arch.tlb_addr;
539         gd->arch.tlb_addr = tlb_addr;
540         gd->arch.tlb_size = tlb_size;
541
542         /* We do the carveouts only for QCS404, for now. */
543         if (fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) {
544                 carveout_start = get_timer(0);
545                 /* Takes ~20-50ms on SDM845 */
546                 carve_out_reserved_memory();
547                 debug("carveout time: %lums\n", get_timer(carveout_start));
548         }
549         dcache_enable();
550 }
This page took 0.059257 seconds and 4 git commands to generate.