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> | |
20 | #include <asm/geode.h> | |
21 | #include <asm/olpc.h> | |
22 | ||
23 | #ifdef CONFIG_OPEN_FIRMWARE | |
24 | #include <asm/ofw.h> | |
25 | #endif | |
26 | ||
27 | struct olpc_platform_t olpc_platform_info; | |
28 | EXPORT_SYMBOL_GPL(olpc_platform_info); | |
29 | ||
30 | static DEFINE_SPINLOCK(ec_lock); | |
31 | ||
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 | */ | |
112 | int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, | |
113 | unsigned char *outbuf, size_t outlen) | |
114 | { | |
115 | unsigned long flags; | |
116 | int ret = -EIO; | |
117 | int i; | |
118 | ||
119 | spin_lock_irqsave(&ec_lock, flags); | |
120 | ||
121 | /* Clear OBF */ | |
122 | for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++) | |
123 | inb(0x68); | |
124 | if (i == 10) { | |
125 | printk(KERN_ERR "olpc-ec: timeout while attempting to " | |
126 | "clear OBF flag!\n"); | |
127 | goto err; | |
128 | } | |
129 | ||
130 | if (wait_on_ibf(0x6c, 0)) { | |
131 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to " | |
132 | "quiesce!\n"); | |
133 | goto err; | |
134 | } | |
135 | ||
136 | restart: | |
137 | /* | |
138 | * Note that if we time out during any IBF checks, that's a failure; | |
139 | * we have to return. There's no way for the kernel to clear that. | |
140 | * | |
141 | * If we time out during an OBF check, we can restart the command; | |
142 | * reissuing it will clear the OBF flag, and we should be alright. | |
143 | * The OBF flag will sometimes misbehave due to what we believe | |
144 | * is a hardware quirk.. | |
145 | */ | |
146 | printk(KERN_DEBUG "olpc-ec: running cmd 0x%x\n", cmd); | |
147 | outb(cmd, 0x6c); | |
148 | ||
149 | if (wait_on_ibf(0x6c, 0)) { | |
150 | printk(KERN_ERR "olpc-ec: timeout waiting for EC to read " | |
151 | "command!\n"); | |
152 | goto err; | |
153 | } | |
154 | ||
155 | if (inbuf && inlen) { | |
156 | /* write data to EC */ | |
157 | for (i = 0; i < inlen; i++) { | |
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 | } | |
163 | printk(KERN_DEBUG "olpc-ec: sending cmd arg 0x%x\n", | |
164 | inbuf[i]); | |
165 | outb(inbuf[i], 0x68); | |
166 | } | |
167 | } | |
168 | if (outbuf && outlen) { | |
169 | /* read data from EC */ | |
170 | for (i = 0; i < outlen; i++) { | |
171 | if (wait_on_obf(0x6c, 1)) { | |
172 | printk(KERN_ERR "olpc-ec: timeout waiting for" | |
173 | " EC to provide data!\n"); | |
174 | goto restart; | |
175 | } | |
176 | outbuf[i] = inb(0x68); | |
177 | printk(KERN_DEBUG "olpc-ec: received 0x%x\n", | |
178 | outbuf[i]); | |
179 | } | |
180 | } | |
181 | ||
182 | ret = 0; | |
183 | err: | |
184 | spin_unlock_irqrestore(&ec_lock, flags); | |
185 | return ret; | |
186 | } | |
187 | EXPORT_SYMBOL_GPL(olpc_ec_cmd); | |
188 | ||
189 | #ifdef CONFIG_OPEN_FIRMWARE | |
190 | static void __init platform_detect(void) | |
191 | { | |
192 | size_t propsize; | |
e51a1ac2 | 193 | __be32 rev; |
3ef0e1f8 AS |
194 | |
195 | if (ofw("getprop", 4, 1, NULL, "board-revision-int", &rev, 4, | |
196 | &propsize) || propsize != 4) { | |
197 | printk(KERN_ERR "ofw: getprop call failed!\n"); | |
e51a1ac2 | 198 | rev = cpu_to_be32(0); |
3ef0e1f8 AS |
199 | } |
200 | olpc_platform_info.boardrev = be32_to_cpu(rev); | |
201 | } | |
202 | #else | |
203 | static void __init platform_detect(void) | |
204 | { | |
205 | /* stopgap until OFW support is added to the kernel */ | |
e51a1ac2 | 206 | olpc_platform_info.boardrev = 0xc2; |
3ef0e1f8 AS |
207 | } |
208 | #endif | |
209 | ||
210 | static int __init olpc_init(void) | |
211 | { | |
212 | unsigned char *romsig; | |
213 | ||
214 | /* The ioremap check is dangerous; limit what we run it on */ | |
215 | if (!is_geode() || geode_has_vsa2()) | |
216 | return 0; | |
217 | ||
218 | spin_lock_init(&ec_lock); | |
219 | ||
220 | romsig = ioremap(0xffffffc0, 16); | |
221 | if (!romsig) | |
222 | return 0; | |
223 | ||
224 | if (strncmp(romsig, "CL1 Q", 7)) | |
225 | goto unmap; | |
226 | if (strncmp(romsig+6, romsig+13, 3)) { | |
227 | printk(KERN_INFO "OLPC BIOS signature looks invalid. " | |
228 | "Assuming not OLPC\n"); | |
229 | goto unmap; | |
230 | } | |
231 | ||
232 | printk(KERN_INFO "OLPC board with OpenFirmware %.16s\n", romsig); | |
233 | olpc_platform_info.flags |= OLPC_F_PRESENT; | |
234 | ||
235 | /* get the platform revision */ | |
236 | platform_detect(); | |
237 | ||
238 | /* assume B1 and above models always have a DCON */ | |
239 | if (olpc_board_at_least(olpc_board(0xb1))) | |
240 | olpc_platform_info.flags |= OLPC_F_DCON; | |
241 | ||
242 | /* get the EC revision */ | |
243 | olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, | |
244 | (unsigned char *) &olpc_platform_info.ecver, 1); | |
245 | ||
246 | /* check to see if the VSA exists */ | |
247 | if (geode_has_vsa2()) | |
248 | olpc_platform_info.flags |= OLPC_F_VSA; | |
249 | ||
250 | printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", | |
251 | ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", | |
252 | olpc_platform_info.boardrev >> 4, | |
253 | olpc_platform_info.ecver); | |
254 | ||
255 | unmap: | |
256 | iounmap(romsig); | |
257 | return 0; | |
258 | } | |
259 | ||
260 | postcore_initcall(olpc_init); |