diff options
Diffstat (limited to 'drivers/of/address.c')
-rw-r--r-- | drivers/of/address.c | 163 |
1 files changed, 132 insertions, 31 deletions
diff --git a/drivers/of/address.c b/drivers/of/address.c index 4e12522a0a..03868406e2 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -1,21 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * address.c - address related devicetree functions * * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <of.h> @@ -74,7 +63,7 @@ static u64 of_bus_default_map(__be32 *addr, const __be32 *range, s = of_read_number(range + na + pna, ns); da = of_read_number(addr, na); - pr_debug("OF: default map, cp=%llx, s=%llx, da=%llx\n", + pr_vdebug("OF: default map, cp=%llx, s=%llx, da=%llx\n", (unsigned long long)cp, (unsigned long long)s, (unsigned long long)da); @@ -136,8 +125,10 @@ static unsigned int of_bus_pci_get_flags(const __be32 *addr) case 0x01: flags |= IORESOURCE_IO; break; - case 0x02: /* 32 bits */ case 0x03: /* 64 bits */ + flags |= IORESOURCE_MEM_64; + /* fallthrough */ + case 0x02: /* 32 bits */ flags |= IORESOURCE_MEM; break; } @@ -164,7 +155,7 @@ static u64 of_bus_pci_map(__be32 *addr, const __be32 *range, int na, int ns, s = of_read_number(range + na + pna, ns); da = of_read_number(addr + 1, na - 1); - pr_debug("OF: PCI map, cp=%llx, s=%llx, da=%llx\n", + pr_vdebug("OF: PCI map, cp=%llx, s=%llx, da=%llx\n", (unsigned long long)cp, (unsigned long long)s, (unsigned long long)da); @@ -310,22 +301,25 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus, * * As far as we know, this damage only exists on Apple machines, so * This code is only enabled on powerpc. --gcl + * + * This quirk also applies for 'dma-ranges' which frequently exist in + * child nodes without 'dma-ranges' in the parent nodes. --RobH */ ranges = of_get_property(parent, rprop, &rlen); #if !defined(CONFIG_PPC) - if (ranges == NULL) { - pr_debug("OF: no ranges; cannot translate\n"); + if (ranges == NULL && strcmp(rprop, "dma-ranges")) { + pr_vdebug("OF: no ranges; cannot translate\n"); return 1; } #endif /* !defined(CONFIG_PPC) */ if (ranges == NULL || rlen == 0) { offset = of_read_number(addr, na); memset(addr, 0, pna * 4); - pr_debug("OF: empty ranges; 1:1 translation\n"); + pr_vdebug("OF: empty ranges; 1:1 translation\n"); goto finish; } - pr_debug("OF: walking ranges...\n"); + pr_vdebug("OF: walking ranges...\n"); /* Now walk through the ranges */ rlen /= 4; @@ -336,14 +330,14 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus, break; } if (offset == OF_BAD_ADDR) { - pr_debug("OF: not found !\n"); + pr_vdebug("OF: not found !\n"); return 1; } memcpy(addr, ranges + na, 4 * pna); finish: of_dump_addr("OF: parent translation for:", addr, pna); - pr_debug("OF: with offset: %llx\n", (unsigned long long)offset); + pr_vdebug("OF: with offset: %llx\n", (unsigned long long)offset); /* Translate it into parent bus space */ return pbus->translate(addr, offset, pna); @@ -368,7 +362,7 @@ static u64 __of_translate_address(struct device_node *dev, int na, ns, pna, pns; u64 result = OF_BAD_ADDR; - pr_debug("OF: ** translation for device %s **\n", dev->full_name); + pr_vdebug("OF: ** translation for device %pOF **\n", dev); /* Get parent & match bus type */ parent = of_get_parent(dev); @@ -379,14 +373,13 @@ static u64 __of_translate_address(struct device_node *dev, /* Count address cells & copy address locally */ bus->count_cells(dev, &na, &ns); if (!OF_CHECK_COUNTS(na, ns)) { - pr_debug("prom_parse: Bad cell count for %s\n", - dev->full_name); + pr_vdebug("prom_parse: Bad cell count for %pOF\n", dev); return OF_BAD_ADDR; } memcpy(addr, in_addr, na * 4); - pr_debug("OF: bus is %s (na=%d, ns=%d) on %s\n", - bus->name, na, ns, parent->full_name); + pr_vdebug("OF: bus is %s (na=%d, ns=%d) on %pOF\n", + bus->name, na, ns, parent); of_dump_addr("OF: translating address:", addr, na); /* Translate */ @@ -397,7 +390,7 @@ static u64 __of_translate_address(struct device_node *dev, /* If root, we have finished */ if (parent == NULL) { - pr_debug("OF: reached root node\n"); + pr_vdebug("OF: reached root node\n"); result = of_read_number(addr, na); break; } @@ -406,13 +399,12 @@ static u64 __of_translate_address(struct device_node *dev, pbus = of_match_bus(parent); pbus->count_cells(dev, &pna, &pns); if (!OF_CHECK_COUNTS(pna, pns)) { - printk(KERN_ERR "prom_parse: Bad cell count for %s\n", - dev->full_name); + printk(KERN_ERR "prom_parse: Bad cell count for %pOF\n", dev); break; } - pr_debug("OF: parent bus is %s (na=%d, ns=%d) on %s\n", - pbus->name, pna, pns, parent->full_name); + pr_vdebug("OF: parent bus is %s (na=%d, ns=%d) on %pOF\n", + pbus->name, pna, pns, parent); /* Apply bus translation */ if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop)) @@ -458,6 +450,33 @@ bool of_can_translate_address(struct device_node *dev) } EXPORT_SYMBOL(of_can_translate_address); +static struct device_node *__of_get_dma_parent(struct device_node *np) +{ + struct of_phandle_args args; + int ret, index; + + index = of_property_match_string(np, "interconnect-names", "dma-mem"); + if (index < 0) + return of_get_parent(np); + + ret = of_parse_phandle_with_args(np, "interconnects", + "#interconnect-cells", + index, &args); + if (ret < 0) + return of_get_parent(np); + + return args.np; +} + +static struct device_node *of_get_next_dma_parent(struct device_node *np) +{ + struct device_node *parent; + + parent = __of_get_dma_parent(np); + + return parent; +} + const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags) { @@ -586,3 +605,85 @@ void __iomem *of_iomap(struct device_node *np, int index) return IOMEM(res.start); } EXPORT_SYMBOL(of_iomap); + +/** + * of_dma_get_range - Get DMA range info + * @np: device node to get DMA range info + * @dma_addr: pointer to store initial DMA address of DMA range + * @paddr: pointer to store initial CPU address of DMA range + * @size: pointer to store size of DMA range + * + * Look in bottom up direction for the first "dma-ranges" property + * and parse it. + * dma-ranges format: + * DMA addr (dma_addr) : naddr cells + * CPU addr (phys_addr_t) : pna cells + * size : nsize cells + * + * It returns -ENODEV if "dma-ranges" property was not found + * for this device in DT. + */ +int of_dma_get_range(struct device_node *np, u64 *dma_addr, u64 *paddr, u64 *size) +{ + struct device_node *node = np; + const __be32 *ranges = NULL; + int len, naddr, nsize, pna; + int ret = 0; + bool found_dma_ranges = false; + u64 dmaaddr; + + while (node) { + ranges = of_get_property(node, "dma-ranges", &len); + + /* Ignore empty ranges, they imply no translation required */ + if (ranges && len > 0) + break; + + /* Once we find 'dma-ranges', then a missing one is an error */ + if (found_dma_ranges && !ranges) { + ret = -ENODEV; + goto out; + } + found_dma_ranges = true; + + node = of_get_next_dma_parent(node); + } + + if (!node || !ranges) { + pr_debug("no dma-ranges found for node(%pOF)\n", np); + ret = -ENODEV; + goto out; + } + + naddr = of_bus_n_addr_cells(node); + nsize = of_bus_n_size_cells(node); + pna = of_n_addr_cells(node); + if ((len / sizeof(__be32)) % (pna + naddr + nsize)) { + ret = -EINVAL; + goto out; + } + + /* dma-ranges format: + * DMA addr : naddr cells + * CPU addr : pna cells + * size : nsize cells + */ + dmaaddr = of_read_number(ranges, naddr); + *paddr = of_translate_dma_address(node, ranges + naddr); + if (*paddr == OF_BAD_ADDR) { + pr_err("translation of DMA address(%llx) to CPU address failed node(%s)\n", + dmaaddr, np->name); + ret = -EINVAL; + goto out; + } + *dma_addr = dmaaddr; + + *size = of_read_number(ranges + naddr + pna, nsize); + + pr_debug("dma_addr(%llx) cpu_addr(%llx) size(%llx)\n", + *dma_addr, *paddr, *size); + +out: + + return ret; +} |