Skip to contentSkip to conversation

power: supply: qcom_battmgr: report charge levels on SM8350-class firmware

PATCH In review Jan-Michael Brummer 2 participants 7 messages Aug 29 – Oct 7

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover power: supply: qcom_battmgr: report charge levels on SM8350-class firmware

On SM8350-class firmware (everything that is not SC8280XP/X1E80100) the battery reports no usable charge level to userspace: charge_full and charge_full_design fail with -ENODATA and there is no charge_now at all. As a consequence UPower cannot derive an absolute energy level and never shows a runtime estimate - the UI is limited to "charging"/"discharging".

Patch 1 is the actual bug: battmgr->unit is only populated from a BATTMGR_BAT_INFO response, which this firmware class never sends, so the mAh guard on the CHARGE_* properties always rejects them. The values are fetched from the firmware and stored on every access, they are just thrown away again.

Patch 2 adds CHARGE_NOW. There is no dedicated firmware property for it, but BATT_CHG_COUNTER carries the remaining charge rather than a monotonic counter on this firmware. This is the part I am least sure about, since it does not match the charge_counter semantics described in Documentation/ABI/testing/sysfs-class-power. Two measurements across the SoC range agree with the reported capacity to within a percent, so at least on this platform the interpretation holds. Happy to drop this patch or to collect a full discharge curve if you prefer.

Both patches were tested on a Fairphone 5 (QCM6490) running postmarketOS. Before the series:

    energy:              0 Wh
    energy-full:         0 Wh
    energy-rate:         12.4713 W
    percentage:          59%

After, discharging and charging:

    energy:              14.4347 Wh       energy:              15.6003 Wh
    energy-full:         16.8807 Wh       energy-full:         18.1505 Wh
    energy-rate:         3.39313 W        energy-rate:         7.3492 W
    time to empty:       4.3 hours        time to full:        20.8 minutes

Two observations that are out of scope here but worth recording:

- power_now reports 72097076 while current_now * voltage_now gives ~11.9 W, and qcom-battmgr-usb/current_now reports 11078000 against an input_current_limit of 3000000. Both look like unscaled firmware values. UPower does not use either, so this series is unaffected.

- state_of_health reports 100 while charge_full/charge_full_design works out to 96.6%, so the firmware appears to derive the two independently.

The time-to-full estimate is optimistic compared to the firmware's own time_to_full_avg because UPower extrapolates linearly and does not model the CV taper. That is expected and not a regression.

Jan-Michael Brummer (2):
power: supply: qcom_battmgr: fix CHARGE_FULL* on SM8350-class firmware
power: supply: qcom_battmgr: expose CHARGE_NOW on SM8350-class
firmware

 drivers/power/supply/qcom_battmgr.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
#1 lore raw

PATCH 1/2 power: supply: qcom_battmgr: fix CHARGE_FULL* on SM8350-class firmware

battmgr->unit is only ever assigned in the BATTMGR_BAT_INFO handler of the SC8280XP callback. The SM8350-class firmware does not implement that request - every property is fetched individually via BATTMGR_BAT_PROPERTY_GET - so unit keeps its zero-initialised value, QCOM_BATTMGR_UNIT_mWh.

POWER_SUPPLY_PROP_CHARGE_FULL and POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN are guarded by 'if (unit != QCOM_BATTMGR_UNIT_mAh) return -ENODATA;', hence both properties always fail on these platforms, even though the values are requested from the firmware and stored in info.last_full_capacity and info.design_capacity on every access:

  $ cat /sys/class/power_supply/qcom-battmgr-bat/charge_full
  cat: read error: No data available

Without a full charge value userspace cannot derive an absolute energy level, so UPower reports a zero energy level and never computes a time-to-empty/time-to-full estimate:

    energy:              0 Wh
    energy-full:         0 Wh
    energy-rate:         12.4713 W
    percentage:          59%

The firmware of this class reports charge domain values, so set the unit statically when registering the SM8350/SM8550 power supplies. With this patch the properties read back consistently on a Fairphone 5:

  charge_full         4116000
  charge_full_design  4260000
  capacity            85
Fixes 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply")
---
 drivers/power/supply/qcom_battmgr.c | 8 ++++++++
 1 file changed, 8 insertions(+)
drivers/power/supply/qcom_battmgr.c +8 -0
index 490137a..98a70e1 100644
@@ -1690,6 +1690,14 @@ 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 {
+		/*
+		 * The SM8350-class firmware has no BATTMGR_BAT_INFO request, so
+		 * @unit is never populated from the firmware. It reports charge
+		 * domain values, so set it statically here - otherwise the
+		 * CHARGE_* properties are rejected with -ENODATA.
+		 */
+		battmgr->unit = QCOM_BATTMGR_UNIT_mAh;
+
 		if (battmgr->variant == QCOM_BATTMGR_SM8550)
 			psy_desc = &sm8550_bat_psy_desc;
 		else
#2 lore raw

Re: [PATCH 1/2] power: supply: qcom_battmgr: fix CHARGE_FULL* on SM8350-class firmware

Quoted 28 lines · Jan-Michael Brummer

battmgr->unit is only ever assigned in the BATTMGR_BAT_INFO handler of the SC8280XP callback. The SM8350-class firmware does not implement that request - every property is fetched individually via BATTMGR_BAT_PROPERTY_GET - so unit keeps its zero-initialised value, QCOM_BATTMGR_UNIT_mWh.

POWER_SUPPLY_PROP_CHARGE_FULL and POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN are guarded by 'if (unit != QCOM_BATTMGR_UNIT_mAh) return -ENODATA;', hence both properties always fail on these platforms, even though the values are requested from the firmware and stored in info.last_full_capacity and info.design_capacity on every access:

  $ cat /sys/class/power_supply/qcom-battmgr-bat/charge_full
  cat: read error: No data available

Without a full charge value userspace cannot derive an absolute energy level, so UPower reports a zero energy level and never computes a time-to-empty/time-to-full estimate:

    energy:              0 Wh
    energy-full:         0 Wh
    energy-rate:         12.4713 W
    percentage:          59%

The firmware of this class reports charge domain values, so set the unit statically when registering the SM8350/SM8550 power supplies. With this patch the properties read back consistently on a Fairphone 5:

Quoted 7 lines
  charge_full         4116000
  charge_full_design  4260000
  capacity            85
Fixes 29e8142b5623 ("power: supply: Introduce Qualcomm PMIC GLINK power supply")

---

This also implicitly unlocks reporting

POWER_SUPPLY_PROP_CHARGE_EMPTY
POWER_SUPPLY_PROP_CHARGE_NOW

which are not populated and will always report 0.

We should probably further limit that.

Konrad

#3 lore raw

PATCH 2/2 power: supply: qcom_battmgr: expose CHARGE_NOW on SM8350-class firmware

In reply to Jan-Michael Brummer

The SM8350-class firmware does not implement a dedicated 'charge now' property. It does however report BATT_CHG_COUNTER, and on this class of firmware that value is the remaining charge in uAh rather than a monotonic counter. Measured on a Fairphone 5 with charge_full at 4116000 uAh:

  BATT_CHG_COUNTER   reported capacity   counter / charge_full
  2434614            59%                 59.1%
  3525354            85%                 85.6%

Map POWER_SUPPLY_PROP_CHARGE_NOW onto the same firmware property and store the value in status.capacity, which the CHARGE_NOW/ENERGY_NOW case of qcom_battmgr_bat_get_property() already reads.

Together with the preceding patch this gives userspace both the full charge and the current charge. UPower now derives an energy level and runtime estimates in both directions:

    state:               discharging      state:               charging
    energy:              14.4347 Wh       energy:              15.6003 Wh
    energy-full:         16.8807 Wh       energy-full:         18.1505 Wh
    energy-rate:         3.39313 W        energy-rate:         7.3492 W
    time to empty:       4.3 hours        time to full:        20.8 minutes
---
 drivers/power/supply/qcom_battmgr.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
drivers/power/supply/qcom_battmgr.c +7 -1
index 98a70e1..f7c120f 100644
@@ -441,6 +441,7 @@ static const u8 sm8350_bat_prop_map[] = {
 	[POWER_SUPPLY_PROP_CYCLE_COUNT] = BATT_CYCLE_COUNT,
 	[POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN] =  BATT_CHG_FULL_DESIGN,
 	[POWER_SUPPLY_PROP_CHARGE_FULL] = BATT_CHG_FULL,
+	[POWER_SUPPLY_PROP_CHARGE_NOW] = BATT_CHG_COUNTER,
 	[POWER_SUPPLY_PROP_MODEL_NAME] = BATT_MODEL_NAME,
 	[POWER_SUPPLY_PROP_TIME_TO_FULL_AVG] = BATT_TTF_AVG,
 	[POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG] = BATT_TTE_AVG,
@@ -878,6 +879,7 @@ static const enum power_supply_property sm8350_bat_props[] = {
 	POWER_SUPPLY_PROP_CYCLE_COUNT,
 	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
 	POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
@@ -910,6 +912,7 @@ static const enum power_supply_property sm8550_bat_props[] = {
 	POWER_SUPPLY_PROP_CYCLE_COUNT,
 	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
 	POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
@@ -1442,7 +1445,10 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
 			battmgr->info.technology = le32_to_cpu(resp->intval.value);
 			break;
 		case BATT_CHG_COUNTER:
-			battmgr->info.charge_count = le32_to_cpu(resp->intval.value);
+			val = le32_to_cpu(resp->intval.value);
+			battmgr->info.charge_count = val;
+			/* the firmware reports the remaining charge here */
+			battmgr->status.capacity = val;
 			break;
 		case BATT_CYCLE_COUNT:
 			battmgr->info.cycle_count = le32_to_cpu(resp->intval.value);
#4 lore raw

Re: [PATCH 2/2] power: supply: qcom_battmgr: expose CHARGE_NOW on SM8350-class firmware

Quoted 18 lines · Jan-Michael Brummer

The SM8350-class firmware does not implement a dedicated 'charge now' property. It does however report BATT_CHG_COUNTER, and on this class of firmware that value is the remaining charge in uAh rather than a monotonic counter. Measured on a Fairphone 5 with charge_full at 4116000 uAh:

  BATT_CHG_COUNTER   reported capacity   counter / charge_full
  2434614            59%                 59.1%
  3525354            85%                 85.6%

Map POWER_SUPPLY_PROP_CHARGE_NOW onto the same firmware property and store the value in status.capacity, which the CHARGE_NOW/ENERGY_NOW case of qcom_battmgr_bat_get_property() already reads.

Together with the preceding patch this gives userspace both the full charge and the current charge. UPower now derives an energy level and runtime estimates in both directions:

Quoted 8 lines
    state:               discharging      state:               charging
    energy:              14.4347 Wh       energy:              15.6003 Wh
    energy-full:         16.8807 Wh       energy-full:         18.1505 Wh
    energy-rate:         3.39313 W        energy-rate:         7.3492 W
    time to empty:       4.3 hours        time to full:        20.8 minutes

---

Ok this makes more sense now. We should still gate the other property though.

[...]

drivers/power/supply/qcom_battmgr.c static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, · line 1451

Quoted 9 lines
drivers/power/supply/qcom_battmgr.c +4 -1
@@ -1442,7 +1445,10 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
 			battmgr->info.technology = le32_to_cpu(resp->intval.value);
 			break;
 		case BATT_CHG_COUNTER:
-			battmgr->info.charge_count = le32_to_cpu(resp->intval.value);
+			val = le32_to_cpu(resp->intval.value);
+			battmgr->info.charge_count = val;
+			/* the firmware reports the remaining charge here */
+			battmgr->status.capacity = val;

Let me try and find whether we can rely on this - on newer targets many properties are more or less just front-ends for vendor plumbing (because there's a ton of different chargers etc.)

Konrad

#5 lore raw

Re: [PATCH 2/2] power: supply: qcom_battmgr: expose CHARGE_NOW on SM8350-class firmware

Quoted 32 lines · Konrad Dybcio
Quoted 18 lines · Jan-Michael Brummer

The SM8350-class firmware does not implement a dedicated 'charge now' property. It does however report BATT_CHG_COUNTER, and on this class of firmware that value is the remaining charge in uAh rather than a monotonic counter. Measured on a Fairphone 5 with charge_full at 4116000 uAh:

  BATT_CHG_COUNTER   reported capacity   counter / charge_full
  2434614            59%                 59.1%
  3525354            85%                 85.6%

Map POWER_SUPPLY_PROP_CHARGE_NOW onto the same firmware property and store the value in status.capacity, which the CHARGE_NOW/ENERGY_NOW case of qcom_battmgr_bat_get_property() already reads.

Together with the preceding patch this gives userspace both the full charge and the current charge. UPower now derives an energy level and runtime estimates in both directions:

Quoted 8 lines
    state:               discharging      state:               charging
    energy:              14.4347 Wh       energy:              15.6003 Wh
    energy-full:         16.8807 Wh       energy-full:         18.1505 Wh
    energy-rate:         3.39313 W        energy-rate:         7.3492 W
    time to empty:       4.3 hours        time to full:        20.8 minutes

---

Ok this makes more sense now. We should still gate the other property though.

[...]

Quoted 13 lines

drivers/power/supply/qcom_battmgr.c static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, · line 1451

Quoted 9 lines
drivers/power/supply/qcom_battmgr.c +4 -1
@@ -1442,7 +1445,10 @@ static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,
 			battmgr->info.technology = le32_to_cpu(resp->intval.value);
 			break;
 		case BATT_CHG_COUNTER:
-			battmgr->info.charge_count = le32_to_cpu(resp->intval.value);
+			val = le32_to_cpu(resp->intval.value);
+			battmgr->info.charge_count = val;
+			/* the firmware reports the remaining charge here */
+			battmgr->status.capacity = val;

Let me try and find whether we can rely on this - on newer targets many properties are more or less just front-ends for vendor plumbing (because there's a ton of different chargers etc.)

There's a comment saying /* remaining capacity in uAh */, so it seems like this should be OK

Konrad

#6 lore raw

Re: [PATCH 2/2] power: supply: qcom_battmgr: expose CHARGE_NOW on SM8350-class firmware

Am Mittwoch, dem 02.09.2026 um 18:21 +0200 schrieb Konrad Dybcio:

Quoted 37 lines · Konrad Dybcio
Quoted 36 lines · Jan-Michael Brummer
Quoted 35 lines

The SM8350-class firmware does not implement a dedicated 'charge now'
property. It does however report BATT_CHG_COUNTER, and on this class of
firmware that value is the remaining charge in uAh rather than a monotonic counter. Measured on a Fairphone 5 with charge_full at 4116000 uAh:

  BATT_CHG_COUNTER   reported capacity   counter / charge_full 2434614            59%                 59.1% 3525354            85%                 85.6%

Map POWER_SUPPLY_PROP_CHARGE_NOW onto the same firmware property and
store the value in status.capacity, which the CHARGE_NOW/ENERGY_NOW case
of qcom_battmgr_bat_get_property() already reads.

Together with the preceding patch this gives userspace both the full
charge and the current charge. UPower now derives an energy level and
runtime estimates in both directions:

    state:               discharging      state:
charging
    energy:              14.4347 Wh       energy:
15.6003 Wh
    energy-full:         16.8807 Wh       energy-full:
18.1505 Wh
    energy-rate:         3.39313 W        energy-rate:
7.3492 W
    time to empty:       4.3 hours        time to full:
20.8 minutes

Quoted 5 lines
Quoted 5 lines
Quoted 2 lines

---

Ok this makes more sense now. We should still gate the other property though.

Hi Konrad,

so this one is fine and can be merged? Or am i'm missing something?

Quoted 25 lines
Quoted 12 lines

[...]

Quoted 9 lines
(quoted diff) +0 -0
@@ -1442,7 +1445,10 @@ static void
 qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr,

  battmgr->info.technology = le32_to_cpu(resp->intval.value);
  break;
  case BATT_CHG_COUNTER:

(quoted diff) +0 -1
-			battmgr->info.charge_count =

le32_to_cpu(resp->intval.value);

drivers/power/supply/qcom_battmgr.c +1 -0
+			val = le32_to_cpu(resp->intval.value);
Quoted 8 lines

drivers/power/supply/qcom_battmgr.c static void qcom_battmgr_sm8350_callback(struct qcom_battmgr *battmgr, · line 1451

Quoted 4 lines
(quoted diff) +2 -0
+			battmgr->info.charge_count = val;
+			/* the firmware reports the remaining

charge here */

drivers/power/supply/qcom_battmgr.c +1 -0
+			battmgr->status.capacity = val;

Let me try and find whether we can rely on this - on newer targets many properties are more or less just front-ends for vendor plumbing (because there's a ton of different chargers etc.)

There's a comment saying /* remaining capacity in uAh */, so it seems like this should be OK

Konrad