diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 29321f8032a490..b69680d24109e1 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -1378,6 +1378,52 @@ static int dcp_typec_port_busy(struct apple_dcp_typec_port *port, return -ENOSPC; } +/* + * @waiting has a display (HPD up) but no pipeline. A direct DP-alt port that + * holds one of @waiting's pipelines with no display behind it (its sink has + * never asserted HPD, as a USB-C hub with a DP output and nothing plugged in) + * gives it up: a pipeline goes to a display, not to whichever port entered DP + * mode first. The idle port keeps DP mode and is routed again when a pipeline + * is free and its own sink asserts HPD. Returns true if one was released. + */ +static bool dcp_typec_reclaim_idle(struct apple_dcp_typec_port *waiting) +{ + struct apple_dcp_typec_route *route; + struct apple_dcp_typec_port *idle; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(route, &waiting->routes, port_link) { + list_for_each_entry(idle, &dcp_typec_ports, link) { + struct apple_dcp_typec_route *owner = idle->owner; + struct apple_dcp *dcp; + const char *from = NULL, *to = NULL; + + if (idle == waiting || !owner || owner->tunnel || + owner->dcp != route->dcp || idle->safe_pending || + idle->dp_hpd || idle->hpd) + continue; + dcp = owner->dcp; + if (dcp->typec_cable_connected || + (dcp->typec_connector && + READ_ONCE(dcp->typec_connector->connected))) + continue; + if (dcp_typec_route_deactivate(owner)) + return false; + idle->preferred_route = owner; + idle->owner = NULL; + idle->busy_noted = false; + of_property_read_string(idle->connector_np, "label", &from); + of_property_read_string(waiting->connector_np, "label", &to); + dev_info(dcp->dev, + "external display pipeline moves from %s (no display) to the display on %s\n", + from ?: "a USB-C port", to ?: "a USB-C port"); + return true; + } + } + return false; +} + static int dcp_typec_route_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { @@ -1519,6 +1565,9 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, if (!port->owner) { best = dcp_typec_free_route(port); + /* A pipeline held by a port with no display goes to this one. */ + if (!best && hpd && dcp_typec_reclaim_idle(port)) + best = dcp_typec_free_route(port); /* Every pipeline is driving another port's display. */ if (!best) return hpd ? dcp_typec_port_busy(port, route->dcp) : -ENOSPC;