18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * FDT Address translation based on u-boot fdt_support.c which in turn was
48c2ecf20Sopenharmony_ci * based on the kernel unflattened DT address translation code.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * (C) Copyright 2007
78c2ecf20Sopenharmony_ci * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Copyright 2010-2011 Freescale Semiconductor, Inc.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#define pr_fmt(fmt)	"OF: fdt: " fmt
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/libfdt.h>
168c2ecf20Sopenharmony_ci#include <linux/of.h>
178c2ecf20Sopenharmony_ci#include <linux/of_fdt.h>
188c2ecf20Sopenharmony_ci#include <linux/sizes.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/* Max address size we deal with */
218c2ecf20Sopenharmony_ci#define OF_MAX_ADDR_CELLS	4
228c2ecf20Sopenharmony_ci#define OF_CHECK_COUNTS(na, ns)	((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \
238c2ecf20Sopenharmony_ci			(ns) > 0)
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/* Debug utility */
268c2ecf20Sopenharmony_ci#ifdef DEBUG
278c2ecf20Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na)
288c2ecf20Sopenharmony_ci{
298c2ecf20Sopenharmony_ci	pr_debug("%s", s);
308c2ecf20Sopenharmony_ci	while(na--)
318c2ecf20Sopenharmony_ci		pr_cont(" %08x", *(addr++));
328c2ecf20Sopenharmony_ci	pr_cont("\n");
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci#else
358c2ecf20Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na) { }
368c2ecf20Sopenharmony_ci#endif
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/* Callbacks for bus specific translators */
398c2ecf20Sopenharmony_cistruct of_bus {
408c2ecf20Sopenharmony_ci	void		(*count_cells)(const void *blob, int parentoffset,
418c2ecf20Sopenharmony_ci				int *addrc, int *sizec);
428c2ecf20Sopenharmony_ci	u64		(*map)(__be32 *addr, const __be32 *range,
438c2ecf20Sopenharmony_ci				int na, int ns, int pna);
448c2ecf20Sopenharmony_ci	int		(*translate)(__be32 *addr, u64 offset, int na);
458c2ecf20Sopenharmony_ci};
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/* Default translator (generic bus) */
488c2ecf20Sopenharmony_cistatic void __init fdt_bus_default_count_cells(const void *blob, int parentoffset,
498c2ecf20Sopenharmony_ci					       int *addrc, int *sizec)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	const __be32 *prop;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if (addrc) {
548c2ecf20Sopenharmony_ci		prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL);
558c2ecf20Sopenharmony_ci		if (prop)
568c2ecf20Sopenharmony_ci			*addrc = be32_to_cpup(prop);
578c2ecf20Sopenharmony_ci		else
588c2ecf20Sopenharmony_ci			*addrc = dt_root_addr_cells;
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	if (sizec) {
628c2ecf20Sopenharmony_ci		prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL);
638c2ecf20Sopenharmony_ci		if (prop)
648c2ecf20Sopenharmony_ci			*sizec = be32_to_cpup(prop);
658c2ecf20Sopenharmony_ci		else
668c2ecf20Sopenharmony_ci			*sizec = dt_root_size_cells;
678c2ecf20Sopenharmony_ci	}
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range,
718c2ecf20Sopenharmony_ci				      int na, int ns, int pna)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	u64 cp, s, da;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	cp = of_read_number(range, na);
768c2ecf20Sopenharmony_ci	s  = of_read_number(range + na + pna, ns);
778c2ecf20Sopenharmony_ci	da = of_read_number(addr, na);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	pr_debug("default map, cp=%llx, s=%llx, da=%llx\n",
808c2ecf20Sopenharmony_ci	    cp, s, da);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	if (da < cp || da >= (cp + s))
838c2ecf20Sopenharmony_ci		return OF_BAD_ADDR;
848c2ecf20Sopenharmony_ci	return da - cp;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	u64 a = of_read_number(addr, na);
908c2ecf20Sopenharmony_ci	memset(addr, 0, na * 4);
918c2ecf20Sopenharmony_ci	a += offset;
928c2ecf20Sopenharmony_ci	if (na > 1)
938c2ecf20Sopenharmony_ci		addr[na - 2] = cpu_to_fdt32(a >> 32);
948c2ecf20Sopenharmony_ci	addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	return 0;
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci/* Array of bus specific translators */
1008c2ecf20Sopenharmony_cistatic const struct of_bus of_busses[] __initconst = {
1018c2ecf20Sopenharmony_ci	/* Default */
1028c2ecf20Sopenharmony_ci	{
1038c2ecf20Sopenharmony_ci		.count_cells = fdt_bus_default_count_cells,
1048c2ecf20Sopenharmony_ci		.map = fdt_bus_default_map,
1058c2ecf20Sopenharmony_ci		.translate = fdt_bus_default_translate,
1068c2ecf20Sopenharmony_ci	},
1078c2ecf20Sopenharmony_ci};
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic int __init fdt_translate_one(const void *blob, int parent,
1108c2ecf20Sopenharmony_ci				    const struct of_bus *bus,
1118c2ecf20Sopenharmony_ci				    const struct of_bus *pbus, __be32 *addr,
1128c2ecf20Sopenharmony_ci				    int na, int ns, int pna, const char *rprop)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	const __be32 *ranges;
1158c2ecf20Sopenharmony_ci	int rlen;
1168c2ecf20Sopenharmony_ci	int rone;
1178c2ecf20Sopenharmony_ci	u64 offset = OF_BAD_ADDR;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	ranges = fdt_getprop(blob, parent, rprop, &rlen);
1208c2ecf20Sopenharmony_ci	if (!ranges)
1218c2ecf20Sopenharmony_ci		return 1;
1228c2ecf20Sopenharmony_ci	if (rlen == 0) {
1238c2ecf20Sopenharmony_ci		offset = of_read_number(addr, na);
1248c2ecf20Sopenharmony_ci		memset(addr, 0, pna * 4);
1258c2ecf20Sopenharmony_ci		pr_debug("empty ranges, 1:1 translation\n");
1268c2ecf20Sopenharmony_ci		goto finish;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	pr_debug("walking ranges...\n");
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* Now walk through the ranges */
1328c2ecf20Sopenharmony_ci	rlen /= 4;
1338c2ecf20Sopenharmony_ci	rone = na + pna + ns;
1348c2ecf20Sopenharmony_ci	for (; rlen >= rone; rlen -= rone, ranges += rone) {
1358c2ecf20Sopenharmony_ci		offset = bus->map(addr, ranges, na, ns, pna);
1368c2ecf20Sopenharmony_ci		if (offset != OF_BAD_ADDR)
1378c2ecf20Sopenharmony_ci			break;
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci	if (offset == OF_BAD_ADDR) {
1408c2ecf20Sopenharmony_ci		pr_debug("not found !\n");
1418c2ecf20Sopenharmony_ci		return 1;
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci	memcpy(addr, ranges + na, 4 * pna);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci finish:
1468c2ecf20Sopenharmony_ci	of_dump_addr("parent translation for:", addr, pna);
1478c2ecf20Sopenharmony_ci	pr_debug("with offset: %llx\n", offset);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	/* Translate it into parent bus space */
1508c2ecf20Sopenharmony_ci	return pbus->translate(addr, offset, pna);
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci/*
1548c2ecf20Sopenharmony_ci * Translate an address from the device-tree into a CPU physical address,
1558c2ecf20Sopenharmony_ci * this walks up the tree and applies the various bus mappings on the
1568c2ecf20Sopenharmony_ci * way.
1578c2ecf20Sopenharmony_ci *
1588c2ecf20Sopenharmony_ci * Note: We consider that crossing any level with #size-cells == 0 to mean
1598c2ecf20Sopenharmony_ci * that translation is impossible (that is we are not dealing with a value
1608c2ecf20Sopenharmony_ci * that can be mapped to a cpu physical address). This is not really specified
1618c2ecf20Sopenharmony_ci * that way, but this is traditionally the way IBM at least do things
1628c2ecf20Sopenharmony_ci */
1638c2ecf20Sopenharmony_cistatic u64 __init fdt_translate_address(const void *blob, int node_offset)
1648c2ecf20Sopenharmony_ci{
1658c2ecf20Sopenharmony_ci	int parent, len;
1668c2ecf20Sopenharmony_ci	const struct of_bus *bus, *pbus;
1678c2ecf20Sopenharmony_ci	const __be32 *reg;
1688c2ecf20Sopenharmony_ci	__be32 addr[OF_MAX_ADDR_CELLS];
1698c2ecf20Sopenharmony_ci	int na, ns, pna, pns;
1708c2ecf20Sopenharmony_ci	u64 result = OF_BAD_ADDR;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	pr_debug("** translation for device %s **\n",
1738c2ecf20Sopenharmony_ci		 fdt_get_name(blob, node_offset, NULL));
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	reg = fdt_getprop(blob, node_offset, "reg", &len);
1768c2ecf20Sopenharmony_ci	if (!reg) {
1778c2ecf20Sopenharmony_ci		pr_err("warning: device tree node '%s' has no address.\n",
1788c2ecf20Sopenharmony_ci			fdt_get_name(blob, node_offset, NULL));
1798c2ecf20Sopenharmony_ci		goto bail;
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	/* Get parent & match bus type */
1838c2ecf20Sopenharmony_ci	parent = fdt_parent_offset(blob, node_offset);
1848c2ecf20Sopenharmony_ci	if (parent < 0)
1858c2ecf20Sopenharmony_ci		goto bail;
1868c2ecf20Sopenharmony_ci	bus = &of_busses[0];
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* Cound address cells & copy address locally */
1898c2ecf20Sopenharmony_ci	bus->count_cells(blob, parent, &na, &ns);
1908c2ecf20Sopenharmony_ci	if (!OF_CHECK_COUNTS(na, ns)) {
1918c2ecf20Sopenharmony_ci		pr_err("Bad cell count for %s\n",
1928c2ecf20Sopenharmony_ci		       fdt_get_name(blob, node_offset, NULL));
1938c2ecf20Sopenharmony_ci		goto bail;
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci	memcpy(addr, reg, na * 4);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	pr_debug("bus (na=%d, ns=%d) on %s\n",
1988c2ecf20Sopenharmony_ci		 na, ns, fdt_get_name(blob, parent, NULL));
1998c2ecf20Sopenharmony_ci	of_dump_addr("translating address:", addr, na);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	/* Translate */
2028c2ecf20Sopenharmony_ci	for (;;) {
2038c2ecf20Sopenharmony_ci		/* Switch to parent bus */
2048c2ecf20Sopenharmony_ci		node_offset = parent;
2058c2ecf20Sopenharmony_ci		parent = fdt_parent_offset(blob, node_offset);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci		/* If root, we have finished */
2088c2ecf20Sopenharmony_ci		if (parent < 0) {
2098c2ecf20Sopenharmony_ci			pr_debug("reached root node\n");
2108c2ecf20Sopenharmony_ci			result = of_read_number(addr, na);
2118c2ecf20Sopenharmony_ci			break;
2128c2ecf20Sopenharmony_ci		}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci		/* Get new parent bus and counts */
2158c2ecf20Sopenharmony_ci		pbus = &of_busses[0];
2168c2ecf20Sopenharmony_ci		pbus->count_cells(blob, parent, &pna, &pns);
2178c2ecf20Sopenharmony_ci		if (!OF_CHECK_COUNTS(pna, pns)) {
2188c2ecf20Sopenharmony_ci			pr_err("Bad cell count for %s\n",
2198c2ecf20Sopenharmony_ci				fdt_get_name(blob, node_offset, NULL));
2208c2ecf20Sopenharmony_ci			break;
2218c2ecf20Sopenharmony_ci		}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci		pr_debug("parent bus (na=%d, ns=%d) on %s\n",
2248c2ecf20Sopenharmony_ci			 pna, pns, fdt_get_name(blob, parent, NULL));
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci		/* Apply bus translation */
2278c2ecf20Sopenharmony_ci		if (fdt_translate_one(blob, node_offset, bus, pbus,
2288c2ecf20Sopenharmony_ci					addr, na, ns, pna, "ranges"))
2298c2ecf20Sopenharmony_ci			break;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci		/* Complete the move up one level */
2328c2ecf20Sopenharmony_ci		na = pna;
2338c2ecf20Sopenharmony_ci		ns = pns;
2348c2ecf20Sopenharmony_ci		bus = pbus;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci		of_dump_addr("one level translation:", addr, na);
2378c2ecf20Sopenharmony_ci	}
2388c2ecf20Sopenharmony_ci bail:
2398c2ecf20Sopenharmony_ci	return result;
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci/**
2438c2ecf20Sopenharmony_ci * of_flat_dt_translate_address - translate DT addr into CPU phys addr
2448c2ecf20Sopenharmony_ci * @node: node in the flat blob
2458c2ecf20Sopenharmony_ci */
2468c2ecf20Sopenharmony_ciu64 __init of_flat_dt_translate_address(unsigned long node)
2478c2ecf20Sopenharmony_ci{
2488c2ecf20Sopenharmony_ci	return fdt_translate_address(initial_boot_params, node);
2498c2ecf20Sopenharmony_ci}
250