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\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->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 gpio collie_batt_gpios[] = {
 281        { COLLIE_GPIO_CO,           GPIOF_IN,           "main battery full" },
 282        { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN,           "main battery low" },
 283        { COLLIE_GPIO_CHARGE_ON,    GPIOF_OUT_INIT_LOW, "main charge on" },
 284        { COLLIE_GPIO_MBAT_ON,      GPIOF_OUT_INIT_LOW, "main battery" },
 285        { COLLIE_GPIO_TMP_ON,       GPIOF_OUT_INIT_LOW, "main battery temp" },
 286        { COLLIE_GPIO_BBAT_ON,      GPIOF_OUT_INIT_LOW, "backup battery" },
 287};
 288
 289#ifdef CONFIG_PM
 290static int collie_bat_suspend(struct ucb1x00_dev *dev)
 291{
 292        /* flush all pending status updates */
 293        flush_work(&bat_work);
 294        return 0;
 295}
 296
 297static int collie_bat_resume(struct ucb1x00_dev *dev)
 298{
 299        /* things may have changed while we were away */
 300        schedule_work(&bat_work);
 301        return 0;
 302}
 303#else
 304#define collie_bat_suspend NULL
 305#define collie_bat_resume NULL
 306#endif
 307
 308static int collie_bat_probe(struct ucb1x00_dev *dev)
 309{
 310        int ret;
 311
 312        if (!machine_is_collie())
 313                return -ENODEV;
 314
 315        ucb = dev->ucb;
 316
 317        ret = gpio_request_array(collie_batt_gpios,
 318                                 ARRAY_SIZE(collie_batt_gpios));
 319        if (ret)
 320                return ret;
 321
 322        mutex_init(&collie_bat_main.work_lock);
 323
 324        INIT_WORK(&bat_work, collie_bat_work);
 325
 326        ret = power_supply_register(&dev->ucb->dev, &collie_bat_main.psy);
 327        if (ret)
 328                goto err_psy_reg_main;
 329        ret = power_supply_register(&dev->ucb->dev, &collie_bat_bu.psy);
 330        if (ret)
 331                goto err_psy_reg_bu;
 332
 333        ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO),
 334                                collie_bat_gpio_isr,
 335                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
 336                                "main full", &collie_bat_main);
 337        if (!ret) {
 338                schedule_work(&bat_work);
 339                return 0;
 340        }
 341        power_supply_unregister(&collie_bat_bu.psy);
 342err_psy_reg_bu:
 343        power_supply_unregister(&collie_bat_main.psy);
 344err_psy_reg_main:
 345
 346        /* see comment in collie_bat_remove */
 347        cancel_work_sync(&bat_work);
 348        gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
 349        return ret;
 350}
 351
 352static void collie_bat_remove(struct ucb1x00_dev *dev)
 353{
 354        free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main);
 355
 356        power_supply_unregister(&collie_bat_bu.psy);
 357        power_supply_unregister(&collie_bat_main.psy);
 358
 359        /*
 360         * Now cancel the bat_work.  We won't get any more schedules,
 361         * since all sources (isr and external_power_changed) are
 362         * unregistered now.
 363         */
 364        cancel_work_sync(&bat_work);
 365        gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
 366}
 367
 368static struct ucb1x00_driver collie_bat_driver = {
 369        .add            = collie_bat_probe,
 370        .remove         = collie_bat_remove,
 371        .suspend        = collie_bat_suspend,
 372        .resume         = collie_bat_resume,
 373};
 374
 375static int __init collie_bat_init(void)
 376{
 377        return ucb1x00_register_driver(&collie_bat_driver);
 378}
 379
 380static void __exit collie_bat_exit(void)
 381{
 382        ucb1x00_unregister_driver(&collie_bat_driver);
 383}
 384
 385module_init(collie_bat_init);
 386module_exit(collie_bat_exit);
 387
 388MODULE_LICENSE("GPL");
 389MODULE_AUTHOR("Thomas Kunze");
 390MODULE_DESCRIPTION("Collie battery driver");
 391
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.