18e8e69d6SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 25a5bf490STodd Brandt /* 35a5bf490STodd Brandt * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver 45a5bf490STodd Brandt * 5394088f0SAndrejus Basovas * Copyright (C) 2020-2021 Andrejus Basovas <xxx@yyy.tld> 6394088f0SAndrejus Basovas * Copyright (C) 2016-2021 Hans de Goede <hdegoede@redhat.com> 75a5bf490STodd Brandt * Copyright (C) 2014 Intel Corporation 85a5bf490STodd Brandt * 95a5bf490STodd Brandt * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 105a5bf490STodd Brandt */ 115a5bf490STodd Brandt 12b60c75b6SHans de Goede #include <linux/dmi.h> 135a5bf490STodd Brandt #include <linux/module.h> 145a5bf490STodd Brandt #include <linux/kernel.h> 155a5bf490STodd Brandt #include <linux/device.h> 165a5bf490STodd Brandt #include <linux/regmap.h> 175a5bf490STodd Brandt #include <linux/jiffies.h> 185a5bf490STodd Brandt #include <linux/interrupt.h> 195a5bf490STodd Brandt #include <linux/mfd/axp20x.h> 205a5bf490STodd Brandt #include <linux/platform_device.h> 215a5bf490STodd Brandt #include <linux/power_supply.h> 225a5bf490STodd Brandt #include <linux/iio/consumer.h> 234949fc5eSHans de Goede #include <asm/unaligned.h> 24394088f0SAndrejus Basovas #include <asm/iosf_mbi.h> 255a5bf490STodd Brandt 262b5a4b4bSHans de Goede #define PS_STAT_VBUS_TRIGGER (1 << 0) 272b5a4b4bSHans de Goede #define PS_STAT_BAT_CHRG_DIR (1 << 2) 282b5a4b4bSHans de Goede #define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) 292b5a4b4bSHans de Goede #define PS_STAT_VBUS_VALID (1 << 4) 302b5a4b4bSHans de Goede #define PS_STAT_VBUS_PRESENT (1 << 5) 312b5a4b4bSHans de Goede 325a5bf490STodd Brandt #define CHRG_STAT_BAT_SAFE_MODE (1 << 3) 335a5bf490STodd Brandt #define CHRG_STAT_BAT_VALID (1 << 4) 345a5bf490STodd Brandt #define CHRG_STAT_BAT_PRESENT (1 << 5) 355a5bf490STodd Brandt #define CHRG_STAT_CHARGING (1 << 6) 365a5bf490STodd Brandt #define CHRG_STAT_PMIC_OTP (1 << 7) 375a5bf490STodd Brandt 385a5bf490STodd Brandt #define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ 395a5bf490STodd Brandt #define CHRG_CCCV_CC_BIT_POS 0 405a5bf490STodd Brandt #define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ 415a5bf490STodd Brandt #define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ 425a5bf490STodd Brandt #define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ 435a5bf490STodd Brandt #define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ 445a5bf490STodd Brandt #define CHRG_CCCV_CV_BIT_POS 5 455a5bf490STodd Brandt #define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ 465a5bf490STodd Brandt #define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ 475a5bf490STodd Brandt #define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ 485a5bf490STodd Brandt #define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ 495a5bf490STodd Brandt #define CHRG_CCCV_CHG_EN (1 << 7) 505a5bf490STodd Brandt 515a5bf490STodd Brandt #define FG_CNTL_OCV_ADJ_STAT (1 << 2) 525a5bf490STodd Brandt #define FG_CNTL_OCV_ADJ_EN (1 << 3) 535a5bf490STodd Brandt #define FG_CNTL_CAP_ADJ_STAT (1 << 4) 545a5bf490STodd Brandt #define FG_CNTL_CAP_ADJ_EN (1 << 5) 555a5bf490STodd Brandt #define FG_CNTL_CC_EN (1 << 6) 565a5bf490STodd Brandt #define FG_CNTL_GAUGE_EN (1 << 7) 575a5bf490STodd Brandt 584949fc5eSHans de Goede #define FG_15BIT_WORD_VALID (1 << 15) 594949fc5eSHans de Goede #define FG_15BIT_VAL_MASK 0x7fff 604949fc5eSHans de Goede 615a5bf490STodd Brandt #define FG_REP_CAP_VALID (1 << 7) 625a5bf490STodd Brandt #define FG_REP_CAP_VAL_MASK 0x7F 635a5bf490STodd Brandt 645a5bf490STodd Brandt #define FG_DES_CAP1_VALID (1 << 7) 655a5bf490STodd Brandt #define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */ 665a5bf490STodd Brandt 675a5bf490STodd Brandt #define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */ 685a5bf490STodd Brandt 695a5bf490STodd Brandt #define FG_OCV_CAP_VALID (1 << 7) 705a5bf490STodd Brandt #define FG_OCV_CAP_VAL_MASK 0x7F 715a5bf490STodd Brandt #define FG_CC_CAP_VALID (1 << 7) 725a5bf490STodd Brandt #define FG_CC_CAP_VAL_MASK 0x7F 735a5bf490STodd Brandt 745a5bf490STodd Brandt #define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ 755a5bf490STodd Brandt #define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ 765a5bf490STodd Brandt #define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ 775a5bf490STodd Brandt #define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ 785a5bf490STodd Brandt #define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ 795a5bf490STodd Brandt #define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ 805a5bf490STodd Brandt 815a5bf490STodd Brandt #define DEV_NAME "axp288_fuel_gauge" 825a5bf490STodd Brandt 835a5bf490STodd Brandt /* 1.1mV per LSB expressed in uV */ 845a5bf490STodd Brandt #define VOLTAGE_FROM_ADC(a) ((a * 11) / 10) 85888f9743SHans de Goede /* properties converted to uV, uA */ 865a5bf490STodd Brandt #define PROP_VOLT(a) ((a) * 1000) 875a5bf490STodd Brandt #define PROP_CURR(a) ((a) * 1000) 885a5bf490STodd Brandt 89394088f0SAndrejus Basovas #define AXP288_REG_UPDATE_INTERVAL (60 * HZ) 905a5bf490STodd Brandt #define AXP288_FG_INTR_NUM 6 915a5bf490STodd Brandt enum { 925a5bf490STodd Brandt QWBTU_IRQ = 0, 935a5bf490STodd Brandt WBTU_IRQ, 945a5bf490STodd Brandt QWBTO_IRQ, 955a5bf490STodd Brandt WBTO_IRQ, 965a5bf490STodd Brandt WL2_IRQ, 975a5bf490STodd Brandt WL1_IRQ, 985a5bf490STodd Brandt }; 995a5bf490STodd Brandt 100331645e1SHans de Goede enum { 101331645e1SHans de Goede BAT_CHRG_CURR, 102331645e1SHans de Goede BAT_D_CURR, 103331645e1SHans de Goede BAT_VOLT, 104331645e1SHans de Goede IIO_CHANNEL_NUM 105331645e1SHans de Goede }; 106331645e1SHans de Goede 1075a5bf490STodd Brandt struct axp288_fg_info { 1087eef3e66SHans de Goede struct device *dev; 1095a5bf490STodd Brandt struct regmap *regmap; 1105a5bf490STodd Brandt struct regmap_irq_chip_data *regmap_irqc; 1115a5bf490STodd Brandt int irq[AXP288_FG_INTR_NUM]; 112331645e1SHans de Goede struct iio_channel *iio_channel[IIO_CHANNEL_NUM]; 113297d716fSKrzysztof Kozlowski struct power_supply *bat; 1145a5bf490STodd Brandt struct mutex lock; 1155a5bf490STodd Brandt int status; 116888f9743SHans de Goede int max_volt; 117c371d449SHans de Goede int pwr_op; 118c371d449SHans de Goede int low_cap; 1195a5bf490STodd Brandt struct dentry *debug_file; 120394088f0SAndrejus Basovas 121394088f0SAndrejus Basovas char valid; /* zero until following fields are valid */ 122394088f0SAndrejus Basovas unsigned long last_updated; /* in jiffies */ 123394088f0SAndrejus Basovas 124394088f0SAndrejus Basovas int pwr_stat; 125394088f0SAndrejus Basovas int fg_res; 126394088f0SAndrejus Basovas int bat_volt; 127394088f0SAndrejus Basovas int d_curr; 128394088f0SAndrejus Basovas int c_curr; 129394088f0SAndrejus Basovas int ocv; 130394088f0SAndrejus Basovas int fg_cc_mtr1; 131394088f0SAndrejus Basovas int fg_des_cap1; 1325a5bf490STodd Brandt }; 1335a5bf490STodd Brandt 1345a5bf490STodd Brandt static enum power_supply_property fuel_gauge_props[] = { 1355a5bf490STodd Brandt POWER_SUPPLY_PROP_STATUS, 1365a5bf490STodd Brandt POWER_SUPPLY_PROP_PRESENT, 1375a5bf490STodd Brandt POWER_SUPPLY_PROP_HEALTH, 1385a5bf490STodd Brandt POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 1395a5bf490STodd Brandt POWER_SUPPLY_PROP_VOLTAGE_NOW, 1405a5bf490STodd Brandt POWER_SUPPLY_PROP_VOLTAGE_OCV, 1415a5bf490STodd Brandt POWER_SUPPLY_PROP_CURRENT_NOW, 1425a5bf490STodd Brandt POWER_SUPPLY_PROP_CAPACITY, 1435a5bf490STodd Brandt POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, 1445a5bf490STodd Brandt POWER_SUPPLY_PROP_TECHNOLOGY, 1455a5bf490STodd Brandt POWER_SUPPLY_PROP_CHARGE_FULL, 1465a5bf490STodd Brandt POWER_SUPPLY_PROP_CHARGE_NOW, 1475a5bf490STodd Brandt }; 1485a5bf490STodd Brandt 1495a5bf490STodd Brandt static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) 1505a5bf490STodd Brandt { 1515a5bf490STodd Brandt unsigned int val; 152f17bda7fSHans de Goede int ret; 1535a5bf490STodd Brandt 1545a5bf490STodd Brandt ret = regmap_read(info->regmap, reg, &val); 1556f074bc8SHans de Goede if (ret < 0) { 1567eef3e66SHans de Goede dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret); 1576f074bc8SHans de Goede return ret; 1586f074bc8SHans de Goede } 1595a5bf490STodd Brandt 1605a5bf490STodd Brandt return val; 1615a5bf490STodd Brandt } 1625a5bf490STodd Brandt 1635a5bf490STodd Brandt static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) 1645a5bf490STodd Brandt { 1655a5bf490STodd Brandt int ret; 1665a5bf490STodd Brandt 1675a5bf490STodd Brandt ret = regmap_write(info->regmap, reg, (unsigned int)val); 1685a5bf490STodd Brandt 1695a5bf490STodd Brandt if (ret < 0) 1707eef3e66SHans de Goede dev_err(info->dev, "Error writing reg 0x%02x err: %d\n", reg, ret); 1715a5bf490STodd Brandt 1725a5bf490STodd Brandt return ret; 1735a5bf490STodd Brandt } 1745a5bf490STodd Brandt 1754949fc5eSHans de Goede static int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg) 1764949fc5eSHans de Goede { 1774949fc5eSHans de Goede unsigned char buf[2]; 1784949fc5eSHans de Goede int ret; 1794949fc5eSHans de Goede 1804949fc5eSHans de Goede ret = regmap_bulk_read(info->regmap, reg, buf, 2); 1814949fc5eSHans de Goede if (ret < 0) { 1827eef3e66SHans de Goede dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret); 1834949fc5eSHans de Goede return ret; 1844949fc5eSHans de Goede } 1854949fc5eSHans de Goede 1864949fc5eSHans de Goede ret = get_unaligned_be16(buf); 1874949fc5eSHans de Goede if (!(ret & FG_15BIT_WORD_VALID)) { 1887eef3e66SHans de Goede dev_err(info->dev, "Error reg 0x%02x contents not valid\n", reg); 1894949fc5eSHans de Goede return -ENXIO; 1904949fc5eSHans de Goede } 1914949fc5eSHans de Goede 1924949fc5eSHans de Goede return ret & FG_15BIT_VAL_MASK; 1934949fc5eSHans de Goede } 1944949fc5eSHans de Goede 195248efcf0SHans de Goede static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg) 196248efcf0SHans de Goede { 197248efcf0SHans de Goede unsigned char buf[2]; 198248efcf0SHans de Goede int ret; 199248efcf0SHans de Goede 200248efcf0SHans de Goede ret = regmap_bulk_read(info->regmap, reg, buf, 2); 201248efcf0SHans de Goede if (ret < 0) { 2027eef3e66SHans de Goede dev_err(info->dev, "Error reading reg 0x%02x err: %d\n", reg, ret); 203248efcf0SHans de Goede return ret; 204248efcf0SHans de Goede } 205248efcf0SHans de Goede 206248efcf0SHans de Goede /* 12-bit data values have upper 8 bits in buf[0], lower 4 in buf[1] */ 207248efcf0SHans de Goede return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f); 208248efcf0SHans de Goede } 209248efcf0SHans de Goede 210394088f0SAndrejus Basovas static int fuel_gauge_update_registers(struct axp288_fg_info *info) 211394088f0SAndrejus Basovas { 212394088f0SAndrejus Basovas int ret; 213394088f0SAndrejus Basovas 214394088f0SAndrejus Basovas if (info->valid && time_before(jiffies, info->last_updated + AXP288_REG_UPDATE_INTERVAL)) 215394088f0SAndrejus Basovas return 0; 216394088f0SAndrejus Basovas 217394088f0SAndrejus Basovas dev_dbg(info->dev, "Fuel Gauge updating register values...\n"); 218394088f0SAndrejus Basovas 219394088f0SAndrejus Basovas ret = iosf_mbi_block_punit_i2c_access(); 220394088f0SAndrejus Basovas if (ret < 0) 221394088f0SAndrejus Basovas return ret; 222394088f0SAndrejus Basovas 223394088f0SAndrejus Basovas ret = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); 224394088f0SAndrejus Basovas if (ret < 0) 225394088f0SAndrejus Basovas goto out; 226394088f0SAndrejus Basovas info->pwr_stat = ret; 227394088f0SAndrejus Basovas 228394088f0SAndrejus Basovas ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 229394088f0SAndrejus Basovas if (ret < 0) 230394088f0SAndrejus Basovas goto out; 231394088f0SAndrejus Basovas info->fg_res = ret; 232394088f0SAndrejus Basovas 233394088f0SAndrejus Basovas ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &info->bat_volt); 234394088f0SAndrejus Basovas if (ret < 0) 235394088f0SAndrejus Basovas goto out; 236394088f0SAndrejus Basovas 237394088f0SAndrejus Basovas if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) { 238394088f0SAndrejus Basovas info->d_curr = 0; 239394088f0SAndrejus Basovas ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr); 240394088f0SAndrejus Basovas if (ret < 0) 241394088f0SAndrejus Basovas goto out; 242394088f0SAndrejus Basovas } else { 243394088f0SAndrejus Basovas info->c_curr = 0; 244394088f0SAndrejus Basovas ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &info->d_curr); 245394088f0SAndrejus Basovas if (ret < 0) 246394088f0SAndrejus Basovas goto out; 247394088f0SAndrejus Basovas } 248394088f0SAndrejus Basovas 249394088f0SAndrejus Basovas ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); 250394088f0SAndrejus Basovas if (ret < 0) 251394088f0SAndrejus Basovas goto out; 252394088f0SAndrejus Basovas info->ocv = ret; 253394088f0SAndrejus Basovas 254394088f0SAndrejus Basovas ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); 255394088f0SAndrejus Basovas if (ret < 0) 256394088f0SAndrejus Basovas goto out; 257394088f0SAndrejus Basovas info->fg_cc_mtr1 = ret; 258394088f0SAndrejus Basovas 259394088f0SAndrejus Basovas ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG); 260394088f0SAndrejus Basovas if (ret < 0) 261394088f0SAndrejus Basovas goto out; 262394088f0SAndrejus Basovas info->fg_des_cap1 = ret; 263394088f0SAndrejus Basovas 264394088f0SAndrejus Basovas info->last_updated = jiffies; 265394088f0SAndrejus Basovas info->valid = 1; 266394088f0SAndrejus Basovas ret = 0; 267394088f0SAndrejus Basovas out: 268394088f0SAndrejus Basovas iosf_mbi_unblock_punit_i2c_access(); 269394088f0SAndrejus Basovas return ret; 270394088f0SAndrejus Basovas } 271394088f0SAndrejus Basovas 2725a5bf490STodd Brandt static void fuel_gauge_get_status(struct axp288_fg_info *info) 2735a5bf490STodd Brandt { 274394088f0SAndrejus Basovas int pwr_stat = info->pwr_stat; 275394088f0SAndrejus Basovas int fg_res = info->fg_res; 276394088f0SAndrejus Basovas int curr = info->d_curr; 2772b5a4b4bSHans de Goede 2782b5a4b4bSHans de Goede /* Report full if Vbus is valid and the reported capacity is 100% */ 279f451655cSHans de Goede if (!(pwr_stat & PS_STAT_VBUS_VALID)) 280f451655cSHans de Goede goto not_full; 281f451655cSHans de Goede 282f451655cSHans de Goede if (!(fg_res & FG_REP_CAP_VALID)) 283f451655cSHans de Goede goto not_full; 284f451655cSHans de Goede 285f451655cSHans de Goede fg_res &= ~FG_REP_CAP_VALID; 286f451655cSHans de Goede if (fg_res == 100) { 2872b5a4b4bSHans de Goede info->status = POWER_SUPPLY_STATUS_FULL; 2885a5bf490STodd Brandt return; 2895a5bf490STodd Brandt } 290f451655cSHans de Goede 291f451655cSHans de Goede /* 292f451655cSHans de Goede * Sometimes the charger turns itself off before fg-res reaches 100%. 293f451655cSHans de Goede * When this happens the AXP288 reports a not-charging status and 294f451655cSHans de Goede * 0 mA discharge current. 295f451655cSHans de Goede */ 296f451655cSHans de Goede if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR)) 297f451655cSHans de Goede goto not_full; 298f451655cSHans de Goede 299f451655cSHans de Goede if (curr == 0) { 300f451655cSHans de Goede info->status = POWER_SUPPLY_STATUS_FULL; 301f451655cSHans de Goede return; 3022b5a4b4bSHans de Goede } 3035a5bf490STodd Brandt 304f451655cSHans de Goede not_full: 3052b5a4b4bSHans de Goede if (pwr_stat & PS_STAT_BAT_CHRG_DIR) 3065a5bf490STodd Brandt info->status = POWER_SUPPLY_STATUS_CHARGING; 3075a5bf490STodd Brandt else 3082b5a4b4bSHans de Goede info->status = POWER_SUPPLY_STATUS_DISCHARGING; 3095a5bf490STodd Brandt } 3105a5bf490STodd Brandt 3115a5bf490STodd Brandt static int fuel_gauge_battery_health(struct axp288_fg_info *info) 3125a5bf490STodd Brandt { 313394088f0SAndrejus Basovas int vocv = VOLTAGE_FROM_ADC(info->ocv); 314394088f0SAndrejus Basovas int health = POWER_SUPPLY_HEALTH_UNKNOWN; 3155a5bf490STodd Brandt 316888f9743SHans de Goede if (vocv > info->max_volt) 3175a5bf490STodd Brandt health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 3185a5bf490STodd Brandt else 3195a5bf490STodd Brandt health = POWER_SUPPLY_HEALTH_GOOD; 3205a5bf490STodd Brandt 3215a5bf490STodd Brandt return health; 3225a5bf490STodd Brandt } 3235a5bf490STodd Brandt 3245a5bf490STodd Brandt static int fuel_gauge_get_property(struct power_supply *ps, 3255a5bf490STodd Brandt enum power_supply_property prop, 3265a5bf490STodd Brandt union power_supply_propval *val) 3275a5bf490STodd Brandt { 328297d716fSKrzysztof Kozlowski struct axp288_fg_info *info = power_supply_get_drvdata(ps); 329394088f0SAndrejus Basovas int ret, value; 3305a5bf490STodd Brandt 3315a5bf490STodd Brandt mutex_lock(&info->lock); 332394088f0SAndrejus Basovas 333394088f0SAndrejus Basovas ret = fuel_gauge_update_registers(info); 334394088f0SAndrejus Basovas if (ret < 0) 335394088f0SAndrejus Basovas goto out; 336394088f0SAndrejus Basovas 3375a5bf490STodd Brandt switch (prop) { 3385a5bf490STodd Brandt case POWER_SUPPLY_PROP_STATUS: 3395a5bf490STodd Brandt fuel_gauge_get_status(info); 3405a5bf490STodd Brandt val->intval = info->status; 3415a5bf490STodd Brandt break; 3425a5bf490STodd Brandt case POWER_SUPPLY_PROP_HEALTH: 3435a5bf490STodd Brandt val->intval = fuel_gauge_battery_health(info); 3445a5bf490STodd Brandt break; 3455a5bf490STodd Brandt case POWER_SUPPLY_PROP_VOLTAGE_NOW: 346394088f0SAndrejus Basovas value = VOLTAGE_FROM_ADC(info->bat_volt); 3475a5bf490STodd Brandt val->intval = PROP_VOLT(value); 3485a5bf490STodd Brandt break; 3495a5bf490STodd Brandt case POWER_SUPPLY_PROP_VOLTAGE_OCV: 350394088f0SAndrejus Basovas value = VOLTAGE_FROM_ADC(info->ocv); 3515a5bf490STodd Brandt val->intval = PROP_VOLT(value); 3525a5bf490STodd Brandt break; 3535a5bf490STodd Brandt case POWER_SUPPLY_PROP_CURRENT_NOW: 354394088f0SAndrejus Basovas if (info->d_curr > 0) 355394088f0SAndrejus Basovas value = -1 * info->d_curr; 356394088f0SAndrejus Basovas else 357394088f0SAndrejus Basovas value = info->c_curr; 358394088f0SAndrejus Basovas 3595a5bf490STodd Brandt val->intval = PROP_CURR(value); 3605a5bf490STodd Brandt break; 3615a5bf490STodd Brandt case POWER_SUPPLY_PROP_PRESENT: 362c371d449SHans de Goede if (info->pwr_op & CHRG_STAT_BAT_PRESENT) 3635a5bf490STodd Brandt val->intval = 1; 3645a5bf490STodd Brandt else 3655a5bf490STodd Brandt val->intval = 0; 3665a5bf490STodd Brandt break; 3675a5bf490STodd Brandt case POWER_SUPPLY_PROP_CAPACITY: 368394088f0SAndrejus Basovas if (!(info->fg_res & FG_REP_CAP_VALID)) 3697eef3e66SHans de Goede dev_err(info->dev, "capacity measurement not valid\n"); 370394088f0SAndrejus Basovas val->intval = (info->fg_res & FG_REP_CAP_VAL_MASK); 3715a5bf490STodd Brandt break; 3725a5bf490STodd Brandt case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 373c371d449SHans de Goede val->intval = (info->low_cap & 0x0f); 3745a5bf490STodd Brandt break; 3755a5bf490STodd Brandt case POWER_SUPPLY_PROP_TECHNOLOGY: 3765a5bf490STodd Brandt val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 3775a5bf490STodd Brandt break; 3785a5bf490STodd Brandt case POWER_SUPPLY_PROP_CHARGE_NOW: 379394088f0SAndrejus Basovas val->intval = info->fg_cc_mtr1 * FG_DES_CAP_RES_LSB; 3805a5bf490STodd Brandt break; 3815a5bf490STodd Brandt case POWER_SUPPLY_PROP_CHARGE_FULL: 382394088f0SAndrejus Basovas val->intval = info->fg_des_cap1 * FG_DES_CAP_RES_LSB; 3835a5bf490STodd Brandt break; 3845a5bf490STodd Brandt case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 385888f9743SHans de Goede val->intval = PROP_VOLT(info->max_volt); 3865a5bf490STodd Brandt break; 3875a5bf490STodd Brandt default: 388394088f0SAndrejus Basovas ret = -EINVAL; 3895a5bf490STodd Brandt } 3905a5bf490STodd Brandt 391394088f0SAndrejus Basovas out: 3925a5bf490STodd Brandt mutex_unlock(&info->lock); 3935a5bf490STodd Brandt return ret; 3945a5bf490STodd Brandt } 3955a5bf490STodd Brandt 3965a5bf490STodd Brandt static int fuel_gauge_set_property(struct power_supply *ps, 3975a5bf490STodd Brandt enum power_supply_property prop, 3985a5bf490STodd Brandt const union power_supply_propval *val) 3995a5bf490STodd Brandt { 400297d716fSKrzysztof Kozlowski struct axp288_fg_info *info = power_supply_get_drvdata(ps); 401c371d449SHans de Goede int new_low_cap, ret = 0; 4025a5bf490STodd Brandt 4035a5bf490STodd Brandt mutex_lock(&info->lock); 4045a5bf490STodd Brandt switch (prop) { 4055a5bf490STodd Brandt case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 4065a5bf490STodd Brandt if ((val->intval < 0) || (val->intval > 15)) { 4075a5bf490STodd Brandt ret = -EINVAL; 4085a5bf490STodd Brandt break; 4095a5bf490STodd Brandt } 410c371d449SHans de Goede new_low_cap = info->low_cap; 411c371d449SHans de Goede new_low_cap &= 0xf0; 412c371d449SHans de Goede new_low_cap |= (val->intval & 0xf); 413c371d449SHans de Goede ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, new_low_cap); 414c371d449SHans de Goede if (ret == 0) 415c371d449SHans de Goede info->low_cap = new_low_cap; 4165a5bf490STodd Brandt break; 4175a5bf490STodd Brandt default: 4185a5bf490STodd Brandt ret = -EINVAL; 4195a5bf490STodd Brandt break; 4205a5bf490STodd Brandt } 4215a5bf490STodd Brandt 4225a5bf490STodd Brandt mutex_unlock(&info->lock); 4235a5bf490STodd Brandt return ret; 4245a5bf490STodd Brandt } 4255a5bf490STodd Brandt 4265a5bf490STodd Brandt static int fuel_gauge_property_is_writeable(struct power_supply *psy, 4275a5bf490STodd Brandt enum power_supply_property psp) 4285a5bf490STodd Brandt { 4295a5bf490STodd Brandt int ret; 4305a5bf490STodd Brandt 4315a5bf490STodd Brandt switch (psp) { 4325a5bf490STodd Brandt case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 4335a5bf490STodd Brandt ret = 1; 4345a5bf490STodd Brandt break; 4355a5bf490STodd Brandt default: 4365a5bf490STodd Brandt ret = 0; 4375a5bf490STodd Brandt } 4385a5bf490STodd Brandt 4395a5bf490STodd Brandt return ret; 4405a5bf490STodd Brandt } 4415a5bf490STodd Brandt 4425a5bf490STodd Brandt static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) 4435a5bf490STodd Brandt { 4445a5bf490STodd Brandt struct axp288_fg_info *info = dev; 4455a5bf490STodd Brandt int i; 4465a5bf490STodd Brandt 4475a5bf490STodd Brandt for (i = 0; i < AXP288_FG_INTR_NUM; i++) { 4485a5bf490STodd Brandt if (info->irq[i] == irq) 4495a5bf490STodd Brandt break; 4505a5bf490STodd Brandt } 4515a5bf490STodd Brandt 4525a5bf490STodd Brandt if (i >= AXP288_FG_INTR_NUM) { 4537eef3e66SHans de Goede dev_warn(info->dev, "spurious interrupt!!\n"); 4545a5bf490STodd Brandt return IRQ_NONE; 4555a5bf490STodd Brandt } 4565a5bf490STodd Brandt 4575a5bf490STodd Brandt switch (i) { 4585a5bf490STodd Brandt case QWBTU_IRQ: 4597eef3e66SHans de Goede dev_info(info->dev, "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); 4605a5bf490STodd Brandt break; 4615a5bf490STodd Brandt case WBTU_IRQ: 4627eef3e66SHans de Goede dev_info(info->dev, "Battery under temperature in work mode IRQ (WBTU)\n"); 4635a5bf490STodd Brandt break; 4645a5bf490STodd Brandt case QWBTO_IRQ: 4657eef3e66SHans de Goede dev_info(info->dev, "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); 4665a5bf490STodd Brandt break; 4675a5bf490STodd Brandt case WBTO_IRQ: 4687eef3e66SHans de Goede dev_info(info->dev, "Battery over temperature in work mode IRQ (WBTO)\n"); 4695a5bf490STodd Brandt break; 4705a5bf490STodd Brandt case WL2_IRQ: 4717eef3e66SHans de Goede dev_info(info->dev, "Low Batt Warning(2) INTR\n"); 4725a5bf490STodd Brandt break; 4735a5bf490STodd Brandt case WL1_IRQ: 4747eef3e66SHans de Goede dev_info(info->dev, "Low Batt Warning(1) INTR\n"); 4755a5bf490STodd Brandt break; 4765a5bf490STodd Brandt default: 4777eef3e66SHans de Goede dev_warn(info->dev, "Spurious Interrupt!!!\n"); 4785a5bf490STodd Brandt } 4795a5bf490STodd Brandt 480394088f0SAndrejus Basovas info->valid = 0; /* Force updating of the cached registers */ 481394088f0SAndrejus Basovas 482297d716fSKrzysztof Kozlowski power_supply_changed(info->bat); 4835a5bf490STodd Brandt return IRQ_HANDLED; 4845a5bf490STodd Brandt } 4855a5bf490STodd Brandt 4865a5bf490STodd Brandt static void fuel_gauge_external_power_changed(struct power_supply *psy) 4875a5bf490STodd Brandt { 488297d716fSKrzysztof Kozlowski struct axp288_fg_info *info = power_supply_get_drvdata(psy); 4895a5bf490STodd Brandt 490394088f0SAndrejus Basovas info->valid = 0; /* Force updating of the cached registers */ 491297d716fSKrzysztof Kozlowski power_supply_changed(info->bat); 4925a5bf490STodd Brandt } 4935a5bf490STodd Brandt 494297d716fSKrzysztof Kozlowski static const struct power_supply_desc fuel_gauge_desc = { 495297d716fSKrzysztof Kozlowski .name = DEV_NAME, 496297d716fSKrzysztof Kozlowski .type = POWER_SUPPLY_TYPE_BATTERY, 497297d716fSKrzysztof Kozlowski .properties = fuel_gauge_props, 498297d716fSKrzysztof Kozlowski .num_properties = ARRAY_SIZE(fuel_gauge_props), 499297d716fSKrzysztof Kozlowski .get_property = fuel_gauge_get_property, 500297d716fSKrzysztof Kozlowski .set_property = fuel_gauge_set_property, 501297d716fSKrzysztof Kozlowski .property_is_writeable = fuel_gauge_property_is_writeable, 502297d716fSKrzysztof Kozlowski .external_power_changed = fuel_gauge_external_power_changed, 503297d716fSKrzysztof Kozlowski }; 504297d716fSKrzysztof Kozlowski 5057eef3e66SHans de Goede static void fuel_gauge_init_irq(struct axp288_fg_info *info, struct platform_device *pdev) 5065a5bf490STodd Brandt { 5075a5bf490STodd Brandt int ret, i, pirq; 5085a5bf490STodd Brandt 5095a5bf490STodd Brandt for (i = 0; i < AXP288_FG_INTR_NUM; i++) { 5107eef3e66SHans de Goede pirq = platform_get_irq(pdev, i); 5115a5bf490STodd Brandt info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); 5125a5bf490STodd Brandt if (info->irq[i] < 0) { 5137eef3e66SHans de Goede dev_warn(info->dev, "regmap_irq get virq failed for IRQ %d: %d\n", 5145a5bf490STodd Brandt pirq, info->irq[i]); 5155a5bf490STodd Brandt info->irq[i] = -1; 5165a5bf490STodd Brandt goto intr_failed; 5175a5bf490STodd Brandt } 5185a5bf490STodd Brandt ret = request_threaded_irq(info->irq[i], 5195a5bf490STodd Brandt NULL, fuel_gauge_thread_handler, 5205a5bf490STodd Brandt IRQF_ONESHOT, DEV_NAME, info); 5215a5bf490STodd Brandt if (ret) { 5227eef3e66SHans de Goede dev_warn(info->dev, "request irq failed for IRQ %d: %d\n", 5235a5bf490STodd Brandt pirq, info->irq[i]); 5245a5bf490STodd Brandt info->irq[i] = -1; 5255a5bf490STodd Brandt goto intr_failed; 5265a5bf490STodd Brandt } 5275a5bf490STodd Brandt } 5285a5bf490STodd Brandt return; 5295a5bf490STodd Brandt 5305a5bf490STodd Brandt intr_failed: 5315a5bf490STodd Brandt for (; i > 0; i--) { 5325a5bf490STodd Brandt free_irq(info->irq[i - 1], info); 5335a5bf490STodd Brandt info->irq[i - 1] = -1; 5345a5bf490STodd Brandt } 5355a5bf490STodd Brandt } 5365a5bf490STodd Brandt 537b60c75b6SHans de Goede /* 538b60c75b6SHans de Goede * Some devices have no battery (HDMI sticks) and the axp288 battery's 539b60c75b6SHans de Goede * detection reports one despite it not being there. 5406f3ed834SHans de Goede * Please keep this listed sorted alphabetically. 541b60c75b6SHans de Goede */ 5420973e96bSHans de Goede static const struct dmi_system_id axp288_no_battery_list[] = { 543b60c75b6SHans de Goede { 5449274c783SHans de Goede /* ACEPC T8 Cherry Trail Z8350 mini PC */ 5459274c783SHans de Goede .matches = { 5469274c783SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), 5479274c783SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), 5489274c783SHans de Goede DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"), 5499274c783SHans de Goede /* also match on somewhat unique bios-version */ 5509274c783SHans de Goede DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), 5519274c783SHans de Goede }, 5529274c783SHans de Goede }, 5539274c783SHans de Goede { 5549274c783SHans de Goede /* ACEPC T11 Cherry Trail Z8350 mini PC */ 5559274c783SHans de Goede .matches = { 5569274c783SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), 5579274c783SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), 5589274c783SHans de Goede DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"), 5599274c783SHans de Goede /* also match on somewhat unique bios-version */ 5609274c783SHans de Goede DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), 5619274c783SHans de Goede }, 5629274c783SHans de Goede }, 5639274c783SHans de Goede { 5646f3ed834SHans de Goede /* ECS EF20EA */ 5656f3ed834SHans de Goede .matches = { 5666f3ed834SHans de Goede DMI_MATCH(DMI_PRODUCT_NAME, "EF20EA"), 5676f3ed834SHans de Goede }, 5686f3ed834SHans de Goede }, 5696f3ed834SHans de Goede { 570b60c75b6SHans de Goede /* Intel Cherry Trail Compute Stick, Windows version */ 571b60c75b6SHans de Goede .matches = { 572e42fe5b2SJeffery Miller DMI_MATCH(DMI_SYS_VENDOR, "Intel"), 573b60c75b6SHans de Goede DMI_MATCH(DMI_PRODUCT_NAME, "STK1AW32SC"), 574b60c75b6SHans de Goede }, 575b60c75b6SHans de Goede }, 576b60c75b6SHans de Goede { 577b60c75b6SHans de Goede /* Intel Cherry Trail Compute Stick, version without an OS */ 578b60c75b6SHans de Goede .matches = { 579e42fe5b2SJeffery Miller DMI_MATCH(DMI_SYS_VENDOR, "Intel"), 580b60c75b6SHans de Goede DMI_MATCH(DMI_PRODUCT_NAME, "STK1A32SC"), 581b60c75b6SHans de Goede }, 582b60c75b6SHans de Goede }, 583b60c75b6SHans de Goede { 5844ac54b88SRafael Gandolfi /* Meegopad T02 */ 5854ac54b88SRafael Gandolfi .matches = { 5864ac54b88SRafael Gandolfi DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"), 5874ac54b88SRafael Gandolfi }, 5884ac54b88SRafael Gandolfi }, 5896b714ea4SHans de Goede { /* Mele PCG03 Mini PC */ 5906b714ea4SHans de Goede .matches = { 5916b714ea4SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"), 5926b714ea4SHans de Goede DMI_EXACT_MATCH(DMI_BOARD_NAME, "Mini PC"), 5936b714ea4SHans de Goede }, 5946b714ea4SHans de Goede }, 595fa7da744SHans de Goede { 596fa7da744SHans de Goede /* Minix Neo Z83-4 mini PC */ 597fa7da744SHans de Goede .matches = { 598fa7da744SHans de Goede DMI_MATCH(DMI_SYS_VENDOR, "MINIX"), 599fa7da744SHans de Goede DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), 600fa7da744SHans de Goede } 601fa7da744SHans de Goede }, 6023a06b912SHans de Goede { 6033a06b912SHans de Goede /* Various Ace PC/Meegopad/MinisForum/Wintel Mini-PCs/HDMI-sticks */ 6043a06b912SHans de Goede .matches = { 6053a06b912SHans de Goede DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), 6063a06b912SHans de Goede DMI_MATCH(DMI_CHASSIS_TYPE, "3"), 6073a06b912SHans de Goede DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), 6083a06b912SHans de Goede DMI_MATCH(DMI_BIOS_VERSION, "5.11"), 6093a06b912SHans de Goede }, 6103a06b912SHans de Goede }, 611b60c75b6SHans de Goede {} 612b60c75b6SHans de Goede }; 613b60c75b6SHans de Goede 6145a5bf490STodd Brandt static int axp288_fuel_gauge_probe(struct platform_device *pdev) 6155a5bf490STodd Brandt { 616331645e1SHans de Goede int i, ret = 0; 6175a5bf490STodd Brandt struct axp288_fg_info *info; 6185a5bf490STodd Brandt struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 619297d716fSKrzysztof Kozlowski struct power_supply_config psy_cfg = {}; 620331645e1SHans de Goede static const char * const iio_chan_name[] = { 621331645e1SHans de Goede [BAT_CHRG_CURR] = "axp288-chrg-curr", 622331645e1SHans de Goede [BAT_D_CURR] = "axp288-chrg-d-curr", 623331645e1SHans de Goede [BAT_VOLT] = "axp288-batt-volt", 624331645e1SHans de Goede }; 62504d6f72fSHans de Goede unsigned int val; 6265a5bf490STodd Brandt 6270973e96bSHans de Goede if (dmi_check_system(axp288_no_battery_list)) 628b60c75b6SHans de Goede return -ENODEV; 629b60c75b6SHans de Goede 6305a5bf490STodd Brandt info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 6315a5bf490STodd Brandt if (!info) 6325a5bf490STodd Brandt return -ENOMEM; 6335a5bf490STodd Brandt 6347eef3e66SHans de Goede info->dev = &pdev->dev; 6355a5bf490STodd Brandt info->regmap = axp20x->regmap; 6365a5bf490STodd Brandt info->regmap_irqc = axp20x->regmap_irqc; 6375a5bf490STodd Brandt info->status = POWER_SUPPLY_STATUS_UNKNOWN; 638394088f0SAndrejus Basovas info->valid = 0; 6395a5bf490STodd Brandt 6405a5bf490STodd Brandt platform_set_drvdata(pdev, info); 6415a5bf490STodd Brandt 6425a5bf490STodd Brandt mutex_init(&info->lock); 6435a5bf490STodd Brandt 644331645e1SHans de Goede for (i = 0; i < IIO_CHANNEL_NUM; i++) { 645331645e1SHans de Goede /* 646331645e1SHans de Goede * Note cannot use devm_iio_channel_get because x86 systems 647331645e1SHans de Goede * lack the device<->channel maps which iio_channel_get will 648331645e1SHans de Goede * try to use when passed a non NULL device pointer. 649331645e1SHans de Goede */ 650331645e1SHans de Goede info->iio_channel[i] = 651331645e1SHans de Goede iio_channel_get(NULL, iio_chan_name[i]); 652331645e1SHans de Goede if (IS_ERR(info->iio_channel[i])) { 653331645e1SHans de Goede ret = PTR_ERR(info->iio_channel[i]); 654331645e1SHans de Goede dev_dbg(&pdev->dev, "error getting iiochan %s: %d\n", 655331645e1SHans de Goede iio_chan_name[i], ret); 656331645e1SHans de Goede /* Wait for axp288_adc to load */ 657331645e1SHans de Goede if (ret == -ENODEV) 658331645e1SHans de Goede ret = -EPROBE_DEFER; 659331645e1SHans de Goede 660331645e1SHans de Goede goto out_free_iio_chan; 661331645e1SHans de Goede } 662331645e1SHans de Goede } 663331645e1SHans de Goede 664213e19d6SHans de Goede ret = iosf_mbi_block_punit_i2c_access(); 665213e19d6SHans de Goede if (ret < 0) 666213e19d6SHans de Goede goto out_free_iio_chan; 667213e19d6SHans de Goede 668964b3e9bSHans de Goede /* 669964b3e9bSHans de Goede * On some devices the fuelgauge and charger parts of the axp288 are 670964b3e9bSHans de Goede * not used, check that the fuelgauge is enabled (CC_CTRL != 0). 671964b3e9bSHans de Goede */ 672964b3e9bSHans de Goede ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val); 673964b3e9bSHans de Goede if (ret < 0) 674213e19d6SHans de Goede goto unblock_punit_i2c_access; 675964b3e9bSHans de Goede if (val == 0) { 676964b3e9bSHans de Goede ret = -ENODEV; 677213e19d6SHans de Goede goto unblock_punit_i2c_access; 678964b3e9bSHans de Goede } 679964b3e9bSHans de Goede 680888f9743SHans de Goede ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); 681888f9743SHans de Goede if (ret < 0) 682213e19d6SHans de Goede goto unblock_punit_i2c_access; 683888f9743SHans de Goede 684888f9743SHans de Goede if (!(ret & FG_DES_CAP1_VALID)) { 685888f9743SHans de Goede dev_err(&pdev->dev, "axp288 not configured by firmware\n"); 686331645e1SHans de Goede ret = -ENODEV; 687213e19d6SHans de Goede goto unblock_punit_i2c_access; 688888f9743SHans de Goede } 689888f9743SHans de Goede 690888f9743SHans de Goede ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); 691888f9743SHans de Goede if (ret < 0) 692213e19d6SHans de Goede goto unblock_punit_i2c_access; 693888f9743SHans de Goede switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) { 694888f9743SHans de Goede case CHRG_CCCV_CV_4100MV: 695888f9743SHans de Goede info->max_volt = 4100; 696888f9743SHans de Goede break; 697888f9743SHans de Goede case CHRG_CCCV_CV_4150MV: 698888f9743SHans de Goede info->max_volt = 4150; 699888f9743SHans de Goede break; 700888f9743SHans de Goede case CHRG_CCCV_CV_4200MV: 701888f9743SHans de Goede info->max_volt = 4200; 702888f9743SHans de Goede break; 703888f9743SHans de Goede case CHRG_CCCV_CV_4350MV: 704888f9743SHans de Goede info->max_volt = 4350; 705888f9743SHans de Goede break; 706888f9743SHans de Goede } 707888f9743SHans de Goede 708c371d449SHans de Goede ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); 709c371d449SHans de Goede if (ret < 0) 710213e19d6SHans de Goede goto unblock_punit_i2c_access; 711c371d449SHans de Goede info->pwr_op = ret; 712c371d449SHans de Goede 713c371d449SHans de Goede ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); 714c371d449SHans de Goede if (ret < 0) 715213e19d6SHans de Goede goto unblock_punit_i2c_access; 716c371d449SHans de Goede info->low_cap = ret; 717c371d449SHans de Goede 718213e19d6SHans de Goede unblock_punit_i2c_access: 719213e19d6SHans de Goede iosf_mbi_unblock_punit_i2c_access(); 720213e19d6SHans de Goede /* In case we arrive here by goto because of a register access error */ 721213e19d6SHans de Goede if (ret < 0) 722213e19d6SHans de Goede goto out_free_iio_chan; 723213e19d6SHans de Goede 724297d716fSKrzysztof Kozlowski psy_cfg.drv_data = info; 725297d716fSKrzysztof Kozlowski info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg); 726297d716fSKrzysztof Kozlowski if (IS_ERR(info->bat)) { 727297d716fSKrzysztof Kozlowski ret = PTR_ERR(info->bat); 7285a5bf490STodd Brandt dev_err(&pdev->dev, "failed to register battery: %d\n", ret); 729331645e1SHans de Goede goto out_free_iio_chan; 7305a5bf490STodd Brandt } 7315a5bf490STodd Brandt 7327eef3e66SHans de Goede fuel_gauge_init_irq(info, pdev); 7335a5bf490STodd Brandt 734888f9743SHans de Goede return 0; 735331645e1SHans de Goede 736331645e1SHans de Goede out_free_iio_chan: 737331645e1SHans de Goede for (i = 0; i < IIO_CHANNEL_NUM; i++) 738331645e1SHans de Goede if (!IS_ERR_OR_NULL(info->iio_channel[i])) 739331645e1SHans de Goede iio_channel_release(info->iio_channel[i]); 740331645e1SHans de Goede 741331645e1SHans de Goede return ret; 7425a5bf490STodd Brandt } 7435a5bf490STodd Brandt 744f1f27a4aSKrzysztof Kozlowski static const struct platform_device_id axp288_fg_id_table[] = { 7455a5bf490STodd Brandt { .name = DEV_NAME }, 7465a5bf490STodd Brandt {}, 7475a5bf490STodd Brandt }; 74899e33fbdSJavier Martinez Canillas MODULE_DEVICE_TABLE(platform, axp288_fg_id_table); 7495a5bf490STodd Brandt 7505a5bf490STodd Brandt static int axp288_fuel_gauge_remove(struct platform_device *pdev) 7515a5bf490STodd Brandt { 7525a5bf490STodd Brandt struct axp288_fg_info *info = platform_get_drvdata(pdev); 7535a5bf490STodd Brandt int i; 7545a5bf490STodd Brandt 755297d716fSKrzysztof Kozlowski power_supply_unregister(info->bat); 7565a5bf490STodd Brandt 7575a5bf490STodd Brandt for (i = 0; i < AXP288_FG_INTR_NUM; i++) 7585a5bf490STodd Brandt if (info->irq[i] >= 0) 7595a5bf490STodd Brandt free_irq(info->irq[i], info); 7605a5bf490STodd Brandt 761331645e1SHans de Goede for (i = 0; i < IIO_CHANNEL_NUM; i++) 762331645e1SHans de Goede iio_channel_release(info->iio_channel[i]); 763331645e1SHans de Goede 7645a5bf490STodd Brandt return 0; 7655a5bf490STodd Brandt } 7665a5bf490STodd Brandt 7675a5bf490STodd Brandt static struct platform_driver axp288_fuel_gauge_driver = { 7685a5bf490STodd Brandt .probe = axp288_fuel_gauge_probe, 7695a5bf490STodd Brandt .remove = axp288_fuel_gauge_remove, 7705a5bf490STodd Brandt .id_table = axp288_fg_id_table, 7715a5bf490STodd Brandt .driver = { 7725a5bf490STodd Brandt .name = DEV_NAME, 7735a5bf490STodd Brandt }, 7745a5bf490STodd Brandt }; 7755a5bf490STodd Brandt 7765a5bf490STodd Brandt module_platform_driver(axp288_fuel_gauge_driver); 7775a5bf490STodd Brandt 778409e718eSRamakrishna Pallala MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); 7795a5bf490STodd Brandt MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>"); 7805a5bf490STodd Brandt MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); 7815a5bf490STodd Brandt MODULE_LICENSE("GPL"); 782