linux/drivers/power/supply/wm97xx_battery.c
<<
>>
Prefs
   1/*
   2 * Battery measurement code for WM97xx
   3 *
   4 * based on tosa_battery.c
   5 *
   6 * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
   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
  14#include <linux/init.h>
  15#include <linux/kernel.h>
  16#include <linux/module.h>
  17#include <linux/platform_device.h>
  18#include <linux/power_supply.h>
  19#include <linux/wm97xx.h>
  20#include <linux/spinlock.h>
  21#include <linux/interrupt.h>
  22#include <linux/gpio.h>
  23#include <linux/irq.h>
  24#include <linux/slab.h>
  25
  26static struct work_struct bat_work;
  27static DEFINE_MUTEX(work_lock);
  28static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
  29static enum power_supply_property *prop;
  30
  31static unsigned long wm97xx_read_bat(struct power_supply *bat_ps)
  32{
  33        struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps);
  34
  35        return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent),
  36                                        pdata->batt_aux) * pdata->batt_mult /
  37                                        pdata->batt_div;
  38}
  39
  40static unsigned long wm97xx_read_temp(struct power_supply *bat_ps)
  41{
  42        struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps);
  43
  44        return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent),
  45                                        pdata->temp_aux) * pdata->temp_mult /
  46                                        pdata->temp_div;
  47}
  48
  49static int wm97xx_bat_get_property(struct power_supply *bat_ps,
  50                            enum power_supply_property psp,
  51                            union power_supply_propval *val)
  52{
  53        struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps);
  54
  55        switch (psp) {
  56        case POWER_SUPPLY_PROP_STATUS:
  57                val->intval = bat_status;
  58                break;
  59        case POWER_SUPPLY_PROP_TECHNOLOGY:
  60                val->intval = pdata->batt_tech;
  61                break;
  62        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
  63                if (pdata->batt_aux >= 0)
  64                        val->intval = wm97xx_read_bat(bat_ps);
  65                else
  66                        return -EINVAL;
  67                break;
  68        case POWER_SUPPLY_PROP_TEMP:
  69                if (pdata->temp_aux >= 0)
  70                        val->intval = wm97xx_read_temp(bat_ps);
  71                else
  72                        return -EINVAL;
  73                break;
  74        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
  75                if (pdata->max_voltage >= 0)
  76                        val->intval = pdata->max_voltage;
  77                else
  78                        return -EINVAL;
  79                break;
  80        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
  81                if (pdata->min_voltage >= 0)
  82                        val->intval = pdata->min_voltage;
  83                else
  84                        return -EINVAL;
  85                break;
  86        case POWER_SUPPLY_PROP_PRESENT:
  87                val->intval = 1;
  88                break;
  89        default:
  90                return -EINVAL;
  91        }
  92        return 0;
  93}
  94
  95static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps)
  96{
  97        schedule_work(&bat_work);
  98}
  99
 100static void wm97xx_bat_update(struct power_supply *bat_ps)
 101{
 102        int old_status = bat_status;
 103        struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps);
 104
 105        mutex_lock(&work_lock);
 106
 107        bat_status = (pdata->charge_gpio >= 0) ?
 108                        (gpio_get_value(pdata->charge_gpio) ?
 109                        POWER_SUPPLY_STATUS_DISCHARGING :
 110                        POWER_SUPPLY_STATUS_CHARGING) :
 111                        POWER_SUPPLY_STATUS_UNKNOWN;
 112
 113        if (old_status != bat_status) {
 114                pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status,
 115                                        bat_status);
 116                power_supply_changed(bat_ps);
 117        }
 118
 119        mutex_unlock(&work_lock);
 120}
 121
 122static struct power_supply *bat_psy;
 123static struct power_supply_desc bat_psy_desc = {
 124        .type                   = POWER_SUPPLY_TYPE_BATTERY,
 125        .get_property           = wm97xx_bat_get_property,
 126        .external_power_changed = wm97xx_bat_external_power_changed,
 127        .use_for_apm            = 1,
 128};
 129
 130static void wm97xx_bat_work(struct work_struct *work)
 131{
 132        wm97xx_bat_update(bat_psy);
 133}
 134
 135static irqreturn_t wm97xx_chrg_irq(int irq, void *data)
 136{
 137        schedule_work(&bat_work);
 138        return IRQ_HANDLED;
 139}
 140
 141#ifdef CONFIG_PM
 142static int wm97xx_bat_suspend(struct device *dev)
 143{
 144        flush_work(&bat_work);
 145        return 0;
 146}
 147
 148static int wm97xx_bat_resume(struct device *dev)
 149{
 150        schedule_work(&bat_work);
 151        return 0;
 152}
 153
 154static const struct dev_pm_ops wm97xx_bat_pm_ops = {
 155        .suspend        = wm97xx_bat_suspend,
 156        .resume         = wm97xx_bat_resume,
 157};
 158#endif
 159
 160static int wm97xx_bat_probe(struct platform_device *dev)
 161{
 162        int ret = 0;
 163        int props = 1;  /* POWER_SUPPLY_PROP_PRESENT */
 164        int i = 0;
 165        struct wm97xx_batt_pdata *pdata = dev->dev.platform_data;
 166        struct power_supply_config cfg = {};
 167
 168        if (!pdata) {
 169                dev_err(&dev->dev, "No platform data supplied\n");
 170                return -EINVAL;
 171        }
 172
 173        cfg.drv_data = pdata;
 174
 175        if (dev->id != -1)
 176                return -EINVAL;
 177
 178        if (gpio_is_valid(pdata->charge_gpio)) {
 179                ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
 180                if (ret)
 181                        goto err;
 182                ret = gpio_direction_input(pdata->charge_gpio);
 183                if (ret)
 184                        goto err2;
 185                ret = request_irq(gpio_to_irq(pdata->charge_gpio),
 186                                wm97xx_chrg_irq, 0,
 187                                "AC Detect", dev);
 188                if (ret)
 189                        goto err2;
 190                props++;        /* POWER_SUPPLY_PROP_STATUS */
 191        }
 192
 193        if (pdata->batt_tech >= 0)
 194                props++;        /* POWER_SUPPLY_PROP_TECHNOLOGY */
 195        if (pdata->temp_aux >= 0)
 196                props++;        /* POWER_SUPPLY_PROP_TEMP */
 197        if (pdata->batt_aux >= 0)
 198                props++;        /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
 199        if (pdata->max_voltage >= 0)
 200                props++;        /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
 201        if (pdata->min_voltage >= 0)
 202                props++;        /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
 203
 204        prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
 205        if (!prop) {
 206                ret = -ENOMEM;
 207                goto err3;
 208        }
 209
 210        prop[i++] = POWER_SUPPLY_PROP_PRESENT;
 211        if (pdata->charge_gpio >= 0)
 212                prop[i++] = POWER_SUPPLY_PROP_STATUS;
 213        if (pdata->batt_tech >= 0)
 214                prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
 215        if (pdata->temp_aux >= 0)
 216                prop[i++] = POWER_SUPPLY_PROP_TEMP;
 217        if (pdata->batt_aux >= 0)
 218                prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
 219        if (pdata->max_voltage >= 0)
 220                prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
 221        if (pdata->min_voltage >= 0)
 222                prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
 223
 224        INIT_WORK(&bat_work, wm97xx_bat_work);
 225
 226        if (!pdata->batt_name) {
 227                dev_info(&dev->dev, "Please consider setting proper battery "
 228                                "name in platform definition file, falling "
 229                                "back to name \"wm97xx-batt\"\n");
 230                bat_psy_desc.name = "wm97xx-batt";
 231        } else
 232                bat_psy_desc.name = pdata->batt_name;
 233
 234        bat_psy_desc.properties = prop;
 235        bat_psy_desc.num_properties = props;
 236
 237        bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, &cfg);
 238        if (!IS_ERR(bat_psy)) {
 239                schedule_work(&bat_work);
 240        } else {
 241                ret = PTR_ERR(bat_psy);
 242                goto err4;
 243        }
 244
 245        return 0;
 246err4:
 247        kfree(prop);
 248err3:
 249        if (gpio_is_valid(pdata->charge_gpio))
 250                free_irq(gpio_to_irq(pdata->charge_gpio), dev);
 251err2:
 252        if (gpio_is_valid(pdata->charge_gpio))
 253                gpio_free(pdata->charge_gpio);
 254err:
 255        return ret;
 256}
 257
 258static int wm97xx_bat_remove(struct platform_device *dev)
 259{
 260        struct wm97xx_batt_pdata *pdata = dev->dev.platform_data;
 261
 262        if (pdata && gpio_is_valid(pdata->charge_gpio)) {
 263                free_irq(gpio_to_irq(pdata->charge_gpio), dev);
 264                gpio_free(pdata->charge_gpio);
 265        }
 266        cancel_work_sync(&bat_work);
 267        power_supply_unregister(bat_psy);
 268        kfree(prop);
 269        return 0;
 270}
 271
 272static struct platform_driver wm97xx_bat_driver = {
 273        .driver = {
 274                .name   = "wm97xx-battery",
 275#ifdef CONFIG_PM
 276                .pm     = &wm97xx_bat_pm_ops,
 277#endif
 278        },
 279        .probe          = wm97xx_bat_probe,
 280        .remove         = wm97xx_bat_remove,
 281};
 282
 283module_platform_driver(wm97xx_bat_driver);
 284
 285MODULE_LICENSE("GPL");
 286MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
 287MODULE_DESCRIPTION("WM97xx battery driver");
 288