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 26 static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ 27 static struct work_struct bat_work; 28 static struct ucb1x00 *ucb; 29 30 struct 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 54 static struct collie_bat collie_bat_main; 55 56 static 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 75 static 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 95 static 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 = power_supply_get_drvdata(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 142 static void collie_bat_external_power_changed(struct power_supply *psy) 143 { 144 schedule_work(&bat_work); 145 } 146 147 static irqreturn_t collie_bat_gpio_isr(int irq, void *data) 148 { 149 pr_info("collie_bat_gpio irq\n"); 150 schedule_work(&bat_work); 151 return IRQ_HANDLED; 152 } 153 154 static 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->desc->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 195 static void collie_bat_work(struct work_struct *work) 196 { 197 collie_bat_update(&collie_bat_main); 198 } 199 200 201 static 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 212 static 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 222 static const struct power_supply_desc collie_bat_main_desc = { 223 .name = "main-battery", 224 .type = POWER_SUPPLY_TYPE_BATTERY, 225 .properties = collie_bat_main_props, 226 .num_properties = ARRAY_SIZE(collie_bat_main_props), 227 .get_property = collie_bat_get_property, 228 .external_power_changed = collie_bat_external_power_changed, 229 .use_for_apm = 1, 230 }; 231 232 static struct collie_bat collie_bat_main = { 233 .status = POWER_SUPPLY_STATUS_DISCHARGING, 234 .full_chrg = -1, 235 .psy = NULL, 236 237 .gpio_full = COLLIE_GPIO_CO, 238 .gpio_charge_on = COLLIE_GPIO_CHARGE_ON, 239 240 .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, 241 242 .gpio_bat = COLLIE_GPIO_MBAT_ON, 243 .adc_bat = UCB_ADC_INP_AD1, 244 .adc_bat_divider = 155, 245 .bat_max = 4310000, 246 .bat_min = 1551 * 1000000 / 414, 247 248 .gpio_temp = COLLIE_GPIO_TMP_ON, 249 .adc_temp = UCB_ADC_INP_AD0, 250 .adc_temp_divider = 10000, 251 }; 252 253 static const struct power_supply_desc collie_bat_bu_desc = { 254 .name = "backup-battery", 255 .type = POWER_SUPPLY_TYPE_BATTERY, 256 .properties = collie_bat_bu_props, 257 .num_properties = ARRAY_SIZE(collie_bat_bu_props), 258 .get_property = collie_bat_get_property, 259 .external_power_changed = collie_bat_external_power_changed, 260 }; 261 262 static struct collie_bat collie_bat_bu = { 263 .status = POWER_SUPPLY_STATUS_UNKNOWN, 264 .full_chrg = -1, 265 .psy = NULL, 266 267 .gpio_full = -1, 268 .gpio_charge_on = -1, 269 270 .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, 271 272 .gpio_bat = COLLIE_GPIO_BBAT_ON, 273 .adc_bat = UCB_ADC_INP_AD1, 274 .adc_bat_divider = 155, 275 .bat_max = 3000000, 276 .bat_min = 1900000, 277 278 .gpio_temp = -1, 279 .adc_temp = -1, 280 .adc_temp_divider = -1, 281 }; 282 283 static struct gpio collie_batt_gpios[] = { 284 { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" }, 285 { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" }, 286 { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" }, 287 { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" }, 288 { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" }, 289 { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" }, 290 }; 291 292 #ifdef CONFIG_PM 293 static int wakeup_enabled; 294 295 static int collie_bat_suspend(struct ucb1x00_dev *dev) 296 { 297 /* flush all pending status updates */ 298 flush_work(&bat_work); 299 300 if (device_may_wakeup(&dev->ucb->dev) && 301 collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING) 302 wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); 303 else 304 wakeup_enabled = 0; 305 306 return 0; 307 } 308 309 static int collie_bat_resume(struct ucb1x00_dev *dev) 310 { 311 if (wakeup_enabled) 312 disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO)); 313 314 /* things may have changed while we were away */ 315 schedule_work(&bat_work); 316 return 0; 317 } 318 #else 319 #define collie_bat_suspend NULL 320 #define collie_bat_resume NULL 321 #endif 322 323 static int collie_bat_probe(struct ucb1x00_dev *dev) 324 { 325 int ret; 326 struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {}; 327 328 if (!machine_is_collie()) 329 return -ENODEV; 330 331 ucb = dev->ucb; 332 333 ret = gpio_request_array(collie_batt_gpios, 334 ARRAY_SIZE(collie_batt_gpios)); 335 if (ret) 336 return ret; 337 338 mutex_init(&collie_bat_main.work_lock); 339 340 INIT_WORK(&bat_work, collie_bat_work); 341 342 psy_main_cfg.drv_data = &collie_bat_main; 343 collie_bat_main.psy = power_supply_register(&dev->ucb->dev, 344 &collie_bat_main_desc, 345 &psy_main_cfg); 346 if (IS_ERR(collie_bat_main.psy)) { 347 ret = PTR_ERR(collie_bat_main.psy); 348 goto err_psy_reg_main; 349 } 350 351 psy_bu_cfg.drv_data = &collie_bat_bu; 352 collie_bat_bu.psy = power_supply_register(&dev->ucb->dev, 353 &collie_bat_bu_desc, 354 &psy_bu_cfg); 355 if (IS_ERR(collie_bat_bu.psy)) { 356 ret = PTR_ERR(collie_bat_bu.psy); 357 goto err_psy_reg_bu; 358 } 359 360 ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO), 361 collie_bat_gpio_isr, 362 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 363 "main full", &collie_bat_main); 364 if (ret) 365 goto err_irq; 366 367 device_init_wakeup(&ucb->dev, 1); 368 schedule_work(&bat_work); 369 370 return 0; 371 372 err_irq: 373 power_supply_unregister(collie_bat_bu.psy); 374 err_psy_reg_bu: 375 power_supply_unregister(collie_bat_main.psy); 376 err_psy_reg_main: 377 378 /* see comment in collie_bat_remove */ 379 cancel_work_sync(&bat_work); 380 gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); 381 return ret; 382 } 383 384 static void collie_bat_remove(struct ucb1x00_dev *dev) 385 { 386 free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); 387 388 power_supply_unregister(collie_bat_bu.psy); 389 power_supply_unregister(collie_bat_main.psy); 390 391 /* 392 * Now cancel the bat_work. We won't get any more schedules, 393 * since all sources (isr and external_power_changed) are 394 * unregistered now. 395 */ 396 cancel_work_sync(&bat_work); 397 gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios)); 398 } 399 400 static struct ucb1x00_driver collie_bat_driver = { 401 .add = collie_bat_probe, 402 .remove = collie_bat_remove, 403 .suspend = collie_bat_suspend, 404 .resume = collie_bat_resume, 405 }; 406 407 static int __init collie_bat_init(void) 408 { 409 return ucb1x00_register_driver(&collie_bat_driver); 410 } 411 412 static void __exit collie_bat_exit(void) 413 { 414 ucb1x00_unregister_driver(&collie_bat_driver); 415 } 416 417 module_init(collie_bat_init); 418 module_exit(collie_bat_exit); 419 420 MODULE_LICENSE("GPL"); 421 MODULE_AUTHOR("Thomas Kunze"); 422 MODULE_DESCRIPTION("Collie battery driver"); 423