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 | 21 | #include <linux/of.h> |
45bb1674 | 22 | #include <linux/of_platform.h> |
c10d1e26 | 23 | #include <linux/of_pdt.h> |
45bb1674 | 24 | #include <asm/olpc.h> |
c10d1e26 AS |
25 | #include <asm/olpc_ofw.h> |
26 | ||
27 | static phandle __init olpc_dt_getsibling(phandle node) | |
28 | { | |
29 | const void *args[] = { (void *)node }; | |
30 | void *res[] = { &node }; | |
31 | ||
32 | if ((s32)node == -1) | |
33 | return 0; | |
34 | ||
35 | if (olpc_ofw("peer", args, res) || (s32)node == -1) | |
36 | return 0; | |
37 | ||
38 | return node; | |
39 | } | |
40 | ||
41 | static phandle __init olpc_dt_getchild(phandle node) | |
42 | { | |
43 | const void *args[] = { (void *)node }; | |
44 | void *res[] = { &node }; | |
45 | ||
46 | if ((s32)node == -1) | |
47 | return 0; | |
48 | ||
49 | if (olpc_ofw("child", args, res) || (s32)node == -1) { | |
50 | pr_err("PROM: %s: fetching child failed!\n", __func__); | |
51 | return 0; | |
52 | } | |
53 | ||
54 | return node; | |
55 | } | |
56 | ||
57 | static int __init olpc_dt_getproplen(phandle node, const char *prop) | |
58 | { | |
59 | const void *args[] = { (void *)node, prop }; | |
60 | int len; | |
61 | void *res[] = { &len }; | |
62 | ||
63 | if ((s32)node == -1) | |
64 | return -1; | |
65 | ||
66 | if (olpc_ofw("getproplen", args, res)) { | |
67 | pr_err("PROM: %s: getproplen failed!\n", __func__); | |
68 | return -1; | |
69 | } | |
70 | ||
71 | return len; | |
72 | } | |
73 | ||
74 | static int __init olpc_dt_getproperty(phandle node, const char *prop, | |
75 | char *buf, int bufsize) | |
76 | { | |
77 | int plen; | |
78 | ||
79 | plen = olpc_dt_getproplen(node, prop); | |
80 | if (plen > bufsize || plen < 1) { | |
81 | return -1; | |
82 | } else { | |
83 | const void *args[] = { (void *)node, prop, buf, (void *)plen }; | |
84 | void *res[] = { &plen }; | |
85 | ||
86 | if (olpc_ofw("getprop", args, res)) { | |
87 | pr_err("PROM: %s: getprop failed!\n", __func__); | |
88 | return -1; | |
89 | } | |
90 | } | |
91 | ||
92 | return plen; | |
93 | } | |
94 | ||
95 | static int __init olpc_dt_nextprop(phandle node, char *prev, char *buf) | |
96 | { | |
97 | const void *args[] = { (void *)node, prev, buf }; | |
98 | int success; | |
99 | void *res[] = { &success }; | |
100 | ||
101 | buf[0] = '\0'; | |
102 | ||
103 | if ((s32)node == -1) | |
104 | return -1; | |
105 | ||
106 | if (olpc_ofw("nextprop", args, res) || success != 1) | |
107 | return -1; | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | static int __init olpc_dt_pkg2path(phandle node, char *buf, | |
113 | const int buflen, int *len) | |
114 | { | |
115 | const void *args[] = { (void *)node, buf, (void *)buflen }; | |
116 | void *res[] = { len }; | |
117 | ||
118 | if ((s32)node == -1) | |
119 | return -1; | |
120 | ||
121 | if (olpc_ofw("package-to-path", args, res) || *len < 1) | |
122 | return -1; | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | static unsigned int prom_early_allocated __initdata; | |
128 | ||
129 | void * __init prom_early_alloc(unsigned long size) | |
130 | { | |
b5318d30 AS |
131 | static u8 *mem; |
132 | static size_t free_mem; | |
c10d1e26 AS |
133 | void *res; |
134 | ||
b5318d30 AS |
135 | if (free_mem < size) { |
136 | const size_t chunk_size = max(PAGE_SIZE, size); | |
137 | ||
138 | /* | |
139 | * To mimimize the number of allocations, grab at least | |
140 | * PAGE_SIZE of memory (that's an arbitrary choice that's | |
141 | * fast enough on the platforms we care about while minimizing | |
142 | * wasted bootmem) and hand off chunks of it to callers. | |
143 | */ | |
2a5bda5a | 144 | res = memblock_alloc(chunk_size, 0); |
60cba5a5 | 145 | BUG_ON(!res); |
b5318d30 AS |
146 | prom_early_allocated += chunk_size; |
147 | memset(res, 0, chunk_size); | |
148 | free_mem = chunk_size; | |
149 | mem = res; | |
150 | } | |
c10d1e26 | 151 | |
b5318d30 AS |
152 | /* allocate from the local cache */ |
153 | free_mem -= size; | |
154 | res = mem; | |
155 | mem += size; | |
c10d1e26 AS |
156 | return res; |
157 | } | |
158 | ||
159 | static struct of_pdt_ops prom_olpc_ops __initdata = { | |
160 | .nextprop = olpc_dt_nextprop, | |
161 | .getproplen = olpc_dt_getproplen, | |
162 | .getproperty = olpc_dt_getproperty, | |
163 | .getchild = olpc_dt_getchild, | |
164 | .getsibling = olpc_dt_getsibling, | |
165 | .pkg2path = olpc_dt_pkg2path, | |
166 | }; | |
167 | ||
f70d8ef4 DD |
168 | static phandle __init olpc_dt_finddevice(const char *path) |
169 | { | |
170 | phandle node; | |
171 | const void *args[] = { path }; | |
172 | void *res[] = { &node }; | |
173 | ||
174 | if (olpc_ofw("finddevice", args, res)) { | |
175 | pr_err("olpc_dt: finddevice failed!\n"); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | if ((s32) node == -1) | |
180 | return 0; | |
181 | ||
182 | return node; | |
183 | } | |
184 | ||
185 | static int __init olpc_dt_interpret(const char *words) | |
186 | { | |
187 | int result; | |
188 | const void *args[] = { words }; | |
189 | void *res[] = { &result }; | |
190 | ||
191 | if (olpc_ofw("interpret", args, res)) { | |
192 | pr_err("olpc_dt: interpret failed!\n"); | |
193 | return -1; | |
194 | } | |
195 | ||
196 | return result; | |
197 | } | |
198 | ||
199 | /* | |
200 | * Extract board revision directly from OFW device tree. | |
201 | * We can't use olpc_platform_info because that hasn't been set up yet. | |
202 | */ | |
203 | static u32 __init olpc_dt_get_board_revision(void) | |
204 | { | |
205 | phandle node; | |
206 | __be32 rev; | |
207 | int r; | |
208 | ||
209 | node = olpc_dt_finddevice("/"); | |
210 | if (!node) | |
211 | return 0; | |
212 | ||
213 | r = olpc_dt_getproperty(node, "board-revision-int", | |
214 | (char *) &rev, sizeof(rev)); | |
215 | if (r < 0) | |
216 | return 0; | |
217 | ||
218 | return be32_to_cpu(rev); | |
219 | } | |
220 | ||
221 | void __init olpc_dt_fixup(void) | |
222 | { | |
223 | int r; | |
224 | char buf[64]; | |
225 | phandle node; | |
226 | u32 board_rev; | |
227 | ||
228 | node = olpc_dt_finddevice("/battery@0"); | |
229 | if (!node) | |
230 | return; | |
231 | ||
232 | /* | |
233 | * If the battery node has a compatible property, we are running a new | |
234 | * enough firmware and don't have fixups to make. | |
235 | */ | |
236 | r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf)); | |
237 | if (r > 0) | |
238 | return; | |
239 | ||
240 | pr_info("PROM DT: Old firmware detected, applying fixes\n"); | |
241 | ||
242 | /* Add olpc,xo1-battery compatible marker to battery node */ | |
243 | olpc_dt_interpret("\" /battery@0\" find-device" | |
244 | " \" olpc,xo1-battery\" +compatible" | |
245 | " device-end"); | |
246 | ||
247 | board_rev = olpc_dt_get_board_revision(); | |
248 | if (!board_rev) | |
249 | return; | |
250 | ||
251 | if (board_rev >= olpc_board_pre(0xd0)) { | |
252 | /* XO-1.5: add dcon device */ | |
253 | olpc_dt_interpret("\" /pci/display@1\" find-device" | |
254 | " new-device" | |
255 | " \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" | |
256 | " finish-device device-end"); | |
257 | } else { | |
258 | /* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */ | |
259 | olpc_dt_interpret("\" /pci/display@1,1\" find-device" | |
260 | " new-device" | |
261 | " \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" | |
262 | " finish-device device-end" | |
263 | " \" /rtc\" find-device" | |
264 | " \" olpc,xo1-rtc\" +compatible" | |
265 | " device-end"); | |
266 | } | |
267 | } | |
268 | ||
c10d1e26 AS |
269 | void __init olpc_dt_build_devicetree(void) |
270 | { | |
271 | phandle root; | |
272 | ||
273 | if (!olpc_ofw_is_installed()) | |
274 | return; | |
275 | ||
f70d8ef4 DD |
276 | olpc_dt_fixup(); |
277 | ||
c10d1e26 AS |
278 | root = olpc_dt_getsibling(0); |
279 | if (!root) { | |
280 | pr_err("PROM: unable to get root node from OFW!\n"); | |
281 | return; | |
282 | } | |
283 | of_pdt_build_devicetree(root, &prom_olpc_ops); | |
284 | ||
285 | pr_info("PROM DT: Built device tree with %u bytes of memory.\n", | |
286 | prom_early_allocated); | |
287 | } | |
45bb1674 DD |
288 | |
289 | /* A list of DT node/bus matches that we want to expose as platform devices */ | |
290 | static struct of_device_id __initdata of_ids[] = { | |
291 | { .compatible = "olpc,xo1-battery" }, | |
292 | { .compatible = "olpc,xo1-dcon" }, | |
293 | { .compatible = "olpc,xo1-rtc" }, | |
294 | {}, | |
295 | }; | |
296 | ||
297 | static int __init olpc_create_platform_devices(void) | |
298 | { | |
299 | if (machine_is_olpc()) | |
300 | return of_platform_bus_probe(NULL, of_ids, NULL); | |
301 | else | |
302 | return 0; | |
303 | } | |
304 | device_initcall(olpc_create_platform_devices); |