From e896e0d29ba30d462176c7611399c10297a6845f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:40 +0800 Subject: [PATCH 01/96] power: reset: pwr-mlxbf: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-2-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/pwr-mlxbf.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/power/reset/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c index 4f1cd1c0018c..b0abc7e1f984 100644 --- a/drivers/power/reset/pwr-mlxbf.c +++ b/drivers/power/reset/pwr-mlxbf.c @@ -69,8 +69,6 @@ static int pwr_mlxbf_probe(struct platform_device *pdev) return err; err = devm_request_irq(dev, irq, pwr_mlxbf_irq, 0, hid, priv); - if (err) - dev_err(dev, "Failed request of %s irq\n", priv->hid); return err; } From 098e2181f76fbcdd1592c1878a812bc9dbadc669 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:41 +0800 Subject: [PATCH 02/96] power: supply: 88pm860x_battery: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-3-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/88pm860x_battery.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c index edae1e843c51..8d93c7a6a5f0 100644 --- a/drivers/power/supply/88pm860x_battery.c +++ b/drivers/power/supply/88pm860x_battery.c @@ -959,20 +959,14 @@ static int pm860x_battery_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL, pm860x_coulomb_handler, IRQF_ONESHOT, "coulomb", info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq_cc, ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL, pm860x_batt_handler, IRQF_ONESHOT, "battery", info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq_batt, ret); + if (ret < 0) return ret; - } return 0; From a446886a5c112d6a7d019161cf520b1d6f10470f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:42 +0800 Subject: [PATCH 03/96] power: supply: 88pm860x_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-4-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/88pm860x_charger.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c index 8d99c6ff72ed..47f95f31e472 100644 --- a/drivers/power/supply/88pm860x_charger.c +++ b/drivers/power/supply/88pm860x_charger.c @@ -716,11 +716,8 @@ static int pm860x_charger_probe(struct platform_device *pdev) pm860x_irq_descs[i].handler, IRQF_ONESHOT, pm860x_irq_descs[i].name, info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq[i], ret); + if (ret < 0) return ret; - } } return 0; } From 2865c7e80aee424a5266f8d089f7926394fe2d7c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:43 +0800 Subject: [PATCH 04/96] power: supply: ab8500_btemp: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260709033428.362970-5-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_btemp.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index e5202a7b6209..032140bd6682 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -787,11 +787,8 @@ static int ab8500_btemp_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_btemp_irq[i].name, di); - if (ret) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_btemp_irq[i].name, irq, ret); + if (ret) return ret; - } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_btemp_irq[i].name, irq, ret); } From defdb28de9b3afb4811423a302ff11526595d098 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:44 +0800 Subject: [PATCH 05/96] power: supply: ab8500_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260709033428.362970-6-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 1813fbdfa1c1..bc6c070a564d 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3605,11 +3605,8 @@ static int ab8500_charger_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_charger_irq[i].name, di); - if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); + if (ret) return ret; - } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_charger_irq[i].name, irq, ret); } From aa5f4decedfb4fc5cd0fe49ab256ad4304d192e4 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:45 +0800 Subject: [PATCH 06/96] power: supply: ab8500_fg: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260709033428.362970-7-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index eb5c1ae68e44..f8f392592193 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3178,8 +3178,6 @@ static int ab8500_fg_probe(struct platform_device *pdev) ab8500_fg_irq[i].name, di); if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n", - ab8500_fg_irq[i].name, irq, ret); destroy_workqueue(di->fg_wq); return ret; } From 88a66a3c93fe21f8fb6904a2ef95d53e637b355b Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:46 +0800 Subject: [PATCH 07/96] power: supply: act8945a_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-8-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/act8945a_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 9dec4486b143..7e4b087f7679 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -620,10 +620,8 @@ static int act8945a_charger_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, IRQF_TRIGGER_FALLING, "act8945a_interrupt", charger); - if (ret) { - dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); + if (ret) return ret; - } platform_set_drvdata(pdev, charger); From daa60df947f41198defb37acef01b0c6cb819056 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:47 +0800 Subject: [PATCH 08/96] power: supply: axp20x_ac_power: Remove redundant dev_err()/dev_err_probe() The devm_request_any_context_irq() now automatically logs detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-9-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_ac_power.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index 5f6ea416fa30..474661ba7cf2 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -383,11 +383,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_ac_power_irq, 0, DRVNAME, power); - if (ret < 0) { - dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", - axp_data->irq_names[i], ret); + if (ret < 0) return ret; - } } return 0; From d8b7f5b39c3487349f77676a78e44093ec42b7c4 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:48 +0800 Subject: [PATCH 09/96] power: supply: axp20x_usb_power: Remove redundant dev_err()/dev_err_probe() The devm_request_any_context_irq() now automatically logs detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-10-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index e75d1e377ac1..29f105f9212d 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -1030,11 +1030,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_usb_power_irq, 0, DRVNAME, power); - if (ret < 0) { - dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", - axp_data->irq_names[i], ret); + if (ret < 0) return ret; - } } if (axp20x_usb_vbus_needs_polling(power)) From 5ec359933b7cd110e35aa298438cc4beeff5cc4b Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:49 +0800 Subject: [PATCH 10/96] power: supply: axp288_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-11-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index 24a8b1ee2fec..8f2090356017 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -946,8 +946,7 @@ static int axp288_charger_probe(struct platform_device *pdev) NULL, axp288_charger_irq_thread_handler, IRQF_ONESHOT, info->pdev->name, info); if (ret) - return dev_err_probe(dev, ret, "failed to request interrupt=%d\n", - info->irq[i]); + return ret; } return 0; From dfb7997da1df482f29d19b18071bd9a18f35704c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:50 +0800 Subject: [PATCH 11/96] power: supply: axp288_fuel_gauge: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-12-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp288_fuel_gauge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 5af334c0a980..aa04c54ff97e 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -791,7 +791,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) fuel_gauge_thread_handler, IRQF_ONESHOT, DEV_NAME, info); if (ret) - return dev_err_probe(dev, ret, "requesting IRQ %d\n", info->irq[i]); + return ret; } return 0; From 6d5c4deea04580a08ad1ed3a13c11fac0dbefa7b Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:51 +0800 Subject: [PATCH 12/96] power: supply: bq24190_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-13-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 6700d578a98f..2a1ea83898ae 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -2170,10 +2170,8 @@ static int bq24190_probe(struct i2c_client *client) bq24190_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "bq24190-charger", bdi); - if (ret < 0) { - dev_err(dev, "Can't set up irq handler\n"); + if (ret < 0) goto out_charger; - } ret = bq24190_register_vbus_regulator(bdi); if (ret < 0) From 1fa348a2a1825a23ab7836671f067640c98174bd Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:52 +0800 Subject: [PATCH 13/96] power: supply: bq24257_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-14-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24257_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 72f1bfea8d54..d6286be4af74 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -1052,10 +1052,8 @@ static int bq24257_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, bq->info->name, bq); - if (ret) { - dev_err(dev, "Failed to request IRQ #%d\n", client->irq); + if (ret) return ret; - } return 0; } From d77b6dd6903da71896aa20863ee51df7f54b9e7c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:53 +0800 Subject: [PATCH 14/96] power: supply: bq24735-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-15-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24735-charger.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index 99abbaf0470f..22c2b86c3b30 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -462,12 +462,8 @@ static int bq24735_charger_probe(struct i2c_client *client) IRQF_ONESHOT, supply_desc->name, charger->charger); - if (ret) { - dev_err(&client->dev, - "Unable to register IRQ %d err %d\n", - client->irq, ret); + if (ret) return ret; - } } else { ret = device_property_read_u32(&client->dev, "poll-interval", &charger->poll_interval); From 4f22a6bd5130779014d19a9946bda07768325930 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:54 +0800 Subject: [PATCH 15/96] power: supply: bq256xx_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-16-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq256xx_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 5a6634fde837..4ea89b7897c8 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -1752,10 +1752,8 @@ static int bq256xx_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&client->dev), bq); - if (ret < 0) { - dev_err(dev, "get irq fail: %d\n", ret); + if (ret < 0) return ret; - } } ret = bq256xx_hw_init(bq); From c1de2a3f1f4170105db56003be42c079dbc264dc Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:55 +0800 Subject: [PATCH 16/96] power: supply: bq257xx_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-17-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq257xx_charger.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 9c082865e745..10d242cdaa0a 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -753,8 +753,6 @@ static int bq257xx_charger_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&bq->client->dev), pdata); - if (ret < 0) - dev_err_probe(dev, ret, "Charger get irq failed\n"); } return ret; From fb5ce24c6e907165022a7df627bdf573d2c8fd1f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:56 +0800 Subject: [PATCH 17/96] power: supply: cpcap-battery: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-18-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-battery.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 59c741993ef8..1d2b8bb08564 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -957,12 +957,8 @@ static int cpcap_battery_init_irq(struct platform_device *pdev, cpcap_battery_irq_thread, IRQF_SHARED | IRQF_ONESHOT, name, ddata); - if (error) { - dev_err(ddata->dev, "could not get irq %s: %i\n", - name, error); - + if (error) return error; - } d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); if (!d) From 16447b9ef408768d94b9b15b589f287eb8ff2127 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:57 +0800 Subject: [PATCH 18/96] power: supply: cpcap-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-19-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 24221244b45b..b29b77698de7 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -762,12 +762,8 @@ static int cpcap_usb_init_irq(struct platform_device *pdev, cpcap_charger_irq_thread, IRQF_SHARED | IRQF_ONESHOT, name, ddata); - if (error) { - dev_err(ddata->dev, "could not get irq %s: %i\n", - name, error); - + if (error) return error; - } d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); if (!d) From 79993fd38d1848c4cfc5d51b185c534cc4ed0b87 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:58 +0800 Subject: [PATCH 19/96] power: supply: da9150-fg: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-20-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/da9150-fg.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c index 4f28ef1bba1a..7b2fa723acd9 100644 --- a/drivers/power/supply/da9150-fg.c +++ b/drivers/power/supply/da9150-fg.c @@ -524,10 +524,8 @@ static int da9150_fg_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, IRQF_ONESHOT, "FG", fg); - if (ret) { - dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + if (ret) return ret; - } return 0; } From 9c6283da58db46a0cc747c87d187939fb53aa326 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:33:59 +0800 Subject: [PATCH 20/96] power: supply: generic-adc-battery: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-21-panchuang@vivo.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 f5f2566b3a32..6913179ebb84 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -246,7 +246,7 @@ static int gab_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "battery charged", adc_bat); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "Failed to register irq\n"); + return ret; } platform_set_drvdata(pdev, adc_bat); From 5fe1b9dfa5c3c51a9d4d68b407ad9c49096e4b3e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:00 +0800 Subject: [PATCH 21/96] power: supply: max14656_charger_detector: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-22-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max14656_charger_detector.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index b6c3bc0d9ec1..e36d0fe1846b 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -288,10 +288,8 @@ static int max14656_probe(struct i2c_client *client) ret = devm_request_irq(dev, chip->irq, max14656_irq, IRQF_TRIGGER_FALLING, MAX14656_NAME, chip); - if (ret) { - dev_err(dev, "request_irq %d failed\n", chip->irq); + if (ret) return -EINVAL; - } enable_irq_wake(chip->irq); schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000)); From 05c9bf35a036944171344711e664e032bf518e08 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:01 +0800 Subject: [PATCH 22/96] power: supply: max77759_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-23-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max77759_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c index c606d7bafcb8..874b9c958e51 100644 --- a/drivers/power/supply/max77759_charger.c +++ b/drivers/power/supply/max77759_charger.c @@ -533,9 +533,7 @@ static int max77759_init_irqhandler(struct max77759_charger *chg) ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, thread_fn, 0, name, chg); if (ret) - return dev_err_probe(dev, ret, - "Unable to register irq handler for %s\n", - chgr_irqs_str[i]); + return ret; } return 0; From 28b94b06c64a1b423e64357f4c2cf3256f3a35f7 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:02 +0800 Subject: [PATCH 23/96] power: supply: max8903_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-24-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max8903_charger.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c index 45fbaad6c647..47334bfc8381 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -364,11 +364,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 DC IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for DC (%d)\n", - gpiod_to_irq(data->dok), ret); + if (ret) return ret; - } } if (data->uok) { @@ -377,11 +374,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 USB IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for USB (%d)\n", - gpiod_to_irq(data->uok), ret); + if (ret) return ret; - } } if (data->flt) { @@ -390,11 +384,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Fault", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for Fault (%d)\n", - gpiod_to_irq(data->flt), ret); + if (ret) return ret; - } } return 0; From 342b3ce23061ff9ad5c033596c60a663d15af116 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:03 +0800 Subject: [PATCH 24/96] power: supply: max8971_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-25-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max8971_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/max8971_charger.c b/drivers/power/supply/max8971_charger.c index 49a05858bef8..9525ab5da582 100644 --- a/drivers/power/supply/max8971_charger.c +++ b/drivers/power/supply/max8971_charger.c @@ -684,7 +684,7 @@ static int max8971_probe(struct i2c_client *client) err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt, IRQF_ONESHOT | IRQF_SHARED, client->name, priv); if (err) - return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq); + return err; extcon = of_graph_get_remote_node(dev->of_node, -1, -1); if (!extcon) From f73b8c7c3ee61e08819a688bef98c8c454b6d4d5 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:04 +0800 Subject: [PATCH 25/96] power: supply: mp2629_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-26-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/mp2629_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c index d281c1059629..d7224a63abe2 100644 --- a/drivers/power/supply/mp2629_charger.c +++ b/drivers/power/supply/mp2629_charger.c @@ -631,10 +631,8 @@ static int mp2629_charger_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, mp2629_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_RISING, "mp2629-charger", charger); - if (ret) { - dev_err(dev, "failed to request gpio IRQ\n"); + if (ret) return ret; - } regmap_update_bits(charger->regmap, MP2629_REG_INTERRUPT, GENMASK(6, 5), BIT(6) | BIT(5)); From 576014dbc39716304c7ee22dc5c0998222ae6676 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:05 +0800 Subject: [PATCH 26/96] power: supply: mt6360_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-27-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/mt6360_charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c index d3b0731f6562..b4d462343151 100644 --- a/drivers/power/supply/mt6360_charger.c +++ b/drivers/power/supply/mt6360_charger.c @@ -721,8 +721,7 @@ static int mt6360_chg_irq_register(struct platform_device *pdev) irq_descs[i].name, platform_get_drvdata(pdev)); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "Failed to request %s irq\n", - irq_descs[i].name); + return ret; } return 0; From 1a3d312038e6e1d19b8fd9146fc544f09336c8bd Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:06 +0800 Subject: [PATCH 27/96] power: supply: mt6370-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-28-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/mt6370-charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c index 916556baa854..9d388bc53098 100644 --- a/drivers/power/supply/mt6370-charger.c +++ b/drivers/power/supply/mt6370-charger.c @@ -837,9 +837,7 @@ static int mt6370_chg_init_irq(struct mt6370_priv *priv) IRQF_TRIGGER_FALLING, dev_name(priv->dev), priv); if (ret) - return dev_err_probe(priv->dev, ret, - "Failed to request irq %s\n", - mt6370_chg_irqs[i].name); + return ret; } return 0; From 4fed9665ab09cb7c40f894506b8ee2730ed9f3e1 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:07 +0800 Subject: [PATCH 28/96] power: supply: pf1550-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Reviewed-by: Samuel Kayode Link: https://patch.msgid.link/20260709033428.362970-29-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/pf1550-charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c index 41036f4cb64a..94fe81bab5a2 100644 --- a/drivers/power/supply/pf1550-charger.c +++ b/drivers/power/supply/pf1550-charger.c @@ -612,8 +612,7 @@ static int pf1550_charger_probe(struct platform_device *pdev) IRQF_NO_SUSPEND, "pf1550-charger", chg); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed irq request\n"); + return ret; } pf1550_dt_parse_dev_info(chg); From cd05fa1ffae4e21c520b64e203f50b7d76d35a4f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:08 +0800 Subject: [PATCH 29/96] power: supply: qcom_smbb: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-30-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbb.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c index 28afe758a2da..f33aab9619c4 100644 --- a/drivers/power/supply/qcom_smbb.c +++ b/drivers/power/supply/qcom_smbb.c @@ -937,11 +937,8 @@ static int smbb_charger_probe(struct platform_device *pdev) rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, smbb_charger_irqs[i].handler, IRQF_ONESHOT, smbb_charger_irqs[i].name, chg); - if (rc) { - dev_err(&pdev->dev, "failed to request irq '%s'\n", - smbb_charger_irqs[i].name); + if (rc) return rc; - } } /* From c01b2be3845ffae11a067cf93a7833ca840d7d1c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:09 +0800 Subject: [PATCH 30/96] power: supply: qcom_smbx: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-31-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_smbx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index bf2e2ccc454a..7bda5cd77a75 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -922,8 +922,7 @@ static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name, rc = devm_request_threaded_irq(chip->dev, irqnum, NULL, handler, IRQF_ONESHOT, name, chip); if (rc < 0) - return dev_err_probe(chip->dev, rc, "Couldn't request irq %s\n", - name); + return rc; if (irq) *irq = irqnum; From 6d5c18483f0cf12eb636dcf46be452054f3aef4f Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:10 +0800 Subject: [PATCH 31/96] power: supply: rk817_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-32-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rk817_charger.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c index 9436c6bbf51f..5558b182a1a6 100644 --- a/drivers/power/supply/rk817_charger.c +++ b/drivers/power/supply/rk817_charger.c @@ -1186,19 +1186,15 @@ static int rk817_charger_probe(struct platform_device *pdev) rk817_plug_in_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_in", charger); - if (ret) { - return dev_err_probe(&pdev->dev, ret, - "plug_in_irq request failed!\n"); - } + if (ret) + return ret; ret = devm_request_threaded_irq(charger->dev, plugout_irq, NULL, rk817_plug_out_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_out", charger); - if (ret) { - return dev_err_probe(&pdev->dev, ret, - "plug_out_irq request failed!\n"); - } + if (ret) + return ret; ret = devm_delayed_work_autocancel(&pdev->dev, &charger->work, rk817_charging_monitor); From a69f6b1e30f72c5fdeab81319c378ebb6e539803 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:11 +0800 Subject: [PATCH 32/96] power: supply: rn5t618_power: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-33-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rn5t618_power.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index 40dec55a9f73..5963a55341ee 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -800,11 +800,8 @@ static int rn5t618_power_probe(struct platform_device *pdev) "rn5t618_power", &pdev->dev); - if (ret < 0) { - dev_err(&pdev->dev, "request IRQ:%d fail\n", - info->irq); + if (ret < 0) info->irq = -1; - } } return 0; From 406b652bf9c47d6e784fd2ab723e6798d322ef47 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:12 +0800 Subject: [PATCH 33/96] power: supply: rt9455_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-34-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9455_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 7045d2908148..4f3a799db654 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1676,10 +1676,8 @@ static int rt9455_probe(struct i2c_client *client) rt9455_irq_handler_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, RT9455_DRIVER_NAME, info); - if (ret) { - dev_err(dev, "Failed to register IRQ handler\n"); + if (ret) goto put_usb_notifier; - } ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr); if (ret) { From 1683815ef8892a56c2e07548b879f341bfda2952 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:13 +0800 Subject: [PATCH 34/96] power: supply: rt9467-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-35-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9467-charger.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index 44c26fb37a77..83ac1a9766ce 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -1031,8 +1031,7 @@ static int rt9467_request_interrupt(struct rt9467_chg_data *data) ret = devm_request_threaded_irq(dev, virq, NULL, chg_irqs[i].handler, IRQF_ONESHOT, chg_irqs[i].name, data); if (ret) - return dev_err_probe(dev, ret, "Failed to request (%s) irq\n", - chg_irqs[i].name); + return ret; } return 0; From 94f491a228bdde8982a3fd72cd1d46b12eef378e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:14 +0800 Subject: [PATCH 35/96] power: supply: rt9471: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-36-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9471.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index e7f843f12c98..e1cfe484ea91 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -584,8 +584,7 @@ static int rt9471_register_interrupts(struct rt9471_chip *chip) ret = devm_request_threaded_irq(dev, virq, NULL, curr->handler, IRQF_ONESHOT, curr->name, chip); if (ret) - return dev_err_probe(dev, ret, "Failed to register IRQ (%s)\n", - curr->name); + return ret; } return 0; From bfae8c8ad5adc1796d7982feb2dd4a0bd947ba84 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:15 +0800 Subject: [PATCH 36/96] power: supply: sbs-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-37-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index a00c710601e8..e7b4e10dcfd5 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -217,7 +217,7 @@ static int sbs_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&client->dev), chip); if (ret) - return dev_err_probe(&client->dev, ret, "Failed to request irq\n"); + return ret; } else { ret = devm_delayed_work_autocancel(&client->dev, &chip->work, sbs_delayed_work); From 139c9e37448d215647d8c1a98974c0d8309af1de Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:16 +0800 Subject: [PATCH 37/96] power: supply: sc27xx_fuel_gauge: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-38-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sc27xx_fuel_gauge.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index a7ed9de8a289..9af0017fa88e 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -1231,10 +1231,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) sc27xx_fgu_interrupt, IRQF_NO_SUSPEND | IRQF_ONESHOT, pdev->name, data); - if (ret) { - dev_err(data->dev, "failed to request fgu IRQ\n"); + if (ret) return ret; - } irq = gpiod_to_irq(data->gpiod); if (irq < 0) { @@ -1247,10 +1245,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, data); - if (ret) { - dev_err(dev, "failed to request IRQ\n"); + if (ret) return ret; - } return 0; } From 4e8ef72bbd3ef34e1137d365e7719399f45d5c0b Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:17 +0800 Subject: [PATCH 38/96] power: supply: surface-rt-ec: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-39-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/surface-rt-ec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/surface-rt-ec.c b/drivers/power/supply/surface-rt-ec.c index a728ae0db858..da57b0a1a495 100644 --- a/drivers/power/supply/surface-rt-ec.c +++ b/drivers/power/supply/surface-rt-ec.c @@ -337,7 +337,7 @@ static int srt_ec_probe(struct i2c_client *client) ret = devm_request_threaded_irq(dev, client->irq, NULL, srt_psy_detect_irq, IRQF_ONESHOT, client->name, srt); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request interrupt\n"); + return ret; ret = devm_delayed_work_autocancel(dev, &srt->poll_work, srt_bat_poll_work); if (ret < 0) From ceaee7db26ffaeb78b64df9f21be4a1c7823793a Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:18 +0800 Subject: [PATCH 39/96] power: supply: tps65090-charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-40-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/tps65090-charger.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c index d010f013af8c..36340c56d272 100644 --- a/drivers/power/supply/tps65090-charger.c +++ b/drivers/power/supply/tps65090-charger.c @@ -302,12 +302,8 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (irq != -ENXIO) { ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, tps65090_charger_isr, IRQF_ONESHOT, "tps65090-charger", cdata); - if (ret) { - dev_err(cdata->dev, - "Unable to register irq %d err %d\n", irq, - ret); + if (ret) return ret; - } } else { cdata->poll_task = kthread_run(tps65090_charger_poll_task, cdata, "ktps65090charger"); From abd5cd2d712cae2a7abc3852d9fbd05696d4b9eb Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:19 +0800 Subject: [PATCH 40/96] power: supply: tps65217_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-41-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/tps65217_charger.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 6af17ce0b204..cd00a3f372b3 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -239,12 +239,8 @@ static int tps65217_charger_probe(struct platform_device *pdev) tps65217_charger_irq, IRQF_SHARED, "tps65217-charger", charger); - if (ret) { - dev_err(charger->dev, - "Unable to register irq %d err %d\n", irq[i], - ret); + if (ret) return ret; - } /* Check current state */ tps65217_charger_irq(-1, charger); From ca5cb9488b800a5b39954391530244da41076f79 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:20 +0800 Subject: [PATCH 41/96] power: supply: twl4030_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-42-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 04216b2bfb6c..1cace8f73927 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -1055,19 +1055,13 @@ static int twl4030_bci_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL, twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name, bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_chg, ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL, twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_bci, ret); + if (ret < 0) return ret; - } /* Enable interrupts now. */ reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | From c2d8a2b01bef42f4e50a87e5880f75401be1290c Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:21 +0800 Subject: [PATCH 42/96] power: supply: twl6030_charger: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-43-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl6030_charger.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c index b4ec26ff257c..020512d0e306 100644 --- a/drivers/power/supply/twl6030_charger.c +++ b/drivers/power/supply/twl6030_charger.c @@ -542,9 +542,7 @@ static int twl6030_charger_probe(struct platform_device *pdev) IRQF_ONESHOT, pdev->name, charger); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "could not request irq %d\n", - charger->irq_chg); + return ret; /* turing to charging to configure things */ twl6030_charger_write(CONTROLLER_CTRL1, 0); From 5a6b9c36c56340812d49817524a5d96f7522c74d Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 11:34:22 +0800 Subject: [PATCH 43/96] power: supply: ucs1002_power: Remove redundant dev_err()/dev_err_probe() The devm_request_threaded_irq() and devm_request_irq() now automatically log detailed error messages on failure. This eliminates the need for driver-specific dev_err() and dev_err_probe() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709033428.362970-44-panchuang@vivo.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ucs1002_power.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 3f44cc902f84..735880ed1cc8 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -647,21 +647,15 @@ static int ucs1002_probe(struct i2c_client *client) ucs1002_charger_irq, IRQF_ONESHOT, "ucs1002-a_det", info); - if (ret) { - dev_err(dev, "Failed to request A_DET threaded irq: %d\n", - ret); + if (ret) return ret; - } } if (irq_alert > 0) { ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq, 0,"ucs1002-alert", info); - if (ret) { - dev_err(dev, "Failed to request ALERT threaded irq: %d\n", - ret); + if (ret) return ret; - } } return 0; From 6a3b47a5a5fc0e294aa6d62ea70b63571f829823 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:53:07 -0500 Subject: [PATCH 44/96] power: supply: Match DT value types The battery drivers read properties whose bindings use signed values for temperature thresholds and unsigned values for voltage thresholds. Some helpers used the opposite signedness, which makes property type checking report real mismatches. Use signed helpers where the binding and DTS allow negative values, and use unsigned helpers for voltage properties documented as uint32 cells. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260612215308.1888834-1-robh@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc2941-battery-gauge.c | 2 +- drivers/power/supply/max17042_battery.c | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index 5b6760722f38..d1be314f36a0 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -463,7 +463,7 @@ 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_u32(np, "lltc,resistor-sense", &r_sense); + ret = of_property_read_s32(np, "lltc,resistor-sense", &r_sense); if (ret < 0) return dev_err_probe(&client->dev, ret, "Could not find lltc,resistor-sense in devicetree\n"); diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 639dacdb9b31..581699335a5f 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -1027,10 +1027,14 @@ static int max17042_parse_dt(struct max17042_chip *chip) chip->temp_min = INT_MIN; if (of_property_read_s32(np, "maxim,over-heat-temp", &chip->temp_max)) chip->temp_max = INT_MAX; - if (of_property_read_s32(np, "maxim,dead-volt", &chip->vmin)) + if (of_property_read_u32(np, "maxim,dead-volt", &prop)) chip->vmin = INT_MIN; - if (of_property_read_s32(np, "maxim,over-volt", &chip->vmax)) + else + chip->vmin = prop; + if (of_property_read_u32(np, "maxim,over-volt", &prop)) chip->vmax = INT_MAX; + else + chip->vmin = prop; return 0; } From 402684c39cb911bc772ffc13bfee95ab57cdd2f1 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:53:24 -0500 Subject: [PATCH 45/96] power: reset: syscon-reboot: Read "priority" as unsigned The restart-handler binding documents "priority" as a uint32 value in the range 0 to 255. The syscon reboot driver stored it in an int and used the signed DT helper, which makes the helper type disagree with the schema. Read "priority" as u32 so the driver follows the binding while preserving the same default value. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260612215325.1889302-1-robh@kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/reset/syscon-reboot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c index 2e2cf5f62d73..60e855cae68e 100644 --- a/drivers/power/reset/syscon-reboot.c +++ b/drivers/power/reset/syscon-reboot.c @@ -67,7 +67,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) { struct syscon_reboot_context *ctx; struct device *dev = &pdev->dev; - int priority; + u32 priority; int err; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); @@ -81,7 +81,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) return PTR_ERR(ctx->map); } - if (of_property_read_s32(pdev->dev.of_node, "priority", &priority)) + if (of_property_read_u32(pdev->dev.of_node, "priority", &priority)) priority = 192; ctx->rd = of_device_get_match_data(dev); From 631adfa03595c63b2a621cdc62db60bb1ce5f91c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 15 Jun 2026 14:31:05 +0800 Subject: [PATCH 46/96] power: supply: cros_pchg: unregister EC notifier cros_pchg_probe() registers an EC event notifier whose callback uses the devm-allocated charger_data via container_of(). The driver has no remove callback and does not unregister the notifier, so the notifier chain can retain a pointer to freed driver state after unbind or probe cleanup. Register a devm cleanup action immediately after the notifier is installed so the notifier is unregistered before the driver state is released. Also fail probe if the notifier cannot be registered, instead of leaving a charger device that cannot receive EC events. Signed-off-by: Pengpeng Hou Reviewed-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260615063105.39152-1-pengpeng@iscas.ac.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_peripheral_charger.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c index 9f67a6dbd94e..0f4e34710b46 100644 --- a/drivers/power/supply/cros_peripheral_charger.c +++ b/drivers/power/supply/cros_peripheral_charger.c @@ -259,6 +259,14 @@ static int cros_ec_notify(struct notifier_block *nb, return cros_pchg_event(charger); } +static void cros_pchg_unregister_notifier(void *data) +{ + struct charger_data *charger = data; + + blocking_notifier_chain_unregister(&charger->ec_device->event_notifier, + &charger->notifier); +} + static int cros_pchg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -346,9 +354,10 @@ static int cros_pchg_probe(struct platform_device *pdev) ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier, nb); if (ret < 0) - dev_err(dev, "Failed to register notifier (err:%d)\n", ret); + return dev_err_probe(dev, ret, "Failed to register notifier\n"); - return 0; + return devm_add_action_or_reset(dev, cros_pchg_unregister_notifier, + charger); } #ifdef CONFIG_PM_SLEEP From 48355ce49359740f52e94d3623f6fc557ce341f0 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Tue, 16 Jun 2026 23:59:45 -0500 Subject: [PATCH 47/96] power: supply: cros_usbpd-charger: bound the EC-reported port count cros_usbpd_charger_probe() reads two port counts from the EC and uses one of them, num_charger_ports, as the loop bound when populating a fixed-size array: struct port_data *ports[EC_USB_PD_MAX_PORTS]; /* 8 entries */ ... for (i = 0; i < charger->num_charger_ports; i++) charger->ports[charger->num_registered_psy++] = port; Both num_usbpd_ports (from EC_CMD_USB_PD_PORTS) and num_charger_ports (from EC_CMD_CHARGE_PORT_COUNT) are u8 values reported by the EC. The only validation is a sanity check that compares the two EC-reported values against each other: if (num_charger_ports < num_usbpd_ports || num_charger_ports > num_usbpd_ports + 1) return -EPROTO; It never checks either count against EC_USB_PD_MAX_PORTS, the size of the ports[] array. A malfunctioning, malicious or compromised EC that reports num_usbpd_ports == num_charger_ports == N for any N > 8 (for example both 255) passes this check, and the loop then writes N pointers into the 8-entry ports[] array embedded in the devm_kzalloc()'d charger_data, overflowing it by up to 255 - 8 = 247 entries (~1976 bytes): a slab out-of-bounds write. Reject a port count larger than the ports[] array can hold. Fixes: f68b883e8fad ("power: supply: add cros-ec USBPD charger driver.") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260616-b4-disp-5e197080-v2-1-8aa5bffce945@proton.me Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_usbpd-charger.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index 308e1d4e6dd8..e410048aa0ab 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -589,10 +589,13 @@ static int cros_usbpd_charger_probe(struct platform_device *pd) /* * Sanity checks on the number of ports: - * there should be at most 1 dedicated port + * there should be at most 1 dedicated port, and the count must + * not exceed the maximum number of supported ports + * (EC_USB_PD_MAX_PORTS). */ if (charger->num_charger_ports < charger->num_usbpd_ports || - charger->num_charger_ports > (charger->num_usbpd_ports + 1)) { + charger->num_charger_ports > (charger->num_usbpd_ports + 1) || + charger->num_charger_ports > EC_USB_PD_MAX_PORTS) { dev_err(dev, "Unexpected number of charge port count\n"); ret = -EPROTO; goto fail_nowarn; From 380f91cdbd3b551ecb6c0b4759c7a1257b055049 Mon Sep 17 00:00:00 2001 From: Jean-Michel Hautbois Date: Wed, 17 Jun 2026 21:17:15 +0200 Subject: [PATCH 48/96] power: reset: add MCF5441x RCM power-on reason driver Add a driver that decodes the Reset Status Register (RSR) of the Freescale ColdFire MCF5441x Reset Controller Module at probe time and exposes the cause via the power_on_reason sysfs ABI. The RSR can latch several cause bits simultaneously (Reference Manual chapter 12.3.2); the driver picks one cause per a priority that favours the most explanatory diagnostic. The register layout is described in the driver so it can be built with COMPILE_TEST on any architecture. Signed-off-by: Jean-Michel Hautbois Link: https://patch.msgid.link/20260617-coldfire-rcm-power-on-reason-v3-1-1a7c7b6b14e7@yoseli.org Signed-off-by: Sebastian Reichel --- MAINTAINERS | 6 ++ drivers/power/reset/Kconfig | 12 +++ drivers/power/reset/Makefile | 1 + drivers/power/reset/mcf-rcm-reset.c | 126 ++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 drivers/power/reset/mcf-rcm-reset.c diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..15632f9d5d5e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10291,6 +10291,12 @@ S: Maintained F: drivers/mmc/host/sdhci-esdhc-mcf.c F: include/linux/platform_data/mmc-esdhc-mcf.h +FREESCALE COLDFIRE M5441X RCM POWER-ON REASON DRIVER +M: Jean-Michel Hautbois +L: linux-m68k@lists.linux-m68k.org +S: Maintained +F: drivers/power/reset/mcf-rcm-reset.c + FREESCALE DIU FRAMEBUFFER DRIVER M: Timur Tabi L: linux-fbdev@vger.kernel.org diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 124afb99febe..bce996bbef28 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -128,6 +128,18 @@ config POWER_RESET_LINKSTATION Say Y here if you have a Buffalo LinkStation LS421D/E. +config POWER_RESET_MCF_RCM + tristate "Freescale ColdFire RCM power-on reason driver" + depends on M5441x || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the cause of the last reset on Freescale + ColdFire 5441x SoCs through the standard power_on_reason sysfs + ABI. It reads the Reset Status Register (RSR) of the Reset + Controller Module once at probe time and reports a normalised + string (regular power-up, reset button action, software reset + or unknown reason). + config POWER_RESET_MACSMC tristate "Apple SMC reset/power-off driver" depends on MFD_MACSMC diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index d7ae97241a83..e31cab4ba78e 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MCF_RCM) += mcf-rcm-reset.o obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o diff --git a/drivers/power/reset/mcf-rcm-reset.c b/drivers/power/reset/mcf-rcm-reset.c new file mode 100644 index 000000000000..18f11c05a868 --- /dev/null +++ b/drivers/power/reset/mcf-rcm-reset.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Freescale ColdFire MCF5441x RCM power-on reason driver + * + * Copyright (C) 2026 Jean-Michel Hautbois + */ + +#include +#include +#include +#include +#include + +/* + * Reset Status Register (RSR) layout, MCF54418 Reference Manual chapter + * 12.3.2. The register is 8-bit read-only and latches the cause (or + * causes) of the most recent reset until the next one occurs. + */ +#define MCF_RSR_SOFT 0x20 /* Last reset caused by software */ +#define MCF_RSR_LOC 0x10 /* Last reset caused by PLL loss of clock */ +#define MCF_RSR_POR 0x08 /* Last reset caused by power-on */ +#define MCF_RSR_EXT 0x04 /* Last reset caused by external pin */ +#define MCF_RSR_WDRCORE 0x02 /* Last reset caused by core watchdog */ +#define MCF_RSR_LOL 0x01 /* Last reset caused by PLL loss of lock */ + +#define MCF_RSR_KNOWN_CAUSES (MCF_RSR_POR | MCF_RSR_EXT | MCF_RSR_WDRCORE | \ + MCF_RSR_LOC | MCF_RSR_LOL | MCF_RSR_SOFT) + +struct mcf_rcm { + const char *reason; +}; + +/* + * Decode RSR into a power_on_reason string. + * + * The MCF5441x Reset Status Register can latch several cause bits at the + * same time (Reference Manual chapter 12.3.2: "one or more status bits + * may be set at the same time"). A power-on, for example, also resets + * the PLL and may co-flag LOC and LOL during the boot sequence. The + * power_on_reason ABI carries a single string, so this routine picks + * one cause; the chosen priority surfaces the most explanatory one for + * diagnostics: + * + * POR cold boot dominates any spurious co-flagged cause + * EXT operator action via the RESET pin + * WDRCORE core watchdog timeout, a fault to investigate + * LOC, LOL PLL clock or lock failure, hardware fault + * SOFT explicit software-requested reset + */ +static const char *mcf_rcm_decode(u8 rsr) +{ + if (rsr & MCF_RSR_POR) + return POWER_ON_REASON_REGULAR; + if (rsr & MCF_RSR_EXT) + return POWER_ON_REASON_RST_BTN; + if (rsr & MCF_RSR_WDRCORE) + return POWER_ON_REASON_WATCHDOG; + if (rsr & (MCF_RSR_LOC | MCF_RSR_LOL)) + return POWER_ON_REASON_CPU_CLK_FAIL; + if (rsr & MCF_RSR_SOFT) + return POWER_ON_REASON_SOFTWARE; + return POWER_ON_REASON_UNKNOWN; +} + +static ssize_t power_on_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcf_rcm *rcm = platform_get_drvdata(to_platform_device(dev)); + + return sysfs_emit(buf, "%s\n", rcm->reason); +} +static DEVICE_ATTR_RO(power_on_reason); + +static struct attribute *mcf_rcm_attrs[] = { + &dev_attr_power_on_reason.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mcf_rcm); + +static int mcf_rcm_probe(struct platform_device *pdev) +{ + struct mcf_rcm *rcm; + void __iomem *base; + u8 rsr; + + rcm = devm_kzalloc(&pdev->dev, sizeof(*rcm), GFP_KERNEL); + if (!rcm) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + rsr = readb_relaxed(base); + rcm->reason = mcf_rcm_decode(rsr); + + platform_set_drvdata(pdev, rcm); + + if (!(rsr & MCF_RSR_KNOWN_CAUSES)) + dev_warn(&pdev->dev, "Unknown reset cause (RSR=0x%02x)\n", rsr); + else + dev_info(&pdev->dev, "Starting after %s (RSR=0x%02x)\n", + rcm->reason, rsr); + + return 0; +} + +static const struct platform_device_id mcf_rcm_id[] = { + { "mcf-rcm-reset" }, + { } +}; +MODULE_DEVICE_TABLE(platform, mcf_rcm_id); + +static struct platform_driver mcf_rcm_driver = { + .probe = mcf_rcm_probe, + .id_table = mcf_rcm_id, + .driver = { + .name = "mcf-rcm-reset", + .dev_groups = mcf_rcm_groups, + }, +}; +module_platform_driver(mcf_rcm_driver); + +MODULE_AUTHOR("Jean-Michel Hautbois "); +MODULE_DESCRIPTION("Freescale ColdFire RCM power-on reason driver"); +MODULE_LICENSE("GPL"); From 5ec27fa6b9d937188a81724f09df82707cca944e Mon Sep 17 00:00:00 2001 From: Boris Shtrasman Date: Thu, 25 Jun 2026 19:05:54 +0300 Subject: [PATCH 49/96] selftests: helpers: handle multi line in test_sysfs_prop_optional_list Modify test_sysfs_prop_optional_list to allow multi line parameters, in order to comply with checkpatch output that limit line by 100 chars. Allow use cases like: test_sysfs_prop_optional_list \ property_name "value1","value2","val3","very_long_option"\ ,"extra_long_option" \ ,"even_more_data" Signed-off-by: Boris Shtrasman Link: https://patch.msgid.link/20260625160556.54830-2-borissh1983@gmail.com Signed-off-by: Sebastian Reichel --- tools/testing/selftests/power_supply/helpers.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/power_supply/helpers.sh b/tools/testing/selftests/power_supply/helpers.sh index 1ec90d7c9108..ff1175aeed72 100644 --- a/tools/testing/selftests/power_supply/helpers.sh +++ b/tools/testing/selftests/power_supply/helpers.sh @@ -108,7 +108,7 @@ test_sysfs_prop_optional_range() { test_sysfs_prop_optional_list() { PROP=$1 - LIST=$2 + shift TEST_NAME="$DEVNAME".sysfs."$PROP" @@ -118,9 +118,9 @@ test_sysfs_prop_optional_list() { valid=0 OLDIFS=$IFS - IFS="," - for item in $LIST; do - if [ "$DATA" = "$item" ]; then + IFS=",$IFS" + for item in $*; do + if [ "$item" ] && [ "$DATA" = "$item" ]; then valid=1 break fi From e4426ddf7844096b6ced5de716401dcf1eb1e08b Mon Sep 17 00:00:00 2001 From: Boris Shtrasman Date: Wed, 24 Jun 2026 16:57:17 +0300 Subject: [PATCH 50/96] power: supply: Add PbAc, NiZn, RAM, and ZnAr support Add four new members to the POWER_SUPPLY_TECHNOLOGY enum and sysfs interface to represent the Smart Battery Data Specification v1.1 (Section 5.1.30 DeviceChemistry) battery types: - Lead Acid (PbAc) - Nickel Zinc (NiZn) - Rechargeable Alkaline-Manganese (RAM) - Zinc Air (ZnAr) Update documentation to express these types. Update ABI testing for these types. Link: https://sbs-forum.org/specs/sbdat110.pdf Signed-off-by: Boris Shtrasman Link: https://patch.msgid.link/20260624135718.286771-2-borissh1983@gmail.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 2 +- drivers/power/supply/power_supply_sysfs.c | 4 ++++ include/linux/power_supply.h | 4 ++++ .../selftests/power_supply/test_power_supply_properties.sh | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 32697b926cc8..5641f1fd5fd6 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -525,7 +525,7 @@ Description: Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", - "NiCd", "LiMn" + "NiCd", "LiMn", "PbAc", "NiZn", "RAM", "ZnAr" What: /sys/class/power_supply//voltage_avg, diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index f30a7b9ccd5e..9d6b24856c8b 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -124,6 +124,10 @@ static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = { [POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe", [POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd", [POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn", + [POWER_SUPPLY_TECHNOLOGY_PbAc] = "PbAc", + [POWER_SUPPLY_TECHNOLOGY_NiZn] = "NiZn", + [POWER_SUPPLY_TECHNOLOGY_RAM] = "RAM", + [POWER_SUPPLY_TECHNOLOGY_ZnAr] = "ZnAr", }; static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a0..034800cd21da 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -83,6 +83,10 @@ enum { POWER_SUPPLY_TECHNOLOGY_LiFe, POWER_SUPPLY_TECHNOLOGY_NiCd, POWER_SUPPLY_TECHNOLOGY_LiMn, + POWER_SUPPLY_TECHNOLOGY_PbAc, + POWER_SUPPLY_TECHNOLOGY_NiZn, + POWER_SUPPLY_TECHNOLOGY_RAM, + POWER_SUPPLY_TECHNOLOGY_ZnAr, }; enum { diff --git a/tools/testing/selftests/power_supply/test_power_supply_properties.sh b/tools/testing/selftests/power_supply/test_power_supply_properties.sh index a66b1313ed88..1ebac6fe5d23 100755 --- a/tools/testing/selftests/power_supply/test_power_supply_properties.sh +++ b/tools/testing/selftests/power_supply/test_power_supply_properties.sh @@ -74,7 +74,8 @@ for DEVNAME in $supplies; do test_sysfs_prop_optional model_name test_sysfs_prop_optional manufacturer test_sysfs_prop_optional serial_number - test_sysfs_prop_optional_list technology "Unknown","NiMH","Li-ion","Li-poly","LiFe","NiCd","LiMn" + test_sysfs_prop_optional_list technology "Unknown","NiMH","Li-ion","Li-poly","LiFe","NiCd"\ + ,"LiMn","PbAc","NiZn","RAM","ZnAr" test_sysfs_prop_optional cycle_count From f97d9166048ddea147b5673330f9900bf08c725d Mon Sep 17 00:00:00 2001 From: Boris Shtrasman Date: Wed, 24 Jun 2026 16:57:18 +0300 Subject: [PATCH 51/96] power: supply: sbs-battery: Add PbAc, NiZn, RAM, and ZnAr support Add support for PbAc, NiZn, RAM, and ZnAr chemistries as defined in the Smart Battery Data Specification v1.1 (Section 5.1.30 DeviceChemistry). Currently, the sbs-battery driver only handles LION, LiP, NiCd and NiMH. The Smart Battery specification defines 8 possible values: - Lead Acid (PbAc) - Lithium Ion (LION) - Nickel Cadmium (NiCd) - Nickel Metal Hydride (NiMH) - Nickel Zinc (NiZn) - Rechargeable Alkaline-Manganese (RAM) - Zinc Air (ZnAr) - Lithium Polymer (LiP) Link: https://sbs-forum.org/specs/sbdat110.pdf Signed-off-by: Boris Shtrasman Link: https://patch.msgid.link/20260624135718.286771-3-borissh1983@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 017ec06be766..17b0fe296902 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -860,6 +860,14 @@ static int sbs_get_chemistry(struct sbs_info *chip, chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strncasecmp(chemistry, "NiMH", 4)) chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; + else if (!strncasecmp(chemistry, "PbAc", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_PbAc; + else if (!strncasecmp(chemistry, "NiZn", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiZn; + else if (!strncasecmp(chemistry, "RAM", 3)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_RAM; + else if (!strncasecmp(chemistry, "ZnAr", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_ZnAr; else chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; From b04510c3af6dd34b2e8dcf303114d77d5e443cac Mon Sep 17 00:00:00 2001 From: Steffen Dirkwinkel Date: Thu, 25 Jun 2026 19:07:34 +0200 Subject: [PATCH 52/96] power: supply: leds: create triggers based on properties, not type Currently only battery power supplies get triggers for other properties and other supplies only get the online trigger. This changes it to provide the triggers for any power supply depending on what properties are available. Batteries will still get the same triggers if the properties are there, but now other power supplies can get the triggers too. Signed-off-by: Steffen Dirkwinkel Link: https://patch.msgid.link/20260625-std-power-supply-triggers-v1-1-d80db570d329@beckhoff.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_leds.c | 88 ++++++++++++------------ include/linux/power_supply.h | 3 +- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index 1548aaba3362..538cc0440e53 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -17,8 +17,6 @@ #include "power_supply.h" -/* Battery specific LEDs triggers. */ - struct power_supply_led_trigger { struct led_trigger trig; struct power_supply *psy; @@ -89,7 +87,7 @@ static void power_supply_unregister_led_trigger(struct led_trigger *trig) kfree(psy_trig); } -static void power_supply_update_bat_leds(struct power_supply *psy) +static void power_supply_update_status_leds(struct power_supply *psy) { union power_supply_propval status; unsigned int intensity_green[3] = { 0, 255, 0 }; @@ -102,7 +100,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) switch (status.intval) { case POWER_SUPPLY_STATUS_FULL: - led_trigger_event(psy->trig, LED_FULL); + led_trigger_event(psy->charging_or_full_trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_FULL); /* Going from blink to LED on requires a LED_OFF event to stop blink */ @@ -114,7 +112,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; case POWER_SUPPLY_STATUS_CHARGING: - led_trigger_event(psy->trig, LED_FULL); + led_trigger_event(psy->charging_or_full_trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_FULL); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0); @@ -124,7 +122,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; default: - led_trigger_event(psy->trig, LED_OFF); + led_trigger_event(psy->charging_or_full_trig, LED_OFF); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_event(psy->charging_blink_full_solid_trig, @@ -135,21 +133,15 @@ static void power_supply_update_bat_leds(struct power_supply *psy) } } -static void power_supply_remove_bat_triggers(struct power_supply *psy) -{ - power_supply_unregister_led_trigger(psy->trig); - power_supply_unregister_led_trigger(psy->charging_trig); - power_supply_unregister_led_trigger(psy->full_trig); - power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); - power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); -} - -static int power_supply_create_bat_triggers(struct power_supply *psy) +static int power_supply_create_status_triggers(struct power_supply *psy) { int err = 0; + if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_STATUS)) + return 0; + power_supply_register_led_trigger(psy, "%s-charging-or-full", - &psy->trig, &err); + &psy->charging_or_full_trig, &err); power_supply_register_led_trigger(psy, "%s-charging", &psy->charging_trig, &err); power_supply_register_led_trigger(psy, "%s-full", @@ -158,15 +150,11 @@ static int power_supply_create_bat_triggers(struct power_supply *psy) &psy->charging_blink_full_solid_trig, &err); power_supply_register_led_trigger(psy, "%s-charging-orange-full-green", &psy->charging_orange_full_green_trig, &err); - if (err) - power_supply_remove_bat_triggers(psy); return err; } -/* Generated power specific LEDs triggers. */ - -static void power_supply_update_gen_leds(struct power_supply *psy) +static void power_supply_update_online_leds(struct power_supply *psy) { union power_supply_propval online; @@ -175,43 +163,53 @@ static void power_supply_update_gen_leds(struct power_supply *psy) dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); - if (online.intval) - led_trigger_event(psy->trig, LED_FULL); - else - led_trigger_event(psy->trig, LED_OFF); + led_trigger_event(psy->online_trig, online.intval ? LED_FULL : LED_OFF); } -static int power_supply_create_gen_triggers(struct power_supply *psy) +static int power_supply_create_online_trigger(struct power_supply *psy) { - return power_supply_register_led_trigger(psy, "%s-online", &psy->trig, NULL); -} + int err = 0; -static void power_supply_remove_gen_triggers(struct power_supply *psy) -{ - power_supply_unregister_led_trigger(psy->trig); -} + if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_ONLINE)) + return 0; -/* Choice what triggers to create&update. */ + power_supply_register_led_trigger(psy, "%s-online", &psy->online_trig, + &err); + + return err; +} void power_supply_update_leds(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_update_bat_leds(psy); - else - power_supply_update_gen_leds(psy); + power_supply_update_online_leds(psy); + power_supply_update_status_leds(psy); } int power_supply_create_triggers(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - return power_supply_create_bat_triggers(psy); - return power_supply_create_gen_triggers(psy); + int err; + + err = power_supply_create_online_trigger(psy); + if (err) + goto err_remove; + + err = power_supply_create_status_triggers(psy); + if (err) + goto err_remove; + + return 0; + +err_remove: + power_supply_remove_triggers(psy); + return err; } void power_supply_remove_triggers(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_remove_bat_triggers(psy); - else - power_supply_remove_gen_triggers(psy); + power_supply_unregister_led_trigger(psy->online_trig); + power_supply_unregister_led_trigger(psy->charging_or_full_trig); + power_supply_unregister_led_trigger(psy->charging_trig); + power_supply_unregister_led_trigger(psy->full_trig); + power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); + power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); } diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 034800cd21da..9b4030802d78 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -350,7 +350,8 @@ struct power_supply { #endif #ifdef CONFIG_LEDS_TRIGGERS - struct led_trigger *trig; + struct led_trigger *charging_or_full_trig; + struct led_trigger *online_trig; struct led_trigger *charging_trig; struct led_trigger *full_trig; struct led_trigger *charging_blink_full_solid_trig; From 741a9b09775517c76a5029c58a03725b414d7e20 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 1 Jul 2026 12:59:08 +0200 Subject: [PATCH 53/96] dt-bindings: power: reset: qcom,pon: Narrow allowed reboot modes reboot-mode schema allows arbitrary "mode-.* properties but only a subset actually makes sense and is valid. Provide negative look-ahead pattern to disallow any modes not supported by the device, which tightens the binding. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260701105907.197150-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/reset/qcom,pon.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml index 979a377cb4ff..71d43d3602f7 100644 --- a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml +++ b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml @@ -79,6 +79,10 @@ allOf: reg-names: items: - const: pon + patternProperties: + # Negative look-ahead to disallow unsupported modes. The '$' has to be + # part of lookahead group to work, instead of trailing outside of (). + "^mode-(?!(bootloader$|recovery$))": false else: patternProperties: "^mode-.*$": false From 39a4e68035ba6a11f94105056fc3906df1a5b3ea Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Fri, 3 Jul 2026 01:15:47 +0530 Subject: [PATCH 54/96] power: supply: bq256xx: drop always-true inner condition In bq256xx_array_parse() the inner "if (val < array[i])" repeats the second half of the enclosing "if (val > array[i - 1] && val < array[i])", so it is always true and the "else return i" arm is dead code. The function performs a round-down table lookup, so returning i - 1 for a value that falls strictly between two entries is the intended result. Collapse the redundant branch into a single "return i - 1;"; no functional change. Signed-off-by: Anas Khan Link: https://patch.msgid.link/20260702194547.65209-1-anxkhn28@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq256xx_charger.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 4ea89b7897c8..ba88cf90e730 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -347,12 +347,8 @@ static int bq256xx_array_parse(int array_size, int val, const int array[]) if (val == array[i]) return i; - if (val > array[i - 1] && val < array[i]) { - if (val < array[i]) - return i - 1; - else - return i; - } + if (val > array[i - 1] && val < array[i]) + return i - 1; } return -EINVAL; } From ffefc5d9a052f67bc87b57d1107fd7cc1fa1a3d4 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:23:36 +0800 Subject: [PATCH 55/96] power: reset: piix4-poweroff: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704152336.51384-1-pengpeng@iscas.ac.cn Signed-off-by: Sebastian Reichel --- drivers/power/reset/piix4-poweroff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c index e6822c021000..1a6f3d22d297 100644 --- a/drivers/power/reset/piix4-poweroff.c +++ b/drivers/power/reset/piix4-poweroff.c @@ -96,6 +96,7 @@ static const struct pci_device_id piix4_poweroff_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) }, { 0 }, }; +MODULE_DEVICE_TABLE(pci, piix4_poweroff_ids); static struct pci_driver piix4_poweroff_driver = { .name = "piix4-poweroff", From 9da4152d376cacee7f74f37080fc3c459309e1da Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:24:28 +0800 Subject: [PATCH 56/96] power: supply: cw2015_battery: add missing MODULE_DEVICE_TABLE() The driver has a match table for the i2c bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704152428.51844-1-pengpeng@iscas.ac.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/cw2015_battery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 7496295d2948..1bcb60cf763d 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -736,6 +736,7 @@ static const struct i2c_device_id cw_bat_id_table[] = { { .name = "cw2015" }, { } }; +MODULE_DEVICE_TABLE(i2c, cw_bat_id_table); static const struct of_device_id cw2015_of_match[] = { { .compatible = "cellwise,cw2015" }, From 353438b3107d56e36a642f117c2b4accc963abbd Mon Sep 17 00:00:00 2001 From: Herman van Hazendonk Date: Fri, 5 Jun 2026 11:18:12 +0200 Subject: [PATCH 57/96] dt-bindings: power: supply: maxim,max8903: add DC and USB input current-limit controls Add three optional properties to the MAX8903 charger binding to describe board-level GPIO control of the DC and USB input current limits: DC input (TA / DOK pin): - dc-current-limit-gpios (1..4 GPIOs): mux control lines feeding the MAX8903 IDC resistor mux; - dc-current-limit-mapping (uint32-matrix of {microamps, gpio_bit_pattern} pairs): the available current levels and the GPIO bit pattern that selects each level. USB input (USB / UOK pin): - usb-current-limit-gpios: a single GPIO driving the IUSB pin. The IUSB pin is silicon-fixed by the MAX8903 datasheet (Pin Description): low selects 100 mA, high selects 500 mA. Only the GPIO mapping is board-specific; the current values are not exposed in DT. A dependentRequired block ties dc-current-limit-gpios and dc-current-limit-mapping together so an incomplete DT cannot pass validation with only one half of the pair. These let userspace clamp the input draw via the standard POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT power_supply attribute. The HP TouchPad uses both: two TLMM lines select between 0.5 A, 1.0 A, 1.5 A and 2.0 A DC input current limits behind the MAX8903B charger, and a third TLMM line picks the IUSB 100 mA / 500 mA limit. These are purely additive; existing platforms remain unaffected. Signed-off-by: Herman van Hazendonk Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260605-submit-power-max8903-dc-limit-v2-1-0c5396e98f14@herrie.org Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/maxim,max8903.yaml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml index 86af38378999..b3d8c0a5f214 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml @@ -44,9 +44,40 @@ properties: maxItems: 1 description: USB suspend pin (active high, output) + dc-current-limit-gpios: + minItems: 1 + maxItems: 4 + description: + GPIOs controlling DC input current limit via resistor mux. + Used with dc-current-limit-mapping to select charging current. + + dc-current-limit-mapping: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + minItems: 2 + maxItems: 16 + description: | + Array of (current_microamps, gpio_bit_pattern) pairs defining available + DC current limits. The gpio_bit_pattern is applied to dc-current-limit-gpios + to select that current level. + items: + items: + - description: Current limit in microamps + - description: GPIO bit pattern value + + usb-current-limit-gpios: + maxItems: 1 + description: + GPIO driving the MAX8903 IUSB pin. IUSB is a silicon-fixed + two-state input - low selects 100 mA, high selects 500 mA. + Only the GPIO mapping is board-specific. + required: - compatible +dependentRequired: + dc-current-limit-gpios: [dc-current-limit-mapping] + dc-current-limit-mapping: [dc-current-limit-gpios] + anyOf: - required: - dok-gpios @@ -65,3 +96,25 @@ examples: chg-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>; cen-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; }; + - | + /* Example with DC and USB current limit control */ + #include + charger { + compatible = "maxim,max8903"; + dok-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>; + flt-gpios = <&gpio2 2 GPIO_ACTIVE_LOW>; + chg-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>; + cen-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; + dcm-gpios = <&gpio2 6 GPIO_ACTIVE_HIGH>; + + /* DC input current limit via IDC resistor mux */ + dc-current-limit-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>, + <&gpio1 1 GPIO_ACTIVE_HIGH>; + dc-current-limit-mapping = <750000 0>, /* GPIO[1:0]=0b00 */ + <900000 1>, /* GPIO[1:0]=0b01 */ + <1400000 3>, /* GPIO[1:0]=0b11 */ + <2000000 2>; /* GPIO[1:0]=0b10 */ + + /* USB current limit: IUSB pin, 100 mA (low) / 500 mA (high) */ + usb-current-limit-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; + }; From 828414660052b629378764e5d5c1f07e60716a48 Mon Sep 17 00:00:00 2001 From: Herman van Hazendonk Date: Fri, 5 Jun 2026 11:18:13 +0200 Subject: [PATCH 58/96] power: supply: max8903: add DC and USB input current-limit GPIO controls Add two optional current-limit knobs surfaced through POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, selected by which input source is currently online: - DC (DOK / TA-IN): a "dc-current-limit-gpios" array drives the GPIOs of an external resistor mux connected to the MAX8903 IDC pin (DC Current-Limit Set Input, pin 11). The IDC pin programs the step-down DC input current limit from 0.5 A to 2 A via R_IDC when the DCM mode pin is logic-high. The DT property "dc-current-limit-mapping" describes the (current_ua, gpio_value) pairs the board can program; the driver picks the largest entry whose limit is <= the requested limit. A 0 uA entry, used to stop drawing DC current, is selectable by issuing a 0 uA request (the selection uses a -1 "not found" sentinel rather than tracking best_limit > 0, so the all-zero entry can win). - USB (UOK / USB-IN): a single "usb-current-limit-gpio" drives the MAX8903 IUSB pin (USB Current-Limit Set Input, pin 7). The IUSB pin is silicon-fixed per the MAX8903 datasheet Pin Description: logic-low selects 100 mA, logic-high selects 500 mA. The two values are encoded as MAX8903_USB_CURRENT_LIMIT_{LOW,HIGH}_UA #defines with the datasheet quote in a header comment. The requested limit picks HIGH if it can absorb 500 mA, else LOW, else returns -EINVAL rather than silently programming a higher current that would violate the system power budget. Mirroring the DC-priority policy in the get path: when ta_in is asserted the part draws from DC regardless of USB state, so the set path only routes to USB when DC is not online. The dispatch in max8903_set_property() to the DC vs USB path needs to match the active source flag set by the corresponding *_ok GPIO IRQ handler; both update sites take a new struct mutex source_lock so the check and the resulting hardware write cannot be torn by a concurrent IRQ flipping the source-online flag mid-decision. The DOK/UOK IRQ handlers hold source_lock across the full read-modify-evaluate block (line sampling, ta_in/usb_in update, dcm/cen GPIO writes, psy type update) so the cen enable calculation reads a stable other-source flag rather than racing with the peer IRQ. The IRQs are requested with IRQF_ONESHOT (threaded), so a sleepable lock is the right primitive in both contexts. max8903_get_property() also takes source_lock briefly to snapshot the source flags and current-limit values so userspace never observes a torn pair of (source-online flag, current-limit ua). dc-current-limit-mapping gpio_value entries are validated at parse against the GPIO array width so a malformed DT value is rejected instead of being silently truncated by gpiod_set_array_value() and selecting the wrong mux level. ndescs is also bounded to < BITS_PER_TYPE(u32) to keep BIT(ndescs) well-defined on 32-bit. The mapping is additionally required to contain a gpio_value=0 entry: devm_gpiod_get_array_optional() asks for GPIOD_OUT_LOW, so the hardware mux starts at gpio_value 0, and the driver seeds dc_current_limit_ua from the matching map entry. A DT lacking the all-zero entry is rejected with -EINVAL because otherwise the reported INPUT_CURRENT_LIMIT could disagree with the mux state until a set_property write picks a real value. Negative INPUT_CURRENT_LIMIT requests are rejected at the set_property entry: val->intval is signed, the set_*_current_limit() helpers take a u32, and a negative cast would silently widen to a huge unsigned value, bypass the upper-bound guard and program the maximum permitted current. Signed-off-by: Herman van Hazendonk Link: https://patch.msgid.link/20260605-submit-power-max8903-dc-limit-v2-2-0c5396e98f14@herrie.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/max8903_charger.c | 415 ++++++++++++++++++++++++- 1 file changed, 398 insertions(+), 17 deletions(-) diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c index 47334bfc8381..d5267ba19969 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -9,11 +9,29 @@ #include #include #include +#include #include +#include #include #include #include +/* + * IUSB pin: hardcoded by silicon to 100 mA (low) / 500 mA (high). + * MAX8903A/B/C/D/E/F/G/H/I datasheet, "Pin Description" table: + * "USB Current-Limit Set Input. Drive IUSB logic-low to set the + * USB current limit to 100mA. Drive IUSB logic-high to set the + * USB current limit to 500mA." + * Not a board parameter - never DT-configurable. + */ +#define MAX8903_USB_CURRENT_LIMIT_LOW_UA 100000 +#define MAX8903_USB_CURRENT_LIMIT_HIGH_UA 500000 + +struct max8903_current_limit_mapping { + u32 limit_ua; /* Current limit in microamps */ + u32 gpio_value; /* GPIO bit pattern */ +}; + struct max8903_data { struct device *dev; struct power_supply *psy; @@ -31,6 +49,25 @@ struct max8903_data { struct gpio_desc *flt; /* Fault output */ struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */ struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */ + + /* DC current limit control (ISET pins) */ + struct gpio_descs *dc_current_limit_gpios; + struct max8903_current_limit_mapping *dc_current_limit_map; + u32 dc_current_limit_map_size; + u32 dc_current_limit_ua; /* Current setting in uA */ + + /* USB current limit control (IUSB pin) */ + struct gpio_desc *usb_current_limit_gpio; + u32 usb_current_limit_ua; /* Current setting in uA */ + + /* + * Serialises ta_in / usb_in updates against + * max8903_set_property() which steers the current-limit write to + * the DC or USB path based on which source is currently online. + * The IRQ handlers are requested with IRQF_ONESHOT (threaded), so + * a sleepable mutex is the right primitive in both contexts. + */ + struct mutex source_lock; bool fault; bool usb_in; bool ta_in; @@ -40,6 +77,7 @@ static enum power_supply_property max8903_charger_props[] = { POWER_SUPPLY_PROP_STATUS, /* Charger status output */ POWER_SUPPLY_PROP_ONLINE, /* External power source */ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, /* Input current limit */ }; static int max8903_get_property(struct power_supply *psy, @@ -47,6 +85,24 @@ static int max8903_get_property(struct power_supply *psy, union power_supply_propval *val) { struct max8903_data *data = power_supply_get_drvdata(psy); + bool ta_in, usb_in; + u32 dc_limit, usb_limit; + + /* + * Snapshot the source flags and current-limit settings under the + * source_lock that the IRQs (max8903_dcin / max8903_usbin) and + * max8903_set_property() take when updating them, so we never + * observe a torn pair of (source-online flag, current-limit ua). + * The gpiod_get_value() reads further down deliberately stay + * outside the lock — they hit the GPIO controller, not driver + * state, and the IRQs do not touch them under the lock either. + */ + mutex_lock(&data->source_lock); + ta_in = data->ta_in; + usb_in = data->usb_in; + dc_limit = data->dc_current_limit_ua; + usb_limit = data->usb_current_limit_ua; + mutex_unlock(&data->source_lock); switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -55,21 +111,42 @@ static int max8903_get_property(struct power_supply *psy, if (gpiod_get_value(data->chg)) /* CHG asserted */ val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (data->usb_in || data->ta_in) + else if (usb_in || ta_in) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } break; case POWER_SUPPLY_PROP_ONLINE: - val->intval = 0; - if (data->usb_in || data->ta_in) - val->intval = 1; + val->intval = (ta_in || usb_in) ? 1 : 0; break; case POWER_SUPPLY_PROP_HEALTH: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - if (data->fault) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + /* + * data->fault is a single bool toggled from one IRQ + * handler, so a torn read is not possible; no need to + * extend source_lock coverage here. + */ + val->intval = data->fault ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + : POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* + * Hardware prioritises DC over USB - when ta_in is asserted + * the part draws from the DC input regardless of USB state. + * So always report the DC-side limit when DC is online, and + * refuse rather than silently fall back to the USB cap if + * the DC GPIOs are not configured - that would mis-describe + * the active source. Same policy applies in the set path. + */ + if (ta_in) { + if (!data->dc_current_limit_gpios) + return -ENODATA; + val->intval = dc_limit; + } else if (usb_in && data->usb_current_limit_gpio) { + val->intval = usb_limit; + } else { + return -ENODATA; + } break; default: return -EINVAL; @@ -78,6 +155,144 @@ static int max8903_get_property(struct power_supply *psy, return 0; } +static int max8903_set_dc_current_limit(struct max8903_data *data, u32 limit_ua) +{ + int i, best_idx = -1; + /* + * The mapping's gpio_value fits in the lowest ndescs bits of one + * unsigned long (parse_dc_current_limit enforces ndescs < 32 and + * gpio_value < BIT(ndescs)); a single-word bitmap is sufficient + * on both 32- and 64-bit builds. Don't use bitmap_from_arr32() - + * that macro reinterprets its source pointer as unsigned long on + * 64-bit and would read past the on-stack u32. + */ + DECLARE_BITMAP(values, BITS_PER_TYPE(u32)); + + if (!data->dc_current_limit_gpios) + return -EOPNOTSUPP; + + /* + * Find the highest supported current <= requested. Use a -1 + * "not found" sentinel rather than tracking best_limit > 0 so + * that a 0 uA entry (used to disable charging) can be selected + * by a 0 uA request. + */ + for (i = 0; i < data->dc_current_limit_map_size; i++) { + if (data->dc_current_limit_map[i].limit_ua > limit_ua) + continue; + if (best_idx < 0 || + data->dc_current_limit_map[i].limit_ua > + data->dc_current_limit_map[best_idx].limit_ua) + best_idx = i; + } + + if (best_idx < 0) + return -EINVAL; + + bitmap_zero(values, BITS_PER_TYPE(u32)); + values[0] = data->dc_current_limit_map[best_idx].gpio_value; + gpiod_set_array_value_cansleep(data->dc_current_limit_gpios->ndescs, + data->dc_current_limit_gpios->desc, + data->dc_current_limit_gpios->info, + values); + + data->dc_current_limit_ua = data->dc_current_limit_map[best_idx].limit_ua; + dev_dbg(data->dev, "DC current limit set to %u uA\n", + data->dc_current_limit_ua); + + return 0; +} + +static int max8903_set_usb_current_limit(struct max8903_data *data, u32 limit_ua) +{ + u32 selected; + int gpio_val; + + if (!data->usb_current_limit_gpio) + return -EOPNOTSUPP; + + /* + * IUSB is a single-bit input with two silicon-fixed settings; + * pick HIGH (500 mA) iff the caller's cap can absorb it, else + * LOW (100 mA), else refuse rather than program a higher current + * than the request allows. + */ + if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_HIGH_UA) { + selected = MAX8903_USB_CURRENT_LIMIT_HIGH_UA; + gpio_val = 1; + } else if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_LOW_UA) { + selected = MAX8903_USB_CURRENT_LIMIT_LOW_UA; + gpio_val = 0; + } else { + return -EINVAL; + } + + gpiod_set_value_cansleep(data->usb_current_limit_gpio, gpio_val); + data->usb_current_limit_ua = selected; + + dev_dbg(data->dev, "USB current limit set to %u uA\n", + data->usb_current_limit_ua); + + return 0; +} + +static int max8903_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8903_data *data = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* + * val->intval is signed; the set_*_current_limit() helpers + * take a u32. Reject negatives explicitly so a negative + * request cannot widen into a huge unsigned value, bypass + * the "limit <= cap" bounds check inside the helper, and + * silently program the maximum permitted current. + */ + if (val->intval < 0) + return -EINVAL; + /* + * Hold source_lock across the source check and the + * resulting hardware write so the IRQ handler cannot + * flip ta_in/usb_in between them and have us program the + * limit for a source that has just gone offline. Mirror + * the DC-priority policy of the get path: if DC is online + * route to the DC helper (refuse if DC GPIOs aren't + * configured) rather than fall through to USB. + */ + mutex_lock(&data->source_lock); + if (data->ta_in) + ret = data->dc_current_limit_gpios ? + max8903_set_dc_current_limit(data, val->intval) : + -ENODEV; + else if (data->usb_in && data->usb_current_limit_gpio) + ret = max8903_set_usb_current_limit(data, val->intval); + else + ret = -EINVAL; + mutex_unlock(&data->source_lock); + return ret; + default: + return -EINVAL; + } +} + +static int max8903_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct max8903_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return data->dc_current_limit_gpios || + data->usb_current_limit_gpio; + default: + return 0; + } +} + static irqreturn_t max8903_dcin(int irq, void *_data) { struct max8903_data *data = _data; @@ -91,10 +306,21 @@ static irqreturn_t max8903_dcin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* + * Hold source_lock across the full read-modify-evaluate block: + * - so a concurrent max8903_set_property() sees a consistent + * state (lock release would otherwise expose a window where + * data->ta_in is updated but the cen/dcm writes still pend); + * - so the cen enable calculation reads a stable data->usb_in + * rather than racing with max8903_usbin() and writing the + * wrong enable state. + */ + mutex_lock(&data->source_lock); ta_in = gpiod_get_value(data->dok); - - if (ta_in == data->ta_in) + if (ta_in == data->ta_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } data->ta_in = ta_in; @@ -119,9 +345,6 @@ static irqreturn_t max8903_dcin(int irq, void *_data) gpiod_set_value(data->cen, val); } - dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? - "Connected" : "Disconnected"); - old_type = data->psy_desc.type; if (data->ta_in) @@ -130,6 +353,10 @@ static irqreturn_t max8903_dcin(int irq, void *_data) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + mutex_unlock(&data->source_lock); + + dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); @@ -150,10 +377,13 @@ static irqreturn_t max8903_usbin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* See max8903_dcin(): hold the lock across the full update. */ + mutex_lock(&data->source_lock); usb_in = gpiod_get_value(data->uok); - - if (usb_in == data->usb_in) + if (usb_in == data->usb_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } data->usb_in = usb_in; @@ -176,9 +406,6 @@ static irqreturn_t max8903_usbin(int irq, void *_data) gpiod_set_value(data->cen, val); } - dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? - "Connected" : "Disconnected"); - old_type = data->psy_desc.type; if (data->ta_in) @@ -187,6 +414,10 @@ static irqreturn_t max8903_usbin(int irq, void *_data) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + mutex_unlock(&data->source_lock); + + dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); @@ -221,6 +452,145 @@ static irqreturn_t max8903_fault(int irq, void *_data) return IRQ_HANDLED; } +static int max8903_parse_dc_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev = &pdev->dev; + int ret, i, map_size; + u32 *map; + + data->dc_current_limit_gpios = devm_gpiod_get_array_optional(dev, + "dc-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->dc_current_limit_gpios)) + return dev_err_probe(dev, PTR_ERR(data->dc_current_limit_gpios), + "failed to get DC current limit GPIOs"); + + if (!data->dc_current_limit_gpios) + return 0; /* Optional feature not present */ + + /* + * gpio_value entries below are bit patterns indexed into the + * dc-current-limit GPIO array. The driver represents them in + * a single unsigned long for gpiod_set_array_value_cansleep(), + * and BIT(ndescs) further down assumes ndescs fits in a u32 + * shift; reject pathological DTs at parse time instead of + * relying on undefined-behaviour-free dtschema. The binding + * already caps maxItems at 4 so this is purely defensive. + */ + if (data->dc_current_limit_gpios->ndescs >= BITS_PER_TYPE(u32)) { + dev_err(dev, "dc-current-limit-gpios: %u GPIOs exceeds %u-bit cap\n", + data->dc_current_limit_gpios->ndescs, + (unsigned int)BITS_PER_TYPE(u32)); + return -EINVAL; + } + + /* Parse mapping: pairs of (current_ua, gpio_value) */ + map_size = device_property_count_u32(dev, "dc-current-limit-mapping"); + if (map_size <= 0 || map_size % 2) { + dev_err(dev, "invalid dc-current-limit-mapping\n"); + return -EINVAL; + } + + /* + * map[] is a scratch buffer used only inside this function to + * read the property and unpack it into data->dc_current_limit_map. + * Use a plain kmalloc + kfree rather than devm_*: there is no + * reason to keep the raw mirror around for the lifetime of the + * device. + */ + map = kmalloc_array(map_size, sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + ret = device_property_read_u32_array(dev, "dc-current-limit-mapping", + map, map_size); + if (ret) { + dev_err(dev, "failed to read dc-current-limit-mapping\n"); + kfree(map); + return ret; + } + + data->dc_current_limit_map_size = map_size / 2; + data->dc_current_limit_map = devm_kcalloc(dev, + data->dc_current_limit_map_size, + sizeof(*data->dc_current_limit_map), + GFP_KERNEL); + if (!data->dc_current_limit_map) { + kfree(map); + return -ENOMEM; + } + + for (i = 0; i < data->dc_current_limit_map_size; i++) { + u32 gpio_value = map[i * 2 + 1]; + + /* + * gpio_value is the bitmap programmed across the + * dc-current-limit GPIOs, so it cannot represent more + * bits than the GPIO array width. A larger value would + * be silently truncated by gpiod_set_array_value() and + * select the wrong limit; reject it at parse time so + * the bogus DT is visible to the integrator. + */ + if (gpio_value >= BIT(data->dc_current_limit_gpios->ndescs)) { + dev_err(dev, + "dc-current-limit-mapping entry %d: gpio_value 0x%x exceeds %u-GPIO range\n", + i, gpio_value, + data->dc_current_limit_gpios->ndescs); + kfree(map); + return -EINVAL; + } + data->dc_current_limit_map[i].limit_ua = map[i * 2]; + data->dc_current_limit_map[i].gpio_value = gpio_value; + } + + kfree(map); + + /* + * devm_gpiod_get_array_optional() above asked for GPIOD_OUT_LOW, + * so the hardware mux starts at gpio_value 0. Require the DT + * mapping to include a gpio_value=0 entry so the software + * current-limit state has a definite initial value matching the + * hardware. Without this entry we would have to guess and the + * reported INPUT_CURRENT_LIMIT could disagree with what the + * mux is actually wired to until a set_property write picks a + * real value. + */ + for (i = 0; i < data->dc_current_limit_map_size; i++) + if (data->dc_current_limit_map[i].gpio_value == 0) + break; + if (i == data->dc_current_limit_map_size) { + dev_err(dev, + "dc-current-limit-mapping must include a gpio_value=0 entry to describe the boot-time mux state\n"); + return -EINVAL; + } + data->dc_current_limit_ua = data->dc_current_limit_map[i].limit_ua; + + dev_dbg(dev, "DC current limit control: %d levels available, initial %u uA\n", + data->dc_current_limit_map_size, data->dc_current_limit_ua); + + return 0; +} + +static int max8903_parse_usb_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev = &pdev->dev; + + data->usb_current_limit_gpio = devm_gpiod_get_optional(dev, + "usb-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->usb_current_limit_gpio)) + return dev_err_probe(dev, PTR_ERR(data->usb_current_limit_gpio), + "failed to get USB current limit GPIO"); + + if (!data->usb_current_limit_gpio) + return 0; /* Optional feature not present */ + + /* Start at low current (IUSB low = 100 mA) for safety */ + data->usb_current_limit_ua = MAX8903_USB_CURRENT_LIMIT_LOW_UA; + + return 0; +} + static int max8903_setup_gpios(struct platform_device *pdev) { struct max8903_data *data = platform_get_drvdata(pdev); @@ -335,17 +705,28 @@ static int max8903_probe(struct platform_device *pdev) return -ENOMEM; data->dev = dev; + mutex_init(&data->source_lock); platform_set_drvdata(pdev, data); ret = max8903_setup_gpios(pdev); if (ret) return ret; + ret = max8903_parse_dc_current_limit(pdev, data); + if (ret) + return ret; + + ret = max8903_parse_usb_current_limit(pdev, data); + if (ret) + return ret; + data->psy_desc.name = "max8903_charger"; data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : POWER_SUPPLY_TYPE_BATTERY); data->psy_desc.get_property = max8903_get_property; + data->psy_desc.set_property = max8903_set_property; + data->psy_desc.property_is_writeable = max8903_property_is_writeable; data->psy_desc.properties = max8903_charger_props; data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); From 45413165bef33bb7d5544a3331f39492cdde9418 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Mon, 29 Jun 2026 17:44:57 +0200 Subject: [PATCH 59/96] power: supply: rt9471: Remove superfluous unpacking of propval Remove extra variable in `set/get_property`-functions that is only supplied directly to other functions. The value is not used for anything else. Moreover, wrap these lines. Otherwise `checkpatch` warns about too long lines. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/6b4367512dfad62e865de8b6be52eefd5385fa26.1782746851.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9471.c | 49 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index e1cfe484ea91..1a004ba91985 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -370,23 +370,28 @@ static int rt9471_charger_set_property(struct power_supply *psy, const union power_supply_propval *val) { struct rt9471_chip *chip = power_supply_get_drvdata(psy); - int value = val->intval; switch (psp) { case POWER_SUPPLY_PROP_STATUS: - return regmap_field_write(chip->rm_fields[F_CHG_EN], !!value); + return regmap_field_write(chip->rm_fields[F_CHG_EN], + !!val->intval); case POWER_SUPPLY_PROP_ONLINE: - return regmap_field_write(chip->rm_fields[F_HZ], !value); + return regmap_field_write(chip->rm_fields[F_HZ], !val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return rt9471_set_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, value); + return rt9471_set_value_by_field_range( + chip, F_ICHG_REG, RT9471_RANGE_ICHG, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return rt9471_set_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, value); + return rt9471_set_value_by_field_range( + chip, F_VBAT_REG, RT9471_RANGE_VCHG, val->intval); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return rt9471_set_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, value); + return rt9471_set_value_by_field_range( + chip, F_AICR, RT9471_RANGE_AICR, val->intval); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: - return rt9471_set_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, value); + return rt9471_set_value_by_field_range( + chip, F_MIVR, RT9471_RANGE_MIVR, val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: - return rt9471_set_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, value); + return rt9471_set_value_by_field_range( + chip, F_IPRE_CHG, RT9471_RANGE_IPRE, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9471_set_ieoc(chip, val->intval); default: @@ -402,35 +407,39 @@ static int rt9471_charger_get_property(struct power_supply *psy, union power_supply_propval *val) { struct rt9471_chip *chip = power_supply_get_drvdata(psy); - int *pvalue = &val->intval; switch (psp) { case POWER_SUPPLY_PROP_STATUS: - return rt9471_get_status(chip, pvalue); + return rt9471_get_status(chip, &val->intval); case POWER_SUPPLY_PROP_ONLINE: - return rt9471_get_vbus_good(chip, pvalue); + return rt9471_get_vbus_good(chip, &val->intval); case POWER_SUPPLY_PROP_CURRENT_MAX: - return rt9471_get_usb_type_current(chip, pvalue); + return rt9471_get_usb_type_current(chip, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return rt9471_get_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, pvalue); + return rt9471_get_value_by_field_range( + chip, F_ICHG_REG, RT9471_RANGE_ICHG, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - *pvalue = RT9471_ICHG_MAXUA; + val->intval = RT9471_ICHG_MAXUA; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return rt9471_get_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, pvalue); + return rt9471_get_value_by_field_range( + chip, F_VBAT_REG, RT9471_RANGE_VCHG, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: val->intval = RT9471_VCHG_MAXUV; return 0; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return rt9471_get_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, pvalue); + return rt9471_get_value_by_field_range( + chip, F_AICR, RT9471_RANGE_AICR, &val->intval); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: - return rt9471_get_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, pvalue); + return rt9471_get_value_by_field_range( + chip, F_MIVR, RT9471_RANGE_MIVR, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: - return rt9471_get_usb_type(chip, pvalue); + return rt9471_get_usb_type(chip, &val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: - return rt9471_get_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, pvalue); + return rt9471_get_value_by_field_range( + chip, F_IPRE_CHG, RT9471_RANGE_IPRE, &val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: - return rt9471_get_ieoc(chip, pvalue); + return rt9471_get_ieoc(chip, &val->intval); case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = rt9471_model; return 0; From 0e2a6f7c0fa69293fa6f53dcf3406a9d7a1b1816 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Mon, 29 Jun 2026 17:44:59 +0200 Subject: [PATCH 60/96] power: supply: bq24190: Remove unused watchdog struct field The field member `watchdog` in `struct bq24190_dev_info` is only set once in `bq24190_set_config()` during probe, and never read again. Remove this unnecessary field. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/5c9b9ac1599704ff94dba34b9b2a82f62f348bd4.1782746851.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 2a1ea83898ae..19f5c61a2986 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -243,7 +243,6 @@ struct bq24190_dev_info { struct mutex f_reg_lock; u8 f_reg; u8 ss_reg; - u8 watchdog; const struct bq24190_chip_info *info; }; @@ -756,9 +755,6 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) if (ret < 0) return ret; - bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >> - BQ24190_REG_CTTC_WATCHDOG_SHIFT); - /* * According to the "Host Mode and default Mode" section of the * manual, a write to any register causes the bq24190 to switch From 50ffe6eedbd8d1ff8fbc2b451bbade8ca3063487 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Mon, 29 Jun 2026 17:44:59 +0200 Subject: [PATCH 61/96] power: supply: bq24190: Disable watchdog with bq24190_write_mask() The watchdog is disabled by updating the register field with a `bq24190_read()` and `bq24190_write()`. Combine this instead with `bq24190_write_mask(..., 0)`. Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/efa4524aa6879fd4452807f747a9e522183246a1.1782746851.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 19f5c61a2986..906b572c708f 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -751,10 +751,6 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) int ret; u8 v; - ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v); - if (ret < 0) - return ret; - /* * According to the "Host Mode and default Mode" section of the * manual, a write to any register causes the bq24190 to switch @@ -763,9 +759,9 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) * So, by simply turning off the WDT, we accomplish both with the * same write. */ - v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK; - - ret = bq24190_write(bdi, BQ24190_REG_CTTC, v); + ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC, + BQ24190_REG_CTTC_WATCHDOG_MASK, + BQ24190_REG_CTTC_WATCHDOG_SHIFT, 0); if (ret < 0) return ret; From 657cd3a42e937276262c0a8ae6b01a87004309de Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Wed, 22 Jul 2026 19:50:59 +0000 Subject: [PATCH 62/96] power: supply: cros_usbpd: Limit port counts to EC_USB_PD_MAX_PORTS Currently the cros_usbpd-charger driver probe iterates based on raw charger port count returned by the embedded controller. The only check is against the number of USB PD ports which the embedded controller also defines. A malicious embedded controller could return an inaccurate port count (up to 255) resulting in an out of bounds write and subsequent memory corruption. Update helper functions in cros_usbpd-charger to limit port counts to EC_USB_PD_MAX_PORTS. Fixes: 3af15cfacd1e ("power: supply: cros: add support for dedicated port") Cc: stable@vger.kernel.org Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Link: https://patch.msgid.link/20260722195059.1420738-1-jthies@google.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_usbpd-charger.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index e410048aa0ab..9a1038981824 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -125,6 +125,11 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger) if (ret < 0) return ret; + if (resp.port_count > EC_USB_PD_MAX_PORTS) { + dev_warn(charger->dev, "Charge port count out of bounds\n"); + return EC_USB_PD_MAX_PORTS; + } + return resp.port_count; } @@ -138,6 +143,11 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger) if (ret < 0) return ret; + if (resp.num_ports > EC_USB_PD_MAX_PORTS) { + dev_warn(charger->dev, "USB PD port count out of bounds\n"); + return EC_USB_PD_MAX_PORTS; + } + return resp.num_ports; } From 42326d59391cf287bf21224f3fdca528be31ece2 Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Date: Fri, 24 Jul 2026 18:46:04 +0530 Subject: [PATCH 63/96] power: reset: reboot-mode: Remove devres based allocations Devres APIs are intended for use in drivers, where the managed lifetime of resources is tied directly to the driver attach/detach cycle. To ensure correct lifetime handling, avoid using devres-based allocations in the reboot-mode and explicitly handle allocation and cleanup of resources. Fixes: cfaf0a90789a ("power: reset: reboot-mode: Expose sysfs for registered reboot_modes") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607191025.h6bQp891-lkp@intel.com/ Reviewed-by: Bartosz Golaszewski Signed-off-by: Shivendra Pratap Link: https://patch.msgid.link/20260724-arm-psci-system_reset2-vendor-reboots-v24-1-ed5125785ef6@oss.qualcomm.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/reboot-mode.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index d20e44db0532..af00c00eceee 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -168,10 +169,11 @@ error: */ int reboot_mode_register(struct reboot_mode_driver *reboot) { - struct mode_info *info; + struct mode_info *info = NULL; struct property *prop; struct device_node *np = reboot->dev->of_node; size_t len = strlen(PREFIX); + u32 magic; int ret; INIT_LIST_HEAD(&reboot->head); @@ -180,22 +182,22 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) if (strncmp(prop->name, PREFIX, len)) continue; - info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL); + if (device_property_read_u32(reboot->dev, prop->name, &magic)) { + dev_dbg(reboot->dev, "reboot mode %s without magic number\n", + prop->name); + continue; + } + + info = kzalloc_obj(*info, GFP_KERNEL); if (!info) { ret = -ENOMEM; goto error; } - if (of_property_read_u32(np, prop->name, &info->magic)) { - dev_err(reboot->dev, "reboot mode %s without magic number\n", - info->mode); - devm_kfree(reboot->dev, info); - continue; - } - + info->magic = magic; info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); if (!info->mode) { - ret = -ENOMEM; + ret = -ENOMEM; goto error; } else if (info->mode[0] == '\0') { kfree_const(info->mode); @@ -206,6 +208,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) } list_add_tail(&info->list, &reboot->head); + info = NULL; } reboot->reboot_notifier.notifier_call = reboot_mode_notify; @@ -218,6 +221,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) return 0; error: + kfree(info); reboot_mode_unregister(reboot); return ret; } @@ -261,12 +265,16 @@ static inline void reboot_mode_unregister_device(struct reboot_mode_driver *rebo int reboot_mode_unregister(struct reboot_mode_driver *reboot) { struct mode_info *info; + struct mode_info *next; unregister_reboot_notifier(&reboot->reboot_notifier); reboot_mode_unregister_device(reboot); - list_for_each_entry(info, &reboot->head, list) + list_for_each_entry_safe(info, next, &reboot->head, list) { + list_del(&info->list); kfree_const(info->mode); + kfree(info); + } return 0; } From 25453aaffac01909232a20409dff1a9e6d609a11 Mon Sep 17 00:00:00 2001 From: Steffen Dirkwinkel Date: Fri, 24 Jul 2026 09:27:35 +0200 Subject: [PATCH 64/96] power: supply: hold extensions_sem when creating LED triggers LED triggers were created in __power_supply_register without holding the extensions_sem lock. Since commit b04510c3af6d ("power: supply: leds: create triggers based on properties, not type") we call power_supply_has_property during trigger creation and access the extensions there. Move power_supply_create_triggers down into the lock scope used for power_supply_add_hwmon_sysfs for the same reason. Fixes: b04510c3af6d ("power: supply: leds: create triggers based on properties, not type") Reported-by: Chaitanya Kumar Borah Closes: https://lore.kernel.org/all/a95a2720-4092-4b49-bd9d-b700f1c2680d@intel.com/ Signed-off-by: Steffen Dirkwinkel Tested-by: Chaitanya Kumar Borah Link: https://patch.msgid.link/20260724-power-supply-triggers-lockdep-v1-1-9b451b1f1916@beckhoff.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 2532e221b2e1..cbf6ebef7850 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1636,11 +1636,11 @@ __power_supply_register(struct device *parent, if (rc) goto register_thermal_failed; - rc = power_supply_create_triggers(psy); - if (rc) - goto create_triggers_failed; - scoped_guard(rwsem_read, &psy->extensions_sem) { + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; + rc = power_supply_add_hwmon_sysfs(psy); if (rc) goto add_hwmon_sysfs_failed; From 3e7a1ebc32fad5a558254a478efd401c17a24381 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 23 Jul 2026 22:53:10 +0000 Subject: [PATCH 65/96] power: supply: rt9455: quiesce delayed work before teardown The threaded IRQ handler can queue pwr_rdy_work, max_charging_time_work and batt_presence_work. pwr_rdy_work and batt_presence_work can also queue max_charging_time_work, while batt_presence_work can requeue itself. rt9455_remove() cancels max_charging_time_work before batt_presence_work. The latter can therefore queue max_charging_time_work after it has already been cancelled: rt9455_remove() workqueue cancel pwr_rdy_work cancel max_charging_time_work batt_presence_work queues max_charging_time_work cancel batt_presence_work return devres frees rt9455_info max_charging_time_work dereferences rt9455_info The IRQ also remains registered until devres cleanup and can queue more work after any of the cancellation calls. If rt9455_hw_init() fails after the IRQ has been requested, probe returns without cancelling work that may already have been queued. A pending callback can then access rt9455_info after it has been freed. Register rt9455_cancel_all_delayed_works() through devm_add_action_or_reset() right after devm_power_supply_register(). devres invokes the action in reverse registration order, after the managed IRQ has been freed and before rt9455_info is released, so the delayed works are drained in both rt9455_remove() and the probe error path. Cancel pwr_rdy_work and batt_presence_work before max_charging_time_work because both can queue the latter. This issue was found by an in-house static analysis tool. Fixes: e86d69dd786e ("power_supply: Add support for Richtek RT9455 battery charger") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260723225310.12663-1-fanwu01@zju.edu.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9455_charger.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 4f3a799db654..313a7371211e 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1582,6 +1582,19 @@ static const struct regmap_config rt9455_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +static void rt9455_cancel_all_delayed_works(void *data) +{ + struct rt9455_info *info = data; + + /* + * Both pwr_rdy_work and batt_presence_work can queue + * max_charging_time_work, so cancel them first. + */ + cancel_delayed_work_sync(&info->pwr_rdy_work); + cancel_delayed_work_sync(&info->batt_presence_work); + cancel_delayed_work_sync(&info->max_charging_time_work); +} + static int rt9455_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; @@ -1672,6 +1685,10 @@ static int rt9455_probe(struct i2c_client *client) goto put_usb_notifier; } + ret = devm_add_action_or_reset(dev, rt9455_cancel_all_delayed_works, info); + if (ret) + goto put_usb_notifier; + ret = devm_request_threaded_irq(dev, client->irq, NULL, rt9455_irq_handler_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, @@ -1710,10 +1727,6 @@ static void rt9455_remove(struct i2c_client *client) if (info->nb.notifier_call) usb_unregister_notifier(info->usb_phy, &info->nb); #endif - - cancel_delayed_work_sync(&info->pwr_rdy_work); - cancel_delayed_work_sync(&info->max_charging_time_work); - cancel_delayed_work_sync(&info->batt_presence_work); } static const struct i2c_device_id rt9455_i2c_id_table[] = { From b7c8d46411317f135e7791c0eddc56691de63f07 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:28 +0900 Subject: [PATCH 66/96] power: supply: pm8916_lbc: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260723184538.3888637-27-ekffu200098@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/pm8916_lbc.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c index 6b631012a795..c4e74d6b9c34 100644 --- a/drivers/power/supply/pm8916_lbc.c +++ b/drivers/power/supply/pm8916_lbc.c @@ -232,11 +232,7 @@ static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg) return ret; /* Disable charger timeout. */ - ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); - if (ret) - return ret; - - return ret; + return regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); } static const struct power_supply_desc pm8916_lbc_charger_psy_desc = { From af4ee48a09cfb7a2440cc4822e9fda43a76cfbe5 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Fri, 24 Jul 2026 17:54:37 +0800 Subject: [PATCH 67/96] power: supply: pf1550: enable charging when battery profile exists PF1550 starts in charger mode 1, where charging is disabled. The driver comment says that mode 2 should be selected for applications using a battery, but the condition is inverted: PF1550_CHG_BAT_ON is written only when power_supply_get_battery_info() fails. Consequently, a board with a valid monitored-battery profile is left in the default charger-off mode, while a board without battery information enables charging with fallback settings. Select mode 2 when battery information is available. Fixes: 4b6b6433a97d ("power: supply: pf1550: add battery charger support") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Reviewed-by: Frank Li Link: https://patch.msgid.link/097F0559A936ACCB+20260724095437.368905-1-raoxu@uniontech.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/pf1550-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c index 94fe81bab5a2..2ead1df60e65 100644 --- a/drivers/power/supply/pf1550-charger.c +++ b/drivers/power/supply/pf1550-charger.c @@ -514,7 +514,7 @@ static int pf1550_reg_init(struct pf1550_charger *chg) * a battery. The other supported mode is mode 2, the charger is turned * on to charge a battery when present. */ - if (power_supply_get_battery_info(chg->charger, &info)) { + if (!power_supply_get_battery_info(chg->charger, &info)) { ret = regmap_write(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_OPER, PF1550_CHG_BAT_ON); From 863c32a83e4235eb0cbf6106f2b124e645302156 Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Wed, 22 Jul 2026 12:44:16 +0800 Subject: [PATCH 68/96] power: supply: bq25890: Fix power_supply reference leak bq25890_fw_probe() acquires a reference to a secondary charger using power_supply_get_by_name(), but the reference is not released on later probe failures or on driver detach. In particular, failures after bq25890_fw_probe() returns successfully, such as a failure in bq25890_hw_init(), also leak the reference. Register a device-managed cleanup action immediately after acquiring the secondary charger. This releases the reference on all subsequent probe failures and on driver detach. Found by code review. Signed-off-by: Ma Ke Cc: stable@vger.kernel.org Fixes: d54bf877fd87 ("power: supply: bq25890: Add support for having a secondary charger IC") Link: https://patch.msgid.link/20260722044416.1623621-1-make_ruc2021@163.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index c1c12a447178..119d651712a4 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1389,6 +1389,14 @@ static int bq25890_fw_read_u32_props(struct bq25890_device *bq) return 0; } +static void bq25890_release_secondary_chrg(void *data) +{ + struct bq25890_device *bq = data; + + power_supply_put(bq->secondary_chrg); + bq->secondary_chrg = NULL; +} + static int bq25890_fw_probe(struct bq25890_device *bq) { int ret; @@ -1401,6 +1409,10 @@ static int bq25890_fw_probe(struct bq25890_device *bq) bq->secondary_chrg = power_supply_get_by_name(str); if (!bq->secondary_chrg) return -EPROBE_DEFER; + + ret = devm_add_action_or_reset(bq->dev, bq25890_release_secondary_chrg, bq); + if (ret) + return ret; } /* Optional, left at 0 if property is not present */ From 7380075135496df3b07a61c002928af0582b2bf6 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Mon, 29 Jun 2026 00:01:19 +0200 Subject: [PATCH 69/96] dt-bindings: power: supply: Add TI BQ25630 charger Add devicetree bindings for the TI BQ25630 battery charger. It's I2C controlled and sends interrupts. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/3c28e53cff6d2e6ee94f8bf516ffa75134cb0959.1782683551.git.waqar.hameed@axis.com Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/ti,bq25630.yaml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml diff --git a/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml b/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml new file mode 100644 index 000000000000..3f2904731c5a --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/ti,bq25630.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI BQ25630 battery charger + +maintainers: + - Waqar Hameed + +description: | + I2C controlled single cell Li-ion and Li-polymer 5A buck charger. + Datasheet: https://www.ti.com/lit/gpn/bq25630 + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: ti,bq25630 + + reg: + const: 0x6b + + interrupts: + maxItems: 1 + description: + Device sends active low 256 µs pulse. Type should therefore be + IRQ_TYPE_EDGE_FALLING. + +required: + - compatible + - reg + - interrupts + - monitored-battery + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + charger@6b { + compatible = "ti,bq25630"; + reg = <0x6b>; + interrupts = <13 IRQ_TYPE_EDGE_FALLING>; + monitored-battery = <&bat>; + }; + }; +... From 81a10126cb33d553e9b08a5c09b0cb9835cab2d7 Mon Sep 17 00:00:00 2001 From: Waqar Hameed Date: Mon, 29 Jun 2026 00:01:19 +0200 Subject: [PATCH 70/96] power: supply: Add driver for TI BQ25630 charger TI BQ25630 is a battery charger that is I2C controlled. Despite its model name, it is rather different from the other devices in the BQ256xx family; it has a completely different register layout and some other additional functionality (see the datasheet for more details [1]). The most "annoying" thing is that it has two different register lengths: 8-bit and 16-bit. Moreover, the 16-bit registers are further partitioned into either being little- or big-endian... Luckily, `regmap` has support for multiple `regmap_config`s (by setting unique names). Therefore, use three different `regmap_config`s for the corresponding registers. ADC functionality has been left out, due to it not having any real-world use-cases. The `enum power_supply_property` values are straightforward to map. Some properties are clamped (e.g. voltage/current ranges). Common `bq25630_read/write_limit()` functions for this are therefore suitable. Interrupts are sent whenever a state change is detected. Save the state status registers in `bq25630_data` and `memcmp()` this in order to decide if `power_supply_changed()` should be called or not. The actual state values are in (and fetched from) the other `power_supply_property`-mapped registers. [1] https://www.ti.com/lit/gpn/bq25630 Signed-off-by: Waqar Hameed Link: https://patch.msgid.link/ca5228dc74705adf96f0af5363ccb65bb965640b.1782683551.git.waqar.hameed@axis.com [Set power-supply type to POWER_SUPPLY_TYPE_USB] Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 7 + drivers/power/supply/Makefile | 1 + drivers/power/supply/bq25630_charger.c | 1073 ++++++++++++++++++++++++ 3 files changed, 1081 insertions(+) create mode 100644 drivers/power/supply/bq25630_charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a..f1334dcdf39d 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -839,6 +839,13 @@ config CHARGER_BQ25980 Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960 series of fast battery chargers. +config CHARGER_BQ25630 + tristate "TI BQ25630 battery charger driver" + depends on I2C + select REGMAP_I2C + help + Say Y to enable support for the TI BQ25630 battery charger. + config CHARGER_BQ256XX tristate "TI BQ256XX battery charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929..fff92f5ca095 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -105,6 +105,7 @@ obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o obj-$(CONFIG_CHARGER_BQ257XX) += bq257xx_charger.o obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o +obj-$(CONFIG_CHARGER_BQ25630) += bq25630_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c new file mode 100644 index 000000000000..165f8c67b489 --- /dev/null +++ b/drivers/power/supply/bq25630_charger.c @@ -0,0 +1,1073 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for TI BQ25630 charger. + * + * Copyright (C) 2026 Axis Communications AB + */ + +#include +#include +#include +#include +#include + +#include + +#define BQ25630_DRV_NAME "bq25630-charger" + +/* Registers. */ +#define BQ25630_REG_CHARGE_CURRENT_LIMIT 0x02 +#define BQ25630_REG_CHARGE_VOLTAGE_LIMIT 0x04 +#define BQ25630_REG_INPUT_CURRENT_LIMIT 0x06 +#define BQ25630_REG_INPUT_VOLTAGE_LIMIT 0x08 +#define BQ25630_REG_IOTG_REGULATION 0x0a +#define BQ25630_REG_VOTG_REGULATION 0x0c +#define BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE 0x0e +#define BQ25630_REG_PRECHARGE_CONTROL 0x10 +#define BQ25630_REG_TERMINATION_CONTROL 0x12 +#define BQ25630_REG_CHARGE_TIMER_CONTROL 0x14 +#define BQ25630_REG_CHARGER_CONTROL_0 0x15 +#define BQ25630_REG_CHARGER_CONTROL_1 0x16 +#define BQ25630_REG_CHARGER_CONTROL_2 0x17 +#define BQ25630_REG_CHARGER_CONTROL_3 0x18 +#define BQ25630_REG_CHARGER_CONTROL_4 0x19 +#define BQ25630_REG_CHARGER_CONTROL_5 0x1a +#define BQ25630_REG_NTC_CONTROL_0 0x1b +#define BQ25630_REG_NTC_CONTROL_1 0x1c +#define BQ25630_REG_NTC_CONTROL_2 0x1d +#define BQ25630_REG_NTC_CONTROL_3 0x1e +#define BQ25630_REG_CHARGER_STATUS_0 0x1f +#define BQ25630_REG_CHARGER_STATUS_1 0x20 +#define BQ25630_REG_CHARGER_STATUS_2 0x21 +#define BQ25630_REG_FAULT_STATUS 0x22 +#define BQ25630_REG_CHARGER_FLAG_0 0x23 +#define BQ25630_REG_CHARGER_FLAG_1 0x24 +#define BQ25630_REG_FAULT_FLAG 0x25 +#define BQ25630_REG_CHARGER_MASK_0 0x26 +#define BQ25630_REG_CHARGER_MASK_1 0x27 +#define BQ25630_REG_FAULT_MASK 0x28 +#define BQ25630_REG_ICO_CURRENT_LIMIT 0x29 +#define BQ25630_REG_ADC_CONTROL 0x2b +#define BQ25630_REG_ADC_CHANNEL_DISABLE_1 0x2c +#define BQ25630_REG_IBUS_ADC 0x32 +#define BQ25630_REG_IBAT_ADC 0x34 +#define BQ25630_REG_VBUS_ADC 0x36 +#define BQ25630_REG_VPMID_ADC 0x38 +#define BQ25630_REG_VBAT_ADC 0x3a +#define BQ25630_REG_VSYS_ADC 0x3c +#define BQ25630_REG_TS_ADC 0x3e +#define BQ25630_REG_TDIE_ADC 0x40 +#define BQ25630_REG_USB_C_CONTROL_0 0x44 +#define BQ25630_REG_USB_C_CONTROL_1 0x45 +#define BQ25630_REG_LIQUID_CONTROL_0 0x46 +#define BQ25630_REG_LIQUID_CONTROL_1 0x47 +#define BQ25630_REG_USB_C_INFORMATION_0 0x48 +#define BQ25630_REG_USB_C_INFORMATION_1 0x49 +#define BQ25630_REG_USB_DAC_CONTROL_0 0x4a +#define BQ25630_REG_USB_DAC_CONTROL_1 0x4b +#define BQ25630_REG_PART_INFORMATION 0x4d + +#define BQ25630_NR_STAT_REGS \ + (BQ25630_REG_FAULT_FLAG - BQ25630_REG_CHARGER_STATUS_0 + 1) + +/* Charge current limits. */ +#define BQ25630_ICHG_MIN_REGVAL 0x04 +#define BQ25630_ICHG_MIN 80000 +#define BQ25630_ICHG_MAX 5040000 +#define BQ25630_ICHG_STEP 20000 + +/* Charge voltage limits. */ +#define BQ25630_VREG_MIN_REGVAL 0x15e +#define BQ25630_VREG_MIN 3500000 +#define BQ25630_VREG_MAX 4800000 +#define BQ25630_VREG_STEP 10000 + +/* Input current limits. */ +#define BQ25630_IINDPM_MIN_REGVAL 0x0a +#define BQ25630_IINDPM_MIN 100000 +#define BQ25630_IINDPM_MAX 3200000 +#define BQ25630_IINDPM_STEP 10000 + +/* Input voltage limits. */ +#define BQ25630_VINDPM_MIN_REGVAL 0x5f +#define BQ25630_VINDPM_MIN 3800000 +#define BQ25630_VINDPM_MAX 16800000 +#define BQ25630_VINDPM_STEP 40000 + +/* Minimal system voltage limits. */ +#define BQ25630_VSYSMIN_MIN_REGVAL 0x20 +#define BQ25630_VSYSMIN_MIN 2560000 +#define BQ25630_VSYSMIN_MAX 4000000 +#define BQ25630_VSYSMIN_STEP 80000 + +/* Pre-charge current limits. */ +#define BQ25630_IPRECHG_MIN_REGVAL 0x02 +#define BQ25630_IPRECHG_MIN 40000 +#define BQ25630_IPRECHG_MAX 1000000 +#define BQ25630_IPRECHG_STEP 20000 + +/* Termination current limits. */ +#define BQ25630_ITERM_MIN_REGVAL 0x03 +#define BQ25630_ITERM_MIN 30000 +#define BQ25630_ITERM_MAX 1000000 +#define BQ25630_ITERM_STEP 10000 + +/* Charge types. */ +#define BQ25630_CHG_STAT_NOT_CHARGING 0x00 +#define BQ25630_CHG_STAT_TRICKLE_CHARGE 0x01 +#define BQ25630_CHG_STAT_PRE_CHARGE 0x02 +#define BQ25630_CHG_STAT_FAST_CHARGE 0x03 +#define BQ25630_CHG_STAT_TAPER_CHARGE 0x04 +#define BQ25630_CHG_STAT_TERMINATION 0x07 + +/* USB types (VBUS). */ +#define BQ25630_VBUS_STAT_NONE 0x00 +#define BQ25630_VBUS_STAT_SDP 0x01 +#define BQ25630_VBUS_STAT_CDP 0x02 +#define BQ25630_VBUS_STAT_DCP 0x03 +#define BQ25630_VBUS_STAT_HVDCP 0x06 +#define BQ25630_VBUS_STAT_BOOST_OTG 0x07 +#define BQ25630_VBUS_STAT_USB_C_DEFAULT 0x08 +#define BQ25630_VBUS_STAT_USB_C_MEDIUM 0x09 +#define BQ25630_VBUS_STAT_USB_C_HIGH 0x0a + +/* Temperature status. */ +#define BQ25630_TS_STAT_NORMAL 0x00 +#define BQ25630_TS_STAT_COLD 0x01 +#define BQ25630_TS_STAT_HOT 0x02 +#define BQ25630_TS_STAT_COOL 0x03 +#define BQ25630_TS_STAT_WARM 0x04 +#define BQ25630_TS_STAT_PRECOOL 0x05 +#define BQ25630_TS_STAT_PREWARM 0x06 + +/* Register fields. */ +enum bq25630_regfield { + /* Charge current limit. */ + BQ25630_REGF_ICHG, + /* Charge voltage limit. */ + BQ25630_REGF_VREG, + /* Input current limit. */ + BQ25630_REGF_IINDPM, + /* Input voltage limit. */ + BQ25630_REGF_VINDPM, + /* Minimal system voltage. */ + BQ25630_REGF_VSYSMIN, + /* Pre-charge current limit. */ + BQ25630_REGF_IPRECHG, + /* Termination current threshold. */ + BQ25630_REGF_ITERM, + + /* IBUS ADC reading. */ + BQ25630_REGF_IBUS_ADC, + /* VBUS ADC reading. */ + BQ25630_REGF_VBUS_ADC, + + /* Watchdog timer. */ + BQ25630_REGF_WATCHDOG, + /* Enable charger. */ + BQ25630_REGF_EN_CHG, + /* Register reset. */ + BQ25630_REGF_REG_RST, + /* BATFET control. */ + BQ25630_REGF_BATFET_CTRL, + /* Power good indicator. */ + BQ25630_REGF_PG_STAT, + /* Charge status. */ + BQ25630_REGF_CHG_STAT, + /* VBUS status. */ + BQ25630_REGF_VBUS_STAT, + + /* Temperature zone. */ + BQ25630_REGF_TS_STAT, + /* Temperature shutdwon. */ + BQ25630_REGF_TSHUT_STAT, + /* OTG fault. */ + BQ25630_REGF_OTG_FAULT_STAT, + /* System voltage fault. */ + BQ25630_REGF_VSYS_FAULT_STAT, + /* Battery fault. */ + BQ25630_REGF_BAT_FAULT_STAT, + /* VBUS fault. */ + BQ25630_REGF_VBUS_FAULT_STAT, + + /* Sentinel value. */ + BQ25630_REGF_MAX +}; + +static const struct reg_field bq25630_regfields[] = { + [BQ25630_REGF_ICHG] = + REG_FIELD(BQ25630_REG_CHARGE_CURRENT_LIMIT, 4, 11), + [BQ25630_REGF_VREG] = + REG_FIELD(BQ25630_REG_CHARGE_VOLTAGE_LIMIT, 3, 11), + [BQ25630_REGF_IINDPM] = + REG_FIELD(BQ25630_REG_INPUT_CURRENT_LIMIT, 3, 11), + [BQ25630_REGF_VINDPM] = + REG_FIELD(BQ25630_REG_INPUT_VOLTAGE_LIMIT, 5, 13), + + [BQ25630_REGF_VSYSMIN] = + REG_FIELD(BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE, 6, 11), + + [BQ25630_REGF_IPRECHG] = + REG_FIELD(BQ25630_REG_PRECHARGE_CONTROL, 4, 9), + [BQ25630_REGF_ITERM] = + REG_FIELD(BQ25630_REG_TERMINATION_CONTROL, 3, 9), + + [BQ25630_REGF_IBUS_ADC] = REG_FIELD(BQ25630_REG_IBUS_ADC, 1, 15), + [BQ25630_REGF_VBUS_ADC] = REG_FIELD(BQ25630_REG_VBUS_ADC, 2, 14), + + [BQ25630_REGF_WATCHDOG] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 0, 1), + [BQ25630_REGF_EN_CHG] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 5, 5), + [BQ25630_REGF_REG_RST] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_2, 7, 7), + [BQ25630_REGF_BATFET_CTRL] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_3, 0, 1), + [BQ25630_REGF_PG_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_0, 7, 7), + [BQ25630_REGF_CHG_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_1, 3, 5), + [BQ25630_REGF_VBUS_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_2, 4, 7), + + [BQ25630_REGF_TS_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 0, 2), + [BQ25630_REGF_TSHUT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 3, 3), + [BQ25630_REGF_OTG_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 4, 4), + [BQ25630_REGF_VSYS_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 5, 5), + [BQ25630_REGF_BAT_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 6, 6), + [BQ25630_REGF_VBUS_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 7, 7), +}; + +/* 8-bit value regmap. */ +static const struct regmap_range bq25630_read_reg_range8[] = { + regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL, + BQ25630_REG_FAULT_MASK), + regmap_reg_range(BQ25630_REG_ADC_CONTROL, + BQ25630_REG_ADC_CHANNEL_DISABLE_1), + regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0, + BQ25630_REG_PART_INFORMATION), +}; + +static const struct regmap_range bq25630_write_reg_range8[] = { + regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL, + BQ25630_REG_NTC_CONTROL_3), + regmap_reg_range(BQ25630_REG_CHARGER_MASK_0, + BQ25630_REG_FAULT_MASK), + regmap_reg_range(BQ25630_REG_ADC_CONTROL, + BQ25630_REG_ADC_CHANNEL_DISABLE_1), + regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0, + BQ25630_REG_LIQUID_CONTROL_1), + regmap_reg_range(BQ25630_REG_USB_DAC_CONTROL_0, + BQ25630_REG_USB_DAC_CONTROL_1), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table8 = { + .yes_ranges = bq25630_read_reg_range8, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range8), +}; + +static const struct regmap_access_table bq25630_write_reg_access_table8 = { + .yes_ranges = bq25630_write_reg_range8, + .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range8), +}; + +static const struct regmap_config bq25630_regmap_config8 = { + .name = BQ25630_DRV_NAME "-8bit", + .reg_bits = 8, + .val_bits = 8, + .max_register = BQ25630_REG_PART_INFORMATION, + .rd_table = &bq25630_read_reg_access_table8, + .wr_table = &bq25630_write_reg_access_table8, +}; + +/* 16-bit little-endian value regmap. */ +static const struct regmap_range bq25630_read_reg_range16le[] = { + regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT, + BQ25630_REG_TERMINATION_CONTROL), + regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT, + BQ25630_REG_ICO_CURRENT_LIMIT), +}; + +static const struct regmap_range bq25630_write_reg_range16le[] = { + regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT, + BQ25630_REG_TERMINATION_CONTROL), + regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT, + BQ25630_REG_ICO_CURRENT_LIMIT), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table16le = { + .yes_ranges = bq25630_read_reg_range16le, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16le), +}; + +static const struct regmap_access_table bq25630_write_reg_access_table16le = { + .yes_ranges = bq25630_write_reg_range16le, + .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range16le), +}; + +static const struct regmap_config bq25630_regmap_config16le = { + .name = BQ25630_DRV_NAME "-16bit-le", + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 2, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + /* + * Datasheet doesn't mention that this register is little-endian, but it + * looks like it? + */ + .max_register = BQ25630_REG_TERMINATION_CONTROL, + .rd_table = &bq25630_read_reg_access_table16le, + .wr_table = &bq25630_write_reg_access_table16le, +}; + +/* 16-bit big-endian value regmap. */ +static const struct regmap_range bq25630_read_reg_range16be[] = { + regmap_reg_range(BQ25630_REG_IBUS_ADC, + BQ25630_REG_TDIE_ADC), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table16be = { + .yes_ranges = bq25630_read_reg_range16be, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16be), +}; + +static const struct regmap_config bq25630_regmap_config16be = { + .name = BQ25630_DRV_NAME "-16bit-be", + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 2, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = BQ25630_REG_TDIE_ADC, + .rd_table = &bq25630_read_reg_access_table16be, + .wr_table = NULL, +}; + +struct bq25630_data { + struct device *dev; + struct regmap *regmap8; + struct regmap *regmap16le; + struct regmap *regmap16be; + struct regmap_field *regfields[BQ25630_REGF_MAX]; + + struct power_supply *psy; + struct power_supply_battery_info *batinfo; + + /* State status from IRQs. */ + u8 statregs[BQ25630_NR_STAT_REGS]; +}; + +static int bq25630_alloc_regfield_range(struct bq25630_data *data, + const enum bq25630_regfield from, + const enum bq25630_regfield to, + struct regmap *regmap) +{ + int i; + + for (i = from; i <= to; ++i) { + data->regfields[i] = devm_regmap_field_alloc( + data->dev, regmap, bq25630_regfields[i]); + if (IS_ERR(data->regfields[i])) + return dev_err_probe( + data->dev, PTR_ERR(data->regfields[i]), + "Could not allocate register field %d\n", i); + } + + return 0; +} + +static irqreturn_t bq25630_irq_thread(int irq, void *dev_id) +{ + struct bq25630_data *data = dev_id; + u8 regbuf[BQ25630_NR_STAT_REGS]; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(regbuf) != ARRAY_SIZE(data->statregs)); + + ret = regmap_bulk_read(data->regmap8, BQ25630_REG_CHARGER_STATUS_0, + regbuf, ARRAY_SIZE(regbuf)); + if (ret) { + dev_err(data->dev, "Could not bulk read IRQ registers (%d)\n", + ret); + goto out; + } + + if (memcmp(data->statregs, regbuf, ARRAY_SIZE(data->statregs))) { + power_supply_changed(data->psy); + memcpy(data->statregs, regbuf, ARRAY_SIZE(data->statregs)); + } + +out: + return IRQ_HANDLED; +} + +static int bq25630_read_limit(struct bq25630_data *data, + const enum bq25630_regfield regfield, + const int minval, const int step, + const int minregval, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[regfield], ®val); + if (ret) { + dev_err(data->dev, "Could not read limit (%d)\n", ret); + return ret; + } + + *val = minval + step * (regval - minregval); + + return 0; +} + +static int bq25630_write_limit(struct bq25630_data *data, + const enum bq25630_regfield regfield, + const int minval, const int maxval, + const int step, const int minregval, int val) +{ + unsigned int regval; + int ret; + + val = clamp(val, minval, maxval); + regval = minregval + ((val - minval) / step); + ret = regmap_field_write(data->regfields[regfield], regval); + if (ret) { + dev_err(data->dev, "Could not write limit (%d)\n", ret); + return ret; + } + + return 0; +} + +static int bq25630_read_charge_type(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_CHG_STAT], + ®val); + if (ret) { + dev_err(data->dev, "Could not read charge type (%d)\n", ret); + return ret; + } + + switch (regval) { + case BQ25630_CHG_STAT_NOT_CHARGING: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case BQ25630_CHG_STAT_TRICKLE_CHARGE: + case BQ25630_CHG_STAT_PRE_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case BQ25630_CHG_STAT_FAST_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case BQ25630_CHG_STAT_TAPER_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + break; + case BQ25630_CHG_STAT_TERMINATION: + *val = POWER_SUPPLY_CHARGE_TYPE_BYPASS; + break; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +static int bq25630_read_health(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + u8 temp; + + ret = regmap_read(data->regmap8, BQ25630_REG_FAULT_STATUS, ®val); + if (ret) { + dev_err(data->dev, "Could not read fault status (%d)\n", ret); + return ret; + } + + temp = regval & GENMASK(bq25630_regfields[BQ25630_REGF_TS_STAT].msb, + bq25630_regfields[BQ25630_REGF_TS_STAT].lsb); + if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].lsb)) { + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].lsb)) { + /* + * We can't differentiate between dead, under voltage or over + * voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].lsb)) { + /* + * We can't differentiate between under voltage or over voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].lsb)) { + /* + * We can't differentiate between under voltage or over voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_TSHUT_STAT].msb, + bq25630_regfields[BQ25630_REGF_TSHUT_STAT].lsb)) { + /* Temperature shutdown is always due to hot temperatures. */ + *val = POWER_SUPPLY_HEALTH_HOT; + } else if (temp) { + switch (temp) { + case BQ25630_TS_STAT_COLD: + *val = POWER_SUPPLY_HEALTH_COLD; + break; + case BQ25630_TS_STAT_COOL: + *val = POWER_SUPPLY_HEALTH_COOL; + break; + case BQ25630_TS_STAT_WARM: + *val = POWER_SUPPLY_HEALTH_WARM; + break; + case BQ25630_TS_STAT_HOT: + *val = POWER_SUPPLY_HEALTH_HOT; + break; + default: + /* Interpret PRECOOL and PREWARM as NORMAL. */ + *val = POWER_SUPPLY_HEALTH_GOOD; + } + } else { + *val = POWER_SUPPLY_HEALTH_GOOD; + } + + return 0; +} + +static int bq25630_read_vbus(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_VBUS_STAT], + ®val); + if (ret) { + dev_err(data->dev, "Could not read VBUS (%d)\n", ret); + return ret; + } + + switch (regval) { + case BQ25630_VBUS_STAT_NONE: + *val = -1; + break; + case BQ25630_VBUS_STAT_SDP: + *val = POWER_SUPPLY_USB_TYPE_SDP; + break; + case BQ25630_VBUS_STAT_CDP: + *val = POWER_SUPPLY_USB_TYPE_CDP; + break; + case BQ25630_VBUS_STAT_DCP: + case BQ25630_VBUS_STAT_HVDCP: + *val = POWER_SUPPLY_USB_TYPE_DCP; + break; + case BQ25630_VBUS_STAT_USB_C_DEFAULT: + case BQ25630_VBUS_STAT_USB_C_MEDIUM: + case BQ25630_VBUS_STAT_USB_C_HIGH: + *val = POWER_SUPPLY_USB_TYPE_C; + break; + default: + *val = POWER_SUPPLY_USB_TYPE_UNKNOWN; + } + + return 0; +} + +static int bq25630_get_status(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_PG_STAT], ®val); + if (ret) { + dev_err(data->dev, "Could not read PG status (%d)", ret); + return ret; + } + + if (!regval) { + /* There is not enough power, battery must be discharging. */ + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG], ®val); + if (ret) { + dev_err(data->dev, "Could not read charge status (%d)", ret); + return ret; + } + + if (!regval) { + /* Charging is not enabled, battery must be discharging. */ + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = bq25630_read_charge_type(data, val); + if (ret) + return ret; + + switch (*val) { + case POWER_SUPPLY_CHARGE_TYPE_NONE: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_CHARGE_TYPE_BYPASS: + /* Corresponds to BQ25630_CHG_STAT_TERMINATION. */ + *val = POWER_SUPPLY_STATUS_FULL; + break; + default: + *val = POWER_SUPPLY_STATUS_CHARGING; + } + + return 0; +} + +static int bq25630_reset(struct bq25630_data *data) +{ + unsigned int regval = 1; + int ret; + + ret = regmap_field_force_write(data->regfields[BQ25630_REGF_REG_RST], + regval); + if (ret) { + dev_err(data->dev, + "Could not force write reset register field (%d)\n", + ret); + return ret; + } + + /* + * After a successful register reset, the device signals by resetting + * this register field to 0. Try reading it for some interrupt cycles. + */ + ret = regmap_field_read_poll_timeout( + data->regfields[BQ25630_REGF_REG_RST], regval, regval == 0, 256, + 100000); + if (ret) { + dev_err(data->dev, "Could not read reset register field (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int bq25630_setup(struct bq25630_data *data) +{ + int ret; + + ret = bq25630_reset(data); + if (ret) { + dev_err(data->dev, "Could not reset device (%d)\n", ret); + return ret; + } + + /* Disable the watchdog. */ + ret = regmap_field_write(data->regfields[BQ25630_REGF_WATCHDOG], 0); + if (ret) { + dev_err(data->dev, "Could not write watchdog timer (%d)\n", + ret); + return ret; + } + + /* + * Set values according to battery info. Warn on missing "dangerous" + * properties. + */ + if (data->batinfo->voltage_min_design_uv >= 0) { + ret = bq25630_write_limit(data, BQ25630_REGF_VSYSMIN, + BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_MAX, + BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, + data->batinfo->voltage_min_design_uv); + if (ret) + return ret; + } else + dev_warn(data->dev, + "Using default value for minimum voltage\n"); + + if (data->batinfo->constant_charge_voltage_max_uv >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_VREG, BQ25630_VREG_MIN, + BQ25630_VREG_MAX, BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, + data->batinfo->constant_charge_voltage_max_uv); + if (ret) + return ret; + } else + dev_warn(data->dev, + "Using default value for maximum constant charge voltage\n"); + + if (data->batinfo->constant_charge_current_max_ua >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_ICHG, BQ25630_ICHG_MIN, + BQ25630_ICHG_MAX, BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, + data->batinfo->constant_charge_current_max_ua); + if (ret) + return ret; + } else + dev_warn(data->dev, + "Using default value for maximum constant charge current\n"); + + if (data->batinfo->charge_term_current_ua >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_ITERM, BQ25630_ITERM_MIN, + BQ25630_ITERM_MAX, BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + data->batinfo->charge_term_current_ua); + if (ret) + return ret; + } + + if (data->batinfo->precharge_current_ua >= 0) { + ret = bq25630_write_limit(data, BQ25630_REGF_IPRECHG, + BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_MAX, + BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, + data->batinfo->precharge_current_ua); + if (ret) + return ret; + } + + return 0; +} + +static int bq25630_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq25630_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq25630_get_status(data, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CHARGE_TYPES: + ret = bq25630_read_charge_type(data, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq25630_read_health(data, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG], + &val->intval); + if (ret || !val->intval) { + /* Charging is not even enabled. */ + break; + } + + ret = bq25630_read_vbus(data, &val->intval); + val->intval = val->intval >= 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = bq25630_read_limit(data, BQ25630_REGF_VSYSMIN, + BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_ICHG, + BQ25630_ICHG_MIN, BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = BQ25630_ICHG_MAX; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25630_read_limit(data, BQ25630_REGF_VREG, + BQ25630_VREG_MIN, BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = BQ25630_VREG_MAX; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25630_read_limit(data, BQ25630_REGF_IINDPM, + BQ25630_IINDPM_MIN, + BQ25630_IINDPM_STEP, + BQ25630_IINDPM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25630_read_limit(data, BQ25630_REGF_VINDPM, + BQ25630_VINDPM_MIN, + BQ25630_VINDPM_STEP, + BQ25630_VINDPM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_USB_TYPE: + ret = bq25630_read_vbus(data, &val->intval); + if (!ret && val->intval < 0) { + /* Nothing connected. */ + val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_IPRECHG, + BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_ITERM, + BQ25630_ITERM_MIN, BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "BQ25630"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Texas Instruments"; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bq25630_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq25630_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_field_write(data->regfields[BQ25630_REGF_EN_CHG], + !!val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = bq25630_write_limit( + data, BQ25630_REGF_VSYSMIN, BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_MAX, BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25630_write_limit(data, BQ25630_REGF_ICHG, + BQ25630_ICHG_MIN, BQ25630_ICHG_MAX, + BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25630_write_limit(data, BQ25630_REGF_VREG, + BQ25630_VREG_MIN, BQ25630_VREG_MAX, + BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25630_write_limit( + data, BQ25630_REGF_IINDPM, BQ25630_IINDPM_MIN, + BQ25630_IINDPM_MAX, BQ25630_IINDPM_STEP, + BQ25630_IINDPM_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25630_write_limit( + data, BQ25630_REGF_VINDPM, BQ25630_VINDPM_MIN, + BQ25630_VINDPM_MAX, BQ25630_VINDPM_STEP, + BQ25630_VINDPM_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = bq25630_write_limit( + data, BQ25630_REGF_IPRECHG, BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_MAX, BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = bq25630_write_limit(data, BQ25630_REGF_ITERM, + BQ25630_ITERM_MIN, BQ25630_ITERM_MAX, + BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + val->intval); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bq25630_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + 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_INPUT_VOLTAGE_LIMIT: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return true; + default: + return false; + } +} + +static const enum power_supply_property bq25630_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_TYPES, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const struct power_supply_desc bq25630_charger_psy_desc = { + .name = BQ25630_DRV_NAME, + .type = POWER_SUPPLY_TYPE_USB, + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_BYPASS) | + BIT(POWER_SUPPLY_CHARGE_TYPE_UNKNOWN), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_C), + .properties = bq25630_charger_properties, + .num_properties = ARRAY_SIZE(bq25630_charger_properties), + .get_property = bq25630_charger_get_property, + .set_property = bq25630_charger_set_property, + .property_is_writeable = bq25630_charger_property_is_writeable, +}; + +static int bq25630_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct bq25630_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &client->dev; + + data->regmap8 = devm_regmap_init_i2c(client, &bq25630_regmap_config8); + if (IS_ERR(data->regmap8)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap8), + "Could not initialize regmap8\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_WATCHDOG, + BQ25630_REGF_TS_STAT, + data->regmap8); + if (ret) + return ret; + + data->regmap16le = + devm_regmap_init_i2c(client, &bq25630_regmap_config16le); + if (IS_ERR(data->regmap16le)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap16le), + "Could not initialize regmap16le\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_ICHG, + BQ25630_REGF_ITERM, + data->regmap16le); + if (ret) + return ret; + + data->regmap16be = + devm_regmap_init_i2c(client, &bq25630_regmap_config16be); + if (IS_ERR(data->regmap16be)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap16be), + "Could not initialize regmap16be\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_IBUS_ADC, + BQ25630_REGF_VBUS_ADC, + data->regmap16be); + if (ret) + return ret; + + psy_cfg.drv_data = data; + psy_cfg.fwnode = dev_fwnode(data->dev); + data->psy = devm_power_supply_register( + data->dev, &bq25630_charger_psy_desc, &psy_cfg); + if (IS_ERR(data->psy)) + return dev_err_probe(data->dev, PTR_ERR(data->psy), + "Could not register power supply\n"); + + ret = power_supply_get_battery_info(data->psy, &data->batinfo); + if (ret) + return dev_err_probe(data->dev, ret, + "Could not get battery info\n"); + + /* + * Device sends active low 256 µs pulse to report status and fault. + * + * Note that we need to request this *after* registering the power + * supply so devm destructs it correctly in the reverse order. Otherwise + * spurious interrupts could call power_supply_changed() wrongly with a + * uninitialized/deallocated power supply. + */ + ret = devm_request_threaded_irq(data->dev, client->irq, NULL, + bq25630_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + NULL, data); + if (ret) + return dev_err_probe(data->dev, ret, "Could not request IRQ\n"); + + ret = bq25630_setup(data); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id bq25630_of_match[] = { + { + .compatible = "ti,bq25630", + }, + {} +}; +MODULE_DEVICE_TABLE(of, bq25630_of_match); + +static struct i2c_driver bq25630_driver = { + .driver = { + .name = BQ25630_DRV_NAME, + .of_match_table = bq25630_of_match, + }, + .probe = bq25630_probe, +}; +module_i2c_driver(bq25630_driver); + +MODULE_AUTHOR("Waqar Hameed "); +MODULE_DESCRIPTION("TI BQ25630 charger driver"); +MODULE_LICENSE("GPL"); From 6eba34732524067da2aad5ddfdfbc641ded10e9e Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Sat, 25 Jul 2026 15:25:40 +0800 Subject: [PATCH 71/96] power: supply: twl4030_charger: cancel workers via devm bci is devm-allocated. Two workers (bci->work and bci->current_worker) dereference it. twl4030_bci_remove() disables charging and masks interrupts. It cancels neither worker. A worker pending at remove() can run after devm frees bci. The USB transceiver comes from devm_usb_get_phy_by_node(). devm unregisters its notifier only after remove() returns. A cancel_work_sync() in remove() can then race a notifier reschedule. devm_work_autocancel() and devm_delayed_work_autocancel() avoid that. They cancel the workers during devm release, before bci is freed. The current_worker is registered first, since devm will cancel in reverse order and bci->work can reschedule current_worker. Suggested-by: Sebastian Reichel Fixes: d6ccc442b1210 ("twl4030_charger: Make the driver atomic notifier safe") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20260702172128.2001753-1-maoyixie.tju@gmail.com Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/20260725072540.3092504-1-maoyixie.tju@gmail.com [Move comment about order into the commit message] Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 1cace8f73927..c46b22ab8d99 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1002,8 +1003,15 @@ static int twl4030_bci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bci); - INIT_WORK(&bci->work, twl4030_bci_usb_work); - INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); + ret = devm_delayed_work_autocancel(&pdev->dev, &bci->current_worker, + twl4030_current_worker); + if (ret) + return ret; + + ret = devm_work_autocancel(&pdev->dev, &bci->work, + twl4030_bci_usb_work); + if (ret) + return ret; bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); if (IS_ERR(bci->channel_vac)) { From 13cf94703737b4b9a6826987b46020df0642b1e0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 3 Jun 2026 00:10:55 +0400 Subject: [PATCH 72/96] power: supply: bq257xx: Add support for BQ25792 Add support for TI BQ25792 integrated battery charger and buck-boost converter. It shares high-level logic of operation with the already supported BQ25703A, but has a different register map, bit definitions and some of the lower-level hardware states. Signed-off-by: Alexey Charkov Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260603-bq25792-v7-7-d487bed276d0@flipper.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq257xx_charger.c | 528 ++++++++++++++++++++++++- include/linux/mfd/bq257xx.h | 14 + 2 files changed, 541 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 10d242cdaa0a..b735bb02a8e0 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -88,6 +89,53 @@ struct bq257xx_chg { u32 vsys_min; }; +/** + * bq25792_read16() - Read a 16-bit value from device register + * @pdata: driver platform data + * @reg: register address to read from + * @val: pointer to store the register value + * + * Read a 16-bit big-endian value from the BQ25792 device via regmap + * and convert to CPU byte order. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val) +{ + __be16 regval; + int ret; + + ret = regmap_raw_read(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be16_to_cpu(regval); + return 0; +} + +/** + * bq25792_write16() - Write a 16-bit value to device register + * @pdata: driver platform data + * @reg: register address to write to + * @val: 16-bit value to write in CPU byte order + * + * Convert the value to big-endian and write a 16-bit value to the + * BQ25792 device via regmap. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val) +{ + __be16 regval = cpu_to_be16(val); + int ret; + + ret = regmap_raw_write(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + return 0; +} + /** * bq25703_get_state() - Get the current state of the device * @pdata: driver platform data @@ -119,6 +167,43 @@ static int bq25703_get_state(struct bq257xx_chg *pdata) return 0; } +/** + * bq25792_get_state() - Get the current state of the device + * @pdata: driver platform data + * + * Get the current state of the BQ25792 charger by reading status + * registers. Updates the online, charging, overvoltage, and fault + * status fields in the driver data structure. + * + * Return: Returns 0 on success or error on failure to read device. + */ +static int bq25792_get_state(struct bq257xx_chg *pdata) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, ®); + if (ret) + return ret; + + pdata->online = reg & BQ25792_REG1B_PG_STAT; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, ®); + if (ret) + return ret; + + pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, ®); + if (ret) + return ret; + + pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK; + pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK; + + return 0; +} + /** * bq25703_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data @@ -142,6 +227,31 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_min_vsys() - Get the minimum system voltage + * @pdata: driver platform data + * @intval: pointer to store the minimum voltage value + * + * Read the current minimum system voltage setting from the device + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg); + *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_MINVSYS_MIN_UV; + + return ret; +} + /** * bq25703_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data @@ -166,6 +276,29 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) reg); } +/** + * bq25792_set_min_vsys() - Set the minimum system voltage + * @pdata: driver platform data + * @vsys: voltage value to set in uV + * + * Set the minimum system voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys) +{ + unsigned int reg; + int vsys_min = pdata->vsys_min; + + vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV); + reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg); + + return regmap_write(pdata->bq->regmap, + BQ25792_REG00_MIN_SYS_VOLTAGE, reg); +} + /** * bq25703_get_cur() - Get the reported current from the battery * @pdata: driver platform data @@ -195,6 +328,30 @@ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_cur() - Get the reported current from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery current value + * + * Read the current ADC value from the device representing the battery + * charge or discharge current and return it in microamps. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, ®); + if (ret < 0) + return ret; + + *intval = (s16)reg * BQ25792_ADCIBAT_STEP_UA; + + return ret; +} + /** * bq25703_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data @@ -218,6 +375,30 @@ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_ichg_cur() - Get the maximum reported charge current + * @pdata: driver platform data + * @intval: pointer to store the maximum charge current value + * + * Read the programmed maximum charge current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) * + BQ25792_ICHG_STEP_UA; + + return ret; +} + /** * bq25703_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data @@ -242,6 +423,28 @@ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) reg); } +/** + * bq25792_set_ichg_cur() - Set the maximum charge current + * @pdata: driver platform data + * @ichg: current value to set in uA + * + * Set the maximum charge current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) +{ + int ichg_max = pdata->ichg_max; + u16 reg; + + ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max); + reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK, + (ichg / BQ25792_ICHG_STEP_UA)); + + return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg); +} + /** * bq25703_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data @@ -265,6 +468,30 @@ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_chrg_volt() - Get the maximum set charge voltage + * @pdata: driver platform data + * @intval: pointer to store the maximum charge voltage value + * + * Read the current charge voltage limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) * + BQ25792_VBATREG_STEP_UV; + + return ret; +} + /** * bq25703_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data @@ -291,6 +518,29 @@ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) reg); } +/** + * bq25792_set_chrg_volt() - Set the maximum charge voltage + * @pdata: driver platform data + * @vbat: voltage value to set in uV + * + * Set the maximum charge voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) +{ + int vbat_max = pdata->vbat_max; + u16 reg; + + vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max); + + reg = FIELD_PREP(BQ25792_REG01_VREG_MASK, + (vbat / BQ25792_VBATREG_STEP_UV)); + + return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg); +} + /** * bq25703_get_iindpm() - Get the maximum set input current * @pdata: driver platform data @@ -319,6 +569,30 @@ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_iindpm() - Get the maximum set input current + * @pdata: driver platform data + * @intval: pointer to store the maximum input current value + * + * Read the current input current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg); + *intval = reg * BQ25792_IINDPM_STEP_UA; + + return ret; +} + /** * bq25703_set_iindpm() - Set the maximum input current * @pdata: driver platform data @@ -344,6 +618,29 @@ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) FIELD_PREP(BQ25703_IINDPM_MASK, reg)); } +/** + * bq25792_set_iindpm() - Set the maximum input current + * @pdata: driver platform data + * @iindpm: current value in uA + * + * Set the maximum input current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm) +{ + u16 reg; + int iindpm_max = pdata->iindpm_max; + + iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max); + + reg = iindpm / BQ25792_IINDPM_STEP_UA; + + return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, + FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg)); +} + /** * bq25703_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data @@ -368,6 +665,30 @@ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_vbat() - Get the reported voltage from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery voltage value + * + * Read the current ADC value representing the battery voltage + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, ®); + if (ret) + return ret; + + *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV; + + return ret; +} + /** * bq25703_hw_init() - Set all the required registers to init the charger * @pdata: driver platform data @@ -434,6 +755,108 @@ static int bq25703_hw_init(struct bq257xx_chg *pdata) return ret; } +/** + * bq25792_hw_init() - Initialize BQ25792 hardware + * @pdata: driver platform data + * + * Initialize the BQ25792 by disabling the watchdog, enabling discharge + * current sensing with 5A limit, and configuring input current regulation. + * Set the charge current, charge voltage, minimum system voltage, and + * input current limit from platform data. Enable and configure the ADC + * to measure all available channels. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq25792_hw_init(struct bq257xx_chg *pdata) +{ + struct regmap *regmap = pdata->bq->regmap; + int ret = 0; + u8 reg; + + /* Disable watchdog (TODO: make it work instead) */ + ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0); + if (ret) + return ret; + + /* + * Enable battery discharge current sensing, 5A discharge current + * limit, input current regulation and ship FET functions + */ + ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5, + BQ25792_REG14_SFET_PRESENT | + BQ25792_REG14_EN_IBAT | + BQ25792_IBAT_5A | + BQ25792_REG14_EN_IINDPM); + if (ret) + return ret; + + if (pdata->vbat_max < 5000000) { + /* 1S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S); + } else if (pdata->vbat_max < 10000000) { + /* 2S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S); + } else if (pdata->vbat_max < 14000000) { + /* 3S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S); + } else { + /* 4S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S); + } + + /* Recharge voltage detection deglitch time (default 1024ms) */ + reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS); + + /* Recharge voltage offset: 5% of the set charge voltage */ + reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK, + (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV); + + ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + if (ret) + return ret; + + ret = bq25792_set_min_vsys(pdata, pdata->vsys_min); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); + if (ret) + return ret; + + /* Enable the Input Current Optimizer (the rest is at POR value) */ + ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0, + BQ25792_REG0F_EN_AUTO_IBATDIS | + BQ25792_REG0F_EN_CHG | + BQ25792_REG0F_EN_ICO | + BQ25792_REG0F_EN_TERM); + if (ret) + return ret; + + /* Enable the ADC. */ + ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN); + if (ret) + return ret; + + /* Clear per-channel ADC disable bits - enable all channels */ + ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0); + if (ret) + return ret; + + ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0); + if (ret) + return ret; + + return ret; +} + /** * bq25703_hw_shutdown() - Set registers for shutdown * @pdata: driver platform data @@ -446,6 +869,30 @@ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); } +/** + * bq25792_hw_shutdown() - Shutdown BQ25792 hardware + * @pdata: driver platform data + * + * Perform hardware shutdown for the BQ25792. Currently a no-op + * as the device does not require special shutdown configuration. + */ +static void bq25792_hw_shutdown(struct bq257xx_chg *pdata) +{ + /* Nothing to do here */ +} + +/** + * bq257xx_set_charger_property() - Set a power supply property + * @psy: power supply device + * @prop: power supply property to set + * @val: value to set for the property + * + * Handle requests to set power supply properties such as input current + * limit, constant charge voltage, and constant charge current. Routes + * the request to the chip-specific implementation. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_set_charger_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) @@ -469,6 +916,19 @@ static int bq257xx_set_charger_property(struct power_supply *psy, return -EINVAL; } +/** + * bq257xx_get_charger_property() - Get a power supply property + * @psy: power supply device + * @psp: power supply property to get + * @val: pointer to store the property value + * + * Handle requests to get power supply properties, including status, + * health, manufacturer, online state, and various voltage/current + * measurements. Reads current device state and routes chip-specific + * property requests to appropriate handlers. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_get_charger_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -550,6 +1010,17 @@ static enum power_supply_property bq257xx_power_supply_props[] = { POWER_SUPPLY_PROP_USB_TYPE, }; +/** + * bq257xx_property_is_writeable() - Check if a property is writeable + * @psy: power supply device + * @prop: power supply property to check + * + * Determines which power supply properties can be written to. Only + * charge current limit, charge voltage limit, and input current + * limit are writeable. + * + * Return: Returns 1 if property is writeable, 0 otherwise. + */ static int bq257xx_property_is_writeable(struct power_supply *psy, enum power_supply_property prop) { @@ -622,6 +1093,17 @@ out: power_supply_changed(psy); } +/** + * bq257xx_irq_handler_thread() - Handle charger interrupt + * @irq: interrupt number + * @private: pointer to driver private data + * + * Thread handler for charger interrupts. Triggers re-evaluation of + * external power status and updates power supply state in response + * to charger events. + * + * Return: Returns IRQ_HANDLED if interrupt was processed. + */ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) { struct bq257xx_chg *pdata = private; @@ -662,6 +1144,22 @@ static const struct bq257xx_chip_info bq25703_chip_info = { .bq257xx_get_min_vsys = &bq25703_get_min_vsys, }; +static const struct bq257xx_chip_info bq25792_chip_info = { + .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA, + .bq257xx_hw_init = &bq25792_hw_init, + .bq257xx_hw_shutdown = &bq25792_hw_shutdown, + .bq257xx_get_state = &bq25792_get_state, + .bq257xx_get_ichg = &bq25792_get_ichg_cur, + .bq257xx_set_ichg = &bq25792_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25792_get_chrg_volt, + .bq257xx_set_vbatreg = &bq25792_set_chrg_volt, + .bq257xx_get_iindpm = &bq25792_get_iindpm, + .bq257xx_set_iindpm = &bq25792_set_iindpm, + .bq257xx_get_cur = &bq25792_get_cur, + .bq257xx_get_vbat = &bq25792_get_vbat, + .bq257xx_get_min_vsys = &bq25792_get_min_vsys, +}; + /** * bq257xx_parse_dt() - Parse the device tree for required properties * @pdata: driver platform data @@ -707,6 +1205,17 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, 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. + */ static int bq257xx_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -722,7 +1231,17 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return -ENOMEM; pdata->bq = bq; - pdata->chip = &bq25703_chip_info; + + switch (bq->type) { + case BQ25703A: + pdata->chip = &bq25703_chip_info; + break; + case BQ25792: + pdata->chip = &bq25792_chip_info; + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown chip type\n"); + } platform_set_drvdata(pdev, pdata); @@ -758,6 +1277,13 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return ret; } +/** + * bq257xx_charger_shutdown() - Shutdown routine for charger platform device + * @pdev: platform device + * + * Called during system shutdown to perform charger cleanup, including + * disabling watchdog timers or other chip-specific shutdown procedures. + */ static void bq257xx_charger_shutdown(struct platform_device *pdev) { struct bq257xx_chg *pdata = platform_get_drvdata(pdev); diff --git a/include/linux/mfd/bq257xx.h b/include/linux/mfd/bq257xx.h index 4ec72eb920f2..379ef4ee8291 100644 --- a/include/linux/mfd/bq257xx.h +++ b/include/linux/mfd/bq257xx.h @@ -200,6 +200,20 @@ #define BQ25792_REG0A_TRECHG_MASK GENMASK(5, 4) #define BQ25792_REG0A_VRECHG_MASK GENMASK(3, 0) +#define BQ25792_CELL_1S 0 +#define BQ25792_CELL_2S 1 +#define BQ25792_CELL_3S 2 +#define BQ25792_CELL_4S 3 + +#define BQ25792_TRECHG_64MS 0 +#define BQ25792_TRECHG_256MS 1 +#define BQ25792_TRECHG_1024MS 2 +#define BQ25792_TRECHG_2048MS 3 + +#define BQ25792_VRECHG_MIN_UV 50000 +#define BQ25792_VRECHG_STEP_UV 50000 +#define BQ25792_VRECHG_MAX_UV 800000 + /* VOTG regulation */ #define BQ25792_REG0B_VOTG_MASK GENMASK(10, 0) From 0962125d9001077e91bc90656245f009e3c6c129 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Tue, 14 Jul 2026 21:10:52 +0000 Subject: [PATCH 73/96] power: supply: Add helpers to get and put arrays of power supply handles Add power_supply_get_system_batteries() to allow drivers to obtain a list of registered battery type power supply references in the system. Also add power_supply_put_system_batteries() to perform cleanup after the former function is called. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20260714-batt-status-v5-1-9de4aa900b69@google.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 153 +++++++++++++++++++++++ include/linux/power_supply.h | 15 +++ 2 files changed, 168 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 2532e221b2e1..70f35f64cc72 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +static bool power_supply_is_system_battery(struct power_supply *psy) +{ + union power_supply_propval val; + + if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) + return false; + + if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, + &val)) + if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) + return false; + + return true; +} + +static int __power_supply_get_num_system_batteries(struct power_supply *epsy, + void *data) +{ + int *count = data; + + if (power_supply_is_system_battery(epsy)) + (*count)++; + + return 0; +} + +static int power_supply_get_num_system_batteries(struct device *dev) +{ + int ret, count = 0; + + ret = power_supply_for_each_psy(&count, + __power_supply_get_num_system_batteries); + + dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); + + if (ret) + return ret; + + return count; +} + +struct psy_get_supplies_data { + int cnt; + int size; + struct power_supply **psys; +}; + +static int +__power_supply_populate_system_batteries_array(struct power_supply *epsy, + void *_data) +{ + struct psy_get_supplies_data *data = _data; + + if (power_supply_is_system_battery(epsy)) { + if (data->size <= data->cnt) + return -EOVERFLOW; + + get_device(&epsy->dev); + data->psys[data->cnt] = epsy; + atomic_inc(&epsy->use_cnt); + data->cnt++; + } + + return 0; +} + +static int +power_supply_populate_system_batteries_array(struct device *dev, int size, + struct power_supply **batteries) +{ + int ret; + + struct psy_get_supplies_data data = { + .cnt = 0, + .size = size, + .psys = batteries, + }; + + ret = power_supply_for_each_psy(&data, + __power_supply_populate_system_batteries_array); + + dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", + __func__, data.cnt, data.size, ret); + + if (ret < 0 || !data.cnt) { + power_supply_put_system_batteries(batteries, data.cnt); + return ret; + } + + return data.cnt; +} + +/** + * power_supply_get_system_batteries() - Fetches references to battery type + * power supplies in the system. + * @dev: Pointer to device requesting the power supply refs. + * @psys: Pointer to an array of power supply refs. + * + * Helper function to get handles to battery type power supplies in the system. + * If acquiring a ref to a power supply fails, then the search for battery type + * power supplies will abort and the acquired power supply references will be + * released. + * + * Return: Indicates the number of battery type power supplies returned on + * success or a negative error code on failure. + * + * Call power_supply_put_system_batteries() after use to cleanup resources. + */ +int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + int ret; + + if (!psys) + return -EINVAL; + + ret = power_supply_get_num_system_batteries(dev); + if (ret <= 0) { + *psys = NULL; + return ret; + } + + *psys = kzalloc_objs(**psys, ret); + if (!*psys) + return -ENOMEM; + + ret = power_supply_populate_system_batteries_array(dev, ret, *psys); + if (ret <= 0) + *psys = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); + +/** + * power_supply_put_system_batteries() - Cleanup resources allocated by + * power_supply_get_system_batteries() + * @psys: Array of power supply references to release and free. + * @count: Number of elements in the array. + */ +void power_supply_put_system_batteries(struct power_supply **psys, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (psys[i]) + power_supply_put(psys[i]); + } + + kfree(psys); +} +EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); + /** * power_supply_put() - Drop reference obtained with power_supply_get_by_name * @psy: Reference to put diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a0..9167a33d074b 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); extern void power_supply_unreg_notifier(struct notifier_block *nb); #if IS_ENABLED(CONFIG_POWER_SUPPLY) extern struct power_supply *power_supply_get_by_name(const char *name); +extern int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys); +extern void power_supply_put_system_batteries(struct power_supply **psys, int count); extern void power_supply_put(struct power_supply *psy); #else static inline void power_supply_put(struct power_supply *psy) {} static inline struct power_supply *power_supply_get_by_name(const char *name) { return NULL; } +static inline int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + if (psys) + *psys = NULL; + return 0; +} + +static inline void power_supply_put_system_batteries(struct power_supply **psys, + int count) +{ +} #endif extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, const char *property); From 6027892925b8d19d2245c2d077e2ae35b49cc2b1 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Sun, 26 Jul 2026 07:22:05 +0000 Subject: [PATCH 74/96] power: supply: sbs-battery: Use a per-device serial number buffer sbs_get_battery_serial_number() formats the battery serial number into sbs_serial[], a single file-scope buffer shared by every sbs-battery instance, and points val->strval at it. Nothing restricts this driver to one instance. It binds per I2C client, and sbs-manager registers one muxed I2C channel per supported battery specifically so that the smart battery driver can be bound to each of them, so several sbs-battery instances on one system is a supported configuration. The power supply core reads strval after the driver's get_property() callback has returned: power_supply_show_property() fills a local union power_supply_propval, then formats it with sysfs_emit(). Two concurrent POWER_SUPPLY_PROP_SERIAL_NUMBER reads on different batteries therefore race for the shared buffer - battery B's sprintf() can land between battery A filling the buffer and the core reading it, and battery A then reports battery B's serial number. Move the buffer into struct sbs_info so that each battery formats into its own storage. It is deliberately not added to the chip->strings[] array: those entries hold the cached constant strings that sbs_invalidate_cached_props() clears on presence changes, whereas the serial number is re-read from its word register on every access. Fixes: d3ab61ecbab2 ("bq20z75: Add support for more power supply properties") Assisted-by: Claude:claude-opus-5 Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20260726072206.7-2-bbnpreetsingh@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 17b0fe296902..4407f684752b 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -217,6 +217,7 @@ struct sbs_info { u32 flags; int technology; char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1]; + char serial[5]; }; static char *sbs_get_string_buf(struct sbs_info *chip, @@ -821,18 +822,18 @@ static int sbs_get_battery_capacity(struct i2c_client *client, return 0; } -static char sbs_serial[5]; static int sbs_get_battery_serial_number(struct i2c_client *client, union power_supply_propval *val) { + struct sbs_info *chip = i2c_get_clientdata(client); int ret; ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); if (ret < 0) return ret; - sprintf(sbs_serial, "%04x", ret); - val->strval = sbs_serial; + sprintf(chip->serial, "%04x", ret); + val->strval = chip->serial; return 0; } From 645448becbd0b52216c6741897dd412f9ff1b925 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Sun, 26 Jul 2026 07:22:06 +0000 Subject: [PATCH 75/96] power: supply: sbs-battery: Bound the serial number conversion to 16 bits The SBS SerialNumber register is a 16-bit word and chip->serial[] is sized for its four hex digits plus the NUL terminator. The value is carried in an int, though, and only the negative half of that range is rejected before the conversion, so the compiler has to assume [0, INT_MAX] - up to eight digits: drivers/power/supply/sbs-battery.c:835:32: warning: '%04x' directive writing between 4 and 8 bytes into a region of size 5 [-Wformat-overflow=] drivers/power/supply/sbs-battery.c:835:31: note: directive argument in the range [0, 2147483647] drivers/power/supply/sbs-battery.c:835:9: note: 'sprintf' output between 5 and 9 bytes into a destination of size 5 The overflow is not reachable: sbs_read_word_data() returns the result of i2c_smbus_read_word_data(), which yields at most 0xffff on success, and negative returns are rejected just above. Cast to u16 to state the register width at the point of use, which also lets the compiler prove the buffer is large enough. No functional change. This is the only W=1 warning in this driver. Assisted-by: Claude:claude-opus-5 Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20260726072206.7-3-bbnpreetsingh@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 4407f684752b..501c8e069755 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -832,7 +832,7 @@ static int sbs_get_battery_serial_number(struct i2c_client *client, if (ret < 0) return ret; - sprintf(chip->serial, "%04x", ret); + sprintf(chip->serial, "%04x", (u16)ret); val->strval = chip->serial; return 0; From 659cc3d8d5ef246263873fce72c8cadeeed073cc Mon Sep 17 00:00:00 2001 From: Jianing Li Date: Mon, 27 Jul 2026 14:48:25 +0800 Subject: [PATCH 76/96] power: supply: max17040: propagate register read errors max17040_get_vcell() and max17040_get_soc() ignore errors returned by regmap_read(). When an I2C transfer fails, the uninitialized register value is converted and reported to userspace as a valid voltage or state of charge. The polling worker can also replace the cached state of charge with the bogus value and emit a spurious change event. Propagate read errors through the power supply get_property callback and keep the last valid cached state of charge when polling fails. Fixes: c6f4a42de60b ("Add MAX17040 Fuel Gauge driver") Cc: stable@vger.kernel.org Signed-off-by: Jianing Li Link: https://patch.msgid.link/20260727064825.948-1-m13940358460@163.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 26 ++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index e94d53b36aa4..98d34d5f3c80 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -192,8 +192,11 @@ static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell) static int max17040_get_vcell(struct max17040_chip *chip) { u32 vcell; + int ret; - regmap_read(chip->regmap, MAX17040_VCELL, &vcell); + ret = regmap_read(chip->regmap, MAX17040_VCELL, &vcell); + if (ret) + return ret; return max17040_raw_vcell_to_uvolts(chip, vcell); } @@ -201,8 +204,11 @@ static int max17040_get_vcell(struct max17040_chip *chip) static int max17040_get_soc(struct max17040_chip *chip) { u32 soc; + int ret; - regmap_read(chip->regmap, MAX17040_SOC, &soc); + ret = regmap_read(chip->regmap, MAX17040_SOC, &soc); + if (ret) + return ret; return soc >> (chip->quirk_double_soc ? 9 : 8); } @@ -261,7 +267,11 @@ static int max17040_get_of_data(struct max17040_chip *chip) static void max17040_check_changes(struct max17040_chip *chip) { - chip->soc = max17040_get_soc(chip); + int soc; + + soc = max17040_get_soc(chip); + if (soc >= 0) + chip->soc = soc; } static void max17040_queue_work(struct max17040_chip *chip) @@ -396,10 +406,16 @@ static int max17040_get_property(struct power_supply *psy, val->intval = max17040_get_online(chip); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = max17040_get_vcell(chip); + ret = max17040_get_vcell(chip); + if (ret < 0) + return ret; + val->intval = ret; break; case POWER_SUPPLY_PROP_CAPACITY: - val->intval = max17040_get_soc(chip); + ret = max17040_get_soc(chip); + if (ret < 0) + return ret; + val->intval = ret; break; case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: val->intval = chip->low_soc_alert; From ab1112df8f4ffa88cb024dd370c432ced80f77d8 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Mon, 27 Jul 2026 16:41:19 +0900 Subject: [PATCH 77/96] power: supply: qcom_battmgr: terminate the strings from firmware The qcom_battmgr_sc8280xp_strcpy() takes a Pascal-style string when the firmware sends one. Otherwise it copies all BATTMGR_STRING_LEN bytes and leaves the destination without a terminator. Those destinations are model_number, serial_number and oem_info, each BATTMGR_STRING_LEN and declared next to each other. They go out to user space as val->strval, which power_supply_format_property() prints with "%s", so a firmware string that fills the whole field makes that read run into the following members. Use strscpy() so the copy always terminates, the way the SM8350 path already does for the same field. Fixes: 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260727074119.2585463-1-sammiee5311@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 490137a23d00..c76389b2f7e8 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1231,7 +1231,7 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) memcpy(dest, src + 1, len); dest[len] = '\0'; } else { - memcpy(dest, src, BATTMGR_STRING_LEN); + strscpy(dest, src, BATTMGR_STRING_LEN); } } From c1eb5905fdce35a66173658a93819641e5220c18 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:12:59 -0400 Subject: [PATCH 78/96] power: supply: Add registration init callback Some battery drivers need to consume monitored-battery data before their power_supply is visible. That lets them prepare hardware configuration from parsed battery information without racing userspace exposure. power_supply_get_battery_info() already runs in __power_supply_register() for battery devices before device_add(). Add an optional descriptor init callback after driver data and battery info are available. The callback runs in sleepable process context while the power supply is still unpublished. Keep the callback synchronous: it must not publish changes or start asynchronous activity that can access the power supply before registration completes. Require callbacks to return zero or a negative errno. Defensively reject positive returns so registration cannot return an invalid error pointer. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-2-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 8 ++++++++ include/linux/power_supply.h | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index fe48dfc6ff7b..00d8bc98d588 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1777,6 +1777,14 @@ __power_supply_register(struct device *parent, init_rwsem(&psy->extensions_sem); INIT_LIST_HEAD(&psy->extensions); + if (desc->init) { + rc = desc->init(psy); + if (WARN_ON_ONCE(rc > 0)) + rc = -EINVAL; + if (rc) + goto check_supplies_failed; + } + rc = device_add(dev); if (rc) goto device_add_failed; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index d9c8cce9faad..a9c056f13077 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -285,6 +285,15 @@ struct power_supply_desc { int (*property_is_writeable)(struct power_supply *psy, enum power_supply_property psp); void (*external_power_changed)(struct power_supply *psy); + /* + * Optional registration-time initialization. This runs in sleepable + * process context after driver data and any battery info are available, + * but before the device is added. The callback must not publish changes or + * start asynchronous activity that can access the power supply before + * registration completes. Return 0 on success or a negative errno on + * failure. + */ + int (*init)(struct power_supply *psy); /* * Set if thermal zone should not be created for this power supply. From f05e30fd2ffd962e6f30dc0abd528b4182ecdf4b Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:00 -0400 Subject: [PATCH 79/96] power: supply: max17042_battery: Separate MAX17055 initialization MAX17055 uses ModelCfg.Refresh instead of the characterization-data flow used by the other supported gauges. Move its existing initialization steps into a dedicated helper without changing behavior. This isolates the chip-specific path for subsequent EZ Config changes. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-3-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 581699335a5f..16b7ef52419d 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -848,18 +848,24 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg); } +static void max17055_init_chip(struct max17042_chip *chip) +{ + max17042_override_por_values(chip); + + regmap_write_bits(chip->regmap, MAX17055_ModelCfg, + MAX17055_MODELCFG_REFRESH_BIT, + MAX17055_MODELCFG_REFRESH_BIT); +} + static int max17042_init_chip(struct max17042_chip *chip) { struct regmap *map = chip->regmap; int ret; - max17042_override_por_values(chip); - - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) { - regmap_write_bits(map, MAX17055_ModelCfg, - MAX17055_MODELCFG_REFRESH_BIT, - MAX17055_MODELCFG_REFRESH_BIT); - } + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + max17055_init_chip(chip); + else + max17042_override_por_values(chip); /* After Power up, the MAX17042 requires 500mS in order * to perform signal debouncing and initial SOC reporting From e8e7cdd9ce46a763e5428e2673e90c6a5f91f6d8 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:01 -0400 Subject: [PATCH 80/96] power: supply: max17042_battery: Use bool for init_complete init_complete is a binary state. Change it from int to bool before adding retry support. The initialization worker and probe path update the flag while property reads sample it. Use READ_ONCE() and WRITE_ONCE() to make those lockless accesses explicit. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-4-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 16b7ef52419d..91a55c30667f 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -70,11 +70,11 @@ struct max17042_chip { enum max170xx_chip_type chip_type; struct max17042_config_data *config_data; struct work_struct work; - int init_complete; int irq; int task_period; bool enable_current_sense; bool enable_por_init; + bool init_complete; unsigned int r_sns; int vmin; /* in millivolts */ int vmax; /* in millivolts */ @@ -257,7 +257,7 @@ static int max17042_get_property(struct power_supply *psy, u32 data; u64 data64; - if (!chip->init_complete) + if (!READ_ONCE(chip->init_complete)) return -EAGAIN; switch (psp) { @@ -1007,7 +1007,7 @@ static void max17042_init_worker(struct work_struct *work) return; } - chip->init_complete = 1; + WRITE_ONCE(chip->init_complete, true); } #ifdef CONFIG_OF @@ -1261,7 +1261,7 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq return ret; schedule_work(&chip->work); } else { - chip->init_complete = 1; + WRITE_ONCE(chip->init_complete, true); } return 0; From 01da1f4a6373f8d6a5a2d5d5adc256431087ccc4 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:02 -0400 Subject: [PATCH 81/96] power: supply: max17042_battery: Convert initialization to delayed work Periodic MAX17055 retries need a delayed work item. Convert the existing initialization work in advance without adding retries or changing when the first attempt runs. Use schedule_delayed_work() with a zero delay so the work continues to run on system_wq, matching schedule_work() rather than introducing a separate workqueue policy change. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-5-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 91a55c30667f..bfc563ff9c31 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -69,7 +69,7 @@ struct max17042_chip { struct power_supply *battery; enum max170xx_chip_type chip_type; struct max17042_config_data *config_data; - struct work_struct work; + struct delayed_work work; int irq; int task_period; bool enable_current_sense; @@ -996,7 +996,7 @@ static irqreturn_t max17042_thread_handler(int id, void *dev) static void max17042_init_worker(struct work_struct *work) { - struct max17042_chip *chip = container_of(work, + struct max17042_chip *chip = container_of(to_delayed_work(work), struct max17042_chip, work); int ret; @@ -1255,11 +1255,11 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq regmap_read(chip->regmap, MAX17042_STATUS, &val); if (val & STATUS_POR_BIT) { - ret = devm_work_autocancel(dev, &chip->work, - max17042_init_worker); + ret = devm_delayed_work_autocancel(dev, &chip->work, + max17042_init_worker); if (ret) return ret; - schedule_work(&chip->work); + schedule_delayed_work(&chip->work, 0); } else { WRITE_ONCE(chip->init_complete, true); } From 98d02b5128833c3d812e62a2616f33f1524c8394 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:03 -0400 Subject: [PATCH 82/96] power: supply: max17042_battery: Propagate status register errors The driver ignores errors from the initial STATUS read and final POR clear. A failed read can leave the POR decision based on an invalid value, while a failed clear can report initialization complete with POR still set. Check the probe-time read and return the POR-clear result from max17042_init_chip(). Keep this error handling separate from the retry policy added later. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-6-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index bfc563ff9c31..f59dec8864ef 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -909,8 +909,7 @@ static int max17042_init_chip(struct max17042_chip *chip) } /* Init complete, Clear the POR bit */ - regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); - return 0; + return regmap_clear_bits(map, MAX17042_STATUS, STATUS_POR_BIT); } static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) @@ -1253,7 +1252,10 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq chip->irq = irq; - regmap_read(chip->regmap, MAX17042_STATUS, &val); + ret = regmap_read(chip->regmap, MAX17042_STATUS, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read status\n"); + if (val & STATUS_POR_BIT) { ret = devm_delayed_work_autocancel(dev, &chip->work, max17042_init_worker); From fc9ff915b1fb6f6f826953d1bef3087d7a489d65 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:04 -0400 Subject: [PATCH 83/96] power: supply: max17042_battery: Retry failed MAX17055 initialization MAX17055 initialization can fail on register I/O. Propagate the ModelCfg Refresh command error and retry MAX17055 failures every 10 seconds so a transient startup error does not become permanent. Report failures for the other gauges without changing their one-shot behavior. Notify consumers whenever initialization succeeds, including on the first attempt. The core registration notification is deferred and can observe -EAGAIN while asynchronous gauge initialization is still running, so a new notification is needed when driver-backed properties become available. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-7-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 37 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index f59dec8864ef..e44795d54b43 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -63,6 +63,8 @@ #define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */ #define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */ +#define MAX17055_INIT_RETRY_DELAY_MS 10000 + struct max17042_chip { struct device *dev; struct regmap *regmap; @@ -848,13 +850,13 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg); } -static void max17055_init_chip(struct max17042_chip *chip) +static int max17055_init_chip(struct max17042_chip *chip) { max17042_override_por_values(chip); - regmap_write_bits(chip->regmap, MAX17055_ModelCfg, - MAX17055_MODELCFG_REFRESH_BIT, - MAX17055_MODELCFG_REFRESH_BIT); + return regmap_write_bits(chip->regmap, MAX17055_ModelCfg, + MAX17055_MODELCFG_REFRESH_BIT, + MAX17055_MODELCFG_REFRESH_BIT); } static int max17042_init_chip(struct max17042_chip *chip) @@ -862,10 +864,13 @@ static int max17042_init_chip(struct max17042_chip *chip) struct regmap *map = chip->regmap; int ret; - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) - max17055_init_chip(chip); - else + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) { + ret = max17055_init_chip(chip); + if (ret) + return ret; + } else { max17042_override_por_values(chip); + } /* After Power up, the MAX17042 requires 500mS in order * to perform signal debouncing and initial SOC reporting @@ -997,16 +1002,26 @@ static void max17042_init_worker(struct work_struct *work) { struct max17042_chip *chip = container_of(to_delayed_work(work), struct max17042_chip, work); - int ret; + int ret = 0; /* Initialize registers according to values from config_data */ - if (chip->enable_por_init && chip->config_data) { + if (chip->enable_por_init && chip->config_data) ret = max17042_init_chip(chip); - if (ret) - return; + + if (ret) { + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) { + dev_warn_ratelimited(chip->dev, + "initialization failed: %d, retrying\n", ret); + schedule_delayed_work(&chip->work, + msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS)); + } else { + dev_err(chip->dev, "initialization failed: %d\n", ret); + } + return; } WRITE_ONCE(chip->init_complete, true); + power_supply_changed(chip->battery); } #ifdef CONFIG_OF From 6f384023851d67bd242bf40e5a91328eb92edb5d Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:05 -0400 Subject: [PATCH 84/96] power: supply: max17042_battery: Program MAX17055 EZ Config values MAX17055 EZ Config requires DesignCap, dQAcc, IChgTerm, and dPAcc to be programmed coherently. Write each supplied stable register once, wait 1 ms, and verify its read-back instead of relying on the unchecked generic POR override path. Derive dPAcc from the effective DesignCap, dQAcc, and ModelCfg values so the accumulator matches the selected charge-voltage range. Propagate all read, write, verification, and range errors to the retrying init worker. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-8-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 99 ++++++++++++++++++++++++- include/linux/power/max17042_battery.h | 1 + 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index e44795d54b43..c262be38e1ed 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -63,6 +63,8 @@ #define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */ #define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */ +#define MAX17055_DPACC_FACTOR 44138 +#define MAX17055_DPACC_VCHG_FACTOR 51200 #define MAX17055_INIT_RETRY_DELAY_MS 10000 struct max17042_chip { @@ -569,6 +571,24 @@ static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) return ret; } +static int max17055_write_verify_reg(struct regmap *map, u8 reg, u32 value) +{ + u32 read_value; + int ret; + + ret = regmap_write(map, reg, value); + if (ret) + return ret; + + usleep_range(1000, 2000); + + ret = regmap_read(map, reg, &read_value); + if (ret) + return ret; + + return read_value == value ? 0 : -EIO; +} + static inline void max17042_override_por(struct regmap *map, u8 reg, u16 value) { @@ -804,8 +824,12 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17042_CONFIG, config->config); max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); - max17042_override_por(map, MAX17042_DesignCap, config->design_cap); - max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); + if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { + max17042_override_por(map, MAX17042_DesignCap, + config->design_cap); + max17042_override_por(map, MAX17042_ICHGTerm, + config->ichgt_term); + } max17042_override_por(map, MAX17042_AtRate, config->at_rate); max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); @@ -815,8 +839,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17042_FullCAP, config->fullcap); max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom); - max17042_override_por(map, MAX17042_dQacc, config->dqacc); - max17042_override_por(map, MAX17042_dPacc, config->dpacc); + if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { + max17042_override_por(map, MAX17042_dQacc, config->dqacc); + max17042_override_por(map, MAX17042_dPacc, config->dpacc); + } max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); max17042_override_por(map, MAX17042_TempCo, config->tcompc0); @@ -850,10 +876,75 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg); } +static int max17055_override_battery_values(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->config_data; + struct regmap *map = chip->regmap; + unsigned int design_cap; + unsigned int model_cfg; + unsigned int dqacc; + u64 dpacc; + int ret; + + if (config->design_cap) { + ret = max17055_write_verify_reg(map, MAX17042_DesignCap, + config->design_cap); + if (ret) + return ret; + } + + if (config->dqacc) { + ret = max17055_write_verify_reg(map, MAX17042_dQacc, + config->dqacc); + if (ret) + return ret; + } + + if (config->ichgt_term) { + ret = max17055_write_verify_reg(map, MAX17042_ICHGTerm, + config->ichgt_term); + if (ret) + return ret; + } + + if (!config->design_cap && !config->dqacc) + return 0; + + ret = regmap_read(map, MAX17042_DesignCap, &design_cap); + if (ret) + return ret; + + ret = regmap_read(map, MAX17042_dQacc, &dqacc); + if (ret) + return ret; + + ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg); + if (ret) + return ret; + + if (!design_cap || !dqacc) + return -ERANGE; + + dpacc = (u64)dqacc * + (model_cfg & MAX17055_MODELCFG_VCHG_BIT ? + MAX17055_DPACC_VCHG_FACTOR : MAX17055_DPACC_FACTOR); + do_div(dpacc, design_cap); + if (dpacc > U16_MAX) + return -ERANGE; + + return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc); +} + static int max17055_init_chip(struct max17042_chip *chip) { + int ret; + max17042_override_por_values(chip); + ret = max17055_override_battery_values(chip); + if (ret) + return ret; + return regmap_write_bits(chip->regmap, MAX17055_ModelCfg, MAX17055_MODELCFG_REFRESH_BIT, MAX17055_MODELCFG_REFRESH_BIT); diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index 13aeab1597c6..61af6fe2f4fe 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -25,6 +25,7 @@ #define MAX17042_CHARACTERIZATION_DATA_SIZE 48 #define MAX17055_MODELCFG_REFRESH_BIT BIT(15) +#define MAX17055_MODELCFG_VCHG_BIT BIT(10) enum max17042_register { MAX17042_STATUS = 0x00, From 111a2d606a493c3b2f03864ea34a29df3a9e7c06 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:06 -0400 Subject: [PATCH 85/96] power: supply: max17042_battery: Follow MAX17055 POR sequence Wait until the MAX17055 is ready, leave hibernate around EZ Config, and wait for ModelCfg.Refresh to clear as required by the power-on reset initialization flow. Restore HibCfg on every exit path. Keep failed restoration state so a retry restores hibernate before starting a new initialization transaction. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-9-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 91 ++++++++++++++++++++++--- include/linux/power/max17042_battery.h | 1 + 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index c262be38e1ed..ae90f3d4afc0 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -65,7 +65,12 @@ #define MAX17055_DPACC_FACTOR 44138 #define MAX17055_DPACC_VCHG_FACTOR 51200 +#define MAX17055_FSTAT_DNR_BIT BIT(0) +#define MAX17055_DNR_POLL_US 10000 +#define MAX17055_DNR_TIMEOUT_US 2000000 #define MAX17055_INIT_RETRY_DELAY_MS 10000 +#define MAX17055_REFRESH_POLL_US 10000 +#define MAX17055_REFRESH_TIMEOUT_US 1000000 struct max17042_chip { struct device *dev; @@ -79,6 +84,8 @@ struct max17042_chip { bool enable_current_sense; bool enable_por_init; bool init_complete; + bool hib_restore_pending; + u16 hib_cfg; unsigned int r_sns; int vmin; /* in millivolts */ int vmax; /* in millivolts */ @@ -935,19 +942,86 @@ static int max17055_override_battery_values(struct max17042_chip *chip) return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc); } +static int max17055_restore_hibernate(struct max17042_chip *chip) +{ + int restore_hib_ret; + int soft_wakeup_ret; + + soft_wakeup_ret = regmap_write(chip->regmap, MAX17055_SoftWakeup, 0); + restore_hib_ret = max17055_write_verify_reg(chip->regmap, + MAX17055_HibCfg, + chip->hib_cfg); + if (!soft_wakeup_ret && !restore_hib_ret) + chip->hib_restore_pending = false; + + return soft_wakeup_ret ?: restore_hib_ret; +} + static int max17055_init_chip(struct max17042_chip *chip) { + struct regmap *map = chip->regmap; + unsigned int hib_cfg; + unsigned int model_cfg; + unsigned int fstat; + int restore_ret; int ret; + if (chip->hib_restore_pending) { + ret = max17055_restore_hibernate(chip); + if (ret) + return ret; + } + + ret = regmap_read_poll_timeout(map, MAX17042_FSTAT, fstat, + !(fstat & MAX17055_FSTAT_DNR_BIT), + MAX17055_DNR_POLL_US, + MAX17055_DNR_TIMEOUT_US); + if (ret) + return ret; + + ret = regmap_read(map, MAX17055_HibCfg, &hib_cfg); + if (ret) + return ret; + + chip->hib_cfg = hib_cfg; + chip->hib_restore_pending = true; + + ret = regmap_write(map, MAX17055_SoftWakeup, 0x0090); + if (ret) + goto restore_hibernate; + + ret = max17055_write_verify_reg(map, MAX17055_HibCfg, 0); + if (ret) + goto restore_hibernate; + + ret = regmap_write(map, MAX17055_SoftWakeup, 0); + if (ret) + goto restore_hibernate; + max17042_override_por_values(chip); ret = max17055_override_battery_values(chip); if (ret) - return ret; + goto restore_hibernate; - return regmap_write_bits(chip->regmap, MAX17055_ModelCfg, - MAX17055_MODELCFG_REFRESH_BIT, - MAX17055_MODELCFG_REFRESH_BIT); + ret = regmap_write_bits(map, MAX17055_ModelCfg, + MAX17055_MODELCFG_REFRESH_BIT, + MAX17055_MODELCFG_REFRESH_BIT); + if (ret) + goto restore_hibernate; + + ret = regmap_read_poll_timeout(map, MAX17055_ModelCfg, model_cfg, + !(model_cfg & + MAX17055_MODELCFG_REFRESH_BIT), + MAX17055_REFRESH_POLL_US, + MAX17055_REFRESH_TIMEOUT_US); + +restore_hibernate: + restore_ret = max17055_restore_hibernate(chip); + if (restore_ret) + return restore_ret; + + return ret; } static int max17042_init_chip(struct max17042_chip *chip) @@ -961,15 +1035,10 @@ static int max17042_init_chip(struct max17042_chip *chip) return ret; } else { max17042_override_por_values(chip); - } - /* After Power up, the MAX17042 requires 500mS in order - * to perform signal debouncing and initial SOC reporting - */ - msleep(500); + /* Allow signal debouncing and initial SOC reporting. */ + msleep(500); - if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { - /* Initialize configuration */ max17042_write_config_regs(chip); /* write cell characterization data */ diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index 61af6fe2f4fe..810e068eafd2 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -125,6 +125,7 @@ enum max17055_register { MAX17055_ConvgCfg = 0x49, MAX17055_VFRemCap = 0x4A, + MAX17055_SoftWakeup = 0x60, MAX17055_STATUS2 = 0xB0, MAX17055_POWER = 0xB1, From ed2b9b4b9694239f9a39f3e5087d879f5e37e315 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:07 -0400 Subject: [PATCH 86/96] power: supply: max17042_battery: Initialize MAX17055 from battery info Use charge-full-design-microamp-hours and charge-term-current-microamp from monitored-battery to prepare MAX17055 DesignCap, IChgTerm, and EZ Config dQAcc values in the power-supply registration callback. Apply positive battery values and treat zero like an absent property. Validate all supplied positive values before changing the sparse configuration. Reject values that quantize to zero, capacities that cannot produce dQAcc, and termination currents outside the signed register range. Limit this path to MAX17055 because the other supported gauges require complete characterization data. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-10-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index ae90f3d4afc0..c0498d5a40b0 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -63,6 +63,7 @@ #define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */ #define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */ +#define MAX17055_DQACC_DIV 32 #define MAX17055_DPACC_FACTOR 44138 #define MAX17055_DPACC_VCHG_FACTOR 51200 #define MAX17055_FSTAT_DNR_BIT BIT(0) @@ -1245,6 +1246,85 @@ static int max17042_init_defaults(struct max17042_chip *chip) return 0; } +static int max17042_apply_battery_properties(struct max17042_chip *chip, + struct power_supply_battery_info *info) +{ + struct max17042_config_data *config; + struct device *dev = chip->dev; + bool have_design_cap; + bool have_ichgt_term; + u16 design_cap = 0; + u16 ichgt_term = 0; + u16 dqacc = 0; + u64 data64; + + if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) + return 0; + + have_design_cap = chip->enable_current_sense && + info->charge_full_design_uah > 0; + have_ichgt_term = chip->enable_current_sense && + info->charge_term_current_ua > 0; + if (!have_design_cap && !have_ichgt_term) + return 0; + + if (have_design_cap) { + data64 = (u64)info->charge_full_design_uah * chip->r_sns; + do_div(data64, MAX17042_CAPACITY_LSB); + if (!data64) + return dev_err_probe(dev, -ERANGE, + "battery design capacity is too small for sense resistor\n"); + if (data64 > U16_MAX) + return dev_err_probe(dev, -ERANGE, + "battery design capacity exceeds register range\n"); + + design_cap = (u16)data64; + dqacc = design_cap / MAX17055_DQACC_DIV; + if (!dqacc) + return dev_err_probe(dev, -ERANGE, + "battery design capacity is too small for EZ config\n"); + } + + if (have_ichgt_term) { + data64 = (u64)info->charge_term_current_ua * chip->r_sns; + do_div(data64, MAX17042_CURRENT_LSB); + if (!data64) + return dev_err_probe(dev, -ERANGE, + "charge termination current is too small for sense resistor\n"); + if (data64 > S16_MAX) + return dev_err_probe(dev, -ERANGE, + "charge termination current exceeds positive register range\n"); + + ichgt_term = (u16)data64; + } + + config = chip->config_data; + if (!config) { + config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + } + + if (have_design_cap) { + config->design_cap = design_cap; + config->dqacc = dqacc; + } + if (have_ichgt_term) + config->ichgt_term = ichgt_term; + + chip->config_data = config; + chip->enable_por_init = true; + + return 0; +} + +static int max17042_init_battery(struct power_supply *psy) +{ + struct max17042_chip *chip = power_supply_get_drvdata(psy); + + return max17042_apply_battery_properties(chip, psy->battery_info); +} + static const struct regmap_config max17042_regmap_config = { .name = "max17042", .reg_bits = 8, @@ -1298,6 +1378,7 @@ static const struct power_supply_desc max17042_psy_desc = { .set_property = max17042_set_property, .property_is_writeable = max17042_property_is_writeable, .external_power_changed = power_supply_changed, + .init = max17042_init_battery, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props), }; @@ -1308,6 +1389,7 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = { .get_property = max17042_get_property, .set_property = max17042_set_property, .property_is_writeable = max17042_property_is_writeable, + .init = max17042_init_battery, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, }; From 6a376b843d937d2330b0111404b62589236620c1 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:08 -0400 Subject: [PATCH 87/96] power: supply: max17042_battery: Honor MAX17055 charge voltage MAX17055 ModelCfg.VChg selects the charge-voltage range used by EZ Config. Consume voltage-max-design-microvolt from monitored-battery and set VChg only when the value is strictly greater than 4.275 V. Reject an explicit zero voltage and preserve the register when the property is absent. Update only the VChg bit and verify its read-back so unrelated ModelCfg fields remain intact. Treat a VChg-only override as an accumulator change: read effective DesignCap and dQAcc, derive and verify the matching dPAcc, then request model refresh. This keeps voltage-only firmware descriptions coherent with the selected charge-voltage range. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20260727011319.621794-11-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 42 +++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index c0498d5a40b0..8869faae9a91 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -67,6 +67,7 @@ #define MAX17055_DPACC_FACTOR 44138 #define MAX17055_DPACC_VCHG_FACTOR 51200 #define MAX17055_FSTAT_DNR_BIT BIT(0) +#define MAX17055_VCHG_THRESHOLD_UV 4275000 #define MAX17055_DNR_POLL_US 10000 #define MAX17055_DNR_TIMEOUT_US 2000000 #define MAX17055_INIT_RETRY_DELAY_MS 10000 @@ -84,6 +85,7 @@ struct max17042_chip { int task_period; bool enable_current_sense; bool enable_por_init; + bool enable_vchg_override; bool init_complete; bool hib_restore_pending; u16 hib_cfg; @@ -880,7 +882,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17047_V_empty, config->vempty); } - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 && + !chip->enable_vchg_override) max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg); } @@ -915,7 +918,19 @@ static int max17055_override_battery_values(struct max17042_chip *chip) return ret; } - if (!config->design_cap && !config->dqacc) + if (chip->enable_vchg_override) { + ret = regmap_update_bits(map, MAX17055_ModelCfg, + MAX17055_MODELCFG_VCHG_BIT, + config->model_cfg & + MAX17055_MODELCFG_VCHG_BIT); + if (ret) + return ret; + + usleep_range(1000, 2000); + } + + if (!config->design_cap && !config->dqacc && + !chip->enable_vchg_override) return 0; ret = regmap_read(map, MAX17042_DesignCap, &design_cap); @@ -929,6 +944,10 @@ static int max17055_override_battery_values(struct max17042_chip *chip) ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg); if (ret) return ret; + if (chip->enable_vchg_override && + (model_cfg & MAX17055_MODELCFG_VCHG_BIT) != + (config->model_cfg & MAX17055_MODELCFG_VCHG_BIT)) + return -EIO; if (!design_cap || !dqacc) return -ERANGE; @@ -1253,9 +1272,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip, struct device *dev = chip->dev; bool have_design_cap; bool have_ichgt_term; + bool have_vchg; u16 design_cap = 0; u16 ichgt_term = 0; u16 dqacc = 0; + u16 model_cfg = 0; u64 data64; if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) @@ -1265,7 +1286,8 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip, info->charge_full_design_uah > 0; have_ichgt_term = chip->enable_current_sense && info->charge_term_current_ua > 0; - if (!have_design_cap && !have_ichgt_term) + have_vchg = info->voltage_max_design_uv >= 0; + if (!have_design_cap && !have_ichgt_term && !have_vchg) return 0; if (have_design_cap) { @@ -1298,6 +1320,15 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip, ichgt_term = (u16)data64; } + if (have_vchg) { + if (!info->voltage_max_design_uv) + return dev_err_probe(dev, -EINVAL, + "battery design voltage must be positive\n"); + + if (info->voltage_max_design_uv > MAX17055_VCHG_THRESHOLD_UV) + model_cfg = MAX17055_MODELCFG_VCHG_BIT; + } + config = chip->config_data; if (!config) { config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); @@ -1311,6 +1342,11 @@ static int max17042_apply_battery_properties(struct max17042_chip *chip, } if (have_ichgt_term) config->ichgt_term = ichgt_term; + if (have_vchg) { + config->model_cfg &= ~MAX17055_MODELCFG_VCHG_BIT; + config->model_cfg |= model_cfg; + chip->enable_vchg_override = true; + } chip->config_data = config; chip->enable_por_init = true; From c84ccde22d9d3070a9ee144e825270a7a784c570 Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Sun, 26 Jul 2026 21:13:09 -0400 Subject: [PATCH 88/96] dt-bindings: power: supply: max17042: Allow generic power-supply properties The MAX17042 family binding references the generic power-supply schema, but additionalProperties only recognizes properties declared directly by this schema. Use unevaluatedProperties so generic properties such as monitored-battery are accepted while unknown properties remain rejected. Remove the local power-supplies declaration because it is already inherited from power-supply.yaml. Assisted-by: OpenCode:gpt-5.6-sol Signed-off-by: Vincent Cloutier Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260727011319.621794-12-vincent.cloutier@icloud.com Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power/supply/maxim,max17042.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml index 242b33f2bcba..b2e5ddc5c580 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml @@ -67,13 +67,11 @@ properties: Voltage threshold to report battery as over voltage (in mV). Default is not to report over-voltage events. - power-supplies: true - required: - compatible - reg -additionalProperties: false +unevaluatedProperties: false examples: - | From 15c53209e67ad450fb791aff5e0bd3dcc4349e36 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Tue, 28 Jul 2026 15:38:29 -0500 Subject: [PATCH 89/96] dt-bindings: power: supply: sgm41542: document sgm41542 Document the SG Micro sgm41542 battery charger/boost converter. The parameters of input-current-limit-microamp and input-voltage-limit-microvolt are defined as such since they are in common use among multiple bindings currently. Signed-off-by: Chris Morgan Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260728203832.166402-2-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- .../power/supply/sgmicro,sgm41542.yaml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml diff --git a/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml new file mode 100644 index 000000000000..af80fe528505 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/sgmicro,sgm41542.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SGM41542 Battery Charger + +description: + The SGMicro SGM41542 is a single cell battery charger/boost controller. + +maintainers: + - Chris Morgan + - Xu Shengfei + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: sgmicro,sgm41542 + + input-current-limit-microamp: + default: 2400000 + minimum: 100000 + maximum: 3800000 + + input-voltage-limit-microvolt: + default: 4500000 + minimum: 3900000 + maximum: 12000000 + + interrupts: + maxItems: 1 + + monitored-battery: + description: | + The charger uses the following battery properties + constant-charge-current-max-microamp (default 2040000) + constant-charge-voltage-max-microvolt (default 4208000) + charge-term-current-microamp (default 180000) + precharge-current-microamp (default 180000) + + reg: + maxItems: 1 + + regulators: + type: object + properties: + otg-vbus: + type: object + description: OTG boost regulator + $ref: /schemas/regulator/regulator.yaml + unevaluatedProperties: false + additionalProperties: false + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + #include + + battery: battery { + compatible = "simple-battery"; + constant-charge-current-max-microamp = <10000000>; + constant-charge-voltage-max-microvolt = <4350000>; + precharge-current-microamp = <180000>; + charge-term-current-microamp = <300000>; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + charger@3b { + compatible = "sgmicro,sgm41542"; + reg = <0x3b>; + input-current-limit-microamp = <3000000>; + input-voltage-limit-microvolt = <4500000>; + interrupt-parent = <&gpio0>; + interrupts = <26 IRQ_TYPE_EDGE_FALLING>; + monitored-battery = <&battery>; + + regulators { + otg-vbus { + regulator-max-microvolt = <5000000>; + regulator-min-microvolt = <5000000>; + }; + }; + }; + }; From 5584ad5706e594c7648655171785aa4fffdd3db5 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Tue, 28 Jul 2026 15:38:30 -0500 Subject: [PATCH 90/96] power: supply: sgm41542: Add SG Micro sgm41542 charger Add support for the SG Micro SGM41542 charger/boost converter. Driver was adapted from Rockchip BSP driver [1] and confirmed with vendor datasheet [2]. [1] https://github.com/rockchip-linux/kernel/blob/develop-6.6/drivers/power/supply/sgm41542_charger.c [2] https://www.sg-micro.de/rect/assets/1e8de70b-657e-4156-be68-a64fdbe8e418/SGM41541_SGM41542.pdf Signed-off-by: Chris Morgan Link: https://patch.msgid.link/20260728203832.166402-3-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 9 + drivers/power/supply/Makefile | 1 + drivers/power/supply/sgm41542_charger.c | 1127 +++++++++++++++++++++++ 3 files changed, 1137 insertions(+) create mode 100644 drivers/power/supply/sgm41542_charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f1334dcdf39d..b89ef40df7f8 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -873,6 +873,15 @@ config CHARGER_S2M devices provide USB power supply information and also required for USB OTG role switching. +config CHARGER_SGM41542 + tristate "SGM41542 charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + depends on REGULATOR + select REGMAP_I2C + help + Say Y to enable support for the SGM41542 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB3XX Battery Charger" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index fff92f5ca095..f3c0d1006c1a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -109,6 +109,7 @@ obj-$(CONFIG_CHARGER_BQ25630) += bq25630_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o +obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c new file mode 100644 index 000000000000..8880962528d6 --- /dev/null +++ b/drivers/power/supply/sgm41542_charger.c @@ -0,0 +1,1127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Charger driver for SGM4154x + * + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGM4154X_MANUFACTURER "SGMICRO" +#define SGM4154X_NAME "sgm41542" + +#define SGM4154X_CHRG_CTRL_0 0x00 +#define SGM4154X_HIZ_EN BIT(7) +#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0) +#define SGM4154X_IINDPM_I_MIN_UA 100000 +#define SGM4154X_IINDPM_I_MAX_UA 3800000 +#define SGM4154X_IINDPM_STEP_UA 100000 +#define SGM4154X_IINDPM_DEF_UA 2400000 + +#define SGM4154X_CHRG_CTRL_1 0x01 +#define SGM4154X_WDT_RST BIT(6) +#define SGM4154X_OTG_EN BIT(5) +#define SGM4154X_CHRG_EN BIT(4) + +#define SGM4154X_CHRG_CTRL_2 0x02 +#define SGM4154X_BOOST_LIM BIT(7) +#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0) +#define SGM4154X_ICHRG_I_STEP_UA 60000 +#define SGM4154X_ICHRG_I_MIN_UA 0 +#define SGM4154X_ICHRG_I_MAX_UA 3780000 +#define SGM4154X_ICHRG_I_DEF_UA 2040000 + +#define SGM4154X_CHRG_CTRL_3 0x03 +#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4) +#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_PRECHRG_I_MIN_UA 60000 +#define SGM4154X_PRECHRG_I_MAX_UA 780000 +#define SGM4154X_PRECHRG_I_DEF_UA 180000 +#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0) +#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_TERMCHRG_I_MIN_UA 60000 +#define SGM4154X_TERMCHRG_I_MAX_UA 960000 +#define SGM4154X_TERMCHRG_I_DEF_UA 180000 + +#define SGM4154X_CHRG_CTRL_4 0x04 +#define SGM4154X_VREG_V_MASK GENMASK(7, 3) +#define SGM4154X_VREG_V_MAX_UV 4624000 +#define SGM4154X_VREG_V_MIN_UV 3856000 +#define SGM4154X_VREG_V_DEF_UV 4208000 +#define SGM4154X_VREG_V_STEP_UV 32000 +#define SGM4154X_VRECHARGE BIT(0) +#define SGM4154X_VRECHRG_STEP_UV 100000 +#define SGM4154X_VRECHRG_OFFSET_UV 100000 + +#define SGM4154X_CHRG_CTRL_5 0x05 +#define SGM4154X_TERM_EN BIT(7) +#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4) + +#define SGM4154X_CHRG_CTRL_6 0x06 +#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6) +#define SGM4154X_OVP_14V (BIT(7) | BIT(6)) +#define SGM4154X_OVP_10_5V BIT(7) +#define SGM4154X_OVP_6_5V BIT(6) +#define SGM4154X_OVP_5_5V 0 +#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V +#define SGM4154X_BOOSTV GENMASK(5, 4) +#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0) +#define SGM4154X_VINDPM_V_MIN_UV 3900000 +#define SGM4154X_VINDPM_V_MAX_UV 12000000 +#define SGM4154X_VINDPM_STEP_UV 100000 +#define SGM4154X_VINDPM_DEF_UV 4500000 + +#define SGM4154X_CHRG_CTRL_7 0x07 + +#define SGM4154X_CHRG_STAT 0x08 +#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5) +#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5)) +#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6)) +#define SGM4154X_UNKNOWN (BIT(7) | BIT(5)) +#define SGM4154X_USB_DCP (BIT(6) | BIT(5)) +#define SGM4154X_USB_CDP BIT(6) +#define SGM4154X_USB_SDP BIT(5) +#define SGM4154X_NOT_CHRGING 0 +#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3) +#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3)) +#define SGM4154X_FAST_CHRG BIT(4) +#define SGM4154X_PRECHRG BIT(3) +#define SGM4154X_PG_STAT BIT(2) +#define SGM4154X_THERM_STAT BIT(1) +#define SGM4154X_VSYS_STAT BIT(0) + +#define SGM4154X_CHRG_FAULT 0x09 +#define SGM4154X_TEMP_MASK GENMASK(2, 0) +#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1)) +#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0)) +#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0)) +#define SGM4154X_TEMP_WARM BIT(1) +#define SGM4154X_TEMP_NORMAL BIT(0) + +#define SGM4154X_CHRG_CTRL_A 0x0a +#define SGM4154X_VBUS_GOOD BIT(7) +#define SGM4154X_VINDPM_INT_MASK BIT(1) +#define SGM4154X_IINDPM_INT_MASK BIT(0) + +#define SGM4154X_CHRG_CTRL_B 0x0b +#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3)) +#define SGM4154X_PN_MASK GENMASK(6, 3) + +#define SGM4154X_CHRG_CTRL_C 0x0c + +#define SGM4154X_CHRG_CTRL_D 0x0d +#define SGM4154X_JEITA_EN BIT(0) + +#define SGM4154X_INPUT_DET 0x0e +#define SGM4154X_DPDM_ONGOING BIT(7) + +#define SGM4154X_CHRG_CTRL_F 0x0f +#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0) + +#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000) + +struct sgm4154x_init_data { + int ilim; /* input current limit */ + int vlim; /* minimum system voltage limit */ + int iterm; /* termination current */ + int iprechg; /* precharge current */ + int max_ichg; /* maximum charge current */ + int max_vreg; /* maximum charge voltage */ +}; + +struct sgm4154x_state { + bool vsys_stat; + bool therm_stat; + bool online; + u8 chrg_stat; + bool chrg_en; + bool vbus_gd; + u8 chrg_type; + u8 health; + u8 chrg_fault; + u8 ntc_fault; +}; + +struct sgm4154x_device { + struct device *dev; + struct power_supply *charger; + struct regmap *regmap; + struct sgm4154x_init_data init_data; + struct sgm4154x_state state; + struct regulator_dev *otg_rdev; + struct mutex lock; + bool watchdog_enable; + struct workqueue_struct *sgm_monitor_wq; + struct delayed_work sgm_delay_work; +}; + +enum SGM4154X_VINDPM_OS { + VINDPM_OS_3900MV, + VINDPM_OS_5900MV, + VINDPM_OS_7500MV, + VINDPM_OS_10500MV, +}; + +enum sgm4154x_wdt_values { + SGM4154X_WDT_TIMER_DISABLE = 0, + SGM4154X_WDT_TIMER_40S = 1, + SGM4154X_WDT_TIMER_80S = 2, + SGM4154X_WDT_TIMER_160S = 3, +}; + +static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_TERMCHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set term current error!\n"); + + return ret; +} + +static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA; + + reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_PRECHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set precharge current error!\n"); + + return ret; +} + +static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg); + reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_2, + SGM4154X_ICHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set icharge current error!\n"); + + return ret; +} + +static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, ®); + if (ret) { + dev_err(sgm->dev, "get charge current error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg); + + return val * SGM4154X_ICHRG_I_STEP_UA; +} + +static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt) +{ + int reg_val; + int ret; + + /* + * Note that the value of 0x01111 represents a "special value" + * corresponding to 4352000uV instead of the expected 4336000uV, + * per the datasheet. All other values are as expected. So not + * only do we need to clamp between max and min values, but + * also clamp anything below 4352000uv to 4304000uv to prevent + * overcharging. + */ + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg); + if (chrg_volt < 4352000) + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000); + reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV; + reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VREG_V_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set charge voltage error!\n"); + + return ret; +} + +static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, ®); + if (ret) { + dev_err(sgm->dev, "get charge voltage error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_VREG_V_MASK, reg); + + /* + * 0x01111 is a special value meaning 4352000uV, all other + * values are as expected based on the offset and step values. + */ + if (val == 0x0f) + return 4352000; + + return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV; +} + +static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm, + unsigned int vindpm) +{ + enum SGM4154X_VINDPM_OS os_val; + unsigned int offset; + u8 reg_val; + int ret; + + + if (vindpm < SGM4154X_VINDPM_V_MIN_UV || + vindpm > SGM4154X_VINDPM_V_MAX_UV) { + dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm); + return -EINVAL; + } + + /* + * Supported ranges per the datasheet are as follows: + * 3.9v - 5.4v with a 3.9v offset + * 5.9v - 7.4v with a 5.9v offset + * 7.5v - 9.0v with a 7.5v offset + * 10.5v - 12.0v with a 10.5v offset + * Step size is a constant 100mv + */ + if (vindpm < 5900000) { + offset = 3900000; + vindpm = clamp(vindpm, offset, 5400000); + os_val = VINDPM_OS_3900MV; + } else if (vindpm < 7500000) { + offset = 5900000; + vindpm = clamp(vindpm, offset, 7400000); + os_val = VINDPM_OS_5900MV; + } else if (vindpm < 10500000) { + offset = 7500000; + vindpm = clamp(vindpm, offset, 9000000); + os_val = VINDPM_OS_7500MV; + } else { + offset = 10500000; + vindpm = clamp(vindpm, offset, 12000000); + os_val = VINDPM_OS_10500MV; + } + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_F, + SGM4154X_VINDPM_OS_MASK, + os_val); + if (ret) { + dev_err(sgm->dev, "set vin dpm error!\n"); + return ret; + } + + reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6, + SGM4154X_VINDPM_V_MASK, reg_val); + if (ret) + dev_err(sgm->dev, "input voltage error!\n"); + + return ret; +} + +static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm) +{ + int reg_val; + int ret; + + if (iindpm < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Protect against a timing-issue edge case if a sysfs write occurs in the middle + * of a probe (very unlikely). + */ + if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Per the datasheet, values between 100000uA and 3100000uA work + * as expected with the register defined as having a step of + * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e). + * The register value of 0x1f however corresponds to 3800000uA not + * 3200000uA as one would expect. + */ + if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim)) + iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim); + + if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA) + iindpm = 3100000; + + if (iindpm == SGM4154X_IINDPM_I_MAX_UA) + reg_val = 0x1f; + else + reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_0, + SGM4154X_IINDPM_I_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set input current limit error!\n"); + + return ret; +} + +static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm) +{ + int ret; + int ilim; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim); + if (ret) { + dev_err(sgm->dev, "get input current limit error!\n"); + return ret; + } + + ilim &= SGM4154X_IINDPM_I_MASK; + + /* Max value is not 3200000uA as expected but is 3800000uA */ + if (ilim == SGM4154X_IINDPM_I_MASK) + return SGM4154X_IINDPM_I_MAX_UA; + + ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA; + + return ilim; +} + +static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_WDT_RST, + SGM4154X_WDT_RST); + + if (ret) + dev_err(sgm->dev, "set watchdog timer error!\n"); + + return ret; +} + +static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time) +{ + int ret; + + if (time > SGM4154X_WDT_TIMER_160S) + return -EINVAL; + + time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_5, + SGM4154X_WDT_TIMER_MASK, + time); + + if (ret) { + dev_err(sgm->dev, "set watchdog timer error!\n"); + return ret; + } + + if (time) { + if (!sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); + sgm->watchdog_enable = true; + } else { + sgm->watchdog_enable = false; + sgm4154x_watchdog_timer_reset(sgm); + } + + return ret; +} + +static int sgm4154x_enable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + SGM4154X_CHRG_EN); + if (ret) + dev_err(sgm->dev, "enable charger error!\n"); + + return ret; +} + +static int sgm4154x_disable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + 0); + if (ret) + dev_err(sgm->dev, "disable charger error!\n"); + + return ret; +} + +static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm) +{ + int reg_val; + int ret; + + reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_6, + SGM4154X_VAC_OVP_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set vac ovp error!\n"); + + return ret; +} + +static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt) +{ + int reg_val; + int ret; + + reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VRECHARGE, + reg_val); + if (ret) + dev_err(sgm->dev, "set recharger error!\n"); + + return ret; +} + +static int sgm4154x_get_state(struct sgm4154x_device *sgm, + struct sgm4154x_state *state) +{ + unsigned int reg; + int ret; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n"); + return ret; + } + state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK; + state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK; + state->online = !!(reg & SGM4154X_PG_STAT); + state->therm_stat = !!(reg & SGM4154X_THERM_STAT); + state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT); + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n"); + return ret; + } + state->chrg_fault = reg; + state->ntc_fault = reg & SGM4154X_TEMP_MASK; + state->health = state->ntc_fault; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n"); + return ret; + } + state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD); + + return ret; +} + +static int sgm4154x_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_ONLINE: + return true; + default: + return false; + } +} + +static int sgm4154x_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + int ret = -EINVAL; + + guard(mutex)(&sgm->lock); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + ret = sgm4154x_enable_charger(sgm); + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + } else { + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + ret = sgm4154x_disable_charger(sgm); + } + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = sgm4154x_set_input_curr_lim(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = sgm4154x_set_ichrg_curr(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = sgm4154x_set_chrg_volt(sgm, val->intval); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int sgm4154x_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct sgm4154x_state state; + int ret; + + scoped_guard(mutex, &sgm->lock) { + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return ret; + } + sgm->state = state; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!state.chrg_stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_stat == SGM4154X_TERM_CHRG) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (state.chrg_stat) { + case SGM4154X_PRECHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_FAST_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case SGM4154X_TERM_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_NOT_CHRGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = SGM4154X_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = SGM4154X_NAME; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = state.vbus_gd; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = sgm4154x_get_chrg_volt(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = sgm4154x_get_ichrg_curr(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + val->intval = sgm->init_data.vlim; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = sgm4154x_get_input_curr_lim(sgm); + if (val->intval < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return ret; +} + +static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private) +{ + struct sgm4154x_device *sgm = private; + struct sgm4154x_state oldstate, state; + int ret; + + guard(mutex)(&sgm->lock); + + oldstate = sgm->state; + + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return IRQ_NONE; + } + + sgm->state = state; + + if (state.vbus_gd && !oldstate.vbus_gd) { + if (sgm->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) { + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) { + dev_err(sgm->dev, "set input current error!\n"); + /* + * Reading IRQ clears interrupt, so return handled + * even if error. + */ + } + } + } + + power_supply_changed(sgm->charger); + + return IRQ_HANDLED; +} + +static int sgm4154x_hw_init(struct power_supply *psy) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct power_supply_battery_info *bat_info; + int ret; + u32 val; + + /* + * If unable to read devicetree info, use default/reset + * values from hardware. If the devicetree info has data out + * of range for any of the values, use the default value. + */ + sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA; + sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA; + sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA; + sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV; + sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV; + sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA; + + ret = power_supply_get_battery_info(psy, &bat_info); + if (ret) + dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n"); + else { + if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) && + (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA)) + sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua; + if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) && + (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV)) + sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv; + if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) && + (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA)) + sgm->init_data.iterm = bat_info->charge_term_current_ua; + if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) && + (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA)) + sgm->init_data.iprechg = bat_info->precharge_current_ua; + + power_supply_put_battery_info(psy, bat_info); + } + + ret = device_property_read_u32(sgm->dev, + "input-voltage-limit-microvolt", + &val); + if (!ret) + sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV, + SGM4154X_VINDPM_V_MAX_UV); + + ret = device_property_read_u32(sgm->dev, + "input-current-limit-microamp", + &val); + if (!ret) + sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA, + SGM4154X_IINDPM_I_MAX_UA); + + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + + ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg); + if (ret) + return ret; + + ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg); + if (ret) + return ret; + + ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm); + if (ret) + return ret; + + ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg); + if (ret) + return ret; + + ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim); + if (ret) + return ret; + + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) + return ret; + + ret = sgm4154x_set_vac_ovp(sgm); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_D, + SGM4154X_JEITA_EN, + 0); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_IINDPM_INT_MASK, + SGM4154X_IINDPM_INT_MASK); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_VINDPM_INT_MASK, + SGM4154X_VINDPM_INT_MASK); + if (ret) + return ret; + + /* + * Recharge microvolt set to 200000 by BSP driver instead of hardware + * default value of 100000. + */ + ret = sgm4154x_set_recharge_volt_ua(sgm, 200000); + + return ret; +} + +static enum power_supply_property sgm4154x_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_PRESENT +}; + +static struct power_supply_desc sgm4154x_power_supply_desc = { + .name = "sgm4154x-charger", + .type = POWER_SUPPLY_TYPE_USB, + .init = sgm4154x_hw_init, + .properties = sgm4154x_power_supply_props, + .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props), + .get_property = sgm4154x_charger_get_property, + .set_property = sgm4154x_charger_set_property, + .property_is_writeable = sgm4154x_property_is_writeable, +}; + +static const struct regmap_config sgm4154x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SGM4154X_CHRG_CTRL_F, + .cache_type = REGCACHE_NONE, +}; + +static const u32 sgm4154x_chg_otg_cur_ua[] = { + 1200000, 2000000, +}; + +static const struct regulator_ops sgm4154x_vbus_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const struct regulator_desc sgm4154x_otg_rdesc = { + .of_match = "otg-vbus", + .name = "otg-vbus", + .regulators_node = of_match_ptr("regulators"), + .ops = &sgm4154x_vbus_ops, + .owner = THIS_MODULE, + .type = REGULATOR_VOLTAGE, + .min_uV = 4850000, + .uV_step = 150000, + .n_voltages = 4, + .vsel_reg = SGM4154X_CHRG_CTRL_6, + .vsel_mask = SGM4154X_BOOSTV, + .enable_reg = SGM4154X_CHRG_CTRL_1, + .enable_mask = SGM4154X_OTG_EN, + .curr_table = sgm4154x_chg_otg_cur_ua, + .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua), + .csel_reg = SGM4154X_CHRG_CTRL_2, + .csel_mask = SGM4154X_BOOST_LIM, +}; + +static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm) +{ + struct regulator_config config = { + .dev = sgm->dev, + .regmap = sgm->regmap, + .driver_data = sgm, + }; + + sgm->otg_rdev = devm_regulator_register(sgm->dev, + &sgm4154x_otg_rdesc, + &config); + + return PTR_ERR_OR_ZERO(sgm->otg_rdev); +} + +static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm) +{ + int ret; + int val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val); + if (ret) + return ret; + + if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID) + dev_warn(sgm->dev, "sgm4154x device ID mismatch\n"); + + return 0; +} + +static void sgm_charger_work(struct work_struct *work) +{ + struct sgm4154x_device *sgm = + container_of(work, + struct sgm4154x_device, + sgm_delay_work.work); + + sgm4154x_watchdog_timer_reset(sgm); + if (sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); +} + +static void sgm4154x_disable_irq_wake(void *data) +{ + struct sgm4154x_device *sgm = data; + struct i2c_client *client = to_i2c_client(sgm->dev); + + disable_irq_wake(client->irq); +} + +static int sgm4154x_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct sgm4154x_device *sgm; + int ret; + + sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL); + if (!sgm) + return -ENOMEM; + + sgm->dev = dev; + + sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config); + if (IS_ERR(sgm->regmap)) + return dev_err_probe(dev, PTR_ERR(sgm->regmap), + "Failed to allocate register map\n"); + + i2c_set_clientdata(client, sgm); + + ret = devm_mutex_init(dev, &sgm->lock); + if (ret) + return ret; + + ret = sgm4154x_hw_chipid_detect(sgm); + if (ret) + return dev_err_probe(dev, ret, "Unable to read HW ID\n"); + + devm_device_init_wakeup(dev); + + sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq", + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!sgm->sgm_monitor_wq) + return -EINVAL; + + ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work, + sgm_charger_work); + if (ret) + return dev_err_probe(dev, ret, "Unable to register delayed work\n"); + + psy_cfg.drv_data = sgm; + psy_cfg.fwnode = dev_fwnode(dev); + + sgm->charger = devm_power_supply_register(sgm->dev, + &sgm4154x_power_supply_desc, + &psy_cfg); + if (IS_ERR(sgm->charger)) + return dev_err_probe(dev, PTR_ERR(sgm->charger), + "Failed to register power supply\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sgm4154x_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "sgm41542-irq", sgm); + if (ret) + return ret; + + ret = enable_irq_wake(client->irq); + if (!ret) { + ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm); + if (ret) + return ret; + } + } + + ret = sgm4154x_vbus_regulator_register(sgm); + if (ret) { + return dev_err_probe(dev, ret, + "Unable to register VBUS regulator\n"); + } + + return 0; +} + +static int __maybe_unused sgm4154x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + bool watchdog = sgm->watchdog_enable; + int ret; + + /* + * Disable watchdog during suspend and stop delayed work. When + * delayed work is restarted after resume watchdog will be + * re-enabled if it was previously enabled. Retain the current + * state of the watchdog timer though, so it can be re-enabled + * if necessary by the work queue once resume is triggered. + */ + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + cancel_delayed_work_sync(&sgm->sgm_delay_work); + sgm->watchdog_enable = watchdog; + return 0; +} + +static int __maybe_unused sgm4154x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + int ret; + + if (sgm->watchdog_enable) { + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + if (ret) + return ret; + } + + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, 0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(sgm4154x_pm_ops, sgm4154x_suspend, sgm4154x_resume); + +static const struct i2c_device_id sgm4154x_i2c_ids[] = { + { "sgm41542" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids); + +static const struct of_device_id sgm4154x_of_match[] = { + { .compatible = "sgmicro,sgm41542", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sgm4154x_of_match); + +static struct i2c_driver sgm4154x_driver = { + .driver = { + .name = "sgm4154x-charger", + .of_match_table = sgm4154x_of_match, + .pm = &sgm4154x_pm_ops, + }, + .probe = sgm4154x_probe, + .id_table = sgm4154x_i2c_ids, +}; + +module_i2c_driver(sgm4154x_driver); + +MODULE_AUTHOR("Xu Shengfei "); +MODULE_DESCRIPTION("sgm4154x charger driver"); +MODULE_LICENSE("GPL"); From 9092828e187f7cb3c7346a64e01a10d8f75ca246 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 30 Jul 2026 09:17:12 +0800 Subject: [PATCH 91/96] power: supply: bq25630: Scope battery information to bq25630_setup() data->batinfo is only used by bq25630_setup() to program the initial charge limits, but power_supply_get_battery_info() allocates it on psy->dev, so it stays around for the lifetime of the device. Nothing else in the driver uses it. Get the battery information in bq25630_setup(), just before it is read, and release it on every path out of that function. The driver data no longer has to carry the pointer. Signed-off-by: Linmao Li Reviewed-by: Waqar Hameed Link: https://patch.msgid.link/20260730011713.3332913-2-lilinmao@kylinos.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25630_charger.c | 48 ++++++++++++++------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c index 165f8c67b489..54c62b7d6514 100644 --- a/drivers/power/supply/bq25630_charger.c +++ b/drivers/power/supply/bq25630_charger.c @@ -356,7 +356,6 @@ struct bq25630_data { struct regmap_field *regfields[BQ25630_REGF_MAX]; struct power_supply *psy; - struct power_supply_battery_info *batinfo; /* State status from IRQs. */ u8 statregs[BQ25630_NR_STAT_REGS]; @@ -668,6 +667,7 @@ static int bq25630_reset(struct bq25630_data *data) static int bq25630_setup(struct bq25630_data *data) { + struct power_supply_battery_info *batinfo; int ret; ret = bq25630_reset(data); @@ -684,69 +684,78 @@ static int bq25630_setup(struct bq25630_data *data) return ret; } + ret = power_supply_get_battery_info(data->psy, &batinfo); + if (ret) { + dev_err(data->dev, "Could not get battery info (%d)\n", ret); + return ret; + } + /* * Set values according to battery info. Warn on missing "dangerous" * properties. */ - if (data->batinfo->voltage_min_design_uv >= 0) { + if (batinfo->voltage_min_design_uv >= 0) { ret = bq25630_write_limit(data, BQ25630_REGF_VSYSMIN, BQ25630_VSYSMIN_MIN, BQ25630_VSYSMIN_MAX, BQ25630_VSYSMIN_STEP, BQ25630_VSYSMIN_MIN_REGVAL, - data->batinfo->voltage_min_design_uv); + batinfo->voltage_min_design_uv); if (ret) - return ret; + goto out_put_batinfo; } else dev_warn(data->dev, "Using default value for minimum voltage\n"); - if (data->batinfo->constant_charge_voltage_max_uv >= 0) { + if (batinfo->constant_charge_voltage_max_uv >= 0) { ret = bq25630_write_limit( data, BQ25630_REGF_VREG, BQ25630_VREG_MIN, BQ25630_VREG_MAX, BQ25630_VREG_STEP, BQ25630_VREG_MIN_REGVAL, - data->batinfo->constant_charge_voltage_max_uv); + batinfo->constant_charge_voltage_max_uv); if (ret) - return ret; + goto out_put_batinfo; } else dev_warn(data->dev, "Using default value for maximum constant charge voltage\n"); - if (data->batinfo->constant_charge_current_max_ua >= 0) { + if (batinfo->constant_charge_current_max_ua >= 0) { ret = bq25630_write_limit( data, BQ25630_REGF_ICHG, BQ25630_ICHG_MIN, BQ25630_ICHG_MAX, BQ25630_ICHG_STEP, BQ25630_ICHG_MIN_REGVAL, - data->batinfo->constant_charge_current_max_ua); + batinfo->constant_charge_current_max_ua); if (ret) - return ret; + goto out_put_batinfo; } else dev_warn(data->dev, "Using default value for maximum constant charge current\n"); - if (data->batinfo->charge_term_current_ua >= 0) { + if (batinfo->charge_term_current_ua >= 0) { ret = bq25630_write_limit( data, BQ25630_REGF_ITERM, BQ25630_ITERM_MIN, BQ25630_ITERM_MAX, BQ25630_ITERM_STEP, BQ25630_ITERM_MIN_REGVAL, - data->batinfo->charge_term_current_ua); + batinfo->charge_term_current_ua); if (ret) - return ret; + goto out_put_batinfo; } - if (data->batinfo->precharge_current_ua >= 0) { + if (batinfo->precharge_current_ua >= 0) { ret = bq25630_write_limit(data, BQ25630_REGF_IPRECHG, BQ25630_IPRECHG_MIN, BQ25630_IPRECHG_MAX, BQ25630_IPRECHG_STEP, BQ25630_IPRECHG_MIN_REGVAL, - data->batinfo->precharge_current_ua); + batinfo->precharge_current_ua); if (ret) - return ret; + goto out_put_batinfo; } - return 0; +out_put_batinfo: + power_supply_put_battery_info(data->psy, batinfo); + + return ret; } static int bq25630_charger_get_property(struct power_supply *psy, @@ -1024,11 +1033,6 @@ static int bq25630_probe(struct i2c_client *client) return dev_err_probe(data->dev, PTR_ERR(data->psy), "Could not register power supply\n"); - ret = power_supply_get_battery_info(data->psy, &data->batinfo); - if (ret) - return dev_err_probe(data->dev, ret, - "Could not get battery info\n"); - /* * Device sends active low 256 µs pulse to report status and fault. * From fdece8642eca8e20a218837f58c0ae6d83fe53a1 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 30 Jul 2026 09:17:13 +0800 Subject: [PATCH 92/96] power: supply: bq25630: Initialize hardware before exposing the power supply bq25630_setup() resets the device, disables the watchdog and programs the charge limits from the battery information. It runs at the end of bq25630_probe(), that is after the power supply has been registered, so the device is already exposed to the system while the hardware still holds its power-on defaults. power_supply_desc::init runs during registration, after the driver data and the fwnode are available and before the device is added. Use it for bq25630_setup() and drop the explicit call from bq25630_probe(). The callback is passed the power supply, so take the driver data from it and use it for the battery information as well: data->psy is only assigned once devm_power_supply_register() returns, which is after the callback has run. Signed-off-by: Linmao Li Reviewed-by: Waqar Hameed Link: https://patch.msgid.link/20260730011713.3332913-3-lilinmao@kylinos.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25630_charger.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c index 54c62b7d6514..200f74f8eab6 100644 --- a/drivers/power/supply/bq25630_charger.c +++ b/drivers/power/supply/bq25630_charger.c @@ -665,8 +665,9 @@ static int bq25630_reset(struct bq25630_data *data) return 0; } -static int bq25630_setup(struct bq25630_data *data) +static int bq25630_setup(struct power_supply *psy) { + struct bq25630_data *data = power_supply_get_drvdata(psy); struct power_supply_battery_info *batinfo; int ret; @@ -684,7 +685,7 @@ static int bq25630_setup(struct bq25630_data *data) return ret; } - ret = power_supply_get_battery_info(data->psy, &batinfo); + ret = power_supply_get_battery_info(psy, &batinfo); if (ret) { dev_err(data->dev, "Could not get battery info (%d)\n", ret); return ret; @@ -753,7 +754,7 @@ static int bq25630_setup(struct bq25630_data *data) } out_put_batinfo: - power_supply_put_battery_info(data->psy, batinfo); + power_supply_put_battery_info(psy, batinfo); return ret; } @@ -976,6 +977,7 @@ static const struct power_supply_desc bq25630_charger_psy_desc = { .get_property = bq25630_charger_get_property, .set_property = bq25630_charger_set_property, .property_is_writeable = bq25630_charger_property_is_writeable, + .init = bq25630_setup, }; static int bq25630_probe(struct i2c_client *client) @@ -1048,10 +1050,6 @@ static int bq25630_probe(struct i2c_client *client) if (ret) return dev_err_probe(data->dev, ret, "Could not request IRQ\n"); - ret = bq25630_setup(data); - if (ret) - return ret; - return 0; } From 84b3a9353e3f32ebd0dc5bf1ab378742bd7cb682 Mon Sep 17 00:00:00 2001 From: Rakesh Kota Date: Fri, 24 Jul 2026 15:44:11 +0530 Subject: [PATCH 93/96] dt-bindings: power: reset: qcom-pon: Add new compatible PMM8654AU PMM8654AU is a distinct PMIC variant from PMM8650AU despite sharing the same PMIC subtype. PMM8654AU implements additional registers added to the "hlos" register address spaces, so add qcom,pmm8654au-pon as a fallback to qcom,pmk8350-pon to distinguish it from the baseline PMK8350 PON implementation. Reviewed-by: Dmitry Baryshkov Signed-off-by: Rakesh Kota Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260724-b4-add_pwrkey_and_resin-v6-1-41acc214d93a@oss.qualcomm.com Signed-off-by: Sebastian Reichel --- .../bindings/power/reset/qcom,pon.yaml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml index 71d43d3602f7..3d07aa004eb6 100644 --- a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml +++ b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml @@ -17,12 +17,16 @@ description: | properties: compatible: - enum: - - qcom,pm8916-pon - - qcom,pm8941-pon - - qcom,pms405-pon - - qcom,pm8998-pon - - qcom,pmk8350-pon + oneOf: + - enum: + - qcom,pm8916-pon + - qcom,pm8941-pon + - qcom,pms405-pon + - qcom,pm8998-pon + - qcom,pmk8350-pon + - items: + - const: qcom,pmm8654au-pon + - const: qcom,pmk8350-pon reg: description: | From 60c5b8a9ef4dbc5d69bbc1a960fe55826cb3b643 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Tue, 28 Jul 2026 20:33:39 +0800 Subject: [PATCH 94/96] power: supply: isp1704_charger: cancel work on remove The USB notifier and initial VBUS detection can schedule isp->work. The remove path unregisters the notifier and power supply, but does not wait for queued or running work before tearing down the power supply state. Cancel the work after unregistering the notifier. Do this before unregistering the power supply. This issue was found by a static analysis tool. Signed-off-by: Hongyan Xu Link: https://patch.msgid.link/20260728123423.781-5-getshell@seu.edu.cn Fixes: ec46475f3e31 ("power_supply: Add isp1704 charger detection driver") Signed-off-by: Sebastian Reichel --- drivers/power/supply/isp1704_charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c index 237912a92272..e329321d06db 100644 --- a/drivers/power/supply/isp1704_charger.c +++ b/drivers/power/supply/isp1704_charger.c @@ -482,6 +482,7 @@ static void isp1704_charger_remove(struct platform_device *pdev) struct isp1704_charger *isp = platform_get_drvdata(pdev); usb_unregister_notifier(isp->phy, &isp->nb); + cancel_work_sync(&isp->work); power_supply_unregister(isp->psy); isp1704_charger_set_power(isp, 0); } From dfc859bb8d332c525872f1a44028137724fa1998 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Wed, 29 Jul 2026 18:46:59 +0800 Subject: [PATCH 95/96] power: supply: sc2731_charger: cancel work on remove The USB notifier and initial charger detection can schedule info->work. The remove path unregisters the notifier, but does not cancel queued or running work before the devm-allocated driver data is released. Set the platform drvdata used by remove, then cancel the work after unregistering the notifier. This issue was found by a static analysis tool. Fixes: 8ac1091ed18b ("power: supply: sc2731_charger: Add one work to charge/discharge") Reviewed-by: Baolin Wang Signed-off-by: Hongyan Xu Link: https://patch.msgid.link/5d48b827687168cb1b1bfe85f17945566b42829d.1785321763.git.getshell@seu.edu.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/sc2731_charger.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c index 58b86fd78771..2b25e44da797 100644 --- a/drivers/power/supply/sc2731_charger.c +++ b/drivers/power/supply/sc2731_charger.c @@ -466,6 +466,7 @@ static int sc2731_charger_probe(struct platform_device *pdev) mutex_init(&info->lock); info->dev = &pdev->dev; INIT_WORK(&info->work, sc2731_charger_work); + platform_set_drvdata(pdev, info); info->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!info->regmap) { @@ -516,6 +517,7 @@ static void sc2731_charger_remove(struct platform_device *pdev) struct sc2731_charger_info *info = platform_get_drvdata(pdev); usb_unregister_notifier(info->usb_phy, &info->usb_notify); + cancel_work_sync(&info->work); } static const struct of_device_id sc2731_charger_of_match[] = { From c57cb36f76eb7ced45f57af1a890d8f3a6d76342 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 28 Jul 2026 03:01:23 +0000 Subject: [PATCH 96/96] power: supply: charger-manager: register regulators before exposing sysfs charger_manager_remove() and the err_reg_extcon probe error path free each charger regulator with regulator_put() before tearing down the power_supply sysfs entries (power_supply_unregister()). charger_manager_remove() also calls try_charger_enable(cm, false) after the regulator_put() loop. A concurrent write to a charger's externally_control sysfs attribute that lands between regulator_put() and power_supply_unregister() can run charger_externally_control_store() and call try_charger_enable(), which, when charging is enabled, dereferences the already-freed consumer handle. When charging is enabled, try_charger_enable(cm, false) in .remove() also dereferences the freed handles directly. Both leave use-after-free windows. Symmetrically, probe registers the sysfs entries (power_supply_register) before acquiring the regulators (regulator_get, inside charger_manager_register_extcon), so userspace can reach externally_control before the regulators are available. Split charger_manager_register_extcon() on the sync/async boundary: charger_manager_get_regulators() (regulator_get only, no async producer) now runs before power_supply_register() so sysfs is not live before regulators are available, and charger_manager_register_extcon() keeps only the extcon notifier/work setup, still after power_supply_register() so a power_supply_register() failure cannot reach extcon setup. This keeps the sysfs setup/teardown ordering symmetric without introducing an asynchronous producer on the earlier probe-error path. Move power_supply_unregister() and try_charger_enable(cm, false) ahead of the regulator_put() loop on both teardown paths, and adjust err_reg_extcon (power_supply_unregister() then fall through err_regulator for regulator_put(); get_regulators self-rolls back on its own failure). This does not address the separate extcon-notifier-driven deref of the same handles, which needs its own synchronization design. Found by an in-house static analysis tool. Fixes: 3950c7865cd7 ("charger-manager: Add support sysfs entry for charger") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260728030123.230202-1-fanwu01@zju.edu.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/charger-manager.c | 54 +++++++++++++++++++------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 71b6e49a835f..fd4cd9cd2ce2 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1014,6 +1014,29 @@ static int charger_extcon_init(struct charger_manager *cm, return 0; } +static int charger_manager_get_regulators(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + struct charger_regulator *charger; + int i, ret; + + for (i = 0; i < desc->num_charger_regulators; i++) { + charger = &desc->charger_regulators[i]; + charger->consumer = regulator_get(cm->dev, + charger->regulator_name); + if (IS_ERR(charger->consumer)) { + dev_err(cm->dev, "Cannot find charger(%s)\n", + charger->regulator_name); + ret = PTR_ERR(charger->consumer); + while (i-- > 0) + regulator_put(desc->charger_regulators[i].consumer); + return ret; + } + charger->cm = cm; + } + return 0; +} + /** * charger_manager_register_extcon - Register extcon device to receive state * of charger cable. @@ -1036,15 +1059,6 @@ static int charger_manager_register_extcon(struct charger_manager *cm) for (i = 0; i < desc->num_charger_regulators; i++) { charger = &desc->charger_regulators[i]; - charger->consumer = regulator_get(cm->dev, - charger->regulator_name); - if (IS_ERR(charger->consumer)) { - dev_err(cm->dev, "Cannot find charger(%s)\n", - charger->regulator_name); - return PTR_ERR(charger->consumer); - } - charger->cm = cm; - for (j = 0; j < charger->num_cables; j++) { struct charger_cable *cable = &charger->cables[j]; @@ -1580,13 +1594,23 @@ static int charger_manager_probe(struct platform_device *pdev) } psy_cfg.attr_grp = desc->sysfs_groups; + /* + * Acquire charger regulators before exposing the sysfs entries, so + * userspace cannot reach externally_control before the regulators + * (and charger->cm) are available. Mirrors the order in remove(). + */ + ret = charger_manager_get_regulators(cm); + if (ret < 0) + return ret; + cm->charger_psy = power_supply_register(&pdev->dev, &cm->charger_psy_desc, &psy_cfg); if (IS_ERR(cm->charger_psy)) { dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", cm->charger_psy_desc.name); - return PTR_ERR(cm->charger_psy); + ret = PTR_ERR(cm->charger_psy); + goto err_regulator; } /* Register extcon device for charger cable */ @@ -1620,11 +1644,11 @@ static int charger_manager_probe(struct platform_device *pdev) return 0; err_reg_extcon: + power_supply_unregister(cm->charger_psy); +err_regulator: for (i = 0; i < desc->num_charger_regulators; i++) regulator_put(desc->charger_regulators[i].consumer); - power_supply_unregister(cm->charger_psy); - return ret; } @@ -1642,12 +1666,12 @@ static void charger_manager_remove(struct platform_device *pdev) cancel_work_sync(&setup_polling); cancel_delayed_work_sync(&cm_monitor_work); - for (i = 0 ; i < desc->num_charger_regulators ; i++) - regulator_put(desc->charger_regulators[i].consumer); + try_charger_enable(cm, false); power_supply_unregister(cm->charger_psy); - try_charger_enable(cm, false); + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); } static const struct platform_device_id charger_manager_id[] = {