linux/drivers/power/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/device.h>
  26#include <linux/workqueue.h>
  27#include <linux/mfd/axp20x.h>
  28#include <linux/platform_device.h>
  29#include <linux/power_supply.h>
  30#include <linux/iio/consumer.h>
  31#include <linux/debugfs.h>
  32#include <linux/seq_file.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 CV_4100                                         4100    /* 4100mV */
  54#define CV_4150                                         4150    /* 4150mV */
  55#define CV_4200                                         4200    /* 4200mV */
  56#define CV_4350                                         4350    /* 4350mV */
  57
  58#define TEMP_IRQ_CFG_QWBTU                      (1 << 0)
  59#define TEMP_IRQ_CFG_WBTU                       (1 << 1)
  60#define TEMP_IRQ_CFG_QWBTO                      (1 << 2)
  61#define TEMP_IRQ_CFG_WBTO                       (1 << 3)
  62#define TEMP_IRQ_CFG_MASK                       0xf
  63
  64#define FG_IRQ_CFG_LOWBATT_WL2          (1 << 0)
  65#define FG_IRQ_CFG_LOWBATT_WL1          (1 << 1)
  66#define FG_IRQ_CFG_LOWBATT_MASK         0x3
  67#define LOWBAT_IRQ_STAT_LOWBATT_WL2     (1 << 0)
  68#define LOWBAT_IRQ_STAT_LOWBATT_WL1     (1 << 1)
  69
  70#define FG_CNTL_OCV_ADJ_STAT            (1 << 2)
  71#define FG_CNTL_OCV_ADJ_EN                      (1 << 3)
  72#define FG_CNTL_CAP_ADJ_STAT            (1 << 4)
  73#define FG_CNTL_CAP_ADJ_EN                      (1 << 5)
  74#define FG_CNTL_CC_EN                           (1 << 6)
  75#define FG_CNTL_GAUGE_EN                        (1 << 7)
  76
  77#define FG_REP_CAP_VALID                        (1 << 7)
  78#define FG_REP_CAP_VAL_MASK                     0x7F
  79
  80#define FG_DES_CAP1_VALID                       (1 << 7)
  81#define FG_DES_CAP1_VAL_MASK            0x7F
  82#define FG_DES_CAP0_VAL_MASK            0xFF
  83#define FG_DES_CAP_RES_LSB                      1456    /* 1.456mAhr */
  84
  85#define FG_CC_MTR1_VALID                        (1 << 7)
  86#define FG_CC_MTR1_VAL_MASK                     0x7F
  87#define FG_CC_MTR0_VAL_MASK                     0xFF
  88#define FG_DES_CC_RES_LSB                       1456    /* 1.456mAhr */
  89
  90#define FG_OCV_CAP_VALID                        (1 << 7)
  91#define FG_OCV_CAP_VAL_MASK                     0x7F
  92#define FG_CC_CAP_VALID                         (1 << 7)
  93#define FG_CC_CAP_VAL_MASK                      0x7F
  94
  95#define FG_LOW_CAP_THR1_MASK            0xf0    /* 5% tp 20% */
  96#define FG_LOW_CAP_THR1_VAL                     0xa0    /* 15 perc */
  97#define FG_LOW_CAP_THR2_MASK            0x0f    /* 0% to 15% */
  98#define FG_LOW_CAP_WARN_THR                     14  /* 14 perc */
  99#define FG_LOW_CAP_CRIT_THR                     4   /* 4 perc */
 100#define FG_LOW_CAP_SHDN_THR                     0   /* 0 perc */
 101
 102#define STATUS_MON_DELAY_JIFFIES    (HZ * 60)   /*60 sec */
 103#define NR_RETRY_CNT    3
 104#define DEV_NAME        "axp288_fuel_gauge"
 105
 106/* 1.1mV per LSB expressed in uV */
 107#define VOLTAGE_FROM_ADC(a)                     ((a * 11) / 10)
 108/* properties converted to tenths of degrees, uV, uA, uW */
 109#define PROP_TEMP(a)            ((a) * 10)
 110#define UNPROP_TEMP(a)          ((a) / 10)
 111#define PROP_VOLT(a)            ((a) * 1000)
 112#define PROP_CURR(a)            ((a) * 1000)
 113
 114#define AXP288_FG_INTR_NUM      6
 115enum {
 116        QWBTU_IRQ = 0,
 117        WBTU_IRQ,
 118        QWBTO_IRQ,
 119        WBTO_IRQ,
 120        WL2_IRQ,
 121        WL1_IRQ,
 122};
 123
 124struct axp288_fg_info {
 125        struct platform_device *pdev;
 126        struct axp20x_fg_pdata *pdata;
 127        struct regmap *regmap;
 128        struct regmap_irq_chip_data *regmap_irqc;
 129        int irq[AXP288_FG_INTR_NUM];
 130        struct power_supply *bat;
 131        struct mutex lock;
 132        int status;
 133        struct delayed_work status_monitor;
 134        struct dentry *debug_file;
 135};
 136
 137static enum power_supply_property fuel_gauge_props[] = {
 138        POWER_SUPPLY_PROP_STATUS,
 139        POWER_SUPPLY_PROP_PRESENT,
 140        POWER_SUPPLY_PROP_HEALTH,
 141        POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
 142        POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
 143        POWER_SUPPLY_PROP_VOLTAGE_NOW,
 144        POWER_SUPPLY_PROP_VOLTAGE_OCV,
 145        POWER_SUPPLY_PROP_CURRENT_NOW,
 146        POWER_SUPPLY_PROP_CAPACITY,
 147        POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
 148        POWER_SUPPLY_PROP_TEMP,
 149        POWER_SUPPLY_PROP_TEMP_MAX,
 150        POWER_SUPPLY_PROP_TEMP_MIN,
 151        POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
 152        POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
 153        POWER_SUPPLY_PROP_TECHNOLOGY,
 154        POWER_SUPPLY_PROP_CHARGE_FULL,
 155        POWER_SUPPLY_PROP_CHARGE_NOW,
 156        POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 157        POWER_SUPPLY_PROP_MODEL_NAME,
 158};
 159
 160static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
 161{
 162        int ret, i;
 163        unsigned int val;
 164
 165        for (i = 0; i < NR_RETRY_CNT; i++) {
 166                ret = regmap_read(info->regmap, reg, &val);
 167                if (ret == -EBUSY)
 168                        continue;
 169                else
 170                        break;
 171        }
 172
 173        if (ret < 0)
 174                dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
 175
 176        return val;
 177}
 178
 179static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
 180{
 181        int ret;
 182
 183        ret = regmap_write(info->regmap, reg, (unsigned int)val);
 184
 185        if (ret < 0)
 186                dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret);
 187
 188        return ret;
 189}
 190
 191static int pmic_read_adc_val(const char *name, int *raw_val,
 192                struct axp288_fg_info *info)
 193{
 194        int ret, val = 0;
 195        struct iio_channel *indio_chan;
 196
 197        indio_chan = iio_channel_get(NULL, name);
 198        if (IS_ERR_OR_NULL(indio_chan)) {
 199                ret = PTR_ERR(indio_chan);
 200                goto exit;
 201        }
 202        ret = iio_read_channel_raw(indio_chan, &val);
 203        if (ret < 0) {
 204                dev_err(&info->pdev->dev,
 205                        "IIO channel read error: %x, %x\n", ret, val);
 206                goto err_exit;
 207        }
 208
 209        dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val);
 210        *raw_val = val;
 211
 212err_exit:
 213        iio_channel_release(indio_chan);
 214exit:
 215        return ret;
 216}
 217
 218#ifdef CONFIG_DEBUG_FS
 219static int fuel_gauge_debug_show(struct seq_file *s, void *data)
 220{
 221        struct axp288_fg_info *info = s->private;
 222        int raw_val, ret;
 223
 224        seq_printf(s, " PWR_STATUS[%02x] : %02x\n",
 225                AXP20X_PWR_INPUT_STATUS,
 226                fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));
 227        seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",
 228                AXP20X_PWR_OP_MODE,
 229                fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));
 230        seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",
 231                AXP20X_CHRG_CTRL1,
 232                fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));
 233        seq_printf(s, "       VLTF[%02x] : %02x\n",
 234                AXP20X_V_LTF_DISCHRG,
 235                fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));
 236        seq_printf(s, "       VHTF[%02x] : %02x\n",
 237                AXP20X_V_HTF_DISCHRG,
 238                fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));
 239        seq_printf(s, "    CC_CTRL[%02x] : %02x\n",
 240                AXP20X_CC_CTRL,
 241                fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));
 242        seq_printf(s, "BATTERY CAP[%02x] : %02x\n",
 243                AXP20X_FG_RES,
 244                fuel_gauge_reg_readb(info, AXP20X_FG_RES));
 245        seq_printf(s, "    FG_RDC1[%02x] : %02x\n",
 246                AXP288_FG_RDC1_REG,
 247                fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));
 248        seq_printf(s, "    FG_RDC0[%02x] : %02x\n",
 249                AXP288_FG_RDC0_REG,
 250                fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
 251        seq_printf(s, "    FG_OCVH[%02x] : %02x\n",
 252                AXP288_FG_OCVH_REG,
 253                fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG));
 254        seq_printf(s, "    FG_OCVL[%02x] : %02x\n",
 255                AXP288_FG_OCVL_REG,
 256                fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG));
 257        seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n",
 258                AXP288_FG_DES_CAP1_REG,
 259                fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG));
 260        seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n",
 261                AXP288_FG_DES_CAP0_REG,
 262                fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG));
 263        seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n",
 264                AXP288_FG_CC_MTR1_REG,
 265                fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG));
 266        seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n",
 267                AXP288_FG_CC_MTR0_REG,
 268                fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG));
 269        seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
 270                AXP288_FG_OCV_CAP_REG,
 271                fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
 272        seq_printf(s, "  FG_CC_CAP[%02x] : %02x\n",
 273                AXP288_FG_CC_CAP_REG,
 274                fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));
 275        seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",
 276                AXP288_FG_LOW_CAP_REG,
 277                fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));
 278        seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",
 279                AXP288_FG_TUNE0,
 280                fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));
 281        seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",
 282                AXP288_FG_TUNE1,
 283                fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));
 284        seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",
 285                AXP288_FG_TUNE2,
 286                fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));
 287        seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",
 288                AXP288_FG_TUNE3,
 289                fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));
 290        seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",
 291                AXP288_FG_TUNE4,
 292                fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));
 293        seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",
 294                AXP288_FG_TUNE5,
 295                fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));
 296
 297        ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
 298        if (ret >= 0)
 299                seq_printf(s, "axp288-batttemp : %d\n", raw_val);
 300        ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info);
 301        if (ret >= 0)
 302                seq_printf(s, "axp288-pmictemp : %d\n", raw_val);
 303        ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info);
 304        if (ret >= 0)
 305                seq_printf(s, "axp288-systtemp : %d\n", raw_val);
 306        ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info);
 307        if (ret >= 0)
 308                seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);
 309        ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info);
 310        if (ret >= 0)
 311                seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);
 312        ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
 313        if (ret >= 0)
 314                seq_printf(s, "axp288-battvolt : %d\n", raw_val);
 315
 316        return 0;
 317}
 318
 319static int debug_open(struct inode *inode, struct file *file)
 320{
 321        return single_open(file, fuel_gauge_debug_show, inode->i_private);
 322}
 323
 324static const struct file_operations fg_debug_fops = {
 325        .open       = debug_open,
 326        .read       = seq_read,
 327        .llseek     = seq_lseek,
 328        .release    = single_release,
 329};
 330
 331static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
 332{
 333        info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL,
 334                info, &fg_debug_fops);
 335}
 336
 337static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
 338{
 339        debugfs_remove(info->debug_file);
 340}
 341#else
 342static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
 343{
 344}
 345static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
 346{
 347}
 348#endif
 349
 350static void fuel_gauge_get_status(struct axp288_fg_info *info)
 351{
 352        int pwr_stat, ret;
 353        int charge, discharge;
 354
 355        pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
 356        if (pwr_stat < 0) {
 357                dev_err(&info->pdev->dev,
 358                        "PWR STAT read failed:%d\n", pwr_stat);
 359                return;
 360        }
 361        ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
 362        if (ret < 0) {
 363                dev_err(&info->pdev->dev,
 364                        "ADC charge current read failed:%d\n", ret);
 365                return;
 366        }
 367        ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
 368        if (ret < 0) {
 369                dev_err(&info->pdev->dev,
 370                        "ADC discharge current read failed:%d\n", ret);
 371                return;
 372        }
 373
 374        if (charge > 0)
 375                info->status = POWER_SUPPLY_STATUS_CHARGING;
 376        else if (discharge > 0)
 377                info->status = POWER_SUPPLY_STATUS_DISCHARGING;
 378        else {
 379                if (pwr_stat & CHRG_STAT_BAT_PRESENT)
 380                        info->status = POWER_SUPPLY_STATUS_FULL;
 381                else
 382                        info->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
 383        }
 384}
 385
 386static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)
 387{
 388        int ret = 0, raw_val;
 389
 390        ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
 391        if (ret < 0)
 392                goto vbatt_read_fail;
 393
 394        *vbatt = VOLTAGE_FROM_ADC(raw_val);
 395vbatt_read_fail:
 396        return ret;
 397}
 398
 399static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
 400{
 401        int ret, value = 0;
 402        int charge, discharge;
 403
 404        ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
 405        if (ret < 0)
 406                goto current_read_fail;
 407        ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
 408        if (ret < 0)
 409                goto current_read_fail;
 410
 411        if (charge > 0)
 412                value = charge;
 413        else if (discharge > 0)
 414                value = -1 * discharge;
 415
 416        *cur = value;
 417current_read_fail:
 418        return ret;
 419}
 420
 421static int temp_to_adc(struct axp288_fg_info *info, int tval)
 422{
 423        int rntc = 0, i, ret, adc_val;
 424        int rmin, rmax, tmin, tmax;
 425        int tcsz = info->pdata->tcsz;
 426
 427        /* get the Rntc resitance value for this temp */
 428        if (tval > info->pdata->thermistor_curve[0][1]) {
 429                rntc = info->pdata->thermistor_curve[0][0];
 430        } else if (tval <= info->pdata->thermistor_curve[tcsz-1][1]) {
 431                rntc = info->pdata->thermistor_curve[tcsz-1][0];
 432        } else {
 433                for (i = 1; i < tcsz; i++) {
 434                        if (tval > info->pdata->thermistor_curve[i][1]) {
 435                                rmin = info->pdata->thermistor_curve[i-1][0];
 436                                rmax = info->pdata->thermistor_curve[i][0];
 437                                tmin = info->pdata->thermistor_curve[i-1][1];
 438                                tmax = info->pdata->thermistor_curve[i][1];
 439                                rntc = rmin + ((rmax - rmin) *
 440                                        (tval - tmin) / (tmax - tmin));
 441                                break;
 442                        }
 443                }
 444        }
 445
 446        /* we need the current to calculate the proper adc voltage */
 447        ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
 448        if (ret < 0) {
 449                dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
 450                ret = 0x30;
 451        }
 452
 453        /*
 454         * temperature is proportional to NTS thermistor resistance
 455         * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
 456         * [12-bit ADC VAL] = R_NTC(Ω) * current / 800
 457         */
 458        adc_val = rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800;
 459
 460        return adc_val;
 461}
 462
 463static int adc_to_temp(struct axp288_fg_info *info, int adc_val)
 464{
 465        int ret, r, i, tval = 0;
 466        int rmin, rmax, tmin, tmax;
 467        int tcsz = info->pdata->tcsz;
 468
 469        ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
 470        if (ret < 0) {
 471                dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
 472                ret = 0x30;
 473        }
 474
 475        /*
 476         * temperature is proportional to NTS thermistor resistance
 477         * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
 478         * R_NTC(Ω) = [12-bit ADC VAL] * 800 / current
 479         */
 480        r = adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3)));
 481
 482        if (r < info->pdata->thermistor_curve[0][0]) {
 483                tval = info->pdata->thermistor_curve[0][1];
 484        } else if (r >= info->pdata->thermistor_curve[tcsz-1][0]) {
 485                tval = info->pdata->thermistor_curve[tcsz-1][1];
 486        } else {
 487                for (i = 1; i < tcsz; i++) {
 488                        if (r < info->pdata->thermistor_curve[i][0]) {
 489                                rmin = info->pdata->thermistor_curve[i-1][0];
 490                                rmax = info->pdata->thermistor_curve[i][0];
 491                                tmin = info->pdata->thermistor_curve[i-1][1];
 492                                tmax = info->pdata->thermistor_curve[i][1];
 493                                tval = tmin + ((tmax - tmin) *
 494                                        (r - rmin) / (rmax - rmin));
 495                                break;
 496                        }
 497                }
 498        }
 499
 500        return tval;
 501}
 502
 503static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp)
 504{
 505        int ret, raw_val = 0;
 506
 507        ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
 508        if (ret < 0)
 509                goto temp_read_fail;
 510
 511        *btemp = adc_to_temp(info, raw_val);
 512
 513temp_read_fail:
 514        return ret;
 515}
 516
 517static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
 518{
 519        int ret, value;
 520
 521        /* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */
 522        ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG);
 523        if (ret < 0)
 524                goto vocv_read_fail;
 525        value = ret << 4;
 526
 527        ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG);
 528        if (ret < 0)
 529                goto vocv_read_fail;
 530        value |= (ret & 0xf);
 531
 532        *vocv = VOLTAGE_FROM_ADC(value);
 533vocv_read_fail:
 534        return ret;
 535}
 536
 537static int fuel_gauge_battery_health(struct axp288_fg_info *info)
 538{
 539        int temp, vocv;
 540        int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN;
 541
 542        ret = fuel_gauge_get_btemp(info, &temp);
 543        if (ret < 0)
 544                goto health_read_fail;
 545
 546        ret = fuel_gauge_get_vocv(info, &vocv);
 547        if (ret < 0)
 548                goto health_read_fail;
 549
 550        if (vocv > info->pdata->max_volt)
 551                health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
 552        else if (temp > info->pdata->max_temp)
 553                health = POWER_SUPPLY_HEALTH_OVERHEAT;
 554        else if (temp < info->pdata->min_temp)
 555                health = POWER_SUPPLY_HEALTH_COLD;
 556        else if (vocv < info->pdata->min_volt)
 557                health = POWER_SUPPLY_HEALTH_DEAD;
 558        else
 559                health = POWER_SUPPLY_HEALTH_GOOD;
 560
 561health_read_fail:
 562        return health;
 563}
 564
 565static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info)
 566{
 567        int ret, adc_val;
 568
 569        /* program temperature threshold as 1/16 ADC value */
 570        adc_val = temp_to_adc(info, info->pdata->max_temp);
 571        ret = fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >> 4);
 572
 573        return ret;
 574}
 575
 576static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info)
 577{
 578        int ret, adc_val;
 579
 580        /* program temperature threshold as 1/16 ADC value */
 581        adc_val = temp_to_adc(info, info->pdata->min_temp);
 582        ret = fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >> 4);
 583
 584        return ret;
 585}
 586
 587static int fuel_gauge_get_property(struct power_supply *ps,
 588                enum power_supply_property prop,
 589                union power_supply_propval *val)
 590{
 591        struct axp288_fg_info *info = power_supply_get_drvdata(ps);
 592        int ret = 0, value;
 593
 594        mutex_lock(&info->lock);
 595        switch (prop) {
 596        case POWER_SUPPLY_PROP_STATUS:
 597                fuel_gauge_get_status(info);
 598                val->intval = info->status;
 599                break;
 600        case POWER_SUPPLY_PROP_HEALTH:
 601                val->intval = fuel_gauge_battery_health(info);
 602                break;
 603        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 604                ret = fuel_gauge_get_vbatt(info, &value);
 605                if (ret < 0)
 606                        goto fuel_gauge_read_err;
 607                val->intval = PROP_VOLT(value);
 608                break;
 609        case POWER_SUPPLY_PROP_VOLTAGE_OCV:
 610                ret = fuel_gauge_get_vocv(info, &value);
 611                if (ret < 0)
 612                        goto fuel_gauge_read_err;
 613                val->intval = PROP_VOLT(value);
 614                break;
 615        case POWER_SUPPLY_PROP_CURRENT_NOW:
 616                ret = fuel_gauge_get_current(info, &value);
 617                if (ret < 0)
 618                        goto fuel_gauge_read_err;
 619                val->intval = PROP_CURR(value);
 620                break;
 621        case POWER_SUPPLY_PROP_PRESENT:
 622                ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
 623                if (ret < 0)
 624                        goto fuel_gauge_read_err;
 625
 626                if (ret & CHRG_STAT_BAT_PRESENT)
 627                        val->intval = 1;
 628                else
 629                        val->intval = 0;
 630                break;
 631        case POWER_SUPPLY_PROP_CAPACITY:
 632                ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
 633                if (ret < 0)
 634                        goto fuel_gauge_read_err;
 635
 636                if (!(ret & FG_REP_CAP_VALID))
 637                        dev_err(&info->pdev->dev,
 638                                "capacity measurement not valid\n");
 639                val->intval = (ret & FG_REP_CAP_VAL_MASK);
 640                break;
 641        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 642                ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
 643                if (ret < 0)
 644                        goto fuel_gauge_read_err;
 645                val->intval = (ret & 0x0f);
 646                break;
 647        case POWER_SUPPLY_PROP_TEMP:
 648                ret = fuel_gauge_get_btemp(info, &value);
 649                if (ret < 0)
 650                        goto fuel_gauge_read_err;
 651                val->intval = PROP_TEMP(value);
 652                break;
 653        case POWER_SUPPLY_PROP_TEMP_MAX:
 654        case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
 655                val->intval = PROP_TEMP(info->pdata->max_temp);
 656                break;
 657        case POWER_SUPPLY_PROP_TEMP_MIN:
 658        case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
 659                val->intval = PROP_TEMP(info->pdata->min_temp);
 660                break;
 661        case POWER_SUPPLY_PROP_TECHNOLOGY:
 662                val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
 663                break;
 664        case POWER_SUPPLY_PROP_CHARGE_NOW:
 665                ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG);
 666                if (ret < 0)
 667                        goto fuel_gauge_read_err;
 668
 669                value = (ret & FG_CC_MTR1_VAL_MASK) << 8;
 670                ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG);
 671                if (ret < 0)
 672                        goto fuel_gauge_read_err;
 673                value |= (ret & FG_CC_MTR0_VAL_MASK);
 674                val->intval = value * FG_DES_CAP_RES_LSB;
 675                break;
 676        case POWER_SUPPLY_PROP_CHARGE_FULL:
 677                ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
 678                if (ret < 0)
 679                        goto fuel_gauge_read_err;
 680
 681                value = (ret & FG_DES_CAP1_VAL_MASK) << 8;
 682                ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG);
 683                if (ret < 0)
 684                        goto fuel_gauge_read_err;
 685                value |= (ret & FG_DES_CAP0_VAL_MASK);
 686                val->intval = value * FG_DES_CAP_RES_LSB;
 687                break;
 688        case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
 689                val->intval = PROP_CURR(info->pdata->design_cap);
 690                break;
 691        case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
 692                val->intval = PROP_VOLT(info->pdata->max_volt);
 693                break;
 694        case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
 695                val->intval = PROP_VOLT(info->pdata->min_volt);
 696                break;
 697        case POWER_SUPPLY_PROP_MODEL_NAME:
 698                val->strval = info->pdata->battid;
 699                break;
 700        default:
 701                mutex_unlock(&info->lock);
 702                return -EINVAL;
 703        }
 704
 705        mutex_unlock(&info->lock);
 706        return 0;
 707
 708fuel_gauge_read_err:
 709        mutex_unlock(&info->lock);
 710        return ret;
 711}
 712
 713static int fuel_gauge_set_property(struct power_supply *ps,
 714                enum power_supply_property prop,
 715                const union power_supply_propval *val)
 716{
 717        struct axp288_fg_info *info = power_supply_get_drvdata(ps);
 718        int ret = 0;
 719
 720        mutex_lock(&info->lock);
 721        switch (prop) {
 722        case POWER_SUPPLY_PROP_STATUS:
 723                info->status = val->intval;
 724                break;
 725        case POWER_SUPPLY_PROP_TEMP_MIN:
 726        case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
 727                if ((val->intval < PD_DEF_MIN_TEMP) ||
 728                        (val->intval > PD_DEF_MAX_TEMP)) {
 729                        ret = -EINVAL;
 730                        break;
 731                }
 732                info->pdata->min_temp = UNPROP_TEMP(val->intval);
 733                ret = fuel_gauge_set_low_btemp_alert(info);
 734                if (ret < 0)
 735                        dev_err(&info->pdev->dev,
 736                                "temp alert min set fail:%d\n", ret);
 737                break;
 738        case POWER_SUPPLY_PROP_TEMP_MAX:
 739        case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
 740                if ((val->intval < PD_DEF_MIN_TEMP) ||
 741                        (val->intval > PD_DEF_MAX_TEMP)) {
 742                        ret = -EINVAL;
 743                        break;
 744                }
 745                info->pdata->max_temp = UNPROP_TEMP(val->intval);
 746                ret = fuel_gauge_set_high_btemp_alert(info);
 747                if (ret < 0)
 748                        dev_err(&info->pdev->dev,
 749                                "temp alert max set fail:%d\n", ret);
 750                break;
 751        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 752                if ((val->intval < 0) || (val->intval > 15)) {
 753                        ret = -EINVAL;
 754                        break;
 755                }
 756                ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
 757                if (ret < 0)
 758                        break;
 759                ret &= 0xf0;
 760                ret |= (val->intval & 0xf);
 761                ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret);
 762                break;
 763        default:
 764                ret = -EINVAL;
 765                break;
 766        }
 767
 768        mutex_unlock(&info->lock);
 769        return ret;
 770}
 771
 772static int fuel_gauge_property_is_writeable(struct power_supply *psy,
 773        enum power_supply_property psp)
 774{
 775        int ret;
 776
 777        switch (psp) {
 778        case POWER_SUPPLY_PROP_STATUS:
 779        case POWER_SUPPLY_PROP_TEMP_MIN:
 780        case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
 781        case POWER_SUPPLY_PROP_TEMP_MAX:
 782        case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
 783        case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
 784                ret = 1;
 785                break;
 786        default:
 787                ret = 0;
 788        }
 789
 790        return ret;
 791}
 792
 793static void fuel_gauge_status_monitor(struct work_struct *work)
 794{
 795        struct axp288_fg_info *info = container_of(work,
 796                struct axp288_fg_info, status_monitor.work);
 797
 798        fuel_gauge_get_status(info);
 799        power_supply_changed(info->bat);
 800        schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
 801}
 802
 803static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
 804{
 805        struct axp288_fg_info *info = dev;
 806        int i;
 807
 808        for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
 809                if (info->irq[i] == irq)
 810                        break;
 811        }
 812
 813        if (i >= AXP288_FG_INTR_NUM) {
 814                dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
 815                return IRQ_NONE;
 816        }
 817
 818        switch (i) {
 819        case QWBTU_IRQ:
 820                dev_info(&info->pdev->dev,
 821                        "Quit Battery under temperature in work mode IRQ (QWBTU)\n");
 822                break;
 823        case WBTU_IRQ:
 824                dev_info(&info->pdev->dev,
 825                        "Battery under temperature in work mode IRQ (WBTU)\n");
 826                break;
 827        case QWBTO_IRQ:
 828                dev_info(&info->pdev->dev,
 829                        "Quit Battery over temperature in work mode IRQ (QWBTO)\n");
 830                break;
 831        case WBTO_IRQ:
 832                dev_info(&info->pdev->dev,
 833                        "Battery over temperature in work mode IRQ (WBTO)\n");
 834                break;
 835        case WL2_IRQ:
 836                dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
 837                break;
 838        case WL1_IRQ:
 839                dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
 840                break;
 841        default:
 842                dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
 843        }
 844
 845        power_supply_changed(info->bat);
 846        return IRQ_HANDLED;
 847}
 848
 849static void fuel_gauge_external_power_changed(struct power_supply *psy)
 850{
 851        struct axp288_fg_info *info = power_supply_get_drvdata(psy);
 852
 853        power_supply_changed(info->bat);
 854}
 855
 856static const struct power_supply_desc fuel_gauge_desc = {
 857        .name                   = DEV_NAME,
 858        .type                   = POWER_SUPPLY_TYPE_BATTERY,
 859        .properties             = fuel_gauge_props,
 860        .num_properties         = ARRAY_SIZE(fuel_gauge_props),
 861        .get_property           = fuel_gauge_get_property,
 862        .set_property           = fuel_gauge_set_property,
 863        .property_is_writeable  = fuel_gauge_property_is_writeable,
 864        .external_power_changed = fuel_gauge_external_power_changed,
 865};
 866
 867static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info)
 868{
 869        int ret;
 870        u8 reg_val;
 871
 872        ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
 873        if (ret < 0) {
 874                dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
 875                return ret;
 876        }
 877        ret = (ret & FG_REP_CAP_VAL_MASK);
 878
 879        if (ret > FG_LOW_CAP_WARN_THR)
 880                reg_val = FG_LOW_CAP_WARN_THR;
 881        else if (ret > FG_LOW_CAP_CRIT_THR)
 882                reg_val = FG_LOW_CAP_CRIT_THR;
 883        else
 884                reg_val = FG_LOW_CAP_SHDN_THR;
 885
 886        reg_val |= FG_LOW_CAP_THR1_VAL;
 887        ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val);
 888        if (ret < 0)
 889                dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
 890
 891        return ret;
 892}
 893
 894static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info)
 895{
 896        int ret;
 897        u8 val;
 898
 899        ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
 900        if (ret < 0)
 901                goto fg_prog_ocv_fail;
 902        else
 903                val = (ret & ~CHRG_CCCV_CV_MASK);
 904
 905        switch (info->pdata->max_volt) {
 906        case CV_4100:
 907                val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS);
 908                break;
 909        case CV_4150:
 910                val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS);
 911                break;
 912        case CV_4200:
 913                val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
 914                break;
 915        case CV_4350:
 916                val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS);
 917                break;
 918        default:
 919                val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
 920                break;
 921        }
 922
 923        ret = fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val);
 924fg_prog_ocv_fail:
 925        return ret;
 926}
 927
 928static int fuel_gauge_program_design_cap(struct axp288_fg_info *info)
 929{
 930        int ret;
 931
 932        ret = fuel_gauge_reg_writeb(info,
 933                AXP288_FG_DES_CAP1_REG, info->pdata->cap1);
 934        if (ret < 0)
 935                goto fg_prog_descap_fail;
 936
 937        ret = fuel_gauge_reg_writeb(info,
 938                AXP288_FG_DES_CAP0_REG, info->pdata->cap0);
 939
 940fg_prog_descap_fail:
 941        return ret;
 942}
 943
 944static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info)
 945{
 946        int ret = 0, i;
 947
 948        for (i = 0; i < OCV_CURVE_SIZE; i++) {
 949                ret = fuel_gauge_reg_writeb(info,
 950                        AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve[i]);
 951                if (ret < 0)
 952                        goto fg_prog_ocv_fail;
 953        }
 954
 955fg_prog_ocv_fail:
 956        return ret;
 957}
 958
 959static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info)
 960{
 961        int ret;
 962
 963        ret = fuel_gauge_reg_writeb(info,
 964                AXP288_FG_RDC1_REG, info->pdata->rdc1);
 965        if (ret < 0)
 966                goto fg_prog_ocv_fail;
 967
 968        ret = fuel_gauge_reg_writeb(info,
 969                AXP288_FG_RDC0_REG, info->pdata->rdc0);
 970
 971fg_prog_ocv_fail:
 972        return ret;
 973}
 974
 975static void fuel_gauge_init_config_regs(struct axp288_fg_info *info)
 976{
 977        int ret;
 978
 979        /*
 980         * check if the config data is already
 981         * programmed and if so just return.
 982         */
 983
 984        ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
 985        if (ret < 0) {
 986                dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n");
 987        } else if (!(ret & FG_DES_CAP1_VALID)) {
 988                dev_info(&info->pdev->dev, "FG data needs to be initialized\n");
 989        } else {
 990                dev_info(&info->pdev->dev, "FG data is already initialized\n");
 991                return;
 992        }
 993
 994        ret = fuel_gauge_program_vbatt_full(info);
 995        if (ret < 0)
 996                dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret);
 997
 998        ret = fuel_gauge_program_design_cap(info);
 999        if (ret < 0)
1000                dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret);
1001
1002        ret = fuel_gauge_program_rdc_vals(info);
1003        if (ret < 0)
1004                dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret);
1005
1006        ret = fuel_gauge_program_ocv_curve(info);
1007        if (ret < 0)
1008                dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret);
1009
1010        ret = fuel_gauge_set_lowbatt_thresholds(info);
1011        if (ret < 0)
1012                dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret);
1013
1014        ret = fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef);
1015        if (ret < 0)
1016                dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret);
1017}
1018
1019static void fuel_gauge_init_irq(struct axp288_fg_info *info)
1020{
1021        int ret, i, pirq;
1022
1023        for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
1024                pirq = platform_get_irq(info->pdev, i);
1025                info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
1026                if (info->irq[i] < 0) {
1027                        dev_warn(&info->pdev->dev,
1028                                "regmap_irq get virq failed for IRQ %d: %d\n",
1029                                pirq, info->irq[i]);
1030                        info->irq[i] = -1;
1031                        goto intr_failed;
1032                }
1033                ret = request_threaded_irq(info->irq[i],
1034                                NULL, fuel_gauge_thread_handler,
1035                                IRQF_ONESHOT, DEV_NAME, info);
1036                if (ret) {
1037                        dev_warn(&info->pdev->dev,
1038                                "request irq failed for IRQ %d: %d\n",
1039                                pirq, info->irq[i]);
1040                        info->irq[i] = -1;
1041                        goto intr_failed;
1042                } else {
1043                        dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n",
1044                                pirq, info->irq[i]);
1045                }
1046        }
1047        return;
1048
1049intr_failed:
1050        for (; i > 0; i--) {
1051                free_irq(info->irq[i - 1], info);
1052                info->irq[i - 1] = -1;
1053        }
1054}
1055
1056static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info)
1057{
1058        int ret;
1059        unsigned int val;
1060
1061        ret = fuel_gauge_set_high_btemp_alert(info);
1062        if (ret < 0)
1063                dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", ret);
1064
1065        ret = fuel_gauge_set_low_btemp_alert(info);
1066        if (ret < 0)
1067                dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", ret);
1068
1069        /* enable interrupts */
1070        val = fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN);
1071        val |= TEMP_IRQ_CFG_MASK;
1072        fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val);
1073
1074        val = fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN);
1075        val |= FG_IRQ_CFG_LOWBATT_MASK;
1076        val = fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val);
1077}
1078
1079static int axp288_fuel_gauge_probe(struct platform_device *pdev)
1080{
1081        int ret = 0;
1082        struct axp288_fg_info *info;
1083        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
1084        struct power_supply_config psy_cfg = {};
1085
1086        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
1087        if (!info)
1088                return -ENOMEM;
1089
1090        info->pdev = pdev;
1091        info->regmap = axp20x->regmap;
1092        info->regmap_irqc = axp20x->regmap_irqc;
1093        info->status = POWER_SUPPLY_STATUS_UNKNOWN;
1094        info->pdata = pdev->dev.platform_data;
1095        if (!info->pdata)
1096                return -ENODEV;
1097
1098        platform_set_drvdata(pdev, info);
1099
1100        mutex_init(&info->lock);
1101        INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
1102
1103        psy_cfg.drv_data = info;
1104        info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
1105        if (IS_ERR(info->bat)) {
1106                ret = PTR_ERR(info->bat);
1107                dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
1108                return ret;
1109        }
1110
1111        fuel_gauge_create_debugfs(info);
1112        fuel_gauge_init_config_regs(info);
1113        fuel_gauge_init_irq(info);
1114        fuel_gauge_init_hw_regs(info);
1115        schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
1116
1117        return ret;
1118}
1119
1120static const struct platform_device_id axp288_fg_id_table[] = {
1121        { .name = DEV_NAME },
1122        {},
1123};
1124
1125static int axp288_fuel_gauge_remove(struct platform_device *pdev)
1126{
1127        struct axp288_fg_info *info = platform_get_drvdata(pdev);
1128        int i;
1129
1130        cancel_delayed_work_sync(&info->status_monitor);
1131        power_supply_unregister(info->bat);
1132        fuel_gauge_remove_debugfs(info);
1133
1134        for (i = 0; i < AXP288_FG_INTR_NUM; i++)
1135                if (info->irq[i] >= 0)
1136                        free_irq(info->irq[i], info);
1137
1138        return 0;
1139}
1140
1141static struct platform_driver axp288_fuel_gauge_driver = {
1142        .probe = axp288_fuel_gauge_probe,
1143        .remove = axp288_fuel_gauge_remove,
1144        .id_table = axp288_fg_id_table,
1145        .driver = {
1146                .name = DEV_NAME,
1147        },
1148};
1149
1150module_platform_driver(axp288_fuel_gauge_driver);
1151
1152MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
1153MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
1154MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
1155MODULE_LICENSE("GPL");
1156