Merge tag 'for-4.2' of git://git.infradead.org/battery-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 23 Jun 2015 23:10:27 +0000 (16:10 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 23 Jun 2015 23:10:27 +0000 (16:10 -0700)
Pull power supply and reset updates from Sebastian Reichel:

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

 - MAX17042 battery: add health & temperature support

 - BQ2415x charger: add ACPI support

 - misc fixes and cleanups

* tag 'for-4.2' of git://git.infradead.org/battery-2.6: (32 commits)
  power_supply: Correct kerneldoc copy paste errors
  wm831x_power: Fix off-by-one at free_irq()
  power_supply: rt9455_charger: Fix error reported by static analysis tool
  power_supply: bq24257: use flags argument of devm_gpiod_get
  power_supply: bq25890: use flags argument of devm_gpiod_get
  sbs-battery: add option to always register battery
  power: Add devm_power_supply_get_by_phandle() helper function
  power_supply: max17042: Add OF support for setting thresholds
  power_supply: sysfs: Bring back write to writeable properties
  power_supply: rt9455_charger: Check if CONFIG_USB_PHY is enabled
  power: reset: gpio-restart: increase priority slightly
  power_supply: bq25890: make chip_id int
  power_supply: Add support for Richtek RT9455 battery charger
  Documentation: devicetree: Add Richtek RT9455 bindings
  of: Add vendor prefix for Richtek Technology Corporation
  power_supply: 88pm860x_charger: Do not call free_irq() twice
  power: bq24190_charger: Change first_time flag reset condition
  power: axp288_charger: axp288 charger driver
  power: max17042_battery: add HEALTH and TEMP_* properties support
  power_supply: Add support for TI BQ25890 charger chip
  ...

29 files changed:
Documentation/devicetree/bindings/power/bq24257.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power/bq25890.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power/rt9455_charger.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power_supply/max17042_battery.txt
Documentation/devicetree/bindings/vendor-prefixes.txt
drivers/power/88pm860x_charger.c
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/axp288_charger.c [new file with mode: 0644]
drivers/power/axp288_fuel_gauge.c
drivers/power/bq2415x_charger.c
drivers/power/bq24190_charger.c
drivers/power/bq24257_charger.c [new file with mode: 0644]
drivers/power/bq25890_charger.c [new file with mode: 0644]
drivers/power/charger-manager.c
drivers/power/max17042_battery.c
drivers/power/power_supply_core.c
drivers/power/power_supply_leds.c
drivers/power/power_supply_sysfs.c
drivers/power/reset/at91-reset.c
drivers/power/reset/gpio-poweroff.c
drivers/power/reset/gpio-restart.c
drivers/power/reset/ltc2952-poweroff.c
drivers/power/rt9455_charger.c [new file with mode: 0644]
drivers/power/sbs-battery.c
drivers/power/wm831x_power.c
include/linux/mfd/axp20x.h
include/linux/power/max17042_battery.h
include/linux/power_supply.h

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