aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/olpc.c
diff options
context:
space:
mode:
authorAndres Salomon <dilinger@queued.net>2008-04-29 03:59:53 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2008-04-29 11:06:07 -0400
commit3ef0e1f8cad0a851b3dbf91802b14af7dd780352 (patch)
tree805450fdb2c056e7ba61ac3c26529cccd09e952f /arch/x86/kernel/olpc.c
parent2f9b12a31fcb738ea8c9eb0d4ddf906c6f1d696c (diff)
x86: olpc: add One Laptop Per Child architecture support
This adds support for OLPC XO hardware. Open Firmware on XOs don't contain the VSA, so it is necessary to emulate the PCI BARs in the kernel. This also adds functionality for running EC commands, and a CONFIG_OLPC. A number of OLPC drivers depend upon CONFIG_OLPC. olpc_ec_timeout is a hack to work around Embedded Controller bugs. [akpm@linux-foundation.org: build fix] [akpm@linux-foundation.org: geode_has_vsa build fix] [akpm@linux-foundation.org: olpc_register_battery_callback doesn't exist] Signed-off-by: Andres Salomon <dilinger@debian.org> Acked-by: Ingo Molnar <mingo@elte.hu> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Andi Kleen <ak@suse.de> Cc: Jordan Crouse <jordan.crouse@amd.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'arch/x86/kernel/olpc.c')
-rw-r--r--arch/x86/kernel/olpc.c260
1 files changed, 260 insertions, 0 deletions
diff --git a/arch/x86/kernel/olpc.c b/arch/x86/kernel/olpc.c
new file mode 100644
index 000000000000..3e6672274807
--- /dev/null
+++ b/arch/x86/kernel/olpc.c
@@ -0,0 +1,260 @@
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
27struct olpc_platform_t olpc_platform_info;
28EXPORT_SYMBOL_GPL(olpc_platform_info);
29
30static 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 */
36static int ec_timeout = EC_BASE_TIMEOUT;
37
38static 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
55static inline unsigned int ibf_status(unsigned int port)
56{
57 return !!(inb(port) & 0x02);
58}
59
60static 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))
66static 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))
86static 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 */
112int 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
136restart:
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;
183err:
184 spin_unlock_irqrestore(&ec_lock, flags);
185 return ret;
186}
187EXPORT_SYMBOL_GPL(olpc_ec_cmd);
188
189#ifdef CONFIG_OPEN_FIRMWARE
190static void __init platform_detect(void)
191{
192 size_t propsize;
193 u32 rev;
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");
198 rev = 0;
199 }
200 olpc_platform_info.boardrev = be32_to_cpu(rev);
201}
202#else
203static void __init platform_detect(void)
204{
205 /* stopgap until OFW support is added to the kernel */
206 olpc_platform_info.boardrev = be32_to_cpu(0xc2);
207}
208#endif
209
210static 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
255unmap:
256 iounmap(romsig);
257 return 0;
258}
259
260postcore_initcall(olpc_init);