Linux Power Management development
 help / color / mirror / Atom feed
From: Sebastian Reichel <sre@kernel.org>
To: Vincent Cloutier <vincent.cloutier@icloud.com>
Cc: Hans de Goede <hansg@kernel.org>,
	 Krzysztof Kozlowski <krzk@kernel.org>,
	Marek Szyprowski <m.szyprowski@samsung.com>,
	 Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>,
	Purism Kernel Team <kernel@puri.sm>,
	 Rob Herring <robh@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	linux-pm@vger.kernel.org,  devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	 Vincent Cloutier <vincent@cloutier.co>
Subject: Re: [PATCH v3 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info
Date: Wed, 22 Jul 2026 00:23:41 +0200	[thread overview]
Message-ID: <al_qZdt-_DbSMFta@venus> (raw)
In-Reply-To: <20260721185904.40756-3-vincent.cloutier@icloud.com>

[-- Attachment #1: Type: text/plain, Size: 17665 bytes --]

Hello Vincent,

On Tue, Jul 21, 2026 at 02:57:34PM -0400, Vincent Cloutier wrote:
> From: Vincent Cloutier <vincent@cloutier.co>
> 
> MAX17055 can use charge-full-design-microamp-hours and
> charge-term-current-microamp from monitored-battery for its POR
> configuration. Prepare quantized DesignCap, IChgTerm, and EZ Config dQAcc
> values in the power-supply registration callback after the core has parsed
> battery information.
> 
> Validate all supplied values before changing the sparse configuration.
> Reject zero, values that quantize to zero, capacities that cannot produce
> dQAcc, and positive termination currents outside the signed register range.
> Limit this path to MAX17055 because the other supported gauges require
> complete characterization data.
> 
> Follow the documented POR sequence. Wait for FSTAT.DNR, save HibCfg, exit
> hibernate, write stable battery registers once and verify them after 1 ms,
> issue command writes directly, poll until refresh clears, and restore
> HibCfg. Clear POR only after every step succeeds.
> 
> Keep driver-backed properties unavailable while initialization is
> incomplete. If an I/O error interrupts the transaction, restore hibernate
> before the next attempt and retry periodically so a transient startup
> failure does not become permanent.
> 
> Assisted-by: OpenCode:gpt-5.6-sol
> Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
> ---

Please split this into smaller patches doing one thing at a time,
which makes it a lot easier to review and also helps to identify
potential bugs with git bisect in the future.

At least the init_complete and the change from 'struct work_struct
work' to 'struct delayed_work work' could be done in advance.

>  drivers/power/supply/max17042_battery.c | 335 +++++++++++++++++++++---
>  include/linux/power/max17042_battery.h  |   2 +
>  2 files changed, 306 insertions(+), 31 deletions(-)
> 
> diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
> index d409d2f0d383..b25bf26135e0 100644
> --- a/drivers/power/supply/max17042_battery.c
> +++ b/drivers/power/supply/max17042_battery.c
> @@ -62,18 +62,30 @@
>  #define MAX17042_RESISTANCE_LSB		1 / 4096 /* Ω */
>  #define MAX17042_TEMPERATURE_LSB	1 / 256 /* °C */
>  
> +#define MAX17055_DQACC_DIV		32
> +#define MAX17055_DPACC_FACTOR		44138
> +#define MAX17055_DPACC_VCHG_FACTOR	51200
> +#define MAX17055_FSTAT_DNR_BIT		BIT(0)
> +#define MAX17055_DNR_POLL_US		10000
> +#define MAX17055_DNR_TIMEOUT_US		2000000
> +#define MAX17055_INIT_RETRY_DELAY_MS	10000
> +#define MAX17055_REFRESH_POLL_US		10000
> +#define MAX17055_REFRESH_TIMEOUT_US	1000000
> +
>  struct max17042_chip {
>  	struct device *dev;
>  	struct regmap *regmap;
>  	struct power_supply *battery;
>  	enum max170xx_chip_type chip_type;
>  	struct max17042_config_data *config_data;
> -	struct work_struct work;
> -	int    init_complete;
> +	struct delayed_work work;
>  	int    irq;
>  	int    task_period;
>  	bool   enable_current_sense;
>  	bool   enable_por_init;
> +	bool   init_complete;
> +	bool   hib_restore_pending;
> +	u16    hib_cfg;
>  	unsigned int r_sns;
>  	int    vmin;	/* in millivolts */
>  	int    vmax;	/* in millivolts */
> @@ -256,7 +268,7 @@ static int max17042_get_property(struct power_supply *psy,
>  	u32 data;
>  	u64 data64;
>  
> -	if (!chip->init_complete)
> +	if (!READ_ONCE(chip->init_complete))
>  		return -EAGAIN;
>  
>  	switch (psp) {
> @@ -566,6 +578,24 @@ static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
>  	return ret;
>  }
>  
> +static int max17055_write_verify_reg(struct regmap *map, u8 reg, u32 value)
> +{
> +	u32 read_value;
> +	int ret;
> +
> +	ret = regmap_write(map, reg, value);
> +	if (ret)
> +		return ret;
> +
> +	usleep_range(1000, 2000);
> +
> +	ret = regmap_read(map, reg, &read_value);
> +	if (ret)
> +		return ret;
> +
> +	return read_value == value ? 0 : -EIO;
> +}
> +
>  static inline void max17042_override_por(struct regmap *map,
>  					 u8 reg, u16 value)
>  {
> @@ -801,8 +831,12 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
>  	max17042_override_por(map, MAX17042_CONFIG, config->config);
>  	max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
>  
> -	max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
> -	max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
> +	if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
> +		max17042_override_por(map, MAX17042_DesignCap,
> +				      config->design_cap);
> +		max17042_override_por(map, MAX17042_ICHGTerm,
> +				      config->ichgt_term);
> +	}
>  
>  	max17042_override_por(map, MAX17042_AtRate, config->at_rate);
>  	max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
> @@ -812,8 +846,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
>  
>  	max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
>  	max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
> -	max17042_override_por(map, MAX17042_dQacc, config->dqacc);
> -	max17042_override_por(map, MAX17042_dPacc, config->dpacc);
> +	if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
> +		max17042_override_por(map, MAX17042_dQacc, config->dqacc);
> +		max17042_override_por(map, MAX17042_dPacc, config->dpacc);
> +	}
>  
>  	max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
>  	max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
> @@ -847,26 +883,162 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
>  		max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
>  }
>  
> -static int max17042_init_chip(struct max17042_chip *chip)
> +static int max17055_override_battery_values(struct max17042_chip *chip)
>  {
> +	struct max17042_config_data *config = chip->config_data;
>  	struct regmap *map = chip->regmap;
> +	unsigned int design_cap;
> +	unsigned int model_cfg;
> +	unsigned int dqacc;
> +	u64 dpacc;
>  	int ret;
>  
> +	if (config->design_cap) {
> +		ret = max17055_write_verify_reg(map, MAX17042_DesignCap,
> +						config->design_cap);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (config->dqacc) {
> +		ret = max17055_write_verify_reg(map, MAX17042_dQacc,
> +						config->dqacc);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (config->ichgt_term) {
> +		ret = max17055_write_verify_reg(map, MAX17042_ICHGTerm,
> +						config->ichgt_term);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!config->design_cap && !config->dqacc)
> +		return 0;
> +
> +	ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(map, MAX17042_dQacc, &dqacc);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg);
> +	if (ret)
> +		return ret;
> +
> +	if (!design_cap || !dqacc)
> +		return -ERANGE;
> +
> +	dpacc = (u64)dqacc *
> +		(model_cfg & MAX17055_MODELCFG_VCHG_BIT ?
> +		 MAX17055_DPACC_VCHG_FACTOR : MAX17055_DPACC_FACTOR);
> +	do_div(dpacc, design_cap);
> +	if (dpacc > U16_MAX)
> +		return -ERANGE;
> +
> +	return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc);
> +}
> +
> +static int max17055_restore_hibernate(struct max17042_chip *chip)
> +{
> +	int restore_hib_ret;
> +	int soft_wakeup_ret;
> +
> +	soft_wakeup_ret = regmap_write(chip->regmap, MAX17055_SoftWakeup, 0);
> +	restore_hib_ret = max17055_write_verify_reg(chip->regmap,
> +						    MAX17055_HibCfg,
> +						    chip->hib_cfg);
> +	if (!soft_wakeup_ret && !restore_hib_ret)
> +		chip->hib_restore_pending = false;
> +
> +	return soft_wakeup_ret ?: restore_hib_ret;
> +}
> +
> +static int max17055_init_chip(struct max17042_chip *chip)
> +{
> +	struct regmap *map = chip->regmap;
> +	unsigned int hib_cfg;
> +	unsigned int model_cfg;
> +	unsigned int fstat;
> +	int restore_ret;
> +	int ret;
> +
> +	if (chip->hib_restore_pending) {
> +		ret = max17055_restore_hibernate(chip);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = regmap_read_poll_timeout(map, MAX17042_FSTAT, fstat,
> +				       !(fstat & MAX17055_FSTAT_DNR_BIT),
> +				       MAX17055_DNR_POLL_US,
> +				       MAX17055_DNR_TIMEOUT_US);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(map, MAX17055_HibCfg, &hib_cfg);
> +	if (ret)
> +		return ret;
> +
> +	chip->hib_cfg = hib_cfg;
> +	chip->hib_restore_pending = true;
> +
> +	ret = regmap_write(map, MAX17055_SoftWakeup, 0x0090);
> +	if (ret)
> +		goto restore_hibernate;
> +
> +	ret = max17055_write_verify_reg(map, MAX17055_HibCfg, 0);
> +	if (ret)
> +		goto restore_hibernate;
> +
> +	ret = regmap_write(map, MAX17055_SoftWakeup, 0);
> +	if (ret)
> +		goto restore_hibernate;
> +
>  	max17042_override_por_values(chip);
>  
> +	ret = max17055_override_battery_values(chip);
> +	if (ret)
> +		goto restore_hibernate;
> +
> +	ret = regmap_write_bits(map, MAX17055_ModelCfg,
> +				MAX17055_MODELCFG_REFRESH_BIT,
> +				MAX17055_MODELCFG_REFRESH_BIT);
> +	if (ret)
> +		goto restore_hibernate;
> +
> +	ret = regmap_read_poll_timeout(map, MAX17055_ModelCfg, model_cfg,
> +				       !(model_cfg &
> +					 MAX17055_MODELCFG_REFRESH_BIT),
> +				       MAX17055_REFRESH_POLL_US,
> +				       MAX17055_REFRESH_TIMEOUT_US);
> +
> +restore_hibernate:
> +	restore_ret = max17055_restore_hibernate(chip);
> +	if (restore_ret)
> +		return restore_ret;
> +
> +	return ret;
> +}
> +
> +static int max17042_init_chip(struct max17042_chip *chip)
> +{
> +	struct regmap *map = chip->regmap;
> +	int ret;
> +
>  	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
> -		regmap_write_bits(map, MAX17055_ModelCfg,
> -				  MAX17055_MODELCFG_REFRESH_BIT,
> -				  MAX17055_MODELCFG_REFRESH_BIT);
> -	}
> +		ret = max17055_init_chip(chip);
> +		if (ret)
> +			return ret;
> +	} else {
> +		max17042_override_por_values(chip);
>  
> -	/* After Power up, the MAX17042 requires 500mS in order
> -	 * to perform signal debouncing and initial SOC reporting
> -	 */
> -	msleep(500);
> +		/* Allow signal debouncing and initial SOC reporting. */
> +		msleep(500);
>  
> -	if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) {
> -		/* Initialize configuration */
>  		max17042_write_config_regs(chip);
>  
>  		/* write cell characterization data */
> @@ -902,8 +1074,7 @@ static int max17042_init_chip(struct max17042_chip *chip)
>  	}
>  
>  	/* Init complete, Clear the POR bit */
> -	regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
> -	return 0;
> +	return regmap_clear_bits(map, MAX17042_STATUS, STATUS_POR_BIT);

another correct, but unrelated change.

>  }
>  
>  static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
> @@ -989,18 +1160,28 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
>  
>  static void max17042_init_worker(struct work_struct *work)
>  {
> -	struct max17042_chip *chip = container_of(work,
> +	struct max17042_chip *chip = container_of(to_delayed_work(work),
>  				struct max17042_chip, work);
> -	int ret;
> +	int ret = 0;
>  
>  	/* Initialize registers according to values from config_data */
> -	if (chip->enable_por_init && chip->config_data) {
> +	if (chip->enable_por_init && chip->config_data)
>  		ret = max17042_init_chip(chip);
> -		if (ret)
> -			return;
> +
> +	if (ret) {
> +		if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
> +			dev_warn_ratelimited(chip->dev,
> +				"initialization failed: %d, retrying\n", ret);
> +			queue_delayed_work(system_freezable_wq, &chip->work,
> +				msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS));
> +		} else {
> +			dev_err(chip->dev, "initialization failed: %d\n", ret);
> +		}
> +		return;
>  	}
>  
> -	chip->init_complete = 1;
> +	WRITE_ONCE(chip->init_complete, true);
> +	power_supply_changed(chip->battery);
>  }
>  
>  #ifdef CONFIG_OF
> @@ -1060,6 +1241,93 @@ static int max17042_init_defaults(struct max17042_chip *chip)
>  	return 0;
>  }
>  
> +static int max17042_apply_battery_properties(struct max17042_chip *chip,
> +					     struct power_supply_battery_info *info)
> +{
> +	struct max17042_config_data *config;
> +	struct device *dev = chip->dev;
> +	bool have_design_cap;
> +	bool have_ichgt_term;
> +	u16 design_cap = 0;
> +	u16 ichgt_term = 0;
> +	u16 dqacc = 0;
> +	u64 data64;
> +
> +	if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
> +		return 0;
> +
> +	have_design_cap = chip->enable_current_sense &&
> +		info->charge_full_design_uah >= 0;
> +	have_ichgt_term = chip->enable_current_sense &&
> +		info->charge_term_current_ua >= 0;
> +	if (!have_design_cap && !have_ichgt_term)
> +		return 0;
> +
> +	if (have_design_cap) {
> +		if (!info->charge_full_design_uah)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "battery design capacity must be positive\n");

If it's negative you set have_design_cap=false. This would only be
printed for charge_full_design_uah = 0. It seems more sensible to
also set have_design_cap to false for this case and drop this extra
check.

Greetings,

-- Sebastian

> +		data64 = (u64)info->charge_full_design_uah * chip->r_sns;
> +		do_div(data64, MAX17042_CAPACITY_LSB);
> +		if (!data64)
> +			return dev_err_probe(dev, -ERANGE,
> +					     "battery design capacity is too small for sense resistor\n");
> +		if (data64 > U16_MAX)
> +			return dev_err_probe(dev, -ERANGE,
> +					     "battery design capacity exceeds register range\n");
> +
> +		design_cap = (u16)data64;
> +		dqacc = design_cap / MAX17055_DQACC_DIV;
> +		if (!dqacc)
> +			return dev_err_probe(dev, -ERANGE,
> +					     "battery design capacity is too small for EZ config\n");
> +	}
> +
> +	if (have_ichgt_term) {
> +		if (!info->charge_term_current_ua)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "charge termination current must be positive\n");
> +
> +		data64 = (u64)info->charge_term_current_ua * chip->r_sns;
> +		do_div(data64, MAX17042_CURRENT_LSB);
> +		if (!data64)
> +			return dev_err_probe(dev, -ERANGE,
> +					     "charge termination current is too small for sense resistor\n");
> +		if (data64 > S16_MAX)
> +			return dev_err_probe(dev, -ERANGE,
> +					     "charge termination current exceeds positive register range\n");
> +
> +		ichgt_term = (u16)data64;
> +	}
> +
> +	config = chip->config_data;
> +	if (!config) {
> +		config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
> +		if (!config)
> +			return -ENOMEM;
> +	}
> +
> +	if (have_design_cap) {
> +		config->design_cap = design_cap;
> +		config->dqacc = dqacc;
> +	}
> +	if (have_ichgt_term)
> +		config->ichgt_term = ichgt_term;
> +
> +	chip->config_data = config;
> +	chip->enable_por_init = true;
> +
> +	return 0;
> +}
> +
> +static int max17042_init_battery(struct power_supply *psy)
> +{
> +	struct max17042_chip *chip = power_supply_get_drvdata(psy);
> +
> +	return max17042_apply_battery_properties(chip, psy->battery_info);
> +}
> +
>  static const struct regmap_config max17042_regmap_config = {
>  	.name = "max17042",
>  	.reg_bits = 8,
> @@ -1113,6 +1381,7 @@ static const struct power_supply_desc max17042_psy_desc = {
>  	.set_property	= max17042_set_property,
>  	.property_is_writeable	= max17042_property_is_writeable,
>  	.external_power_changed	= power_supply_changed,
> +	.init		= max17042_init_battery,
>  	.properties	= max17042_battery_props,
>  	.num_properties	= ARRAY_SIZE(max17042_battery_props),
>  };
> @@ -1123,6 +1392,7 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
>  	.get_property	= max17042_get_property,
>  	.set_property	= max17042_set_property,
>  	.property_is_writeable	= max17042_property_is_writeable,
> +	.init		= max17042_init_battery,
>  	.properties	= max17042_battery_props,
>  	.num_properties	= ARRAY_SIZE(max17042_battery_props) - 2,
>  };
> @@ -1242,15 +1512,18 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
>  
>  	chip->irq = irq;
>  
> -	regmap_read(chip->regmap, MAX17042_STATUS, &val);
> +	ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to read status\n");
> +
>  	if (val & STATUS_POR_BIT) {
> -		ret = devm_work_autocancel(dev, &chip->work,
> -					   max17042_init_worker);
> +		ret = devm_delayed_work_autocancel(dev, &chip->work,
> +						   max17042_init_worker);
>  		if (ret)
>  			return ret;
> -		schedule_work(&chip->work);
> +		queue_delayed_work(system_freezable_wq, &chip->work, 0);
>  	} else {
> -		chip->init_complete = 1;
> +		WRITE_ONCE(chip->init_complete, true);
>  	}
>  
>  	return 0;
> diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
> index 13aeab1597c6..810e068eafd2 100644
> --- a/include/linux/power/max17042_battery.h
> +++ b/include/linux/power/max17042_battery.h
> @@ -25,6 +25,7 @@
>  #define MAX17042_CHARACTERIZATION_DATA_SIZE 48
>  
>  #define MAX17055_MODELCFG_REFRESH_BIT	BIT(15)
> +#define MAX17055_MODELCFG_VCHG_BIT	BIT(10)
>  
>  enum max17042_register {
>  	MAX17042_STATUS		= 0x00,
> @@ -124,6 +125,7 @@ enum max17055_register {
>  
>  	MAX17055_ConvgCfg	= 0x49,
>  	MAX17055_VFRemCap	= 0x4A,
> +	MAX17055_SoftWakeup	= 0x60,
>  
>  	MAX17055_STATUS2	= 0xB0,
>  	MAX17055_POWER		= 0xB1,
> -- 
> 2.55.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2026-07-21 22:23 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-21 18:57 [PATCH v3 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
2026-07-21 18:57 ` [PATCH v3 1/4] power: supply: Add registration init callback Vincent Cloutier
2026-07-21 18:57 ` [PATCH v3 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
2026-07-21 22:23   ` Sebastian Reichel [this message]
2026-07-21 18:57 ` [PATCH v3 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
2026-07-21 18:57 ` [PATCH v3 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=al_qZdt-_DbSMFta@venus \
    --to=sre@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=hansg@kernel.org \
    --cc=kernel@puri.sm \
    --cc=krzk@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=m.szyprowski@samsung.com \
    --cc=robh@kernel.org \
    --cc=sebastian.krzyszkowiak@puri.sm \
    --cc=vincent.cloutier@icloud.com \
    --cc=vincent@cloutier.co \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox