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] [thread-next>] [day] [month] [year] [list]
Message-ID: <20211025224054.iu4qtvn6kawxhh46@earth.universe>
Date:   Tue, 26 Oct 2021 00:40:54 +0200
From:   Sebastian Reichel <sebastian.reichel@...labora.com>
To:     Thomas Marangoni <thomas.marangoni@....at>
Cc:     wens@...e.org, linux-pm@...r.kernel.org,
        linux-kernel@...r.kernel.org
Subject: Re: [PATCH] PM: Added functionality to the axp20x_battery driver

Hi,

On Mon, Oct 25, 2021 at 04:44:55PM +0200, Thomas Marangoni wrote:
> This patch adds missing features of the axp209 battery
> functionality to the driver.

I don't consider "Add missing features" as one logical change, so
please split this according to Documentation/process/submitting-patches.rst

> New and present features have been added to the device tree configuration.
> 
> Following features have been implemented:
> - Set/Get of OCV curve, this is used to tune the capacity status (setting these
>   values is only possible with the device tree).
> - Set/Get of voltage low alert, this will trigger an interrupt if the given
>   voltage level is reached. Level 1 will print a warning and level 2 will shutdown
>   the device.
> - Set/Get of temperature sense current, this is useful if a none default NTC is
>   used for temperature sensing.
> - Set/Get of temperature sense rate, this defines how often the ADC is getting
>   the temperature values.
> - Set/Get of temperature charging and discharging voltages, this defines the
>   temperature ranges (as voltage) where the battery can be charged.
>   (setting these values is only possible with the device tree).
> - Get of temperature voltage, this returns the voltage that is present on the NTC.

Why is the temperature not converted to °C allowing to use
standard properties and being more user friendly?

> These custom properties have been added to /sys:
> - voltage_low_alert_level1 (RW)
> - voltage_low_alert_level2 (RW)
> - ocv_curve (RO)
> - temperature_sense_current (RW)
> - temperature_sense_rate (RW)
> - temperature_sense_voltage_now (RO)
> - temperature_discharge_threshold_voltage_range (RO)
> - temperature_charge_threshold_voltage_range (RO)
> 
> These IRQs have been added:
> - BATT_PLUGIN (generic, useful for udev)
> - BATT_REMOVAL (generic, useful for udev)
> - CHARG (generic, useful for udev)
> - CHARG_DONE (generic, useful for udev)
> - BATT_TEMP_HIGH (prints warning, axp stops charging/discharging)
> - BATT_TEMP_LOW (prints warning, axp stops charging/discharging)
> - LOW_PWR_LVL1 (prints warning)
> - LOW_PWR_LVL2 (prints warning and initializes a system shutdown)

Battery temperature and low power events should also be reported
through the HEALTH property if possible (i.e. if this information
can be read from a register that keeps the state until the condition
changes). In that case the IRQ should also trigger
power_supply_changed().

> These properties have been added to be applied from the device tree:
> - low-voltage-level1-microvolt
> - low-voltage-level2-microvolt
> - temperature-sense-current-microamp
> - temperature-sense-rate-hertz
> - temperature-discharge-range-microvolt
> - temperature-charge-range-microvolt
> - voltage-max-design-microvolt
> - ocv-capacity-table-0
> 
> Signed-off-by: Thomas Marangoni <thomas.marangoni@....at>
> ---
>  drivers/mfd/axp20x.c                  |  13 +
>  drivers/power/supply/axp20x_battery.c | 938 +++++++++++++++++++++++++-
>  2 files changed, 945 insertions(+), 6 deletions(-)

DT ABI changes require updates to the devicetree binding files [0] &
[1] with the DT maintainers being in Cc.

Custom sysfs properties need to be documented in the userspace ABI
documentation in a new file [2], or become standard properties and
be documented in [3].

[0] Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml
[1] Documentation/devicetree/bindings/power/supply/battery.yaml
[2] Documentation/ABI/testing/sysfs-class-power-axp20x-battery
[3] Documentation/ABI/testing/sysfs-class-power

For now I will stop reviewing here ;)

Thanks,

-- Sebastian

> diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
> index 8161a5dc68e8..05dea452b513 100644
> --- a/drivers/mfd/axp20x.c
> +++ b/drivers/mfd/axp20x.c
> @@ -191,6 +191,17 @@ static const struct resource axp20x_usb_power_supply_resources[] = {
>  	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_VBUS_NOT_VALID, "VBUS_NOT_VALID"),
>  };
>  
> +static const struct resource axp20x_battery_power_supply_resources[] = {
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_PLUGIN, "BATT_PLUGIN"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_REMOVAL, "BATT_REMOVAL"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG, "CHARG"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_CHARG_DONE, "CHARG_DONE"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_TEMP_HIGH, "BATT_TEMP_HIGH"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_BATT_TEMP_LOW, "BATT_TEMP_LOW"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_LOW_PWR_LVL1, "LOW_PWR_LVL1"),
> +	DEFINE_RES_IRQ_NAMED(AXP20X_IRQ_LOW_PWR_LVL2, "LOW_PWR_LVL2"),
> +};
> +
>  static const struct resource axp22x_usb_power_supply_resources[] = {
>  	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"),
>  	DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"),
> @@ -604,6 +615,8 @@ static const struct mfd_cell axp20x_cells[] = {
>  	}, {
>  		.name		= "axp20x-battery-power-supply",
>  		.of_compatible	= "x-powers,axp209-battery-power-supply",
> +		.num_resources	= ARRAY_SIZE(axp20x_battery_power_supply_resources),
> +		.resources	= axp20x_battery_power_supply_resources,
>  	}, {
>  		.name		= "axp20x-ac-power-supply",
>  		.of_compatible	= "x-powers,axp202-ac-power-supply",
> diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
> index 18a9db0df4b1..5997c8192c73 100644
> --- a/drivers/power/supply/axp20x_battery.c
> +++ b/drivers/power/supply/axp20x_battery.c
> @@ -31,6 +31,7 @@
>  #include <linux/iio/iio.h>
>  #include <linux/iio/consumer.h>
>  #include <linux/mfd/axp20x.h>
> +#include <linux/reboot.h>
>  
>  #define AXP20X_PWR_STATUS_BAT_CHARGING	BIT(2)
>  
> @@ -56,6 +57,25 @@
>  
>  #define AXP20X_V_OFF_MASK		GENMASK(2, 0)
>  
> +#define AXP20X_APS_WARN_MASK		GENMASK(7, 0)
> +
> +#define AXP20X_TEMP_MASK		GENMASK(7, 0)
> +
> +#define AXP20X_ADC_TS_RATE_MASK		GENMASK(7, 6)
> +#define AXP20X_ADC_TS_RATE_25Hz		(0 << 6)
> +#define AXP20X_ADC_TS_RATE_50Hz		(1 << 6)
> +#define AXP20X_ADC_TS_RATE_100Hz	(2 << 6)
> +#define AXP20X_ADC_TS_RATE_200Hz	(3 << 6)
> +
> +#define AXP20X_ADC_TS_CURRENT_MASK	GENMASK(5, 4)
> +#define AXP20X_ADC_TS_CURRENT_20uA	(0 << 4)
> +#define AXP20X_ADC_TS_CURRENT_40uA	(1 << 4)
> +#define AXP20X_ADC_TS_CURRENT_60uA	(2 << 4)
> +#define AXP20X_ADC_TS_CURRENT_80uA	(3 << 4)
> +
> +
> +#define DRVNAME "axp20x-battery-power-supply"
> +
>  struct axp20x_batt_ps;
>  
>  struct axp_data {
> @@ -78,6 +98,79 @@ struct axp20x_batt_ps {
>  	const struct axp_data	*data;
>  };
>  
> +/*
> + * OCV curve has fixed values and percentage can be adjusted, this array represents
> + * the fixed values in uV
> + */
> +const int axp20x_ocv_values_uV[AXP20X_OCV_MAX + 1] = {
> +	3132800,
> +	3273600,
> +	3414400,
> +	3555200,
> +	3625600,
> +	3660800,
> +	3696000,
> +	3731200,
> +	3766400,
> +	3801600,
> +	3836800,
> +	3872000,
> +	3942400,
> +	4012800,
> +	4083200,
> +	4153600,
> +};
> +
> +static irqreturn_t axp20x_battery_power_irq(int irq, void *devid)
> +{
> +	struct axp20x_batt_ps *axp20x_batt = devid;
> +
> +	power_supply_changed(axp20x_batt->batt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t axp20x_battery_low_voltage_alert1_irq(int irq, void *devid)
> +{
> +	struct axp20x_batt_ps *axp20x_batt = devid;
> +
> +	dev_warn(axp20x_batt->dev, "Battery voltage low!");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +
> +static irqreturn_t axp20x_battery_low_voltage_alert2_irq(int irq, void *devid)
> +{
> +	struct axp20x_batt_ps *axp20x_batt = devid;
> +
> +	dev_emerg(axp20x_batt->dev, "Battery voltage very low! Iniatializing shutdown.");
> +
> +	orderly_poweroff(true);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t axp20x_battery_temperature_low_irq(int irq, void *devid)
> +{
> +	struct axp20x_batt_ps *axp20x_batt = devid;
> +
> +	dev_crit(axp20x_batt->dev, "Battery temperature to low!");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +
> +static irqreturn_t axp20x_battery_temperature_high_irq(int irq, void *devid)
> +{
> +	struct axp20x_batt_ps *axp20x_batt = devid;
> +
> +	dev_crit(axp20x_batt->dev, "Battery temperature to high!");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +
>  static int axp20x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt,
>  					  int *val)
>  {
> @@ -181,6 +274,361 @@ static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp,
>  	return 0;
>  }
>  
> +static int axp20x_battery_set_ocv_table(struct axp20x_batt_ps *axp_batt,
> +					struct power_supply_battery_ocv_table ocv_table[AXP20X_OCV_MAX+1],
> +					int ocv_table_size)
> +{
> +	int ret, i, error = 0;
> +
> +	if (ocv_table_size != AXP20X_OCV_MAX+1)
> +		return 1;
> +
> +	for (i = 0; i < ocv_table_size; i++) {
> +		ret = regmap_update_bits(axp_batt->regmap, AXP20X_OCV(i),
> +			GENMASK(7, 0), ocv_table[i].capacity);
> +
> +		if (ret)
> +			error = ret;
> +	}
> +
> +	return error;
> +}
> +
> +static int axp20x_battery_set_voltage_low_alert1(struct axp20x_batt_ps *axp_batt,
> +					 int voltage_alert)
> +{
> +	int ret;
> +	/* converts the warning voltage level in uV to the neeeded reg value */
> +	int val1 = (voltage_alert - 2867200) / (1400 * 4);
> +
> +	if (val1 < 0 || val1 > AXP20X_APS_WARN_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_APS_WARN_L1,
> +				  AXP20X_APS_WARN_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_voltage_low_alert1(struct axp20x_batt_ps *axp_batt,
> +						 int *voltage_alert)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_APS_WARN_L1, &reg);
> +	if (ret)
> +		return ret;
> +
> +	/* converts the reg value to warning voltage level in uV */
> +	*voltage_alert = 2867200 + (1400 * (reg & AXP20X_APS_WARN_MASK) * 4);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_voltage_low_alert2(struct axp20x_batt_ps *axp_batt,
> +					 int voltage_alert)
> +{
> +	int ret;
> +
> +	/* converts the warning voltage level in uV to the neeeded reg value */
> +	int val1 = (voltage_alert - 2867200) / (1400 * 4);
> +
> +	if (val1 < 0 || val1 > AXP20X_APS_WARN_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_APS_WARN_L2,
> +				  AXP20X_APS_WARN_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_voltage_low_alert2(struct axp20x_batt_ps *axp_batt,
> +						 int *voltage_alert)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_APS_WARN_L2, &reg);
> +	if (ret)
> +		return ret;
> +
> +	/* converts the reg value to warning voltage level in uV */
> +	*voltage_alert = 2867200 + (1400 * (reg & AXP20X_APS_WARN_MASK) * 4);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_sense_current(struct axp20x_batt_ps *axp_batt,
> +							int sense_current)
> +{
> +	int ret;
> +	int reg = -1;
> +
> +	switch (sense_current) {
> +	case 20:
> +		reg = AXP20X_ADC_TS_CURRENT_20uA;
> +		break;
> +	case 40:
> +		reg = AXP20X_ADC_TS_CURRENT_40uA;
> +		break;
> +	case 60:
> +		reg = AXP20X_ADC_TS_CURRENT_60uA;
> +		break;
> +	case 80:
> +		reg = AXP20X_ADC_TS_CURRENT_80uA;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (reg < 0)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_ADC_RATE,
> +				  AXP20X_ADC_TS_CURRENT_MASK, reg);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_sense_current(struct axp20x_batt_ps *axp_batt,
> +							int *sense_current)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_ADC_RATE, &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg = reg & AXP20X_ADC_TS_CURRENT_MASK;
> +
> +	switch (reg) {
> +	case AXP20X_ADC_TS_CURRENT_20uA:
> +		*sense_current = 20;
> +		break;
> +	case AXP20X_ADC_TS_CURRENT_40uA:
> +		*sense_current = 40;
> +		break;
> +	case AXP20X_ADC_TS_CURRENT_60uA:
> +		*sense_current = 60;
> +		break;
> +	case AXP20X_ADC_TS_CURRENT_80uA:
> +		*sense_current = 80;
> +		break;
> +	default:
> +		*sense_current = -1;
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_sense_rate(struct axp20x_batt_ps *axp_batt,
> +						     int sample_rate)
> +{
> +	int ret;
> +	int reg = -1;
> +
> +	switch (sample_rate) {
> +	case 25:
> +		reg = AXP20X_ADC_TS_RATE_25Hz;
> +		break;
> +	case 50:
> +		reg = AXP20X_ADC_TS_RATE_50Hz;
> +		break;
> +	case 100:
> +		reg = AXP20X_ADC_TS_RATE_100Hz;
> +		break;
> +	case 200:
> +		reg = AXP20X_ADC_TS_RATE_200Hz;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (reg < 0)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_ADC_RATE,
> +				  AXP20X_ADC_TS_RATE_MASK, reg);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_sense_rate(struct axp20x_batt_ps *axp_batt,
> +						     int *sample_rate)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_ADC_RATE, &reg);
> +	if (ret)
> +		return ret;
> +
> +	reg = reg & AXP20X_ADC_TS_RATE_MASK;
> +
> +	switch (reg) {
> +	case AXP20X_ADC_TS_RATE_25Hz:
> +		*sample_rate = 25;
> +		break;
> +	case AXP20X_ADC_TS_RATE_50Hz:
> +		*sample_rate = 50;
> +		break;
> +	case AXP20X_ADC_TS_RATE_100Hz:
> +		*sample_rate = 100;
> +		break;
> +	case AXP20X_ADC_TS_RATE_200Hz:
> +		*sample_rate = 200;
> +		break;
> +	default:
> +		*sample_rate = -1;
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_discharge_voltage_min(struct axp20x_batt_ps *axp_batt,
> +								int voltage)
> +{
> +	int ret;
> +
> +	int val1 = voltage / (0x10 * 800);
> +
> +	if (val1 < 0 || val1 > AXP20X_TEMP_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_LTF_DISCHRG,
> +				  AXP20X_TEMP_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_discharge_voltage_min(struct axp20x_batt_ps *axp_batt,
> +								int *voltage)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_V_LTF_DISCHRG, &reg);
> +	if (ret)
> +		return ret;
> +
> +	*voltage = reg * 0x10 * 800;
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_discharge_voltage_max(struct axp20x_batt_ps *axp_batt,
> +								int voltage)
> +{
> +	int ret;
> +
> +	int val1 = voltage / (0x10 * 800);
> +
> +	if (val1 < 0 || val1 > AXP20X_TEMP_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_HTF_DISCHRG,
> +				  AXP20X_TEMP_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_discharge_voltage_max(struct axp20x_batt_ps *axp_batt,
> +								int *voltage)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_V_HTF_DISCHRG, &reg);
> +	if (ret)
> +		return ret;
> +
> +	*voltage = reg * 0x10 * 800;
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_charge_voltage_min(struct axp20x_batt_ps *axp_batt,
> +							     int voltage)
> +{
> +	int ret;
> +
> +	int val1 = voltage / (0x10 * 800);
> +
> +	if (val1 < 0 || val1 > AXP20X_TEMP_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_LTF_CHRG,
> +				  AXP20X_TEMP_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_charge_voltage_min(struct axp20x_batt_ps *axp_batt,
> +							     int *voltage)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_V_LTF_CHRG, &reg);
> +	if (ret)
> +		return ret;
> +
> +	*voltage = reg * 0x10 * 800;
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_set_temperature_charge_voltage_max(struct axp20x_batt_ps *axp_batt,
> +							     int voltage)
> +{
> +	int ret;
> +
> +	int val1 = voltage / (0x10 * 800);
> +
> +	if (val1 < 0 || val1 > AXP20X_TEMP_MASK)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(axp_batt->regmap, AXP20X_V_HTF_CHRG,
> +				  AXP20X_TEMP_MASK, val1);
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temperature_charge_voltage_max(struct axp20x_batt_ps *axp_batt,
> +							     int *voltage)
> +{
> +	int reg, ret;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_V_HTF_CHRG, &reg);
> +	if (ret)
> +		return ret;
> +
> +	*voltage = reg * 0x10 * 800;
> +
> +	return ret;
> +}
> +
> +static int axp20x_battery_get_temp_sense_voltage_now(struct axp20x_batt_ps *axp_batt,
> +						     int *voltage)
> +{
> +	int reg, ret, val1;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_L, &reg);
> +	if (ret)
> +		return ret;
> +
> +	val1 = reg;
> +
> +	ret = regmap_read(axp_batt->regmap, AXP20X_TS_IN_H, &reg);
> +	if (ret)
> +		return ret;
> +
> +	/* merging high and low value */
> +	val1 = (reg << 4) | val1;
> +
> +	/* convert register value to real uV */
> +	*voltage = val1 * 800;
> +
> +	return ret;
> +}
> +
>  static int axp20x_battery_get_prop(struct power_supply *psy,
>  				   enum power_supply_property psp,
>  				   union power_supply_propval *val)
> @@ -461,7 +909,8 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
>  		return axp20x_set_voltage_min_design(axp20x_batt, val->intval);
>  
>  	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> -		return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval);
> +		return axp20x_batt->data->set_max_voltage(axp20x_batt,
> +							  val->intval);
>  
>  	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
>  		return axp20x_set_constant_charge_current(axp20x_batt,
> @@ -472,13 +921,16 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
>  	case POWER_SUPPLY_PROP_STATUS:
>  		switch (val->intval) {
>  		case POWER_SUPPLY_STATUS_CHARGING:
> -			return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1,
> -				AXP20X_CHRG_CTRL1_ENABLE, AXP20X_CHRG_CTRL1_ENABLE);
> +			return regmap_update_bits(axp20x_batt->regmap,
> +						  AXP20X_CHRG_CTRL1,
> +						  AXP20X_CHRG_CTRL1_ENABLE,
> +						  AXP20X_CHRG_CTRL1_ENABLE);
>  
>  		case POWER_SUPPLY_STATUS_DISCHARGING:
>  		case POWER_SUPPLY_STATUS_NOT_CHARGING:
> -			return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1,
> -				AXP20X_CHRG_CTRL1_ENABLE, 0);
> +			return regmap_update_bits(axp20x_batt->regmap,
> +						  AXP20X_CHRG_CTRL1,
> +						  AXP20X_CHRG_CTRL1_ENABLE, 0);
>  		}
>  		fallthrough;
>  	default:
> @@ -510,6 +962,275 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
>  	       psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
>  }
>  
> +/* -- Custom attributes ----------------------------------------------------- */
> +
> +static ssize_t voltage_low_alert_level1_show(struct device *dev,
> +					     struct device_attribute *attr,
> +					     char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int voltage_alert;
> +
> +	axp20x_battery_get_voltage_low_alert1(axp20x_batt, &voltage_alert);
> +	status = sprintf(buf, "%d\n", voltage_alert);
> +
> +	return status;
> +}
> +
> +static ssize_t voltage_low_alert_level1_store(struct device *dev,
> +					      struct device_attribute *attr,
> +					      const char *buf, size_t count)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	unsigned long value;
> +	int status;
> +
> +	status = kstrtoul(buf, 0, &value);
> +	if (status)
> +		return status;
> +
> +	status = axp20x_battery_set_voltage_low_alert1(axp20x_batt, value);
> +	if (status)
> +		return status;
> +
> +	return count;
> +}
> +
> +DEVICE_ATTR_RW(voltage_low_alert_level1);
> +
> +static ssize_t voltage_low_alert_level2_show(struct device *dev,
> +					     struct device_attribute *attr,
> +					     char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int voltage_alert;
> +
> +	axp20x_battery_get_voltage_low_alert2(axp20x_batt, &voltage_alert);
> +	status = sprintf(buf, "%d\n", voltage_alert);
> +
> +	return status;
> +}
> +
> +static ssize_t voltage_low_alert_level2_store(struct device *dev,
> +					      struct device_attribute *attr,
> +					      const char *buf, size_t count)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	unsigned long value;
> +	int status;
> +
> +	status = kstrtoul(buf, 0, &value);
> +	if (status)
> +		return status;
> +
> +	status = axp20x_battery_set_voltage_low_alert2(axp20x_batt, value);
> +	if (status)
> +		return status;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(voltage_low_alert_level2);
> +
> +static ssize_t ocv_curve_show(struct device *dev, struct device_attribute *attr,
> +			      char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status, ret, reg, i;
> +
> +	int ocv_curve_size = AXP20X_OCV_MAX+1;
> +	struct power_supply_battery_ocv_table ocv_curve[AXP20X_OCV_MAX+1];
> +
> +
> +	status = 0;
> +	for (i = 0; i < ocv_curve_size; i++) {
> +		ret = regmap_read(axp20x_batt->regmap, AXP20X_OCV(i), &reg);
> +		if (ret)
> +			status = ret;
> +		ocv_curve[i].capacity = reg;
> +		ocv_curve[i].ocv = axp20x_ocv_values_uV[i];
> +	}
> +
> +	if (status)
> +		return status;
> +
> +	status = 0;
> +	for (i = 0; i < ocv_curve_size; i++) {
> +		ret = sprintf(buf, "%sOCV_%d=%d\nCAP_%d=%d\n", buf, i,
> +			      ocv_curve[i].ocv, i, ocv_curve[i].capacity);
> +		if (ret)
> +			status = ret;
> +	}
> +
> +	return status;
> +}
> +
> +static DEVICE_ATTR_RO(ocv_curve);
> +
> +static ssize_t temperature_sense_current_show(struct device *dev,
> +					      struct device_attribute *attr,
> +					      char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int sense_current;
> +
> +	axp20x_battery_get_temperature_sense_current(axp20x_batt, &sense_current);
> +	status = sprintf(buf, "%d\n", sense_current);
> +
> +	return status;
> +}
> +
> +static ssize_t temperature_sense_current_store(struct device *dev,
> +					       struct device_attribute *attr,
> +					       const char *buf, size_t count)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	unsigned long value;
> +	int status;
> +
> +	status = kstrtoul(buf, 0, &value);
> +	if (status)
> +		return status;
> +
> +	status = axp20x_battery_set_temperature_sense_current(axp20x_batt, value);
> +	if (status)
> +		return status;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(temperature_sense_current);
> +
> +static ssize_t temperature_sense_rate_show(struct device *dev,
> +					   struct device_attribute *attr,
> +					   char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int sense_rate;
> +
> +	axp20x_battery_get_temperature_sense_rate(axp20x_batt, &sense_rate);
> +	status = sprintf(buf, "%d\n", sense_rate);
> +
> +	return status;
> +}
> +
> +static ssize_t temperature_sense_rate_store(struct device *dev,
> +					    struct device_attribute *attr,
> +					    const char *buf, size_t count)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	unsigned long value;
> +	int status;
> +
> +	status = kstrtoul(buf, 0, &value);
> +	if (status)
> +		return status;
> +
> +	status = axp20x_battery_set_temperature_sense_rate(axp20x_batt, value);
> +	if (status)
> +		return status;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(temperature_sense_rate);
> +
> +static ssize_t temperature_sense_voltage_now_show(struct device *dev,
> +						  struct device_attribute *attr,
> +						  char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int voltage;
> +
> +	axp20x_battery_get_temp_sense_voltage_now(axp20x_batt, &voltage);
> +	status = sprintf(buf, "%d\n", voltage);
> +
> +	return status;
> +}
> +
> +static DEVICE_ATTR_RO(temperature_sense_voltage_now);
> +
> +static ssize_t temperature_discharge_threshold_voltage_range_show(struct device *dev,
> +								  struct device_attribute *attr,
> +								  char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int min_voltage, max_voltage;
> +
> +	axp20x_battery_get_temperature_discharge_voltage_min(axp20x_batt,
> +							     &min_voltage);
> +	axp20x_battery_get_temperature_discharge_voltage_max(axp20x_batt,
> +							     &max_voltage);
> +
> +	status = sprintf(buf, "MIN=%d\nMAX=%d\n", min_voltage, max_voltage);
> +
> +	return status;
> +}
> +
> +static DEVICE_ATTR_RO(temperature_discharge_threshold_voltage_range);
> +
> +static ssize_t temperature_charge_threshold_voltage_range_show(struct device *dev,
> +							       struct device_attribute *attr,
> +							       char *buf)
> +{
> +	struct power_supply *psy = dev_get_drvdata(dev);
> +	struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy);
> +	int status;
> +
> +	int min_voltage, max_voltage;
> +
> +	axp20x_battery_get_temperature_charge_voltage_min(axp20x_batt,
> +							  &min_voltage);
> +	axp20x_battery_get_temperature_charge_voltage_max(axp20x_batt,
> +							  &max_voltage);
> +
> +	status = sprintf(buf, "MIN=%d\nMAX=%d\n", min_voltage, max_voltage);
> +
> +	return status;
> +}
> +
> +static DEVICE_ATTR_RO(temperature_charge_threshold_voltage_range);
> +
> +static struct attribute *axp20x_batt_attrs[] = {
> +	&dev_attr_voltage_low_alert_level1.attr,
> +	&dev_attr_voltage_low_alert_level2.attr,
> +	&dev_attr_ocv_curve.attr,
> +	&dev_attr_temperature_sense_current.attr,
> +	&dev_attr_temperature_sense_rate.attr,
> +	&dev_attr_temperature_sense_voltage_now.attr,
> +	&dev_attr_temperature_discharge_threshold_voltage_range.attr,
> +	&dev_attr_temperature_charge_threshold_voltage_range.attr,
> +	NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(axp20x_batt);
> +
> +/* -- Custom attributes END ------------------------------------------------- */
> +
>  static const struct power_supply_desc axp20x_batt_ps_desc = {
>  	.name = "axp20x-battery",
>  	.type = POWER_SUPPLY_TYPE_BATTERY,
> @@ -520,6 +1241,9 @@ static const struct power_supply_desc axp20x_batt_ps_desc = {
>  	.set_property = axp20x_battery_set_prop,
>  };
>  
> +static const char * const irq_names[] = { "BATT_PLUGIN", "BATT_REMOVAL", "CHARG",
> +					  "CHARG_DONE", NULL };
> +
>  static const struct axp_data axp209_data = {
>  	.ccc_scale = 100000,
>  	.ccc_offset = 300000,
> @@ -559,10 +1283,12 @@ MODULE_DEVICE_TABLE(of, axp20x_battery_ps_id);
>  
>  static int axp20x_power_probe(struct platform_device *pdev)
>  {
> +	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
>  	struct axp20x_batt_ps *axp20x_batt;
>  	struct power_supply_config psy_cfg = {};
>  	struct power_supply_battery_info info;
>  	struct device *dev = &pdev->dev;
> +	int i, irq, ret = 0;
>  
>  	if (!of_device_is_available(pdev->dev.of_node))
>  		return -ENODEV;
> @@ -602,6 +1328,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
>  
>  	psy_cfg.drv_data = axp20x_batt;
>  	psy_cfg.of_node = pdev->dev.of_node;
> +	psy_cfg.attr_grp = axp20x_batt_groups;
>  
>  	axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev);
>  
> @@ -615,14 +1342,105 @@ static int axp20x_power_probe(struct platform_device *pdev)
>  	}
>  
>  	if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
> +		struct device_node *battery_np;
> +
>  		int vmin = info.voltage_min_design_uv;
> +		int vmax = info.voltage_max_design_uv;
>  		int ccc = info.constant_charge_current_max_ua;
> +		struct power_supply_battery_ocv_table ocv_table[AXP20X_OCV_MAX+1];
> +		int ocv_table_size = 0;
> +		int lvl1 = 0;
> +		int lvl2 = 0;
> +		int temp_sense_current = 0;
> +		int temp_sense_rate = 0;
> +		int temp_discharge_min = -1;
> +		int temp_discharge_max = -1;
> +		int temp_charge_min = -1;
> +		int temp_charge_max = -1;
> +
> +		int i = 0, j = 0;
> +		bool too_many_ocv_tables = false;
> +		bool too_many_ocv_values = false;
> +		bool ocv_values_mismatch = false;
> +
> +		battery_np = of_parse_phandle(axp20x_batt->batt->of_node,
> +					      "monitored-battery", 0);
> +
> +		of_property_read_u32(battery_np, "low-voltage-level1-microvolt",
> +				     &lvl1);
> +		of_property_read_u32(battery_np, "low-voltage-level2-microvolt",
> +				     &lvl2);
> +		of_property_read_u32(battery_np, "temperature-sense-current-microamp",
> +				     &temp_sense_current);
> +		of_property_read_u32(battery_np, "temperature-sense-rate-hertz",
> +				     &temp_sense_rate);
> +
> +		of_property_read_u32_index(battery_np, "temperature-discharge-range-microvolt",
> +					   0, &temp_discharge_min);
> +		of_property_read_u32_index(battery_np, "temperature-discharge-range-microvolt",
> +					   1, &temp_discharge_max);
> +
> +		of_property_read_u32_index(battery_np, "temperature-charge-range-microvolt",
> +					   0, &temp_charge_min);
> +		of_property_read_u32_index(battery_np, "temperature-charge-range-microvolt",
> +					   1, &temp_charge_max);
>  
>  		if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
>  							      vmin))
>  			dev_err(&pdev->dev,
>  				"couldn't set voltage_min_design\n");
>  
> +		if (vmax > 0 && axp20x_battery_set_max_voltage(axp20x_batt,
> +							       vmax))
> +			dev_err(&pdev->dev,
> +				"couldn't set voltage_max_design\n");
> +
> +		if (lvl1 > 0 && axp20x_battery_set_voltage_low_alert1(axp20x_batt,
> +								      lvl1))
> +			dev_err(&pdev->dev,
> +				"couldn't set voltage_low_alert_level1\n");
> +
> +		if (lvl2 > 0 && axp20x_battery_set_voltage_low_alert2(axp20x_batt,
> +								      lvl2))
> +			dev_err(&pdev->dev,
> +				"couldn't set voltage_low_alert_level2\n");
> +
> +		if (temp_sense_current > 0 &&
> +		    axp20x_battery_set_temperature_sense_current(axp20x_batt,
> +								 temp_sense_current))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_current\n");
> +
> +		if (temp_sense_rate > 0 &&
> +		    axp20x_battery_set_temperature_sense_rate(axp20x_batt,
> +							      temp_sense_rate))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_rate\n");
> +
> +		if (temp_discharge_min >= 0 &&
> +		    axp20x_battery_set_temperature_discharge_voltage_min(axp20x_batt,
> +									 temp_discharge_min))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_rate\n");
> +
> +		if (temp_discharge_max >= 0 &&
> +		    axp20x_battery_set_temperature_discharge_voltage_max(axp20x_batt,
> +									 temp_discharge_max))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_rate\n");
> +
> +		if (temp_charge_min >= 0 &&
> +		    axp20x_battery_set_temperature_charge_voltage_min(axp20x_batt,
> +								      temp_charge_min))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_rate\n");
> +
> +		if (temp_charge_max >= 0 &&
> +		    axp20x_battery_set_temperature_charge_voltage_max(axp20x_batt,
> +								      temp_charge_max))
> +			dev_err(&pdev->dev,
> +				"couldn't set temperature_sense_rate\n");
> +
>  		/* Set max to unverified value to be able to set CCC */
>  		axp20x_batt->max_ccc = ccc;
>  
> @@ -634,6 +1452,57 @@ static int axp20x_power_probe(struct platform_device *pdev)
>  			axp20x_batt->max_ccc = ccc;
>  			axp20x_set_constant_charge_current(axp20x_batt, ccc);
>  		}
> +
> +		too_many_ocv_tables = false;
> +		too_many_ocv_values = false;
> +		ocv_values_mismatch = false;
> +		for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
> +			if (info.ocv_table_size[i] == -EINVAL ||
> +			   info.ocv_temp[i] == -EINVAL ||
> +			   info.ocv_table[i] == NULL)
> +				continue;
> +
> +			if (info.ocv_table_size[i] > (AXP20X_OCV_MAX+1)) {
> +				too_many_ocv_values = true;
> +				dev_err(&pdev->dev, "Too many values in ocv table, only %d values are supported",
> +					AXP20X_OCV_MAX + 1);
> +				break;
> +			}
> +
> +			if (i > 0) {
> +				too_many_ocv_tables = true;
> +				dev_err(&pdev->dev, "Only one ocv table is supported");
> +				break;
> +			}
> +
> +			for (j = 0; j < info.ocv_table_size[i]; j++) {
> +				if (info.ocv_table[i][j].ocv != axp20x_ocv_values_uV[j]) {
> +					ocv_values_mismatch = true;
> +					break;
> +				}
> +			}
> +
> +			if (ocv_values_mismatch) {
> +				dev_err(&pdev->dev, "ocv tables missmatches requirements");
> +				dev_info(&pdev->dev, "ocv table requires following ocv values in that order:");
> +				for (j = 0; j < AXP20X_OCV_MAX+1; j++) {
> +					dev_info(&pdev->dev, "%d uV",
> +						 axp20x_ocv_values_uV[j]);
> +				}
> +				break;
> +			}
> +
> +			ocv_table_size = info.ocv_table_size[i];
> +			for (j = 0; j < info.ocv_table_size[i]; j++)
> +				ocv_table[j] = info.ocv_table[i][j];
> +
> +		}
> +
> +		if (!too_many_ocv_tables && !too_many_ocv_values &&
> +		    !ocv_values_mismatch)
> +			axp20x_battery_set_ocv_table(axp20x_batt, ocv_table,
> +						     ocv_table_size);
> +
>  	}
>  
>  	/*
> @@ -643,13 +1512,70 @@ static int axp20x_power_probe(struct platform_device *pdev)
>  	axp20x_get_constant_charge_current(axp20x_batt,
>  					   &axp20x_batt->max_ccc);
>  
> +	/* Request irqs after registering, as irqs may trigger immediately */
> +	for (i = 0; irq_names[i]; i++) {
> +		irq = platform_get_irq_byname(pdev, irq_names[i]);
> +		if (irq < 0) {
> +			dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
> +				 irq_names[i], irq);
> +			continue;
> +		}
> +		irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
> +		ret = devm_request_any_context_irq(&pdev->dev, irq,
> +						   axp20x_battery_power_irq, 0,
> +						   DRVNAME, axp20x_batt);
> +		if (ret < 0)
> +			dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
> +				 irq_names[i], ret);
> +	}
> +
> +	irq = platform_get_irq_byname(pdev, "LOW_PWR_LVL1");
> +	irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, irq,
> +					   axp20x_battery_low_voltage_alert1_irq,
> +					   0, DRVNAME, axp20x_batt);
> +
> +	if (ret < 0)
> +		dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_LOW_PWR_LVL1 IRQ: %d\n",
> +			 ret);
> +
> +	irq = platform_get_irq_byname(pdev, "LOW_PWR_LVL2");
> +	irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, irq,
> +					   axp20x_battery_low_voltage_alert2_irq,
> +					   0, DRVNAME, axp20x_batt);
> +
> +	if (ret < 0)
> +		dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_LOW_PWR_LVL2 IRQ: %d\n",
> +			 ret);
> +
> +	irq = platform_get_irq_byname(pdev, "BATT_TEMP_LOW");
> +	irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, irq,
> +					   axp20x_battery_temperature_low_irq,
> +					   0, DRVNAME, axp20x_batt);
> +
> +	if (ret < 0)
> +		dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_BATT_TEMP_LOW IRQ: %d\n",
> +			 ret);
> +
> +	irq = platform_get_irq_byname(pdev, "BATT_TEMP_HIGH");
> +	irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
> +	ret = devm_request_any_context_irq(&pdev->dev, irq,
> +					   axp20x_battery_temperature_high_irq,
> +					   0, DRVNAME, axp20x_batt);
> +
> +	if (ret < 0)
> +		dev_warn(&pdev->dev, "Error requesting AXP20X_IRQ_BATT_TEMP_HIGH IRQ: %d\n",
> +			 ret);
> +
>  	return 0;
>  }
>  
>  static struct platform_driver axp20x_batt_driver = {
>  	.probe    = axp20x_power_probe,
>  	.driver   = {
> -		.name  = "axp20x-battery-power-supply",
> +		.name  = DRVNAME,
>  		.of_match_table = axp20x_battery_ps_id,
>  	},
>  };
> -- 
> 2.25.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