162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * FDT Address translation based on u-boot fdt_support.c which in turn was 462306a36Sopenharmony_ci * based on the kernel unflattened DT address translation code. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * (C) Copyright 2007 762306a36Sopenharmony_ci * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright 2010-2011 Freescale Semiconductor, Inc. 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define pr_fmt(fmt) "OF: fdt: " fmt 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/libfdt.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/of_fdt.h> 1862306a36Sopenharmony_ci#include <linux/sizes.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* Max address size we deal with */ 2162306a36Sopenharmony_ci#define OF_MAX_ADDR_CELLS 4 2262306a36Sopenharmony_ci#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \ 2362306a36Sopenharmony_ci (ns) > 0) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* Debug utility */ 2662306a36Sopenharmony_ci#ifdef DEBUG 2762306a36Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci pr_debug("%s", s); 3062306a36Sopenharmony_ci while(na--) 3162306a36Sopenharmony_ci pr_cont(" %08x", *(addr++)); 3262306a36Sopenharmony_ci pr_cont("\n"); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci#else 3562306a36Sopenharmony_cistatic void __init of_dump_addr(const char *s, const __be32 *addr, int na) { } 3662306a36Sopenharmony_ci#endif 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* Callbacks for bus specific translators */ 3962306a36Sopenharmony_cistruct of_bus { 4062306a36Sopenharmony_ci void (*count_cells)(const void *blob, int parentoffset, 4162306a36Sopenharmony_ci int *addrc, int *sizec); 4262306a36Sopenharmony_ci u64 (*map)(__be32 *addr, const __be32 *range, 4362306a36Sopenharmony_ci int na, int ns, int pna); 4462306a36Sopenharmony_ci int (*translate)(__be32 *addr, u64 offset, int na); 4562306a36Sopenharmony_ci}; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* Default translator (generic bus) */ 4862306a36Sopenharmony_cistatic void __init fdt_bus_default_count_cells(const void *blob, int parentoffset, 4962306a36Sopenharmony_ci int *addrc, int *sizec) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci const __be32 *prop; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci if (addrc) { 5462306a36Sopenharmony_ci prop = fdt_getprop(blob, parentoffset, "#address-cells", NULL); 5562306a36Sopenharmony_ci if (prop) 5662306a36Sopenharmony_ci *addrc = be32_to_cpup(prop); 5762306a36Sopenharmony_ci else 5862306a36Sopenharmony_ci *addrc = dt_root_addr_cells; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (sizec) { 6262306a36Sopenharmony_ci prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL); 6362306a36Sopenharmony_ci if (prop) 6462306a36Sopenharmony_ci *sizec = be32_to_cpup(prop); 6562306a36Sopenharmony_ci else 6662306a36Sopenharmony_ci *sizec = dt_root_size_cells; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic u64 __init fdt_bus_default_map(__be32 *addr, const __be32 *range, 7162306a36Sopenharmony_ci int na, int ns, int pna) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci u64 cp, s, da; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci cp = of_read_number(range, na); 7662306a36Sopenharmony_ci s = of_read_number(range + na + pna, ns); 7762306a36Sopenharmony_ci da = of_read_number(addr, na); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci pr_debug("default map, cp=%llx, s=%llx, da=%llx\n", 8062306a36Sopenharmony_ci cp, s, da); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (da < cp || da >= (cp + s)) 8362306a36Sopenharmony_ci return OF_BAD_ADDR; 8462306a36Sopenharmony_ci return da - cp; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int __init fdt_bus_default_translate(__be32 *addr, u64 offset, int na) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci u64 a = of_read_number(addr, na); 9062306a36Sopenharmony_ci memset(addr, 0, na * 4); 9162306a36Sopenharmony_ci a += offset; 9262306a36Sopenharmony_ci if (na > 1) 9362306a36Sopenharmony_ci addr[na - 2] = cpu_to_fdt32(a >> 32); 9462306a36Sopenharmony_ci addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return 0; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/* Array of bus specific translators */ 10062306a36Sopenharmony_cistatic const struct of_bus of_busses[] __initconst = { 10162306a36Sopenharmony_ci /* Default */ 10262306a36Sopenharmony_ci { 10362306a36Sopenharmony_ci .count_cells = fdt_bus_default_count_cells, 10462306a36Sopenharmony_ci .map = fdt_bus_default_map, 10562306a36Sopenharmony_ci .translate = fdt_bus_default_translate, 10662306a36Sopenharmony_ci }, 10762306a36Sopenharmony_ci}; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int __init fdt_translate_one(const void *blob, int parent, 11062306a36Sopenharmony_ci const struct of_bus *bus, 11162306a36Sopenharmony_ci const struct of_bus *pbus, __be32 *addr, 11262306a36Sopenharmony_ci int na, int ns, int pna, const char *rprop) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci const __be32 *ranges; 11562306a36Sopenharmony_ci int rlen; 11662306a36Sopenharmony_ci int rone; 11762306a36Sopenharmony_ci u64 offset = OF_BAD_ADDR; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci ranges = fdt_getprop(blob, parent, rprop, &rlen); 12062306a36Sopenharmony_ci if (!ranges) 12162306a36Sopenharmony_ci return 1; 12262306a36Sopenharmony_ci if (rlen == 0) { 12362306a36Sopenharmony_ci offset = of_read_number(addr, na); 12462306a36Sopenharmony_ci memset(addr, 0, pna * 4); 12562306a36Sopenharmony_ci pr_debug("empty ranges, 1:1 translation\n"); 12662306a36Sopenharmony_ci goto finish; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci pr_debug("walking ranges...\n"); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* Now walk through the ranges */ 13262306a36Sopenharmony_ci rlen /= 4; 13362306a36Sopenharmony_ci rone = na + pna + ns; 13462306a36Sopenharmony_ci for (; rlen >= rone; rlen -= rone, ranges += rone) { 13562306a36Sopenharmony_ci offset = bus->map(addr, ranges, na, ns, pna); 13662306a36Sopenharmony_ci if (offset != OF_BAD_ADDR) 13762306a36Sopenharmony_ci break; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci if (offset == OF_BAD_ADDR) { 14062306a36Sopenharmony_ci pr_debug("not found !\n"); 14162306a36Sopenharmony_ci return 1; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci memcpy(addr, ranges + na, 4 * pna); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci finish: 14662306a36Sopenharmony_ci of_dump_addr("parent translation for:", addr, pna); 14762306a36Sopenharmony_ci pr_debug("with offset: %llx\n", offset); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* Translate it into parent bus space */ 15062306a36Sopenharmony_ci return pbus->translate(addr, offset, pna); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci/* 15462306a36Sopenharmony_ci * Translate an address from the device-tree into a CPU physical address, 15562306a36Sopenharmony_ci * this walks up the tree and applies the various bus mappings on the 15662306a36Sopenharmony_ci * way. 15762306a36Sopenharmony_ci * 15862306a36Sopenharmony_ci * Note: We consider that crossing any level with #size-cells == 0 to mean 15962306a36Sopenharmony_ci * that translation is impossible (that is we are not dealing with a value 16062306a36Sopenharmony_ci * that can be mapped to a cpu physical address). This is not really specified 16162306a36Sopenharmony_ci * that way, but this is traditionally the way IBM at least do things 16262306a36Sopenharmony_ci */ 16362306a36Sopenharmony_cistatic u64 __init fdt_translate_address(const void *blob, int node_offset) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci int parent, len; 16662306a36Sopenharmony_ci const struct of_bus *bus, *pbus; 16762306a36Sopenharmony_ci const __be32 *reg; 16862306a36Sopenharmony_ci __be32 addr[OF_MAX_ADDR_CELLS]; 16962306a36Sopenharmony_ci int na, ns, pna, pns; 17062306a36Sopenharmony_ci u64 result = OF_BAD_ADDR; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci pr_debug("** translation for device %s **\n", 17362306a36Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci reg = fdt_getprop(blob, node_offset, "reg", &len); 17662306a36Sopenharmony_ci if (!reg) { 17762306a36Sopenharmony_ci pr_err("warning: device tree node '%s' has no address.\n", 17862306a36Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 17962306a36Sopenharmony_ci goto bail; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* Get parent & match bus type */ 18362306a36Sopenharmony_ci parent = fdt_parent_offset(blob, node_offset); 18462306a36Sopenharmony_ci if (parent < 0) 18562306a36Sopenharmony_ci goto bail; 18662306a36Sopenharmony_ci bus = &of_busses[0]; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* Cound address cells & copy address locally */ 18962306a36Sopenharmony_ci bus->count_cells(blob, parent, &na, &ns); 19062306a36Sopenharmony_ci if (!OF_CHECK_COUNTS(na, ns)) { 19162306a36Sopenharmony_ci pr_err("Bad cell count for %s\n", 19262306a36Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 19362306a36Sopenharmony_ci goto bail; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci memcpy(addr, reg, na * 4); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci pr_debug("bus (na=%d, ns=%d) on %s\n", 19862306a36Sopenharmony_ci na, ns, fdt_get_name(blob, parent, NULL)); 19962306a36Sopenharmony_ci of_dump_addr("translating address:", addr, na); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* Translate */ 20262306a36Sopenharmony_ci for (;;) { 20362306a36Sopenharmony_ci /* Switch to parent bus */ 20462306a36Sopenharmony_ci node_offset = parent; 20562306a36Sopenharmony_ci parent = fdt_parent_offset(blob, node_offset); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci /* If root, we have finished */ 20862306a36Sopenharmony_ci if (parent < 0) { 20962306a36Sopenharmony_ci pr_debug("reached root node\n"); 21062306a36Sopenharmony_ci result = of_read_number(addr, na); 21162306a36Sopenharmony_ci break; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* Get new parent bus and counts */ 21562306a36Sopenharmony_ci pbus = &of_busses[0]; 21662306a36Sopenharmony_ci pbus->count_cells(blob, parent, &pna, &pns); 21762306a36Sopenharmony_ci if (!OF_CHECK_COUNTS(pna, pns)) { 21862306a36Sopenharmony_ci pr_err("Bad cell count for %s\n", 21962306a36Sopenharmony_ci fdt_get_name(blob, node_offset, NULL)); 22062306a36Sopenharmony_ci break; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci pr_debug("parent bus (na=%d, ns=%d) on %s\n", 22462306a36Sopenharmony_ci pna, pns, fdt_get_name(blob, parent, NULL)); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* Apply bus translation */ 22762306a36Sopenharmony_ci if (fdt_translate_one(blob, node_offset, bus, pbus, 22862306a36Sopenharmony_ci addr, na, ns, pna, "ranges")) 22962306a36Sopenharmony_ci break; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* Complete the move up one level */ 23262306a36Sopenharmony_ci na = pna; 23362306a36Sopenharmony_ci ns = pns; 23462306a36Sopenharmony_ci bus = pbus; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci of_dump_addr("one level translation:", addr, na); 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci bail: 23962306a36Sopenharmony_ci return result; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci/** 24362306a36Sopenharmony_ci * of_flat_dt_translate_address - translate DT addr into CPU phys addr 24462306a36Sopenharmony_ci * @node: node in the flat blob 24562306a36Sopenharmony_ci */ 24662306a36Sopenharmony_ciu64 __init of_flat_dt_translate_address(unsigned long node) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci return fdt_translate_address(initial_boot_params, node); 24962306a36Sopenharmony_ci} 250