--- /dev/null
+// 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");