linux/drivers/power/supply/axp288_fuel_gauge.c
<<
>>
Prefs
   1/*
   2 * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver
   3 *
   4 * Copyright (C) 2014 Intel Corporation
   5 *
   6 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   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 as published by
  10 * the Free Software Foundation; version 2 of the License.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15 * General Public License for more details.
  16 *
  17 */
  18
  19#include <linux/module.h>
  20#include <linux/kernel.h>
  21#include <linux/device.h>
  22#include <linux/regmap.h>
  23#include <linux/jiffies.h>
  24#include <linux/interrupt.h>
  25#include <linux/workqueue.h>
  26#include <linux/mfd/axp20x.h>
  27#include <linux/platform_device.h>
  28#include <linux/power_supply.h>
  29#include <linux/iio/consumer.h>
  30#include <linux/debugfs.h>
  31#include <linux/seq_file.h>
  32#include <asm/unaligned.h>
  33
  34#define CHRG_STAT_BAT_SAFE_MODE         (1 << 3)
  35#define CHRG_STAT_BAT_VALID                     (1 << 4)
  36#define CHRG_STAT_BAT_PRESENT           (1 << 5)
  37#define CHRG_STAT_CHARGING                      (1 << 6)
  38#define CHRG_STAT_PMIC_OTP                      (1 << 7)
  39
  40#define CHRG_CCCV_CC_MASK                       0xf     /* 4 bits */
  41#define CHRG_CCCV_CC_BIT_POS            0
  42#define CHRG_CCCV_CC_OFFSET                     200     /* 200mA */
  43#define CHRG_CCCV_CC_LSB_RES            200     /* 200mA */
  44#define CHRG_CCCV_ITERM_20P                     (1 << 4)    /* 20% of CC */
  45#define CHRG_CCCV_CV_MASK                       0x60        /* 2 bits */
  46#define CHRG_CCCV_CV_BIT_POS            5
  47#define CHRG_CCCV_CV_4100MV                     0x0     /* 4.10V */
  48#define CHRG_CCCV_CV_4150MV                     0x1     /* 4.15V */
  49#define CHRG_CCCV_CV_4200MV                     0x2     /* 4.20V */
  50#define CHRG_CCCV_CV_4350MV                     0x3     /* 4.35V */
  51#define CHRG_CCCV_CHG_EN                        (1 << 7)
  52
  53#define FG_CNTL_OCV_ADJ_STAT            (1 << 2)
  54#define FG_CNTL_OCV_ADJ_EN                      (1 << 3)
  55#define FG_CNTL_CAP_ADJ_STAT            (1 << 4)
  56#define FG_CNTL_CAP_ADJ_EN                      (1 << 5)
  57#define FG_CNTL_CC_EN                           (1 << 6)
  58#define FG_CNTL_GAUGE_EN                        (1 << 7)
  59
  60#define FG_15BIT_WORD_VALID                     (1 << 15)
  61#define FG_15BIT_VAL_MASK                       0x7fff
  62
  63#define FG_REP_CAP_VALID                        (1 << 7)
  64#define FG_REP_CAP_VAL_MASK                     0x7F
  65
  66#define FG_DES_CAP1_VALID                       (1 << 7)
  67#define FG_DES_CAP_RES_LSB                      1456    /* 1.456mAhr */
  68
  69#define FG_DES_CC_RES_LSB                       1456    /* 1.456mAhr */
  70
  71#define FG_OCV_CAP_VALID                        (1 << 7)
  72#define FG_OCV_CAP_VAL_MASK                     0x7F
  73#define FG_CC_CAP_VALID                         (1 << 7)
  74#define FG_CC_CAP_VAL_MASK                      0x7F
  75
  76#define FG_LOW_CAP_THR1_MASK            0xf0    /* 5% tp 20% */
  77#define FG_LOW_CAP_THR1_VAL                     0xa0    /* 15 perc */
  78#define FG_LOW_CAP_THR2_MASK            0x0f    /* 0% to 15% */
  79#define FG_LOW_CAP_WARN_THR                     14  /* 14 perc */
  80#define FG_LOW_CAP_CRIT_THR                     4   /* 4 perc */
  81#define FG_LOW_CAP_SHDN_THR                     0   /* 0 perc */
  82
  83#define STATUS_MON_DELAY_JIFFIES    (HZ * 60)   /*60 sec */
  84#define NR_RETRY_CNT    3
  85#define DEV_NAME        "axp288_fuel_gauge"
  86
  87/* 1.1mV per LSB expressed in uV */
  88#define VOLTAGE_FROM_ADC(a)                     ((a * 11) / 10)
  89/* properties converted to uV, uA */
  90#define PROP_VOLT(a)            ((a) * 1000)
  91#define PROP_CURR(a)            ((a) * 1000)
  92
  93#define AXP288_FG_INTR_NUM      6
  94enum {
  95        QWBTU_IRQ = 0,
  96        WBTU_IRQ,
  97        QWBTO_IRQ,
  98        WBTO_IRQ,
  99        WL2_IRQ,
 100        WL1_IRQ,
 101};
 102
 103struct axp288_fg_info {
 104        struct platform_device *pdev;
 105        struct regmap *regmap;
 106        struct regmap_irq_chip_data *regmap_irqc;
 107        int irq[AXP288_FG_INTR_NUM];
 108        struct power_supply *bat;
 109        struct mutex lock;
 110        int status;
 111        int max_volt;
 112        struct delayed_work status_monitor;
 113        struct dentry *debug_file;
 114};
 115
 116static enum power_supply_property fuel_gauge_props[] = {
 117        POWER_SUPPLY_PROP_STATUS,
 118        POWER_SUPPLY_PROP_PRESENT,
 119        POWER_SUPPLY_PROP_HEALTH,
 120        POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
 121        POWER_SUPPLY_PROP_VOLTAGE_NOW,
 122        POWER_SUPPLY_PROP_VOLTAGE_OCV,
 123        POWER_SUPPLY_PROP_CURRENT_NOW,
 124        POWER_SUPPLY_PROP_CAPACITY,
 125        POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
 126        POWER_SUPPLY_PROP_TECHNOLOGY,
 127        POWER_SUPPLY_PROP_CHARGE_FULL,
 128        POWER_SUPPLY_PROP_CHARGE_NOW,
 129};
 130
 131static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
 132{
 133        int ret, i;
 134        unsigned int val;
 135
 136        for (i = 0; i < NR_RETRY_CNT; i++) {
 137                ret = regmap_read(info->regmap, reg, &val);
 138                if (ret == -EBUSY)
 139                        continue;
 140                else
 141                        break;
 142        }
 143
 144        if (ret < 0) {
 145                dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
 146                return ret;
 147        }
 148
 149        return val;
 150}
 151
 152static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
 153{
 154        int ret;
 155
 156        ret = regmap_write(info->regmap, reg, (unsigned int)val);
 157
 158        if (ret < 0)
 159                dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret);
 160
 161        return ret;
 162}
 163
 164static int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg)
 165{
 166        unsigned char buf[2];
 167        int ret;
 168
 169        ret = regmap_bulk_read(info->regmap, reg, buf, 2);
 170        if (ret < 0) {
 171                dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
 172                        reg, ret);
 173                return ret;
 174        }
 175
 176        ret = get_unaligned_be16(buf);
 177        if (!(ret & FG_15BIT_WORD_VALID)) {
 178                dev_err(&info->pdev->dev, "Error reg 0x%02x contents not valid\n",
 179                        reg);
 180                return -ENXIO;
 181        }
 182
 183        return ret & FG_15BIT_VAL_MASK;
 184}
 185
 186static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg)
 187{
 188        unsigned char buf[2];
 189        int ret;
 190
 191        ret = regmap_bulk_read(info->regmap, reg, buf, 2);
 192        if (ret < 0) {
 193                dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
 194                        reg, ret);
 195                return ret;
 196        }
 197
 198        /* 12-bit data values have upper 8 bits in buf[0], lower 4 in buf[1] */
 199        return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f);
 200}
 201
 202static int pmic_read_adc_val(const char *name, int *raw_val,
 203                struct axp288_fg_info *info)
 204{
 205        int ret, val = 0;
 206        struct iio_channel *indio_chan;
 207
 208        indio_chan = iio_channel_get(NULL, name);
 209        if (IS_ERR_OR_NULL(indio_chan)) {
 210                ret = PTR_ERR(indio_chan);
 211                goto exit;
 212        }
 213        ret = iio_read_channel_raw(indio_chan, &val);
 214        if (ret < 0) {
 215                dev_err(&info->pdev->dev,
 216                        "IIO channel read error: %x, %x\n", ret, val);
 217                goto err_exit;
 218        }
 219
 220        dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val);
 221        *raw_val = val;
 222
 223err_exit:
 224        iio_channel_release(indio_chan);
 225exit:
 226        return ret;
 227}
 228
 229#ifdef CONFIG_DEBUG_FS
 230static int fuel_gauge_debug_show(struct seq_file *s, void *data)
 231{
 232        struct axp288_fg_info *info = s->private;
 233        int raw_val, ret;
 234
 235        seq_printf(s, " PWR_STATUS[%02x] : %02x\n",
 236                AXP20X_PWR_INPUT_STATUS,
 237                fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));
 238        seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",
 239                AXP20X_PWR_OP_MODE,
 240                fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));
 241        seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",
 242                AXP20X_CHRG_CTRL1,
 243                fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));
 244        seq_printf(s, "       VLTF[%02x] : %02x\n",
 245                AXP20X_V_LTF_DISCHRG,
 246                fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));
 247        seq_printf(s, "       VHTF[%02x] : %02x\n",
 248                AXP20X_V_HTF_DISCHRG,
 249                fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));
 250        seq_printf(s, "    CC_CTRL[%02x] : %02x\n",
 251                AXP20X_CC_CTRL,
 252                fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));
 253        seq_printf(s, "BATTERY CAP[%02x] : %02x\n",
 254                AXP20X_FG_RES,
 255                fuel_gauge_reg_readb(info, AXP20X_FG_RES));
 256        seq_printf(s, "    FG_RDC1[%02x] : %02x\n",
 257                AXP288_FG_RDC1_REG,
 258                fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));
 259        seq_printf(s, "    FG_RDC0[%02x] : %02x\n",
 260                AXP288_FG_RDC0_REG,
 261                fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
 262        seq_printf(s, "     FG_OCV[%02x] : %04x\n",
 263                AXP288_FG_OCVH_REG,
 264                fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG));
 265        seq_printf(s, " FG_DES_CAP[%02x] : %04x\n",
 266                AXP288_FG_DES_CAP1_REG,
 267                fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG));
 268        seq_printf(s, "  FG_CC_MTR[%02x] : %04x\n",
 269                AXP288_FG_CC_MTR1_REG,
 270                fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG));
 271        seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
 272                AXP288_FG_OCV_CAP_REG,
 273                fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
 274        seq_printf(s, "  FG_CC_CAP[%02x] : %02x\n",
 275                AXP288_FG_CC_CAP_REG,
 276                fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));
 277        seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",
 278                AXP288_FG_LOW_CAP_REG,
 279                fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));
 280        seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",
 281                AXP288_FG_TUNE0,
 282                fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));
 283        seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",
 284                AXP288_FG_TUNE1,
 285                fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));
 286        seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",
 287                AXP288_FG_TUNE2,
 288                fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));
 289        seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",
 290                AXP288_FG_TUNE3,
 291                fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));
 292        seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",
 293                AXP288_FG_TUNE4,
 294                fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));
 295        seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",
 296                AXP288_FG_TUNE5,
 297                fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));
 298
 299        ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
 300        if (ret >= 0)
 301                seq_printf(s, "axp288-batttemp : %d\n", raw_val);
 302        ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info);
 303        if (ret >= 0)
 304                seq_printf(s, "axp288-pmictemp : %d\n", raw_val);
 305        ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info);
 306        if (ret >= 0)
 307                seq_printf(s, "axp288-systtemp : %d\n", raw_val);
 308        ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info);
 309        if (ret >= 0)
 310                seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);
 311        ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info);
 312        if (ret >= 0)
 313                seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);
 314        ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
 315        if (ret >= 0)
 316                seq_printf(s, "axp288-battvolt : %d\n", raw_val);
 317
 318        return 0;
 319}
 320
 321static int debug_open(struct inode *inode, struct file *file)
 322{
 323        return single_open(file, fuel_gauge_debug_show, inode->i_private);
 324}
 325
 326static const struct file_operations fg_debug_fops = {
 327        .open       = debug_open,
 328        .read       = seq_read,
 329        .llseek     = seq_lseek,
 330        .release    = single_release,
 331};
 332
 333static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
 334{
 335        info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL,
 336                info, &fg_debug_fops);
 337}
 338
 339static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
 340{
 341        debugfs_remove(info->debug_file);
 342}
 343#else
 344static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
 345{
 346}
 347static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
 348{
 349}
 350#endif
 351
 352static void fuel_gauge_get_status(struct axp288_fg_info *info)
 353{
 354        int pwr_stat, ret;
 355        int charge, discharge;
 356
 357        pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
 358        if (pwr_stat < 0) {
 359                dev_err(&info->pdev->dev,
 360                        "PWR STAT read failed:%d\n", pwr_stat);
 361                return;
 362        }
 363        ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
 364        if (ret < 0) {
 365                dev_err(&info->pdev->dev,
 366                        "ADC charge current read failed:%d\n", ret);
 367                return;
 368        }
 369        ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
 370        if (ret < 0) {
 371                dev_err(&info->pdev->dev,
 372                        "ADC discharge current read failed:%d\n", ret);
 373                return;
 374        }
 375
 376        if (charge > 0)
 377                info->status = POWER_SUPPLY_STATUS_CHARGING;
 378        else if (discharge > 0)
 379                info->status = POWER_SUPPLY_STATUS_DISCHARGING;
 380        else {
 381                if (pwr_stat & CHRG_STAT_BAT_PRESENT)
 382                        info->status = POWER_SUPPLY_STATUS_FULL;
 383                else
 384                        info->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
 385        }
 386}
 387
 388static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)
 389{
 390        int ret = 0, raw_val;
 391
 392        ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
 393        if (ret < 0)
 394                goto vbatt_read_fail;
 395
 396        *vbatt = VOLTAGE_FROM_ADC(raw_val);
 397vbatt_read_fail:
 398        return ret;
 399}
 400
 401static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
 402{
 403        int ret, value = 0;
 404        int charge, discharge;
 405
 406        ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
 407        if (ret < 0)
 408                goto current_read_fail;
 409        ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
 410        if (ret < 0)
 411                goto current_read_fail;
 412
 413        if (charge > 0)
 414                value = charge;
 415        else if (discharge > 0)
 416                value = -1 * discharge;
 417
 418        *cur = value;
 419current_read_fail:
 420        return ret;
 421}
 422
 423static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
 424{
 425        int ret;
 426
 427        ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
 428        if (ret >= 0)
 429                *vocv = VOLTAGE_FROM_ADC(ret);
 430
 431        return ret;
 432}
 433
 434static int fuel_gauge_battery_health(struct axp288_fg_info *info)
 435{
 436        int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN;
 437
 438        ret = fuel_gauge_get_vocv(info, &vocv);
 439        if (ret < 0)
 440                goto health_read_fail;
 441
 442        if (vocv > info->max_volt)
 443                health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
 444        else
 445                health = POWER_SUPPLY_HEALTH_GOOD;
 446
 447health_read_fail:
 448        return health;
 449}
 450
 451static int fuel_gauge_get_property(struct power_supply *ps,
 452                enum power_supply_property prop,
 453                union power_supply_propval *val)
 454{
 455        struct axp288_fg_info *info = power_supply_get_drvdata(ps);
 456        int ret = 0, value;
 457
 458        mutex_lock(&info->lock);
 459        switch (prop) {
 460        case POWER_SUPPLY_PROP_STATUS:
 461                fuel_gauge_get_status(info);
 462                val->intval = info->status;
 463                break;
 464        case POWER_SUPPLY_PROP_HEALTH:
 465                val->intval = fuel_gauge_battery_health(info);
 466                break;
 467        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 468                ret = fuel_gauge_get_vbatt(info, &value);
 469                if (ret < 0)
 470                        goto fuel_gauge_read_err;
 471                val->intval = PROP_VOLT(value);
 472                break;
 473        case POWER_SUPPLY_PROP_VOLTAGE_OCV:
 474                ret = fuel_gauge_get_vocv(info, &value);
 475                if (ret < 0)
 476                        goto fuel_gauge_read_err;
 477                val->intval = PROP_VOLT(value);
 478                break;
 479        case POWER_SUPPLY_PROP_CURRENT_NOW:
 480                ret = fuel_gauge_get_current(info, &value);
 481                if (ret < 0)
 482                        goto fuel_gauge_read_err;
 483                val->intval = PROP_CURR(value);
 484                break;
 485        case POWER_SUPPLY_PROP_PRESENT:
 486                ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
 487                if (ret < 0)
 488                        goto fuel_gauge_read_err;
 489
 490                if (ret & CHRG_STAT_BAT_PRESENT)
 491                        val->intval = 1;
 492                else
 493                        val->intval = 0;
 494                break;
 495        case POWER_SUPPLY_PROP_CAPACITY:
 496                ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
 497                if (ret < 0)
 498                        goto fuel_gauge_read_err;
 499
 500                if (!(ret & FG_REP_CAP_VALID))
 501                        dev_err(&info->pdev->dev,
 502                                "capacity measurement not valid\n");
 503                val->intval = (ret & FG_REP_CAP_VAL_MASK);
 504                break;
 505        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 506                ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
 507                if (ret < 0)
 508                        goto fuel_gauge_read_err;
 509                val->intval = (ret & 0x0f);
 510                break;
 511        case POWER_SUPPLY_PROP_TECHNOLOGY:
 512                val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
 513                break;
 514        case POWER_SUPPLY_PROP_CHARGE_NOW:
 515                ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
 516                if (ret < 0)
 517                        goto fuel_gauge_read_err;
 518
 519                val->intval = ret * FG_DES_CAP_RES_LSB;
 520                break;
 521        case POWER_SUPPLY_PROP_CHARGE_FULL:
 522                ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG);
 523                if (ret < 0)
 524                        goto fuel_gauge_read_err;
 525
 526                val->intval = ret * FG_DES_CAP_RES_LSB;
 527                break;
 528        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
 529                val->intval = PROP_VOLT(info->max_volt);
 530                break;
 531        default:
 532                mutex_unlock(&info->lock);
 533                return -EINVAL;
 534        }
 535
 536        mutex_unlock(&info->lock);
 537        return 0;
 538
 539fuel_gauge_read_err:
 540        mutex_unlock(&info->lock);
 541        return ret;
 542}
 543
 544static int fuel_gauge_set_property(struct power_supply *ps,
 545                enum power_supply_property prop,
 546                const union power_supply_propval *val)
 547{
 548        struct axp288_fg_info *info = power_supply_get_drvdata(ps);
 549        int ret = 0;
 550
 551        mutex_lock(&info->lock);
 552        switch (prop) {
 553        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 554                if ((val->intval < 0) || (val->intval > 15)) {
 555                        ret = -EINVAL;
 556                        break;
 557                }
 558                ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
 559                if (ret < 0)
 560                        break;
 561                ret &= 0xf0;
 562                ret |= (val->intval & 0xf);
 563                ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret);
 564                break;
 565        default:
 566                ret = -EINVAL;
 567                break;
 568        }
 569
 570        mutex_unlock(&info->lock);
 571        return ret;
 572}
 573
 574static int fuel_gauge_property_is_writeable(struct power_supply *psy,
 575        enum power_supply_property psp)
 576{
 577        int ret;
 578
 579        switch (psp) {
 580        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 581                ret = 1;
 582                break;
 583        default:
 584                ret = 0;
 585        }
 586
 587        return ret;
 588}
 589
 590static void fuel_gauge_status_monitor(struct work_struct *work)
 591{
 592        struct axp288_fg_info *info = container_of(work,
 593                struct axp288_fg_info, status_monitor.work);
 594
 595        fuel_gauge_get_status(info);
 596        power_supply_changed(info->bat);
 597        schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
 598}
 599
 600static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
 601{
 602        struct axp288_fg_info *info = dev;
 603        int i;
 604
 605        for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
 606                if (info->irq[i] == irq)
 607                        break;
 608        }
 609
 610        if (i >= AXP288_FG_INTR_NUM) {
 611                dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
 612                return IRQ_NONE;
 613        }
 614
 615        switch (i) {
 616        case QWBTU_IRQ:
 617                dev_info(&info->pdev->dev,
 618                        "Quit Battery under temperature in work mode IRQ (QWBTU)\n");
 619                break;
 620        case WBTU_IRQ:
 621                dev_info(&info->pdev->dev,
 622                        "Battery under temperature in work mode IRQ (WBTU)\n");
 623                break;
 624        case QWBTO_IRQ:
 625                dev_info(&info->pdev->dev,
 626                        "Quit Battery over temperature in work mode IRQ (QWBTO)\n");
 627                break;
 628        case WBTO_IRQ:
 629                dev_info(&info->pdev->dev,
 630                        "Battery over temperature in work mode IRQ (WBTO)\n");
 631                break;
 632        case WL2_IRQ:
 633                dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
 634                break;
 635        case WL1_IRQ:
 636                dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
 637                break;
 638        default:
 639                dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
 640        }
 641
 642        power_supply_changed(info->bat);
 643        return IRQ_HANDLED;
 644}
 645
 646static void fuel_gauge_external_power_changed(struct power_supply *psy)
 647{
 648        struct axp288_fg_info *info = power_supply_get_drvdata(psy);
 649
 650        power_supply_changed(info->bat);
 651}
 652
 653static const struct power_supply_desc fuel_gauge_desc = {
 654        .name                   = DEV_NAME,
 655        .type                   = POWER_SUPPLY_TYPE_BATTERY,
 656        .properties             = fuel_gauge_props,
 657        .num_properties         = ARRAY_SIZE(fuel_gauge_props),
 658        .get_property           = fuel_gauge_get_property,
 659        .set_property           = fuel_gauge_set_property,
 660        .property_is_writeable  = fuel_gauge_property_is_writeable,
 661        .external_power_changed = fuel_gauge_external_power_changed,
 662};
 663
 664static void fuel_gauge_init_irq(struct axp288_fg_info *info)
 665{
 666        int ret, i, pirq;
 667
 668        for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
 669                pirq = platform_get_irq(info->pdev, i);
 670                info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
 671                if (info->irq[i] < 0) {
 672                        dev_warn(&info->pdev->dev,
 673                                "regmap_irq get virq failed for IRQ %d: %d\n",
 674                                pirq, info->irq[i]);
 675                        info->irq[i] = -1;
 676                        goto intr_failed;
 677                }
 678                ret = request_threaded_irq(info->irq[i],
 679                                NULL, fuel_gauge_thread_handler,
 680                                IRQF_ONESHOT, DEV_NAME, info);
 681                if (ret) {
 682                        dev_warn(&info->pdev->dev,
 683                                "request irq failed for IRQ %d: %d\n",
 684                                pirq, info->irq[i]);
 685                        info->irq[i] = -1;
 686                        goto intr_failed;
 687                } else {
 688                        dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n",
 689                                pirq, info->irq[i]);
 690                }
 691        }
 692        return;
 693
 694intr_failed:
 695        for (; i > 0; i--) {
 696                free_irq(info->irq[i - 1], info);
 697                info->irq[i - 1] = -1;
 698        }
 699}
 700
 701static int axp288_fuel_gauge_probe(struct platform_device *pdev)
 702{
 703        int ret = 0;
 704        struct axp288_fg_info *info;
 705        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
 706        struct power_supply_config psy_cfg = {};
 707
 708        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
 709        if (!info)
 710                return -ENOMEM;
 711
 712        info->pdev = pdev;
 713        info->regmap = axp20x->regmap;
 714        info->regmap_irqc = axp20x->regmap_irqc;
 715        info->status = POWER_SUPPLY_STATUS_UNKNOWN;
 716
 717        platform_set_drvdata(pdev, info);
 718
 719        mutex_init(&info->lock);
 720        INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
 721
 722        ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
 723        if (ret < 0)
 724                return ret;
 725
 726        if (!(ret & FG_DES_CAP1_VALID)) {
 727                dev_err(&pdev->dev, "axp288 not configured by firmware\n");
 728                return -ENODEV;
 729        }
 730
 731        ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
 732        if (ret < 0)
 733                return ret;
 734        switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
 735        case CHRG_CCCV_CV_4100MV:
 736                info->max_volt = 4100;
 737                break;
 738        case CHRG_CCCV_CV_4150MV:
 739                info->max_volt = 4150;
 740                break;
 741        case CHRG_CCCV_CV_4200MV:
 742                info->max_volt = 4200;
 743                break;
 744        case CHRG_CCCV_CV_4350MV:
 745                info->max_volt = 4350;
 746                break;
 747        }
 748
 749        psy_cfg.drv_data = info;
 750        info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
 751        if (IS_ERR(info->bat)) {
 752                ret = PTR_ERR(info->bat);
 753                dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
 754                return ret;
 755        }
 756
 757        fuel_gauge_create_debugfs(info);
 758        fuel_gauge_init_irq(info);
 759        schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
 760
 761        return 0;
 762}
 763
 764static const struct platform_device_id axp288_fg_id_table[] = {
 765        { .name = DEV_NAME },
 766        {},
 767};
 768MODULE_DEVICE_TABLE(platform, axp288_fg_id_table);
 769
 770static int axp288_fuel_gauge_remove(struct platform_device *pdev)
 771{
 772        struct axp288_fg_info *info = platform_get_drvdata(pdev);
 773        int i;
 774
 775        cancel_delayed_work_sync(&info->status_monitor);
 776        power_supply_unregister(info->bat);
 777        fuel_gauge_remove_debugfs(info);
 778
 779        for (i = 0; i < AXP288_FG_INTR_NUM; i++)
 780                if (info->irq[i] >= 0)
 781                        free_irq(info->irq[i], info);
 782
 783        return 0;
 784}
 785
 786static struct platform_driver axp288_fuel_gauge_driver = {
 787        .probe = axp288_fuel_gauge_probe,
 788        .remove = axp288_fuel_gauge_remove,
 789        .id_table = axp288_fg_id_table,
 790        .driver = {
 791                .name = DEV_NAME,
 792        },
 793};
 794
 795module_platform_driver(axp288_fuel_gauge_driver);
 796
 797MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
 798MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
 799MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
 800MODULE_LICENSE("GPL");
 801