summary refs log tree commit diff
path: root/drivers/power
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-06-23 16:10:27 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2015-06-23 16:10:27 -0700
commit36a1624d8844b6c165daf61649e6b68c02d0835f (patch)
tree70159ac0dfd7d298f2c7abc615a9f5c9f9188b71 /drivers/power
parent5262f25f0983ff6f2c21e3dadcfd0c809e5aa286 (diff)
parent43df61051ab9af985ec439ee9be9c299e7de34e8 (diff)
downloadlinux-36a1624d8844b6c165daf61649e6b68c02d0835f.tar.gz
Merge tag 'for-4.2' of git://git.infradead.org/battery-2.6
Pull power supply and reset updates from Sebastian Reichel:

 - new charger drivers: BQ24257, BQ25890, AXP288, RT9455

 - MAX17042 battery: add health & temperature support

 - BQ2415x charger: add ACPI support

 - misc fixes and cleanups

* tag 'for-4.2' of git://git.infradead.org/battery-2.6: (32 commits)
  power_supply: Correct kerneldoc copy paste errors
  wm831x_power: Fix off-by-one at free_irq()
  power_supply: rt9455_charger: Fix error reported by static analysis tool
  power_supply: bq24257: use flags argument of devm_gpiod_get
  power_supply: bq25890: use flags argument of devm_gpiod_get
  sbs-battery: add option to always register battery
  power: Add devm_power_supply_get_by_phandle() helper function
  power_supply: max17042: Add OF support for setting thresholds
  power_supply: sysfs: Bring back write to writeable properties
  power_supply: rt9455_charger: Check if CONFIG_USB_PHY is enabled
  power: reset: gpio-restart: increase priority slightly
  power_supply: bq25890: make chip_id int
  power_supply: Add support for Richtek RT9455 battery charger
  Documentation: devicetree: Add Richtek RT9455 bindings
  of: Add vendor prefix for Richtek Technology Corporation
  power_supply: 88pm860x_charger: Do not call free_irq() twice
  power: bq24190_charger: Change first_time flag reset condition
  power: axp288_charger: axp288 charger driver
  power: max17042_battery: add HEALTH and TEMP_* properties support
  power_supply: Add support for TI BQ25890 charger chip
  ...
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/88pm860x_charger.c1
-rw-r--r--drivers/power/Kconfig28
-rw-r--r--drivers/power/Makefile4
-rw-r--r--drivers/power/axp288_charger.c941
-rw-r--r--drivers/power/axp288_fuel_gauge.c2
-rw-r--r--drivers/power/bq2415x_charger.c94
-rw-r--r--drivers/power/bq24190_charger.c11
-rw-r--r--drivers/power/bq24257_charger.c858
-rw-r--r--drivers/power/bq25890_charger.c994
-rw-r--r--drivers/power/charger-manager.c3
-rw-r--r--drivers/power/max17042_battery.c199
-rw-r--r--drivers/power/power_supply_core.c106
-rw-r--r--drivers/power/power_supply_leds.c4
-rw-r--r--drivers/power/power_supply_sysfs.c4
-rw-r--r--drivers/power/reset/at91-reset.c2
-rw-r--r--drivers/power/reset/gpio-poweroff.c25
-rw-r--r--drivers/power/reset/gpio-restart.c2
-rw-r--r--drivers/power/reset/ltc2952-poweroff.c7
-rw-r--r--drivers/power/rt9455_charger.c1752
-rw-r--r--drivers/power/sbs-battery.c21
-rw-r--r--drivers/power/wm831x_power.c1
21 files changed, 4971 insertions, 88 deletions
diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c
index 0e448c68c02b..297e72dc70e6 100644
--- a/drivers/power/88pm860x_charger.c
+++ b/drivers/power/88pm860x_charger.c
@@ -742,7 +742,6 @@ static int pm860x_charger_remove(struct platform_device *pdev)
 	int i;
 
 	power_supply_unregister(info->usb);
-	free_irq(info->irq[0], info);
 	for (i = 0; i < info->irq_nums; i++)
 		free_irq(info->irq[i], info);
 	return 0;
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 4091fb092d06..08beeed5485d 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -204,6 +204,13 @@ config CHARGER_DA9150
 	  This driver can also be built as a module. If so, the module will be
 	  called da9150-charger.
 
+config AXP288_CHARGER
+	tristate "X-Powers AXP288 Charger"
+	depends on MFD_AXP20X && EXTCON_AXP288
+	help
+	  Say yes here to have support X-Power AXP288 power management IC (PMIC)
+	  integrated charger.
+
 config AXP288_FUEL_GAUGE
 	tristate "X-Powers AXP288 Fuel Gauge"
 	depends on MFD_AXP20X && IIO
@@ -388,12 +395,26 @@ config CHARGER_BQ24190
 	help
 	  Say Y to enable support for the TI BQ24190 battery charger.
 
+config CHARGER_BQ24257
+	tristate "TI BQ24257 battery charger driver"
+	depends on I2C && GPIOLIB
+	depends on REGMAP_I2C
+	help
+	  Say Y to enable support for the TI BQ24257 battery charger.
+
 config CHARGER_BQ24735
 	tristate "TI BQ24735 battery charger support"
 	depends on I2C && GPIOLIB
 	help
 	  Say Y to enable support for the TI BQ24735 battery charger.
 
+config CHARGER_BQ25890
+	tristate "TI BQ25890 battery charger driver"
+	depends on I2C && GPIOLIB
+	select REGMAP_I2C
+	help
+	  Say Y to enable support for the TI BQ25890 battery charger.
+
 config CHARGER_SMB347
 	tristate "Summit Microelectronics SMB347 Battery Charger"
 	depends on I2C
@@ -439,6 +460,13 @@ config BATTERY_RT5033
 	  The fuelgauge calculates and determines the battery state of charge
 	  according to battery open circuit voltage.
 
+config CHARGER_RT9455
+	tristate "Richtek RT9455 battery charger driver"
+	depends on I2C && GPIOLIB
+	select REGMAP_I2C
+	help
+	  Say Y to enable support for Richtek RT9455 battery charger.
+
 source "drivers/power/reset/Kconfig"
 
 endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b7b0181c95e5..5752ce818f51 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_BATTERY_MAX17040)	+= max17040_battery.o
 obj-$(CONFIG_BATTERY_MAX17042)	+= max17042_battery.o
 obj-$(CONFIG_BATTERY_Z2)	+= z2_battery.o
 obj-$(CONFIG_BATTERY_RT5033)	+= rt5033_battery.o
+obj-$(CONFIG_CHARGER_RT9455)	+= rt9455_charger.o
 obj-$(CONFIG_BATTERY_S3C_ADC)	+= s3c_adc_battery.o
 obj-$(CONFIG_BATTERY_TWL4030_MADC)	+= twl4030_madc_battery.o
 obj-$(CONFIG_CHARGER_88PM860X)	+= 88pm860x_charger.o
@@ -58,9 +59,12 @@ obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)	+= max8998_charger.o
 obj-$(CONFIG_CHARGER_BQ2415X)	+= bq2415x_charger.o
 obj-$(CONFIG_CHARGER_BQ24190)	+= bq24190_charger.o
+obj-$(CONFIG_CHARGER_BQ24257)	+= bq24257_charger.o
 obj-$(CONFIG_CHARGER_BQ24735)	+= bq24735-charger.o
+obj-$(CONFIG_CHARGER_BQ25890)	+= bq25890_charger.o
 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
+obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
diff --git a/drivers/power/axp288_charger.c b/drivers/power/axp288_charger.c
new file mode 100644
index 000000000000..5680317f4823
--- /dev/null
+++ b/drivers/power/axp288_charger.c
@@ -0,0 +1,941 @@
+/*
+ * axp288_charger.c - X-power AXP288 PMIC Charger driver
+ *
+ * Copyright (C) 2014 Intel Corporation
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/device.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/usb/otg.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/notifier.h>
+#include <linux/property.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/extcon.h>
+
+#define PS_STAT_VBUS_TRIGGER		(1 << 0)
+#define PS_STAT_BAT_CHRG_DIR		(1 << 2)
+#define PS_STAT_VBAT_ABOVE_VHOLD	(1 << 3)
+#define PS_STAT_VBUS_VALID		(1 << 4)
+#define PS_STAT_VBUS_PRESENT		(1 << 5)
+
+#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 VBUS_ISPOUT_CUR_LIM_MASK	0x03
+#define VBUS_ISPOUT_CUR_LIM_BIT_POS	0
+#define VBUS_ISPOUT_CUR_LIM_900MA	0x0	/* 900mA */
+#define VBUS_ISPOUT_CUR_LIM_1500MA	0x1	/* 1500mA */
+#define VBUS_ISPOUT_CUR_LIM_2000MA	0x2	/* 2000mA */
+#define VBUS_ISPOUT_CUR_NO_LIM		0x3	/* 2500mA */
+#define VBUS_ISPOUT_VHOLD_SET_MASK	0x31
+#define VBUS_ISPOUT_VHOLD_SET_BIT_POS	0x3
+#define VBUS_ISPOUT_VHOLD_SET_OFFSET	4000	/* 4000mV */
+#define VBUS_ISPOUT_VHOLD_SET_LSB_RES	100	/* 100mV */
+#define VBUS_ISPOUT_VHOLD_SET_4300MV	0x3	/* 4300mV */
+#define VBUS_ISPOUT_VBUS_PATH_DIS	(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 CNTL2_CC_TIMEOUT_MASK		0x3	/* 2 bits */
+#define CNTL2_CC_TIMEOUT_OFFSET		6	/* 6 Hrs */
+#define CNTL2_CC_TIMEOUT_LSB_RES	2	/* 2 Hrs */
+#define CNTL2_CC_TIMEOUT_12HRS		0x3	/* 12 Hrs */
+#define CNTL2_CHGLED_TYPEB		(1 << 4)
+#define CNTL2_CHG_OUT_TURNON		(1 << 5)
+#define CNTL2_PC_TIMEOUT_MASK		0xC0
+#define CNTL2_PC_TIMEOUT_OFFSET		40	/* 40 mins */
+#define CNTL2_PC_TIMEOUT_LSB_RES	10	/* 10 mins */
+#define CNTL2_PC_TIMEOUT_70MINS		0x3
+
+#define CHRG_ILIM_TEMP_LOOP_EN		(1 << 3)
+#define CHRG_VBUS_ILIM_MASK		0xf0
+#define CHRG_VBUS_ILIM_BIT_POS		4
+#define CHRG_VBUS_ILIM_100MA		0x0	/* 100mA */
+#define CHRG_VBUS_ILIM_500MA		0x1	/* 500mA */
+#define CHRG_VBUS_ILIM_900MA		0x2	/* 900mA */
+#define CHRG_VBUS_ILIM_1500MA		0x3	/* 1500mA */
+#define CHRG_VBUS_ILIM_2000MA		0x4	/* 2000mA */
+#define CHRG_VBUS_ILIM_2500MA		0x5	/* 2500mA */
+#define CHRG_VBUS_ILIM_3000MA		0x6	/* 3000mA */
+
+#define CHRG_VLTFC_0C			0xA5	/* 0 DegC */
+#define CHRG_VHTFC_45C			0x1F	/* 45 DegC */
+
+#define BAT_IRQ_CFG_CHRG_DONE		(1 << 2)
+#define BAT_IRQ_CFG_CHRG_START		(1 << 3)
+#define BAT_IRQ_CFG_BAT_SAFE_EXIT	(1 << 4)
+#define BAT_IRQ_CFG_BAT_SAFE_ENTER	(1 << 5)
+#define BAT_IRQ_CFG_BAT_DISCON		(1 << 6)
+#define BAT_IRQ_CFG_BAT_CONN		(1 << 7)
+#define BAT_IRQ_CFG_BAT_MASK		0xFC
+
+#define TEMP_IRQ_CFG_QCBTU		(1 << 4)
+#define TEMP_IRQ_CFG_CBTU		(1 << 5)
+#define TEMP_IRQ_CFG_QCBTO		(1 << 6)
+#define TEMP_IRQ_CFG_CBTO		(1 << 7)
+#define TEMP_IRQ_CFG_MASK		0xF0
+
+#define FG_CNTL_OCV_ADJ_EN		(1 << 3)
+
+#define CV_4100MV			4100	/* 4100mV */
+#define CV_4150MV			4150	/* 4150mV */
+#define CV_4200MV			4200	/* 4200mV */
+#define CV_4350MV			4350	/* 4350mV */
+
+#define CC_200MA			200	/*  200mA */
+#define CC_600MA			600	/*  600mA */
+#define CC_800MA			800	/*  800mA */
+#define CC_1000MA			1000	/* 1000mA */
+#define CC_1600MA			1600	/* 1600mA */
+#define CC_2000MA			2000	/* 2000mA */
+
+#define ILIM_100MA			100	/* 100mA */
+#define ILIM_500MA			500	/* 500mA */
+#define ILIM_900MA			900	/* 900mA */
+#define ILIM_1500MA			1500	/* 1500mA */
+#define ILIM_2000MA			2000	/* 2000mA */
+#define ILIM_2500MA			2500	/* 2500mA */
+#define ILIM_3000MA			3000	/* 3000mA */
+
+#define AXP288_EXTCON_DEV_NAME		"axp288_extcon"
+
+#define AXP288_EXTCON_SLOW_CHARGER		"SLOW-CHARGER"
+#define AXP288_EXTCON_DOWNSTREAM_CHARGER	"CHARGE-DOWNSTREAM"
+#define AXP288_EXTCON_FAST_CHARGER		"FAST-CHARGER"
+
+enum {
+	VBUS_OV_IRQ = 0,
+	CHARGE_DONE_IRQ,
+	CHARGE_CHARGING_IRQ,
+	BAT_SAFE_QUIT_IRQ,
+	BAT_SAFE_ENTER_IRQ,
+	QCBTU_IRQ,
+	CBTU_IRQ,
+	QCBTO_IRQ,
+	CBTO_IRQ,
+	CHRG_INTR_END,
+};
+
+struct axp288_chrg_info {
+	struct platform_device *pdev;
+	struct axp20x_chrg_pdata *pdata;
+	struct regmap *regmap;
+	struct regmap_irq_chip_data *regmap_irqc;
+	int irq[CHRG_INTR_END];
+	struct power_supply *psy_usb;
+	struct mutex lock;
+
+	/* OTG/Host mode */
+	struct {
+		struct work_struct work;
+		struct extcon_specific_cable_nb cable;
+		struct notifier_block id_nb;
+		bool id_short;
+	} otg;
+
+	/* SDP/CDP/DCP USB charging cable notifications */
+	struct {
+		struct extcon_dev *edev;
+		bool connected;
+		enum power_supply_type chg_type;
+		struct notifier_block nb;
+		struct work_struct work;
+	} cable;
+
+	int health;
+	int inlmt;
+	int cc;
+	int cv;
+	int max_cc;
+	int max_cv;
+	bool online;
+	bool present;
+	bool enable_charger;
+	bool is_charger_enabled;
+};
+
+static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc)
+{
+	u8 reg_val;
+	int ret;
+
+	if (cc < CHRG_CCCV_CC_OFFSET)
+		cc = CHRG_CCCV_CC_OFFSET;
+	else if (cc > info->max_cc)
+		cc = info->max_cc;
+
+	reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES;
+	cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
+	reg_val = reg_val << CHRG_CCCV_CC_BIT_POS;
+
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_CHRG_CTRL1,
+				CHRG_CCCV_CC_MASK, reg_val);
+	if (ret >= 0)
+		info->cc = cc;
+
+	return ret;
+}
+
+static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv)
+{
+	u8 reg_val;
+	int ret;
+
+	if (cv <= CV_4100MV) {
+		reg_val = CHRG_CCCV_CV_4100MV;
+		cv = CV_4100MV;
+	} else if (cv <= CV_4150MV) {
+		reg_val = CHRG_CCCV_CV_4150MV;
+		cv = CV_4150MV;
+	} else if (cv <= CV_4200MV) {
+		reg_val = CHRG_CCCV_CV_4200MV;
+		cv = CV_4200MV;
+	} else {
+		reg_val = CHRG_CCCV_CV_4350MV;
+		cv = CV_4350MV;
+	}
+
+	reg_val = reg_val << CHRG_CCCV_CV_BIT_POS;
+
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_CHRG_CTRL1,
+				CHRG_CCCV_CV_MASK, reg_val);
+
+	if (ret >= 0)
+		info->cv = cv;
+
+	return ret;
+}
+
+static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info,
+					   int inlmt)
+{
+	int ret;
+	unsigned int val;
+	u8 reg_val;
+
+	/* Read in limit register */
+	ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val);
+	if (ret < 0)
+		goto set_inlmt_fail;
+
+	if (inlmt <= ILIM_100MA) {
+		reg_val = CHRG_VBUS_ILIM_100MA;
+		inlmt = ILIM_100MA;
+	} else if (inlmt <= ILIM_500MA) {
+		reg_val = CHRG_VBUS_ILIM_500MA;
+		inlmt = ILIM_500MA;
+	} else if (inlmt <= ILIM_900MA) {
+		reg_val = CHRG_VBUS_ILIM_900MA;
+		inlmt = ILIM_900MA;
+	} else if (inlmt <= ILIM_1500MA) {
+		reg_val = CHRG_VBUS_ILIM_1500MA;
+		inlmt = ILIM_1500MA;
+	} else if (inlmt <= ILIM_2000MA) {
+		reg_val = CHRG_VBUS_ILIM_2000MA;
+		inlmt = ILIM_2000MA;
+	} else if (inlmt <= ILIM_2500MA) {
+		reg_val = CHRG_VBUS_ILIM_2500MA;
+		inlmt = ILIM_2500MA;
+	} else {
+		reg_val = CHRG_VBUS_ILIM_3000MA;
+		inlmt = ILIM_3000MA;
+	}
+
+	reg_val = (val & ~CHRG_VBUS_ILIM_MASK)
+			| (reg_val << CHRG_VBUS_ILIM_BIT_POS);
+	ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val);
+	if (ret >= 0)
+		info->inlmt = inlmt;
+	else
+		dev_err(&info->pdev->dev, "charger BAK control %d\n", ret);
+
+
+set_inlmt_fail:
+	return ret;
+}
+
+static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info,
+								bool enable)
+{
+	int ret;
+
+	if (enable)
+		ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
+					VBUS_ISPOUT_VBUS_PATH_DIS, 0);
+	else
+		ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
+			VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS);
+
+	if (ret < 0)
+		dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret);
+
+
+	return ret;
+}
+
+static int axp288_charger_enable_charger(struct axp288_chrg_info *info,
+								bool enable)
+{
+	int ret;
+
+	if (enable)
+		ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
+				CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN);
+	else
+		ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
+				CHRG_CCCV_CHG_EN, 0);
+	if (ret < 0)
+		dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret);
+	else
+		info->is_charger_enabled = enable;
+
+	return ret;
+}
+
+static int axp288_charger_is_present(struct axp288_chrg_info *info)
+{
+	int ret, present = 0;
+	unsigned int val;
+
+	ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val & PS_STAT_VBUS_PRESENT)
+		present = 1;
+	return present;
+}
+
+static int axp288_charger_is_online(struct axp288_chrg_info *info)
+{
+	int ret, online = 0;
+	unsigned int val;
+
+	ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val & PS_STAT_VBUS_VALID)
+		online = 1;
+	return online;
+}
+
+static int axp288_get_charger_health(struct axp288_chrg_info *info)
+{
+	int ret, pwr_stat, chrg_stat;
+	int health = POWER_SUPPLY_HEALTH_UNKNOWN;
+	unsigned int val;
+
+	ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+	if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT))
+		goto health_read_fail;
+	else
+		pwr_stat = val;
+
+	ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val);
+	if (ret < 0)
+		goto health_read_fail;
+	else
+		chrg_stat = val;
+
+	if (!(pwr_stat & PS_STAT_VBUS_VALID))
+		health = POWER_SUPPLY_HEALTH_DEAD;
+	else if (chrg_stat & CHRG_STAT_PMIC_OTP)
+		health = POWER_SUPPLY_HEALTH_OVERHEAT;
+	else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE)
+		health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+	else
+		health = POWER_SUPPLY_HEALTH_GOOD;
+
+health_read_fail:
+	return health;
+}
+
+static int axp288_charger_usb_set_property(struct power_supply *psy,
+				    enum power_supply_property psp,
+				    const union power_supply_propval *val)
+{
+	struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
+	int ret = 0;
+	int scaled_val;
+
+	mutex_lock(&info->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		scaled_val = min(val->intval, info->max_cc);
+		scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
+		ret = axp288_charger_set_cc(info, scaled_val);
+		if (ret < 0)
+			dev_warn(&info->pdev->dev, "set charge current failed\n");
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		scaled_val = min(val->intval, info->max_cv);
+		scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
+		ret = axp288_charger_set_cv(info, scaled_val);
+		if (ret < 0)
+			dev_warn(&info->pdev->dev, "set charge voltage failed\n");
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int axp288_charger_usb_get_property(struct power_supply *psy,
+				    enum power_supply_property psp,
+				    union power_supply_propval *val)
+{
+	struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	mutex_lock(&info->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		/* Check for OTG case first */
+		if (info->otg.id_short) {
+			val->intval = 0;
+			break;
+		}
+		ret = axp288_charger_is_present(info);
+		if (ret < 0)
+			goto psy_get_prop_fail;
+		info->present = ret;
+		val->intval = info->present;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		/* Check for OTG case first */
+		if (info->otg.id_short) {
+			val->intval = 0;
+			break;
+		}
+		ret = axp288_charger_is_online(info);
+		if (ret < 0)
+			goto psy_get_prop_fail;
+		info->online = ret;
+		val->intval = info->online;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = axp288_get_charger_health(info);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		val->intval = info->cc * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		val->intval = info->max_cc * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		val->intval = info->cv * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		val->intval = info->max_cv * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+		val->intval = info->inlmt * 1000;
+		break;
+	default:
+		ret = -EINVAL;
+		goto psy_get_prop_fail;
+	}
+
+psy_get_prop_fail:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int axp288_charger_property_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		ret = 1;
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property axp288_usb_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_TYPE,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+};
+
+static const struct power_supply_desc axp288_charger_desc = {
+	.name			= "axp288_charger",
+	.type			= POWER_SUPPLY_TYPE_USB,
+	.properties		= axp288_usb_props,
+	.num_properties		= ARRAY_SIZE(axp288_usb_props),
+	.get_property		= axp288_charger_usb_get_property,
+	.set_property		= axp288_charger_usb_set_property,
+	.property_is_writeable	= axp288_charger_property_is_writeable,
+};
+
+static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev)
+{
+	struct axp288_chrg_info *info = dev;
+	int i;
+
+	for (i = 0; i < CHRG_INTR_END; i++) {
+		if (info->irq[i] == irq)
+			break;
+	}
+
+	if (i >= CHRG_INTR_END) {
+		dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
+		return IRQ_NONE;
+	}
+
+	switch (i) {
+	case VBUS_OV_IRQ:
+		dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n");
+		break;
+	case CHARGE_DONE_IRQ:
+		dev_dbg(&info->pdev->dev, "Charging Done INTR\n");
+		break;
+	case CHARGE_CHARGING_IRQ:
+		dev_dbg(&info->pdev->dev, "Start Charging IRQ\n");
+		break;
+	case BAT_SAFE_QUIT_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Quit Safe Mode(restart timer) Charging IRQ\n");
+		break;
+	case BAT_SAFE_ENTER_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Enter Safe Mode(timer expire) Charging IRQ\n");
+		break;
+	case QCBTU_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Quit Battery Under Temperature(CHRG) INTR\n");
+		break;
+	case CBTU_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Hit Battery Under Temperature(CHRG) INTR\n");
+		break;
+	case QCBTO_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Quit Battery Over Temperature(CHRG) INTR\n");
+		break;
+	case CBTO_IRQ:
+		dev_dbg(&info->pdev->dev,
+			"Hit Battery Over Temperature(CHRG) INTR\n");
+		break;
+	default:
+		dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
+		goto out;
+	}
+
+	power_supply_changed(info->psy_usb);
+out:
+	return IRQ_HANDLED;
+}
+
+static void axp288_charger_extcon_evt_worker(struct work_struct *work)
+{
+	struct axp288_chrg_info *info =
+	    container_of(work, struct axp288_chrg_info, cable.work);
+	int ret, current_limit;
+	bool changed = false;
+	struct extcon_dev *edev = info->cable.edev;
+	bool old_connected = info->cable.connected;
+
+	/* Determine cable/charger type */
+	if (extcon_get_cable_state(edev, AXP288_EXTCON_SLOW_CHARGER) > 0) {
+		dev_dbg(&info->pdev->dev, "USB SDP charger  is connected");
+		info->cable.connected = true;
+		info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
+	} else if (extcon_get_cable_state(edev,
+				AXP288_EXTCON_DOWNSTREAM_CHARGER) > 0) {
+		dev_dbg(&info->pdev->dev, "USB CDP charger is connected");
+		info->cable.connected = true;
+		info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP;
+	} else if (extcon_get_cable_state(edev,
+					AXP288_EXTCON_FAST_CHARGER) > 0) {
+		dev_dbg(&info->pdev->dev, "USB DCP charger is connected");
+		info->cable.connected = true;
+		info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+	} else {
+		if (old_connected)
+			dev_dbg(&info->pdev->dev, "USB charger disconnected");
+		info->cable.connected = false;
+		info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
+	}
+
+	/* Cable status changed */
+	if (old_connected != info->cable.connected)
+		changed = true;
+
+	if (!changed)
+		return;
+
+	mutex_lock(&info->lock);
+
+	if (info->is_charger_enabled && !info->cable.connected) {
+		info->enable_charger = false;
+		ret = axp288_charger_enable_charger(info, info->enable_charger);
+		if (ret < 0)
+			dev_err(&info->pdev->dev,
+				"cannot disable charger (%d)", ret);
+
+	} else if (!info->is_charger_enabled && info->cable.connected) {
+		switch (info->cable.chg_type) {
+		case POWER_SUPPLY_TYPE_USB:
+			current_limit = ILIM_500MA;
+			break;
+		case POWER_SUPPLY_TYPE_USB_CDP:
+			current_limit = ILIM_1500MA;
+			break;
+		case POWER_SUPPLY_TYPE_USB_DCP:
+			current_limit = ILIM_2000MA;
+			break;
+		default:
+			/* Unknown */
+			current_limit = 0;
+			break;
+		}
+
+		/* Set vbus current limit first, then enable charger */
+		ret = axp288_charger_set_vbus_inlmt(info, current_limit);
+		if (ret < 0) {
+			dev_err(&info->pdev->dev,
+				"error setting current limit (%d)", ret);
+		} else {
+			info->enable_charger = (current_limit > 0);
+			ret = axp288_charger_enable_charger(info,
+							info->enable_charger);
+			if (ret < 0)
+				dev_err(&info->pdev->dev,
+					"cannot enable charger (%d)", ret);
+		}
+	}
+
+	if (changed)
+		info->health = axp288_get_charger_health(info);
+
+	mutex_unlock(&info->lock);
+
+	if (changed)
+		power_supply_changed(info->psy_usb);
+}
+
+static int axp288_charger_handle_cable_evt(struct notifier_block *nb,
+					  unsigned long event, void *param)
+{
+	struct axp288_chrg_info *info =
+	    container_of(nb, struct axp288_chrg_info, cable.nb);
+
+	schedule_work(&info->cable.work);
+
+	return NOTIFY_OK;
+}
+
+static void axp288_charger_otg_evt_worker(struct work_struct *work)
+{
+	struct axp288_chrg_info *info =
+	    container_of(work, struct axp288_chrg_info, otg.work);
+	int ret;
+
+	/* Disable VBUS path before enabling the 5V boost */
+	ret = axp288_charger_vbus_path_select(info, !info->otg.id_short);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "vbus path disable failed\n");
+}
+
+static int axp288_charger_handle_otg_evt(struct notifier_block *nb,
+				   unsigned long event, void *param)
+{
+	struct axp288_chrg_info *info =
+	    container_of(nb, struct axp288_chrg_info, otg.id_nb);
+	struct extcon_dev *edev = param;
+	int usb_host = extcon_get_cable_state(edev, "USB-Host");
+
+	dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
+				usb_host ? "attached" : "detached");
+
+	/*
+	 * Set usb_id_short flag to avoid running charger detection logic
+	 * in case usb host.
+	 */
+	info->otg.id_short = usb_host;
+	schedule_work(&info->otg.work);
+
+	return NOTIFY_OK;
+}
+
+static void charger_init_hw_regs(struct axp288_chrg_info *info)
+{
+	int ret, cc, cv;
+	unsigned int val;
+
+	/* Program temperature thresholds */
+	ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+							AXP20X_V_LTF_CHRG, ret);
+
+	ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+							AXP20X_V_HTF_CHRG, ret);
+
+	/* Do not turn-off charger o/p after charge cycle ends */
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_CHRG_CTRL2,
+				CNTL2_CHG_OUT_TURNON, 1);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+						AXP20X_CHRG_CTRL2, ret);
+
+	/* Enable interrupts */
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_IRQ2_EN,
+				BAT_IRQ_CFG_BAT_MASK, 1);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+						AXP20X_IRQ2_EN, ret);
+
+	ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN,
+				TEMP_IRQ_CFG_MASK, 1);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+						AXP20X_IRQ3_EN, ret);
+
+	/* Setup ending condition for charging to be 10% of I(chrg) */
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_CHRG_CTRL1,
+				CHRG_CCCV_ITERM_20P, 0);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+						AXP20X_CHRG_CTRL1, ret);
+
+	/* Disable OCV-SOC curve calibration */
+	ret = regmap_update_bits(info->regmap,
+				AXP20X_CC_CTRL,
+				FG_CNTL_OCV_ADJ_EN, 0);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+						AXP20X_CC_CTRL, ret);
+
+	/* Init charging current and voltage */
+	info->max_cc = info->pdata->max_cc;
+	info->max_cv = info->pdata->max_cv;
+
+	/* Read current charge voltage and current limit */
+	ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val);
+	if (ret < 0) {
+		/* Assume default if cannot read */
+		info->cc = info->pdata->def_cc;
+		info->cv = info->pdata->def_cv;
+	} else {
+		/* Determine charge voltage */
+		cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS;
+		switch (cv) {
+		case CHRG_CCCV_CV_4100MV:
+			info->cv = CV_4100MV;
+			break;
+		case CHRG_CCCV_CV_4150MV:
+			info->cv = CV_4150MV;
+			break;
+		case CHRG_CCCV_CV_4200MV:
+			info->cv = CV_4200MV;
+			break;
+		case CHRG_CCCV_CV_4350MV:
+			info->cv = CV_4350MV;
+			break;
+		default:
+			info->cv = INT_MAX;
+			break;
+		}
+
+		/* Determine charge current limit */
+		cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS;
+		cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
+		info->cc = cc;
+
+		/* Program default charging voltage and current */
+		cc = min(info->pdata->def_cc, info->max_cc);
+		cv = min(info->pdata->def_cv, info->max_cv);
+
+		ret = axp288_charger_set_cc(info, cc);
+		if (ret < 0)
+			dev_warn(&info->pdev->dev,
+					"error(%d) in setting CC\n", ret);
+
+		ret = axp288_charger_set_cv(info, cv);
+		if (ret < 0)
+			dev_warn(&info->pdev->dev,
+					"error(%d) in setting CV\n", ret);
+	}
+}
+
+static int axp288_charger_probe(struct platform_device *pdev)
+{
+	int ret, i, pirq;
+	struct axp288_chrg_info *info;
+	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+	struct power_supply_config charger_cfg = {};
+
+	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->pdata = pdev->dev.platform_data;
+
+	if (!info->pdata) {
+		/* Try ACPI provided pdata via device properties */
+		if (!device_property_present(&pdev->dev,
+						"axp288_charger_data\n"))
+			dev_err(&pdev->dev, "failed to get platform data\n");
+		return -ENODEV;
+	}
+
+	info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
+	if (info->cable.edev == NULL) {
+		dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n",
+			AXP288_EXTCON_DEV_NAME);
+		return -EPROBE_DEFER;
+	}
+
+	/* Register for extcon notification */
+	INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
+	info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
+	ret = extcon_register_notifier(info->cable.edev, &info->cable.nb);
+	if (ret) {
+		dev_err(&info->pdev->dev,
+			"failed to register extcon notifier %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, info);
+	mutex_init(&info->lock);
+
+	/* Register with power supply class */
+	charger_cfg.drv_data = info;
+	info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc,
+						&charger_cfg);
+	if (IS_ERR(info->psy_usb)) {
+		dev_err(&pdev->dev, "failed to register power supply charger\n");
+		ret = PTR_ERR(info->psy_usb);
+		goto psy_reg_failed;
+	}
+
+	/* Register for OTG notification */
+	INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
+	info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
+	ret = extcon_register_interest(&info->otg.cable, NULL, "USB-Host",
+				       &info->otg.id_nb);
+	if (ret)
+		dev_warn(&pdev->dev, "failed to register otg notifier\n");
+
+	if (info->otg.cable.edev)
+		info->otg.id_short = extcon_get_cable_state(
+					info->otg.cable.edev, "USB-Host");
+
+	/* Register charger interrupts */
+	for (i = 0; i < CHRG_INTR_END; 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,
+				"failed to get virtual interrupt=%d\n", pirq);
+			ret = info->irq[i];
+			goto intr_reg_failed;
+		}
+		ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
+					NULL, axp288_charger_irq_thread_handler,
+					IRQF_ONESHOT, info->pdev->name, info);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to request interrupt=%d\n",
+								info->irq[i]);
+			goto intr_reg_failed;
+		}
+	}
+
+	charger_init_hw_regs(info);
+
+	return 0;
+
+intr_reg_failed:
+	if (info->otg.cable.edev)
+		extcon_unregister_interest(&info->otg.cable);
+	power_supply_unregister(info->psy_usb);
+psy_reg_failed:
+	extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
+	return ret;
+}
+
+static int axp288_charger_remove(struct platform_device *pdev)
+{
+	struct axp288_chrg_info *info =  dev_get_drvdata(&pdev->dev);
+
+	if (info->otg.cable.edev)
+		extcon_unregister_interest(&info->otg.cable);
+
+	extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
+	power_supply_unregister(info->psy_usb);
+
+	return 0;
+}
+
+static struct platform_driver axp288_charger_driver = {
+	.probe = axp288_charger_probe,
+	.remove = axp288_charger_remove,
+	.driver = {
+		.name = "axp288_charger",
+	},
+};
+
+module_platform_driver(axp288_charger_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_DESCRIPTION("X-power AXP288 Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c
index bd1dbfee2515..50c0110d6b58 100644
--- a/drivers/power/axp288_fuel_gauge.c
+++ b/drivers/power/axp288_fuel_gauge.c
@@ -1117,7 +1117,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
 	return ret;
 }
 
-static struct platform_device_id axp288_fg_id_table[] = {
+static const struct platform_device_id axp288_fg_id_table[] = {
 	{ .name = DEV_NAME },
 	{},
 };
diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c
index 6c534dcbc19c..e98dcb661cc9 100644
--- a/drivers/power/bq2415x_charger.c
+++ b/drivers/power/bq2415x_charger.c
@@ -35,6 +35,7 @@
 #include <linux/idr.h>
 #include <linux/i2c.h>
 #include <linux/slab.h>
+#include <linux/acpi.h>
 
 #include <linux/power/bq2415x_charger.h>
 
@@ -631,7 +632,7 @@ static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
 	int val;
 
 	if (bq->init_data.resistor_sense <= 0)
-		return -ENOSYS;
+		return -EINVAL;
 
 	val = (mA * bq->init_data.resistor_sense - 37400) / 6800;
 	if (val < 0)
@@ -650,7 +651,7 @@ static int bq2415x_get_charge_current(struct bq2415x_device *bq)
 	int ret;
 
 	if (bq->init_data.resistor_sense <= 0)
-		return -ENOSYS;
+		return -EINVAL;
 
 	ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
 			BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
@@ -665,7 +666,7 @@ static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
 	int val;
 
 	if (bq->init_data.resistor_sense <= 0)
-		return -ENOSYS;
+		return -EINVAL;
 
 	val = (mA * bq->init_data.resistor_sense - 3400) / 3400;
 	if (val < 0)
@@ -684,7 +685,7 @@ static int bq2415x_get_termination_current(struct bq2415x_device *bq)
 	int ret;
 
 	if (bq->init_data.resistor_sense <= 0)
-		return -ENOSYS;
+		return -EINVAL;
 
 	ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
 			BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
@@ -1166,7 +1167,7 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
 
 	if (strncmp(buf, "auto", 4) == 0) {
 		if (bq->automode < 0)
-			return -ENOSYS;
+			return -EINVAL;
 		bq->automode = 1;
 		mode = bq->reported_mode;
 	} else if (strncmp(buf, "off", 3) == 0) {
@@ -1530,13 +1531,14 @@ static int bq2415x_probe(struct i2c_client *client,
 {
 	int ret;
 	int num;
-	char *name;
+	char *name = NULL;
 	struct bq2415x_device *bq;
 	struct device_node *np = client->dev.of_node;
 	struct bq2415x_platform_data *pdata = client->dev.platform_data;
+	const struct acpi_device_id *acpi_id = NULL;
 
-	if (!np && !pdata) {
-		dev_err(&client->dev, "platform data missing\n");
+	if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
+		dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n");
 		return -ENODEV;
 	}
 
@@ -1547,7 +1549,14 @@ static int bq2415x_probe(struct i2c_client *client,
 	if (num < 0)
 		return num;
 
-	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+	if (id) {
+		name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+	} else if (ACPI_HANDLE(&client->dev)) {
+		acpi_id =
+			acpi_match_device(client->dev.driver->acpi_match_table,
+					  &client->dev);
+		name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num);
+	}
 	if (!name) {
 		dev_err(&client->dev, "failed to allocate device name\n");
 		ret = -ENOMEM;
@@ -1556,63 +1565,72 @@ static int bq2415x_probe(struct i2c_client *client,
 
 	bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL);
 	if (!bq) {
-		dev_err(&client->dev, "failed to allocate device data\n");
 		ret = -ENOMEM;
 		goto error_2;
 	}
 
 	if (np) {
-		bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection");
+		bq->notify_psy = power_supply_get_by_phandle(np,
+						"ti,usb-charger-detection");
 
 		if (IS_ERR(bq->notify_psy)) {
 			dev_info(&client->dev,
-				"no 'ti,usb-charger-detection' property (err=%ld)\n",
+				 "no 'ti,usb-charger-detection' property (err=%ld)\n",
 				PTR_ERR(bq->notify_psy));
 			bq->notify_psy = NULL;
 		} else if (!bq->notify_psy) {
 			ret = -EPROBE_DEFER;
 			goto error_2;
 		}
-	}
-	else if (pdata->notify_device)
+	} else if (pdata && pdata->notify_device) {
 		bq->notify_psy = power_supply_get_by_name(pdata->notify_device);
-	else
+	} else {
 		bq->notify_psy = NULL;
+	}
 
 	i2c_set_clientdata(client, bq);
 
 	bq->id = num;
 	bq->dev = &client->dev;
-	bq->chip = id->driver_data;
+	if (id)
+		bq->chip = id->driver_data;
+	else if (ACPI_HANDLE(bq->dev))
+		bq->chip = acpi_id->driver_data;
 	bq->name = name;
 	bq->mode = BQ2415X_MODE_OFF;
 	bq->reported_mode = BQ2415X_MODE_OFF;
 	bq->autotimer = 0;
 	bq->automode = 0;
 
-	if (np) {
-		ret = of_property_read_u32(np, "ti,current-limit",
-				&bq->init_data.current_limit);
+	if (np || ACPI_HANDLE(bq->dev)) {
+		ret = device_property_read_u32(bq->dev,
+					       "ti,current-limit",
+					       &bq->init_data.current_limit);
 		if (ret)
 			goto error_3;
-		ret = of_property_read_u32(np, "ti,weak-battery-voltage",
-				&bq->init_data.weak_battery_voltage);
+		ret = device_property_read_u32(bq->dev,
+					"ti,weak-battery-voltage",
+					&bq->init_data.weak_battery_voltage);
 		if (ret)
 			goto error_3;
-		ret = of_property_read_u32(np, "ti,battery-regulation-voltage",
+		ret = device_property_read_u32(bq->dev,
+				"ti,battery-regulation-voltage",
 				&bq->init_data.battery_regulation_voltage);
 		if (ret)
 			goto error_3;
-		ret = of_property_read_u32(np, "ti,charge-current",
-				&bq->init_data.charge_current);
+		ret = device_property_read_u32(bq->dev,
+					       "ti,charge-current",
+					       &bq->init_data.charge_current);
 		if (ret)
 			goto error_3;
-		ret = of_property_read_u32(np, "ti,termination-current",
+		ret = device_property_read_u32(bq->dev,
+				"ti,termination-current",
 				&bq->init_data.termination_current);
 		if (ret)
 			goto error_3;
-		ret = of_property_read_u32(np, "ti,resistor-sense",
-				&bq->init_data.resistor_sense);
+		ret = device_property_read_u32(bq->dev,
+					       "ti,resistor-sense",
+					       &bq->init_data.resistor_sense);
 		if (ret)
 			goto error_3;
 	} else {
@@ -1648,7 +1666,8 @@ static int bq2415x_probe(struct i2c_client *client,
 		}
 
 		/* Query for initial reported_mode and set it */
-		bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy);
+		bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED,
+				      bq->notify_psy);
 		bq2415x_set_mode(bq, bq->reported_mode);
 
 		bq->automode = 1;
@@ -1727,9 +1746,28 @@ static const struct i2c_device_id bq2415x_i2c_id_table[] = {
 };
 MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
 
+static const struct acpi_device_id bq2415x_i2c_acpi_match[] = {
+	{ "BQ2415X", BQUNKNOWN },
+	{ "BQ241500", BQ24150 },
+	{ "BQA24150", BQ24150A },
+	{ "BQ241510", BQ24151 },
+	{ "BQA24151", BQ24151A },
+	{ "BQ241520", BQ24152 },
+	{ "BQ241530", BQ24153 },
+	{ "BQA24153", BQ24153A },
+	{ "BQ241550", BQ24155 },
+	{ "BQ241560", BQ24156 },
+	{ "BQA24156", BQ24156A },
+	{ "BQS24157", BQ24157S },
+	{ "BQ241580", BQ24158 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match);
+
 static struct i2c_driver bq2415x_driver = {
 	.driver = {
 		.name = "bq2415x-charger",
+		.acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match),
 	},
 	.probe = bq2415x_probe,
 	.remove = bq2415x_remove,
diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c
index 407c4af83891..052db78c3736 100644
--- a/drivers/power/bq24190_charger.c
+++ b/drivers/power/bq24190_charger.c
@@ -1258,10 +1258,13 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
 	 * register reset so we should ignore that one (the very first
 	 * interrupt received).
 	 */
-	if (alert_userspace && !bdi->first_time) {
-		power_supply_changed(bdi->charger);
-		power_supply_changed(bdi->battery);
-		bdi->first_time = false;
+	if (alert_userspace) {
+		if (!bdi->first_time) {
+			power_supply_changed(bdi->charger);
+			power_supply_changed(bdi->battery);
+		} else {
+			bdi->first_time = false;
+		}
 	}
 
 out:
diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c
new file mode 100644
index 000000000000..5859bc7c1616
--- /dev/null
+++ b/drivers/power/bq24257_charger.c
@@ -0,0 +1,858 @@
+/*
+ * TI BQ24257 charger driver
+ *
+ * Copyright (C) 2015 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <linux/acpi.h>
+#include <linux/of.h>
+
+#define BQ24257_REG_1			0x00
+#define BQ24257_REG_2			0x01
+#define BQ24257_REG_3			0x02
+#define BQ24257_REG_4			0x03
+#define BQ24257_REG_5			0x04
+#define BQ24257_REG_6			0x05
+#define BQ24257_REG_7			0x06
+
+#define BQ24257_MANUFACTURER		"Texas Instruments"
+#define BQ24257_STAT_IRQ		"stat"
+#define BQ24257_PG_GPIO			"pg"
+
+#define BQ24257_ILIM_SET_DELAY		1000	/* msec */
+
+enum bq24257_fields {
+	F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT,			    /* REG 1 */
+	F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE,  /* REG 2 */
+	F_VBAT, F_USB_DET,					    /* REG 3 */
+	F_ICHG, F_ITERM,					    /* REG 4 */
+	F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */
+	F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_STAT,		    /* REG 6 */
+	F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM,		    /* REG 7 */
+
+	F_MAX_FIELDS
+};
+
+/* initial field values, converted from uV/uA */
+struct bq24257_init_data {
+	u8 ichg;	/* charge current      */
+	u8 vbat;	/* regulation voltage  */
+	u8 iterm;	/* termination current */
+};
+
+struct bq24257_state {
+	u8 status;
+	u8 fault;
+	bool power_good;
+};
+
+struct bq24257_device {
+	struct i2c_client *client;
+	struct device *dev;
+	struct power_supply *charger;
+
+	struct regmap *rmap;
+	struct regmap_field *rmap_fields[F_MAX_FIELDS];
+
+	struct gpio_desc *pg;
+
+	struct delayed_work iilimit_setup_work;
+
+	struct bq24257_init_data init_data;
+	struct bq24257_state state;
+
+	struct mutex lock; /* protect state data */
+};
+
+static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case BQ24257_REG_2:
+	case BQ24257_REG_4:
+		return false;
+
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config bq24257_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = BQ24257_REG_7,
+	.cache_type = REGCACHE_RBTREE,
+
+	.volatile_reg = bq24257_is_volatile_reg,
+};
+
+static const struct reg_field bq24257_reg_fields[] = {
+	/* REG 1 */
+	[F_WD_FAULT]		= REG_FIELD(BQ24257_REG_1, 7, 7),
+	[F_WD_EN]		= REG_FIELD(BQ24257_REG_1, 6, 6),
+	[F_STAT]		= REG_FIELD(BQ24257_REG_1, 4, 5),
+	[F_FAULT]		= REG_FIELD(BQ24257_REG_1, 0, 3),
+	/* REG 2 */
+	[F_RESET]		= REG_FIELD(BQ24257_REG_2, 7, 7),
+	[F_IILIMIT]		= REG_FIELD(BQ24257_REG_2, 4, 6),
+	[F_EN_STAT]		= REG_FIELD(BQ24257_REG_2, 3, 3),
+	[F_EN_TERM]		= REG_FIELD(BQ24257_REG_2, 2, 2),
+	[F_CE]			= REG_FIELD(BQ24257_REG_2, 1, 1),
+	[F_HZ_MODE]		= REG_FIELD(BQ24257_REG_2, 0, 0),
+	/* REG 3 */
+	[F_VBAT]		= REG_FIELD(BQ24257_REG_3, 2, 7),
+	[F_USB_DET]		= REG_FIELD(BQ24257_REG_3, 0, 1),
+	/* REG 4 */
+	[F_ICHG]		= REG_FIELD(BQ24257_REG_4, 3, 7),
+	[F_ITERM]		= REG_FIELD(BQ24257_REG_4, 0, 2),
+	/* REG 5 */
+	[F_LOOP_STATUS]		= REG_FIELD(BQ24257_REG_5, 6, 7),
+	[F_LOW_CHG]		= REG_FIELD(BQ24257_REG_5, 5, 5),
+	[F_DPDM_EN]		= REG_FIELD(BQ24257_REG_5, 4, 4),
+	[F_CE_STATUS]		= REG_FIELD(BQ24257_REG_5, 3, 3),
+	[F_VINDPM]		= REG_FIELD(BQ24257_REG_5, 0, 2),
+	/* REG 6 */
+	[F_X2_TMR_EN]		= REG_FIELD(BQ24257_REG_6, 7, 7),
+	[F_TMR]			= REG_FIELD(BQ24257_REG_6, 5, 6),
+	[F_SYSOFF]		= REG_FIELD(BQ24257_REG_6, 4, 4),
+	[F_TS_STAT]		= REG_FIELD(BQ24257_REG_6, 0, 2),
+	/* REG 7 */
+	[F_VOVP]		= REG_FIELD(BQ24257_REG_7, 5, 7),
+	[F_CLR_VDP]		= REG_FIELD(BQ24257_REG_7, 4, 4),
+	[F_FORCE_BATDET]	= REG_FIELD(BQ24257_REG_7, 3, 3),
+	[F_FORCE_PTM]		= REG_FIELD(BQ24257_REG_7, 2, 2)
+};
+
+static const u32 bq24257_vbat_map[] = {
+	3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000,
+	3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000,
+	3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000,
+	3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000,
+	4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000,
+	4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000
+};
+
+#define BQ24257_VBAT_MAP_SIZE		ARRAY_SIZE(bq24257_vbat_map)
+
+static const u32 bq24257_ichg_map[] = {
+	500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000,
+	950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000,
+	1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000,
+	1750000, 1800000, 1850000, 1900000, 1950000, 2000000
+};
+
+#define BQ24257_ICHG_MAP_SIZE		ARRAY_SIZE(bq24257_ichg_map)
+
+static const u32 bq24257_iterm_map[] = {
+	50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000
+};
+
+#define BQ24257_ITERM_MAP_SIZE		ARRAY_SIZE(bq24257_iterm_map)
+
+static int bq24257_field_read(struct bq24257_device *bq,
+			      enum bq24257_fields field_id)
+{
+	int ret;
+	int val;
+
+	ret = regmap_field_read(bq->rmap_fields[field_id], &val);
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int bq24257_field_write(struct bq24257_device *bq,
+			       enum bq24257_fields field_id, u8 val)
+{
+	return regmap_field_write(bq->rmap_fields[field_id], val);
+}
+
+static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size)
+{
+	u8 idx;
+
+	for (idx = 1; idx < map_size; idx++)
+		if (value < map[idx])
+			break;
+
+	return idx - 1;
+}
+
+enum bq24257_status {
+	STATUS_READY,
+	STATUS_CHARGE_IN_PROGRESS,
+	STATUS_CHARGE_DONE,
+	STATUS_FAULT,
+};
+
+enum bq24257_fault {
+	FAULT_NORMAL,
+	FAULT_INPUT_OVP,
+	FAULT_INPUT_UVLO,
+	FAULT_SLEEP,
+	FAULT_BAT_TS,
+	FAULT_BAT_OVP,
+	FAULT_TS,
+	FAULT_TIMER,
+	FAULT_NO_BAT,
+	FAULT_ISET,
+	FAULT_INPUT_LDO_LOW,
+};
+
+static int bq24257_power_supply_get_property(struct power_supply *psy,
+					     enum power_supply_property psp,
+					     union power_supply_propval *val)
+{
+	struct bq24257_device *bq = power_supply_get_drvdata(psy);
+	struct bq24257_state state;
+
+	mutex_lock(&bq->lock);
+	state = bq->state;
+	mutex_unlock(&bq->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!state.power_good)
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		else if (state.status == STATUS_READY)
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		else if (state.status == STATUS_CHARGE_IN_PROGRESS)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else if (state.status == STATUS_CHARGE_DONE)
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = BQ24257_MANUFACTURER;
+		break;
+
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = state.power_good;
+		break;
+
+	case POWER_SUPPLY_PROP_HEALTH:
+		switch (state.fault) {
+		case FAULT_NORMAL:
+			val->intval = POWER_SUPPLY_HEALTH_GOOD;
+			break;
+
+		case FAULT_INPUT_OVP:
+		case FAULT_BAT_OVP:
+			val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+			break;
+
+		case FAULT_TS:
+		case FAULT_BAT_TS:
+			val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+			break;
+
+		case FAULT_TIMER:
+			val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+			break;
+
+		default:
+			val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+			break;
+		}
+
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		val->intval = bq24257_ichg_map[bq->init_data.ichg];
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1];
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		val->intval = bq24257_vbat_map[bq->init_data.vbat];
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1];
+		break;
+
+	case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+		val->intval = bq24257_iterm_map[bq->init_data.iterm];
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bq24257_get_chip_state(struct bq24257_device *bq,
+				  struct bq24257_state *state)
+{
+	int ret;
+
+	ret = bq24257_field_read(bq, F_STAT);
+	if (ret < 0)
+		return ret;
+
+	state->status = ret;
+
+	ret = bq24257_field_read(bq, F_FAULT);
+	if (ret < 0)
+		return ret;
+
+	state->fault = ret;
+
+	state->power_good = !gpiod_get_value_cansleep(bq->pg);
+
+	return 0;
+}
+
+static bool bq24257_state_changed(struct bq24257_device *bq,
+				  struct bq24257_state *new_state)
+{
+	int ret;
+
+	mutex_lock(&bq->lock);
+	ret = (bq->state.status != new_state->status ||
+	       bq->state.fault != new_state->fault ||
+	       bq->state.power_good != new_state->power_good);
+	mutex_unlock(&bq->lock);
+
+	return ret;
+}
+
+enum bq24257_loop_status {
+	LOOP_STATUS_NONE,
+	LOOP_STATUS_IN_DPM,
+	LOOP_STATUS_IN_CURRENT_LIMIT,
+	LOOP_STATUS_THERMAL,
+};
+
+enum bq24257_in_ilimit {
+	IILIMIT_100,
+	IILIMIT_150,
+	IILIMIT_500,
+	IILIMIT_900,
+	IILIMIT_1500,
+	IILIMIT_2000,
+	IILIMIT_EXT,
+	IILIMIT_NONE,
+};
+
+enum bq24257_port_type {
+	PORT_TYPE_DCP,		/* Dedicated Charging Port */
+	PORT_TYPE_CDP,		/* Charging Downstream Port */
+	PORT_TYPE_SDP,		/* Standard Downstream Port */
+	PORT_TYPE_NON_STANDARD,
+};
+
+enum bq24257_safety_timer {
+	SAFETY_TIMER_45,
+	SAFETY_TIMER_360,
+	SAFETY_TIMER_540,
+	SAFETY_TIMER_NONE,
+};
+
+static int bq24257_iilimit_autoset(struct bq24257_device *bq)
+{
+	int loop_status;
+	int iilimit;
+	int port_type;
+	int ret;
+	const u8 new_iilimit[] = {
+		[PORT_TYPE_DCP] = IILIMIT_2000,
+		[PORT_TYPE_CDP] = IILIMIT_2000,
+		[PORT_TYPE_SDP] = IILIMIT_500,
+		[PORT_TYPE_NON_STANDARD] = IILIMIT_500
+	};
+
+	ret = bq24257_field_read(bq, F_LOOP_STATUS);
+	if (ret < 0)
+		goto error;
+
+	loop_status = ret;
+
+	ret = bq24257_field_read(bq, F_IILIMIT);
+	if (ret < 0)
+		goto error;
+
+	iilimit = ret;
+
+	/*
+	 * All USB ports should be able to handle 500mA. If not, DPM will lower
+	 * the charging current to accommodate the power source. No need to set
+	 * a lower IILIMIT value.
+	 */
+	if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500)
+		return 0;
+
+	ret = bq24257_field_read(bq, F_USB_DET);
+	if (ret < 0)
+		goto error;
+
+	port_type = ret;
+
+	ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]);
+	if (ret < 0)
+		goto error;
+
+	ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360);
+	if (ret < 0)
+		goto error;
+
+	ret = bq24257_field_write(bq, F_CLR_VDP, 1);
+	if (ret < 0)
+		goto error;
+
+	dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n",
+		port_type, loop_status, new_iilimit[port_type]);
+
+	return 0;
+
+error:
+	dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
+	return ret;
+}
+
+static void bq24257_iilimit_setup_work(struct work_struct *work)
+{
+	struct bq24257_device *bq = container_of(work, struct bq24257_device,
+						 iilimit_setup_work.work);
+
+	bq24257_iilimit_autoset(bq);
+}
+
+static void bq24257_handle_state_change(struct bq24257_device *bq,
+					struct bq24257_state *new_state)
+{
+	int ret;
+	struct bq24257_state old_state;
+	bool reset_iilimit = false;
+	bool config_iilimit = false;
+
+	mutex_lock(&bq->lock);
+	old_state = bq->state;
+	mutex_unlock(&bq->lock);
+
+	if (!new_state->power_good) {			     /* power removed */
+		cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+		/* activate D+/D- port detection algorithm */
+		ret = bq24257_field_write(bq, F_DPDM_EN, 1);
+		if (ret < 0)
+			goto error;
+
+		reset_iilimit = true;
+	} else if (!old_state.power_good) {		    /* power inserted */
+		config_iilimit = true;
+	} else if (new_state->fault == FAULT_NO_BAT) {	   /* battery removed */
+		cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+		reset_iilimit = true;
+	} else if (old_state.fault == FAULT_NO_BAT) {    /* battery connected */
+		config_iilimit = true;
+	} else if (new_state->fault == FAULT_TIMER) { /* safety timer expired */
+		dev_err(bq->dev, "Safety timer expired! Battery dead?\n");
+	}
+
+	if (reset_iilimit) {
+		ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500);
+		if (ret < 0)
+			goto error;
+	} else if (config_iilimit) {
+		schedule_delayed_work(&bq->iilimit_setup_work,
+				      msecs_to_jiffies(BQ24257_ILIM_SET_DELAY));
+	}
+
+	return;
+
+error:
+	dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
+}
+
+static irqreturn_t bq24257_irq_handler_thread(int irq, void *private)
+{
+	int ret;
+	struct bq24257_device *bq = private;
+	struct bq24257_state state;
+
+	ret = bq24257_get_chip_state(bq, &state);
+	if (ret < 0)
+		return IRQ_HANDLED;
+
+	if (!bq24257_state_changed(bq, &state))
+		return IRQ_HANDLED;
+
+	dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n",
+		state.status, state.fault, state.power_good);
+
+	bq24257_handle_state_change(bq, &state);
+
+	mutex_lock(&bq->lock);
+	bq->state = state;
+	mutex_unlock(&bq->lock);
+
+	power_supply_changed(bq->charger);
+
+	return IRQ_HANDLED;
+}
+
+static int bq24257_hw_init(struct bq24257_device *bq)
+{
+	int ret;
+	int i;
+	struct bq24257_state state;
+
+	const struct {
+		int field;
+		u32 value;
+	} init_data[] = {
+		{F_ICHG, bq->init_data.ichg},
+		{F_VBAT, bq->init_data.vbat},
+		{F_ITERM, bq->init_data.iterm}
+	};
+
+	/*
+	 * Disable the watchdog timer to prevent the IC from going back to
+	 * default settings after 50 seconds of I2C inactivity.
+	 */
+	ret = bq24257_field_write(bq, F_WD_EN, 0);
+	if (ret < 0)
+		return ret;
+
+	/* configure the charge currents and voltages */
+	for (i = 0; i < ARRAY_SIZE(init_data); i++) {
+		ret = bq24257_field_write(bq, init_data[i].field,
+					  init_data[i].value);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = bq24257_get_chip_state(bq, &state);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&bq->lock);
+	bq->state = state;
+	mutex_unlock(&bq->lock);
+
+	if (!state.power_good)
+		/* activate D+/D- detection algorithm */
+		ret = bq24257_field_write(bq, F_DPDM_EN, 1);
+	else if (state.fault != FAULT_NO_BAT)
+		ret = bq24257_iilimit_autoset(bq);
+
+	return ret;
+}
+
+static enum power_supply_property bq24257_power_supply_props[] = {
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+};
+
+static char *bq24257_charger_supplied_to[] = {
+	"main-battery",
+};
+
+static const struct power_supply_desc bq24257_power_supply_desc = {
+	.name = "bq24257-charger",
+	.type = POWER_SUPPLY_TYPE_USB,
+	.properties = bq24257_power_supply_props,
+	.num_properties = ARRAY_SIZE(bq24257_power_supply_props),
+	.get_property = bq24257_power_supply_get_property,
+};
+
+static int bq24257_power_supply_init(struct bq24257_device *bq)
+{
+	struct power_supply_config psy_cfg = { .drv_data = bq, };
+
+	psy_cfg.supplied_to = bq24257_charger_supplied_to;
+	psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to);
+
+	bq->charger = power_supply_register(bq->dev, &bq24257_power_supply_desc,
+					    &psy_cfg);
+	if (IS_ERR(bq->charger))
+		return PTR_ERR(bq->charger);
+
+	return 0;
+}
+
+static int bq24257_irq_probe(struct bq24257_device *bq)
+{
+	struct gpio_desc *stat_irq;
+
+	stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0, GPIOD_IN);
+	if (IS_ERR(stat_irq)) {
+		dev_err(bq->dev, "could not probe stat_irq pin\n");
+		return PTR_ERR(stat_irq);
+	}
+
+	return gpiod_to_irq(stat_irq);
+}
+
+static int bq24257_pg_gpio_probe(struct bq24257_device *bq)
+{
+	bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN);
+	if (IS_ERR(bq->pg)) {
+		dev_err(bq->dev, "could not probe PG pin\n");
+		return PTR_ERR(bq->pg);
+	}
+
+	return 0;
+}
+
+static int bq24257_fw_probe(struct bq24257_device *bq)
+{
+	int ret;
+	u32 property;
+
+	ret = device_property_read_u32(bq->dev, "ti,charge-current", &property);
+	if (ret < 0)
+		return ret;
+
+	bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map,
+					      BQ24257_ICHG_MAP_SIZE);
+
+	ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage",
+				       &property);
+	if (ret < 0)
+		return ret;
+
+	bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map,
+					      BQ24257_VBAT_MAP_SIZE);
+
+	ret = device_property_read_u32(bq->dev, "ti,termination-current",
+				       &property);
+	if (ret < 0)
+		return ret;
+
+	bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map,
+					       BQ24257_ITERM_MAP_SIZE);
+
+	return 0;
+}
+
+static int bq24257_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct device *dev = &client->dev;
+	struct bq24257_device *bq;
+	int ret;
+	int i;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+		return -ENODEV;
+	}
+
+	bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
+	if (!bq)
+		return -ENOMEM;
+
+	bq->client = client;
+	bq->dev = dev;
+
+	mutex_init(&bq->lock);
+
+	bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config);
+	if (IS_ERR(bq->rmap)) {
+		dev_err(dev, "failed to allocate register map\n");
+		return PTR_ERR(bq->rmap);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) {
+		const struct reg_field *reg_fields = bq24257_reg_fields;
+
+		bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
+							     reg_fields[i]);
+		if (IS_ERR(bq->rmap_fields[i])) {
+			dev_err(dev, "cannot allocate regmap field\n");
+			return PTR_ERR(bq->rmap_fields[i]);
+		}
+	}
+
+	i2c_set_clientdata(client, bq);
+
+	INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work);
+
+	if (!dev->platform_data) {
+		ret = bq24257_fw_probe(bq);
+		if (ret < 0) {
+			dev_err(dev, "Cannot read device properties.\n");
+			return ret;
+		}
+	} else {
+		return -ENODEV;
+	}
+
+	/* we can only check Power Good status by probing the PG pin */
+	ret = bq24257_pg_gpio_probe(bq);
+	if (ret < 0)
+		return ret;
+
+	/* reset all registers to defaults */
+	ret = bq24257_field_write(bq, F_RESET, 1);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Put the RESET bit back to 0, in cache. For some reason the HW always
+	 * returns 1 on this bit, so this is the only way to avoid resetting the
+	 * chip every time we update another field in this register.
+	 */
+	ret = bq24257_field_write(bq, F_RESET, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = bq24257_hw_init(bq);
+	if (ret < 0) {
+		dev_err(dev, "Cannot initialize the chip.\n");
+		return ret;
+	}
+
+	if (client->irq <= 0)
+		client->irq = bq24257_irq_probe(bq);
+
+	if (client->irq < 0) {
+		dev_err(dev, "no irq resource found\n");
+		return client->irq;
+	}
+
+	ret = devm_request_threaded_irq(dev, client->irq, NULL,
+					bq24257_irq_handler_thread,
+					IRQF_TRIGGER_FALLING |
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					BQ24257_STAT_IRQ, bq);
+	if (ret)
+		return ret;
+
+	ret = bq24257_power_supply_init(bq);
+	if (ret < 0)
+		dev_err(dev, "Failed to register power supply\n");
+
+	return ret;
+}
+
+static int bq24257_remove(struct i2c_client *client)
+{
+	struct bq24257_device *bq = i2c_get_clientdata(client);
+
+	cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+	power_supply_unregister(bq->charger);
+
+	bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq24257_suspend(struct device *dev)
+{
+	struct bq24257_device *bq = dev_get_drvdata(dev);
+	int ret = 0;
+
+	cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+	/* reset all registers to default (and activate standalone mode) */
+	ret = bq24257_field_write(bq, F_RESET, 1);
+	if (ret < 0)
+		dev_err(bq->dev, "Cannot reset chip to standalone mode.\n");
+
+	return ret;
+}
+
+static int bq24257_resume(struct device *dev)
+{
+	int ret;
+	struct bq24257_device *bq = dev_get_drvdata(dev);
+
+	ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7);
+	if (ret < 0)
+		return ret;
+
+	ret = bq24257_field_write(bq, F_RESET, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = bq24257_hw_init(bq);
+	if (ret < 0) {
+		dev_err(bq->dev, "Cannot init chip after resume.\n");
+		return ret;
+	}
+
+	/* signal userspace, maybe state changed while suspended */
+	power_supply_changed(bq->charger);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops bq24257_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume)
+};
+
+static const struct i2c_device_id bq24257_i2c_ids[] = {
+	{ "bq24257", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids);
+
+static const struct of_device_id bq24257_of_match[] = {
+	{ .compatible = "ti,bq24257", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, bq24257_of_match);
+
+static const struct acpi_device_id bq24257_acpi_match[] = {
+	{"BQ242570", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match);
+
+static struct i2c_driver bq24257_driver = {
+	.driver = {
+		.name = "bq24257-charger",
+		.of_match_table = of_match_ptr(bq24257_of_match),
+		.acpi_match_table = ACPI_PTR(bq24257_acpi_match),
+		.pm = &bq24257_pm,
+	},
+	.probe = bq24257_probe,
+	.remove = bq24257_remove,
+	.id_table = bq24257_i2c_ids,
+};
+module_i2c_driver(bq24257_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION("bq24257 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c
new file mode 100644
index 000000000000..f993a55cde20
--- /dev/null
+++ b/drivers/power/bq25890_charger.c
@@ -0,0 +1,994 @@
+/*
+ * TI BQ25890 charger driver
+ *
+ * Copyright (C) 2015 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/usb/phy.h>
+
+#include <linux/acpi.h>
+#include <linux/of.h>
+
+#define BQ25890_MANUFACTURER		"Texas Instruments"
+#define BQ25890_IRQ_PIN			"bq25890_irq"
+
+#define BQ25890_ID			3
+
+enum bq25890_fields {
+	F_EN_HIZ, F_EN_ILIM, F_IILIM,				     /* Reg00 */
+	F_BHOT, F_BCOLD, F_VINDPM_OFS,				     /* Reg01 */
+	F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
+	F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN,	     /* Reg02 */
+	F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN,    /* Reg03 */
+	F_PUMPX_EN, F_ICHG,					     /* Reg04 */
+	F_IPRECHG, F_ITERM,					     /* Reg05 */
+	F_VREG, F_BATLOWV, F_VRECHG,				     /* Reg06 */
+	F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR,
+	F_JEITA_ISET,						     /* Reg07 */
+	F_BATCMP, F_VCLAMP, F_TREG,				     /* Reg08 */
+	F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET,
+	F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN,	     /* Reg09 */
+	F_BOOSTV, F_BOOSTI,					     /* Reg0A */
+	F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */
+	F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT,
+	F_NTC_FAULT,						     /* Reg0C */
+	F_FORCE_VINDPM, F_VINDPM,				     /* Reg0D */
+	F_THERM_STAT, F_BATV,					     /* Reg0E */
+	F_SYSV,							     /* Reg0F */
+	F_TSPCT,						     /* Reg10 */
+	F_VBUS_GD, F_VBUSV,					     /* Reg11 */
+	F_ICHGR,						     /* Reg12 */
+	F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM,			     /* Reg13 */
+	F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV,   /* Reg14 */
+
+	F_MAX_FIELDS
+};
+
+/* initial field values, converted to register values */
+struct bq25890_init_data {
+	u8 ichg;	/* charge current		*/
+	u8 vreg;	/* regulation voltage		*/
+	u8 iterm;	/* termination current		*/
+	u8 iprechg;	/* precharge current		*/
+	u8 sysvmin;	/* minimum system voltage limit */
+	u8 boostv;	/* boost regulation voltage	*/
+	u8 boosti;	/* boost current limit		*/
+	u8 boostf;	/* boost frequency		*/
+	u8 ilim_en;	/* enable ILIM pin		*/
+	u8 treg;	/* thermal regulation threshold */
+};
+
+struct bq25890_state {
+	u8 online;
+	u8 chrg_status;
+	u8 chrg_fault;
+	u8 vsys_status;
+	u8 boost_fault;
+	u8 bat_fault;
+};
+
+struct bq25890_device {
+	struct i2c_client *client;
+	struct device *dev;
+	struct power_supply *charger;
+
+	struct usb_phy *usb_phy;
+	struct notifier_block usb_nb;
+	struct work_struct usb_work;
+	unsigned long usb_event;
+
+	struct regmap *rmap;
+	struct regmap_field *rmap_fields[F_MAX_FIELDS];
+
+	int chip_id;
+	struct bq25890_init_data init_data;
+	struct bq25890_state state;
+
+	struct mutex lock; /* protect state data */
+};
+
+static const struct regmap_range bq25890_readonly_reg_ranges[] = {
+	regmap_reg_range(0x0b, 0x0c),
+	regmap_reg_range(0x0e, 0x13),
+};
+
+static const struct regmap_access_table bq25890_writeable_regs = {
+	.no_ranges = bq25890_readonly_reg_ranges,
+	.n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges),
+};
+
+static const struct regmap_range bq25890_volatile_reg_ranges[] = {
+	regmap_reg_range(0x00, 0x00),
+	regmap_reg_range(0x09, 0x09),
+	regmap_reg_range(0x0b, 0x0c),
+	regmap_reg_range(0x0e, 0x14),
+};
+
+static const struct regmap_access_table bq25890_volatile_regs = {
+	.yes_ranges = bq25890_volatile_reg_ranges,
+	.n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges),
+};
+
+static const struct regmap_config bq25890_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 0x14,
+	.cache_type = REGCACHE_RBTREE,
+
+	.wr_table = &bq25890_writeable_regs,
+	.volatile_table = &bq25890_volatile_regs,
+};
+
+static const struct reg_field bq25890_reg_fields[] = {
+	/* REG00 */
+	[F_EN_HIZ]		= REG_FIELD(0x00, 7, 7),
+	[F_EN_ILIM]		= REG_FIELD(0x00, 6, 6),
+	[F_IILIM]		= REG_FIELD(0x00, 0, 5),
+	/* REG01 */
+	[F_BHOT]		= REG_FIELD(0x01, 6, 7),
+	[F_BCOLD]		= REG_FIELD(0x01, 5, 5),
+	[F_VINDPM_OFS]		= REG_FIELD(0x01, 0, 4),
+	/* REG02 */
+	[F_CONV_START]		= REG_FIELD(0x02, 7, 7),
+	[F_CONV_RATE]		= REG_FIELD(0x02, 6, 6),
+	[F_BOOSTF]		= REG_FIELD(0x02, 5, 5),
+	[F_ICO_EN]		= REG_FIELD(0x02, 4, 4),
+	[F_HVDCP_EN]		= REG_FIELD(0x02, 3, 3),
+	[F_MAXC_EN]		= REG_FIELD(0x02, 2, 2),
+	[F_FORCE_DPM]		= REG_FIELD(0x02, 1, 1),
+	[F_AUTO_DPDM_EN]	= REG_FIELD(0x02, 0, 0),
+	/* REG03 */
+	[F_BAT_LOAD_EN]		= REG_FIELD(0x03, 7, 7),
+	[F_WD_RST]		= REG_FIELD(0x03, 6, 6),
+	[F_OTG_CFG]		= REG_FIELD(0x03, 5, 5),
+	[F_CHG_CFG]		= REG_FIELD(0x03, 4, 4),
+	[F_SYSVMIN]		= REG_FIELD(0x03, 1, 3),
+	/* REG04 */
+	[F_PUMPX_EN]		= REG_FIELD(0x04, 7, 7),
+	[F_ICHG]		= REG_FIELD(0x04, 0, 6),
+	/* REG05 */
+	[F_IPRECHG]		= REG_FIELD(0x05, 4, 7),
+	[F_ITERM]		= REG_FIELD(0x05, 0, 3),
+	/* REG06 */
+	[F_VREG]		= REG_FIELD(0x06, 2, 7),
+	[F_BATLOWV]		= REG_FIELD(0x06, 1, 1),
+	[F_VRECHG]		= REG_FIELD(0x06, 0, 0),
+	/* REG07 */
+	[F_TERM_EN]		= REG_FIELD(0x07, 7, 7),
+	[F_STAT_DIS]		= REG_FIELD(0x07, 6, 6),
+	[F_WD]			= REG_FIELD(0x07, 4, 5),
+	[F_TMR_EN]		= REG_FIELD(0x07, 3, 3),
+	[F_CHG_TMR]		= REG_FIELD(0x07, 1, 2),
+	[F_JEITA_ISET]		= REG_FIELD(0x07, 0, 0),
+	/* REG08 */
+	[F_BATCMP]		= REG_FIELD(0x08, 6, 7),
+	[F_VCLAMP]		= REG_FIELD(0x08, 2, 4),
+	[F_TREG]		= REG_FIELD(0x08, 0, 1),
+	/* REG09 */
+	[F_FORCE_ICO]		= REG_FIELD(0x09, 7, 7),
+	[F_TMR2X_EN]		= REG_FIELD(0x09, 6, 6),
+	[F_BATFET_DIS]		= REG_FIELD(0x09, 5, 5),
+	[F_JEITA_VSET]		= REG_FIELD(0x09, 4, 4),
+	[F_BATFET_DLY]		= REG_FIELD(0x09, 3, 3),
+	[F_BATFET_RST_EN]	= REG_FIELD(0x09, 2, 2),
+	[F_PUMPX_UP]		= REG_FIELD(0x09, 1, 1),
+	[F_PUMPX_DN]		= REG_FIELD(0x09, 0, 0),
+	/* REG0A */
+	[F_BOOSTV]		= REG_FIELD(0x0A, 4, 7),
+	[F_BOOSTI]		= REG_FIELD(0x0A, 0, 2),
+	/* REG0B */
+	[F_VBUS_STAT]		= REG_FIELD(0x0B, 5, 7),
+	[F_CHG_STAT]		= REG_FIELD(0x0B, 3, 4),
+	[F_PG_STAT]		= REG_FIELD(0x0B, 2, 2),
+	[F_SDP_STAT]		= REG_FIELD(0x0B, 1, 1),
+	[F_VSYS_STAT]		= REG_FIELD(0x0B, 0, 0),
+	/* REG0C */
+	[F_WD_FAULT]		= REG_FIELD(0x0C, 7, 7),
+	[F_BOOST_FAULT]		= REG_FIELD(0x0C, 6, 6),
+	[F_CHG_FAULT]		= REG_FIELD(0x0C, 4, 5),
+	[F_BAT_FAULT]		= REG_FIELD(0x0C, 3, 3),
+	[F_NTC_FAULT]		= REG_FIELD(0x0C, 0, 2),
+	/* REG0D */
+	[F_FORCE_VINDPM]	= REG_FIELD(0x0D, 7, 7),
+	[F_VINDPM]		= REG_FIELD(0x0D, 0, 6),
+	/* REG0E */
+	[F_THERM_STAT]		= REG_FIELD(0x0E, 7, 7),
+	[F_BATV]		= REG_FIELD(0x0E, 0, 6),
+	/* REG0F */
+	[F_SYSV]		= REG_FIELD(0x0F, 0, 6),
+	/* REG10 */
+	[F_TSPCT]		= REG_FIELD(0x10, 0, 6),
+	/* REG11 */
+	[F_VBUS_GD]		= REG_FIELD(0x11, 7, 7),
+	[F_VBUSV]		= REG_FIELD(0x11, 0, 6),
+	/* REG12 */
+	[F_ICHGR]		= REG_FIELD(0x12, 0, 6),
+	/* REG13 */
+	[F_VDPM_STAT]		= REG_FIELD(0x13, 7, 7),
+	[F_IDPM_STAT]		= REG_FIELD(0x13, 6, 6),
+	[F_IDPM_LIM]		= REG_FIELD(0x13, 0, 5),
+	/* REG14 */
+	[F_REG_RST]		= REG_FIELD(0x14, 7, 7),
+	[F_ICO_OPTIMIZED]	= REG_FIELD(0x14, 6, 6),
+	[F_PN]			= REG_FIELD(0x14, 3, 5),
+	[F_TS_PROFILE]		= REG_FIELD(0x14, 2, 2),
+	[F_DEV_REV]		= REG_FIELD(0x14, 0, 1)
+};
+
+/*
+ * Most of the val -> idx conversions can be computed, given the minimum,
+ * maximum and the step between values. For the rest of conversions, we use
+ * lookup tables.
+ */
+enum bq25890_table_ids {
+	/* range tables */
+	TBL_ICHG,
+	TBL_ITERM,
+	TBL_IPRECHG,
+	TBL_VREG,
+	TBL_BATCMP,
+	TBL_VCLAMP,
+	TBL_BOOSTV,
+	TBL_SYSVMIN,
+
+	/* lookup tables */
+	TBL_TREG,
+	TBL_BOOSTI,
+};
+
+/* Thermal Regulation Threshold lookup table, in degrees Celsius */
+static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 };
+
+#define BQ25890_TREG_TBL_SIZE		ARRAY_SIZE(bq25890_treg_tbl)
+
+/* Boost mode current limit lookup table, in uA */
+static const u32 bq25890_boosti_tbl[] = {
+	500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000
+};
+
+#define BQ25890_BOOSTI_TBL_SIZE		ARRAY_SIZE(bq25890_boosti_tbl)
+
+struct bq25890_range {
+	u32 min;
+	u32 max;
+	u32 step;
+};
+
+struct bq25890_lookup {
+	const u32 *tbl;
+	u32 size;
+};
+
+static const union {
+	struct bq25890_range  rt;
+	struct bq25890_lookup lt;
+} bq25890_tables[] = {
+	/* range tables */
+	[TBL_ICHG] =	{ .rt = {0,	  5056000, 64000} },	 /* uA */
+	[TBL_ITERM] =	{ .rt = {64000,   1024000, 64000} },	 /* uA */
+	[TBL_VREG] =	{ .rt = {3840000, 4608000, 16000} },	 /* uV */
+	[TBL_BATCMP] =	{ .rt = {0,	  140,     20} },	 /* mOhm */
+	[TBL_VCLAMP] =	{ .rt = {0,	  224000,  32000} },	 /* uV */
+	[TBL_BOOSTV] =	{ .rt = {4550000, 5510000, 64000} },	 /* uV */
+	[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} },	 /* uV */
+
+	/* lookup tables */
+	[TBL_TREG] =	{ .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
+	[TBL_BOOSTI] =	{ .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} }
+};
+
+static int bq25890_field_read(struct bq25890_device *bq,
+			      enum bq25890_fields field_id)
+{
+	int ret;
+	int val;
+
+	ret = regmap_field_read(bq->rmap_fields[field_id], &val);
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+static int bq25890_field_write(struct bq25890_device *bq,
+			       enum bq25890_fields field_id, u8 val)
+{
+	return regmap_field_write(bq->rmap_fields[field_id], val);
+}
+
+static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id)
+{
+	u8 idx;
+
+	if (id >= TBL_TREG) {
+		const u32 *tbl = bq25890_tables[id].lt.tbl;
+		u32 tbl_size = bq25890_tables[id].lt.size;
+
+		for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
+			;
+	} else {
+		const struct bq25890_range *rtbl = &bq25890_tables[id].rt;
+		u8 rtbl_size;
+
+		rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
+
+		for (idx = 1;
+		     idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
+		     idx++)
+			;
+	}
+
+	return idx - 1;
+}
+
+static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id)
+{
+	const struct bq25890_range *rtbl;
+
+	/* lookup table? */
+	if (id >= TBL_TREG)
+		return bq25890_tables[id].lt.tbl[idx];
+
+	/* range table */
+	rtbl = &bq25890_tables[id].rt;
+
+	return (rtbl->min + idx * rtbl->step);
+}
+
+enum bq25890_status {
+	STATUS_NOT_CHARGING,
+	STATUS_PRE_CHARGING,
+	STATUS_FAST_CHARGING,
+	STATUS_TERMINATION_DONE,
+};
+
+enum bq25890_chrg_fault {
+	CHRG_FAULT_NORMAL,
+	CHRG_FAULT_INPUT,
+	CHRG_FAULT_THERMAL_SHUTDOWN,
+	CHRG_FAULT_TIMER_EXPIRED,
+};
+
+static int bq25890_power_supply_get_property(struct power_supply *psy,
+					     enum power_supply_property psp,
+					     union power_supply_propval *val)
+{
+	int ret;
+	struct bq25890_device *bq = power_supply_get_drvdata(psy);
+	struct bq25890_state state;
+
+	mutex_lock(&bq->lock);
+	state = bq->state;
+	mutex_unlock(&bq->lock);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!state.online)
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		else if (state.chrg_status == STATUS_NOT_CHARGING)
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		else if (state.chrg_status == STATUS_PRE_CHARGING ||
+			 state.chrg_status == STATUS_FAST_CHARGING)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else if (state.chrg_status == STATUS_TERMINATION_DONE)
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+		break;
+
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = BQ25890_MANUFACTURER;
+		break;
+
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = state.online;
+		break;
+
+	case POWER_SUPPLY_PROP_HEALTH:
+		if (!state.chrg_fault && !state.bat_fault && !state.boost_fault)
+			val->intval = POWER_SUPPLY_HEALTH_GOOD;
+		else if (state.bat_fault)
+			val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED)
+			val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+		else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN)
+			val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+		else
+			val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
+		if (ret < 0)
+			return ret;
+
+		/* converted_val = ADC_val * 50mA (table 10.3.19) */
+		val->intval = ret * 50000;
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		val->intval = bq25890_tables[TBL_ICHG].rt.max;
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		if (!state.online) {
+			val->intval = 0;
+			break;
+		}
+
+		ret = bq25890_field_read(bq, F_BATV); /* read measured value */
+		if (ret < 0)
+			return ret;
+
+		/* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */
+		val->intval = 2304000 + ret * 20000;
+		break;
+
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		val->intval = bq25890_tables[TBL_VREG].rt.max;
+		break;
+
+	case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+		val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bq25890_get_chip_state(struct bq25890_device *bq,
+				  struct bq25890_state *state)
+{
+	int i, ret;
+
+	struct {
+		enum bq25890_fields id;
+		u8 *data;
+	} state_fields[] = {
+		{F_CHG_STAT,	&state->chrg_status},
+		{F_PG_STAT,	&state->online},
+		{F_VSYS_STAT,	&state->vsys_status},
+		{F_BOOST_FAULT, &state->boost_fault},
+		{F_BAT_FAULT,	&state->bat_fault},
+		{F_CHG_FAULT,	&state->chrg_fault}
+	};
+
+	for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
+		ret = bq25890_field_read(bq, state_fields[i].id);
+		if (ret < 0)
+			return ret;
+
+		*state_fields[i].data = ret;
+	}
+
+	dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n",
+		state->chrg_status, state->online, state->vsys_status,
+		state->chrg_fault, state->boost_fault, state->bat_fault);
+
+	return 0;
+}
+
+static bool bq25890_state_changed(struct bq25890_device *bq,
+				  struct bq25890_state *new_state)
+{
+	struct bq25890_state old_state;
+
+	mutex_lock(&bq->lock);
+	old_state = bq->state;
+	mutex_unlock(&bq->lock);
+
+	return (old_state.chrg_status != new_state->chrg_status ||
+		old_state.chrg_fault != new_state->chrg_fault	||
+		old_state.online != new_state->online		||
+		old_state.bat_fault != new_state->bat_fault	||
+		old_state.boost_fault != new_state->boost_fault ||
+		old_state.vsys_status != new_state->vsys_status);
+}
+
+static void bq25890_handle_state_change(struct bq25890_device *bq,
+					struct bq25890_state *new_state)
+{
+	int ret;
+	struct bq25890_state old_state;
+
+	mutex_lock(&bq->lock);
+	old_state = bq->state;
+	mutex_unlock(&bq->lock);
+
+	if (!new_state->online) {			     /* power removed */
+		/* disable ADC */
+		ret = bq25890_field_write(bq, F_CONV_START, 0);
+		if (ret < 0)
+			goto error;
+	} else if (!old_state.online) {			    /* power inserted */
+		/* enable ADC, to have control of charge current/voltage */
+		ret = bq25890_field_write(bq, F_CONV_START, 1);
+		if (ret < 0)
+			goto error;
+	}
+
+	return;
+
+error:
+	dev_err(bq->dev, "Error communicating with the chip.\n");
+}
+
+static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
+{
+	struct bq25890_device *bq = private;
+	int ret;
+	struct bq25890_state state;
+
+	ret = bq25890_get_chip_state(bq, &state);
+	if (ret < 0)
+		goto handled;
+
+	if (!bq25890_state_changed(bq, &state))
+		goto handled;
+
+	bq25890_handle_state_change(bq, &state);
+
+	mutex_lock(&bq->lock);
+	bq->state = state;
+	mutex_unlock(&bq->lock);
+
+	power_supply_changed(bq->charger);
+
+handled:
+	return IRQ_HANDLED;
+}
+
+static int bq25890_chip_reset(struct bq25890_device *bq)
+{
+	int ret;
+	int rst_check_counter = 10;
+
+	ret = bq25890_field_write(bq, F_REG_RST, 1);
+	if (ret < 0)
+		return ret;
+
+	do {
+		ret = bq25890_field_read(bq, F_REG_RST);
+		if (ret < 0)
+			return ret;
+
+		usleep_range(5, 10);
+	} while (ret == 1 && --rst_check_counter);
+
+	if (!rst_check_counter)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int bq25890_hw_init(struct bq25890_device *bq)
+{
+	int ret;
+	int i;
+	struct bq25890_state state;
+
+	const struct {
+		enum bq25890_fields id;
+		u32 value;
+	} init_data[] = {
+		{F_ICHG,	 bq->init_data.ichg},
+		{F_VREG,	 bq->init_data.vreg},
+		{F_ITERM,	 bq->init_data.iterm},
+		{F_IPRECHG,	 bq->init_data.iprechg},
+		{F_SYSVMIN,	 bq->init_data.sysvmin},
+		{F_BOOSTV,	 bq->init_data.boostv},
+		{F_BOOSTI,	 bq->init_data.boosti},
+		{F_BOOSTF,	 bq->init_data.boostf},
+		{F_EN_ILIM,	 bq->init_data.ilim_en},
+		{F_TREG,	 bq->init_data.treg}
+	};
+
+	ret = bq25890_chip_reset(bq);
+	if (ret < 0)
+		return ret;
+
+	/* disable watchdog */
+	ret = bq25890_field_write(bq, F_WD, 0);
+	if (ret < 0)
+		return ret;
+
+	/* initialize currents/voltages and other parameters */
+	for (i = 0; i < ARRAY_SIZE(init_data); i++) {
+		ret = bq25890_field_write(bq, init_data[i].id,
+					  init_data[i].value);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Configure ADC for continuous conversions. This does not enable it. */
+	ret = bq25890_field_write(bq, F_CONV_RATE, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = bq25890_get_chip_state(bq, &state);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&bq->lock);
+	bq->state = state;
+	mutex_unlock(&bq->lock);
+
+	return 0;
+}
+
+static enum power_supply_property bq25890_power_supply_props[] = {
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+};
+
+static char *bq25890_charger_supplied_to[] = {
+	"main-battery",
+};
+
+static const struct power_supply_desc bq25890_power_supply_desc = {
+	.name = "bq25890-charger",
+	.type = POWER_SUPPLY_TYPE_USB,
+	.properties = bq25890_power_supply_props,
+	.num_properties = ARRAY_SIZE(bq25890_power_supply_props),
+	.get_property = bq25890_power_supply_get_property,
+};
+
+static int bq25890_power_supply_init(struct bq25890_device *bq)
+{
+	struct power_supply_config psy_cfg = { .drv_data = bq, };
+
+	psy_cfg.supplied_to = bq25890_charger_supplied_to;
+	psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to);
+
+	bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc,
+					    &psy_cfg);
+
+	return PTR_ERR_OR_ZERO(bq->charger);
+}
+
+static void bq25890_usb_work(struct work_struct *data)
+{
+	int ret;
+	struct bq25890_device *bq =
+			container_of(data, struct bq25890_device, usb_work);
+
+	switch (bq->usb_event) {
+	case USB_EVENT_ID:
+		/* Enable boost mode */
+		ret = bq25890_field_write(bq, F_OTG_CFG, 1);
+		if (ret < 0)
+			goto error;
+		break;
+
+	case USB_EVENT_NONE:
+		/* Disable boost mode */
+		ret = bq25890_field_write(bq, F_OTG_CFG, 0);
+		if (ret < 0)
+			goto error;
+
+		power_supply_changed(bq->charger);
+		break;
+	}
+
+	return;
+
+error:
+	dev_err(bq->dev, "Error switching to boost/charger mode.\n");
+}
+
+static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
+				void *priv)
+{
+	struct bq25890_device *bq =
+			container_of(nb, struct bq25890_device, usb_nb);
+
+	bq->usb_event = val;
+	queue_work(system_power_efficient_wq, &bq->usb_work);
+
+	return NOTIFY_OK;
+}
+
+static int bq25890_irq_probe(struct bq25890_device *bq)
+{
+	struct gpio_desc *irq;
+
+	irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN);
+	if (IS_ERR(irq)) {
+		dev_err(bq->dev, "Could not probe irq pin.\n");
+		return PTR_ERR(irq);
+	}
+
+	return gpiod_to_irq(irq);
+}
+
+static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
+{
+	int ret;
+	u32 property;
+	int i;
+	struct bq25890_init_data *init = &bq->init_data;
+	struct {
+		char *name;
+		bool optional;
+		enum bq25890_table_ids tbl_id;
+		u8 *conv_data; /* holds converted value from given property */
+	} props[] = {
+		/* required properties */
+		{"ti,charge-current", false, TBL_ICHG, &init->ichg},
+		{"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg},
+		{"ti,termination-current", false, TBL_ITERM, &init->iterm},
+		{"ti,precharge-current", false, TBL_ITERM, &init->iprechg},
+		{"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin},
+		{"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv},
+		{"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti},
+
+		/* optional properties */
+		{"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}
+	};
+
+	/* initialize data for optional properties */
+	init->treg = 3; /* 120 degrees Celsius */
+
+	for (i = 0; i < ARRAY_SIZE(props); i++) {
+		ret = device_property_read_u32(bq->dev, props[i].name,
+					       &property);
+		if (ret < 0) {
+			if (props[i].optional)
+				continue;
+
+			return ret;
+		}
+
+		*props[i].conv_data = bq25890_find_idx(property,
+						       props[i].tbl_id);
+	}
+
+	return 0;
+}
+
+static int bq25890_fw_probe(struct bq25890_device *bq)
+{
+	int ret;
+	struct bq25890_init_data *init = &bq->init_data;
+
+	ret = bq25890_fw_read_u32_props(bq);
+	if (ret < 0)
+		return ret;
+
+	init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin");
+	init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq");
+
+	return 0;
+}
+
+static int bq25890_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct device *dev = &client->dev;
+	struct bq25890_device *bq;
+	int ret;
+	int i;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+		return -ENODEV;
+	}
+
+	bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
+	if (!bq)
+		return -ENOMEM;
+
+	bq->client = client;
+	bq->dev = dev;
+
+	mutex_init(&bq->lock);
+
+	bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config);
+	if (IS_ERR(bq->rmap)) {
+		dev_err(dev, "failed to allocate register map\n");
+		return PTR_ERR(bq->rmap);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) {
+		const struct reg_field *reg_fields = bq25890_reg_fields;
+
+		bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
+							     reg_fields[i]);
+		if (IS_ERR(bq->rmap_fields[i])) {
+			dev_err(dev, "cannot allocate regmap field\n");
+			return PTR_ERR(bq->rmap_fields[i]);
+		}
+	}
+
+	i2c_set_clientdata(client, bq);
+
+	bq->chip_id = bq25890_field_read(bq, F_PN);
+	if (bq->chip_id < 0) {
+		dev_err(dev, "Cannot read chip ID.\n");
+		return bq->chip_id;
+	}
+
+	if (bq->chip_id != BQ25890_ID) {
+		dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
+		return -ENODEV;
+	}
+
+	if (!dev->platform_data) {
+		ret = bq25890_fw_probe(bq);
+		if (ret < 0) {
+			dev_err(dev, "Cannot read device properties.\n");
+			return ret;
+		}
+	} else {
+		return -ENODEV;
+	}
+
+	ret = bq25890_hw_init(bq);
+	if (ret < 0) {
+		dev_err(dev, "Cannot initialize the chip.\n");
+		return ret;
+	}
+
+	if (client->irq <= 0)
+		client->irq = bq25890_irq_probe(bq);
+
+	if (client->irq < 0) {
+		dev_err(dev, "No irq resource found.\n");
+		return client->irq;
+	}
+
+	/* OTG reporting */
+	bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+	if (!IS_ERR_OR_NULL(bq->usb_phy)) {
+		INIT_WORK(&bq->usb_work, bq25890_usb_work);
+		bq->usb_nb.notifier_call = bq25890_usb_notifier;
+		usb_register_notifier(bq->usb_phy, &bq->usb_nb);
+	}
+
+	ret = devm_request_threaded_irq(dev, client->irq, NULL,
+					bq25890_irq_handler_thread,
+					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					BQ25890_IRQ_PIN, bq);
+	if (ret)
+		goto irq_fail;
+
+	ret = bq25890_power_supply_init(bq);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register power supply\n");
+		goto irq_fail;
+	}
+
+	return 0;
+
+irq_fail:
+	if (!IS_ERR_OR_NULL(bq->usb_phy))
+		usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
+
+	return ret;
+}
+
+static int bq25890_remove(struct i2c_client *client)
+{
+	struct bq25890_device *bq = i2c_get_clientdata(client);
+
+	power_supply_unregister(bq->charger);
+
+	if (!IS_ERR_OR_NULL(bq->usb_phy))
+		usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
+
+	/* reset all registers to default values */
+	bq25890_chip_reset(bq);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq25890_suspend(struct device *dev)
+{
+	struct bq25890_device *bq = dev_get_drvdata(dev);
+
+	/*
+	 * If charger is removed, while in suspend, make sure ADC is diabled
+	 * since it consumes slightly more power.
+	 */
+	return bq25890_field_write(bq, F_CONV_START, 0);
+}
+
+static int bq25890_resume(struct device *dev)
+{
+	int ret;
+	struct bq25890_state state;
+	struct bq25890_device *bq = dev_get_drvdata(dev);
+
+	ret = bq25890_get_chip_state(bq, &state);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&bq->lock);
+	bq->state = state;
+	mutex_unlock(&bq->lock);
+
+	/* Re-enable ADC only if charger is plugged in. */
+	if (state.online) {
+		ret = bq25890_field_write(bq, F_CONV_START, 1);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* signal userspace, maybe state changed while suspended */
+	power_supply_changed(bq->charger);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops bq25890_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume)
+};
+
+static const struct i2c_device_id bq25890_i2c_ids[] = {
+	{ "bq25890", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
+
+static const struct of_device_id bq25890_of_match[] = {
+	{ .compatible = "ti,bq25890", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, bq25890_of_match);
+
+static const struct acpi_device_id bq25890_acpi_match[] = {
+	{"BQ258900", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match);
+
+static struct i2c_driver bq25890_driver = {
+	.driver = {
+		.name = "bq25890-charger",
+		.of_match_table = of_match_ptr(bq25890_of_match),
+		.acpi_match_table = ACPI_PTR(bq25890_acpi_match),
+		.pm = &bq25890_pm,
+	},
+	.probe = bq25890_probe,
+	.remove = bq25890_remove,
+	.id_table = bq25890_i2c_ids,
+};
+module_i2c_driver(bq25890_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION("bq25890 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
index 0aed13f90891..1c202ccbd2a6 100644
--- a/drivers/power/charger-manager.c
+++ b/drivers/power/charger-manager.c
@@ -1768,7 +1768,8 @@ static int charger_manager_probe(struct platform_device *pdev)
 
 	INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
 
-	cm->charger_psy = power_supply_register(NULL, &cm->charger_psy_desc,
+	cm->charger_psy = power_supply_register(&pdev->dev,
+						&cm->charger_psy_desc,
 						&psy_cfg);
 	if (IS_ERR(cm->charger_psy)) {
 		dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index 6cc5e87ec031..e89255764745 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -63,6 +63,8 @@
 #define dP_ACC_100	0x1900
 #define dP_ACC_200	0x3200
 
+#define MAX17042_VMAX_TOLERANCE		50 /* 50 mV */
+
 struct max17042_chip {
 	struct i2c_client *client;
 	struct regmap *regmap;
@@ -85,10 +87,94 @@ static enum power_supply_property max17042_battery_props[] = {
 	POWER_SUPPLY_PROP_CHARGE_FULL,
 	POWER_SUPPLY_PROP_CHARGE_COUNTER,
 	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+	POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+	POWER_SUPPLY_PROP_TEMP_MIN,
+	POWER_SUPPLY_PROP_TEMP_MAX,
+	POWER_SUPPLY_PROP_HEALTH,
 	POWER_SUPPLY_PROP_CURRENT_NOW,
 	POWER_SUPPLY_PROP_CURRENT_AVG,
 };
 
+static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
+{
+	int ret;
+	u32 data;
+	struct regmap *map = chip->regmap;
+
+	ret = regmap_read(map, MAX17042_TEMP, &data);
+	if (ret < 0)
+		return ret;
+
+	*temp = data;
+	/* The value is signed. */
+	if (*temp & 0x8000) {
+		*temp = (0x7fff & ~*temp) + 1;
+		*temp *= -1;
+	}
+
+	/* The value is converted into deci-centigrade scale */
+	/* Units of LSB = 1 / 256 degree Celsius */
+	*temp = *temp * 10 / 256;
+	return 0;
+}
+
+static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
+{
+	int temp, vavg, vbatt, ret;
+	u32 val;
+
+	ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val);
+	if (ret < 0)
+		goto health_error;
+
+	/* bits [0-3] unused */
+	vavg = val * 625 / 8;
+	/* Convert to millivolts */
+	vavg /= 1000;
+
+	ret = regmap_read(chip->regmap, MAX17042_VCELL, &val);
+	if (ret < 0)
+		goto health_error;
+
+	/* bits [0-3] unused */
+	vbatt = val * 625 / 8;
+	/* Convert to millivolts */
+	vbatt /= 1000;
+
+	if (vavg < chip->pdata->vmin) {
+		*health = POWER_SUPPLY_HEALTH_DEAD;
+		goto out;
+	}
+
+	if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
+		*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		goto out;
+	}
+
+	ret = max17042_get_temperature(chip, &temp);
+	if (ret < 0)
+		goto health_error;
+
+	if (temp <= chip->pdata->temp_min) {
+		*health = POWER_SUPPLY_HEALTH_COLD;
+		goto out;
+	}
+
+	if (temp >= chip->pdata->temp_max) {
+		*health = POWER_SUPPLY_HEALTH_OVERHEAT;
+		goto out;
+	}
+
+	*health = POWER_SUPPLY_HEALTH_GOOD;
+
+out:
+	return 0;
+
+health_error:
+	return ret;
+}
+
 static int max17042_get_property(struct power_supply *psy,
 			    enum power_supply_property psp,
 			    union power_supply_propval *val)
@@ -181,19 +267,34 @@ static int max17042_get_property(struct power_supply *psy,
 		val->intval = data * 1000 / 2;
 		break;
 	case POWER_SUPPLY_PROP_TEMP:
-		ret = regmap_read(map, MAX17042_TEMP, &data);
+		ret = max17042_get_temperature(chip, &val->intval);
+		if (ret < 0)
+			return ret;
+		break;
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+		ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+		if (ret < 0)
+			return ret;
+		/* LSB is Alert Minimum. In deci-centigrade */
+		val->intval = (data & 0xff) * 10;
+		break;
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+		ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+		if (ret < 0)
+			return ret;
+		/* MSB is Alert Maximum. In deci-centigrade */
+		val->intval = (data >> 8) * 10;
+		break;
+	case POWER_SUPPLY_PROP_TEMP_MIN:
+		val->intval = chip->pdata->temp_min;
+		break;
+	case POWER_SUPPLY_PROP_TEMP_MAX:
+		val->intval = chip->pdata->temp_max;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		ret = max17042_get_battery_health(chip, &val->intval);
 		if (ret < 0)
 			return ret;
-
-		val->intval = data;
-		/* The value is signed. */
-		if (val->intval & 0x8000) {
-			val->intval = (0x7fff & ~val->intval) + 1;
-			val->intval *= -1;
-		}
-		/* The value is converted into deci-centigrade scale */
-		/* Units of LSB = 1 / 256 degree Celsius */
-		val->intval = val->intval * 10 / 256;
 		break;
 	case POWER_SUPPLY_PROP_CURRENT_NOW:
 		if (chip->pdata->enable_current_sense) {
@@ -237,6 +338,69 @@ static int max17042_get_property(struct power_supply *psy,
 	return 0;
 }
 
+static int max17042_set_property(struct power_supply *psy,
+			    enum power_supply_property psp,
+			    const union power_supply_propval *val)
+{
+	struct max17042_chip *chip = power_supply_get_drvdata(psy);
+	struct regmap *map = chip->regmap;
+	int ret = 0;
+	u32 data;
+	int8_t temp;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+		ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+		if (ret < 0)
+			return ret;
+
+		/* Input in deci-centigrade, convert to centigrade */
+		temp = val->intval / 10;
+		/* force min < max */
+		if (temp >= (int8_t)(data >> 8))
+			temp = (int8_t)(data >> 8) - 1;
+		/* Write both MAX and MIN ALERT */
+		data = (data & 0xff00) + temp;
+		ret = regmap_write(map, MAX17042_TALRT_Th, data);
+		break;
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+		ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+		if (ret < 0)
+			return ret;
+
+		/* Input in Deci-Centigrade, convert to centigrade */
+		temp = val->intval / 10;
+		/* force max > min */
+		if (temp <= (int8_t)(data & 0xff))
+			temp = (int8_t)(data & 0xff) + 1;
+		/* Write both MAX and MIN ALERT */
+		data = (data & 0xff) + (temp << 8);
+		ret = regmap_write(map, MAX17042_TALRT_Th, data);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int max17042_property_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+	case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+		ret = 1;
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
 static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
 {
 	int retries = 8;
@@ -645,6 +809,15 @@ max17042_get_pdata(struct device *dev)
 		pdata->enable_current_sense = true;
 	}
 
+	if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
+		pdata->temp_min = INT_MIN;
+	if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
+		pdata->temp_max = INT_MAX;
+	if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
+		pdata->vmin = INT_MIN;
+	if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
+		pdata->vmax = INT_MAX;
+
 	return pdata;
 }
 #else
@@ -665,6 +838,8 @@ static const struct power_supply_desc max17042_psy_desc = {
 	.name		= "max170xx_battery",
 	.type		= POWER_SUPPLY_TYPE_BATTERY,
 	.get_property	= max17042_get_property,
+	.set_property	= max17042_set_property,
+	.property_is_writeable	= max17042_property_is_writeable,
 	.properties	= max17042_battery_props,
 	.num_properties	= ARRAY_SIZE(max17042_battery_props),
 };
@@ -673,6 +848,8 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
 	.name		= "max170xx_battery",
 	.type		= POWER_SUPPLY_TYPE_BATTERY,
 	.get_property	= max17042_get_property,
+	.set_property	= max17042_set_property,
+	.property_is_writeable	= max17042_property_is_writeable,
 	.properties	= max17042_battery_props,
 	.num_properties	= ARRAY_SIZE(max17042_battery_props) - 2,
 };
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index 2ed4a4a6b3c5..869284c2e1e8 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -30,6 +30,8 @@ EXPORT_SYMBOL_GPL(power_supply_notifier);
 
 static struct device_type power_supply_dev_type;
 
+#define POWER_SUPPLY_DEFERRED_REGISTER_TIME	msecs_to_jiffies(10)
+
 static bool __power_supply_is_supplied_by(struct power_supply *supplier,
 					 struct power_supply *supply)
 {
@@ -121,6 +123,30 @@ void power_supply_changed(struct power_supply *psy)
 }
 EXPORT_SYMBOL_GPL(power_supply_changed);
 
+/*
+ * Notify that power supply was registered after parent finished the probing.
+ *
+ * Often power supply is registered from driver's probe function. However
+ * calling power_supply_changed() directly from power_supply_register()
+ * would lead to execution of get_property() function provided by the driver
+ * too early - before the probe ends.
+ *
+ * Avoid that by waiting on parent's mutex.
+ */
+static void power_supply_deferred_register_work(struct work_struct *work)
+{
+	struct power_supply *psy = container_of(work, struct power_supply,
+						deferred_register_work.work);
+
+	if (psy->dev.parent)
+		mutex_lock(&psy->dev.parent->mutex);
+
+	power_supply_changed(psy);
+
+	if (psy->dev.parent)
+		mutex_unlock(&psy->dev.parent->mutex);
+}
+
 #ifdef CONFIG_OF
 #include <linux/of.h>
 
@@ -420,6 +446,45 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np,
 	return psy;
 }
 EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
+
+static void devm_power_supply_put(struct device *dev, void *res)
+{
+	struct power_supply **psy = res;
+
+	power_supply_put(*psy);
+}
+
+/**
+ * devm_power_supply_get_by_phandle() - Resource managed version of
+ *  power_supply_get_by_phandle()
+ * @dev: Pointer to device holding phandle property
+ * @phandle_name: Name of property holding a power supply phandle
+ *
+ * Return: On success returns a reference to a power supply with
+ * matching name equals to value under @property, NULL or ERR_PTR otherwise.
+ */
+struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
+						      const char *property)
+{
+	struct power_supply **ptr, *psy;
+
+	if (!dev->of_node)
+		return ERR_PTR(-ENODEV);
+
+	ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	psy = power_supply_get_by_phandle(dev->of_node, property);
+	if (IS_ERR_OR_NULL(psy)) {
+		devres_free(ptr);
+	} else {
+		*ptr = psy;
+		devres_add(dev, ptr);
+	}
+	return psy;
+}
+EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
 #endif /* CONFIG_OF */
 
 int power_supply_get_property(struct power_supply *psy,
@@ -645,6 +710,10 @@ __power_supply_register(struct device *parent,
 	struct power_supply *psy;
 	int rc;
 
+	if (!parent)
+		pr_warn("%s: Expected proper parent device for '%s'\n",
+			__func__, desc->name);
+
 	psy = kzalloc(sizeof(*psy), GFP_KERNEL);
 	if (!psy)
 		return ERR_PTR(-ENOMEM);
@@ -659,7 +728,6 @@ __power_supply_register(struct device *parent,
 	dev->release = power_supply_dev_release;
 	dev_set_drvdata(dev, psy);
 	psy->desc = desc;
-	atomic_inc(&psy->use_cnt);
 	if (cfg) {
 		psy->drv_data = cfg->drv_data;
 		psy->of_node = cfg->of_node;
@@ -672,6 +740,8 @@ __power_supply_register(struct device *parent,
 		goto dev_set_name_failed;
 
 	INIT_WORK(&psy->changed_work, power_supply_changed_work);
+	INIT_DELAYED_WORK(&psy->deferred_register_work,
+			  power_supply_deferred_register_work);
 
 	rc = power_supply_check_supplies(psy);
 	if (rc) {
@@ -700,7 +770,20 @@ __power_supply_register(struct device *parent,
 	if (rc)
 		goto create_triggers_failed;
 
-	power_supply_changed(psy);
+	/*
+	 * Update use_cnt after any uevents (most notably from device_add()).
+	 * We are here still during driver's probe but
+	 * the power_supply_uevent() calls back driver's get_property
+	 * method so:
+	 * 1. Driver did not assigned the returned struct power_supply,
+	 * 2. Driver could not finish initialization (anything in its probe
+	 *    after calling power_supply_register()).
+	 */
+	atomic_inc(&psy->use_cnt);
+
+	queue_delayed_work(system_power_efficient_wq,
+			   &psy->deferred_register_work,
+			   POWER_SUPPLY_DEFERRED_REGISTER_TIME);
 
 	return psy;
 
@@ -720,7 +803,8 @@ dev_set_name_failed:
 
 /**
  * power_supply_register() - Register new power supply
- * @parent:	Device to be a parent of power supply's device
+ * @parent:	Device to be a parent of power supply's device, usually
+ *		the device which probe function calls this
  * @desc:	Description of power supply, must be valid through whole
  *		lifetime of this power supply
  * @cfg:	Run-time specific configuration accessed during registering,
@@ -740,8 +824,9 @@ struct power_supply *__must_check power_supply_register(struct device *parent,
 EXPORT_SYMBOL_GPL(power_supply_register);
 
 /**
- * power_supply_register() - Register new non-waking-source power supply
- * @parent:	Device to be a parent of power supply's device
+ * power_supply_register_no_ws() - Register new non-waking-source power supply
+ * @parent:	Device to be a parent of power supply's device, usually
+ *		the device which probe function calls this
  * @desc:	Description of power supply, must be valid through whole
  *		lifetime of this power supply
  * @cfg:	Run-time specific configuration accessed during registering,
@@ -769,8 +854,9 @@ static void devm_power_supply_release(struct device *dev, void *res)
 }
 
 /**
- * power_supply_register() - Register managed power supply
- * @parent:	Device to be a parent of power supply's device
+ * devm_power_supply_register() - Register managed power supply
+ * @parent:	Device to be a parent of power supply's device, usually
+ *		the device which probe function calls this
  * @desc:	Description of power supply, must be valid through whole
  *		lifetime of this power supply
  * @cfg:	Run-time specific configuration accessed during registering,
@@ -804,8 +890,9 @@ devm_power_supply_register(struct device *parent,
 EXPORT_SYMBOL_GPL(devm_power_supply_register);
 
 /**
- * power_supply_register() - Register managed non-waking-source power supply
- * @parent:	Device to be a parent of power supply's device
+ * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply
+ * @parent:	Device to be a parent of power supply's device, usually
+ *		the device which probe function calls this
  * @desc:	Description of power supply, must be valid through whole
  *		lifetime of this power supply
  * @cfg:	Run-time specific configuration accessed during registering,
@@ -849,6 +936,7 @@ void power_supply_unregister(struct power_supply *psy)
 {
 	WARN_ON(atomic_dec_return(&psy->use_cnt));
 	cancel_work_sync(&psy->changed_work);
+	cancel_delayed_work_sync(&psy->deferred_register_work);
 	sysfs_remove_link(&psy->dev.kobj, "powers");
 	power_supply_remove_triggers(psy);
 	psy_unregister_cooler(psy);
diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c
index 2d41a43fc81a..2277ad9c2f68 100644
--- a/drivers/power/power_supply_leds.c
+++ b/drivers/power/power_supply_leds.c
@@ -25,7 +25,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
 	unsigned long delay_on = 0;
 	unsigned long delay_off = 0;
 
-	if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
+	if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
 		return;
 
 	dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval);
@@ -115,7 +115,7 @@ static void power_supply_update_gen_leds(struct power_supply *psy)
 {
 	union power_supply_propval online;
 
-	if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
+	if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
 		return;
 
 	dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 9134e3d2d95e..ed2d7fd0c734 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -125,7 +125,7 @@ static ssize_t power_supply_store_property(struct device *dev,
 
 	value.intval = long_val;
 
-	ret = psy->desc->set_property(psy, off, &value);
+	ret = power_supply_set_property(psy, off, &value);
 	if (ret < 0)
 		return ret;
 
@@ -223,7 +223,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
 
 		if (property == attrno) {
 			if (psy->desc->property_is_writeable &&
-			    power_supply_property_is_writeable(psy, property) > 0)
+			    psy->desc->property_is_writeable(psy, property) > 0)
 				mode |= S_IWUSR;
 
 			return mode;
diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c
index ca461ebc7ae8..36dc52fb2ec8 100644
--- a/drivers/power/reset/at91-reset.c
+++ b/drivers/power/reset/at91-reset.c
@@ -243,7 +243,7 @@ static int at91_reset_probe(struct platform_device *pdev)
 	return 0;
 }
 
-static struct platform_device_id at91_reset_plat_match[] = {
+static const struct platform_device_id at91_reset_plat_match[] = {
 	{ "at91-sam9260-reset", (unsigned long)at91sam9260_restart },
 	{ "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart },
 	{ /* sentinel */ }
diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c
index e5332f1db8a7..be3d81ff51cc 100644
--- a/drivers/power/reset/gpio-poweroff.c
+++ b/drivers/power/reset/gpio-poweroff.c
@@ -48,6 +48,7 @@ static void gpio_poweroff_do_poweroff(void)
 static int gpio_poweroff_probe(struct platform_device *pdev)
 {
 	bool input = false;
+	enum gpiod_flags flags;
 
 	/* If a pm_power_off function has already been added, leave it alone */
 	if (pm_power_off != NULL) {
@@ -57,25 +58,15 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
 		return -EBUSY;
 	}
 
-	reset_gpio = devm_gpiod_get(&pdev->dev, NULL);
-	if (IS_ERR(reset_gpio))
-		return PTR_ERR(reset_gpio);
-
 	input = of_property_read_bool(pdev->dev.of_node, "input");
+	if (input)
+		flags = GPIOD_IN;
+	else
+		flags = GPIOD_OUT_LOW;
 
-	if (input) {
-		if (gpiod_direction_input(reset_gpio)) {
-			dev_err(&pdev->dev,
-				"Could not set direction of reset GPIO to input\n");
-			return -ENODEV;
-		}
-	} else {
-		if (gpiod_direction_output(reset_gpio, 0)) {
-			dev_err(&pdev->dev,
-				"Could not set direction of reset GPIO\n");
-			return -ENODEV;
-		}
-	}
+	reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
+	if (IS_ERR(reset_gpio))
+		return PTR_ERR(reset_gpio);
 
 	pm_power_off = &gpio_poweroff_do_poweroff;
 	return 0;
diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c
index edb327efee8b..829b45f42021 100644
--- a/drivers/power/reset/gpio-restart.c
+++ b/drivers/power/reset/gpio-restart.c
@@ -78,7 +78,7 @@ static int gpio_restart_probe(struct platform_device *pdev)
 	}
 
 	gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
-	gpio_restart->restart_handler.priority = 128;
+	gpio_restart->restart_handler.priority = 129;
 	gpio_restart->active_delay_ms = 100;
 	gpio_restart->inactive_delay_ms = 100;
 	gpio_restart->wait_delay_ms = 3000;
diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c
index 5f855f99bdfc..15fed9d8f871 100644
--- a/drivers/power/reset/ltc2952-poweroff.c
+++ b/drivers/power/reset/ltc2952-poweroff.c
@@ -201,16 +201,15 @@ static int ltc2952_poweroff_init(struct platform_device *pdev)
 		return ret;
 	}
 
-	data->gpio_trigger = devm_gpiod_get(&pdev->dev, "trigger", GPIOD_IN);
+	data->gpio_trigger = devm_gpiod_get_optional(&pdev->dev, "trigger",
+						     GPIOD_IN);
 	if (IS_ERR(data->gpio_trigger)) {
 		/*
 		 * It's not a problem if the trigger gpio isn't available, but
 		 * it is worth a warning if its use was defined in the device
 		 * tree.
 		 */
-		if (PTR_ERR(data->gpio_trigger) != -ENOENT)
-			dev_err(&pdev->dev,
-				"unable to claim gpio \"trigger\"\n");
+		dev_err(&pdev->dev, "unable to claim gpio \"trigger\"\n");
 		data->gpio_trigger = NULL;
 	}
 
diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c
new file mode 100644
index 000000000000..08baac6e3ada
--- /dev/null
+++ b/drivers/power/rt9455_charger.c
@@ -0,0 +1,1752 @@
+/*
+ * Driver for Richtek RT9455WSC battery charger.
+ *
+ * Copyright (C) 2015 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/interrupt.h>
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/usb/phy.h>
+#include <linux/regmap.h>
+
+#define RT9455_MANUFACTURER			"Richtek"
+#define RT9455_MODEL_NAME			"RT9455"
+#define RT9455_DRIVER_NAME			"rt9455-charger"
+
+#define RT9455_IRQ_NAME				"interrupt"
+
+#define RT9455_PWR_RDY_DELAY			1 /* 1 second */
+#define RT9455_MAX_CHARGING_TIME		21600 /* 6 hrs */
+#define RT9455_BATT_PRESENCE_DELAY		60 /* 60 seconds */
+
+#define RT9455_CHARGE_MODE			0x00
+#define RT9455_BOOST_MODE			0x01
+
+#define RT9455_FAULT				0x03
+
+#define RT9455_IAICR_100MA			0x00
+#define RT9455_IAICR_500MA			0x01
+#define RT9455_IAICR_NO_LIMIT			0x03
+
+#define RT9455_CHARGE_DISABLE			0x00
+#define RT9455_CHARGE_ENABLE			0x01
+
+#define RT9455_PWR_FAULT			0x00
+#define RT9455_PWR_GOOD				0x01
+
+#define RT9455_REG_CTRL1			0x00 /* CTRL1 reg address */
+#define RT9455_REG_CTRL2			0x01 /* CTRL2 reg address */
+#define RT9455_REG_CTRL3			0x02 /* CTRL3 reg address */
+#define RT9455_REG_DEV_ID			0x03 /* DEV_ID reg address */
+#define RT9455_REG_CTRL4			0x04 /* CTRL4 reg address */
+#define RT9455_REG_CTRL5			0x05 /* CTRL5 reg address */
+#define RT9455_REG_CTRL6			0x06 /* CTRL6 reg address */
+#define RT9455_REG_CTRL7			0x07 /* CTRL7 reg address */
+#define RT9455_REG_IRQ1				0x08 /* IRQ1 reg address */
+#define RT9455_REG_IRQ2				0x09 /* IRQ2 reg address */
+#define RT9455_REG_IRQ3				0x0A /* IRQ3 reg address */
+#define RT9455_REG_MASK1			0x0B /* MASK1 reg address */
+#define RT9455_REG_MASK2			0x0C /* MASK2 reg address */
+#define RT9455_REG_MASK3			0x0D /* MASK3 reg address */
+
+enum rt9455_fields {
+	F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */
+
+	F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ,
+	F_OPA_MODE, /* CTRL2 reg fields */
+
+	F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */
+
+	F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */
+
+	F_RST, /* CTRL4 reg fields */
+
+	F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/
+
+	F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */
+
+	F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */
+
+	F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */
+
+	F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI,
+	F_CHMIVRI, /* IRQ2 reg fields */
+
+	F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */
+
+	F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */
+
+	F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM,
+	F_CHMIVRIM, /* MASK2 reg fields */
+
+	F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */
+
+	F_MAX_FIELDS
+};
+
+static const struct reg_field rt9455_reg_fields[] = {
+	[F_STAT]		= REG_FIELD(RT9455_REG_CTRL1, 4, 5),
+	[F_BOOST]		= REG_FIELD(RT9455_REG_CTRL1, 3, 3),
+	[F_PWR_RDY]		= REG_FIELD(RT9455_REG_CTRL1, 2, 2),
+	[F_OTG_PIN_POLARITY]	= REG_FIELD(RT9455_REG_CTRL1, 1, 1),
+
+	[F_IAICR]		= REG_FIELD(RT9455_REG_CTRL2, 6, 7),
+	[F_TE_SHDN_EN]		= REG_FIELD(RT9455_REG_CTRL2, 5, 5),
+	[F_HIGHER_OCP]		= REG_FIELD(RT9455_REG_CTRL2, 4, 4),
+	[F_TE]			= REG_FIELD(RT9455_REG_CTRL2, 3, 3),
+	[F_IAICR_INT]		= REG_FIELD(RT9455_REG_CTRL2, 2, 2),
+	[F_HIZ]			= REG_FIELD(RT9455_REG_CTRL2, 1, 1),
+	[F_OPA_MODE]		= REG_FIELD(RT9455_REG_CTRL2, 0, 0),
+
+	[F_VOREG]		= REG_FIELD(RT9455_REG_CTRL3, 2, 7),
+	[F_OTG_PL]		= REG_FIELD(RT9455_REG_CTRL3, 1, 1),
+	[F_OTG_EN]		= REG_FIELD(RT9455_REG_CTRL3, 0, 0),
+
+	[F_VENDOR_ID]		= REG_FIELD(RT9455_REG_DEV_ID, 4, 7),
+	[F_CHIP_REV]		= REG_FIELD(RT9455_REG_DEV_ID, 0, 3),
+
+	[F_RST]			= REG_FIELD(RT9455_REG_CTRL4, 7, 7),
+
+	[F_TMR_EN]		= REG_FIELD(RT9455_REG_CTRL5, 7, 7),
+	[F_MIVR]		= REG_FIELD(RT9455_REG_CTRL5, 4, 5),
+	[F_IPREC]		= REG_FIELD(RT9455_REG_CTRL5, 2, 3),
+	[F_IEOC_PERCENTAGE]	= REG_FIELD(RT9455_REG_CTRL5, 0, 1),
+
+	[F_IAICR_SEL]		= REG_FIELD(RT9455_REG_CTRL6, 7, 7),
+	[F_ICHRG]		= REG_FIELD(RT9455_REG_CTRL6, 4, 6),
+	[F_VPREC]		= REG_FIELD(RT9455_REG_CTRL6, 0, 2),
+
+	[F_BATD_EN]		= REG_FIELD(RT9455_REG_CTRL7, 6, 6),
+	[F_CHG_EN]		= REG_FIELD(RT9455_REG_CTRL7, 4, 4),
+	[F_VMREG]		= REG_FIELD(RT9455_REG_CTRL7, 0, 3),
+
+	[F_TSDI]		= REG_FIELD(RT9455_REG_IRQ1, 7, 7),
+	[F_VINOVPI]		= REG_FIELD(RT9455_REG_IRQ1, 6, 6),
+	[F_BATAB]		= REG_FIELD(RT9455_REG_IRQ1, 0, 0),
+
+	[F_CHRVPI]		= REG_FIELD(RT9455_REG_IRQ2, 7, 7),
+	[F_CHBATOVI]		= REG_FIELD(RT9455_REG_IRQ2, 5, 5),
+	[F_CHTERMI]		= REG_FIELD(RT9455_REG_IRQ2, 4, 4),
+	[F_CHRCHGI]		= REG_FIELD(RT9455_REG_IRQ2, 3, 3),
+	[F_CH32MI]		= REG_FIELD(RT9455_REG_IRQ2, 2, 2),
+	[F_CHTREGI]		= REG_FIELD(RT9455_REG_IRQ2, 1, 1),
+	[F_CHMIVRI]		= REG_FIELD(RT9455_REG_IRQ2, 0, 0),
+
+	[F_BSTBUSOVI]		= REG_FIELD(RT9455_REG_IRQ3, 7, 7),
+	[F_BSTOLI]		= REG_FIELD(RT9455_REG_IRQ3, 6, 6),
+	[F_BSTLOWVI]		= REG_FIELD(RT9455_REG_IRQ3, 5, 5),
+	[F_BST32SI]		= REG_FIELD(RT9455_REG_IRQ3, 3, 3),
+
+	[F_TSDM]		= REG_FIELD(RT9455_REG_MASK1, 7, 7),
+	[F_VINOVPIM]		= REG_FIELD(RT9455_REG_MASK1, 6, 6),
+	[F_BATABM]		= REG_FIELD(RT9455_REG_MASK1, 0, 0),
+
+	[F_CHRVPIM]		= REG_FIELD(RT9455_REG_MASK2, 7, 7),
+	[F_CHBATOVIM]		= REG_FIELD(RT9455_REG_MASK2, 5, 5),
+	[F_CHTERMIM]		= REG_FIELD(RT9455_REG_MASK2, 4, 4),
+	[F_CHRCHGIM]		= REG_FIELD(RT9455_REG_MASK2, 3, 3),
+	[F_CH32MIM]		= REG_FIELD(RT9455_REG_MASK2, 2, 2),
+	[F_CHTREGIM]		= REG_FIELD(RT9455_REG_MASK2, 1, 1),
+	[F_CHMIVRIM]		= REG_FIELD(RT9455_REG_MASK2, 0, 0),
+
+	[F_BSTVINOVIM]		= REG_FIELD(RT9455_REG_MASK3, 7, 7),
+	[F_BSTOLIM]		= REG_FIELD(RT9455_REG_MASK3, 6, 6),
+	[F_BSTLOWVIM]		= REG_FIELD(RT9455_REG_MASK3, 5, 5),
+	[F_BST32SIM]		= REG_FIELD(RT9455_REG_MASK3, 3, 3),
+};
+
+#define GET_MASK(fid)	(BIT(rt9455_reg_fields[fid].msb + 1) - \
+			 BIT(rt9455_reg_fields[fid].lsb))
+
+/*
+ * Each array initialised below shows the possible real-world values for a
+ * group of bits belonging to RT9455 registers. The arrays are sorted in
+ * ascending order. The index of each real-world value represents the value
+ * that is encoded in the group of bits belonging to RT9455 registers.
+ */
+/* REG06[6:4] (ICHRG) in uAh */
+static const int rt9455_ichrg_values[] = {
+	 500000,  650000,  800000,  950000, 1100000, 1250000, 1400000, 1550000
+};
+
+/*
+ * When the charger is in charge mode, REG02[7:2] represent battery regulation
+ * voltage.
+ */
+/* REG02[7:2] (VOREG) in uV */
+static const int rt9455_voreg_values[] = {
+	3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000,
+	3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000,
+	3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000,
+	3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000,
+	4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000,
+	4300000, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000,
+	4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000,
+	4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000
+};
+
+/*
+ * When the charger is in boost mode, REG02[7:2] represent boost output
+ * voltage.
+ */
+/* REG02[7:2] (Boost output voltage) in uV */
+static const int rt9455_boost_voltage_values[] = {
+	4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000,
+	4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000,
+	4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000,
+	5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000,
+	5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000,
+	5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000,
+	5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
+	5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
+};
+
+/* REG07[3:0] (VMREG) in uV */
+static const int rt9455_vmreg_values[] = {
+	4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000,
+	4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000
+};
+
+/* REG05[5:4] (IEOC_PERCENTAGE) */
+static const int rt9455_ieoc_percentage_values[] = {
+	10, 30, 20, 30
+};
+
+/* REG05[1:0] (MIVR) in uV */
+static const int rt9455_mivr_values[] = {
+	4000000, 4250000, 4500000, 5000000
+};
+
+/* REG05[1:0] (IAICR) in uA */
+static const int rt9455_iaicr_values[] = {
+	100000, 500000, 1000000, 2000000
+};
+
+struct rt9455_info {
+	struct i2c_client		*client;
+	struct regmap			*regmap;
+	struct regmap_field		*regmap_fields[F_MAX_FIELDS];
+	struct power_supply		*charger;
+#if IS_ENABLED(CONFIG_USB_PHY)
+	struct usb_phy			*usb_phy;
+	struct notifier_block		nb;
+#endif
+	struct delayed_work		pwr_rdy_work;
+	struct delayed_work		max_charging_time_work;
+	struct delayed_work		batt_presence_work;
+	u32				voreg;
+	u32				boost_voltage;
+};
+
+/*
+ * Iterate through each element of the 'tbl' array until an element whose value
+ * is greater than v is found. Return the index of the respective element,
+ * or the index of the last element in the array, if no such element is found.
+ */
+static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v)
+{
+	int i;
+
+	/*
+	 * No need to iterate until the last index in the table because
+	 * if no element greater than v is found in the table,
+	 * or if only the last element is greater than v,
+	 * function returns the index of the last element.
+	 */
+	for (i = 0; i < tbl_size - 1; i++)
+		if (v <= tbl[i])
+			return i;
+
+	return (tbl_size - 1);
+}
+
+static int rt9455_get_field_val(struct rt9455_info *info,
+				enum rt9455_fields field,
+				const int tbl[], int tbl_size, int *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_field_read(info->regmap_fields[field], &v);
+	if (ret)
+		return ret;
+
+	v = (v >= tbl_size) ? (tbl_size - 1) : v;
+	*val = tbl[v];
+
+	return 0;
+}
+
+static int rt9455_set_field_val(struct rt9455_info *info,
+				enum rt9455_fields field,
+				const int tbl[], int tbl_size, int val)
+{
+	unsigned int idx = rt9455_find_idx(tbl, tbl_size, val);
+
+	return regmap_field_write(info->regmap_fields[field], idx);
+}
+
+static int rt9455_register_reset(struct rt9455_info *info)
+{
+	struct device *dev = &info->client->dev;
+	unsigned int v;
+	int ret, limit = 100;
+
+	ret = regmap_field_write(info->regmap_fields[F_RST], 0x01);
+	if (ret) {
+		dev_err(dev, "Failed to set RST bit\n");
+		return ret;
+	}
+
+	/*
+	 * To make sure that reset operation has finished, loop until RST bit
+	 * is set to 0.
+	 */
+	do {
+		ret = regmap_field_read(info->regmap_fields[F_RST], &v);
+		if (ret) {
+			dev_err(dev, "Failed to read RST bit\n");
+			return ret;
+		}
+
+		if (!v)
+			break;
+
+		usleep_range(10, 100);
+	} while (--limit);
+
+	if (!limit)
+		return -EIO;
+
+	return 0;
+}
+
+/* Charger power supply property routines */
+static enum power_supply_property rt9455_charger_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static char *rt9455_charger_supplied_to[] = {
+	"main-battery",
+};
+
+static int rt9455_charger_get_status(struct rt9455_info *info,
+				     union power_supply_propval *val)
+{
+	unsigned int v, pwr_rdy;
+	int ret;
+
+	ret = regmap_field_read(info->regmap_fields[F_PWR_RDY],
+				&pwr_rdy);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
+		return ret;
+	}
+
+	/*
+	 * If PWR_RDY bit is unset, the battery is discharging. Otherwise,
+	 * STAT bits value must be checked.
+	 */
+	if (!pwr_rdy) {
+		val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		return 0;
+	}
+
+	ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read STAT bits\n");
+		return ret;
+	}
+
+	switch (v) {
+	case 0:
+		/*
+		 * If PWR_RDY bit is set, but STAT bits value is 0, the charger
+		 * may be in one of the following cases:
+		 * 1. CHG_EN bit is 0.
+		 * 2. CHG_EN bit is 1 but the battery is not connected.
+		 * In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is
+		 * returned.
+		 */
+		val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		return 0;
+	case 1:
+		val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		return 0;
+	case 2:
+		val->intval = POWER_SUPPLY_STATUS_FULL;
+		return 0;
+	default:
+		val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		return 0;
+	}
+}
+
+static int rt9455_charger_get_health(struct rt9455_info *info,
+				     union power_supply_propval *val)
+{
+	struct device *dev = &info->client->dev;
+	unsigned int v;
+	int ret;
+
+	val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ1 register\n");
+		return ret;
+	}
+
+	if (v & GET_MASK(F_TSDI)) {
+		val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+		return 0;
+	}
+	if (v & GET_MASK(F_VINOVPI)) {
+		val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		return 0;
+	}
+	if (v & GET_MASK(F_BATAB)) {
+		val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		return 0;
+	}
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ2 register\n");
+		return ret;
+	}
+
+	if (v & GET_MASK(F_CHBATOVI)) {
+		val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		return 0;
+	}
+	if (v & GET_MASK(F_CH32MI)) {
+		val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+		return 0;
+	}
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ3 register\n");
+		return ret;
+	}
+
+	if (v & GET_MASK(F_BSTBUSOVI)) {
+		val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		return 0;
+	}
+	if (v & GET_MASK(F_BSTOLI)) {
+		val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+		return 0;
+	}
+	if (v & GET_MASK(F_BSTLOWVI)) {
+		val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		return 0;
+	}
+	if (v & GET_MASK(F_BST32SI)) {
+		val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+		return 0;
+	}
+
+	ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
+	if (ret) {
+		dev_err(dev, "Failed to read STAT bits\n");
+		return ret;
+	}
+
+	if (v == RT9455_FAULT) {
+		val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+		return 0;
+	}
+
+	return 0;
+}
+
+static int rt9455_charger_get_battery_presence(struct rt9455_info *info,
+					       union power_supply_propval *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_field_read(info->regmap_fields[F_BATAB], &v);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read BATAB bit\n");
+		return ret;
+	}
+
+	/*
+	 * Since BATAB is 1 when battery is NOT present and 0 otherwise,
+	 * !BATAB is returned.
+	 */
+	val->intval = !v;
+
+	return 0;
+}
+
+static int rt9455_charger_get_online(struct rt9455_info *info,
+				     union power_supply_propval *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
+		return ret;
+	}
+
+	val->intval = (int)v;
+
+	return 0;
+}
+
+static int rt9455_charger_get_current(struct rt9455_info *info,
+				      union power_supply_propval *val)
+{
+	int curr;
+	int ret;
+
+	ret = rt9455_get_field_val(info, F_ICHRG,
+				   rt9455_ichrg_values,
+				   ARRAY_SIZE(rt9455_ichrg_values),
+				   &curr);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read ICHRG value\n");
+		return ret;
+	}
+
+	val->intval = curr;
+
+	return 0;
+}
+
+static int rt9455_charger_get_current_max(struct rt9455_info *info,
+					  union power_supply_propval *val)
+{
+	int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1;
+
+	val->intval = rt9455_ichrg_values[idx];
+
+	return 0;
+}
+
+static int rt9455_charger_get_voltage(struct rt9455_info *info,
+				      union power_supply_propval *val)
+{
+	int voltage;
+	int ret;
+
+	ret = rt9455_get_field_val(info, F_VOREG,
+				   rt9455_voreg_values,
+				   ARRAY_SIZE(rt9455_voreg_values),
+				   &voltage);
+	if (ret) {
+		dev_err(&info->client->dev, "Failed to read VOREG value\n");
+		return ret;
+	}
+
+	val->intval = voltage;
+
+	return 0;
+}
+
+static int rt9455_charger_get_voltage_max(struct rt9455_info *info,
+					  union power_supply_propval *val)
+{
+	int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
+
+	val->intval = rt9455_vmreg_values[idx];
+
+	return 0;
+}
+
+static int rt9455_charger_get_term_current(struct rt9455_info *info,
+					   union power_supply_propval *val)
+{
+	struct device *dev = &info->client->dev;
+	int ichrg, ieoc_percentage, ret;
+
+	ret = rt9455_get_field_val(info, F_ICHRG,
+				   rt9455_ichrg_values,
+				   ARRAY_SIZE(rt9455_ichrg_values),
+				   &ichrg);
+	if (ret) {
+		dev_err(dev, "Failed to read ICHRG value\n");
+		return ret;
+	}
+
+	ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE,
+				   rt9455_ieoc_percentage_values,
+				   ARRAY_SIZE(rt9455_ieoc_percentage_values),
+				   &ieoc_percentage);
+	if (ret) {
+		dev_err(dev, "Failed to read IEOC value\n");
+		return ret;
+	}
+
+	val->intval = ichrg * ieoc_percentage / 100;
+
+	return 0;
+}
+
+static int rt9455_charger_get_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       union power_supply_propval *val)
+{
+	struct rt9455_info *info = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		return rt9455_charger_get_status(info, val);
+	case POWER_SUPPLY_PROP_HEALTH:
+		return rt9455_charger_get_health(info, val);
+	case POWER_SUPPLY_PROP_PRESENT:
+		return rt9455_charger_get_battery_presence(info, val);
+	case POWER_SUPPLY_PROP_ONLINE:
+		return rt9455_charger_get_online(info, val);
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		return rt9455_charger_get_current(info, val);
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		return rt9455_charger_get_current_max(info, val);
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		return rt9455_charger_get_voltage(info, val);
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		return rt9455_charger_get_voltage_max(info, val);
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		return 0;
+	case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+		return rt9455_charger_get_term_current(info, val);
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = RT9455_MODEL_NAME;
+		return 0;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = RT9455_MANUFACTURER;
+		return 0;
+	default:
+		return -ENODATA;
+	}
+}
+
+static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg,
+			  u32 ieoc_percentage,
+			  u32 mivr, u32 iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int idx, ret;
+
+	ret = rt9455_register_reset(info);
+	if (ret) {
+		dev_err(dev, "Power On Reset failed\n");
+		return ret;
+	}
+
+	/* Set TE bit in order to enable end of charge detection */
+	ret = regmap_field_write(info->regmap_fields[F_TE], 1);
+	if (ret) {
+		dev_err(dev, "Failed to set TE bit\n");
+		return ret;
+	}
+
+	/* Set TE_SHDN_EN bit in order to enable end of charge detection */
+	ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1);
+	if (ret) {
+		dev_err(dev, "Failed to set TE_SHDN_EN bit\n");
+		return ret;
+	}
+
+	/*
+	 * Set BATD_EN bit in order to enable battery detection
+	 * when charging is done
+	 */
+	ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1);
+	if (ret) {
+		dev_err(dev, "Failed to set BATD_EN bit\n");
+		return ret;
+	}
+
+	/*
+	 * Disable Safety Timer. In charge mode, this timer terminates charging
+	 * if no read or write via I2C is done within 32 minutes. This timer
+	 * avoids overcharging the baterry when the OS is not loaded and the
+	 * charger is connected to a power source.
+	 * In boost mode, this timer triggers BST32SI interrupt if no read or
+	 * write via I2C is done within 32 seconds.
+	 * When the OS is loaded and the charger driver is inserted, it is used
+	 * delayed_work, named max_charging_time_work, to avoid overcharging
+	 * the battery.
+	 */
+	ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00);
+	if (ret) {
+		dev_err(dev, "Failed to disable Safety Timer\n");
+		return ret;
+	}
+
+	/* Set ICHRG to value retrieved from device-specific data */
+	ret = rt9455_set_field_val(info, F_ICHRG,
+				   rt9455_ichrg_values,
+				   ARRAY_SIZE(rt9455_ichrg_values), ichrg);
+	if (ret) {
+		dev_err(dev, "Failed to set ICHRG value\n");
+		return ret;
+	}
+
+	/* Set IEOC Percentage to value retrieved from device-specific data */
+	ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE,
+				   rt9455_ieoc_percentage_values,
+				   ARRAY_SIZE(rt9455_ieoc_percentage_values),
+				   ieoc_percentage);
+	if (ret) {
+		dev_err(dev, "Failed to set IEOC Percentage value\n");
+		return ret;
+	}
+
+	/* Set VOREG to value retrieved from device-specific data */
+	ret = rt9455_set_field_val(info, F_VOREG,
+				   rt9455_voreg_values,
+				   ARRAY_SIZE(rt9455_voreg_values),
+				   info->voreg);
+	if (ret) {
+		dev_err(dev, "Failed to set VOREG value\n");
+		return ret;
+	}
+
+	/* Set VMREG value to maximum (4.45V). */
+	idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
+	ret = rt9455_set_field_val(info, F_VMREG,
+				   rt9455_vmreg_values,
+				   ARRAY_SIZE(rt9455_vmreg_values),
+				   rt9455_vmreg_values[idx]);
+	if (ret) {
+		dev_err(dev, "Failed to set VMREG value\n");
+		return ret;
+	}
+
+	/*
+	 * Set MIVR to value retrieved from device-specific data.
+	 * If no value is specified, default value for MIVR is 4.5V.
+	 */
+	if (mivr == -1)
+		mivr = 4500000;
+
+	ret = rt9455_set_field_val(info, F_MIVR,
+				   rt9455_mivr_values,
+				   ARRAY_SIZE(rt9455_mivr_values), mivr);
+	if (ret) {
+		dev_err(dev, "Failed to set MIVR value\n");
+		return ret;
+	}
+
+	/*
+	 * Set IAICR to value retrieved from device-specific data.
+	 * If no value is specified, default value for IAICR is 500 mA.
+	 */
+	if (iaicr == -1)
+		iaicr = 500000;
+
+	ret = rt9455_set_field_val(info, F_IAICR,
+				   rt9455_iaicr_values,
+				   ARRAY_SIZE(rt9455_iaicr_values), iaicr);
+	if (ret) {
+		dev_err(dev, "Failed to set IAICR value\n");
+		return ret;
+	}
+
+	/*
+	 * Set IAICR_INT bit so that IAICR value is determined by IAICR bits
+	 * and not by OTG pin.
+	 */
+	ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01);
+	if (ret) {
+		dev_err(dev, "Failed to set IAICR_INT bit\n");
+		return ret;
+	}
+
+	/*
+	 * Disable CHMIVRI interrupt. Because the driver sets MIVR value,
+	 * CHMIVRI is triggered, but there is no action to be taken by the
+	 * driver when CHMIVRI is triggered.
+	 */
+	ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01);
+	if (ret) {
+		dev_err(dev, "Failed to mask CHMIVRI interrupt\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+/*
+ * Before setting the charger into boost mode, boost output voltage is
+ * set. This is needed because boost output voltage may differ from battery
+ * regulation voltage. F_VOREG bits represent either battery regulation voltage
+ * or boost output voltage, depending on the mode the charger is. Both battery
+ * regulation voltage and boost output voltage are read from DT/ACPI during
+ * probe.
+ */
+static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	ret = rt9455_set_field_val(info, F_VOREG,
+				   rt9455_boost_voltage_values,
+				   ARRAY_SIZE(rt9455_boost_voltage_values),
+				   info->boost_voltage);
+	if (ret) {
+		dev_err(dev, "Failed to set boost output voltage value\n");
+		return ret;
+	}
+
+	return 0;
+}
+#endif
+
+/*
+ * Before setting the charger into charge mode, battery regulation voltage is
+ * set. This is needed because boost output voltage may differ from battery
+ * regulation voltage. F_VOREG bits represent either battery regulation voltage
+ * or boost output voltage, depending on the mode the charger is. Both battery
+ * regulation voltage and boost output voltage are read from DT/ACPI during
+ * probe.
+ */
+static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	ret = rt9455_set_field_val(info, F_VOREG,
+				   rt9455_voreg_values,
+				   ARRAY_SIZE(rt9455_voreg_values),
+				   info->voreg);
+	if (ret) {
+		dev_err(dev, "Failed to set VOREG value\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info,
+						  bool *_is_battery_absent,
+						  bool *_alert_userspace)
+{
+	unsigned int irq1, mask1, mask2;
+	struct device *dev = &info->client->dev;
+	bool is_battery_absent = false;
+	bool alert_userspace = false;
+	int ret;
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ1 register\n");
+		return ret;
+	}
+
+	ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
+	if (ret) {
+		dev_err(dev, "Failed to read MASK1 register\n");
+		return ret;
+	}
+
+	if (irq1 & GET_MASK(F_TSDI)) {
+		dev_err(dev, "Thermal shutdown fault occurred\n");
+		alert_userspace = true;
+	}
+
+	if (irq1 & GET_MASK(F_VINOVPI)) {
+		dev_err(dev, "Overvoltage input occurred\n");
+		alert_userspace = true;
+	}
+
+	if (irq1 & GET_MASK(F_BATAB)) {
+		dev_err(dev, "Battery absence occurred\n");
+		is_battery_absent = true;
+		alert_userspace = true;
+
+		if ((mask1 & GET_MASK(F_BATABM)) == 0) {
+			ret = regmap_field_write(info->regmap_fields[F_BATABM],
+						 0x01);
+			if (ret) {
+				dev_err(dev, "Failed to mask BATAB interrupt\n");
+				return ret;
+			}
+		}
+
+		ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
+		if (ret) {
+			dev_err(dev, "Failed to read MASK2 register\n");
+			return ret;
+		}
+
+		if (mask2 & GET_MASK(F_CHTERMIM)) {
+			ret = regmap_field_write(
+				info->regmap_fields[F_CHTERMIM], 0x00);
+			if (ret) {
+				dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
+				return ret;
+			}
+		}
+
+		if (mask2 & GET_MASK(F_CHRCHGIM)) {
+			ret = regmap_field_write(
+				info->regmap_fields[F_CHRCHGIM], 0x00);
+			if (ret) {
+				dev_err(dev, "Failed to unmask CHRCHGI interrupt\n");
+				return ret;
+			}
+		}
+
+		/*
+		 * When the battery is absent, max_charging_time_work is
+		 * cancelled, since no charging is done.
+		 */
+		cancel_delayed_work_sync(&info->max_charging_time_work);
+		/*
+		 * Since no interrupt is triggered when the battery is
+		 * reconnected, max_charging_time_work is not rescheduled.
+		 * Therefore, batt_presence_work is scheduled to check whether
+		 * the battery is still absent or not.
+		 */
+		queue_delayed_work(system_power_efficient_wq,
+				   &info->batt_presence_work,
+				   RT9455_BATT_PRESENCE_DELAY * HZ);
+	}
+
+	*_is_battery_absent = is_battery_absent;
+
+	if (alert_userspace)
+		*_alert_userspace = alert_userspace;
+
+	return 0;
+}
+
+static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info,
+						  bool is_battery_absent,
+						  bool *_alert_userspace)
+{
+	unsigned int irq2, mask2;
+	struct device *dev = &info->client->dev;
+	bool alert_userspace = false;
+	int ret;
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ2 register\n");
+		return ret;
+	}
+
+	ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
+	if (ret) {
+		dev_err(dev, "Failed to read MASK2 register\n");
+		return ret;
+	}
+
+	if (irq2 & GET_MASK(F_CHRVPI)) {
+		dev_dbg(dev, "Charger fault occurred\n");
+		alert_userspace = true;
+		/*
+		 * CHRVPI bit is set in 2 cases:
+		 * 1. when the power source is connected to the charger.
+		 * 2. when the power source is disconnected from the charger.
+		 * To identify the case, PWR_RDY bit is checked. Because
+		 * PWR_RDY bit is set / cleared after CHRVPI interrupt is
+		 * triggered, it is used delayed_work to later read PWR_RDY bit.
+		 */
+		queue_delayed_work(system_power_efficient_wq,
+				   &info->pwr_rdy_work,
+				   RT9455_PWR_RDY_DELAY * HZ);
+	}
+	if (irq2 & GET_MASK(F_CHBATOVI)) {
+		dev_err(dev, "Battery OVP occurred\n");
+		alert_userspace = true;
+	}
+	if (irq2 & GET_MASK(F_CHTERMI)) {
+		dev_dbg(dev, "Charge terminated\n");
+		if (!is_battery_absent) {
+			if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) {
+				ret = regmap_field_write(
+					info->regmap_fields[F_CHTERMIM], 0x01);
+				if (ret) {
+					dev_err(dev, "Failed to mask CHTERMI interrupt\n");
+					return ret;
+				}
+				/*
+				 * Update MASK2 value, since CHTERMIM bit is
+				 * set.
+				 */
+				mask2 = mask2 | GET_MASK(F_CHTERMIM);
+			}
+			cancel_delayed_work_sync(&info->max_charging_time_work);
+			alert_userspace = true;
+		}
+	}
+	if (irq2 & GET_MASK(F_CHRCHGI)) {
+		dev_dbg(dev, "Recharge request\n");
+		ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+					 RT9455_CHARGE_ENABLE);
+		if (ret) {
+			dev_err(dev, "Failed to enable charging\n");
+			return ret;
+		}
+		if (mask2 & GET_MASK(F_CHTERMIM)) {
+			ret = regmap_field_write(
+				info->regmap_fields[F_CHTERMIM], 0x00);
+			if (ret) {
+				dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
+				return ret;
+			}
+			/* Update MASK2 value, since CHTERMIM bit is cleared. */
+			mask2 = mask2 & ~GET_MASK(F_CHTERMIM);
+		}
+		if (!is_battery_absent) {
+			/*
+			 * No need to check whether the charger is connected to
+			 * power source when CHRCHGI is received, since CHRCHGI
+			 * is not triggered if the charger is not connected to
+			 * the power source.
+			 */
+			queue_delayed_work(system_power_efficient_wq,
+					   &info->max_charging_time_work,
+					   RT9455_MAX_CHARGING_TIME * HZ);
+			alert_userspace = true;
+		}
+	}
+	if (irq2 & GET_MASK(F_CH32MI)) {
+		dev_err(dev, "Charger fault. 32 mins timeout occurred\n");
+		alert_userspace = true;
+	}
+	if (irq2 & GET_MASK(F_CHTREGI)) {
+		dev_warn(dev,
+			 "Charger warning. Thermal regulation loop active\n");
+		alert_userspace = true;
+	}
+	if (irq2 & GET_MASK(F_CHMIVRI)) {
+		dev_dbg(dev,
+			"Charger warning. Input voltage MIVR loop active\n");
+	}
+
+	if (alert_userspace)
+		*_alert_userspace = alert_userspace;
+
+	return 0;
+}
+
+static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info,
+						  bool *_alert_userspace)
+{
+	unsigned int irq3, mask3;
+	struct device *dev = &info->client->dev;
+	bool alert_userspace = false;
+	int ret;
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ3 register\n");
+		return ret;
+	}
+
+	ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3);
+	if (ret) {
+		dev_err(dev, "Failed to read MASK3 register\n");
+		return ret;
+	}
+
+	if (irq3 & GET_MASK(F_BSTBUSOVI)) {
+		dev_err(dev, "Boost fault. Overvoltage input occurred\n");
+		alert_userspace = true;
+	}
+	if (irq3 & GET_MASK(F_BSTOLI)) {
+		dev_err(dev, "Boost fault. Overload\n");
+		alert_userspace = true;
+	}
+	if (irq3 & GET_MASK(F_BSTLOWVI)) {
+		dev_err(dev, "Boost fault. Battery voltage too low\n");
+		alert_userspace = true;
+	}
+	if (irq3 & GET_MASK(F_BST32SI)) {
+		dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n");
+		alert_userspace = true;
+	}
+
+	if (alert_userspace) {
+		dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n");
+		ret = rt9455_set_voreg_before_charge_mode(info);
+		if (ret) {
+			dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+			return ret;
+		}
+		ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+					 RT9455_CHARGE_MODE);
+		if (ret) {
+			dev_err(dev, "Failed to set charger in charge mode\n");
+			return ret;
+		}
+		*_alert_userspace = alert_userspace;
+	}
+
+	return 0;
+}
+
+static irqreturn_t rt9455_irq_handler_thread(int irq, void *data)
+{
+	struct rt9455_info *info = data;
+	struct device *dev;
+	bool alert_userspace = false;
+	bool is_battery_absent = false;
+	unsigned int status;
+	int ret;
+
+	if (!info)
+		return IRQ_NONE;
+
+	dev = &info->client->dev;
+
+	if (irq != info->client->irq) {
+		dev_err(dev, "Interrupt is not for RT9455 charger\n");
+		return IRQ_NONE;
+	}
+
+	ret = regmap_field_read(info->regmap_fields[F_STAT], &status);
+	if (ret) {
+		dev_err(dev, "Failed to read STAT bits\n");
+		return IRQ_HANDLED;
+	}
+	dev_dbg(dev, "Charger status is %d\n", status);
+
+	/*
+	 * Each function that processes an IRQ register receives as output
+	 * parameter alert_userspace pointer. alert_userspace is set to true
+	 * in such a function only if an interrupt has occurred in the
+	 * respective interrupt register. This way, it is avoided the following
+	 * case: interrupt occurs only in IRQ1 register,
+	 * rt9455_irq_handler_check_irq1_register() function sets to true
+	 * alert_userspace, but rt9455_irq_handler_check_irq2_register()
+	 * and rt9455_irq_handler_check_irq3_register() functions set to false
+	 * alert_userspace and power_supply_changed() is never called.
+	 */
+	ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent,
+						     &alert_userspace);
+	if (ret) {
+		dev_err(dev, "Failed to handle IRQ1 register\n");
+		return IRQ_HANDLED;
+	}
+
+	ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent,
+						     &alert_userspace);
+	if (ret) {
+		dev_err(dev, "Failed to handle IRQ2 register\n");
+		return IRQ_HANDLED;
+	}
+
+	ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace);
+	if (ret) {
+		dev_err(dev, "Failed to handle IRQ3 register\n");
+		return IRQ_HANDLED;
+	}
+
+	if (alert_userspace) {
+		/*
+		 * Sometimes, an interrupt occurs while rt9455_probe() function
+		 * is executing and power_supply_register() is not yet called.
+		 * Do not call power_supply_charged() in this case.
+		 */
+		if (info->charger)
+			power_supply_changed(info->charger);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg,
+				   u32 *ieoc_percentage,
+				   u32 *mivr, u32 *iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	if (!dev->of_node && !ACPI_HANDLE(dev)) {
+		dev_err(dev, "No support for either device tree or ACPI\n");
+		return -EINVAL;
+	}
+	/*
+	 * ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory
+	 * parameters.
+	 */
+	ret = device_property_read_u32(dev, "richtek,output-charge-current",
+				       ichrg);
+	if (ret) {
+		dev_err(dev, "Error: missing \"output-charge-current\" property\n");
+		return ret;
+	}
+
+	ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage",
+				       ieoc_percentage);
+	if (ret) {
+		dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n");
+		return ret;
+	}
+
+	ret = device_property_read_u32(dev,
+				       "richtek,battery-regulation-voltage",
+				       &info->voreg);
+	if (ret) {
+		dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n");
+		return ret;
+	}
+
+	ret = device_property_read_u32(dev, "richtek,boost-output-voltage",
+				       &info->boost_voltage);
+	if (ret) {
+		dev_err(dev, "Error: missing \"boost-output-voltage\" property\n");
+		return ret;
+	}
+
+	/*
+	 * MIVR and IAICR are optional parameters. Do not return error if one of
+	 * them is not present in ACPI table or device tree specification.
+	 */
+	device_property_read_u32(dev, "richtek,min-input-voltage-regulation",
+				 mivr);
+	device_property_read_u32(dev, "richtek,avg-input-current-regulation",
+				 iaicr);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+static int rt9455_usb_event_none(struct rt9455_info *info,
+				 u8 opa_mode, u8 iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	if (opa_mode == RT9455_BOOST_MODE) {
+		ret = rt9455_set_voreg_before_charge_mode(info);
+		if (ret) {
+			dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+			return ret;
+		}
+		/*
+		 * If the charger is in boost mode, and it has received
+		 * USB_EVENT_NONE, this means the consumer device powered by the
+		 * charger is not connected anymore.
+		 * In this case, the charger goes into charge mode.
+		 */
+		dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n");
+		ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+					 RT9455_CHARGE_MODE);
+		if (ret) {
+			dev_err(dev, "Failed to set charger in charge mode\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n");
+	if (iaicr != RT9455_IAICR_100MA) {
+		ret = regmap_field_write(info->regmap_fields[F_IAICR],
+					 RT9455_IAICR_100MA);
+		if (ret) {
+			dev_err(dev, "Failed to set IAICR value\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_vbus(struct rt9455_info *info,
+				 u8 opa_mode, u8 iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	if (opa_mode == RT9455_BOOST_MODE) {
+		ret = rt9455_set_voreg_before_charge_mode(info);
+		if (ret) {
+			dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+			return ret;
+		}
+		/*
+		 * If the charger is in boost mode, and it has received
+		 * USB_EVENT_VBUS, this means the consumer device powered by the
+		 * charger is not connected anymore.
+		 * In this case, the charger goes into charge mode.
+		 */
+		dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n");
+		ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+					 RT9455_CHARGE_MODE);
+		if (ret) {
+			dev_err(dev, "Failed to set charger in charge mode\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n");
+	if (iaicr != RT9455_IAICR_500MA) {
+		ret = regmap_field_write(info->regmap_fields[F_IAICR],
+					 RT9455_IAICR_500MA);
+		if (ret) {
+			dev_err(dev, "Failed to set IAICR value\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_id(struct rt9455_info *info,
+			       u8 opa_mode, u8 iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	if (opa_mode == RT9455_CHARGE_MODE) {
+		ret = rt9455_set_boost_voltage_before_boost_mode(info);
+		if (ret) {
+			dev_err(dev, "Failed to set boost output voltage before entering boost mode\n");
+			return ret;
+		}
+		/*
+		 * If the charger is in charge mode, and it has received
+		 * USB_EVENT_ID, this means a consumer device is connected and
+		 * it should be powered by the charger.
+		 * In this case, the charger goes into boost mode.
+		 */
+		dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n");
+		ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+					 RT9455_BOOST_MODE);
+		if (ret) {
+			dev_err(dev, "Failed to set charger in boost mode\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n");
+	if (iaicr != RT9455_IAICR_100MA) {
+		ret = regmap_field_write(info->regmap_fields[F_IAICR],
+					 RT9455_IAICR_100MA);
+		if (ret) {
+			dev_err(dev, "Failed to set IAICR value\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_charger(struct rt9455_info *info,
+				    u8 opa_mode, u8 iaicr)
+{
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	if (opa_mode == RT9455_BOOST_MODE) {
+		ret = rt9455_set_voreg_before_charge_mode(info);
+		if (ret) {
+			dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+			return ret;
+		}
+		/*
+		 * If the charger is in boost mode, and it has received
+		 * USB_EVENT_CHARGER, this means the consumer device powered by
+		 * the charger is not connected anymore.
+		 * In this case, the charger goes into charge mode.
+		 */
+		dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n");
+		ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+					 RT9455_CHARGE_MODE);
+		if (ret) {
+			dev_err(dev, "Failed to set charger in charge mode\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n");
+	if (iaicr != RT9455_IAICR_NO_LIMIT) {
+		ret = regmap_field_write(info->regmap_fields[F_IAICR],
+					 RT9455_IAICR_NO_LIMIT);
+		if (ret) {
+			dev_err(dev, "Failed to set IAICR value\n");
+			return NOTIFY_DONE;
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int rt9455_usb_event(struct notifier_block *nb,
+			    unsigned long event, void *power)
+{
+	struct rt9455_info *info = container_of(nb, struct rt9455_info, nb);
+	struct device *dev = &info->client->dev;
+	unsigned int opa_mode, iaicr;
+	int ret;
+
+	/*
+	 * Determine whether the charger is in charge mode
+	 * or in boost mode.
+	 */
+	ret = regmap_field_read(info->regmap_fields[F_OPA_MODE],
+				&opa_mode);
+	if (ret) {
+		dev_err(dev, "Failed to read OPA_MODE value\n");
+		return NOTIFY_DONE;
+	}
+
+	ret = regmap_field_read(info->regmap_fields[F_IAICR],
+				&iaicr);
+	if (ret) {
+		dev_err(dev, "Failed to read IAICR value\n");
+		return NOTIFY_DONE;
+	}
+
+	dev_dbg(dev, "Received USB event %lu\n", event);
+	switch (event) {
+	case USB_EVENT_NONE:
+		return rt9455_usb_event_none(info, opa_mode, iaicr);
+	case USB_EVENT_VBUS:
+		return rt9455_usb_event_vbus(info, opa_mode, iaicr);
+	case USB_EVENT_ID:
+		return rt9455_usb_event_id(info, opa_mode, iaicr);
+	case USB_EVENT_CHARGER:
+		return rt9455_usb_event_charger(info, opa_mode, iaicr);
+	default:
+		dev_err(dev, "Unknown USB event\n");
+	}
+	return NOTIFY_DONE;
+}
+#endif
+
+static void rt9455_pwr_rdy_work_callback(struct work_struct *work)
+{
+	struct rt9455_info *info = container_of(work, struct rt9455_info,
+						pwr_rdy_work.work);
+	struct device *dev = &info->client->dev;
+	unsigned int pwr_rdy;
+	int ret;
+
+	ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy);
+	if (ret) {
+		dev_err(dev, "Failed to read PWR_RDY bit\n");
+		return;
+	}
+	switch (pwr_rdy) {
+	case RT9455_PWR_FAULT:
+		dev_dbg(dev, "Charger disconnected from power source\n");
+		cancel_delayed_work_sync(&info->max_charging_time_work);
+		break;
+	case RT9455_PWR_GOOD:
+		dev_dbg(dev, "Charger connected to power source\n");
+		ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+					 RT9455_CHARGE_ENABLE);
+		if (ret) {
+			dev_err(dev, "Failed to enable charging\n");
+			return;
+		}
+		queue_delayed_work(system_power_efficient_wq,
+				   &info->max_charging_time_work,
+				   RT9455_MAX_CHARGING_TIME * HZ);
+		break;
+	}
+}
+
+static void rt9455_max_charging_time_work_callback(struct work_struct *work)
+{
+	struct rt9455_info *info = container_of(work, struct rt9455_info,
+						max_charging_time_work.work);
+	struct device *dev = &info->client->dev;
+	int ret;
+
+	dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n");
+	ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+				 RT9455_CHARGE_DISABLE);
+	if (ret)
+		dev_err(dev, "Failed to disable charging\n");
+}
+
+static void rt9455_batt_presence_work_callback(struct work_struct *work)
+{
+	struct rt9455_info *info = container_of(work, struct rt9455_info,
+						batt_presence_work.work);
+	struct device *dev = &info->client->dev;
+	unsigned int irq1, mask1;
+	int ret;
+
+	ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
+	if (ret) {
+		dev_err(dev, "Failed to read IRQ1 register\n");
+		return;
+	}
+
+	/*
+	 * If the battery is still absent, batt_presence_work is rescheduled.
+	 * Otherwise, max_charging_time is scheduled.
+	 */
+	if (irq1 & GET_MASK(F_BATAB)) {
+		queue_delayed_work(system_power_efficient_wq,
+				   &info->batt_presence_work,
+				   RT9455_BATT_PRESENCE_DELAY * HZ);
+	} else {
+		queue_delayed_work(system_power_efficient_wq,
+				   &info->max_charging_time_work,
+				   RT9455_MAX_CHARGING_TIME * HZ);
+
+		ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
+		if (ret) {
+			dev_err(dev, "Failed to read MASK1 register\n");
+			return;
+		}
+
+		if (mask1 & GET_MASK(F_BATABM)) {
+			ret = regmap_field_write(info->regmap_fields[F_BATABM],
+						 0x00);
+			if (ret)
+				dev_err(dev, "Failed to unmask BATAB interrupt\n");
+		}
+	}
+}
+
+static const struct power_supply_desc rt9455_charger_desc = {
+	.name			= RT9455_DRIVER_NAME,
+	.type			= POWER_SUPPLY_TYPE_USB,
+	.properties		= rt9455_charger_properties,
+	.num_properties		= ARRAY_SIZE(rt9455_charger_properties),
+	.get_property		= rt9455_charger_get_property,
+};
+
+static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case RT9455_REG_DEV_ID:
+	case RT9455_REG_IRQ1:
+	case RT9455_REG_IRQ2:
+	case RT9455_REG_IRQ3:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case RT9455_REG_DEV_ID:
+	case RT9455_REG_CTRL5:
+	case RT9455_REG_CTRL6:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config rt9455_regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+	.writeable_reg	= rt9455_is_writeable_reg,
+	.volatile_reg	= rt9455_is_volatile_reg,
+	.max_register	= RT9455_REG_MASK3,
+	.cache_type	= REGCACHE_RBTREE,
+};
+
+static int rt9455_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct device *dev = &client->dev;
+	struct rt9455_info *info;
+	struct power_supply_config rt9455_charger_config = {};
+	/*
+	 * Mandatory device-specific data values. Also, VOREG and boost output
+	 * voltage are mandatory values, but they are stored in rt9455_info
+	 * structure.
+	 */
+	u32 ichrg, ieoc_percentage;
+	/* Optional device-specific data values. */
+	u32 mivr = -1, iaicr = -1;
+	int i, ret;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+		return -ENODEV;
+	}
+	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->client = client;
+	i2c_set_clientdata(client, info);
+
+	info->regmap = devm_regmap_init_i2c(client,
+					    &rt9455_regmap_config);
+	if (IS_ERR(info->regmap)) {
+		dev_err(dev, "Failed to initialize register map\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < F_MAX_FIELDS; i++) {
+		info->regmap_fields[i] =
+			devm_regmap_field_alloc(dev, info->regmap,
+						rt9455_reg_fields[i]);
+		if (IS_ERR(info->regmap_fields[i])) {
+			dev_err(dev,
+				"Failed to allocate regmap field = %d\n", i);
+			return PTR_ERR(info->regmap_fields[i]);
+		}
+	}
+
+	ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage,
+				      &mivr, &iaicr);
+	if (ret) {
+		dev_err(dev, "Failed to discover charger\n");
+		return ret;
+	}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+	info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+	if (IS_ERR(info->usb_phy)) {
+		dev_err(dev, "Failed to get USB transceiver\n");
+	} else {
+		info->nb.notifier_call = rt9455_usb_event;
+		ret = usb_register_notifier(info->usb_phy, &info->nb);
+		if (ret) {
+			dev_err(dev, "Failed to register USB notifier\n");
+			/*
+			 * If usb_register_notifier() fails, set notifier_call
+			 * to NULL, to avoid calling usb_unregister_notifier().
+			 */
+			info->nb.notifier_call = NULL;
+		}
+	}
+#endif
+
+	INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback);
+	INIT_DEFERRABLE_WORK(&info->max_charging_time_work,
+			     rt9455_max_charging_time_work_callback);
+	INIT_DEFERRABLE_WORK(&info->batt_presence_work,
+			     rt9455_batt_presence_work_callback);
+
+	rt9455_charger_config.of_node		= dev->of_node;
+	rt9455_charger_config.drv_data		= info;
+	rt9455_charger_config.supplied_to	= rt9455_charger_supplied_to;
+	rt9455_charger_config.num_supplicants	=
+					ARRAY_SIZE(rt9455_charger_supplied_to);
+	ret = devm_request_threaded_irq(dev, client->irq, NULL,
+					rt9455_irq_handler_thread,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					RT9455_DRIVER_NAME, info);
+	if (ret) {
+		dev_err(dev, "Failed to register IRQ handler\n");
+		goto put_usb_notifier;
+	}
+
+	ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr);
+	if (ret) {
+		dev_err(dev, "Failed to set charger to its default values\n");
+		goto put_usb_notifier;
+	}
+
+	info->charger = devm_power_supply_register(dev, &rt9455_charger_desc,
+						   &rt9455_charger_config);
+	if (IS_ERR(info->charger)) {
+		dev_err(dev, "Failed to register charger\n");
+		ret = PTR_ERR(info->charger);
+		goto put_usb_notifier;
+	}
+
+	return 0;
+
+put_usb_notifier:
+#if IS_ENABLED(CONFIG_USB_PHY)
+	if (info->nb.notifier_call)  {
+		usb_unregister_notifier(info->usb_phy, &info->nb);
+		info->nb.notifier_call = NULL;
+	}
+#endif
+	return ret;
+}
+
+static int rt9455_remove(struct i2c_client *client)
+{
+	int ret;
+	struct rt9455_info *info = i2c_get_clientdata(client);
+
+	ret = rt9455_register_reset(info);
+	if (ret)
+		dev_err(&info->client->dev, "Failed to set charger to its default values\n");
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+	if (info->nb.notifier_call)
+		usb_unregister_notifier(info->usb_phy, &info->nb);
+#endif
+
+	cancel_delayed_work_sync(&info->pwr_rdy_work);
+	cancel_delayed_work_sync(&info->max_charging_time_work);
+	cancel_delayed_work_sync(&info->batt_presence_work);
+
+	return ret;
+}
+
+static const struct i2c_device_id rt9455_i2c_id_table[] = {
+	{ RT9455_DRIVER_NAME, 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table);
+
+static const struct of_device_id rt9455_of_match[] = {
+	{ .compatible = "richtek,rt9455", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, rt9455_of_match);
+
+static const struct acpi_device_id rt9455_i2c_acpi_match[] = {
+	{ "RT945500", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match);
+
+static struct i2c_driver rt9455_driver = {
+	.probe		= rt9455_probe,
+	.remove		= rt9455_remove,
+	.id_table	= rt9455_i2c_id_table,
+	.driver = {
+		.name		= RT9455_DRIVER_NAME,
+		.of_match_table	= of_match_ptr(rt9455_of_match),
+		.acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match),
+	},
+};
+module_i2c_driver(rt9455_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anda-Maria Nicolae <anda-maria.nicolae@intel.com>");
+MODULE_ALIAS("i2c:rt9455-charger");
+MODULE_DESCRIPTION("Richtek RT9455 Charger Driver");
diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c
index de1178659d4b..d6226d68b574 100644
--- a/drivers/power/sbs-battery.c
+++ b/drivers/power/sbs-battery.c
@@ -28,6 +28,7 @@
 #include <linux/interrupt.h>
 #include <linux/gpio.h>
 #include <linux/of.h>
+#include <linux/stat.h>
 
 #include <linux/power/sbs-battery.h>
 
@@ -170,6 +171,7 @@ struct sbs_info {
 
 static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
 static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
+static bool force_load;
 
 static int sbs_read_word_data(struct i2c_client *client, u8 address)
 {
@@ -885,14 +887,17 @@ static int sbs_probe(struct i2c_client *client,
 
 skip_gpio:
 	/*
-	 * Before we register, we need to make sure we can actually talk
+	 * Before we register, we might need to make sure we can actually talk
 	 * to the battery.
 	 */
-	rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
-	if (rc < 0) {
-		dev_err(&client->dev, "%s: Failed to get device status\n",
-			__func__);
-		goto exit_psupply;
+	if (!force_load) {
+		rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
+
+		if (rc < 0) {
+			dev_err(&client->dev, "%s: Failed to get device status\n",
+				__func__);
+			goto exit_psupply;
+		}
 	}
 
 	chip->power_supply = power_supply_register(&client->dev, sbs_desc,
@@ -991,3 +996,7 @@ module_i2c_driver(sbs_battery_driver);
 
 MODULE_DESCRIPTION("SBS battery monitor driver");
 MODULE_LICENSE("GPL");
+
+module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(force_load,
+		 "Attempt to load the driver even if no battery is connected");
diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c
index 0161bdabd5a3..db11ae6599f3 100644
--- a/drivers/power/wm831x_power.c
+++ b/drivers/power/wm831x_power.c
@@ -609,6 +609,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
 	return ret;
 
 err_bat_irq:
+	--i;
 	for (; i >= 0; i--) {
 		irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
 		free_irq(irq, power);