lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Fri, 4 Mar 2022 22:50:38 +0100
From:   Sebastian Reichel <sebastian.reichel@...labora.com>
To:     Samuel Holland <samuel@...lland.org>
Cc:     Ondrej Jirman <x@....cz>, linux-kernel@...r.kernel.org,
        linux-pm@...r.kernel.org, Rob Herring <robh@...nel.org>
Subject: Re: [PATCH v2 3/3] power: supply: Add a driver for Injoinic power
 bank ICs

Hi,

On Sun, Feb 13, 2022 at 10:22:59PM -0600, Samuel Holland wrote:
> 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@...lland.org>
> ---

Thanks, queued (together with patches 1&2).

-- Sebastian

> Changes in v2:
>  - Add check for allocation failure
>  - Rename "charger" to "battery" for accuracy
>  - Add separate supply for USB 5V boost output stage
>  - Move POWER_SUPPLY_PROP_ONLINE to the boost supply
>  - Add POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN to the boost supply
> 
>  MAINTAINERS                         |   5 +
>  drivers/power/supply/Kconfig        |   8 +
>  drivers/power/supply/Makefile       |   1 +
>  drivers/power/supply/ip5xxx_power.c | 638 ++++++++++++++++++++++++++++
>  4 files changed, 652 insertions(+)
>  create mode 100644 drivers/power/supply/ip5xxx_power.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fca970a46e77..5c8ef361a69b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9475,6 +9475,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@...lland.org>
> +S:	Maintained
> +F:	drivers/power/supply/ip5xxx_power.c
> +
>  INOTIFY
>  M:	Jan Kara <jack@...e.cz>
>  R:	Amir Goldstein <amir73il@...il.com>
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index b366e2fd8e97..539f66018ec4 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -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
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 2c1b264b2046..7a70c740324e 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -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
> index 000000000000..218e8e689a3f
> --- /dev/null
> +++ b/drivers/power/supply/ip5xxx_power.c
> @@ -0,0 +1,638 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (C) 2021 Samuel Holland <samuel@...lland.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@...lland.org>");
> +MODULE_DESCRIPTION("Injoinic IP5xxx power bank IC driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.33.1
> 

Download attachment "signature.asc" of type "application/pgp-signature" (834 bytes)

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ