]>
Commit | Line | Data |
---|---|---|
3ef0e1f8 AS |
1 | /* |
2 | * Support for the OLPC DCON and OLPC EC access | |
3 | * | |
4 | * Copyright © 2006 Advanced Micro Devices, Inc. | |
5 | * Copyright © 2007-2008 Andres Salomon <dilinger@debian.org> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
14 | #include <linux/init.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/delay.h> | |
17 | #include <linux/spinlock.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/string.h> | |
d5d0e88c | 20 | |
3ef0e1f8 | 21 | #include <asm/geode.h> |
d5d0e88c | 22 | #include <asm/setup.h> |
3ef0e1f8 AS |
23 | #include <asm/olpc.h> |
24 | ||
25 | #ifdef CONFIG_OPEN_FIRMWARE | |
26 | #include <asm/ofw.h> | |
27 | #endif | |
28 | ||
29 | struct olpc_platform_t olpc_platform_info; | |
30 | EXPORT_SYMBOL_GPL(olpc_platform_info); | |
31 | ||
32 | static DEFINE_SPINLOCK(ec_lock); | |
33 | ||
34 | /* what the timeout *should* be (in ms) */ | |
35 | #define EC_BASE_TIMEOUT 20 | |
36 | ||
37 | /* the timeout that bugs in the EC might force us to actually use */ | |
38 | static int ec_timeout = EC_BASE_TIMEOUT; | |
39 | ||
40 | static int __init olpc_ec_timeout_set(char *str) | |
41 | { | |
42 | if (get_option(&str, &ec_timeout) != 1) { | |
43 | ec_timeout = EC_BASE_TIMEOUT; | |
44 | printk(KERN_ERR "olpc-ec: invalid argument to " | |
45 | "'olpc_ec_timeout=', ignoring!\n"); | |
46 | } | |
47 | printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n", | |
48 | ec_timeout); | |
49 | return 1; | |
50 | } | |
51 | __setup("olpc_ec_timeout=", olpc_ec_timeout_set); | |
52 | ||
53 | /* | |
54 | * These {i,o}bf_status functions return whether the buffers are full or not. | |
55 | */ | |
56 | ||
57 | static inline unsigned int ibf_status(unsigned int port) | |
58 | { | |
59 | return !!(inb(port) & 0x02); | |
60 | } | |
61 | ||
62 | static inline unsigned int obf_status(unsigned int port) | |
63 | { | |
64 | return inb(port) & 0x01; | |
65 | } | |
66 | ||
67 | #define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d)) | |
68 | static int __wait_on_ibf(unsigned int line, unsigned int port, int desired) | |
69 | { | |
70 | unsigned int timeo; | |
71 | int state = ibf_status(port); | |
72 | ||
73 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | |
74 | mdelay(1); | |
75 | state = ibf_status(port); | |
76 | } | |
77 | ||
78 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | |
79 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | |
80 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for IBF!\n", | |
81 | line, ec_timeout - timeo); | |
82 | } | |
83 | ||
84 | return !(state == desired); | |
85 | } | |
86 | ||
87 | #define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d)) | |
88 | static int __wait_on_obf(unsigned int line, unsigned int port, int desired) | |
89 | { | |
90 | unsigned int timeo; | |
91 | int state = obf_status(port); | |
92 | ||
93 | for (timeo = ec_timeout; state != desired && timeo; timeo--) { | |
94 | mdelay(1); | |
95 | state = obf_status(port); | |
96 | } | |
97 | ||
98 | if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) && | |
99 | timeo < (ec_timeout - EC_BASE_TIMEOUT)) { | |
100 | printk(KERN_WARNING "olpc-ec: %d: waited %u ms for OBF!\n", | |
101 | line, ec_timeout - timeo); | |
102 | } | |
103 | ||
104 | return !(state == desired); | |
105 | } | |
106 | ||
107 | /* | |
108 | * This allows the kernel to run Embedded Controller commands. The EC is | |
109 | * documented at <http://wiki.laptop.org/go/Embedded_controller>, and the | |
110 | * available EC commands are here: | |
111 | * <http://wiki.laptop.org/go/Ec_specification>. Unfortunately, while | |
112 | * OpenFirmware's source is available, the EC's is not. | |
113 | */ | |
114 | int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, | |
115 | unsigned char *outbuf, size_t outlen) | |
116 | { | |
117 | unsigned long flags; | |
118 | int ret = -EIO; | |
119 | int i; | |
120 | ||
121 | spin_lock_irqsave(&ec_lock, flags); | |
122 | ||
123 | /* Clear OBF */ | |
124 | for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++) | |
125 | inb(0x68); | |
126 | if (i == 10) { | |
127 | printk(KERN_ERR "olpc-ec: timeout while attempting to " | |
128 | "clear OBF flag!\n"); | |
129 | goto err; | |
130 | } | |
131 | ||
132 | if (wait_on_ibf(0x6c, 0)) { | |
133 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to " | |
134 | "quiesce!\n"); | |
135 | goto err; | |
136 | } | |
137 | ||
138 | restart: | |
139 | /* | |
140 | * Note that if we time out during any IBF checks, that's a failure; | |
141 | * we have to return. There's no way for the kernel to clear that. | |
142 | * | |
143 | * If we time out during an OBF check, we can restart the command; | |
144 | * reissuing it will clear the OBF flag, and we should be alright. | |
145 | * The OBF flag will sometimes misbehave due to what we believe | |
146 | * is a hardware quirk.. | |
147 | */ | |
148 | printk(KERN_DEBUG "olpc-ec: running cmd 0x%x\n", cmd); | |
149 | outb(cmd, 0x6c); | |
150 | ||
151 | if (wait_on_ibf(0x6c, 0)) { | |
152 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to read " | |
153 | "command!\n"); | |
154 | goto err; | |
155 | } | |
156 | ||
157 | if (inbuf && inlen) { | |
158 | /* write data to EC */ | |
159 | for (i = 0; i < inlen; i++) { | |
160 | if (wait_on_ibf(0x6c, 0)) { | |
161 | printk(KERN_ERR "olpc-ec: timeout waiting for" | |
162 | " EC accept data!\n"); | |
163 | goto err; | |
164 | } | |
165 | printk(KERN_DEBUG "olpc-ec: sending cmd arg 0x%x\n", | |
166 | inbuf[i]); | |
167 | outb(inbuf[i], 0x68); | |
168 | } | |
169 | } | |
170 | if (outbuf && outlen) { | |
171 | /* read data from EC */ | |
172 | for (i = 0; i < outlen; i++) { | |
173 | if (wait_on_obf(0x6c, 1)) { | |
174 | printk(KERN_ERR "olpc-ec: timeout waiting for" | |
175 | " EC to provide data!\n"); | |
176 | goto restart; | |
177 | } | |
178 | outbuf[i] = inb(0x68); | |
179 | printk(KERN_DEBUG "olpc-ec: received 0x%x\n", | |
180 | outbuf[i]); | |
181 | } | |
182 | } | |
183 | ||
184 | ret = 0; | |
185 | err: | |
186 | spin_unlock_irqrestore(&ec_lock, flags); | |
187 | return ret; | |
188 | } | |
189 | EXPORT_SYMBOL_GPL(olpc_ec_cmd); | |
190 | ||
191 | #ifdef CONFIG_OPEN_FIRMWARE | |
192 | static void __init platform_detect(void) | |
193 | { | |
194 | size_t propsize; | |
e51a1ac2 | 195 | __be32 rev; |
3ef0e1f8 AS |
196 | |
197 | if (ofw("getprop", 4, 1, NULL, "board-revision-int", &rev, 4, | |
198 | &propsize) || propsize != 4) { | |
199 | printk(KERN_ERR "ofw: getprop call failed!\n"); | |
e51a1ac2 | 200 | rev = cpu_to_be32(0); |
3ef0e1f8 AS |
201 | } |
202 | olpc_platform_info.boardrev = be32_to_cpu(rev); | |
203 | } | |
204 | #else | |
205 | static void __init platform_detect(void) | |
206 | { | |
207 | /* stopgap until OFW support is added to the kernel */ | |
e49590b6 | 208 | olpc_platform_info.boardrev = olpc_board(0xc2); |
3ef0e1f8 AS |
209 | } |
210 | #endif | |
211 | ||
212 | static int __init olpc_init(void) | |
213 | { | |
214 | unsigned char *romsig; | |
215 | ||
216 | /* The ioremap check is dangerous; limit what we run it on */ | |
f060f270 | 217 | if (!is_geode() || cs5535_has_vsa2()) |
3ef0e1f8 AS |
218 | return 0; |
219 | ||
220 | spin_lock_init(&ec_lock); | |
221 | ||
222 | romsig = ioremap(0xffffffc0, 16); | |
223 | if (!romsig) | |
224 | return 0; | |
225 | ||
226 | if (strncmp(romsig, "CL1 Q", 7)) | |
227 | goto unmap; | |
228 | if (strncmp(romsig+6, romsig+13, 3)) { | |
229 | printk(KERN_INFO "OLPC BIOS signature looks invalid. " | |
230 | "Assuming not OLPC\n"); | |
231 | goto unmap; | |
232 | } | |
233 | ||
234 | printk(KERN_INFO "OLPC board with OpenFirmware %.16s\n", romsig); | |
235 | olpc_platform_info.flags |= OLPC_F_PRESENT; | |
236 | ||
237 | /* get the platform revision */ | |
238 | platform_detect(); | |
239 | ||
240 | /* assume B1 and above models always have a DCON */ | |
241 | if (olpc_board_at_least(olpc_board(0xb1))) | |
242 | olpc_platform_info.flags |= OLPC_F_DCON; | |
243 | ||
244 | /* get the EC revision */ | |
245 | olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, | |
246 | (unsigned char *) &olpc_platform_info.ecver, 1); | |
247 | ||
d5d0e88c TG |
248 | #ifdef CONFIG_PCI_OLPC |
249 | /* If the VSA exists let it emulate PCI, if not emulate in kernel */ | |
250 | if (!cs5535_has_vsa2()) | |
251 | x86_init.pci.arch_init = pci_olpc_init; | |
252 | #endif | |
3ef0e1f8 AS |
253 | |
254 | printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", | |
255 | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", | |
256 | olpc_platform_info.boardrev >> 4, | |
257 | olpc_platform_info.ecver); | |
258 | ||
259 | unmap: | |
260 | iounmap(romsig); | |
261 | return 0; | |
262 | } | |
263 | ||
264 | postcore_initcall(olpc_init); |