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 = container_of(psy, struct tosa_bat, 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->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 struct tosa_bat tosa_bat_main = { 226 .status = POWER_SUPPLY_STATUS_DISCHARGING, 227 .full_chrg = -1, 228 .psy = { 229 .name = "main-battery", 230 .type = POWER_SUPPLY_TYPE_BATTERY, 231 .properties = tosa_bat_main_props, 232 .num_properties = ARRAY_SIZE(tosa_bat_main_props), 233 .get_property = tosa_bat_get_property, 234 .external_power_changed = tosa_bat_external_power_changed, 235 .use_for_apm = 1, 236 }, 237 238 .gpio_full = TOSA_GPIO_BAT0_CRG, 239 .gpio_charge_off = TOSA_GPIO_CHARGE_OFF, 240 241 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 242 243 .gpio_bat = TOSA_GPIO_BAT0_V_ON, 244 .adc_bat = WM97XX_AUX_ID3, 245 .adc_bat_divider = 414, 246 .bat_max = 4310000, 247 .bat_min = 1551 * 1000000 / 414, 248 249 .gpio_temp = TOSA_GPIO_BAT1_TH_ON, 250 .adc_temp = WM97XX_AUX_ID2, 251 .adc_temp_divider = 10000, 252}; 253 254static struct tosa_bat tosa_bat_jacket = { 255 .status = POWER_SUPPLY_STATUS_DISCHARGING, 256 .full_chrg = -1, 257 .psy = { 258 .name = "jacket-battery", 259 .type = POWER_SUPPLY_TYPE_BATTERY, 260 .properties = tosa_bat_main_props, 261 .num_properties = ARRAY_SIZE(tosa_bat_main_props), 262 .get_property = tosa_bat_get_property, 263 .external_power_changed = tosa_bat_external_power_changed, 264 }, 265 266 .is_present = tosa_jacket_bat_is_present, 267 .gpio_full = TOSA_GPIO_BAT1_CRG, 268 .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC, 269 270 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 271 272 .gpio_bat = TOSA_GPIO_BAT1_V_ON, 273 .adc_bat = WM97XX_AUX_ID3, 274 .adc_bat_divider = 414, 275 .bat_max = 4310000, 276 .bat_min = 1551 * 1000000 / 414, 277 278 .gpio_temp = TOSA_GPIO_BAT0_TH_ON, 279 .adc_temp = WM97XX_AUX_ID2, 280 .adc_temp_divider = 10000, 281}; 282 283static struct tosa_bat tosa_bat_bu = { 284 .status = POWER_SUPPLY_STATUS_UNKNOWN, 285 .full_chrg = -1, 286 287 .psy = { 288 .name = "backup-battery", 289 .type = POWER_SUPPLY_TYPE_BATTERY, 290 .properties = tosa_bat_bu_props, 291 .num_properties = ARRAY_SIZE(tosa_bat_bu_props), 292 .get_property = tosa_bat_get_property, 293 .external_power_changed = tosa_bat_external_power_changed, 294 }, 295 296 .gpio_full = -1, 297 .gpio_charge_off = -1, 298 299 .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, 300 301 .gpio_bat = TOSA_GPIO_BU_CHRG_ON, 302 .adc_bat = WM97XX_AUX_ID4, 303 .adc_bat_divider = 1266, 304 305 .gpio_temp = -1, 306 .adc_temp = -1, 307 .adc_temp_divider = -1, 308}; 309 310static struct gpio tosa_bat_gpios[] = { 311 { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" }, 312 { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" }, 313 { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" }, 314 { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" }, 315 { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" }, 316 { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, 317 { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" }, 318 { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, 319 { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" }, 320 { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" }, 321 { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" }, 322 { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" }, 323 { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" }, 324}; 325 326#ifdef CONFIG_PM 327static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) 328{ 329 /* flush all pending status updates */ 330 flush_work(&bat_work); 331 return 0; 332} 333 334static int tosa_bat_resume(struct platform_device *dev) 335{ 336 /* things may have changed while we were away */ 337 schedule_work(&bat_work); 338 return 0; 339} 340#else 341#define tosa_bat_suspend NULL 342#define tosa_bat_resume NULL 343#endif 344 345static int tosa_bat_probe(struct platform_device *dev) 346{ 347 int ret; 348 349 if (!machine_is_tosa()) 350 return -ENODEV; 351 352 ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 353 if (ret) 354 return ret; 355 356 mutex_init(&tosa_bat_main.work_lock); 357 mutex_init(&tosa_bat_jacket.work_lock); 358 359 INIT_WORK(&bat_work, tosa_bat_work); 360 361 ret = power_supply_register(&dev->dev, &tosa_bat_main.psy); 362 if (ret) 363 goto err_psy_reg_main; 364 ret = power_supply_register(&dev->dev, &tosa_bat_jacket.psy); 365 if (ret) 366 goto err_psy_reg_jacket; 367 ret = power_supply_register(&dev->dev, &tosa_bat_bu.psy); 368 if (ret) 369 goto err_psy_reg_bu; 370 371 ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), 372 tosa_bat_gpio_isr, 373 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 374 "main full", &tosa_bat_main); 375 if (ret) 376 goto err_req_main; 377 378 ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), 379 tosa_bat_gpio_isr, 380 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 381 "jacket full", &tosa_bat_jacket); 382 if (ret) 383 goto err_req_jacket; 384 385 ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), 386 tosa_bat_gpio_isr, 387 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 388 "jacket detect", &tosa_bat_jacket); 389 if (!ret) { 390 schedule_work(&bat_work); 391 return 0; 392 } 393 394 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); 395err_req_jacket: 396 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); 397err_req_main: 398 power_supply_unregister(&tosa_bat_bu.psy); 399err_psy_reg_bu: 400 power_supply_unregister(&tosa_bat_jacket.psy); 401err_psy_reg_jacket: 402 power_supply_unregister(&tosa_bat_main.psy); 403err_psy_reg_main: 404 405 /* see comment in tosa_bat_remove */ 406 cancel_work_sync(&bat_work); 407 408 gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 409 return ret; 410} 411 412static int tosa_bat_remove(struct platform_device *dev) 413{ 414 free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket); 415 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket); 416 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main); 417 418 power_supply_unregister(&tosa_bat_bu.psy); 419 power_supply_unregister(&tosa_bat_jacket.psy); 420 power_supply_unregister(&tosa_bat_main.psy); 421 422 /* 423 * Now cancel the bat_work. We won't get any more schedules, 424 * since all sources (isr and external_power_changed) are 425 * unregistered now. 426 */ 427 cancel_work_sync(&bat_work); 428 gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios)); 429 return 0; 430} 431 432static struct platform_driver tosa_bat_driver = { 433 .driver.name = "wm97xx-battery", 434 .driver.owner = THIS_MODULE, 435 .probe = tosa_bat_probe, 436 .remove = tosa_bat_remove, 437 .suspend = tosa_bat_suspend, 438 .resume = tosa_bat_resume, 439}; 440 441module_platform_driver(tosa_bat_driver); 442 443MODULE_LICENSE("GPL"); 444MODULE_AUTHOR("Dmitry Baryshkov"); 445MODULE_DESCRIPTION("Tosa battery driver"); 446MODULE_ALIAS("platform:wm97xx-battery"); 447