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

From: Qianhui Xu

Date: Mon Oct 26 2026 - 08:34:27 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. 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 | 40 +++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)

diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c
index edec139..fc739fd 100644
--- a/drivers/usb/roles/class.c
+++ b/drivers/usb/roles/class.c
@@ -157,6 +157,42 @@ 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);
+ if (!IS_ERR_OR_NULL(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 +205,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 +230,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);
--
2.53.0