Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v2.6.31-rc6 461 lines 11 kB view raw
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_TEMP: 280 ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); 281 if (ret) 282 return ret; 283 284 val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; 285 break; 286 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 287 ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); 288 if (ret) 289 return ret; 290 291 val->intval = (int)be16_to_cpu(ec_word) * 100 / 256; 292 break; 293 case POWER_SUPPLY_PROP_CHARGE_COUNTER: 294 ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); 295 if (ret) 296 return ret; 297 298 val->intval = (int)be16_to_cpu(ec_word) * 6250 / 15; 299 break; 300 case POWER_SUPPLY_PROP_SERIAL_NUMBER: 301 ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); 302 if (ret) 303 return ret; 304 305 sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); 306 val->strval = bat_serial; 307 break; 308 default: 309 ret = -EINVAL; 310 break; 311 } 312 313 return ret; 314} 315 316static enum power_supply_property olpc_bat_props[] = { 317 POWER_SUPPLY_PROP_STATUS, 318 POWER_SUPPLY_PROP_PRESENT, 319 POWER_SUPPLY_PROP_HEALTH, 320 POWER_SUPPLY_PROP_TECHNOLOGY, 321 POWER_SUPPLY_PROP_VOLTAGE_AVG, 322 POWER_SUPPLY_PROP_CURRENT_AVG, 323 POWER_SUPPLY_PROP_CAPACITY, 324 POWER_SUPPLY_PROP_TEMP, 325 POWER_SUPPLY_PROP_TEMP_AMBIENT, 326 POWER_SUPPLY_PROP_MANUFACTURER, 327 POWER_SUPPLY_PROP_SERIAL_NUMBER, 328 POWER_SUPPLY_PROP_CHARGE_COUNTER, 329}; 330 331/* EEPROM reading goes completely around the power_supply API, sadly */ 332 333#define EEPROM_START 0x20 334#define EEPROM_END 0x80 335#define EEPROM_SIZE (EEPROM_END - EEPROM_START) 336 337static ssize_t olpc_bat_eeprom_read(struct kobject *kobj, 338 struct bin_attribute *attr, char *buf, loff_t off, size_t count) 339{ 340 uint8_t ec_byte; 341 int ret; 342 int i; 343 344 if (off >= EEPROM_SIZE) 345 return 0; 346 if (off + count > EEPROM_SIZE) 347 count = EEPROM_SIZE - off; 348 349 for (i = 0; i < count; i++) { 350 ec_byte = EEPROM_START + off + i; 351 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1); 352 if (ret) { 353 pr_err("olpc-battery: " 354 "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n", 355 ec_byte, ret); 356 return -EIO; 357 } 358 } 359 360 return count; 361} 362 363static struct bin_attribute olpc_bat_eeprom = { 364 .attr = { 365 .name = "eeprom", 366 .mode = S_IRUGO, 367 .owner = THIS_MODULE, 368 }, 369 .size = 0, 370 .read = olpc_bat_eeprom_read, 371}; 372 373/********************************************************************* 374 * Initialisation 375 *********************************************************************/ 376 377static struct platform_device *bat_pdev; 378 379static struct power_supply olpc_bat = { 380 .properties = olpc_bat_props, 381 .num_properties = ARRAY_SIZE(olpc_bat_props), 382 .get_property = olpc_bat_get_property, 383 .use_for_apm = 1, 384}; 385 386void olpc_battery_trigger_uevent(unsigned long cause) 387{ 388 if (cause & EC_SCI_SRC_ACPWR) 389 kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE); 390 if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY)) 391 kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE); 392} 393 394static int __init olpc_bat_init(void) 395{ 396 int ret = 0; 397 uint8_t status; 398 399 if (!olpc_platform_info.ecver) 400 return -ENXIO; 401 402 /* 403 * We've seen a number of EC protocol changes; this driver requires 404 * the latest EC protocol, supported by 0x44 and above. 405 */ 406 if (olpc_platform_info.ecver < 0x44) { 407 printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " 408 "battery driver.\n", olpc_platform_info.ecver); 409 return -ENXIO; 410 } 411 412 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); 413 if (ret) 414 return ret; 415 416 /* Ignore the status. It doesn't actually matter */ 417 418 bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0); 419 if (IS_ERR(bat_pdev)) 420 return PTR_ERR(bat_pdev); 421 422 ret = power_supply_register(&bat_pdev->dev, &olpc_ac); 423 if (ret) 424 goto ac_failed; 425 426 olpc_bat.name = bat_pdev->name; 427 428 ret = power_supply_register(&bat_pdev->dev, &olpc_bat); 429 if (ret) 430 goto battery_failed; 431 432 ret = device_create_bin_file(olpc_bat.dev, &olpc_bat_eeprom); 433 if (ret) 434 goto eeprom_failed; 435 436 goto success; 437 438eeprom_failed: 439 power_supply_unregister(&olpc_bat); 440battery_failed: 441 power_supply_unregister(&olpc_ac); 442ac_failed: 443 platform_device_unregister(bat_pdev); 444success: 445 return ret; 446} 447 448static void __exit olpc_bat_exit(void) 449{ 450 device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom); 451 power_supply_unregister(&olpc_bat); 452 power_supply_unregister(&olpc_ac); 453 platform_device_unregister(bat_pdev); 454} 455 456module_init(olpc_bat_init); 457module_exit(olpc_bat_exit); 458 459MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 460MODULE_LICENSE("GPL"); 461MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");