From b3e6d01c33ebeb7734126acf4e80f7a1b67ad8d8 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Mon, 3 Aug 2026 14:07:20 -0700 Subject: [PATCH 01/77] Documentation: sysfs-class-power: Update Long_Life description While adding charge limiting support to the Lenovo WMI drivers, there was some back and forth about whether charge_types or charge_control_end_threshold was the appropriate attribute to expose a battery charge limiting toggle that is fixed in the BIOS. The confusion arose because the charge_control_end_threshold description closely matches the functional change the hardware is making, while the charge_types functionality better suits the actual an on/off toggle that occurs in the BIOS. This specific scenario is not explicitly enumerated in the documentation, though it is fairly common. Given that the original intention was to use it this way[1],[2], and that the samsung-laptop[3], ideapad-laptop[4], and lenovo-wmi-other[5] drivers all use the convention of charge_types with an exposed Long_Life and Standard value for this, codify it in the Documentation to avoid confusion in the future. [1] https://lore.kernel.org/linux-pm/49993a42-aa91-46bf-acef-4a089db4c2db@redhat.com/ [2] https://lore.kernel.org/platform-driver-x86/20241209204051.8786-1-hdegoede@redhat.com/ [3] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=de2884c6cdd3d133704ce37393590dd1c761500c [4] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=da8f2708f9b69707f4efeb432a18395e46b4666f [5] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9ca8fc065b88b327acbfdc33454efea391639716 Suggested-by: Hans de Goede Signed-off-by: Derek J. Clark Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260803210720.23844-1-derekjohn.clark@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 5641f1fd5fd6..98b389845d1e 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -365,9 +365,12 @@ Contact: linux-pm@vger.kernel.org Description: Represents a battery percentage level, above which charging will stop. Not all hardware is capable of setting this to an arbitrary - percentage. Drivers will round written values to the nearest - supported value. Reading back the value will show the actual - threshold set by the driver. + value, instead providing different minimum, maximum, or step + values. Drivers will round written values to the nearest supported + value. Reading back the value will show the actual threshold set + by the driver. For hardware that only supports a single fixed + value, use charge_types with a value of "Long Life" (vs "Standard") + instead' Access: Read, Write @@ -398,8 +401,9 @@ Description: when to start and stop charging. Advanced users can use this to drastically extend battery life. Long Life: - The charger reduces its charging rate in order to - prolong the battery health. + The charger firmware reduces its charging rate and/or + maximum charging percentage to a hardware specified + fixed limit in order to prolong the battery health. Bypass: The charger bypasses the charging path around the integrated converter allowing for a "smart" wall From 2f0dec12007891398e6e3041aca0b8656918973d Mon Sep 17 00:00:00 2001 From: Robin Snyders Date: Wed, 12 Aug 2026 01:29:58 +0200 Subject: [PATCH 02/77] power: supply: qcom_smbx: include base when petting watchdog BARK_BITE_WDOG_PET is an offset from the charger peripheral base, but the watchdog bark handler writes it as an absolute SPMI address. This can write another PMIC peripheral and leave the charger watchdog unserviced. Add the charger base, as all other qcom_smbx register accesses do. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Robin Snyders Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260812-qcom-smbx-fixes-v1-1-eb48246be599@snyders.xyz Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index 67fdb4335338..e6f2328f295c 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -764,7 +764,7 @@ static irqreturn_t smb_handle_wdog_bark(int irq, void *data) power_supply_changed(chip->chg_psy); - rc = regmap_write(chip->regmap, BARK_BITE_WDOG_PET, + rc = regmap_write(chip->regmap, chip->base + BARK_BITE_WDOG_PET, BARK_BITE_WDOG_PET_BIT); if (rc < 0) dev_err(chip->dev, "Couldn't pet the dog rc=%d\n", rc); From 68f645192f64e1037685dbdb0dffb41a5a012056 Mon Sep 17 00:00:00 2001 From: Robin Snyders Date: Wed, 12 Aug 2026 01:29:59 +0200 Subject: [PATCH 03/77] power: supply: qcom_smbx: test battery health status bits BATTERY_CHARGER_STATUS_2 contains independent status and fault bits. Switching on the complete register only recognizes a fault when it is the sole asserted bit, and can report a healthy battery when another status bit is set at the same time. Test the relevant fault bits in priority order, matching the Qualcomm downstream driver handling. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Robin Snyders Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260812-qcom-smbx-fixes-v1-2-eb48246be599@snyders.xyz Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index e6f2328f295c..0e50d81aefc2 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -632,26 +632,20 @@ static int smb_get_prop_health(struct smb_chip *chip, int *val) return rc; } - switch (stat) { - case CHARGER_ERROR_STATUS_BAT_OV_BIT: + if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - return 0; - case BAT_TEMP_STATUS_TOO_COLD_BIT: + else if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT) *val = POWER_SUPPLY_HEALTH_COLD; - return 0; - case BAT_TEMP_STATUS_TOO_HOT_BIT: + else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT) *val = POWER_SUPPLY_HEALTH_OVERHEAT; - return 0; - case BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT: + else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT) *val = POWER_SUPPLY_HEALTH_COOL; - return 0; - case BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT: + else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT) *val = POWER_SUPPLY_HEALTH_WARM; - return 0; - default: + else *val = POWER_SUPPLY_HEALTH_GOOD; - return 0; - } + + return 0; } static int smb_get_property(struct power_supply *psy, From 2a26d260e72a95e9f13b9e475f63aa404600fcb2 Mon Sep 17 00:00:00 2001 From: Robin Snyders Date: Wed, 12 Aug 2026 01:30:00 +0200 Subject: [PATCH 04/77] power: supply: qcom_smbx: fix float voltage selector FLOAT_VOLTAGE_CFG encodes 3,487,500 uV as selector zero and advances in 7,500 uV steps. The current calculation adds one to that selector, so it programs the requested float voltage one step too high. Remove the extra offset and name the hardware encoding constants. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Robin Snyders Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260812-qcom-smbx-fixes-v1-3-eb48246be599@snyders.xyz Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index 0e50d81aefc2..e236b95c0ebf 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -352,6 +352,8 @@ /* pmi8998 registers represent current in increments of 1/40th of an amp */ #define CURRENT_SCALE_FACTOR 25000 +#define SMB2_FLOAT_VOLTAGE_MIN_UV 3487500 +#define SMB2_FLOAT_VOLTAGE_STEP_UV 7500 /* clang-format on */ enum charger_status { @@ -993,7 +995,8 @@ static int smb_probe(struct platform_device *pdev) return dev_err_probe(chip->dev, rc, "Failed to init status change work\n"); - rc = (chip->batt_info->voltage_max_design_uv - 3487500) / 7500 + 1; + rc = (chip->batt_info->voltage_max_design_uv - + SMB2_FLOAT_VOLTAGE_MIN_UV) / SMB2_FLOAT_VOLTAGE_STEP_UV; rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG, FLOAT_VOLTAGE_SETTING_MASK, rc); if (rc < 0) From a2d7dc538da2cb4a7426f0742197e762cb635f38 Mon Sep 17 00:00:00 2001 From: Robin Snyders Date: Wed, 12 Aug 2026 01:30:01 +0200 Subject: [PATCH 05/77] power: supply: qcom_smbx: validate battery float voltage power_supply_get_battery_info() leaves absent optional properties at -EINVAL. The driver uses voltage_max_design_uv without checking it, so a missing or out-of-range value can become an invalid float-voltage selector. Prefer the constant-charge voltage because it describes the charger's CV target, then fall back to the battery design voltage. Both properties are optional in the simple-battery binding, so preserve the hardware setting when neither is present. Reject a supplied target outside the SMB2 range. smb_init_hw() enables charging before battery data is parsed and before the remaining fallible probe steps. Save the initial charging-enable bit and install a managed rollback before initialization. Keep the rollback active across battery-info, voltage, IRQ and wake-IRQ setup, then remove it only after probe can no longer fail. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Robin Snyders Link: https://patch.msgid.link/20260812-qcom-smbx-fixes-v1-4-eb48246be599@snyders.xyz Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 61 ++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index e236b95c0ebf..55ffe9857e49 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -353,6 +353,7 @@ /* pmi8998 registers represent current in increments of 1/40th of an amp */ #define CURRENT_SCALE_FACTOR 25000 #define SMB2_FLOAT_VOLTAGE_MIN_UV 3487500 +#define SMB2_FLOAT_VOLTAGE_MAX_UV 4920000 #define SMB2_FLOAT_VOLTAGE_STEP_UV 7500 /* clang-format on */ @@ -380,6 +381,7 @@ struct smb_init_register { * @base: Base address for smb registers * @regmap: Register map * @batt_info: Battery data from DT + * @initial_charge_enable: Charging enable state before hardware setup * @status_change_work: Worker to handle plug/unplug events * @cable_irq: USB plugin IRQ * @wakeup_enabled: If the cable IRQ will cause a wakeup @@ -393,6 +395,7 @@ struct smb_chip { unsigned int base; struct regmap *regmap; struct power_supply_battery_info *batt_info; + u8 initial_charge_enable; struct delayed_work status_change_work; int cable_irq; @@ -905,6 +908,19 @@ static int smb_init_hw(struct smb_chip *chip) return 0; } +static void smb_restore_charge_enable(void *data) +{ + struct smb_chip *chip = data; + int rc; + + rc = regmap_update_bits(chip->regmap, + chip->base + CHARGING_ENABLE_CMD, + CHARGING_ENABLE_CMD_BIT, + chip->initial_charge_enable); + if (rc < 0) + dev_err(chip->dev, "Couldn't restore charging state: %d\n", rc); +} + static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name, irqreturn_t (*handler)(int irq, void *data)) { @@ -931,6 +947,9 @@ static int smb_probe(struct platform_device *pdev) struct power_supply_config supply_config = {}; struct power_supply_desc *desc; struct smb_chip *chip; + unsigned int charge_enable; + unsigned int float_voltage_sel; + int float_voltage_uv; int rc, irq; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); @@ -961,6 +980,18 @@ static int smb_probe(struct platform_device *pdev) "Couldn't get usbin_i IIO channel\n"); } + rc = regmap_read(chip->regmap, chip->base + CHARGING_ENABLE_CMD, + &charge_enable); + if (rc < 0) + return dev_err_probe(chip->dev, rc, + "Couldn't read charging state\n"); + + chip->initial_charge_enable = charge_enable & CHARGING_ENABLE_CMD_BIT; + rc = devm_add_action_or_reset(chip->dev, smb_restore_charge_enable, chip); + if (rc) + return dev_err_probe(chip->dev, rc, + "Couldn't register charging state rollback\n"); + rc = smb_init_hw(chip); if (rc < 0) return rc; @@ -995,13 +1026,35 @@ static int smb_probe(struct platform_device *pdev) return dev_err_probe(chip->dev, rc, "Failed to init status change work\n"); - rc = (chip->batt_info->voltage_max_design_uv - - SMB2_FLOAT_VOLTAGE_MIN_UV) / SMB2_FLOAT_VOLTAGE_STEP_UV; + if (power_supply_battery_info_has_prop(chip->batt_info, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX)) { + float_voltage_uv = + chip->batt_info->constant_charge_voltage_max_uv; + } else if (power_supply_battery_info_has_prop(chip->batt_info, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN)) { + float_voltage_uv = chip->batt_info->voltage_max_design_uv; + } else { + dev_warn(chip->dev, "No battery float voltage; preserving hardware setting\n"); + goto skip_float_voltage; + } + + if (float_voltage_uv < SMB2_FLOAT_VOLTAGE_MIN_UV || + float_voltage_uv > SMB2_FLOAT_VOLTAGE_MAX_UV) + return dev_err_probe(chip->dev, -EINVAL, + "float voltage %d uV outside %d-%d uV\n", + float_voltage_uv, + SMB2_FLOAT_VOLTAGE_MIN_UV, + SMB2_FLOAT_VOLTAGE_MAX_UV); + + float_voltage_sel = + (float_voltage_uv - SMB2_FLOAT_VOLTAGE_MIN_UV) / + SMB2_FLOAT_VOLTAGE_STEP_UV; rc = regmap_update_bits(chip->regmap, chip->base + FLOAT_VOLTAGE_CFG, - FLOAT_VOLTAGE_SETTING_MASK, rc); + FLOAT_VOLTAGE_SETTING_MASK, float_voltage_sel); if (rc < 0) return dev_err_probe(chip->dev, rc, "Couldn't set vbat max\n"); +skip_float_voltage: rc = smb_init_irq(chip, &irq, "bat-ov", smb_handle_batt_overvoltage); if (rc < 0) return rc; @@ -1025,6 +1078,8 @@ static int smb_probe(struct platform_device *pdev) if (rc < 0) return dev_err_probe(chip->dev, rc, "Couldn't set wake irq\n"); + devm_remove_action(chip->dev, smb_restore_charge_enable, chip); + platform_set_drvdata(pdev, chip); /* Initialise charger state */ From 2548e4fa576a87a3b0d1b15a9c3f909bc54887b5 Mon Sep 17 00:00:00 2001 From: Robin Snyders Date: Wed, 12 Aug 2026 01:30:02 +0200 Subject: [PATCH 06/77] power: supply: qcom_smbx: notify battery overvoltage recovery The battery-overvoltage IRQ is configured for both edges, but its handler only notifies the power-supply core while the fault bit is asserted. The falling edge therefore does not publish the recovered health and status. The handler also ignores register-read failures before testing status. Handle read errors, log only an asserted fault, and notify the power-supply core after every successful edge read. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Robin Snyders Link: https://patch.msgid.link/20260812-qcom-smbx-fixes-v1-5-eb48246be599@snyders.xyz Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index 55ffe9857e49..f5557b8cc92a 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -722,16 +722,22 @@ static irqreturn_t smb_handle_batt_overvoltage(int irq, void *data) { struct smb_chip *chip = data; unsigned int status; + int rc; - regmap_read(chip->regmap, chip->base + BATTERY_CHARGER_STATUS_2, - &status); + rc = regmap_read(chip->regmap, + chip->base + BATTERY_CHARGER_STATUS_2, &status); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read charger status: %d\n", rc); + return IRQ_HANDLED; + } if (status & CHARGER_ERROR_STATUS_BAT_OV_BIT) { /* The hardware stops charging automatically */ dev_err(chip->dev, "battery overvoltage detected\n"); - power_supply_changed(chip->chg_psy); } + power_supply_changed(chip->chg_psy); + return IRQ_HANDLED; } From f0ff5673aa07ded2bb383ac526c1faee4fe9ca07 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 05:22:29 +0800 Subject: [PATCH 07/77] power: supply: bq27xxx: synchronize work cancellation on suspend The poll work requeues itself via mod_delayed_work() while the device is not removed. cancel_delayed_work() does not wait for a callback that is already running, so a poll racing with suspend can keep issuing I2C transfers after suspend returns and requeue itself into early resume. Use cancel_delayed_work_sync() to quiesce polling before suspend completes, as the teardown path already does. Fixes: dfcb264a01a9 ("power: supply: bq27xxx: Stop and start delayed work in suspend and resume") Cc: stable@vger.kernel.org Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260827-bq27xxx-suspend-cancel-sync-v1-1-77a67711cbc3@cherr.cc Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 01c9ec1116fa..4d4809a70b62 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -2291,7 +2291,7 @@ static int bq27xxx_battery_suspend(struct device *dev) { struct bq27xxx_device_info *di = dev_get_drvdata(dev); - cancel_delayed_work(&di->work); + cancel_delayed_work_sync(&di->work); return 0; } From 38c8b37600b63db724c9c21d18d9989359353cc4 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Thu, 27 Aug 2026 10:05:39 +0800 Subject: [PATCH 08/77] power: supply: s2mu005-battery: return errors from dev_err_probe() in probe s2mu005_fg_i2c_probe() calls dev_err_probe() when devm_mutex_init() or devm_request_threaded_irq() fails, but drops the return value and carries on, finally returning 0. A failed probe is then reported as a success: the device stays registered with an uninitialized mutex and without its IRQ handler installed. Return the error from dev_err_probe() in both places so that the probe fails and the driver core logs the error state properly. Fixes: aa2132799817 ("power: supply: add support for S2MU005 battery fuel gauge device") Cc: Yassine Oudjana Cc: Kaustabh Chakraborty Cc: Sebastian Reichel Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260827020539.22837-1-min_halo@163.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/s2mu005-battery.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/s2mu005-battery.c b/drivers/power/supply/s2mu005-battery.c index 53de6605b492..a76154e5a091 100644 --- a/drivers/power/supply/s2mu005-battery.c +++ b/drivers/power/supply/s2mu005-battery.c @@ -262,7 +262,7 @@ static int s2mu005_fg_i2c_probe(struct i2c_client *client) ret = devm_mutex_init(dev, &priv->monout_mutex); if (ret) - dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n"); + return dev_err_probe(dev, ret, "failed to initialize MONOUT mutex\n"); psy_desc = device_get_match_data(dev); @@ -277,7 +277,7 @@ static int s2mu005_fg_i2c_probe(struct i2c_client *client) s2mu005_handle_irq, IRQF_ONESHOT, psy_desc->name, priv); if (ret) - dev_err_probe(dev, ret, "failed to request IRQ\n"); + return dev_err_probe(dev, ret, "failed to request IRQ\n"); return 0; } From 1101833121a447999309a01f03d5b35bc1652e09 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:18:49 +0200 Subject: [PATCH 09/77] power: supply: mp2629: Use the full batt_impedance_compensation name in the ABI document drivers/power/supply/mp2629_charger.c creates the attribute with DEVICE_ATTR_RW(batt_impedance_compensation). The ABI file documents it under the truncated name batt_impedance_compen, which does not exist in sysfs. Use the full name. Fixes: fe27f04e0cdc ("power: supply: mp2629: Add impedance compensation config") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260905101849.42830-1-kmehltretter@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power-mp2629 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-power-mp2629 b/Documentation/ABI/testing/sysfs-class-power-mp2629 index 914d67caac0d..a85f926ce9c2 100644 --- a/Documentation/ABI/testing/sysfs-class-power-mp2629 +++ b/Documentation/ABI/testing/sysfs-class-power-mp2629 @@ -1,4 +1,4 @@ -What: /sys/class/power_supply/mp2629_battery/batt_impedance_compen +What: /sys/class/power_supply/mp2629_battery/batt_impedance_compensation Date: April 2020 KernelVersion: 5.7 Description: From f265940e54d749c77507da083a3c16a481f5f5c5 Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:45:00 +0800 Subject: [PATCH 10/77] power: supply: mm8013: Publish the OF module alias The MM8013 driver matches mitsumi,mm8013 Device Tree clients, but exports only its legacy I2C ID table. The I2C core emits an OF modalias for these clients rather than i2c:mm8013. Publish the existing OF match table without changing gauge operations. Fixes: c75f4bf6800b ("power: supply: Introduce MM8013 fuel gauge driver") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905134500.67832-1-hppiscas@163.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/mm8013.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c index 819667a27cad..c38f48338d42 100644 --- a/drivers/power/supply/mm8013.c +++ b/drivers/power/supply/mm8013.c @@ -293,6 +293,7 @@ static const struct of_device_id mm8013_match_table[] = { { .compatible = "mitsumi,mm8013" }, {} }; +MODULE_DEVICE_TABLE(of, mm8013_match_table); static struct i2c_driver mm8013_i2c_driver = { .probe = mm8013_probe, From db560f4b1c5253853f978385db066220e0c218dc Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 3 Sep 2026 08:13:12 +0000 Subject: [PATCH 11/77] power: supply: bq27xxx: Ensure poll work is cancelled on remove The di->work poll work is queued from the poll callback itself and from bq27xxx_external_power_changed(). The cancel_delayed_work_sync() in bq27xxx_battery_teardown() runs while the power_supply is still registered, so external_power_changed() can re-arm the work after the cancel, and it runs after the driver data has been freed. Register a devm_delayed_work_autocancel() before devm_power_supply_register(), so on unwind the power_supply is unregistered first, after which no new external_power_changed() callback can be dispatched, and the poll work is cancelled only then. Drop the now-redundant cancel from bq27xxx_battery_teardown(). This issue was found by an in-house static analysis tool. Fixes: 740b755a3b34 ("bq27x00: Poll battery state") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260903081312.225535-1-fanwu01@zju.edu.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 4d4809a70b62..cd30993b8a89 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -43,6 +43,7 @@ */ #include +#include #include #include #include @@ -2234,7 +2235,6 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) }; int ret; - INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); ret = devm_mutex_init(di->dev, &di->lock); if (ret) return ret; @@ -2255,6 +2255,12 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) psy_desc->get_property = bq27xxx_battery_get_property; psy_desc->external_power_changed = bq27xxx_external_power_changed; + /* Cancel the poll work after the power_supply is unregistered. */ + ret = devm_delayed_work_autocancel(di->dev, &di->work, + bq27xxx_battery_poll); + if (ret) + return ret; + di->bat = devm_power_supply_register(di->dev, psy_desc, &psy_cfg); if (IS_ERR(di->bat)) return dev_err_probe(di->dev, PTR_ERR(di->bat), @@ -2281,8 +2287,6 @@ void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) mutex_lock(&di->lock); di->removed = true; mutex_unlock(&di->lock); - - cancel_delayed_work_sync(&di->work); } EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown); From ece9baab2a6e79224d3f591893be23b540b1625f Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Tue, 8 Sep 2026 00:35:58 -0500 Subject: [PATCH 12/77] power: supply: bq24190_charger: use the wake irq API bq24190_probe() calls enable_irq_wake() on the charger interrupt and never calls disable_irq_wake(). That is wrong in three ways. It is unbalanced. free_irq() does not touch desc->wake_depth -- only irq_set_irq_wake() does -- so after the driver is unbound the descriptor is still one wake reference deep and the irqchip is still configured for wake, forever, for an interrupt nobody owns any more. It ignores the device's wakeup policy. The chip is made a system wake source whether or not anyone asked for it, and because device_init_wakeup() is never called there is no power/wakeup attribute for userspace to say otherwise. A board that would rather not be woken by its charger has no way to express that. And it arms the interrupt for wake permanently, from probe onwards, rather than for the sleep it is meant to cover. Use the wake irq API instead: devm_device_init_wakeup() to make the device wakeup-capable and enabled -- preserving today's behaviour -- and devm_pm_set_wake_irq() to hand the interrupt to the PM core, which arms it in dpm_suspend_noirq() (only if device_may_wakeup(), immediately before suspend_device_irqs()) and disarms it on resume. Both are unwound by devm, so the reference is balanced on unbind, and userspace gets the power/wakeup control it should have had. No intended change in behaviour: charger events still wake the system by default. The same conversion was made to another charger driver for the same reason, in commit 6c5393771c50 ("power: supply: qcom_pmi8998_charger: fix wakeirq"). Fixes: f385e6e2a153 ("power: bq24190_charger: Use PM runtime autosuspend") Assisted-by: LLM Signed-off-by: Ryan Brue Link: https://patch.msgid.link/20260908-rbrue-suez-upstreaming-bq24190_charger-use-wake-irq-api-v1-1-c3f10ae2a34a@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 4bea6fd83c36..376b0b06a096 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -2168,7 +2169,13 @@ static int bq24190_probe(struct i2c_client *client) if (ret < 0) goto out_charger; - enable_irq_wake(client->irq); + ret = devm_device_init_wakeup(dev); + if (ret < 0) + goto out_charger; + + ret = devm_pm_set_wake_irq(dev, client->irq); + if (ret < 0) + goto out_charger; pm_runtime_put_autosuspend(dev); From fbbfe77eb37ad1de7912c63a6adfe1b09a14670b Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 14:42:12 +0530 Subject: [PATCH 13/77] power: reset: keystone: fix missing space in error message The two string literals are concatenated, so the message prints "has to contain atleast one entry". Assisted-by: LLM Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260907091212.28352-1-hemanth.selam@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/keystone-reset.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index 3c44cd6cee0a..491ba490b8ea 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -112,8 +112,7 @@ static int rsctrl_probe(struct platform_device *pdev) for (i = 0; i < WDT_MUX_NUMBER; i++) { ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val); if (ret == -EOVERFLOW && !i) { - dev_err(dev, "ti,wdt-list property has to contain at" - "least one entry\n"); + dev_err(dev, "ti,wdt-list property has to contain at least one entry\n"); return -EINVAL; } else if (ret) { break; From 4798fbcd9148455a143b03985d89f956c78c53ba Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:09:46 +0530 Subject: [PATCH 14/77] power: supply: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260907063947.34852-2-hemanth.selam@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 2 +- drivers/power/supply/charger-manager.c | 2 +- drivers/power/supply/da9030_battery.c | 2 +- drivers/power/supply/max17042_battery.c | 2 +- drivers/power/supply/sbs-charger.c | 4 ++-- drivers/power/supply/smb347-charger.c | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 741a45cc5f35..f7510ada6b73 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1590,7 +1590,7 @@ static int bq25890_suspend(struct device *dev) struct bq25890_device *bq = dev_get_drvdata(dev); /* - * If charger is removed, while in suspend, make sure ADC is diabled + * If charger is removed, while in suspend, make sure ADC is disabled * since it consumes slightly more power. */ return bq25890_field_write(bq, F_CONV_RATE, 0); diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index fd4cd9cd2ce2..8e8c5109401a 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -904,7 +904,7 @@ static bool cm_setup_timer(void) } /** - * charger_extcon_work - enable/diable charger according to the state + * charger_extcon_work - enable/disable charger according to the state * of charger cable * * @work: work_struct of the function charger_extcon_work. diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c index d25279c26030..8c4daec0a6b6 100644 --- a/drivers/power/supply/da9030_battery.c +++ b/drivers/power/supply/da9030_battery.c @@ -263,7 +263,7 @@ static void da9030_charger_check_state(struct da9030_charger *charger) } else if (charger->adc.vbat_res > charger->thresholds.vbat_low) { /* we are charging and passed LOW_THRESH, - so upate DA9030 VBAT threshold + so update DA9030 VBAT threshold */ da903x_write(charger->master, DA9030_VBATMON, charger->thresholds.vbat_low); diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index cbf3ac9c3e14..a53970af56cb 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -1080,7 +1080,7 @@ static int max17042_init_chip(struct max17042_chip *chip) /* update capacity params */ max17042_update_capacity_regs(chip); - /* delay must be atleast 350mS to allow VFSOC + /* delay must be at least 350mS to allow VFSOC * to be calculated from the new configuration */ msleep(350); diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index e7b4e10dcfd5..bc40bbb429e6 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -2,10 +2,10 @@ /* * Copyright (c) 2016, Prodys S.L. * - * This adds support for sbs-charger compilant chips as defined here: + * This adds support for sbs-charger compliant chips as defined here: * http://sbs-forum.org/specs/sbc110.pdf * - * Implemetation based on sbs-battery.c + * Implementation based on sbs-battery.c */ #include diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index c1b67dac8ef6..72dfa15b3d80 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -149,7 +149,7 @@ * @pre_charge_current: current (in uA) to use in pre-charging phase * @termination_current: current (in uA) used to determine when the * charging cycle terminates - * @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to + * @pre_to_fast_voltage: voltage (in uV) threshold used for transitioning to * pre-charge to fast charge mode * @mains_current_limit: maximum input current drawn from AC/DC input (in uA) * @usb_hc_current_limit: maximum input high current (in uA) drawn from USB @@ -302,7 +302,7 @@ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) * * Function checks whether any power source is connected to the charger and * updates internal state accordingly. If there is a change to previous state - * function returns %1, otherwise %0 and negative errno in case of errror. + * function returns %1, otherwise %0 and negative errno in case of error. */ static int smb347_update_ps_status(struct smb347_charger *smb) { From f170c05e3e08aa2068601db369078bb83dc5e7e7 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:07 +0200 Subject: [PATCH 15/77] power: supply: Pack power_supply_desc to eliminate holes `pahole` reports that there are two holes in `struct power_supply_desc`: struct power_supply_desc { const char * name; /* 0 4 */ enum power_supply_type type; /* 4 4 */ u8 charge_behaviours; /* 8 1 */ /* XXX 3 bytes hole, try to pack */ u32 charge_types; /* 12 4 */ u32 usb_types; /* 16 4 */ const enum power_supply_property * properties; /* 20 4 */ size_t num_properties; /* 24 4 */ int (*get_property)(...); /* 28 4 */ int (*set_property)(...); /* 32 4 */ int (*property_is_writeable)(...); /* 36 4 */ void (*external_power_changed)(...); /* 40 4 */ int (*init)(struct power_supply *); /* 44 4 */ bool no_thermal; /* 48 1 */ /* XXX 3 bytes hole, try to pack */ int use_for_apm; /* 52 4 */ /* size: 56, cachelines: 1, members: 14 */ /* sum members: 50, holes: 2, sum holes: 6 */ /* last cacheline: 56 bytes */ }; This can be optimized by moving `u8 charge_behaviours` to the end and swapping `int use_for_apm` with `bool no_thermal`: struct power_supply_desc { const char * name; /* 0 4 */ enum power_supply_type type; /* 4 4 */ u32 charge_types; /* 8 4 */ u32 usb_types; /* 12 4 */ const enum power_supply_property * properties; /* 16 4 */ size_t num_properties; /* 20 4 */ int (*get_property)(...); /* 24 4 */ int (*set_property)(...); /* 28 4 */ int (*property_is_writeable)(...); /* 32 4 */ void (*external_power_changed)(...); /* 36 4 */ int (*init)(struct power_supply *); /* 40 4 */ int use_for_apm; /* 44 4 */ bool no_thermal; /* 48 1 */ u8 charge_behaviours; /* 49 1 */ /* size: 52, cachelines: 1, members: 14 */ /* padding: 2 */ /* last cacheline: 52 bytes */ }; Do this in order to save 4 bytes. This will also help when adding new members in the future to this `struct`. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/5b6d1d588c8aaf237c45638c29a2ff23f0a07f34.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- include/linux/power_supply.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index e749d2189335..fcd05f4a8883 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -259,7 +259,6 @@ struct power_supply_config { struct power_supply_desc { const char *name; enum power_supply_type type; - u8 charge_behaviours; u32 charge_types; u32 usb_types; const enum power_supply_property *properties; @@ -295,14 +294,17 @@ struct power_supply_desc { */ int (*init)(struct power_supply *psy); + /* For APM emulation, think legacy userspace. */ + int use_for_apm; + /* * Set if thermal zone should not be created for this power supply. * For example for virtual supplies forwarding calls to actual * sensors or other supplies. */ bool no_thermal; - /* For APM emulation, think legacy userspace. */ - int use_for_apm; + + u8 charge_behaviours; }; struct power_supply_ext { From 4fe4d3dc5c20c62789c718946bdb4f72a7cf363d Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:08 +0200 Subject: [PATCH 16/77] power: supply: Add sysfs entry for system load control There are devices that can control the connection from power to system load. For example, with a field-effect transistor between a battery and the system load (BATFET). Drivers for these devices are currently enrolling their own custom `sysfs` property to control this. In order to unify this, add a new `sysfs` entry for controlling such switch and corresponding `power_supply_property` with `enum` values. The obvious states are "on" and "off", i.e. there is a connection or not, respectively. However, many devices can also enter special modes such as "low-power", "shipping" or "deep sleep". Since the members in `struct power_supply_desc` already are ordered, adding `load_switches` only increases the size with 4 bytes, as can be seen from `pahole`: struct power_supply_desc { const char * name; /* 0 4 */ enum power_supply_type type; /* 4 4 */ u32 charge_types; /* 8 4 */ u32 usb_types; /* 12 4 */ u32 load_switches; /* 16 4 */ const enum power_supply_property * properties; /* 20 4 */ size_t num_properties; /* 24 4 */ int (*get_property)(...); /* 28 4 */ int (*set_property)(...); /* 32 4 */ int (*property_is_writeable)(...); /* 36 4 */ void (*external_power_changed)(...); /* 40 4 */ int (*init)(struct power_supply *); /* 44 4 */ int use_for_apm; /* 48 4 */ bool no_thermal; /* 52 1 */ u8 charge_behaviours; /* 53 1 */ /* size: 56, cachelines: 1, members: 15 */ /* padding: 2 */ /* last cacheline: 56 bytes */ }; Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/d86f528de9a058091f806d9676639239cfeaf805.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 25 +++++++++++++++++++++ drivers/power/supply/power_supply_sysfs.c | 17 ++++++++++++++ include/linux/power_supply.h | 10 +++++++++ 3 files changed, 52 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 98b389845d1e..2cf3f53a652b 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -594,6 +594,31 @@ Description: Valid values: 0 - 100 (percent) +What: /sys/class/power_supply//load_switch +Date: June 2026 +Contact: linux-pm@vger.kernel.org +Description: + Devices can control the connection from power to system load. + For example, with a field-effect transistor between a battery + and the system load (BATFET). This entry controls such switch. + The obvious states are "on" and "off", i.e. there is a + connection or not, respectively. However, many devices can also + enter special modes such as "low-power", "shipping" or "deep + sleep". In these modes the switch is usually off and the + quiescent current quite low. + + Access: Read, Write + + Valid values: + + ============= ================================== + "Unknown" (0) Unknown mode. + "On" (1) Power is connected to the load. + "Off" (2) Power is disconnected to the load. + "Standby" (3) Low-power mode. + "Ship" (4) Ship mode. + ============= ================================== + **USB Properties** What: /sys/class/power_supply//input_current_limit diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 9d6b24856c8b..b0f524d152cf 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -152,6 +152,14 @@ static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = { [POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge", }; +static const char *const POWER_SUPPLY_LOAD_SWITCH_TEXT[] = { + [POWER_SUPPLY_LOAD_SWITCH_UNKNOWN] = "Unknown", + [POWER_SUPPLY_LOAD_SWITCH_ON] = "On", + [POWER_SUPPLY_LOAD_SWITCH_OFF] = "Off", + [POWER_SUPPLY_LOAD_SWITCH_STANDBY] = "Standby", + [POWER_SUPPLY_LOAD_SWITCH_SHIP] = "Ship", +}; + static struct power_supply_attr power_supply_attrs[] __ro_after_init = { /* Properties of type `int' */ POWER_SUPPLY_ENUM_ATTR(STATUS), @@ -231,6 +239,7 @@ static struct power_supply_attr power_supply_attrs[] __ro_after_init = { POWER_SUPPLY_ATTR(MANUFACTURE_DAY), POWER_SUPPLY_ATTR(INTERNAL_RESISTANCE), POWER_SUPPLY_ATTR(STATE_OF_HEALTH), + POWER_SUPPLY_ENUM_ATTR(LOAD_SWITCH), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(MODEL_NAME), POWER_SUPPLY_ATTR(MANUFACTURER), @@ -400,6 +409,14 @@ static ssize_t power_supply_format_property(struct device *dev, ret = power_supply_show_charge_types(dev, psy, value.intval, buf); break; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + if (uevent) /* no possible values in uevents */ + goto default_format; + ret = power_supply_show_enum_with_available( + dev, POWER_SUPPLY_LOAD_SWITCH_TEXT, + ARRAY_SIZE(POWER_SUPPLY_LOAD_SWITCH_TEXT), + psy->desc->load_switches, value.intval, buf); + break; case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER: ret = sysfs_emit(buf, "%s\n", value.strval); break; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index fcd05f4a8883..f6cbeffda653 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -104,6 +104,14 @@ enum { POWER_SUPPLY_SCOPE_DEVICE, }; +enum { + POWER_SUPPLY_LOAD_SWITCH_UNKNOWN = 0, + POWER_SUPPLY_LOAD_SWITCH_ON, + POWER_SUPPLY_LOAD_SWITCH_OFF, + POWER_SUPPLY_LOAD_SWITCH_STANDBY, + POWER_SUPPLY_LOAD_SWITCH_SHIP, +}; + enum power_supply_property { /* Properties of type `int' */ POWER_SUPPLY_PROP_STATUS = 0, @@ -182,6 +190,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_MANUFACTURE_DAY, POWER_SUPPLY_PROP_INTERNAL_RESISTANCE, POWER_SUPPLY_PROP_STATE_OF_HEALTH, + POWER_SUPPLY_PROP_LOAD_SWITCH, /* Properties of type `const char *' */ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, @@ -261,6 +270,7 @@ struct power_supply_desc { enum power_supply_type type; u32 charge_types; u32 usb_types; + u32 load_switches; const enum power_supply_property *properties; size_t num_properties; From 7e051a18fc49959d9cac63c46f1bf34616a208af Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:08 +0200 Subject: [PATCH 17/77] power: supply: ltc4162-l: Use POWER_SUPPLY_PROP_LOAD_SWITCH The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to select ship mode, but keep old custom `sysfs` ABI for backward compatibility. However, add a note in ABI documentation that one should prefer the property `load_switch` instead. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/d3fd0bc718393250815d1c3ef8544a480badbf69.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- .../ABI/testing/sysfs-class-power-ltc4162l | 2 + drivers/power/supply/ltc4162-l-charger.c | 56 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power-ltc4162l b/Documentation/ABI/testing/sysfs-class-power-ltc4162l index ba30db93052b..ad46ad834af0 100644 --- a/Documentation/ABI/testing/sysfs-class-power-ltc4162l +++ b/Documentation/ABI/testing/sysfs-class-power-ltc4162l @@ -77,6 +77,8 @@ Description: The ship mode, when armed, activates once the input voltage drops below 1V. + Note: use /sys/class/power_supply/ltc4162-l/load_switch instead. + Access: Read, Write Valid values: 0 (disable) or 1 (enable) diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c index 5c09e0368e6f..01b0511da9ae 100644 --- a/drivers/power/supply/ltc4162-l-charger.c +++ b/drivers/power/supply/ltc4162-l-charger.c @@ -755,6 +755,41 @@ static int ltc4162l_set_term_current(struct ltc4162l_info *info, BIT(2), BIT(2)); } +static int ltc4162l_get_ship_mode(struct ltc4162l_info *info, + union power_supply_propval *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, ®val); + if (ret < 0) + return ret; + + val->intval = regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? + POWER_SUPPLY_LOAD_SWITCH_SHIP : + POWER_SUPPLY_LOAD_SWITCH_ON; + + return 0; +} + +static int ltc4162l_set_ship_mode(struct ltc4162l_info *info, int value) +{ + unsigned int regval; + + switch (value) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = 0; + break; + case POWER_SUPPLY_LOAD_SWITCH_SHIP: + regval = LTC4162L_ARM_SHIP_MODE_MAGIC; + break; + default: + return -EINVAL; + } + + return regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE, regval); +} + /* Custom properties */ static const char * const ltc4162l_charge_status_name[] = { "ilim_reg_active", /* 32 */ @@ -890,15 +925,15 @@ static ssize_t arm_ship_mode_show(struct device *dev, { struct power_supply *psy = to_power_supply(dev); struct ltc4162l_info *info = power_supply_get_drvdata(psy); - unsigned int regval; + union power_supply_propval val; int ret; - ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, ®val); - if (ret) + ret = ltc4162l_get_ship_mode(info, &val); + if (ret < 0) return ret; return sysfs_emit(buf, "%u\n", - regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0); + val.intval == POWER_SUPPLY_LOAD_SWITCH_SHIP ? 1 : 0); } static ssize_t arm_ship_mode_store(struct device *dev, @@ -915,8 +950,9 @@ static ssize_t arm_ship_mode_store(struct device *dev, if (ret < 0) return ret; - ret = regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE, - value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0); + ret = ltc4162l_set_ship_mode(info, + value ? POWER_SUPPLY_LOAD_SWITCH_SHIP : + POWER_SUPPLY_LOAD_SWITCH_ON); if (ret < 0) return ret; @@ -981,6 +1017,8 @@ static int ltc4162l_get_property(struct power_supply *psy, return chip_info->get_die_temp(info, val); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return ltc4162l_get_term_current(info, val); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return ltc4162l_get_ship_mode(info, val); default: return -EINVAL; } @@ -1003,6 +1041,8 @@ static int ltc4162l_set_property(struct power_supply *psy, return ltc4162l_set_iin_limit(info, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return ltc4162l_set_term_current(info, val->intval); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return ltc4162l_set_ship_mode(info, val->intval); default: return -EINVAL; } @@ -1016,6 +1056,7 @@ static int ltc4162l_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return 1; default: return 0; @@ -1037,10 +1078,13 @@ static enum power_supply_property ltc4162l_properties[] = { POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_LOAD_SWITCH, }; static const struct power_supply_desc ltc4162l_desc = { .type = POWER_SUPPLY_TYPE_MAINS, + .load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP), .properties = ltc4162l_properties, .num_properties = ARRAY_SIZE(ltc4162l_properties), .get_property = ltc4162l_get_property, From cb012fe405f0f487e9480472f3558e678556ed49 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:09 +0200 Subject: [PATCH 18/77] power: supply: rt9471: Use POWER_SUPPLY_PROP_LOAD_SWITCH The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to control the BATFET, but keep old custom `sysfs` ABI for backward compatibility. However, add a note in ABI documentation that one should prefer the property `load_switch` instead. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/aff418f8059465203ef814a1b508b84741175ab9.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- .../ABI/testing/sysfs-class-power-rt9471 | 2 + drivers/power/supply/rt9471.c | 54 ++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power-rt9471 b/Documentation/ABI/testing/sysfs-class-power-rt9471 index 0a390ee5ac21..90cf94c1cbf6 100644 --- a/Documentation/ABI/testing/sysfs-class-power-rt9471 +++ b/Documentation/ABI/testing/sysfs-class-power-rt9471 @@ -11,6 +11,8 @@ Description: mode. 'Disable' also can help to leave it, but it's more like to abort the action before the device really enter shipping mode. + Note: use /sys/class/power_supply/rt9471-*/load_switch instead. + Access: Read, Write Valid values: - 1: enabled diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index b67f5abaa993..b16d39a774dd 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -265,6 +265,39 @@ static int rt9471_get_ieoc(struct rt9471_chip *chip, int *microamp) return rt9471_get_value_by_field_range(chip, F_IEOC_CHG, RT9471_RANGE_IEOC, microamp); } +static int rt9471_set_batfet(struct rt9471_chip *chip, int val) +{ + unsigned int regval; + + switch (val) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = 0; + break; + case POWER_SUPPLY_LOAD_SWITCH_OFF: + regval = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(chip->rm_fields[F_BATFET_DIS], regval); +} + +static int rt9471_get_batfet(struct rt9471_chip *chip, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(chip->rm_fields[F_BATFET_DIS], ®val); + if (ret < 0) + return ret; + + *val = regval ? POWER_SUPPLY_LOAD_SWITCH_OFF : + POWER_SUPPLY_LOAD_SWITCH_ON; + + return 0; +} + static int rt9471_get_status(struct rt9471_chip *chip, int *status) { unsigned int ic_stat; @@ -342,6 +375,7 @@ static enum power_supply_property rt9471_charger_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_LOAD_SWITCH, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, }; @@ -358,6 +392,7 @@ static int rt9471_charger_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return 1; default: return 0; @@ -393,6 +428,8 @@ static int rt9471_charger_set_property(struct power_supply *psy, chip, F_IPRE_CHG, RT9471_RANGE_IPRE, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9471_set_ieoc(chip, val->intval); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return rt9471_set_batfet(chip, val->intval); default: return -EINVAL; } @@ -439,6 +476,8 @@ static int rt9471_charger_get_property(struct power_supply *psy, chip, F_IPRE_CHG, RT9471_RANGE_IPRE, &val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9471_get_ieoc(chip, &val->intval); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return rt9471_get_batfet(chip, &val->intval); case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = rt9471_model; return 0; @@ -649,14 +688,14 @@ static ssize_t sysoff_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct rt9471_chip *chip = psy_device_to_chip(dev); - unsigned int sysoff_enable; + unsigned int val; int ret; - ret = regmap_field_read(chip->rm_fields[F_BATFET_DIS], &sysoff_enable); - if (ret) + ret = rt9471_get_batfet(chip, &val); + if (ret < 0) return ret; - return sysfs_emit(buf, "%d\n", sysoff_enable); + return sysfs_emit(buf, "%d\n", val == POWER_SUPPLY_LOAD_SWITCH_OFF); } static ssize_t sysoff_enable_store(struct device *dev, @@ -671,8 +710,9 @@ static ssize_t sysoff_enable_store(struct device *dev, if (ret) return ret; - ret = regmap_field_write(chip->rm_fields[F_BATFET_DIS], !!tmp); - if (ret) + ret = rt9471_set_batfet(chip, tmp ? POWER_SUPPLY_LOAD_SWITCH_OFF : + POWER_SUPPLY_LOAD_SWITCH_ON); + if (ret < 0) return ret; return count; @@ -744,6 +784,8 @@ static int rt9471_register_psy(struct rt9471_chip *chip) BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN); + desc->load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_OFF); desc->properties = rt9471_charger_properties; desc->num_properties = ARRAY_SIZE(rt9471_charger_properties); desc->get_property = rt9471_charger_get_property; From 3f21de6784a654503cc148169d1f59d89971da13 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:09 +0200 Subject: [PATCH 19/77] power: supply: rt9467: Use POWER_SUPPLY_PROP_LOAD_SWITCH The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to control the BATFET, but keep old custom `sysfs` ABI for backward compatibility. However, add a note in ABI documentation that one should prefer the property `load_switch` instead. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/11d5d3f03f34cdc47a9ec9f1ebec9e1d9457414f.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- .../ABI/testing/sysfs-class-power-rt9467 | 2 + drivers/power/supply/rt9467-charger.c | 54 ++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power-rt9467 b/Documentation/ABI/testing/sysfs-class-power-rt9467 index 619b7c45d145..bfbfd78ec1c1 100644 --- a/Documentation/ABI/testing/sysfs-class-power-rt9467 +++ b/Documentation/ABI/testing/sysfs-class-power-rt9467 @@ -13,6 +13,8 @@ Description: 'Disable' also can help to leave it, but it's more like to abort the action before the device really enter shipping mode. + Note: use /sys/class/power_supply/rt9467-*/load_switch instead. + Access: Read, Write Valid values: - 1: enabled diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index a9a83b263e18..f0d10f44b109 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -633,6 +633,39 @@ static int rt9467_psy_set_ieoc(struct rt9467_chg_data *data, int microamp) return ret; } +static int rt9467_set_batfet(struct rt9467_chg_data *data, int val) +{ + unsigned int regval; + + switch (val) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = 0; + break; + case POWER_SUPPLY_LOAD_SWITCH_OFF: + regval = 1; + break; + default: + return -EINVAL; + } + + return regmap_field_write(data->rm_field[F_SHIP_MODE], regval); +} + +static int rt9467_get_batfet(struct rt9467_chg_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->rm_field[F_SHIP_MODE], ®val); + if (ret < 0) + return ret; + + *val = regval ? POWER_SUPPLY_LOAD_SWITCH_OFF : + POWER_SUPPLY_LOAD_SWITCH_ON; + + return 0; +} + static const enum power_supply_property rt9467_chg_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, @@ -648,6 +681,7 @@ static const enum power_supply_property rt9467_chg_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_LOAD_SWITCH, }; static int rt9467_psy_get_property(struct power_supply *psy, @@ -711,6 +745,8 @@ static int rt9467_psy_get_property(struct power_supply *psy, val->intval = data->ieoc_ua; mutex_unlock(&data->ichg_ieoc_lock); return 0; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return rt9467_get_batfet(data, &val->intval); default: return -ENODATA; } @@ -747,6 +783,8 @@ static int rt9467_psy_set_property(struct power_supply *psy, RT9467_RANGE_IPREC, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9467_psy_set_ieoc(data, val->intval); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return rt9467_set_batfet(data, val->intval); default: return -EINVAL; } @@ -764,6 +802,7 @@ static int rt9467_chg_prop_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return 1; default: return 0; @@ -777,6 +816,8 @@ static const struct power_supply_desc rt9467_chg_psy_desc = { BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), + .load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_OFF), .properties = rt9467_chg_properties, .num_properties = ARRAY_SIZE(rt9467_chg_properties), .property_is_writeable = rt9467_chg_prop_is_writeable, @@ -793,14 +834,14 @@ static ssize_t sysoff_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct rt9467_chg_data *data = psy_device_to_chip(dev); - unsigned int sysoff_enable; + int val; int ret; - ret = regmap_field_read(data->rm_field[F_SHIP_MODE], &sysoff_enable); - if (ret) + ret = rt9467_get_batfet(data, &val); + if (ret < 0) return ret; - return sysfs_emit(buf, "%d\n", sysoff_enable); + return sysfs_emit(buf, "%d\n", val == POWER_SUPPLY_LOAD_SWITCH_OFF); } static ssize_t sysoff_enable_store(struct device *dev, @@ -815,8 +856,9 @@ static ssize_t sysoff_enable_store(struct device *dev, if (ret) return ret; - ret = regmap_field_write(data->rm_field[F_SHIP_MODE], !!tmp); - if (ret) + ret = rt9467_set_batfet(data, tmp ? POWER_SUPPLY_LOAD_SWITCH_OFF : + POWER_SUPPLY_LOAD_SWITCH_ON); + if (ret < 0) return ret; return count; From a329f374fb2b98a4594c44169c7dc97775028676 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:09 +0200 Subject: [PATCH 20/77] power: supply: bq24257: Use POWER_SUPPLY_PROP_LOAD_SWITCH The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to control `SYSOFF`, but keep old custom `sysfs` ABI for backward compatibility. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/d5b2615e549725a9294f9dd51787692591743cce.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24257_charger.c | 43 +++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 944a7aa6f187..8eea7d0395ce 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -295,6 +295,40 @@ static int bq24257_set_input_current_limit(struct bq24257_device *bq, BQ24257_IILIMIT_MAP_SIZE)); } +static int bq24257_get_sys_switch(struct bq24257_device *bq, + union power_supply_propval *val) +{ + int ret; + + ret = bq24257_field_read(bq, F_SYSOFF); + if (ret < 0) + return ret; + + val->intval = ret ? POWER_SUPPLY_LOAD_SWITCH_OFF : + POWER_SUPPLY_LOAD_SWITCH_ON; + + return 0; +} + +static int bq24257_set_sys_switch(struct bq24257_device *bq, + const union power_supply_propval *val) +{ + u8 regval; + + switch (val->intval) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = 0; + break; + case POWER_SUPPLY_LOAD_SWITCH_OFF: + regval = 1; + break; + default: + return -EINVAL; + } + + return bq24257_field_write(bq, F_SYSOFF, regval); +} + static int bq24257_power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -381,7 +415,8 @@ static int bq24257_power_supply_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return bq24257_get_input_current_limit(bq, val); - + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return bq24257_get_sys_switch(bq, val); default: return -EINVAL; } @@ -398,6 +433,8 @@ static int bq24257_power_supply_set_property(struct power_supply *psy, switch (prop) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: return bq24257_set_input_current_limit(bq, val); + case POWER_SUPPLY_PROP_LOAD_SWITCH: + return bq24257_set_sys_switch(bq, val); default: return -EINVAL; } @@ -408,6 +445,7 @@ static int bq24257_power_supply_property_is_writeable(struct power_supply *psy, { switch (psp) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return true; default: return false; @@ -740,6 +778,7 @@ static enum power_supply_property bq24257_power_supply_props[] = { POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_LOAD_SWITCH, }; static char *bq24257_charger_supplied_to[] = { @@ -749,6 +788,8 @@ static char *bq24257_charger_supplied_to[] = { static const struct power_supply_desc bq24257_power_supply_desc = { .name = "bq24257-charger", .type = POWER_SUPPLY_TYPE_USB, + .load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_OFF), .properties = bq24257_power_supply_props, .num_properties = ARRAY_SIZE(bq24257_power_supply_props), .get_property = bq24257_power_supply_get_property, From 00d436f278929fb0db11ba5279dce21f82a177db Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:10 +0200 Subject: [PATCH 21/77] power: supply: bq24190: Use POWER_SUPPLY_PROP_LOAD_SWITCH The `online`-property has historically been (ab)used for controlling the BATFET in this driver. The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to control the BATFET, but keep the support for `online` in order to have ABI backward compatibility. Moreover, don't bother with converting the `online`-functions to use the new `charger_get/set_batfet_ctrl()` to keep the code more readable. It's already quite messy, e.g. `charge_set_online()` calls `battery_set_online()`. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/44792a20fb581cfd3587fd71147f29e22c7f3749.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 376b0b06a096..c9b86d5a21a4 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1295,6 +1295,58 @@ static int bq24190_charger_set_iinlimit(struct bq24190_dev_info *bdi, ARRAY_SIZE(bq24190_isc_iinlim_values), val->intval); } +static int bq24190_charger_get_batfet_ctrl(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 regval; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, + ®val); + if (ret < 0) + return ret; + + /* + * Datasheet defines shipping mode as BATFET off _and_ the watchdog + * disabled. Since the watchdog is disabled during the whole lifetime + * (from probe), shipping mode is the only "off" state that can be + * achievable. + */ + val->intval = regval ? POWER_SUPPLY_LOAD_SWITCH_SHIP : + POWER_SUPPLY_LOAD_SWITCH_ON; + return 0; +} + +static int +bq24190_charger_set_batfet_ctrl(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + u8 regval; + + /* + * Datasheet defines shipping mode as BATFET off _and_ the watchdog + * disabled. Since the watchdog is disabled during the whole lifetime + * (from probe), shipping mode is the only "off" state that can be + * achievable. + */ + switch (val->intval) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = 0; + break; + case POWER_SUPPLY_LOAD_SWITCH_SHIP: + regval = 1; + break; + default: + return -EINVAL; + } + + return bq24190_write_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, regval); +} + static int bq24190_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { @@ -1351,6 +1403,9 @@ static int bq24190_charger_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_SCOPE_SYSTEM; ret = 0; break; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + ret = bq24190_charger_get_batfet_ctrl(bdi, val); + break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = bdi->model_name; ret = 0; @@ -1401,6 +1456,9 @@ static int bq24190_charger_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ret = bq24190_charger_set_iinlimit(bdi, val); break; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + ret = bq24190_charger_set_batfet_ctrl(bdi, val); + break; default: ret = -EINVAL; } @@ -1421,6 +1479,7 @@ static int bq24190_charger_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return 1; default: return 0; @@ -1481,6 +1540,7 @@ static enum power_supply_property bq24190_charger_properties[] = { POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_LOAD_SWITCH, }; static char *bq24190_charger_supplied_to[] = { @@ -1499,6 +1559,8 @@ static const struct power_supply_desc bq24190_charger_desc = { .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) | BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) | BIT(POWER_SUPPLY_CHARGE_TYPE_FAST), + .load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP), }; /* Battery power supply property routines */ From ac6656c56125ca0c7547789547b8c45c3b40f5e4 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Tue, 1 Sep 2026 21:35:10 +0200 Subject: [PATCH 22/77] power: supply: bq25630: Add support for BATFET control The framework supports controlling system load with `POWER_SUPPLY_PROP_LOAD_SWITCH`. Use this to select between the different supported BATFET control modes. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/f9d62a7fbabb1a920b31f79438cc737df78d0140.1788290289.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25630_charger.c | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c index 200f74f8eab6..bf54ff557a40 100644 --- a/drivers/power/supply/bq25630_charger.c +++ b/drivers/power/supply/bq25630_charger.c @@ -112,6 +112,12 @@ #define BQ25630_ITERM_MAX 1000000 #define BQ25630_ITERM_STEP 10000 +/* BATFET control modes. */ +#define BQ25630_BATFET_CTRL_IDLE 0x00 +#define BQ25630_BATFET_CTRL_SHUTDOWN 0x01 +#define BQ25630_BATFET_CTRL_SHIP 0x02 +#define BQ25630_BATFET_CTRL_STANDBY 0x03 + /* Charge types. */ #define BQ25630_CHG_STAT_NOT_CHARGING 0x00 #define BQ25630_CHG_STAT_TRICKLE_CHARGE 0x01 @@ -587,6 +593,71 @@ static int bq25630_read_vbus(struct bq25630_data *data, int *val) return 0; } +static int bq25630_read_batfet_ctrl(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_BATFET_CTRL], + ®val); + if (ret) { + dev_err(data->dev, "Could not read BATFET control (%d)\n", ret); + return ret; + } + + switch (regval) { + case BQ25630_BATFET_CTRL_IDLE: + *val = POWER_SUPPLY_LOAD_SWITCH_ON; + break; + case BQ25630_BATFET_CTRL_SHUTDOWN: + *val = POWER_SUPPLY_LOAD_SWITCH_OFF; + break; + case BQ25630_BATFET_CTRL_SHIP: + *val = POWER_SUPPLY_LOAD_SWITCH_SHIP; + break; + case BQ25630_BATFET_CTRL_STANDBY: + *val = POWER_SUPPLY_LOAD_SWITCH_STANDBY; + break; + default: + *val = POWER_SUPPLY_LOAD_SWITCH_UNKNOWN; + } + + return 0; +} + +static int bq25630_write_batfet_ctrl(struct bq25630_data *data, int val) +{ + unsigned int regval; + int ret; + + switch (val) { + case POWER_SUPPLY_LOAD_SWITCH_ON: + regval = BQ25630_BATFET_CTRL_IDLE; + break; + case POWER_SUPPLY_LOAD_SWITCH_OFF: + regval = BQ25630_BATFET_CTRL_SHUTDOWN; + break; + case POWER_SUPPLY_LOAD_SWITCH_SHIP: + regval = BQ25630_BATFET_CTRL_SHIP; + break; + case POWER_SUPPLY_LOAD_SWITCH_STANDBY: + regval = BQ25630_BATFET_CTRL_STANDBY; + break; + default: + return -EINVAL; + } + + ret = regmap_field_write(data->regfields[BQ25630_REGF_BATFET_CTRL], + regval); + if (ret) { + dev_err(data->dev, "Could not write BATFET control (%d)\n", + ret); + return ret; + } + + return 0; +} + static int bq25630_get_status(struct bq25630_data *data, int *val) { unsigned int regval; @@ -845,6 +916,9 @@ static int bq25630_charger_get_property(struct power_supply *psy, BQ25630_ITERM_MIN_REGVAL, &val->intval); break; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + ret = bq25630_read_batfet_ctrl(data, &val->intval); + break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = "BQ25630"; break; @@ -913,6 +987,9 @@ static int bq25630_charger_set_property(struct power_supply *psy, BQ25630_ITERM_MIN_REGVAL, val->intval); break; + case POWER_SUPPLY_PROP_LOAD_SWITCH: + ret = bq25630_write_batfet_ctrl(data, val->intval); + break; default: return -EINVAL; } @@ -932,6 +1009,7 @@ static int bq25630_charger_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + case POWER_SUPPLY_PROP_LOAD_SWITCH: return true; default: return false; @@ -954,6 +1032,7 @@ static const enum power_supply_property bq25630_charger_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_LOAD_SWITCH, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, }; @@ -972,6 +1051,11 @@ static const struct power_supply_desc bq25630_charger_psy_desc = { BIT(POWER_SUPPLY_USB_TYPE_DCP) | BIT(POWER_SUPPLY_USB_TYPE_CDP) | BIT(POWER_SUPPLY_USB_TYPE_C), + .load_switches = BIT(POWER_SUPPLY_LOAD_SWITCH_UNKNOWN) | + BIT(POWER_SUPPLY_LOAD_SWITCH_ON) | + BIT(POWER_SUPPLY_LOAD_SWITCH_OFF) | + BIT(POWER_SUPPLY_LOAD_SWITCH_STANDBY) | + BIT(POWER_SUPPLY_LOAD_SWITCH_SHIP), .properties = bq25630_charger_properties, .num_properties = ARRAY_SIZE(bq25630_charger_properties), .get_property = bq25630_charger_get_property, From c3a6ff174c98f2c7ea213765ff6969140c381e90 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Tue, 18 Aug 2026 11:48:20 +0200 Subject: [PATCH 23/77] dt-bindings: power: bq27xxx: bq27z561: allow interrupts property According to the technical reference manual for bq27z561 it has support for an interrupt on an INT pin. Remove it from the list of devices without interrupt support to better describe the hardware. Fixes: b4f7a727c29c ("dt-bindings: power: supply: bq27xxx: document optional interrupt") Signed-off-by: Henrik Grimler Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260818-bq27z746-v2-1-0371e206665f@axis.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/bq27xxx.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml index bc05400186cf..d985ae632599 100644 --- a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml +++ b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml @@ -91,7 +91,6 @@ allOf: - ti,bq27742 - ti,bq27545 - ti,bq27411 - - ti,bq27z561 - ti,bq28z610 - ti,bq34z100 - ti,bq78z100 From 122e0246f5bd573663203dd783161f27d790ce39 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Tue, 18 Aug 2026 11:48:21 +0200 Subject: [PATCH 24/77] dt-bindings: power: bq27xxx: document bq27z746 Add compatible for Texas Instruments bq27z746 fuel gauge, which is similar to its sibling bq27z561. Unlike bq27z561, bq27z746 includes some protection logic and can drive external MOSFETs. The register map is a subset of the one found in bq27z561, with bq27z561 having six extra registers at the end at 0x70 - 0x7A. Among the ManufacturerAccess registers there are some more differences mainly related to MOSFET control and deep sleep/shipping mode. Signed-off-by: Henrik Grimler Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260818-bq27z746-v2-2-0371e206665f@axis.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/bq27xxx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml index d985ae632599..79d1a3c8c685 100644 --- a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml +++ b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml @@ -47,6 +47,7 @@ properties: - ti,bq27441 - ti,bq27621 - ti,bq27z561 + - ti,bq27z746 - ti,bq28z610 - ti,bq34z100 - ti,bq78z100 From abe09806ef8b76f47e6248c82abc1d1623e9f985 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Tue, 18 Aug 2026 11:48:22 +0200 Subject: [PATCH 25/77] power: supply: bq27xxx: add support for bq27z746 bq27z746 is similar to bq27z561, but has additional battery protection logic. The standard register map is a subset of the one found in bq27z561. There are some more differences among the ManufacturerAccess registers, but the ManufacturerAccess registers are currently not handled by the driver in any way. Therefore use the same regs and props for bq27z746 and bq27z561. Signed-off-by: Henrik Grimler Acked-by: Andrew Davis Link: https://patch.msgid.link/20260818-bq27z746-v2-3-0371e206665f@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 6 ++++++ drivers/power/supply/bq27xxx_battery_i2c.c | 2 ++ include/linux/power/bq27xxx_battery.h | 1 + 3 files changed, 9 insertions(+) diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index cd30993b8a89..17f5b58609be 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -37,6 +37,7 @@ * https://www.ti.com/product/bq27441-g1 * https://www.ti.com/product/bq27621-g1 * https://www.ti.com/product/bq27z561 + * https://www.ti.com/product/bq27z746 * https://www.ti.com/product/bq28z610 * https://www.ti.com/product/bq34z100-g1 * https://www.ti.com/product/bq78z100 @@ -502,6 +503,7 @@ static u8 [BQ27XXX_REG_AP] = 0x22, BQ27XXX_DM_REG_ROWS, }, +#define bq27z746_regs bq27z561_regs bq28z610_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, [BQ27XXX_REG_TEMP] = 0x06, @@ -837,6 +839,8 @@ static enum power_supply_property bq27z561_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +#define bq27z746_props bq27z561_props + static enum power_supply_property bq28z610_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, @@ -995,6 +999,7 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = { #endif #define bq27z561_dm_regs NULL +#define bq27z746_dm_regs NULL #define bq28z610_dm_regs NULL #define bq34z100_dm_regs NULL #define bq78z100_dm_regs NULL @@ -1053,6 +1058,7 @@ static struct { [BQ27441] = BQ27XXX_DATA(bq27441, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM), [BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM), [BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS), + [BQ27Z746] = BQ27XXX_DATA(bq27z746, 0 , BQ27Z561_O_BITS), [BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS), [BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \ BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM), diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index c4e7a9521d08..2a6b4534cb28 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -253,6 +253,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = { { .name = "bq27441", .driver_data = BQ27441 }, { .name = "bq27621", .driver_data = BQ27621 }, { .name = "bq27z561", .driver_data = BQ27Z561 }, + { .name = "bq27z746", .driver_data = BQ27Z746 }, { .name = "bq28z610", .driver_data = BQ28Z610 }, { .name = "bq34z100", .driver_data = BQ34Z100 }, { .name = "bq78z100", .driver_data = BQ78Z100 }, @@ -290,6 +291,7 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { { .compatible = "ti,bq27441" }, { .compatible = "ti,bq27621" }, { .compatible = "ti,bq27z561" }, + { .compatible = "ti,bq27z746" }, { .compatible = "ti,bq28z610" }, { .compatible = "ti,bq34z100" }, { .compatible = "ti,bq78z100" }, diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index d56e1276aafe..0b833c4bb583 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -33,6 +33,7 @@ enum bq27xxx_chip { BQ27441, BQ27621, BQ27Z561, + BQ27Z746, BQ28Z610, BQ34Z100, BQ78Z100, From 4a35810a847411c466a15d1737d11e8e818dc197 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 24 Aug 2026 16:42:17 +0800 Subject: [PATCH 26/77] power: supply: bq2515x: Propagate charge-enable read errors bq2515x_get_charge_disable() returns bool even though reading the charger-disable state can fail. A negative error is therefore converted to true, so POWER_SUPPLY_PROP_STATUS reports NOT_CHARGING with success instead of propagating the failure. The GPIO read is not checked for errors either. Return an int from the helper, check both reads, and only convert a valid charger-disable state to the charging-status boolean. Fixes: 44908459275e ("power: supply: bq25150 introduce the bq25150") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260824084217.670213-1-lilinmao@kylinos.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq2515x_charger.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c index 0208358ebbe4..c2b17562cf8b 100644 --- a/drivers/power/supply/bq2515x_charger.c +++ b/drivers/power/supply/bq2515x_charger.c @@ -420,7 +420,7 @@ static int bq2515x_get_battery_current_now(struct bq2515x_device *bq2515x) BQ2515X_ICHG_DIVISOR); } -static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x) +static int bq2515x_get_charge_disable(struct bq2515x_device *bq2515x) { int ret; int ce_pin; @@ -428,6 +428,8 @@ static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x) int charger_disable; ce_pin = gpiod_get_value_cansleep(bq2515x->ce_gpio); + if (ce_pin < 0) + return ce_pin; ret = regmap_read(bq2515x->regmap, BQ2515X_ICCTRL2, &icctrl2); if (ret) @@ -435,10 +437,7 @@ static bool bq2515x_get_charge_disable(struct bq2515x_device *bq2515x) charger_disable = icctrl2 & BQ2515X_CHARGER_DISABLE; - if (charger_disable || ce_pin) - return true; - - return false; + return charger_disable || ce_pin; } static int bq2515x_set_charge_disable(struct bq2515x_device *bq2515x, int val) @@ -615,7 +614,11 @@ static int bq2515x_charging_status(struct bq2515x_device *bq2515x, else status1_no_fault = false; - ce_status = (!bq2515x_get_charge_disable(bq2515x)); + ret = bq2515x_get_charge_disable(bq2515x); + if (ret < 0) + return ret; + + ce_status = !ret; /* * If there are no faults and charging is enabled, then status is From 4fc88ba435dadbc05990951e3f3fbd8ccd2df140 Mon Sep 17 00:00:00 2001 From: Matt DeVillier Date: Tue, 25 Aug 2026 07:09:00 -0500 Subject: [PATCH 27/77] power: supply: cros_charge-control: adopt EC charge state on probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver previously always forced AUTO with no charge limits at probe, which discarded sustainer thresholds and modes set by firmware or firmware setup before the kernel loaded. For command versions that support GET (v2+), read the EC state into the driver cache instead. Valid sustainer limits are adopted as AUTO with those thresholds: while the sustainer is active the EC may report IDLE or DISCHARGE as a transient hold/discharge step, which must not be exposed as inhibit-charge or force-discharge. Sustainer off (-1/-1) still maps to Linux "no limit" (0/100); other invalid limit pairs are remapped the same way with a warning and pushed back to the EC so the cache stays in sync. If GET fails, fall back to the previous defaults and SET them on the EC. Command version 1 still cannot report state and keeps forcing a well-known configuration. Signed-off-by: Matt DeVillier Acked-by: Thomas Weißschuh Reviewed-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260825120929.13168-2-matt.devillier@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_charge-control.c | 157 ++++++++++++++++++--- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index e0f168624807..07bcc6a9aeef 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -21,12 +21,22 @@ /* * Semantics of data *returned* from the EC API and Linux sysfs differ - * slightly, also the v1 API can not return any data. - * To match the expected sysfs API, data is never read back from the EC but - * cached in the driver. + * slightly: + * - EC sustainer off is lower=upper=-1 + * - Linux "no limit" is start=0, end=100 + * Also the v1 API can not return any data. * - * Changes to the EC bypassing the driver will not be reflected in sysfs. - * Any change to "charge_behaviour" will synchronize the EC with the driver state. + * While the sustainer is active the EC may report IDLE or DISCHARGE as the + * current charge-control mode; that is an internal hold/discharge step, not a + * Linux inhibit-charge / force-discharge request. Valid sustainer limits are + * therefore adopted as AUTO with the reported thresholds. + * + * Sysfs reads come from a driver-side cache. On probe, command versions that + * support GET (v2+) are initialized from the EC so firmware or firmware-setup + * programmed limits and modes are preserved (unlike earlier behaviour that + * always forced AUTO with no limits). v1 still forces a well-known EC state. + * Subsequent sysfs writes keep the cache and EC in sync; changes that bypass + * the driver are not reflected until the next probe. */ struct cros_chctl_priv { @@ -44,18 +54,23 @@ struct cros_chctl_priv { }; static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec, - u8 cmd_version, struct ec_params_charge_control *req) + u8 cmd_version, + struct ec_params_charge_control *req, + struct ec_response_charge_control *resp) { - int ret; static const u8 outsizes[] = { [1] = offsetof(struct ec_params_charge_control, cmd), [2] = sizeof(struct ec_params_charge_control), [3] = sizeof(struct ec_params_charge_control), }; + size_t insize = resp ? sizeof(*resp) : 0; + int ret; + + if (resp) + *resp = (struct ec_response_charge_control){}; ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req, - outsizes[cmd_version], NULL, 0); - + outsizes[cmd_version], resp, insize); if (ret < 0) return ret; @@ -94,7 +109,120 @@ static int cros_chctl_configure_ec(struct cros_chctl_priv *priv) req.sustain_soc.upper = -1; } - return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req); + return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, + &req, NULL); +} + +static int cros_chctl_get_ec_status(struct cros_chctl_priv *priv, + struct ec_response_charge_control *resp) +{ + struct ec_params_charge_control req = { + .cmd = EC_CHARGE_CONTROL_CMD_GET, + }; + + return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, + &req, resp); +} + +static void cros_chctl_set_default_state(struct cros_chctl_priv *priv) +{ + lockdep_assert_held(&priv->lock); + + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + priv->current_start_threshold = 0; + priv->current_end_threshold = 100; +} + +static bool cros_chctl_sustainer_limits_valid(s8 lower, s8 upper) +{ + return lower >= 0 && upper >= 0 && lower <= 100 && upper <= 100 && lower <= upper; +} + +static int cros_chctl_adopt_ec_mode(struct cros_chctl_priv *priv, u32 mode) +{ + lockdep_assert_held(&priv->lock); + + switch (mode) { + case CHARGE_CONTROL_NORMAL: + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + return 0; + case CHARGE_CONTROL_IDLE: + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + return 0; + case CHARGE_CONTROL_DISCHARGE: + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + return 0; + default: + dev_warn(priv->dev, "unknown charge control mode %u\n", mode); + return -EINVAL; + } +} + +static int cros_chctl_init_state(struct cros_chctl_priv *priv) +{ + struct ec_response_charge_control resp; + s8 lower, upper; + int ret; + + guard(mutex)(&priv->lock); + + /* v1 cannot report current state; force a well-known EC configuration. */ + if (priv->cmd_version < 2) { + cros_chctl_set_default_state(priv); + return cros_chctl_configure_ec(priv); + } + + ret = cros_chctl_get_ec_status(priv, &resp); + if (ret < 0) { + dev_warn(priv->dev, + "failed to read EC charge state (%pe), applying defaults\n", + ERR_PTR(ret)); + goto defaults; + } + + lower = resp.sustain_soc.lower; + upper = resp.sustain_soc.upper; + + /* + * Valid sustainer limits mean "auto with thresholds". The EC mode may + * be IDLE/DISCHARGE while the sustainer holds or bleeds SoC; do not + * expose that as inhibit-charge / force-discharge. + */ + if (cros_chctl_sustainer_limits_valid(lower, upper)) { + priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + priv->current_start_threshold = lower; + priv->current_end_threshold = upper; + } else { + ret = cros_chctl_adopt_ec_mode(priv, resp.mode); + if (ret < 0) + goto defaults; + + /* + * Sustainer off is lower=upper=-1 → Linux "no limit" (0/100). + * Any other non-valid pair is unexpected; remap, warn, and + * push the cleaned state back to the EC. + */ + priv->current_start_threshold = 0; + priv->current_end_threshold = 100; + + if (!(lower == -1 && upper == -1)) { + dev_warn(priv->dev, + "invalid EC sustainer limits (%d/%d), treating as no limit\n", + lower, upper); + return cros_chctl_configure_ec(priv); + } + } + + dev_dbg(priv->dev, + "adopted EC charge state: behaviour=%d start=%u end=%u (ec mode=%u)\n", + priv->current_behaviour, priv->current_start_threshold, + priv->current_end_threshold, resp.mode); + + return 0; + +defaults: + cros_chctl_set_default_state(priv); + return cros_chctl_configure_ec(priv); } static int cros_chctl_psy_ext_get_prop(struct power_supply *psy, @@ -152,7 +280,6 @@ static int cros_chctl_psy_ext_set_threshold(struct cros_chctl_priv *priv, return 0; } - static int cros_chctl_psy_ext_set_prop(struct power_supply *psy, const struct power_supply_ext *ext, void *data, @@ -305,13 +432,7 @@ static int cros_chctl_probe(struct platform_device *pdev) priv->battery_hook.add_battery = cros_chctl_add_battery; priv->battery_hook.remove_battery = cros_chctl_remove_battery; - priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; - priv->current_start_threshold = 0; - priv->current_end_threshold = 100; - - /* Bring EC into well-known state */ - scoped_guard(mutex, &priv->lock) - ret = cros_chctl_configure_ec(priv); + ret = cros_chctl_init_state(priv); if (ret < 0) return ret; From e0844bd22a9f97885882bde3c4a441710e895240 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Mon, 28 Sep 2026 17:26:17 -0500 Subject: [PATCH 28/77] power: supply: bq24190_charger: don't reset registers across system suspend bq24190_pm_suspend() calls bq24190_register_reset(), which returns every register to its power-on default, and bq24190_pm_resume() does it again before re-applying the probe-time configuration. On any board that wires the charger's interrupt this makes system suspend unusable. The reset re-arms the chip's 40 s i2c watchdog. bq24190_set_config() turns that watchdog off at probe, deliberately: as the comment there explains, the same write also takes the part out of default mode into host mode. Nothing pets it while the system is asleep, so it expires, resets the registers again and pulses INT. The charger interrupt is a system wake source -- armed unconditionally in probe since commit f385e6e2a153 ("power: bq24190_charger: Use PM runtime autosuspend"), and still enabled by default after the conversion to the wake irq API [1] -- so the pulse wakes the machine. Measured on an MT8173 board (Amazon Fire HD 10 2017, BQ24297) by swapping only this driver between builds with and without this change, asking for a 150 s suspend each time: on charger, with the reset: 41-43 s, 7 runs of 7 on charger, without the reset: 150 s, 3 runs of 3 on battery, with the reset: 40-41 s, 3 runs of 3 on battery, without the reset: 150 s, 3 runs of 3 It is not a charging-only problem. The watchdog runs from VBUS or from the battery, so an unplugged tablet loses suspend in the same way. ftrace names the wake source. Across the resume, the first device interrupt after the machine comes back is the charger: 1270.760799: suspend_resume: machine_suspend[3] end 1270.775683: irq_handler_entry: irq=25 name=bq24190-charger 1270.776156: irq_handler_entry: irq=250 name=11010000.i2c Everything between is IPI and arch_timer from bringing the secondary CPUs back up, and no RTC interrupt appears anywhere in the trace -- the 150 s wake alarm never fired. The cause leaves nothing behind for userspace to find, because WATCHDOG_FAULT is in the latch-on-read fault register and the driver's own interrupt handler has already consumed it. The reset also discards host configuration that nothing restores. Resume calls bq24190_set_config(), which writes only the watchdog, SYS_MIN, IPRECHG, ITERM, ICHG and VREG, so: - IINLIM returns to its power-on default. Boards without a charger-type detector set the input limit from userspace; measured here, one ordinary suspend/resume silently took input_current_limit from 1500 mA to 500 mA, and to 100 mA when running from the battery, where the default differs. - EN_HIZ is cleared, so a charger the host put into high-impedance mode starts drawing from VBUS again the moment the system sleeps. There is no way to have both. Default mode is what the reset is for, and on this part default mode and a disarmed watchdog are mutually exclusive: a write to any register moves the chip into host mode, and it only returns to default mode when the watchdog times out. Parking it in default mode for the sleep therefore always leaves a timer armed that will fire, and on any board that wires INT that firing is a wake. A per-board opt-out would not be choosing between two workable configurations, only between a working suspend and a broken one. Resetting in suspend buys nothing worth this. The part is in host mode with its watchdog off; it charges on its own, switching from constant current to constant voltage and terminating when the battery is full, and nothing in it can expire or change while the host sleeps. Resetting on resume is weaker still: the host is awake and about to reconfigure the chip, so the reset only guarantees the loss. The one thing the reset does guard against is a host that never comes back: a chip in host mode keeps whatever it was last told, where default mode would revert to the power-on values. But that is the state the driver leaves it in for all of normal runtime operation already, so sleep is not special, and the part still makes its own constant-current to constant-voltage transition and terminates when the battery is full. So drop it in both directions. The suspend callback has nothing left to do and goes away. Resume re-applies the probe-time configuration, which is idempotent and also recovers a part that did somehow fall back to default mode, since bq24190_set_config() starts by turning the watchdog off. This has been proposed before. Hans de Goede sent the same change in 2017 [2] and Sebastian Reichel agreed with the reasoning [3], but v6 kept the reset on by default and added the "disable-reset" device property instead [4]. That property cannot answer this: it is set only from x86 platform code -- i2c-cht-wc and x86-android-tablets -- and is not in bq24190.yaml, so no DT board can reach it. Every in-tree DT user of this driver (tegra124-xiaomi-mocha, qcom-msm8974-lge-nexus5-hammerhead, qcom-msm8974pro-oneplus-bacon, rk3188-bqedison2qc) wires the charger interrupt and so has the same wake armed, with no way to opt out. If some board does want the reset, it would be better expressed the other way round. The vendor kernel for this board does not reset across suspend either: its driver, drivers/power/mt81xx/bq24297.c, only masks the charger interrupt on suspend and unmasks it on resume. Reproduced on a BQ24297. The reasoning applies to the family, but the other parts were not available to test. [1] https://lore.kernel.org/all/20260908-rbrue-suez-upstreaming-bq24190_charger-use-wake-irq-api-v1-1-c3f10ae2a34a@gmail.com/ [2] https://lore.kernel.org/all/20170322145536.30570-5-hdegoede@redhat.com/ [3] https://lore.kernel.org/all/20170323112052.ukyazi4pnji7n6st@earth/ [4] https://lore.kernel.org/all/20170414165233.4532-1-hdegoede@redhat.com/ Fixes: d7bf353fd0aa ("bq24190_charger: Add support for TI BQ24190 Battery Charger") Assisted-by: LLM Signed-off-by: Ryan Brue Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260928-rbrue-suez-upstreaming-bq24190_charger-no-reset-regs-sys-suspend-v1-1-9a7689f12c05@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index c9b86d5a21a4..7a5867dd74c2 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -2311,25 +2311,6 @@ static __maybe_unused int bq24190_runtime_resume(struct device *dev) return 0; } -static __maybe_unused int bq24190_pm_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct bq24190_dev_info *bdi = i2c_get_clientdata(client); - int error; - - error = pm_runtime_resume_and_get(bdi->dev); - if (error < 0) - dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); - - bq24190_register_reset(bdi); - - if (error >= 0) { - pm_runtime_put_autosuspend(bdi->dev); - } - - return 0; -} - static __maybe_unused int bq24190_pm_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -2343,7 +2324,14 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev) if (error < 0) dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error); - bq24190_register_reset(bdi); + /* + * The chip kept its configuration through the sleep: it is in host + * mode with the i2c watchdog off, so nothing expired and nothing was + * reset. Do not reset it here either -- a userspace setting such as + * EN_HIZ or IINLIM would otherwise be silently lost on every resume. + * Re-applying the probe-time configuration is idempotent and cheap, + * and covers a part that did somehow fall back to default mode. + */ bq24190_set_config(bdi); bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg); @@ -2362,7 +2350,7 @@ static __maybe_unused int bq24190_pm_resume(struct device *dev) static const struct dev_pm_ops bq24190_pm_ops = { SET_RUNTIME_PM_OPS(bq24190_runtime_suspend, bq24190_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(bq24190_pm_suspend, bq24190_pm_resume) + SET_SYSTEM_SLEEP_PM_OPS(NULL, bq24190_pm_resume) }; static const struct i2c_device_id bq24190_i2c_ids[] = { From 80d28539c1f9baab72210501729d67e3d9daa4b8 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 25 Sep 2026 10:59:24 +0200 Subject: [PATCH 29/77] dt-bindings: power: supply: ab8500: Add AB8505 charger AB8500 and AB8505 expose different charger interrupt sets. Add a unique AB8505 charger compatible with the AB8500 compatible as its fallback. Use the variant-specific string to require the fourteen main and USB charger interrupts on AB8500 and the nine USB charger interrupts on AB8505. Also clarify that autopower_cfg describes the AB8500 facility, since the corresponding AB8505 register bit is reserved. Fixes: eb917497734c ("dt-bindings: power: supply: ab8500: Convert to DT schema format") Assisted-by: LLM Reviewed-by: Krzysztof Kozlowski Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260925-ab8500-charger-dt-v3-1-f80b8d295e2d@kernel.org Signed-off-by: Sebastian Reichel --- .../supply/stericsson,ab8500-charger.yaml | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml index 4f19744844e9..dcffcecaea13 100644 --- a/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml +++ b/Documentation/devicetree/bindings/power/supply/stericsson,ab8500-charger.yaml @@ -10,12 +10,13 @@ title: AB8500 Charger maintainers: - Sebastian Reichel -allOf: - - $ref: power-supply.yaml# - properties: compatible: - const: stericsson,ab8500-charger + oneOf: + - const: stericsson,ab8500-charger + - items: + - const: stericsson,ab8505-charger + - const: stericsson,ab8500-charger monitored-battery: true @@ -28,27 +29,15 @@ properties: autopower_cfg: type: boolean - description: automatic poweron after powerloss + description: AB8500 automatic poweron after powerloss interrupts: + minItems: 9 maxItems: 14 interrupt-names: - items: - - const: MAIN_CH_UNPLUG_DET - - const: MAIN_CHARGE_PLUG_DET - - const: MAIN_EXT_CH_NOT_OK - - const: MAIN_CH_TH_PROT_R - - const: MAIN_CH_TH_PROT_F - - const: VBUS_DET_F - - const: VBUS_DET_R - - const: USB_LINK_STATUS - - const: USB_CH_TH_PROT_R - - const: USB_CH_TH_PROT_F - - const: USB_CHARGER_NOT_OKR - - const: VBUS_OVV - - const: CH_WD_EXP - - const: VBUS_CH_DROP_END + minItems: 9 + maxItems: 14 io-channels: minItems: 2 @@ -74,6 +63,53 @@ required: - io-channels - io-channel-names +allOf: + - $ref: power-supply.yaml# + - if: + properties: + compatible: + contains: + const: stericsson,ab8505-charger + then: + properties: + interrupts: + minItems: 9 + maxItems: 9 + + interrupt-names: + items: + - const: VBUS_DET_F + - const: VBUS_DET_R + - const: USB_LINK_STATUS + - const: USB_CH_TH_PROT_R + - const: USB_CH_TH_PROT_F + - const: USB_CHARGER_NOT_OKR + - const: VBUS_OVV + - const: CH_WD_EXP + - const: VBUS_CH_DROP_END + else: + properties: + interrupts: + minItems: 14 + maxItems: 14 + + interrupt-names: + items: + - const: MAIN_CH_UNPLUG_DET + - const: MAIN_CHARGE_PLUG_DET + - const: MAIN_EXT_CH_NOT_OK + - const: MAIN_CH_TH_PROT_R + - const: MAIN_CH_TH_PROT_F + - const: VBUS_DET_F + - const: VBUS_DET_R + - const: USB_LINK_STATUS + - const: USB_CH_TH_PROT_R + - const: USB_CH_TH_PROT_F + - const: USB_CHARGER_NOT_OKR + - const: VBUS_OVV + - const: CH_WD_EXP + - const: VBUS_CH_DROP_END + additionalProperties: false examples: From 6bd80f22c764fe8c56c62c118387f86a3ca98cff Mon Sep 17 00:00:00 2001 From: Maurizio Casciano Date: Fri, 25 Sep 2026 00:51:52 +0200 Subject: [PATCH 30/77] power: supply: core: Honor supplied-from with CONFIG_OF=y The supplied-from device property is the name-based counterpart to firmware-node power-supplies references. It was added for non-DT platforms, but its parser is compiled only when CONFIG_OF is disabled. CONFIG_OF is a global kernel option, so x86 systems commonly enable it even when individual power supplies are described by software nodes. Consequently, these consumers never populate supplied_from and supplier notifications do not reach their external_power_changed() callbacks. On a Lenovo Yoga Book YB1-X91L, ftrace showed the Whiskey Cove supplier notification running without invoking the BQ25892 callback, leaving the input current limit at its boot-time value. Use a single power_supply_check_supplies() implementation for all configurations. Preserve firmware-node power-supplies references as the preferred firmware description and use the name-based supplied-from property only when no references are specified. Continue to honor an explicitly provided supplied_from list first and propagate errors from specified firmware references. With the fix, ftrace shows the BQ25892 callback on hotplug. A boot-offline test changes its input current limit from 500 mA to 2 A. Fixes: 58a36bb06891 ("power: supply: core: Add support for supplied-from device-property") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Maurizio Casciano Link: https://patch.msgid.link/20260924225152.985489-1-mauriziocasciano7@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 84 ++++++++++++------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 00d8bc98d588..c097601ac549 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -190,7 +190,35 @@ static void power_supply_deferred_register_work(struct work_struct *work) device_unlock(psy->dev.parent); } -#ifdef CONFIG_OF +static int power_supply_check_supplies_by_name(struct power_supply *psy) +{ + struct device *parent = psy->dev.parent; + int nval, ret; + + if (!parent) + return 0; + + nval = device_property_string_array_count(parent, "supplied-from"); + if (nval <= 0) + return 0; + + psy->supplied_from = devm_kmalloc_array(&psy->dev, nval, + sizeof(*psy->supplied_from), + GFP_KERNEL); + if (!psy->supplied_from) + return -ENOMEM; + + ret = device_property_read_string_array(parent, "supplied-from", + (const char **)psy->supplied_from, + nval); + if (ret < 0) + return ret; + + psy->num_supplies = nval; + + return 0; +} + static int __power_supply_populate_supplied_from(struct power_supply *epsy, void *data) { @@ -262,22 +290,21 @@ static int power_supply_find_supply_from_fwnode(struct fwnode_handle *supply_nod static int power_supply_check_supplies(struct power_supply *psy) { struct fwnode_handle *np; - int cnt = 0; + int cnt, ret; /* If there is already a list honor it */ if (psy->supplied_from && psy->num_supplies > 0) return 0; - /* No device node found, nothing to do */ - if (!psy->dev.fwnode) - return 0; + for (cnt = 0; psy->dev.fwnode; cnt++) { + np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt); + if (IS_ERR(np)) { + ret = PTR_ERR(np); + if (ret != -ENOENT) + return ret; - do { - int ret; - - np = fwnode_find_reference(psy->dev.fwnode, "power-supplies", cnt++); - if (IS_ERR(np)) break; + } ret = power_supply_find_supply_from_fwnode(np); fwnode_handle_put(np); @@ -286,48 +313,21 @@ static int power_supply_check_supplies(struct power_supply *psy) dev_dbg(&psy->dev, "Failed to find supply!\n"); return ret; } - } while (!IS_ERR(np)); + } - /* Missing valid "power-supplies" entries */ - if (cnt == 1) - return 0; + /* Fall back to the name-based property if no references were specified. */ + if (!cnt) + return power_supply_check_supplies_by_name(psy); /* All supplies found, allocate char * array for filling */ psy->supplied_from = devm_kcalloc(&psy->dev, - cnt - 1, sizeof(*psy->supplied_from), + cnt, sizeof(*psy->supplied_from), GFP_KERNEL); if (!psy->supplied_from) return -ENOMEM; return power_supply_populate_supplied_from(psy); } -#else -static int power_supply_check_supplies(struct power_supply *psy) -{ - int nval, ret; - - if (!psy->dev.parent) - return 0; - - nval = device_property_string_array_count(psy->dev.parent, "supplied-from"); - if (nval <= 0) - return 0; - - psy->supplied_from = devm_kmalloc_array(&psy->dev, nval, - sizeof(char *), GFP_KERNEL); - if (!psy->supplied_from) - return -ENOMEM; - - ret = device_property_read_string_array(psy->dev.parent, - "supplied-from", (const char **)psy->supplied_from, nval); - if (ret < 0) - return ret; - - psy->num_supplies = nval; - - return 0; -} -#endif struct psy_am_i_supplied_data { struct power_supply *psy; From ab2f870a67f547d3a6a55f5557cb7aecffb0ecde Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Wed, 23 Sep 2026 22:15:35 -0500 Subject: [PATCH 31/77] dt-bindings: power: supply: battery: allow 101 ocv-capacity points ocv-capacity-table-N is capped at 100 points, while the capacity percent in each point may be anything from 0 to 100. A table with a point for every percent, both ends included, needs 101 and does not fit. Raise the cap to 101. power_supply_get_battery_info() sizes each table from the property, so no kernel change is needed. Assisted-by: LLM Signed-off-by: Ryan Brue Acked-by: Conor Dooley Link: https://patch.msgid.link/20260923-rbrue-suez-upstreaming-battery-ocv-table-128-v2-1-b827406d768d@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/battery.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/power/supply/battery.yaml b/Documentation/devicetree/bindings/power/supply/battery.yaml index 8ebf05d9497c..9575ee1491bb 100644 --- a/Documentation/devicetree/bindings/power/supply/battery.yaml +++ b/Documentation/devicetree/bindings/power/supply/battery.yaml @@ -154,7 +154,8 @@ patternProperties: of the battery and corresponding battery capacity percent, which is used to look up battery capacity according to current OCV value. And the open circuit voltage unit is microvolt. - maxItems: 100 + # One point for each capacity percent from 0 to 100 + maxItems: 101 items: items: - description: open circuit voltage (OCV) in microvolts From f7c62ebfd9127b12a1e39f1cc7f3131cb3065886 Mon Sep 17 00:00:00 2001 From: Liviu Nicoara Date: Sun, 20 Sep 2026 15:34:02 -0400 Subject: [PATCH 32/77] power: supply: qcom_battmgr: Report capacity on X1E80100 Commit 3f87baacea4d ("power: supply: qcom_battmgr: Report battery capacity") added POWER_SUPPLY_PROP_CAPACITY to the SC8280XP battery properties, computed from the firmware's charge and last-full values. When commit cc3e883a0625 ("power: supply: qcom_battmgr: Add charge control support") later gave X1E80100 a property table of its own, the table was copied without the capacity property, so X1E80100 machines expose energy_now and energy_full but no capacity. The percentage is already computed for this variant in the shared SC8280XP callback; only the property list is missing it. Add it, in the same place it sits in the SC8280XP table. Tested on a Dell XPS 13 9345: capacity reads 94 with energy_now 52640000 and energy_full 55540000. Fixes: cc3e883a0625 ("power: supply: qcom_battmgr: Add charge control support") Signed-off-by: Liviu Nicoara Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260920193402.2402-1-lnicoara@thinkoid.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 7716eb9e1aff..a7237ba09be1 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -830,6 +830,7 @@ static const enum power_supply_property x1e80100_bat_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, From c9ed44ab230f4359cdb3a72263c65daf31bd8528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 21 Sep 2026 11:41:47 +0200 Subject: [PATCH 33/77] power: supply: axp20x*: Drop check for disabled devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 6b5c350648b8 ("mfd: mfd-core: Honour Device Tree's request to disable a child-device") the mfd core checks for disabled devices, so there is no use in doing that again in the mfd child driver. Drop the check in the two power drivers. Signed-off-by: Uwe Kleine-König Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/c17a6c915abdb99612a52d2bb8cff78050f6b0a6.1789983244.git.ukleinek@debian.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_ac_power.c | 3 --- drivers/power/supply/axp20x_battery.c | 3 --- drivers/power/supply/axp20x_usb_power.c | 3 --- 3 files changed, 9 deletions(-) diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index d9892b351621..46889567bc18 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -326,9 +326,6 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) const struct axp_data *axp_data; int i, irq, ret; - if (!of_device_is_available(pdev->dev.of_node)) - return -ENODEV; - if (!axp20x) { dev_err(&pdev->dev, "Parent drvdata not set\n"); return -EINVAL; diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index ee8701a6e907..c119014b319a 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -1097,9 +1097,6 @@ static int axp20x_power_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; int ret; - if (!of_device_is_available(pdev->dev.of_node)) - return -ENODEV; - axp20x_batt = devm_kzalloc(&pdev->dev, sizeof(*axp20x_batt), GFP_KERNEL); if (!axp20x_batt) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index e951b2b64b25..463f9fe35cec 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -924,9 +924,6 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) const struct axp_data *axp_data; int i, irq, ret; - if (!of_device_is_available(pdev->dev.of_node)) - return -ENODEV; - if (!axp20x) { dev_err(&pdev->dev, "Parent drvdata not set\n"); return -EINVAL; From da54acbc2ff2470c49259a91a5f48c4b0cce09d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 21 Sep 2026 11:41:48 +0200 Subject: [PATCH 34/77] power: supply: axp20x_usb: Introduce a helper variable for &pdev->dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the next commit adding several new usages of &pdev->dev, a helper variable is well justified. To not clutter that commit, split out the introduction of the helper out in this separate change to simplify review. Signed-off-by: Uwe Kleine-König Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/f0e6c7ff8dce9f1a0903d83ff3ef782a18cd63d2.1789983244.git.ukleinek@debian.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 463f9fe35cec..7ee3ec49d695 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -918,70 +918,70 @@ static void axp20x_usb_power_parse_dt(struct device *dev, static int axp20x_usb_power_probe(struct platform_device *pdev) { - struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct axp20x_dev *axp20x = dev_get_drvdata(dev->parent); struct power_supply_config psy_cfg = {}; struct axp20x_usb_power *power; const struct axp_data *axp_data; int i, irq, ret; if (!axp20x) { - dev_err(&pdev->dev, "Parent drvdata not set\n"); + dev_err(dev, "Parent drvdata not set\n"); return -EINVAL; } - axp_data = of_device_get_match_data(&pdev->dev); + axp_data = of_device_get_match_data(dev); - power = devm_kzalloc(&pdev->dev, - struct_size(power, irqs, axp_data->num_irq_names), + power = devm_kzalloc(dev, struct_size(power, irqs, axp_data->num_irq_names), GFP_KERNEL); if (!power) return -ENOMEM; platform_set_drvdata(pdev, power); - power->dev = &pdev->dev; + power->dev = dev; power->axp_data = axp_data; power->regmap = axp20x->regmap; power->num_irqs = axp_data->num_irq_names; - power->curr_lim_fld = devm_regmap_field_alloc(&pdev->dev, power->regmap, + power->curr_lim_fld = devm_regmap_field_alloc(dev, power->regmap, axp_data->curr_lim_fld); if (IS_ERR(power->curr_lim_fld)) return PTR_ERR(power->curr_lim_fld); - axp20x_usb_power_parse_dt(&pdev->dev, power); + axp20x_usb_power_parse_dt(dev, power); - ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + ret = axp20x_regmap_field_alloc_optional(dev, power->regmap, axp_data->vbus_valid_bit, &power->vbus_valid_bit); if (ret) return ret; - ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + ret = axp20x_regmap_field_alloc_optional(dev, power->regmap, axp_data->vbus_mon_bit, &power->vbus_mon_bit); if (ret) return ret; - ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + ret = axp20x_regmap_field_alloc_optional(dev, power->regmap, axp_data->usb_bc_en_bit, &power->usb_bc_en_bit); if (ret) return ret; - ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + ret = axp20x_regmap_field_alloc_optional(dev, power->regmap, axp_data->usb_bc_det_fld, &power->usb_bc_det_fld); if (ret) return ret; - ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, + ret = axp20x_regmap_field_alloc_optional(dev, power->regmap, axp_data->vbus_disable_bit, &power->vbus_disable_bit); if (ret) return ret; - ret = devm_delayed_work_autocancel(&pdev->dev, &power->vbus_detect, + ret = devm_delayed_work_autocancel(dev, &power->vbus_detect, axp_data->axp20x_read_vbus); if (ret) return ret; @@ -1008,10 +1008,10 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return ret; } - psy_cfg.fwnode = dev_fwnode(&pdev->dev); + psy_cfg.fwnode = dev_fwnode(dev); psy_cfg.drv_data = power; - power->supply = devm_power_supply_register(&pdev->dev, + power->supply = devm_power_supply_register(dev, axp_data->power_desc, &psy_cfg); if (IS_ERR(power->supply)) @@ -1024,7 +1024,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return irq; power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); - ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], + ret = devm_request_any_context_irq(dev, power->irqs[i], axp20x_usb_power_irq, 0, DRVNAME, power); if (ret < 0) From f945606a8cb34c97d644ed487282f96641b05185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 21 Sep 2026 11:41:49 +0200 Subject: [PATCH 35/77] power: supply: axp20x_usb: Improve probe error reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't be silent about probe fail issues. Just having axp20x-usb-power-supply in /sys/kernel/debug/devices_deferred without a reason is making it harder than necessary to debug the actual issue. So be a bit more cooperative and add an error message for the typical failure points. Exit paths where the error is -ENOMEM and register write failures are skipped, these should be quite obvious even without a dedicated error message. Signed-off-by: Uwe Kleine-König Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/b699f8251afed736af454a981630926e45eb7da7.1789983244.git.ukleinek@debian.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 7ee3ec49d695..7625366104bc 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -925,10 +925,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) const struct axp_data *axp_data; int i, irq, ret; - if (!axp20x) { - dev_err(dev, "Parent drvdata not set\n"); - return -EINVAL; - } + if (!axp20x) + return dev_err_probe(dev, -EINVAL, "Parent drvdata not set\n"); axp_data = of_device_get_match_data(dev); @@ -998,7 +996,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ret = axp_data->axp20x_cfg_adc_reg(power); if (ret) - return ret; + return dev_err_probe(dev, ret, + "Failed to initialize for %s usage\n", + IS_ENABLED(CONFIG_AXP20X_ADC) ? "iio" : "adc"); } if (power->usb_bc_en_bit) { @@ -1015,12 +1015,14 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) axp_data->power_desc, &psy_cfg); if (IS_ERR(power->supply)) - return PTR_ERR(power->supply); + return dev_err_probe(dev, PTR_ERR(power->supply), + "Failed to register power supply\n"); /* Request irqs after registering, as irqs may trigger immediately */ for (i = 0; i < axp_data->num_irq_names; i++) { irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); if (irq < 0) + /* platform_get_irq_byname() already produced an error message */ return irq; power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); @@ -1028,6 +1030,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) axp20x_usb_power_irq, 0, DRVNAME, power); if (ret < 0) + /* devm_request_any_context_irq() already produced an error message */ return ret; } From 6415ce7204af0e282f32c5ca69953d4224ac49fb Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:13:10 -0700 Subject: [PATCH 36/77] power: supply: qcom_smbx: Remove const from desc allocation type In preparation for making the devm_kmalloc family of allocators type aware, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (Before, the allocator would always return "void *", which can be implicitly cast to any pointer type.) The assigned type is "struct power_supply_desc *", but the converted allocation type would be "const struct power_supply_desc *", as the size was taken from "smb_psy_desc", which is a "static const struct power_supply_desc". As there is no general way to remove const qualifiers, take the size from the assignment target instead. No change in allocation size results. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/power/supply/qcom_smbx.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260917211309.i.765-kees@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index f5557b8cc92a..d02027e7f90d 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -1005,7 +1005,7 @@ static int smb_probe(struct platform_device *pdev) supply_config.drv_data = chip; supply_config.fwnode = dev_fwnode(&pdev->dev); - desc = devm_kzalloc(chip->dev, sizeof(smb_psy_desc), GFP_KERNEL); + desc = devm_kzalloc(chip->dev, sizeof(*desc), GFP_KERNEL); if (!desc) return -ENOMEM; memcpy(desc, &smb_psy_desc, sizeof(smb_psy_desc)); From 5526d100dd4785bb41793fb758defe24e14f61ab Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 2 Oct 2026 16:02:54 +0400 Subject: [PATCH 37/77] dt-bindings: power: supply: bq27xxx: add BQ28Z620 Add a compatible for the TI BQ28Z620, a firmware derivative of the BQ28Z610 that keeps its standard command set but drops the Battery Trip Point feature and relocates much of the data flash as a result. Keep it standalone rather than falling back to ti,bq28z610, as software programming the BQ28Z610 data flash layout would corrupt the configuration of a BQ28Z620. Link: https://www.ti.com/product/BQ28Z620 Signed-off-by: Alexey Charkov Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20261002-bq28z620-v1-1-bdcc10d42519@flipper.net Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/supply/bq27xxx.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml index 79d1a3c8c685..be0f1f7292c8 100644 --- a/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml +++ b/Documentation/devicetree/bindings/power/supply/bq27xxx.yaml @@ -49,6 +49,7 @@ properties: - ti,bq27z561 - ti,bq27z746 - ti,bq28z610 + - ti,bq28z620 - ti,bq34z100 - ti,bq78z100 @@ -93,6 +94,7 @@ allOf: - ti,bq27545 - ti,bq27411 - ti,bq28z610 + - ti,bq28z620 - ti,bq34z100 - ti,bq78z100 then: From 5d32ee3bff8bc943095d04e801e1b45f1c695753 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Fri, 2 Oct 2026 16:02:55 +0400 Subject: [PATCH 38/77] power: supply: bq27xxx: add support for BQ28Z620 Match the BQ28Z620 under its own chip ID so that code touching its relocated data flash can tell it apart from the BQ28Z610. Reuse the BQ28Z610 register map and properties, as the standard commands the driver reads are unchanged. Keep the 10 mW scaling of AveragePower() even though the BQ28Z620 TRM lists it in mW: readings on hardware match V * I only in 10 mW units. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20261002-bq28z620-v1-2-bdcc10d42519@flipper.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 6 ++++++ drivers/power/supply/bq27xxx_battery_i2c.c | 2 ++ include/linux/power/bq27xxx_battery.h | 1 + 3 files changed, 9 insertions(+) diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 17f5b58609be..09272249b4f7 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -39,6 +39,7 @@ * https://www.ti.com/product/bq27z561 * https://www.ti.com/product/bq27z746 * https://www.ti.com/product/bq28z610 + * https://www.ti.com/product/bq28z620 * https://www.ti.com/product/bq34z100-g1 * https://www.ti.com/product/bq78z100 */ @@ -525,6 +526,7 @@ static u8 [BQ27XXX_REG_AP] = 0x22, BQ27XXX_DM_REG_ROWS, }, +#define bq28z620_regs bq28z610_regs bq34z100_regs[BQ27XXX_REG_MAX] = { [BQ27XXX_REG_CTRL] = 0x00, [BQ27XXX_REG_TEMP] = 0x0c, @@ -861,6 +863,8 @@ static enum power_supply_property bq28z610_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +#define bq28z620_props bq28z610_props + static enum power_supply_property bq34z100_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, @@ -1001,6 +1005,7 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = { #define bq27z561_dm_regs NULL #define bq27z746_dm_regs NULL #define bq28z610_dm_regs NULL +#define bq28z620_dm_regs NULL #define bq34z100_dm_regs NULL #define bq78z100_dm_regs NULL @@ -1060,6 +1065,7 @@ static struct { [BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS), [BQ27Z746] = BQ27XXX_DATA(bq27z746, 0 , BQ27Z561_O_BITS), [BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS), + [BQ28Z620] = BQ27XXX_DATA(bq28z620, 0 , BQ27Z561_O_BITS), [BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \ BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM), [BQ78Z100] = BQ27XXX_DATA(bq78z100, 0 , BQ27Z561_O_BITS), diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index 2a6b4534cb28..715ce8b6e401 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -255,6 +255,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = { { .name = "bq27z561", .driver_data = BQ27Z561 }, { .name = "bq27z746", .driver_data = BQ27Z746 }, { .name = "bq28z610", .driver_data = BQ28Z610 }, + { .name = "bq28z620", .driver_data = BQ28Z620 }, { .name = "bq34z100", .driver_data = BQ34Z100 }, { .name = "bq78z100", .driver_data = BQ78Z100 }, { } @@ -293,6 +294,7 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { { .compatible = "ti,bq27z561" }, { .compatible = "ti,bq27z746" }, { .compatible = "ti,bq28z610" }, + { .compatible = "ti,bq28z620" }, { .compatible = "ti,bq34z100" }, { .compatible = "ti,bq78z100" }, {}, diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index 0b833c4bb583..731ea837aac3 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -35,6 +35,7 @@ enum bq27xxx_chip { BQ27Z561, BQ27Z746, BQ28Z610, + BQ28Z620, BQ34Z100, BQ78Z100, }; From 09a29213bf15630e85a375478832b074b2cfc5c8 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 14:21:27 +0000 Subject: [PATCH 39/77] power: supply: ltc2941: Fix of_node reference leak in ltc294x_i2c_probe() ltc294x_i2c_probe() takes a reference on the device node with of_node_get(), but never drops it, so the reference leaks on every return path, including the success path. The node is only used to read the battery properties and to set info->supply_desc.name, so release it after its last use and also on the lltc,resistor-sense error path. Fixes: 085bc24d1553 ("Add LTC2941/LTC2943 Battery Gauge Driver") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917142127.2156568-1-vulab@iscas.ac.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc2941-battery-gauge.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index d1be314f36a0..6ebb555f203d 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -464,9 +464,11 @@ static int ltc294x_i2c_probe(struct i2c_client *client) /* r_sense can be negative, when sense+ is connected to the battery * instead of the sense-. This results in reversed measurements. */ ret = of_property_read_s32(np, "lltc,resistor-sense", &r_sense); - if (ret < 0) + if (ret < 0) { + of_node_put(np); return dev_err_probe(&client->dev, ret, "Could not find lltc,resistor-sense in devicetree\n"); + } info->r_sense = r_sense; ret = of_property_read_u32(np, "lltc,prescaler-exponent", @@ -477,6 +479,8 @@ static int ltc294x_i2c_probe(struct i2c_client *client) prescaler_exp = LTC2941_MAX_PRESCALER_EXP; } + of_node_put(np); + if (info->id == LTC2943_ID) { if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP) prescaler_exp = LTC2943_MAX_PRESCALER_EXP; From 1a64071bb30d97b75a1029943b6a3481098b254b Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 10 Sep 2026 18:32:59 +0400 Subject: [PATCH 40/77] power: supply: bq257xx: Use psy directly instead of driver data bq257xx_external_power_changed() is handed the power supply it is called for, but passes pdata->charger to power_supply_am_i_supplied(). Those are not equivalent during probe or teardown, and a supplier's changed_work can reach power_supply_external_power_changed() when pdata->charger doesn't point anywhere meaningful (e.g. freed memory during teardown). Use the psy argument, which is valid whenever the callback runs. Fixes: 1cc017b7f9c7 ("power: supply: bq257xx: Add support for BQ257XX charger") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260910-bq257xx-init-v3-2-4e219a1a04a5@flipper.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq257xx_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index b735bb02a8e0..ab3519454193 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -1055,7 +1055,7 @@ static void bq257xx_external_power_changed(struct power_supply *psy) pdata->chip->bq257xx_get_state(pdata); - pdata->supplied = power_supply_am_i_supplied(pdata->charger); + pdata->supplied = power_supply_am_i_supplied(psy); if (pdata->supplied < 0) return; From 455e49c6700c8111d492923952a1fe3b98da0195 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 10 Sep 2026 18:33:00 +0400 Subject: [PATCH 41/77] power: supply: bq257xx: Convert parameters setup to the new .init callback Switch the battery parsing and hardware initialization from happening inside the probe routine (after the power supply is registered) to the new .init callback which gets run during the registration, thus avoiding the exposure of a not-yet-fully-configured power supply to the rest of the system. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260910-bq257xx-init-v3-3-4e219a1a04a5@flipper.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq257xx_charger.c | 122 ++++++++++++------------- 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index ab3519454193..2177cee29a4f 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -73,6 +73,7 @@ struct bq257xx_chg { const struct bq257xx_chip_info *chip; struct bq257xx_device *bq; struct power_supply *charger; + struct device *dev; bool online; bool charging; bool fast_charge; @@ -1112,6 +1113,54 @@ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) return IRQ_HANDLED; } +/** + * bq257xx_charger_init() - Initialization for charger power supply device + * @psy: power supply device + * + * Set device parameters during registration, before the power supply becomes + * accessible to the broader system. + * + * Return: Returns 0 on success or error code on failure. + */ +static int bq257xx_charger_init(struct power_supply *psy) +{ + struct bq257xx_chg *pdata = power_supply_get_drvdata(psy); + struct power_supply_battery_info *bat_info; + struct device *dev = pdata->dev; + int ret; + + ret = power_supply_get_battery_info(psy, &bat_info); + if (ret) + return dev_err_probe(dev, ret, + "Unable to get battery info\n"); + + if ((bat_info->voltage_min_design_uv <= 0) || + (bat_info->constant_charge_voltage_max_uv <= 0) || + (bat_info->constant_charge_current_max_ua <= 0)) { + power_supply_put_battery_info(psy, bat_info); + return dev_err_probe(dev, -EINVAL, + "Required bat info missing or invalid\n"); + } + + pdata->vsys_min = bat_info->voltage_min_design_uv; + pdata->vbat_max = bat_info->constant_charge_voltage_max_uv; + pdata->ichg_max = bat_info->constant_charge_current_max_ua; + + power_supply_put_battery_info(psy, bat_info); + + ret = device_property_read_u32(dev, + "input-current-limit-microamp", + &pdata->iindpm_max); + if (ret) + pdata->iindpm_max = pdata->chip->default_iindpm_uA; + + ret = pdata->chip->bq257xx_hw_init(pdata); + if (ret) + return dev_err_probe(dev, ret, "Cannot initialize the charger\n"); + + return 0; +} + static const struct power_supply_desc bq257xx_power_supply_desc = { .name = "bq257xx-charger", .type = POWER_SUPPLY_TYPE_USB, @@ -1126,6 +1175,7 @@ static const struct power_supply_desc bq257xx_power_supply_desc = { .set_property = bq257xx_set_charger_property, .property_is_writeable = bq257xx_property_is_writeable, .external_power_changed = bq257xx_external_power_changed, + .init = bq257xx_charger_init, }; static const struct bq257xx_chip_info bq25703_chip_info = { @@ -1160,58 +1210,12 @@ static const struct bq257xx_chip_info bq25792_chip_info = { .bq257xx_get_min_vsys = &bq25792_get_min_vsys, }; -/** - * bq257xx_parse_dt() - Parse the device tree for required properties - * @pdata: driver platform data - * @psy_cfg: power supply config data - * @dev: device struct - * - * Read the device tree to identify the minimum system voltage, the - * maximum charge current, the maximum charge voltage, and the maximum - * input current. - * - * Return: Returns 0 on success or error code on error. - */ -static int bq257xx_parse_dt(struct bq257xx_chg *pdata, - struct power_supply_config *psy_cfg, struct device *dev) -{ - struct power_supply_battery_info *bat_info; - int ret; - - ret = power_supply_get_battery_info(pdata->charger, - &bat_info); - if (ret) - return dev_err_probe(dev, ret, - "Unable to get battery info\n"); - - if ((bat_info->voltage_min_design_uv <= 0) || - (bat_info->constant_charge_voltage_max_uv <= 0) || - (bat_info->constant_charge_current_max_ua <= 0)) - return dev_err_probe(dev, -EINVAL, - "Required bat info missing or invalid\n"); - - pdata->vsys_min = bat_info->voltage_min_design_uv; - pdata->vbat_max = bat_info->constant_charge_voltage_max_uv; - pdata->ichg_max = bat_info->constant_charge_current_max_ua; - - power_supply_put_battery_info(pdata->charger, bat_info); - - ret = device_property_read_u32(dev, - "input-current-limit-microamp", - &pdata->iindpm_max); - if (ret) - pdata->iindpm_max = pdata->chip->default_iindpm_uA; - - return 0; -} - /** * bq257xx_charger_probe() - Probe routine for charger platform device * @pdev: platform device * * Probe the charger device, allocate driver data structure, select the * appropriate chip-specific function pointers, register the power supply, - * parse device tree properties for battery limits, initialize hardware, * and set up the interrupt handler if available. * * Return: Returns 0 on success or error code on failure. @@ -1222,7 +1226,6 @@ static int bq257xx_charger_probe(struct platform_device *pdev) struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent); struct bq257xx_chg *pdata; struct power_supply_config psy_cfg = { }; - int ret; device_set_of_node_from_dev(dev, pdev->dev.parent); @@ -1231,6 +1234,7 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return -ENOMEM; pdata->bq = bq; + pdata->dev = dev; switch (bq->type) { case BQ25703A: @@ -1255,26 +1259,16 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(pdata->charger), "Power supply register charger failed\n"); - ret = bq257xx_parse_dt(pdata, &psy_cfg, dev); - if (ret) - return ret; - - ret = pdata->chip->bq257xx_hw_init(pdata); - if (ret) - return dev_err_probe(dev, ret, "Cannot initialize the charger\n"); - - platform_set_drvdata(pdev, pdata); - if (bq->client->irq) { - ret = devm_request_threaded_irq(dev, bq->client->irq, NULL, - bq257xx_irq_handler_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - dev_name(&bq->client->dev), pdata); + return devm_request_threaded_irq(dev, bq->client->irq, NULL, + bq257xx_irq_handler_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(&bq->client->dev), pdata); } - return ret; + return 0; } /** From 23a00d8fb0452a4b182f4fea01e244f47758f4ee Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 21 Aug 2026 17:39:49 +0200 Subject: [PATCH 42/77] power: supply: Fix power limit documentation The documentation for the input current and voltage limits recommends using those properties for problems that can be solved using a power limit. This contradicts the recommendation to prefer input_power_limit for such cases. Similarly, the input_power_limit documentation recommends using the current or voltage limits for problems that can be solved with a power limit, which conveys the opposite of the intended meaning. Fix the wording to clarify that input_power_limit should be preferred whenever a power limit can address the problem, while current or voltage limits should be used when it cannot. Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260821153949.2914201-1-daniel.lezcano@oss.qualcomm.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 2cf3f53a652b..671786d4600d 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -629,8 +629,8 @@ Description: supply. Normally this is configured based on the type of connection made (e.g. A configured SDP should output a maximum of 500mA so the input current limit is set to the same value). - Use preferably input_power_limit, and for problems that can be - solved using power limit use input_current_limit. + Use preferably input_power_limit, and for problems that can not + be solved using power limit use input_current_limit. Access: Read, Write @@ -647,8 +647,8 @@ Description: input power to 5V when the screen is on to meet Google's skin temperature targets). Note that this feature should not be used for safety critical things. - Use preferably input_power_limit, and for problems that can be - solved using power limit use input_voltage_limit. + Use preferably input_power_limit, and for problems that can not + be solved using power limit use input_voltage_limit. Access: Read, Write @@ -662,7 +662,7 @@ Description: in the supply. Normally this is configured based on system-level knowledge or user input. Use preferably this feature to limit the incoming power and use current/voltage - limit only for problems that can be solved using power limit. + limit only for problems that can not be solved using power limit. Access: Read, Write From 2648e3d4ca08014eb6125d96acb177e767d7c807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 12:53:35 -0300 Subject: [PATCH 43/77] power: supply: return -ENOMEM on OCV table alloc failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit power_supply_get_battery_info() maps a failed kcalloc() for the OCV capacity table to -EINVAL. Return -ENOMEM instead, matching the resistance-temp-table allocation path in the same function. Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260831155339.181487-2-ivanrwcm25@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index c097601ac549..15dcbae82b3d 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -907,7 +907,7 @@ int power_supply_get_battery_info(struct power_supply *psy, u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); if (!propdata) { power_supply_put_battery_info(psy, info); - err = -EINVAL; + err = -ENOMEM; goto out_put_node; } err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen); From c996fa11ce8616aa3e8f5ebc34aa31a73dafac60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 12:53:36 -0300 Subject: [PATCH 44/77] power: supply: restore hwmon if extension update fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit power_supply_update_sysfs_and_hwmon() removes the existing hwmon group before recreating it. If recreation fails, register_extension() rolled back the extension but left hwmon absent. After tearing down the failed extension, best-effort recreate hwmon so the supply keeps its previous monitoring attributes when possible. Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260831155339.181487-3-ivanrwcm25@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 15dcbae82b3d..191cb50c2088 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1595,6 +1595,16 @@ int power_supply_register_extension(struct power_supply *psy, const struct power sysfs_hwmon_failed: power_supply_sysfs_remove_extension(psy, ext); + list_del(®->list_head); + kfree(reg); + /* + * update_sysfs_and_hwmon() may have already torn down hwmon before + * failing to recreate it. Recreate without the failed extension. + */ + if (power_supply_add_hwmon_sysfs(psy)) + dev_warn(&psy->dev, "failed to restore hwmon after extension error\n"); + return ret; + sysfs_add_failed: list_del(®->list_head); kfree(reg); From 420a1e2763b5df3ed90f92d97cf30fe43822bba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 12:53:37 -0300 Subject: [PATCH 45/77] docs: power: supply: fix power_supply_class.rst status, health and time_to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct STATUS/HEALTH references to POWER_SUPPLY_* enums in include/linux/power_supply.h instead of the nonexistent battery.h macros. Document TIME_TO_{EMPTY,FULL}_{NOW,AVG}, fix the "operatable" typo, and note that MODEL_NAME and MANUFACTURER already exist. Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260831155339.181487-4-ivanrwcm25@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/power/power_supply_class.rst | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Documentation/power/power_supply_class.rst b/Documentation/power/power_supply_class.rst index 6d11f8c594a0..0072b41d4d45 100644 --- a/Documentation/power/power_supply_class.rst +++ b/Documentation/power/power_supply_class.rst @@ -82,7 +82,8 @@ _NOW STATUS this attribute represents operating status (charging, full, discharging (i.e., powering a load), etc.). This corresponds to - `BATTERY_STATUS_*` values, as defined in battery.h. + `POWER_SUPPLY_STATUS_*` values, as defined in + include/linux/power_supply.h. CHARGE_TYPE batteries can typically charge at different rates. @@ -95,8 +96,8 @@ AUTHENTIC to the platform is authentic(1) or non-authentic(0). HEALTH - represents health of the battery. Values corresponds to - POWER_SUPPLY_HEALTH_*, defined in battery.h. + represents health of the battery. Values correspond to + POWER_SUPPLY_HEALTH_*, defined in include/linux/power_supply.h. VOLTAGE_OCV open circuit voltage of the battery. @@ -202,16 +203,17 @@ TEMP_AMBIENT_ALERT_MIN TEMP_AMBIENT_ALERT_MAX maximum ambient temperature alert. TEMP_MIN - minimum operatable temperature + minimum operable temperature TEMP_MAX - maximum operatable temperature + maximum operable temperature -TIME_TO_EMPTY +TIME_TO_EMPTY_NOW, TIME_TO_EMPTY_AVG seconds left for battery to be considered empty - (i.e., while battery powers a load) -TIME_TO_FULL + (i.e., while battery powers a load). *_NOW is a + momentary reading; *_AVG is averaged over a fixed period. +TIME_TO_FULL_NOW, TIME_TO_FULL_AVG seconds left for battery to be considered full - (i.e., while battery is charging) + (i.e., while battery is charging). Same now/avg distinction. Battery <-> external power supply interaction @@ -253,9 +255,6 @@ A: The attributes available currently are the ones currently provided by the drivers written. - Good candidates to add in future: model/part#, cycle_time, manufacturer, - etc. - Q: I have some very specific attribute (e.g., battery color). Should I add From 27049920d0cf0396242f517dfdf2694e7b8daa67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 12:53:39 -0300 Subject: [PATCH 46/77] power: supply: use tabs in power_supply_get_property_direct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The return statement was indented with spaces; use tabs to match kernel coding style and silence checkpatch. Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260831155339.181487-6-ivanrwcm25@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 191cb50c2088..d682989b18cb 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1442,7 +1442,7 @@ EXPORT_SYMBOL_GPL(power_supply_get_property); int power_supply_get_property_direct(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - return __power_supply_get_property(psy, psp, val, false); + return __power_supply_get_property(psy, psp, val, false); } EXPORT_SYMBOL_GPL(power_supply_get_property_direct); From 8761a4577c4fd489fb7af435f4b11ab6762dab66 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 13 Sep 2026 00:41:51 +0530 Subject: [PATCH 47/77] power: Use %pe to print error pointers symbolically Replace PTR_ERR() and %ld/%li with %pe and pass the original pointer directly to dev_info(), dev_Warn and dev_dbg(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Link: https://patch.msgid.link/20260912191156.185448-2-sumeet4linux@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/macsmc-reboot.c | 4 ++-- drivers/power/supply/cpcap-charger.c | 4 ++-- drivers/power/supply/intel_dc_ti_battery.c | 2 +- drivers/power/supply/qcom_smbx.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c index 9fc36fa68676..7b8460a55e9d 100644 --- a/drivers/power/reset/macsmc-reboot.c +++ b/drivers/power/reset/macsmc-reboot.c @@ -221,8 +221,8 @@ static int macsmc_reboot_probe(struct platform_device *pdev) if (IS_ERR(cell)) { if (PTR_ERR(cell) == -EPROBE_DEFER) return -EPROBE_DEFER; - dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n", - nvmem_names[i], PTR_ERR(cell)); + dev_warn(&pdev->dev, "Missing NVMEM cell %s (%pe)\n", + nvmem_names[i], cell); /* Non fatal, we'll deal with it */ cell = NULL; } diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 34a57dd5c3cf..d423a8bdc0c2 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -808,8 +808,8 @@ static void cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata) ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode", i, GPIOD_OUT_HIGH); if (IS_ERR(ddata->gpio[i])) { - dev_info(ddata->dev, "no mode change GPIO%i: %li\n", - i, PTR_ERR(ddata->gpio[i])); + dev_info(ddata->dev, "no mode change GPIO%i: %pe\n", + i, ddata->gpio[i]); ddata->gpio[i] = NULL; } } diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c index cb6fa8d88b43..5819df23a543 100644 --- a/drivers/power/supply/intel_dc_ti_battery.c +++ b/drivers/power/supply/intel_dc_ti_battery.c @@ -349,7 +349,7 @@ static int dc_ti_battery_probe(struct platform_device *pdev) chip->vbat_channel = devm_iio_channel_get(dev, "VBAT"); if (IS_ERR(chip->vbat_channel)) { - dev_dbg(dev, "devm_iio_channel_get() ret %ld\n", PTR_ERR(chip->vbat_channel)); + dev_dbg(dev, "devm_iio_channel_get() ret %pe\n", chip->vbat_channel); return dev_err_probe(dev, -EPROBE_DEFER, "Waiting for VBAT IIO channel\n"); } diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index d02027e7f90d..3951326f488f 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -618,7 +618,7 @@ static int smb_get_iio_chan(struct smb_chip *chip, struct iio_channel *chan, } if (IS_ERR(chan)) { - dev_err(chip->dev, "Failed to chan, err = %li", PTR_ERR(chan)); + dev_err(chip->dev, "Failed to chan, err = %pe", chan); return PTR_ERR(chan); } From df2914e2048556478980df4da89d16390c368e4d Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Tue, 15 Sep 2026 05:25:29 +0000 Subject: [PATCH 48/77] power: supply: max17042_battery: Make the fractional macros safe The fractional LSB macros expand to a plain division, so they only work when multiplied first and evaluate to 0 anywhere else. Take the value as a macro argument and apply it with mult_frac(), which properly handles it. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202606062322.TyvCPB3l-lkp@intel.com/ Assisted-by: LLM Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20260915052529.7-1-bbnpreetsingh@gmail.com [fixed commit message, dropped useless AI comment] Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index a53970af56cb..da4f89bb40ff 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -57,10 +58,11 @@ #define MAX17042_CURRENT_LSB 1562500ll /* 1.5625µV/Rsense */ #define MAX17042_CAPACITY_LSB 5000000ll /* 5.0µVH/Rsense */ -#define MAX17042_TIME_LSB 5625 / 1000 /* s */ -#define MAX17042_VOLTAGE_LSB 625 / 8 /* µV */ -#define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */ -#define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */ + +#define MAX17042_TIME_LSB(val) mult_frac(val, 5625, 1000) /* s */ +#define MAX17042_VOLTAGE_LSB(val) mult_frac(val, 625, 8) /* µV */ +#define MAX17042_RESISTANCE_LSB(val) mult_frac(val, 1, 4096) /* Ω */ +#define MAX17042_TEMPERATURE_LSB(val) mult_frac(val, 1, 256) /* °C */ #define MAX17055_DQACC_DIV 32 #define MAX17055_DPACC_FACTOR 44138 @@ -138,7 +140,7 @@ static int max17042_get_temperature(struct max17042_chip *chip, int *temp) *temp = sign_extend32(data, 15); /* The value is converted into deci-centigrade scale */ - *temp = *temp * 10 * MAX17042_TEMPERATURE_LSB; + *temp = MAX17042_TEMPERATURE_LSB(*temp * 10); return 0; } @@ -214,7 +216,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health) goto health_error; /* bits [0-3] unused */ - vavg = val * MAX17042_VOLTAGE_LSB; + vavg = MAX17042_VOLTAGE_LSB(val); /* Convert to millivolts */ vavg /= 1000; @@ -223,7 +225,7 @@ static int max17042_get_battery_health(struct max17042_chip *chip, int *health) goto health_error; /* bits [0-3] unused */ - vbatt = val * MAX17042_VOLTAGE_LSB; + vbatt = MAX17042_VOLTAGE_LSB(val); /* Convert to millivolts */ vbatt /= 1000; @@ -330,21 +332,21 @@ static int max17042_get_property(struct power_supply *psy, if (ret < 0) return ret; - val->intval = data * MAX17042_VOLTAGE_LSB; + val->intval = MAX17042_VOLTAGE_LSB(data); break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: ret = regmap_read(map, MAX17042_AvgVCELL, &data); if (ret < 0) return ret; - val->intval = data * MAX17042_VOLTAGE_LSB; + val->intval = MAX17042_VOLTAGE_LSB(data); break; case POWER_SUPPLY_PROP_VOLTAGE_OCV: ret = regmap_read(map, MAX17042_OCVInternal, &data); if (ret < 0) return ret; - val->intval = data * MAX17042_VOLTAGE_LSB; + val->intval = MAX17042_VOLTAGE_LSB(data); break; case POWER_SUPPLY_PROP_CAPACITY: if (chip->enable_current_sense) @@ -473,7 +475,7 @@ static int max17042_get_property(struct power_supply *psy, if (data == U16_MAX) return -ENODATA; - val->intval = data * MAX17042_TIME_LSB; + val->intval = MAX17042_TIME_LSB(data); break; case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055 && @@ -488,7 +490,7 @@ static int max17042_get_property(struct power_supply *psy, if (data == U16_MAX) return -ENODATA; - val->intval = data * MAX17042_TIME_LSB; + val->intval = MAX17042_TIME_LSB(data); break; default: return -EINVAL; From 16f506a13b58a55308449bbcbec89c817d619ca2 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:23 +0200 Subject: [PATCH 49/77] power: supply: ab8500_fg: Accept status from supplied power Commit dc77721ea4aa ("power: supply: ab8500: Set typing and props") changed the charging algorithm power supply type from battery to unknown so userspace would not mistake it for a second battery. The fuel gauge, however, processes status changes only from battery-type supplies. It therefore never learns that charging started and keeps running its discharge state machine. The power supply relationship already limits the callback to supplies naming the fuel gauge in supplied_to. Accept status from any such supply while retaining the battery type checks for battery-specific properties. The Samsung-GT-I8160-Codina and Samsung-GT-I8160_HD-Codina product trees keep the charging algorithm typed as a battery. The Samsung-GT-I8530-Gavini, Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle and Samsung-SGH-T599-Codina-TMO alternative fuel-gauge drivers instead pass cable state directly. Both approaches preserve the charging state handoff that was lost in mainline. Fixes: dc77721ea4aa ("power: supply: ab8500: Set typing and props") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-1-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 62 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index e6b5efdf5dc9..016ed35e90f9 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2205,42 +2205,36 @@ static int ab8500_fg_get_ext_psy_data(struct power_supply *ext, void *data) switch (prop) { case POWER_SUPPLY_PROP_STATUS: - switch (ext->desc->type) { - case POWER_SUPPLY_TYPE_BATTERY: - switch (ret.intval) { - case POWER_SUPPLY_STATUS_UNKNOWN: - case POWER_SUPPLY_STATUS_DISCHARGING: - case POWER_SUPPLY_STATUS_NOT_CHARGING: - if (!di->flags.charging) - break; - di->flags.charging = false; - di->flags.fully_charged = false; - if (di->bm->capacity_scaling) - ab8500_fg_update_cap_scalers(di); - queue_work(di->fg_wq, &di->fg_work); + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) break; - case POWER_SUPPLY_STATUS_FULL: - if (di->flags.fully_charged) - break; - di->flags.fully_charged = true; - di->flags.force_full = true; - /* Save current capacity as maximum */ - di->bat_cap.max_mah = di->bat_cap.mah; - queue_work(di->fg_wq, &di->fg_work); - break; - case POWER_SUPPLY_STATUS_CHARGING: - if (di->flags.charging && - !di->flags.fully_charged) - break; - di->flags.charging = true; - di->flags.fully_charged = false; - if (di->bm->capacity_scaling) - ab8500_fg_update_cap_scalers(di); - queue_work(di->fg_wq, &di->fg_work); - break; - } + di->flags.charging = false; + di->flags.fully_charged = false; + if (di->bm->capacity_scaling) + ab8500_fg_update_cap_scalers(di); + queue_work(di->fg_wq, &di->fg_work); break; - default: + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + di->flags.force_full = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging && + !di->flags.fully_charged) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + if (di->bm->capacity_scaling) + ab8500_fg_update_cap_scalers(di); + queue_work(di->fg_wq, &di->fg_work); break; } break; From 4d40dba1815d46c6ef9019ed80e3b998b0fc443d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:24 +0200 Subject: [PATCH 50/77] power: supply: ab8500_fg: Report sub-percent charge changes The fuel gauge updates bat_cap.mah from the coulomb counter but exposes bat_cap.prev_mah through CHARGE_NOW and ENERGY_NOW. It only refreshes prev_mah when the rounded integer capacity percentage changes, making the momentary properties appear frozen between percentage steps. Refresh prev_mah and notify userspace whenever the new charge remains within the currently accepted percentage. If a discharge estimate tries to cross upward into a higher percentage, the existing rejection still leaves both the reported percentage and charge unchanged. The AB8500 fuel-gauge implementations in the Samsung-GT-I8160-Codina, Samsung-GT-I8160_HD-Codina, Samsung-GT-I8530-Gavini, Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle and Samsung-SGH-T599-Codina-TMO product trees retain the same percentage-gated prev_mah update and contain no finer-grained reporting workaround. Fixes: 13151631b5bd ("ab8500-fg: A8500 fuel gauge driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-2-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 016ed35e90f9..c5eaa32c4a2d 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -1387,6 +1387,13 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) } } + /* Keep the charge properties current between percentage changes. */ + if (di->bat_cap.prev_percent == percent && + di->bat_cap.prev_mah != di->bat_cap.mah) { + di->bat_cap.prev_mah = di->bat_cap.mah; + changed = true; + } + if (changed) { if (di->bm->capacity_scaling) { di->bat_cap.cap_scale.scaled_cap = From b4d3dc743df09cf467271e1490963ed9699ddfa1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:25 +0200 Subject: [PATCH 51/77] power: supply: ab8500: Correct register definitions The charger LED PWM register encodes its duty cycle as (N + 1) / 256. The value for 252/256 is therefore 0xfb, not 0xbf. Both output-current codes 0xe and 0xf select 1.5 A, while 0x3f cannot fit in the four-bit field. Also correct the AB8505 BatCtrl bit 0 current source from 18 uA to 8 uA. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-3-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 5 ++--- drivers/power/supply/ab8500_btemp.c | 2 +- drivers/power/supply/ab8500_charger.c | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index 180a016b3662..b73fccdc76d2 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -151,8 +151,7 @@ #define CH_OP_CUR_LVL_0P9 0x08 #define CH_OP_CUR_LVL_1P4 0x0D #define CH_OP_CUR_LVL_1P5 0x0E -#define CH_OP_CUR_LVL_1P6 0x0F -#define CH_OP_CUR_LVL_2P 0x3F +#define CH_OP_CUR_LVL_1P5_MAX 0x0F /* BTEMP High thermal limits */ #define BTEMP_HIGH_TH_57_0 0x00 @@ -233,7 +232,7 @@ enum bup_vch_sel { /* BatCtrl Current Source Constants */ #define BAT_CTRL_7U_ENA 0x01 #define BAT_CTRL_20U_ENA 0x02 -#define BAT_CTRL_18U_ENA 0x01 +#define BAT_CTRL_8U_ENA 0x01 #define BAT_CTRL_16U_ENA 0x02 #define BAT_CTRL_CMP_ENA 0x04 #define FORCE_BAT_CTRL_CMP_HIGH 0x08 diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index 90a61d9db72c..d5c955176dd0 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -42,7 +42,7 @@ #define BTEMP_BATCTRL_CURR_SRC_20UA 20 #define BTEMP_BATCTRL_CURR_SRC_16UA 16 -#define BTEMP_BATCTRL_CURR_SRC_18UA 18 +#define BTEMP_BATCTRL_CURR_SRC_8UA 8 #define BTEMP_BATCTRL_CURR_SRC_60UA 60 #define BTEMP_BATCTRL_CURR_SRC_120UA 120 diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index ff38254d06ef..7e6afcf0ac7d 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -66,7 +66,7 @@ #define LED_INDICATOR_PWM_ENA 0x01 #define LED_INDICATOR_PWM_DIS 0x00 #define LED_IND_CUR_5MA 0x04 -#define LED_INDICATOR_PWM_DUTY_252_256 0xBF +#define LED_INDICATOR_PWM_DUTY_252_256 0xFB /* HW failure constants */ #define MAIN_CH_TH_PROT 0x02 @@ -3063,7 +3063,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, - CH_OP_CUR_LVL_1P6); + CH_OP_CUR_LVL_1P5_MAX); if (ret) { dev_err(di->dev, "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); From 0bc9adf42698de962e845a9355b53bdbee5b5895 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:26 +0200 Subject: [PATCH 52/77] power: supply: ab8500_fg: Drop nonexistent AB8505 controls The AB8505 RTC register map has no register at offset 0x15, but the fuel-gauge driver initializes and exposes that offset as a power-cut flag-time control. PcutCtlAndStatus bit 4, exposed as powercut_flag, is reserved as well. Remove both nonexistent controls and their internal configuration field so the driver no longer accesses reserved hardware locations. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-4-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500-bm.h | 3 - drivers/power/supply/ab8500_bmdata.c | 1 - drivers/power/supply/ab8500_fg.c | 85 ---------------------------- 3 files changed, 89 deletions(-) diff --git a/drivers/power/supply/ab8500-bm.h b/drivers/power/supply/ab8500-bm.h index b73fccdc76d2..209744c77c3d 100644 --- a/drivers/power/supply/ab8500-bm.h +++ b/drivers/power/supply/ab8500-bm.h @@ -245,7 +245,6 @@ enum bup_vch_sel { #define AB8505_RTC_PCUT_CTL_STATUS_REG 0x12 #define AB8505_RTC_PCUT_TIME_REG 0x13 #define AB8505_RTC_PCUT_MAX_TIME_REG 0x14 -#define AB8505_RTC_PCUT_FLAG_TIME_REG 0x15 #define AB8505_RTC_PCUT_RESTART_REG 0x16 #define AB8505_RTC_PCUT_DEBOUNCE_REG 0x17 @@ -286,7 +285,6 @@ struct ab8500_fg; * battery full while in maintenance, in per cent * @pcut_enable: Enable power cut feature in ab8505 * @pcut_max_time: Max time threshold - * @pcut_flag_time: Flagtime threshold * @pcut_max_restart: Max number of restarts * @pcut_debounce_time: Sets battery debounce time */ @@ -307,7 +305,6 @@ struct ab8500_fg_parameters { int maint_thres; bool pcut_enable; u8 pcut_max_time; - u8 pcut_flag_time; u8 pcut_max_restart; u8 pcut_debounce_time; }; diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index 19ed52852804..d7fd2d313ff1 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -98,7 +98,6 @@ static const struct ab8500_fg_parameters fg = { .user_cap_limit = 15, .pcut_enable = 1, .pcut_max_time = 127, - .pcut_flag_time = 112, .pcut_max_restart = 15, .pcut_debounce_time = 2, }; diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index c5eaa32c4a2d..70e29c9e8108 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2355,14 +2355,6 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) goto out; } - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time); - - if (ret) { - dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__); - goto out; - } - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart); @@ -2569,56 +2561,6 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di) return ret; } -static ssize_t ab8505_powercut_flagtime_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int ret; - u8 reg_value; - struct power_supply *psy = dev_to_psy(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n"); - goto fail; - } - - return sysfs_emit(buf, "%d\n", (reg_value & 0x7F)); - -fail: - return ret; -} - -static ssize_t ab8505_powercut_flagtime_write(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int ret; - int reg_value; - struct power_supply *psy = dev_to_psy(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - if (kstrtoint(buf, 10, ®_value)) - goto fail; - - if (reg_value > 0x7F) { - dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n"); - goto fail; - } - - ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value); - - if (ret < 0) - dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n"); - -fail: - return count; -} - static ssize_t ab8505_powercut_maxtime_read(struct device *dev, struct device_attribute *attr, char *buf) @@ -2815,30 +2757,6 @@ static ssize_t ab8505_powercut_write(struct device *dev, return count; } -static ssize_t ab8505_powercut_flag_read(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - - int ret; - u8 reg_value; - struct power_supply *psy = dev_to_psy(dev); - struct ab8500_fg *di = power_supply_get_drvdata(psy); - - ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, - AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); - - if (ret < 0) { - dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); - goto fail; - } - - return sysfs_emit(buf, "%d\n", ((reg_value & 0x10) >> 4)); - -fail: - return ret; -} - static ssize_t ab8505_powercut_debounce_read(struct device *dev, struct device_attribute *attr, char *buf) @@ -2913,8 +2831,6 @@ static ssize_t ab8505_powercut_enable_status_read(struct device *dev, } static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { - __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP), - ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write), __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP), ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write), __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP), @@ -2924,7 +2840,6 @@ static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { ab8505_powercut_restart_counter_read, NULL), __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP), ab8505_powercut_read, ab8505_powercut_write), - __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL), __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP), ab8505_powercut_debounce_read, ab8505_powercut_debounce_write), __ATTR(powercut_enable_status, S_IRUGO, From 95990cda8e42dda4bd3f1475d578323981bd8579 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:27 +0200 Subject: [PATCH 53/77] power: supply: ab8500_btemp: Fix event temperature reporting The event-aware path initializes its local temperature to zero and compares that zero against the thermal thresholds. Consequently some events report the measured temperature after it crosses the event threshold, while others always report the threshold. Start with the measured temperature, clamp it according to the active event, and convert to tenths of a degree only once. This path is used by AB8505 and later variants. Fixes: 1f855824757e ("ab8500-btemp: AB8500 battery temperature driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-5-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_btemp.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index d5c955176dd0..d8845fd2f084 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -468,39 +468,29 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di, */ static int ab8500_btemp_get_temp(struct ab8500_btemp *di) { - int temp = 0; + int temp = di->bat_temp; /* * The BTEMP events are not reliabe on AB8500 cut3.3 * and prior versions */ - if (is_ab8500_3p3_or_earlier(di->parent)) { - temp = di->bat_temp * 10; - } else { + if (!is_ab8500_3p3_or_earlier(di->parent)) { if (di->events.btemp_low) { if (temp > di->btemp_ranges.btemp_low_limit) - temp = di->btemp_ranges.btemp_low_limit * 10; - else - temp = di->bat_temp * 10; + temp = di->btemp_ranges.btemp_low_limit; } else if (di->events.btemp_high) { if (temp < di->btemp_ranges.btemp_high_limit) - temp = di->btemp_ranges.btemp_high_limit * 10; - else - temp = di->bat_temp * 10; + temp = di->btemp_ranges.btemp_high_limit; } else if (di->events.btemp_lowmed) { if (temp > di->btemp_ranges.btemp_med_limit) - temp = di->btemp_ranges.btemp_med_limit * 10; - else - temp = di->bat_temp * 10; + temp = di->btemp_ranges.btemp_med_limit; } else if (di->events.btemp_medhigh) { if (temp < di->btemp_ranges.btemp_med_limit) - temp = di->btemp_ranges.btemp_med_limit * 10; - else - temp = di->bat_temp * 10; - } else - temp = di->bat_temp * 10; + temp = di->btemp_ranges.btemp_med_limit; + } } - return temp; + + return temp * 10; } /** From 3b33eda4067494658d51db0792377fef98787e3f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:28 +0200 Subject: [PATCH 54/77] power: supply: ab8500: Preserve battery termination current The battery-info parser uses a negative value for an unspecified termination current. Testing the value as a boolean instead replaces every valid positive current from the battery description with 200 mA. Apply the default only when the property is unspecified so batteries retain their described charging termination current. Fixes: 9c20899da46b ("power: supply: ab8500: Standardize termination current") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-6-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_bmdata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c index d7fd2d313ff1..0f04893d6bf9 100644 --- a/drivers/power/supply/ab8500_bmdata.c +++ b/drivers/power/supply/ab8500_bmdata.c @@ -173,7 +173,7 @@ int ab8500_bm_of_probe(struct power_supply *psy, if (bi->constant_charge_voltage_max_uv < 0) bi->constant_charge_voltage_max_uv = 4100000; - if (bi->charge_term_current_ua) + if (bi->charge_term_current_ua < 0) /* Charging stops when we drop below this current */ bi->charge_term_current_ua = 200000; From 3e1d2517347f6bb72f74e63ec77d1634385fbf21 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:29 +0200 Subject: [PATCH 55/77] power: supply: ab8500_charger: Fix AC charger re-enable The AC check-enable callback passes the embedded USB charger object to ab8500_charger_ac_en(). That helper derives the parent with the AC charger offset, so the wrong object yields an invalid ab8500_charger pointer. Pass the AC charger object when re-enabling AC charging. Fixes: bc6e02871402 ("power: supply: ab8500: Standardize CV voltage") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-7-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 7e6afcf0ac7d..19959e48287c 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -1778,7 +1778,7 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, } /** - * ab8500_charger_ac_check_enable() - enable usb charging + * ab8500_charger_ac_check_enable() - enable AC charging * @charger: pointer to the ux500_charger structure * @vset_uv: charging voltage in microvolt * @iset_ua: charger output current in micrompere @@ -1817,7 +1817,7 @@ static int ab8500_charger_ac_check_enable(struct ux500_charger *charger, return ret; } - ret = ab8500_charger_ac_en(&di->usb_chg, true, vset_uv, iset_ua); + ret = ab8500_charger_ac_en(&di->ac_chg, true, vset_uv, iset_ua); if (ret < 0) { dev_err(di->dev, "failed to enable AC charger %d\n", __LINE__); From 2b697df2b6394613a3bb168adfa8a845ad7c83d9 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:30 +0200 Subject: [PATCH 56/77] power: supply: ab8500_charger: Decode AB8505 USB status AB8505 keeps USB link status in the same bit position as AB8500 but defines a different five-bit value table. Reusing the AB8500 enum treats several reserved AB8505 values as chargers and misidentifies the AB8505 charger-fault and DM-high states. Add an AB8505 decoder, reject reserved and non-charging states, and retain conservative current limits for supported charger types. Restrict the invalid-link workaround to AB8500 and stop processing when the status register cannot be read. Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-8-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 170 +++++++++++++++++++++----- 1 file changed, 142 insertions(+), 28 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 19959e48287c..4810dfbe948d 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -136,6 +136,33 @@ enum ab8500_charger_link_status { USB_STAT_ACA_DOCK_CHARGER, }; +/* UsbLink1Status register values used by AB8505 */ +enum ab8505_charger_link_status { + AB8505_USB_STAT_OFF, + AB8505_USB_STAT_SDP_NOT_CHARGING, + AB8505_USB_STAT_SDP_CHARGING, + AB8505_USB_STAT_SDP_SUSPENDED, + AB8505_USB_STAT_CDP, + AB8505_USB_STAT_RESERVED_5, + AB8505_USB_STAT_RESERVED_6, + AB8505_USB_STAT_DCP, + AB8505_USB_STAT_ACA_RID_A, + AB8505_USB_STAT_ACA_RID_B, + AB8505_USB_STAT_ACA_RID_C, + AB8505_USB_STAT_RESERVED_11, + AB8505_USB_STAT_RESERVED_12, + AB8505_USB_STAT_UPSTREAM_IDGND, + AB8505_USB_STAT_CHARGER_NOT_OK, + AB8505_USB_STAT_CHARGER_DM_HIGH, + AB8505_USB_STAT_PHY_ENABLED, + AB8505_USB_STAT_UPSTREAM_NO_IDGND, + AB8505_USB_STAT_UPSTREAM_IDGND_VBUS, + AB8505_USB_STAT_CHARGER_SE1, + AB8505_USB_STAT_CARKIT_1, + AB8505_USB_STAT_CARKIT_2, + AB8505_USB_STAT_ACA_DOCK_CHARGER, +}; + enum ab8500_usb_state { AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ @@ -699,20 +726,102 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) } /** - * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * ab8505_charger_max_usb_curr() - get the max current for the USB type * @di: pointer to the ab8500_charger structure * @link_status: the identified USB type * - * Get the maximum current that is allowed to be drawn from the host - * based on the USB type. + * Decode AB8505-specific UsbLink1Status values and get the maximum current + * that is allowed to be drawn based on the USB type. * Returns error code in case of failure else 0 on success */ +static int ab8505_charger_max_usb_curr(struct ab8500_charger *di, + u8 link_status) +{ + int ret = 0; + + di->is_aca_rid = 0; + + switch (link_status) { + case AB8505_USB_STAT_SDP_NOT_CHARGING: + case AB8505_USB_STAT_SDP_CHARGING: + case AB8505_USB_STAT_SDP_SUSPENDED: + case AB8505_USB_STAT_CDP: + case AB8505_USB_STAT_CHARGER_DM_HIGH: + case AB8505_USB_STAT_UPSTREAM_NO_IDGND: + case AB8505_USB_STAT_UPSTREAM_IDGND_VBUS: + case AB8505_USB_STAT_CHARGER_SE1: + case AB8505_USB_STAT_CARKIT_1: + case AB8505_USB_STAT_CARKIT_2: + case AB8505_USB_STAT_ACA_DOCK_CHARGER: + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5; + break; + case AB8505_USB_STAT_DCP: + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5; + break; + case AB8505_USB_STAT_ACA_RID_A: + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 1; + break; + case AB8505_USB_STAT_ACA_RID_B: + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P3; + di->is_aca_rid = 1; + break; + case AB8505_USB_STAT_ACA_RID_C: + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_1P5; + di->is_aca_rid = 1; + break; + case AB8505_USB_STAT_OFF: + if (di->vbus_detected) { + di->usb_device_is_unrecognised = true; + di->max_usb_in_curr.usb_type_max_ua = + USB_CH_IP_CUR_LVL_1P5; + break; + } + fallthrough; + case AB8505_USB_STAT_UPSTREAM_IDGND: + case AB8505_USB_STAT_PHY_ENABLED: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + case AB8505_USB_STAT_CHARGER_NOT_OK: + di->flags.vbus_collapse = true; + dev_err(di->dev, "USB Type - VBUS has collapsed\n"); + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + case AB8505_USB_STAT_RESERVED_5: + case AB8505_USB_STAT_RESERVED_6: + case AB8505_USB_STAT_RESERVED_11: + case AB8505_USB_STAT_RESERVED_12: + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + } + + return ret; +} + +/** + * ab8500_charger_max_usb_curr() - get the max current for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn based on the USB type. + * Returns error code in case of failure else 0 on success. + */ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, - enum ab8500_charger_link_status link_status) + u8 link_status) { int ret = 0; di->usb_device_is_unrecognised = false; + if (is_ab8505(di->parent)) { + ret = ab8505_charger_max_usb_curr(di, link_status); + goto out; + } /* * Platform only supports USB 2.0. @@ -721,7 +830,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, * should set USB_CH_IP_CUR_LVL_0P5. */ - switch (link_status) { + switch ((enum ab8500_charger_link_status)link_status) { case USB_STAT_STD_HOST_NC: case USB_STAT_STD_HOST_C_NS: case USB_STAT_STD_HOST_C_S: @@ -825,6 +934,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, break; } +out: di->max_usb_in_curr.set_max_ua = di->max_usb_in_curr.usb_type_max_ua; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, di->max_usb_in_curr.set_max_ua); @@ -866,8 +976,7 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di) val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; else val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; - ret = ab8500_charger_max_usb_curr(di, - (enum ab8500_charger_link_status) val); + ret = ab8500_charger_max_usb_curr(di, val); return ret; } @@ -929,8 +1038,7 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) if (val) break; } - ret = ab8500_charger_max_usb_curr(di, - (enum ab8500_charger_link_status) val); + ret = ab8500_charger_max_usb_curr(di, val); return ret; } @@ -2277,6 +2385,7 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) int ret; u8 val; u8 link_status; + u8 link_status_mask; struct ab8500_charger *di = container_of(work, struct ab8500_charger, usb_link_status_work); @@ -2303,20 +2412,22 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) ret = abx500_get_register_interruptible(di->dev, AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); - if (ret >= 0) - dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val); - else - dev_dbg(di->dev, "Error reading USB link status\n"); + if (ret < 0) { + dev_err(di->dev, "Error reading USB link status\n"); + return; + } + dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val); if (is_ab8500(di->parent)) - link_status = AB8500_USB_LINK_STATUS; + link_status_mask = AB8500_USB_LINK_STATUS; else - link_status = AB8505_USB_LINK_STATUS; + link_status_mask = AB8505_USB_LINK_STATUS; + link_status = (val & link_status_mask) >> USB_LINK_STATUS_SHIFT; if (detected_chargers & USB_PW_CONN) { - if (((val & link_status) >> USB_LINK_STATUS_SHIFT) == - USB_STAT_NOT_VALID_LINK && - di->invalid_charger_detect_state == 0) { + if (is_ab8500(di->parent) && + link_status == USB_STAT_NOT_VALID_LINK && + di->invalid_charger_detect_state == 0) { dev_dbg(di->dev, "Invalid charger detected, state= 0\n"); /*Enable charger*/ @@ -2339,18 +2450,21 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) abx500_mask_and_set_register_interruptible(di->dev, AB8500_USB, AB8500_USB_LINE_CTRL2_REG, USB_CH_DET, 0x00); - /*Check link status*/ - if (is_ab8500(di->parent)) - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINE_STAT_REG, - &val); - else - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINK1_STAT_REG, - &val); + /* Check link status */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, + AB8500_USB_LINE_STAT_REG, + &val); + if (ret < 0) { + dev_err(di->dev, + "Error reading USB link status\n"); + return; + } + link_status = (val & link_status_mask) >> + USB_LINK_STATUS_SHIFT; dev_dbg(di->dev, "USB link status= 0x%02x\n", - (val & link_status) >> USB_LINK_STATUS_SHIFT); + link_status); di->invalid_charger_detect_state = 2; } } else { From dcecf527632297011153f088573689a1ad1de790 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:31 +0200 Subject: [PATCH 57/77] power: supply: ab8500_charger: Respect AB8505 register layout Several AB8505 charger fields differ from AB8500. Its long VBUS falling debounce is 300 ms rather than 100 ms, and its VBUS overvoltage selector has a different encoding programmed through OTP. Writing the AB8500 6.3 V value selects 9 V on AB8505. AB8505 revision 3 also defines SwControlFallback as reserved, correcting older documentation which led the driver to support autopower there. Preserve the AB8505 overvoltage setting, wait for its documented debounce, and reject the unsupported autopower setting. Fixes: 0ed5107fa860 ("ab8500-charger: Do not touch VBUSOVV bits") Fixes: b016322293c7 ("ab8500-charger: Add support for autopower on AB8505 and AB9540") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-9-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 4810dfbe948d..08042ef222be 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -52,7 +52,7 @@ #define MAIN_CH_DET 0x01 #define MAIN_CH_CV_ON 0x04 #define USB_CH_CV_ON 0x08 -#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC_LONG 0x02 #define VBUS_DET_DBNC1 0x01 #define OTP_ENABLE_WD 0x01 #define DROP_COUNT_RESET 0x01 @@ -383,6 +383,10 @@ static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di, u8 bit; int ret; + /* SwControlFallback is reserved and must remain clear on AB8505. */ + if (is_ab8505(di->parent)) + return; + dev_dbg(di->dev, "SW Fallback: %d\n", fallback); if (is_ab8500(di->parent)) { @@ -703,12 +707,16 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) if (!probe) { /* - * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100 + * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC_LONG * when disconnecting ACA even though no * charger was connected. Try waiting a little - * longer than the 100 ms of VBUS_DET_DBNC100... + * longer than its 100 ms debounce. AB8505 uses + * a 300 ms falling debounce for the same bit. */ - msleep(110); + if (is_ab8505(di->parent)) + msleep(310); + else + msleep(110); } ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, AB8500_CH_USBCH_STAT1_REG, &val); @@ -719,7 +727,7 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) dev_dbg(di->dev, "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__, val); - if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC_LONG)) result |= USB_PW_CONN; return result; @@ -3185,7 +3193,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) } } - if (is_ab8505_2p0(di->parent)) + if (is_ab8505(di->parent)) ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_USBCH_CTRL2_REG, @@ -3534,11 +3542,14 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->bm = &ab8500_bm_data; - di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); - /* get parent data */ di->dev = dev; di->parent = dev_get_drvdata(pdev->dev.parent); + di->autopower_cfg = of_property_read_bool(np, "autopower_cfg"); + if (di->autopower_cfg && is_ab8505(di->parent)) { + dev_warn(dev, "autopower is not supported on AB8505\n"); + di->autopower_cfg = false; + } /* Get ADC channels */ if (!is_ab8505(di->parent)) { From 1098c619ce7595a77a8c2e366a703e9fc5f29bf7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:32 +0200 Subject: [PATCH 58/77] power: supply: ab8500_charger: Handle detection errors ab8500_charger_detect_chargers() returns either a charger bitmask or a negative register-access error. The probe and component-bind paths use a negative return value as a bitmask, making failed reads appear as both AC and USB connections. Propagate detection errors and release the bind-time workqueue on failure. Fixes: 84edbeeab67c ("ab8500-charger: AB8500 charger driver") Fixes: 1c1f13a006ed ("power: supply: ab8500: Move to componentized binding") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-10-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 08042ef222be..938c3923afde 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3462,6 +3462,10 @@ static int ab8500_charger_bind(struct device *dev) } ch_stat = ab8500_charger_detect_chargers(di, false); + if (ch_stat < 0) { + destroy_workqueue(di->charger_wq); + return ch_stat; + } if (ch_stat & AC_PW_CONN) { if (is_ab8500(di->parent)) @@ -3747,6 +3751,10 @@ static int ab8500_charger_probe(struct platform_device *pdev) /* Identify the connected charger types during startup */ charger_status = ab8500_charger_detect_chargers(di, true); + if (charger_status < 0) { + ret = charger_status; + goto remove_ab8500_bm; + } if (charger_status & AC_PW_CONN) { di->ac.charger_connected = 1; di->ac_conn = true; From 52c8cdcc1146d86fae524a589d4512c8d59ea8b4 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:33 +0200 Subject: [PATCH 59/77] power: supply: ab8500_charger: Skip absent AB8505 main charger AB8505 contains only the USB charger. It has no integrated main charger, main-charger status register, or main-charger interrupts. Avoid reading the missing status register, requesting the five missing interrupts, or disabling an AC charger which was never enabled. This lets the driver bind using only the AB8505 USB charger resources. Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-11-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 32 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 938c3923afde..c7225135af57 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -692,16 +692,19 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) int ret; u8 val; - /* Check for AC charger */ - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_STATUS1_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; - } + /* AB8505 has no integrated main charger. */ + if (!is_ab8505(di->parent)) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } - if (val & MAIN_CH_DET) - result = AC_PW_CONN; + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + } /* Check for USB charger */ @@ -3500,7 +3503,8 @@ static void ab8500_charger_unbind(struct device *dev) int ret; /* Disable AC charging */ - ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + if (di->ac_chg.enabled) + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); /* Disable USB charging */ ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); @@ -3724,7 +3728,13 @@ static int ab8500_charger_probe(struct platform_device *pdev) } /* Request interrupts */ - for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + /* The first five interrupts belong to the AB8500 main charger. */ + if (is_ab8505(di->parent)) + i = 5; + else + i = 0; + + for (; i < ARRAY_SIZE(ab8500_charger_irq); i++) { irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); if (irq < 0) return irq; From 95fba2e3055df99ed19e7d6399101bac42b3e7d5 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:34 +0200 Subject: [PATCH 60/77] power: supply: ab8500_charger: Retry AB8505 USB detection AB8505 reports UsbLink1Status state 14 when the USB charger port is not OK. The manual says that software can return the port detector to the detection state by setting the self-clearing ChargerCtrl.DropCountReset bit. The Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle, and Samsung-SGH-T599-Codina-TMO product trees are reference points for a workaround that cycles the USB charger and retries detection three times before rejecting VBUS. Only the Kyle tree writes 1 to DropCountReset; the other two write 0 and therefore do not request the documented reset. Add the same bounded recovery for AB8505, preserving the configured USB charger control bits across each cycle. Keep the charger connected while recovering, then report the fault and disconnect it if all three attempts fail. Leave AB8500 behavior unchanged. Fixes: d4337660d069 ("ab8500-charger: Add AB8505_USB_LINK_STATUS") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-12-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 116 ++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index c7225135af57..8430bc247df7 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -99,6 +99,8 @@ #define CHARGER_STATUS_POLL 10 /* in ms */ +#define AB8505_USB_CHARGER_NOT_OK_RETRIES 3 + #define CHG_WD_INTERVAL (60 * HZ) #define AB8500_SW_CONTROL_FALLBACK 0x03 @@ -261,6 +263,8 @@ struct ab8500_charger_max_usb_in_curr { * @is_aca_rid: Incicate if accessory is ACA type * @current_stepping_sessions: * Counter for current stepping sessions + * @usb_charger_not_ok_retries: + * Number of AB8505 USB charger recovery attempts * @parent: Pointer to the struct ab8500 * @adc_main_charger_v ADC channel for main charger voltage * @adc_main_charger_c ADC channel for main charger current @@ -314,6 +318,7 @@ struct ab8500_charger { int invalid_charger_detect_state; int is_aca_rid; atomic_t current_stepping_sessions; + unsigned int usb_charger_not_ok_retries; struct ab8500 *parent; struct iio_channel *adc_main_charger_v; struct iio_channel *adc_main_charger_c; @@ -473,6 +478,9 @@ static void ab8500_power_supply_changed(struct ab8500_charger *di, static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, bool connected) { + if (!connected && !di->vbus_detected) + di->usb_charger_not_ok_retries = 0; + if (connected != di->usb.charger_connected) { dev_dbg(di->dev, "USB connected:%i\n", connected); di->usb.charger_connected = connected; @@ -796,10 +804,19 @@ static int ab8505_charger_max_usb_curr(struct ab8500_charger *di, ret = -ENXIO; break; case AB8505_USB_STAT_CHARGER_NOT_OK: - di->flags.vbus_collapse = true; - dev_err(di->dev, "USB Type - VBUS has collapsed\n"); di->max_usb_in_curr.usb_type_max_ua = USB_CH_IP_CUR_LVL_0P05; - ret = -ENXIO; + if (di->usb_charger_not_ok_retries < + AB8505_USB_CHARGER_NOT_OK_RETRIES) { + dev_warn(di->dev, + "USB Type - VBUS has collapsed, retrying\n"); + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, 0); + ret = -EAGAIN; + } else { + dev_err(di->dev, + "USB Type - VBUS recovery failed\n"); + ret = -ENXIO; + } break; case AB8505_USB_STAT_RESERVED_5: case AB8505_USB_STAT_RESERVED_6: @@ -2587,6 +2604,48 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) } } +static int ab8505_charger_retry_usb_detection(struct ab8500_charger *di) +{ + u8 usbch_ctrl1; + bool usb_enabled; + int restore_ret; + int ret; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, + &usbch_ctrl1); + if (ret < 0) + return ret; + + usb_enabled = usbch_ctrl1 & USB_CH_ENA; + if (usb_enabled) { + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, + USB_CH_ENA, 0); + if (ret < 0) + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CHARGER_CTRL, + DROP_COUNT_RESET, + DROP_COUNT_RESET); + + if (usb_enabled) { + restore_ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, + USB_CH_ENA, + USB_CH_ENA); + if (restore_ret < 0) + return restore_ret; + } + + return ret; +} + /** * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status * @work: pointer to the work_struct structure @@ -2596,7 +2655,10 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) { int ret; + u8 link_status = 0; u8 reg_value; + bool charger_not_ok; + bool prev_collapse; bool prev_status; struct ab8500_charger *di = container_of(work, @@ -2609,19 +2671,61 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) dev_err(di->dev, "%s ab8500 read failed\n", __func__); return; } - prev_status = di->flags.usbchargernotok; + charger_not_ok = reg_value & VBUS_CH_NOK; + if (is_ab8505(di->parent)) { + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINK1_STAT_REG, + &link_status); + if (ret < 0) { + dev_err(di->dev, "%s USB link status read failed\n", + __func__); + return; + } - if (reg_value & VBUS_CH_NOK) { + link_status = (link_status & AB8505_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + charger_not_ok |= + link_status == AB8505_USB_STAT_CHARGER_NOT_OK; + } + + prev_status = di->flags.usbchargernotok; + prev_collapse = di->flags.vbus_collapse; + + if (is_ab8505(di->parent) && + link_status == AB8505_USB_STAT_CHARGER_NOT_OK && + di->usb_charger_not_ok_retries < + AB8505_USB_CHARGER_NOT_OK_RETRIES) { + di->usb_charger_not_ok_retries++; + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = true; + dev_warn(di->dev, "Retrying USB charger detection (%u/%u)\n", + di->usb_charger_not_ok_retries, + AB8505_USB_CHARGER_NOT_OK_RETRIES); + + ret = ab8505_charger_retry_usb_detection(di); + if (ret < 0) + dev_err(di->dev, "USB charger retry failed: %d\n", ret); + + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else if (charger_not_ok) { di->flags.usbchargernotok = true; + if (is_ab8505(di->parent) && + link_status == AB8505_USB_STAT_CHARGER_NOT_OK) { + di->flags.vbus_collapse = false; + ab8500_charger_set_usb_connected(di, false); + } /* Check again in 1sec */ queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, HZ); } else { di->flags.usbchargernotok = false; di->flags.vbus_collapse = false; + di->usb_charger_not_ok_retries = 0; } - if (prev_status != di->flags.usbchargernotok) + if (prev_status != di->flags.usbchargernotok || + prev_collapse != di->flags.vbus_collapse) ab8500_power_supply_changed(di, di->usb_chg.psy); } From 76bdf9d78245bb068207c23e9bf43d4b9bb201c3 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:35 +0200 Subject: [PATCH 61/77] power: supply: ab8500_charger: Recover inactive AB8505 charger UsbChCtrl1.UsbChEna can remain set while UsbChStatus1.UsbChOn is clear. The existing recovery only checks the control bit, so it considers the charger enabled and leaves charging stopped. The Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle, and Samsung-SGH-T599-Codina-TMO product trees are reference points for checking both bits and cycling the charger when they disagree. Extend the USB charger check to detect this AB8505 state and enable the check in the charging algorithm for AB8505. Only cycle the charger when VBUS is debounced and no charger-not-OK, thermal, overvoltage, or watchdog fault accounts for the inactive hardware. Fixes: 4dcdf57773fd ("ab8500-bm: Quick re-attach charging behaviour") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-13-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 9 ++--- drivers/power/supply/ab8500_charger.c | 51 +++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index d2b9880b0770..71d82c6c193e 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -1241,12 +1241,9 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) charger_status = ab8500_chargalg_check_charger_connection(di); - if (is_ab8500(di->parent)) { - ret = ab8500_chargalg_check_charger_enable(di); - if (ret < 0) - dev_err(di->dev, "Checking charger is enabled error" - ": Returned Value %d\n", ret); - } + ret = ab8500_chargalg_check_charger_enable(di); + if (ret < 0) + dev_err(di->dev, "Checking charger enable failed: %d\n", ret); /* * First check if we have a charger connected. diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 8430bc247df7..19cc56624ee4 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -52,6 +52,7 @@ #define MAIN_CH_DET 0x01 #define MAIN_CH_CV_ON 0x04 #define USB_CH_CV_ON 0x08 +#define USB_CH_ON 0x04 #define VBUS_DET_DBNC_LONG 0x02 #define VBUS_DET_DBNC1 0x01 #define OTP_ENABLE_WD 0x01 @@ -1877,6 +1878,9 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, int vset_uv, int iset_ua) { u8 usbch_ctrl1 = 0; + u8 usbch_status1; + u8 usbch_status2; + bool reenable; int ret = 0; struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); @@ -1892,7 +1896,52 @@ static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, } dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1); - if (!(usbch_ctrl1 & USB_CH_ENA)) { + reenable = !(usbch_ctrl1 & USB_CH_ENA); + if (is_ab8505(di->parent)) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, + &usbch_status1); + if (ret < 0) { + dev_err(di->dev, "USB charger status read failed\n"); + return ret; + } + + if (!reenable && (usbch_status1 & USB_CH_ON)) + return 0; + + if (!(usbch_status1 & VBUS_DET_DBNC_LONG)) + return 0; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_USBCH_STAT2_REG, + &usbch_status2); + if (ret < 0) { + dev_err(di->dev, "USB charger fault status read failed\n"); + return ret; + } + + if ((usbch_status2 & + (VBUS_CH_NOK | USB_CH_TH_PROT | VBUS_OVV_TH)) || + di->flags.usbchargernotok || di->flags.usb_thermal_prot || + di->flags.vbus_ovv || di->usb.wd_expired) + return 0; + + if (!reenable) { + dev_info(di->dev, + "USB charger enabled but inactive, cycling it\n"); + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, + USB_CH_ENA, 0); + if (ret < 0) + return ret; + reenable = true; + } + } + + if (reenable) { dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); ret = abx500_mask_and_set_register_interruptible(di->dev, From 9a0461fb5e7cc64ed303bee3661452879ee139a1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:36 +0200 Subject: [PATCH 62/77] power: supply: ab8500_fg: Finalize current measurements ab8500_fg_inst_curr_start() returns with cc_lock held and the CCEOC IRQ enabled. The bounded sampling loop in the load-compensated voltage path returns without calling ab8500_fg_inst_curr_finalize() when it expires, leaving both resources active. The next fuel-gauge work item then blocks on cc_lock and all cached power-supply values stop updating. The Samsung-GT-I8530-Gavini, Samsung-GT-S7710-Skomer, Samsung-SGH-I407-Kyle and Samsung-SGH-T599-Codina-TMO product trees use a bounded sampling loop in drivers/battery/abb_fuelgauge.c but still call the finalizer. Follow that pattern so the normal completion timeout also performs the required cleanup. Check the start result as well and return an uncompensated voltage if the instantaneous current transaction cannot be started or completed. Fixes: edc400e1632f ("power: supply: ab8500_fg: Break loop for measurement") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-14-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 70e29c9e8108..b3f29a43745d 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -934,9 +934,15 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always) int i = 0; int vbat_uv = 0; int rcomp; + int ret; /* Average the instant current to get a stable current measurement */ - ab8500_fg_inst_curr_start(di); + ret = ab8500_fg_inst_curr_start(di); + if (ret) { + dev_err(di->dev, "failed to start instantaneous current measurement\n"); + di->vbat_uv = ab8500_fg_bat_voltage(di); + return di->vbat_uv; + } do { vbat_uv += ab8500_fg_bat_voltage(di); @@ -945,15 +951,14 @@ static int ab8500_load_comp_fg_bat_voltage(struct ab8500_fg *di, bool always) } while (!ab8500_fg_inst_curr_done(di) && i <= WAIT_FOR_INST_CURRENT_MAX); - if (i > WAIT_FOR_INST_CURRENT_MAX) { + ret = ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua); + if (ret) { dev_err(di->dev, "TIMEOUT: return uncompensated measurement of VBAT\n"); di->vbat_uv = vbat_uv / i; return di->vbat_uv; } - ab8500_fg_inst_curr_finalize(di, &di->inst_curr_ua); - /* * If there is too high current dissipation, the compensation cannot be * trusted so return an error unless we must return something here, as From edafb2e4642b72b0f86ce5990d5c29c116bb642b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 08:49:37 +0200 Subject: [PATCH 63/77] power: supply: ab8500_fg: Avoid reserved AB8505 CCConf bit AB8500 defines CCConf bit 1 as CCDeepSleepEna, but AB8505 reserves bits 7:1 and only defines bit 0, CCPwrUpEna. The fuel-gauge driver currently writes both bits whenever it starts the coulomb counter. The Samsung-GT-S7710-Skomer and related Samsung product trees use the same unconditional value in their fuel-gauge drivers, so they provide no AB8505 workaround to carry over. Select the value from the detected PMIC variant instead, retaining the deep-sleep enable bit on all existing non-AB8505 paths and avoiding the reserved bit on AB8505. Fixes: a982362c1723 ("mfd: Support for the AB8500 AB8505 variant") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260916-ab8500-charging-v1-15-e986ed321cc6@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index b3f29a43745d..069e7fee1d16 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -451,6 +451,16 @@ static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) avg->avg = sample; } +static u8 ab8500_fg_cc_config(struct ab8500_fg *di) +{ + u8 config = CC_PWR_UP_ENA; + + if (!is_ab8505(di->parent)) + config |= CC_DEEP_SLEEP_ENA; + + return config; +} + /** * ab8500_fg_coulomb_counter() - enable coulomb counter * @di: pointer to the ab8500_fg structure @@ -481,7 +491,7 @@ static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) /* Start the CC */ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CC_CONF_REG, - (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + ab8500_fg_cc_config(di)); if (ret) goto cc_err; @@ -555,7 +565,7 @@ int ab8500_fg_inst_curr_start(struct ab8500_fg *di) /* Start the CC */ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CC_CONF_REG, - (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + ab8500_fg_cc_config(di)); if (ret) goto fail; } else { From b33596a615d163372f5e7519338d860cbfc37217 Mon Sep 17 00:00:00 2001 From: Reza Kurniawan Date: Sat, 12 Sep 2026 22:18:11 +0700 Subject: [PATCH 64/77] power: supply: bq256xx: Expose VBUS state through extcon Useful for USB controllers needing to sense VBUS state changes. This is needed for devices like Xiaomi Redmi 5A (riva) that doesn't have its VBUS session comparator wired directly to the USB connector pin to automatically enable certain USB functions upon the presence of a USB power input. Signed-off-by: Reza Kurniawan Link: https://patch.msgid.link/20260912-riva-usb-v2-1-997fe0171ee5@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 1 + drivers/power/supply/bq256xx_charger.c | 96 +++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index b89ef40df7f8..ee7dc5f2617a 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -850,6 +850,7 @@ config CHARGER_BQ256XX tristate "TI BQ256XX battery charger driver" depends on I2C depends on GPIOLIB || COMPILE_TEST + depends on EXTCON || !EXTCON select REGMAP_I2C help Say Y to enable support for the TI BQ256XX battery chargers. The diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 4b1f81b1ed86..454bc497b0d8 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -16,6 +16,7 @@ #include #include #include +#include #define BQ256XX_MANUFACTURER "Texas Instruments" @@ -209,6 +210,7 @@ enum bq256xx_id { * @client: i2c client structure * @regmap: register map structure * @dev: device structure + * @edev: extcon device registered to report vbus state changes * @charger: power supply registered for the charger * @battery: power supply registered for the battery * @lock: mutex lock structure @@ -229,6 +231,7 @@ enum bq256xx_id { struct bq256xx_device { struct i2c_client *client; struct device *dev; + struct extcon_dev *edev; struct power_supply *charger; struct power_supply *battery; struct mutex lock; @@ -1164,8 +1167,14 @@ static int bq256xx_get_charger_property(struct power_supply *psy, return ret; } -static bool bq256xx_state_changed(struct bq256xx_device *bq, - struct bq256xx_state *new_state) +enum bq256xx_state_change { + CHANGED_NONE, + CHANGED_VBUS, + CHANGED_OTHER +}; + +static enum bq256xx_state_change bq256xx_state_changed(struct bq256xx_device *bq, + struct bq256xx_state *new_state) { struct bq256xx_state old_state; @@ -1173,27 +1182,92 @@ static bool bq256xx_state_changed(struct bq256xx_device *bq, old_state = bq->state; mutex_unlock(&bq->lock); - return memcmp(&old_state, new_state, sizeof(struct bq256xx_state)) != 0; + if (new_state->online != old_state.online) + return CHANGED_VBUS; + + if (memcmp(&old_state, new_state, sizeof(struct bq256xx_state)) != 0) + return CHANGED_OTHER; + + return CHANGED_NONE; +} + +static const unsigned int bq256xx_usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_NONE, +}; + +static void bq256xx_extcon_update(struct bq256xx_device *bq, bool online) +{ + int ret; + + if (!bq->edev) + return; + + ret = extcon_set_state_sync(bq->edev, EXTCON_USB, online); + if (ret) + dev_err(bq->dev, "Failed to update extcon state: %d\n", ret); +} + +static int bq256xx_extcon_init(struct bq256xx_device *bq) +{ + unsigned int charger_status_0; + int ret; + + if (!IS_REACHABLE(CONFIG_EXTCON)) { + dev_dbg(bq->dev, "Extcon support is disabled\n"); + return 0; + } + + bq->edev = devm_extcon_dev_allocate(bq->dev, bq256xx_usb_extcon_cable); + if (IS_ERR(bq->edev)) { + dev_err(bq->dev, "Failed to allocate extcon device\n"); + return PTR_ERR(bq->edev); + } + + ret = devm_extcon_dev_register(bq->dev, bq->edev); + if (ret < 0) { + dev_err(bq->dev, "Failed to register extcon device\n"); + return ret; + } + + ret = regmap_read(bq->regmap, BQ256XX_CHARGER_STATUS_0, + &charger_status_0); + if (ret) { + dev_err(bq->dev, "Failed to read charger status\n"); + return ret; + } + + bq256xx_extcon_update(bq, !!(charger_status_0 & BQ256XX_PG_STAT_MASK)); + + return 0; } static irqreturn_t bq256xx_irq_handler_thread(int irq, void *private) { struct bq256xx_device *bq = private; struct bq256xx_state state; + enum bq256xx_state_change changed; int ret; ret = bq256xx_get_state(bq, &state); if (ret < 0) goto irq_out; - if (!bq256xx_state_changed(bq, &state)) + changed = bq256xx_state_changed(bq, &state); + switch (changed) { + case CHANGED_NONE: goto irq_out; - mutex_lock(&bq->lock); - bq->state = state; - mutex_unlock(&bq->lock); + case CHANGED_VBUS: + bq256xx_extcon_update(bq, state.online); + fallthrough; + case CHANGED_OTHER: + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); - power_supply_changed(bq->charger); + power_supply_changed(bq->charger); + } irq_out: return IRQ_HANDLED; @@ -1743,6 +1817,12 @@ static int bq256xx_probe(struct i2c_client *client) if (!IS_ERR_OR_NULL(bq->usb3_phy)) usb_register_notifier(bq->usb3_phy, &bq->usb_nb); + ret = bq256xx_extcon_init(bq); + if (ret) { + dev_err(dev, "Failed to register extcon device\n"); + return ret; + } + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, bq256xx_irq_handler_thread, From 4856b49317ccd4ce96a0e8970df7d13ce8975a02 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 4 Oct 2026 00:37:28 -0400 Subject: [PATCH 65/77] power: supply: pf1550: drain IRQ work before unregistering supplies The VBUS work calls power_supply_changed(), but its managed cancellation is registered before the supplies. Cleanup can therefore unregister a supply while IRQ-triggered work is still pending. Commit 838767f50747 ("power: supply: pf1550: Fix use-after-free in power_supply_changed()") moved IRQ requests after supply registration, but left work cancellation before the supplies. That change protects the direct IRQ callbacks, while this fix drains the work they queue. Register the three work items after both supplies and before the IRQs. Cleanup then frees the IRQ producers, drains the work, and unregisters the supplies. The teardown ordering issue was found by static analysis. Fixes: 4b6b6433a97d ("power: supply: pf1550: add battery charger support") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261004043728.1140015-1-mhun512@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/pf1550-charger.c | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c index 2ead1df60e65..e3d4e9817f91 100644 --- a/drivers/power/supply/pf1550-charger.c +++ b/drivers/power/supply/pf1550-charger.c @@ -566,6 +566,22 @@ static int pf1550_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, chg); + psy_cfg.drv_data = chg; + + chg->charger = devm_power_supply_register(&pdev->dev, + &pf1550_charger_desc, + &psy_cfg); + if (IS_ERR(chg->charger)) + return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger), + "failed: power supply register\n"); + + chg->battery = devm_power_supply_register(&pdev->dev, + &pf1550_battery_desc, + &psy_cfg); + if (IS_ERR(chg->battery)) + return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery), + "failed: power supply register\n"); + ret = devm_delayed_work_autocancel(chg->dev, &chg->vbus_sense_work, pf1550_chg_vbus_work); if (ret) @@ -584,22 +600,6 @@ static int pf1550_charger_probe(struct platform_device *pdev) return dev_err_probe(chg->dev, ret, "failed to add battery sense work\n"); - psy_cfg.drv_data = chg; - - chg->charger = devm_power_supply_register(&pdev->dev, - &pf1550_charger_desc, - &psy_cfg); - if (IS_ERR(chg->charger)) - return dev_err_probe(&pdev->dev, PTR_ERR(chg->charger), - "failed: power supply register\n"); - - chg->battery = devm_power_supply_register(&pdev->dev, - &pf1550_battery_desc, - &psy_cfg); - if (IS_ERR(chg->battery)) - return dev_err_probe(&pdev->dev, PTR_ERR(chg->battery), - "failed: power supply register\n"); - for (i = 0; i < PF1550_CHARGER_IRQ_NR; i++) { irq = platform_get_irq(pdev, i); if (irq < 0) From 5c1c602463d597c5189ddc5a73dece588ba2c57a Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 4 Oct 2026 00:10:19 -0400 Subject: [PATCH 66/77] power: supply: sbs-battery: disable polling before supply teardown The managed poll-work cancellation is registered before the power supply, so cleanup unregisters the supply before draining polling. A pending or running poller can then call power_supply_changed() on the released supply. Commit 8d59cf3887fb ("power: supply: sbs-battery: Fix use-after-free in power_supply_changed()") moved the IRQ request after supply registration, but did not change the poll-work cancellation order. Add a later managed action that disables and drains polling before supply teardown. Its retained disabled state prevents external-power callbacks from rearming the poll work after it has been drained. Keep the original autocancel to initialize work before supply callbacks can run and to cover failed supply registration. The teardown ordering issue was found by static analysis. Fixes: 6d0c5de2fd84 ("power: supply: Clean-up few drivers by using managed work init") Cc: stable@vger.kernel.org # 6.10+ Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261004041019.1123723-1-mhun512@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 501c8e069755..6714313bb3a3 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -1068,6 +1068,13 @@ static void sbs_alert(struct i2c_client *client, enum i2c_alert_protocol prot, sbs_supply_changed(i2c_get_clientdata(client)); } +static void sbs_disable_work(void *data) +{ + struct sbs_info *chip = data; + + disable_delayed_work_sync(&chip->work); +} + static void sbs_external_power_changed(struct power_supply *psy) { struct sbs_info *chip = power_supply_get_drvdata(psy); @@ -1208,6 +1215,10 @@ static int sbs_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), "Failed to register power supply\n"); + rc = devm_add_action_or_reset(&client->dev, sbs_disable_work, chip); + if (rc) + return rc; + if (!chip->gpio_detect) goto out; From 36b0abe904ac9cbdce6161d9c7c0bcac963bb3fe Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 9 Oct 2026 18:24:29 +0700 Subject: [PATCH 67/77] power: supply: generic-adc-battery: Propagate charged GPIO errors devm_gpiod_get_optional() may return an error pointer such as -EPROBE_DEFER, but gab_probe() only checks for NULL. The error pointer is then passed to gpiod_to_irq() and the original error, including -EPROBE_DEFER, is lost. Check the return value and propagate errors from the charged GPIO lookup. Fixes: b0327ffb133f ("power: supply: generic-adc-battery: Use GPIO descriptors") Signed-off-by: bui duc phuc Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20261009112429.33318-1-phucduc.bui@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/generic-adc-battery.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index 185f55d2fd70..f3ca3ebb2533 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -238,6 +238,9 @@ static int gab_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "Failed to register delayed work\n"); adc_bat->charge_finished = devm_gpiod_get_optional(&pdev->dev, "charged", GPIOD_IN); + if (IS_ERR(adc_bat->charge_finished)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc_bat->charge_finished), + "failed to get charged GPIO\n"); if (adc_bat->charge_finished) { int irq; From 1fba787142011145e1071cee9448b3677fb2323d Mon Sep 17 00:00:00 2001 From: Fabricio Akio Date: Fri, 2 Oct 2026 22:29:32 -0300 Subject: [PATCH 68/77] power: supply: qcom_smbx: set the charge enable after software control The init sequence sets CHARGING_ENABLE_CMD before CHGR_CFG2 selects software control of the charge enable. The Motorola Moto X4's bootloader leaves the charger watchdog armed to disable charging when it bites, which it does after a few minutes in fastboot. Charging then stays disabled in the next boot until the cable is replugged. Setting the command after CHGR_CFG2 re-enables it. Fixes: 8648aeb5d7b7 ("power: supply: add Qualcomm PMI8998 SMB2 Charger driver") Cc: stable@vger.kernel.org Signed-off-by: Fabricio Akio Link: https://patch.msgid.link/20261002-qcom-smbx-enable-order-v1-1-7df92bd809db@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index 3951326f488f..52bb5a6eb145 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -819,11 +819,7 @@ static const struct smb_init_register smb_init_seq[] = { .mask = SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT | VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT, .val = VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT }, - /* Enable charging */ { .addr = USBIN_OPTIONS_1_CFG, .mask = HVDCP_EN_BIT, .val = 0 }, - { .addr = CHARGING_ENABLE_CMD, - .mask = CHARGING_ENABLE_CMD_BIT, - .val = CHARGING_ENABLE_CMD_BIT }, /* * Match downstream defaults * CHG_EN_SRC_BIT - charger enable is controlled by software @@ -844,6 +840,13 @@ static const struct smb_init_register smb_init_seq[] = { AUTO_RECHG_BIT | EN_ANALOG_DROP_IN_VBATT_BIT | CHARGER_INHIBIT_BIT, .val = CHARGER_INHIBIT_BIT }, + /* + * Set the enable command only once CHGR_CFG2 has selected software + * control, or a charger its watchdog has disabled stays disabled. + */ + { .addr = CHARGING_ENABLE_CMD, + .mask = CHARGING_ENABLE_CMD_BIT, + .val = CHARGING_ENABLE_CMD_BIT }, /* STAT pin software override, match downstream. Parallel charging? */ { .addr = STAT_CFG, .mask = STAT_SW_OVERRIDE_CFG_BIT, From 1f2a40f954c848dff9d8bb6d81e404f9e47f0e81 Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Mon, 5 Oct 2026 15:34:18 +1100 Subject: [PATCH 69/77] power: supply: cros_charge-control: restore EC state on resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EC resets its charge control configuration when the AP powers off, so configured sustainer thresholds and charge behaviour are lost across hibernation. Nothing notices the reset. Commit 4fc88ba435da ("power: supply: cros_charge-control: adopt EC charge state on probe"), in the power-supply tree, made the driver adopt the EC's charge state, but only at probe, and a resume is not a probe. Sysfs therefore keeps reporting the configured values while the EC charges the battery to full. On a Framework Laptop 16 (Ryzen AI 300, charge control command version 3) with charge_control_end_threshold set to 80, a hibernate and resume cycle leaves the attribute still reading 80 while the battery charges past it to 100%. Add a system sleep resume hook that reprograms the EC from the cached state. The same EC behaviour and the same fix already exist for another driver, see commit 2643187ccb86 ("platform/x86: ayaneo-ec: Add suspend hook"). Reprogramming the EC is only strictly required after hibernation, but it is cheap and idempotent, so hook every resume path rather than rely on the EC preserving its state across each kind of sleep transition. Note that re-reading the state from the EC on resume, by reusing cros_chctl_init_state() or otherwise, would be wrong even on the command versions that support GET: the EC has been reset to its defaults by the time the hook runs, so adopting that state would silently discard the configuration the user asked for. The driver's cached state is the only remaining record of it, which is why it is pushed to the EC rather than refreshed from it. Fixes: c6ed48ef5259 ("power: supply: add ChromeOS EC based charge control driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Andrew Pam Reviewed-by: Tzung-Bi Shih Acked-by: Thomas Weißschuh Link: https://patch.msgid.link/20261005043418.243937-1-andrew@fastmailteam.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_charge-control.c | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c index 07bcc6a9aeef..e5841b40563e 100644 --- a/drivers/power/supply/cros_charge-control.c +++ b/drivers/power/supply/cros_charge-control.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \ @@ -420,6 +421,7 @@ static int cros_chctl_probe(struct platform_device *pdev) priv->dev = dev; priv->cros_ec = cros_ec; + dev_set_drvdata(dev, priv); if (priv->cmd_version == 1) priv->psy_ext = &cros_chctl_psy_ext_v1; @@ -439,6 +441,28 @@ static int cros_chctl_probe(struct platform_device *pdev) return devm_battery_hook_register(dev, &priv->battery_hook); } +static int cros_chctl_resume(struct device *dev) +{ + struct cros_chctl_priv *priv = dev_get_drvdata(dev); + int ret; + + /* + * The EC resets its charge control state when the AP powers off, so the + * configuration is lost across hibernation, while the values exposed to + * userspace still describe it. The cached state is the only remaining + * record of that configuration: reprogram the EC from it rather than + * re-reading the EC, which would adopt the reset defaults instead. + */ + guard(mutex)(&priv->lock); + ret = cros_chctl_configure_ec(priv); + if (ret) + dev_err(dev, "failed to restore charge control state: %d\n", ret); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cros_chctl_pm_ops, NULL, cros_chctl_resume); + static const struct platform_device_id cros_chctl_id[] = { { .name = "cros-charge-control" }, { } @@ -446,7 +470,10 @@ static const struct platform_device_id cros_chctl_id[] = { MODULE_DEVICE_TABLE(platform, cros_chctl_id); static struct platform_driver cros_chctl_driver = { - .driver.name = "cros-charge-control", + .driver = { + .name = "cros-charge-control", + .pm = pm_sleep_ptr(&cros_chctl_pm_ops), + }, .probe = cros_chctl_probe, .id_table = cros_chctl_id, }; From 4d49915df53ddde71556e507bdc49b81cd4609c8 Mon Sep 17 00:00:00 2001 From: Robert Killick Date: Mon, 5 Oct 2026 14:56:45 +0100 Subject: [PATCH 70/77] power: supply: sysfs: Ratelimit "unavailable enum value" warning power_supply_show_enum_with_available() warns every time a driver reports a value that is not in its declared set of available values. For usb_type this happens on every sysfs read and on every uevent, so a driver that reports such a value and calls power_supply_changed() frequently can flood the kernel log. This can happen with drivers that pass a firmware-provided value through unchanged, as qcom_battmgr does for usb_type. On a OnePlus 10 Pro (SM8450), whose firmware reports a vendor-specific usb_type, this produced ~128 warnings per second and kept systemd-journald at ~30% CPU. The bug that causes the bad value needs fixing in the driver itself, but one misbehaving driver should not be able to flood the kernel log. Use dev_warn_ratelimited(), as power_supply_show_property() already does for its own errors, so the problem is still reported, just not on every read. Signed-off-by: Robert Killick Link: https://patch.msgid.link/20261005-ratelimit-enum-warn-v1-1-b77b48de3c47@killick.dev Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index b0f524d152cf..9704557ffe1d 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -310,7 +310,8 @@ static ssize_t power_supply_show_enum_with_available( } if (!match) { - dev_warn(dev, "driver reporting unavailable enum value %d\n", value); + dev_warn_ratelimited(dev, "driver reporting unavailable enum value %d\n", + value); return -EINVAL; } From a49f86cd1333c6e12fd37bc0b3234ace10072553 Mon Sep 17 00:00:00 2001 From: Joonhoe Kim <26rote@gmail.com> Date: Mon, 12 Oct 2026 22:00:53 +0900 Subject: [PATCH 71/77] power: supply: qcom_battmgr: report the USB type from USB_ADAP_TYPE On SM8350 and SM8550 class firmware the USB power supply reads its usb_type from the USB_TYPE property. On a Kaanapali (SM8850) tablet that property is 1 (SDP) whatever is plugged in, while USB_ADAP_TYPE reports the adapter, using the same numbering as POWER_SUPPLY_USB_TYPE_*: PC port USB_TYPE 1 USB_ADAP_TYPE 1 (SDP) USB-C dock, 5 V PD USB_TYPE 1 USB_ADAP_TYPE 6 (PD) 65 W PPS charger USB_TYPE 1 USB_ADAP_TYPE 8 (PD_PPS) Qualcomm's downstream qti_battery_charger driver also maps USB_ADAP_TYPE to POWER_SUPPLY_PROP_USB_TYPE. Request USB_ADAP_TYPE for POWER_SUPPLY_PROP_USB_TYPE instead. Tested on a Lenovo Legion Tab Y700 gen 5 (qcom,kaanapali-pmic-glink, which uses the SM8550 path). Only tested on this device; the SM8350 variant shares the property map and was not tested. Fixes: 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply") Reviewed-by: Kamal Wadhwa Assisted-by: LLM Signed-off-by: Joonhoe Kim <26rote@gmail.com> Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20261012130053.442-1-26rote@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index a7237ba09be1..3529d4a564ca 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -976,7 +976,7 @@ static const u8 sm8350_usb_prop_map[] = { [POWER_SUPPLY_PROP_CURRENT_NOW] = USB_CURR_NOW, [POWER_SUPPLY_PROP_CURRENT_MAX] = USB_CURR_MAX, [POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT] = USB_INPUT_CURR_LIMIT, - [POWER_SUPPLY_PROP_USB_TYPE] = USB_TYPE, + [POWER_SUPPLY_PROP_USB_TYPE] = USB_ADAP_TYPE, }; static int qcom_battmgr_usb_sm8350_update(struct qcom_battmgr *battmgr, @@ -1514,7 +1514,7 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, case USB_INPUT_CURR_LIMIT: battmgr->usb.current_limit = le32_to_cpu(resp->intval.value); break; - case USB_TYPE: + case USB_ADAP_TYPE: battmgr->usb.usb_type = le32_to_cpu(resp->intval.value); break; default: From 8ae5a3f40045ff486826ed6b6a83e39a0f380073 Mon Sep 17 00:00:00 2001 From: Tim Douglas Date: Mon, 12 Oct 2026 22:21:53 -0400 Subject: [PATCH 72/77] power: supply: generic-adc-battery: fix status with no supplier power_supply_am_i_supplied() returns -ENODEV when no registered power supply supplies the battery. generic-adc-battery's gab_work() tests this return value as a boolean, so -ENODEV takes the supplied branch and reports CHARGING, or NOT_CHARGING if the charge-finished GPIO is asserted, instead of DISCHARGING. This was noticed while bringing up an out-of-tree Linux port to the WikiReader: the driver reported CHARGING on a device powered by two AAA batteries with no charging hardware. Only a positive return value indicates an online supplier, so treat zero and negative returns as discharging. Fixes: 46cecd130d55 ("power: supply: Fix power_supply_am_i_supplied to return -ENODEV when apropriate") Cc: stable@vger.kernel.org Signed-off-by: Tim Douglas Link: https://patch.msgid.link/20261013022153.6707-1-me@timdoug.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/generic-adc-battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index f3ca3ebb2533..c8ad3f11e808 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -130,7 +130,7 @@ static void gab_work(struct work_struct *work) adc_bat = container_of(delayed_work, struct gab, bat_work); status = adc_bat->status; - if (!power_supply_am_i_supplied(adc_bat->psy)) + if (power_supply_am_i_supplied(adc_bat->psy) <= 0) adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; else if (gab_charge_finished(adc_bat)) adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING; From 87fd20da8ca8bacdaa9bff5860167f208077dc51 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 13 Oct 2026 13:34:51 -0400 Subject: [PATCH 73/77] power: supply: qcom_battmgr: Notify charger supplies of online changes On SC8280XP and X1E80100 the AC, USB and wireless supplies take their online state from the charging source in the battery status response. The firmware sends no notification for those supplies when the charging source changes, and the driver only announces the battery, so userspace never learns that the charger was plugged in or out. UPower keeps the state it read at startup: a laptop booted on the charger never goes on battery. Call power_supply_changed() for a supply whose online state changes when a status response is parsed. A supply that is not registered yet, when the battery is read during probe, reports its state when it registers. Tested on a Microsoft Surface Pro 11 (X1E80100): after restarting UPower on the charger, unplugging now sets OnBattery, and plugging in clears it. Fixes: 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Brandon Williams Link: https://patch.msgid.link/20261013-upstream-battmgr-charger-online-v1-1-731973e133a0@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 3529d4a564ca..99ae0920f726 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1255,6 +1255,23 @@ static unsigned int qcom_battmgr_sc8280xp_convert_temp(unsigned int temperature) return DIV_ROUND_CLOSEST(temperature, 10); } +/* + * The charger supplies' online state comes from the battery status response, + * and the firmware sends no notification of its own when it changes, so + * announce the change here. A supply that is not registered yet announces its + * state when it is. + */ +static void qcom_battmgr_sc8280xp_set_online(struct power_supply *psy, + bool *online, bool value) +{ + if (*online == value) + return; + + *online = value; + if (psy) + power_supply_changed(psy); +} + static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, const struct qcom_battmgr_message *resp, size_t len) @@ -1324,9 +1341,12 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, battmgr->status.temperature = qcom_battmgr_sc8280xp_convert_temp(le32_to_cpu(resp->status.temperature)); source = le32_to_cpu(resp->status.charging_source); - battmgr->ac.online = source == BATTMGR_CHARGING_SOURCE_AC; - battmgr->usb.online = source == BATTMGR_CHARGING_SOURCE_USB; - battmgr->wireless.online = source == BATTMGR_CHARGING_SOURCE_WIRELESS; + qcom_battmgr_sc8280xp_set_online(battmgr->ac_psy, &battmgr->ac.online, + source == BATTMGR_CHARGING_SOURCE_AC); + qcom_battmgr_sc8280xp_set_online(battmgr->usb_psy, &battmgr->usb.online, + source == BATTMGR_CHARGING_SOURCE_USB); + qcom_battmgr_sc8280xp_set_online(battmgr->wls_psy, &battmgr->wireless.online, + source == BATTMGR_CHARGING_SOURCE_WIRELESS); if (battmgr->info.last_full_capacity != 0) { /* * 100 * battmgr->status.capacity can overflow a 32bit From 9c034b714ff97bbb0b9b9946857f7244db11eea0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Thu, 15 Oct 2026 16:45:03 +0400 Subject: [PATCH 74/77] power: supply: bq257xx: Describe the dev field in the driver data structure The commit moving the device initialization to the new .init callback missed the description of the newly introduced dev field in the driver data structure - add an entry to the kerneldoc. No code changes. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202610120726.XkN50Smh-lkp@intel.com/ Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20261015-bq257xx-dev-comment-v1-1-c87ee6a3b251@flipper.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq257xx_charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 2177cee29a4f..569608bc0d6a 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -54,6 +54,7 @@ struct bq257xx_chip_info { * @chip: hw specific functions * @bq: parent MFD device * @charger: power supply device + * @dev: charger platform device * @online: charger input is present * @charging: charger is actively charging the battery * @fast_charge: charger is in fast charge mode From 9f7f7a85945156d5c7626390a8c46d43d5159abc Mon Sep 17 00:00:00 2001 From: Hang Nan <2122295973@qq.com> Date: Sun, 18 Oct 2026 00:39:05 +0200 Subject: [PATCH 75/77] power_supply: serialize property access with unregister Extend 5b4bd13e0552 ("power: supply: core: prevent unregistering a power supply while a callback runs") fix to also cover the get and set property hooks. This has been tested with a small out-of-tree module registering a supply whose get property function caches its drv_data pointer and then sleeps 300 ms, while the module's main thread calls power_supply_unregister() and then frees that drv_data: unpatched: unregister() returns after 78 us callback then reads drv_data 0x6b6b6b6b (slub poison) => the driver callback read freed memory patched: unregister() returns after 302549 us (the 300 ms callback) callback then reads drv_data 0x12345678 (intact) => power_supply_unregister() waits for the in-flight callback The lock is taken in the wrappers rather than in the helper so that the _direct() variants stay lockless: a caller that has to read its own supply from within a callback uses those, and rwsem read locking is not recursive once a writer is waiting. Fixes: bc1540561c9e ("power_supply: Add API for safe access of power supply function attrs") Fixes: 5b4bd13e0552 ("power: supply: core: prevent unregistering a power supply while a callback runs") Signed-off-by: Hang Nan <2122295973@qq.com> [massively trim down AI commit message and comments] Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index c3f3c70b9449..2e1058bd24c8 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1422,6 +1422,12 @@ static int __power_supply_get_property(struct power_supply *psy, enum power_supp int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + /* Protect driver data from being free'd */ + guard(rwsem_read)(&psy->epc_sem); + + if (psy->removing) + return -ENODEV; + return __power_supply_get_property(psy, psp, val, true); } EXPORT_SYMBOL_GPL(power_supply_get_property); @@ -1479,6 +1485,12 @@ static int __power_supply_set_property(struct power_supply *psy, enum power_supp int power_supply_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { + /* Protect driver data from being free'd */ + guard(rwsem_read)(&psy->epc_sem); + + if (psy->removing) + return -ENODEV; + return __power_supply_set_property(psy, psp, val, true); } EXPORT_SYMBOL_GPL(power_supply_set_property); From 6c8098e04ee4d233399e40e7311715844910f8f8 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 8 Oct 2026 11:14:57 +0200 Subject: [PATCH 76/77] power: supply: qcom_battmgr: report full batteries The SC8280XP battery-status response has no explicit full state. When a battery reaches its full capacity on external power, neither the charging nor discharging bit is set. The driver consequently reports "Not charging", which can make userspace show the system as battery powered. Use the response's power-online bit and the reported capacity to identify a battery that is full while externally powered. Preserve "Not charging" for a battery whose charging is inhibited below its full capacity. Fixes: 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply") Assisted-by: LLM Signed-off-by: Bjorn Andersson Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20261008-battmgr-full-v1-1-4f5485a9c421@oss.qualcomm.com [put variable definition in Reverse-Christmas-tree order] Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 99ae0920f726..636d6f6acecc 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1277,6 +1277,7 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, size_t len) { unsigned int opcode = le32_to_cpu(resp->hdr.opcode); + unsigned int power_state; unsigned int source; unsigned int state; size_t payload_len = len - sizeof(struct pmic_glink_hdr); @@ -1327,19 +1328,24 @@ static void qcom_battmgr_sc8280xp_callback(struct qcom_battmgr *battmgr, return; } - state = le32_to_cpu(resp->status.battery_state); - if (state & BIT(0)) - battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING; - else if (state & BIT(1)) - battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING; - else - battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING; - battmgr->status.capacity = le32_to_cpu(resp->status.capacity) * 1000; battmgr->status.power_now = le32_to_cpu(resp->status.rate) * 1000; battmgr->status.voltage_now = le32_to_cpu(resp->status.battery_voltage) * 1000; battmgr->status.temperature = qcom_battmgr_sc8280xp_convert_temp(le32_to_cpu(resp->status.temperature)); + state = le32_to_cpu(resp->status.battery_state); + power_state = le32_to_cpu(resp->status.power_state); + if (state & BIT(0)) + battmgr->status.status = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state & BIT(1)) + battmgr->status.status = POWER_SUPPLY_STATUS_CHARGING; + else if ((power_state & BIT(0)) && + battmgr->info.last_full_capacity && + battmgr->status.capacity >= battmgr->info.last_full_capacity) + battmgr->status.status = POWER_SUPPLY_STATUS_FULL; + else + battmgr->status.status = POWER_SUPPLY_STATUS_NOT_CHARGING; + source = le32_to_cpu(resp->status.charging_source); qcom_battmgr_sc8280xp_set_online(battmgr->ac_psy, &battmgr->ac.online, source == BATTMGR_CHARGING_SOURCE_AC); From f0f400459bf6a037e70768de623fff3f8920b8bb Mon Sep 17 00:00:00 2001 From: Igor Velkov Date: Sun, 18 Oct 2026 03:57:53 +0000 Subject: [PATCH 77/77] power: supply: gpio-charger: Report discharging without input power The status property follows only the charge-status GPIO. Without input power a charger such as the BQ24133 releases its open-drain STAT pin, and depending on the board the GPIO then reads as charging. On Kobol Helios64 the board runs from its battery after a mains loss, and the charger reports online 0 with status "Charging". Report "Discharging" whenever the online GPIO is present and inactive. Fixes: df68c44c720a ("power: supply: gpio-charger: Add support for charger status.") Assisted-by: LLM Signed-off-by: Igor Velkov Link: https://patch.msgid.link/20261018-gpio-charger-offline-v2-1-482795f9e27c@iav.lv Signed-off-by: Sebastian Reichel --- drivers/power/supply/gpio-charger.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 2504190eba82..1dad49acc424 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -101,7 +101,10 @@ static int gpio_charger_get_property(struct power_supply *psy, val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod); break; case POWER_SUPPLY_PROP_STATUS: - if (gpiod_get_value_cansleep(gpio_charger->charge_status)) + /* Without input power the battery discharges, whatever the status pin says */ + if (gpio_charger->gpiod && !gpiod_get_value_cansleep(gpio_charger->gpiod)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (gpiod_get_value_cansleep(gpio_charger->charge_status)) val->intval = POWER_SUPPLY_STATUS_CHARGING; else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;