macsmc-battery: charge_now/energy_now reported at inconsistent scale (both under and over); capacity intermittently stuck at 100%
Summary
The macsmc-battery power supply driver reports charge_now and energy_now at an inconsistent, time-varying scale relative to charge_full/energy_full. The same *_now pair is, within each sample, scaled by an identical factor, but that factor changes over time and has been observed ranging from ~0.4x to ~10x the physically-correct value. As a result:
- Any percentage computed as
now/full is wrong, in both directions (under- and over-reporting).
- The dedicated
POWER_SUPPLY_CAPACITY attribute (sourced from a separate fuel-gauge register, not computed from now/full) is closer to reality but is intermittently stuck at 100 even when the battery is not full.
The actual battery level is ~95-96% (confirmed by the user; corroborated by time_to_empty_now). Userspace that follows capacity/upower percentage therefore sees either a stuck 100% or a briefly correct ~95%, while anything that computes from charge_now/energy_now sees garbage (e.g. 54%, 69%, 39%, 652%, 1073%). Low-battery alerts never fire while capacity is stuck at 100.
Hardware / Environment
- Device: Apple MacBook Pro (14-inch, M1 Pro, 2021) —
apple,j314s, apple,t6000
- Battery:
bq40z651, serial F8Y13660648Q1LT61, 861 charge cycles, health: Good
- Design capacity:
charge_full_design=6,075,000 µAh / energy_full_design=69,255,000 µWh (~69.3 Wh)
- Actual full capacity (learned):
charge_full≈5,225,000 µAh / energy_full≈59,565,000 µWh (~59.6 Wh) → ~86% health (consistent with 861 cycles). These *_full values are stable and correct.
- System firmware:
unknown v1.5.2 (m1n1 stage2)
- OS: Arch Linux ARM (asahi-alarm repo)
- Kernel:
7.0.13.asahi1-1 (linux-asahi package, 7.0.13-1-1-ARCH, aarch64) — this is the latest available in the repo; updating does not fix it.
- upower: 1.91.2-1
- Kernel cmdline:
BOOT_IMAGE=/boot/vmlinuz-linux-asahi root=UUID=... rw loglevel=3 quiet splash appledrm.show_notch=1
Key observation: capacity is independent of now/full
Across all samples, POWER_SUPPLY_CAPACITY cannot be derived from charge_now/charge_full or energy_now/energy_full by simple division — e.g. charge_now=3,603,000 / charge_full=5,225,000 = 69%, yet capacity=95. This confirms the driver populates capacity from a separate fuel-gauge source. The *_now fields and the capacity field are independently (and differently) broken.
Reproduction 1 — charge_now/energy_now under-reporting while capacity is correct (battery ~95-96%, discharging)
1-second interval, battery discharging, actual level ~95-96%:
cap=95 raw=69.4% charge_now=3603000 energy_now=41074200 tte=23100s power_now=-7519000uW
cap=95 raw=69.4% charge_now=3603000 energy_now=41074200 tte=23280s power_now=-8699000uW
cap=95 raw=69.4% charge_now=3603000 energy_now=41074200 tte=23280s power_now=-9355000uW
cap=95 raw=69.4% charge_now=3603000 energy_now=41074200 tte=23280s power_now=-9320000uW
cap=95 raw=64.5% charge_now=3347000 energy_now=38155800 tte=23280s power_now=-9321000uW
cap=95 raw=64.5% charge_now=3347000 energy_now=38155800 tte=23280s power_now=-9454000uW
cap=95 raw=64.5% charge_now=3347000 energy_now=38155800 tte=23280s power_now=-9417000uW
cap=95 raw=64.5% charge_now=3347000 energy_now=38155800 tte=23280s power_now=-9530000uW
cap=95 raw=59.5% charge_now=3091000 energy_now=35237400 tte=23220s power_now=-9469000uW
cap=95 raw=59.5% charge_now=3091000 energy_now=35237400 tte=23220s power_now=-9368000uW
cap=95 raw=59.5% charge_now=3091000 energy_now=35237400 tte=23160s power_now=-9505000uW
cap=95 raw=59.5% charge_now=3091000 energy_now=35237400 tte=23160s power_now=-9321000uW
cap=95 raw=59.5% charge_now=3091000 energy_now=35237400 tte=23220s power_now=-9357000uW
cap=95 raw=54.6% charge_now=2835000 energy_now=32319000 tte=23220s power_now=-9367000uW
cap=95 raw=54.6% charge_now=2835000 energy_now=32319000 tte=23220s power_now=-9231000uW
Here charge_now decreases smoothly (3,603,000 → 2,835,000) but reads ~54-69% of charge_full, while capacity=95 and time_to_empty_now≈23,200s (6.4 h). At ~9.4 W, 6.4 h implies ~56-61 Wh remaining ≈ 95-102% of energy_full — consistent with capacity=95 and the user-reported ~96%, and inconsistent with the charge_now-derived 54-69%. So here charge_now/energy_now are under-reporting by ~0.5-0.7x while capacity is correct.
Reproduction 2 — charge_now/energy_now over-reporting and capacity stuck at 100 (battery actually ~96%, discharging)
1-second interval, same session, a few minutes earlier (still discharging, actual level still ~96%):
t=1 status=Discharging capacity=100 charge_now=34067000 charge_full=5225000 (raw 652.0%) energy_now=388363800 energy_full=59565000
t=2 status=Discharging capacity=100 charge_now=34067000 charge_full=5225000 (raw 652.0%) energy_now=388363800 energy_full=59565000
t=3 status=Discharging capacity=100 charge_now=33811000 charge_full=5225000 (raw 647.1%) energy_now=385445400 energy_full=59565000
t=4 status=Discharging capacity=100 charge_now=33811000 charge_full=5225000 (raw 647.1%) energy_now=385445400 energy_full=59565000
t=5 status=Discharging capacity=100 charge_now=33811000 charge_full=5225000 (raw 647.1%) energy_now=385445400 energy_full=59565000
t=6 status=Discharging capacity=100 charge_now=33811000 charge_full=5225000 (raw 647.1%) energy_now=385445400 energy_full=59565000
t=7 status=Discharging capacity=100 charge_now=33555000 charge_full=5225000 (raw 642.2%) energy_now=382527000 energy_full=59565000
t=8 status=Discharging capacity=100 charge_now=33555000 charge_full=5225000 (raw 642.2%) energy_now=382527000 energy_full=59565000
t=9 status=Discharging capacity=100 charge_now=33555000 charge_full=5225000 (raw 642.2%) energy_now=382527000 energy_full=59565000
t=10 status=Discharging capacity=100 charge_now=33555000 charge_full=5225000 (raw 642.2%) energy_now=382527000 energy_full=59565000
Here charge_now/charge_full = 652% (over-reporting by ~6.5x; also seen at ~10x: 55,827,000/5,202,000 = 1073%), and capacity is stuck at 100 despite the battery being at ~96%. upower exposes this as percentage: 100%, energy: 639.346 Wh, time to empty: 3.4 days — all absurd for a 69 Wh-design battery.
Reproduction 3 — charge_now/energy_now briefly under-report at ~0.4x
A one-off reading captured during the same discharge:
charge_now=2,068,000 charge_full=5,234,000 -> 39.5%
energy_now=23,575,200 energy_full=59,667,600 -> 39.5%
charge_now and energy_now are internally consistent with each other (both 39.5%) but inconsistent with reality (~96%) and with time_to_empty_now. This is an under-reporting outlier at ~0.4x, not the true state.
Summary of observed *_now scale factors vs. physically-correct value
| Observed reading |
charge_now/charge_full |
Inferred scale vs. true (~96%) |
| 2,068,000 / 5,234,000 |
39.5% |
~0.4x (under) |
| 2,835,000 / 5,225,000 |
54.6% |
~0.6x (under) |
| 3,603,000 / 5,225,000 |
69.4% |
~0.7x (under) |
| 34,067,000 / 5,225,000 |
652% |
~6.5x (over) |
| 55,827,000 / 5,202,000 |
1073% |
~10x (over) |
Within each sample, charge_now and energy_now are scaled by the same factor; the *_full values are stable and correct.
capacity behavior
- Sourced independently from
now/full (see "Key observation").
- Intermittently stuck at 100 even when the battery is at ~96% (Reproduction 2). When stuck, low-battery alerts never fire (safety/usability concern: unexpected shutdowns).
- At other times reports a value consistent with reality and
time_to_empty_now (Reproduction 1: capacity=95).
upower -d excerpt (captured during an over-reporting + stuck-capacity window)
Device: /org/freedesktop/UPower/devices/battery_macsmc_battery
native-path: macsmc-battery
model: bq40z651
state: discharging
energy: 639.346 Wh <-- absurd (from inflated *_now)
energy-full: 639.346 Wh <-- equals "energy" because now>=full
energy-full-design: 69.255 Wh
voltage: 12.535 V
energy-rate: 7.871 W
charge-cycles: 861
percentage: 100%
capacity: 100% <-- health also misreported (should be ~86%)
time to empty: 3.4 days <-- derived from inflated energy
charge-start-threshold: 75%
charge-end-threshold: 80%
Note: upower's capacity (health) is also wrong here (100% instead of ~86% = 59.5/69.3), suggesting energy_full is also occasionally inflated when upower samples it (sysfs polling showed energy_full stable at 59.5 Wh, but upower caught 639 Wh).
Full sysfs uevent (captured during a ~0.4x under-reporting window)
POWER_SUPPLY_NAME=macsmc-battery
POWER_SUPPLY_TYPE=Battery
POWER_SUPPLY_STATUS=Discharging
POWER_SUPPLY_HEALTH=Good
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_CYCLE_COUNT=861
POWER_SUPPLY_VOLTAGE_MAX=12998000
POWER_SUPPLY_VOLTAGE_MIN=8940000
POWER_SUPPLY_VOLTAGE_MAX_DESIGN=12825000
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=9000000
POWER_SUPPLY_VOLTAGE_NOW=12516000
POWER_SUPPLY_CURRENT_NOW=-815000
POWER_SUPPLY_POWER_NOW=-10200000
POWER_SUPPLY_CHARGE_FULL_DESIGN=6075000
POWER_SUPPLY_CHARGE_FULL=5234000
POWER_SUPPLY_CHARGE_NOW=2068000
POWER_SUPPLY_CHARGE_COUNTER=-769574
POWER_SUPPLY_ENERGY_FULL_DESIGN=69255000
POWER_SUPPLY_ENERGY_FULL=59667600
POWER_SUPPLY_ENERGY_NOW=23575200
POWER_SUPPLY_CAPACITY=100 <-- stuck at 100 even though charge_now/charge_full = 39.5%
POWER_SUPPLY_CAPACITY_LEVEL=Normal
POWER_SUPPLY_TEMP=341
POWER_SUPPLY_TIME_TO_EMPTY_NOW=25680
Suggested investigation
- The
macsmc-battery driver appears to source charge_now/energy_now from a register/field whose unit or scaling is unstable across reads (returning anything from ~0.4x to ~10x the correct value), while charge_full/energy_full are stable. Identify which SMC key/register backs *_now and whether it sometimes returns a cumulative counter, a raw ADC value, or a value in a different unit rather than instantaneous remaining charge/energy.
POWER_SUPPLY_CAPACITY is populated independently of now/full (good) but gets stuck at 100. Worth checking whether the driver caches/clamps it and only refreshes on certain events (e.g. charge state transitions) rather than on every read.
- If helpful, I can collect: longer traces,
dmesg output, readings while charging vs. discharging, or concurrent time_to_empty_now/power_now samples to triangulate the true state.
- Separately:
upower's health capacity (energy_full/design) was observed as 100% when it should be ~86%; may indicate energy_full is also occasionally inflated at the moments upower samples it.
Workaround in use
None at the kernel level (the latest linux-asahi 7.0.13.asahi1-1 available in asahi-alarm is already installed and exhibits the issue). Userspace workaround under consideration: prefer POWER_SUPPLY_CAPACITY over now/full-derived percentage, and when capacity is stuck at 100 for extended periods while discharging, fall back to time_to_empty_now × power_now / energy_full to estimate the true level.
macsmc-battery:
charge_now/energy_nowreported at inconsistent scale (both under and over);capacityintermittently stuck at 100%Summary
The
macsmc-batterypower supply driver reportscharge_nowandenergy_nowat an inconsistent, time-varying scale relative tocharge_full/energy_full. The same*_nowpair is, within each sample, scaled by an identical factor, but that factor changes over time and has been observed ranging from ~0.4x to ~10x the physically-correct value. As a result:now/fullis wrong, in both directions (under- and over-reporting).POWER_SUPPLY_CAPACITYattribute (sourced from a separate fuel-gauge register, not computed fromnow/full) is closer to reality but is intermittently stuck at 100 even when the battery is not full.The actual battery level is ~95-96% (confirmed by the user; corroborated by
time_to_empty_now). Userspace that followscapacity/upowerpercentagetherefore sees either a stuck 100% or a briefly correct ~95%, while anything that computes fromcharge_now/energy_nowsees garbage (e.g. 54%, 69%, 39%, 652%, 1073%). Low-battery alerts never fire whilecapacityis stuck at 100.Hardware / Environment
apple,j314s,apple,t6000bq40z651, serialF8Y13660648Q1LT61, 861 charge cycles, health: Goodcharge_full_design=6,075,000 µAh/energy_full_design=69,255,000 µWh(~69.3 Wh)charge_full≈5,225,000 µAh/energy_full≈59,565,000 µWh(~59.6 Wh) → ~86% health (consistent with 861 cycles). These*_fullvalues are stable and correct.unknown v1.5.2(m1n1 stage2)7.0.13.asahi1-1(linux-asahipackage,7.0.13-1-1-ARCH, aarch64) — this is the latest available in the repo; updating does not fix it.BOOT_IMAGE=/boot/vmlinuz-linux-asahi root=UUID=... rw loglevel=3 quiet splash appledrm.show_notch=1Key observation:
capacityis independent ofnow/fullAcross all samples,
POWER_SUPPLY_CAPACITYcannot be derived fromcharge_now/charge_fullorenergy_now/energy_fullby simple division — e.g.charge_now=3,603,000 / charge_full=5,225,000 = 69%, yetcapacity=95. This confirms the driver populatescapacityfrom a separate fuel-gauge source. The*_nowfields and thecapacityfield are independently (and differently) broken.Reproduction 1 —
charge_now/energy_nowunder-reporting whilecapacityis correct (battery ~95-96%, discharging)1-second interval, battery discharging, actual level ~95-96%:
Here
charge_nowdecreases smoothly (3,603,000 → 2,835,000) but reads ~54-69% ofcharge_full, whilecapacity=95andtime_to_empty_now≈23,200s(6.4 h). At ~9.4 W, 6.4 h implies ~56-61 Wh remaining ≈ 95-102% ofenergy_full— consistent withcapacity=95and the user-reported ~96%, and inconsistent with thecharge_now-derived 54-69%. So herecharge_now/energy_noware under-reporting by ~0.5-0.7x whilecapacityis correct.Reproduction 2 —
charge_now/energy_nowover-reporting andcapacitystuck at 100 (battery actually ~96%, discharging)1-second interval, same session, a few minutes earlier (still discharging, actual level still ~96%):
Here
charge_now/charge_full = 652%(over-reporting by ~6.5x; also seen at ~10x:55,827,000/5,202,000 = 1073%), andcapacityis stuck at 100 despite the battery being at ~96%.upowerexposes this aspercentage: 100%,energy: 639.346 Wh,time to empty: 3.4 days— all absurd for a 69 Wh-design battery.Reproduction 3 —
charge_now/energy_nowbriefly under-report at ~0.4xA one-off reading captured during the same discharge:
charge_nowandenergy_noware internally consistent with each other (both 39.5%) but inconsistent with reality (~96%) and withtime_to_empty_now. This is an under-reporting outlier at ~0.4x, not the true state.Summary of observed
*_nowscale factors vs. physically-correct valuecharge_now/charge_fullWithin each sample,
charge_nowandenergy_noware scaled by the same factor; the*_fullvalues are stable and correct.capacitybehaviornow/full(see "Key observation").time_to_empty_now(Reproduction 1:capacity=95).upower -dexcerpt (captured during an over-reporting + stuck-capacity window)Note:
upower'scapacity(health) is also wrong here (100% instead of ~86% = 59.5/69.3), suggestingenergy_fullis also occasionally inflated when upower samples it (sysfs polling showedenergy_fullstable at 59.5 Wh, but upower caught 639 Wh).Full sysfs
uevent(captured during a ~0.4x under-reporting window)Suggested investigation
macsmc-batterydriver appears to sourcecharge_now/energy_nowfrom a register/field whose unit or scaling is unstable across reads (returning anything from ~0.4x to ~10x the correct value), whilecharge_full/energy_fullare stable. Identify which SMC key/register backs*_nowand whether it sometimes returns a cumulative counter, a raw ADC value, or a value in a different unit rather than instantaneous remaining charge/energy.POWER_SUPPLY_CAPACITYis populated independently ofnow/full(good) but gets stuck at 100. Worth checking whether the driver caches/clamps it and only refreshes on certain events (e.g. charge state transitions) rather than on every read.dmesgoutput, readings while charging vs. discharging, or concurrenttime_to_empty_now/power_nowsamples to triangulate the true state.upower's healthcapacity(energy_full/design) was observed as 100% when it should be ~86%; may indicateenergy_fullis also occasionally inflated at the moments upower samples it.Workaround in use
None at the kernel level (the latest
linux-asahi 7.0.13.asahi1-1available inasahi-alarmis already installed and exhibits the issue). Userspace workaround under consideration: preferPOWER_SUPPLY_CAPACITYovernow/full-derived percentage, and whencapacityis stuck at 100 for extended periods while discharging, fall back totime_to_empty_now×power_now/energy_fullto estimate the true level.