blob: 7720e4c2ac0bee899432ab3e54e724b2b76c2c79 [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>
Randy Dunlapac316722018-06-19 22:47:28 -070013#include <linux/mod_devicetable.h>
Andres Salomon144bbea2009-06-30 02:15:26 -040014#include <linux/types.h>
David Woodhousefb972872007-05-04 00:51:18 +040015#include <linux/err.h>
Andres Salomon144bbea2009-06-30 02:15:26 -040016#include <linux/device.h>
Lubomir Rintelf7a228e2019-04-18 16:46:50 +020017#include <linux/of.h>
David Woodhousefb972872007-05-04 00:51:18 +040018#include <linux/platform_device.h>
19#include <linux/power_supply.h>
20#include <linux/jiffies.h>
21#include <linux/sched.h>
Andres Salomon3bf94282012-07-11 01:16:29 -070022#include <linux/olpc-ec.h>
David Woodhousefb972872007-05-04 00:51:18 +040023#include <asm/olpc.h>
24
25
26#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
27#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */
Andres Salomon75d88072008-05-14 16:20:38 -070028#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */
David Woodhousefb972872007-05-04 00:51:18 +040029#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */
30#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */
31#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */
32#define EC_BAT_SOC 0x16 /* uint8_t, percentage */
33#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */
34#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */
35#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */
36
37#define BAT_STAT_PRESENT 0x01
38#define BAT_STAT_FULL 0x02
39#define BAT_STAT_LOW 0x04
40#define BAT_STAT_DESTROY 0x08
41#define BAT_STAT_AC 0x10
42#define BAT_STAT_CHARGING 0x20
43#define BAT_STAT_DISCHARGING 0x40
Andres Salomon8f7e5792009-06-30 02:16:17 -040044#define BAT_STAT_TRICKLE 0x80
David Woodhousefb972872007-05-04 00:51:18 +040045
46#define BAT_ERR_INFOFAIL 0x02
47#define BAT_ERR_OVERVOLTAGE 0x04
48#define BAT_ERR_OVERTEMP 0x05
49#define BAT_ERR_GAUGESTOP 0x06
50#define BAT_ERR_OUT_OF_CONTROL 0x07
51#define BAT_ERR_ID_FAIL 0x09
52#define BAT_ERR_ACR_FAIL 0x10
53
54#define BAT_ADDR_MFR_TYPE 0x5F
55
Lubomir Rintel33554d82019-04-18 16:46:51 +020056struct olpc_battery_data {
57 struct power_supply *olpc_ac;
58 struct power_supply *olpc_bat;
59 char bat_serial[17];
Lubomir Rintel8ecefda2019-04-18 16:46:53 +020060 bool new_proto;
Lubomir Rintel76311b92019-04-18 16:46:54 +020061 bool little_endian;
Lubomir Rintel33554d82019-04-18 16:46:51 +020062};
63
David Woodhousefb972872007-05-04 00:51:18 +040064/*********************************************************************
65 * Power
66 *********************************************************************/
67
68static int olpc_ac_get_prop(struct power_supply *psy,
69 enum power_supply_property psp,
70 union power_supply_propval *val)
71{
72 int ret = 0;
73 uint8_t status;
74
75 switch (psp) {
76 case POWER_SUPPLY_PROP_ONLINE:
77 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
78 if (ret)
79 return ret;
80
81 val->intval = !!(status & BAT_STAT_AC);
82 break;
83 default:
84 ret = -EINVAL;
85 break;
86 }
87 return ret;
88}
89
90static enum power_supply_property olpc_ac_props[] = {
91 POWER_SUPPLY_PROP_ONLINE,
92};
93
Krzysztof Kozlowski297d7162015-03-12 08:44:11 +010094static const struct power_supply_desc olpc_ac_desc = {
David Woodhousefb972872007-05-04 00:51:18 +040095 .name = "olpc-ac",
96 .type = POWER_SUPPLY_TYPE_MAINS,
97 .properties = olpc_ac_props,
98 .num_properties = ARRAY_SIZE(olpc_ac_props),
99 .get_property = olpc_ac_get_prop,
100};
101
Lubomir Rintel33554d82019-04-18 16:46:51 +0200102static int olpc_bat_get_status(struct olpc_battery_data *data,
103 union power_supply_propval *val, uint8_t ec_byte)
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700104{
Lubomir Rintel8ecefda2019-04-18 16:46:53 +0200105 if (data->new_proto) {
Andres Salomon8f7e5792009-06-30 02:16:17 -0400106 if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700107 val->intval = POWER_SUPPLY_STATUS_CHARGING;
108 else if (ec_byte & BAT_STAT_DISCHARGING)
109 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
110 else if (ec_byte & BAT_STAT_FULL)
111 val->intval = POWER_SUPPLY_STATUS_FULL;
112 else /* er,... */
113 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
114 } else {
115 /* Older EC didn't report charge/discharge bits */
116 if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */
117 val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
118 else if (ec_byte & BAT_STAT_FULL)
119 val->intval = POWER_SUPPLY_STATUS_FULL;
120 else /* Not _necessarily_ true but EC doesn't tell all yet */
121 val->intval = POWER_SUPPLY_STATUS_CHARGING;
122 }
123
124 return 0;
125}
126
127static int olpc_bat_get_health(union power_supply_propval *val)
128{
129 uint8_t ec_byte;
130 int ret;
131
132 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
133 if (ret)
134 return ret;
135
136 switch (ec_byte) {
137 case 0:
138 val->intval = POWER_SUPPLY_HEALTH_GOOD;
139 break;
140
141 case BAT_ERR_OVERTEMP:
142 val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
143 break;
144
145 case BAT_ERR_OVERVOLTAGE:
146 val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
147 break;
148
149 case BAT_ERR_INFOFAIL:
150 case BAT_ERR_OUT_OF_CONTROL:
151 case BAT_ERR_ID_FAIL:
152 case BAT_ERR_ACR_FAIL:
153 val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
154 break;
155
156 default:
157 /* Eep. We don't know this failure code */
158 ret = -EIO;
159 }
160
161 return ret;
162}
163
164static int olpc_bat_get_mfr(union power_supply_propval *val)
165{
166 uint8_t ec_byte;
167 int ret;
168
169 ec_byte = BAT_ADDR_MFR_TYPE;
170 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
171 if (ret)
172 return ret;
173
174 switch (ec_byte >> 4) {
175 case 1:
176 val->strval = "Gold Peak";
177 break;
178 case 2:
179 val->strval = "BYD";
180 break;
181 default:
182 val->strval = "Unknown";
183 break;
184 }
185
186 return ret;
187}
188
189static int olpc_bat_get_tech(union power_supply_propval *val)
190{
191 uint8_t ec_byte;
192 int ret;
193
194 ec_byte = BAT_ADDR_MFR_TYPE;
195 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
196 if (ret)
197 return ret;
198
199 switch (ec_byte & 0xf) {
200 case 1:
201 val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
202 break;
203 case 2:
204 val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
205 break;
206 default:
207 val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
208 break;
209 }
210
211 return ret;
212}
213
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100214static int olpc_bat_get_charge_full_design(union power_supply_propval *val)
215{
216 uint8_t ec_byte;
217 union power_supply_propval tech;
218 int ret, mfr;
219
220 ret = olpc_bat_get_tech(&tech);
221 if (ret)
222 return ret;
223
224 ec_byte = BAT_ADDR_MFR_TYPE;
225 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
226 if (ret)
227 return ret;
228
229 mfr = ec_byte >> 4;
230
231 switch (tech.intval) {
232 case POWER_SUPPLY_TECHNOLOGY_NiMH:
233 switch (mfr) {
234 case 1: /* Gold Peak */
235 val->intval = 3000000*.8;
236 break;
237 default:
238 return -EIO;
239 }
240 break;
241
242 case POWER_SUPPLY_TECHNOLOGY_LiFe:
243 switch (mfr) {
Richard A. Smithecc2edd2012-07-15 22:43:51 +0100244 case 1: /* Gold Peak, fall through */
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100245 case 2: /* BYD */
Richard A. Smithecc2edd2012-07-15 22:43:51 +0100246 val->intval = 2800000;
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100247 break;
248 default:
249 return -EIO;
250 }
251 break;
252
253 default:
254 return -EIO;
255 }
256
257 return ret;
258}
259
Sascha Silbe20fd9832010-12-10 23:05:20 +0100260static int olpc_bat_get_charge_now(union power_supply_propval *val)
261{
262 uint8_t soc;
263 union power_supply_propval full;
264 int ret;
265
266 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1);
267 if (ret)
268 return ret;
269
270 ret = olpc_bat_get_charge_full_design(&full);
271 if (ret)
272 return ret;
273
274 val->intval = soc * (full.intval / 100);
275 return 0;
276}
277
Richard A. Smith5619d0b2012-07-15 22:43:25 +0100278static int olpc_bat_get_voltage_max_design(union power_supply_propval *val)
279{
280 uint8_t ec_byte;
281 union power_supply_propval tech;
282 int mfr;
283 int ret;
284
285 ret = olpc_bat_get_tech(&tech);
286 if (ret)
287 return ret;
288
289 ec_byte = BAT_ADDR_MFR_TYPE;
290 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
291 if (ret)
292 return ret;
293
294 mfr = ec_byte >> 4;
295
296 switch (tech.intval) {
297 case POWER_SUPPLY_TECHNOLOGY_NiMH:
298 switch (mfr) {
299 case 1: /* Gold Peak */
300 val->intval = 6000000;
301 break;
302 default:
303 return -EIO;
304 }
305 break;
306
307 case POWER_SUPPLY_TECHNOLOGY_LiFe:
308 switch (mfr) {
309 case 1: /* Gold Peak */
310 val->intval = 6400000;
311 break;
312 case 2: /* BYD */
313 val->intval = 6500000;
314 break;
315 default:
316 return -EIO;
317 }
318 break;
319
320 default:
321 return -EIO;
322 }
323
324 return ret;
325}
326
Lubomir Rintel76311b92019-04-18 16:46:54 +0200327static u16 ecword_to_cpu(struct olpc_battery_data *data, u16 ec_word)
328{
329 if (data->little_endian)
Lubomir Rintelbaf59642019-05-11 10:56:14 +0200330 return le16_to_cpu((__force __le16)ec_word);
Lubomir Rintel76311b92019-04-18 16:46:54 +0200331 else
Lubomir Rintelbaf59642019-05-11 10:56:14 +0200332 return be16_to_cpu((__force __be16)ec_word);
Lubomir Rintel76311b92019-04-18 16:46:54 +0200333}
334
David Woodhousefb972872007-05-04 00:51:18 +0400335/*********************************************************************
336 * Battery properties
337 *********************************************************************/
338static int olpc_bat_get_property(struct power_supply *psy,
339 enum power_supply_property psp,
340 union power_supply_propval *val)
341{
Lubomir Rintel33554d82019-04-18 16:46:51 +0200342 struct olpc_battery_data *data = power_supply_get_drvdata(psy);
David Woodhousefb972872007-05-04 00:51:18 +0400343 int ret = 0;
Lubomir Rintelbaf59642019-05-11 10:56:14 +0200344 u16 ec_word;
David Woodhousefb972872007-05-04 00:51:18 +0400345 uint8_t ec_byte;
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700346 __be64 ser_buf;
David Woodhousefb972872007-05-04 00:51:18 +0400347
348 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1);
349 if (ret)
350 return ret;
351
352 /* Theoretically there's a race here -- the battery could be
353 removed immediately after we check whether it's present, and
354 then we query for some other property of the now-absent battery.
355 It doesn't matter though -- the EC will return the last-known
356 information, and it's as if we just ran that _little_ bit faster
357 and managed to read it out before the battery went away. */
Andres Salomon8f7e5792009-06-30 02:16:17 -0400358 if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) &&
359 psp != POWER_SUPPLY_PROP_PRESENT)
David Woodhousefb972872007-05-04 00:51:18 +0400360 return -ENODEV;
361
362 switch (psp) {
363 case POWER_SUPPLY_PROP_STATUS:
Lubomir Rintel33554d82019-04-18 16:46:51 +0200364 ret = olpc_bat_get_status(data, val, ec_byte);
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700365 if (ret)
366 return ret;
367 break;
Andres Salomonee8076e2009-07-02 09:45:18 -0400368 case POWER_SUPPLY_PROP_CHARGE_TYPE:
369 if (ec_byte & BAT_STAT_TRICKLE)
370 val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
371 else if (ec_byte & BAT_STAT_CHARGING)
372 val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
373 else
374 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
375 break;
David Woodhousefb972872007-05-04 00:51:18 +0400376 case POWER_SUPPLY_PROP_PRESENT:
Andres Salomon8f7e5792009-06-30 02:16:17 -0400377 val->intval = !!(ec_byte & (BAT_STAT_PRESENT |
378 BAT_STAT_TRICKLE));
David Woodhousefb972872007-05-04 00:51:18 +0400379 break;
380
381 case POWER_SUPPLY_PROP_HEALTH:
382 if (ec_byte & BAT_STAT_DESTROY)
383 val->intval = POWER_SUPPLY_HEALTH_DEAD;
384 else {
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700385 ret = olpc_bat_get_health(val);
David Woodhousefb972872007-05-04 00:51:18 +0400386 if (ret)
387 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400388 }
389 break;
390
391 case POWER_SUPPLY_PROP_MANUFACTURER:
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700392 ret = olpc_bat_get_mfr(val);
David Woodhousefb972872007-05-04 00:51:18 +0400393 if (ret)
394 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400395 break;
396 case POWER_SUPPLY_PROP_TECHNOLOGY:
Andres Salomonb2bd8a32008-05-02 13:41:59 -0700397 ret = olpc_bat_get_tech(val);
David Woodhousefb972872007-05-04 00:51:18 +0400398 if (ret)
399 return ret;
David Woodhousefb972872007-05-04 00:51:18 +0400400 break;
401 case POWER_SUPPLY_PROP_VOLTAGE_AVG:
Sascha Silbe22fadd72010-12-10 23:05:21 +0100402 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
David Woodhousefb972872007-05-04 00:51:18 +0400403 ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2);
404 if (ret)
405 return ret;
406
Lubomir Rintel76311b92019-04-18 16:46:54 +0200407 val->intval = ecword_to_cpu(data, ec_word) * 9760L / 32;
David Woodhousefb972872007-05-04 00:51:18 +0400408 break;
409 case POWER_SUPPLY_PROP_CURRENT_AVG:
Sascha Silbe22fadd72010-12-10 23:05:21 +0100410 case POWER_SUPPLY_PROP_CURRENT_NOW:
David Woodhousefb972872007-05-04 00:51:18 +0400411 ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2);
412 if (ret)
413 return ret;
414
Lubomir Rintel76311b92019-04-18 16:46:54 +0200415 val->intval = ecword_to_cpu(data, ec_word) * 15625L / 120;
David Woodhousefb972872007-05-04 00:51:18 +0400416 break;
417 case POWER_SUPPLY_PROP_CAPACITY:
418 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
419 if (ret)
420 return ret;
421 val->intval = ec_byte;
422 break;
Andres Salomonb294a292009-06-30 02:13:01 -0400423 case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
424 if (ec_byte & BAT_STAT_FULL)
425 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
426 else if (ec_byte & BAT_STAT_LOW)
427 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
428 else
429 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
430 break;
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100431 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
432 ret = olpc_bat_get_charge_full_design(val);
433 if (ret)
434 return ret;
435 break;
Sascha Silbe20fd9832010-12-10 23:05:20 +0100436 case POWER_SUPPLY_PROP_CHARGE_NOW:
437 ret = olpc_bat_get_charge_now(val);
438 if (ret)
439 return ret;
440 break;
David Woodhousefb972872007-05-04 00:51:18 +0400441 case POWER_SUPPLY_PROP_TEMP:
442 ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2);
443 if (ret)
444 return ret;
Harvey Harrison8e9c7712008-10-15 22:01:23 -0700445
Lubomir Rintel76311b92019-04-18 16:46:54 +0200446 val->intval = ecword_to_cpu(data, ec_word) * 10 / 256;
David Woodhousefb972872007-05-04 00:51:18 +0400447 break;
448 case POWER_SUPPLY_PROP_TEMP_AMBIENT:
449 ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
450 if (ret)
451 return ret;
452
Lubomir Rintel76311b92019-04-18 16:46:54 +0200453 val->intval = (int)ecword_to_cpu(data, ec_word) * 10 / 256;
David Woodhousefb972872007-05-04 00:51:18 +0400454 break;
Andres Salomon8e552c32008-05-12 21:46:29 -0400455 case POWER_SUPPLY_PROP_CHARGE_COUNTER:
456 ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
457 if (ret)
458 return ret;
459
Lubomir Rintel76311b92019-04-18 16:46:54 +0200460 val->intval = ecword_to_cpu(data, ec_word) * 6250 / 15;
Andres Salomon8e552c32008-05-12 21:46:29 -0400461 break;
David Woodhouse1ca5b9d2008-05-04 01:31:42 -0400462 case POWER_SUPPLY_PROP_SERIAL_NUMBER:
463 ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
464 if (ret)
465 return ret;
466
Lubomir Rintel33554d82019-04-18 16:46:51 +0200467 sprintf(data->bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
468 val->strval = data->bat_serial;
David Woodhouse1ca5b9d2008-05-04 01:31:42 -0400469 break;
Richard A. Smith5619d0b2012-07-15 22:43:25 +0100470 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
471 ret = olpc_bat_get_voltage_max_design(val);
472 if (ret)
473 return ret;
474 break;
David Woodhousefb972872007-05-04 00:51:18 +0400475 default:
476 ret = -EINVAL;
477 break;
478 }
479
480 return ret;
481}
482
Daniel Drakec566d292010-12-29 19:12:01 +0000483static enum power_supply_property olpc_xo1_bat_props[] = {
David Woodhousefb972872007-05-04 00:51:18 +0400484 POWER_SUPPLY_PROP_STATUS,
Andres Salomonee8076e2009-07-02 09:45:18 -0400485 POWER_SUPPLY_PROP_CHARGE_TYPE,
David Woodhousefb972872007-05-04 00:51:18 +0400486 POWER_SUPPLY_PROP_PRESENT,
487 POWER_SUPPLY_PROP_HEALTH,
488 POWER_SUPPLY_PROP_TECHNOLOGY,
489 POWER_SUPPLY_PROP_VOLTAGE_AVG,
Sascha Silbe22fadd72010-12-10 23:05:21 +0100490 POWER_SUPPLY_PROP_VOLTAGE_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400491 POWER_SUPPLY_PROP_CURRENT_AVG,
Sascha Silbe22fadd72010-12-10 23:05:21 +0100492 POWER_SUPPLY_PROP_CURRENT_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400493 POWER_SUPPLY_PROP_CAPACITY,
Andres Salomonb294a292009-06-30 02:13:01 -0400494 POWER_SUPPLY_PROP_CAPACITY_LEVEL,
Sascha Silbeb202a5e2010-12-10 23:05:19 +0100495 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
Sascha Silbe20fd9832010-12-10 23:05:20 +0100496 POWER_SUPPLY_PROP_CHARGE_NOW,
David Woodhousefb972872007-05-04 00:51:18 +0400497 POWER_SUPPLY_PROP_TEMP,
498 POWER_SUPPLY_PROP_TEMP_AMBIENT,
499 POWER_SUPPLY_PROP_MANUFACTURER,
David Woodhouse1ca5b9d2008-05-04 01:31:42 -0400500 POWER_SUPPLY_PROP_SERIAL_NUMBER,
Andres Salomon8e552c32008-05-12 21:46:29 -0400501 POWER_SUPPLY_PROP_CHARGE_COUNTER,
Richard A. Smith5619d0b2012-07-15 22:43:25 +0100502 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
David Woodhousefb972872007-05-04 00:51:18 +0400503};
504
Daniel Drakec566d292010-12-29 19:12:01 +0000505/* XO-1.5 does not have ambient temperature property */
506static enum power_supply_property olpc_xo15_bat_props[] = {
507 POWER_SUPPLY_PROP_STATUS,
508 POWER_SUPPLY_PROP_CHARGE_TYPE,
509 POWER_SUPPLY_PROP_PRESENT,
510 POWER_SUPPLY_PROP_HEALTH,
511 POWER_SUPPLY_PROP_TECHNOLOGY,
512 POWER_SUPPLY_PROP_VOLTAGE_AVG,
Sascha Silbebf542a42011-01-12 23:23:23 +0100513 POWER_SUPPLY_PROP_VOLTAGE_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000514 POWER_SUPPLY_PROP_CURRENT_AVG,
Sascha Silbebf542a42011-01-12 23:23:23 +0100515 POWER_SUPPLY_PROP_CURRENT_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000516 POWER_SUPPLY_PROP_CAPACITY,
517 POWER_SUPPLY_PROP_CAPACITY_LEVEL,
Sascha Silbebf542a42011-01-12 23:23:23 +0100518 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
519 POWER_SUPPLY_PROP_CHARGE_NOW,
Daniel Drakec566d292010-12-29 19:12:01 +0000520 POWER_SUPPLY_PROP_TEMP,
521 POWER_SUPPLY_PROP_MANUFACTURER,
522 POWER_SUPPLY_PROP_SERIAL_NUMBER,
523 POWER_SUPPLY_PROP_CHARGE_COUNTER,
Richard A. Smith5619d0b2012-07-15 22:43:25 +0100524 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
Daniel Drakec566d292010-12-29 19:12:01 +0000525};
526
Andres Salomond7eb9e32008-05-02 13:41:58 -0700527/* EEPROM reading goes completely around the power_supply API, sadly */
528
529#define EEPROM_START 0x20
530#define EEPROM_END 0x80
531#define EEPROM_SIZE (EEPROM_END - EEPROM_START)
532
Chris Wright2c3c8be2010-05-12 18:28:57 -0700533static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
Andres Salomond7eb9e32008-05-02 13:41:58 -0700534 struct bin_attribute *attr, char *buf, loff_t off, size_t count)
535{
536 uint8_t ec_byte;
Andres Salomon04a820e2009-06-30 02:14:00 -0400537 int ret;
538 int i;
Andres Salomond7eb9e32008-05-02 13:41:58 -0700539
Andres Salomon04a820e2009-06-30 02:14:00 -0400540 for (i = 0; i < count; i++) {
541 ec_byte = EEPROM_START + off + i;
542 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700543 if (ret) {
Andres Salomon04a820e2009-06-30 02:14:00 -0400544 pr_err("olpc-battery: "
545 "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n",
546 ec_byte, ret);
Andres Salomond7eb9e32008-05-02 13:41:58 -0700547 return -EIO;
548 }
549 }
550
551 return count;
552}
553
Lubomir Rintel31e22082019-04-18 16:46:55 +0200554static struct bin_attribute olpc_bat_eeprom = {
Andres Salomond7eb9e32008-05-02 13:41:58 -0700555 .attr = {
556 .name = "eeprom",
557 .mode = S_IRUGO,
Andres Salomond7eb9e32008-05-02 13:41:58 -0700558 },
Vladimir Zapolskiy3e1d9c62015-07-27 00:26:49 +0300559 .size = EEPROM_SIZE,
Andres Salomond7eb9e32008-05-02 13:41:58 -0700560 .read = olpc_bat_eeprom_read,
561};
562
Andres Salomon144bbea2009-06-30 02:15:26 -0400563/* Allow userspace to see the specific error value pulled from the EC */
564
565static ssize_t olpc_bat_error_read(struct device *dev,
566 struct device_attribute *attr, char *buf)
567{
568 uint8_t ec_byte;
569 ssize_t ret;
570
571 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
572 if (ret < 0)
573 return ret;
574
575 return sprintf(buf, "%d\n", ec_byte);
576}
577
Lubomir Rintel31e22082019-04-18 16:46:55 +0200578static struct device_attribute olpc_bat_error = {
Andres Salomon144bbea2009-06-30 02:15:26 -0400579 .attr = {
580 .name = "error",
581 .mode = S_IRUGO,
582 },
583 .show = olpc_bat_error_read,
584};
585
Lubomir Rintel31e22082019-04-18 16:46:55 +0200586static struct attribute *olpc_bat_sysfs_attrs[] = {
587 &olpc_bat_error.attr,
588 NULL
589};
590
591static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = {
592 &olpc_bat_eeprom,
593 NULL
594};
595
596static const struct attribute_group olpc_bat_sysfs_group = {
597 .attrs = olpc_bat_sysfs_attrs,
598 .bin_attrs = olpc_bat_sysfs_bin_attrs,
599
600};
601
602static const struct attribute_group *olpc_bat_sysfs_groups[] = {
603 &olpc_bat_sysfs_group,
604 NULL
605};
606
David Woodhousefb972872007-05-04 00:51:18 +0400607/*********************************************************************
608 * Initialisation
609 *********************************************************************/
610
Krzysztof Kozlowski297d7162015-03-12 08:44:11 +0100611static struct power_supply_desc olpc_bat_desc = {
Daniel Drakec3503fd2011-08-10 21:45:36 +0100612 .name = "olpc-battery",
David Woodhousefb972872007-05-04 00:51:18 +0400613 .get_property = olpc_bat_get_property,
614 .use_for_apm = 1,
615};
616
Daniel Drakecae659a2011-08-10 21:46:02 +0100617static int olpc_battery_suspend(struct platform_device *pdev,
618 pm_message_t state)
619{
Lubomir Rintel33554d82019-04-18 16:46:51 +0200620 struct olpc_battery_data *data = platform_get_drvdata(pdev);
621
622 if (device_may_wakeup(&data->olpc_ac->dev))
Daniel Drakecae659a2011-08-10 21:46:02 +0100623 olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
624 else
625 olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
626
Lubomir Rintel33554d82019-04-18 16:46:51 +0200627 if (device_may_wakeup(&data->olpc_bat->dev))
Daniel Drakecae659a2011-08-10 21:46:02 +0100628 olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
629 | EC_SCI_SRC_BATERR);
630 else
631 olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
632 | EC_SCI_SRC_BATERR);
633
634 return 0;
635}
636
Bill Pembertonc8afa642012-11-19 13:22:23 -0500637static int olpc_battery_probe(struct platform_device *pdev)
David Woodhousefb972872007-05-04 00:51:18 +0400638{
Lubomir Rintel31e22082019-04-18 16:46:55 +0200639 struct power_supply_config bat_psy_cfg = {};
640 struct power_supply_config ac_psy_cfg = {};
Lubomir Rintel33554d82019-04-18 16:46:51 +0200641 struct olpc_battery_data *data;
David Woodhousefb972872007-05-04 00:51:18 +0400642 uint8_t status;
Lubomir Rintel8ecefda2019-04-18 16:46:53 +0200643 uint8_t ecver;
Lubomir Rintel33554d82019-04-18 16:46:51 +0200644 int ret;
645
646 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
647 if (!data)
648 return -ENOMEM;
649 platform_set_drvdata(pdev, data);
David Woodhousefb972872007-05-04 00:51:18 +0400650
Lubomir Rintel8ecefda2019-04-18 16:46:53 +0200651 /* See if the EC is already there and get the EC revision */
652 ret = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ecver, 1);
653 if (ret)
654 return ret;
655
Lubomir Rintel76311b92019-04-18 16:46:54 +0200656 if (of_find_compatible_node(NULL, NULL, "olpc,xo1.75-ec")) {
657 /* XO 1.75 */
658 data->new_proto = true;
659 data->little_endian = true;
660 } else if (ecver > 0x44) {
Lubomir Rintel8ecefda2019-04-18 16:46:53 +0200661 /* XO 1 or 1.5 with a new EC firmware. */
662 data->new_proto = true;
663 } else if (ecver < 0x44) {
664 /*
665 * We've seen a number of EC protocol changes; this driver
666 * requires the latest EC protocol, supported by 0x44 and above.
667 */
Andres Salomon484d6d52008-05-02 13:41:59 -0700668 printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
Lubomir Rintel8ecefda2019-04-18 16:46:53 +0200669 "battery driver.\n", ecver);
David Woodhousefb972872007-05-04 00:51:18 +0400670 return -ENXIO;
671 }
672
673 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
674 if (ret)
675 return ret;
676
677 /* Ignore the status. It doesn't actually matter */
678
Lubomir Rintel31e22082019-04-18 16:46:55 +0200679 ac_psy_cfg.of_node = pdev->dev.of_node;
680 ac_psy_cfg.drv_data = data;
Lubomir Rintel33554d82019-04-18 16:46:51 +0200681
Lubomir Rintel31e22082019-04-18 16:46:55 +0200682 data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc,
683 &ac_psy_cfg);
Lubomir Rintel33554d82019-04-18 16:46:51 +0200684 if (IS_ERR(data->olpc_ac))
685 return PTR_ERR(data->olpc_ac);
686
Lubomir Rintelf7a228e2019-04-18 16:46:50 +0200687 if (of_device_is_compatible(pdev->dev.of_node, "olpc,xo1.5-battery")) {
688 /* XO-1.5 */
Krzysztof Kozlowski297d7162015-03-12 08:44:11 +0100689 olpc_bat_desc.properties = olpc_xo15_bat_props;
690 olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
Lubomir Rintelf7a228e2019-04-18 16:46:50 +0200691 } else {
692 /* XO-1 */
Krzysztof Kozlowski297d7162015-03-12 08:44:11 +0100693 olpc_bat_desc.properties = olpc_xo1_bat_props;
694 olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
Daniel Drakec566d292010-12-29 19:12:01 +0000695 }
David Woodhousefb972872007-05-04 00:51:18 +0400696
Lubomir Rintel31e22082019-04-18 16:46:55 +0200697 bat_psy_cfg.of_node = pdev->dev.of_node;
698 bat_psy_cfg.drv_data = data;
699 bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups;
700
701 data->olpc_bat = devm_power_supply_register(&pdev->dev, &olpc_bat_desc,
702 &bat_psy_cfg);
Lubomir Rintelb0280d02019-04-18 16:46:52 +0200703 if (IS_ERR(data->olpc_bat))
704 return PTR_ERR(data->olpc_bat);
David Woodhousefb972872007-05-04 00:51:18 +0400705
Daniel Drakecae659a2011-08-10 21:46:02 +0100706 if (olpc_ec_wakeup_available()) {
Lubomir Rintel33554d82019-04-18 16:46:51 +0200707 device_set_wakeup_capable(&data->olpc_ac->dev, true);
708 device_set_wakeup_capable(&data->olpc_bat->dev, true);
Daniel Drakecae659a2011-08-10 21:46:02 +0100709 }
710
Daniel Drakec3503fd2011-08-10 21:45:36 +0100711 return 0;
David Woodhousefb972872007-05-04 00:51:18 +0400712}
713
Greg Kroah-Hartman6d2cea42012-12-21 15:04:54 -0800714static const struct of_device_id olpc_battery_ids[] = {
Daniel Drakec3503fd2011-08-10 21:45:36 +0100715 { .compatible = "olpc,xo1-battery" },
Lubomir Rintelf7a228e2019-04-18 16:46:50 +0200716 { .compatible = "olpc,xo1.5-battery" },
Daniel Drakec3503fd2011-08-10 21:45:36 +0100717 {}
718};
719MODULE_DEVICE_TABLE(of, olpc_battery_ids);
720
Anton Vorontsov5519d002011-11-24 22:49:07 +0400721static struct platform_driver olpc_battery_driver = {
Daniel Drakec3503fd2011-08-10 21:45:36 +0100722 .driver = {
723 .name = "olpc-battery",
Daniel Drakec3503fd2011-08-10 21:45:36 +0100724 .of_match_table = olpc_battery_ids,
725 },
726 .probe = olpc_battery_probe,
Daniel Drakecae659a2011-08-10 21:46:02 +0100727 .suspend = olpc_battery_suspend,
Daniel Drakec3503fd2011-08-10 21:45:36 +0100728};
729
Axel Lin300bac72011-11-26 12:01:10 +0800730module_platform_driver(olpc_battery_driver);
David Woodhousefb972872007-05-04 00:51:18 +0400731
732MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
733MODULE_LICENSE("GPL");
734MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");