All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] X-Power AXP288 PMIC Fuel Gauge Driver
@ 2015-01-22 20:52 ` Todd Brandt
  0 siblings, 0 replies; 11+ messages in thread
From: Todd Brandt @ 2015-01-22 20:52 UTC (permalink / raw)
  To: linux-pm, linux-iio, linux-kernel
  Cc: todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones,
	sre, dbaryshkov, dwmw2, david.woodhouse

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

 drivers/mfd/axp20x.c              |    8 +-
 drivers/power/Kconfig             |    9 +
 drivers/power/Makefile            |    1 +
 drivers/power/axp288_fuel_gauge.c | 1148 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/axp20x.h        |   43 +-
 5 files changed, 1203 insertions(+), 6 deletions(-)
 create mode 100644 drivers/power/axp288_fuel_gauge.c

-- 
2.1.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 0/3] X-Power AXP288 PMIC Fuel Gauge Driver
@ 2015-01-22 20:52 ` Todd Brandt
  0 siblings, 0 replies; 11+ messages in thread
From: Todd Brandt @ 2015-01-22 20:52 UTC (permalink / raw)
  To: linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: todd.e.brandt-VuQAYsv1563Yd54FQh9/CA,
	todd.e.brandt-ral2JQCrhuEAvxtiuMwx3w,
	jacob.jun.pan-VuQAYsv1563Yd54FQh9/CA,
	sameo-VuQAYsv1563Yd54FQh9/CA, lee.jones-QSEj5FYQhm4dnm+yROfE0A,
	sre-DgEjT+Ai2ygdnm+yROfE0A, dbaryshkov-Re5JQEeQqe8AvxtiuMwx3w,
	dwmw2-wEGCiKHe2LqWVfeAwA7xHQ,
	david.woodhouse-ral2JQCrhuEAvxtiuMwx3w

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

 drivers/mfd/axp20x.c              |    8 +-
 drivers/power/Kconfig             |    9 +
 drivers/power/Makefile            |    1 +
 drivers/power/axp288_fuel_gauge.c | 1148 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/axp20x.h        |   43 +-
 5 files changed, 1203 insertions(+), 6 deletions(-)
 create mode 100644 drivers/power/axp288_fuel_gauge.c

-- 
2.1.0

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 1/3] mfd/axp20x: change battery cell name to fuel gauge
  2015-01-22 20:52 ` Todd Brandt
  (?)
@ 2015-01-22 20:52 ` Todd Brandt
  2015-01-25 12:57     ` Sebastian Reichel
  -1 siblings, 1 reply; 11+ messages in thread
From: Todd Brandt @ 2015-01-22 20:52 UTC (permalink / raw)
  To: linux-pm, linux-iio, linux-kernel
  Cc: todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones,
	sre, dbaryshkov, dwmw2, david.woodhouse

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>
---
 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

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",
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/3] mfd/axp20x: add support for fuel gauge cell
  2015-01-22 20:52 ` Todd Brandt
  (?)
  (?)
@ 2015-01-22 20:52 ` Todd Brandt
  2015-01-25 12:59   ` Sebastian Reichel
  -1 siblings, 1 reply; 11+ messages in thread
From: Todd Brandt @ 2015-01-22 20:52 UTC (permalink / raw)
  To: linux-pm, linux-iio, linux-kernel
  Cc: todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones,
	sre, dbaryshkov, dwmw2, david.woodhouse

Register definitions and platform data structure
for fuel gauge cell devices.

Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>
---
 include/linux/mfd/axp20x.h | 43 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 41 insertions(+), 2 deletions(-)

v2: same as v1 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 */
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver
  2015-01-22 20:52 ` Todd Brandt
                   ` (2 preceding siblings ...)
  (?)
@ 2015-01-22 20:52 ` Todd Brandt
  2015-01-25 10:58   ` Sebastian Reichel
                     ` (2 more replies)
  -1 siblings, 3 replies; 11+ messages in thread
From: Todd Brandt @ 2015-01-22 20:52 UTC (permalink / raw)
  To: linux-pm, linux-iio, linux-kernel
  Cc: todd.e.brandt, todd.e.brandt, jacob.jun.pan, sameo, lee.jones,
	sre, dbaryshkov, dwmw2, david.woodhouse

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>
---
 drivers/power/Kconfig             |    9 +
 drivers/power/Makefile            |    1 +
 drivers/power/axp288_fuel_gauge.c | 1148 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1158 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

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..e221d92
--- /dev/null
+++ b/drivers/power/axp288_fuel_gauge.c
@@ -0,0 +1,1148 @@
+/*
+ * 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;
+
+	fuel_gauge_create_debugfs(info);
+	platform_set_drvdata(pdev, info);
+
+	mutex_init(&info->lock);
+	INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
+
+	fuel_gauge_init_config_regs(info);
+
+	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_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);
+
+	cancel_delayed_work_sync(&info->status_monitor);
+	power_supply_unregister(&info->bat);
+	fuel_gauge_remove_debugfs(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,
+		.owner = THIS_MODULE,
+	},
+};
+
+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");
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver
  2015-01-22 20:52 ` [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt
@ 2015-01-25 10:58   ` Sebastian Reichel
  2015-01-25 22:34   ` Jonathan Cameron
  2015-01-27  0:31   ` Jacob Pan
  2 siblings, 0 replies; 11+ messages in thread
From: Sebastian Reichel @ 2015-01-25 10:58 UTC (permalink / raw)
  To: Todd Brandt
  Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, jacob.jun.pan,
	sameo, lee.jones, dbaryshkov, dwmw2, david.woodhouse

[-- Attachment #1: Type: text/plain, Size: 2040 bytes --]

Hi,

On Thu, Jan 22, 2015 at 12:52:07PM -0800, Todd Brandt wrote:
> 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

Apart from two issues the driver looks fine to me.

> [...]
>
> +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;
> +	}

This is missing in axp288_fuel_gauge_remove().

> +}
>
> [...]
>
> +static int axp288_fuel_gauge_remove(struct platform_device *pdev)
> +{
> +	struct axp288_fg_info *info = platform_get_drvdata(pdev);
> +
> +	cancel_delayed_work_sync(&info->status_monitor);
> +	power_supply_unregister(&info->bat);
> +	fuel_gauge_remove_debugfs(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,
> +		.owner = THIS_MODULE,

please remove .owner, it will be set automatically by
module_platform_driver.

> [...]

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 1/3] mfd/axp20x: change battery cell name to fuel gauge
@ 2015-01-25 12:57     ` Sebastian Reichel
  0 siblings, 0 replies; 11+ messages in thread
From: Sebastian Reichel @ 2015-01-25 12:57 UTC (permalink / raw)
  To: Todd Brandt
  Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, jacob.jun.pan,
	sameo, lee.jones, dbaryshkov, dwmw2, david.woodhouse

[-- Attachment #1: Type: text/plain, Size: 1371 bytes --]

On Thu, Jan 22, 2015 at 12:52:05PM -0800, 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>
> ---
>  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
> 
> 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",

Acked-By: Sebastian Reichel <sre@kernel.org>

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 1/3] mfd/axp20x: change battery cell name to fuel gauge
@ 2015-01-25 12:57     ` Sebastian Reichel
  0 siblings, 0 replies; 11+ messages in thread
From: Sebastian Reichel @ 2015-01-25 12:57 UTC (permalink / raw)
  To: Todd Brandt
  Cc: linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	todd.e.brandt-ral2JQCrhuEAvxtiuMwx3w,
	jacob.jun.pan-VuQAYsv1563Yd54FQh9/CA,
	sameo-VuQAYsv1563Yd54FQh9/CA, lee.jones-QSEj5FYQhm4dnm+yROfE0A,
	dbaryshkov-Re5JQEeQqe8AvxtiuMwx3w, dwmw2-wEGCiKHe2LqWVfeAwA7xHQ,
	david.woodhouse-ral2JQCrhuEAvxtiuMwx3w

[-- Attachment #1: Type: text/plain, Size: 1424 bytes --]

On Thu, Jan 22, 2015 at 12:52:05PM -0800, 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-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
> ---
>  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
> 
> 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",

Acked-By: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 2/3] mfd/axp20x: add support for fuel gauge cell
  2015-01-22 20:52 ` [PATCH v2 2/3] mfd/axp20x: add support for fuel gauge cell Todd Brandt
@ 2015-01-25 12:59   ` Sebastian Reichel
  0 siblings, 0 replies; 11+ messages in thread
From: Sebastian Reichel @ 2015-01-25 12:59 UTC (permalink / raw)
  To: Todd Brandt
  Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, jacob.jun.pan,
	sameo, lee.jones, dbaryshkov, dwmw2, david.woodhouse

[-- Attachment #1: Type: text/plain, Size: 2421 bytes --]

On Thu, Jan 22, 2015 at 12:52:06PM -0800, Todd Brandt wrote:
> Register definitions and platform data structure
> for fuel gauge cell devices.
> 
> Signed-off-by: Todd Brandt <todd.e.brandt@linux.intel.com>
> ---
>  include/linux/mfd/axp20x.h | 43 +++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 41 insertions(+), 2 deletions(-)
> 
> v2: same as v1 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];
> +};

Acked-By: Sebastian Reichel <sre@kernel.org>

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver
  2015-01-22 20:52 ` [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt
  2015-01-25 10:58   ` Sebastian Reichel
@ 2015-01-25 22:34   ` Jonathan Cameron
  2015-01-27  0:31   ` Jacob Pan
  2 siblings, 0 replies; 11+ messages in thread
From: Jonathan Cameron @ 2015-01-25 22:34 UTC (permalink / raw)
  To: Todd Brandt, linux-pm, linux-iio, linux-kernel
  Cc: todd.e.brandt, jacob.jun.pan, sameo, lee.jones, sre, dbaryshkov,
	dwmw2, david.woodhouse

On 22/01/15 20:52, Todd Brandt wrote:
> 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>
The IIO bits in here are fine, though they do raise a few
questions abotu the IIO driver.

I note here that we convert a number of channels to real units having
requested them 'raw' from the IIO driver.  Now this relies on a quirk
of how IIO handling of sysfs reads and in kernel reads is currently
implemented.  These channels don't actually 'claim' to support raw
reading at all.  It's not in the chan spec and right now we don't have
the result of this case defined.

Do we allow for drivers to not export interfaces via IIOs sysfs interface
but still allow them to be accessed from other kernel drivers?

The other point to note here is that we could push a lot of the
'scale' stuff in here into IIO and use a processed read.  This is
obviously not terribly important if we allow the use of read_raw without
an infomask having been defined for the channel, but if we were to export
these from IIO via sysfs this info should be there, not here.

This driver looks fine to me!  Note there is nothing in the above to
necessarily block this fuel gauge driver, but we probably want to clean
the iio driver up (and might be able to simplify this driver slightly
as a result when we have).

Jonathan
> ---
>  drivers/power/Kconfig             |    9 +
>  drivers/power/Makefile            |    1 +
>  drivers/power/axp288_fuel_gauge.c | 1148 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 1158 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
> 
> 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..e221d92
> --- /dev/null
> +++ b/drivers/power/axp288_fuel_gauge.c
> @@ -0,0 +1,1148 @@
> +/*
> + * 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;
> +
> +	fuel_gauge_create_debugfs(info);
> +	platform_set_drvdata(pdev, info);
> +
> +	mutex_init(&info->lock);
> +	INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
> +
> +	fuel_gauge_init_config_regs(info);
> +
> +	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_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);
> +
> +	cancel_delayed_work_sync(&info->status_monitor);
> +	power_supply_unregister(&info->bat);
> +	fuel_gauge_remove_debugfs(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,
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +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");
> 


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver
  2015-01-22 20:52 ` [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt
  2015-01-25 10:58   ` Sebastian Reichel
  2015-01-25 22:34   ` Jonathan Cameron
@ 2015-01-27  0:31   ` Jacob Pan
  2 siblings, 0 replies; 11+ messages in thread
From: Jacob Pan @ 2015-01-27  0:31 UTC (permalink / raw)
  To: Todd Brandt
  Cc: linux-pm, linux-iio, linux-kernel, todd.e.brandt, sameo,
	lee.jones, sre, dbaryshkov, dwmw2, david.woodhouse

On Thu, 22 Jan 2015 12:52:07 -0800
Todd Brandt <todd.e.brandt@linux.intel.com> wrote:

> 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>
Looks good to me with a few small things below. Otherwise,
Acked-by:  Jacob Pan <jacob.jun.pan@linux.intel.com>

> ---
>  drivers/power/Kconfig             |    9 +
>  drivers/power/Makefile            |    1 +
>  drivers/power/axp288_fuel_gauge.c | 1148
> +++++++++++++++++++++++++++++++++++++ 3 files changed, 1158
> 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
> 
> 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..e221d92
> --- /dev/null
> +++ b/drivers/power/axp288_fuel_gauge.c
> @@ -0,0 +1,1148 @@
> +/*
> + * 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;
> +
> +	fuel_gauge_create_debugfs(info);
> +	platform_set_drvdata(pdev, info);
> +
> +	mutex_init(&info->lock);
> +	INIT_DELAYED_WORK(&info->status_monitor,
> fuel_gauge_status_monitor); +
> +	fuel_gauge_init_config_regs(info);
would it be better to move this later, right before init_irq? then we
know for sure these new configurations will be used.
> +
> +	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);
remove debugfs here? or move debugfs creation after this.

> +		return ret;
> +	}
> +
> +	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);
> +
> +	cancel_delayed_work_sync(&info->status_monitor);
> +	power_supply_unregister(&info->bat);
> +	fuel_gauge_remove_debugfs(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,
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +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");

[Jacob Pan]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2015-01-27  0:31 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-22 20:52 [PATCH v2 0/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt
2015-01-22 20:52 ` Todd Brandt
2015-01-22 20:52 ` [PATCH v2 1/3] mfd/axp20x: change battery cell name to fuel gauge Todd Brandt
2015-01-25 12:57   ` Sebastian Reichel
2015-01-25 12:57     ` Sebastian Reichel
2015-01-22 20:52 ` [PATCH v2 2/3] mfd/axp20x: add support for fuel gauge cell Todd Brandt
2015-01-25 12:59   ` Sebastian Reichel
2015-01-22 20:52 ` [PATCH v2 3/3] X-Power AXP288 PMIC Fuel Gauge Driver Todd Brandt
2015-01-25 10:58   ` Sebastian Reichel
2015-01-25 22:34   ` Jonathan Cameron
2015-01-27  0:31   ` Jacob Pan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.