The changes in this post are scripted at github.com/jamieede123/macbookair6-1-mint-tweaks :
sudo ./bootstrap.sh --allapplies them, idempotently and reversibly. The rest is the reasoning and the measurements behind each one.
The machine is the same one from the Broadcom wl driver write-up
: an 11-inch MacBook Air from mid 2013 (MacBookAir6,1, a Haswell Core i5-4250U), running Linux Mint rather than macOS. With kernel 6.17 booting and wifi working, the remaining job was battery life and responsiveness on Linux Mint. This covers the changes that affected each, with the measurements.
Read the meter in the right state ¶
On an apparently idle machine, the battery reported a 24.7 W draw:
$ awk '{printf "%.1f W\n", $1/1000000}' /sys/class/power_supply/BAT0/power_now
24.7 W
That number is not consumption. The laptop was plugged in and charging, and power_now reports the charge rate while on AC, not what the system is using. Unplugged and idle, the same file reads around 10 W. Every reading below is taken on battery.
The battery’s usable capacity reflects its age:
$ cd /sys/class/power_supply/BAT0
$ echo "design $(cat energy_full_design) now-full $(cat energy_full)"
design 51000000 now-full 41050000
51.0 Wh of design capacity, 41.05 Wh of usable capacity today, about 80 percent of original. That is a ceiling no tuning can raise.
Deep sleep ¶
The two system sleep states
that matter here are s2idle (suspend-to-idle, which keeps much of the system in a light doze) and deep (suspend-to-RAM, the classic S3 state that powers down nearly everything but memory). This Air defaulted to the lighter one:
$ cat /sys/power/mem_sleep
[s2idle] deep
The brackets mark the active choice. With s2idle, a closed lid can shed ten to twenty percent of charge overnight; deep reduces that to a trickle. Switching the default is a one-line kernel parameter in /etc/default/grub:
-GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=off acpi_osi=!Darwin"
+GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=off acpi_osi=!Darwin mem_sleep_default=deep"
After sudo update-grub and a reboot, the active state moves:
$ cat /sys/power/mem_sleep
s2idle [deep]
A small number of Macs do not resume cleanly from deep; if that happens, removing the parameter restores the old behaviour.
TLP profiles ¶
TLP applies one set of settings on AC and another on battery and switches between them when the cable moves. Two of its settings matter most on this machine, and both are battery-only.
Wi-Fi power saving, WIFI_PWR_ON_BAT
, lets the radio doze between packets:
WIFI_PWR_ON_AC=off
WIFI_PWR_ON_BAT=on
And PCIe Active State Power Management, PCIE_ASPM_ON_BAT
, drops the PCIe bus into its deepest power-saving state when idle:
PCIE_ASPM_ON_AC=default
PCIE_ASPM_ON_BAT=powersupersave
Because these only apply on battery, checking them while plugged in shows nothing changed, which is correct, not broken. Forcing battery mode confirms they engage:
$ sudo tlp bat
$ iw dev wlp3s0 get power_save
Power save: on
$ cat /sys/module/pcie_aspm/parameters/policy
default performance powersave [powersupersave]
On AC the same two read off and [default]: full responsiveness on wall power, savings on battery, with no manual intervention.
zram ¶
zram is a compressed block device that lives in RAM, used here as swap. This Air has 4 GB of memory soldered to the board, so there is no upgrade path. Under memory pressure it previously swapped to the SSD file and stuttered; zram compresses pages and keeps them in RAM instead, which is far faster than touching the disk. After a few hours of real use with an editor and a browser open, it is full and working:
$ zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle 1.9G 1.8G 620.6M 638.3M [SWAP]
That is 1.8 GB of swapped pages held in 638 MB of RAM, a compression ratio close to 2.9 to 1. The priority ordering does the rest: zram is filled before the SSD file, so most swap stays in memory and only the overflow reaches the disk.
$ swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 3.9G 365.2M -2
/dev/zram0 partition 1.9G 1.9G 5
zram (priority 5) is full while only 365 MB has spilled to the swapfile (priority -2). Without zram, all of that swap would have been SSD writes. Installing zram-config sets this up as a systemd service that returns on every boot. On 4 GB it is the most noticeable responsiveness change, more than any CPU setting.
Boot time ¶
One service accounted for nearly half the boot time. systemd-analyze blame named it:
$ systemd-analyze
Startup finished in 6.569s (kernel) + 16.167s (userspace) = 22.737s
$ systemd-analyze blame | head -1
10.815s NetworkManager-wait-online.service
NetworkManager-wait-online
is a oneshot service that holds the network-online target until the network is fully up. A laptop reaching the desktop does not need to block on that, so masking it removes the delay:
$ sudo systemctl mask NetworkManager-wait-online.service
Boot drops by roughly ten seconds. Nothing in a desktop session depends on the wait.
The biggest lever: brightness ¶
The largest single saving was none of the device tuning above. It was screen brightness. Measured idle draw on battery, before and after dropping the backlight from full to 40 percent:
$ cat /sys/class/backlight/intel_backlight/max_brightness # 2777
$ # 100% -> ~10.4 W, 40% (1110/2777) -> ~8.6 W
| Setting | Avg draw | Est. runtime on 41 Wh |
|---|---|---|
| Brightness 100% | ~10.4 W | ~4.0 h |
| Brightness 40% | ~8.6 W | ~4.7 h |
The backlight change saved about 1.8 W, more than the Wi-Fi, PCIe, and audio savings combined. powertop
run with --auto-tune moved the figure by less than the measurement noise, because TLP already applies the same tunables; it is useful as a diagnostic for what is drawing power, not as a second tuner on top of TLP. The CPU was also busy during these readings, with an editor and a browser open, which is where most of the draw goes. Device tuning does not reclaim watts that active work is spending.
Energy-performance bias ¶
The CPU’s energy-performance bias governs how the machine responds rather than how long it lasts. On the intel_pstate
driver it is a hint, not a speed cap: turbo stays available regardless, and the bias only changes how eagerly the chip ramps to high clocks. TLP sets it per power source, performance-leaning on AC and balanced on battery:
# on AC
energy_perf_bias (0=perf, 15=save): 4
no_turbo (0=on): 0
scaling_max_freq: 2600000
# on battery
energy_perf_bias (0=perf, 15=save): 8
Turbo to the full 2.6 GHz is on in both states, so there is no flat performance penalty for running unplugged. The bias of 8 on battery makes the chip slightly slower to jump to high frequency, trading a little responsiveness for power. Lowering it toward 4 on battery makes it feel as immediate as on AC, at a modest runtime cost.
End state ¶
deepsuspend-to-RAM as the default sleep state, for low standby drain.- TLP switching profiles automatically: Wi-Fi power saving and PCIe
powersupersaveon battery, full performance on AC. - zram as compressed in-RAM swap, the main responsiveness win on 4 GB.
NetworkManager-wait-onlinemasked, for a roughly ten-second faster boot.- Brightness as the dominant battery dial.
This Air now gives four to five hours of light use, about the ceiling for an eleven-year-old battery at 80 percent health. The soldered 4 GB is the other hard limit, and the reason zram earns its place. The method is the same as patching the wl driver: change one thing, measure in the right state, keep what the meter rewards.
Every step here is in the repo
; ./bootstrap.sh --status shows which are already applied on a given machine.