From 4da4932b130b21d7367d56859f558c4e8a8f101f Mon Sep 17 00:00:00 2001 From: Andreas Fernandez Date: Fri, 23 Oct 2026 11:10:03 +0200 Subject: [PATCH 1/3] drm/apple: dcp: let a Type-C port with HPD take over a pipeline held without it The PD controllers on the M1 Pro/Max laptops report a DP alt mode state at probe for ports that have no display behind them, for example an empty port or a dock without a display. Whichever port is routed first gets a Type-C pipeline. If that is the wrong one, the port with the display finds every pipeline taken (-ENOSPC) and stays dark until the other port is unplugged, which can also put the LUKS prompt on the internal panel of a closed laptop. When a port whose sink asserts HPD finds no free pipeline, release one held by a direct DP-alt port whose sink never asserted HPD and take it. Tunnels, ports with a pending safe state and pipelines whose fixed output is in use are left alone. The released port drops its applied state so its next update is routed again once a pipeline is free. Link: https://github.com/omacom/omarchy-pkgs/issues/1037 Signed-off-by: Andreas Fernandez --- drivers/gpu/drm/apple/dcp-fabric.c | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 29321f8032a490..afdeaa974fe165 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -1378,6 +1378,45 @@ static int dcp_typec_port_busy(struct apple_dcp_typec_port *port, return -ENOSPC; } +/* + * PD controllers report a DP state at probe for ports with no display behind + * them, an empty port or one with a dock that has none, and whichever port is + * routed first wins the pipeline. A port whose sink asserts HPD and finds no + * free pipeline takes one over from a direct DP-alt port whose sink never + * did. Tunnels, ports in a SAFE transition and pipelines whose fixed output + * is in use are left alone. The released port drops its applied state, so + * its next update is routed again once a pipeline is free. + */ +static struct apple_dcp_typec_route * +dcp_typec_take_over_without_hpd(struct apple_dcp_typec_port *port) +{ + struct apple_dcp_typec_route *candidate; + + list_for_each_entry(candidate, &port->routes, port_link) { + struct apple_dcp_typec_route *held = + candidate->dcp->active_typec_route; + int ret; + + if (!held || held->tunnel || held->port == port || + held->port->hpd || held->port->dp_hpd || + held->port->safe_pending || + dcp_typec_route_busy(candidate)) + continue; + + ret = dcp_typec_route_deactivate(held); + if (ret) + return ERR_PTR(ret); + held->port->owner = NULL; + held->port->applied_valid = false; + dev_info(candidate->dcp->dev, + "released Type-C DPTX PHY %u held without HPD\n", + held->dptx_phy); + return candidate; + } + + return NULL; +} + static int dcp_typec_route_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { @@ -1519,6 +1558,10 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, if (!port->owner) { best = dcp_typec_free_route(port); + if (!best && hpd) + best = dcp_typec_take_over_without_hpd(port); + if (IS_ERR(best)) + return PTR_ERR(best); /* Every pipeline is driving another port's display. */ if (!best) return hpd ? dcp_typec_port_busy(port, route->dcp) : -ENOSPC; From bd1ad7b9b2795b7d3b3c7e616a54b78f9c92c6e0 Mon Sep 17 00:00:00 2001 From: Andreas Fernandez Date: Fri, 23 Oct 2026 11:10:03 +0200 Subject: [PATCH 2/3] drm/apple: dcp: route a DP-alt port only once its sink asserts HPD Outside compositor pairing order, a direct DP-alt port takes the lowest free pipeline as soon as it enters DP mode, whether or not a display is behind it. A USB-C dock without a display does exactly that, and on an M1 Pro with two pipelines it leaves one display dark when it is plugged in before them. The previous patch can take the pipeline back, but a hand-over 6 ms after the dock's route was set left the firmware's DPTX service on that DCP answering -110 until reboot. Leave a port that owns no pipeline and has no HPD unrouted, and route it when the port controller reports HPD as a new status. A port that already owns a pipeline keeps it across an HPD drop, as before, and the compositor-ordered path is unchanged. Signed-off-by: Andreas Fernandez --- drivers/gpu/drm/apple/dcp-fabric.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index afdeaa974fe165..41f8fdacebf3bb 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -1557,14 +1557,27 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, } if (!port->owner) { + /* + * Nothing behind the port asserts HPD yet, a dock without a + * display for one: leave the pipelines to ports with a display. + * The port controller reports HPD as a new status when a sink + * appears, and the port is routed then. A port that already + * owns a pipeline keeps it across an HPD drop, as before. + */ + if (!hpd) { + port->hpd = false; + port->applied_valid = true; + return 0; + } + best = dcp_typec_free_route(port); - if (!best && hpd) + if (!best) best = dcp_typec_take_over_without_hpd(port); if (IS_ERR(best)) return PTR_ERR(best); /* Every pipeline is driving another port's display. */ if (!best) - return hpd ? dcp_typec_port_busy(port, route->dcp) : -ENOSPC; + return dcp_typec_port_busy(port, route->dcp); ret = dcp_typec_route_activate(best, best->xbar); if (ret) From 2a37f6f206456462970b3bbb3bf9bf8bd8944acd Mon Sep 17 00:00:00 2001 From: Andreas Fernandez Date: Fri, 23 Oct 2026 11:10:03 +0200 Subject: [PATCH 3/3] usb: typec: tipd: treat -ENOSPC as a taken display on every SoC The DCP fabric now reports a port without a pipeline as -ENOSPC on every SoC, but cd321x_display_taken() only accepts it on H17 and expects -EBUSY elsewhere. On an M1 Pro, a dock in DP alt mode with both pipelines driving other displays gets "failed to configure Type-C connection: -28" three times, the cable revalidation fails and the USB role is never set, so everything behind the dock is gone. Accept -ENOSPC as a taken display everywhere and keep -EBUSY as one outside H17 for older fabrics. Signed-off-by: Andreas Fernandez --- drivers/usb/typec/tipd/cd321x-pm-test.c | 2 +- drivers/usb/typec/tipd/cd321x-pm.h | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/cd321x-pm-test.c b/drivers/usb/typec/tipd/cd321x-pm-test.c index aef346d7bb4d8b..51d6d86f39e58a 100644 --- a/drivers/usb/typec/tipd/cd321x-pm-test.c +++ b/drivers/usb/typec/tipd/cd321x-pm-test.c @@ -713,7 +713,7 @@ static void cd321x_display_taken_test(struct kunit *test) KUNIT_EXPECT_FALSE(test, cd321x_display_taken(0, true)); /* Other SoCs keep -EBUSY as the taken display. */ KUNIT_EXPECT_TRUE(test, cd321x_display_taken(-EBUSY, false)); - KUNIT_EXPECT_FALSE(test, cd321x_display_taken(-ENOSPC, false)); + KUNIT_EXPECT_TRUE(test, cd321x_display_taken(-ENOSPC, false)); KUNIT_EXPECT_FALSE(test, cd321x_display_taken(-EAGAIN, false)); KUNIT_EXPECT_FALSE(test, cd321x_display_taken(0, false)); } diff --git a/drivers/usb/typec/tipd/cd321x-pm.h b/drivers/usb/typec/tipd/cd321x-pm.h index 8260e559b0c87f..b3f1bc8fad8567 100644 --- a/drivers/usb/typec/tipd/cd321x-pm.h +++ b/drivers/usb/typec/tipd/cd321x-pm.h @@ -355,12 +355,14 @@ static inline void cd321x_pm_remove(struct cd321x_pm_state *pm) /* * A display mux reports -ENOSPC when every display pipeline is driving * another port. That lasts until the other display goes away, so the port's - * USB path is set up without the display and nothing is retried. Any other - * error, such as -EBUSY from a PHY in the middle of a transition, is retried. + * USB path is set up without the display and nothing is retried. The DCP + * fabric reports it as -ENOSPC on every SoC now; older trees used -EBUSY + * outside H17, which is still accepted there. Any other error, such as + * -EBUSY from an H17 PHY in the middle of a transition, is retried. */ static inline bool cd321x_display_taken(int ret, bool enospc) { - return ret == (enospc ? -ENOSPC : -EBUSY); + return ret == -ENOSPC || (!enospc && ret == -EBUSY); } enum cd321x_dp_send {