1/* 2 * Battery and Power Management code for the Sharp SL-5x00 3 * 4 * Copyright (C) 2009 Thomas Kunze 5 * 6 * based on tosa_battery.c 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 * 12 */ 13#include <linux/kernel.h> 14#include <linux/module.h> 15#include <linux/power_supply.h> 16#include <linux/delay.h> 17#include <linux/spinlock.h> 18#include <linux/interrupt.h> 19#include <linux/gpio.h> 20#include <linux/mfd/ucb1x00.h> 21 22#include <asm/mach/sharpsl_param.h> 23#include <asm/mach-types.h> 24#include <mach/collie.h> 25 26static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ 27static struct work_struct bat_work; 28static struct ucb1x00 *ucb; 29 30struct collie_bat { 31 int status; 32 struct power_supply psy; 33 int full_chrg; 34 35 struct mutex work_lock; /* protects data */ 36 37 bool (*is_present)(struct collie_bat *bat); 38 int gpio_full; 39 int gpio_charge_on; 40 41 int technology; 42 43 int gpio_bat; 44 int adc_bat; 45 int adc_bat_divider; 46 int bat_max; 47 int bat_min; 48 49 int gpio_temp; 50 int adc_temp; 51 int adc_temp_divider; 52}; 53 54static struct collie_bat collie_bat_main; 55 56static unsigned long collie_read_bat(struct collie_bat *bat) 57{ 58 unsigned long value = 0; 59 60 if (bat->gpio_bat < 0 || bat->adc_bat < 0) 61 return 0; 62 mutex_lock(&bat_lock); 63 gpio_set_value(bat->gpio_bat, 1); 64 msleep(5); 65 ucb1x00_adc_enable(ucb); 66 value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC); 67 ucb1x00_adc_disable(ucb); 68 gpio_set_value(bat->gpio_bat, 0); 69 mutex_unlock(&bat_lock); 70 value = value * 1000000 / bat->adc_bat_divider; 71 72 return value; 73} 74 75static unsigned long collie_read_temp(struct collie_bat *bat) 76{ 77 unsigned long value = 0; 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 ucb1x00_adc_enable(ucb); 85 value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC); 86 ucb1x00_adc_disable(ucb); 87 gpio_set_value(bat->gpio_temp, 0); 88 mutex_unlock(&bat_lock); 89 90 value = value * 10000 / bat->adc_temp_divider; 91 92 return value; 93} 94 95static int collie_bat_get_property(struct power_supply *psy, 96 enum power_supply_property psp, 97 union power_supply_propval *val) 98{ 99 int ret = 0; 100 struct collie_bat *bat = container_of(psy, struct collie_bat, psy); 101 102 if (bat->is_present && !bat->is_present(bat) 103 && psp != POWER_SUPPLY_PROP_PRESENT) { 104 return -ENODEV; 105 } 106 107 switch (psp) { 108 case POWER_SUPPLY_PROP_STATUS: 109 val->intval = bat->status; 110 break; 111 case POWER_SUPPLY_PROP_TECHNOLOGY: 112 val->intval = bat->technology; 113 break; 114 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 115 val->intval = collie_read_bat(bat); 116 break; 117 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 118 if (bat->full_chrg == -1) 119 val->intval = bat->bat_max; 120 else 121 val->intval = bat->full_chrg; 122 break; 123 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 124 val->intval = bat->bat_max; 125 break; 126 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: 127 val->intval = bat->bat_min; 128 break; 129 case POWER_SUPPLY_PROP_TEMP: 130 val->intval = collie_read_temp(bat); 131 break; 132 case POWER_SUPPLY_PROP_PRESENT: 133 val->intval = bat->is_present ? bat->is_present(bat) : 1; 134 break; 135 default: 136 ret = -EINVAL; 137 break; 138 } 139 return ret; 140} 141 142static void collie_bat_external_power_changed(struct power_supply *psy) 143{ 144 schedule_work(&bat_work); 145} 146 147static irqreturn_t collie_bat_gpio_isr(int irq, void *data) 148{ 149 pr_info("collie_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq))); 150 schedule_work(&bat_work); 151 return IRQ_HANDLED; 152} 153 154static void collie_bat_update(struct collie_bat *bat) 155{ 156 int old; 157 struct power_supply *psy = &bat->psy; 158 159 mutex_lock(&bat->work_lock); 160 161 old = bat->status; 162 163 if (bat->is_present && !bat->is_present(bat)) { 164 printk(KERN_NOTICE "%s not present\n", psy->name); 165 bat->status = POWER_SUPPLY_STATUS_UNKNOWN; 166 bat->full_chrg = -1; 167 } else if (power_supply_am_i_supplied(psy)) { 168 if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { 169 gpio_set_value(bat->gpio_charge_on, 1); 170 mdelay(15); 171 } 172 173 if (gpio_get_value(bat->gpio_full)) { 174 if (old == POWER_SUPPLY_STATUS_CHARGING || 175 bat->full_chrg == -1) 176 bat->full_chrg = collie_read_bat(bat); 177 178 gpio_set_value(bat->gpio_charge_on, 0); 179 bat->status = POWER_SUPPLY_STATUS_FULL; 180 } else { 181 gpio_set_value(bat->gpio_charge_on, 1); 182 bat->status = POWER_SUPPLY_STATUS_CHARGING; 183 } 184 } else { 185 gpio_set_value(bat->gpio_charge_on, 0); 186 bat->status = POWER_SUPPLY_STATUS_DISCHARGING; 187 } 188 189 if (old != bat->status) 190 power_supply_changed(psy); 191 192 mutex_unlock(&bat->work_lock); 193} 194 195static void collie_bat_work(struct work_struct *work) 196{ 197 collie_bat_update(&collie_bat_main); 198} 199 200 201static enum power_supply_property collie_bat_main_props[] = { 202 POWER_SUPPLY_PROP_STATUS, 203 POWER_SUPPLY_PROP_TECHNOLOGY, 204 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 205 POWER_SUPPLY_PROP_VOLTAGE_NOW, 206 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 207 POWER_SUPPLY_PROP_VOLTAGE_MAX, 208 POWER_SUPPLY_PROP_PRESENT, 209 POWER_SUPPLY_PROP_TEMP, 210}; 211 212static enum power_supply_property collie_bat_bu_props[] = { 213 POWER_SUPPLY_PROP_STATUS, 214 POWER_SUPPLY_PROP_TECHNOLOGY, 215 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 216 POWER_SUPPLY_PROP_VOLTAGE_NOW, 217 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 218 POWER_SUPPLY_PROP_VOLTAGE_MAX, 219 POWER_SUPPLY_PROP_PRESENT, 220}; 221 222static struct collie_bat collie_bat_main = { 223 .status = POWER_SUPPLY_STATUS_DISCHARGING, 224 .full_chrg = -1, 225 .psy = { 226 .name = "main-battery", 227 .type = POWER_SUPPLY_TYPE_BATTERY, 228 .properties = collie_bat_main_props, 229 .num_properties = ARRAY_SIZE(collie_bat_main_props), 230 .get_property = collie_bat_get_property, 231 .external_power_changed = collie_bat_external_power_changed, 232 .use_for_apm = 1, 233 }, 234 235 .gpio_full = COLLIE_GPIO_CO, 236 .gpio_charge_on = COLLIE_GPIO_CHARGE_ON, 237 238 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 239 240 .gpio_bat = COLLIE_GPIO_MBAT_ON, 241 .adc_bat = UCB_ADC_INP_AD1, 242 .adc_bat_divider = 155, 243 .bat_max = 4310000, 244 .bat_min = 1551 * 1000000 / 414, 245 246 .gpio_temp = COLLIE_GPIO_TMP_ON, 247 .adc_temp = UCB_ADC_INP_AD0, 248 .adc_temp_divider = 10000, 249}; 250 251static struct collie_bat collie_bat_bu = { 252 .status = POWER_SUPPLY_STATUS_UNKNOWN, 253 .full_chrg = -1, 254 255 .psy = { 256 .name = "backup-battery", 257 .type = POWER_SUPPLY_TYPE_BATTERY, 258 .properties = collie_bat_bu_props, 259 .num_properties = ARRAY_SIZE(collie_bat_bu_props), 260 .get_property = collie_bat_get_property, 261 .external_power_changed = collie_bat_external_power_changed, 262 }, 263 264 .gpio_full = -1, 265 .gpio_charge_on = -1, 266 267 .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, 268 269 .gpio_bat = COLLIE_GPIO_BBAT_ON, 270 .adc_bat = UCB_ADC_INP_AD1, 271 .adc_bat_divider = 155, 272 .bat_max = 3000000, 273 .bat_min = 1900000, 274 275 .gpio_temp = -1, 276 .adc_temp = -1, 277 .adc_temp_divider = -1, 278}; 279 280static struct { 281 int gpio; 282 char *name; 283 bool output; 284 int value; 285} gpios[] = { 286 { COLLIE_GPIO_CO, "main battery full", 0, 0 }, 287 { COLLIE_GPIO_MAIN_BAT_LOW, "main battery low", 0, 0 }, 288 { COLLIE_GPIO_CHARGE_ON, "main charge on", 1, 0 }, 289 { COLLIE_GPIO_MBAT_ON, "main battery", 1, 0 }, 290 { COLLIE_GPIO_TMP_ON, "main battery temp", 1, 0 }, 291 { COLLIE_GPIO_BBAT_ON, "backup battery", 1, 0 }, 292}; 293 294#ifdef CONFIG_PM 295static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state) 296{ 297 /* flush all pending status updates */ 298 flush_scheduled_work(); 299 return 0; 300} 301 302static int collie_bat_resume(struct ucb1x00_dev *dev) 303{ 304 /* things may have changed while we were away */ 305 schedule_work(&bat_work); 306 return 0; 307} 308#else 309#define collie_bat_suspend NULL 310#define collie_bat_resume NULL 311#endif 312 313static int __devinit collie_bat_probe(struct ucb1x00_dev *dev) 314{ 315 int ret; 316 int i; 317 318 if (!machine_is_collie()) 319 return -ENODEV; 320 321 ucb = dev->ucb; 322 323 for (i = 0; i < ARRAY_SIZE(gpios); i++) { 324 ret = gpio_request(gpios[i].gpio, gpios[i].name); 325 if (ret) { 326 i--; 327 goto err_gpio; 328 } 329 330 if (gpios[i].output) 331 ret = gpio_direction_output(gpios[i].gpio, 332 gpios[i].value); 333 else 334 ret = gpio_direction_input(gpios[i].gpio); 335 336 if (ret) 337 goto err_gpio; 338 } 339 340 mutex_init(&collie_bat_main.work_lock); 341 342 INIT_WORK(&bat_work, collie_bat_work); 343 344 ret = power_supply_register(&dev->ucb->dev, &collie_bat_main.psy); 345 if (ret) 346 goto err_psy_reg_main; 347 ret = power_supply_register(&dev->ucb->dev, &collie_bat_bu.psy); 348 if (ret) 349 goto err_psy_reg_bu; 350 351 ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), 352 collie_bat_gpio_isr, 353 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 354 "main full", &collie_bat_main); 355 if (!ret) { 356 schedule_work(&bat_work); 357 return 0; 358 } 359 power_supply_unregister(&collie_bat_bu.psy); 360err_psy_reg_bu: 361 power_supply_unregister(&collie_bat_main.psy); 362err_psy_reg_main: 363 364 /* see comment in collie_bat_remove */ 365 flush_scheduled_work(); 366 367 i--; 368err_gpio: 369 for (; i >= 0; i--) 370 gpio_free(gpios[i].gpio); 371 372 return ret; 373} 374 375static void __devexit collie_bat_remove(struct ucb1x00_dev *dev) 376{ 377 int i; 378 379 free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); 380 381 power_supply_unregister(&collie_bat_bu.psy); 382 power_supply_unregister(&collie_bat_main.psy); 383 384 /* 385 * now flush all pending work. 386 * we won't get any more schedules, since all 387 * sources (isr and external_power_changed) 388 * are unregistered now. 389 */ 390 flush_scheduled_work(); 391 392 for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--) 393 gpio_free(gpios[i].gpio); 394} 395 396static struct ucb1x00_driver collie_bat_driver = { 397 .add = collie_bat_probe, 398 .remove = __devexit_p(collie_bat_remove), 399 .suspend = collie_bat_suspend, 400 .resume = collie_bat_resume, 401}; 402 403static int __init collie_bat_init(void) 404{ 405 return ucb1x00_register_driver(&collie_bat_driver); 406} 407 408static void __exit collie_bat_exit(void) 409{ 410 ucb1x00_unregister_driver(&collie_bat_driver); 411} 412 413module_init(collie_bat_init); 414module_exit(collie_bat_exit); 415 416MODULE_LICENSE("GPL"); 417MODULE_AUTHOR("Thomas Kunze"); 418MODULE_DESCRIPTION("Collie battery driver"); 419