]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
3ef0e1f8 AS |
2 | /* |
3 | * Support for the OLPC DCON and OLPC EC access | |
4 | * | |
5 | * Copyright © 2006 Advanced Micro Devices, Inc. | |
6 | * Copyright © 2007-2008 Andres Salomon <dilinger@debian.org> | |
3ef0e1f8 AS |
7 | */ |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/init.h> | |
cc3ae7b0 | 11 | #include <linux/export.h> |
3ef0e1f8 | 12 | #include <linux/delay.h> |
3ef0e1f8 AS |
13 | #include <linux/io.h> |
14 | #include <linux/string.h> | |
447b1d43 | 15 | #include <linux/platform_device.h> |
45bb1674 | 16 | #include <linux/of.h> |
bc4ecd5a | 17 | #include <linux/syscore_ops.h> |
a3c8121b | 18 | #include <linux/mutex.h> |
3bf9428f | 19 | #include <linux/olpc-ec.h> |
d5d0e88c | 20 | |
3ef0e1f8 | 21 | #include <asm/geode.h> |
d5d0e88c | 22 | #include <asm/setup.h> |
3ef0e1f8 | 23 | #include <asm/olpc.h> |
fd699c76 | 24 | #include <asm/olpc_ofw.h> |
3ef0e1f8 AS |
25 | |
26 | struct olpc_platform_t olpc_platform_info; | |
27 | EXPORT_SYMBOL_GPL(olpc_platform_info); | |
28 | ||
bc4ecd5a DD |
29 | /* EC event mask to be applied during suspend (defining wakeup sources). */ |
30 | static u16 ec_wakeup_mask; | |
31 | ||
3ef0e1f8 AS |
32 | /* what the timeout *should* be (in ms) */ |
33 | #define EC_BASE_TIMEOUT 20 | |
34 | ||
35 | /* the timeout that bugs in the EC might force us to actually use */ | |
36 | static int ec_timeout = EC_BASE_TIMEOUT; | |
37 | ||
38 | static int __init olpc_ec_timeout_set(char *str) | |
39 | { | |
40 | if (get_option(&str, &ec_timeout) != 1) { | |
41 | ec_timeout = EC_BASE_TIMEOUT; | |
42 | printk(KERN_ERR "olpc-ec: invalid argument to " | |
43 | "'olpc_ec_timeout=', ignoring!\n"); | |
44 | } | |
45 | printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n", | |
46 | ec_timeout); | |
47 | return 1; | |
48 | } | |
49 | __setup("olpc_ec_timeout=", olpc_ec_timeout_set); | |
50 | ||
51 | /* | |
52 | * These {i,o}bf_status functions return whether the buffers are full or not. | |
53 | */ | |
54 | ||
55 | static inline unsigned int ibf_status(unsigned int port) | |
56 | { | |
57 | return !!(inb(port) & 0x02); | |
58 | } | |
59 | ||
60 | static inline unsigned int obf_status(unsigned int port) | |
61 | { | |
62 | return inb(port) & 0x01; | |
63 | } | |
64 | ||
65 | #define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d)) | |
66 | static int __wait_on_ibf(unsigned int line, unsigned int port, int desired) | |
67 | { | |
68 | unsigned int timeo; | |
69 | int state = ibf_status(port); | |
70 | ||
71 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | |
72 | mdelay(1); | |
73 | state = ibf_status(port); | |
74 | } | |
75 | ||
76 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | |
77 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | |
78 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for IBF!\n", | |
79 | line, ec_timeout - timeo); | |
80 | } | |
81 | ||
82 | return !(state == desired); | |
83 | } | |
84 | ||
85 | #define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d)) | |
86 | static int __wait_on_obf(unsigned int line, unsigned int port, int desired) | |
87 | { | |
88 | unsigned int timeo; | |
89 | int state = obf_status(port); | |
90 | ||
91 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | |
92 | mdelay(1); | |
93 | state = obf_status(port); | |
94 | } | |
95 | ||
96 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | |
97 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | |
98 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for OBF!\n", | |
99 | line, ec_timeout - timeo); | |
100 | } | |
101 | ||
102 | return !(state == desired); | |
103 | } | |
104 | ||
105 | /* | |
106 | * This allows the kernel to run Embedded Controller commands. The EC is | |
107 | * documented at <http://wiki.laptop.org/go/Embedded_controller>, and the | |
108 | * available EC commands are here: | |
109 | * <http://wiki.laptop.org/go/Ec_specification>. Unfortunately, while | |
110 | * OpenFirmware's source is available, the EC's is not. | |
111 | */ | |
85f90cf6 AS |
112 | static int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, |
113 | size_t outlen, void *arg) | |
3ef0e1f8 | 114 | { |
3ef0e1f8 AS |
115 | int ret = -EIO; |
116 | int i; | |
286e5b97 | 117 | int restarts = 0; |
3ef0e1f8 | 118 | |
3ef0e1f8 AS |
119 | /* Clear OBF */ |
120 | for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++) | |
121 | inb(0x68); | |
122 | if (i == 10) { | |
123 | printk(KERN_ERR "olpc-ec: timeout while attempting to " | |
124 | "clear OBF flag!\n"); | |
125 | goto err; | |
126 | } | |
127 | ||
128 | if (wait_on_ibf(0x6c, 0)) { | |
129 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to " | |
130 | "quiesce!\n"); | |
131 | goto err; | |
132 | } | |
133 | ||
134 | restart: | |
135 | /* | |
136 | * Note that if we time out during any IBF checks, that's a failure; | |
137 | * we have to return. There's no way for the kernel to clear that. | |
138 | * | |
139 | * If we time out during an OBF check, we can restart the command; | |
140 | * reissuing it will clear the OBF flag, and we should be alright. | |
141 | * The OBF flag will sometimes misbehave due to what we believe | |
142 | * is a hardware quirk.. | |
143 | */ | |
25971865 | 144 | pr_devel("olpc-ec: running cmd 0x%x\n", cmd); |
3ef0e1f8 AS |
145 | outb(cmd, 0x6c); |
146 | ||
147 | if (wait_on_ibf(0x6c, 0)) { | |
148 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to read " | |
149 | "command!\n"); | |
150 | goto err; | |
151 | } | |
152 | ||
153 | if (inbuf && inlen) { | |
154 | /* write data to EC */ | |
155 | for (i = 0; i < inlen; i++) { | |
a3ea14df PF |
156 | pr_devel("olpc-ec: sending cmd arg 0x%x\n", inbuf[i]); |
157 | outb(inbuf[i], 0x68); | |
3ef0e1f8 AS |
158 | if (wait_on_ibf(0x6c, 0)) { |
159 | printk(KERN_ERR "olpc-ec: timeout waiting for" | |
160 | " EC accept data!\n"); | |
161 | goto err; | |
162 | } | |
3ef0e1f8 AS |
163 | } |
164 | } | |
165 | if (outbuf && outlen) { | |
166 | /* read data from EC */ | |
167 | for (i = 0; i < outlen; i++) { | |
168 | if (wait_on_obf(0x6c, 1)) { | |
169 | printk(KERN_ERR "olpc-ec: timeout waiting for" | |
170 | " EC to provide data!\n"); | |
286e5b97 PF |
171 | if (restarts++ < 10) |
172 | goto restart; | |
173 | goto err; | |
3ef0e1f8 AS |
174 | } |
175 | outbuf[i] = inb(0x68); | |
25971865 | 176 | pr_devel("olpc-ec: received 0x%x\n", outbuf[i]); |
3ef0e1f8 AS |
177 | } |
178 | } | |
179 | ||
180 | ret = 0; | |
181 | err: | |
3ef0e1f8 AS |
182 | return ret; |
183 | } | |
3ef0e1f8 | 184 | |
bc4ecd5a DD |
185 | void olpc_ec_wakeup_set(u16 value) |
186 | { | |
187 | ec_wakeup_mask |= value; | |
188 | } | |
189 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); | |
190 | ||
191 | void olpc_ec_wakeup_clear(u16 value) | |
192 | { | |
193 | ec_wakeup_mask &= ~value; | |
194 | } | |
195 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); | |
196 | ||
197 | /* | |
198 | * Returns true if the compile and runtime configurations allow for EC events | |
199 | * to wake the system. | |
200 | */ | |
201 | bool olpc_ec_wakeup_available(void) | |
202 | { | |
203 | if (!machine_is_olpc()) | |
204 | return false; | |
205 | ||
206 | /* | |
207 | * XO-1 EC wakeups are available when olpc-xo1-sci driver is | |
208 | * compiled in | |
209 | */ | |
210 | #ifdef CONFIG_OLPC_XO1_SCI | |
211 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ | |
212 | return true; | |
213 | #endif | |
214 | ||
a0f30f59 DD |
215 | /* |
216 | * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is | |
217 | * compiled in | |
218 | */ | |
219 | #ifdef CONFIG_OLPC_XO15_SCI | |
220 | if (olpc_platform_info.boardrev >= olpc_board_pre(0xd0)) /* XO-1.5 */ | |
221 | return true; | |
222 | #endif | |
223 | ||
bc4ecd5a DD |
224 | return false; |
225 | } | |
226 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); | |
227 | ||
228 | int olpc_ec_mask_write(u16 bits) | |
229 | { | |
230 | if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { | |
231 | __be16 ec_word = cpu_to_be16(bits); | |
232 | return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *) &ec_word, 2, | |
233 | NULL, 0); | |
234 | } else { | |
235 | unsigned char ec_byte = bits & 0xff; | |
236 | return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0); | |
237 | } | |
238 | } | |
239 | EXPORT_SYMBOL_GPL(olpc_ec_mask_write); | |
240 | ||
241 | int olpc_ec_sci_query(u16 *sci_value) | |
242 | { | |
243 | int ret; | |
244 | ||
245 | if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { | |
246 | __be16 ec_word; | |
247 | ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, | |
248 | NULL, 0, (void *) &ec_word, 2); | |
249 | if (ret == 0) | |
250 | *sci_value = be16_to_cpu(ec_word); | |
251 | } else { | |
252 | unsigned char ec_byte; | |
253 | ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); | |
254 | if (ret == 0) | |
255 | *sci_value = ec_byte; | |
256 | } | |
257 | ||
258 | return ret; | |
259 | } | |
260 | EXPORT_SYMBOL_GPL(olpc_ec_sci_query); | |
261 | ||
45bb1674 | 262 | static bool __init check_ofw_architecture(struct device_node *root) |
3e3c4860 | 263 | { |
45bb1674 DD |
264 | const char *olpc_arch; |
265 | int propsize; | |
3e3c4860 | 266 | |
45bb1674 | 267 | olpc_arch = of_get_property(root, "architecture", &propsize); |
3e3c4860 DD |
268 | return propsize == 5 && strncmp("OLPC", olpc_arch, 5) == 0; |
269 | } | |
270 | ||
45bb1674 | 271 | static u32 __init get_board_revision(struct device_node *root) |
3ef0e1f8 | 272 | { |
45bb1674 DD |
273 | int propsize; |
274 | const __be32 *rev; | |
275 | ||
276 | rev = of_get_property(root, "board-revision-int", &propsize); | |
277 | if (propsize != 4) | |
278 | return 0; | |
279 | ||
280 | return be32_to_cpu(*rev); | |
3ef0e1f8 | 281 | } |
3e3c4860 DD |
282 | |
283 | static bool __init platform_detect(void) | |
3ef0e1f8 | 284 | { |
45bb1674 DD |
285 | struct device_node *root = of_find_node_by_path("/"); |
286 | bool success; | |
287 | ||
288 | if (!root) | |
3e3c4860 | 289 | return false; |
45bb1674 DD |
290 | |
291 | success = check_ofw_architecture(root); | |
292 | if (success) { | |
293 | olpc_platform_info.boardrev = get_board_revision(root); | |
294 | olpc_platform_info.flags |= OLPC_F_PRESENT; | |
295 | } | |
296 | ||
297 | of_node_put(root); | |
298 | return success; | |
3ef0e1f8 | 299 | } |
3ef0e1f8 | 300 | |
447b1d43 DD |
301 | static int __init add_xo1_platform_devices(void) |
302 | { | |
303 | struct platform_device *pdev; | |
304 | ||
305 | pdev = platform_device_register_simple("xo1-rfkill", -1, NULL, 0); | |
306 | if (IS_ERR(pdev)) | |
307 | return PTR_ERR(pdev); | |
308 | ||
309 | pdev = platform_device_register_simple("olpc-xo1", -1, NULL, 0); | |
447b1d43 | 310 | |
c7ba9f7c | 311 | return PTR_ERR_OR_ZERO(pdev); |
447b1d43 DD |
312 | } |
313 | ||
85f90cf6 AS |
314 | static int olpc_xo1_ec_probe(struct platform_device *pdev) |
315 | { | |
316 | /* get the EC revision */ | |
317 | olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, | |
318 | (unsigned char *) &olpc_platform_info.ecver, 1); | |
319 | ||
320 | /* EC version 0x5f adds support for wide SCI mask */ | |
321 | if (olpc_platform_info.ecver >= 0x5f) | |
322 | olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI; | |
323 | ||
324 | pr_info("OLPC board revision %s%X (EC=%x)\n", | |
325 | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", | |
326 | olpc_platform_info.boardrev >> 4, | |
327 | olpc_platform_info.ecver); | |
328 | ||
329 | return 0; | |
330 | } | |
1fcfd08b AS |
331 | static int olpc_xo1_ec_suspend(struct platform_device *pdev) |
332 | { | |
333 | olpc_ec_mask_write(ec_wakeup_mask); | |
334 | ||
335 | /* | |
336 | * Squelch SCIs while suspended. This is a fix for | |
337 | * <http://dev.laptop.org/ticket/1835>. | |
338 | */ | |
339 | return olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0); | |
340 | } | |
341 | ||
342 | static int olpc_xo1_ec_resume(struct platform_device *pdev) | |
343 | { | |
344 | /* Tell the EC to stop inhibiting SCIs */ | |
345 | olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0); | |
346 | ||
347 | /* | |
348 | * Tell the wireless module to restart USB communication. | |
349 | * Must be done twice. | |
350 | */ | |
351 | olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); | |
352 | olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); | |
353 | ||
354 | return 0; | |
355 | } | |
85f90cf6 AS |
356 | |
357 | static struct olpc_ec_driver ec_xo1_driver = { | |
1fcfd08b AS |
358 | .probe = olpc_xo1_ec_probe, |
359 | .suspend = olpc_xo1_ec_suspend, | |
360 | .resume = olpc_xo1_ec_resume, | |
361 | .ec_cmd = olpc_xo1_ec_cmd, | |
362 | }; | |
363 | ||
364 | static struct olpc_ec_driver ec_xo1_5_driver = { | |
85f90cf6 AS |
365 | .probe = olpc_xo1_ec_probe, |
366 | .ec_cmd = olpc_xo1_ec_cmd, | |
bc4ecd5a DD |
367 | }; |
368 | ||
3ef0e1f8 AS |
369 | static int __init olpc_init(void) |
370 | { | |
447b1d43 DD |
371 | int r = 0; |
372 | ||
3e3c4860 | 373 | if (!olpc_ofw_present() || !platform_detect()) |
3ef0e1f8 AS |
374 | return 0; |
375 | ||
85f90cf6 | 376 | /* register the XO-1 and 1.5-specific EC handler */ |
1fcfd08b AS |
377 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ |
378 | olpc_ec_driver_register(&ec_xo1_driver, NULL); | |
379 | else | |
380 | olpc_ec_driver_register(&ec_xo1_5_driver, NULL); | |
85f90cf6 | 381 | platform_device_register_simple("olpc-ec", -1, NULL, 0); |
3ef0e1f8 | 382 | |
3ef0e1f8 AS |
383 | /* assume B1 and above models always have a DCON */ |
384 | if (olpc_board_at_least(olpc_board(0xb1))) | |
385 | olpc_platform_info.flags |= OLPC_F_DCON; | |
386 | ||
d5d0e88c | 387 | #ifdef CONFIG_PCI_OLPC |
76fb6570 DD |
388 | /* If the VSA exists let it emulate PCI, if not emulate in kernel. |
389 | * XO-1 only. */ | |
390 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0) && | |
391 | !cs5535_has_vsa2()) | |
d5d0e88c TG |
392 | x86_init.pci.arch_init = pci_olpc_init; |
393 | #endif | |
3ef0e1f8 | 394 | |
447b1d43 DD |
395 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */ |
396 | r = add_xo1_platform_devices(); | |
397 | if (r) | |
398 | return r; | |
399 | } | |
400 | ||
3ef0e1f8 AS |
401 | return 0; |
402 | } | |
403 | ||
404 | postcore_initcall(olpc_init); |