Devicetree
 help / color / mirror / Atom feed
* [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info
@ 2026-07-20  1:11 Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 1/4] power: supply: Add registration init callback Vincent Cloutier
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Vincent Cloutier @ 2026-07-20  1:11 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

From: Vincent Cloutier <vincent@cloutier.co>

Hi,

This is the remaining unmerged tail of the MAX17042/MAX17055 v1 series.
Six v1 patches are upstream, and the v1 cleanup patch is covered by
699f0f71ac98. This revision retains the requested registration callback
architecture and strengthens the MAX17055 POR sequence.

The power-supply callback runs after the core parses monitored-battery but
before device_add(). This lets the driver validate and prepare DesignCap,
IChgTerm, dQAcc, and VChg without another firmware parser or a
power_supply_config handoff. dPAcc is derived later from register
read-back.

The MAX17055 path waits for FSTAT.DNR, saves HibCfg, exits hibernate,
writes each stable battery register once, and verifies it after 1 ms.
SoftWakeup is written directly, and Refresh is requested once before
polling. The driver derives dPAcc from read-back DesignCap, dQAcc, and
ModelCfg values, restores HibCfg, and clears POR only after the complete
sequence succeeds.

Driver-backed properties remain unavailable while initialization is
incomplete. An interrupted transaction restores hibernate and retries
every 10 seconds without depending on POR still being set, so a transient
startup error does not become permanent.

VChg remains a masked ModelCfg update. A voltage-only override reads
effective DesignCap and dQAcc and refreshes dPAcc for the selected voltage
range. Zero is rejected. Exactly 4.275 V selects the low range; only values
above 4.275 V set VChg. An absent property preserves firmware's setting.

---

Changes since v1:

- send only the unmerged battery-info and VChg work
- split the power-supply core and MAX17055 driver changes
- use the requested registration init callback
- wait for FSTAT.DNR before programming POR values
- save, exit, and restore hibernate around MAX17055 EZ Config
- write stable registers once, wait 1 ms, and verify their read-back
- issue each command once and poll ModelCfg.Refresh
- derive dPAcc from register read-back values
- retry interrupted initialization periodically without a POR recheck
- keep properties unavailable until initialization completes
- recalculate dPAcc for a VChg-only override and reject zero voltage
- allow monitored-battery in the MAX17042 family binding
- drop the Librem 5 battery description because its user-replaceable pack
  cannot be identified reliably from the board revision

The series was also folded into a Librem 5r4 kernel and tested after a full
battery-removal gauge POR using a local battery description. Status.POR and
FSTAT.DNR cleared, HibCfg was restored, and ModelCfg.Refresh cleared. The
driver programmed dQAcc=0x002b and dPAcc=0x054b; the live dQAcc had moved to
0x002a after initialization. The other raw values and sysfs properties
matched the expected 4.2 V EZ Config result, with no MAX17055 initialization
errors.

v1: https://lore.kernel.org/all/20260406205759.493288-1-vincent.cloutier@icloud.com/

Vincent Cloutier (4):
  power: supply: Add registration init callback
  power: supply: max17042_battery: Initialize MAX17055 from battery info
  power: supply: max17042_battery: Honor MAX17055 charge voltage
  dt-bindings: power: supply: max17042: Allow monitored-battery

 .../bindings/power/supply/maxim,max17042.yaml |   2 +
 drivers/power/supply/max17042_battery.c       | 373 ++++++++++++++++--
 drivers/power/supply/power_supply_core.c      |   8 +
 include/linux/power/max17042_battery.h        |   2 +
 include/linux/power_supply.h                  |   8 +
 5 files changed, 361 insertions(+), 32 deletions(-)


base-commit: f2ec6312bf711369561bdcb22f8a63c0b118c479
-- 
2.55.0

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/4] power: supply: Add registration init callback
  2026-07-20  1:11 [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
@ 2026-07-20  1:11 ` Vincent Cloutier
  2026-07-20  1:26   ` sashiko-bot
  2026-07-20  1:11 ` [PATCH v2 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Vincent Cloutier @ 2026-07-20  1:11 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

From: Vincent Cloutier <vincent@cloutier.co>

Some battery drivers need to consume monitored-battery data before their
power_supply is visible. That lets them prepare hardware configuration from
parsed battery information without racing userspace exposure.

power_supply_get_battery_info() already runs in
__power_supply_register() for battery devices before device_add(). Add an
optional descriptor init callback after driver data and battery info are
available. The callback runs in sleepable process context while the power
supply is still unpublished.

Require callbacks to return zero or a negative errno. Defensively reject
positive returns so registration cannot return an invalid error pointer.

Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/power_supply_core.c | 8 ++++++++
 include/linux/power_supply.h             | 8 ++++++++
 2 files changed, 16 insertions(+)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 2532e221b2e1..3ed4c253706f 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -1624,6 +1624,14 @@ __power_supply_register(struct device *parent,
 	init_rwsem(&psy->extensions_sem);
 	INIT_LIST_HEAD(&psy->extensions);
 
+	if (desc->init) {
+		rc = desc->init(psy);
+		if (WARN_ON_ONCE(rc > 0))
+			rc = -EINVAL;
+		if (rc)
+			goto check_supplies_failed;
+	}
+
 	rc = device_add(dev);
 	if (rc)
 		goto device_add_failed;
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 7a5e4c3242a0..6042f5e1fdc2 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -281,6 +281,14 @@ struct power_supply_desc {
 	int (*property_is_writeable)(struct power_supply *psy,
 				     enum power_supply_property psp);
 	void (*external_power_changed)(struct power_supply *psy);
+	/*
+	 * Optional registration-time initialization. This runs in sleepable
+	 * process context after driver data and any battery info are available,
+	 * but before the device is added. The callback must not publish changes
+	 * because registration is not complete. Return 0 on success or a negative
+	 * errno on failure.
+	 */
+	int (*init)(struct power_supply *psy);
 
 	/*
 	 * Set if thermal zone should not be created for this power supply.
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info
  2026-07-20  1:11 [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 1/4] power: supply: Add registration init callback Vincent Cloutier
@ 2026-07-20  1:11 ` Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier
  3 siblings, 0 replies; 8+ messages in thread
From: Vincent Cloutier @ 2026-07-20  1:11 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

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>
---
 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);
 }
 
 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");
+
+		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


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage
  2026-07-20  1:11 [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 1/4] power: supply: Add registration init callback Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
@ 2026-07-20  1:11 ` Vincent Cloutier
  2026-07-20  1:11 ` [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier
  3 siblings, 0 replies; 8+ messages in thread
From: Vincent Cloutier @ 2026-07-20  1:11 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

From: Vincent Cloutier <vincent@cloutier.co>

MAX17055 ModelCfg.VChg selects the charge-voltage range used by EZ Config.
Consume voltage-max-design-microvolt from monitored-battery and set VChg
only when the value is strictly greater than 4.275 V. Reject an explicit
zero voltage and preserve the register when the property is absent.

Update only the VChg bit and verify its read-back so unrelated ModelCfg
fields remain intact. Treat a VChg-only override as an accumulator change:
read effective DesignCap and dQAcc, derive and verify the matching dPAcc,
then request model refresh. This keeps voltage-only firmware descriptions
coherent with the selected charge-voltage range.

Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 drivers/power/supply/max17042_battery.c | 42 +++++++++++++++++++++++--
 1 file changed, 39 insertions(+), 3 deletions(-)

diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index b25bf26135e0..0f6bf6d4c031 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -66,6 +66,7 @@
 #define MAX17055_DPACC_FACTOR		44138
 #define MAX17055_DPACC_VCHG_FACTOR	51200
 #define MAX17055_FSTAT_DNR_BIT		BIT(0)
+#define MAX17055_VCHG_THRESHOLD_UV	4275000
 #define MAX17055_DNR_POLL_US		10000
 #define MAX17055_DNR_TIMEOUT_US		2000000
 #define MAX17055_INIT_RETRY_DELAY_MS	10000
@@ -83,6 +84,7 @@ struct max17042_chip {
 	int    task_period;
 	bool   enable_current_sense;
 	bool   enable_por_init;
+	bool   enable_vchg_override;
 	bool   init_complete;
 	bool   hib_restore_pending;
 	u16    hib_cfg;
@@ -879,7 +881,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
 		max17042_override_por(map, MAX17047_V_empty, config->vempty);
 	}
 
-	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+	if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 &&
+	    !chip->enable_vchg_override)
 		max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
 }
 
@@ -914,7 +917,19 @@ static int max17055_override_battery_values(struct max17042_chip *chip)
 			return ret;
 	}
 
-	if (!config->design_cap && !config->dqacc)
+	if (chip->enable_vchg_override) {
+		ret = regmap_update_bits(map, MAX17055_ModelCfg,
+					 MAX17055_MODELCFG_VCHG_BIT,
+					 config->model_cfg &
+					 MAX17055_MODELCFG_VCHG_BIT);
+		if (ret)
+			return ret;
+
+		usleep_range(1000, 2000);
+	}
+
+	if (!config->design_cap && !config->dqacc &&
+	    !chip->enable_vchg_override)
 		return 0;
 
 	ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
@@ -928,6 +943,10 @@ static int max17055_override_battery_values(struct max17042_chip *chip)
 	ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg);
 	if (ret)
 		return ret;
+	if (chip->enable_vchg_override &&
+	    (model_cfg & MAX17055_MODELCFG_VCHG_BIT) !=
+	    (config->model_cfg & MAX17055_MODELCFG_VCHG_BIT))
+		return -EIO;
 
 	if (!design_cap || !dqacc)
 		return -ERANGE;
@@ -1248,9 +1267,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 	struct device *dev = chip->dev;
 	bool have_design_cap;
 	bool have_ichgt_term;
+	bool have_vchg;
 	u16 design_cap = 0;
 	u16 ichgt_term = 0;
 	u16 dqacc = 0;
+	u16 model_cfg = 0;
 	u64 data64;
 
 	if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055)
@@ -1260,7 +1281,8 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 		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)
+	have_vchg = info->voltage_max_design_uv >= 0;
+	if (!have_design_cap && !have_ichgt_term && !have_vchg)
 		return 0;
 
 	if (have_design_cap) {
@@ -1301,6 +1323,15 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 		ichgt_term = (u16)data64;
 	}
 
+	if (have_vchg) {
+		if (!info->voltage_max_design_uv)
+			return dev_err_probe(dev, -EINVAL,
+					     "battery design voltage must be positive\n");
+
+		if (info->voltage_max_design_uv > MAX17055_VCHG_THRESHOLD_UV)
+			model_cfg = MAX17055_MODELCFG_VCHG_BIT;
+	}
+
 	config = chip->config_data;
 	if (!config) {
 		config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL);
@@ -1314,6 +1345,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip,
 	}
 	if (have_ichgt_term)
 		config->ichgt_term = ichgt_term;
+	if (have_vchg) {
+		config->model_cfg &= ~MAX17055_MODELCFG_VCHG_BIT;
+		config->model_cfg |= model_cfg;
+		chip->enable_vchg_override = true;
+	}
 
 	chip->config_data = config;
 	chip->enable_por_init = true;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery
  2026-07-20  1:11 [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
                   ` (2 preceding siblings ...)
  2026-07-20  1:11 ` [PATCH v2 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
@ 2026-07-20  1:11 ` Vincent Cloutier
  2026-07-20  1:21   ` sashiko-bot
  2026-07-20  7:06   ` Krzysztof Kozlowski
  3 siblings, 2 replies; 8+ messages in thread
From: Vincent Cloutier @ 2026-07-20  1:11 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Hans de Goede, Krzysztof Kozlowski, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

From: Vincent Cloutier <vincent@cloutier.co>

The MAX17042 family binding references the generic power-supply schema,
but its additionalProperties restriction means generic properties must
also be admitted explicitly.

Allow monitored-battery so these fuel gauges can reference the physical
battery whose characteristics they consume.

Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
 .../devicetree/bindings/power/supply/maxim,max17042.yaml        | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
index 242b33f2bcba..ace684159e0e 100644
--- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
+++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
@@ -67,6 +67,8 @@ properties:
       Voltage threshold to report battery as over voltage (in mV).
       Default is not to report over-voltage events.
 
+  monitored-battery: true
+
   power-supplies: true
 
 required:
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery
  2026-07-20  1:11 ` [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier
@ 2026-07-20  1:21   ` sashiko-bot
  2026-07-20  7:06   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-20  1:21 UTC (permalink / raw)
  To: Vincent Cloutier; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Infinite retry loop in `max17042_init_worker` on permanent configuration error for MAX17055 devices.
--

--- Patch [4]: [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery ---
commit 442f6d786e796de83bd68c7855b401281db302f3
Author: Vincent Cloutier <vincent@cloutier.co>

dt-bindings: power: supply: max17042: Allow monitored-battery

The MAX17042 family binding references the generic power-supply schema,
but its additionalProperties restriction means generic properties must
also be admitted explicitly. Allow monitored-battery so these fuel gauges
can reference the physical battery whose characteristics they consume.

> diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
> index 242b33f2bcba5..ace684159e0e2 100644
> --- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
> +++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
> @@ -67,6 +67,8 @@ properties:
>        Voltage threshold to report battery as over voltage (in mV).
>        Default is not to report over-voltage events.
>  
> +  monitored-battery: true
> +

[Severity: High]
Does allowing monitored-battery expose an infinite retry loop in the driver
for MAX17055 devices?

If a MAX17055 device is configured with a voltage-max-design-microvolt >
4.275V but lacks charge-full-design-microamp-hours (or lacks a shunt
resistor), enable_vchg_override is set but config->design_cap remains 0.

During initialization, max17055_override_battery_values() attempts to read
the uninitialized (0) DesignCap and dQAcc registers directly from the
hardware. Since they evaluate to 0, it returns a permanent -ERANGE error:

drivers/power/supply/max17042_battery.c:max17055_override_battery_values() {
...
	ret = regmap_read(map, MAX17042_DesignCap, &design_cap);
...
	if (!design_cap || !dqacc)
		return -ERANGE;
...
}

However, max17042_init_worker() intercepts this error and blindly requeues
the initialization work for MAX17055 devices on any failure:

drivers/power/supply/max17042_battery.c:max17042_init_worker() {
...
	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));
...
}

Because the hardware registers continually read as 0, this creates an
infinite retry loop triggered by valid, non-malicious device tree
configurations, leading to missing battery status and log spam.

>    power-supplies: true
>  
>  required:

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720011144.1280219-1-vincent.cloutier@icloud.com?part=4

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 1/4] power: supply: Add registration init callback
  2026-07-20  1:11 ` [PATCH v2 1/4] power: supply: Add registration init callback Vincent Cloutier
@ 2026-07-20  1:26   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-20  1:26 UTC (permalink / raw)
  To: Vincent Cloutier; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Driver hardware resources tied to the parent device will outlive psy on registration failure, causing dangling pointers.

Pre-existing issues:
- [High] Missing cancel_work_sync(&psy->changed_work) on the __power_supply_register error path leads to a Use-After-Free if changed_work is queued.
--

--- Patch [1]: [PATCH v2 1/4] power: supply: Add registration init callback ---
commit 7157b5979312471cbd162dd14b7a0dbf059d5a5a
Author: Vincent Cloutier <vincent@cloutier.co>

power: supply: Add registration init callback
    
Some battery drivers need to consume monitored-battery data before their
power_supply is visible. That lets them prepare hardware configuration from
parsed battery information without racing userspace exposure.

> diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
> --- a/drivers/power/supply/power_supply_core.c
> +++ b/drivers/power/supply/power_supply_core.c
> @@ -1624,6 +1624,14 @@ __power_supply_register(struct device *parent,
>  	init_rwsem(&psy->extensions_sem);
>  	INIT_LIST_HEAD(&psy->extensions);
>  
> +	if (desc->init) {
> +		rc = desc->init(psy);

[Severity: High]
If a driver implements desc->init() to configure hardware interrupts or
timers that receive the psy pointer as their context, how are those resources
cleaned up if a subsequent step like device_add() fails?

Because there is no desc->exit() or similar teardown callback, the driver
has no hook to synchronously stop its asynchronous resources before the core
subsystem frees psy via put_device() on the error path. If an interrupt
fires before the driver's probe error handling runs, will it dereference the
freed psy pointer?

[Severity: High]
This isn't a bug introduced by this patch, but this change might make it
reachable. If a driver's desc->init() triggers an interrupt that legitimately
calls power_supply_changed(psy), it queues psy->changed_work.

If a subsequent registration step such as device_add() fails,
__power_supply_register() jumps to the error path and unconditionally frees
psy:

__power_supply_register() {
    ...
    device_add_failed:
    check_supplies_failed:
    dev_set_name_failed:
        put_device(dev);
        return ERR_PTR(rc);
}

The error path doesn't cancel psy->changed_work. When the workqueue executes
the pending work, will it access the freed psy memory? Could a
cancel_work_sync(&psy->changed_work) be added to the error path?

> +		if (WARN_ON_ONCE(rc > 0))
> +			rc = -EINVAL;
> +		if (rc)
> +			goto check_supplies_failed;
> +	}
> +
>  	rc = device_add(dev);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720011144.1280219-1-vincent.cloutier@icloud.com?part=1

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery
  2026-07-20  1:11 ` [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier
  2026-07-20  1:21   ` sashiko-bot
@ 2026-07-20  7:06   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 8+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-20  7:06 UTC (permalink / raw)
  To: Vincent Cloutier
  Cc: Sebastian Reichel, Hans de Goede, Marek Szyprowski,
	Sebastian Krzyszkowiak, Purism Kernel Team, Rob Herring,
	Conor Dooley, linux-pm, devicetree, linux-kernel,
	Vincent Cloutier

On Sun, Jul 19, 2026 at 09:11:40PM -0400, Vincent Cloutier wrote:
> From: Vincent Cloutier <vincent@cloutier.co>
> 
> The MAX17042 family binding references the generic power-supply schema,
> but its additionalProperties restriction means generic properties must
> also be admitted explicitly.

With the same explanation, just change additionalProps into
unevaluatedProperties.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-07-20  7:06 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20  1:11 [PATCH v2 0/4] power: supply: initialize MAX17055 from battery info Vincent Cloutier
2026-07-20  1:11 ` [PATCH v2 1/4] power: supply: Add registration init callback Vincent Cloutier
2026-07-20  1:26   ` sashiko-bot
2026-07-20  1:11 ` [PATCH v2 2/4] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
2026-07-20  1:11 ` [PATCH v2 3/4] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
2026-07-20  1:11 ` [PATCH v2 4/4] dt-bindings: power: supply: max17042: Allow monitored-battery Vincent Cloutier
2026-07-20  1:21   ` sashiko-bot
2026-07-20  7:06   ` Krzysztof Kozlowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox