Contributors: 3
Author Tokens Token Proportion Commits Commit Proportion
Samuel Holland 2353 99.87% 1 33.33%
Ondrej Jirman 2 0.08% 1 33.33%
Uwe Kleine-König 1 0.04% 1 33.33%
Total 2356 3


// 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 * 149197, 200);
		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		= 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");