linux/drivers/power/supply/twl4030_madc_battery.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Dumb driver for LiIon batteries using TWL4030 madc.
   4 *
   5 * Copyright 2013 Golden Delicious Computers
   6 * Lukas Märdian <lukas@goldelico.com>
   7 *
   8 * Based on dumb driver for gta01 battery
   9 * Copyright 2009 Openmoko, Inc
  10 * Balaji Rao <balajirrao@openmoko.org>
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/param.h>
  15#include <linux/delay.h>
  16#include <linux/workqueue.h>
  17#include <linux/platform_device.h>
  18#include <linux/power_supply.h>
  19#include <linux/slab.h>
  20#include <linux/sort.h>
  21#include <linux/power/twl4030_madc_battery.h>
  22#include <linux/iio/consumer.h>
  23
  24struct twl4030_madc_battery {
  25        struct power_supply *psy;
  26        struct twl4030_madc_bat_platform_data *pdata;
  27        struct iio_channel *channel_temp;
  28        struct iio_channel *channel_ichg;
  29        struct iio_channel *channel_vbat;
  30};
  31
  32static enum power_supply_property twl4030_madc_bat_props[] = {
  33        POWER_SUPPLY_PROP_PRESENT,
  34        POWER_SUPPLY_PROP_STATUS,
  35        POWER_SUPPLY_PROP_TECHNOLOGY,
  36        POWER_SUPPLY_PROP_VOLTAGE_NOW,
  37        POWER_SUPPLY_PROP_CURRENT_NOW,
  38        POWER_SUPPLY_PROP_CAPACITY,
  39        POWER_SUPPLY_PROP_CHARGE_FULL,
  40        POWER_SUPPLY_PROP_CHARGE_NOW,
  41        POWER_SUPPLY_PROP_TEMP,
  42        POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
  43};
  44
  45static int madc_read(struct iio_channel *channel)
  46{
  47        int val, err;
  48        err = iio_read_channel_processed(channel, &val);
  49        if (err < 0)
  50                return err;
  51
  52        return val;
  53}
  54
  55static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)
  56{
  57        return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;
  58}
  59
  60static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)
  61{
  62        return madc_read(bt->channel_vbat);
  63}
  64
  65static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)
  66{
  67        return madc_read(bt->channel_ichg) * 1000;
  68}
  69
  70static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)
  71{
  72        return madc_read(bt->channel_temp) * 10;
  73}
  74
  75static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
  76                                        int volt)
  77{
  78        struct twl4030_madc_bat_calibration *calibration;
  79        int i, res = 0;
  80
  81        /* choose charging curve */
  82        if (twl4030_madc_bat_get_charging_status(bat))
  83                calibration = bat->pdata->charging;
  84        else
  85                calibration = bat->pdata->discharging;
  86
  87        if (volt > calibration[0].voltage) {
  88                res = calibration[0].level;
  89        } else {
  90                for (i = 0; calibration[i+1].voltage >= 0; i++) {
  91                        if (volt <= calibration[i].voltage &&
  92                                        volt >= calibration[i+1].voltage) {
  93                                /* interval found - interpolate within range */
  94                                res = calibration[i].level -
  95                                        ((calibration[i].voltage - volt) *
  96                                        (calibration[i].level -
  97                                        calibration[i+1].level)) /
  98                                        (calibration[i].voltage -
  99                                        calibration[i+1].voltage);
 100                                break;
 101                        }
 102                }
 103        }
 104        return res;
 105}
 106
 107static int twl4030_madc_bat_get_property(struct power_supply *psy,
 108                                        enum power_supply_property psp,
 109                                        union power_supply_propval *val)
 110{
 111        struct twl4030_madc_battery *bat = power_supply_get_drvdata(psy);
 112
 113        switch (psp) {
 114        case POWER_SUPPLY_PROP_STATUS:
 115                if (twl4030_madc_bat_voltscale(bat,
 116                                twl4030_madc_bat_get_voltage(bat)) > 95)
 117                        val->intval = POWER_SUPPLY_STATUS_FULL;
 118                else {
 119                        if (twl4030_madc_bat_get_charging_status(bat))
 120                                val->intval = POWER_SUPPLY_STATUS_CHARGING;
 121                        else
 122                                val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
 123                }
 124                break;
 125        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 126                val->intval = twl4030_madc_bat_get_voltage(bat) * 1000;
 127                break;
 128        case POWER_SUPPLY_PROP_TECHNOLOGY:
 129                val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
 130                break;
 131        case POWER_SUPPLY_PROP_CURRENT_NOW:
 132                val->intval = twl4030_madc_bat_get_current(bat);
 133                break;
 134        case POWER_SUPPLY_PROP_PRESENT:
 135                /* assume battery is always present */
 136                val->intval = 1;
 137                break;
 138        case POWER_SUPPLY_PROP_CHARGE_NOW: {
 139                        int percent = twl4030_madc_bat_voltscale(bat,
 140                                        twl4030_madc_bat_get_voltage(bat));
 141                        val->intval = (percent * bat->pdata->capacity) / 100;
 142                        break;
 143                }
 144        case POWER_SUPPLY_PROP_CAPACITY:
 145                val->intval = twl4030_madc_bat_voltscale(bat,
 146                                        twl4030_madc_bat_get_voltage(bat));
 147                break;
 148        case POWER_SUPPLY_PROP_CHARGE_FULL:
 149                val->intval = bat->pdata->capacity;
 150                break;
 151        case POWER_SUPPLY_PROP_TEMP:
 152                val->intval = twl4030_madc_bat_get_temp(bat);
 153                break;
 154        case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
 155                        int percent = twl4030_madc_bat_voltscale(bat,
 156                                        twl4030_madc_bat_get_voltage(bat));
 157                        /* in mAh */
 158                        int chg = (percent * (bat->pdata->capacity/1000))/100;
 159
 160                        /* assume discharge with 400 mA (ca. 1.5W) */
 161                        val->intval = (3600l * chg) / 400;
 162                        break;
 163                }
 164        default:
 165                return -EINVAL;
 166        }
 167
 168        return 0;
 169}
 170
 171static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
 172{
 173        power_supply_changed(psy);
 174}
 175
 176static const struct power_supply_desc twl4030_madc_bat_desc = {
 177        .name                   = "twl4030_battery",
 178        .type                   = POWER_SUPPLY_TYPE_BATTERY,
 179        .properties             = twl4030_madc_bat_props,
 180        .num_properties         = ARRAY_SIZE(twl4030_madc_bat_props),
 181        .get_property           = twl4030_madc_bat_get_property,
 182        .external_power_changed = twl4030_madc_bat_ext_changed,
 183
 184};
 185
 186static int twl4030_cmp(const void *a, const void *b)
 187{
 188        return ((struct twl4030_madc_bat_calibration *)b)->voltage -
 189                ((struct twl4030_madc_bat_calibration *)a)->voltage;
 190}
 191
 192static int twl4030_madc_battery_probe(struct platform_device *pdev)
 193{
 194        struct twl4030_madc_battery *twl4030_madc_bat;
 195        struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
 196        struct power_supply_config psy_cfg = {};
 197        int ret = 0;
 198
 199        twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),
 200                                GFP_KERNEL);
 201        if (!twl4030_madc_bat)
 202                return -ENOMEM;
 203
 204        twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp");
 205        if (IS_ERR(twl4030_madc_bat->channel_temp)) {
 206                ret = PTR_ERR(twl4030_madc_bat->channel_temp);
 207                goto err;
 208        }
 209
 210        twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg");
 211        if (IS_ERR(twl4030_madc_bat->channel_ichg)) {
 212                ret = PTR_ERR(twl4030_madc_bat->channel_ichg);
 213                goto err_temp;
 214        }
 215
 216        twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat");
 217        if (IS_ERR(twl4030_madc_bat->channel_vbat)) {
 218                ret = PTR_ERR(twl4030_madc_bat->channel_vbat);
 219                goto err_ichg;
 220        }
 221
 222        /* sort charging and discharging calibration data */
 223        sort(pdata->charging, pdata->charging_size,
 224                sizeof(struct twl4030_madc_bat_calibration),
 225                twl4030_cmp, NULL);
 226        sort(pdata->discharging, pdata->discharging_size,
 227                sizeof(struct twl4030_madc_bat_calibration),
 228                twl4030_cmp, NULL);
 229
 230        twl4030_madc_bat->pdata = pdata;
 231        platform_set_drvdata(pdev, twl4030_madc_bat);
 232        psy_cfg.drv_data = twl4030_madc_bat;
 233        twl4030_madc_bat->psy = power_supply_register(&pdev->dev,
 234                                                      &twl4030_madc_bat_desc,
 235                                                      &psy_cfg);
 236        if (IS_ERR(twl4030_madc_bat->psy)) {
 237                ret = PTR_ERR(twl4030_madc_bat->psy);
 238                goto err_vbat;
 239        }
 240
 241        return 0;
 242
 243err_vbat:
 244        iio_channel_release(twl4030_madc_bat->channel_vbat);
 245err_ichg:
 246        iio_channel_release(twl4030_madc_bat->channel_ichg);
 247err_temp:
 248        iio_channel_release(twl4030_madc_bat->channel_temp);
 249err:
 250        return ret;
 251}
 252
 253static int twl4030_madc_battery_remove(struct platform_device *pdev)
 254{
 255        struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
 256
 257        power_supply_unregister(bat->psy);
 258
 259        iio_channel_release(bat->channel_vbat);
 260        iio_channel_release(bat->channel_ichg);
 261        iio_channel_release(bat->channel_temp);
 262
 263        return 0;
 264}
 265
 266static struct platform_driver twl4030_madc_battery_driver = {
 267        .driver = {
 268                .name = "twl4030_madc_battery",
 269        },
 270        .probe  = twl4030_madc_battery_probe,
 271        .remove = twl4030_madc_battery_remove,
 272};
 273module_platform_driver(twl4030_madc_battery_driver);
 274
 275MODULE_LICENSE("GPL");
 276MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
 277MODULE_DESCRIPTION("twl4030_madc battery driver");
 278MODULE_ALIAS("platform:twl4030_madc_battery");
 279