Skip to the content.

Linux on a 2015 MacBook Pro (MacBookPro11,5): Hardware Notes

As of 2026-10-14. This file covers everything that applies on any distro. Distro-specific setup is in omarchy.md and linux-mint.md. The findings come from two installs on this same Mac: Linux Mint 22.3 (kernel 7.0, radeon) and Omarchy (kernel 7.2.5, amdgpu).

Linux runs well on this Mac with four kernel parameters, a Bluetooth workaround, and one hard trade-off. On the AMD GPU, stock kernels give you Vulkan (amdgpu) or real sleep (radeon), not both. A small amdgpu patch, submitted upstream, gives both (see Deep sleep on amdgpu). The internal screen is wired to the AMD GPU, and that causes most of the quirks.

Hardware

Kernel parameters

Add these to whatever bootloader the distro uses:

Parameter Needed? Why
intel_iommu=off always With the Intel IOMMU (VT-d) on, radeon renders white or blank windows and amdgpu leaves the panel black. Nothing is logged; the corruption is silent. See Launchpad bug 1971146. Downside: no DMA protection against malicious Thunderbolt devices. Fallback if it ever recurs: iommu=pt.
nvme_core.default_ps_max_latency_us=0 for this SSD Turns off NVMe low-power states (APST). The SX8200 Pro is known to freeze, drop off the bus, or go read-only in them, especially in Macs. Next step if problems persist: pcie_aspm=off.
radeon.si_support=1 amdgpu.si_support=0 or radeon.si_support=0 amdgpu.si_support=1 always pick one Chooses the driver for the SI GPU (see GPU). Since kernel 7.0 the default is amdgpu, so radeon has to be forced explicitly.
amdgpu.ip_block_mask=0x7f amdgpu only Turns off the UVD (video decode) and VCE (video encode) blocks, which can’t resume from s2idle (see Sleep).
mem_sleep_default=s2idle stock amdgpu only Deep (S3) sleep hangs on stock amdgpu. On radeon, or on amdgpu with the patch from Deep sleep on amdgpu, keep the default deep.

Check with cat /proc/cmdline after rebooting.

Load the GPU drivers in the initramfs (i915 + radeon or amdgpu). Otherwise the driver can load about 5 s into boot, after the display server has already started on the temporary boot framebuffer (simpledrm), and the screen goes black. This was seen on Mint with Xorg; how to add them is distro-specific.

GPU

How the GPUs are wired

radeon vs amdgpu

  radeon amdgpu
Vulkan on the AMD GPU none: RADV refuses radeon (Intel’s Haswell Vulkan works, at about half the speed) RADV, so Steam/Proton/DXVK, Zed, wgpu, WebGPU
OpenGL, OpenCL same (Mesa radeonsi) same
VA-API video decode worked on Mint/Xorg; failed on Omarchy/Wayland (radeonsi_drv_video.so init failed) off because of ip_block_mask
Idle temperature / fans baseline about 3 °C warmer, about 600 rpm more fan. Memory clock pinned at 1125 MHz, no clock gating on SI; power_dpm_force_performance_level=low doesn’t fix either
Awake idle draw not measured ~30 W
Deep sleep (S3) works, 0.21 W hangs on resume; works with the patch, ~0.46 W
s2idle ~21 W; broke VCE and lost the panel EDID ~22 W, works only with the mask
Hibernate not tested works with the patch, if Wi-Fi is unloaded first (see Hibernate)
Chromium (Omarchy, Wayland) rendering artifacts with intel_iommu=off already set; only --disable-gpu fixed them. Unexplained. fine

amdgpu.ip_block_mask=0x7f

amdgpu splits this GPU into 9 blocks, numbered 0–8 (list them with journalctl -b -k | grep 'ip block'). 0x7f keeps blocks 0–6 and turns off block 7, uvd_v3_1, and block 8, vce_v1_0. Effect on use:

Battery idea (untested, risky)

Boot with the Intel GPU driving the panel and the AMD GPU powered off:

  1. gpu-switch -i (0xbb/gpu-switch) sets the EFI variable gpu-power-prefs, so the firmware muxes the panel to Intel.
  2. echo OFF > /sys/kernel/debug/vgaswitcheroo/switch from a boot service.

This would fix idle heat and draw, and maybe sleep. You’d lose about half the 3D performance and probably external displays, which are likely wired to the AMD GPU. Recovery: boot recovery mode and run gpu-switch -d, or as a last resort reset NVRAM (Cmd+Option+P+R at power-on).

Sleep & suspend

Real sleep means deep (S3). s2idle resumes on amdgpu once UVD/VCE are masked, but it isn’t really sleep on this hardware, because Haswell Macs have no S0ix low-power idle. radeon handles deep sleep out of the box. Stock amdgpu hangs on resume from deep sleep, and a small patch fixes that (see Deep sleep on amdgpu).

Driver Mode Result Draw Full battery lasts Failure in the log
radeon deep (S3) works 0.21 W ~20 days none
radeon s2idle resumes, but broken 21.3 W ~5 h failed VCE resume (-22), failed testing IB on ring 5/6/7, radeon_dp_link_train_cr: clock recovery failed, then EDID lost (Xorg/Cinnamon dropped to 1× scale)
amdgpu deep (S3) hangs n/a n/a ring gfx test failed (-110), resume of IP block <gfx_v6_0> failed -110, ring sdma0 timeout; the mask doesn’t help
amdgpu + mask + patch deep (S3) works ~0.46 W ~9 days none; 1.40 W over the whole 10 min window, which includes ~22 s awake (mostly the slow resume)
amdgpu s2idle hangs n/a n/a UVD Firmware validate fail (-22), resume of IP block <uvd_v3_1> failed, then endless Fence fallback timer expired on ring sdma0
amdgpu + mask s2idle works ~22 W ~5 h none; resume 0.4 s
amdgpu + mask hibernate dark screen after the LUKS prompt n/a n/a nothing logged; the restored kernel hangs before journald writes. Wi-Fi was loaded, so the cause is unclear
amdgpu + mask + patch hibernate hangs with Wi-Fi loaded; works with the brcmfmac sleep hook off n/a none; amdgpu restore takes ~1.2 s

All measured on battery with the lid open, for 10 minutes, using sleep-test.sh. The script measures the awake draw just before sleeping and subtracts the seconds spent awake inside the window. The patched-amdgpu figure is that corrected value. The older radeon and s2idle rows were measured before this correction, but they’re barely affected: radeon on Mint had the MTRR patch (fast resume), and s2idle draw is close to the awake draw anyway. For comparison, the awake idle draw on amdgpu is about 30 W.

Deep sleep on amdgpu

Symptom: with stock amdgpu, every resume from deep sleep (S3) fails, and so does a GPU reset. The screen stays black, and the log shows ring gfx test failed (-110) and resume of IP block <gfx_v6_0> failed -110. The system itself stays up. The same failure is reported on stock Arch 7.2.9 in drm/amd #5978.

Cause (verified with register dumps on this Mac):

Fix: kernel/amdgpu-si-reset-on-init.patch makes amdgpu reset and initialise the GPU itself at load, on Apple boards with VRAM at 0. amdgpu then uses the VBIOS layout from the start, and later re-inits keep it. The boot log shows PCI CONFIG reset, GPU posting now... and VRAM: 2048M 0x000000F400000000. This is the same approach that the kernel already uses for the 2017 and 2018 MacBook Pros with Polaris GPUs.

Slow wake from S3 (MTRR bug). The patch and an Ubuntu build script are in kernel/. Apple firmware changes the CPU cache settings (MTRRs) during S3 on half the cores, and those cores run extremely slowly until the kernel restores them. “Enabling non-boot CPUs” takes 0.7–5 s per CPU, about 15–23 s in total. CPU 4, the boot core’s hyperthread sibling, comes back fast. The fix is an unmerged patch written for exactly this Mac: x86/mtrr: Expedite cache init if MTRRs change on resume (Chris Paulson-Ellis, Feb 2026). On Mint it cut kernel resume from 15 s to about 5 s, and dmesg then shows mtrr: your CPUs had unexpected MTRR settings on resume. Check whether it has been merged before building it.

Lid open: sleeping with the lid open wakes the Mac after about 2 s, because the lid sensor (LID0 in /proc/acpi/wakeup) is a wake source. That’s harmless in normal use.

Runtime vs persistent: echo s2idle | sudo tee /sys/power/mem_sleep only lasts until reboot. After adding mem_sleep_default to the kernel command line, the running boot keeps the old mode until you reboot.

Measuring power: use the charge drawn (µAh) × the average voltage. Charge × voltage at each end overstates it: the voltage sags about 0.7 V right after unplugging, which once produced a fake 54 W “awake” reading. For a live reading, multiply current_now by voltage_now in /sys/class/power_supply/BAT0.

Hibernate

Hibernate works on amdgpu with the patch from Deep sleep on amdgpu, as long as the Wi-Fi driver is unloaded before hibernating. Untested on radeon. Tested on 7.2.5 with LUKS + a btrfs swap file:

Testing tips:

suspend-then-hibernate works: deep sleep first, then after HibernateDelaySec the Mac wakes itself with an RTC alarm and hibernates. Tested with a 2-minute delay on lid close; Wi-Fi was back about 5 s after the restore.

The Wi-Fi hook runs three times in one cycle: pre suspend-then-hibernate before the deep sleep, post suspend-then-hibernate after the timed wake, and pre hibernate about 0.2 s later. A hook that only runs modprobe brcmfmac in post breaks here: the unload in pre hibernate fails with Module brcmfmac is in use while the firmware is still loading, the system hibernates with the chip half set up, and after the restore the log shows brcmf_fwvid_request_module: mod=wcc: failed -16 and Firmware has halted or crashed. Wi-Fi stays dead until a reboot. That’s why wifi/brcmfmac-sleep waits for the interface after loading and retries the unload.

# /etc/systemd/sleep.conf.d/suspend-then-hibernate.conf
[Sleep]
HibernateDelaySec=2h

# /etc/systemd/logind.conf.d/30-lid-suspend-then-hibernate.conf
[Login]
HandleLidSwitch=suspend-then-hibernate

Apply the logind change with sudo systemctl kill -s HUP systemd-logind, which reloads logind without ending the session. To test, set HibernateDelaySec=2min first and close the lid for about 3 minutes.

Bluetooth

The Apple BCM20703 chip misses its reset on about 2 of 5 boots, warm reboots included. It was seen on both distros. The log shows hci0: command 0x0c03 tx timeout and BCM: Reset failed (-110), and there’s no controller; usb 1-8: Failed to suspend device, error -110 shows that USB autosuspend of the chip fails. Reloading btusb doesn’t help, because the chip itself is hung. A suspend/resume, or a full shutdown and power-on, brings it back.

The fix is bluetooth/bt-fix.sh. It needs systemd and Python 3 (sudo bash bt-fix.sh, undo with undo), and installs:

usbcore.autosuspend=-1 set through /etc/modprobe.d does nothing when usbcore is built into the kernel, which it usually is. It would have to go on the kernel command line, and btusb turns autosuspend back on for its device anyway.

After recovery the radio may be soft-blocked: rfkill unblock bluetooth. The first scan can be slow. One untested theory is that 2.4 GHz Wi-Fi on channel 1 shares the antenna.

Wi-Fi

Wi-Fi works out of the box with brcmfmac and firmware 7.35.177.61. It warns no clm_blob available, which may limit the available channels. Omarchy’s Apple installer step adds options brcmfmac feature_disable=0x82000 (/etc/modprobe.d/brcmfmac.conf), because Broadcom’s in-firmware supplicant fails the WPA handshake on Apple hardware; Mint ran fine without it.

After deep sleep (S3) the chip sometimes doesn’t come back. S3 cuts the chip’s power, which s2idle doesn’t. Usually Wi-Fi reconnects about 2–3 s after resume. Sometimes, though, the firmware stops answering: right after resume dmesg shows brcmf_msgbuf_query_dcmd: Timeout on response for query command, then endless -5 and later -12 errors (Failed to reserve space in commonring, scan error), and there are no networks until a reboot. On Omarchy it happened on 1 of 2 S3 cycles, and the second cycle was fine.

Recovery: wifi/wifi-reload.sh (sudo bash wifi-reload.sh). It reloads the driver, which reloads the chip’s firmware, and if no interface appears it also removes and rescans the PCI device. A plain modprobe -r brcmfmac fails, because the vendor module brcmfmac_wcc holds a reference to it, so remove brcmfmac_wcc first.

Hibernate hangs on restore when brcmfmac is loaded (see Hibernate).

Fix for both: wifi/brcmfmac-sleep, a systemd sleep hook that unloads the driver before deep sleep or hibernate and loads it again after resume, so the chip starts with fresh firmware instead of resuming. s2idle keeps the chip powered, so the hook skips it. Install with sudo install -Dm755 wifi/brcmfmac-sleep /usr/lib/systemd/system-sleep/brcmfmac.

rtcwake writes straight to /sys/power/state and skips systemd’s sleep hooks. To test the hook, set an RTC alarm and suspend through systemd: echo +30 | sudo tee /sys/class/rtc/rtc0/wakealarm && systemctl suspend.

Camera (FaceTime HD)

Use the patjak/facetimehd driver through DKMS, plus the firmware from patjak/facetimehd-firmware, which extracts it from Apple’s Boot Camp package. On Arch use the AUR packages facetimehd-dkms and facetimehd-firmware; elsewhere, build it with DKMS from source (commit c9b063a3c0, version 0.7.0.1, worked with kernel 7.0). Blacklist bdc_pci (/etc/modprobe.d/facetimehd.conf). When it’s healthy, dmesg shows facetimehd ... loaded set file facetimehd/1871_01XX.dat, and /dev/video0 gives 1280×720 at 30 fps. Apps that read the camera through PipeWire (Cheese, possibly browsers and OBS) can fail with can't negotiate buffers. Apps that use v4l2 directly (guvcview, gst-launch-1.0 v4l2src) work.

Fans and thermals

Other

Testing & troubleshooting

Test every sleep change with a timed self-wake before trusting the lid:

sudo rtcwake -m mem -s 20                                                      # whatever mem_sleep selects
echo s2idle | sudo tee /sys/power/mem_sleep && sudo rtcwake -m freeze -s 20   # force s2idle, this boot only
sudo bash sleep-test.sh s2idle 10                                              # power draw, on battery
sudo bash sleep-test.sh awake 10                                               # baseline

After a hang and a hard reset:

journalctl --list-boots | tail -5
journalctl -b -1 -k | grep -Ei 'PM: suspend|Waking|radeon|amdgpu.*(ERROR|fail|timeout)|gmux'
journalctl -b -1 -k | grep 'ip block'          # amdgpu block numbers + which are disabled
journalctl -b 0 -k | grep 'PM: Image'          # 'Image not found' after a hibernate = image was consumed
cat /proc/cmdline
lspci -k -s 01:00.0 | grep 'in use'            # radeon or amdgpu

Error codes: -110 is a timeout (the hardware didn’t respond), and -22 is an invalid argument (here, a firmware check failing).