1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * OLPC-specific OFW device tree support code. |
4 | * |
5 | * Paul Mackerras August 1996. |
6 | * Copyright (C) 1996-2005 Paul Mackerras. |
7 | * |
8 | * Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner. |
9 | * {engebret|bergner}@us.ibm.com |
10 | * |
11 | * Adapted for sparc by David S. Miller davem@davemloft.net |
12 | * Adapted for x86/OLPC by Andres Salomon <dilinger@queued.net> |
13 | */ |
14 | |
15 | #include <linux/kernel.h> |
16 | #include <linux/memblock.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_pdt.h> |
19 | #include <asm/olpc.h> |
20 | #include <asm/olpc_ofw.h> |
21 | |
22 | static phandle __init olpc_dt_getsibling(phandle node) |
23 | { |
24 | const void *args[] = { (void *)node }; |
25 | void *res[] = { &node }; |
26 | |
27 | if ((s32)node == -1) |
28 | return 0; |
29 | |
30 | if (olpc_ofw("peer" , args, res) || (s32)node == -1) |
31 | return 0; |
32 | |
33 | return node; |
34 | } |
35 | |
36 | static phandle __init olpc_dt_getchild(phandle node) |
37 | { |
38 | const void *args[] = { (void *)node }; |
39 | void *res[] = { &node }; |
40 | |
41 | if ((s32)node == -1) |
42 | return 0; |
43 | |
44 | if (olpc_ofw("child" , args, res) || (s32)node == -1) { |
45 | pr_err("PROM: %s: fetching child failed!\n" , __func__); |
46 | return 0; |
47 | } |
48 | |
49 | return node; |
50 | } |
51 | |
52 | static int __init olpc_dt_getproplen(phandle node, const char *prop) |
53 | { |
54 | const void *args[] = { (void *)node, prop }; |
55 | int len; |
56 | void *res[] = { &len }; |
57 | |
58 | if ((s32)node == -1) |
59 | return -1; |
60 | |
61 | if (olpc_ofw("getproplen" , args, res)) { |
62 | pr_err("PROM: %s: getproplen failed!\n" , __func__); |
63 | return -1; |
64 | } |
65 | |
66 | return len; |
67 | } |
68 | |
69 | static int __init olpc_dt_getproperty(phandle node, const char *prop, |
70 | char *buf, int bufsize) |
71 | { |
72 | int plen; |
73 | |
74 | plen = olpc_dt_getproplen(node, prop); |
75 | if (plen > bufsize || plen < 1) { |
76 | return -1; |
77 | } else { |
78 | const void *args[] = { (void *)node, prop, buf, (void *)plen }; |
79 | void *res[] = { &plen }; |
80 | |
81 | if (olpc_ofw("getprop" , args, res)) { |
82 | pr_err("PROM: %s: getprop failed!\n" , __func__); |
83 | return -1; |
84 | } |
85 | } |
86 | |
87 | return plen; |
88 | } |
89 | |
90 | static int __init olpc_dt_nextprop(phandle node, char *prev, char *buf) |
91 | { |
92 | const void *args[] = { (void *)node, prev, buf }; |
93 | int success; |
94 | void *res[] = { &success }; |
95 | |
96 | buf[0] = '\0'; |
97 | |
98 | if ((s32)node == -1) |
99 | return -1; |
100 | |
101 | if (olpc_ofw("nextprop" , args, res) || success != 1) |
102 | return -1; |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | static int __init olpc_dt_pkg2path(phandle node, char *buf, |
108 | const int buflen, int *len) |
109 | { |
110 | const void *args[] = { (void *)node, buf, (void *)buflen }; |
111 | void *res[] = { len }; |
112 | |
113 | if ((s32)node == -1) |
114 | return -1; |
115 | |
116 | if (olpc_ofw("package-to-path" , args, res) || *len < 1) |
117 | return -1; |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static unsigned int prom_early_allocated __initdata; |
123 | |
124 | void * __init prom_early_alloc(unsigned long size) |
125 | { |
126 | static u8 *mem; |
127 | static size_t free_mem; |
128 | void *res; |
129 | |
130 | if (free_mem < size) { |
131 | const size_t chunk_size = max(PAGE_SIZE, size); |
132 | |
133 | /* |
134 | * To minimize the number of allocations, grab at least |
135 | * PAGE_SIZE of memory (that's an arbitrary choice that's |
136 | * fast enough on the platforms we care about while minimizing |
137 | * wasted bootmem) and hand off chunks of it to callers. |
138 | */ |
139 | res = memblock_alloc(size: chunk_size, SMP_CACHE_BYTES); |
140 | if (!res) |
141 | panic(fmt: "%s: Failed to allocate %zu bytes\n" , __func__, |
142 | chunk_size); |
143 | BUG_ON(!res); |
144 | prom_early_allocated += chunk_size; |
145 | memset(res, 0, chunk_size); |
146 | free_mem = chunk_size; |
147 | mem = res; |
148 | } |
149 | |
150 | /* allocate from the local cache */ |
151 | free_mem -= size; |
152 | res = mem; |
153 | mem += size; |
154 | return res; |
155 | } |
156 | |
157 | static struct of_pdt_ops prom_olpc_ops __initdata = { |
158 | .nextprop = olpc_dt_nextprop, |
159 | .getproplen = olpc_dt_getproplen, |
160 | .getproperty = olpc_dt_getproperty, |
161 | .getchild = olpc_dt_getchild, |
162 | .getsibling = olpc_dt_getsibling, |
163 | .pkg2path = olpc_dt_pkg2path, |
164 | }; |
165 | |
166 | static phandle __init olpc_dt_finddevice(const char *path) |
167 | { |
168 | phandle node; |
169 | const void *args[] = { path }; |
170 | void *res[] = { &node }; |
171 | |
172 | if (olpc_ofw("finddevice" , args, res)) { |
173 | pr_err("olpc_dt: finddevice failed!\n" ); |
174 | return 0; |
175 | } |
176 | |
177 | if ((s32) node == -1) |
178 | return 0; |
179 | |
180 | return node; |
181 | } |
182 | |
183 | static int __init olpc_dt_interpret(const char *words) |
184 | { |
185 | int result; |
186 | const void *args[] = { words }; |
187 | void *res[] = { &result }; |
188 | |
189 | if (olpc_ofw("interpret" , args, res)) { |
190 | pr_err("olpc_dt: interpret failed!\n" ); |
191 | return -1; |
192 | } |
193 | |
194 | return result; |
195 | } |
196 | |
197 | /* |
198 | * Extract board revision directly from OFW device tree. |
199 | * We can't use olpc_platform_info because that hasn't been set up yet. |
200 | */ |
201 | static u32 __init olpc_dt_get_board_revision(void) |
202 | { |
203 | phandle node; |
204 | __be32 rev; |
205 | int r; |
206 | |
207 | node = olpc_dt_finddevice(path: "/" ); |
208 | if (!node) |
209 | return 0; |
210 | |
211 | r = olpc_dt_getproperty(node, prop: "board-revision-int" , |
212 | buf: (char *) &rev, bufsize: sizeof(rev)); |
213 | if (r < 0) |
214 | return 0; |
215 | |
216 | return be32_to_cpu(rev); |
217 | } |
218 | |
219 | static int __init olpc_dt_compatible_match(phandle node, const char *compat) |
220 | { |
221 | char buf[64], *p; |
222 | int plen, len; |
223 | |
224 | plen = olpc_dt_getproperty(node, prop: "compatible" , buf, bufsize: sizeof(buf)); |
225 | if (plen <= 0) |
226 | return 0; |
227 | |
228 | len = strlen(compat); |
229 | for (p = buf; p < buf + plen; p += strlen(p) + 1) { |
230 | if (strcmp(p, compat) == 0) |
231 | return 1; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static void __init olpc_dt_fixup(void) |
238 | { |
239 | phandle node; |
240 | u32 board_rev; |
241 | |
242 | node = olpc_dt_finddevice(path: "/battery@0" ); |
243 | if (!node) |
244 | return; |
245 | |
246 | board_rev = olpc_dt_get_board_revision(); |
247 | if (!board_rev) |
248 | return; |
249 | |
250 | if (board_rev >= olpc_board_pre(0xd0)) { |
251 | /* XO-1.5 */ |
252 | |
253 | if (olpc_dt_compatible_match(node, compat: "olpc,xo1.5-battery" )) |
254 | return; |
255 | |
256 | /* Add olpc,xo1.5-battery compatible marker to battery node */ |
257 | olpc_dt_interpret(words: "\" /battery@0\" find-device" ); |
258 | olpc_dt_interpret(words: " \" olpc,xo1.5-battery\" +compatible" ); |
259 | olpc_dt_interpret(words: "device-end" ); |
260 | |
261 | if (olpc_dt_compatible_match(node, compat: "olpc,xo1-battery" )) { |
262 | /* |
263 | * If we have a olpc,xo1-battery compatible, then we're |
264 | * running a new enough firmware that already has |
265 | * the dcon node. |
266 | */ |
267 | return; |
268 | } |
269 | |
270 | /* Add dcon device */ |
271 | olpc_dt_interpret(words: "\" /pci/display@1\" find-device" ); |
272 | olpc_dt_interpret(words: " new-device" ); |
273 | olpc_dt_interpret(words: " \" dcon\" device-name" ); |
274 | olpc_dt_interpret(words: " \" olpc,xo1-dcon\" +compatible" ); |
275 | olpc_dt_interpret(words: " finish-device" ); |
276 | olpc_dt_interpret(words: "device-end" ); |
277 | } else { |
278 | /* XO-1 */ |
279 | |
280 | if (olpc_dt_compatible_match(node, compat: "olpc,xo1-battery" )) { |
281 | /* |
282 | * If we have a olpc,xo1-battery compatible, then we're |
283 | * running a new enough firmware that already has |
284 | * the dcon and RTC nodes. |
285 | */ |
286 | return; |
287 | } |
288 | |
289 | /* Add dcon device, mark RTC as olpc,xo1-rtc */ |
290 | olpc_dt_interpret(words: "\" /pci/display@1,1\" find-device" ); |
291 | olpc_dt_interpret(words: " new-device" ); |
292 | olpc_dt_interpret(words: " \" dcon\" device-name" ); |
293 | olpc_dt_interpret(words: " \" olpc,xo1-dcon\" +compatible" ); |
294 | olpc_dt_interpret(words: " finish-device" ); |
295 | olpc_dt_interpret(words: "device-end" ); |
296 | |
297 | olpc_dt_interpret(words: "\" /rtc\" find-device" ); |
298 | olpc_dt_interpret(words: " \" olpc,xo1-rtc\" +compatible" ); |
299 | olpc_dt_interpret(words: "device-end" ); |
300 | } |
301 | |
302 | /* Add olpc,xo1-battery compatible marker to battery node */ |
303 | olpc_dt_interpret(words: "\" /battery@0\" find-device" ); |
304 | olpc_dt_interpret(words: " \" olpc,xo1-battery\" +compatible" ); |
305 | olpc_dt_interpret(words: "device-end" ); |
306 | } |
307 | |
308 | void __init olpc_dt_build_devicetree(void) |
309 | { |
310 | phandle root; |
311 | |
312 | if (!olpc_ofw_is_installed()) |
313 | return; |
314 | |
315 | olpc_dt_fixup(); |
316 | |
317 | root = olpc_dt_getsibling(node: 0); |
318 | if (!root) { |
319 | pr_err("PROM: unable to get root node from OFW!\n" ); |
320 | return; |
321 | } |
322 | of_pdt_build_devicetree(root_node: root, ops: &prom_olpc_ops); |
323 | |
324 | pr_info("PROM DT: Built device tree with %u bytes of memory.\n" , |
325 | prom_early_allocated); |
326 | } |
327 | |