* [PATCH 0/2] power: supply: qcom_battmgr: Add thermal mitigation support
@ 2026-06-09 9:16 DhruvinRajpura
2026-06-09 9:16 ` [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation DhruvinRajpura
2026-06-09 9:16 ` [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura
0 siblings, 2 replies; 6+ messages in thread
From: DhruvinRajpura @ 2026-06-09 9:16 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Sebastian Reichel
Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm,
dhruvin.rajpura, kamal.wadhwa, jishnu.prakash, Dhruvin Rajpura,
Subbaraman Narayanamurthy, Umang Chheda
Commit c85c191694cb ("power: supply: remove faulty cooling logic")
removed automatic cooling device registration from the power supply
framework. As a result, drivers requiring thermal mitigation must now
explicitly register their own cooling devices with correct semantics.
This series adds thermal mitigation support to the Qualcomm PMIC GLink
battery manager driver by registering a thermal cooling device that
controls the battery fast charge current (FCC) in response to thermal
events. The cooling states map to discrete current limits defined in
the qcom,thermal-mitigation DT property, with state 0 representing the
hardware maximum current queried from firmware and each subsequent state
reducing the current to the next DT-defined value.
The series is structured as follows:
- Patch 1 documents the new qcom,thermal-mitigation DT property in
the qcom,pmic-glink.yaml binding file.
- Patch 2 implements the thermal cooling device registration in the
qcom_battmgr driver, including FCC control via firmware, PDR
restart resilience, and power supply property exposure.
The implementation has been validated on a target where setting the
cooling state via sysfs correctly reduces the observed battery charging
current as expected.
Signed-off-by: Dhruvin Rajpura <drajpura@qti.qualcomm.com>
---
Dhruvin Rajpura (2):
bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation
power: supply: qcom_battmgr: Add thermal mitigation support
.../bindings/soc/qcom/qcom,pmic-glink.yaml | 10 +
drivers/power/supply/qcom_battmgr.c | 256 +++++++++++++++++++++
2 files changed, 266 insertions(+)
---
base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
change-id: 20260609-cooling_device_reg-8e95c8eaab80
Best regards,
--
DhruvinRajpura <drajpura@qti.qualcomm.com>
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation 2026-06-09 9:16 [PATCH 0/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura @ 2026-06-09 9:16 ` DhruvinRajpura 2026-06-09 9:26 ` sashiko-bot 2026-06-09 9:16 ` [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura 1 sibling, 1 reply; 6+ messages in thread From: DhruvinRajpura @ 2026-06-09 9:16 UTC (permalink / raw) To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, dhruvin.rajpura, kamal.wadhwa, jishnu.prakash, Dhruvin Rajpura From: Dhruvin Rajpura <drajpura@qti.qualcomm.com> The qcom,thermal-mitigation property allows platforms to define discrete fast charge current limits used by the thermal framework to throttle battery charging in response to thermal events. Document the qcom,thermal-mitigation property to describe the array of fast charge current limits in microamps that map to thermal cooling states. Signed-off-by: Dhruvin Rajpura <drajpura@qti.qualcomm.com> --- .../devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml index ff01d2f3ee5b..fcb69ebaa7cb 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml @@ -58,6 +58,16 @@ properties: The array should contain a gpio entry for each PMIC Glink connector, in reg order. It is defined that GPIO active level means "CC2" or Reversed/Flipped orientation. + qcom,thermal-mitigation: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Array of fast charge current limit values for different system thermal + mitigation levels. This should be a flat array that denotes the maximum + charging current (in uA) for each thermal level. Elements should be listed + in monotonically decreasing (non-increasing) order. + minItems: 1 + maxItems: 10 + nvmem-cells: minItems: 3 maxItems: 3 -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation 2026-06-09 9:16 ` [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation DhruvinRajpura @ 2026-06-09 9:26 ` sashiko-bot 0 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-06-09 9:26 UTC (permalink / raw) To: DhruvinRajpura; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Missing `#cooling-cells` property in the `qcom,pmic-glink.yaml` schema breaks devicetree validation. -- commit 0b39bda76eaec494654108cde4e92f788edf6f16 Author: Dhruvin Rajpura <drajpura@qti.qualcomm.com> bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation This commit documents the qcom,thermal-mitigation property for the PMIC GLINK schema. It defines an array of fast charge current limits used by the thermal framework for battery throttling. > diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > index ff01d2f3ee5be..fcb69ebaa7cb5 100644 > --- a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml > @@ -58,6 +58,16 @@ properties: > The array should contain a gpio entry for each PMIC Glink connector, in reg order. > It is defined that GPIO active level means "CC2" or Reversed/Flipped orientation. > > + qcom,thermal-mitigation: > + $ref: /schemas/types.yaml#/definitions/uint32-array > + description: > + Array of fast charge current limit values for different system thermal [Severity: Medium] Does this introduce a regression by missing the #cooling-cells property? By adding qcom,thermal-mitigation, the device acts as a thermal framework cooling provider. For a platform devicetree to actually reference this cooling provider in a thermal zone, the provider node must declare the #cooling-cells property. Since the schema limits which properties can be added, won't platforms fail validation when they try to declare #cooling-cells to wire up this new capability? > + mitigation levels. This should be a flat array that denotes the maximum > + charging current (in uA) for each thermal level. Elements should be listed > + in monotonically decreasing (non-increasing) order. > + minItems: 1 > + maxItems: 10 > + -- Sashiko AI review · https://sashiko.dev/#/patchset/20260609-cooling_device_reg-v1-0-e15bddcb0086@qti.qualcomm.com?part=1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support 2026-06-09 9:16 [PATCH 0/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura 2026-06-09 9:16 ` [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation DhruvinRajpura @ 2026-06-09 9:16 ` DhruvinRajpura 2026-06-09 9:30 ` sashiko-bot 2026-06-09 9:42 ` Konrad Dybcio 1 sibling, 2 replies; 6+ messages in thread From: DhruvinRajpura @ 2026-06-09 9:16 UTC (permalink / raw) To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, dhruvin.rajpura, kamal.wadhwa, jishnu.prakash, Dhruvin Rajpura, Subbaraman Narayanamurthy, Umang Chheda From: Dhruvin Rajpura <drajpura@qti.qualcomm.com> Earlier commit c85c191694cb ("power: supply: remove faulty cooling logic") removed the automatic cooling device registration from the power supply framework due to inverted cooling logic and missing CHARGE_CONTROL_LIMIT_MAX validation across drivers. As a result, drivers that need thermal mitigation support must now explicitly register their own cooling devices with correct semantics. The battery charger firmware exposes a fast charge current (FCC) limit property that can be used to throttle charging current in response to thermal events. Without explicit cooling device registration, the thermal framework has no way to reduce charging current when the device is under thermal stress, which can lead to excessive heat buildup during charging. Register a thermal cooling device that maps discrete current levels defined in the qcom,thermal-mitigation DT property to cooling states, where level 0 represents the hardware maximum current queried from firmware and each subsequent level reduces current to the next DT-defined value. Track the last applied FCC value so thermal limits are preserved across firmware protection domain restart (PDR) events. Expose CHARGE_CONTROL_LIMIT and CHARGE_CONTROL_LIMIT_MAX as power supply properties to allow userspace to observe and control the current throttle level. Co-developed-by: Subbaraman Narayanamurthy <quic_subbaram@quicinc.com> Signed-off-by: Subbaraman Narayanamurthy <quic_subbaram@quicinc.com> Co-developed-by: Umang Chheda <quic_uchheda@quicinc.com> Signed-off-by: Umang Chheda <quic_uchheda@quicinc.com> Signed-off-by: Dhruvin Rajpura <drajpura@qti.qualcomm.com> --- drivers/power/supply/qcom_battmgr.c | 256 ++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 490137a23d00..cbc129e9aa17 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -14,6 +14,7 @@ #include <linux/soc/qcom/pdr.h> #include <linux/soc/qcom/pmic_glink.h> #include <linux/math.h> +#include <linux/thermal.h> #include <linux/units.h> #define BATTMGR_CHEMISTRY_LEN 4 @@ -283,6 +284,7 @@ struct qcom_battmgr_status { unsigned int discharge_time; unsigned int charge_time; + unsigned int max_charge_ctl_limit; }; struct qcom_battmgr_ac { @@ -321,9 +323,18 @@ struct qcom_battmgr { enum qcom_battmgr_unit unit; int error; + + int curr_thermal_level; + int num_thermal_levels; + + u32 *thermal_levels; + u32 thermal_fcc_ua; + u32 last_fcc_ua; + struct completion ack; bool service_up; + bool thermal_dev_registered; struct qcom_battmgr_info info; struct qcom_battmgr_status status; @@ -449,6 +460,8 @@ static const u8 sm8350_bat_prop_map[] = { [POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW, [POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD] = BATT_CHG_CTRL_START_THR, [POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD] = BATT_CHG_CTRL_END_THR, + [POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT] = BATT_CHG_CTRL_LIM, + [POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX] = BATT_CHG_CTRL_LIM_MAX, }; static int qcom_battmgr_bat_sm8350_update(struct qcom_battmgr *battmgr, @@ -612,6 +625,12 @@ static int qcom_battmgr_bat_get_property(struct power_supply *psy, return -ENODATA; val->intval = battmgr->status.percent; break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = battmgr->curr_thermal_level; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = battmgr->num_thermal_levels; + break; case POWER_SUPPLY_PROP_TEMP: val->intval = battmgr->status.temperature; break; @@ -770,6 +789,55 @@ static int qcom_battmgr_bat_is_writeable(struct power_supply *psy, return 0; } +static int __battery_psy_set_charge_current(struct qcom_battmgr *battmgr, + u32 fcc_ua) +{ + int ret; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_SET, + BATT_CHG_CTRL_LIM, fcc_ua); + mutex_unlock(&battmgr->lock); + if (ret < 0) { + dev_err(battmgr->dev, "Failed to set FCC %u, ret=%d\n", fcc_ua, ret); + } else { + dev_dbg(battmgr->dev, "Set FCC to %u uA\n", fcc_ua); + battmgr->last_fcc_ua = fcc_ua; + } + + return ret; +} + +static int battery_psy_set_charge_current(struct qcom_battmgr *battmgr, + int val) +{ + u32 fcc_ua, prev_fcc_ua; + int ret; + + if (!battmgr->num_thermal_levels) + return 0; + + if (battmgr->num_thermal_levels < 0) { + dev_err(battmgr->dev, "Incorrect num_thermal_levels\n"); + return -EINVAL; + } + + if (val < 0 || val > battmgr->num_thermal_levels) + return -EINVAL; + + fcc_ua = battmgr->thermal_levels[val]; + prev_fcc_ua = battmgr->thermal_fcc_ua; + battmgr->thermal_fcc_ua = fcc_ua; + + ret = __battery_psy_set_charge_current(battmgr, fcc_ua); + if (!ret) + battmgr->curr_thermal_level = val; + else + battmgr->thermal_fcc_ua = prev_fcc_ua; + + return ret; +} + static int qcom_battmgr_bat_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *pval) @@ -784,6 +852,8 @@ static int qcom_battmgr_bat_set_property(struct power_supply *psy, return qcom_battmgr_set_charge_start_threshold(battmgr, pval->intval); case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: return qcom_battmgr_set_charge_end_threshold(battmgr, pval->intval); + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return battery_psy_set_charge_current(battmgr, pval->intval); default: return -EINVAL; } @@ -791,6 +861,19 @@ static int qcom_battmgr_bat_set_property(struct power_supply *psy, return 0; } +static int qcom_battmgr_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return 1; + default: + break; + } + + return 0; +} + static const enum power_supply_property sc8280xp_bat_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, @@ -872,6 +955,8 @@ static const enum power_supply_property sm8350_bat_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_CHARGE_COUNTER, @@ -892,6 +977,8 @@ static const struct power_supply_desc sm8350_bat_psy_desc = { .properties = sm8350_bat_props, .num_properties = ARRAY_SIZE(sm8350_bat_props), .get_property = qcom_battmgr_bat_get_property, + .set_property = qcom_battmgr_bat_set_property, + .property_is_writeable = qcom_battmgr_prop_is_writeable, }; static const enum power_supply_property sm8550_bat_props[] = { @@ -1434,6 +1521,11 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, case BATT_CURR_NOW: battmgr->status.current_now = le32_to_cpu(resp->intval.value); break; + case BATT_CHG_CTRL_LIM: + break; + case BATT_CHG_CTRL_LIM_MAX: + battmgr->status.max_charge_ctl_limit = le32_to_cpu(resp->intval.value); + break; case BATT_TEMP: val = le32_to_cpu(resp->intval.value); battmgr->status.temperature = DIV_ROUND_CLOSEST(val, 10); @@ -1479,6 +1571,25 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, break; } break; + + case BATTMGR_BAT_PROPERTY_SET: + property = le32_to_cpu(resp->intval.property); + if (payload_len != sizeof(resp->intval)) { + dev_warn(battmgr->dev, + "invalid payload length for %#x request: %zd\n", + property, payload_len); + battmgr->error = -ENODATA; + return; + } + + battmgr->error = le32_to_cpu(resp->intval.result); + if (battmgr->error) { + dev_err(battmgr->dev, "Error in response for opcode :%#x prop_id :%u, ret :%d\n", + opcode, property, battmgr->error); + goto out_complete; + } + break; + case BATTMGR_USB_PROPERTY_GET: property = le32_to_cpu(resp->intval.property); if (payload_len != sizeof(resp->intval)) { @@ -1583,6 +1694,133 @@ static void qcom_battmgr_callback(const void *data, size_t len, void *priv) qcom_battmgr_sm8350_callback(battmgr, data, len); } +static int +qcom_battmgr_chg_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long *state) +{ + struct qcom_battmgr *battmgr = tcd->devdata; + + *state = battmgr->num_thermal_levels; + + return 0; +} + +static int +qcom_battmgr_chg_get_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long *state) +{ + struct qcom_battmgr *battmgr = tcd->devdata; + + *state = battmgr->curr_thermal_level; + + return 0; +} + +static int +qcom_battmgr_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, + unsigned long state) +{ + struct qcom_battmgr *battmgr = tcd->devdata; + + return battery_psy_set_charge_current(battmgr, (int)state); +} + +static const struct thermal_cooling_device_ops battery_tcd_ops = { + .get_max_state = qcom_battmgr_chg_get_max_charge_cntl_limit, + .get_cur_state = qcom_battmgr_chg_get_cur_charge_cntl_limit, + .set_cur_state = qcom_battmgr_set_cur_charge_cntl_limit, +}; + +static int qcom_battmgr_register_thermal_cooling_device(struct qcom_battmgr *battmgr) +{ + struct thermal_cooling_device *tcd; + u32 prev; + int ret, i; + + /* Skip registering thermal cooling device if thermal levels are not defined */ + if (!battmgr->num_thermal_levels) + return 0; + + if (battmgr->thermal_dev_registered) + return 0; + + mutex_lock(&battmgr->lock); + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_GET, + BATT_CHG_CTRL_LIM_MAX, 0); + mutex_unlock(&battmgr->lock); + + if (ret < 0) { + dev_err(battmgr->dev, "Unable to read CHG_CTRL_LIMIT_MAX ret :%d\n", ret); + return ret; + } + + prev = battmgr->status.max_charge_ctl_limit; + + for (i = 1; i <= battmgr->num_thermal_levels; i++) { + if (battmgr->thermal_levels[i] > prev) { + dev_err(battmgr->dev, "Thermal values not defined as expected\n"); + return -EINVAL; + } + prev = battmgr->thermal_levels[i]; + } + + battmgr->thermal_levels[0] = battmgr->status.max_charge_ctl_limit; + battmgr->thermal_fcc_ua = battmgr->status.max_charge_ctl_limit; + + /* Register Thermal Cooling Device */ + tcd = devm_thermal_of_cooling_device_register(battmgr->dev, + battmgr->dev->of_node, + (char *)battmgr->bat_psy->desc->name, + battmgr, &battery_tcd_ops); + if (IS_ERR_OR_NULL(tcd)) { + ret = PTR_ERR_OR_ZERO(tcd); + dev_err(battmgr->dev, "Failed to register thermal cooling device ret=%d\n", ret); + return ret; + } + + battmgr->thermal_dev_registered = true; + + return ret; +} + +static int qcom_battmgr_parse_dt(struct qcom_battmgr *battmgr) +{ + struct device_node *node = battmgr->dev->of_node; + int len, ret; + + ret = of_property_count_elems_of_size(node, "qcom,thermal-mitigation", sizeof(u32)); + if (ret == -EINVAL) + return 0; /* property not defined, thermal mitigation disabled */ + if (ret <= 0) + return ret; /* real error, propagate */ + + len = ret; + + battmgr->thermal_levels = devm_kcalloc(battmgr->dev, len + 1, + sizeof(*battmgr->thermal_levels), + GFP_KERNEL); + if (!battmgr->thermal_levels) + return -ENOMEM; + + /* + * Element 0 is for normal charging current. Elements from index 1 + * onwards is for thermal mitigation charging currents. + */ + + ret = of_property_read_u32_array(node, "qcom,thermal-mitigation", + &battmgr->thermal_levels[1], len); + if (ret < 0) { + dev_err(battmgr->dev, "Error in reading qcom,thermal-mitigation, ret=%d\n", + ret); + + return ret; + } + + battmgr->num_thermal_levels = len; + + return ret; +} + static void qcom_battmgr_enable_worker(struct work_struct *work) { struct qcom_battmgr *battmgr = container_of(work, struct qcom_battmgr, enable_work); @@ -1596,6 +1834,18 @@ static void qcom_battmgr_enable_worker(struct work_struct *work) ret = qcom_battmgr_request(battmgr, &req, sizeof(req)); if (ret) dev_err(battmgr->dev, "failed to request power notifications\n"); + + if (!battmgr->thermal_dev_registered) { + ret = qcom_battmgr_register_thermal_cooling_device(battmgr); + if (ret < 0) + dev_err(battmgr->dev, "failed to register thermal cooling device ret:%d\n", + ret); + } else if (battmgr->last_fcc_ua) { + ret = __battery_psy_set_charge_current(battmgr, battmgr->last_fcc_ua); + if (ret < 0) + dev_err(battmgr->dev, "Failed to set FCC (%u uA), ret:%d\n", + battmgr->last_fcc_ua, ret); + } } static void qcom_battmgr_pdr_notify(void *priv, int state) @@ -1690,6 +1940,12 @@ static int qcom_battmgr_probe(struct auxiliary_device *adev, return dev_err_probe(dev, PTR_ERR(battmgr->wls_psy), "failed to register wireless charing power supply\n"); } else { + ret = qcom_battmgr_parse_dt(battmgr); + if (ret < 0) { + dev_err(dev, "Failed to parse thermal mitigation levels ret:%d\n", ret); + return ret; + } + if (battmgr->variant == QCOM_BATTMGR_SM8550) psy_desc = &sm8550_bat_psy_desc; else -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support 2026-06-09 9:16 ` [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura @ 2026-06-09 9:30 ` sashiko-bot 2026-06-09 9:42 ` Konrad Dybcio 1 sibling, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-06-09 9:30 UTC (permalink / raw) To: DhruvinRajpura; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: New issues: - [High] Software state properties are updated outside of `battmgr->lock`, creating data races and state desynchronization. - [High] Thermal mitigation state is lost on firmware restart (PDR) if the charging limit was throttled to 0 uA. - [High] Calling `devm_thermal_of_cooling_device_register()` from an asynchronous workqueue causes a resource leak and use-after-free race with device unbind. - [Medium] The SM8550 variant lacks the `CHARGE_CONTROL_LIMIT` and `CHARGE_CONTROL_LIMIT_MAX` sysfs properties. - [Medium] Reading the charge control limits triggers synchronous, blocking firmware requests whose results are ignored. - [Medium] Userspace sysfs race allows throttling to 0 uA due to uninitialized `thermal_levels[0]`. Pre-existing issues: - [High] `qcom_battmgr_request` is called without acquiring `battmgr->lock` in `qcom_battmgr_enable_worker`. -- commit c50b550e81a56aef117f049101952557cf7ba5cd Author: Dhruvin Rajpura <drajpura@qti.qualcomm.com> power: supply: qcom_battmgr: Add thermal mitigation support This patch explicitly registers a thermal cooling device for the battery manager to throttle charging current during thermal stress. It maps discrete current levels from the qcom,thermal-mitigation DT property to cooling states. It also exposes CHARGE_CONTROL_LIMIT properties to sysfs to allow userspace observation and control. > diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c > --- a/drivers/power/supply/qcom_battmgr.c > +++ b/drivers/power/supply/qcom_battmgr.c > @@ -449,6 +460,8 @@ static const u8 sm8350_bat_prop_map[] = { > [POWER_SUPPLY_PROP_POWER_NOW] = BATT_POWER_NOW, > [POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD] = BATT_CHG_CTRL_START_THR, > [POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD] = BATT_CHG_CTRL_END_THR, > + [POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT] = BATT_CHG_CTRL_LIM, > + [POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX] = BATT_CHG_CTRL_LIM_MAX, > }; [Severity: Medium] Does mapping BATT_CHG_CTRL_LIM trigger a blocking BATTMGR_BAT_PROPERTY_GET request over GLINK when userspace reads POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT? Looking at qcom_battmgr_bat_get_property(), the result of this firmware update is discarded, and the locally cached curr_thermal_level is returned instead. Could this be avoided to prevent a useless IPC call? [ ... ] > @@ -770,6 +789,55 @@ static int qcom_battmgr_bat_is_writeable(struct power_supply *psy, > return 0; > } > > +static int __battery_psy_set_charge_current(struct qcom_battmgr *battmgr, > + u32 fcc_ua) > +{ > + int ret; > + > + mutex_lock(&battmgr->lock); > + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_SET, > + BATT_CHG_CTRL_LIM, fcc_ua); > + mutex_unlock(&battmgr->lock); > + if (ret < 0) { > + dev_err(battmgr->dev, "Failed to set FCC %u, ret=%d\n", fcc_ua, ret); > + } else { > + dev_dbg(battmgr->dev, "Set FCC to %u uA\n", fcc_ua); > + battmgr->last_fcc_ua = fcc_ua; [Severity: High] Can concurrent updates desynchronize the software tracking variables from the hardware state? The GLINK request is protected by battmgr->lock, but last_fcc_ua is assigned here after the lock is released. > + } > + > + return ret; > +} > + > +static int battery_psy_set_charge_current(struct qcom_battmgr *battmgr, > + int val) > +{ > + u32 fcc_ua, prev_fcc_ua; > + int ret; > + > + if (!battmgr->num_thermal_levels) > + return 0; > + > + if (battmgr->num_thermal_levels < 0) { > + dev_err(battmgr->dev, "Incorrect num_thermal_levels\n"); > + return -EINVAL; > + } > + > + if (val < 0 || val > battmgr->num_thermal_levels) > + return -EINVAL; > + > + fcc_ua = battmgr->thermal_levels[val]; [Severity: Medium] If sysfs writes to CHARGE_CONTROL_LIMIT occur after probe() but before qcom_battmgr_enable_worker() has initialized thermal_levels[0], could this read a zero-initialized value and improperly throttle charging to 0 uA? > + prev_fcc_ua = battmgr->thermal_fcc_ua; > + battmgr->thermal_fcc_ua = fcc_ua; > + > + ret = __battery_psy_set_charge_current(battmgr, fcc_ua); > + if (!ret) > + battmgr->curr_thermal_level = val; > + else > + battmgr->thermal_fcc_ua = prev_fcc_ua; [Severity: High] Additionally, thermal_fcc_ua and curr_thermal_level are updated here completely outside of battmgr->lock. If thermal framework and sysfs operations concurrently set the charge current, could the software state interleave with hardware updates? > + > + return ret; > +} [ ... ] > @@ -892,6 +977,8 @@ static const struct power_supply_desc sm8350_bat_psy_desc = { > .properties = sm8350_bat_props, > .num_properties = ARRAY_SIZE(sm8350_bat_props), > .get_property = qcom_battmgr_bat_get_property, > + .set_property = qcom_battmgr_bat_set_property, > + .property_is_writeable = qcom_battmgr_prop_is_writeable, > }; > > static const enum power_supply_property sm8550_bat_props[] = { [Severity: Medium] The commit message states that CHARGE_CONTROL_LIMIT and CHARGE_CONTROL_LIMIT_MAX are exposed to allow userspace control. Should these properties also be added to sm8550_bat_props to expose them for the SM8550 variant? [ ... ] > +static int qcom_battmgr_register_thermal_cooling_device(struct qcom_battmgr *battmgr) > +{ > + struct thermal_cooling_device *tcd; > + u32 prev; > + int ret, i; [ ... ] > + battmgr->thermal_levels[0] = battmgr->status.max_charge_ctl_limit; > + battmgr->thermal_fcc_ua = battmgr->status.max_charge_ctl_limit; > + > + /* Register Thermal Cooling Device */ > + tcd = devm_thermal_of_cooling_device_register(battmgr->dev, > + battmgr->dev->of_node, > + (char *)battmgr->bat_psy->desc->name, > + battmgr, &battery_tcd_ops); [Severity: High] Is it safe to allocate devm_ resources from an asynchronous workqueue? If the driver unbinds, devres_release_all() will free the battmgr structure. If qcom_battmgr_enable_worker() runs concurrently or just after, could it register a cooling device to a dead device's devres list, leading to a resource leak and eventual use-after-free when the thermal core polls it? [ ... ] > @@ -1596,6 +1834,18 @@ static void qcom_battmgr_enable_worker(struct work_struct *work) > struct qcom_battmgr *battmgr = container_of(work, struct qcom_battmgr, enable_work); > struct qcom_battmgr_enable_request req = { > .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_BATTMGR), > .hdr.type = cpu_to_le32(PMIC_GLINK_NOTIFY), > .hdr.opcode = cpu_to_le32(BATTMGR_REQUEST_NOTIFICATION), > }; > int ret; > > ret = qcom_battmgr_request(battmgr, &req, sizeof(req)); [Severity: High] This isn't a bug introduced by this patch, but is it intentional that qcom_battmgr_request() is called here without holding battmgr->lock? If sysfs operations take the lock simultaneously, could this concurrent execution mutate the GLINK communication state and corrupt reinit_completion(&battmgr->ack)? > if (ret) > dev_err(battmgr->dev, "failed to request power notifications\n"); > + > + if (!battmgr->thermal_dev_registered) { > + ret = qcom_battmgr_register_thermal_cooling_device(battmgr); > + if (ret < 0) > + dev_err(battmgr->dev, "failed to register thermal cooling device ret:%d\n", > + ret); > + } else if (battmgr->last_fcc_ua) { [Severity: High] Does checking if (battmgr->last_fcc_ua) prevent restoration of the mitigation limit after a firmware restart? If the device was legitimately throttled to 0 microamps due to extreme heat, this check evaluates to false and skips the restoration, which could cause the firmware to resume charging at the maximum limit. > + ret = __battery_psy_set_charge_current(battmgr, battmgr->last_fcc_ua); > + if (ret < 0) > + dev_err(battmgr->dev, "Failed to set FCC (%u uA), ret:%d\n", > + battmgr->last_fcc_ua, ret); > + } > } -- Sashiko AI review · https://sashiko.dev/#/patchset/20260609-cooling_device_reg-v1-0-e15bddcb0086@qti.qualcomm.com?part=2 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support 2026-06-09 9:16 ` [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura 2026-06-09 9:30 ` sashiko-bot @ 2026-06-09 9:42 ` Konrad Dybcio 1 sibling, 0 replies; 6+ messages in thread From: Konrad Dybcio @ 2026-06-09 9:42 UTC (permalink / raw) To: DhruvinRajpura, Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sebastian Reichel Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm, kamal.wadhwa, jishnu.prakash, Dhruvin Rajpura, Subbaraman Narayanamurthy, Umang Chheda On 6/9/26 11:16 AM, DhruvinRajpura wrote: > From: Dhruvin Rajpura <drajpura@qti.qualcomm.com> > > Earlier commit c85c191694cb ("power: supply: remove faulty cooling > logic") removed the automatic cooling device registration from > the power supply framework due to inverted cooling logic and > missing CHARGE_CONTROL_LIMIT_MAX validation across drivers. As > a result, drivers that need thermal mitigation support must now > explicitly register their own cooling devices with correct > semantics. > > The battery charger firmware exposes a fast charge current (FCC) > limit property that can be used to throttle charging current in > response to thermal events. Without explicit cooling device > registration, the thermal framework has no way to reduce charging > current when the device is under thermal stress, which can lead > to excessive heat buildup during charging. Register a thermal > cooling device that maps discrete current levels defined in the > qcom,thermal-mitigation DT property to cooling states, where > level 0 represents the hardware maximum current queried from > firmware and each subsequent level reduces current to the next > DT-defined value. Track the last applied FCC value so thermal > limits are preserved across firmware protection domain restart > (PDR) events. Expose CHARGE_CONTROL_LIMIT and > CHARGE_CONTROL_LIMIT_MAX as power supply properties to allow > userspace to observe and control the current throttle level. > > Co-developed-by: Subbaraman Narayanamurthy <quic_subbaram@quicinc.com> > Signed-off-by: Subbaraman Narayanamurthy <quic_subbaram@quicinc.com> > Co-developed-by: Umang Chheda <quic_uchheda@quicinc.com> > Signed-off-by: Umang Chheda <quic_uchheda@quicinc.com> > Signed-off-by: Dhruvin Rajpura <drajpura@qti.qualcomm.com> > --- [...] > +static int __battery_psy_set_charge_current(struct qcom_battmgr *battmgr, > + u32 fcc_ua) > +{ > + int ret; > + > + mutex_lock(&battmgr->lock); > + ret = qcom_battmgr_request_property(battmgr, BATTMGR_BAT_PROPERTY_SET, > + BATT_CHG_CTRL_LIM, fcc_ua); > + mutex_unlock(&battmgr->lock); I think the entirety of this function, particularly the assignment, could use this lock [...] > + if (ret < 0) { > + dev_err(battmgr->dev, "Unable to read CHG_CTRL_LIMIT_MAX ret :%d\n", ret); style: "ret: %d" [...] > +static int qcom_battmgr_parse_dt(struct qcom_battmgr *battmgr) > +{ > + struct device_node *node = battmgr->dev->of_node; > + int len, ret; > + > + ret = of_property_count_elems_of_size(node, "qcom,thermal-mitigation", sizeof(u32)); > + if (ret == -EINVAL) > + return 0; /* property not defined, thermal mitigation disabled */ > + if (ret <= 0) > + return ret; /* real error, propagate */ Does battmgr really expect the OS to house this information? Can we not retrieve it from the running firmware? [...] > + /* > + * Element 0 is for normal charging current. Elements from index 1 > + * onwards is for thermal mitigation charging currents. > + */ Documentation/ABI/testing/sysfs-class-power: What: /sys/class/power_supply/<supply_name>/charge_control_limit Date: Oct 2012 Contact: linux-pm@vger.kernel.org Description: Maximum allowable charging current. Used for charge rate throttling for thermal cooling or improving battery health. Access: Read, Write Valid values: Represented in microamps My reading of your patch is that you expect a level to be passed, not a uA value Konrad ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-09 9:42 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-09 9:16 [PATCH 0/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura 2026-06-09 9:16 ` [PATCH 1/2] bindings: power: supply: qcom,pmic-glink: Document thermal-mitigation DhruvinRajpura 2026-06-09 9:26 ` sashiko-bot 2026-06-09 9:16 ` [PATCH 2/2] power: supply: qcom_battmgr: Add thermal mitigation support DhruvinRajpura 2026-06-09 9:30 ` sashiko-bot 2026-06-09 9:42 ` Konrad Dybcio
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox