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

From: Wojciech Ziemba
Date: Mon Feb 06 2017 - 20:11:08 EST


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