Skip to content

[fairydust] DP alt mode on any USB-C port: experiments and a proposal (t6000) #637

Description

@makhov

While debugging #634 I looked at why DP only works on USB-C Left Front on the M1 Pro. As far as I can tell it's only the static wiring in the DT (ENABLE_DCPEXT_TYPEC, /* hacks */), not a hardware or firmware limit. I tested that on a MacBookPro18,3 (t6000, j314s) with fairydust ce9f2eba72c0, and I'd like to check a direction before writing real code.

How it's wired today

  • dcpext0 is fixed to the HDMI port (atcphy3, mux-index = <0>), and dcpext1 to Left Front (atcphy1, mux-index = <2>, apple,dptx-phy = <1>, displayport = <&dcpext1> on typec1 only).
  • dcp_platform_probe() selects the crossbar once and never deselects it. dcp->phy and dcp->dptx_phy come from the DT at probe time.
  • There is one DRM connector per DCP, and its fwnode is the DCP node.

The pieces for doing this dynamically are mostly there already:

  • Each ATC PHY has its own display crossbar, and on t6000 it accepts any dispext (n_ufp = 9, state = dispext × 2 + sub-output). Deselecting it goes back to MUX_IDLE_DISCONNECT.
  • The firmware gets the target PHY with every dptxport_connect(service, 0, atc, die) call, not once at boot.

Experiments

  1. dcpext1 statically on Left Back. A DT-only change: phys = <&atcphy0 PHY_TYPE_DP>, mux-controls = <&atcphy0_xbar 0>, mux-index = <2>, apple,dptx-phy = <0>, and displayport moved to typec0. The crossbar logged 70304c000.mux: Switched dpphy to dispext1,0. A plug on Left Back gave link, EDID (19 modes) and a 1920x1080@60 modeset, with no new firmware errors, and unplug tore down cleanly.

  2. Moving dcpext1 between two ports at runtime. This was throwaway code: dcpext1 got both PHYs and crossbars in the DT, plus a debugfs knob. While nothing is connected, the knob deselects the old crossbar, mux_control_try_select()s the new one, and swaps dcp->phy and dcp->dptx_phy. It worked in one boot, both ways, with no reboot or firmware restart:

    302.050019 b0304c000.mux: Switched dpphy to disconnected state
    302.050342 70304c000.mux: Switched dpphy to dispext1,0
    302.050568 28cc00000.dcp: DP route 0 -> 1, dptx target phy 0
    323.289997 28cc00000.dcp: dcp_dptx_connect(port=0)          <- Left Back, picture
    344.228438 b0304c000.mux: Switched dpphy to dispext1,0
    344.228723 28cc00000.dcp: DP route 1 -> 0, dptx target phy 1
    349.084967 28cc00000.dcp: dcp_dptx_connect(port=0)          <- Left Front, picture
    

    The test also shows why a shared connector doesn't work: with both ports' displayport pointing at dcpext1, disconnect events from either port land on the same connector.

  3. HDMI (by reading the code, not tested). The HDMI converter is on atcphy3, and its crossbar should take dispext1 just as well. dcpext0 is tied to HDMI only because the HDMI port's GPIOs, PHY and crossbar sit in the dcpext0 node.

Proposal

  • One DRM connector per physical port (the three USB-C ports plus HDMI), with the usb-c-connector node as connector->fwnode. typec/altmodes/displayport.c already falls back to the port fwnode, so OOB HPD stays one-to-one and userspace gets stable per-port outputs.
  • dcpext as a pool, bound at plug time. On HPD: pick a free dcpext, select that port's crossbar, point the DCP at the port's PHY and ATC index, power the port's domain, then connect. The firmware reads EDID after connect, so binding can't wait until modeset. On unplug, release everything. If no dcpext is free, the connector says so in the log instead of going black.
  • HDMI is just another member of the pool, not a permanent owner of dcpext0. On t6000 that means any two of HDMI plus the three USB-C ports, which matches Apple's two-external-display spec.
  • DT bindings that describe per-port links (connector → PHY/crossbar) instead of per-DCP phandles, replacing ENABLE_DCPEXT_TYPEC.
  • Longer term, drop the tipd OOB hack in favour of the mainline DP altmode path (as on sven/atcphy).

Open questions

  1. Is per-port connector + dcpext pool the direction you'd want, or do you have another design in mind?
  2. How should the DT binding look? Where should the port → crossbar/PHY/ATC-index information live, and what's left in the DCP nodes?
  3. Boot handoff: dcpext0 is "enabled by the loader". Does m1n1/iBoot leave HDMI running on dcpext0, so the pool has to adopt that state at probe?
  4. Power domains: ps_atc1_common and ps_atc3_common are forced always-on for DP-alt/HDMI resume. Is controlling them per bind realistic, or is there a known reason they must stay on?
  5. What are dispextN,1 and the dpin0/dpin1 crossbar controls? Are they the second dptxport unit, or USB4 tunnelling?
  6. For t6001/t6002: is the die field in dptxport_connect absolute or relative to the DCP's die?

If this direction sounds right, I'm happy to implement it in pieces (per-port connectors, dcpext allocator + crossbar handling, power domains, bindings) and test it on j314s.

Experiment code (throwaway, not for merging): makhov@68538f3aab9d on branch exp/dp-runtime-route.

DT change used for experiment 2
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -156,6 +156,8 @@
 		typec0: connector {
 			compatible = "usb-c-connector";
 			label = "USB-C Left Back";
+			/* EXPERIMENT: DP runtime route 1 */
+			displayport = <&dcpext1>;
 			power-role = "dual";
 			data-role = "dual";
 
@@ -281,10 +283,11 @@
 
 	/* hacks */
 	apple,dptx-phy = <1>;
-	phys = <&atcphy1 PHY_TYPE_DP>;
-	phy-names = "dp-phy";
-	mux-controls = <&atcphy1_xbar 0>;
-	mux-control-names = "dp-xbar";
+	apple,dptx-phy-alt = <0>;
+	phys = <&atcphy1 PHY_TYPE_DP>, <&atcphy0 PHY_TYPE_DP>;
+	phy-names = "dp-phy", "dp-phy-alt";
+	mux-controls = <&atcphy1_xbar 0>, <&atcphy0_xbar 0>;
+	mux-control-names = "dp-xbar", "dp-xbar-alt";
 	mux-index = <2>;
 };
 
@@ -296,6 +299,14 @@
         status = "okay";
 };
 
+&atcphy0_xbar {
+	status = "okay";
+};
+
+&ps_atc0_common {
+	apple,always-on;
+};
+
 &ps_atc1_common {
 	apple,always-on; /* Needs to stay on for DP-alt suspend/resume */
 };

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions