linux/drivers/power/supply/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 = 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
 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\n");
 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->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
 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 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
 232static 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
 253static 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
 262static 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
 283static 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
 293static int wakeup_enabled;
 294
 295static 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
 309static 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
 323static 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
 372err_irq:
 373        power_supply_unregister(collie_bat_bu.psy);
 374err_psy_reg_bu:
 375        power_supply_unregister(collie_bat_main.psy);
 376err_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
 384static 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
 400static 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
 407static int __init collie_bat_init(void)
 408{
 409        return ucb1x00_register_driver(&collie_bat_driver);
 410}
 411
 412static void __exit collie_bat_exit(void)
 413{
 414        ucb1x00_unregister_driver(&collie_bat_driver);
 415}
 416
 417module_init(collie_bat_init);
 418module_exit(collie_bat_exit);
 419
 420MODULE_LICENSE("GPL");
 421MODULE_AUTHOR("Thomas Kunze");
 422MODULE_DESCRIPTION("Collie battery driver");
 423