diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 29321f8032a490..41f8fdacebf3bb 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) { @@ -1518,10 +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) + 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) 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 {