1/* 2 * Battery and Power Management code for the Sharp SL-6000x 3 * 4 * Copyright (c) 2005 Dirk Opfer 5 * Copyright (c) 2008 Dmitry Baryshkov 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12#include <linux/kernel.h> 13#include <linux/module.h> 14#include <linux/power_supply.h> 15#include <linux/wm97xx.h> 16#include <linux/delay.h> 17#include <linux/spinlock.h> 18#include <linux/interrupt.h> 19#include <linux/gpio.h> 20 21#include <asm/mach-types.h> 22#include <mach/tosa.h> 23 24static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ 25static struct work_struct bat_work; 26 27struct tosa_bat { 28 int status; 29 struct power_supply *psy; 30 int full_chrg; 31 32 struct mutex work_lock; /* protects data */ 33 34 bool (*is_present)(struct tosa_bat *bat); 35 int gpio_full; 36 int gpio_charge_off; 37 38 int technology; 39 40 int gpio_bat; 41 int adc_bat; 42 int adc_bat_divider; 43 int bat_max; 44 int bat_min; 45 46 int gpio_temp; 47 int adc_temp; 48 int adc_temp_divider; 49}; 50 51static struct tosa_bat tosa_bat_main; 52static struct tosa_bat tosa_bat_jacket; 53 54static unsigned long tosa_read_bat(struct tosa_bat *bat) 55{ 56 unsigned long value = 0; 57 58 if (bat->gpio_bat < 0 || bat->adc_bat < 0) 59 return 0; 60 61 mutex_lock(&bat_lock); 62 gpio_set_value(bat->gpio_bat, 1); 63 msleep(5); 64 value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), 65 bat->adc_bat); 66 gpio_set_value(bat->gpio_bat, 0); 67 mutex_unlock(&bat_lock); 68 69 value = value * 1000000 / bat->adc_bat_divider; 70 71 return value; 72} 73 74static unsigned long tosa_read_temp(struct tosa_bat *bat) 75{ 76 unsigned long value = 0; 77 78 if (bat->gpio_temp < 0 || bat->adc_temp < 0) 79 return 0; 80 81 mutex_lock(&bat_lock); 82 gpio_set_value(bat->gpio_temp, 1); 83 msleep(5); 84 value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent), 85 bat->adc_temp); 86 gpio_set_value(bat->gpio_temp, 0); 87 mutex_unlock(&bat_lock); 88 89 value = value * 10000 / bat->adc_temp_divider; 90 91 return value; 92} 93 94static int tosa_bat_get_property(struct power_supply *psy, 95 enum power_supply_property psp, 96 union power_supply_propval *val) 97{ 98 int ret = 0; 99 struct tosa_bat *bat = power_supply_get_drvdata(psy); 100 101 if (bat->is_present && !bat->is_present(bat) 102 && psp != POWER_SUPPLY_PROP_PRESENT) { 103 return -ENODEV; 104 } 105 106 switch (psp) { 107 case POWER_SUPPLY_PROP_STATUS: 108 val->intval = bat->status; 109 break; 110 case POWER_SUPPLY_PROP_TECHNOLOGY: 111 val->intval = bat->technology; 112 break; 113 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 114 val->intval = tosa_read_bat(bat); 115 break; 116 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 117 if (bat->full_chrg == -1) 118 val->intval = bat->bat_max; 119 else 120 val->intval = bat->full_chrg; 121 break; 122 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 123 val->intval = bat->bat_max; 124 break; 125 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 126 val->intval = bat->bat_min; 127 break; 128 case POWER_SUPPLY_PROP_TEMP: 129 val->intval = tosa_read_temp(bat); 130 break; 131 case POWER_SUPPLY_PROP_PRESENT: 132 val->intval = bat->is_present ? bat->is_present(bat) : 1; 133 break; 134 default: 135 ret = -EINVAL; 136 break; 137 } 138 return ret; 139} 140 141static bool tosa_jacket_bat_is_present(struct tosa_bat *bat) 142{ 143 return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0; 144} 145 146static void tosa_bat_external_power_changed(struct power_supply *psy) 147{ 148 schedule_work(&bat_work); 149} 150 151static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) 152{ 153 pr_info("tosa_bat_gpio irq\n"); 154 schedule_work(&bat_work); 155 return IRQ_HANDLED; 156} 157 158static void tosa_bat_update(struct tosa_bat *bat) 159{ 160 int old; 161 struct power_supply *psy = bat->psy; 162 163 mutex_lock(&bat->work_lock); 164 165 old = bat->status; 166 167 if (bat->is_present && !bat->is_present(bat)) { 168 printk(KERN_NOTICE "%s not present\n", psy->desc->name); 169 bat->status = POWER_SUPPLY_STATUS_UNKNOWN; 170 bat->full_chrg = -1; 171 } else if (power_supply_am_i_supplied(psy)) { 172 if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { 173 gpio_set_value(bat->gpio_charge_off, 0); 174 mdelay(15); 175 } 176 177 if (gpio_get_value(bat->gpio_full)) { 178 if (old == POWER_SUPPLY_STATUS_CHARGING || 179 bat->full_chrg == -1) 180 bat->full_chrg = tosa_read_bat(bat); 181 182 gpio_set_value(bat->gpio_charge_off, 1); 183 bat->status = POWER_SUPPLY_STATUS_FULL; 184 } else { 185 gpio_set_value(bat->gpio_charge_off, 0); 186 bat->status = POWER_SUPPLY_STATUS_CHARGING; 187 } 188 } else { 189 gpio_set_value(bat->gpio_charge_off, 1); 190 bat->status = POWER_SUPPLY_STATUS_DISCHARGING; 191 } 192 193 if (old != bat->status) 194 power_supply_changed(psy); 195 196 mutex_unlock(&bat->work_lock); 197} 198 199static void tosa_bat_work(struct work_struct *work) 200{ 201 tosa_bat_update(&tosa_bat_main); 202 tosa_bat_update(&tosa_bat_jacket); 203} 204 205 206static enum power_supply_property tosa_bat_main_props[] = { 207 POWER_SUPPLY_PROP_STATUS, 208 POWER_SUPPLY_PROP_TECHNOLOGY, 209 POWER_SUPPLY_PROP_VOLTAGE_NOW, 210 POWER_SUPPLY_PROP_VOLTAGE_MAX, 211 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 212 POWER_SUPPLY_PROP_TEMP, 213 POWER_SUPPLY_PROP_PRESENT, 214}; 215 216static enum power_supply_property tosa_bat_bu_props[] = { 217 POWER_SUPPLY_PROP_STATUS, 218 POWER_SUPPLY_PROP_TECHNOLOGY, 219 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 220 POWER_SUPPLY_PROP_VOLTAGE_NOW, 221 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 222 POWER_SUPPLY_PROP_PRESENT, 223}; 224 225static const struct power_supply_desc tosa_bat_main_desc = { 226 .name = "main-battery", 227 .type = POWER_SUPPLY_TYPE_BATTERY, 228 .properties = tosa_bat_main_props, 229 .num_properties = ARRAY_SIZE(tosa_bat_main_props), 230 .get_property = tosa_bat_get_property, 231 .external_power_changed = tosa_bat_external_power_changed, 232 .use_for_apm = 1, 233}; 234 235static const struct power_supply_desc tosa_bat_jacket_desc = { 236 .name = "jacket-battery", 237 .type = POWER_SUPPLY_TYPE_BATTERY, 238 .properties = tosa_bat_main_props, 239 .num_properties = ARRAY_SIZE(tosa_bat_main_props), 240 .get_property = tosa_bat_get_property, 241 .external_power_changed = tosa_bat_external_power_changed, 242}; 243 244static const struct power_supply_desc tosa_bat_bu_desc = { 245 .name = "backup-battery", 246 .type = POWER_SUPPLY_TYPE_BATTERY, 247 .properties = tosa_bat_bu_props, 248 .num_properties = ARRAY_SIZE(tosa_bat_bu_props), 249 .get_property = tosa_bat_get_property, 250 .external_power_changed = tosa_bat_external_power_changed, 251}; 252 253static struct tosa_bat tosa_bat_main = { 254 .status = POWER_SUPPLY_STATUS_DISCHARGING, 255 .full_chrg = -1, 256 .psy = NULL, 257 258 .gpio_full = TOSA_GPIO_BAT0_CRG, 259 .gpio_charge_off = TOSA_GPIO_CHARGE_OFF, 260 261 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 262 263 .gpio_bat = TOSA_GPIO_BAT0_V_ON, 264 .adc_bat = WM97XX_AUX_ID3, 265 .adc_bat_divider = 414, 266 .bat_max = 4310000, 267 .bat_min = 1551 * 1000000 / 414, 268 269 .gpio_temp = TOSA_GPIO_BAT1_TH_ON, 270 .adc_temp = WM97XX_AUX_ID2, 271 .adc_temp_divider = 10000, 272}; 273 274static struct tosa_bat tosa_bat_jacket = { 275 .status = POWER_SUPPLY_STATUS_DISCHARGING, 276 .full_chrg = -1, 277 .psy = NULL, 278 279 .is_present = tosa_jacket_bat_is_present, 280 .gpio_full = TOSA_GPIO_BAT1_CRG, 281 .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC, 282 283 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 284 285 .gpio_bat = TOSA_GPIO_BAT1_V_ON, 286 .adc_bat = WM97XX_AUX_ID3, 287 .adc_bat_divider = 414, 288 .bat_max = 4310000, 289 .bat_min = 1551 * 1000000 / 414, 290 291 .gpio_temp = TOSA_GPIO_BAT0_TH_ON, 292 .adc_temp = WM97XX_AUX_ID2, 293 .adc_temp_divider = 10000, 294}; 295 296static struct tosa_bat tosa_bat_bu = { 297 .status = POWER_SUPPLY_STATUS_UNKNOWN, 298 .full_chrg = -1, 299 .psy = NULL, 300 301 .gpio_full = -1, 302 .gpio_charge_off = -1, 303 304 .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, 305 306 .gpio_bat = TOSA_GPIO_BU_CHRG_ON, 307 .adc_bat = WM97XX_AUX_ID4, 308 .adc_bat_divider = 1266, 309 310 .gpio_temp = -1, 311 .adc_temp = -1, 312 .adc_temp_divider = -1, 313}; 314 315static struct gpio tosa_bat_gpios[] = { 316 { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" }, 317 { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" }, 318 { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" }, 319 { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" }, 320 { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" }, 321 { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, 322 { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" }, 323 { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, 324 { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" }, 325 { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" }, 326 { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" }, 327 { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" }, 328 { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" }, 329}; 330 331#ifdef CONFIG_PM 332static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) 333{ 334 /* flush all pending status updates */ 335 flush_work(&bat_work); 336 return 0; 337} 338 339static int tosa_bat_resume(struct platform_device *dev) 340{ 341 /* things may have changed while we were away */ 342 schedule_work(&bat_work); 343 return 0; 344} 345#else 346#define tosa_bat_suspend NULL 347#define tosa_bat_resume NULL 348#endif 349 350static int tosa_bat_probe(struct platform_device *dev) 351{ 352 int ret; 353 struct power_supply_config main_psy_cfg = {}, 354 jacket_psy_cfg = {}, 355 bu_psy_cfg = {}; 356 357 if (!machine_is_tosa()) 358 return -ENODEV; 359 360 ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 361 if (ret) 362 return ret; 363 364 mutex_init(&tosa_bat_main.work_lock); 365 mutex_init(&tosa_bat_jacket.work_lock); 366 367 INIT_WORK(&bat_work, tosa_bat_work); 368 369 main_psy_cfg.drv_data = &tosa_bat_main; 370 tosa_bat_main.psy = power_supply_register(&dev->dev, 371 &tosa_bat_main_desc, 372 &main_psy_cfg); 373 if (IS_ERR(tosa_bat_main.psy)) { 374 ret = PTR_ERR(tosa_bat_main.psy); 375 goto err_psy_reg_main; 376 } 377 378 jacket_psy_cfg.drv_data = &tosa_bat_jacket; 379 tosa_bat_jacket.psy = power_supply_register(&dev->dev, 380 &tosa_bat_jacket_desc, 381 &jacket_psy_cfg); 382 if (IS_ERR(tosa_bat_jacket.psy)) { 383 ret = PTR_ERR(tosa_bat_jacket.psy); 384 goto err_psy_reg_jacket; 385 } 386 387 bu_psy_cfg.drv_data = &tosa_bat_bu; 388 tosa_bat_bu.psy = power_supply_register(&dev->dev, &tosa_bat_bu_desc, 389 &bu_psy_cfg); 390 if (IS_ERR(tosa_bat_bu.psy)) { 391 ret = PTR_ERR(tosa_bat_bu.psy); 392 goto err_psy_reg_bu; 393 } 394 395 ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), 396 tosa_bat_gpio_isr, 397 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 398 "main full", &tosa_bat_main); 399 if (ret) 400 goto err_req_main; 401 402 ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), 403 tosa_bat_gpio_isr, 404 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 405 "jacket full", &tosa_bat_jacket); 406 if (ret) 407 goto err_req_jacket; 408 409 ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), 410 tosa_bat_gpio_isr, 411 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 412 "jacket detect", &tosa_bat_jacket); 413 if (!ret) { 414 schedule_work(&bat_work); 415 return 0; 416 } 417 418 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); 419err_req_jacket: 420 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); 421err_req_main: 422 power_supply_unregister(tosa_bat_bu.psy); 423err_psy_reg_bu: 424 power_supply_unregister(tosa_bat_jacket.psy); 425err_psy_reg_jacket: 426 power_supply_unregister(tosa_bat_main.psy); 427err_psy_reg_main: 428 429 /* see comment in tosa_bat_remove */ 430 cancel_work_sync(&bat_work); 431 432 gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 433 return ret; 434} 435 436static int tosa_bat_remove(struct platform_device *dev) 437{ 438 free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket); 439 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); 440 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); 441 442 power_supply_unregister(tosa_bat_bu.psy); 443 power_supply_unregister(tosa_bat_jacket.psy); 444 power_supply_unregister(tosa_bat_main.psy); 445 446 /* 447 * Now cancel the bat_work. We won't get any more schedules, 448 * since all sources (isr and external_power_changed) are 449 * unregistered now. 450 */ 451 cancel_work_sync(&bat_work); 452 gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 453 return 0; 454} 455 456static struct platform_driver tosa_bat_driver = { 457 .driver.name = "wm97xx-battery", 458 .driver.owner = THIS_MODULE, 459 .probe = tosa_bat_probe, 460 .remove = tosa_bat_remove, 461 .suspend = tosa_bat_suspend, 462 .resume = tosa_bat_resume, 463}; 464 465module_platform_driver(tosa_bat_driver); 466 467MODULE_LICENSE("GPL"); 468MODULE_AUTHOR("Dmitry Baryshkov"); 469MODULE_DESCRIPTION("Tosa battery driver"); 470MODULE_ALIAS("platform:wm97xx-battery"); 471