diff options
author | David Woodhouse <dwmw2@infradead.org> | 2007-05-03 16:51:18 -0400 |
---|---|---|
committer | David Woodhouse <dwmw2@infradead.org> | 2007-07-10 06:28:22 -0400 |
commit | fb972873a767220333ffb509de8d9131336e212c (patch) | |
tree | bb3212e2f531be875f8c8e82bb6236ed2d975760 /drivers/power/olpc_battery.c | |
parent | efea58e397dec84e37209c98619d39a46872db4d (diff) |
[BATTERY] One Laptop Per Child power/battery driver
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Signed-off-by: Anton Vorontsov <cbou@mail.ru>
Diffstat (limited to 'drivers/power/olpc_battery.c')
-rw-r--r-- | drivers/power/olpc_battery.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c new file mode 100644 index 000000000000..878684df7667 --- /dev/null +++ b/drivers/power/olpc_battery.c | |||
@@ -0,0 +1,352 @@ | |||
1 | /* | ||
2 | * Battery driver for One Laptop Per Child board. | ||
3 | * | ||
4 | * Copyright © 2006 David Woodhouse <dwmw2@infradead.org> | ||
5 | * | ||
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 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/err.h> | ||
13 | #include <linux/platform_device.h> | ||
14 | #include <linux/power_supply.h> | ||
15 | #include <linux/jiffies.h> | ||
16 | #include <linux/sched.h> | ||
17 | #include <asm/olpc.h> | ||
18 | |||
19 | |||
20 | #define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ | ||
21 | #define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ | ||
22 | #define EC_BAT_ACR 0x12 | ||
23 | #define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ | ||
24 | #define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ | ||
25 | #define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ | ||
26 | #define EC_BAT_SOC 0x16 /* uint8_t, percentage */ | ||
27 | #define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ | ||
28 | #define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ | ||
29 | #define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ | ||
30 | |||
31 | #define BAT_STAT_PRESENT 0x01 | ||
32 | #define BAT_STAT_FULL 0x02 | ||
33 | #define BAT_STAT_LOW 0x04 | ||
34 | #define BAT_STAT_DESTROY 0x08 | ||
35 | #define BAT_STAT_AC 0x10 | ||
36 | #define BAT_STAT_CHARGING 0x20 | ||
37 | #define BAT_STAT_DISCHARGING 0x40 | ||
38 | |||
39 | #define BAT_ERR_INFOFAIL 0x02 | ||
40 | #define BAT_ERR_OVERVOLTAGE 0x04 | ||
41 | #define BAT_ERR_OVERTEMP 0x05 | ||
42 | #define BAT_ERR_GAUGESTOP 0x06 | ||
43 | #define BAT_ERR_OUT_OF_CONTROL 0x07 | ||
44 | #define BAT_ERR_ID_FAIL 0x09 | ||
45 | #define BAT_ERR_ACR_FAIL 0x10 | ||
46 | |||
47 | #define BAT_ADDR_MFR_TYPE 0x5F | ||
48 | |||
49 | /********************************************************************* | ||
50 | * Power | ||
51 | *********************************************************************/ | ||
52 | |||
53 | static int olpc_ac_get_prop(struct power_supply *psy, | ||
54 | enum power_supply_property psp, | ||
55 | union power_supply_propval *val) | ||
56 | { | ||
57 | int ret = 0; | ||
58 | uint8_t status; | ||
59 | |||
60 | switch (psp) { | ||
61 | case POWER_SUPPLY_PROP_ONLINE: | ||
62 | ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); | ||
63 | if (ret) | ||
64 | return ret; | ||
65 | |||
66 | val->intval = !!(status & BAT_STAT_AC); | ||
67 | break; | ||
68 | default: | ||
69 | ret = -EINVAL; | ||
70 | break; | ||
71 | } | ||
72 | return ret; | ||
73 | } | ||
74 | |||
75 | static enum power_supply_property olpc_ac_props[] = { | ||
76 | POWER_SUPPLY_PROP_ONLINE, | ||
77 | }; | ||
78 | |||
79 | static struct power_supply olpc_ac = { | ||
80 | .name = "olpc-ac", | ||
81 | .type = POWER_SUPPLY_TYPE_MAINS, | ||
82 | .properties = olpc_ac_props, | ||
83 | .num_properties = ARRAY_SIZE(olpc_ac_props), | ||
84 | .get_property = olpc_ac_get_prop, | ||
85 | }; | ||
86 | |||
87 | /********************************************************************* | ||
88 | * Battery properties | ||
89 | *********************************************************************/ | ||
90 | static int olpc_bat_get_property(struct power_supply *psy, | ||
91 | enum power_supply_property psp, | ||
92 | union power_supply_propval *val) | ||
93 | { | ||
94 | int ret = 0; | ||
95 | int16_t ec_word; | ||
96 | uint8_t ec_byte; | ||
97 | |||
98 | ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); | ||
99 | if (ret) | ||
100 | return ret; | ||
101 | |||
102 | /* Theoretically there's a race here -- the battery could be | ||
103 | removed immediately after we check whether it's present, and | ||
104 | then we query for some other property of the now-absent battery. | ||
105 | It doesn't matter though -- the EC will return the last-known | ||
106 | information, and it's as if we just ran that _little_ bit faster | ||
107 | and managed to read it out before the battery went away. */ | ||
108 | if (!(ec_byte & BAT_STAT_PRESENT) && psp != POWER_SUPPLY_PROP_PRESENT) | ||
109 | return -ENODEV; | ||
110 | |||
111 | switch (psp) { | ||
112 | case POWER_SUPPLY_PROP_STATUS: | ||
113 | if (olpc_platform_info.ecver > 0x44) { | ||
114 | if (ec_byte & BAT_STAT_CHARGING) | ||
115 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | ||
116 | else if (ec_byte & BAT_STAT_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 /* er,... */ | ||
121 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | ||
122 | } else { | ||
123 | /* Older EC didn't report charge/discharge bits */ | ||
124 | if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ | ||
125 | val->intval = POWER_SUPPLY_STATUS_DISCHARGING; | ||
126 | else if (ec_byte & BAT_STAT_FULL) | ||
127 | val->intval = POWER_SUPPLY_STATUS_FULL; | ||
128 | else /* Not _necessarily_ true but EC doesn't tell all yet */ | ||
129 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | ||
130 | break; | ||
131 | } | ||
132 | case POWER_SUPPLY_PROP_PRESENT: | ||
133 | val->intval = !!(ec_byte & BAT_STAT_PRESENT); | ||
134 | break; | ||
135 | |||
136 | case POWER_SUPPLY_PROP_HEALTH: | ||
137 | if (ec_byte & BAT_STAT_DESTROY) | ||
138 | val->intval = POWER_SUPPLY_HEALTH_DEAD; | ||
139 | else { | ||
140 | ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); | ||
141 | if (ret) | ||
142 | return ret; | ||
143 | |||
144 | switch (ec_byte) { | ||
145 | case 0: | ||
146 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | ||
147 | break; | ||
148 | |||
149 | case BAT_ERR_OVERTEMP: | ||
150 | val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; | ||
151 | break; | ||
152 | |||
153 | case BAT_ERR_OVERVOLTAGE: | ||
154 | val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; | ||
155 | break; | ||
156 | |||
157 | case BAT_ERR_INFOFAIL: | ||
158 | case BAT_ERR_OUT_OF_CONTROL: | ||
159 | case BAT_ERR_ID_FAIL: | ||
160 | case BAT_ERR_ACR_FAIL: | ||
161 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | ||
162 | break; | ||
163 | |||
164 | default: | ||
165 | /* Eep. We don't know this failure code */ | ||
166 | return -EIO; | ||
167 | } | ||
168 | } | ||
169 | break; | ||
170 | |||
171 | case POWER_SUPPLY_PROP_MANUFACTURER: | ||
172 | ec_byte = BAT_ADDR_MFR_TYPE; | ||
173 | ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); | ||
174 | if (ret) | ||
175 | return ret; | ||
176 | |||
177 | switch (ec_byte >> 4) { | ||
178 | case 1: | ||
179 | val->strval = "Gold Peak"; | ||
180 | break; | ||
181 | case 2: | ||
182 | val->strval = "BYD"; | ||
183 | break; | ||
184 | default: | ||
185 | val->strval = "Unknown"; | ||
186 | break; | ||
187 | } | ||
188 | break; | ||
189 | case POWER_SUPPLY_PROP_TECHNOLOGY: | ||
190 | ec_byte = BAT_ADDR_MFR_TYPE; | ||
191 | ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); | ||
192 | if (ret) | ||
193 | return ret; | ||
194 | |||
195 | switch (ec_byte & 0xf) { | ||
196 | case 1: | ||
197 | val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; | ||
198 | break; | ||
199 | case 2: | ||
200 | val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; | ||
201 | break; | ||
202 | default: | ||
203 | val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; | ||
204 | break; | ||
205 | } | ||
206 | break; | ||
207 | case POWER_SUPPLY_PROP_VOLTAGE_AVG: | ||
208 | ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); | ||
209 | if (ret) | ||
210 | return ret; | ||
211 | |||
212 | ec_word = be16_to_cpu(ec_word); | ||
213 | val->intval = ec_word * 9760L / 32; | ||
214 | break; | ||
215 | case POWER_SUPPLY_PROP_CURRENT_AVG: | ||
216 | ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); | ||
217 | if (ret) | ||
218 | return ret; | ||
219 | |||
220 | ec_word = be16_to_cpu(ec_word); | ||
221 | val->intval = ec_word * 15625L / 120; | ||
222 | break; | ||
223 | case POWER_SUPPLY_PROP_CAPACITY: | ||
224 | ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); | ||
225 | if (ret) | ||
226 | return ret; | ||
227 | val->intval = ec_byte; | ||
228 | break; | ||
229 | case POWER_SUPPLY_PROP_CAPACITY_LEVEL: | ||
230 | if (ec_byte & BAT_STAT_FULL) | ||
231 | val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; | ||
232 | else if (ec_byte & BAT_STAT_LOW) | ||
233 | val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; | ||
234 | else | ||
235 | val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; | ||
236 | break; | ||
237 | case POWER_SUPPLY_PROP_TEMP: | ||
238 | ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); | ||
239 | if (ret) | ||
240 | return ret; | ||
241 | ec_word = be16_to_cpu(ec_word); | ||
242 | val->intval = ec_word * 100 / 256; | ||
243 | break; | ||
244 | case POWER_SUPPLY_PROP_TEMP_AMBIENT: | ||
245 | ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); | ||
246 | if (ret) | ||
247 | return ret; | ||
248 | |||
249 | ec_word = be16_to_cpu(ec_word); | ||
250 | val->intval = ec_word * 100 / 256; | ||
251 | break; | ||
252 | default: | ||
253 | ret = -EINVAL; | ||
254 | break; | ||
255 | } | ||
256 | |||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | static enum power_supply_property olpc_bat_props[] = { | ||
261 | POWER_SUPPLY_PROP_STATUS, | ||
262 | POWER_SUPPLY_PROP_PRESENT, | ||
263 | POWER_SUPPLY_PROP_HEALTH, | ||
264 | POWER_SUPPLY_PROP_TECHNOLOGY, | ||
265 | POWER_SUPPLY_PROP_VOLTAGE_AVG, | ||
266 | POWER_SUPPLY_PROP_CURRENT_AVG, | ||
267 | POWER_SUPPLY_PROP_CAPACITY, | ||
268 | POWER_SUPPLY_PROP_CAPACITY_LEVEL, | ||
269 | POWER_SUPPLY_PROP_TEMP, | ||
270 | POWER_SUPPLY_PROP_TEMP_AMBIENT, | ||
271 | POWER_SUPPLY_PROP_MANUFACTURER, | ||
272 | }; | ||
273 | |||
274 | /********************************************************************* | ||
275 | * Initialisation | ||
276 | *********************************************************************/ | ||
277 | |||
278 | static struct platform_device *bat_pdev; | ||
279 | |||
280 | static struct power_supply olpc_bat = { | ||
281 | .properties = olpc_bat_props, | ||
282 | .num_properties = ARRAY_SIZE(olpc_bat_props), | ||
283 | .get_property = olpc_bat_get_property, | ||
284 | .use_for_apm = 1, | ||
285 | }; | ||
286 | |||
287 | void olpc_battery_trigger_uevent(unsigned long cause) | ||
288 | { | ||
289 | if (cause & EC_SCI_SRC_ACPWR) | ||
290 | kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE); | ||
291 | if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY)) | ||
292 | kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE); | ||
293 | } | ||
294 | |||
295 | static int __init olpc_bat_init(void) | ||
296 | { | ||
297 | int ret = 0; | ||
298 | uint8_t status; | ||
299 | |||
300 | if (!olpc_platform_info.ecver) | ||
301 | return -ENXIO; | ||
302 | if (olpc_platform_info.ecver < 0x43) { | ||
303 | printk(KERN_NOTICE "OLPC EC version 0x%02x too old for battery driver.\n", olpc_platform_info.ecver); | ||
304 | return -ENXIO; | ||
305 | } | ||
306 | |||
307 | ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); | ||
308 | if (ret) | ||
309 | return ret; | ||
310 | |||
311 | /* Ignore the status. It doesn't actually matter */ | ||
312 | |||
313 | bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0); | ||
314 | if (IS_ERR(bat_pdev)) | ||
315 | return PTR_ERR(bat_pdev); | ||
316 | |||
317 | ret = power_supply_register(&bat_pdev->dev, &olpc_ac); | ||
318 | if (ret) | ||
319 | goto ac_failed; | ||
320 | |||
321 | olpc_bat.name = bat_pdev->name; | ||
322 | |||
323 | ret = power_supply_register(&bat_pdev->dev, &olpc_bat); | ||
324 | if (ret) | ||
325 | goto battery_failed; | ||
326 | |||
327 | olpc_register_battery_callback(&olpc_battery_trigger_uevent); | ||
328 | goto success; | ||
329 | |||
330 | battery_failed: | ||
331 | power_supply_unregister(&olpc_ac); | ||
332 | ac_failed: | ||
333 | platform_device_unregister(bat_pdev); | ||
334 | success: | ||
335 | return ret; | ||
336 | } | ||
337 | |||
338 | static void __exit olpc_bat_exit(void) | ||
339 | { | ||
340 | olpc_deregister_battery_callback(); | ||
341 | power_supply_unregister(&olpc_bat); | ||
342 | power_supply_unregister(&olpc_ac); | ||
343 | platform_device_unregister(bat_pdev); | ||
344 | return; | ||
345 | } | ||
346 | |||
347 | module_init(olpc_bat_init); | ||
348 | module_exit(olpc_bat_exit); | ||
349 | |||
350 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | ||
351 | MODULE_LICENSE("GPL"); | ||
352 | MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); | ||