* [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info
@ 2026-07-27 1:12 Vincent Cloutier
2026-07-27 1:12 ` [PATCH v4 01/11] power: supply: Add registration init callback Vincent Cloutier
` (10 more replies)
0 siblings, 11 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:12 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 splits the MAX17055 driver work into smaller steps.
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. The callback remains synchronous and cannot
start activity that accesses the power supply before registration
completes. 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. The delayed work continues to use
system_wq, matching the original work item.
Positive DesignCap and IChgTerm battery values are applied, while zero is
treated like an absent value. Supplied positive values that cannot be
represented by the hardware are rejected.
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 v3:
- split the previous MAX17055 driver patch into initialization extraction,
init_complete type/access, delayed-work conversion, STATUS error handling,
retry, EZ Config programming, POR transaction, and battery-info steps
(Sebastian)
- retain system_wq when converting initialization to delayed work
- explain why initialization completion sends a power-supply notification
- treat zero DesignCap and IChgTerm values like absent properties (Sebastian)
- make the registration init callback's synchronous lifetime contract explicit
- remove the redundant power-supplies binding declaration (Krzysztof)
Changes since v2:
- use unevaluatedProperties in the MAX17042 binding instead of declaring
monitored-battery explicitly (Krzysztof)
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 generic power-supply properties in the MAX17042 family binding
- drop the Librem 5 battery description from this series; defer
board-specific battery profile selection to separate work
The series was folded into a Librem 5r4 kernel. A temporary test DTB with
zero DesignCap and IChgTerm values booted with the MAX17055 registered and
no initialization errors, confirming that zero is treated as absent.
After restoring the positive battery profile and performing a full
battery-removal gauge POR, 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.
v3: https://lore.kernel.org/all/20260721185904.40756-1-vincent.cloutier@icloud.com/
v2: https://lore.kernel.org/all/20260720011144.1280219-1-vincent.cloutier@icloud.com/
v1: https://lore.kernel.org/all/20260406205759.493288-1-vincent.cloutier@icloud.com/
Vincent Cloutier (11):
power: supply: Add registration init callback
power: supply: max17042_battery: Separate MAX17055 initialization
power: supply: max17042_battery: Use bool for init_complete
power: supply: max17042_battery: Convert initialization to delayed
work
power: supply: max17042_battery: Propagate status register errors
power: supply: max17042_battery: Retry failed MAX17055 initialization
power: supply: max17042_battery: Program MAX17055 EZ Config values
power: supply: max17042_battery: Follow MAX17055 POR sequence
power: supply: max17042_battery: Initialize MAX17055 from battery info
power: supply: max17042_battery: Honor MAX17055 charge voltage
dt-bindings: power: supply: max17042: Allow generic power-supply
properties
.../bindings/power/supply/maxim,max17042.yaml | 4 +-
drivers/power/supply/max17042_battery.c | 365 ++++++++++++++++--
drivers/power/supply/power_supply_core.c | 8 +
include/linux/power/max17042_battery.h | 2 +
include/linux/power_supply.h | 9 +
5 files changed, 353 insertions(+), 35 deletions(-)
base-commit: f2ec6312bf711369561bdcb22f8a63c0b118c479
--
2.55.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4 01/11] power: supply: Add registration init callback
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
@ 2026-07-27 1:12 ` Vincent Cloutier
2026-07-27 1:28 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 02/11] power: supply: max17042_battery: Separate MAX17055 initialization Vincent Cloutier
` (9 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:12 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.
Keep the callback synchronous: it must not publish changes or start
asynchronous activity that can access the power supply before registration
completes.
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 | 9 +++++++++
2 files changed, 17 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..de5599458176 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -281,6 +281,15 @@ 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 or
+ * start asynchronous activity that can access the power supply before
+ * registration completes. 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] 15+ messages in thread
* [PATCH v4 02/11] power: supply: max17042_battery: Separate MAX17055 initialization
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
2026-07-27 1:12 ` [PATCH v4 01/11] power: supply: Add registration init callback Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 03/11] power: supply: max17042_battery: Use bool for init_complete Vincent Cloutier
` (8 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 uses ModelCfg.Refresh instead of the characterization-data
flow used by the other supported gauges. Move its existing initialization
steps into a dedicated helper without changing behavior. This isolates the
chip-specific path for subsequent EZ Config changes.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index d409d2f0d383..70c5d5ce945d 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -847,18 +847,24 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
+static void max17055_init_chip(struct max17042_chip *chip)
+{
+ max17042_override_por_values(chip);
+
+ regmap_write_bits(chip->regmap, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_REFRESH_BIT,
+ MAX17055_MODELCFG_REFRESH_BIT);
+}
+
static int max17042_init_chip(struct max17042_chip *chip)
{
struct regmap *map = chip->regmap;
int ret;
- max17042_override_por_values(chip);
-
- if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
- regmap_write_bits(map, MAX17055_ModelCfg,
- MAX17055_MODELCFG_REFRESH_BIT,
- MAX17055_MODELCFG_REFRESH_BIT);
- }
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ max17055_init_chip(chip);
+ else
+ max17042_override_por_values(chip);
/* After Power up, the MAX17042 requires 500mS in order
* to perform signal debouncing and initial SOC reporting
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 03/11] power: supply: max17042_battery: Use bool for init_complete
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
2026-07-27 1:12 ` [PATCH v4 01/11] power: supply: Add registration init callback Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 02/11] power: supply: max17042_battery: Separate MAX17055 initialization Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 04/11] power: supply: max17042_battery: Convert initialization to delayed work Vincent Cloutier
` (7 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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>
init_complete is a binary state. Change it from int to bool before adding
retry support.
The initialization worker and probe path update the flag while property
reads sample it. Use READ_ONCE() and WRITE_ONCE() to make those lockless
accesses explicit.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 70c5d5ce945d..e11d73681a67 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -69,11 +69,11 @@ struct max17042_chip {
enum max170xx_chip_type chip_type;
struct max17042_config_data *config_data;
struct work_struct work;
- int init_complete;
int irq;
int task_period;
bool enable_current_sense;
bool enable_por_init;
+ bool init_complete;
unsigned int r_sns;
int vmin; /* in millivolts */
int vmax; /* in millivolts */
@@ -256,7 +256,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) {
@@ -1006,7 +1006,7 @@ static void max17042_init_worker(struct work_struct *work)
return;
}
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
}
#ifdef CONFIG_OF
@@ -1256,7 +1256,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
return ret;
schedule_work(&chip->work);
} else {
- chip->init_complete = 1;
+ WRITE_ONCE(chip->init_complete, true);
}
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 04/11] power: supply: max17042_battery: Convert initialization to delayed work
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (2 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 03/11] power: supply: max17042_battery: Use bool for init_complete Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors Vincent Cloutier
` (6 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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>
Periodic MAX17055 retries need a delayed work item. Convert the existing
initialization work in advance without adding retries or changing when the
first attempt runs.
Use schedule_delayed_work() with a zero delay so the work continues to run
on system_wq, matching schedule_work() rather than introducing a separate
workqueue policy change.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index e11d73681a67..38abd3604643 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -68,7 +68,7 @@ struct max17042_chip {
struct power_supply *battery;
enum max170xx_chip_type chip_type;
struct max17042_config_data *config_data;
- struct work_struct work;
+ struct delayed_work work;
int irq;
int task_period;
bool enable_current_sense;
@@ -995,7 +995,7 @@ 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;
@@ -1250,11 +1250,11 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq
regmap_read(chip->regmap, MAX17042_STATUS, &val);
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);
+ schedule_delayed_work(&chip->work, 0);
} else {
WRITE_ONCE(chip->init_complete, true);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (3 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 04/11] power: supply: max17042_battery: Convert initialization to delayed work Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:31 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 06/11] power: supply: max17042_battery: Retry failed MAX17055 initialization Vincent Cloutier
` (5 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 driver ignores errors from the initial STATUS read and final POR clear.
A failed read can leave the POR decision based on an invalid value, while a
failed clear can report initialization complete with POR still set.
Check the probe-time read and return the POR-clear result from
max17042_init_chip(). Keep this error handling separate from the retry
policy added later.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 38abd3604643..9210bdb4d67e 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -908,8 +908,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)
@@ -1248,7 +1247,10 @@ 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_delayed_work_autocancel(dev, &chip->work,
max17042_init_worker);
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 06/11] power: supply: max17042_battery: Retry failed MAX17055 initialization
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (4 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values Vincent Cloutier
` (4 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 initialization can fail on register I/O. Propagate the ModelCfg
Refresh command error and retry MAX17055 failures every 10 seconds so a
transient startup error does not become permanent. Report failures for the
other gauges without changing their one-shot behavior.
Notify consumers whenever initialization succeeds, including on the first
attempt. The core registration notification is deferred and can observe
-EAGAIN while asynchronous gauge initialization is still running, so a new
notification is needed when driver-backed properties become available.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 37 +++++++++++++++++--------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 9210bdb4d67e..27d881d4324f 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -62,6 +62,8 @@
#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */
#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */
+#define MAX17055_INIT_RETRY_DELAY_MS 10000
+
struct max17042_chip {
struct device *dev;
struct regmap *regmap;
@@ -847,13 +849,13 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
-static void max17055_init_chip(struct max17042_chip *chip)
+static int max17055_init_chip(struct max17042_chip *chip)
{
max17042_override_por_values(chip);
- regmap_write_bits(chip->regmap, MAX17055_ModelCfg,
- MAX17055_MODELCFG_REFRESH_BIT,
- MAX17055_MODELCFG_REFRESH_BIT);
+ return regmap_write_bits(chip->regmap, MAX17055_ModelCfg,
+ MAX17055_MODELCFG_REFRESH_BIT,
+ MAX17055_MODELCFG_REFRESH_BIT);
}
static int max17042_init_chip(struct max17042_chip *chip)
@@ -861,10 +863,13 @@ static int max17042_init_chip(struct max17042_chip *chip)
struct regmap *map = chip->regmap;
int ret;
- if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
- max17055_init_chip(chip);
- else
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
+ 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
@@ -996,16 +1001,26 @@ static void max17042_init_worker(struct work_struct *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);
+ schedule_delayed_work(&chip->work,
+ msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS));
+ } else {
+ dev_err(chip->dev, "initialization failed: %d\n", ret);
+ }
+ return;
}
WRITE_ONCE(chip->init_complete, true);
+ power_supply_changed(chip->battery);
}
#ifdef CONFIG_OF
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (5 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 06/11] power: supply: max17042_battery: Retry failed MAX17055 initialization Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:28 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 08/11] power: supply: max17042_battery: Follow MAX17055 POR sequence Vincent Cloutier
` (3 subsequent siblings)
10 siblings, 1 reply; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 EZ Config requires DesignCap, dQAcc, IChgTerm, and dPAcc to
be programmed coherently. Write each supplied stable register once, wait
1 ms, and verify its read-back instead of relying on the unchecked generic
POR override path.
Derive dPAcc from the effective DesignCap, dQAcc, and ModelCfg values so
the accumulator matches the selected charge-voltage range. Propagate all
read, write, verification, and range errors to the retrying init worker.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 99 ++++++++++++++++++++++++-
include/linux/power/max17042_battery.h | 1 +
2 files changed, 96 insertions(+), 4 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 27d881d4324f..600b2689f316 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -62,6 +62,8 @@
#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */
#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */
+#define MAX17055_DPACC_FACTOR 44138
+#define MAX17055_DPACC_VCHG_FACTOR 51200
#define MAX17055_INIT_RETRY_DELAY_MS 10000
struct max17042_chip {
@@ -568,6 +570,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)
{
@@ -803,8 +823,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);
@@ -814,8 +838,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);
@@ -849,10 +875,75 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
}
+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_init_chip(struct max17042_chip *chip)
{
+ int ret;
+
max17042_override_por_values(chip);
+ ret = max17055_override_battery_values(chip);
+ if (ret)
+ return ret;
+
return regmap_write_bits(chip->regmap, MAX17055_ModelCfg,
MAX17055_MODELCFG_REFRESH_BIT,
MAX17055_MODELCFG_REFRESH_BIT);
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index 13aeab1597c6..61af6fe2f4fe 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,
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 08/11] power: supply: max17042_battery: Follow MAX17055 POR sequence
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (6 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 09/11] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
` (2 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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>
Wait until the MAX17055 is ready, leave hibernate around EZ Config,
and wait for ModelCfg.Refresh to clear as required by the power-on reset
initialization flow.
Restore HibCfg on every exit path. Keep failed restoration state so a
retry restores hibernate before starting a new initialization transaction.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 91 ++++++++++++++++++++++---
include/linux/power/max17042_battery.h | 1 +
2 files changed, 81 insertions(+), 11 deletions(-)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 600b2689f316..33a2cc378b46 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -64,7 +64,12 @@
#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;
@@ -78,6 +83,8 @@ struct max17042_chip {
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 */
@@ -934,19 +941,86 @@ static int max17055_override_battery_values(struct max17042_chip *chip)
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)
- return ret;
+ goto restore_hibernate;
- return regmap_write_bits(chip->regmap, MAX17055_ModelCfg,
- MAX17055_MODELCFG_REFRESH_BIT,
- MAX17055_MODELCFG_REFRESH_BIT);
+ 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)
@@ -960,15 +1034,10 @@ static int max17042_init_chip(struct max17042_chip *chip)
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 */
diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h
index 61af6fe2f4fe..810e068eafd2 100644
--- a/include/linux/power/max17042_battery.h
+++ b/include/linux/power/max17042_battery.h
@@ -125,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] 15+ messages in thread
* [PATCH v4 09/11] power: supply: max17042_battery: Initialize MAX17055 from battery info
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (7 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 08/11] power: supply: max17042_battery: Follow MAX17055 POR sequence Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 10/11] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 11/11] dt-bindings: power: supply: max17042: Allow generic power-supply properties Vincent Cloutier
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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>
Use charge-full-design-microamp-hours and
charge-term-current-microamp from monitored-battery to prepare MAX17055
DesignCap, IChgTerm, and EZ Config dQAcc values in the power-supply
registration callback.
Apply positive battery values and treat zero like an absent property.
Validate all supplied positive values before changing the sparse
configuration. Reject values that quantize to zero, capacities that cannot
produce dQAcc, and termination currents outside the signed register range.
Limit this path to MAX17055 because the other supported gauges require
complete characterization data.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/power/supply/max17042_battery.c | 82 +++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 33a2cc378b46..db3b1bc5388f 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -62,6 +62,7 @@
#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)
@@ -1240,6 +1241,85 @@ 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) {
+ 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) {
+ 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,
@@ -1293,6 +1373,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),
};
@@ -1303,6 +1384,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,
};
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v4 10/11] power: supply: max17042_battery: Honor MAX17055 charge voltage
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (8 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 09/11] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 11/11] dt-bindings: power: supply: max17042: Allow generic power-supply properties Vincent Cloutier
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 db3b1bc5388f..6278e7c89bdd 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) {
@@ -1293,6 +1315,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);
@@ -1306,6 +1337,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] 15+ messages in thread
* [PATCH v4 11/11] dt-bindings: power: supply: max17042: Allow generic power-supply properties
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
` (9 preceding siblings ...)
2026-07-27 1:13 ` [PATCH v4 10/11] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
@ 2026-07-27 1:13 ` Vincent Cloutier
10 siblings, 0 replies; 15+ messages in thread
From: Vincent Cloutier @ 2026-07-27 1:13 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 additionalProperties only recognizes properties declared directly by
this schema.
Use unevaluatedProperties so generic properties such as monitored-battery
are accepted while unknown properties remain rejected. Remove the local
power-supplies declaration because it is already inherited from
power-supply.yaml.
Assisted-by: OpenCode:gpt-5.6-sol
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
.../devicetree/bindings/power/supply/maxim,max17042.yaml | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
index 242b33f2bcba..b2e5ddc5c580 100644
--- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
+++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml
@@ -67,13 +67,11 @@ properties:
Voltage threshold to report battery as over voltage (in mV).
Default is not to report over-voltage events.
- power-supplies: true
-
required:
- compatible
- reg
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values
2026-07-27 1:13 ` [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values Vincent Cloutier
@ 2026-07-27 1:28 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-27 1:28 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:
- [Medium] Returning -ERANGE for deterministic configuration errors causes the init worker to retry infinitely, wasting system resources.
- [Medium] Missing dPAcc re-derivation when only ModelCfg is configured, leading to incoherent accumulator state.
--
--- Patch [7]: [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values ---
commit bd3ee7ab717292e5ba55e9546c583b92b58089e3
Author: Vincent Cloutier <vincent@cloutier.co>
power: supply: max17042_battery: Program MAX17055 EZ Config values
MAX17055 EZ Config requires DesignCap, dQAcc, IChgTerm, and dPAcc to
be programmed coherently. Write each supplied stable register once, wait
1 ms, and verify its read-back instead of relying on the unchecked generic
POR override path.
> diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
> index 27d881d4324f0..600b2689f3160 100644
> --- a/drivers/power/supply/max17042_battery.c
> +++ b/drivers/power/supply/max17042_battery.c
[ ... ]
> @@ -849,10 +875,75 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
> max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg);
> }
>
> +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 && !config->dqacc)
> + return 0;
[Severity: Medium]
If config->model_cfg is provided but config->design_cap and config->dqacc are 0,
does this early return bypass dPAcc derivation?
Since max17042_override_por_values() updates MAX17055_ModelCfg, if the new
model_cfg alters the VCHG bit, the accumulator's factor changes and strictly
requires dPAcc to be re-derived. By exiting early, the driver leaves dPAcc at
its POR default, which may not match the newly selected charge-voltage range.
> +
> + 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;
[Severity: Medium]
Will returning -ERANGE here cause max17042_init_worker() to retry infinitely?
If the POR default registers evaluate to 0 or if the dpacc calculation exceeds
U16_MAX, this returns a deterministic -ERANGE error. This propagates back to
max17042_init_worker(), which catches the error and continuously reschedules
itself every 10 seconds, constantly waking up the CPU and spamming the log:
drivers/power/supply/max17042_battery.c:max17042_init_worker() {
...
if (chip->enable_por_init && chip->config_data)
ret = max17042_init_chip(chip);
if (ret) {
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) {
dev_warn_ratelimited(chip->dev,
"initialization failed: %d, retrying\n", ret);
schedule_delayed_work(&chip->work,
msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS));
}
...
}
> +
> + 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);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727011319.621794-1-vincent.cloutier@icloud.com?part=7
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 01/11] power: supply: Add registration init callback
2026-07-27 1:12 ` [PATCH v4 01/11] power: supply: Add registration init callback Vincent Cloutier
@ 2026-07-27 1:28 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-27 1:28 UTC (permalink / raw)
To: Vincent Cloutier; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Use-After-Free of `psy->changed_work` in `__power_supply_register()` error path.
--
--- Patch [1]: [PATCH v4 01/11] power: supply: Add registration init callback ---
commit 91f996f8711a8d5770312f212add1d5d3cecfc83
Author: Vincent Cloutier <vincent@cloutier.co>
power: supply: Add registration init callback
This commit adds an optional registration init callback for battery drivers
to consume monitored-battery data before their power_supply is visible.
> diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
> index 2532e221b2e19..3ed4c253706f3 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);
[Severity: High]
This is a pre-existing issue, but does the error path handling after this
device_add() call lead to a use-after-free of psy->changed_work?
If device_add() succeeds, the device is published to the power_supply class.
Another thread can then find the published device via
power_supply_get_by_name(), take a reference, and call power_supply_changed(),
which queues psy->changed_work on the system workqueue.
If a subsequent initialization step in __power_supply_register() fails
(such as psy_register_thermal), the code jumps to the error path:
register_thermal_failed:
wakeup_init_failed:
device_del(dev);
device_add_failed:
check_supplies_failed:
dev_set_name_failed:
put_device(dev);
return ERR_PTR(rc);
This drops the local reference to the power supply structure via put_device()
but omits canceling any potentially queued work with
cancel_work_sync(&psy->changed_work).
Once the other thread drops its reference, psy is freed by
power_supply_dev_release(). The system workqueue will then dereference the
freed memory when it executes power_supply_changed_work().
> if (rc)
> goto device_add_failed;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727011319.621794-1-vincent.cloutier@icloud.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors
2026-07-27 1:13 ` [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors Vincent Cloutier
@ 2026-07-27 1:31 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-27 1:31 UTC (permalink / raw)
To: Vincent Cloutier; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Probe failure leaves device interrupts actively enabled on the hardware, potentially causing an interrupt storm.
- [High] Multiple hardware configuration functions ignore the return value of regmap_read, using uninitialized stack variables to write arbitrary values to hardware registers.
- [High] The threaded IRQ handler returns IRQ_HANDLED upon failing to read the device status, defeating the kernel's spurious interrupt detector and risking a hard interrupt storm.
--
--- Patch [5]: [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors ---
commit bced78410601fcdc0467a660acdbfd2bb2e4527e
Author: Vincent Cloutier <vincent@cloutier.co>
power: supply: max17042_battery: Propagate status register errors
The driver adds error checking for the initial STATUS read and final POR
clear during initialization. A failed read will now propagate the error
instead of ignoring it, and the POR-clear result is returned directly.
> diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
> index 38abd3604643b..9210bdb4d67eb 100644
> --- a/drivers/power/supply/max17042_battery.c
> +++ b/drivers/power/supply/max17042_battery.c
> @@ -908,8 +908,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)
[Severity: High]
This is a pre-existing issue, but functions like max17042_set_soc_threshold
and max17042_load_new_capacity_params ignore the return value of regmap_read.
If the I2C read fails, can this lead to uninitialized stack variables
(soc, vfSoc, full_cap0) being bit-shifted and written directly back
to critical hardware registers?
For example:
drivers/power/supply/max17042_battery.c:max17042_set_soc_threshold() {
...
regmap_read(map, MAX17042_RepSOC, &soc);
soc_tr = (soc + off) << 8;
...
}
[ ... ]
> @@ -1248,7 +1247,10 @@ 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");
> +
[Severity: High]
This isn't a bug introduced by this patch, but returning here exposes a missing
cleanup step.
If this new read fails, we return an error and the devm framework frees
the IRQ handler. However, earlier in max17042_probe, we enable hardware alerts
with regmap_update_bits(chip->regmap, MAX17042_CONFIG, CFG_ALRT_BIT_ENBL, ...).
Since there is no devm-managed cleanup or manual unwind for this alert
enablement, and the IRQ is requested with IRQF_SHARED, could this leave the
device actively asserting the interrupt line without a registered handler,
resulting in an interrupt storm?
> if (val & STATUS_POR_BIT) {
> ret = devm_delayed_work_autocancel(dev, &chip->work,
> max17042_init_worker);
[Severity: High]
This is a pre-existing issue, but I noticed a potential problem in
max17042_thread_handler():
drivers/power/supply/max17042_battery.c:max17042_thread_handler() {
...
ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
if (ret)
return IRQ_HANDLED;
...
}
If regmap_read fails (e.g., due to a transient I2C bus error), the handler
returns IRQ_HANDLED without clearing the interrupt bits on the device.
Since the handler reports success (IRQ_HANDLED) while the physical device
is still asserting the shared interrupt line, does this bypass the kernel's
spurious interrupt detector and risk a hard interrupt storm?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727011319.621794-1-vincent.cloutier@icloud.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-27 1:31 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 1:12 [PATCH v4 00/11] power: supply: initialize MAX17055 from battery info Vincent Cloutier
2026-07-27 1:12 ` [PATCH v4 01/11] power: supply: Add registration init callback Vincent Cloutier
2026-07-27 1:28 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 02/11] power: supply: max17042_battery: Separate MAX17055 initialization Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 03/11] power: supply: max17042_battery: Use bool for init_complete Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 04/11] power: supply: max17042_battery: Convert initialization to delayed work Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 05/11] power: supply: max17042_battery: Propagate status register errors Vincent Cloutier
2026-07-27 1:31 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 06/11] power: supply: max17042_battery: Retry failed MAX17055 initialization Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 07/11] power: supply: max17042_battery: Program MAX17055 EZ Config values Vincent Cloutier
2026-07-27 1:28 ` sashiko-bot
2026-07-27 1:13 ` [PATCH v4 08/11] power: supply: max17042_battery: Follow MAX17055 POR sequence Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 09/11] power: supply: max17042_battery: Initialize MAX17055 from battery info Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 10/11] power: supply: max17042_battery: Honor MAX17055 charge voltage Vincent Cloutier
2026-07-27 1:13 ` [PATCH v4 11/11] dt-bindings: power: supply: max17042: Allow generic power-supply properties Vincent Cloutier
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox