diff --git a/arch/arm/mach-mvebu/armada3700/cpu.c b/arch/arm/mach-mvebu/armada3700/cpu.c index 1abac7c9a4..9aec0ce9a4 100644 --- a/arch/arm/mach-mvebu/armada3700/cpu.c +++ b/arch/arm/mach-mvebu/armada3700/cpu.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -280,36 +281,81 @@ static u32 find_pcie_window_base(void) return -1; } +static int fdt_setprop_inplace_u32_partial(void *blob, int node, + const char *name, + u32 idx, u32 val) +{ + val = cpu_to_fdt32(val); + + return fdt_setprop_inplace_namelen_partial(blob, node, name, + strlen(name), + idx * sizeof(u32), + &val, sizeof(u32)); +} + int a3700_fdt_fix_pcie_regions(void *blob) { - u32 new_ranges[14], base; + int acells, pacells, scells; + u32 base, fix_offset; const u32 *ranges; - int node, len; + int node, pnode; + int ret, i, len; + + base = find_pcie_window_base(); + if (base == -1) + return -ENOENT; node = fdt_node_offset_by_compatible(blob, -1, "marvell,armada-3700-pcie"); if (node < 0) return node; ranges = fdt_getprop(blob, node, "ranges", &len); - if (!ranges) + if (!ranges || len % sizeof(u32)) return -ENOENT; - if (len != sizeof(new_ranges)) - return -EINVAL; + /* + * The "ranges" property is an array of + * { } + * + * All 3 elements can span a diffent number of cells. Fetch their sizes. + */ + pnode = fdt_parent_offset(blob, node); + acells = fdt_address_cells(blob, node); + pacells = fdt_address_cells(blob, pnode); + scells = fdt_size_cells(blob, node); - memcpy(new_ranges, ranges, len); - - base = find_pcie_window_base(); - if (base == -1) + /* Child PCI addresses always use 3 cells */ + if (acells != 3) return -ENOENT; - new_ranges[2] = cpu_to_fdt32(base); - new_ranges[4] = new_ranges[2]; + /* Calculate fixup offset from first child address (in last cell) */ + fix_offset = base - fdt32_to_cpu(ranges[2]); - new_ranges[9] = cpu_to_fdt32(base + 0x1000000); - new_ranges[11] = new_ranges[9]; + /* + * Fix address (last cell) of each child address and each parent + * address + */ + for (i = 0; i < len / sizeof(u32); i += acells + pacells + scells) { + int idx; - return fdt_setprop_inplace(blob, node, "ranges", new_ranges, len); + /* fix child address */ + idx = i + acells - 1; + ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx, + fdt32_to_cpu(ranges[idx]) + + fix_offset); + if (ret) + return ret; + + /* fix parent address */ + idx = i + acells + pacells - 1; + ret = fdt_setprop_inplace_u32_partial(blob, node, "ranges", idx, + fdt32_to_cpu(ranges[idx]) + + fix_offset); + if (ret) + return ret; + } + + return 0; } void reset_cpu(void)