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