diff options
-rw-r--r-- | arch/arm/mach-mvebu/armada3700/cpu.c | 81 |
1 files changed, 55 insertions, 26 deletions
diff --git a/arch/arm/mach-mvebu/armada3700/cpu.c b/arch/arm/mach-mvebu/armada3700/cpu.c index 23492f49dae..52b5109b735 100644 --- a/arch/arm/mach-mvebu/armada3700/cpu.c +++ b/arch/arm/mach-mvebu/armada3700/cpu.c @@ -316,8 +316,8 @@ static int fdt_setprop_inplace_u32_partial(void *blob, int node, int a3700_fdt_fix_pcie_regions(void *blob) { - int acells, pacells, scells; - u32 base, fix_offset; + u32 base, lowest_cpu_addr, fix_offset; + int pci_cells, cpu_cells, size_cells; const u32 *ranges; int node, pnode; int ret, i, len; @@ -331,51 +331,80 @@ int a3700_fdt_fix_pcie_regions(void *blob) return node; ranges = fdt_getprop(blob, node, "ranges", &len); - if (!ranges || len % sizeof(u32)) - return -ENOENT; + if (!ranges || !len || len % sizeof(u32)) + return -EINVAL; /* * The "ranges" property is an array of - * { <child address> <parent address> <size in child address space> } + * { <PCI address> <CPU address> <size in PCI address space> } + * where number of PCI address cells and size cells is stored in the + * "#address-cells" and "#size-cells" properties of the same node + * containing the "ranges" property and number of CPU address cells + * is stored in the parent's "#address-cells" property. * - * All 3 elements can span a diffent number of cells. Fetch their sizes. + * All 3 elements can span a diffent number of cells. Fetch them. */ pnode = fdt_parent_offset(blob, node); - acells = fdt_address_cells(blob, node); - pacells = fdt_address_cells(blob, pnode); - scells = fdt_size_cells(blob, node); + pci_cells = fdt_address_cells(blob, node); + cpu_cells = fdt_address_cells(blob, pnode); + size_cells = fdt_size_cells(blob, node); - /* Child PCI addresses always use 3 cells */ - if (acells != 3) - return -ENOENT; + /* PCI addresses always use 3 cells */ + if (pci_cells != 3) + return -EINVAL; + + /* CPU addresses on Armada 37xx always use 2 cells */ + if (cpu_cells != 2) + return -EINVAL; + + for (i = 0; i < len / sizeof(u32); + i += pci_cells + cpu_cells + size_cells) { + /* + * Parent CPU addresses on Armada 37xx are always 32-bit, so + * check that the high word is zero. + */ + if (fdt32_to_cpu(ranges[i + pci_cells])) + return -EINVAL; + + if (i == 0 || + fdt32_to_cpu(ranges[i + pci_cells + 1]) < lowest_cpu_addr) + lowest_cpu_addr = fdt32_to_cpu(ranges[i + pci_cells + 1]); + } - /* Calculate fixup offset from first child address (in last cell) */ - fix_offset = base - fdt32_to_cpu(ranges[2]); + /* Calculate fixup offset from the lowest (first) CPU address */ + fix_offset = base - lowest_cpu_addr; - /* If fixup offset is zero then there is nothing to fix */ + /* If fixup offset is zero there is nothing to fix */ if (!fix_offset) return 0; /* - * Fix address (last cell) of each child address and each parent - * address + * Fix each CPU address and corresponding PCI address if PCI address + * is not already remapped (has the same value) */ - for (i = 0; i < len / sizeof(u32); i += acells + pacells + scells) { + for (i = 0; i < len / sizeof(u32); + i += pci_cells + cpu_cells + size_cells) { + u32 cpu_addr; + u64 pci_addr; int idx; - /* fix child address */ - idx = i + acells - 1; + /* Fix CPU address */ + idx = i + pci_cells + cpu_cells - 1; + cpu_addr = fdt32_to_cpu(ranges[idx]); ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx, - fdt32_to_cpu(ranges[idx]) + - fix_offset); + cpu_addr + fix_offset); if (ret) return ret; - /* fix parent address */ - idx = i + acells + pacells - 1; + /* Fix PCI address only if it isn't remapped (is same as CPU) */ + idx = i + pci_cells - 1; + pci_addr = ((u64)fdt32_to_cpu(ranges[idx - 1]) << 32) | + fdt32_to_cpu(ranges[idx]); + if (cpu_addr != pci_addr) + continue; + ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx, - fdt32_to_cpu(ranges[idx]) + - fix_offset); + cpu_addr + fix_offset); if (ret) return ret; } |