olpc_battery.c revision b294a290d24d1196d68399cc3a9b8c50bfb55abd
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/kernel.h> 12#include <linux/module.h> 13#include <linux/err.h> 14#include <linux/platform_device.h> 15#include <linux/power_supply.h> 16#include <linux/jiffies.h> 17#include <linux/sched.h> 18#include <asm/olpc.h> 19 20 21#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ 22#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ 23#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */ 24#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ 25#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ 26#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ 27#define EC_BAT_SOC 0x16 /* uint8_t, percentage */ 28#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ 29#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ 30#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ 31 32#define BAT_STAT_PRESENT 0x01 33#define BAT_STAT_FULL 0x02 34#define BAT_STAT_LOW 0x04 35#define BAT_STAT_DESTROY 0x08 36#define BAT_STAT_AC 0x10 37#define BAT_STAT_CHARGING 0x20 38#define BAT_STAT_DISCHARGING 0x40 39#define BAT_STAT_TRICKLE 0x80 40 41#define BAT_ERR_INFOFAIL 0x02 42#define BAT_ERR_OVERVOLTAGE 0x04 43#define BAT_ERR_OVERTEMP 0x05 44#define BAT_ERR_GAUGESTOP 0x06 45#define BAT_ERR_OUT_OF_CONTROL 0x07 46#define BAT_ERR_ID_FAIL 0x09 47#define BAT_ERR_ACR_FAIL 0x10 48 49#define BAT_ADDR_MFR_TYPE 0x5F 50 51/********************************************************************* 52 * Power 53 *********************************************************************/ 54 55static int olpc_ac_get_prop(struct power_supply *psy, 56 enum power_supply_property psp, 57 union power_supply_propval *val) 58{ 59 int ret = 0; 60 uint8_t status; 61 62 switch (psp) { 63 case POWER_SUPPLY_PROP_ONLINE: 64 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); 65 if (ret) 66 return ret; 67 68 val->intval = !!(status & BAT_STAT_AC); 69 break; 70 default: 71 ret = -EINVAL; 72 break; 73 } 74 return ret; 75} 76 77static enum power_supply_property olpc_ac_props[] = { 78 POWER_SUPPLY_PROP_ONLINE, 79}; 80 81static struct power_supply olpc_ac = { 82 .name = "olpc-ac", 83 .type = POWER_SUPPLY_TYPE_MAINS, 84 .properties = olpc_ac_props, 85 .num_properties = ARRAY_SIZE(olpc_ac_props), 86 .get_property = olpc_ac_get_prop, 87}; 88 89static char bat_serial[17]; /* Ick */ 90 91static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) 92{ 93 if (olpc_platform_info.ecver > 0x44) { 94 if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) 95 val->intval = POWER_SUPPLY_STATUS_CHARGING; 96 else if (ec_byte & BAT_STAT_DISCHARGING) 97 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 98 else if (ec_byte & BAT_STAT_FULL) 99 val->intval = POWER_SUPPLY_STATUS_FULL; 100 else /* er,... */ 101 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; 102 } else { 103 /* Older EC didn't report charge/discharge bits */ 104 if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ 105 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 106 else if (ec_byte & BAT_STAT_FULL) 107 val->intval = POWER_SUPPLY_STATUS_FULL; 108 else /* Not _necessarily_ true but EC doesn't tell all yet */ 109 val->intval = POWER_SUPPLY_STATUS_CHARGING; 110 } 111 112 return 0; 113} 114 115static int olpc_bat_get_health(union power_supply_propval *val) 116{ 117 uint8_t ec_byte; 118 int ret; 119 120 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); 121 if (ret) 122 return ret; 123 124 switch (ec_byte) { 125 case 0: 126 val->intval = POWER_SUPPLY_HEALTH_GOOD; 127 break; 128 129 case BAT_ERR_OVERTEMP: 130 val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; 131 break; 132 133 case BAT_ERR_OVERVOLTAGE: 134 val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 135 break; 136 137 case BAT_ERR_INFOFAIL: 138 case BAT_ERR_OUT_OF_CONTROL: 139 case BAT_ERR_ID_FAIL: 140 case BAT_ERR_ACR_FAIL: 141 val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 142 break; 143 144 default: 145 /* Eep. We don't know this failure code */ 146 ret = -EIO; 147 } 148 149 return ret; 150} 151 152static int olpc_bat_get_mfr(union power_supply_propval *val) 153{ 154 uint8_t ec_byte; 155 int ret; 156 157 ec_byte = BAT_ADDR_MFR_TYPE; 158 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); 159 if (ret) 160 return ret; 161 162 switch (ec_byte >> 4) { 163 case 1: 164 val->strval = "Gold Peak"; 165 break; 166 case 2: 167 val->strval = "BYD"; 168 break; 169 default: 170 val->strval = "Unknown"; 171 break; 172 } 173 174 return ret; 175} 176 177static int olpc_bat_get_tech(union power_supply_propval *val) 178{ 179 uint8_t ec_byte; 180 int ret; 181 182 ec_byte = BAT_ADDR_MFR_TYPE; 183 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); 184 if (ret) 185 return ret; 186 187 switch (ec_byte & 0xf) { 188 case 1: 189 val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; 190 break; 191 case 2: 192 val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; 193 break; 194 default: 195 val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 196 break; 197 } 198 199 return ret; 200} 201 202/********************************************************************* 203 * Battery properties 204 *********************************************************************/ 205static int olpc_bat_get_property(struct power_supply *psy, 206 enum power_supply_property psp, 207 union power_supply_propval *val) 208{ 209 int ret = 0; 210 __be16 ec_word; 211 uint8_t ec_byte; 212 __be64 ser_buf; 213 214 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); 215 if (ret) 216 return ret; 217 218 /* Theoretically there's a race here -- the battery could be 219 removed immediately after we check whether it's present, and 220 then we query for some other property of the now-absent battery. 221 It doesn't matter though -- the EC will return the last-known 222 information, and it's as if we just ran that _little_ bit faster 223 and managed to read it out before the battery went away. */ 224 if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) && 225 psp != POWER_SUPPLY_PROP_PRESENT) 226 return -ENODEV; 227 228 switch (psp) { 229 case POWER_SUPPLY_PROP_STATUS: 230 ret = olpc_bat_get_status(val, ec_byte); 231 if (ret) 232 return ret; 233 break; 234 case POWER_SUPPLY_PROP_PRESENT: 235 val->intval = !!(ec_byte & (BAT_STAT_PRESENT | 236 BAT_STAT_TRICKLE)); 237 break; 238 239 case POWER_SUPPLY_PROP_HEALTH: 240 if (ec_byte & BAT_STAT_DESTROY) 241 val->intval = POWER_SUPPLY_HEALTH_DEAD; 242 else { 243 ret = olpc_bat_get_health(val); 244 if (ret) 245 return ret; 246 } 247 break; 248 249 case POWER_SUPPLY_PROP_MANUFACTURER: 250 ret = olpc_bat_get_mfr(val); 251 if (ret) 252 return ret; 253 break; 254 case POWER_SUPPLY_PROP_TECHNOLOGY: 255 ret = olpc_bat_get_tech(val); 256 if (ret) 257 return ret; 258 break; 259 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 260 ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); 261 if (ret) 262 return ret; 263 264 val->intval = (int)be16_to_cpu(ec_word) * 9760L / 32; 265 break; 266 case POWER_SUPPLY_PROP_CURRENT_AVG: 267 ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); 268 if (ret) 269 return ret; 270 271 val->intval = (int)be16_to_cpu(ec_word) * 15625L / 120; 272 break; 273 case POWER_SUPPLY_PROP_CAPACITY: 274 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); 275 if (ret) 276 return ret; 277 val->intval = ec_byte; 278 break; 279 case POWER_SUPPLY_PROP_CAPACITY_LEVEL: 280 if (ec_byte & BAT_STAT_FULL) 281 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; 282 else if (ec_byte & BAT_STAT_LOW) 283 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; 284 else 285 val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; 286 break; 287 case POWER_SUPPLY_PROP_TEMP: 288 ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); 289 if (ret) 290 return ret; 291 292 val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; 293 break; 294 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 295 ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); 296 if (ret) 297 return ret; 298 299 val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; 300 break; 301 case POWER_SUPPLY_PROP_CHARGE_COUNTER: 302 ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); 303 if (ret) 304 return ret; 305 306 val->intval = (int)be16_to_cpu(ec_word) * 6250 / 15; 307 break; 308 case POWER_SUPPLY_PROP_SERIAL_NUMBER: 309 ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); 310 if (ret) 311 return ret; 312 313 sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); 314 val->strval = bat_serial; 315 break; 316 default: 317 ret = -EINVAL; 318 break; 319 } 320 321 return ret; 322} 323 324static enum power_supply_property olpc_bat_props[] = { 325 POWER_SUPPLY_PROP_STATUS, 326 POWER_SUPPLY_PROP_PRESENT, 327 POWER_SUPPLY_PROP_HEALTH, 328 POWER_SUPPLY_PROP_TECHNOLOGY, 329 POWER_SUPPLY_PROP_VOLTAGE_AVG, 330 POWER_SUPPLY_PROP_CURRENT_AVG, 331 POWER_SUPPLY_PROP_CAPACITY, 332 POWER_SUPPLY_PROP_CAPACITY_LEVEL, 333 POWER_SUPPLY_PROP_TEMP, 334 POWER_SUPPLY_PROP_TEMP_AMBIENT, 335 POWER_SUPPLY_PROP_MANUFACTURER, 336 POWER_SUPPLY_PROP_SERIAL_NUMBER, 337 POWER_SUPPLY_PROP_CHARGE_COUNTER, 338}; 339 340/* EEPROM reading goes completely around the power_supply API, sadly */ 341 342#define EEPROM_START 0x20 343#define EEPROM_END 0x80 344#define EEPROM_SIZE (EEPROM_END - EEPROM_START) 345 346static ssize_t olpc_bat_eeprom_read(struct kobject *kobj, 347 struct bin_attribute *attr, char *buf, loff_t off, size_t count) 348{ 349 uint8_t ec_byte; 350 int ret; 351 int i; 352 353 if (off >= EEPROM_SIZE) 354 return 0; 355 if (off + count > EEPROM_SIZE) 356 count = EEPROM_SIZE - off; 357 358 for (i = 0; i < count; i++) { 359 ec_byte = EEPROM_START + off + i; 360 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1); 361 if (ret) { 362 pr_err("olpc-battery: " 363 "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n", 364 ec_byte, ret); 365 return -EIO; 366 } 367 } 368 369 return count; 370} 371 372static struct bin_attribute olpc_bat_eeprom = { 373 .attr = { 374 .name = "eeprom", 375 .mode = S_IRUGO, 376 .owner = THIS_MODULE, 377 }, 378 .size = 0, 379 .read = olpc_bat_eeprom_read, 380}; 381 382/********************************************************************* 383 * Initialisation 384 *********************************************************************/ 385 386static struct platform_device *bat_pdev; 387 388static struct power_supply olpc_bat = { 389 .properties = olpc_bat_props, 390 .num_properties = ARRAY_SIZE(olpc_bat_props), 391 .get_property = olpc_bat_get_property, 392 .use_for_apm = 1, 393}; 394 395void olpc_battery_trigger_uevent(unsigned long cause) 396{ 397 if (cause & EC_SCI_SRC_ACPWR) 398 kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE); 399 if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY)) 400 kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE); 401} 402 403static int __init olpc_bat_init(void) 404{ 405 int ret = 0; 406 uint8_t status; 407 408 if (!olpc_platform_info.ecver) 409 return -ENXIO; 410 411 /* 412 * We've seen a number of EC protocol changes; this driver requires 413 * the latest EC protocol, supported by 0x44 and above. 414 */ 415 if (olpc_platform_info.ecver < 0x44) { 416 printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " 417 "battery driver.\n", olpc_platform_info.ecver); 418 return -ENXIO; 419 } 420 421 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); 422 if (ret) 423 return ret; 424 425 /* Ignore the status. It doesn't actually matter */ 426 427 bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0); 428 if (IS_ERR(bat_pdev)) 429 return PTR_ERR(bat_pdev); 430 431 ret = power_supply_register(&bat_pdev->dev, &olpc_ac); 432 if (ret) 433 goto ac_failed; 434 435 olpc_bat.name = bat_pdev->name; 436 437 ret = power_supply_register(&bat_pdev->dev, &olpc_bat); 438 if (ret) 439 goto battery_failed; 440 441 ret = device_create_bin_file(olpc_bat.dev, &olpc_bat_eeprom); 442 if (ret) 443 goto eeprom_failed; 444 445 goto success; 446 447eeprom_failed: 448 power_supply_unregister(&olpc_bat); 449battery_failed: 450 power_supply_unregister(&olpc_ac); 451ac_failed: 452 platform_device_unregister(bat_pdev); 453success: 454 return ret; 455} 456 457static void __exit olpc_bat_exit(void) 458{ 459 device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom); 460 power_supply_unregister(&olpc_bat); 461 power_supply_unregister(&olpc_ac); 462 platform_device_unregister(bat_pdev); 463} 464 465module_init(olpc_bat_init); 466module_exit(olpc_bat_exit); 467 468MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 469MODULE_LICENSE("GPL"); 470MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); 471