1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 Imagination Technologies |
4 | * Author: Paul Burton <paul.burton@mips.com> |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/clocksource.h> |
9 | #include <linux/init.h> |
10 | #include <linux/irqchip.h> |
11 | #include <linux/of_clk.h> |
12 | #include <linux/of_fdt.h> |
13 | |
14 | #include <asm/bootinfo.h> |
15 | #include <asm/fw/fw.h> |
16 | #include <asm/irq_cpu.h> |
17 | #include <asm/machine.h> |
18 | #include <asm/mips-cps.h> |
19 | #include <asm/prom.h> |
20 | #include <asm/smp-ops.h> |
21 | #include <asm/time.h> |
22 | |
23 | static __initconst const void *fdt; |
24 | static __initconst const struct mips_machine *mach; |
25 | static __initconst const void *mach_match_data; |
26 | |
27 | void __init prom_init(void) |
28 | { |
29 | plat_get_fdt(); |
30 | BUG_ON(!fdt); |
31 | } |
32 | |
33 | void __init *plat_get_fdt(void) |
34 | { |
35 | const struct mips_machine *check_mach; |
36 | const struct of_device_id *match; |
37 | |
38 | if (fdt) |
39 | /* Already set up */ |
40 | return (void *)fdt; |
41 | |
42 | fdt = (void *)get_fdt(); |
43 | if (fdt && !fdt_check_header(fdt)) { |
44 | /* |
45 | * We have been provided with the appropriate device tree for |
46 | * the board. Make use of it & search for any machine struct |
47 | * based upon the root compatible string. |
48 | */ |
49 | for_each_mips_machine(check_mach) { |
50 | match = mips_machine_is_compatible(check_mach, fdt); |
51 | if (match) { |
52 | mach = check_mach; |
53 | mach_match_data = match->data; |
54 | break; |
55 | } |
56 | } |
57 | } else if (IS_ENABLED(CONFIG_LEGACY_BOARDS)) { |
58 | /* |
59 | * We weren't booted using the UHI boot protocol, but do |
60 | * support some number of boards with legacy boot protocols. |
61 | * Attempt to find the right one. |
62 | */ |
63 | for_each_mips_machine(check_mach) { |
64 | if (!check_mach->detect) |
65 | continue; |
66 | |
67 | if (!check_mach->detect()) |
68 | continue; |
69 | |
70 | mach = check_mach; |
71 | } |
72 | |
73 | /* |
74 | * If we don't recognise the machine then we can't continue, so |
75 | * die here. |
76 | */ |
77 | BUG_ON(!mach); |
78 | |
79 | /* Retrieve the machine's FDT */ |
80 | fdt = mach->fdt; |
81 | } |
82 | return (void *)fdt; |
83 | } |
84 | |
85 | #ifdef CONFIG_RELOCATABLE |
86 | |
87 | void __init plat_fdt_relocated(void *new_location) |
88 | { |
89 | /* |
90 | * reset fdt as the cached value would point to the location |
91 | * before relocations happened and update the location argument |
92 | * if it was passed using UHI |
93 | */ |
94 | fdt = NULL; |
95 | |
96 | if (fw_arg0 == -2) |
97 | fw_arg1 = (unsigned long)new_location; |
98 | } |
99 | |
100 | #endif /* CONFIG_RELOCATABLE */ |
101 | |
102 | void __init plat_mem_setup(void) |
103 | { |
104 | if (mach && mach->fixup_fdt) |
105 | fdt = mach->fixup_fdt(fdt, mach_match_data); |
106 | |
107 | fw_init_cmdline(); |
108 | __dt_setup_arch((void *)fdt); |
109 | } |
110 | |
111 | void __init device_tree_init(void) |
112 | { |
113 | unflatten_and_copy_device_tree(); |
114 | mips_cpc_probe(); |
115 | |
116 | if (!register_cps_smp_ops()) |
117 | return; |
118 | if (!register_vsmp_smp_ops()) |
119 | return; |
120 | |
121 | register_up_smp_ops(); |
122 | } |
123 | |
124 | int __init apply_mips_fdt_fixups(void *fdt_out, size_t fdt_out_size, |
125 | const void *fdt_in, |
126 | const struct mips_fdt_fixup *fixups) |
127 | { |
128 | int err; |
129 | |
130 | err = fdt_open_into(fdt_in, fdt_out, fdt_out_size); |
131 | if (err) { |
132 | pr_err("Failed to open FDT\n" ); |
133 | return err; |
134 | } |
135 | |
136 | for (; fixups->apply; fixups++) { |
137 | err = fixups->apply(fdt_out); |
138 | if (err) { |
139 | pr_err("Failed to apply FDT fixup \"%s\"\n" , |
140 | fixups->description); |
141 | return err; |
142 | } |
143 | } |
144 | |
145 | err = fdt_pack(fdt_out); |
146 | if (err) |
147 | pr_err("Failed to pack FDT\n" ); |
148 | return err; |
149 | } |
150 | |
151 | void __init plat_time_init(void) |
152 | { |
153 | struct device_node *np; |
154 | struct clk *clk; |
155 | |
156 | of_clk_init(NULL); |
157 | |
158 | if (!cpu_has_counter) { |
159 | mips_hpt_frequency = 0; |
160 | } else if (mach && mach->measure_hpt_freq) { |
161 | mips_hpt_frequency = mach->measure_hpt_freq(); |
162 | } else { |
163 | np = of_get_cpu_node(cpu: 0, NULL); |
164 | if (!np) { |
165 | pr_err("Failed to get CPU node\n" ); |
166 | return; |
167 | } |
168 | |
169 | clk = of_clk_get(np, index: 0); |
170 | if (IS_ERR(ptr: clk)) { |
171 | pr_err("Failed to get CPU clock: %ld\n" , PTR_ERR(clk)); |
172 | return; |
173 | } |
174 | |
175 | mips_hpt_frequency = clk_get_rate(clk); |
176 | clk_put(clk); |
177 | |
178 | switch (boot_cpu_type()) { |
179 | case CPU_20KC: |
180 | case CPU_25KF: |
181 | /* The counter runs at the CPU clock rate */ |
182 | break; |
183 | default: |
184 | /* The counter runs at half the CPU clock rate */ |
185 | mips_hpt_frequency /= 2; |
186 | break; |
187 | } |
188 | } |
189 | |
190 | timer_probe(); |
191 | } |
192 | |
193 | void __init arch_init_irq(void) |
194 | { |
195 | struct device_node *intc_node; |
196 | |
197 | intc_node = of_find_compatible_node(NULL, NULL, |
198 | compat: "mti,cpu-interrupt-controller" ); |
199 | if (!cpu_has_veic && !intc_node) |
200 | mips_cpu_irq_init(); |
201 | of_node_put(node: intc_node); |
202 | |
203 | irqchip_init(); |
204 | } |
205 | |