[PATCH] power: supply: Add driver for TI BQ2416X battery charger

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




There is interest in adding a Linux driver for TI BQ2416X battery
charger. The driver supports BQ24160 chip, thus can be easily extended
for other BQ2416X family chargers.
Device exposes 'POWER_SUPPLY_PROP_*' properties and a number of knobs
for controlling the charging process as well as sends power supply change
notification via power-supply subsystem.

Signed-off-by: Wojciech Ziemba <wojciech.ziemba@xxxxxxxxxxxx>
---
 .../devicetree/bindings/power/supply/bq2416x.txt   |   71 +
 drivers/power/supply/Kconfig                       |    7 +
 drivers/power/supply/Makefile                      |    1 +
 drivers/power/supply/bq2416x_charger.c             | 1871 ++++++++++++++++++++
 include/dt-bindings/power/bq2416x_charger.h        |   23 +
 include/linux/power/bq2416x_charger.h              |   80 +
 6 files changed, 2053 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/supply/bq2416x.txt
 create mode 100644 drivers/power/supply/bq2416x_charger.c
 create mode 100644 include/dt-bindings/power/bq2416x_charger.h
 create mode 100644 include/linux/power/bq2416x_charger.h

diff --git a/Documentation/devicetree/bindings/power/supply/bq2416x.txt b/Documentation/devicetree/bindings/power/supply/bq2416x.txt
new file mode 100644
index 0000000..8f4b814
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/bq2416x.txt
@@ -0,0 +1,71 @@
+Binding for TI bq2416x Li-Ion Charger
+
+Required properties:
+===================
+- compatible: one of the following:
+ * "ti,bq24160"
+ * "ti,bq24160a"
+ * "ti,bq24161"
+ * "ti,bq24161b"
+ * "ti,bq24163"
+ * "ti,bq24168"
+
+- reg:			I2c address of the device.
+- interrupt-parent:	The irq controller(phandle) connected to INT pin on BQ2416x
+- interrupts:		The irq number assigned for INT pin.
+
+Optional properties:
+===================
+- interrupt-names:		Meanigfull irq name.
+- ti,charge-voltage:		Charge volatge [mV].
+- ti,charge-current:		Charge current [mA].
+- ti,termination-current:	Termination current [mA}.
+- ti,in-current-limit:		IN source current limit. enum:
+				- IN_CURR_LIM_1500MA (0)
+				- IN_CURR_LIM_2500MA (1)
+
+- ti,usb-current-limit:		USB source current limit. enum:
+				- USB_CURR_LIM_100MA (0)
+				- USB_CURR_LIM_150MA (1)
+				- USB_CURR_LIM_500MA (2)
+				- USB_CURR_LIM_800MA (3)
+				- USB_CURR_LIM_900MA (4)
+				- USB_CURR_LIM_1500MA (5)
+
+- ti,status-pin-enable:		0 or 1. Enable charge status output STAT pin.
+- ti,current-termination-enable:0 or 1. Enable charge current termination.
+- ti,usb-dpm-voltage:		USB dpm voltage [mV]. Refer to datasheet.
+- ti,in-dpm-voltage:		IN dpm voltage [mV].
+- ti,safety-timer:		Safety timer. enum:
+			- TMR_27MIN (0)
+			- TMR_6H (1)
+			- TMR_9H (2)
+			- TMR_OFF (3)
+
+- ti,supplied-to:	string array: max 4. Names of devices to which
+			the charger supplies.
+
+Example:
+=======
+#include <dt-bindings/power/bq2416x_charger.h>
+
+bq24160@6b {
+	compatible = "ti,bq24160";
+	reg = <0x6b>;
+
+	interrupt-parent = <&gpio5>;
+	interrupts = <31 IRQ_TYPE_EDGE_RISING>;
+	interrupt-names = "bq24160-charge-status-change";
+
+	ti,charge-voltage = <4300>;
+	ti,charge-current = <1300>;
+	ti,termination-current = <100>;
+	ti,in-current-limit = <IN_CURR_LIM_1500MA>;
+	ti,usb-current-limit = <USB_CURR_LIM_1500MA>;
+	ti,status-pin-enable = <1>;
+	ti,current-termination-enable = <1>;
+	ti,usb-dpm-voltage = <4300>;
+	ti,in-dpm-voltage = <4300>;
+	ti,safety-timer = <TMR_6H>; /* charge max 6h */
+	ti,supplied-to = "bq27621-0";
+};
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 76806a0..575096e 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -413,6 +413,13 @@ config CHARGER_BQ2415X
 	  You'll need this driver to charge batteries on e.g. Nokia
 	  RX-51/N900.
 
+config CHARGER_BQ2416X
+	tristate "TI BQ2416x Dual-Input, Single Cell Switch-Mode Li-Ion charger"
+	depends on I2C
+	help
+	  Say Y here to enable support for the TI BQ2416x battery charger.
+	  The driver is configured to operate with a single lithium cell.
+
 config CHARGER_BQ24190
 	tristate "TI BQ24190 battery charger driver"
 	depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 36c599d..73711e0 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)	+= max8998_charger.o
 obj-$(CONFIG_CHARGER_QCOM_SMBB)	+= qcom_smbb.o
 obj-$(CONFIG_CHARGER_BQ2415X)	+= bq2415x_charger.o
+obj-$(CONFIG_CHARGER_BQ2416X)	+= bq2416x_charger.o
 obj-$(CONFIG_CHARGER_BQ24190)	+= bq24190_charger.o
 obj-$(CONFIG_CHARGER_BQ24257)	+= bq24257_charger.o
 obj-$(CONFIG_CHARGER_BQ24735)	+= bq24735-charger.o
diff --git a/drivers/power/supply/bq2416x_charger.c b/drivers/power/supply/bq2416x_charger.c
new file mode 100644
index 0000000..fa13e55
--- /dev/null
+++ b/drivers/power/supply/bq2416x_charger.c
@@ -0,0 +1,1871 @@
+/*
+ * Driver for BQ2416X Li-Ion Battery Charger
+ *
+ * Copyright (C) 2015 Verifone, Inc.
+ *
+ * Author: Wojciech Ziemba <wojciech.ziemba@xxxxxxxxxxxx>
+ *
+ * This package 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 PACKAGE IS PROVIDED AS IS AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * The bq2416x series is a 2.5A, Dual-Input, Single-Cell Switched-Mode
+ * Li-Ion Battery Charger with Power
+ * Path Management and I2C Interface
+ *
+ * This driver was tested on BQ24160.
+ *
+ * Datasheets:
+ * http://www.ti.com/product/bq24160
+ * http://www.ti.com/product/bq24160a
+ * http://www.ti.com/product/bq24161
+ * http://www.ti.com/product/bq24161b
+ * http://www.ti.com/product/bq24163
+ * http://www.ti.com/product/bq24168
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/power/bq2416x_charger.h>
+
+/* Get the value of bitfield */
+#define BF_GET(_y, _mask) (((_y) & _mask) >> (__builtin_ffs((int) _mask) - 1))
+/* Shift the value of bitfield. Mask based */
+#define BF_SHIFT(_x, _mask) ((_x) << (__builtin_ffs((int) _mask) - 1))
+/* Watchdog timer. 3 second in reserve */
+#define BQ2416X_WATCHDOG_TIMER		(30 - 3)
+/* Register numbers */
+#define BQ2416X_REG_STATUS		0x00
+#define BQ2416X_REG_SUP_STATUS		0x01
+#define BQ2416X_REG_CONTROL		0x02
+#define BQ2416X_REG_BAT_VOLT		0x03
+#define BQ2416X_REG_VENDOR		0x04
+#define BQ2416X_REG_TERM		0x05
+#define BQ2416X_REG_DPM			0x06
+#define BQ2416X_REG_NTC			0x07
+#define BQ2416X_REG_MAX			0x08
+
+/* status/control register */
+#define BQ2416X_REG_STATUS_TMR_RST_MASK	BIT(7)
+#define BQ2416X_REG_STATUS_STAT_MASK		(BIT(6) | BIT(5) | BIT(4))
+#define BQ2416X_REG_STATUS_SUPPLY_SEL_MASK	BIT(3)
+#define BQ2416X_REG_STATUS_FAULT_MASK		(BIT(2) | BIT(1) | BIT(0))
+
+/* battery/supply status register */
+#define BQ2416X_REG_SUP_STATUS_INSTAT_MASK	(BIT(7) | BIT(6))
+#define BQ2416X_REG_SUP_STATUS_USBSTAT_MASK	(BIT(5) | BIT(4))
+#define BQ2416X_REG_SUP_STATUS_OTG_LOCK_MASK	BIT(3)
+#define BQ2416X_REG_SUP_STATUS_BATSTAT_MASK	(BIT(2) | BIT(1))
+#define BQ2416X_REG_SUP_STATUS_EN_NOBATOP_MASK	BIT(0)
+
+/* control register */
+#define BQ2416X_REG_CONTROL_RESET_MASK		BIT(7)
+#define BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK	(BIT(6) | BIT(5) | BIT(4))
+#define BQ2416X_REG_CONTROL_EN_STAT_MASK	BIT(3)
+#define BQ2416X_REG_CONTROL_TE_MASK		BIT(2)
+#define BQ2416X_REG_CONTROL_CE_MASK		BIT(1)
+#define BQ2416X_REG_CONTROL_HZ_MODE_MASK	BIT(0)
+
+/* control/battery voltage register */
+#define BQ2416X_REG_BAT_VOLT_MASK		(BIT(7) | BIT(6) | BIT(5) | \
+						BIT(4) | BIT(3) | BIT(2))
+#define BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK	BIT(1)
+
+/* vendor/part/revision register */
+#define BQ2416X_REG_VENDOR_REV_MASK		(BIT(2) | BIT(1) | BIT(0))
+#define BQ2416X_REG_VENDOR_CODE_MASK		(BIT(7) | BIT(6) | BIT(5))
+
+/* battery termination fast charge current register */
+#define BQ2416X_REG_TERM_CHRG_CURR_MASK	(BIT(7) | BIT(6) | BIT(5) | \
+						BIT(4) | BIT(3))
+#define BQ2416X_REG_TERM_TERM_CURR_MASK	(BIT(2) | BIT(1) | BIT(0))
+
+/* VIN-DPM voltage/DPPM status register */
+#define BQ2416X_REG_DPM_MINSYS_STATUS_MASK	BIT(7)
+#define BQ2416X_REG_DPM_STATUS_MASK		BIT(6)
+#define BQ2416X_REG_DPM_USB_VOLT_MASK		(BIT(5) | BIT(4) | BIT(3))
+#define BQ2416X_REG_DPM_IN_VOLT_MASK		(BIT(2) | BIT(1) | BIT(0))
+
+/* Safety timer/NTC monitor register */
+#define BQ2416X_REG_NTC_TMRX2_MASK		BIT(7)
+#define BQ2416X_REG_NTC_TMR_MASK		(BIT(6) | BIT(5))
+#define BQ2416X_REG_NTC_TS_EN_MASK		BIT(3)
+#define BQ2416X_REG_NTC_TS_FAULT_MASK		(BIT(2) | BIT(1))
+#define BQ2416X_REG_NTC_LOW_CHARGE_MASK	BIT(0)
+
+/* Charge voltage [mV] */
+#define BQ2416X_CHARGE_VOLTAGE_MIN	3500
+#define BQ2416X_CHARGE_VOLTAGE_MAX	4440
+#define BQ2416X_CHARGE_VOLTAGE_STEP	20
+
+/* IN current limit */
+#define BQ2416X_IN_CURR_LIM_1500	0
+#define BQ2416X_IN_CURR_LIM_2500	1
+
+/* Charge current [mA] */
+#define BQ2416X_CHARGE_CURRENT_MIN	550
+#define BQ2416X_CHARGE_CURRENT_MAX	2500
+#define BQ2416X_CHARGE_CURRENT_STEP	75
+
+/* Charge termination current in mA */
+#define BQ2416X_CHARGE_TERM_CURRENT_MIN		50
+#define BQ2416X_CHARGE_TERM_CURRENT_MAX		400
+#define BQ2416X_CHARGE_TERM_CURRENT_STEP	50
+
+/* USB DPM voltage [mV] */
+#define BQ2416X_DPM_USB_VOLTAGE_MIN	4200
+#define BQ2416X_DPM_USB_VOLTAGE_MAX	4760
+#define BQ2416X_DPM_USB_VOLTAGE_STEP	80
+
+/* IN DPM voltage [mV] */
+#define BQ2416X_DPM_IN_VOLTAGE_MIN	4200
+#define BQ2416X_DPM_IN_VOLTAGE_MAX	4760
+#define BQ2416X_DPM_IN_VOLTAGE_STEP	80
+
+/* Supported chips */
+enum  bq2416x_type {
+	BQ24160,
+	BQ24160A,
+	BQ24161,
+	BQ24161B,
+	BQ24163,
+	BQ24168,
+};
+
+/* Charger status */
+enum {
+	STAT_NO_VALID_SOURCE,
+	STAT_IN_READY,
+	STAT_USB_READY,
+	STAT_CHARGING_FROM_IN,
+	STAT_CHARGING_FROM_USB,
+	STAT_CHARGE_DONE,
+	STAT_NA,
+	STAT_FAULT,
+};
+
+/* Charger status to string/power subsys status map */
+static const struct {
+	const char * const str;
+	const int id;
+} bq2416x_charge_status[] = {
+	[STAT_NO_VALID_SOURCE] = {"No valid source",
+				POWER_SUPPLY_STATUS_NOT_CHARGING},
+	[STAT_IN_READY] = {"IN ready", POWER_SUPPLY_STATUS_NOT_CHARGING},
+	[STAT_USB_READY] = {"USB ready", POWER_SUPPLY_STATUS_NOT_CHARGING},
+	[STAT_CHARGING_FROM_IN] = {"Charging from IN",
+				POWER_SUPPLY_STATUS_CHARGING},
+	[STAT_CHARGING_FROM_USB] = {"Charging from USB",
+				POWER_SUPPLY_STATUS_CHARGING},
+	[STAT_CHARGE_DONE] = {"Charge done", POWER_SUPPLY_STATUS_FULL},
+	[STAT_NA] = {"N/A", POWER_SUPPLY_STATUS_UNKNOWN},
+	[STAT_FAULT] = {"Fault", POWER_SUPPLY_STATUS_NOT_CHARGING},
+};
+
+/* Charger fault */
+enum {
+	FAULT_NORMAL,
+	FAULT_THERMAL_SHUTDOWN,
+	FAULT_BATT_TEMP_FAULT,
+	FAULT_WDOG_TIMER_EXPIRED,
+	FAULT_SAFETY_TIMER_EXPIRED,
+	FAULT_IN_SUPPLY_FAULT,
+	FAULT_USB_SUPPLY_FAULT,
+	FAULT_BATTERY_FAULT,
+};
+
+/* Charger fault to string/power subsys fault map */
+static const struct {
+	const char * const str;
+	const int id;
+} bq2416x_charge_fault[] = {
+	[FAULT_NORMAL] = {"Normal", POWER_SUPPLY_HEALTH_GOOD},
+	[FAULT_THERMAL_SHUTDOWN] = {"Thermal shutdown",
+				POWER_SUPPLY_HEALTH_OVERHEAT},
+	[FAULT_BATT_TEMP_FAULT] = {"Battery temp fault",
+				POWER_SUPPLY_HEALTH_OVERHEAT},
+	[FAULT_WDOG_TIMER_EXPIRED] = {"Watchdog timer expired",
+				POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE},
+	[FAULT_SAFETY_TIMER_EXPIRED] = {"Safety timer expired",
+				POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE},
+	[FAULT_IN_SUPPLY_FAULT] = {"IN Supply fault",
+				POWER_SUPPLY_HEALTH_UNSPEC_FAILURE},
+	[FAULT_USB_SUPPLY_FAULT] = {"USB Supply fault",
+				POWER_SUPPLY_HEALTH_UNSPEC_FAILURE},
+	[FAULT_BATTERY_FAULT] = {"Battery fault", POWER_SUPPLY_HEALTH_DEAD},
+};
+
+/* IN(Wall) source status */
+enum {
+	INSTAT_NORMAL,
+	INSTAT_SUPPLY_OVP,
+	INSTAT_WEAK_SOURCE_CONNECTED,
+	INSTAT_FAULTY_ADAPTER,
+};
+
+/* IN(Wall) source status to string map */
+static const char * const bq2416x_in_status[] = {
+	[INSTAT_NORMAL] = "Normal",
+	[INSTAT_SUPPLY_OVP] = "OVP",
+	[INSTAT_WEAK_SOURCE_CONNECTED] = "Weak source",
+	[INSTAT_FAULTY_ADAPTER] = "Faulty adapter",
+};
+
+/* Battery status */
+enum {
+	BATSTAT_BATTERY_PRESENT,
+	BATSTAT_BATTERY_OVP,
+	BATSTAT_BATTERY_NOT_PRESENT,
+	BATSTAT_BATTERY_NA,
+};
+
+/* Battery status to string map */
+static const char * const bq2416x_bat_status[] = {
+	[BATSTAT_BATTERY_PRESENT] = "present",
+	[BATSTAT_BATTERY_OVP] = "OVP",
+	[BATSTAT_BATTERY_NOT_PRESENT] = "not present",
+	[BATSTAT_BATTERY_NA] = "NA",
+};
+
+static const int bq2416x_usb_curr_lim[] = {
+	[USB_CURR_LIM_100MA] = 100,
+	[USB_CURR_LIM_150MA] = 150,
+	[USB_CURR_LIM_500MA] = 500,
+	[USB_CURR_LIM_800MA] = 800,
+	[USB_CURR_LIM_900MA] = 900,
+	[USB_CURR_LIM_1500MA] = 1500,
+};
+
+static const int const bq24160_in_lim[] = {
+	[IN_CURR_LIM_1500MA] = 1500,
+	[IN_CURR_LIM_2500MA] = 2500,
+};
+
+static const char * const bq2416x_tmr[] = {
+	[TMR_27MIN] = "27min",
+	[TMR_6H] = "6h",
+	[TMR_9H] = "9h",
+	[TMR_OFF] = "off",
+};
+
+/* External NTC Monitoring(TS) fault */
+enum {
+	TS_FAULT_NORMAL,
+	TS_FAULT_COLD_HOT,
+	TS_FAULT_COOL,
+	TS_FAULT_WARM,
+};
+
+static const char * const bq2416x_ts_fault[] = {
+	[TS_FAULT_NORMAL] = "normal",
+	[TS_FAULT_COLD_HOT] = "cold/hot(charge suspended)",
+	[TS_FAULT_COOL] = "cool(half current charge)",
+	[TS_FAULT_WARM] = "warm(voltage reduced)",
+};
+
+/* Firmware response: chip revision */
+enum {
+	VENDOR_REV_10,
+	VENDOR_REV_11,
+	VENDOR_REV_20,
+	VENDOR_REV_21,
+	VENDOR_REV_22,
+	VENDOR_REV_23,
+};
+
+static const char * const bq2416x_revision[] = {
+	[VENDOR_REV_10] = "1.0",
+	[VENDOR_REV_11] = "1.1",
+	[VENDOR_REV_20] = "2.0",
+	[VENDOR_REV_21] = "2.1",
+	[VENDOR_REV_22] = "2.2",
+	[VENDOR_REV_23] = "2.3",
+};
+
+/**
+ * struct bq2416x_priv - this device private data
+ * @dev: this device
+ * @regmap: register map for bq2416x
+ * @pdata: platform data
+ * @psy: power-supply-class for this device
+ * @watchdog: watchdog worker
+ * @model: model of this device
+ * @name: the name of this device instance
+ * @idr: the id of this chip
+ */
+struct bq2416x_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	struct bq2416x_pdata pdata;
+	struct power_supply *psy;
+	struct power_supply_desc psy_desc;
+	struct delayed_work watchdog;
+	char *model;
+	char *name;
+	int idr;
+};
+
+/* each registered chip must have unique id */
+static DEFINE_IDR(bq2416x_idr);
+static DEFINE_MUTEX(bq2416x_idr_mutex);
+
+/**
+ * conv2bit_repr - converts value to its regulation binary representation
+ * @val: value to convert
+ * @min: offset - regulation minimum
+ * @max: regulation maximum
+ * @step: regulation step
+ */
+static inline unsigned int conv2bit_repr(unsigned int val, unsigned int min,
+					unsigned int max, unsigned int step)
+{
+	return (clamp_val(val, min, max) - min) / step;
+}
+
+/* regmap callbacks and configuration */
+static bool bq2416x_writeable(struct device *dev, unsigned int reg)
+{
+	return !(reg == BQ2416X_REG_VENDOR);
+}
+
+static bool bq2416x_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case BQ2416X_REG_BAT_VOLT:
+	case BQ2416X_REG_VENDOR:
+		return false;
+	}
+
+	return true;
+}
+
+static struct regmap_config bq2416x_i2c_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.writeable_reg = bq2416x_writeable,
+	.volatile_reg = bq2416x_volatile,
+	.cache_type = REGCACHE_RBTREE,
+	.max_register = BQ2416X_REG_MAX,
+};
+
+/* power-supply-class callbacks and configuration */
+static int bq2416x_property_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+	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 bq2416x_power_supply_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_HEALTH,
+	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
+};
+
+/**
+ * bq2416x_get_status - get charger status
+ * @bq2416x: the charger device
+ * @status: the pointer to the return value
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_status(struct bq2416x_priv *bq2416x, int *status)
+{
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_STATUS, &reg_val);
+	if (unlikely(ret))
+		return ret;
+
+	reg_val = BF_GET(reg_val, BQ2416X_REG_STATUS_STAT_MASK);
+	*status = bq2416x_charge_status[reg_val].id;
+
+	return ret;
+}
+
+/**
+ * bq2416x_get_charge_type - Returns charge type
+ * @bq2416x: the charger device
+ * @charge_type: the pointer to the return value
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_charge_type(struct bq2416x_priv *bq2416x,
+					int *charge_type)
+{
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_STATUS, &reg_val);
+	if (unlikely(ret))
+		return ret;
+
+	reg_val = BF_GET(reg_val, BQ2416X_REG_STATUS_STAT_MASK);
+	if (bq2416x_charge_status[reg_val].id != POWER_SUPPLY_STATUS_CHARGING)
+		*charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+	else {
+		ret = regmap_read(bq2416x->regmap, BQ2416X_REG_NTC, &reg_val);
+		if (unlikely(ret))
+			return ret;
+
+		if (reg_val & BQ2416X_REG_NTC_LOW_CHARGE_MASK)
+			*charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		else
+			*charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+	}
+
+	return ret;
+}
+
+/**
+ * bq2416x_set_charge_type - sets charge type
+ * @bq2416x: the charger device
+ * @type: new charge type
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_charge_type(struct bq2416x_priv *bq2416x,
+					int type)
+{
+	int ret;
+	unsigned int charge_disable;
+	unsigned int low_charge;
+
+	switch (type) {
+	case POWER_SUPPLY_CHARGE_TYPE_NONE:
+		charge_disable = BQ2416X_REG_CONTROL_CE_MASK;
+		low_charge = 0;
+		break;
+	case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
+		charge_disable = 0;
+		low_charge = BQ2416X_REG_NTC_LOW_CHARGE_MASK;
+		break;
+	case POWER_SUPPLY_CHARGE_TYPE_FAST:
+		charge_disable = 0;
+		low_charge = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_CONTROL,
+				BQ2416X_REG_CONTROL_RESET_MASK |
+				BQ2416X_REG_CONTROL_CE_MASK,
+				charge_disable);
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_NTC,
+				BQ2416X_REG_NTC_LOW_CHARGE_MASK,
+				low_charge);
+
+	return ret;
+}
+
+/**
+ * bq2416x_get_health - returns charger health
+ * @bq2416x: this charger device
+ * @health: the pointer to the return value
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_health(struct bq2416x_priv *bq2416x, int *health)
+{
+	unsigned int reg_val;
+	int ret;
+
+	/* check supply status */
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_STATUS, &reg_val);
+	if (unlikely(ret))
+		return ret;
+
+	reg_val = BF_GET(reg_val, BQ2416X_REG_STATUS_FAULT_MASK);
+	*health = bq2416x_charge_fault[reg_val].id;
+
+	return ret;
+}
+
+/**
+ * bq2416x_get_online - returns online status
+ * @bq2416x: this charger device
+ * @online: the pointer to the return value
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_online(struct bq2416x_priv *bq2416x, int *online)
+{
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_STATUS, &reg_val);
+	if (unlikely(ret))
+		return ret;
+
+	reg_val = BF_GET(reg_val, BQ2416X_REG_STATUS_STAT_MASK);
+	*online = ((reg_val > STAT_NO_VALID_SOURCE) && (reg_val < STAT_NA));
+
+	return ret;
+}
+
+/**
+ * bq2416x_get_charge_current - returns charge current
+ * @bq2416x: the charger device
+ * @curr: the pointer to the return current value in [mA]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_charge_current(struct bq2416x_priv *bq2416x,
+					int *curr)
+{
+	int ret;
+	unsigned int low_charge;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_TERM, curr);
+	if (unlikely(ret))
+		return ret;
+
+	*curr = BF_GET(*curr, BQ2416X_REG_TERM_CHRG_CURR_MASK) *
+		BQ2416X_CHARGE_CURRENT_STEP +
+		BQ2416X_CHARGE_CURRENT_MIN;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_NTC, &low_charge);
+	if (unlikely(ret))
+		return ret;
+
+	/* halve the current value if in low_charge state */
+	*curr >>= low_charge & BQ2416X_REG_NTC_LOW_CHARGE_MASK;
+
+	return ret;
+}
+
+/**
+ * bq2416x_set_charge_current - sets charge current
+ * @bq2416x: the charger device
+ * @curr: new current value in [mA]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_charge_current(struct bq2416x_priv *bq2416x,
+					int curr)
+
+{
+	int ret;
+	unsigned int reg_bits;
+
+	reg_bits = conv2bit_repr(curr, BQ2416X_CHARGE_CURRENT_MIN,
+			BQ2416X_CHARGE_CURRENT_MAX,
+			BQ2416X_CHARGE_CURRENT_STEP);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_TERM,
+				BQ2416X_REG_TERM_CHRG_CURR_MASK,
+				BF_SHIFT(reg_bits,
+				BQ2416X_REG_TERM_CHRG_CURR_MASK));
+	if (unlikely(ret))
+		return ret;
+
+	/* unset low charge */
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_NTC,
+				BQ2416X_REG_NTC_LOW_CHARGE_MASK,
+				0);
+	return ret;
+}
+
+/**
+ * bq2416x_get_charge_voltage - returns charge voltage
+ * @bq2416x: the charger device
+ * @voltage: the pointer to the return voltage value in [mV]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_get_charge_voltage(struct bq2416x_priv *bq2416x,
+					int *voltage)
+{
+	int ret;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_BAT_VOLT, voltage);
+	if (unlikely(ret))
+		return ret;
+
+	*voltage = BF_GET(*voltage, BQ2416X_REG_BAT_VOLT_MASK) *
+		BQ2416X_CHARGE_VOLTAGE_STEP +
+		BQ2416X_CHARGE_VOLTAGE_MIN;
+
+	return ret;
+}
+
+/**
+ * bq2416x_set_charge_voltage - sets charge voltage
+ * @bq2416x: the charger device
+ * @voltage: new voltage value in [mV]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_charge_voltage(struct bq2416x_priv *bq2416x,
+					int voltage)
+{
+	int ret;
+	unsigned int reg_bits;
+
+	reg_bits = conv2bit_repr(voltage, BQ2416X_CHARGE_VOLTAGE_MIN,
+			BQ2416X_CHARGE_VOLTAGE_MAX,
+			BQ2416X_CHARGE_VOLTAGE_STEP);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_BAT_VOLT,
+				BQ2416X_REG_BAT_VOLT_MASK,
+				BF_SHIFT(reg_bits, BQ2416X_REG_BAT_VOLT_MASK));
+	return ret;
+}
+
+/**
+ * bq2416x_set_term_current - sets charge termination current
+ * @bq2416x: the charger device
+ * @term_curr: new charge termination current value in [mA]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_term_current(struct bq2416x_priv *bq2416x,
+					int term_curr)
+{
+	int ret;
+	unsigned int reg_bits;
+
+	reg_bits = conv2bit_repr(term_curr, BQ2416X_CHARGE_TERM_CURRENT_MIN,
+			BQ2416X_CHARGE_TERM_CURRENT_MAX,
+			BQ2416X_CHARGE_TERM_CURRENT_STEP);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_TERM,
+				BQ2416X_REG_TERM_TERM_CURR_MASK,
+				BF_SHIFT(reg_bits,
+				BQ2416X_REG_TERM_TERM_CURR_MASK));
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_NTC,
+				BQ2416X_REG_NTC_LOW_CHARGE_MASK,
+				0);
+	return ret;
+}
+
+/**
+ * bq2416x_set_usb_dpm_voltage - sets USB DPM voltage
+ * @bq2416x: the charger device
+ * @dpm_volt: new USB DPM voltage in [mV]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_usb_dpm_voltage(struct bq2416x_priv *bq2416x,
+					int dpm_volt)
+{
+	int ret;
+	unsigned int reg_bits;
+
+	reg_bits = conv2bit_repr(dpm_volt, BQ2416X_DPM_USB_VOLTAGE_MIN,
+			BQ2416X_DPM_USB_VOLTAGE_MAX,
+			BQ2416X_DPM_USB_VOLTAGE_STEP);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_DPM,
+			BQ2416X_REG_DPM_USB_VOLT_MASK,
+			BF_SHIFT(reg_bits, BQ2416X_REG_DPM_USB_VOLT_MASK));
+
+	return ret;
+}
+
+/**
+ * bq2416x_set_in_dpm_voltage - sets IN(Wall) DPM voltage
+ * @bq2416x: the charger device
+ * @dpm_volt: new IN DPM voltage in [mV]
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_set_in_dpm_voltage(struct bq2416x_priv *bq2416x,
+					int dpm_volt)
+{
+	int ret;
+	unsigned int reg_bits;
+
+	reg_bits = conv2bit_repr(dpm_volt, BQ2416X_DPM_IN_VOLTAGE_MIN,
+			BQ2416X_DPM_IN_VOLTAGE_MAX,
+			BQ2416X_DPM_IN_VOLTAGE_STEP);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_DPM,
+			BQ2416X_REG_DPM_IN_VOLT_MASK,
+			BF_SHIFT(reg_bits, BQ2416X_REG_DPM_IN_VOLT_MASK));
+	return ret;
+}
+
+/**
+ * bq2416x_reset_watchdog_tmr - resets watchdog timer
+ * @bq2416x: the charger device
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_reset_watchdog_tmr(struct bq2416x_priv *bq2416x)
+{
+	int ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_STATUS,
+			BQ2416X_REG_STATUS_TMR_RST_MASK,
+			BQ2416X_REG_STATUS_TMR_RST_MASK);
+	if (unlikely(ret))
+		dev_err(bq2416x->dev, "Can't reset watchdog timer\n");
+
+	return ret;
+}
+
+/**
+ * bq2416x_configure - configures charger per DT/platform data
+ * @bq2416x: the charger device
+ *
+ * Returns 0 if there is no error or negative on error.
+ */
+static int bq2416x_configure(struct bq2416x_priv *bq2416x)
+{
+	struct bq2416x_pdata *pdata = &bq2416x->pdata;
+	int ret;
+	unsigned int mask, bits;
+
+	ret = bq2416x_reset_watchdog_tmr(bq2416x);
+	if (unlikely(ret))
+		return ret;
+
+	ret = bq2416x_set_charge_voltage(bq2416x, pdata->charge_voltage);
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_BAT_VOLT,
+			BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK,
+			BF_SHIFT(pdata->in_curr_limit,
+			BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK));
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_CONTROL, BQ2416X_REG_CONTROL_RESET_MASK |
+			BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK,
+			BF_SHIFT(pdata->usb_curr_limit,
+			BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK));
+	if (unlikely(ret))
+		return ret;
+
+	mask =  BQ2416X_REG_CONTROL_RESET_MASK |
+		BQ2416X_REG_CONTROL_EN_STAT_MASK |
+		BQ2416X_REG_CONTROL_TE_MASK |
+		BQ2416X_REG_CONTROL_CE_MASK;
+
+	bits =  BF_SHIFT(pdata->stat_pin_en,
+			BQ2416X_REG_CONTROL_EN_STAT_MASK) |
+		BF_SHIFT(pdata->curr_term_en,
+			BQ2416X_REG_CONTROL_TE_MASK);
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_CONTROL,
+			mask,
+			bits);
+	if (unlikely(ret))
+		return ret;
+
+	ret = bq2416x_set_charge_current(bq2416x, pdata->charge_current);
+	if (unlikely(ret))
+		return ret;
+
+	ret = bq2416x_set_term_current(bq2416x, pdata->term_current);
+	if (unlikely(ret))
+		return ret;
+
+	ret = bq2416x_set_usb_dpm_voltage(bq2416x, pdata->usb_dpm_voltage);
+	if (unlikely(ret))
+		return ret;
+
+	ret = bq2416x_set_in_dpm_voltage(bq2416x, pdata->in_dpm_voltage);
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_NTC, BQ2416X_REG_NTC_TMR_MASK,
+			BF_SHIFT(pdata->safety_timer,
+			BQ2416X_REG_NTC_TMR_MASK));
+
+	return ret;
+}
+
+/**
+ * Status pin interrupt handler. It sends uevent upon charger status change
+ */
+static irqreturn_t bq2416x_thread_irq(int irq, void *priv)
+{
+	struct bq2416x_priv *bq2416x = priv;
+
+	/* Give registers some time */
+	msleep(300);
+
+	power_supply_changed(bq2416x->psy);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * Worker for watchdog timer reset.
+ */
+static void bq2416x_watchdog_work(struct work_struct *work)
+{
+	struct bq2416x_priv *bq2416x = container_of(work, struct bq2416x_priv,
+						 watchdog.work);
+
+	pm_runtime_get_sync(bq2416x->dev);
+	bq2416x_reset_watchdog_tmr(bq2416x);
+	pm_runtime_put_sync(bq2416x->dev);
+
+	schedule_delayed_work(&bq2416x->watchdog, BQ2416X_WATCHDOG_TIMER * HZ);
+}
+
+/**
+ * power-supply class get property callback
+ */
+static int bq2416x_psy_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+	pm_runtime_get_sync(bq2416x->dev);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		ret = bq2416x_get_status(bq2416x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = bq2416x->model;
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = "Texas Instruments";
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		ret = bq2416x_get_charge_type(bq2416x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		ret = bq2416x_get_health(bq2416x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = bq2416x_get_online(bq2416x, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = bq2416x_get_charge_current(bq2416x, &val->intval);
+		val->intval *= 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		val->intval = BQ2416X_CHARGE_CURRENT_MAX;
+		val->intval *= 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		ret = bq2416x_get_charge_voltage(bq2416x, &val->intval);
+		val->intval *= 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+		val->intval = BQ2416X_CHARGE_VOLTAGE_MAX;
+		val->intval *= 1000;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pm_runtime_put_sync(bq2416x->dev);
+	return ret;
+}
+
+/**
+ * power-supply class set property callback
+ */
+static int bq2416x_psy_set_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					const union power_supply_propval *val)
+{
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+
+	pm_runtime_get_sync(bq2416x->dev);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		ret = bq2416x_set_charge_type(bq2416x, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = bq2416x_set_charge_current(bq2416x, val->intval / 1000);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		ret = bq2416x_set_charge_voltage(bq2416x, val->intval / 1000);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	pm_runtime_put_sync(bq2416x->dev);
+	return ret;
+}
+
+/**
+ * device attributes callbacks
+ */
+static ssize_t bq2416x_sysfs_show_charge_status(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int val;
+	const char *str;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_STATUS, &val);
+	if (unlikely(ret != 0))
+		return ret;
+
+	if (strcmp(attr->attr.name, "charge_status") == 0) {
+		val = BF_GET(val, BQ2416X_REG_STATUS_STAT_MASK);
+		str = bq2416x_charge_status[val].str;
+	} else if (strcmp(attr->attr.name, "charge_fault") == 0) {
+		val = BF_GET(val, BQ2416X_REG_STATUS_FAULT_MASK);
+		str = bq2416x_charge_fault[val].str;
+	} else if (strcmp(attr->attr.name, "supply_sel") == 0) {
+		if (val & BQ2416X_REG_STATUS_SUPPLY_SEL_MASK)
+			str = "usb";
+		else
+			str = "in";
+	} else
+		return -EINVAL;
+
+	return sprintf(buf, "%s\n", str);
+}
+
+static ssize_t bq2416x_sysfs_store_supply_sel(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+
+	if (strncmp(buf, "usb", 3) == 0)
+		ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_STATUS,
+			BQ2416X_REG_STATUS_SUPPLY_SEL_MASK,
+			BQ2416X_REG_STATUS_SUPPLY_SEL_MASK);
+	else if (strncmp(buf, "in", 2) == 0)
+		ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_STATUS,
+			BQ2416X_REG_STATUS_SUPPLY_SEL_MASK,
+			0);
+	else
+		ret = -EINVAL;
+
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_supply_status(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int val;
+	const char *str;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_SUP_STATUS, &val);
+	if (unlikely(ret != 0))
+		return ret;
+
+	if (strcmp(attr->attr.name, "in_status") == 0) {
+		val = BF_GET(val, BQ2416X_REG_SUP_STATUS_INSTAT_MASK);
+		str = bq2416x_in_status[val];
+	} else if (strcmp(attr->attr.name, "usb_status") == 0) {
+		val = BF_GET(val, BQ2416X_REG_SUP_STATUS_USBSTAT_MASK);
+		str = bq2416x_in_status[val];
+	} else if (strcmp(attr->attr.name, "bat_status") == 0) {
+		val = BF_GET(val, BQ2416X_REG_SUP_STATUS_BATSTAT_MASK);
+		str = bq2416x_bat_status[val];
+	} else
+		return -EINVAL;
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t bq2416x_sysfs_show_charge_voltage(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int voltage;
+
+	ret = bq2416x_get_charge_voltage(bq2416x, &voltage);
+	if (unlikely(ret))
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", voltage);
+}
+
+static ssize_t bq2416x_sysfs_store_charge_voltage(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int voltage;
+
+	ret = kstrtouint(buf, 0, &voltage);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	ret = bq2416x_set_charge_voltage(bq2416x, voltage);
+	if (unlikely(ret))
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_in_curr_limit(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int limit;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_BAT_VOLT, &limit);
+	if (unlikely(ret))
+		return ret;
+
+	limit = BF_GET(limit, BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bq24160_in_lim[limit]);
+
+}
+
+static ssize_t bq2416x_sysfs_store_in_curr_limit(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int reg_bits, limit;
+
+	ret = kstrtouint(buf, 0, &limit);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	if (limit < 2500)
+		reg_bits = BQ2416X_IN_CURR_LIM_1500;
+	else
+		reg_bits = BQ2416X_IN_CURR_LIM_2500;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+				BQ2416X_REG_BAT_VOLT,
+				BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK,
+				BF_SHIFT(reg_bits,
+				BQ2416X_REG_BAT_VOLT_IN_CURR_LIM_MASK));
+	if (unlikely(ret))
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_usb_curr_limit(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int limit;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_CONTROL, &limit);
+	if (unlikely(ret != 0))
+		return ret;
+
+	limit = BF_GET(limit, BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bq2416x_usb_curr_lim[limit]);
+}
+
+static ssize_t bq2416x_sysfs_store_usb_curr_limit(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int curr, reg_bits;
+
+	ret = kstrtouint(buf, 0, &curr);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	if (curr < 150)
+		reg_bits = USB_CURR_LIM_100MA;
+	else if (curr < 500)
+		reg_bits = USB_CURR_LIM_150MA;
+	else if (curr < 800)
+		reg_bits = USB_CURR_LIM_500MA;
+	else if (curr < 900)
+		reg_bits = USB_CURR_LIM_800MA;
+	else if (curr < 1500)
+		reg_bits = USB_CURR_LIM_900MA;
+	else
+		reg_bits = USB_CURR_LIM_1500MA;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_CONTROL, BQ2416X_REG_CONTROL_RESET_MASK |
+			BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK,
+			BF_SHIFT(reg_bits,
+			BQ2416X_REG_CONTROL_USB_CURR_LIM_MASK));
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_charge_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret, curr;
+
+	ret = bq2416x_get_charge_current(bq2416x, &curr);
+	if (unlikely(ret))
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", curr);
+}
+
+static ssize_t bq2416x_sysfs_store_charge_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int curr;
+
+	ret = kstrtouint(buf, 0, &curr);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	ret = bq2416x_set_charge_current(bq2416x, curr);
+	if (unlikely(ret))
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_term_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int term_curr;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_TERM, &term_curr);
+	if (unlikely(ret))
+		return ret;
+
+	term_curr = BF_GET(term_curr, BQ2416X_REG_TERM_TERM_CURR_MASK) *
+			BQ2416X_CHARGE_TERM_CURRENT_STEP +
+			BQ2416X_CHARGE_TERM_CURRENT_MIN;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", term_curr);
+}
+
+static ssize_t bq2416x_sysfs_store_term_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int term_curr;
+
+	ret = kstrtouint(buf, 0, &term_curr);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	ret = bq2416x_set_term_current(bq2416x, term_curr);
+	if (unlikely(ret))
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_dpm_voltage(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int reg_val, dpm_volt;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_DPM, &reg_val);
+	if (unlikely(ret != 0))
+		return ret;
+
+	if (strcmp(attr->attr.name, "usb_dpm_voltage") == 0)
+		dpm_volt = BF_GET(reg_val, BQ2416X_REG_DPM_USB_VOLT_MASK);
+	else if (strcmp(attr->attr.name, "in_dpm_voltage") == 0)
+		dpm_volt = BF_GET(reg_val, BQ2416X_REG_DPM_IN_VOLT_MASK);
+	else
+		return -EINVAL;
+
+	dpm_volt = dpm_volt * BQ2416X_DPM_IN_VOLTAGE_STEP +
+		   BQ2416X_DPM_IN_VOLTAGE_MIN;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", dpm_volt);
+}
+
+static ssize_t bq2416x_sysfs_store_dpm_voltage(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int dpm_volt;
+
+	ret = kstrtouint(buf, 0, &dpm_volt);
+	if (unlikely(ret))
+		return -EINVAL;
+
+	if (strcmp(attr->attr.name, "usb_dpm_voltage") == 0)
+		ret = bq2416x_set_usb_dpm_voltage(bq2416x, dpm_volt);
+	else if (strcmp(attr->attr.name, "in_dpm_voltage") == 0)
+		ret = bq2416x_set_in_dpm_voltage(bq2416x, dpm_volt);
+	else
+		ret =  -EINVAL;
+
+	if (ret)
+		return ret;
+
+	return count;
+
+}
+
+static ssize_t bq2416x_sysfs_show_safety_timer(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int val;
+	const char *str;
+
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_NTC, &val);
+	if (unlikely(ret != 0))
+		return ret;
+
+	if (strcmp(attr->attr.name, "safety_timer") == 0) {
+		val = BF_GET(val, BQ2416X_REG_NTC_TMR_MASK);
+		str = bq2416x_tmr[val];
+	} else if (strcmp(attr->attr.name, "ts_fault") == 0) {
+		val = BF_GET(val, BQ2416X_REG_NTC_TS_FAULT_MASK);
+		str = bq2416x_ts_fault[val];
+	} else
+		return -EINVAL;
+
+	return scnprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+static ssize_t bq2416x_sysfs_store_safety_timer(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf,
+					size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int tmr;
+	bool found = false;
+
+	for (tmr = 0; tmr <= TMR_OFF; tmr++) {
+		if (strncmp(buf, bq2416x_tmr[tmr],
+		    strlen(bq2416x_tmr[tmr])) == 0) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found)
+		return -EINVAL;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			BQ2416X_REG_NTC, BQ2416X_REG_NTC_TMR_MASK,
+			BF_SHIFT(tmr, BQ2416X_REG_NTC_TMR_MASK));
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t bq2416x_sysfs_show_bit(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int ret;
+	unsigned int reg_val;
+	unsigned int reg = sattr->nr, mask = sattr->index;
+
+	ret = regmap_read(bq2416x->regmap, reg, &reg_val);
+	if (unlikely(ret != 0))
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", !!(reg_val & mask));
+}
+
+static ssize_t bq2416x_sysfs_store_bit(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	struct power_supply *psy = dev_get_drvdata(dev);
+	struct bq2416x_priv *bq2416x = power_supply_get_drvdata(psy);
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	int ret;
+	unsigned int bits;
+	unsigned int reg = sattr->nr, mask = sattr->index;
+
+	if (strncmp(buf, "1", 1) == 0)
+		bits = mask;
+	else if (strncmp(buf, "0", 1) == 0)
+		bits = 0;
+	else
+		return -EINVAL;
+
+	/* clear reset bit before writeback */
+	if (reg == BQ2416X_REG_CONTROL)
+		mask |= BQ2416X_REG_CONTROL_RESET_MASK;
+
+	ret = regmap_update_bits(bq2416x->regmap,
+			reg,
+			mask,
+			bits);
+	if (unlikely(ret))
+		return ret;
+
+	return count;
+}
+
+#define BIT_DEVICE_ATTR(_name, _mode, _reg, _bit)			\
+	SENSOR_DEVICE_ATTR_2(_name, _mode, bq2416x_sysfs_show_bit,	\
+	bq2416x_sysfs_store_bit, _reg, _bit)
+
+static DEVICE_ATTR(charge_status, 0444,
+			bq2416x_sysfs_show_charge_status, NULL);
+static DEVICE_ATTR(charge_fault, 0444,
+			bq2416x_sysfs_show_charge_status, NULL);
+static DEVICE_ATTR(supply_sel, 0644,
+			bq2416x_sysfs_show_charge_status,
+			bq2416x_sysfs_store_supply_sel);
+static BIT_DEVICE_ATTR(timer_rst, 0200,
+			BQ2416X_REG_STATUS, BQ2416X_REG_STATUS_TMR_RST_MASK);
+static DEVICE_ATTR(in_status, 0444,
+			bq2416x_sysfs_show_supply_status, NULL);
+static DEVICE_ATTR(usb_status, 0444,
+			bq2416x_sysfs_show_supply_status, NULL);
+static BIT_DEVICE_ATTR(otg_lock, 0644,
+			BQ2416X_REG_SUP_STATUS,
+			BQ2416X_REG_SUP_STATUS_OTG_LOCK_MASK);
+static BIT_DEVICE_ATTR(nobatop_en, 0644,
+			BQ2416X_REG_SUP_STATUS,
+			BQ2416X_REG_SUP_STATUS_EN_NOBATOP_MASK);
+static DEVICE_ATTR(bat_status, 0444,
+			bq2416x_sysfs_show_supply_status, NULL);
+static DEVICE_ATTR(charge_voltage, 0644,
+			bq2416x_sysfs_show_charge_voltage,
+			bq2416x_sysfs_store_charge_voltage);
+static DEVICE_ATTR(in_curr_limit, 0644,
+			bq2416x_sysfs_show_in_curr_limit,
+			bq2416x_sysfs_store_in_curr_limit);
+static DEVICE_ATTR(usb_curr_limit, 0644,
+			bq2416x_sysfs_show_usb_curr_limit,
+			bq2416x_sysfs_store_usb_curr_limit);
+static BIT_DEVICE_ATTR(stat_pin_en, 0644,
+			BQ2416X_REG_CONTROL,
+			BQ2416X_REG_CONTROL_EN_STAT_MASK);
+static BIT_DEVICE_ATTR(curr_term_en, 0644,
+			BQ2416X_REG_CONTROL,
+			BQ2416X_REG_CONTROL_TE_MASK);
+static BIT_DEVICE_ATTR(charging_disable, 0644,
+			BQ2416X_REG_CONTROL,
+			BQ2416X_REG_CONTROL_CE_MASK);
+static BIT_DEVICE_ATTR(hz_mode, 0644,
+			BQ2416X_REG_CONTROL,
+			BQ2416X_REG_CONTROL_HZ_MODE_MASK);
+static DEVICE_ATTR(charge_current, 0644,
+			bq2416x_sysfs_show_charge_current,
+			bq2416x_sysfs_store_charge_current);
+static DEVICE_ATTR(term_current, 0644,
+			bq2416x_sysfs_show_term_current,
+			bq2416x_sysfs_store_term_current);
+static BIT_DEVICE_ATTR(min_sys_stat, 0444,
+			BQ2416X_REG_DPM,
+			BQ2416X_REG_DPM_MINSYS_STATUS_MASK);
+static BIT_DEVICE_ATTR(dpm_status, 0444,
+			BQ2416X_REG_DPM,
+			BQ2416X_REG_DPM_STATUS_MASK);
+static DEVICE_ATTR(usb_dpm_voltage, 0644,
+			bq2416x_sysfs_show_dpm_voltage,
+			bq2416x_sysfs_store_dpm_voltage);
+static DEVICE_ATTR(in_dpm_voltage, 0644,
+			bq2416x_sysfs_show_dpm_voltage,
+			bq2416x_sysfs_store_dpm_voltage);
+static BIT_DEVICE_ATTR(safety_timer_x2, 0644,
+			BQ2416X_REG_NTC,
+			BQ2416X_REG_NTC_TMRX2_MASK);
+static DEVICE_ATTR(safety_timer, 0644,
+			bq2416x_sysfs_show_safety_timer,
+			bq2416x_sysfs_store_safety_timer);
+static BIT_DEVICE_ATTR(ts_enable, 0644,
+			BQ2416X_REG_NTC,
+			BQ2416X_REG_NTC_TS_EN_MASK);
+static DEVICE_ATTR(ts_fault, 0444,
+			bq2416x_sysfs_show_safety_timer, NULL);
+static BIT_DEVICE_ATTR(low_charge, 0644,
+			BQ2416X_REG_NTC,
+			BQ2416X_REG_NTC_LOW_CHARGE_MASK);
+
+static struct attribute *bq2416x_sysfs_attributes[] = {
+	&dev_attr_charge_status.attr,
+	&dev_attr_charge_fault.attr,
+	&dev_attr_supply_sel.attr,
+	&sensor_dev_attr_timer_rst.dev_attr.attr,
+	&dev_attr_in_status.attr,
+	&dev_attr_usb_status.attr,
+	&sensor_dev_attr_otg_lock.dev_attr.attr,
+	&sensor_dev_attr_nobatop_en.dev_attr.attr,
+	&dev_attr_bat_status.attr,
+	&dev_attr_charge_voltage.attr,
+	&dev_attr_in_curr_limit.attr,
+	&dev_attr_usb_curr_limit.attr,
+	&sensor_dev_attr_stat_pin_en.dev_attr.attr,
+	&sensor_dev_attr_curr_term_en.dev_attr.attr,
+	&sensor_dev_attr_charging_disable.dev_attr.attr,
+	&sensor_dev_attr_hz_mode.dev_attr.attr,
+	&dev_attr_charge_current.attr,
+	&dev_attr_term_current.attr,
+	&sensor_dev_attr_min_sys_stat.dev_attr.attr,
+	&sensor_dev_attr_dpm_status.dev_attr.attr,
+	&dev_attr_usb_dpm_voltage.attr,
+	&dev_attr_in_dpm_voltage.attr,
+	&sensor_dev_attr_safety_timer_x2.dev_attr.attr,
+	&dev_attr_safety_timer.attr,
+	&sensor_dev_attr_ts_enable.dev_attr.attr,
+	&dev_attr_ts_fault.attr,
+	&sensor_dev_attr_low_charge.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group bq2416x_sysfs_attr_group = {
+	.attrs = bq2416x_sysfs_attributes,
+};
+
+#if defined(CONFIG_OF)
+static const struct of_device_id bq2416x_of_match[] = {
+	{ .compatible = "ti,bq24160" },
+	{ .compatible = "ti,bq24160a" },
+	{ .compatible = "ti,bq24161" },
+	{ .compatible = "ti,bq24161b" },
+	{ .compatible = "ti,bq24163" },
+	{ .compatible = "ti,bq24168" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bq2416x_of_match);
+
+static void bq2416x_pdata_set_default(struct bq2416x_pdata *pdata)
+{
+	pdata->charge_voltage	= 4200;
+	pdata->in_curr_limit	= IN_CURR_LIM_1500MA;
+	pdata->usb_curr_limit	= USB_CURR_LIM_100MA;
+	pdata->stat_pin_en	= 1;
+	pdata->curr_term_en	= 1;
+	pdata->charge_current	= 1150;
+	pdata->term_current	= 100;
+	pdata->usb_dpm_voltage	= 4200;
+	pdata->in_dpm_voltage	= 4200;
+	pdata->safety_timer	= TMR_27MIN;
+	pdata->num_supplicants	= 1;
+	pdata->supplied_to[0]	= "main-battery";
+}
+
+static int bq2416x_pdata_from_of(struct bq2416x_priv *bq2416x)
+{
+	struct device_node *np = bq2416x->dev->of_node;
+	struct bq2416x_pdata *pdata = &bq2416x->pdata;
+	int ret, i, num_strings;
+	unsigned int prop;
+	const char *supplied_to[4];
+
+	bq2416x_pdata_set_default(pdata);
+
+	ret = of_property_read_u32(np, "ti,charge-voltage", &prop);
+	if (!ret)
+		pdata->charge_voltage = prop;
+
+	ret = of_property_read_u32(np, "ti,in-current-limit", &prop);
+	if (!ret)
+		pdata->in_curr_limit = prop;
+
+	ret = of_property_read_u32(np, "ti,usb-current-limit", &prop);
+	if (!ret)
+		pdata->usb_curr_limit = prop;
+
+	ret = of_property_read_u32(np, "ti,status-pin-enable", &prop);
+	if (!ret)
+		pdata->stat_pin_en = prop;
+
+	ret = of_property_read_u32(np, "ti,current-termination-enable", &prop);
+	if (!ret)
+		pdata->curr_term_en = prop;
+
+	ret = of_property_read_u32(np, "ti,charge-current", &prop);
+	if (!ret)
+		pdata->charge_current = prop;
+
+	ret = of_property_read_u32(np, "ti,termination-current", &prop);
+	if (!ret)
+		pdata->term_current = prop;
+
+	ret = of_property_read_u32(np, "ti,usb-dpm-voltage", &prop);
+	if (!ret)
+		pdata->usb_dpm_voltage = prop;
+
+	ret = of_property_read_u32(np, "ti,in-dpm-voltage", &prop);
+	if (!ret)
+		pdata->in_dpm_voltage = prop;
+
+	ret = of_property_read_u32(np, "ti,safety-timer", &prop);
+	if (!ret)
+		pdata->safety_timer = prop;
+
+	ret = of_property_read_string_array(np, "ti,supplied-to",
+				supplied_to, prop);
+	if (ret > 0) {
+		num_strings = ret;
+		if (num_strings > 4)
+			return -EINVAL;
+
+		pdata->num_supplicants = num_strings;
+		for (i = 0; i < num_strings; i++)
+			pdata->supplied_to[i] = supplied_to[i];
+	}
+
+	return ret;
+}
+#else /* CONFIG_OF */
+static int bq2416x_pdata_from_of(struct bq2416x_priv *bq2416x)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+#ifdef CONFIG_PM_SLEEP
+static int bq2416x_suspend(struct device *dev)
+{
+	struct bq2416x_priv *bq2416x = dev_get_drvdata(dev);
+
+	cancel_delayed_work(&bq2416x->watchdog);
+
+	pm_runtime_get_sync(bq2416x->dev);
+	bq2416x_set_charge_type(bq2416x, POWER_SUPPLY_CHARGE_TYPE_NONE);
+	pm_runtime_put_sync(bq2416x->dev);
+
+	return 0;
+}
+
+static int bq2416x_resume(struct device *dev)
+{
+	struct bq2416x_priv *bq2416x = dev_get_drvdata(dev);
+
+	pm_runtime_get_sync(bq2416x->dev);
+	bq2416x_reset_watchdog_tmr(bq2416x);
+	bq2416x_set_charge_type(bq2416x, POWER_SUPPLY_CHARGE_TYPE_FAST);
+	pm_runtime_put_sync(bq2416x->dev);
+
+	schedule_delayed_work(&bq2416x->watchdog, BQ2416X_WATCHDOG_TIMER * HZ);
+
+	power_supply_changed(bq2416x->psy);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(bq2416x_pm_ops, bq2416x_suspend, bq2416x_resume);
+
+static int bq2416x_device_init(struct bq2416x_priv *bq2416x)
+{
+	int ret;
+	unsigned int vendor_reg, vendor_code, revision;
+	struct power_supply_config psy_cfg = { .drv_data = bq2416x };
+	struct bq2416x_pdata *pdata = &bq2416x->pdata;
+
+	dev_set_drvdata(bq2416x->dev, bq2416x);
+
+	if (dev_get_platdata(bq2416x->dev))
+		memcpy(pdata, dev_get_platdata(bq2416x->dev),
+			sizeof(*pdata));
+	else if (bq2416x->dev->of_node) {
+		ret = bq2416x_pdata_from_of(bq2416x);
+		if (ret < 0) {
+			dev_err(bq2416x->dev, "OF: not able to process DT\n");
+			return ret;
+		}
+	}
+
+	pm_runtime_get_sync(bq2416x->dev);
+	ret = regmap_read(bq2416x->regmap, BQ2416X_REG_VENDOR, &vendor_reg);
+	if (unlikely(ret)) {
+		dev_err(bq2416x->dev, "Can't read vendor code\n");
+		return ret;
+	}
+	pm_runtime_put_sync(bq2416x->dev);
+
+	vendor_code = BF_GET(vendor_reg, BQ2416X_REG_VENDOR_CODE_MASK);
+	revision = BF_GET(vendor_reg, BQ2416X_REG_VENDOR_REV_MASK);
+
+	dev_info(bq2416x->dev, "Found BQ2416X, code: 0x%02x rev: %s\n",
+			vendor_code, bq2416x_revision[revision]);
+
+	bq2416x->psy_desc.name = bq2416x->name;
+	bq2416x->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+	bq2416x->psy_desc.properties = bq2416x_power_supply_props;
+	bq2416x->psy_desc.get_property = bq2416x_psy_get_property;
+	bq2416x->psy_desc.set_property = bq2416x_psy_set_property;
+	bq2416x->psy_desc.num_properties =
+					ARRAY_SIZE(bq2416x_power_supply_props);
+	psy_cfg.supplied_to = (char **) pdata->supplied_to;
+	psy_cfg.num_supplicants = pdata->num_supplicants;
+	bq2416x->psy_desc.property_is_writeable = bq2416x_property_is_writeable;
+
+	bq2416x->psy = power_supply_register(bq2416x->dev, &bq2416x->psy_desc,
+						&psy_cfg);
+	if (unlikely(IS_ERR(bq2416x->psy))) {
+		dev_err(bq2416x->dev, "Can't register power supply\n");
+		return PTR_ERR(bq2416x->psy);
+	}
+
+	return ret;
+}
+
+int bq2416x_i2c_probe(struct i2c_client *i2c,
+			     const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(i2c->dev.parent);
+	struct bq2416x_priv *bq2416x;
+	int ret, idr;
+	char *model, *name;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&i2c->dev, "No support for SMBUS_BYTE_DATA\n");
+		return -ENODEV;
+	}
+
+	/* Get id for the new charger device */
+	mutex_lock(&bq2416x_idr_mutex);
+	idr = idr_alloc(&bq2416x_idr, i2c, 0, 0, GFP_KERNEL);
+	mutex_unlock(&bq2416x_idr_mutex);
+
+	if (IS_ERR_VALUE((unsigned long) idr))
+		return idr;
+
+	model = devm_kzalloc(&i2c->dev, strlen(id->name), GFP_KERNEL);
+	if (unlikely(!model)) {
+		dev_err(&i2c->dev, "Failed to allocate name\n");
+		ret = -ENOMEM;
+		goto err_rel_id;
+	}
+	strncpy(model, id->name, strlen(id->name));
+
+	bq2416x = devm_kzalloc(&i2c->dev, sizeof(*bq2416x), GFP_KERNEL);
+	if (unlikely(!bq2416x)) {
+		dev_err(&i2c->dev, "Failed to allocate private data\n");
+		ret = -ENOMEM;
+		goto err_rel_id;
+	}
+
+	bq2416x->regmap = devm_regmap_init_i2c(i2c, &bq2416x_i2c_regmap);
+	if (IS_ERR(bq2416x->regmap)) {
+		ret = PTR_ERR(bq2416x->regmap);
+		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto err_rel_id;
+	}
+
+	name = kasprintf(GFP_KERNEL, "%s-%d", id->name, idr);
+	if (unlikely(!name)) {
+		dev_err(&i2c->dev, "Failed to allocate device name\n");
+		ret = -ENOMEM;
+		goto err_rel_id;
+	}
+
+	pm_runtime_enable(&i2c->dev);
+	pm_runtime_resume(&i2c->dev);
+
+	bq2416x->dev  = &i2c->dev;
+	bq2416x->idr  = idr;
+	bq2416x->model = model;
+	bq2416x->name  = name;
+
+	ret = bq2416x_device_init(bq2416x);
+	if (ret)
+		goto err_free_name;
+
+	ret = bq2416x_configure(bq2416x);
+	if (unlikely(ret)) {
+		dev_err(bq2416x->dev, "Inital configuration failed\n");
+		goto err_unregister_psy;
+	}
+
+	ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+				bq2416x_thread_irq, IRQF_TRIGGER_RISING |
+				IRQF_ONESHOT, "bq2416xinterrupt", bq2416x);
+	if (ret) {
+		dev_err(&i2c->dev, "Can't request IRQ\n");
+		goto err_unregister_psy;
+	}
+
+	ret = sysfs_create_group(&bq2416x->psy->dev.kobj,
+			&bq2416x_sysfs_attr_group);
+	if (unlikely(ret)) {
+		dev_err(bq2416x->dev, "Can't create sysfs entries\n");
+		goto err_unregister_psy;
+	}
+
+	INIT_DELAYED_WORK(&bq2416x->watchdog, bq2416x_watchdog_work);
+	schedule_delayed_work(&bq2416x->watchdog, BQ2416X_WATCHDOG_TIMER * HZ);
+
+	return 0;
+
+err_unregister_psy:
+	power_supply_unregister(bq2416x->psy);
+err_free_name:
+	pm_runtime_disable(&i2c->dev);
+	kfree(name);
+err_rel_id:
+	mutex_lock(&bq2416x_idr_mutex);
+	idr_remove(&bq2416x_idr, idr);
+	mutex_unlock(&bq2416x_idr_mutex);
+
+	return ret;
+}
+
+static int bq2416x_i2c_remove(struct i2c_client *i2c)
+{
+	struct bq2416x_priv *bq2416x = i2c_get_clientdata(i2c);
+
+	cancel_delayed_work_sync(&bq2416x->watchdog);
+	sysfs_remove_group(&bq2416x->psy->dev.kobj, &bq2416x_sysfs_attr_group);
+	power_supply_unregister(bq2416x->psy);
+	pm_runtime_disable(bq2416x->dev);
+
+	mutex_lock(&bq2416x_idr_mutex);
+	idr_remove(&bq2416x_idr, bq2416x->idr);
+	mutex_unlock(&bq2416x_idr_mutex);
+
+	kfree(bq2416x->name);
+
+	return 0;
+}
+
+static const struct i2c_device_id  bq2416x_i2c_id[] = {
+	{ "bq24160",  BQ24160 },
+	{ "bq24160a", BQ24160A },
+	{ "bq24161",  BQ24161 },
+	{ "bq24161b", BQ24161B },
+	{ "bq24163",  BQ24163 },
+	{ "bq24168",  BQ24168 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, bq2416x_i2c_id);
+
+static struct i2c_driver bq2416x_i2c_driver = {
+	.driver = {
+		.name	= "bq2416x-charger",
+		.of_match_table = of_match_ptr(bq2416x_of_match),
+		.pm     = &bq2416x_pm_ops,
+	},
+	.probe		= bq2416x_i2c_probe,
+	.remove		= bq2416x_i2c_remove,
+	.id_table	= bq2416x_i2c_id,
+};
+
+module_i2c_driver(bq2416x_i2c_driver);
+
+MODULE_DESCRIPTION("TI BQ2416x battery charger driver");
+MODULE_AUTHOR("Wojciech Ziemba <wojciech.ziemba@xxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
diff --git a/include/dt-bindings/power/bq2416x_charger.h b/include/dt-bindings/power/bq2416x_charger.h
new file mode 100644
index 0000000..67363fd
--- /dev/null
+++ b/include/dt-bindings/power/bq2416x_charger.h
@@ -0,0 +1,23 @@
+/*
+ * This header provides constants for bq2416x bindings.
+ */
+
+#ifndef _DT_BINDINGS_POWER_H
+#define _DT_BINDINGS_POWER_H
+
+#define IN_CURR_LIM_1500MA	(0)
+#define IN_CURR_LIM_2500MA	(1)
+
+#define USB_CURR_LIM_100MA	(0)
+#define USB_CURR_LIM_150MA	(1)
+#define USB_CURR_LIM_500MA	(2)
+#define USB_CURR_LIM_800MA	(3)
+#define USB_CURR_LIM_900MA	(4)
+#define USB_CURR_LIM_1500MA	(5)
+
+#define TMR_27MIN	(0)
+#define TMR_6H		(1)
+#define TMR_9H		(2)
+#define TMR_OFF		(3)
+
+#endif /* _DT_BINDINGS_POWER_H */
diff --git a/include/linux/power/bq2416x_charger.h b/include/linux/power/bq2416x_charger.h
new file mode 100644
index 0000000..c561666
--- /dev/null
+++ b/include/linux/power/bq2416x_charger.h
@@ -0,0 +1,80 @@
+/*
+ * Driver for BQ2416X Li-Ion Battery Charger
+ *
+ * Copyright (C) 2015 Verifone, Inc.
+ *
+ * Author: Wojciech Ziemba <wojciech.ziemba@xxxxxxxxxxxx>
+ *
+ * This package 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 PACKAGE IS PROVIDED AS IS AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * The bq2416x series is a 2.5A, Dual-Input, Single-Cell Switched-Mode
+ * Li-Ion Battery Charger with Power
+ * Path Management and I2C Interface
+ *
+ */
+
+#ifndef _BQ2416X_CHARGER_H
+#define _BQ2416X_CHARGER_H
+
+/* IN(Wall) source limit */
+enum in_curr_lim {
+	IN_CURR_LIM_1500MA,
+	IN_CURR_LIM_2500MA,
+};
+
+/* USB source current limit */
+enum usb_curr_lim {
+	USB_CURR_LIM_100MA,
+	USB_CURR_LIM_150MA,
+	USB_CURR_LIM_500MA,
+	USB_CURR_LIM_800MA,
+	USB_CURR_LIM_900MA,
+	USB_CURR_LIM_1500MA,
+};
+
+/* Safety timer settings */
+enum safe_tmr {
+	TMR_27MIN,
+	TMR_6H,
+	TMR_9H,
+	TMR_OFF,
+};
+
+/**
+ * struct bq2416x_pdata - Platform data for bq2416x chip. It contains default
+ *			  board voltages and currents.
+ * @charge_voltage: charge voltage in [mV]
+ * @charge_current: charge current in [mA]
+ * @in_curr_limit: Current limit for IN source . Enum 1.5A or 2.5A
+ * @usb_curr_limit: Current limit for USB source Enum 100mA - 1500mA
+ * @curr_term_en: enable charge terination by current
+ * @term_current: charge termination current in [mA]
+ * @usb_dpm_voltage: USB DPM voltage [mV]
+ * @in_dpm_voltage: IN DPM voltage [mV]
+ * @stat_pin_en: status pin enable
+ * @safety_timer: safety timer enum: 27min, 6h, 9h, off.
+ * @num_supplicants: number of notify devices. Max 4.
+ * @supplied_to: array of names of supplied to devices
+ */
+struct bq2416x_pdata {
+	int charge_voltage;
+	int charge_current;
+	enum in_curr_lim in_curr_limit;
+	enum usb_curr_lim usb_curr_limit;
+	int curr_term_en;
+	int term_current;
+	int usb_dpm_voltage;
+	int in_dpm_voltage;
+	int stat_pin_en;
+	enum safe_tmr safety_timer;
+	int num_supplicants;
+	const char *supplied_to[4];
+};
+
+#endif /* _BQ2416X_CHARGER_H */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux