* [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect
@ 2026-07-18 10:37 Carlo Caione
2026-07-18 20:17 ` Marek Vasut
0 siblings, 1 reply; 4+ messages in thread
From: Carlo Caione @ 2026-07-18 10:37 UTC (permalink / raw)
To: GSS_MTK_Uboot_upstream, Bin Meng, Chunfeng Yun, u-boot
Cc: Ryder Lee, Weijie Gao, Igor Belwon, David Lechner, Julien Stephan,
Marek Vasut, Tom Rini, Lukasz Majewski, Mattijs Korpershoek,
Carlo Caione, Vitor Sato Eschholz
Fastboot unregisters the USB gadget from the completion callback of
its final OKAY response. MTU3 QMU can report that request complete
while bytes remain in the endpoint TX FIFO. Disabling the USB function
immediately can therefore disconnect the host before it receives the
response.
Before selecting the high-speed or SuperSpeed disconnect operation, poll
the FIFO state of enabled IN endpoints that have no pending requests.
Bound the wait to 1 ms. If a FIFO does not drain, reset its endpoint,
force the disconnect, and propagate -ETIMEDOUT through the gadget pullup
operation. Endpoints with pending requests are skipped so an ordinary
disconnect does not wait for an active transfer.
Fixes: e09b88cd083d ("usb: add MediaTek USB3 DRD driver")
Signed-off-by: Vitor Sato Eschholz <vsatoes@baylibre.com>
Signed-off-by: Carlo Caione <ccaione@baylibre.com>
---
Changes in v2:
- Reset TX endpoints whose FIFO does not drain
- Propagate the drain timeout through the gadget pullup operation
- Link to v1: https://patch.msgid.link/20260717-ccaione-upstream-mtu3-tx-fifo-drain-v1-1-f867bdfc5d00@baylibre.com
---
drivers/usb/mtu3/mtu3.h | 2 +-
drivers/usb/mtu3/mtu3_core.c | 39 ++++++++++++++++++++++++++++++++++++++-
drivers/usb/mtu3/mtu3_gadget.c | 5 +++--
3 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h
index 8a7ae83ee99..c812cd6fdcf 100644
--- a/drivers/usb/mtu3/mtu3.h
+++ b/drivers/usb/mtu3/mtu3.h
@@ -408,7 +408,7 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set);
void mtu3_ep0_setup(struct mtu3 *mtu);
void mtu3_start(struct mtu3 *mtu);
void mtu3_stop(struct mtu3 *mtu);
-void mtu3_dev_on_off(struct mtu3 *mtu, int is_on);
+int mtu3_dev_on_off(struct mtu3 *mtu, int is_on);
void mtu3_set_speed(struct mtu3 *mtu, enum usb_device_speed speed);
int mtu3_gadget_setup(struct mtu3 *mtu);
diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
index 2f5cc9b1480..da5967ba06a 100644
--- a/drivers/usb/mtu3/mtu3_core.c
+++ b/drivers/usb/mtu3/mtu3_core.c
@@ -10,10 +10,13 @@
#include <linux/log2.h>
#include <linux/bitmap.h>
+#include <linux/iopoll.h>
#include "mtu3.h"
#include "mtu3_dr.h"
+#define MTU3_TX_FIFO_DRAIN_TIMEOUT_US 1000
+
static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size)
{
struct mtu3_fifo_info *fifo = mep->fifo;
@@ -226,6 +229,32 @@ static void mtu3_ep_reset(struct mtu3_ep *mep)
mtu3_clrbits(mtu->mac_base, U3D_EP_RST, rst_bit);
}
+static int mtu3_wait_for_tx_fifo_empty(struct mtu3 *mtu)
+{
+ struct mtu3_ep *mep;
+ u32 value;
+ int ret = 0;
+ int i;
+
+ for (i = 1; i < mtu->num_eps; i++) {
+ mep = mtu->in_eps + i;
+ if (!(mep->flags & MTU3_EP_ENABLED) ||
+ !list_empty(&mep->req_list))
+ continue;
+
+ if (readl_poll_timeout(mtu->mac_base + MU3D_EP_TXCR0(i),
+ value, value & TX_FIFOEMPTY,
+ MTU3_TX_FIFO_DRAIN_TIMEOUT_US)) {
+ dev_warn(mtu->dev, "%s TX FIFO did not drain\n",
+ mep->name);
+ mtu3_ep_reset(mep);
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ return ret;
+}
+
/* set/clear the stall and toggle bits for non-ep0 */
void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
{
@@ -261,8 +290,14 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
set ? "SEND STALL" : "CLEAR STALL, with EP RESET");
}
-void mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
+int mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
{
+ int ret = 0;
+
+ /* QMU completion may precede transmission from the TX FIFO. */
+ if (!is_on)
+ ret = mtu3_wait_for_tx_fifo_empty(mtu);
+
if (mtu->is_u3_ip && mtu->speed >= USB_SPEED_SUPER)
mtu3_ss_func_set(mtu, is_on);
else
@@ -270,6 +305,8 @@ void mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
dev_info(mtu->dev, "gadget (%s) pullup D%s\n",
usb_speed_string(mtu->speed), is_on ? "+" : "-");
+
+ return ret;
}
void mtu3_start(struct mtu3 *mtu)
diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c
index 027b7e61113..da633faae7b 100644
--- a/drivers/usb/mtu3/mtu3_gadget.c
+++ b/drivers/usb/mtu3/mtu3_gadget.c
@@ -451,6 +451,7 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
{
struct mtu3 *mtu = gadget_to_mtu3(gadget);
unsigned long flags;
+ int ret = 0;
dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__,
is_on ? "on" : "off", mtu->is_active ? "" : "in");
@@ -464,12 +465,12 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
mtu->softconnect = is_on;
} else if (is_on != mtu->softconnect) {
mtu->softconnect = is_on;
- mtu3_dev_on_off(mtu, is_on);
+ ret = mtu3_dev_on_off(mtu, is_on);
}
spin_unlock_irqrestore(&mtu->lock, flags);
- return 0;
+ return ret;
}
static int mtu3_gadget_start(struct usb_gadget *gadget,
---
base-commit: 96c308b8d2a6a1496c0a7366db9a7becf42d2454
change-id: 20260717-ccaione-upstream-mtu3-tx-fifo-drain-fe4b66e5c1e4
Best regards,
--
Carlo Caione <ccaione@baylibre.com>
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect
2026-07-18 10:37 [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect Carlo Caione
@ 2026-07-18 20:17 ` Marek Vasut
2026-07-20 7:32 ` Carlo Caione
0 siblings, 1 reply; 4+ messages in thread
From: Marek Vasut @ 2026-07-18 20:17 UTC (permalink / raw)
To: Carlo Caione, GSS_MTK_Uboot_upstream, Bin Meng, Chunfeng Yun,
u-boot
Cc: Ryder Lee, Weijie Gao, Igor Belwon, David Lechner, Julien Stephan,
Marek Vasut, Tom Rini, Lukasz Majewski, Mattijs Korpershoek,
Vitor Sato Eschholz
On 7/18/26 12:37 PM, Carlo Caione wrote:
[...]
> +static int mtu3_wait_for_tx_fifo_empty(struct mtu3 *mtu)
> +{
> + struct mtu3_ep *mep;
> + u32 value;
> + int ret = 0;
> + int i;
> +
> + for (i = 1; i < mtu->num_eps; i++) {
> + mep = mtu->in_eps + i;
> + if (!(mep->flags & MTU3_EP_ENABLED) ||
> + !list_empty(&mep->req_list))
> + continue;
> +
> + if (readl_poll_timeout(mtu->mac_base + MU3D_EP_TXCR0(i),
> + value, value & TX_FIFOEMPTY,
> + MTU3_TX_FIFO_DRAIN_TIMEOUT_US)) {
> + dev_warn(mtu->dev, "%s TX FIFO did not drain\n",
> + mep->name);
> + mtu3_ep_reset(mep);
> + ret = -ETIMEDOUT;
You could get the ETIMEDOUT return value from readl_poll_timeout().
> + }
> + }
> +
> + return ret;
> +}
> +
> /* set/clear the stall and toggle bits for non-ep0 */
> void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
> {
> @@ -261,8 +290,14 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
> set ? "SEND STALL" : "CLEAR STALL, with EP RESET");
> }
>
> -void mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
> +int mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
> {
> + int ret = 0;
> +
> + /* QMU completion may precede transmission from the TX FIFO. */
> + if (!is_on)
> + ret = mtu3_wait_for_tx_fifo_empty(mtu);
Shouldn't this bail early on ret != 0 ?
[...]
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect
2026-07-18 20:17 ` Marek Vasut
@ 2026-07-20 7:32 ` Carlo Caione
[not found] ` <3acd7279-8baa-47b5-b384-425bde8a9d1f@mailbox.org>
0 siblings, 1 reply; 4+ messages in thread
From: Carlo Caione @ 2026-07-20 7:32 UTC (permalink / raw)
To: Marek Vasut, Carlo Caione, GSS_MTK_Uboot_upstream, Bin Meng,
Chunfeng Yun, u-boot
Cc: Ryder Lee, Weijie Gao, Igor Belwon, David Lechner, Julien Stephan,
Marek Vasut, Tom Rini, Lukasz Majewski, Mattijs Korpershoek,
Vitor Sato Eschholz
On Sat Jul 18, 2026 at 10:17 PM CEST, Marek Vasut wrote:
> On 7/18/26 12:37 PM, Carlo Caione wrote:
>
> [...]
>
>> +static int mtu3_wait_for_tx_fifo_empty(struct mtu3 *mtu)
>> +{
>> + struct mtu3_ep *mep;
>> + u32 value;
>> + int ret = 0;
>> + int i;
>> +
>> + for (i = 1; i < mtu->num_eps; i++) {
>> + mep = mtu->in_eps + i;
>> + if (!(mep->flags & MTU3_EP_ENABLED) ||
>> + !list_empty(&mep->req_list))
>> + continue;
>> +
>> + if (readl_poll_timeout(mtu->mac_base + MU3D_EP_TXCR0(i),
>> + value, value & TX_FIFOEMPTY,
>> + MTU3_TX_FIFO_DRAIN_TIMEOUT_US)) {
>> + dev_warn(mtu->dev, "%s TX FIFO did not drain\n",
>> + mep->name);
>> + mtu3_ep_reset(mep);
>> + ret = -ETIMEDOUT;
>
> You could get the ETIMEDOUT return value from readl_poll_timeout().
fair enough.
>> + }
>> + }
>> +
>> + return ret;
>> +}
>> +
>> /* set/clear the stall and toggle bits for non-ep0 */
>> void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
>> {
>> @@ -261,8 +290,14 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
>> set ? "SEND STALL" : "CLEAR STALL, with EP RESET");
>> }
>>
>> -void mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
>> +int mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
>> {
>> + int ret = 0;
>> +
>> + /* QMU completion may precede transmission from the TX FIFO. */
>> + if (!is_on)
>> + ret = mtu3_wait_for_tx_fifo_empty(mtu);
>
> Shouldn't this bail early on ret != 0 ?
No, disconnect must still be forced after resetting a timed-out endpoint
(we still want to disconnect it, even forcefully)
cheers,
--
Carlo Caione
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect
[not found] ` <3acd7279-8baa-47b5-b384-425bde8a9d1f@mailbox.org>
@ 2026-07-21 5:54 ` Carlo Caione via U-Boot
0 siblings, 0 replies; 4+ messages in thread
From: Carlo Caione via U-Boot @ 2026-07-21 5:54 UTC (permalink / raw)
To: Marek Vasut, Carlo Caione, GSS_MTK_Uboot_upstream, Bin Meng,
Chunfeng Yun, u-boot
Cc: Ryder Lee, Weijie Gao, Igor Belwon, David Lechner, Julien Stephan,
Marek Vasut, Tom Rini, Lukasz Majewski, Mattijs Korpershoek,
Vitor Sato Eschholz
On Mon Jul 20, 2026 at 2:20 PM CEST, Marek Vasut wrote:
> On 7/20/26 9:32 AM, Carlo Caione wrote:
>
> Hello Carlo,
>
>>>> @@ -261,8 +290,14 @@ void mtu3_ep_stall_set(struct mtu3_ep *mep, bool set)
>>>> set ? "SEND STALL" : "CLEAR STALL, with EP RESET");
>>>> }
>>>>
>>>> -void mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
>>>> +int mtu3_dev_on_off(struct mtu3 *mtu, int is_on)
>>>> {
>>>> + int ret = 0;
>>>> +
>>>> + /* QMU completion may precede transmission from the TX FIFO. */
>>>> + if (!is_on)
>>>> + ret = mtu3_wait_for_tx_fifo_empty(mtu);
>>>
>>> Shouldn't this bail early on ret != 0 ?
>>
>> No, disconnect must still be forced after resetting a timed-out endpoint
>> (we still want to disconnect it, even forcefully)
> Can you please include a code comment that clarifies this ?
Done in V3 already: https://lore.kernel.org/all/20260720-ccaione-upstream-mtu3-tx-fifo-drain-v3-1-45c0e072b64a@baylibre.com/
thanks,
--
Carlo Caione
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-21 5:54 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-18 10:37 [PATCH v2] usb: mtu3: wait for TX FIFO to drain before disconnect Carlo Caione
2026-07-18 20:17 ` Marek Vasut
2026-07-20 7:32 ` Carlo Caione
[not found] ` <3acd7279-8baa-47b5-b384-425bde8a9d1f@mailbox.org>
2026-07-21 5:54 ` Carlo Caione via U-Boot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.