* [PATCH v4 1/3] dt-bindings: usb: snps,dwc3: describe two connectors sharing one HS bus
2026-10-26 12:32 [PATCH v4 0/3] usb: dwc3: support two connectors sharing one HS data bus Qianhui Xu
@ 2026-10-26 12:32 ` Qianhui Xu
2026-11-02 8:13 ` Krzysztof Kozlowski
2026-10-26 12:32 ` [PATCH v4 2/3] usb: roles: look up role switches attached to a graph endpoint Qianhui Xu
2026-10-26 12:32 ` [PATCH v4 3/3] usb: dwc3: support two connectors sharing one HS data bus Qianhui Xu
2 siblings, 1 reply; 5+ messages in thread
From: Qianhui Xu @ 2026-10-26 12:32 UTC (permalink / raw)
To: Greg Kroah-Hartman, Thinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-usb, devicetree, linux-arm-msm, linux-kernel
Two connectors can share the High Speed D+/D- pair of a single DWC3
controller, with a GPIO selecting which one is connected. Super Speed
lanes stay per connector.
Describe that in snps,dwc3-common.yaml:
- port-select-gpios selects which connector drives the shared HS bus;
- the shared HS bus is port@0 with two numbered endpoints, endpoint@0
and endpoint@1, so that "ports" keeps describing the controller's own
buses as the graph bindings expect;
- each endpoint carries "mux-state", the port-select level of that
connector, and "usb-role-switch" to declare that this connector has
its own role switch input.
When port-select-gpios is absent the endpoint properties are rejected,
so existing single-connector descriptions are unaffected. The two
mux-state values are constrained to be complementary.
Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
---
.../bindings/usb/snps,dwc3-common.yaml | 109 +++++++++++++++++-
1 file changed, 108 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml
index 6c0b8b6..6b06b00 100644
--- a/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml
+++ b/Documentation/devicetree/bindings/usb/snps,dwc3-common.yaml
@@ -13,6 +13,38 @@ description:
Defines the properties of the DWC3 core as being embedded in either an
vendor-specific implementation or as a standalone component.
+$defs:
+ hs-endpoint:
+ $ref: /schemas/graph.yaml#/$defs/endpoint-base
+ unevaluatedProperties: false
+
+ properties:
+ usb-role-switch:
+ type: boolean
+ description:
+ This connector endpoint has its own USB role switch. The switch is
+ registered against this endpoint and looked up with
+ usb_role_switch_at_endpoint().
+
+ mux-state:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+ description:
+ Logical level of the port-select GPIO which connects this
+ connector to the shared High Speed data bus.
+
+ dependentRequired:
+ usb-role-switch: [mux-state]
+ mux-state: [usb-role-switch]
+
+ hs-port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+
+ patternProperties:
+ '^endpoint@[01]$':
+ $ref: '#/$defs/hs-endpoint'
+
allOf:
- $ref: usb-drd.yaml#
- if:
@@ -26,8 +58,83 @@ allOf:
$ref: usb.yaml#
else:
$ref: usb-xhci.yaml#
+ - if:
+ required: [port-select-gpios]
+ then:
+ required: [dr_mode, ports]
+ properties:
+ dr_mode:
+ const: otg
+ ports:
+ required: [port@0]
+ properties:
+ port@0:
+ required: ['#address-cells', '#size-cells', endpoint@0, endpoint@1]
+ properties:
+ endpoint@0:
+ required: [usb-role-switch, mux-state]
+ endpoint@1:
+ required: [usb-role-switch, mux-state]
+ else:
+ properties:
+ ports:
+ properties:
+ port@0:
+ patternProperties:
+ '^endpoint@[01]$':
+ properties:
+ usb-role-switch: false
+ mux-state: false
+ - if:
+ properties:
+ ports:
+ properties:
+ port@0:
+ properties:
+ endpoint@0:
+ properties:
+ mux-state:
+ const: 0
+ then:
+ properties:
+ ports:
+ properties:
+ port@0:
+ properties:
+ endpoint@1:
+ properties:
+ mux-state:
+ const: 1
+ - if:
+ properties:
+ ports:
+ properties:
+ port@0:
+ properties:
+ endpoint@1:
+ properties:
+ mux-state:
+ const: 0
+ then:
+ properties:
+ ports:
+ properties:
+ port@0:
+ properties:
+ endpoint@0:
+ properties:
+ mux-state:
+ const: 1
properties:
+ port-select-gpios:
+ maxItems: 1
+ description:
+ Selects which of the two connectors drives the controller's shared High
+ Speed data bus. Each connector is described by a numbered endpoint of
+ "port@0" that carries its own role-switch input and the "mux-state"
+ level selecting it.
+
extcon:
maxItems: 1
deprecated: true
@@ -411,7 +518,7 @@ properties:
properties:
port@0:
- $ref: /schemas/graph.yaml#/properties/port
+ $ref: '#/$defs/hs-port'
description: High Speed (HS) data bus.
port@1:
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v4 2/3] usb: roles: look up role switches attached to a graph endpoint
2026-10-26 12:32 [PATCH v4 0/3] usb: dwc3: support two connectors sharing one HS data bus Qianhui Xu
2026-10-26 12:32 ` [PATCH v4 1/3] dt-bindings: usb: snps,dwc3: describe two connectors sharing one HS bus Qianhui Xu
@ 2026-10-26 12:32 ` Qianhui Xu
2026-10-26 12:32 ` [PATCH v4 3/3] usb: dwc3: support two connectors sharing one HS data bus Qianhui Xu
2 siblings, 0 replies; 5+ messages in thread
From: Qianhui Xu @ 2026-10-26 12:32 UTC (permalink / raw)
To: Greg Kroah-Hartman, Thinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-usb, devicetree, linux-arm-msm, linux-kernel
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@mail.ustc.edu.cn>
---
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
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v4 3/3] usb: dwc3: support two connectors sharing one HS data bus
2026-10-26 12:32 [PATCH v4 0/3] usb: dwc3: support two connectors sharing one HS data bus Qianhui Xu
2026-10-26 12:32 ` [PATCH v4 1/3] dt-bindings: usb: snps,dwc3: describe two connectors sharing one HS bus Qianhui Xu
2026-10-26 12:32 ` [PATCH v4 2/3] usb: roles: look up role switches attached to a graph endpoint Qianhui Xu
@ 2026-10-26 12:32 ` Qianhui Xu
2 siblings, 0 replies; 5+ messages in thread
From: Qianhui Xu @ 2026-10-26 12:32 UTC (permalink / raw)
To: Greg Kroah-Hartman, Thinh Nguyen, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-usb, devicetree, linux-arm-msm, linux-kernel
On some boards a single DWC3 controller drives two receptacles: the Super
Speed pairs are wired to separate PHYs, while the High Speed data lines
are muxed to whichever connector is in use. Such a controller has to
service two independent role-switch inputs, and the shared data path has
to be moved before the controller is reinitialised for the requested role.
Register one usb_role_switch per connector endpoint described by
"port-select-gpios" and the per-endpoint "mux-state" property. Requests
from the two switches are serialised, the oldest pending request wins, and
the port-select GPIO is only changed once the previous host or gadget
instance has been torn down, which is where __dwc3_set_mode() runs. A
connector only reports its own role while it owns the data path, so the
connector drivers see a consistent view of a shared controller.
The role change keeps the asynchronous semantics of the existing
single-connector role switch: __dwc3_set_mode() is the only place that
programs hardware, and it never aborts half-way, so a failed GPIO write
cannot leave a torn-down role behind. The error is kept in the mux state
and reported back to the connector on its next role request.
While the two-connector mux owns role selection, the debugfs "mode" write
used to force a specific role is refused instead of racing with it.
Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
---
Changes in v4:
- register both role switches before the initial dwc3_set_mode(), and
cancel the queued role work on the init error path (Critical)
- take the request order and the resulting mode request in one critical
section instead of dropping the mux lock around dwc3_set_mode() (High)
- only take a request ticket on a NONE -> role transition, so an in-place
role swap cannot steal the shared data path from the connector that
asked first (High)
- order requests with a monotonic counter instead of jiffies, removing
the time_before() wraparound (High)
- drop the unused desired_role field and report the sticky mux error to
the connector instead of keeping it unread (Medium)
Changes in v3:
- binding moved to "port-select-gpios" plus numbered endpoints carrying
"mux-state" and "usb-role-switch"; the endpoint lookup helper is part of
the series
---
drivers/usb/dwc3/core.c | 29 ++++-
drivers/usb/dwc3/core.h | 39 +++++++
drivers/usb/dwc3/debugfs.c | 4 +
drivers/usb/dwc3/drd.c | 219 ++++++++++++++++++++++++++++++++++++-
4 files changed, 289 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index fd5c2cd..6607e65 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -21,6 +21,7 @@
#include <linux/io.h>
#include <linux/list.h>
#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/of_graph.h>
@@ -165,7 +166,9 @@ EXPORT_SYMBOL_GPL(dwc3_set_prtcap);
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
+ struct dwc3_role_mux *mux = dwc->role_mux;
unsigned long flags;
+ int mux_port = 0;
int ret;
u32 reg;
u32 desired_dr_role;
@@ -174,6 +177,8 @@ static void __dwc3_set_mode(struct work_struct *work)
mutex_lock(&dwc->mutex);
spin_lock_irqsave(&dwc->lock, flags);
desired_dr_role = dwc->desired_dr_role;
+ if (mux)
+ mux_port = mux->desired_port;
spin_unlock_irqrestore(&dwc->lock, flags);
pm_runtime_get_sync(dwc->dev);
@@ -184,7 +189,8 @@ static void __dwc3_set_mode(struct work_struct *work)
if (!desired_dr_role)
goto out;
- if (desired_dr_role == dwc->current_dr_role)
+ if (desired_dr_role == dwc->current_dr_role &&
+ (!mux || mux_port == mux->active_port))
goto out;
if (desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev)
@@ -233,6 +239,27 @@ static void __dwc3_set_mode(struct work_struct *work)
dwc3_writel(dwc, DWC3_GCTL, reg);
}
+ /*
+ * The previous host or gadget instance has been torn down above, so the
+ * shared High Speed data path can now be moved to the requesting
+ * connector. A failure here must not abort the mode change half-way:
+ * the controller would be left with a torn-down role and a stale
+ * current_dr_role, which the next request would tear down a second time.
+ */
+ if (mux && mux_port != mux->active_port) {
+ ret = gpiod_direction_output(mux->select, mux_port);
+ if (ret) {
+ WRITE_ONCE(mux->error, ret);
+ dev_err_ratelimited(dwc->dev,
+ "failed to select USB port %d: %d\n",
+ mux_port, ret);
+ } else {
+ spin_lock_irqsave(&dwc->lock, flags);
+ mux->active_port = mux_port;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ }
+ }
+
spin_lock_irqsave(&dwc->lock, flags);
dwc3_set_prtcap(dwc, desired_dr_role, false);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index f3dabb3..1238376 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1008,6 +1008,43 @@ struct dwc3_glue_ops {
void (*pre_run_stop)(struct dwc3 *dwc, bool is_on);
};
+/* Per-connector state of the optional shared-HS-data-bus mux. */
+struct dwc3_role_port {
+ struct dwc3 *dwc;
+ struct usb_role_switch *sw;
+ struct fwnode_handle *fwnode;
+ enum usb_role role;
+ unsigned int state;
+ /* request order, see dwc3_role_mux.seq */
+ u64 seq;
+};
+
+/**
+ * struct dwc3_role_mux - optional data-path mux between two connectors
+ * @select: GPIO selecting which connector drives the shared HS data lines
+ * @lock: serialises role requests coming from both connectors
+ * @ports: per-connector state, indexed by the endpoint's "mux-state"
+ * @desired_port: index of the connector that currently wins
+ * @active_port: index of the connector currently driving the data lines
+ * @seq: monotonic request counter used to order pending requests
+ * @error: sticky data-path switch error, reported to the role-switch users
+ *
+ * Two connectors can share the High Speed data lines of one controller while
+ * keeping their Super Speed lanes separate. Each connector then needs its own
+ * role-switch input, and the shared data path has to be moved before the
+ * controller is reinitialised for the requested role.
+ */
+struct dwc3_role_mux {
+ struct gpio_desc *select;
+ /* serialises role requests coming from both connectors */
+ struct mutex lock;
+ struct dwc3_role_port ports[2];
+ int desired_port;
+ int active_port;
+ u64 seq;
+ int error;
+};
+
/**
* struct dwc3 - representation of our controller
* @drd_work: workqueue used for role swapping
@@ -1064,6 +1101,7 @@ struct dwc3_glue_ops {
* - USBPHY_INTERFACE_MODE_UTMI
* - USBPHY_INTERFACE_MODE_UTMIW
* @role_sw: usb_role_switch handle
+ * @role_mux: optional shared data-path mux plus per-connector role switches
* @role_switch_default_mode: default operation mode of controller while
* usb role is USB_ROLE_NONE.
* @usb_psy: pointer to power supply interface.
@@ -1265,6 +1303,7 @@ struct dwc3 {
struct notifier_block edev_nb;
enum usb_phy_interface hsphy_mode;
struct usb_role_switch *role_sw;
+ struct dwc3_role_mux *role_mux;
enum usb_dr_mode role_switch_default_mode;
struct power_supply *usb_psy;
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index 65d909e..0d42f10 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -438,6 +438,10 @@ static ssize_t dwc3_mode_write(struct file *file,
u32 mode = 0;
char buf[32];
+ /* The connector providers own both the role and the data-path selection. */
+ if (dwc->role_mux)
+ return -EBUSY;
+
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index f3e37d3..20821d6 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -8,6 +8,7 @@
*/
#include <linux/extcon.h>
+#include <linux/gpio/consumer.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
@@ -501,10 +502,219 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
return role;
}
+/*
+ * Index of the connector that should own the shared data path: the oldest
+ * pending request wins, and the last selection is kept when neither connector
+ * requests a role. Requests are ordered by a monotonic counter rather than by
+ * a timestamp, so a wrapped counter cannot be mistaken for an old request.
+ */
+static int dwc3_role_mux_pick(struct dwc3_role_mux *mux)
+{
+ int sel = mux->desired_port;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mux->ports); i++) {
+ if (mux->ports[i].role == USB_ROLE_NONE)
+ continue;
+ if (mux->ports[sel].role == USB_ROLE_NONE ||
+ mux->ports[i].seq < mux->ports[sel].seq)
+ sel = i;
+ }
+
+ return sel;
+}
+
+static u32 dwc3_role_mux_mode(struct dwc3 *dwc, enum usb_role role)
+{
+ if (role == USB_ROLE_HOST)
+ return DWC3_GCTL_PRTCAP_HOST;
+ if (role == USB_ROLE_DEVICE)
+ return DWC3_GCTL_PRTCAP_DEVICE;
+ if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
+ return DWC3_GCTL_PRTCAP_HOST;
+
+ return DWC3_GCTL_PRTCAP_DEVICE;
+}
+
+static int dwc3_mux_role_set(struct usb_role_switch *sw, enum usb_role role)
+{
+ struct dwc3_role_port *port = usb_role_switch_get_drvdata(sw);
+ struct dwc3 *dwc = port->dwc;
+ struct dwc3_role_mux *mux = dwc->role_mux;
+ unsigned long flags;
+ enum usb_role selected_role;
+ int selected, ret;
+
+ if (role > USB_ROLE_DEVICE)
+ return -EINVAL;
+
+ /*
+ * The request order and the mode request it implies are decided under one
+ * lock, so a second connector cannot change desired_port between the
+ * selection and the dwc3_set_mode() call that consumes it.
+ */
+ mutex_lock(&mux->lock);
+ spin_lock_irqsave(&dwc->lock, flags);
+ /*
+ * A role change between two non-NONE roles of the same connector must not
+ * give that connector a new request ticket: it would otherwise steal the
+ * data path from the connector that asked first.
+ */
+ if (port->role == USB_ROLE_NONE && role != USB_ROLE_NONE)
+ port->seq = ++mux->seq;
+ port->role = role;
+ selected = dwc3_role_mux_pick(mux);
+ mux->desired_port = selected;
+ selected_role = mux->ports[selected].role;
+ /*
+ * Same asynchronous semantics as the single-connector role switch: the
+ * data path and the controller are reprogrammed from __dwc3_set_mode().
+ * dwc3_set_mode() only queues that work, so the mux lock may be held.
+ */
+ dwc3_set_mode(dwc, dwc3_role_mux_mode(dwc, selected_role));
+ ret = mux->error;
+ mux->error = 0;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ mutex_unlock(&mux->lock);
+
+ return ret;
+}
+
+static enum usb_role dwc3_mux_role_get(struct usb_role_switch *sw)
+{
+ struct dwc3_role_port *port = usb_role_switch_get_drvdata(sw);
+ struct dwc3 *dwc = port->dwc;
+ struct dwc3_role_mux *mux = dwc->role_mux;
+ unsigned long flags;
+ enum usb_role role = USB_ROLE_NONE;
+
+ /*
+ * A connector only reports its own role while it owns the shared data
+ * path, so the connector drivers never fight over the controller.
+ */
+ spin_lock_irqsave(&dwc->lock, flags);
+ if (mux->active_port == port->state)
+ role = port->role;
+ spin_unlock_irqrestore(&dwc->lock, flags);
+
+ return role;
+}
+
+static void dwc3_role_mux_exit(struct dwc3 *dwc)
+{
+ struct dwc3_role_mux *mux = dwc->role_mux;
+ unsigned int i;
+
+ if (!mux)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(mux->ports); i++) {
+ if (mux->ports[i].sw)
+ usb_role_switch_unregister(mux->ports[i].sw);
+ mux->ports[i].sw = NULL;
+ fwnode_handle_put(mux->ports[i].fwnode);
+ mux->ports[i].fwnode = NULL;
+ }
+}
+
+/* Returns 1 when the optional two-connector mux has been set up. */
+static int dwc3_role_mux_init(struct dwc3 *dwc, u32 mode)
+{
+ struct usb_role_switch_desc desc = {};
+ struct dwc3_role_mux *mux;
+ struct fwnode_handle *ep;
+ struct gpio_desc *select;
+ char name[64];
+ u32 state;
+ int ret, i;
+
+ select = devm_gpiod_get_optional(dwc->dev, "port-select", GPIOD_ASIS);
+ if (IS_ERR(select))
+ return PTR_ERR(select);
+ if (!select)
+ return 0;
+
+ mux = devm_kzalloc(dwc->dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return -ENOMEM;
+
+ ret = gpiod_get_value_cansleep(select);
+ if (ret < 0)
+ return ret;
+
+ mux->select = select;
+ mux->desired_port = ret;
+ mux->active_port = -1;
+ mutex_init(&mux->lock);
+
+ fwnode_graph_for_each_endpoint(dev_fwnode(dwc->dev), ep) {
+ if (!fwnode_property_present(ep, "usb-role-switch"))
+ continue;
+
+ ret = fwnode_property_read_u32(ep, "mux-state", &state);
+ if (ret || state >= ARRAY_SIZE(mux->ports)) {
+ ret = -EINVAL;
+ goto err_put_ep;
+ }
+ if (mux->ports[state].fwnode) {
+ ret = -EINVAL;
+ goto err_put_ep;
+ }
+
+ mux->ports[state].fwnode = fwnode_handle_get(ep);
+ mux->ports[state].state = state;
+ mux->ports[state].dwc = dwc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mux->ports); i++) {
+ if (!mux->ports[i].fwnode) {
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ dwc->role_mux = mux;
+
+ for (i = 0; i < ARRAY_SIZE(mux->ports); i++) {
+ snprintf(name, sizeof(name), "%s-port%u", dev_name(dwc->dev), i);
+ desc.name = name;
+ desc.fwnode = mux->ports[i].fwnode;
+ desc.set = dwc3_mux_role_set;
+ desc.get = dwc3_mux_role_get;
+ desc.driver_data = &mux->ports[i];
+ mux->ports[i].sw = usb_role_switch_register(dwc->dev, &desc);
+ if (IS_ERR(mux->ports[i].sw)) {
+ ret = PTR_ERR(mux->ports[i].sw);
+ mux->ports[i].sw = NULL;
+ goto err;
+ }
+ }
+
+ /*
+ * The controller is only started in its default role once both switches
+ * are registered: before that a connector driver cannot request a role,
+ * so nothing can race with this initial mode request.
+ */
+ dwc3_set_mode(dwc, mode);
+
+ return 1;
+
+err:
+ dwc3_role_mux_exit(dwc);
+ dwc->role_mux = NULL;
+ /* A mode request queued before the failure must not run on freed state. */
+ cancel_work_sync(&dwc->drd_work);
+ return ret;
+err_put_ep:
+ fwnode_handle_put(ep);
+ goto err;
+}
+
static int dwc3_setup_role_switch(struct dwc3 *dwc)
{
struct usb_role_switch_desc dwc3_role_switch = {NULL};
u32 mode;
+ int ret;
dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
@@ -513,6 +723,11 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;
mode = DWC3_GCTL_PRTCAP_DEVICE;
}
+
+ ret = dwc3_role_mux_init(dwc, mode);
+ if (ret)
+ return ret < 0 ? ret : 0;
+
dwc3_set_mode(dwc, mode);
dwc3_role_switch.fwnode = dev_fwnode(dwc->dev);
@@ -526,7 +741,7 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
if (dwc->dev->of_node) {
/* populate connector entry */
- int ret = devm_of_platform_populate(dwc->dev);
+ ret = devm_of_platform_populate(dwc->dev);
if (ret) {
usb_role_switch_unregister(dwc->role_sw);
@@ -541,6 +756,7 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
#else
#define ROLE_SWITCH 0
#define dwc3_setup_role_switch(x) 0
+static inline void dwc3_role_mux_exit(struct dwc3 *dwc) {}
#endif
int dwc3_drd_init(struct dwc3 *dwc)
@@ -597,6 +813,7 @@ void dwc3_drd_exit(struct dwc3 *dwc)
{
unsigned long flags;
+ dwc3_role_mux_exit(dwc);
if (dwc->role_sw)
usb_role_switch_unregister(dwc->role_sw);
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread