power: supply: Add a driver for Injoinic power bank ICs
authorSamuel Holland <samuel@sholland.org>
Mon, 14 Feb 2022 04:22:59 +0000 (22:22 -0600)
committerSebastian Reichel <sebastian.reichel@collabora.com>
Fri, 4 Mar 2022 21:33:00 +0000 (22:33 +0100)
This driver supports several chip variants which all share the same I2C
register interface. Since the chip will turn off and become inaccessible
under conditions outside of software control (e.g. upon button press or
input voltage removal), some special handling is needed to delay the
initialization of the IC until it is accessible.

Signed-off-by: Samuel Holland <samuel@sholland.org>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
MAINTAINERS
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/ip5xxx_power.c [new file with mode: 0644]

index ea3e6c9..beaf6ac 100644 (file)
@@ -9461,6 +9461,11 @@ F:       include/linux/mfd/ingenic-tcu.h
 F:     sound/soc/codecs/jz47*
 F:     sound/soc/jz4740/
 
+INJOINIC IP5xxx POWER BANK IC DRIVER
+M:     Samuel Holland <samuel@sholland.org>
+S:     Maintained
+F:     drivers/power/supply/ip5xxx_power.c
+
 INOTIFY
 M:     Jan Kara <jack@suse.cz>
 R:     Amir Goldstein <amir73il@gmail.com>
index fa128b9..1aa8323 100644 (file)
@@ -51,6 +51,14 @@ config GENERIC_ADC_BATTERY
          Say Y here to enable support for the generic battery driver
          which uses IIO framework to read adc.
 
+config IP5XXX_POWER
+       tristate "Injoinic IP5xxx power bank IC driver"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Say Y to include support for Injoinic IP5xxx power bank ICs,
+         which include a battery charger and a boost converter.
+
 config MAX8925_POWER
        tristate "MAX8925 battery charger support"
        depends on MFD_MAX8925
index b87eb8d..7f02f36 100644 (file)
@@ -12,6 +12,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY)     += generic-adc-battery.o
 obj-$(CONFIG_PDA_POWER)                += pda_power.o
 obj-$(CONFIG_APM_POWER)                += apm_power.o
 obj-$(CONFIG_AXP20X_POWER)     += axp20x_usb_power.o
+obj-$(CONFIG_IP5XXX_POWER)     += ip5xxx_power.o
 obj-$(CONFIG_MAX8925_POWER)    += max8925_power.o
 obj-$(CONFIG_WM831X_BACKUP)    += wm831x_backup.o
 obj-$(CONFIG_WM831X_POWER)     += wm831x_power.o
diff --git a/drivers/power/supply/ip5xxx_power.c b/drivers/power/supply/ip5xxx_power.c
new file mode 100644 (file)
index 0000000..218e8e6
--- /dev/null
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define IP5XXX_SYS_CTL0                        0x01
+#define IP5XXX_SYS_CTL0_WLED_DET_EN            BIT(4)
+#define IP5XXX_SYS_CTL0_WLED_EN                        BIT(3)
+#define IP5XXX_SYS_CTL0_BOOST_EN               BIT(2)
+#define IP5XXX_SYS_CTL0_CHARGER_EN             BIT(1)
+#define IP5XXX_SYS_CTL1                        0x02
+#define IP5XXX_SYS_CTL1_LIGHT_SHDN_EN          BIT(1)
+#define IP5XXX_SYS_CTL1_LOAD_PWRUP_EN          BIT(0)
+#define IP5XXX_SYS_CTL2                        0x0c
+#define IP5XXX_SYS_CTL2_LIGHT_SHDN_TH          GENMASK(7, 3)
+#define IP5XXX_SYS_CTL3                        0x03
+#define IP5XXX_SYS_CTL3_LONG_PRESS_TIME_SEL    GENMASK(7, 6)
+#define IP5XXX_SYS_CTL3_BTN_SHDN_EN            BIT(5)
+#define IP5XXX_SYS_CTL4                        0x04
+#define IP5XXX_SYS_CTL4_SHDN_TIME_SEL          GENMASK(7, 6)
+#define IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN   BIT(5)
+#define IP5XXX_SYS_CTL5                        0x07
+#define IP5XXX_SYS_CTL5_NTC_DIS                        BIT(6)
+#define IP5XXX_SYS_CTL5_WLED_MODE_SEL          BIT(1)
+#define IP5XXX_SYS_CTL5_BTN_SHDN_SEL           BIT(0)
+#define IP5XXX_CHG_CTL1                        0x22
+#define IP5XXX_CHG_CTL1_BOOST_UVP_SEL          GENMASK(3, 2)
+#define IP5XXX_CHG_CTL2                        0x24
+#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL           GENMASK(6, 5)
+#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V      (0x0 << 5)
+#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V      (0x1 << 5)
+#define IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V     (0x2 << 5)
+#define IP5XXX_CHG_CTL2_CONST_VOLT_SEL         GENMASK(2, 1)
+#define IP5XXX_CHG_CTL4                        0x26
+#define IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN                BIT(6)
+#define IP5XXX_CHG_CTL4A               0x25
+#define IP5XXX_CHG_CTL4A_CONST_CUR_SEL         GENMASK(4, 0)
+#define IP5XXX_MFP_CTL0                        0x51
+#define IP5XXX_MFP_CTL1                        0x52
+#define IP5XXX_GPIO_CTL2               0x53
+#define IP5XXX_GPIO_CTL2A              0x54
+#define IP5XXX_GPIO_CTL3               0x55
+#define IP5XXX_READ0                   0x71
+#define IP5XXX_READ0_CHG_STAT                  GENMASK(7, 5)
+#define IP5XXX_READ0_CHG_STAT_IDLE             (0x0 << 5)
+#define IP5XXX_READ0_CHG_STAT_TRICKLE          (0x1 << 5)
+#define IP5XXX_READ0_CHG_STAT_CONST_VOLT       (0x2 << 5)
+#define IP5XXX_READ0_CHG_STAT_CONST_CUR                (0x3 << 5)
+#define IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP  (0x4 << 5)
+#define IP5XXX_READ0_CHG_STAT_FULL             (0x5 << 5)
+#define IP5XXX_READ0_CHG_STAT_TIMEOUT          (0x6 << 5)
+#define IP5XXX_READ0_CHG_OP                    BIT(4)
+#define IP5XXX_READ0_CHG_END                   BIT(3)
+#define IP5XXX_READ0_CONST_VOLT_TIMEOUT                BIT(2)
+#define IP5XXX_READ0_CHG_TIMEOUT               BIT(1)
+#define IP5XXX_READ0_TRICKLE_TIMEOUT           BIT(0)
+#define IP5XXX_READ0_TIMEOUT                   GENMASK(2, 0)
+#define IP5XXX_READ1                   0x72
+#define IP5XXX_READ1_WLED_PRESENT              BIT(7)
+#define IP5XXX_READ1_LIGHT_LOAD                        BIT(6)
+#define IP5XXX_READ1_VIN_OVERVOLT              BIT(5)
+#define IP5XXX_READ2                   0x77
+#define IP5XXX_READ2_BTN_PRESS                 BIT(3)
+#define IP5XXX_READ2_BTN_LONG_PRESS            BIT(1)
+#define IP5XXX_READ2_BTN_SHORT_PRESS           BIT(0)
+#define IP5XXX_BATVADC_DAT0            0xa2
+#define IP5XXX_BATVADC_DAT1            0xa3
+#define IP5XXX_BATIADC_DAT0            0xa4
+#define IP5XXX_BATIADC_DAT1            0xa5
+#define IP5XXX_BATOCV_DAT0             0xa8
+#define IP5XXX_BATOCV_DAT1             0xa9
+
+struct ip5xxx {
+       struct regmap *regmap;
+       bool initialized;
+};
+
+/*
+ * The IP5xxx charger only responds on I2C when it is "awake". The charger is
+ * generally only awake when VIN is powered or when its boost converter is
+ * enabled. Going into shutdown resets all register values. To handle this:
+ *  1) When any bus error occurs, assume the charger has gone into shutdown.
+ *  2) Attempt the initialization sequence on each subsequent register access
+ *     until it succeeds.
+ */
+static int ip5xxx_read(struct ip5xxx *ip5xxx, unsigned int reg,
+                      unsigned int *val)
+{
+       int ret;
+
+       ret = regmap_read(ip5xxx->regmap, reg, val);
+       if (ret)
+               ip5xxx->initialized = false;
+
+       return ret;
+}
+
+static int ip5xxx_update_bits(struct ip5xxx *ip5xxx, unsigned int reg,
+                             unsigned int mask, unsigned int val)
+{
+       int ret;
+
+       ret = regmap_update_bits(ip5xxx->regmap, reg, mask, val);
+       if (ret)
+               ip5xxx->initialized = false;
+
+       return ret;
+}
+
+static int ip5xxx_initialize(struct power_supply *psy)
+{
+       struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
+       int ret;
+
+       if (ip5xxx->initialized)
+               return 0;
+
+       /*
+        * Disable shutdown under light load.
+        * Enable power on when under load.
+        */
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL1,
+                                IP5XXX_SYS_CTL1_LIGHT_SHDN_EN |
+                                IP5XXX_SYS_CTL1_LOAD_PWRUP_EN,
+                                IP5XXX_SYS_CTL1_LOAD_PWRUP_EN);
+       if (ret)
+               return ret;
+
+       /*
+        * Enable shutdown after a long button press (as configured below).
+        */
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL3,
+                                IP5XXX_SYS_CTL3_BTN_SHDN_EN,
+                                IP5XXX_SYS_CTL3_BTN_SHDN_EN);
+       if (ret)
+               return ret;
+
+       /*
+        * Power on automatically when VIN is removed.
+        */
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL4,
+                                IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN,
+                                IP5XXX_SYS_CTL4_VIN_PULLOUT_BOOST_EN);
+       if (ret)
+               return ret;
+
+       /*
+        * Enable the NTC.
+        * Configure the button for two presses => LED, long press => shutdown.
+        */
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL5,
+                                IP5XXX_SYS_CTL5_NTC_DIS |
+                                IP5XXX_SYS_CTL5_WLED_MODE_SEL |
+                                IP5XXX_SYS_CTL5_BTN_SHDN_SEL,
+                                IP5XXX_SYS_CTL5_WLED_MODE_SEL |
+                                IP5XXX_SYS_CTL5_BTN_SHDN_SEL);
+       if (ret)
+               return ret;
+
+       ip5xxx->initialized = true;
+       dev_dbg(psy->dev.parent, "Initialized after power on\n");
+
+       return 0;
+}
+
+static const enum power_supply_property ip5xxx_battery_properties[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_CHARGE_TYPE,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_OCV,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       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,
+};
+
+static int ip5xxx_battery_get_status(struct ip5xxx *ip5xxx, int *val)
+{
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
+       if (ret)
+               return ret;
+
+       switch (rval & IP5XXX_READ0_CHG_STAT) {
+       case IP5XXX_READ0_CHG_STAT_IDLE:
+               *val = POWER_SUPPLY_STATUS_DISCHARGING;
+               break;
+       case IP5XXX_READ0_CHG_STAT_TRICKLE:
+       case IP5XXX_READ0_CHG_STAT_CONST_CUR:
+       case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
+               *val = POWER_SUPPLY_STATUS_CHARGING;
+               break;
+       case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
+       case IP5XXX_READ0_CHG_STAT_FULL:
+               *val = POWER_SUPPLY_STATUS_FULL;
+               break;
+       case IP5XXX_READ0_CHG_STAT_TIMEOUT:
+               *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int ip5xxx_battery_get_charge_type(struct ip5xxx *ip5xxx, int *val)
+{
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
+       if (ret)
+               return ret;
+
+       switch (rval & IP5XXX_READ0_CHG_STAT) {
+       case IP5XXX_READ0_CHG_STAT_IDLE:
+       case IP5XXX_READ0_CHG_STAT_CONST_VOLT_STOP:
+       case IP5XXX_READ0_CHG_STAT_FULL:
+       case IP5XXX_READ0_CHG_STAT_TIMEOUT:
+               *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+               break;
+       case IP5XXX_READ0_CHG_STAT_TRICKLE:
+               *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+               break;
+       case IP5XXX_READ0_CHG_STAT_CONST_CUR:
+       case IP5XXX_READ0_CHG_STAT_CONST_VOLT:
+               *val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int ip5xxx_battery_get_health(struct ip5xxx *ip5xxx, int *val)
+{
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_read(ip5xxx, IP5XXX_READ0, &rval);
+       if (ret)
+               return ret;
+
+       if (rval & IP5XXX_READ0_TIMEOUT)
+               *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+       else
+               *val = POWER_SUPPLY_HEALTH_GOOD;
+
+       return 0;
+}
+
+static int ip5xxx_battery_get_voltage_max(struct ip5xxx *ip5xxx, int *val)
+{
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
+       if (ret)
+               return ret;
+
+       /*
+        * It is not clear what this will return if
+        * IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN is not set...
+        */
+       switch (rval & IP5XXX_CHG_CTL2_BAT_TYPE_SEL) {
+       case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V:
+               *val = 4200000;
+               break;
+       case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V:
+               *val = 4300000;
+               break;
+       case IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V:
+               *val = 4350000;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int ip5xxx_battery_read_adc(struct ip5xxx *ip5xxx,
+                                  u8 lo_reg, u8 hi_reg, int *val)
+{
+       unsigned int hi, lo;
+       int ret;
+
+       ret = ip5xxx_read(ip5xxx, lo_reg, &lo);
+       if (ret)
+               return ret;
+
+       ret = ip5xxx_read(ip5xxx, hi_reg, &hi);
+       if (ret)
+               return ret;
+
+       *val = sign_extend32(hi << 8 | lo, 13);
+
+       return 0;
+}
+
+static int ip5xxx_battery_get_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      union power_supply_propval *val)
+{
+       struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
+       int raw, ret, vmax;
+       unsigned int rval;
+
+       ret = ip5xxx_initialize(psy);
+       if (ret)
+               return ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               return ip5xxx_battery_get_status(ip5xxx, &val->intval);
+
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               return ip5xxx_battery_get_charge_type(ip5xxx, &val->intval);
+
+       case POWER_SUPPLY_PROP_HEALTH:
+               return ip5xxx_battery_get_health(ip5xxx, &val->intval);
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               return ip5xxx_battery_get_voltage_max(ip5xxx, &val->intval);
+
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATVADC_DAT0,
+                                             IP5XXX_BATVADC_DAT1, &raw);
+
+               val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
+               return 0;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+               ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATOCV_DAT0,
+                                             IP5XXX_BATOCV_DAT1, &raw);
+
+               val->intval = 2600000 + DIV_ROUND_CLOSEST(raw * 26855, 100);
+               return 0;
+
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               ret = ip5xxx_battery_read_adc(ip5xxx, IP5XXX_BATIADC_DAT0,
+                                             IP5XXX_BATIADC_DAT1, &raw);
+
+               val->intval = DIV_ROUND_CLOSEST(raw * 745985, 1000);
+               return 0;
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL4A, &rval);
+               if (ret)
+                       return ret;
+
+               rval &= IP5XXX_CHG_CTL4A_CONST_CUR_SEL;
+               val->intval = 100000 * rval;
+               return 0;
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+               val->intval = 100000 * 0x1f;
+               return 0;
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+               ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
+               if (ret)
+                       return ret;
+
+               ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL2, &rval);
+               if (ret)
+                       return ret;
+
+               rval &= IP5XXX_CHG_CTL2_CONST_VOLT_SEL;
+               val->intval = vmax + 14000 * (rval >> 1);
+               return 0;
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+               ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
+               if (ret)
+                       return ret;
+
+               val->intval = vmax + 14000 * 3;
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ip5xxx_battery_set_voltage_max(struct ip5xxx *ip5xxx, int val)
+{
+       unsigned int rval;
+       int ret;
+
+       switch (val) {
+       case 4200000:
+               rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_2V;
+               break;
+       case 4300000:
+               rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_3V;
+               break;
+       case 4350000:
+               rval = IP5XXX_CHG_CTL2_BAT_TYPE_SEL_4_35V;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
+                                IP5XXX_CHG_CTL2_BAT_TYPE_SEL, rval);
+       if (ret)
+               return ret;
+
+       ret = ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4,
+                                IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN,
+                                IP5XXX_CHG_CTL4_BAT_TYPE_SEL_EN);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int ip5xxx_battery_set_property(struct power_supply *psy,
+                                      enum power_supply_property psp,
+                                      const union power_supply_propval *val)
+{
+       struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
+       unsigned int rval;
+       int ret, vmax;
+
+       ret = ip5xxx_initialize(psy);
+       if (ret)
+               return ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               switch (val->intval) {
+               case POWER_SUPPLY_STATUS_CHARGING:
+                       rval = IP5XXX_SYS_CTL0_CHARGER_EN;
+                       break;
+               case POWER_SUPPLY_STATUS_DISCHARGING:
+               case POWER_SUPPLY_STATUS_NOT_CHARGING:
+                       rval = 0;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
+                                         IP5XXX_SYS_CTL0_CHARGER_EN, rval);
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               return ip5xxx_battery_set_voltage_max(ip5xxx, val->intval);
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               rval = val->intval / 100000;
+               return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL4A,
+                                         IP5XXX_CHG_CTL4A_CONST_CUR_SEL, rval);
+
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+               ret = ip5xxx_battery_get_voltage_max(ip5xxx, &vmax);
+               if (ret)
+                       return ret;
+
+               rval = ((val->intval - vmax) / 14000) << 1;
+               return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL2,
+                                         IP5XXX_CHG_CTL2_CONST_VOLT_SEL, rval);
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ip5xxx_battery_property_is_writeable(struct power_supply *psy,
+                                               enum power_supply_property psp)
+{
+       return psp == POWER_SUPPLY_PROP_STATUS ||
+              psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
+              psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
+              psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
+}
+
+static const struct power_supply_desc ip5xxx_battery_desc = {
+       .name                   = "ip5xxx-battery",
+       .type                   = POWER_SUPPLY_TYPE_BATTERY,
+       .properties             = ip5xxx_battery_properties,
+       .num_properties         = ARRAY_SIZE(ip5xxx_battery_properties),
+       .get_property           = ip5xxx_battery_get_property,
+       .set_property           = ip5xxx_battery_set_property,
+       .property_is_writeable  = ip5xxx_battery_property_is_writeable,
+};
+
+static const enum power_supply_property ip5xxx_boost_properties[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+};
+
+static int ip5xxx_boost_get_property(struct power_supply *psy,
+                                    enum power_supply_property psp,
+                                    union power_supply_propval *val)
+{
+       struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_initialize(psy);
+       if (ret)
+               return ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               ret = ip5xxx_read(ip5xxx, IP5XXX_SYS_CTL0, &rval);
+               if (ret)
+                       return ret;
+
+               val->intval = !!(rval & IP5XXX_SYS_CTL0_BOOST_EN);
+               return 0;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+               ret = ip5xxx_read(ip5xxx, IP5XXX_CHG_CTL1, &rval);
+               if (ret)
+                       return ret;
+
+               rval &= IP5XXX_CHG_CTL1_BOOST_UVP_SEL;
+               val->intval = 4530000 + 100000 * (rval >> 2);
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ip5xxx_boost_set_property(struct power_supply *psy,
+                                    enum power_supply_property psp,
+                                    const union power_supply_propval *val)
+{
+       struct ip5xxx *ip5xxx = power_supply_get_drvdata(psy);
+       unsigned int rval;
+       int ret;
+
+       ret = ip5xxx_initialize(psy);
+       if (ret)
+               return ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               rval = val->intval ? IP5XXX_SYS_CTL0_BOOST_EN : 0;
+               return ip5xxx_update_bits(ip5xxx, IP5XXX_SYS_CTL0,
+                                         IP5XXX_SYS_CTL0_BOOST_EN, rval);
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+               rval = ((val->intval - 4530000) / 100000) << 2;
+               return ip5xxx_update_bits(ip5xxx, IP5XXX_CHG_CTL1,
+                                         IP5XXX_CHG_CTL1_BOOST_UVP_SEL, rval);
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ip5xxx_boost_property_is_writeable(struct power_supply *psy,
+                                             enum power_supply_property psp)
+{
+       return true;
+}
+
+static const struct power_supply_desc ip5xxx_boost_desc = {
+       .name                   = "ip5xxx-boost",
+       .type                   = POWER_SUPPLY_TYPE_USB,
+       .properties             = ip5xxx_boost_properties,
+       .num_properties         = ARRAY_SIZE(ip5xxx_boost_properties),
+       .get_property           = ip5xxx_boost_get_property,
+       .set_property           = ip5xxx_boost_set_property,
+       .property_is_writeable  = ip5xxx_boost_property_is_writeable,
+};
+
+static const struct regmap_config ip5xxx_regmap_config = {
+       .reg_bits               = 8,
+       .val_bits               = 8,
+       .max_register           = IP5XXX_BATOCV_DAT1,
+};
+
+static int ip5xxx_power_probe(struct i2c_client *client)
+{
+       struct power_supply_config psy_cfg = {};
+       struct device *dev = &client->dev;
+       struct power_supply *psy;
+       struct ip5xxx *ip5xxx;
+
+       ip5xxx = devm_kzalloc(dev, sizeof(*ip5xxx), GFP_KERNEL);
+       if (!ip5xxx)
+               return -ENOMEM;
+
+       ip5xxx->regmap = devm_regmap_init_i2c(client, &ip5xxx_regmap_config);
+       if (IS_ERR(ip5xxx->regmap))
+               return PTR_ERR(ip5xxx->regmap);
+
+       psy_cfg.of_node = dev->of_node;
+       psy_cfg.drv_data = ip5xxx;
+
+       psy = devm_power_supply_register(dev, &ip5xxx_battery_desc, &psy_cfg);
+       if (IS_ERR(psy))
+               return PTR_ERR(psy);
+
+       psy = devm_power_supply_register(dev, &ip5xxx_boost_desc, &psy_cfg);
+       if (IS_ERR(psy))
+               return PTR_ERR(psy);
+
+       return 0;
+}
+
+static const struct of_device_id ip5xxx_power_of_match[] = {
+       { .compatible = "injoinic,ip5108" },
+       { .compatible = "injoinic,ip5109" },
+       { .compatible = "injoinic,ip5207" },
+       { .compatible = "injoinic,ip5209" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, ip5xxx_power_of_match);
+
+static struct i2c_driver ip5xxx_power_driver = {
+       .probe_new      = ip5xxx_power_probe,
+       .driver         = {
+               .name           = "ip5xxx-power",
+               .of_match_table = ip5xxx_power_of_match,
+       }
+};
+module_i2c_driver(ip5xxx_power_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver");
+MODULE_LICENSE("GPL");