1 | // SPDX-License-Identifier: GPL-2.0-or-later |
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> |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/init.h> |
11 | #include <linux/export.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/io.h> |
14 | #include <linux/string.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/of.h> |
17 | #include <linux/syscore_ops.h> |
18 | #include <linux/mutex.h> |
19 | #include <linux/olpc-ec.h> |
20 | |
21 | #include <asm/geode.h> |
22 | #include <asm/setup.h> |
23 | #include <asm/olpc.h> |
24 | #include <asm/olpc_ofw.h> |
25 | |
26 | struct olpc_platform_t olpc_platform_info; |
27 | EXPORT_SYMBOL_GPL(olpc_platform_info); |
28 | |
29 | /* what the timeout *should* be (in ms) */ |
30 | #define EC_BASE_TIMEOUT 20 |
31 | |
32 | /* the timeout that bugs in the EC might force us to actually use */ |
33 | static int ec_timeout = EC_BASE_TIMEOUT; |
34 | |
35 | static int __init olpc_ec_timeout_set(char *str) |
36 | { |
37 | if (get_option(str: &str, pint: &ec_timeout) != 1) { |
38 | ec_timeout = EC_BASE_TIMEOUT; |
39 | printk(KERN_ERR "olpc-ec: invalid argument to " |
40 | "'olpc_ec_timeout=', ignoring!\n" ); |
41 | } |
42 | printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n" , |
43 | ec_timeout); |
44 | return 1; |
45 | } |
46 | __setup("olpc_ec_timeout=" , olpc_ec_timeout_set); |
47 | |
48 | /* |
49 | * These {i,o}bf_status functions return whether the buffers are full or not. |
50 | */ |
51 | |
52 | static inline unsigned int ibf_status(unsigned int port) |
53 | { |
54 | return !!(inb(port) & 0x02); |
55 | } |
56 | |
57 | static inline unsigned int obf_status(unsigned int port) |
58 | { |
59 | return inb(port) & 0x01; |
60 | } |
61 | |
62 | #define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d)) |
63 | static int __wait_on_ibf(unsigned int line, unsigned int port, int desired) |
64 | { |
65 | unsigned int timeo; |
66 | int state = ibf_status(port); |
67 | |
68 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { |
69 | mdelay(1); |
70 | state = ibf_status(port); |
71 | } |
72 | |
73 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && |
74 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { |
75 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for IBF!\n" , |
76 | line, ec_timeout - timeo); |
77 | } |
78 | |
79 | return !(state == desired); |
80 | } |
81 | |
82 | #define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d)) |
83 | static int __wait_on_obf(unsigned int line, unsigned int port, int desired) |
84 | { |
85 | unsigned int timeo; |
86 | int state = obf_status(port); |
87 | |
88 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { |
89 | mdelay(1); |
90 | state = obf_status(port); |
91 | } |
92 | |
93 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && |
94 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { |
95 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for OBF!\n" , |
96 | line, ec_timeout - timeo); |
97 | } |
98 | |
99 | return !(state == desired); |
100 | } |
101 | |
102 | /* |
103 | * This allows the kernel to run Embedded Controller commands. The EC is |
104 | * documented at <http://wiki.laptop.org/go/Embedded_controller>, and the |
105 | * available EC commands are here: |
106 | * <http://wiki.laptop.org/go/Ec_specification>. Unfortunately, while |
107 | * OpenFirmware's source is available, the EC's is not. |
108 | */ |
109 | static int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, |
110 | size_t outlen, void *arg) |
111 | { |
112 | int ret = -EIO; |
113 | int i; |
114 | int restarts = 0; |
115 | |
116 | /* Clear OBF */ |
117 | for (i = 0; i < 10 && (obf_status(port: 0x6c) == 1); i++) |
118 | inb(port: 0x68); |
119 | if (i == 10) { |
120 | printk(KERN_ERR "olpc-ec: timeout while attempting to " |
121 | "clear OBF flag!\n" ); |
122 | goto err; |
123 | } |
124 | |
125 | if (wait_on_ibf(0x6c, 0)) { |
126 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to " |
127 | "quiesce!\n" ); |
128 | goto err; |
129 | } |
130 | |
131 | restart: |
132 | /* |
133 | * Note that if we time out during any IBF checks, that's a failure; |
134 | * we have to return. There's no way for the kernel to clear that. |
135 | * |
136 | * If we time out during an OBF check, we can restart the command; |
137 | * reissuing it will clear the OBF flag, and we should be alright. |
138 | * The OBF flag will sometimes misbehave due to what we believe |
139 | * is a hardware quirk.. |
140 | */ |
141 | pr_devel("olpc-ec: running cmd 0x%x\n" , cmd); |
142 | outb(value: cmd, port: 0x6c); |
143 | |
144 | if (wait_on_ibf(0x6c, 0)) { |
145 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to read " |
146 | "command!\n" ); |
147 | goto err; |
148 | } |
149 | |
150 | if (inbuf && inlen) { |
151 | /* write data to EC */ |
152 | for (i = 0; i < inlen; i++) { |
153 | pr_devel("olpc-ec: sending cmd arg 0x%x\n" , inbuf[i]); |
154 | outb(value: inbuf[i], port: 0x68); |
155 | if (wait_on_ibf(0x6c, 0)) { |
156 | printk(KERN_ERR "olpc-ec: timeout waiting for" |
157 | " EC accept data!\n" ); |
158 | goto err; |
159 | } |
160 | } |
161 | } |
162 | if (outbuf && outlen) { |
163 | /* read data from EC */ |
164 | for (i = 0; i < outlen; i++) { |
165 | if (wait_on_obf(0x6c, 1)) { |
166 | printk(KERN_ERR "olpc-ec: timeout waiting for" |
167 | " EC to provide data!\n" ); |
168 | if (restarts++ < 10) |
169 | goto restart; |
170 | goto err; |
171 | } |
172 | outbuf[i] = inb(port: 0x68); |
173 | pr_devel("olpc-ec: received 0x%x\n" , outbuf[i]); |
174 | } |
175 | } |
176 | |
177 | ret = 0; |
178 | err: |
179 | return ret; |
180 | } |
181 | |
182 | static bool __init check_ofw_architecture(struct device_node *root) |
183 | { |
184 | const char *olpc_arch; |
185 | int propsize; |
186 | |
187 | olpc_arch = of_get_property(node: root, name: "architecture" , lenp: &propsize); |
188 | return propsize == 5 && strncmp("OLPC" , olpc_arch, 5) == 0; |
189 | } |
190 | |
191 | static u32 __init get_board_revision(struct device_node *root) |
192 | { |
193 | int propsize; |
194 | const __be32 *rev; |
195 | |
196 | rev = of_get_property(node: root, name: "board-revision-int" , lenp: &propsize); |
197 | if (propsize != 4) |
198 | return 0; |
199 | |
200 | return be32_to_cpu(*rev); |
201 | } |
202 | |
203 | static bool __init platform_detect(void) |
204 | { |
205 | struct device_node *root = of_find_node_by_path(path: "/" ); |
206 | bool success; |
207 | |
208 | if (!root) |
209 | return false; |
210 | |
211 | success = check_ofw_architecture(root); |
212 | if (success) { |
213 | olpc_platform_info.boardrev = get_board_revision(root); |
214 | olpc_platform_info.flags |= OLPC_F_PRESENT; |
215 | |
216 | pr_info("OLPC board revision %s%X\n" , |
217 | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "" , |
218 | olpc_platform_info.boardrev >> 4); |
219 | } |
220 | |
221 | of_node_put(node: root); |
222 | return success; |
223 | } |
224 | |
225 | static int __init add_xo1_platform_devices(void) |
226 | { |
227 | struct platform_device *pdev; |
228 | |
229 | pdev = platform_device_register_simple(name: "xo1-rfkill" , id: -1, NULL, num: 0); |
230 | if (IS_ERR(ptr: pdev)) |
231 | return PTR_ERR(ptr: pdev); |
232 | |
233 | pdev = platform_device_register_simple(name: "olpc-xo1" , id: -1, NULL, num: 0); |
234 | |
235 | return PTR_ERR_OR_ZERO(ptr: pdev); |
236 | } |
237 | |
238 | static int olpc_xo1_ec_suspend(struct platform_device *pdev) |
239 | { |
240 | /* |
241 | * Squelch SCIs while suspended. This is a fix for |
242 | * <http://dev.laptop.org/ticket/1835>. |
243 | */ |
244 | return olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, inlen: 0, NULL, outlen: 0); |
245 | } |
246 | |
247 | static int olpc_xo1_ec_resume(struct platform_device *pdev) |
248 | { |
249 | /* Tell the EC to stop inhibiting SCIs */ |
250 | olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, inlen: 0, NULL, outlen: 0); |
251 | |
252 | /* |
253 | * Tell the wireless module to restart USB communication. |
254 | * Must be done twice. |
255 | */ |
256 | olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, inlen: 0, NULL, outlen: 0); |
257 | olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, inlen: 0, NULL, outlen: 0); |
258 | |
259 | return 0; |
260 | } |
261 | |
262 | static struct olpc_ec_driver ec_xo1_driver = { |
263 | .suspend = olpc_xo1_ec_suspend, |
264 | .resume = olpc_xo1_ec_resume, |
265 | .ec_cmd = olpc_xo1_ec_cmd, |
266 | #ifdef CONFIG_OLPC_XO1_SCI |
267 | /* |
268 | * XO-1 EC wakeups are available when olpc-xo1-sci driver is |
269 | * compiled in |
270 | */ |
271 | .wakeup_available = true, |
272 | #endif |
273 | }; |
274 | |
275 | static struct olpc_ec_driver ec_xo1_5_driver = { |
276 | .ec_cmd = olpc_xo1_ec_cmd, |
277 | #ifdef CONFIG_OLPC_XO15_SCI |
278 | /* |
279 | * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is |
280 | * compiled in |
281 | */ |
282 | .wakeup_available = true, |
283 | #endif |
284 | }; |
285 | |
286 | static int __init olpc_init(void) |
287 | { |
288 | int r = 0; |
289 | |
290 | if (!olpc_ofw_present() || !platform_detect()) |
291 | return 0; |
292 | |
293 | /* register the XO-1 and 1.5-specific EC handler */ |
294 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ |
295 | olpc_ec_driver_register(drv: &ec_xo1_driver, NULL); |
296 | else |
297 | olpc_ec_driver_register(drv: &ec_xo1_5_driver, NULL); |
298 | platform_device_register_simple(name: "olpc-ec" , id: -1, NULL, num: 0); |
299 | |
300 | /* assume B1 and above models always have a DCON */ |
301 | if (olpc_board_at_least(olpc_board(0xb1))) |
302 | olpc_platform_info.flags |= OLPC_F_DCON; |
303 | |
304 | #ifdef CONFIG_PCI_OLPC |
305 | /* If the VSA exists let it emulate PCI, if not emulate in kernel. |
306 | * XO-1 only. */ |
307 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0) && |
308 | !cs5535_has_vsa2()) |
309 | x86_init.pci.arch_init = pci_olpc_init; |
310 | #endif |
311 | |
312 | if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */ |
313 | r = add_xo1_platform_devices(); |
314 | if (r) |
315 | return r; |
316 | } |
317 | |
318 | return 0; |
319 | } |
320 | |
321 | postcore_initcall(olpc_init); |
322 | |