1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH
4  */
5 
6 #include <common.h>
7 #include <dm.h>
8 #include <log.h>
9 #include <mmc.h>
10 #include <spl.h>
11 #include <asm/global_data.h>
12 
13 #if CONFIG_IS_ENABLED(OF_LIBFDT)
14 /**
15  * spl_node_to_boot_device() - maps from a DT-node to a SPL boot device
16  * @node:	of_offset of the node
17  *
18  * The SPL framework uses BOOT_DEVICE_... constants to identify its boot
19  * sources.  These may take on a device-specific meaning, depending on
20  * what nodes are enabled in a DTS (e.g. BOOT_DEVICE_MMC1 may refer to
21  * different controllers/block-devices, depending on which SD/MMC controllers
22  * are enabled in any given DTS).  This function maps from a DT-node back
23  * onto a BOOT_DEVICE_... constant, considering the currently active devices.
24  *
25  * Returns
26  *   -ENOENT, if no device matching the node could be found
27  *   -ENOSYS, if the device matching the node can not be mapped onto a
28  *            SPL boot device (e.g. the third MMC device)
29  *   -1, for unspecified failures
30  *   a positive integer (from the BOOT_DEVICE_... family) on succes.
31  */
32 
spl_node_to_boot_device(int node)33 static int spl_node_to_boot_device(int node)
34 {
35 	struct udevice *parent;
36 
37 	/*
38 	 * This should eventually move into the SPL code, once SPL becomes
39 	 * aware of the block-device layer.  Until then (and to avoid unneeded
40 	 * delays in getting this feature out), it lives at the board-level.
41 	 */
42 	if (!uclass_get_device_by_of_offset(UCLASS_MMC, node, &parent)) {
43 		struct udevice *dev;
44 		struct blk_desc *desc = NULL;
45 
46 		for (device_find_first_child(parent, &dev);
47 		     dev;
48 		     device_find_next_child(&dev)) {
49 			if (device_get_uclass_id(dev) == UCLASS_BLK) {
50 				desc = dev_get_uclass_plat(dev);
51 				break;
52 			}
53 		}
54 
55 		if (!desc)
56 			return -ENOENT;
57 
58 		switch (desc->devnum) {
59 		case 0:
60 			return BOOT_DEVICE_MMC1;
61 		case 1:
62 			return BOOT_DEVICE_MMC2;
63 		default:
64 			return -ENOSYS;
65 		}
66 	} else if (!uclass_get_device_by_of_offset(UCLASS_SPI_FLASH, node,
67 		&parent)) {
68 		return BOOT_DEVICE_SPI;
69 	}
70 
71 	/*
72 	 * SPL doesn't differentiate SPI flashes, so we keep the detection
73 	 * brief and inaccurate... hopefully, the common SPL layer can be
74 	 * extended with awareness of the BLK layer (and matching OF_CONTROL)
75 	 * soon.
76 	 */
77 	if (!uclass_get_device_by_of_offset(UCLASS_SPI_FLASH, node, &parent))
78 		return BOOT_DEVICE_SPI;
79 
80 	return -1;
81 }
82 
83 /**
84  * board_spl_was_booted_from() - retrieves the of-path the SPL was loaded from
85  *
86  * To support a 'same-as-spl' specification in the search-order for the next
87  * stage, we need a SoC- or board-specific way to handshake with what 'came
88  * before us' (either a BROM or TPL stage) and map the info retrieved onto
89  * a OF path.
90  *
91  * Returns
92  *   NULL, on failure or if the device could not be identified
93  *   a of_path (a string), on success
94  */
board_spl_was_booted_from(void)95 __weak const char *board_spl_was_booted_from(void)
96 {
97 	debug("%s: no support for 'same-as-spl' for this board\n", __func__);
98 	return NULL;
99 }
100 
board_boot_order(u32 * spl_boot_list)101 void board_boot_order(u32 *spl_boot_list)
102 {
103 	/* In case of no fdt (or only plat), use spl_boot_device() */
104 	if (!CONFIG_IS_ENABLED(OF_CONTROL) || CONFIG_IS_ENABLED(OF_PLATDATA)) {
105 		spl_boot_list[0] = spl_boot_device();
106 		return;
107 	}
108 
109 	const void *blob = gd->fdt_blob;
110 	int chosen_node = fdt_path_offset(blob, "/chosen");
111 	int idx = 0;
112 	int elem;
113 	int boot_device;
114 	int node;
115 	const char *conf;
116 
117 	if (chosen_node < 0) {
118 		debug("%s: /chosen not found, using spl_boot_device()\n",
119 		      __func__);
120 		spl_boot_list[0] = spl_boot_device();
121 		return;
122 	}
123 
124 	for (elem = 0;
125 	     (conf = fdt_stringlist_get(blob, chosen_node,
126 					"u-boot,spl-boot-order", elem, NULL));
127 	     elem++) {
128 		const char *alias;
129 
130 		/* Handle the case of 'same device the SPL was loaded from' */
131 		if (strncmp(conf, "same-as-spl", 11) == 0) {
132 			conf = board_spl_was_booted_from();
133 			if (!conf)
134 				continue;
135 		}
136 
137 		/* First check if the list element is an alias */
138 		alias = fdt_get_alias(blob, conf);
139 		if (alias)
140 			conf = alias;
141 
142 		/* Try to resolve the config item (or alias) as a path */
143 		node = fdt_path_offset(blob, conf);
144 		if (node < 0) {
145 			debug("%s: could not find %s in FDT\n", __func__, conf);
146 			continue;
147 		}
148 
149 		/* Try to map this back onto SPL boot devices */
150 		boot_device = spl_node_to_boot_device(node);
151 		if (boot_device < 0) {
152 			debug("%s: could not map node @%x to a boot-device\n",
153 			      __func__, node);
154 			continue;
155 		}
156 
157 		spl_boot_list[idx++] = boot_device;
158 	}
159 
160 	/* If we had no matches, fall back to spl_boot_device */
161 	if (idx == 0)
162 		spl_boot_list[0] = spl_boot_device();
163 }
164 #endif
165