Enabling USB-C DisplayPort on the M2 MacBook Air with Asahi Linux

    Getting DisplayPort output working over USB-C on the 2022 M2 MacBook Air running Asahi Linux — including a successful test at 2560×1440 @ 180 Hz.

    August 31, 2026
    Tech
    10 min read

    External display support over USB-C is one of those features that should be completely unremarkable on a modern laptop.

    On an Apple Silicon MacBook Air running Asahi Linux, however, getting a USB-C monitor working involves several layers of Apple's hardware and Linux's display stack that aren't connected correctly in the stock configuration.

    I recently got DisplayPort output working over USB-C on the 2022 M2 MacBook Air running Asahi Linux, including a successful test at 2560×1440 @ 180 Hz.

    The fix is now here:

    dp-altmode-t8112 on GitHub

    This article explains what was broken, how the M1 solution led to the investigation, what I found in Asahi's source tree, and the two changes required to make the external display work.

    Background

    The target machine is the 13-inch MacBook Air with the M2 chip (2022), based on Apple's t8112 platform and identified as apple,j413.

    I'm running Asahi Linux, specifically the Arch-based installation.

    Before this project, I had never used Linux before.

    That matters mainly because this isn't an implementation based on years of Linux kernel or device-tree development. I was able to get deep enough into the system to trace this particular issue because Omarchy made Linux approachable enough for me to actually use it as a daily environment and start investigating what was happening underneath it.

    Once I started looking into the display problem, though, the issue quickly became very technical.

    The hardware wasn't missing DisplayPort support.

    The Linux software stack simply wasn't exposing all of the hardware correctly.

    The M2 Air actually has an external display controller

    The first important discovery is that the M2 Air contains a dedicated external display controller, referred to as dcpext.

    The stock device tree contains the relevant hardware, but the external display controller is disabled.

    At the same time, the USB-C Type-C controller is capable of handling the DisplayPort connection, but the resulting hot-plug event isn't reaching Linux's DRM subsystem.

    So there are actually two separate problems:

    1. Linux needs to boot and configure the external display controller.
    2. Linux needs to know when a DisplayPort monitor is connected.

    The repository therefore uses two separate modifications:

    • a patched device-tree blob (DTB) for the boot chain
    • a patched tps6598x-core kernel module to forward DisplayPort HPD events to DRM.

    Starting with the existing M1 solution

    The obvious place to start was an existing project:

    haripako/dp-altmode

    That project enables external DisplayPort output on the 13-inch M1 MacBook Pro.

    The overall approach was exactly what I needed, but the implementation couldn't simply be copied to the M2 Air.

    The reason is Apple's platform-specific hardware layout.

    The M1 MacBook Pro uses the t8103 platform, while the M2 Air uses t8112.

    The relevant controllers aren't located at the same addresses, and the device-tree configuration differs.

    The most important difference turned out to be the Type-C mux.

    For the M1 implementation, the DP-capable mux uses:

    text
    mux-index = <0>

    The M2 Air uses:

    text
    mux-index = <2>

    The two platforms also place the main and external display controllers differently. The M2 configuration uses dcp@231c00000 for the main display controller and dcp@271c00000 for the external controller.

    This explained why the existing t8103-only scripts rejected the M2 Air rather than simply working out of the box.

    Finding the correct M2 configuration

    The important breakthrough was finding the corresponding configuration in the Asahi Linux fairydust branch.

    Rather than trying to reverse-engineer the hardware wiring from scratch, I could use the device-tree configuration that Asahi already had for the M2 Air.

    The relevant upstream configuration contains the information needed to connect:

    text
    USB-C controller Type-C mux DisplayPort PHY External display controller DRM

    The patch reproduces that wiring for the stock DTBs.

    There are three key device-tree changes.

    1. Enable the external display controller

    The patch adds the dcpext alias:

    text
    dcpext = "/soc/dcp@271c00000"

    and changes the corresponding controller node to:

    text
    status = "okay"

    This tells the boot/kernel stack that the external display controller should actually be initialized.

    But the alias has another purpose.

    Starting with m1n1 1.6.1, the bootloader recognizes the dcpext alias and uses it to reserve the firmware required by the external display controller.

    Without that alias, the controller's firmware is skipped even though the hardware node exists in the device tree.

    That distinction is important.

    The problem wasn't simply:

    "The device tree forgot to enable the controller."

    The bootloader also needed to know that the controller was actually in use.

    2. Connect dcpext to the correct DisplayPort PHY

    The next piece is connecting the external display controller to the correct PHY.

    The M2 Air uses:

    text
    atcphy1

    for the DisplayPort-capable PHY.

    The device-tree configuration specifies the DisplayPort PHY and, critically:

    text
    mux-index = <2>

    This is one of the differences from the M1 MacBook Pro implementation.

    The wrong mux index means the software is looking at the wrong Type-C path.

    The hardware can therefore support the connection while Linux is effectively listening somewhere else.

    The resulting configuration identifies the PHY as the DisplayPort transmitter and connects it to the correct Type-C mux.

    3. Associate the USB-C connector with dcpext

    The final device-tree change is connecting the DisplayPort controller to the actual USB-C connector.

    The relevant connector is:

    text
    usb-pd@3f

    which is labelled:

    text
    USB-C Left-front

    The patch adds a displayport phandle pointing to dcpext.

    Conceptually, this creates the relationship:

    text
    USB-C Left-front └── displayport dcpext

    That relationship becomes important later when the Type-C driver receives a hot-plug event and needs to tell DRM which connector changed state.

    Why the DTB patch wasn't enough

    At this point, the display controller could be configured correctly.

    But plugging in the monitor still didn't automatically give Linux a connected DRM connector.

    The missing component was HPD — Hot Plug Detect.

    DisplayPort uses HPD to signal that a display has been connected or disconnected.

    The M2 Air's Type-C controller knows about this state.

    Linux's DRM subsystem needs to know about it too.

    The problem was that the existing tps6598x-core driver wasn't forwarding that information to DRM in this particular Apple Silicon configuration.

    So the architecture looked something like this:

    text
    Monitor │ DisplayPort USB-C hardware TPS6598x / CD321x │ HPD X [missing path] DRM

    The physical event was happening.

    The kernel simply wasn't passing it along.

    Patching tps6598x-core

    The second part of the solution is therefore a patched version of the Linux tps6598x-core driver.

    Rather than maintaining an entirely separate driver, I rebuilt the current Asahi kernel module out of tree and applied the relevant DisplayPort HPD changes from the Asahi fairydust branch.

    The change monitors data_status on the CD321x controller.

    When the DisplayPort hot-plug state changes, it calls:

    text
    drm_connector_oob_hotplug_event()

    on the connector identified through the device tree's displayport property.

    This provides the missing path:

    text
    CD321x │ data_status HPD detection drm_connector_oob_hotplug_event() DRM Display connector

    That is why both patches are necessary.

    The DTB provides the hardware and connector relationship.

    The kernel module provides the event path.

    The complete solution

    The resulting architecture looks like this:

    text
    USB-C Display USB-C Left-front usb-pd@3f Type-C controller TPS6598x/CD321x │ HPD patched tps6598x-core drm_connector_oob_hotplug_event DRM dcpext DisplayPort PHY atcphy1 Monitor

    The patched device tree establishes the hardware relationships.

    The patched Type-C driver establishes the hot-plug event path.

    Together, they make the external display visible to Linux.

    The result

    After installing both pieces and rebooting with the monitor connected, the DRM subsystem reports:

    text
    card2-DP-1: connected modes=32

    The kernel also reports the hot-plug event:

    text
    #### oob_hotplug status:0x1 ####

    and the Apple display controller subsequently configures the digital output.

    A real test display successfully negotiated:

    text
    2560x1440 @ 180 Hz

    The verification script checks the entire chain, including the enabled dcpext node, its alias, the DisplayPort PHY, mux index 2, the correct USB-C connector, the DRM connector state, and the patched kernel module.

    So this isn't simply a case of Linux detecting an unknown monitor.

    The full DisplayPort path is functioning.

    Which USB-C port works?

    There is an easy-to-miss hardware detail here.

    The working port is the USB-C port on the left edge closest to the front of the laptop, away from the hinge.

    It is identified by the device tree as:

    text
    USB-C Left-front usb-pd@3f

    The other left-side USB-C port, usb-pd@38, isn't the DisplayPort connector used by this configuration.

    The repository includes a helper script that maps the probed ports back to their device-tree labels, since the Linux portN numbering itself shouldn't be treated as a permanent physical-port identifier.

    Making the modification safely

    Because this involves modifying the boot chain and replacing a kernel module, I wanted the process to be as deterministic as possible.

    The installation therefore has several safeguards.

    Before modifying boot.bin, the installer creates a backup on the EFI System Partition.

    The DTB build process is also deliberately strict.

    It downloads the matching Asahi kernel source, builds the stock M2 Air DTB, and compares that freshly generated DTB against the DTB shipped with the installed kernel.

    It will only apply the patch if those match byte-for-byte.

    That prevents a DTB generated for one kernel version from accidentally being installed alongside another.

    The installation then embeds the patched DTB into the m1n1 boot chain.

    The kernel module is similarly compiled against the installed kernel's build tree before installation, with the required DRM symbol checked during the build.

    No firmware is flashed as part of the process.

    Kernel updates

    The main maintenance consideration is kernel updates.

    The patch isn't a generic DTB that can safely be carried across arbitrary Asahi kernel versions.

    When linux-asahi or m1n1 changes, the DTB and kernel module should be rebuilt.

    The intended workflow is:

    bash
    sudo pacman -Syu ./t8112/scripts/rebuild-t8112.sh sudo ./t8112/scripts/install-t8112.sh ./kmod/build.sh sudo ./kmod/hotswap.sh install sudo reboot

    The DTB validation step is important here because it intentionally stops if the newly generated stock DTB no longer matches the expected structure.

    Rather than silently producing something that may boot incorrectly, it forces the patch to be reviewed or regenerated for the new kernel version.

    Recovery

    The modification is also reversible.

    From Linux, the repository provides scripts to restore the stock kernel module and DTBs:

    bash
    sudo ./kmod/hotswap.sh uninstall sudo ./t8112/scripts/revert-t8112.sh sudo reboot

    If Linux won't boot, the original boot.bin can be restored from macOS by mounting the EFI partition and copying the pre-patch backup back over m1n1/boot.bin.

    After returning to Linux, the normal revert process removes the configuration override so that subsequent m1n1 updates don't reinstall the patched DTBs.

    Current limitations

    The patch is specifically about getting the external DisplayPort video path working.

    There are still limitations.

    DisplayPort audio is not currently implemented by this patch.

    There can also be display resynchronisation issues on Linux wake with some monitors, where reconnecting the cable restores the display.

    And because the implementation modifies hardware-specific device-tree and kernel-driver behaviour, it is tied to the relevant Asahi/M2 Air configuration rather than being a universal Apple Silicon DisplayPort solution.

    The source of the implementation

    This project is not intended to replace the upstream Asahi implementation or claim the underlying work as new.

    The M1 implementation by haripako/dp-altmode provided the original structure and approach.

    The M2-specific device-tree wiring comes from the Asahi Linux fairydust branch, specifically the t8112-j413.dts / t8112-jxxx.dtsi configuration.

    m1n1 provides the apple,t8112-dcpext handling required to reserve the external display controller's firmware.

    The tps6598x-core changes are based on the upstream driver with the relevant DisplayPort HPD changes from the fairydust work backported for this setup.

    The repository is released under GPL-2.0-only, consistent with the Linux kernel code the module is derived from.

    Source code

    The complete implementation—including the device-tree patch, build scripts, kernel module patch, installation scripts, verification tools, and recovery procedure—is available here:

    GitHub — dp-altmode-t8112

    The target configuration is:

    • MacBook Air 13-inch, M2, 2022
    • apple,j413
    • apple,t8112
    • Asahi Linux / Arch
    • m1n1 1.6.1+
    • linux-asahi
    • USB-C DisplayPort output through the left-front USB-C port

    The tested result is 2560×1440 @ 180 Hz.

    What looked initially like a missing DisplayPort feature turned out to be a missing connection between several existing pieces: the device tree, m1n1's external display firmware handling, the Type-C controller, HPD events, and Linux DRM.

    Once those pieces were wired together, the M2 Air's USB-C DisplayPort hardware was there all along.