linux/drivers/power/collie_battery.c
<<
>>
Prefs
   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_work_sync(&bat_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        cancel_work_sync(&bat_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 cancel the bat_work.  We won't get any more schedules,
 386         * since all sources (isr and external_power_changed) are
 387         * unregistered now.
 388         */
 389        cancel_work_sync(&bat_work);
 390
 391        for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--)
 392                gpio_free(gpios[i].gpio);
 393}
 394
 395static struct ucb1x00_driver collie_bat_driver = {
 396        .add            = collie_bat_probe,
 397        .remove         = __devexit_p(collie_bat_remove),
 398        .suspend        = collie_bat_suspend,
 399        .resume         = collie_bat_resume,
 400};
 401
 402static int __init collie_bat_init(void)
 403{
 404        return ucb1x00_register_driver(&collie_bat_driver);
 405}
 406
 407static void __exit collie_bat_exit(void)
 408{
 409        ucb1x00_unregister_driver(&collie_bat_driver);
 410}
 411
 412module_init(collie_bat_init);
 413module_exit(collie_bat_exit);
 414
 415MODULE_LICENSE("GPL");
 416MODULE_AUTHOR("Thomas Kunze");
 417MODULE_DESCRIPTION("Collie battery driver");
 418