olpc_battery.c revision 1ca5b9d2183f11bb8b69e04b19a7faf7f600a840
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 53static 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 75static enum power_supply_property olpc_ac_props[] = { 76 POWER_SUPPLY_PROP_ONLINE, 77}; 78 79static 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 87static char bat_serial[17]; /* Ick */ 88 89/********************************************************************* 90 * Battery properties 91 *********************************************************************/ 92static int olpc_bat_get_property(struct power_supply *psy, 93 enum power_supply_property psp, 94 union power_supply_propval *val) 95{ 96 int ret = 0; 97 int16_t ec_word; 98 uint8_t ec_byte; 99 uint64_t ser_buf; 100 101 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); 102 if (ret) 103 return ret; 104 105 /* Theoretically there's a race here -- the battery could be 106 removed immediately after we check whether it's present, and 107 then we query for some other property of the now-absent battery. 108 It doesn't matter though -- the EC will return the last-known 109 information, and it's as if we just ran that _little_ bit faster 110 and managed to read it out before the battery went away. */ 111 if (!(ec_byte & BAT_STAT_PRESENT) && psp != POWER_SUPPLY_PROP_PRESENT) 112 return -ENODEV; 113 114 switch (psp) { 115 case POWER_SUPPLY_PROP_STATUS: 116 if (olpc_platform_info.ecver > 0x44) { 117 if (ec_byte & BAT_STAT_CHARGING) 118 val->intval = POWER_SUPPLY_STATUS_CHARGING; 119 else if (ec_byte & BAT_STAT_DISCHARGING) 120 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 121 else if (ec_byte & BAT_STAT_FULL) 122 val->intval = POWER_SUPPLY_STATUS_FULL; 123 else /* er,... */ 124 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; 125 } else { 126 /* Older EC didn't report charge/discharge bits */ 127 if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ 128 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 129 else if (ec_byte & BAT_STAT_FULL) 130 val->intval = POWER_SUPPLY_STATUS_FULL; 131 else /* Not _necessarily_ true but EC doesn't tell all yet */ 132 val->intval = POWER_SUPPLY_STATUS_CHARGING; 133 break; 134 } 135 case POWER_SUPPLY_PROP_PRESENT: 136 val->intval = !!(ec_byte & BAT_STAT_PRESENT); 137 break; 138 139 case POWER_SUPPLY_PROP_HEALTH: 140 if (ec_byte & BAT_STAT_DESTROY) 141 val->intval = POWER_SUPPLY_HEALTH_DEAD; 142 else { 143 ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); 144 if (ret) 145 return ret; 146 147 switch (ec_byte) { 148 case 0: 149 val->intval = POWER_SUPPLY_HEALTH_GOOD; 150 break; 151 152 case BAT_ERR_OVERTEMP: 153 val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; 154 break; 155 156 case BAT_ERR_OVERVOLTAGE: 157 val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 158 break; 159 160 case BAT_ERR_INFOFAIL: 161 case BAT_ERR_OUT_OF_CONTROL: 162 case BAT_ERR_ID_FAIL: 163 case BAT_ERR_ACR_FAIL: 164 val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 165 break; 166 167 default: 168 /* Eep. We don't know this failure code */ 169 return -EIO; 170 } 171 } 172 break; 173 174 case POWER_SUPPLY_PROP_MANUFACTURER: 175 ec_byte = BAT_ADDR_MFR_TYPE; 176 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); 177 if (ret) 178 return ret; 179 180 switch (ec_byte >> 4) { 181 case 1: 182 val->strval = "Gold Peak"; 183 break; 184 case 2: 185 val->strval = "BYD"; 186 break; 187 default: 188 val->strval = "Unknown"; 189 break; 190 } 191 break; 192 case POWER_SUPPLY_PROP_TECHNOLOGY: 193 ec_byte = BAT_ADDR_MFR_TYPE; 194 ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); 195 if (ret) 196 return ret; 197 198 switch (ec_byte & 0xf) { 199 case 1: 200 val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; 201 break; 202 case 2: 203 val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe; 204 break; 205 default: 206 val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; 207 break; 208 } 209 break; 210 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 211 ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2); 212 if (ret) 213 return ret; 214 215 ec_word = be16_to_cpu(ec_word); 216 val->intval = ec_word * 9760L / 32; 217 break; 218 case POWER_SUPPLY_PROP_CURRENT_AVG: 219 ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2); 220 if (ret) 221 return ret; 222 223 ec_word = be16_to_cpu(ec_word); 224 val->intval = ec_word * 15625L / 120; 225 break; 226 case POWER_SUPPLY_PROP_CAPACITY: 227 ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); 228 if (ret) 229 return ret; 230 val->intval = ec_byte; 231 break; 232 case POWER_SUPPLY_PROP_TEMP: 233 ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2); 234 if (ret) 235 return ret; 236 ec_word = be16_to_cpu(ec_word); 237 val->intval = ec_word * 100 / 256; 238 break; 239 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 240 ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); 241 if (ret) 242 return ret; 243 244 ec_word = be16_to_cpu(ec_word); 245 val->intval = ec_word * 100 / 256; 246 break; 247 case POWER_SUPPLY_PROP_SERIAL_NUMBER: 248 ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); 249 if (ret) 250 return ret; 251 252 sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); 253 val->strval = bat_serial; 254 break; 255 default: 256 ret = -EINVAL; 257 break; 258 } 259 260 return ret; 261} 262 263static enum power_supply_property olpc_bat_props[] = { 264 POWER_SUPPLY_PROP_STATUS, 265 POWER_SUPPLY_PROP_PRESENT, 266 POWER_SUPPLY_PROP_HEALTH, 267 POWER_SUPPLY_PROP_TECHNOLOGY, 268 POWER_SUPPLY_PROP_VOLTAGE_AVG, 269 POWER_SUPPLY_PROP_CURRENT_AVG, 270 POWER_SUPPLY_PROP_CAPACITY, 271 POWER_SUPPLY_PROP_TEMP, 272 POWER_SUPPLY_PROP_TEMP_AMBIENT, 273 POWER_SUPPLY_PROP_MANUFACTURER, 274 POWER_SUPPLY_PROP_SERIAL_NUMBER, 275}; 276 277/********************************************************************* 278 * Initialisation 279 *********************************************************************/ 280 281static struct platform_device *bat_pdev; 282 283static struct power_supply olpc_bat = { 284 .properties = olpc_bat_props, 285 .num_properties = ARRAY_SIZE(olpc_bat_props), 286 .get_property = olpc_bat_get_property, 287 .use_for_apm = 1, 288}; 289 290void olpc_battery_trigger_uevent(unsigned long cause) 291{ 292 if (cause & EC_SCI_SRC_ACPWR) 293 kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE); 294 if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY)) 295 kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE); 296} 297 298static int __init olpc_bat_init(void) 299{ 300 int ret = 0; 301 uint8_t status; 302 303 if (!olpc_platform_info.ecver) 304 return -ENXIO; 305 if (olpc_platform_info.ecver < 0x43) { 306 printk(KERN_NOTICE "OLPC EC version 0x%02x too old for battery driver.\n", olpc_platform_info.ecver); 307 return -ENXIO; 308 } 309 310 ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); 311 if (ret) 312 return ret; 313 314 /* Ignore the status. It doesn't actually matter */ 315 316 bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0); 317 if (IS_ERR(bat_pdev)) 318 return PTR_ERR(bat_pdev); 319 320 ret = power_supply_register(&bat_pdev->dev, &olpc_ac); 321 if (ret) 322 goto ac_failed; 323 324 olpc_bat.name = bat_pdev->name; 325 326 ret = power_supply_register(&bat_pdev->dev, &olpc_bat); 327 if (ret) 328 goto battery_failed; 329 330 goto success; 331 332battery_failed: 333 power_supply_unregister(&olpc_ac); 334ac_failed: 335 platform_device_unregister(bat_pdev); 336success: 337 return ret; 338} 339 340static void __exit olpc_bat_exit(void) 341{ 342 power_supply_unregister(&olpc_bat); 343 power_supply_unregister(&olpc_ac); 344 platform_device_unregister(bat_pdev); 345} 346 347module_init(olpc_bat_init); 348module_exit(olpc_bat_exit); 349 350MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 351MODULE_LICENSE("GPL"); 352MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); 353