[PATCH 2/3] usb: roles: look up role switches attached to a graph endpoint

From: Qianhui Xu

Date: Tue Nov 03 2026 - 22:28:54 EST


usb_role_switch_get() and fwnode_usb_role_switch_get() look for a switch
on the caller's parent device node or through device_connection. That does
not work when one controller exposes a role switch per connector endpoint:
the switch is registered against the remote graph endpoint and the
consumer, the connector, has no phandle to it.

Add usb_role_switch_at_endpoint(), which walks the caller's OF graph
endpoints and matches a switch registered on the remote endpoint, and try
it after the existing parent lookup. A switch that is described by the
graph but not registered yet comes back as -EPROBE_DEFER, as the rest of
the role-switch API does, so the caller retries instead of concluding that
the connector has no role switch at all. There is no behaviour change for
existing users: if nothing is registered against an endpoint the lookup
falls through to the previous paths.

This is needed by DWC3 designs where two Type-C receptacles share one High
Speed data bus.

Signed-off-by: Qianhui Xu <xiaqinghuan@xxxxxxxxxxxxxxxx>
---
drivers/usb/roles/class.c | 45 +++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)

diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c
index edec139b68..ee186d542a 100644
--- a/drivers/usb/roles/class.c
+++ b/drivers/usb/roles/class.c
@@ -157,6 +157,47 @@ usb_role_switch_is_parent(struct fwnode_handle *fwnode)
return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
}

+/*
+ * Look up a role switch registered against the remote endpoint of one of the
+ * caller's OF graph endpoints. This is how a connector endpoint that shares a
+ * USB data bus with another connector (for example two Type-C receptacles on a
+ * single DWC3 controller, selected by a GPIO mux) finds its own role switch.
+ */
+static struct usb_role_switch *
+usb_role_switch_at_endpoint(struct fwnode_handle *fwnode)
+{
+ struct fwnode_handle *ep, *remote, *parent;
+ struct usb_role_switch *sw;
+ bool available;
+
+ fwnode_graph_for_each_endpoint(fwnode, ep) {
+ parent = fwnode_graph_get_remote_port_parent(ep);
+ if (!parent)
+ continue;
+ available = fwnode_device_is_available(parent);
+ fwnode_handle_put(parent);
+ if (!available)
+ continue;
+
+ remote = fwnode_graph_get_remote_endpoint(ep);
+ if (!remote)
+ continue;
+ sw = usb_role_switch_match(remote, "usb-role-switch", NULL);
+ fwnode_handle_put(remote);
+ /*
+ * ERR_PTR(-EPROBE_DEFER) means the switch is described by the graph
+ * but not registered yet: pass it up so the caller defers instead of
+ * concluding that this connector has no role switch at all.
+ */
+ if (sw) {
+ fwnode_handle_put(ep);
+ return sw;
+ }
+ }
+
+ return NULL;
+}
+
/**
* usb_role_switch_get - Find USB role switch linked with the caller
* @dev: The caller device
@@ -169,6 +210,8 @@ struct usb_role_switch *usb_role_switch_get(struct device *dev)
struct usb_role_switch *sw;

sw = usb_role_switch_is_parent(dev_fwnode(dev));
+ if (!sw)
+ sw = usb_role_switch_at_endpoint(dev_fwnode(dev));
if (!sw)
sw = device_connection_find_match(dev, "usb-role-switch", NULL,
usb_role_switch_match);
@@ -192,6 +235,8 @@ struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode)
struct usb_role_switch *sw;

sw = usb_role_switch_is_parent(fwnode);
+ if (!sw)
+ sw = usb_role_switch_at_endpoint(fwnode);
if (!sw)
sw = fwnode_connection_find_match(fwnode, "usb-role-switch",
NULL, usb_role_switch_match);