* [PATCH RESEND v3 0/3] X-Power AXP288 PMIC Fuel Gauge Driver
@ 2015-02-02 23:41 Todd Brandt
2015-02-02 23:41 ` [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Todd Brandt @ 2015-02-02 23:41 UTC (permalink / raw)
To: sre, dbaryshkov, david.woodhouse
Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, todd.e.brandt,
jacob.jun.pan, sameo, lee.jones, dwmw2, jic23
New power_supply driver at driver/power which interfaces with the
axp20x mfd driver as a cell. Provides battery info, monitors for
changes, and generates alerts on temperature and capacity issues
Todd Brandt (3):
mfd/axp20x: change battery cell name to fuel gauge
mfd/axp20x: add support for fuel gauge cell
X-Power AXP288 PMIC Fuel Gauge Driver
v1: 01/07/14
v2: 01/22/14
- replaced the const in axp20x_acpi_match declaration
- reformatted the commit text
- added IIO as driver dependency
- moved debug info to debugfs in file fuelgauge
- added HEALTH_COLD value to health calculation
- added better cleanup to remove call
cancelling any delayed work calls
unregistration of the power_supply
v3: 01/28/14
- moved debugs create and init config regs after error case
- freed the virqs in the remove function
- remove .owner field as it is populated automatically
drivers/mfd/axp20x.c | 8 +-
drivers/power/Kconfig | 9 +
drivers/power/Makefile | 1 +
drivers/power/axp288_fuel_gauge.c | 1151 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/axp20x.h | 43 +-
5 files changed, 1206 insertions(+), 6 deletions(-)
create mode 100644 drivers/power/axp288_fuel_gauge.c
--
1.9.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge 2015-02-02 23:41 [PATCH RESEND v3 0/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt @ 2015-02-02 23:41 ` Todd Brandt 2015-02-16 13:54 ` Lee Jones 2015-02-02 23:41 ` [PATCH RESEND v3 2/3] mfd/axp20x: add support for fuel gauge cell Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2 siblings, 1 reply; 7+ messages in thread From: Todd Brandt @ 2015-02-02 23:41 UTC (permalink / raw) To: sre, dbaryshkov, david.woodhouse Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones, dwmw2, jic23 Name changes to the battery cell structure to a more generic cell type: fuel gauge. Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> Acked-By: Sebastian Reichel <sre@kernel.org> Acked-by: Jacob Pan <jacob.jun.pan@linux.intel.com> --- drivers/mfd/axp20x.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) v1: 01/07/14 v2: 01/22/14 - replaced the const in axp20x_acpi_match declaration v3: same as v2 diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index b1b580a..0acbe52 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -87,7 +87,7 @@ static struct resource axp20x_pek_resources[] = { }, }; -static struct resource axp288_battery_resources[] = { +static struct resource axp288_fuel_gauge_resources[] = { { .start = AXP288_IRQ_QWBTU, .end = AXP288_IRQ_QWBTU, @@ -350,9 +350,9 @@ static struct mfd_cell axp288_cells[] = { .resources = axp288_charger_resources, }, { - .name = "axp288_battery", - .num_resources = ARRAY_SIZE(axp288_battery_resources), - .resources = axp288_battery_resources, + .name = "axp288_fuel_gauge", + .num_resources = ARRAY_SIZE(axp288_fuel_gauge_resources), + .resources = axp288_fuel_gauge_resources, }, { .name = "axp288_pmic_acpi", -- 1.9.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge 2015-02-02 23:41 ` [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt @ 2015-02-16 13:54 ` Lee Jones 0 siblings, 0 replies; 7+ messages in thread From: Lee Jones @ 2015-02-16 13:54 UTC (permalink / raw) To: Todd Brandt Cc: sre, dbaryshkov, david.woodhouse, linux-pm, linux-iio, linux-kernel, todd.e.brandt, jacob.jun.pan, sameo, dwmw2, jic23 On Mon, 02 Feb 2015, Todd Brandt wrote: > Name changes to the battery cell structure to a > more generic cell type: fuel gauge. > > Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> > Acked-By: Sebastian Reichel <sre@kernel.org> > Acked-by: Jacob Pan <jacob.jun.pan@linux.intel.com> > --- > drivers/mfd/axp20x.c | 8 ++++---- > 1 file changed, 4 insertions(+), 4 deletions(-) Applied, thanks. > v1: 01/07/14 > v2: 01/22/14 > - replaced the const in axp20x_acpi_match declaration > v3: same as v2 FYI: Changelogs are usually placed _above_ the diff stat. -- Lee Jones Linaro STMicroelectronics Landing Team Lead Linaro.org │ Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH RESEND v3 2/3] mfd/axp20x: add support for fuel gauge cell 2015-02-02 23:41 [PATCH RESEND v3 0/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt @ 2015-02-02 23:41 ` Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2 siblings, 0 replies; 7+ messages in thread From: Todd Brandt @ 2015-02-02 23:41 UTC (permalink / raw) To: sre, dbaryshkov, david.woodhouse Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones, dwmw2, jic23 Register definitions and platform data structure for fuel gauge cell devices. Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> Acked-By: Sebastian Reichel <sre@kernel.org> Acked-By: Jacob Pan <jacob.jun.pan@linux.intel.com> Acked-By: Lee Jones <lee.jones@linaro.org> --- include/linux/mfd/axp20x.h | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) v3: same as v1-2 and is already accepted, re-included for continuity diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index 81589d1..dfabd6d 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h @@ -124,10 +124,27 @@ enum { #define AXP288_PMIC_ADC_H 0x56 #define AXP288_PMIC_ADC_L 0x57 #define AXP288_ADC_TS_PIN_CTRL 0x84 - #define AXP288_PMIC_ADC_EN 0x84 -#define AXP288_FG_TUNE5 0xed +/* Fuel Gauge */ +#define AXP288_FG_RDC1_REG 0xba +#define AXP288_FG_RDC0_REG 0xbb +#define AXP288_FG_OCVH_REG 0xbc +#define AXP288_FG_OCVL_REG 0xbd +#define AXP288_FG_OCV_CURVE_REG 0xc0 +#define AXP288_FG_DES_CAP1_REG 0xe0 +#define AXP288_FG_DES_CAP0_REG 0xe1 +#define AXP288_FG_CC_MTR1_REG 0xe2 +#define AXP288_FG_CC_MTR0_REG 0xe3 +#define AXP288_FG_OCV_CAP_REG 0xe4 +#define AXP288_FG_CC_CAP_REG 0xe5 +#define AXP288_FG_LOW_CAP_REG 0xe6 +#define AXP288_FG_TUNE0 0xe8 +#define AXP288_FG_TUNE1 0xe9 +#define AXP288_FG_TUNE2 0xea +#define AXP288_FG_TUNE3 0xeb +#define AXP288_FG_TUNE4 0xec +#define AXP288_FG_TUNE5 0xed /* Regulators IDs */ enum { @@ -236,4 +253,26 @@ struct axp20x_dev { const struct regmap_irq_chip *regmap_irq_chip; }; +#define BATTID_LEN 64 +#define OCV_CURVE_SIZE 32 +#define MAX_THERM_CURVE_SIZE 25 +#define PD_DEF_MIN_TEMP 0 +#define PD_DEF_MAX_TEMP 55 + +struct axp20x_fg_pdata { + char battid[BATTID_LEN + 1]; + int design_cap; + int min_volt; + int max_volt; + int max_temp; + int min_temp; + int cap1; + int cap0; + int rdc1; + int rdc0; + int ocv_curve[OCV_CURVE_SIZE]; + int tcsz; + int thermistor_curve[MAX_THERM_CURVE_SIZE][2]; +}; + #endif /* __LINUX_MFD_AXP20X_H */ -- 1.9.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver 2015-02-02 23:41 [PATCH RESEND v3 0/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 2/3] mfd/axp20x: add support for fuel gauge cell Todd Brandt @ 2015-02-02 23:41 ` Todd Brandt 2015-02-02 20:57 ` Brandt, Todd E 2 siblings, 1 reply; 7+ messages in thread From: Todd Brandt @ 2015-02-02 23:41 UTC (permalink / raw) To: sre, dbaryshkov, david.woodhouse Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones, dwmw2, jic23 New power_supply driver at driver/power which interfaces with the axp20x mfd driver as a cell. Provides battery info, monitors for changes, and generates alerts on temperature and capacity issues Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com> Acked-by: Jacob Pan <jacob.jun.pan@linux.intel.com> --- drivers/power/Kconfig | 9 + drivers/power/Makefile | 1 + drivers/power/axp288_fuel_gauge.c | 1151 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1161 insertions(+) create mode 100644 drivers/power/axp288_fuel_gauge.c v1: 01/07/14 v2: 01/22/14 - reformatted the commit text - added IIO as driver dependency - moved debug info to debugfs in file fuelgauge - added HEALTH_COLD value to health calculation - added better cleanup to remove call cancelling any delayed work calls unregistration of the power_supply v3: 01/28/14 - moved debugs create and init config regs after error case - freed the virqs in the remove function - remove .owner field as it is populated automatically diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0108c2a..a1166e0 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -192,6 +192,15 @@ config BATTERY_DA9052 Say Y here to enable support for batteries charger integrated into DA9052 PMIC. +config AXP288_FUEL_GAUGE + tristate "X-Powers AXP288 Fuel Gauge" + depends on MFD_AXP20X && IIO + help + Say yes here to have support for X-Power power management IC (PMIC) + Fuel Gauge. The device provides battery statistics and status + monitoring as well as alerts for battery over/under voltage and + over/under temperature. + config BATTERY_MAX17040 tristate "Maxim MAX17040 Fuel Gauge" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index dfa8942..eb76b23 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -59,3 +59,4 @@ obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_POWER_RESET) += reset/ +obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c new file mode 100644 index 0000000..c86e709 --- /dev/null +++ b/drivers/power/axp288_fuel_gauge.c @@ -0,0 +1,1151 @@ +/* + * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver + * + * Copyright (C) 2014 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/mfd/axp20x.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/iio/consumer.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) +#define CHRG_STAT_BAT_VALID (1 << 4) +#define CHRG_STAT_BAT_PRESENT (1 << 5) +#define CHRG_STAT_CHARGING (1 << 6) +#define CHRG_STAT_PMIC_OTP (1 << 7) + +#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ +#define CHRG_CCCV_CC_BIT_POS 0 +#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ +#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ +#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ +#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ +#define CHRG_CCCV_CV_BIT_POS 5 +#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ +#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ +#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ +#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ +#define CHRG_CCCV_CHG_EN (1 << 7) + +#define CV_4100 4100 /* 4100mV */ +#define CV_4150 4150 /* 4150mV */ +#define CV_4200 4200 /* 4200mV */ +#define CV_4350 4350 /* 4350mV */ + +#define TEMP_IRQ_CFG_QWBTU (1 << 0) +#define TEMP_IRQ_CFG_WBTU (1 << 1) +#define TEMP_IRQ_CFG_QWBTO (1 << 2) +#define TEMP_IRQ_CFG_WBTO (1 << 3) +#define TEMP_IRQ_CFG_MASK 0xf + +#define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0) +#define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1) +#define FG_IRQ_CFG_LOWBATT_MASK 0x3 +#define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0) +#define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1) + +#define FG_CNTL_OCV_ADJ_STAT (1 << 2) +#define FG_CNTL_OCV_ADJ_EN (1 << 3) +#define FG_CNTL_CAP_ADJ_STAT (1 << 4) +#define FG_CNTL_CAP_ADJ_EN (1 << 5) +#define FG_CNTL_CC_EN (1 << 6) +#define FG_CNTL_GAUGE_EN (1 << 7) + +#define FG_REP_CAP_VALID (1 << 7) +#define FG_REP_CAP_VAL_MASK 0x7F + +#define FG_DES_CAP1_VALID (1 << 7) +#define FG_DES_CAP1_VAL_MASK 0x7F +#define FG_DES_CAP0_VAL_MASK 0xFF +#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */ + +#define FG_CC_MTR1_VALID (1 << 7) +#define FG_CC_MTR1_VAL_MASK 0x7F +#define FG_CC_MTR0_VAL_MASK 0xFF +#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */ + +#define FG_OCV_CAP_VALID (1 << 7) +#define FG_OCV_CAP_VAL_MASK 0x7F +#define FG_CC_CAP_VALID (1 << 7) +#define FG_CC_CAP_VAL_MASK 0x7F + +#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */ +#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */ +#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */ +#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */ +#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */ +#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */ + +#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */ +#define NR_RETRY_CNT 3 +#define DEV_NAME "axp288_fuel_gauge" + +/* 1.1mV per LSB expressed in uV */ +#define VOLTAGE_FROM_ADC(a) ((a * 11) / 10) +/* properties converted to tenths of degrees, uV, uA, uW */ +#define PROP_TEMP(a) ((a) * 10) +#define UNPROP_TEMP(a) ((a) / 10) +#define PROP_VOLT(a) ((a) * 1000) +#define PROP_CURR(a) ((a) * 1000) + +#define AXP288_FG_INTR_NUM 6 +enum { + QWBTU_IRQ = 0, + WBTU_IRQ, + QWBTO_IRQ, + WBTO_IRQ, + WL2_IRQ, + WL1_IRQ, +}; + +struct axp288_fg_info { + struct platform_device *pdev; + struct axp20x_fg_pdata *pdata; + struct regmap *regmap; + struct regmap_irq_chip_data *regmap_irqc; + int irq[AXP288_FG_INTR_NUM]; + struct power_supply bat; + struct mutex lock; + int status; + struct delayed_work status_monitor; + struct dentry *debug_file; +}; + +static enum power_supply_property fuel_gauge_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg) +{ + int ret, i; + unsigned int val; + + for (i = 0; i < NR_RETRY_CNT; i++) { + ret = regmap_read(info->regmap, reg, &val); + if (ret == -EBUSY) + continue; + else + break; + } + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret); + + return val; +} + +static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val) +{ + int ret; + + ret = regmap_write(info->regmap, reg, (unsigned int)val); + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret); + + return ret; +} + +static int pmic_read_adc_val(const char *name, int *raw_val, + struct axp288_fg_info *info) +{ + int ret, val = 0; + struct iio_channel *indio_chan; + + indio_chan = iio_channel_get(NULL, name); + if (IS_ERR_OR_NULL(indio_chan)) { + ret = PTR_ERR(indio_chan); + goto exit; + } + ret = iio_read_channel_raw(indio_chan, &val); + if (ret < 0) { + dev_err(&info->pdev->dev, + "IIO channel read error: %x, %x\n", ret, val); + goto err_exit; + } + + dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val); + *raw_val = val; + +err_exit: + iio_channel_release(indio_chan); +exit: + return ret; +} + +#ifdef CONFIG_DEBUG_FS +static int fuel_gauge_debug_show(struct seq_file *s, void *data) +{ + struct axp288_fg_info *info = s->private; + int raw_val, ret; + + seq_printf(s, " PWR_STATUS[%02x] : %02x\n", + AXP20X_PWR_INPUT_STATUS, + fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS)); + seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n", + AXP20X_PWR_OP_MODE, + fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE)); + seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n", + AXP20X_CHRG_CTRL1, + fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1)); + seq_printf(s, " VLTF[%02x] : %02x\n", + AXP20X_V_LTF_DISCHRG, + fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG)); + seq_printf(s, " VHTF[%02x] : %02x\n", + AXP20X_V_HTF_DISCHRG, + fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG)); + seq_printf(s, " CC_CTRL[%02x] : %02x\n", + AXP20X_CC_CTRL, + fuel_gauge_reg_readb(info, AXP20X_CC_CTRL)); + seq_printf(s, "BATTERY CAP[%02x] : %02x\n", + AXP20X_FG_RES, + fuel_gauge_reg_readb(info, AXP20X_FG_RES)); + seq_printf(s, " FG_RDC1[%02x] : %02x\n", + AXP288_FG_RDC1_REG, + fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG)); + seq_printf(s, " FG_RDC0[%02x] : %02x\n", + AXP288_FG_RDC0_REG, + fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG)); + seq_printf(s, " FG_OCVH[%02x] : %02x\n", + AXP288_FG_OCVH_REG, + fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG)); + seq_printf(s, " FG_OCVL[%02x] : %02x\n", + AXP288_FG_OCVL_REG, + fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG)); + seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n", + AXP288_FG_DES_CAP1_REG, + fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG)); + seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n", + AXP288_FG_DES_CAP0_REG, + fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG)); + seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n", + AXP288_FG_CC_MTR1_REG, + fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG)); + seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n", + AXP288_FG_CC_MTR0_REG, + fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG)); + seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n", + AXP288_FG_OCV_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG)); + seq_printf(s, " FG_CC_CAP[%02x] : %02x\n", + AXP288_FG_CC_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG)); + seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n", + AXP288_FG_LOW_CAP_REG, + fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG)); + seq_printf(s, "TUNING_CTL0[%02x] : %02x\n", + AXP288_FG_TUNE0, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE0)); + seq_printf(s, "TUNING_CTL1[%02x] : %02x\n", + AXP288_FG_TUNE1, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE1)); + seq_printf(s, "TUNING_CTL2[%02x] : %02x\n", + AXP288_FG_TUNE2, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE2)); + seq_printf(s, "TUNING_CTL3[%02x] : %02x\n", + AXP288_FG_TUNE3, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE3)); + seq_printf(s, "TUNING_CTL4[%02x] : %02x\n", + AXP288_FG_TUNE4, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE4)); + seq_printf(s, "TUNING_CTL5[%02x] : %02x\n", + AXP288_FG_TUNE5, + fuel_gauge_reg_readb(info, AXP288_FG_TUNE5)); + + ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-batttemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-pmictemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-systtemp : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-chrgcurr : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-dchrgcur : %d\n", raw_val); + ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); + if (ret >= 0) + seq_printf(s, "axp288-battvolt : %d\n", raw_val); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, fuel_gauge_debug_show, inode->i_private); +} + +static const struct file_operations fg_debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void fuel_gauge_create_debugfs(struct axp288_fg_info *info) +{ + info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL, + info, &fg_debug_fops); +} + +static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) +{ + debugfs_remove(info->debug_file); +} +#else +static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info) +{ +} +static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info) +{ +} +#endif + +static void fuel_gauge_get_status(struct axp288_fg_info *info) +{ + int pwr_stat, ret; + int charge, discharge; + + pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); + if (pwr_stat < 0) { + dev_err(&info->pdev->dev, + "PWR STAT read failed:%d\n", pwr_stat); + return; + } + ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); + if (ret < 0) { + dev_err(&info->pdev->dev, + "ADC charge current read failed:%d\n", ret); + return; + } + ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); + if (ret < 0) { + dev_err(&info->pdev->dev, + "ADC discharge current read failed:%d\n", ret); + return; + } + + if (charge > 0) + info->status = POWER_SUPPLY_STATUS_CHARGING; + else if (discharge > 0) + info->status = POWER_SUPPLY_STATUS_DISCHARGING; + else { + if (pwr_stat & CHRG_STAT_BAT_PRESENT) + info->status = POWER_SUPPLY_STATUS_FULL; + else + info->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } +} + +static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt) +{ + int ret = 0, raw_val; + + ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info); + if (ret < 0) + goto vbatt_read_fail; + + *vbatt = VOLTAGE_FROM_ADC(raw_val); +vbatt_read_fail: + return ret; +} + +static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur) +{ + int ret, value = 0; + int charge, discharge; + + ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info); + if (ret < 0) + goto current_read_fail; + ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info); + if (ret < 0) + goto current_read_fail; + + if (charge > 0) + value = charge; + else if (discharge > 0) + value = -1 * discharge; + + *cur = value; +current_read_fail: + return ret; +} + +static int temp_to_adc(struct axp288_fg_info *info, int tval) +{ + int rntc = 0, i, ret, adc_val; + int rmin, rmax, tmin, tmax; + int tcsz = info->pdata->tcsz; + + /* get the Rntc resitance value for this temp */ + if (tval > info->pdata->thermistor_curve[0][1]) { + rntc = info->pdata->thermistor_curve[0][0]; + } else if (tval <= info->pdata->thermistor_curve[tcsz-1][1]) { + rntc = info->pdata->thermistor_curve[tcsz-1][0]; + } else { + for (i = 1; i < tcsz; i++) { + if (tval > info->pdata->thermistor_curve[i][1]) { + rmin = info->pdata->thermistor_curve[i-1][0]; + rmax = info->pdata->thermistor_curve[i][0]; + tmin = info->pdata->thermistor_curve[i-1][1]; + tmax = info->pdata->thermistor_curve[i][1]; + rntc = rmin + ((rmax - rmin) * + (tval - tmin) / (tmax - tmin)); + break; + } + } + } + + /* we need the current to calculate the proper adc voltage */ + ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE); + if (ret < 0) { + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); + ret = 0x30; + } + + /* + * temperature is proportional to NTS thermistor resistance + * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA + * [12-bit ADC VAL] = R_NTC(Ω) * current / 800 + */ + adc_val = rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800; + + return adc_val; +} + +static int adc_to_temp(struct axp288_fg_info *info, int adc_val) +{ + int ret, r, i, tval = 0; + int rmin, rmax, tmin, tmax; + int tcsz = info->pdata->tcsz; + + ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE); + if (ret < 0) { + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); + ret = 0x30; + } + + /* + * temperature is proportional to NTS thermistor resistance + * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA + * R_NTC(Ω) = [12-bit ADC VAL] * 800 / current + */ + r = adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3))); + + if (r < info->pdata->thermistor_curve[0][0]) { + tval = info->pdata->thermistor_curve[0][1]; + } else if (r >= info->pdata->thermistor_curve[tcsz-1][0]) { + tval = info->pdata->thermistor_curve[tcsz-1][1]; + } else { + for (i = 1; i < tcsz; i++) { + if (r < info->pdata->thermistor_curve[i][0]) { + rmin = info->pdata->thermistor_curve[i-1][0]; + rmax = info->pdata->thermistor_curve[i][0]; + tmin = info->pdata->thermistor_curve[i-1][1]; + tmax = info->pdata->thermistor_curve[i][1]; + tval = tmin + ((tmax - tmin) * + (r - rmin) / (rmax - rmin)); + break; + } + } + } + + return tval; +} + +static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp) +{ + int ret, raw_val = 0; + + ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info); + if (ret < 0) + goto temp_read_fail; + + *btemp = adc_to_temp(info, raw_val); + +temp_read_fail: + return ret; +} + +static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv) +{ + int ret, value; + + /* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */ + ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG); + if (ret < 0) + goto vocv_read_fail; + value = ret << 4; + + ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG); + if (ret < 0) + goto vocv_read_fail; + value |= (ret & 0xf); + + *vocv = VOLTAGE_FROM_ADC(value); +vocv_read_fail: + return ret; +} + +static int fuel_gauge_battery_health(struct axp288_fg_info *info) +{ + int temp, vocv; + int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN; + + ret = fuel_gauge_get_btemp(info, &temp); + if (ret < 0) + goto health_read_fail; + + ret = fuel_gauge_get_vocv(info, &vocv); + if (ret < 0) + goto health_read_fail; + + if (vocv > info->pdata->max_volt) + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (temp > info->pdata->max_temp) + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (temp < info->pdata->min_temp) + health = POWER_SUPPLY_HEALTH_COLD; + else if (vocv < info->pdata->min_volt) + health = POWER_SUPPLY_HEALTH_DEAD; + else + health = POWER_SUPPLY_HEALTH_GOOD; + +health_read_fail: + return health; +} + +static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info) +{ + int ret, adc_val; + + /* program temperature threshold as 1/16 ADC value */ + adc_val = temp_to_adc(info, info->pdata->max_temp); + ret = fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >> 4); + + return ret; +} + +static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info) +{ + int ret, adc_val; + + /* program temperature threshold as 1/16 ADC value */ + adc_val = temp_to_adc(info, info->pdata->min_temp); + ret = fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >> 4); + + return ret; +} + +static int fuel_gauge_get_property(struct power_supply *ps, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct axp288_fg_info *info = container_of(ps, + struct axp288_fg_info, bat); + int ret = 0, value; + + mutex_lock(&info->lock); + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + fuel_gauge_get_status(info); + val->intval = info->status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = fuel_gauge_battery_health(info); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = fuel_gauge_get_vbatt(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_VOLT(value); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + ret = fuel_gauge_get_vocv(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_VOLT(value); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = fuel_gauge_get_current(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_CURR(value); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE); + if (ret < 0) + goto fuel_gauge_read_err; + + if (ret & CHRG_STAT_BAT_PRESENT) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); + if (ret < 0) + goto fuel_gauge_read_err; + + if (!(ret & FG_REP_CAP_VALID)) + dev_err(&info->pdev->dev, + "capacity measurement not valid\n"); + val->intval = (ret & FG_REP_CAP_VAL_MASK); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = (ret & 0x0f); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = fuel_gauge_get_btemp(info, &value); + if (ret < 0) + goto fuel_gauge_read_err; + val->intval = PROP_TEMP(value); + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + val->intval = PROP_TEMP(info->pdata->max_temp); + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + val->intval = PROP_TEMP(info->pdata->min_temp); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG); + if (ret < 0) + goto fuel_gauge_read_err; + + value = (ret & FG_CC_MTR1_VAL_MASK) << 8; + ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG); + if (ret < 0) + goto fuel_gauge_read_err; + value |= (ret & FG_CC_MTR0_VAL_MASK); + val->intval = value * FG_DES_CAP_RES_LSB; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); + if (ret < 0) + goto fuel_gauge_read_err; + + value = (ret & FG_DES_CAP1_VAL_MASK) << 8; + ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG); + if (ret < 0) + goto fuel_gauge_read_err; + value |= (ret & FG_DES_CAP0_VAL_MASK); + val->intval = value * FG_DES_CAP_RES_LSB; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = PROP_CURR(info->pdata->design_cap); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = PROP_VOLT(info->pdata->max_volt); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = PROP_VOLT(info->pdata->min_volt); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = info->pdata->battid; + break; + default: + mutex_unlock(&info->lock); + return -EINVAL; + } + + mutex_unlock(&info->lock); + return 0; + +fuel_gauge_read_err: + mutex_unlock(&info->lock); + return ret; +} + +static int fuel_gauge_set_property(struct power_supply *ps, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct axp288_fg_info *info = container_of(ps, + struct axp288_fg_info, bat); + int ret = 0; + + mutex_lock(&info->lock); + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + info->status = val->intval; + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + if ((val->intval < PD_DEF_MIN_TEMP) || + (val->intval > PD_DEF_MAX_TEMP)) { + ret = -EINVAL; + break; + } + info->pdata->min_temp = UNPROP_TEMP(val->intval); + ret = fuel_gauge_set_low_btemp_alert(info); + if (ret < 0) + dev_err(&info->pdev->dev, + "temp alert min set fail:%d\n", ret); + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + if ((val->intval < PD_DEF_MIN_TEMP) || + (val->intval > PD_DEF_MAX_TEMP)) { + ret = -EINVAL; + break; + } + info->pdata->max_temp = UNPROP_TEMP(val->intval); + ret = fuel_gauge_set_high_btemp_alert(info); + if (ret < 0) + dev_err(&info->pdev->dev, + "temp alert max set fail:%d\n", ret); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + if ((val->intval < 0) || (val->intval > 15)) { + ret = -EINVAL; + break; + } + ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG); + if (ret < 0) + break; + ret &= 0xf0; + ret |= (val->intval & 0xf); + ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int fuel_gauge_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_TEMP_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + case POWER_SUPPLY_PROP_TEMP_MAX: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static void fuel_gauge_status_monitor(struct work_struct *work) +{ + struct axp288_fg_info *info = container_of(work, + struct axp288_fg_info, status_monitor.work); + + fuel_gauge_get_status(info); + power_supply_changed(&info->bat); + schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); +} + +static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev) +{ + struct axp288_fg_info *info = dev; + int i; + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + if (info->irq[i] == irq) + break; + } + + if (i >= AXP288_FG_INTR_NUM) { + dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); + return IRQ_NONE; + } + + switch (i) { + case QWBTU_IRQ: + dev_info(&info->pdev->dev, + "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); + break; + case WBTU_IRQ: + dev_info(&info->pdev->dev, + "Battery under temperature in work mode IRQ (WBTU)\n"); + break; + case QWBTO_IRQ: + dev_info(&info->pdev->dev, + "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); + break; + case WBTO_IRQ: + dev_info(&info->pdev->dev, + "Battery over temperature in work mode IRQ (WBTO)\n"); + break; + case WL2_IRQ: + dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n"); + break; + case WL1_IRQ: + dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n"); + break; + default: + dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); + } + + power_supply_changed(&info->bat); + return IRQ_HANDLED; +} + +static void fuel_gauge_external_power_changed(struct power_supply *psy) +{ + struct axp288_fg_info *info = container_of(psy, + struct axp288_fg_info, bat); + + power_supply_changed(&info->bat); +} + +static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info) +{ + int ret; + u8 reg_val; + + ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); + if (ret < 0) { + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret); + return ret; + } + ret = (ret & FG_REP_CAP_VAL_MASK); + + if (ret > FG_LOW_CAP_WARN_THR) + reg_val = FG_LOW_CAP_WARN_THR; + else if (ret > FG_LOW_CAP_CRIT_THR) + reg_val = FG_LOW_CAP_CRIT_THR; + else + reg_val = FG_LOW_CAP_SHDN_THR; + + reg_val |= FG_LOW_CAP_THR1_VAL; + ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val); + if (ret < 0) + dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret); + + return ret; +} + +static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info) +{ + int ret; + u8 val; + + ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1); + if (ret < 0) + goto fg_prog_ocv_fail; + else + val = (ret & ~CHRG_CCCV_CV_MASK); + + switch (info->pdata->max_volt) { + case CV_4100: + val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS); + break; + case CV_4150: + val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS); + break; + case CV_4200: + val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS); + break; + case CV_4350: + val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS); + break; + default: + val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS); + break; + } + + ret = fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val); +fg_prog_ocv_fail: + return ret; +} + +static int fuel_gauge_program_design_cap(struct axp288_fg_info *info) +{ + int ret; + + ret = fuel_gauge_reg_writeb(info, + AXP288_FG_DES_CAP1_REG, info->pdata->cap1); + if (ret < 0) + goto fg_prog_descap_fail; + + ret = fuel_gauge_reg_writeb(info, + AXP288_FG_DES_CAP0_REG, info->pdata->cap0); + +fg_prog_descap_fail: + return ret; +} + +static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info) +{ + int ret = 0, i; + + for (i = 0; i < OCV_CURVE_SIZE; i++) { + ret = fuel_gauge_reg_writeb(info, + AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve[i]); + if (ret < 0) + goto fg_prog_ocv_fail; + } + +fg_prog_ocv_fail: + return ret; +} + +static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info) +{ + int ret; + + ret = fuel_gauge_reg_writeb(info, + AXP288_FG_RDC1_REG, info->pdata->rdc1); + if (ret < 0) + goto fg_prog_ocv_fail; + + ret = fuel_gauge_reg_writeb(info, + AXP288_FG_RDC0_REG, info->pdata->rdc0); + +fg_prog_ocv_fail: + return ret; +} + +static void fuel_gauge_init_config_regs(struct axp288_fg_info *info) +{ + int ret; + + /* + * check if the config data is already + * programmed and if so just return. + */ + + ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG); + if (ret < 0) { + dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n"); + } else if (!(ret & FG_DES_CAP1_VALID)) { + dev_info(&info->pdev->dev, "FG data needs to be initialized\n"); + } else { + dev_info(&info->pdev->dev, "FG data is already initialized\n"); + return; + } + + ret = fuel_gauge_program_vbatt_full(info); + if (ret < 0) + dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret); + + ret = fuel_gauge_program_design_cap(info); + if (ret < 0) + dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret); + + ret = fuel_gauge_program_rdc_vals(info); + if (ret < 0) + dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret); + + ret = fuel_gauge_program_ocv_curve(info); + if (ret < 0) + dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret); + + ret = fuel_gauge_set_lowbatt_thresholds(info); + if (ret < 0) + dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret); + + ret = fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef); + if (ret < 0) + dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret); +} + +static void fuel_gauge_init_irq(struct axp288_fg_info *info) +{ + int ret, i, pirq; + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) { + pirq = platform_get_irq(info->pdev, i); + info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); + if (info->irq[i] < 0) { + dev_warn(&info->pdev->dev, + "regmap_irq get virq failed for IRQ %d: %d\n", + pirq, info->irq[i]); + info->irq[i] = -1; + goto intr_failed; + } + ret = request_threaded_irq(info->irq[i], + NULL, fuel_gauge_thread_handler, + IRQF_ONESHOT, DEV_NAME, info); + if (ret) { + dev_warn(&info->pdev->dev, + "request irq failed for IRQ %d: %d\n", + pirq, info->irq[i]); + info->irq[i] = -1; + goto intr_failed; + } else { + dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n", + pirq, info->irq[i]); + } + } + return; + +intr_failed: + for (; i > 0; i--) { + free_irq(info->irq[i - 1], info); + info->irq[i - 1] = -1; + } +} + +static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info) +{ + int ret; + unsigned int val; + + ret = fuel_gauge_set_high_btemp_alert(info); + if (ret < 0) + dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", ret); + + ret = fuel_gauge_set_low_btemp_alert(info); + if (ret < 0) + dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", ret); + + /* enable interrupts */ + val = fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN); + val |= TEMP_IRQ_CFG_MASK; + fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val); + + val = fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN); + val |= FG_IRQ_CFG_LOWBATT_MASK; + val = fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val); +} + +static int axp288_fuel_gauge_probe(struct platform_device *pdev) +{ + int ret; + struct axp288_fg_info *info; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + info->regmap = axp20x->regmap; + info->regmap_irqc = axp20x->regmap_irqc; + info->status = POWER_SUPPLY_STATUS_UNKNOWN; + info->pdata = pdev->dev.platform_data; + if (!info->pdata) + return -ENODEV; + + platform_set_drvdata(pdev, info); + + mutex_init(&info->lock); + INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor); + + info->bat.name = DEV_NAME; + info->bat.type = POWER_SUPPLY_TYPE_BATTERY; + info->bat.properties = fuel_gauge_props; + info->bat.num_properties = ARRAY_SIZE(fuel_gauge_props); + info->bat.get_property = fuel_gauge_get_property; + info->bat.set_property = fuel_gauge_set_property; + info->bat.property_is_writeable = fuel_gauge_property_is_writeable; + info->bat.external_power_changed = fuel_gauge_external_power_changed; + ret = power_supply_register(&pdev->dev, &info->bat); + if (ret) { + dev_err(&pdev->dev, "failed to register battery: %d\n", ret); + return ret; + } + + fuel_gauge_create_debugfs(info); + fuel_gauge_init_config_regs(info); + fuel_gauge_init_irq(info); + fuel_gauge_init_hw_regs(info); + schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES); + + return ret; +} + +static struct platform_device_id axp288_fg_id_table[] = { + { .name = DEV_NAME }, + {}, +}; + +static int axp288_fuel_gauge_remove(struct platform_device *pdev) +{ + struct axp288_fg_info *info = platform_get_drvdata(pdev); + int i; + + cancel_delayed_work_sync(&info->status_monitor); + power_supply_unregister(&info->bat); + fuel_gauge_remove_debugfs(info); + + for (i = 0; i < AXP288_FG_INTR_NUM; i++) + if (info->irq[i] >= 0) + free_irq(info->irq[i], info); + + return 0; +} + +static struct platform_driver axp288_fuel_gauge_driver = { + .probe = axp288_fuel_gauge_probe, + .remove = axp288_fuel_gauge_remove, + .id_table = axp288_fg_id_table, + .driver = { + .name = DEV_NAME, + }, +}; + +module_platform_driver(axp288_fuel_gauge_driver); + +MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>"); +MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); +MODULE_LICENSE("GPL"); -- 1.9.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* RE: [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver 2015-02-02 23:41 ` [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt @ 2015-02-02 20:57 ` Brandt, Todd E 2015-02-04 15:07 ` sre 0 siblings, 1 reply; 7+ messages in thread From: Brandt, Todd E @ 2015-02-02 20:57 UTC (permalink / raw) To: Todd Brandt, sre@kernel.org, dbaryshkov@gmail.com, Woodhouse, David Cc: linux-pm@vger.kernel.org, linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org, jacob.jun.pan@linux.intel.com, sameo@linux.intel.com, lee.jones@linaro.org, dwmw2@infradead.org, jic23@kernel.org Hi Dmitry and Sebastian, can we get this driver included for 3.20?=0A= =0A= Regards=0A= Todd Brandt=0A= =0A= ________________________________________=0A= From: Todd Brandt [todd.e.brandt@linux.intel.com]=0A= Sent: Monday, February 02, 2015 12:44 PM=0A= To: sre@kernel.org; dbaryshkov@gmail.com; Woodhouse, David=0A= Cc: linux-pm@vger.kernel.org; linux-iio@vger.kernel.org; linux-kernel@vger.= kernel.org; todd.e.brandt@linux.intel.com; Brandt, Todd E; jacob.jun.pan@li= nux.intel.com; sameo@linux.intel.com; lee.jones@linaro.org; dwmw2@infradead= .org; jic23@kernel.org=0A= Subject: [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver=0A= =0A= New power_supply driver at driver/power which interfaces with the=0A= axp20x mfd driver as a cell. Provides battery info, monitors for=0A= changes, and generates alerts on temperature and capacity issues=0A= =0A= Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>=0A= Acked-by: Jacob Pan <jacob.jun.pan@linux.intel.com>=0A= ---=0A= drivers/power/Kconfig | 9 +=0A= drivers/power/Makefile | 1 +=0A= drivers/power/axp288_fuel_gauge.c | 1151 +++++++++++++++++++++++++++++++++= ++++=0A= 3 files changed, 1161 insertions(+)=0A= create mode 100644 drivers/power/axp288_fuel_gauge.c=0A= =0A= v1: 01/07/14=0A= v2: 01/22/14=0A= - reformatted the commit text=0A= - added IIO as driver dependency=0A= - moved debug info to debugfs in file fuelgauge=0A= - added HEALTH_COLD value to health calculation=0A= - added better cleanup to remove call=0A= cancelling any delayed work calls=0A= unregistration of the power_supply=0A= v3: 01/28/14=0A= - moved debugs create and init config regs after error case=0A= - freed the virqs in the remove function=0A= - remove .owner field as it is populated automatically=0A= =0A= diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig=0A= index 0108c2a..a1166e0 100644=0A= --- a/drivers/power/Kconfig=0A= +++ b/drivers/power/Kconfig=0A= @@ -192,6 +192,15 @@ config BATTERY_DA9052=0A= Say Y here to enable support for batteries charger integrated int= o=0A= DA9052 PMIC.=0A= =0A= +config AXP288_FUEL_GAUGE=0A= + tristate "X-Powers AXP288 Fuel Gauge"=0A= + depends on MFD_AXP20X && IIO=0A= + help=0A= + Say yes here to have support for X-Power power management IC (PMI= C)=0A= + Fuel Gauge. The device provides battery statistics and status=0A= + monitoring as well as alerts for battery over/under voltage and= =0A= + over/under temperature.=0A= +=0A= config BATTERY_MAX17040=0A= tristate "Maxim MAX17040 Fuel Gauge"=0A= depends on I2C=0A= diff --git a/drivers/power/Makefile b/drivers/power/Makefile=0A= index dfa8942..eb76b23 100644=0A= --- a/drivers/power/Makefile=0A= +++ b/drivers/power/Makefile=0A= @@ -59,3 +59,4 @@ obj-$(CONFIG_POWER_AVS) +=3D avs/=0A= obj-$(CONFIG_CHARGER_SMB347) +=3D smb347-charger.o=0A= obj-$(CONFIG_CHARGER_TPS65090) +=3D tps65090-charger.o=0A= obj-$(CONFIG_POWER_RESET) +=3D reset/=0A= +obj-$(CONFIG_AXP288_FUEL_GAUGE) +=3D axp288_fuel_gauge.o=0A= diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_= gauge.c=0A= new file mode 100644=0A= index 0000000..c86e709=0A= --- /dev/null=0A= +++ b/drivers/power/axp288_fuel_gauge.c=0A= @@ -0,0 +1,1151 @@=0A= +/*=0A= + * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver=0A= + *=0A= + * Copyright (C) 2014 Intel Corporation=0A= + *=0A= + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~= ~~~=0A= + *=0A= + * This program is free software; you can redistribute it and/or modify=0A= + * it under the terms of the GNU General Public License as published by=0A= + * the Free Software Foundation; version 2 of the License.=0A= + *=0A= + * This program is distributed in the hope that it will be useful, but=0A= + * WITHOUT ANY WARRANTY; without even the implied warranty of=0A= + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU= =0A= + * General Public License for more details.=0A= + *=0A= + */=0A= +=0A= +#include <linux/module.h>=0A= +#include <linux/kernel.h>=0A= +#include <linux/device.h>=0A= +#include <linux/regmap.h>=0A= +#include <linux/jiffies.h>=0A= +#include <linux/interrupt.h>=0A= +#include <linux/device.h>=0A= +#include <linux/workqueue.h>=0A= +#include <linux/mfd/axp20x.h>=0A= +#include <linux/platform_device.h>=0A= +#include <linux/power_supply.h>=0A= +#include <linux/iio/consumer.h>=0A= +#include <linux/debugfs.h>=0A= +#include <linux/seq_file.h>=0A= +=0A= +#define CHRG_STAT_BAT_SAFE_MODE (1 << 3)=0A= +#define CHRG_STAT_BAT_VALID (1 << 4)=0A= +#define CHRG_STAT_BAT_PRESENT (1 << 5)=0A= +#define CHRG_STAT_CHARGING (1 << 6)=0A= +#define CHRG_STAT_PMIC_OTP (1 << 7)=0A= +=0A= +#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */=0A= +#define CHRG_CCCV_CC_BIT_POS 0=0A= +#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */=0A= +#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */=0A= +#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */= =0A= +#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */=0A= +#define CHRG_CCCV_CV_BIT_POS 5=0A= +#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */=0A= +#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */=0A= +#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */=0A= +#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */=0A= +#define CHRG_CCCV_CHG_EN (1 << 7)=0A= +=0A= +#define CV_4100 4100 /* = 4100mV */=0A= +#define CV_4150 4150 /* = 4150mV */=0A= +#define CV_4200 4200 /* = 4200mV */=0A= +#define CV_4350 4350 /* = 4350mV */=0A= +=0A= +#define TEMP_IRQ_CFG_QWBTU (1 << 0)=0A= +#define TEMP_IRQ_CFG_WBTU (1 << 1)=0A= +#define TEMP_IRQ_CFG_QWBTO (1 << 2)=0A= +#define TEMP_IRQ_CFG_WBTO (1 << 3)=0A= +#define TEMP_IRQ_CFG_MASK 0xf=0A= +=0A= +#define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0)=0A= +#define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1)=0A= +#define FG_IRQ_CFG_LOWBATT_MASK 0x3=0A= +#define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0)=0A= +#define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1)=0A= +=0A= +#define FG_CNTL_OCV_ADJ_STAT (1 << 2)=0A= +#define FG_CNTL_OCV_ADJ_EN (1 << 3)=0A= +#define FG_CNTL_CAP_ADJ_STAT (1 << 4)=0A= +#define FG_CNTL_CAP_ADJ_EN (1 << 5)=0A= +#define FG_CNTL_CC_EN (1 << 6)=0A= +#define FG_CNTL_GAUGE_EN (1 << 7)=0A= +=0A= +#define FG_REP_CAP_VALID (1 << 7)=0A= +#define FG_REP_CAP_VAL_MASK 0x7F=0A= +=0A= +#define FG_DES_CAP1_VALID (1 << 7)=0A= +#define FG_DES_CAP1_VAL_MASK 0x7F=0A= +#define FG_DES_CAP0_VAL_MASK 0xFF=0A= +#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */=0A= +=0A= +#define FG_CC_MTR1_VALID (1 << 7)=0A= +#define FG_CC_MTR1_VAL_MASK 0x7F=0A= +#define FG_CC_MTR0_VAL_MASK 0xFF=0A= +#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */=0A= +=0A= +#define FG_OCV_CAP_VALID (1 << 7)=0A= +#define FG_OCV_CAP_VAL_MASK 0x7F=0A= +#define FG_CC_CAP_VALID (1 << 7)=0A= +#define FG_CC_CAP_VAL_MASK 0x7F=0A= +=0A= +#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */=0A= +#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */=0A= +#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */=0A= +#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */=0A= +#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */=0A= +#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */=0A= +=0A= +#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */=0A= +#define NR_RETRY_CNT 3=0A= +#define DEV_NAME "axp288_fuel_gauge"=0A= +=0A= +/* 1.1mV per LSB expressed in uV */=0A= +#define VOLTAGE_FROM_ADC(a) ((a * 11) / 10)=0A= +/* properties converted to tenths of degrees, uV, uA, uW */=0A= +#define PROP_TEMP(a) ((a) * 10)=0A= +#define UNPROP_TEMP(a) ((a) / 10)=0A= +#define PROP_VOLT(a) ((a) * 1000)=0A= +#define PROP_CURR(a) ((a) * 1000)=0A= +=0A= +#define AXP288_FG_INTR_NUM 6=0A= +enum {=0A= + QWBTU_IRQ =3D 0,=0A= + WBTU_IRQ,=0A= + QWBTO_IRQ,=0A= + WBTO_IRQ,=0A= + WL2_IRQ,=0A= + WL1_IRQ,=0A= +};=0A= +=0A= +struct axp288_fg_info {=0A= + struct platform_device *pdev;=0A= + struct axp20x_fg_pdata *pdata;=0A= + struct regmap *regmap;=0A= + struct regmap_irq_chip_data *regmap_irqc;=0A= + int irq[AXP288_FG_INTR_NUM];=0A= + struct power_supply bat;=0A= + struct mutex lock;=0A= + int status;=0A= + struct delayed_work status_monitor;=0A= + struct dentry *debug_file;=0A= +};=0A= +=0A= +static enum power_supply_property fuel_gauge_props[] =3D {=0A= + POWER_SUPPLY_PROP_STATUS,=0A= + POWER_SUPPLY_PROP_PRESENT,=0A= + POWER_SUPPLY_PROP_HEALTH,=0A= + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,=0A= + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,=0A= + POWER_SUPPLY_PROP_VOLTAGE_NOW,=0A= + POWER_SUPPLY_PROP_VOLTAGE_OCV,=0A= + POWER_SUPPLY_PROP_CURRENT_NOW,=0A= + POWER_SUPPLY_PROP_CAPACITY,=0A= + POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,=0A= + POWER_SUPPLY_PROP_TEMP,=0A= + POWER_SUPPLY_PROP_TEMP_MAX,=0A= + POWER_SUPPLY_PROP_TEMP_MIN,=0A= + POWER_SUPPLY_PROP_TEMP_ALERT_MIN,=0A= + POWER_SUPPLY_PROP_TEMP_ALERT_MAX,=0A= + POWER_SUPPLY_PROP_TECHNOLOGY,=0A= + POWER_SUPPLY_PROP_CHARGE_FULL,=0A= + POWER_SUPPLY_PROP_CHARGE_NOW,=0A= + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,=0A= + POWER_SUPPLY_PROP_MODEL_NAME,=0A= +};=0A= +=0A= +static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)=0A= +{=0A= + int ret, i;=0A= + unsigned int val;=0A= +=0A= + for (i =3D 0; i < NR_RETRY_CNT; i++) {=0A= + ret =3D regmap_read(info->regmap, reg, &val);=0A= + if (ret =3D=3D -EBUSY)=0A= + continue;=0A= + else=0A= + break;=0A= + }=0A= +=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);= =0A= +=0A= + return val;=0A= +}=0A= +=0A= +static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 = val)=0A= +{=0A= + int ret;=0A= +=0A= + ret =3D regmap_write(info->regmap, reg, (unsigned int)val);=0A= +=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret)= ;=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static int pmic_read_adc_val(const char *name, int *raw_val,=0A= + struct axp288_fg_info *info)=0A= +{=0A= + int ret, val =3D 0;=0A= + struct iio_channel *indio_chan;=0A= +=0A= + indio_chan =3D iio_channel_get(NULL, name);=0A= + if (IS_ERR_OR_NULL(indio_chan)) {=0A= + ret =3D PTR_ERR(indio_chan);=0A= + goto exit;=0A= + }=0A= + ret =3D iio_read_channel_raw(indio_chan, &val);=0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev,=0A= + "IIO channel read error: %x, %x\n", ret, val);=0A= + goto err_exit;=0A= + }=0A= +=0A= + dev_dbg(&info->pdev->dev, "adc raw val=3D%x\n", val);=0A= + *raw_val =3D val;=0A= +=0A= +err_exit:=0A= + iio_channel_release(indio_chan);=0A= +exit:=0A= + return ret;=0A= +}=0A= +=0A= +#ifdef CONFIG_DEBUG_FS=0A= +static int fuel_gauge_debug_show(struct seq_file *s, void *data)=0A= +{=0A= + struct axp288_fg_info *info =3D s->private;=0A= + int raw_val, ret;=0A= +=0A= + seq_printf(s, " PWR_STATUS[%02x] : %02x\n",=0A= + AXP20X_PWR_INPUT_STATUS,=0A= + fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));=0A= + seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",=0A= + AXP20X_PWR_OP_MODE,=0A= + fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));=0A= + seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",=0A= + AXP20X_CHRG_CTRL1,=0A= + fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));=0A= + seq_printf(s, " VLTF[%02x] : %02x\n",=0A= + AXP20X_V_LTF_DISCHRG,=0A= + fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));=0A= + seq_printf(s, " VHTF[%02x] : %02x\n",=0A= + AXP20X_V_HTF_DISCHRG,=0A= + fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));=0A= + seq_printf(s, " CC_CTRL[%02x] : %02x\n",=0A= + AXP20X_CC_CTRL,=0A= + fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));=0A= + seq_printf(s, "BATTERY CAP[%02x] : %02x\n",=0A= + AXP20X_FG_RES,=0A= + fuel_gauge_reg_readb(info, AXP20X_FG_RES));=0A= + seq_printf(s, " FG_RDC1[%02x] : %02x\n",=0A= + AXP288_FG_RDC1_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));=0A= + seq_printf(s, " FG_RDC0[%02x] : %02x\n",=0A= + AXP288_FG_RDC0_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));=0A= + seq_printf(s, " FG_OCVH[%02x] : %02x\n",=0A= + AXP288_FG_OCVH_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG));=0A= + seq_printf(s, " FG_OCVL[%02x] : %02x\n",=0A= + AXP288_FG_OCVL_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG));=0A= + seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n",=0A= + AXP288_FG_DES_CAP1_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG));=0A= + seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n",=0A= + AXP288_FG_DES_CAP0_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG));=0A= + seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n",=0A= + AXP288_FG_CC_MTR1_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG));=0A= + seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n",=0A= + AXP288_FG_CC_MTR0_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG));=0A= + seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",=0A= + AXP288_FG_OCV_CAP_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));=0A= + seq_printf(s, " FG_CC_CAP[%02x] : %02x\n",=0A= + AXP288_FG_CC_CAP_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));=0A= + seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",=0A= + AXP288_FG_LOW_CAP_REG,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));=0A= + seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",=0A= + AXP288_FG_TUNE0,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));=0A= + seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",=0A= + AXP288_FG_TUNE1,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));=0A= + seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",=0A= + AXP288_FG_TUNE2,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));=0A= + seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",=0A= + AXP288_FG_TUNE3,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));=0A= + seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",=0A= + AXP288_FG_TUNE4,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));=0A= + seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",=0A= + AXP288_FG_TUNE5,=0A= + fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));=0A= +=0A= + ret =3D pmic_read_adc_val("axp288-batt-temp", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-batttemp : %d\n", raw_val);=0A= + ret =3D pmic_read_adc_val("axp288-pmic-temp", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-pmictemp : %d\n", raw_val);=0A= + ret =3D pmic_read_adc_val("axp288-system-temp", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-systtemp : %d\n", raw_val);=0A= + ret =3D pmic_read_adc_val("axp288-chrg-curr", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);=0A= + ret =3D pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);=0A= + ret =3D pmic_read_adc_val("axp288-batt-volt", &raw_val, info);=0A= + if (ret >=3D 0)=0A= + seq_printf(s, "axp288-battvolt : %d\n", raw_val);=0A= +=0A= + return 0;=0A= +}=0A= +=0A= +static int debug_open(struct inode *inode, struct file *file)=0A= +{=0A= + return single_open(file, fuel_gauge_debug_show, inode->i_private);= =0A= +}=0A= +=0A= +static const struct file_operations fg_debug_fops =3D {=0A= + .open =3D debug_open,=0A= + .read =3D seq_read,=0A= + .llseek =3D seq_lseek,=0A= + .release =3D single_release,=0A= +};=0A= +=0A= +static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)=0A= +{=0A= + info->debug_file =3D debugfs_create_file("fuelgauge", 0666, NULL,= =0A= + info, &fg_debug_fops);=0A= +}=0A= +=0A= +static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)=0A= +{=0A= + debugfs_remove(info->debug_file);=0A= +}=0A= +#else=0A= +static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)= =0A= +{=0A= +}=0A= +static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)= =0A= +{=0A= +}=0A= +#endif=0A= +=0A= +static void fuel_gauge_get_status(struct axp288_fg_info *info)=0A= +{=0A= + int pwr_stat, ret;=0A= + int charge, discharge;=0A= +=0A= + pwr_stat =3D fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);= =0A= + if (pwr_stat < 0) {=0A= + dev_err(&info->pdev->dev,=0A= + "PWR STAT read failed:%d\n", pwr_stat);=0A= + return;=0A= + }=0A= + ret =3D pmic_read_adc_val("axp288-chrg-curr", &charge, info);=0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev,=0A= + "ADC charge current read failed:%d\n", ret);=0A= + return;=0A= + }=0A= + ret =3D pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);= =0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev,=0A= + "ADC discharge current read failed:%d\n", ret);=0A= + return;=0A= + }=0A= +=0A= + if (charge > 0)=0A= + info->status =3D POWER_SUPPLY_STATUS_CHARGING;=0A= + else if (discharge > 0)=0A= + info->status =3D POWER_SUPPLY_STATUS_DISCHARGING;=0A= + else {=0A= + if (pwr_stat & CHRG_STAT_BAT_PRESENT)=0A= + info->status =3D POWER_SUPPLY_STATUS_FULL;=0A= + else=0A= + info->status =3D POWER_SUPPLY_STATUS_NOT_CHARGING;= =0A= + }=0A= +}=0A= +=0A= +static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)= =0A= +{=0A= + int ret =3D 0, raw_val;=0A= +=0A= + ret =3D pmic_read_adc_val("axp288-batt-volt", &raw_val, info);=0A= + if (ret < 0)=0A= + goto vbatt_read_fail;=0A= +=0A= + *vbatt =3D VOLTAGE_FROM_ADC(raw_val);=0A= +vbatt_read_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)= =0A= +{=0A= + int ret, value =3D 0;=0A= + int charge, discharge;=0A= +=0A= + ret =3D pmic_read_adc_val("axp288-chrg-curr", &charge, info);=0A= + if (ret < 0)=0A= + goto current_read_fail;=0A= + ret =3D pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);= =0A= + if (ret < 0)=0A= + goto current_read_fail;=0A= +=0A= + if (charge > 0)=0A= + value =3D charge;=0A= + else if (discharge > 0)=0A= + value =3D -1 * discharge;=0A= +=0A= + *cur =3D value;=0A= +current_read_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int temp_to_adc(struct axp288_fg_info *info, int tval)=0A= +{=0A= + int rntc =3D 0, i, ret, adc_val;=0A= + int rmin, rmax, tmin, tmax;=0A= + int tcsz =3D info->pdata->tcsz;=0A= +=0A= + /* get the Rntc resitance value for this temp */=0A= + if (tval > info->pdata->thermistor_curve[0][1]) {=0A= + rntc =3D info->pdata->thermistor_curve[0][0];=0A= + } else if (tval <=3D info->pdata->thermistor_curve[tcsz-1][1]) {=0A= + rntc =3D info->pdata->thermistor_curve[tcsz-1][0];=0A= + } else {=0A= + for (i =3D 1; i < tcsz; i++) {=0A= + if (tval > info->pdata->thermistor_curve[i][1]) {= =0A= + rmin =3D info->pdata->thermistor_curve[i-1]= [0];=0A= + rmax =3D info->pdata->thermistor_curve[i][0= ];=0A= + tmin =3D info->pdata->thermistor_curve[i-1]= [1];=0A= + tmax =3D info->pdata->thermistor_curve[i][1= ];=0A= + rntc =3D rmin + ((rmax - rmin) *=0A= + (tval - tmin) / (tmax - tmin));=0A= + break;=0A= + }=0A= + }=0A= + }=0A= +=0A= + /* we need the current to calculate the proper adc voltage */=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);=0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret= );=0A= + ret =3D 0x30;=0A= + }=0A= +=0A= + /*=0A= + * temperature is proportional to NTS thermistor resistance=0A= + * ADC_RATE[5-4] determines current, 00=3D20uA,01=3D40uA,10=3D60uA,= 11=3D80uA=0A= + * [12-bit ADC VAL] =3D R_NTC(=D9) * current / 800=0A= + */=0A= + adc_val =3D rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800;=0A= +=0A= + return adc_val;=0A= +}=0A= +=0A= +static int adc_to_temp(struct axp288_fg_info *info, int adc_val)=0A= +{=0A= + int ret, r, i, tval =3D 0;=0A= + int rmin, rmax, tmin, tmax;=0A= + int tcsz =3D info->pdata->tcsz;=0A= +=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);=0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret= );=0A= + ret =3D 0x30;=0A= + }=0A= +=0A= + /*=0A= + * temperature is proportional to NTS thermistor resistance=0A= + * ADC_RATE[5-4] determines current, 00=3D20uA,01=3D40uA,10=3D60uA,= 11=3D80uA=0A= + * R_NTC(=D9) =3D [12-bit ADC VAL] * 800 / current=0A= + */=0A= + r =3D adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3)));=0A= +=0A= + if (r < info->pdata->thermistor_curve[0][0]) {=0A= + tval =3D info->pdata->thermistor_curve[0][1];=0A= + } else if (r >=3D info->pdata->thermistor_curve[tcsz-1][0]) {=0A= + tval =3D info->pdata->thermistor_curve[tcsz-1][1];=0A= + } else {=0A= + for (i =3D 1; i < tcsz; i++) {=0A= + if (r < info->pdata->thermistor_curve[i][0]) {=0A= + rmin =3D info->pdata->thermistor_curve[i-1]= [0];=0A= + rmax =3D info->pdata->thermistor_curve[i][0= ];=0A= + tmin =3D info->pdata->thermistor_curve[i-1]= [1];=0A= + tmax =3D info->pdata->thermistor_curve[i][1= ];=0A= + tval =3D tmin + ((tmax - tmin) *=0A= + (r - rmin) / (rmax - rmin));=0A= + break;=0A= + }=0A= + }=0A= + }=0A= +=0A= + return tval;=0A= +}=0A= +=0A= +static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp)= =0A= +{=0A= + int ret, raw_val =3D 0;=0A= +=0A= + ret =3D pmic_read_adc_val("axp288-batt-temp", &raw_val, info);=0A= + if (ret < 0)=0A= + goto temp_read_fail;=0A= +=0A= + *btemp =3D adc_to_temp(info, raw_val);=0A= +=0A= +temp_read_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)=0A= +{=0A= + int ret, value;=0A= +=0A= + /* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG);=0A= + if (ret < 0)=0A= + goto vocv_read_fail;=0A= + value =3D ret << 4;=0A= +=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG);=0A= + if (ret < 0)=0A= + goto vocv_read_fail;=0A= + value |=3D (ret & 0xf);=0A= +=0A= + *vocv =3D VOLTAGE_FROM_ADC(value);=0A= +vocv_read_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_battery_health(struct axp288_fg_info *info)=0A= +{=0A= + int temp, vocv;=0A= + int ret, health =3D POWER_SUPPLY_HEALTH_UNKNOWN;=0A= +=0A= + ret =3D fuel_gauge_get_btemp(info, &temp);=0A= + if (ret < 0)=0A= + goto health_read_fail;=0A= +=0A= + ret =3D fuel_gauge_get_vocv(info, &vocv);=0A= + if (ret < 0)=0A= + goto health_read_fail;=0A= +=0A= + if (vocv > info->pdata->max_volt)=0A= + health =3D POWER_SUPPLY_HEALTH_OVERVOLTAGE;=0A= + else if (temp > info->pdata->max_temp)=0A= + health =3D POWER_SUPPLY_HEALTH_OVERHEAT;=0A= + else if (temp < info->pdata->min_temp)=0A= + health =3D POWER_SUPPLY_HEALTH_COLD;=0A= + else if (vocv < info->pdata->min_volt)=0A= + health =3D POWER_SUPPLY_HEALTH_DEAD;=0A= + else=0A= + health =3D POWER_SUPPLY_HEALTH_GOOD;=0A= +=0A= +health_read_fail:=0A= + return health;=0A= +}=0A= +=0A= +static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info)=0A= +{=0A= + int ret, adc_val;=0A= +=0A= + /* program temperature threshold as 1/16 ADC value */=0A= + adc_val =3D temp_to_adc(info, info->pdata->max_temp);=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >= > 4);=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info)=0A= +{=0A= + int ret, adc_val;=0A= +=0A= + /* program temperature threshold as 1/16 ADC value */=0A= + adc_val =3D temp_to_adc(info, info->pdata->min_temp);=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >= > 4);=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_get_property(struct power_supply *ps,=0A= + enum power_supply_property prop,=0A= + union power_supply_propval *val)=0A= +{=0A= + struct axp288_fg_info *info =3D container_of(ps,=0A= + struct axp288_fg_info, bat);=0A= + int ret =3D 0, value;=0A= +=0A= + mutex_lock(&info->lock);=0A= + switch (prop) {=0A= + case POWER_SUPPLY_PROP_STATUS:=0A= + fuel_gauge_get_status(info);=0A= + val->intval =3D info->status;=0A= + break;=0A= + case POWER_SUPPLY_PROP_HEALTH:=0A= + val->intval =3D fuel_gauge_battery_health(info);=0A= + break;=0A= + case POWER_SUPPLY_PROP_VOLTAGE_NOW:=0A= + ret =3D fuel_gauge_get_vbatt(info, &value);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + val->intval =3D PROP_VOLT(value);=0A= + break;=0A= + case POWER_SUPPLY_PROP_VOLTAGE_OCV:=0A= + ret =3D fuel_gauge_get_vocv(info, &value);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + val->intval =3D PROP_VOLT(value);=0A= + break;=0A= + case POWER_SUPPLY_PROP_CURRENT_NOW:=0A= + ret =3D fuel_gauge_get_current(info, &value);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + val->intval =3D PROP_CURR(value);=0A= + break;=0A= + case POWER_SUPPLY_PROP_PRESENT:=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= +=0A= + if (ret & CHRG_STAT_BAT_PRESENT)=0A= + val->intval =3D 1;=0A= + else=0A= + val->intval =3D 0;=0A= + break;=0A= + case POWER_SUPPLY_PROP_CAPACITY:=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_FG_RES);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= +=0A= + if (!(ret & FG_REP_CAP_VALID))=0A= + dev_err(&info->pdev->dev,=0A= + "capacity measurement not valid\n");=0A= + val->intval =3D (ret & FG_REP_CAP_VAL_MASK);=0A= + break;=0A= + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);= =0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + val->intval =3D (ret & 0x0f);=0A= + break;=0A= + case POWER_SUPPLY_PROP_TEMP:=0A= + ret =3D fuel_gauge_get_btemp(info, &value);=0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + val->intval =3D PROP_TEMP(value);=0A= + break;=0A= + case POWER_SUPPLY_PROP_TEMP_MAX:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:=0A= + val->intval =3D PROP_TEMP(info->pdata->max_temp);=0A= + break;=0A= + case POWER_SUPPLY_PROP_TEMP_MIN:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:=0A= + val->intval =3D PROP_TEMP(info->pdata->min_temp);=0A= + break;=0A= + case POWER_SUPPLY_PROP_TECHNOLOGY:=0A= + val->intval =3D POWER_SUPPLY_TECHNOLOGY_LION;=0A= + break;=0A= + case POWER_SUPPLY_PROP_CHARGE_NOW:=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG);= =0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= +=0A= + value =3D (ret & FG_CC_MTR1_VAL_MASK) << 8;=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG);= =0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + value |=3D (ret & FG_CC_MTR0_VAL_MASK);=0A= + val->intval =3D value * FG_DES_CAP_RES_LSB;=0A= + break;=0A= + case POWER_SUPPLY_PROP_CHARGE_FULL:=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);= =0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= +=0A= + value =3D (ret & FG_DES_CAP1_VAL_MASK) << 8;=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG);= =0A= + if (ret < 0)=0A= + goto fuel_gauge_read_err;=0A= + value |=3D (ret & FG_DES_CAP0_VAL_MASK);=0A= + val->intval =3D value * FG_DES_CAP_RES_LSB;=0A= + break;=0A= + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:=0A= + val->intval =3D PROP_CURR(info->pdata->design_cap);=0A= + break;=0A= + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:=0A= + val->intval =3D PROP_VOLT(info->pdata->max_volt);=0A= + break;=0A= + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:=0A= + val->intval =3D PROP_VOLT(info->pdata->min_volt);=0A= + break;=0A= + case POWER_SUPPLY_PROP_MODEL_NAME:=0A= + val->strval =3D info->pdata->battid;=0A= + break;=0A= + default:=0A= + mutex_unlock(&info->lock);=0A= + return -EINVAL;=0A= + }=0A= +=0A= + mutex_unlock(&info->lock);=0A= + return 0;=0A= +=0A= +fuel_gauge_read_err:=0A= + mutex_unlock(&info->lock);=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_set_property(struct power_supply *ps,=0A= + enum power_supply_property prop,=0A= + const union power_supply_propval *val)=0A= +{=0A= + struct axp288_fg_info *info =3D container_of(ps,=0A= + struct axp288_fg_info, bat);=0A= + int ret =3D 0;=0A= +=0A= + mutex_lock(&info->lock);=0A= + switch (prop) {=0A= + case POWER_SUPPLY_PROP_STATUS:=0A= + info->status =3D val->intval;=0A= + break;=0A= + case POWER_SUPPLY_PROP_TEMP_MIN:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:=0A= + if ((val->intval < PD_DEF_MIN_TEMP) ||=0A= + (val->intval > PD_DEF_MAX_TEMP)) {=0A= + ret =3D -EINVAL;=0A= + break;=0A= + }=0A= + info->pdata->min_temp =3D UNPROP_TEMP(val->intval);=0A= + ret =3D fuel_gauge_set_low_btemp_alert(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev,=0A= + "temp alert min set fail:%d\n", ret);=0A= + break;=0A= + case POWER_SUPPLY_PROP_TEMP_MAX:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:=0A= + if ((val->intval < PD_DEF_MIN_TEMP) ||=0A= + (val->intval > PD_DEF_MAX_TEMP)) {=0A= + ret =3D -EINVAL;=0A= + break;=0A= + }=0A= + info->pdata->max_temp =3D UNPROP_TEMP(val->intval);=0A= + ret =3D fuel_gauge_set_high_btemp_alert(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev,=0A= + "temp alert max set fail:%d\n", ret);=0A= + break;=0A= + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:=0A= + if ((val->intval < 0) || (val->intval > 15)) {=0A= + ret =3D -EINVAL;=0A= + break;=0A= + }=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);= =0A= + if (ret < 0)=0A= + break;=0A= + ret &=3D 0xf0;=0A= + ret |=3D (val->intval & 0xf);=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, = ret);=0A= + break;=0A= + default:=0A= + ret =3D -EINVAL;=0A= + break;=0A= + }=0A= +=0A= + mutex_unlock(&info->lock);=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_property_is_writeable(struct power_supply *psy,=0A= + enum power_supply_property psp)=0A= +{=0A= + int ret;=0A= +=0A= + switch (psp) {=0A= + case POWER_SUPPLY_PROP_STATUS:=0A= + case POWER_SUPPLY_PROP_TEMP_MIN:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:=0A= + case POWER_SUPPLY_PROP_TEMP_MAX:=0A= + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:=0A= + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:=0A= + ret =3D 1;=0A= + break;=0A= + default:=0A= + ret =3D 0;=0A= + }=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static void fuel_gauge_status_monitor(struct work_struct *work)=0A= +{=0A= + struct axp288_fg_info *info =3D container_of(work,=0A= + struct axp288_fg_info, status_monitor.work);=0A= +=0A= + fuel_gauge_get_status(info);=0A= + power_supply_changed(&info->bat);=0A= + schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFI= ES);=0A= +}=0A= +=0A= +static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)=0A= +{=0A= + struct axp288_fg_info *info =3D dev;=0A= + int i;=0A= +=0A= + for (i =3D 0; i < AXP288_FG_INTR_NUM; i++) {=0A= + if (info->irq[i] =3D=3D irq)=0A= + break;=0A= + }=0A= +=0A= + if (i >=3D AXP288_FG_INTR_NUM) {=0A= + dev_warn(&info->pdev->dev, "spurious interrupt!!\n");=0A= + return IRQ_NONE;=0A= + }=0A= +=0A= + switch (i) {=0A= + case QWBTU_IRQ:=0A= + dev_info(&info->pdev->dev,=0A= + "Quit Battery under temperature in work mode IRQ (Q= WBTU)\n");=0A= + break;=0A= + case WBTU_IRQ:=0A= + dev_info(&info->pdev->dev,=0A= + "Battery under temperature in work mode IRQ (WBTU)\= n");=0A= + break;=0A= + case QWBTO_IRQ:=0A= + dev_info(&info->pdev->dev,=0A= + "Quit Battery over temperature in work mode IRQ (QW= BTO)\n");=0A= + break;=0A= + case WBTO_IRQ:=0A= + dev_info(&info->pdev->dev,=0A= + "Battery over temperature in work mode IRQ (WBTO)\n= ");=0A= + break;=0A= + case WL2_IRQ:=0A= + dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");= =0A= + break;=0A= + case WL1_IRQ:=0A= + dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");= =0A= + break;=0A= + default:=0A= + dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");=0A= + }=0A= +=0A= + power_supply_changed(&info->bat);=0A= + return IRQ_HANDLED;=0A= +}=0A= +=0A= +static void fuel_gauge_external_power_changed(struct power_supply *psy)=0A= +{=0A= + struct axp288_fg_info *info =3D container_of(psy,=0A= + struct axp288_fg_info, bat);=0A= +=0A= + power_supply_changed(&info->bat);=0A= +}=0A= +=0A= +static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info)= =0A= +{=0A= + int ret;=0A= + u8 reg_val;=0A= +=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_FG_RES);=0A= + if (ret < 0) {=0A= + dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret= );=0A= + return ret;=0A= + }=0A= + ret =3D (ret & FG_REP_CAP_VAL_MASK);=0A= +=0A= + if (ret > FG_LOW_CAP_WARN_THR)=0A= + reg_val =3D FG_LOW_CAP_WARN_THR;=0A= + else if (ret > FG_LOW_CAP_CRIT_THR)=0A= + reg_val =3D FG_LOW_CAP_CRIT_THR;=0A= + else=0A= + reg_val =3D FG_LOW_CAP_SHDN_THR;=0A= +=0A= + reg_val |=3D FG_LOW_CAP_THR1_VAL;=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val)= ;=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, re= t);=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info)=0A= +{=0A= + int ret;=0A= + u8 val;=0A= +=0A= + ret =3D fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);=0A= + if (ret < 0)=0A= + goto fg_prog_ocv_fail;=0A= + else=0A= + val =3D (ret & ~CHRG_CCCV_CV_MASK);=0A= +=0A= + switch (info->pdata->max_volt) {=0A= + case CV_4100:=0A= + val |=3D (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS);=0A= + break;=0A= + case CV_4150:=0A= + val |=3D (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS);=0A= + break;=0A= + case CV_4200:=0A= + val |=3D (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);=0A= + break;=0A= + case CV_4350:=0A= + val |=3D (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS);=0A= + break;=0A= + default:=0A= + val |=3D (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);=0A= + break;=0A= + }=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val);=0A= +fg_prog_ocv_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_program_design_cap(struct axp288_fg_info *info)=0A= +{=0A= + int ret;=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info,=0A= + AXP288_FG_DES_CAP1_REG, info->pdata->cap1);=0A= + if (ret < 0)=0A= + goto fg_prog_descap_fail;=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info,=0A= + AXP288_FG_DES_CAP0_REG, info->pdata->cap0);=0A= +=0A= +fg_prog_descap_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info)=0A= +{=0A= + int ret =3D 0, i;=0A= +=0A= + for (i =3D 0; i < OCV_CURVE_SIZE; i++) {=0A= + ret =3D fuel_gauge_reg_writeb(info,=0A= + AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve= [i]);=0A= + if (ret < 0)=0A= + goto fg_prog_ocv_fail;=0A= + }=0A= +=0A= +fg_prog_ocv_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info)=0A= +{=0A= + int ret;=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info,=0A= + AXP288_FG_RDC1_REG, info->pdata->rdc1);=0A= + if (ret < 0)=0A= + goto fg_prog_ocv_fail;=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info,=0A= + AXP288_FG_RDC0_REG, info->pdata->rdc0);=0A= +=0A= +fg_prog_ocv_fail:=0A= + return ret;=0A= +}=0A= +=0A= +static void fuel_gauge_init_config_regs(struct axp288_fg_info *info)=0A= +{=0A= + int ret;=0A= +=0A= + /*=0A= + * check if the config data is already=0A= + * programmed and if so just return.=0A= + */=0A= +=0A= + ret =3D fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);=0A= + if (ret < 0) {=0A= + dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n");=0A= + } else if (!(ret & FG_DES_CAP1_VALID)) {=0A= + dev_info(&info->pdev->dev, "FG data needs to be initialized= \n");=0A= + } else {=0A= + dev_info(&info->pdev->dev, "FG data is already initialized\= n");=0A= + return;=0A= + }=0A= +=0A= + ret =3D fuel_gauge_program_vbatt_full(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret);= =0A= +=0A= + ret =3D fuel_gauge_program_design_cap(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret);= =0A= +=0A= + ret =3D fuel_gauge_program_rdc_vals(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret);=0A= +=0A= + ret =3D fuel_gauge_program_ocv_curve(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret);= =0A= +=0A= + ret =3D fuel_gauge_set_lowbatt_thresholds(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret)= ;=0A= +=0A= + ret =3D fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret);= =0A= +}=0A= +=0A= +static void fuel_gauge_init_irq(struct axp288_fg_info *info)=0A= +{=0A= + int ret, i, pirq;=0A= +=0A= + for (i =3D 0; i < AXP288_FG_INTR_NUM; i++) {=0A= + pirq =3D platform_get_irq(info->pdev, i);=0A= + info->irq[i] =3D regmap_irq_get_virq(info->regmap_irqc, pir= q);=0A= + if (info->irq[i] < 0) {=0A= + dev_warn(&info->pdev->dev,=0A= + "regmap_irq get virq failed for IRQ %d: %d\= n",=0A= + pirq, info->irq[i]);=0A= + info->irq[i] =3D -1;=0A= + goto intr_failed;=0A= + }=0A= + ret =3D request_threaded_irq(info->irq[i],=0A= + NULL, fuel_gauge_thread_handler,=0A= + IRQF_ONESHOT, DEV_NAME, info);=0A= + if (ret) {=0A= + dev_warn(&info->pdev->dev,=0A= + "request irq failed for IRQ %d: %d\n",=0A= + pirq, info->irq[i]);=0A= + info->irq[i] =3D -1;=0A= + goto intr_failed;=0A= + } else {=0A= + dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n"= ,=0A= + pirq, info->irq[i]);=0A= + }=0A= + }=0A= + return;=0A= +=0A= +intr_failed:=0A= + for (; i > 0; i--) {=0A= + free_irq(info->irq[i - 1], info);=0A= + info->irq[i - 1] =3D -1;=0A= + }=0A= +}=0A= +=0A= +static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info)=0A= +{=0A= + int ret;=0A= + unsigned int val;=0A= +=0A= + ret =3D fuel_gauge_set_high_btemp_alert(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", r= et);=0A= +=0A= + ret =3D fuel_gauge_set_low_btemp_alert(info);=0A= + if (ret < 0)=0A= + dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", re= t);=0A= +=0A= + /* enable interrupts */=0A= + val =3D fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN);=0A= + val |=3D TEMP_IRQ_CFG_MASK;=0A= + fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val);=0A= +=0A= + val =3D fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN);=0A= + val |=3D FG_IRQ_CFG_LOWBATT_MASK;=0A= + val =3D fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val);=0A= +}=0A= +=0A= +static int axp288_fuel_gauge_probe(struct platform_device *pdev)=0A= +{=0A= + int ret;=0A= + struct axp288_fg_info *info;=0A= + struct axp20x_dev *axp20x =3D dev_get_drvdata(pdev->dev.parent);=0A= +=0A= + info =3D devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);=0A= + if (!info)=0A= + return -ENOMEM;=0A= +=0A= + info->pdev =3D pdev;=0A= + info->regmap =3D axp20x->regmap;=0A= + info->regmap_irqc =3D axp20x->regmap_irqc;=0A= + info->status =3D POWER_SUPPLY_STATUS_UNKNOWN;=0A= + info->pdata =3D pdev->dev.platform_data;=0A= + if (!info->pdata)=0A= + return -ENODEV;=0A= +=0A= + platform_set_drvdata(pdev, info);=0A= +=0A= + mutex_init(&info->lock);=0A= + INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor)= ;=0A= +=0A= + info->bat.name =3D DEV_NAME;=0A= + info->bat.type =3D POWER_SUPPLY_TYPE_BATTERY;=0A= + info->bat.properties =3D fuel_gauge_props;=0A= + info->bat.num_properties =3D ARRAY_SIZE(fuel_gauge_props);=0A= + info->bat.get_property =3D fuel_gauge_get_property;=0A= + info->bat.set_property =3D fuel_gauge_set_property;=0A= + info->bat.property_is_writeable =3D fuel_gauge_property_is_writeabl= e;=0A= + info->bat.external_power_changed =3D fuel_gauge_external_power_chan= ged;=0A= + ret =3D power_supply_register(&pdev->dev, &info->bat);=0A= + if (ret) {=0A= + dev_err(&pdev->dev, "failed to register battery: %d\n", ret= );=0A= + return ret;=0A= + }=0A= +=0A= + fuel_gauge_create_debugfs(info);=0A= + fuel_gauge_init_config_regs(info);=0A= + fuel_gauge_init_irq(info);=0A= + fuel_gauge_init_hw_regs(info);=0A= + schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFI= ES);=0A= +=0A= + return ret;=0A= +}=0A= +=0A= +static struct platform_device_id axp288_fg_id_table[] =3D {=0A= + { .name =3D DEV_NAME },=0A= + {},=0A= +};=0A= +=0A= +static int axp288_fuel_gauge_remove(struct platform_device *pdev)=0A= +{=0A= + struct axp288_fg_info *info =3D platform_get_drvdata(pdev);=0A= + int i;=0A= +=0A= + cancel_delayed_work_sync(&info->status_monitor);=0A= + power_supply_unregister(&info->bat);=0A= + fuel_gauge_remove_debugfs(info);=0A= +=0A= + for (i =3D 0; i < AXP288_FG_INTR_NUM; i++)=0A= + if (info->irq[i] >=3D 0)=0A= + free_irq(info->irq[i], info);=0A= +=0A= + return 0;=0A= +}=0A= +=0A= +static struct platform_driver axp288_fuel_gauge_driver =3D {=0A= + .probe =3D axp288_fuel_gauge_probe,=0A= + .remove =3D axp288_fuel_gauge_remove,=0A= + .id_table =3D axp288_fg_id_table,=0A= + .driver =3D {=0A= + .name =3D DEV_NAME,=0A= + },=0A= +};=0A= +=0A= +module_platform_driver(axp288_fuel_gauge_driver);=0A= +=0A= +MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");=0A= +MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");=0A= +MODULE_LICENSE("GPL");=0A= --=0A= 1.9.1=0A= =0A= ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver 2015-02-02 20:57 ` Brandt, Todd E @ 2015-02-04 15:07 ` sre 0 siblings, 0 replies; 7+ messages in thread From: sre @ 2015-02-04 15:07 UTC (permalink / raw) To: Brandt, Todd E Cc: Todd Brandt, dbaryshkov@gmail.com, Woodhouse, David, linux-pm@vger.kernel.org, linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org, jacob.jun.pan@linux.intel.com, sameo@linux.intel.com, lee.jones@linaro.org, dwmw2@infradead.org, jic23@kernel.org [-- Attachment #1: Type: text/plain, Size: 330 bytes --] Hi, On Mon, Feb 02, 2015 at 08:57:35PM +0000, Brandt, Todd E wrote: > Hi Dmitry and Sebastian, can we get this driver included for 3.20? Since the merge window is near I did not plan to take any more patches for 3.20 (except fixes). Apart from that the patches do not apply cleanly to battery-2.6's master branch. -- Sebastian [-- Attachment #2: Digital signature --] [-- Type: application/pgp-signature, Size: 819 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2015-02-16 13:54 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2015-02-02 23:41 [PATCH RESEND v3 0/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt 2015-02-16 13:54 ` Lee Jones 2015-02-02 23:41 ` [PATCH RESEND v3 2/3] mfd/axp20x: add support for fuel gauge cell Todd Brandt 2015-02-02 23:41 ` [PATCH RESEND v3 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt 2015-02-02 20:57 ` Brandt, Todd E 2015-02-04 15:07 ` sre
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).