18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016-2017 Hans de Goede <hdegoede@redhat.com> 68c2ecf20Sopenharmony_ci * Copyright (C) 2014 Intel Corporation 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/dmi.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/device.h> 158c2ecf20Sopenharmony_ci#include <linux/regmap.h> 168c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 178c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 188c2ecf20Sopenharmony_ci#include <linux/mfd/axp20x.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 218c2ecf20Sopenharmony_ci#include <linux/iio/consumer.h> 228c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 238c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 248c2ecf20Sopenharmony_ci#include <asm/unaligned.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define PS_STAT_VBUS_TRIGGER (1 << 0) 278c2ecf20Sopenharmony_ci#define PS_STAT_BAT_CHRG_DIR (1 << 2) 288c2ecf20Sopenharmony_ci#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) 298c2ecf20Sopenharmony_ci#define PS_STAT_VBUS_VALID (1 << 4) 308c2ecf20Sopenharmony_ci#define PS_STAT_VBUS_PRESENT (1 << 5) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) 338c2ecf20Sopenharmony_ci#define CHRG_STAT_BAT_VALID (1 << 4) 348c2ecf20Sopenharmony_ci#define CHRG_STAT_BAT_PRESENT (1 << 5) 358c2ecf20Sopenharmony_ci#define CHRG_STAT_CHARGING (1 << 6) 368c2ecf20Sopenharmony_ci#define CHRG_STAT_PMIC_OTP (1 << 7) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ 398c2ecf20Sopenharmony_ci#define CHRG_CCCV_CC_BIT_POS 0 408c2ecf20Sopenharmony_ci#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ 418c2ecf20Sopenharmony_ci#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ 428c2ecf20Sopenharmony_ci#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ 438c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ 448c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_BIT_POS 5 458c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ 468c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ 478c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ 488c2ecf20Sopenharmony_ci#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ 498c2ecf20Sopenharmony_ci#define CHRG_CCCV_CHG_EN (1 << 7) 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define FG_CNTL_OCV_ADJ_STAT (1 << 2) 528c2ecf20Sopenharmony_ci#define FG_CNTL_OCV_ADJ_EN (1 << 3) 538c2ecf20Sopenharmony_ci#define FG_CNTL_CAP_ADJ_STAT (1 << 4) 548c2ecf20Sopenharmony_ci#define FG_CNTL_CAP_ADJ_EN (1 << 5) 558c2ecf20Sopenharmony_ci#define FG_CNTL_CC_EN (1 << 6) 568c2ecf20Sopenharmony_ci#define FG_CNTL_GAUGE_EN (1 << 7) 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define FG_15BIT_WORD_VALID (1 << 15) 598c2ecf20Sopenharmony_ci#define FG_15BIT_VAL_MASK 0x7fff 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci#define FG_REP_CAP_VALID (1 << 7) 628c2ecf20Sopenharmony_ci#define FG_REP_CAP_VAL_MASK 0x7F 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define FG_DES_CAP1_VALID (1 << 7) 658c2ecf20Sopenharmony_ci#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */ 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */ 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci#define FG_OCV_CAP_VALID (1 << 7) 708c2ecf20Sopenharmony_ci#define FG_OCV_CAP_VAL_MASK 0x7F 718c2ecf20Sopenharmony_ci#define FG_CC_CAP_VALID (1 << 7) 728c2ecf20Sopenharmony_ci#define FG_CC_CAP_VAL_MASK 0x7F 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ 758c2ecf20Sopenharmony_ci#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ 768c2ecf20Sopenharmony_ci#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ 778c2ecf20Sopenharmony_ci#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ 788c2ecf20Sopenharmony_ci#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ 798c2ecf20Sopenharmony_ci#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci#define NR_RETRY_CNT 3 828c2ecf20Sopenharmony_ci#define DEV_NAME "axp288_fuel_gauge" 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/* 1.1mV per LSB expressed in uV */ 858c2ecf20Sopenharmony_ci#define VOLTAGE_FROM_ADC(a) ((a * 11) / 10) 868c2ecf20Sopenharmony_ci/* properties converted to uV, uA */ 878c2ecf20Sopenharmony_ci#define PROP_VOLT(a) ((a) * 1000) 888c2ecf20Sopenharmony_ci#define PROP_CURR(a) ((a) * 1000) 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci#define AXP288_FG_INTR_NUM 6 918c2ecf20Sopenharmony_cienum { 928c2ecf20Sopenharmony_ci QWBTU_IRQ = 0, 938c2ecf20Sopenharmony_ci WBTU_IRQ, 948c2ecf20Sopenharmony_ci QWBTO_IRQ, 958c2ecf20Sopenharmony_ci WBTO_IRQ, 968c2ecf20Sopenharmony_ci WL2_IRQ, 978c2ecf20Sopenharmony_ci WL1_IRQ, 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cienum { 1018c2ecf20Sopenharmony_ci BAT_TEMP = 0, 1028c2ecf20Sopenharmony_ci PMIC_TEMP, 1038c2ecf20Sopenharmony_ci SYSTEM_TEMP, 1048c2ecf20Sopenharmony_ci BAT_CHRG_CURR, 1058c2ecf20Sopenharmony_ci BAT_D_CURR, 1068c2ecf20Sopenharmony_ci BAT_VOLT, 1078c2ecf20Sopenharmony_ci IIO_CHANNEL_NUM 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistruct axp288_fg_info { 1118c2ecf20Sopenharmony_ci struct platform_device *pdev; 1128c2ecf20Sopenharmony_ci struct regmap *regmap; 1138c2ecf20Sopenharmony_ci struct regmap_irq_chip_data *regmap_irqc; 1148c2ecf20Sopenharmony_ci int irq[AXP288_FG_INTR_NUM]; 1158c2ecf20Sopenharmony_ci struct iio_channel *iio_channel[IIO_CHANNEL_NUM]; 1168c2ecf20Sopenharmony_ci struct power_supply *bat; 1178c2ecf20Sopenharmony_ci struct mutex lock; 1188c2ecf20Sopenharmony_ci int status; 1198c2ecf20Sopenharmony_ci int max_volt; 1208c2ecf20Sopenharmony_ci struct dentry *debug_file; 1218c2ecf20Sopenharmony_ci}; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic enum power_supply_property fuel_gauge_props[] = { 1248c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_STATUS, 1258c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_PRESENT, 1268c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_HEALTH, 1278c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 1288c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_NOW, 1298c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_VOLTAGE_OCV, 1308c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CURRENT_NOW, 1318c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY, 1328c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, 1338c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_TECHNOLOGY, 1348c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_FULL, 1358c2ecf20Sopenharmony_ci POWER_SUPPLY_PROP_CHARGE_NOW, 1368c2ecf20Sopenharmony_ci}; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci int ret, i; 1418c2ecf20Sopenharmony_ci unsigned int val; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci for (i = 0; i < NR_RETRY_CNT; i++) { 1448c2ecf20Sopenharmony_ci ret = regmap_read(info->regmap, reg, &val); 1458c2ecf20Sopenharmony_ci if (ret == -EBUSY) 1468c2ecf20Sopenharmony_ci continue; 1478c2ecf20Sopenharmony_ci else 1488c2ecf20Sopenharmony_ci break; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (ret < 0) { 1528c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n", reg, ret); 1538c2ecf20Sopenharmony_ci return ret; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci return val; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci int ret; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci ret = regmap_write(info->regmap, reg, (unsigned int)val); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (ret < 0) 1668c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "Error writing reg 0x%02x err: %d\n", reg, ret); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci return ret; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci unsigned char buf[2]; 1748c2ecf20Sopenharmony_ci int ret; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci ret = regmap_bulk_read(info->regmap, reg, buf, 2); 1778c2ecf20Sopenharmony_ci if (ret < 0) { 1788c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n", 1798c2ecf20Sopenharmony_ci reg, ret); 1808c2ecf20Sopenharmony_ci return ret; 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci ret = get_unaligned_be16(buf); 1848c2ecf20Sopenharmony_ci if (!(ret & FG_15BIT_WORD_VALID)) { 1858c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "Error reg 0x%02x contents not valid\n", 1868c2ecf20Sopenharmony_ci reg); 1878c2ecf20Sopenharmony_ci return -ENXIO; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return ret & FG_15BIT_VAL_MASK; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci unsigned char buf[2]; 1968c2ecf20Sopenharmony_ci int ret; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci ret = regmap_bulk_read(info->regmap, reg, buf, 2); 1998c2ecf20Sopenharmony_ci if (ret < 0) { 2008c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n", 2018c2ecf20Sopenharmony_ci reg, ret); 2028c2ecf20Sopenharmony_ci return ret; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* 12-bit data values have upper 8 bits in buf[0], lower 4 in buf[1] */ 2068c2ecf20Sopenharmony_ci return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci#ifdef CONFIG_DEBUG_FS 2108c2ecf20Sopenharmony_cistatic int fuel_gauge_debug_show(struct seq_file *s, void *data) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci struct axp288_fg_info *info = s->private; 2138c2ecf20Sopenharmony_ci int raw_val, ret; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci seq_printf(s, " PWR_STATUS[%02x] : %02x\n", 2168c2ecf20Sopenharmony_ci AXP20X_PWR_INPUT_STATUS, 2178c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS)); 2188c2ecf20Sopenharmony_ci seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n", 2198c2ecf20Sopenharmony_ci AXP20X_PWR_OP_MODE, 2208c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE)); 2218c2ecf20Sopenharmony_ci seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n", 2228c2ecf20Sopenharmony_ci AXP20X_CHRG_CTRL1, 2238c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1)); 2248c2ecf20Sopenharmony_ci seq_printf(s, " VLTF[%02x] : %02x\n", 2258c2ecf20Sopenharmony_ci AXP20X_V_LTF_DISCHRG, 2268c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG)); 2278c2ecf20Sopenharmony_ci seq_printf(s, " VHTF[%02x] : %02x\n", 2288c2ecf20Sopenharmony_ci AXP20X_V_HTF_DISCHRG, 2298c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG)); 2308c2ecf20Sopenharmony_ci seq_printf(s, " CC_CTRL[%02x] : %02x\n", 2318c2ecf20Sopenharmony_ci AXP20X_CC_CTRL, 2328c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_CC_CTRL)); 2338c2ecf20Sopenharmony_ci seq_printf(s, "BATTERY CAP[%02x] : %02x\n", 2348c2ecf20Sopenharmony_ci AXP20X_FG_RES, 2358c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP20X_FG_RES)); 2368c2ecf20Sopenharmony_ci seq_printf(s, " FG_RDC1[%02x] : %02x\n", 2378c2ecf20Sopenharmony_ci AXP288_FG_RDC1_REG, 2388c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG)); 2398c2ecf20Sopenharmony_ci seq_printf(s, " FG_RDC0[%02x] : %02x\n", 2408c2ecf20Sopenharmony_ci AXP288_FG_RDC0_REG, 2418c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG)); 2428c2ecf20Sopenharmony_ci seq_printf(s, " FG_OCV[%02x] : %04x\n", 2438c2ecf20Sopenharmony_ci AXP288_FG_OCVH_REG, 2448c2ecf20Sopenharmony_ci fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG)); 2458c2ecf20Sopenharmony_ci seq_printf(s, " FG_DES_CAP[%02x] : %04x\n", 2468c2ecf20Sopenharmony_ci AXP288_FG_DES_CAP1_REG, 2478c2ecf20Sopenharmony_ci fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG)); 2488c2ecf20Sopenharmony_ci seq_printf(s, " FG_CC_MTR[%02x] : %04x\n", 2498c2ecf20Sopenharmony_ci AXP288_FG_CC_MTR1_REG, 2508c2ecf20Sopenharmony_ci fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG)); 2518c2ecf20Sopenharmony_ci seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n", 2528c2ecf20Sopenharmony_ci AXP288_FG_OCV_CAP_REG, 2538c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG)); 2548c2ecf20Sopenharmony_ci seq_printf(s, " FG_CC_CAP[%02x] : %02x\n", 2558c2ecf20Sopenharmony_ci AXP288_FG_CC_CAP_REG, 2568c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG)); 2578c2ecf20Sopenharmony_ci seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n", 2588c2ecf20Sopenharmony_ci AXP288_FG_LOW_CAP_REG, 2598c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG)); 2608c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL0[%02x] : %02x\n", 2618c2ecf20Sopenharmony_ci AXP288_FG_TUNE0, 2628c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE0)); 2638c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL1[%02x] : %02x\n", 2648c2ecf20Sopenharmony_ci AXP288_FG_TUNE1, 2658c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE1)); 2668c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL2[%02x] : %02x\n", 2678c2ecf20Sopenharmony_ci AXP288_FG_TUNE2, 2688c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE2)); 2698c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL3[%02x] : %02x\n", 2708c2ecf20Sopenharmony_ci AXP288_FG_TUNE3, 2718c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE3)); 2728c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL4[%02x] : %02x\n", 2738c2ecf20Sopenharmony_ci AXP288_FG_TUNE4, 2748c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE4)); 2758c2ecf20Sopenharmony_ci seq_printf(s, "TUNING_CTL5[%02x] : %02x\n", 2768c2ecf20Sopenharmony_ci AXP288_FG_TUNE5, 2778c2ecf20Sopenharmony_ci fuel_gauge_reg_readb(info, AXP288_FG_TUNE5)); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_TEMP], &raw_val); 2808c2ecf20Sopenharmony_ci if (ret >= 0) 2818c2ecf20Sopenharmony_ci seq_printf(s, "axp288-batttemp : %d\n", raw_val); 2828c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[PMIC_TEMP], &raw_val); 2838c2ecf20Sopenharmony_ci if (ret >= 0) 2848c2ecf20Sopenharmony_ci seq_printf(s, "axp288-pmictemp : %d\n", raw_val); 2858c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[SYSTEM_TEMP], &raw_val); 2868c2ecf20Sopenharmony_ci if (ret >= 0) 2878c2ecf20Sopenharmony_ci seq_printf(s, "axp288-systtemp : %d\n", raw_val); 2888c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &raw_val); 2898c2ecf20Sopenharmony_ci if (ret >= 0) 2908c2ecf20Sopenharmony_ci seq_printf(s, "axp288-chrgcurr : %d\n", raw_val); 2918c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &raw_val); 2928c2ecf20Sopenharmony_ci if (ret >= 0) 2938c2ecf20Sopenharmony_ci seq_printf(s, "axp288-dchrgcur : %d\n", raw_val); 2948c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &raw_val); 2958c2ecf20Sopenharmony_ci if (ret >= 0) 2968c2ecf20Sopenharmony_ci seq_printf(s, "axp288-battvolt : %d\n", raw_val); 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci return 0; 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(fuel_gauge_debug); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_cistatic void fuel_gauge_create_debugfs(struct axp288_fg_info *info) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL, 3068c2ecf20Sopenharmony_ci info, &fuel_gauge_debug_fops); 3078c2ecf20Sopenharmony_ci} 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) 3108c2ecf20Sopenharmony_ci{ 3118c2ecf20Sopenharmony_ci debugfs_remove(info->debug_file); 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci#else 3148c2ecf20Sopenharmony_cistatic inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci} 3178c2ecf20Sopenharmony_cistatic inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) 3188c2ecf20Sopenharmony_ci{ 3198c2ecf20Sopenharmony_ci} 3208c2ecf20Sopenharmony_ci#endif 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_cistatic void fuel_gauge_get_status(struct axp288_fg_info *info) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci int pwr_stat, fg_res, curr, ret; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); 3278c2ecf20Sopenharmony_ci if (pwr_stat < 0) { 3288c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, 3298c2ecf20Sopenharmony_ci "PWR STAT read failed:%d\n", pwr_stat); 3308c2ecf20Sopenharmony_ci return; 3318c2ecf20Sopenharmony_ci } 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci /* Report full if Vbus is valid and the reported capacity is 100% */ 3348c2ecf20Sopenharmony_ci if (!(pwr_stat & PS_STAT_VBUS_VALID)) 3358c2ecf20Sopenharmony_ci goto not_full; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci fg_res = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 3388c2ecf20Sopenharmony_ci if (fg_res < 0) { 3398c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "FG RES read failed: %d\n", fg_res); 3408c2ecf20Sopenharmony_ci return; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci if (!(fg_res & FG_REP_CAP_VALID)) 3438c2ecf20Sopenharmony_ci goto not_full; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci fg_res &= ~FG_REP_CAP_VALID; 3468c2ecf20Sopenharmony_ci if (fg_res == 100) { 3478c2ecf20Sopenharmony_ci info->status = POWER_SUPPLY_STATUS_FULL; 3488c2ecf20Sopenharmony_ci return; 3498c2ecf20Sopenharmony_ci } 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci /* 3528c2ecf20Sopenharmony_ci * Sometimes the charger turns itself off before fg-res reaches 100%. 3538c2ecf20Sopenharmony_ci * When this happens the AXP288 reports a not-charging status and 3548c2ecf20Sopenharmony_ci * 0 mA discharge current. 3558c2ecf20Sopenharmony_ci */ 3568c2ecf20Sopenharmony_ci if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR)) 3578c2ecf20Sopenharmony_ci goto not_full; 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &curr); 3608c2ecf20Sopenharmony_ci if (ret < 0) { 3618c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, "FG get current failed: %d\n", ret); 3628c2ecf20Sopenharmony_ci return; 3638c2ecf20Sopenharmony_ci } 3648c2ecf20Sopenharmony_ci if (curr == 0) { 3658c2ecf20Sopenharmony_ci info->status = POWER_SUPPLY_STATUS_FULL; 3668c2ecf20Sopenharmony_ci return; 3678c2ecf20Sopenharmony_ci } 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_cinot_full: 3708c2ecf20Sopenharmony_ci if (pwr_stat & PS_STAT_BAT_CHRG_DIR) 3718c2ecf20Sopenharmony_ci info->status = POWER_SUPPLY_STATUS_CHARGING; 3728c2ecf20Sopenharmony_ci else 3738c2ecf20Sopenharmony_ci info->status = POWER_SUPPLY_STATUS_DISCHARGING; 3748c2ecf20Sopenharmony_ci} 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_cistatic int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) 3778c2ecf20Sopenharmony_ci{ 3788c2ecf20Sopenharmony_ci int ret = 0, raw_val; 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &raw_val); 3818c2ecf20Sopenharmony_ci if (ret < 0) 3828c2ecf20Sopenharmony_ci goto vbatt_read_fail; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci *vbatt = VOLTAGE_FROM_ADC(raw_val); 3858c2ecf20Sopenharmony_civbatt_read_fail: 3868c2ecf20Sopenharmony_ci return ret; 3878c2ecf20Sopenharmony_ci} 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_cistatic int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) 3908c2ecf20Sopenharmony_ci{ 3918c2ecf20Sopenharmony_ci int ret, discharge; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci /* First check discharge current, so that we do only 1 read on bat. */ 3948c2ecf20Sopenharmony_ci ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &discharge); 3958c2ecf20Sopenharmony_ci if (ret < 0) 3968c2ecf20Sopenharmony_ci return ret; 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci if (discharge > 0) { 3998c2ecf20Sopenharmony_ci *cur = -1 * discharge; 4008c2ecf20Sopenharmony_ci return 0; 4018c2ecf20Sopenharmony_ci } 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci return iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], cur); 4048c2ecf20Sopenharmony_ci} 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_cistatic int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv) 4078c2ecf20Sopenharmony_ci{ 4088c2ecf20Sopenharmony_ci int ret; 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); 4118c2ecf20Sopenharmony_ci if (ret >= 0) 4128c2ecf20Sopenharmony_ci *vocv = VOLTAGE_FROM_ADC(ret); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci return ret; 4158c2ecf20Sopenharmony_ci} 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic int fuel_gauge_battery_health(struct axp288_fg_info *info) 4188c2ecf20Sopenharmony_ci{ 4198c2ecf20Sopenharmony_ci int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN; 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci ret = fuel_gauge_get_vocv(info, &vocv); 4228c2ecf20Sopenharmony_ci if (ret < 0) 4238c2ecf20Sopenharmony_ci goto health_read_fail; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci if (vocv > info->max_volt) 4268c2ecf20Sopenharmony_ci health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 4278c2ecf20Sopenharmony_ci else 4288c2ecf20Sopenharmony_ci health = POWER_SUPPLY_HEALTH_GOOD; 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_cihealth_read_fail: 4318c2ecf20Sopenharmony_ci return health; 4328c2ecf20Sopenharmony_ci} 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_cistatic int fuel_gauge_get_property(struct power_supply *ps, 4358c2ecf20Sopenharmony_ci enum power_supply_property prop, 4368c2ecf20Sopenharmony_ci union power_supply_propval *val) 4378c2ecf20Sopenharmony_ci{ 4388c2ecf20Sopenharmony_ci struct axp288_fg_info *info = power_supply_get_drvdata(ps); 4398c2ecf20Sopenharmony_ci int ret = 0, value; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci mutex_lock(&info->lock); 4428c2ecf20Sopenharmony_ci switch (prop) { 4438c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_STATUS: 4448c2ecf20Sopenharmony_ci fuel_gauge_get_status(info); 4458c2ecf20Sopenharmony_ci val->intval = info->status; 4468c2ecf20Sopenharmony_ci break; 4478c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_HEALTH: 4488c2ecf20Sopenharmony_ci val->intval = fuel_gauge_battery_health(info); 4498c2ecf20Sopenharmony_ci break; 4508c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_NOW: 4518c2ecf20Sopenharmony_ci ret = fuel_gauge_get_vbatt(info, &value); 4528c2ecf20Sopenharmony_ci if (ret < 0) 4538c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4548c2ecf20Sopenharmony_ci val->intval = PROP_VOLT(value); 4558c2ecf20Sopenharmony_ci break; 4568c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_OCV: 4578c2ecf20Sopenharmony_ci ret = fuel_gauge_get_vocv(info, &value); 4588c2ecf20Sopenharmony_ci if (ret < 0) 4598c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4608c2ecf20Sopenharmony_ci val->intval = PROP_VOLT(value); 4618c2ecf20Sopenharmony_ci break; 4628c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CURRENT_NOW: 4638c2ecf20Sopenharmony_ci ret = fuel_gauge_get_current(info, &value); 4648c2ecf20Sopenharmony_ci if (ret < 0) 4658c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4668c2ecf20Sopenharmony_ci val->intval = PROP_CURR(value); 4678c2ecf20Sopenharmony_ci break; 4688c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_PRESENT: 4698c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); 4708c2ecf20Sopenharmony_ci if (ret < 0) 4718c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci if (ret & CHRG_STAT_BAT_PRESENT) 4748c2ecf20Sopenharmony_ci val->intval = 1; 4758c2ecf20Sopenharmony_ci else 4768c2ecf20Sopenharmony_ci val->intval = 0; 4778c2ecf20Sopenharmony_ci break; 4788c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY: 4798c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); 4808c2ecf20Sopenharmony_ci if (ret < 0) 4818c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci if (!(ret & FG_REP_CAP_VALID)) 4848c2ecf20Sopenharmony_ci dev_err(&info->pdev->dev, 4858c2ecf20Sopenharmony_ci "capacity measurement not valid\n"); 4868c2ecf20Sopenharmony_ci val->intval = (ret & FG_REP_CAP_VAL_MASK); 4878c2ecf20Sopenharmony_ci break; 4888c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 4898c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); 4908c2ecf20Sopenharmony_ci if (ret < 0) 4918c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 4928c2ecf20Sopenharmony_ci val->intval = (ret & 0x0f); 4938c2ecf20Sopenharmony_ci break; 4948c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_TECHNOLOGY: 4958c2ecf20Sopenharmony_ci val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 4968c2ecf20Sopenharmony_ci break; 4978c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_NOW: 4988c2ecf20Sopenharmony_ci ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); 4998c2ecf20Sopenharmony_ci if (ret < 0) 5008c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci val->intval = ret * FG_DES_CAP_RES_LSB; 5038c2ecf20Sopenharmony_ci break; 5048c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CHARGE_FULL: 5058c2ecf20Sopenharmony_ci ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG); 5068c2ecf20Sopenharmony_ci if (ret < 0) 5078c2ecf20Sopenharmony_ci goto fuel_gauge_read_err; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci val->intval = ret * FG_DES_CAP_RES_LSB; 5108c2ecf20Sopenharmony_ci break; 5118c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: 5128c2ecf20Sopenharmony_ci val->intval = PROP_VOLT(info->max_volt); 5138c2ecf20Sopenharmony_ci break; 5148c2ecf20Sopenharmony_ci default: 5158c2ecf20Sopenharmony_ci mutex_unlock(&info->lock); 5168c2ecf20Sopenharmony_ci return -EINVAL; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci mutex_unlock(&info->lock); 5208c2ecf20Sopenharmony_ci return 0; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_cifuel_gauge_read_err: 5238c2ecf20Sopenharmony_ci mutex_unlock(&info->lock); 5248c2ecf20Sopenharmony_ci return ret; 5258c2ecf20Sopenharmony_ci} 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_cistatic int fuel_gauge_set_property(struct power_supply *ps, 5288c2ecf20Sopenharmony_ci enum power_supply_property prop, 5298c2ecf20Sopenharmony_ci const union power_supply_propval *val) 5308c2ecf20Sopenharmony_ci{ 5318c2ecf20Sopenharmony_ci struct axp288_fg_info *info = power_supply_get_drvdata(ps); 5328c2ecf20Sopenharmony_ci int ret = 0; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci mutex_lock(&info->lock); 5358c2ecf20Sopenharmony_ci switch (prop) { 5368c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 5378c2ecf20Sopenharmony_ci if ((val->intval < 0) || (val->intval > 15)) { 5388c2ecf20Sopenharmony_ci ret = -EINVAL; 5398c2ecf20Sopenharmony_ci break; 5408c2ecf20Sopenharmony_ci } 5418c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); 5428c2ecf20Sopenharmony_ci if (ret < 0) 5438c2ecf20Sopenharmony_ci break; 5448c2ecf20Sopenharmony_ci ret &= 0xf0; 5458c2ecf20Sopenharmony_ci ret |= (val->intval & 0xf); 5468c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret); 5478c2ecf20Sopenharmony_ci break; 5488c2ecf20Sopenharmony_ci default: 5498c2ecf20Sopenharmony_ci ret = -EINVAL; 5508c2ecf20Sopenharmony_ci break; 5518c2ecf20Sopenharmony_ci } 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci mutex_unlock(&info->lock); 5548c2ecf20Sopenharmony_ci return ret; 5558c2ecf20Sopenharmony_ci} 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_cistatic int fuel_gauge_property_is_writeable(struct power_supply *psy, 5588c2ecf20Sopenharmony_ci enum power_supply_property psp) 5598c2ecf20Sopenharmony_ci{ 5608c2ecf20Sopenharmony_ci int ret; 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci switch (psp) { 5638c2ecf20Sopenharmony_ci case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: 5648c2ecf20Sopenharmony_ci ret = 1; 5658c2ecf20Sopenharmony_ci break; 5668c2ecf20Sopenharmony_ci default: 5678c2ecf20Sopenharmony_ci ret = 0; 5688c2ecf20Sopenharmony_ci } 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci return ret; 5718c2ecf20Sopenharmony_ci} 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_cistatic irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) 5748c2ecf20Sopenharmony_ci{ 5758c2ecf20Sopenharmony_ci struct axp288_fg_info *info = dev; 5768c2ecf20Sopenharmony_ci int i; 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci for (i = 0; i < AXP288_FG_INTR_NUM; i++) { 5798c2ecf20Sopenharmony_ci if (info->irq[i] == irq) 5808c2ecf20Sopenharmony_ci break; 5818c2ecf20Sopenharmony_ci } 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci if (i >= AXP288_FG_INTR_NUM) { 5848c2ecf20Sopenharmony_ci dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); 5858c2ecf20Sopenharmony_ci return IRQ_NONE; 5868c2ecf20Sopenharmony_ci } 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci switch (i) { 5898c2ecf20Sopenharmony_ci case QWBTU_IRQ: 5908c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, 5918c2ecf20Sopenharmony_ci "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); 5928c2ecf20Sopenharmony_ci break; 5938c2ecf20Sopenharmony_ci case WBTU_IRQ: 5948c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, 5958c2ecf20Sopenharmony_ci "Battery under temperature in work mode IRQ (WBTU)\n"); 5968c2ecf20Sopenharmony_ci break; 5978c2ecf20Sopenharmony_ci case QWBTO_IRQ: 5988c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, 5998c2ecf20Sopenharmony_ci "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); 6008c2ecf20Sopenharmony_ci break; 6018c2ecf20Sopenharmony_ci case WBTO_IRQ: 6028c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, 6038c2ecf20Sopenharmony_ci "Battery over temperature in work mode IRQ (WBTO)\n"); 6048c2ecf20Sopenharmony_ci break; 6058c2ecf20Sopenharmony_ci case WL2_IRQ: 6068c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n"); 6078c2ecf20Sopenharmony_ci break; 6088c2ecf20Sopenharmony_ci case WL1_IRQ: 6098c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n"); 6108c2ecf20Sopenharmony_ci break; 6118c2ecf20Sopenharmony_ci default: 6128c2ecf20Sopenharmony_ci dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); 6138c2ecf20Sopenharmony_ci } 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci power_supply_changed(info->bat); 6168c2ecf20Sopenharmony_ci return IRQ_HANDLED; 6178c2ecf20Sopenharmony_ci} 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic void fuel_gauge_external_power_changed(struct power_supply *psy) 6208c2ecf20Sopenharmony_ci{ 6218c2ecf20Sopenharmony_ci struct axp288_fg_info *info = power_supply_get_drvdata(psy); 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci power_supply_changed(info->bat); 6248c2ecf20Sopenharmony_ci} 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_cistatic const struct power_supply_desc fuel_gauge_desc = { 6278c2ecf20Sopenharmony_ci .name = DEV_NAME, 6288c2ecf20Sopenharmony_ci .type = POWER_SUPPLY_TYPE_BATTERY, 6298c2ecf20Sopenharmony_ci .properties = fuel_gauge_props, 6308c2ecf20Sopenharmony_ci .num_properties = ARRAY_SIZE(fuel_gauge_props), 6318c2ecf20Sopenharmony_ci .get_property = fuel_gauge_get_property, 6328c2ecf20Sopenharmony_ci .set_property = fuel_gauge_set_property, 6338c2ecf20Sopenharmony_ci .property_is_writeable = fuel_gauge_property_is_writeable, 6348c2ecf20Sopenharmony_ci .external_power_changed = fuel_gauge_external_power_changed, 6358c2ecf20Sopenharmony_ci}; 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_cistatic void fuel_gauge_init_irq(struct axp288_fg_info *info) 6388c2ecf20Sopenharmony_ci{ 6398c2ecf20Sopenharmony_ci int ret, i, pirq; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci for (i = 0; i < AXP288_FG_INTR_NUM; i++) { 6428c2ecf20Sopenharmony_ci pirq = platform_get_irq(info->pdev, i); 6438c2ecf20Sopenharmony_ci info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); 6448c2ecf20Sopenharmony_ci if (info->irq[i] < 0) { 6458c2ecf20Sopenharmony_ci dev_warn(&info->pdev->dev, 6468c2ecf20Sopenharmony_ci "regmap_irq get virq failed for IRQ %d: %d\n", 6478c2ecf20Sopenharmony_ci pirq, info->irq[i]); 6488c2ecf20Sopenharmony_ci info->irq[i] = -1; 6498c2ecf20Sopenharmony_ci goto intr_failed; 6508c2ecf20Sopenharmony_ci } 6518c2ecf20Sopenharmony_ci ret = request_threaded_irq(info->irq[i], 6528c2ecf20Sopenharmony_ci NULL, fuel_gauge_thread_handler, 6538c2ecf20Sopenharmony_ci IRQF_ONESHOT, DEV_NAME, info); 6548c2ecf20Sopenharmony_ci if (ret) { 6558c2ecf20Sopenharmony_ci dev_warn(&info->pdev->dev, 6568c2ecf20Sopenharmony_ci "request irq failed for IRQ %d: %d\n", 6578c2ecf20Sopenharmony_ci pirq, info->irq[i]); 6588c2ecf20Sopenharmony_ci info->irq[i] = -1; 6598c2ecf20Sopenharmony_ci goto intr_failed; 6608c2ecf20Sopenharmony_ci } else { 6618c2ecf20Sopenharmony_ci dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n", 6628c2ecf20Sopenharmony_ci pirq, info->irq[i]); 6638c2ecf20Sopenharmony_ci } 6648c2ecf20Sopenharmony_ci } 6658c2ecf20Sopenharmony_ci return; 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_ciintr_failed: 6688c2ecf20Sopenharmony_ci for (; i > 0; i--) { 6698c2ecf20Sopenharmony_ci free_irq(info->irq[i - 1], info); 6708c2ecf20Sopenharmony_ci info->irq[i - 1] = -1; 6718c2ecf20Sopenharmony_ci } 6728c2ecf20Sopenharmony_ci} 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci/* 6758c2ecf20Sopenharmony_ci * Some devices have no battery (HDMI sticks) and the axp288 battery's 6768c2ecf20Sopenharmony_ci * detection reports one despite it not being there. 6778c2ecf20Sopenharmony_ci * Please keep this listed sorted alphabetically. 6788c2ecf20Sopenharmony_ci */ 6798c2ecf20Sopenharmony_cistatic const struct dmi_system_id axp288_fuel_gauge_blacklist[] = { 6808c2ecf20Sopenharmony_ci { 6818c2ecf20Sopenharmony_ci /* ACEPC T8 Cherry Trail Z8350 mini PC */ 6828c2ecf20Sopenharmony_ci .matches = { 6838c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), 6848c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), 6858c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"), 6868c2ecf20Sopenharmony_ci /* also match on somewhat unique bios-version */ 6878c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), 6888c2ecf20Sopenharmony_ci }, 6898c2ecf20Sopenharmony_ci }, 6908c2ecf20Sopenharmony_ci { 6918c2ecf20Sopenharmony_ci /* ACEPC T11 Cherry Trail Z8350 mini PC */ 6928c2ecf20Sopenharmony_ci .matches = { 6938c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), 6948c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), 6958c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"), 6968c2ecf20Sopenharmony_ci /* also match on somewhat unique bios-version */ 6978c2ecf20Sopenharmony_ci DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"), 6988c2ecf20Sopenharmony_ci }, 6998c2ecf20Sopenharmony_ci }, 7008c2ecf20Sopenharmony_ci { 7018c2ecf20Sopenharmony_ci /* ECS EF20EA */ 7028c2ecf20Sopenharmony_ci .matches = { 7038c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "EF20EA"), 7048c2ecf20Sopenharmony_ci }, 7058c2ecf20Sopenharmony_ci }, 7068c2ecf20Sopenharmony_ci { 7078c2ecf20Sopenharmony_ci /* Intel Cherry Trail Compute Stick, Windows version */ 7088c2ecf20Sopenharmony_ci .matches = { 7098c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Intel"), 7108c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "STK1AW32SC"), 7118c2ecf20Sopenharmony_ci }, 7128c2ecf20Sopenharmony_ci }, 7138c2ecf20Sopenharmony_ci { 7148c2ecf20Sopenharmony_ci /* Intel Cherry Trail Compute Stick, version without an OS */ 7158c2ecf20Sopenharmony_ci .matches = { 7168c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Intel"), 7178c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "STK1A32SC"), 7188c2ecf20Sopenharmony_ci }, 7198c2ecf20Sopenharmony_ci }, 7208c2ecf20Sopenharmony_ci { 7218c2ecf20Sopenharmony_ci /* Meegopad T02 */ 7228c2ecf20Sopenharmony_ci .matches = { 7238c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"), 7248c2ecf20Sopenharmony_ci }, 7258c2ecf20Sopenharmony_ci }, 7268c2ecf20Sopenharmony_ci { 7278c2ecf20Sopenharmony_ci /* Meegopad T08 */ 7288c2ecf20Sopenharmony_ci .matches = { 7298c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Default string"), 7308c2ecf20Sopenharmony_ci DMI_MATCH(DMI_BOARD_VENDOR, "To be filled by OEM."), 7318c2ecf20Sopenharmony_ci DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"), 7328c2ecf20Sopenharmony_ci DMI_MATCH(DMI_BOARD_VERSION, "V1.1"), 7338c2ecf20Sopenharmony_ci }, 7348c2ecf20Sopenharmony_ci }, 7358c2ecf20Sopenharmony_ci { 7368c2ecf20Sopenharmony_ci /* Minix Neo Z83-4 mini PC */ 7378c2ecf20Sopenharmony_ci .matches = { 7388c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "MINIX"), 7398c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), 7408c2ecf20Sopenharmony_ci } 7418c2ecf20Sopenharmony_ci }, 7428c2ecf20Sopenharmony_ci {} 7438c2ecf20Sopenharmony_ci}; 7448c2ecf20Sopenharmony_ci 7458c2ecf20Sopenharmony_cistatic int axp288_fuel_gauge_probe(struct platform_device *pdev) 7468c2ecf20Sopenharmony_ci{ 7478c2ecf20Sopenharmony_ci int i, ret = 0; 7488c2ecf20Sopenharmony_ci struct axp288_fg_info *info; 7498c2ecf20Sopenharmony_ci struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); 7508c2ecf20Sopenharmony_ci struct power_supply_config psy_cfg = {}; 7518c2ecf20Sopenharmony_ci static const char * const iio_chan_name[] = { 7528c2ecf20Sopenharmony_ci [BAT_TEMP] = "axp288-batt-temp", 7538c2ecf20Sopenharmony_ci [PMIC_TEMP] = "axp288-pmic-temp", 7548c2ecf20Sopenharmony_ci [SYSTEM_TEMP] = "axp288-system-temp", 7558c2ecf20Sopenharmony_ci [BAT_CHRG_CURR] = "axp288-chrg-curr", 7568c2ecf20Sopenharmony_ci [BAT_D_CURR] = "axp288-chrg-d-curr", 7578c2ecf20Sopenharmony_ci [BAT_VOLT] = "axp288-batt-volt", 7588c2ecf20Sopenharmony_ci }; 7598c2ecf20Sopenharmony_ci unsigned int val; 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci if (dmi_check_system(axp288_fuel_gauge_blacklist)) 7628c2ecf20Sopenharmony_ci return -ENODEV; 7638c2ecf20Sopenharmony_ci 7648c2ecf20Sopenharmony_ci /* 7658c2ecf20Sopenharmony_ci * On some devices the fuelgauge and charger parts of the axp288 are 7668c2ecf20Sopenharmony_ci * not used, check that the fuelgauge is enabled (CC_CTRL != 0). 7678c2ecf20Sopenharmony_ci */ 7688c2ecf20Sopenharmony_ci ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val); 7698c2ecf20Sopenharmony_ci if (ret < 0) 7708c2ecf20Sopenharmony_ci return ret; 7718c2ecf20Sopenharmony_ci if (val == 0) 7728c2ecf20Sopenharmony_ci return -ENODEV; 7738c2ecf20Sopenharmony_ci 7748c2ecf20Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 7758c2ecf20Sopenharmony_ci if (!info) 7768c2ecf20Sopenharmony_ci return -ENOMEM; 7778c2ecf20Sopenharmony_ci 7788c2ecf20Sopenharmony_ci info->pdev = pdev; 7798c2ecf20Sopenharmony_ci info->regmap = axp20x->regmap; 7808c2ecf20Sopenharmony_ci info->regmap_irqc = axp20x->regmap_irqc; 7818c2ecf20Sopenharmony_ci info->status = POWER_SUPPLY_STATUS_UNKNOWN; 7828c2ecf20Sopenharmony_ci 7838c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, info); 7848c2ecf20Sopenharmony_ci 7858c2ecf20Sopenharmony_ci mutex_init(&info->lock); 7868c2ecf20Sopenharmony_ci 7878c2ecf20Sopenharmony_ci for (i = 0; i < IIO_CHANNEL_NUM; i++) { 7888c2ecf20Sopenharmony_ci /* 7898c2ecf20Sopenharmony_ci * Note cannot use devm_iio_channel_get because x86 systems 7908c2ecf20Sopenharmony_ci * lack the device<->channel maps which iio_channel_get will 7918c2ecf20Sopenharmony_ci * try to use when passed a non NULL device pointer. 7928c2ecf20Sopenharmony_ci */ 7938c2ecf20Sopenharmony_ci info->iio_channel[i] = 7948c2ecf20Sopenharmony_ci iio_channel_get(NULL, iio_chan_name[i]); 7958c2ecf20Sopenharmony_ci if (IS_ERR(info->iio_channel[i])) { 7968c2ecf20Sopenharmony_ci ret = PTR_ERR(info->iio_channel[i]); 7978c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "error getting iiochan %s: %d\n", 7988c2ecf20Sopenharmony_ci iio_chan_name[i], ret); 7998c2ecf20Sopenharmony_ci /* Wait for axp288_adc to load */ 8008c2ecf20Sopenharmony_ci if (ret == -ENODEV) 8018c2ecf20Sopenharmony_ci ret = -EPROBE_DEFER; 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_ci goto out_free_iio_chan; 8048c2ecf20Sopenharmony_ci } 8058c2ecf20Sopenharmony_ci } 8068c2ecf20Sopenharmony_ci 8078c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); 8088c2ecf20Sopenharmony_ci if (ret < 0) 8098c2ecf20Sopenharmony_ci goto out_free_iio_chan; 8108c2ecf20Sopenharmony_ci 8118c2ecf20Sopenharmony_ci if (!(ret & FG_DES_CAP1_VALID)) { 8128c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "axp288 not configured by firmware\n"); 8138c2ecf20Sopenharmony_ci ret = -ENODEV; 8148c2ecf20Sopenharmony_ci goto out_free_iio_chan; 8158c2ecf20Sopenharmony_ci } 8168c2ecf20Sopenharmony_ci 8178c2ecf20Sopenharmony_ci ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); 8188c2ecf20Sopenharmony_ci if (ret < 0) 8198c2ecf20Sopenharmony_ci goto out_free_iio_chan; 8208c2ecf20Sopenharmony_ci switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) { 8218c2ecf20Sopenharmony_ci case CHRG_CCCV_CV_4100MV: 8228c2ecf20Sopenharmony_ci info->max_volt = 4100; 8238c2ecf20Sopenharmony_ci break; 8248c2ecf20Sopenharmony_ci case CHRG_CCCV_CV_4150MV: 8258c2ecf20Sopenharmony_ci info->max_volt = 4150; 8268c2ecf20Sopenharmony_ci break; 8278c2ecf20Sopenharmony_ci case CHRG_CCCV_CV_4200MV: 8288c2ecf20Sopenharmony_ci info->max_volt = 4200; 8298c2ecf20Sopenharmony_ci break; 8308c2ecf20Sopenharmony_ci case CHRG_CCCV_CV_4350MV: 8318c2ecf20Sopenharmony_ci info->max_volt = 4350; 8328c2ecf20Sopenharmony_ci break; 8338c2ecf20Sopenharmony_ci } 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci psy_cfg.drv_data = info; 8368c2ecf20Sopenharmony_ci info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg); 8378c2ecf20Sopenharmony_ci if (IS_ERR(info->bat)) { 8388c2ecf20Sopenharmony_ci ret = PTR_ERR(info->bat); 8398c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register battery: %d\n", ret); 8408c2ecf20Sopenharmony_ci goto out_free_iio_chan; 8418c2ecf20Sopenharmony_ci } 8428c2ecf20Sopenharmony_ci 8438c2ecf20Sopenharmony_ci fuel_gauge_create_debugfs(info); 8448c2ecf20Sopenharmony_ci fuel_gauge_init_irq(info); 8458c2ecf20Sopenharmony_ci 8468c2ecf20Sopenharmony_ci return 0; 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ciout_free_iio_chan: 8498c2ecf20Sopenharmony_ci for (i = 0; i < IIO_CHANNEL_NUM; i++) 8508c2ecf20Sopenharmony_ci if (!IS_ERR_OR_NULL(info->iio_channel[i])) 8518c2ecf20Sopenharmony_ci iio_channel_release(info->iio_channel[i]); 8528c2ecf20Sopenharmony_ci 8538c2ecf20Sopenharmony_ci return ret; 8548c2ecf20Sopenharmony_ci} 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_cistatic const struct platform_device_id axp288_fg_id_table[] = { 8578c2ecf20Sopenharmony_ci { .name = DEV_NAME }, 8588c2ecf20Sopenharmony_ci {}, 8598c2ecf20Sopenharmony_ci}; 8608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, axp288_fg_id_table); 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_cistatic int axp288_fuel_gauge_remove(struct platform_device *pdev) 8638c2ecf20Sopenharmony_ci{ 8648c2ecf20Sopenharmony_ci struct axp288_fg_info *info = platform_get_drvdata(pdev); 8658c2ecf20Sopenharmony_ci int i; 8668c2ecf20Sopenharmony_ci 8678c2ecf20Sopenharmony_ci power_supply_unregister(info->bat); 8688c2ecf20Sopenharmony_ci fuel_gauge_remove_debugfs(info); 8698c2ecf20Sopenharmony_ci 8708c2ecf20Sopenharmony_ci for (i = 0; i < AXP288_FG_INTR_NUM; i++) 8718c2ecf20Sopenharmony_ci if (info->irq[i] >= 0) 8728c2ecf20Sopenharmony_ci free_irq(info->irq[i], info); 8738c2ecf20Sopenharmony_ci 8748c2ecf20Sopenharmony_ci for (i = 0; i < IIO_CHANNEL_NUM; i++) 8758c2ecf20Sopenharmony_ci iio_channel_release(info->iio_channel[i]); 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci return 0; 8788c2ecf20Sopenharmony_ci} 8798c2ecf20Sopenharmony_ci 8808c2ecf20Sopenharmony_cistatic struct platform_driver axp288_fuel_gauge_driver = { 8818c2ecf20Sopenharmony_ci .probe = axp288_fuel_gauge_probe, 8828c2ecf20Sopenharmony_ci .remove = axp288_fuel_gauge_remove, 8838c2ecf20Sopenharmony_ci .id_table = axp288_fg_id_table, 8848c2ecf20Sopenharmony_ci .driver = { 8858c2ecf20Sopenharmony_ci .name = DEV_NAME, 8868c2ecf20Sopenharmony_ci }, 8878c2ecf20Sopenharmony_ci}; 8888c2ecf20Sopenharmony_ci 8898c2ecf20Sopenharmony_cimodule_platform_driver(axp288_fuel_gauge_driver); 8908c2ecf20Sopenharmony_ci 8918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>"); 8928c2ecf20Sopenharmony_ciMODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>"); 8938c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); 8948c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 895