blob: eaf917dc30d488eb979188ffc53688719066f6ef [file] [log] [blame]
David Woodhousefb972872007-05-04 00:51:18 +04001/*
2 * Battery driver for One Laptop Per Child board.
3 *
David Woodhouse690e85a2010-08-09 18:13:09 +01004 * Copyright © 2006-2010 David Woodhouse <dwmw2@infradead.org>
David Woodhousefb972872007-05-04 00:51:18 +04005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
Andres Salomon04a820e2009-06-30 02:14:00 -040011#include <linux/kernel.h>
David Woodhousefb972872007-05-04 00:51:18 +040012#include <linux/module.h>
Andres Salomon144bbea2009-06-30 02:15:26 -040013#include <linux/types.h>
David Woodhousefb972872007-05-04 00:51:18 +040014#include <linux/err.h>
Andres Salomon144bbea2009-06-30 02:15:26 -040015#include <linux/device.h>
David Woodhousefb972872007-05-04 00:51:18 +040016#include <linux/platform_device.h>
17#include <linux/power_supply.h>
18#include <linux/jiffies.h>
19#include <linux/sched.h>
Andres Salomon3bf94282012-07-11 01:16:29 -070020#include <linux/olpc-ec.h>
David Woodhousefb972872007-05-04 00:51:18 +040021#include <asm/olpc.h>
22
23
24#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
25#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */
Andres Salomon75d88072008-05-14 16:20:38 -070026#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */
David Woodhousefb972872007-05-04 00:51:18 +040027#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */
28#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */
29#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */
30#define EC_BAT_SOC 0x16 /* uint8_t, percentage */
31#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */
32#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */
33#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */
34
35#define BAT_STAT_PRESENT 0x01
36#define BAT_STAT_FULL 0x02
37#define BAT_STAT_LOW 0x04
38#define BAT_STAT_DESTROY 0x08
39#define BAT_STAT_AC 0x10
40#define BAT_STAT_CHARGING 0x20
41#define BAT_STAT_DISCHARGING 0x40
Andres Salomon8f7e5792009-06-30 02:16:17 -040042#define BAT_STAT_TRICKLE 0x80
David Woodhousefb972872007-05-04 00:51:18 +040043
44#define BAT_ERR_INFOFAIL 0x02
45#define BAT_ERR_OVERVOLTAGE 0x04
46#define BAT_ERR_OVERTEMP 0x05
47#define BAT_ERR_GAUGESTOP 0x06
48#define BAT_ERR_OUT_OF_CONTROL 0x07
49#define BAT_ERR_ID_FAIL 0x09
50#define BAT_ERR_ACR_FAIL 0x10
51
52#define BAT_ADDR_MFR_TYPE 0x5F
53
54/*********************************************************************
55 * Power
56 *********************************************************************/
57
58static int olpc_ac_get_prop(struct power_supply *psy,
59 enum power_supply_property psp,
60 union power_supply_propval *val)
61{
62 int ret = 0;
63 uint8_t status;
64
65 switch (psp) {
66 case POWER_SUPPLY_PROP_ONLINE:
67 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
68 if (ret)
69 return ret;
70
71 val->intval = !!(status & BAT_STAT_AC);
72 break;
73 default:
74 ret = -EINVAL;
75 break;
76 }
77 return ret;
78}
79
80static enum power_supply_property olpc_ac_props[] = {
81 POWER_SUPPLY_PROP_ONLINE,
82};
83
84static struct power_supply olpc_ac = {
85 .name = "olpc-ac",
86 .type = POWER_SUPPLY_TYPE_MAINS,
87 .properties = olpc_ac_props,
88 .num_properties = ARRAY_SIZE(olpc_ac_props),
89 .get_property = olpc_ac_get_prop,
90};
91
David Woodhouse1ca5b9d2008-05-04 01:31:42 -040092static char bat_serial[17]; /* Ick */
93
Andres Salomonb2bd8a32008-05-02 13:41:59 -070094static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte)
95{
96 if (olpc_platform_info.ecver > 0x44) {
Andres Salomon8f7e5792009-06-30 02:16:17 -040097 if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
Andres Salomonb2bd8a32008-05-02 13:41:59 -070098 val->intval = POWER_SUPPLY_STATUS_CHARGING;
99 else if (ec_byte & BAT_STAT_DISCHARGING)
100 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
101 else if (ec_byte & BAT_STAT_FULL)
102 val->intval = POWER_SUPPLY_STATUS_FULL;
103 else /* er,... */
104 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
105 } else {
106 /* Older EC didn't report charge/discharge bits */
107 if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */
108 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
109 else if (ec_byte & BAT_STAT_FULL)
110 val->intval = POWER_SUPPLY_STATUS_FULL;
111 else /* Not _necessarily_ true but EC doesn't tell all yet */
112 val->intval = POWER_SUPPLY_STATUS_CHARGING;
113 }
114
115 return 0;
116}
117
118static int olpc_bat_get_health(union power_supply_propval *val)
119{
120 uint8_t ec_byte;
121 int ret;
122
123 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
124 if (ret)
125 return ret;
126
127 switch (ec_byte) {
128 case 0:
129 val->intval = POWER_SUPPLY_HEALTH_GOOD;
130 break;
131
132 case BAT_ERR_OVERTEMP:
133 val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
134 break;
135
136 case BAT_ERR_OVERVOLTAGE:
137 val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
138 break;
139
140 case BAT_ERR_INFOFAIL:
141 case BAT_ERR_OUT_OF_CONTROL:
142 case BAT_ERR_ID_FAIL:
143 case BAT_ERR_ACR_FAIL:
144 val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
145 break;
146
147 default:
148 /* Eep. We don't know this failure code */
149 ret = -EIO;
150 }
151
152 return ret;
153}
154
155static int olpc_bat_get_mfr(union power_supply_propval *val)
156{
157 uint8_t ec_byte;
158 int ret;
159
160 ec_byte = BAT_ADDR_MFR_TYPE;
161 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
162 if (ret)
163 return ret;
164
165 switch (ec_byte >> 4) {
166 case 1:
167 val->strval = "Gold Peak";
168 break;
169 case 2:
170 val->strval = "BYD";
171 break;
172 default:
173 val->strval = "Unknown";
174 break;
175 }
176
177 return ret;
178}
179
180static int olpc_bat_get_tech(union power_supply_propval *val)
181{
182 uint8_t ec_byte;
183 int ret;
184
185 ec_byte = BAT_ADDR_MFR_TYPE;
186 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
187 if (ret)
188 return ret;
189
190 switch (ec_byte & 0xf) {
191 case 1:
192 val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
193 break;
194 case 2:
195 val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
196 break;
197 default:
198 val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
199 break;
200 }
201
202 return ret;
203}
204
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100205static int olpc_bat_get_charge_full_design(union power_supply_propval *val)
206{
207 uint8_t ec_byte;
208 union power_supply_propval tech;
209 int ret, mfr;
210
211 ret = olpc_bat_get_tech(&tech);
212 if (ret)
213 return ret;
214
215 ec_byte = BAT_ADDR_MFR_TYPE;
216 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
217 if (ret)
218 return ret;
219
220 mfr = ec_byte >> 4;
221
222 switch (tech.intval) {
223 case POWER_SUPPLY_TECHNOLOGY_NiMH:
224 switch (mfr) {
225 case 1: /* Gold Peak */
226 val->intval = 3000000*.8;
227 break;
228 default:
229 return -EIO;
230 }
231 break;
232
233 case POWER_SUPPLY_TECHNOLOGY_LiFe:
234 switch (mfr) {
235 case 1: /* Gold Peak */
236 val->intval = 2800000;
237 break;
238 case 2: /* BYD */
239 val->intval = 3100000;
240 break;
241 default:
242 return -EIO;
243 }
244 break;
245
246 default:
247 return -EIO;
248 }
249
250 return ret;
251}
252
Sascha Silbe20fd9832010-12-10 23:05:20 +0100253static int olpc_bat_get_charge_now(union power_supply_propval *val)
254{
255 uint8_t soc;
256 union power_supply_propval full;
257 int ret;
258
259 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1);
260 if (ret)
261 return ret;
262
263 ret = olpc_bat_get_charge_full_design(&full);
264 if (ret)
265 return ret;
266
267 val->intval = soc * (full.intval / 100);
268 return 0;
269}
270
David Woodhousefb972872007-05-04 00:51:18 +0400271/*********************************************************************
272 * Battery properties
273 *********************************************************************/
274static int olpc_bat_get_property(struct power_supply *psy,
275 enum power_supply_property psp,
276 union power_supply_propval *val)
277{
278 int ret = 0;
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700279 __be16 ec_word;
David Woodhousefb972872007-05-04 00:51:18 +0400280 uint8_t ec_byte;
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700281 __be64 ser_buf;
David Woodhousefb972872007-05-04 00:51:18 +0400282
283 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1);
284 if (ret)
285 return ret;
286
287 /* Theoretically there's a race here -- the battery could be
288 removed immediately after we check whether it's present, and
289 then we query for some other property of the now-absent battery.
290 It doesn't matter though -- the EC will return the last-known
291 information, and it's as if we just ran that _little_ bit faster
292 and managed to read it out before the battery went away. */
Andres Salomon8f7e5792009-06-30 02:16:17 -0400293 if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) &&
294 psp != POWER_SUPPLY_PROP_PRESENT)
David Woodhousefb972872007-05-04 00:51:18 +0400295 return -ENODEV;
296
297 switch (psp) {
298 case POWER_SUPPLY_PROP_STATUS:
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700299 ret = olpc_bat_get_status(val, ec_byte);
300 if (ret)
301 return ret;
302 break;
Andres Salomonee8076e2009-07-02 09:45:18 -0400303 case POWER_SUPPLY_PROP_CHARGE_TYPE:
304 if (ec_byte & BAT_STAT_TRICKLE)
305 val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
306 else if (ec_byte & BAT_STAT_CHARGING)
307 val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
308 else
309 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
310 break;
David Woodhousefb972872007-05-04 00:51:18 +0400311 case POWER_SUPPLY_PROP_PRESENT:
Andres Salomon8f7e5792009-06-30 02:16:17 -0400312 val->intval = !!(ec_byte & (BAT_STAT_PRESENT |
313 BAT_STAT_TRICKLE));
David Woodhousefb972872007-05-04 00:51:18 +0400314 break;
315
316 case POWER_SUPPLY_PROP_HEALTH:
317 if (ec_byte & BAT_STAT_DESTROY)
318 val->intval = POWER_SUPPLY_HEALTH_DEAD;
319 else {
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700320 ret = olpc_bat_get_health(val);
David Woodhousefb972872007-05-04 00:51:18 +0400321 if (ret)
322 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400323 }
324 break;
325
326 case POWER_SUPPLY_PROP_MANUFACTURER:
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700327 ret = olpc_bat_get_mfr(val);
David Woodhousefb972872007-05-04 00:51:18 +0400328 if (ret)
329 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400330 break;
331 case POWER_SUPPLY_PROP_TECHNOLOGY:
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700332 ret = olpc_bat_get_tech(val);
David Woodhousefb972872007-05-04 00:51:18 +0400333 if (ret)
334 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400335 break;
336 case POWER_SUPPLY_PROP_VOLTAGE_AVG:
Sascha Silbe22fadd72010-12-10 23:05:21 +0100337 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
David Woodhousefb972872007-05-04 00:51:18 +0400338 ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2);
339 if (ret)
340 return ret;
341
Richard A. Smith7cfbb292010-09-25 19:19:26 +0100342 val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32;
David Woodhousefb972872007-05-04 00:51:18 +0400343 break;
344 case POWER_SUPPLY_PROP_CURRENT_AVG:
Sascha Silbe22fadd72010-12-10 23:05:21 +0100345 case POWER_SUPPLY_PROP_CURRENT_NOW:
David Woodhousefb972872007-05-04 00:51:18 +0400346 ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2);
347 if (ret)
348 return ret;
349
Richard A. Smith7cfbb292010-09-25 19:19:26 +0100350 val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120;
David Woodhousefb972872007-05-04 00:51:18 +0400351 break;
352 case POWER_SUPPLY_PROP_CAPACITY:
353 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
354 if (ret)
355 return ret;
356 val->intval = ec_byte;
357 break;
Andres Salomonb294a292009-06-30 02:13:01 -0400358 case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
359 if (ec_byte & BAT_STAT_FULL)
360 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
361 else if (ec_byte & BAT_STAT_LOW)
362 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
363 else
364 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
365 break;
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100366 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
367 ret = olpc_bat_get_charge_full_design(val);
368 if (ret)
369 return ret;
370 break;
Sascha Silbe20fd9832010-12-10 23:05:20 +0100371 case POWER_SUPPLY_PROP_CHARGE_NOW:
372 ret = olpc_bat_get_charge_now(val);
373 if (ret)
374 return ret;
375 break;
David Woodhousefb972872007-05-04 00:51:18 +0400376 case POWER_SUPPLY_PROP_TEMP:
377 ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2);
378 if (ret)
379 return ret;
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700380
Richard A. Smith7cfbb292010-09-25 19:19:26 +0100381 val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256;
David Woodhousefb972872007-05-04 00:51:18 +0400382 break;
383 case POWER_SUPPLY_PROP_TEMP_AMBIENT:
384 ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
385 if (ret)
386 return ret;
387
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700388 val->intval = (int)be16_to_cpu(ec_word) * 100 / 256;
David Woodhousefb972872007-05-04 00:51:18 +0400389 break;
Andres Salomon8e552c32008-05-12 21:46:29 -0400390 case POWER_SUPPLY_PROP_CHARGE_COUNTER:
391 ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
392 if (ret)
393 return ret;
394
Richard A. Smith7cfbb292010-09-25 19:19:26 +0100395 val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15;
Andres Salomon8e552c32008-05-12 21:46:29 -0400396 break;
David Woodhouse1ca5b9d2008-05-04 01:31:42 -0400397 case POWER_SUPPLY_PROP_SERIAL_NUMBER:
398 ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
399 if (ret)
400 return ret;
401
402 sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
403 val->strval = bat_serial;
404 break;
David Woodhousefb972872007-05-04 00:51:18 +0400405 default:
406 ret = -EINVAL;
407 break;
408 }
409
410 return ret;
411}
412
Daniel Drakec566d292010-12-29 19:12:01 +0000413static enum power_supply_property olpc_xo1_bat_props[] = {
David Woodhousefb972872007-05-04 00:51:18 +0400414 POWER_SUPPLY_PROP_STATUS,
Andres Salomonee8076e2009-07-02 09:45:18 -0400415 POWER_SUPPLY_PROP_CHARGE_TYPE,
David Woodhousefb972872007-05-04 00:51:18 +0400416 POWER_SUPPLY_PROP_PRESENT,
417 POWER_SUPPLY_PROP_HEALTH,
418 POWER_SUPPLY_PROP_TECHNOLOGY,
419 POWER_SUPPLY_PROP_VOLTAGE_AVG,
Sascha Silbe22fadd72010-12-10 23:05:21 +0100420 POWER_SUPPLY_PROP_VOLTAGE_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400421 POWER_SUPPLY_PROP_CURRENT_AVG,
Sascha Silbe22fadd72010-12-10 23:05:21 +0100422 POWER_SUPPLY_PROP_CURRENT_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400423 POWER_SUPPLY_PROP_CAPACITY,
Andres Salomonb294a292009-06-30 02:13:01 -0400424 POWER_SUPPLY_PROP_CAPACITY_LEVEL,
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100425 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
Sascha Silbe20fd9832010-12-10 23:05:20 +0100426 POWER_SUPPLY_PROP_CHARGE_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400427 POWER_SUPPLY_PROP_TEMP,
428 POWER_SUPPLY_PROP_TEMP_AMBIENT,
429 POWER_SUPPLY_PROP_MANUFACTURER,
David Woodhouse1ca5b9d2008-05-04 01:31:42 -0400430 POWER_SUPPLY_PROP_SERIAL_NUMBER,
Andres Salomon8e552c32008-05-12 21:46:29 -0400431 POWER_SUPPLY_PROP_CHARGE_COUNTER,
David Woodhousefb972872007-05-04 00:51:18 +0400432};
433
Daniel Drakec566d292010-12-29 19:12:01 +0000434/* XO-1.5 does not have ambient temperature property */
435static enum power_supply_property olpc_xo15_bat_props[] = {
436 POWER_SUPPLY_PROP_STATUS,
437 POWER_SUPPLY_PROP_CHARGE_TYPE,
438 POWER_SUPPLY_PROP_PRESENT,
439 POWER_SUPPLY_PROP_HEALTH,
440 POWER_SUPPLY_PROP_TECHNOLOGY,
441 POWER_SUPPLY_PROP_VOLTAGE_AVG,
Sascha Silbebf542a42011-01-12 23:23:23 +0100442 POWER_SUPPLY_PROP_VOLTAGE_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000443 POWER_SUPPLY_PROP_CURRENT_AVG,
Sascha Silbebf542a42011-01-12 23:23:23 +0100444 POWER_SUPPLY_PROP_CURRENT_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000445 POWER_SUPPLY_PROP_CAPACITY,
446 POWER_SUPPLY_PROP_CAPACITY_LEVEL,
Sascha Silbebf542a42011-01-12 23:23:23 +0100447 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
448 POWER_SUPPLY_PROP_CHARGE_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000449 POWER_SUPPLY_PROP_TEMP,
450 POWER_SUPPLY_PROP_MANUFACTURER,
451 POWER_SUPPLY_PROP_SERIAL_NUMBER,
452 POWER_SUPPLY_PROP_CHARGE_COUNTER,
453};
454
Andres Salomond7eb9e32008-05-02 13:41:58 -0700455/* EEPROM reading goes completely around the power_supply API, sadly */
456
457#define EEPROM_START 0x20
458#define EEPROM_END 0x80
459#define EEPROM_SIZE (EEPROM_END - EEPROM_START)
460
Chris Wright2c3c8be2010-05-12 18:28:57 -0700461static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
Andres Salomond7eb9e32008-05-02 13:41:58 -0700462 struct bin_attribute *attr, char *buf, loff_t off, size_t count)
463{
464 uint8_t ec_byte;
Andres Salomon04a820e2009-06-30 02:14:00 -0400465 int ret;
466 int i;
Andres Salomond7eb9e32008-05-02 13:41:58 -0700467
468 if (off >= EEPROM_SIZE)
469 return 0;
470 if (off + count > EEPROM_SIZE)
471 count = EEPROM_SIZE - off;
472
Andres Salomon04a820e2009-06-30 02:14:00 -0400473 for (i = 0; i < count; i++) {
474 ec_byte = EEPROM_START + off + i;
475 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700476 if (ret) {
Andres Salomon04a820e2009-06-30 02:14:00 -0400477 pr_err("olpc-battery: "
478 "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n",
479 ec_byte, ret);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700480 return -EIO;
481 }
482 }
483
484 return count;
485}
486
487static struct bin_attribute olpc_bat_eeprom = {
488 .attr = {
489 .name = "eeprom",
490 .mode = S_IRUGO,
Andres Salomond7eb9e32008-05-02 13:41:58 -0700491 },
492 .size = 0,
493 .read = olpc_bat_eeprom_read,
494};
495
Andres Salomon144bbea2009-06-30 02:15:26 -0400496/* Allow userspace to see the specific error value pulled from the EC */
497
498static ssize_t olpc_bat_error_read(struct device *dev,
499 struct device_attribute *attr, char *buf)
500{
501 uint8_t ec_byte;
502 ssize_t ret;
503
504 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
505 if (ret < 0)
506 return ret;
507
508 return sprintf(buf, "%d\n", ec_byte);
509}
510
511static struct device_attribute olpc_bat_error = {
512 .attr = {
513 .name = "error",
514 .mode = S_IRUGO,
515 },
516 .show = olpc_bat_error_read,
517};
518
David Woodhousefb972872007-05-04 00:51:18 +0400519/*********************************************************************
520 * Initialisation
521 *********************************************************************/
522
David Woodhousefb972872007-05-04 00:51:18 +0400523static struct power_supply olpc_bat = {
Daniel Drakec3503fd2011-08-10 21:45:36 +0100524 .name = "olpc-battery",
David Woodhousefb972872007-05-04 00:51:18 +0400525 .get_property = olpc_bat_get_property,
526 .use_for_apm = 1,
527};
528
Daniel Drakecae659a2011-08-10 21:46:02 +0100529static int olpc_battery_suspend(struct platform_device *pdev,
530 pm_message_t state)
531{
532 if (device_may_wakeup(olpc_ac.dev))
533 olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
534 else
535 olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
536
537 if (device_may_wakeup(olpc_bat.dev))
538 olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
539 | EC_SCI_SRC_BATERR);
540 else
541 olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
542 | EC_SCI_SRC_BATERR);
543
544 return 0;
545}
546
Daniel Drakec3503fd2011-08-10 21:45:36 +0100547static int __devinit olpc_battery_probe(struct platform_device *pdev)
David Woodhousefb972872007-05-04 00:51:18 +0400548{
Daniel Drakec3503fd2011-08-10 21:45:36 +0100549 int ret;
David Woodhousefb972872007-05-04 00:51:18 +0400550 uint8_t status;
551
Andres Salomon484d6d52008-05-02 13:41:59 -0700552 /*
553 * We've seen a number of EC protocol changes; this driver requires
554 * the latest EC protocol, supported by 0x44 and above.
555 */
556 if (olpc_platform_info.ecver < 0x44) {
557 printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
558 "battery driver.\n", olpc_platform_info.ecver);
David Woodhousefb972872007-05-04 00:51:18 +0400559 return -ENXIO;
560 }
561
562 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
563 if (ret)
564 return ret;
565
566 /* Ignore the status. It doesn't actually matter */
567
Daniel Drakec3503fd2011-08-10 21:45:36 +0100568 ret = power_supply_register(&pdev->dev, &olpc_ac);
David Woodhousefb972872007-05-04 00:51:18 +0400569 if (ret)
Daniel Drakec3503fd2011-08-10 21:45:36 +0100570 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400571
Daniel Drakec566d292010-12-29 19:12:01 +0000572 if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */
573 olpc_bat.properties = olpc_xo15_bat_props;
574 olpc_bat.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
575 } else { /* XO-1 */
576 olpc_bat.properties = olpc_xo1_bat_props;
577 olpc_bat.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
578 }
David Woodhousefb972872007-05-04 00:51:18 +0400579
Daniel Drakec3503fd2011-08-10 21:45:36 +0100580 ret = power_supply_register(&pdev->dev, &olpc_bat);
David Woodhousefb972872007-05-04 00:51:18 +0400581 if (ret)
582 goto battery_failed;
583
Andres Salomond7eb9e32008-05-02 13:41:58 -0700584 ret = device_create_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
585 if (ret)
586 goto eeprom_failed;
587
Andres Salomon144bbea2009-06-30 02:15:26 -0400588 ret = device_create_file(olpc_bat.dev, &olpc_bat_error);
589 if (ret)
590 goto error_failed;
591
Daniel Drakecae659a2011-08-10 21:46:02 +0100592 if (olpc_ec_wakeup_available()) {
593 device_set_wakeup_capable(olpc_ac.dev, true);
594 device_set_wakeup_capable(olpc_bat.dev, true);
595 }
596
Daniel Drakec3503fd2011-08-10 21:45:36 +0100597 return 0;
David Woodhousefb972872007-05-04 00:51:18 +0400598
Andres Salomon144bbea2009-06-30 02:15:26 -0400599error_failed:
600 device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700601eeprom_failed:
602 power_supply_unregister(&olpc_bat);
David Woodhousefb972872007-05-04 00:51:18 +0400603battery_failed:
604 power_supply_unregister(&olpc_ac);
David Woodhousefb972872007-05-04 00:51:18 +0400605 return ret;
606}
607
Daniel Drakec3503fd2011-08-10 21:45:36 +0100608static int __devexit olpc_battery_remove(struct platform_device *pdev)
David Woodhousefb972872007-05-04 00:51:18 +0400609{
Andres Salomon144bbea2009-06-30 02:15:26 -0400610 device_remove_file(olpc_bat.dev, &olpc_bat_error);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700611 device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
David Woodhousefb972872007-05-04 00:51:18 +0400612 power_supply_unregister(&olpc_bat);
613 power_supply_unregister(&olpc_ac);
Daniel Drakec3503fd2011-08-10 21:45:36 +0100614 return 0;
David Woodhousefb972872007-05-04 00:51:18 +0400615}
616
Daniel Drakec3503fd2011-08-10 21:45:36 +0100617static const struct of_device_id olpc_battery_ids[] __devinitconst = {
618 { .compatible = "olpc,xo1-battery" },
619 {}
620};
621MODULE_DEVICE_TABLE(of, olpc_battery_ids);
622
Anton Vorontsov5519d002011-11-24 22:49:07 +0400623static struct platform_driver olpc_battery_driver = {
Daniel Drakec3503fd2011-08-10 21:45:36 +0100624 .driver = {
625 .name = "olpc-battery",
626 .owner = THIS_MODULE,
627 .of_match_table = olpc_battery_ids,
628 },
629 .probe = olpc_battery_probe,
630 .remove = __devexit_p(olpc_battery_remove),
Daniel Drakecae659a2011-08-10 21:46:02 +0100631 .suspend = olpc_battery_suspend,
Daniel Drakec3503fd2011-08-10 21:45:36 +0100632};
633
Axel Lin300bac72011-11-26 12:01:10 +0800634module_platform_driver(olpc_battery_driver);
David Woodhousefb972872007-05-04 00:51:18 +0400635
636MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
637MODULE_LICENSE("GPL");
638MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");