1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH
4 */
5
6#include <dm.h>
7#include <fdt_support.h>
8#include <log.h>
9#include <mmc.h>
10#include <spl.h>
11#include <asm/global_data.h>
12#include <dm/uclass-internal.h>
13
14#if CONFIG_IS_ENABLED(OF_LIBFDT)
15/**
16 * spl_node_to_boot_device() - maps from a DT-node to a SPL boot device
17 * @node:	of_offset of the node
18 *
19 * The SPL framework uses BOOT_DEVICE_... constants to identify its boot
20 * sources.  These may take on a device-specific meaning, depending on
21 * what nodes are enabled in a DTS (e.g. BOOT_DEVICE_MMC1 may refer to
22 * different controllers/block-devices, depending on which SD/MMC controllers
23 * are enabled in any given DTS).  This function maps from a DT-node back
24 * onto a BOOT_DEVICE_... constant, considering the currently active devices.
25 *
26 * Returns
27 *   -ENOENT, if no device matching the node could be found
28 *   -ENOSYS, if the device matching the node can not be mapped onto a
29 *            SPL boot device (e.g. the third MMC device)
30 *   -1, for unspecified failures
31 *   a positive integer (from the BOOT_DEVICE_... family) on success.
32 */
33
34static int spl_node_to_boot_device(int node)
35{
36	struct udevice *parent;
37
38	/*
39	 * This should eventually move into the SPL code, once SPL becomes
40	 * aware of the block-device layer.  Until then (and to avoid unneeded
41	 * delays in getting this feature out), it lives at the board-level.
42	 */
43	if (!uclass_get_device_by_of_offset(UCLASS_MMC, node, &parent)) {
44		struct udevice *dev;
45		struct blk_desc *desc = NULL;
46
47		for (device_find_first_child(parent, &dev);
48		     dev;
49		     device_find_next_child(&dev)) {
50			if (device_get_uclass_id(dev) == UCLASS_BLK) {
51				desc = dev_get_uclass_plat(dev);
52				break;
53			}
54		}
55
56		if (!desc)
57			return -ENOENT;
58
59		switch (desc->devnum) {
60		case 0:
61			return BOOT_DEVICE_MMC1;
62		case 1:
63			return BOOT_DEVICE_MMC2;
64		default:
65			return -ENOSYS;
66		}
67	}
68
69	/*
70	 * SPL doesn't differentiate SPI flashes, so we keep the detection
71	 * brief and inaccurate... hopefully, the common SPL layer can be
72	 * extended with awareness of the BLK layer (and matching OF_CONTROL)
73	 * soon.
74	 */
75	if (!uclass_get_device_by_of_offset(UCLASS_SPI_FLASH, node, &parent))
76		return BOOT_DEVICE_SPI;
77
78	return -1;
79}
80
81/**
82 * board_spl_was_booted_from() - retrieves the of-path the SPL was loaded from
83 *
84 * To support a 'same-as-spl' specification in the search-order for the next
85 * stage, we need a SoC- or board-specific way to handshake with what 'came
86 * before us' (either a BROM or TPL stage) and map the info retrieved onto
87 * a OF path.
88 *
89 * Returns
90 *   NULL, on failure or if the device could not be identified
91 *   a of_path (a string), on success
92 */
93__weak const char *board_spl_was_booted_from(void)
94{
95	debug("%s: no support for 'same-as-spl' for this board\n", __func__);
96	return NULL;
97}
98
99void board_boot_order(u32 *spl_boot_list)
100{
101	/* In case of no fdt (or only plat), use spl_boot_device() */
102	if (!CONFIG_IS_ENABLED(OF_CONTROL) || CONFIG_IS_ENABLED(OF_PLATDATA)) {
103		spl_boot_list[0] = spl_boot_device();
104		return;
105	}
106
107	const void *blob = gd->fdt_blob;
108	int chosen_node = fdt_path_offset(blob, "/chosen");
109	int idx = 0;
110	int elem;
111	int boot_device;
112	int node;
113	const char *conf;
114
115	if (chosen_node < 0) {
116		debug("%s: /chosen not found, using spl_boot_device()\n",
117		      __func__);
118		spl_boot_list[0] = spl_boot_device();
119		return;
120	}
121
122	for (elem = 0;
123	     (conf = fdt_stringlist_get(blob, chosen_node,
124					"u-boot,spl-boot-order", elem, NULL));
125	     elem++) {
126		const char *alias;
127
128		/* Handle the case of 'same device the SPL was loaded from' */
129		if (strncmp(conf, "same-as-spl", 11) == 0) {
130			conf = board_spl_was_booted_from();
131			if (!conf)
132				continue;
133		}
134
135		/* First check if the list element is an alias */
136		alias = fdt_get_alias(blob, conf);
137		if (alias)
138			conf = alias;
139
140		/* Try to resolve the config item (or alias) as a path */
141		node = fdt_path_offset(blob, conf);
142		if (node < 0) {
143			debug("%s: could not find %s in FDT\n", __func__, conf);
144			continue;
145		}
146
147		/* Try to map this back onto SPL boot devices */
148		boot_device = spl_node_to_boot_device(node);
149		if (boot_device < 0) {
150			debug("%s: could not map node %s to a boot-device\n",
151			      __func__, conf);
152			continue;
153		}
154
155		spl_boot_list[idx++] = boot_device;
156	}
157
158	/* If we had no matches, fall back to spl_boot_device */
159	if (idx == 0)
160		spl_boot_list[0] = spl_boot_device();
161}
162
163int spl_decode_boot_device(u32 boot_device, char *buf, size_t buflen)
164{
165	struct udevice *dev;
166#if CONFIG_IS_ENABLED(BLK)
167	int dev_num;
168#endif
169	int ret;
170
171	if (boot_device == BOOT_DEVICE_SPI) {
172		/* Revert spl_node_to_boot_device() logic to find appropriate SPI flash device */
173
174		/*
175		 * Devices with multiple SPI flash devices will take the first SPI flash found in
176		 * /chosen/u-boot,spl-boot-order.
177		 */
178		const void *blob = gd->fdt_blob;
179		int chosen_node = fdt_path_offset(blob, "/chosen");
180		int elem;
181		int node;
182		const char *conf;
183
184		if (chosen_node < 0) {
185			debug("%s: /chosen not found\n", __func__);
186			return -ENODEV;
187		}
188
189		for (elem = 0;
190		     (conf = fdt_stringlist_get(blob, chosen_node,
191						"u-boot,spl-boot-order", elem, NULL));
192		     elem++) {
193			const char *alias;
194
195			/* Handle the case of 'same device the SPL was loaded from' */
196			if (strncmp(conf, "same-as-spl", 11) == 0) {
197				conf = board_spl_was_booted_from();
198				if (!conf)
199					continue;
200			}
201
202			/* First check if the list element is an alias */
203			alias = fdt_get_alias(blob, conf);
204			if (alias)
205				conf = alias;
206
207			/* Try to resolve the config item (or alias) as a path */
208			node = fdt_path_offset(blob, conf);
209			if (node < 0) {
210				debug("%s: could not find %s in FDT\n", __func__, conf);
211				continue;
212			}
213
214			ret = uclass_find_device_by_of_offset(UCLASS_SPI_FLASH, node, &dev);
215			if (ret) {
216				debug("%s: could not find udevice for %s\n", __func__, conf);
217				continue;
218			}
219
220			return ofnode_get_path(dev_ofnode(dev), buf, buflen);
221		}
222
223		return -ENODEV;
224	}
225
226#if CONFIG_IS_ENABLED(BLK)
227	dev_num = (boot_device == BOOT_DEVICE_MMC1) ? 0 : 1;
228
229	ret = blk_find_device(UCLASS_MMC, dev_num, &dev);
230	if (ret) {
231		debug("%s: could not find blk device for MMC device %d: %d\n",
232		      __func__, dev_num, ret);
233		return ret;
234	}
235
236	dev = dev_get_parent(dev);
237	return ofnode_get_path(dev_ofnode(dev), buf, buflen);
238#else
239	return -ENODEV;
240#endif
241}
242
243void spl_perform_fixups(struct spl_image_info *spl_image)
244{
245	void *blob = spl_image_fdt_addr(spl_image);
246	char boot_ofpath[512];
247	int chosen, ret;
248
249	/*
250	 * Inject the ofpath of the device the full U-Boot (or Linux in
251	 * Falcon-mode) was booted from into the FDT, if a FDT has been
252	 * loaded at the same time.
253	 */
254	if (!blob)
255		return;
256
257	ret = spl_decode_boot_device(spl_image->boot_device, boot_ofpath, sizeof(boot_ofpath));
258	if (ret) {
259		pr_err("%s: could not map boot_device to ofpath: %d\n", __func__, ret);
260		return;
261	}
262
263	chosen = fdt_find_or_add_subnode(blob, 0, "chosen");
264	if (chosen < 0) {
265		pr_err("%s: could not find/create '/chosen'\n", __func__);
266		return;
267	}
268	fdt_setprop_string(blob, chosen,
269			   "u-boot,spl-boot-device", boot_ofpath);
270}
271#endif
272