* [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq()
@ 2026-04-21 8:24 Xu Yang
2026-04-21 8:24 ` [PATCH 2/3] usb: chipidea: udc: support dynamic gadget add/remove Xu Yang
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Xu Yang @ 2026-04-21 8:24 UTC (permalink / raw)
To: peter.chen, gregkh, jun.li; +Cc: linux-usb, linux-kernel, imx
The VBUS interrupt is configured in multiple places, add a helper function
ci_udc_enable_vbus_irq() to simplify the code.
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
drivers/usb/chipidea/udc.c | 32 +++++++++++++++++---------------
1 file changed, 17 insertions(+), 15 deletions(-)
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index f2de86d0ce40..d4277d6611ee 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1835,6 +1835,20 @@ static const struct usb_ep_ops usb_ep_ops = {
* GADGET block
*****************************************************************************/
+static void ci_udc_enable_vbus_irq(struct ci_hdrc *ci, bool enable)
+{
+ u32 reg = OTGSC_BSVIS;
+
+ if (!ci->is_otg)
+ return;
+
+ if (enable)
+ reg |= OTGSC_BSVIE;
+
+ /* Clear pending BSVIS and enable/disable BSVIE */
+ hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, reg);
+}
+
static int ci_udc_get_frame(struct usb_gadget *_gadget)
{
struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
@@ -2352,23 +2366,13 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci)
pinctrl_select_state(ci->platdata->pctl,
ci->platdata->pins_device);
- if (ci->is_otg)
- /* Clear and enable BSV irq */
- hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
- OTGSC_BSVIS | OTGSC_BSVIE);
-
+ ci_udc_enable_vbus_irq(ci, true);
return 0;
}
static void udc_id_switch_for_host(struct ci_hdrc *ci)
{
- /*
- * host doesn't care B_SESSION_VALID event
- * so clear and disable BSV irq
- */
- if (ci->is_otg)
- hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
-
+ ci_udc_enable_vbus_irq(ci, false);
ci->vbus_active = 0;
if (ci->platdata->pins_device && ci->platdata->pins_default)
@@ -2395,9 +2399,7 @@ static void udc_suspend(struct ci_hdrc *ci)
static void udc_resume(struct ci_hdrc *ci, bool power_lost)
{
if (power_lost) {
- if (ci->is_otg)
- hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
- OTGSC_BSVIS | OTGSC_BSVIE);
+ ci_udc_enable_vbus_irq(ci, true);
if (ci->vbus_active)
usb_gadget_vbus_disconnect(&ci->gadget);
} else if (ci->vbus_active && ci->driver &&
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] usb: chipidea: udc: support dynamic gadget add/remove
2026-04-21 8:24 [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Xu Yang
@ 2026-04-21 8:24 ` Xu Yang
2026-04-21 8:24 ` [PATCH 3/3] usb: chipidea: core: convert ci_role_switch to local variable Xu Yang
2026-04-21 9:28 ` [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Frank Li
2 siblings, 0 replies; 4+ messages in thread
From: Xu Yang @ 2026-04-21 8:24 UTC (permalink / raw)
To: peter.chen, gregkh, jun.li; +Cc: linux-usb, linux-kernel, imx
When the device is connected and enumerated by the host, switching the
role from device to host leaves an asynchronous vbus_event_work() to be
run. This can affect EHCI host controller initialization process.
If vbus_event_work() runs after ehci_run() sets USBCMD.RUNSTOP bit, then
RUNSTOP bit will be cleared. As a result, the host controller fails to
operate.
The log below shows what happens:
[ 87.819925] ci_hdrc ci_hdrc.0: EHCI Host Controller
[ 87.819963] ci_hdrc ci_hdrc.0: new USB bus registered, assigned bus number 1
[ 87.955634] ci_hdrc ci_hdrc.0: USB 2.0, controller refused to start: -110
[ 87.955658] ci_hdrc ci_hdrc.0: startup error -110
[ 87.955682] ci_hdrc ci_hdrc.0: USB bus 1 deregistered
The problem is that the chipidea UDC driver call usb_udc_vbus_handler() to
pull down data line but it doesn't guarantee that this operation has
completed before the host controller starts running.
Now UDC core can properly delete usb gadget device and make sure that vbus
work is cancelled or completed after usb_del_gadget_udc() is returned. But
the udc.c only call usb_del_gadget_udc() in ci_hdrc_gadget_destroy(). To
avoid above issue, let the gadget device add/remove dynamically during USB
role switching.
To support dynamic gadget add/remove, this firstly clear ci->gadget and
ci->ci_hw_ep memory so the driver won't access stale data when initialize
and reuse these data structures again. Secondly, this assign udc_start()
and udc_stop() to rdrv->start and rdrv->stop, meanwhile it removes
udc_id_switch_for_device() and udc_id_switch_for_host(). The things in
them also properly be merged to udc_start()/udc_stop().
Since ci_hdrc_gadget_init() doesn't add gadget anymore, this also adjust
the order of ci_handle_vbus_change() and ci_role_start(), otherwise, NULL
pointer will be met.
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
drivers/usb/chipidea/core.c | 11 +++----
drivers/usb/chipidea/udc.c | 65 +++++++++++++++++++------------------
2 files changed, 38 insertions(+), 38 deletions(-)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 7cfabb04a4fb..95d9db159ce8 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -1191,19 +1191,16 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci->role = ci_get_role(ci);
if (!ci_otg_is_fsm_mode(ci)) {
- /* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET) {
- /* Pull down DP for possible charger detection */
- hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
- ci_handle_vbus_change(ci);
- }
-
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
ci_role(ci)->name);
goto stop;
}
+
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
}
ret = devm_request_irq(dev, ci->irq, ci_irq_handler, IRQF_SHARED,
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index d4277d6611ee..d52f89489893 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -2044,6 +2044,8 @@ static int init_eps(struct ci_hdrc *ci)
{
int retval = 0, i, j;
+ memset(ci->ci_hw_ep, 0, sizeof(ci->ci_hw_ep));
+
for (i = 0; i < ci->hw_ep_max/2; i++)
for (j = RX; j <= TX; j++) {
int k = i + j * ci->hw_ep_max/2;
@@ -2289,6 +2291,8 @@ static int udc_start(struct ci_hdrc *ci)
struct usb_otg_caps *otg_caps = &ci->platdata->ci_otg_caps;
int retval = 0;
+ memset(&ci->gadget, 0, sizeof(ci->gadget));
+
ci->gadget.ops = &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
@@ -2327,10 +2331,15 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.ep0 = &ci->ep0in->ep;
+ if (ci->platdata->pins_device)
+ pinctrl_select_state(ci->platdata->pctl,
+ ci->platdata->pins_device);
+
retval = usb_add_gadget_udc(dev, &ci->gadget);
if (retval)
goto destroy_eps;
+ ci_udc_enable_vbus_irq(ci, true);
return retval;
destroy_eps:
@@ -2342,38 +2351,20 @@ static int udc_start(struct ci_hdrc *ci)
return retval;
}
-/*
- * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC
- *
- * No interrupts active, the IRQ has been released
+/**
+ * udc_stop: deinitialize gadget role
+ * @ci: chipidea controller
*/
-void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
+static void udc_stop(struct ci_hdrc *ci)
{
- if (!ci->roles[CI_ROLE_GADGET])
- return;
-
+ ci_udc_enable_vbus_irq(ci, false);
usb_del_gadget_udc(&ci->gadget);
+ ci->vbus_active = 0;
destroy_eps(ci);
dma_pool_destroy(ci->td_pool);
dma_pool_destroy(ci->qh_pool);
-}
-
-static int udc_id_switch_for_device(struct ci_hdrc *ci)
-{
- if (ci->platdata->pins_device)
- pinctrl_select_state(ci->platdata->pctl,
- ci->platdata->pins_device);
-
- ci_udc_enable_vbus_irq(ci, true);
- return 0;
-}
-
-static void udc_id_switch_for_host(struct ci_hdrc *ci)
-{
- ci_udc_enable_vbus_irq(ci, false);
- ci->vbus_active = 0;
if (ci->platdata->pins_device && ci->platdata->pins_default)
pinctrl_select_state(ci->platdata->pctl,
@@ -2422,7 +2413,6 @@ static void udc_resume(struct ci_hdrc *ci, bool power_lost)
int ci_hdrc_gadget_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
- int ret;
if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC))
return -ENXIO;
@@ -2431,8 +2421,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
if (!rdrv)
return -ENOMEM;
- rdrv->start = udc_id_switch_for_device;
- rdrv->stop = udc_id_switch_for_host;
+ rdrv->start = udc_start;
+ rdrv->stop = udc_stop;
#ifdef CONFIG_PM_SLEEP
rdrv->suspend = udc_suspend;
rdrv->resume = udc_resume;
@@ -2440,9 +2430,22 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
rdrv->irq = udc_irq;
rdrv->name = "gadget";
- ret = udc_start(ci);
- if (!ret)
- ci->roles[CI_ROLE_GADGET] = rdrv;
+ ci->roles[CI_ROLE_GADGET] = rdrv;
- return ret;
+ /* Pull down DP for possible charger detection */
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+ return 0;
+}
+
+/*
+ * ci_hdrc_gadget_destroy: parent remove must call this to remove UDC
+ *
+ * No interrupts active, the IRQ has been released
+ */
+void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
+{
+ struct device *dev = &ci->gadget.dev;
+
+ if (ci->roles[CI_ROLE_GADGET] && device_is_registered(dev))
+ udc_stop(ci);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] usb: chipidea: core: convert ci_role_switch to local variable
2026-04-21 8:24 [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Xu Yang
2026-04-21 8:24 ` [PATCH 2/3] usb: chipidea: udc: support dynamic gadget add/remove Xu Yang
@ 2026-04-21 8:24 ` Xu Yang
2026-04-21 9:28 ` [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Frank Li
2 siblings, 0 replies; 4+ messages in thread
From: Xu Yang @ 2026-04-21 8:24 UTC (permalink / raw)
To: peter.chen, gregkh, jun.li; +Cc: linux-usb, linux-kernel, imx
When a system contains multiple USB controllers, the global ci_role_switch
variable may be overwritten by subsequent driver initialization code.
This can cause issues in the following cases:
- The 2nd ci_hdrc_probe() sees ci_role_switch.fwnode as non-NULL even
though the "usb-role-switch" property is not present for the controller.
- When the ci_hdrc device is unbound and bound again, ci_role_switch
fwnode will not be reassigned, and the old value will be used instead.
Convert ci_role_switch to a local variable to fix these issues.
Fixes: 05559f10ed79 ("usb: chipidea: add role switch class support")
Cc: stable@vger.kernel.org
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
drivers/usb/chipidea/core.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 95d9db159ce8..07563be0013f 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -655,12 +655,6 @@ static enum ci_role ci_get_role(struct ci_hdrc *ci)
return role;
}
-static struct usb_role_switch_desc ci_role_switch = {
- .set = ci_usb_role_switch_set,
- .get = ci_usb_role_switch_get,
- .allow_userspace_control = true,
-};
-
static int ci_get_platdata(struct device *dev,
struct ci_hdrc_platform_data *platdata)
{
@@ -787,9 +781,6 @@ static int ci_get_platdata(struct device *dev,
cable->connected = false;
}
- if (device_property_read_bool(dev, "usb-role-switch"))
- ci_role_switch.fwnode = dev->fwnode;
-
platdata->pctl = devm_pinctrl_get(dev);
if (!IS_ERR(platdata->pctl)) {
struct pinctrl_state *p;
@@ -1033,6 +1024,7 @@ ATTRIBUTE_GROUPS(ci);
static int ci_hdrc_probe(struct platform_device *pdev)
{
+ struct usb_role_switch_desc ci_role_switch = {};
struct device *dev = &pdev->dev;
struct ci_hdrc *ci;
struct resource *res;
@@ -1179,7 +1171,11 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
}
- if (ci_role_switch.fwnode) {
+ if (device_property_read_bool(dev, "usb-role-switch")) {
+ ci_role_switch.set = ci_usb_role_switch_set;
+ ci_role_switch.get = ci_usb_role_switch_get;
+ ci_role_switch.allow_userspace_control = true;
+ ci_role_switch.fwnode = dev_fwnode(dev);
ci_role_switch.driver_data = ci;
ci->role_switch = usb_role_switch_register(dev,
&ci_role_switch);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq()
2026-04-21 8:24 [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Xu Yang
2026-04-21 8:24 ` [PATCH 2/3] usb: chipidea: udc: support dynamic gadget add/remove Xu Yang
2026-04-21 8:24 ` [PATCH 3/3] usb: chipidea: core: convert ci_role_switch to local variable Xu Yang
@ 2026-04-21 9:28 ` Frank Li
2 siblings, 0 replies; 4+ messages in thread
From: Frank Li @ 2026-04-21 9:28 UTC (permalink / raw)
To: Xu Yang; +Cc: peter.chen, gregkh, jun.li, linux-usb, linux-kernel, imx
On Tue, Apr 21, 2026 at 04:24:34PM +0800, Xu Yang wrote:
> The VBUS interrupt is configured in multiple places, add a helper function
> ci_udc_enable_vbus_irq() to simplify the code.
>
> Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/usb/chipidea/udc.c | 32 +++++++++++++++++---------------
> 1 file changed, 17 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
> index f2de86d0ce40..d4277d6611ee 100644
> --- a/drivers/usb/chipidea/udc.c
> +++ b/drivers/usb/chipidea/udc.c
> @@ -1835,6 +1835,20 @@ static const struct usb_ep_ops usb_ep_ops = {
> * GADGET block
> *****************************************************************************/
>
> +static void ci_udc_enable_vbus_irq(struct ci_hdrc *ci, bool enable)
> +{
> + u32 reg = OTGSC_BSVIS;
> +
> + if (!ci->is_otg)
> + return;
> +
> + if (enable)
> + reg |= OTGSC_BSVIE;
> +
> + /* Clear pending BSVIS and enable/disable BSVIE */
> + hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, reg);
> +}
> +
> static int ci_udc_get_frame(struct usb_gadget *_gadget)
> {
> struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget);
> @@ -2352,23 +2366,13 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci)
> pinctrl_select_state(ci->platdata->pctl,
> ci->platdata->pins_device);
>
> - if (ci->is_otg)
> - /* Clear and enable BSV irq */
> - hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
> - OTGSC_BSVIS | OTGSC_BSVIE);
> -
> + ci_udc_enable_vbus_irq(ci, true);
> return 0;
> }
>
> static void udc_id_switch_for_host(struct ci_hdrc *ci)
> {
> - /*
> - * host doesn't care B_SESSION_VALID event
> - * so clear and disable BSV irq
> - */
> - if (ci->is_otg)
> - hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
> -
> + ci_udc_enable_vbus_irq(ci, false);
> ci->vbus_active = 0;
>
> if (ci->platdata->pins_device && ci->platdata->pins_default)
> @@ -2395,9 +2399,7 @@ static void udc_suspend(struct ci_hdrc *ci)
> static void udc_resume(struct ci_hdrc *ci, bool power_lost)
> {
> if (power_lost) {
> - if (ci->is_otg)
> - hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
> - OTGSC_BSVIS | OTGSC_BSVIE);
> + ci_udc_enable_vbus_irq(ci, true);
> if (ci->vbus_active)
> usb_gadget_vbus_disconnect(&ci->gadget);
> } else if (ci->vbus_active && ci->driver &&
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-21 9:28 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-21 8:24 [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Xu Yang
2026-04-21 8:24 ` [PATCH 2/3] usb: chipidea: udc: support dynamic gadget add/remove Xu Yang
2026-04-21 8:24 ` [PATCH 3/3] usb: chipidea: core: convert ci_role_switch to local variable Xu Yang
2026-04-21 9:28 ` [PATCH 1/3] usb: chipidea: udc: add a helper ci_udc_enable_vbus_irq() Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox