|
16 | 16 |
|
17 | 17 | #include <linux/dma-map-ops.h> |
18 | 18 | #include <linux/export.h> |
| 19 | +#include <linux/libfdt.h> |
19 | 20 | #include <linux/pci_ids.h> |
20 | 21 | #include <linux/string_choices.h> |
21 | 22 | #include <asm/bootinfo.h> |
@@ -57,6 +58,101 @@ void __init prom_dtb_init_env(void) |
57 | 58 | loongson_fdt_blob = (void *)fw_arg2; |
58 | 59 | } |
59 | 60 |
|
| 61 | +static int __init lefi_fixup_fdt_serial(void *fdt, u64 uart_addr, u32 uart_clk) |
| 62 | +{ |
| 63 | + int node, len, depth = -1; |
| 64 | + const fdt64_t *reg; |
| 65 | + fdt32_t *clk; |
| 66 | + |
| 67 | + for (node = fdt_next_node(fdt, -1, &depth); |
| 68 | + node >= 0 && depth >= 0; |
| 69 | + node = fdt_next_node(fdt, node, &depth)) { |
| 70 | + reg = fdt_getprop(fdt, node, "reg", &len); |
| 71 | + if (!reg || len <= 8 || fdt64_ld(reg) != uart_addr) |
| 72 | + continue; |
| 73 | + |
| 74 | + clk = fdt_getprop_w(fdt, node, "clock-frequency", &len); |
| 75 | + if (!clk) { |
| 76 | + pr_warn("UART 0x%llx misses clock-frequency property\n", |
| 77 | + uart_addr); |
| 78 | + return -ENOENT; |
| 79 | + } else if (len != 4) { |
| 80 | + pr_warn("UART 0x%llx has invalid clock-frequency property\n", |
| 81 | + uart_addr); |
| 82 | + return -EINVAL; |
| 83 | + } |
| 84 | + |
| 85 | + fdt32_st(clk, uart_clk); |
| 86 | + |
| 87 | + return 0; |
| 88 | + } |
| 89 | + |
| 90 | + return -ENODEV; |
| 91 | +} |
| 92 | + |
| 93 | +static void __init lefi_fixup_fdt(struct system_loongson *system) |
| 94 | +{ |
| 95 | + static unsigned char fdt_buf[16 << 10] __initdata; |
| 96 | + struct uart_device *uartdev; |
| 97 | + bool is_loongson64g; |
| 98 | + u64 uart_base; |
| 99 | + int ret, i; |
| 100 | + |
| 101 | + ret = fdt_open_into(loongson_fdt_blob, fdt_buf, sizeof(fdt_buf)); |
| 102 | + if (ret) { |
| 103 | + pr_err("Failed to open FDT to fix up\n"); |
| 104 | + return; |
| 105 | + } |
| 106 | + |
| 107 | + is_loongson64g = (read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_LOONGSON_64G; |
| 108 | + |
| 109 | + for (i = 0; i < system->nr_uarts; i++) { |
| 110 | + uartdev = &system->uarts[i]; |
| 111 | + |
| 112 | + ret = lefi_fixup_fdt_serial(fdt_buf, uartdev->uart_base, |
| 113 | + uartdev->uartclk); |
| 114 | + /* |
| 115 | + * LOONGSON64G's CPU serials are mapped to two different |
| 116 | + * addresses, one full-featured but differs from |
| 117 | + * previous generations, one fully compatible with them. |
| 118 | + * |
| 119 | + * It's unspecified that which mapping should uart_base refer |
| 120 | + * to, thus we should try fixing up with both. |
| 121 | + */ |
| 122 | + if (ret == -ENODEV && is_loongson64g) { |
| 123 | + switch (uartdev->uart_base) { |
| 124 | + case 0x1fe00100: |
| 125 | + uart_base = 0x1fe001e0; |
| 126 | + break; |
| 127 | + case 0x1fe00110: |
| 128 | + uart_base = 0x1fe001e8; |
| 129 | + break; |
| 130 | + case 0x1fe001e0: |
| 131 | + uart_base = 0x1fe00100; |
| 132 | + break; |
| 133 | + case 0x1fe001e8: |
| 134 | + uart_base = 0x1fe00110; |
| 135 | + break; |
| 136 | + default: |
| 137 | + pr_err("Unexpected UART address 0x%llx passed by firmware\n", |
| 138 | + uartdev->uart_base); |
| 139 | + ret = -EINVAL; |
| 140 | + goto err_fixup; |
| 141 | + } |
| 142 | + |
| 143 | + ret = lefi_fixup_fdt_serial(fdt_buf, uart_base, |
| 144 | + uartdev->uartclk); |
| 145 | + } |
| 146 | + |
| 147 | +err_fixup: |
| 148 | + if (ret) |
| 149 | + pr_err("Couldn't fix up FDT node for UART 0x%llx\n", |
| 150 | + uartdev->uart_base); |
| 151 | + } |
| 152 | + |
| 153 | + loongson_fdt_blob = fdt_buf; |
| 154 | +} |
| 155 | + |
60 | 156 | void __init prom_lefi_init_env(void) |
61 | 157 | { |
62 | 158 | struct boot_params *boot_p; |
@@ -237,4 +333,6 @@ void __init prom_lefi_init_env(void) |
237 | 333 |
|
238 | 334 | if (!loongson_fdt_blob) |
239 | 335 | pr_err("Failed to determine built-in Loongson64 dtb\n"); |
| 336 | + else |
| 337 | + lefi_fixup_fdt(esys); |
240 | 338 | } |
0 commit comments