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