[PATCH 2/5] power: supply: core: Allow getting battery info before psy is registered

From: Alexey Charkov

Date: Mon Sep 07 2026 - 11:14:35 EST


Some power supplies, such as battery chargers, may need to program the
device parameters based on what their connected battery allows. Current
API requires registering the power supply to access battery information,
which is problematic because a registered power supply is immediately
available to the rest of the system, but the battery parameters may not
be set yet in the charger.

Given that the battery info helpers really only need a fwnode and a struct
device to hang devres-allocated resourses on, add a pure dev-based get/put
API alongside the existing psy-based one, which can be used to query the
battery information before registering the power supply.

Signed-off-by: Alexey Charkov <alchark@xxxxxxxxxxx>
---
drivers/power/supply/power_supply_core.c | 102 ++++++++++++++++++++++---------
include/linux/power_supply.h | 4 ++
2 files changed, 77 insertions(+), 29 deletions(-)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 83769fc33575..855c89f712e8 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -725,21 +725,18 @@ struct power_supply *devm_power_supply_get_by_reference(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_reference);

-int power_supply_get_battery_info(struct power_supply *psy,
- struct power_supply_battery_info **info_out)
+static int __power_supply_get_battery_info(struct device *dev,
+ struct fwnode_handle *srcnode,
+ struct power_supply_battery_info **info_out)
{
struct power_supply_resistance_temp_table *resist_table;
struct power_supply_battery_info *info;
- struct fwnode_handle *srcnode, *fwnode;
+ struct fwnode_handle *fwnode;
const char *value;
int err, len, index, proplen;
u32 *propdata __free(kfree) = NULL;
u32 min_max[2];

- srcnode = dev_fwnode(&psy->dev);
- if (!srcnode && psy->dev.parent)
- srcnode = dev_fwnode(psy->dev.parent);
-
fwnode = fwnode_find_reference(srcnode, "monitored-battery", 0);
if (IS_ERR(fwnode))
return PTR_ERR(fwnode);
@@ -750,7 +747,7 @@ int power_supply_get_battery_info(struct power_supply *psy,


/* Try static batteries first */
- err = samsung_sdi_battery_get_info(&psy->dev, value, &info);
+ err = samsung_sdi_battery_get_info(dev, value, &info);
if (!err)
goto out_ret_pointer;
else if (err == -ENODEV)
@@ -765,7 +762,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
goto out_put_node;
}

- info = devm_kzalloc(&psy->dev, sizeof(*info), GFP_KERNEL);
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info) {
err = -ENOMEM;
goto out_put_node;
@@ -826,7 +823,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
else if (!strcmp("lithium-ion-manganese-oxide", value))
info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn;
else
- dev_warn(&psy->dev, "%s unknown battery type\n", value);
+ dev_warn(dev, "%s unknown battery type\n", value);
}

fwnode_property_read_u32(fwnode, "energy-full-design-microwatt-hours",
@@ -877,7 +874,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = len;
goto out_put_node;
} else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
- dev_err(&psy->dev, "Too many temperature values\n");
+ dev_err(dev, "Too many temperature values\n");
err = -EINVAL;
goto out_put_node;
} else if (len > 0) {
@@ -892,28 +889,28 @@ int power_supply_get_battery_info(struct power_supply *psy,
char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d",
index);
if (!propname) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -ENOMEM;
goto out_put_node;
}
proplen = fwnode_property_count_u32(fwnode, propname);
if (proplen < 0 || proplen % 2 != 0) {
- dev_err(&psy->dev, "failed to get %s\n", propname);
- power_supply_put_battery_info(psy, info);
+ dev_err(dev, "failed to get %s\n", propname);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -EINVAL;
goto out_put_node;
}

u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
if (!propdata) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -EINVAL;
goto out_put_node;
}
err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
if (err < 0) {
- dev_err(&psy->dev, "failed to get %s\n", propname);
- power_supply_put_battery_info(psy, info);
+ dev_err(dev, "failed to get %s\n", propname);
+ power_supply_put_battery_info_from_dev(dev, info);
goto out_put_node;
}

@@ -921,9 +918,9 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->ocv_table_size[index] = tab_len;

info->ocv_table[index] = table =
- devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
+ devm_kcalloc(dev, tab_len, sizeof(*table), GFP_KERNEL);
if (!info->ocv_table[index]) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -ENOMEM;
goto out_put_node;
}
@@ -939,14 +936,14 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = 0;
goto out_ret_pointer;
} else if (proplen < 0 || proplen % 2 != 0) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = (proplen < 0) ? proplen : -EINVAL;
goto out_put_node;
}

propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
if (!propdata) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -ENOMEM;
goto out_put_node;
}
@@ -954,17 +951,17 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
propdata, proplen);
if (err < 0) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
goto out_put_node;
}

info->resist_table_size = proplen / 2;
- info->resist_table = resist_table = devm_kcalloc(&psy->dev,
+ info->resist_table = resist_table = devm_kcalloc(dev,
info->resist_table_size,
sizeof(*resist_table),
GFP_KERNEL);
if (!info->resist_table) {
- power_supply_put_battery_info(psy, info);
+ power_supply_put_battery_info_from_dev(dev, info);
err = -ENOMEM;
goto out_put_node;
}
@@ -982,22 +979,69 @@ int power_supply_get_battery_info(struct power_supply *psy,
fwnode_handle_put(fwnode);
return err;
}
+
+int power_supply_get_battery_info(struct power_supply *psy,
+ struct power_supply_battery_info **info_out)
+{
+ struct fwnode_handle *srcnode;
+
+ srcnode = dev_fwnode(&psy->dev);
+ if (!srcnode && psy->dev.parent)
+ srcnode = dev_fwnode(psy->dev.parent);
+
+ return __power_supply_get_battery_info(&psy->dev, srcnode, info_out);
+}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);

-void power_supply_put_battery_info(struct power_supply *psy,
- struct power_supply_battery_info *info)
+/**
+ * power_supply_get_battery_info_from_dev() - Get battery info without a supply
+ * @dev: Device holding the "monitored-battery" reference, which also owns the
+ * devres allocations made for the returned info
+ * @info_out: Pointer to store the resulting battery info
+ *
+ * Same as power_supply_get_battery_info(), but keyed off a plain device rather
+ * than a registered power supply. Chargers that program hardware limits taken
+ * from the battery node need those values *before* they can safely register
+ * their power supply: registering makes the supply callable, so a later probe
+ * failure would free driver data underneath a running callback.
+ *
+ * Release the result with power_supply_put_battery_info_from_dev().
+ *
+ * Return: 0 on success or an error code on failure.
+ */
+int power_supply_get_battery_info_from_dev(struct device *dev,
+ struct power_supply_battery_info **info_out)
+{
+ return __power_supply_get_battery_info(dev, dev_fwnode(dev), info_out);
+}
+EXPORT_SYMBOL_GPL(power_supply_get_battery_info_from_dev);
+
+/**
+ * power_supply_put_battery_info_from_dev() - Release battery info
+ * @dev: Device passed to power_supply_get_battery_info_from_dev()
+ * @info: Battery info to release
+ */
+void power_supply_put_battery_info_from_dev(struct device *dev,
+ struct power_supply_battery_info *info)
{
int i;

for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
if (info->ocv_table[i])
- devm_kfree(&psy->dev, info->ocv_table[i]);
+ devm_kfree(dev, info->ocv_table[i]);
}

if (info->resist_table)
- devm_kfree(&psy->dev, info->resist_table);
+ devm_kfree(dev, info->resist_table);
+
+ devm_kfree(dev, info);
+}
+EXPORT_SYMBOL_GPL(power_supply_put_battery_info_from_dev);

- devm_kfree(&psy->dev, info);
+void power_supply_put_battery_info(struct power_supply *psy,
+ struct power_supply_battery_info *info)
+{
+ power_supply_put_battery_info_from_dev(&psy->dev, info);
}
EXPORT_SYMBOL_GPL(power_supply_put_battery_info);

diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 6a8c4b140a9c..8a999a18c843 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -864,6 +864,10 @@ extern int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info **info_out);
extern void power_supply_put_battery_info(struct power_supply *psy,
struct power_supply_battery_info *info);
+extern int power_supply_get_battery_info_from_dev(struct device *dev,
+ struct power_supply_battery_info **info_out);
+extern void power_supply_put_battery_info_from_dev(struct device *dev,
+ struct power_supply_battery_info *info);
extern bool power_supply_battery_info_has_prop(struct power_supply_battery_info *info,
enum power_supply_property psp);
extern int power_supply_battery_info_get_prop(struct power_supply_battery_info *info,

--
2.54.0