summaryrefslogtreecommitdiff
path: root/src/portable
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-18 15:31:03 +0700
committerhathach <[email protected]>2026-06-18 15:31:03 +0700
commit953abfb3931c67b834838c7fe0b31834cb0d42d6 (patch)
tree6d238832fb486839d7c24d7810ef5c37f56a3966 /src/portable
parentaca8c7a97c4fc93e8883cffdd0a7a22d552cbdc5 (diff)
parent941d63e39a529593ee86caf7ea85e04839680d59 (diff)
Merge remote-tracking branch 'origin/master' into add-ch58x-usbfs
Diffstat (limited to 'src/portable')
-rw-r--r--src/portable/chipidea/ci_fs/dcd_ci_fs.c14
-rw-r--r--src/portable/dialog/da146xx/dcd_da146xx.c42
-rw-r--r--src/portable/mentor/musb/dcd_musb.c800
-rw-r--r--src/portable/mentor/musb/musb_max32.h2
-rw-r--r--src/portable/mentor/musb/musb_ti.h2
-rw-r--r--src/portable/mentor/musb/musb_type.h14
-rw-r--r--src/portable/microchip/samg/dcd_samg.c4
-rw-r--r--src/portable/nordic/nrf5x/dcd_nrf5x.c10
-rw-r--r--src/portable/nuvoton/nuc120/dcd_nuc120.c8
-rw-r--r--src/portable/nuvoton/nuc121/dcd_nuc121.c8
-rw-r--r--src/portable/nuvoton/nuc505/dcd_nuc505.c6
-rw-r--r--src/portable/nxp/lpc17_40/dcd_lpc17_40.c4
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c10
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c294
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c835
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c490
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h159
-rw-r--r--src/portable/renesas/rusb2/dcd_rusb2.c34
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c45
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.c4
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.h4
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_stm32.h64
-rw-r--r--src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c57
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c190
-rw-r--r--src/portable/synopsys/dwc2/dwc2_at32.h26
-rw-r--r--src/portable/synopsys/dwc2/dwc2_bcm.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_common.h16
-rw-r--r--src/portable/synopsys/dwc2/dwc2_efm32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_esp32.h26
-rw-r--r--src/portable/synopsys/dwc2/dwc2_gd32.h8
-rw-r--r--src/portable/synopsys/dwc2/dwc2_info.md116
-rwxr-xr-xsrc/portable/synopsys/dwc2/dwc2_info.py5
-rw-r--r--src/portable/synopsys/dwc2/dwc2_nrf.h95
-rw-r--r--src/portable/synopsys/dwc2/dwc2_stm32.h48
-rw-r--r--src/portable/synopsys/dwc2/dwc2_type.h3
-rw-r--r--src/portable/synopsys/dwc2/dwc2_xmc.h8
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c81
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h140
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c270
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c434
40 files changed, 2579 insertions, 1813 deletions
diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
index 312a98299..62df1a6d5 100644
--- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c
+++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c
@@ -360,7 +360,7 @@ static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size,
unsigned val = USB_ENDPT_EPCTLDIS_MASK;
val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0;
val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK;
- CI_REG->EP[epn].CTL |= val;
+ CI_REG->EP[epn].CTL |= (uint8_t)val;
if (xfer != TUSB_XFER_ISOCHRONOUS) {
bd[odd].dts = 1;
@@ -434,11 +434,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1;
/* When total_bytes is greater than the max packet size,
* it prepares to the next transfer to avoid NAK in advance. */
- next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps;
+ next->bc = (total_bytes >= 2 * mps) ? mps : (total_bytes - mps);
next->addr = buffer + mps;
next->own = 1;
}
- bd->bc = total_bytes >= mps ? mps: total_bytes;
+ bd->bc = (total_bytes >= mps ? mps : total_bytes);
bd->addr = buffer;
__DSB();
bd->own = 1; /* This bit must be set last */
@@ -506,16 +506,16 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
//--------------------------------------------------------------------+
void dcd_int_handler(uint8_t rhport)
{
- uint32_t is = CI_REG->INT_STAT;
- uint32_t msk = CI_REG->INT_EN;
+ uint8_t is = CI_REG->INT_STAT;
+ uint8_t msk = CI_REG->INT_EN;
// clear non-enabled interrupts
- CI_REG->INT_STAT = is & ~msk;
+ CI_REG->INT_STAT = (uint8_t)(is & ~msk);
is &= msk;
if (is & USB_ISTAT_ERROR_MASK) {
/* TODO: */
- uint32_t es = CI_REG->ERR_STAT;
+ uint8_t es = CI_REG->ERR_STAT;
CI_REG->ERR_STAT = es;
CI_REG->INT_STAT = is; /* discard any pending events */
}
diff --git a/src/portable/dialog/da146xx/dcd_da146xx.c b/src/portable/dialog/da146xx/dcd_da146xx.c
index a283c8362..7d90b1f94 100644
--- a/src/portable/dialog/da146xx/dcd_da146xx.c
+++ b/src/portable/dialog/da146xx/dcd_da146xx.c
@@ -148,8 +148,8 @@ typedef struct
#ifndef TU_DA146XX_DMA_RX_CHANNEL
#define TU_DA146XX_DMA_RX_CHANNEL 6
#endif
-#define DA146XX_DMA_USB_MUX (0x6 << (TU_DA146XX_DMA_RX_CHANNEL * 2))
-#define DA146XX_DMA_USB_MUX_MASK (0xF << (TU_DA146XX_DMA_RX_CHANNEL * 2))
+#define DA146XX_DMA_USB_MUX (0x6u << (TU_DA146XX_DMA_RX_CHANNEL * 2))
+#define DA146XX_DMA_USB_MUX_MASK (0xFu << (TU_DA146XX_DMA_RX_CHANNEL * 2))
typedef struct
{
@@ -311,12 +311,12 @@ static void fill_tx_fifo(xfer_ctl_t * xfer)
// Max packet size is set to value greater then FIFO. Enable fifo level warning
// to handle larger packets.
regs->txc |= (3 << USB_USB_TXC1_REG_USB_TFWL_Pos);
- USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos);
+ USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos);
}
else
{
regs->txc &= ~USB_USB_TXC1_REG_USB_TFWL_Msk;
- USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos));
+ USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos));
// Whole packet already in fifo, no need to refill it later. Mark last.
regs->txc |= USB_USB_TXC1_REG_USB_LAST_Msk;
}
@@ -371,14 +371,14 @@ static void start_rx_packet(xfer_ctl_t *xfer)
// For endpoint size greater than FIFO size enable FIFO level warning interrupt
// when FIFO has less than 17 bytes free.
regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk;
- USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos);
+ USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos);
}
}
else if (epnum != 0)
{
// If max_packet_size would fit in FIFO no need for FIFO level warning interrupt.
regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk;
- USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos));
+ USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos));
}
regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk;
}
@@ -388,7 +388,7 @@ static void start_tx_dma(void *src, volatile void *dst, uint16_t size)
// Setup SRC and DST registers
TX_DMA_REGS->DMAx_A_START_REG = (uint32_t)src;
TX_DMA_REGS->DMAx_B_START_REG = (uint32_t)dst;
- // Interrupt not needed
+ // Interrupt is not needed
TX_DMA_REGS->DMAx_INT_REG = size;
TX_DMA_REGS->DMAx_LEN_REG = size - 1;
TX_DMA_REGS->DMAx_CTRL_REG = TX_DMA_START;
@@ -430,7 +430,9 @@ static uint16_t read_rx_fifo(xfer_ctl_t *xfer, uint16_t bytes_in_fifo)
uint8_t *buf = xfer->buffer + xfer->transferred + xfer->last_packet_size;
- for (int i = 0; i < receive_this_time; ++i) buf[i] = regs->rxd;
+ for (int i = 0; i < receive_this_time; ++i) {
+ buf[i] = (uint8_t)regs->rxd;
+ }
xfer->last_packet_size += receive_this_time;
@@ -449,7 +451,9 @@ static void handle_ep0_rx(void)
{
xfer_ctl_t *xfer_in = XFER_CTL_BASE(0, TUSB_DIR_IN);
// Setup packet is in
- for (int i = 0; i < fifo_bytes; ++i) _setup_packet[i] = USB->USB_RXD0_REG;
+ for (int i = 0; i < fifo_bytes; ++i) {
+ _setup_packet[i] = (uint8_t)USB->USB_RXD0_REG;
+ }
xfer->stall = 0;
xfer->data1 = 1;
@@ -469,7 +473,7 @@ static void handle_ep0_rx(void)
}
else
{
- read_rx_fifo(xfer, fifo_bytes);
+ read_rx_fifo(xfer, (uint16_t)fifo_bytes);
if (rxs0 & USB_USB_RXS0_REG_USB_RX_LAST_Msk)
{
xfer->transferred += xfer->last_packet_size;
@@ -553,7 +557,7 @@ static void handle_epx_rx_ev(uint8_t ep)
{
// Disable DMA and update last_packet_size with what DMA reported.
RX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk;
- xfer->last_packet_size = RX_DMA_REGS->DMAx_IDX_REG;
+ xfer->last_packet_size = (uint16_t)RX_DMA_REGS->DMAx_IDX_REG;
// When DMA did not finished (packet was smaller then MPS), DMAx_IDX_REG holds exact number of bytes transmitted.
// When DMA finished value in DMAx_IDX_REG is one less then actual number of transmitted bytes.
if (xfer->last_packet_size == RX_DMA_REGS->DMAx_LEN_REG) xfer->last_packet_size++;
@@ -564,7 +568,7 @@ static void handle_epx_rx_ev(uint8_t ep)
// FIFO maybe empty if DMA read it before or it's final iteration and function already read all that was to read.
if (fifo_bytes > 0)
{
- fifo_bytes = read_rx_fifo(xfer, fifo_bytes);
+ fifo_bytes = read_rx_fifo(xfer, (uint16_t)fifo_bytes);
}
if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST))
{
@@ -624,7 +628,7 @@ static void handle_epx_tx_ev(xfer_ctl_t *xfer)
{
// Disable DMA and update last_packet_size with what DMA reported.
TX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA1_CTRL_REG_DMA_ON_Msk;
- xfer->last_packet_size = TX_DMA_REGS->DMAx_IDX_REG + 1;
+ xfer->last_packet_size = (uint16_t)(TX_DMA_REGS->DMAx_IDX_REG + 1);
// Release DMA to used by other endpoints.
_dcd.dma_ep[TUSB_DIR_IN] = 0;
}
@@ -954,13 +958,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt)
if (dir == TUSB_DIR_OUT)
{
regs->epc_out = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask;
- USB->USB_RXMSK_REG |= 0x11 << (epnum - 1);
+ USB->USB_RXMSK_REG |= 0x11u << (epnum - 1);
REG_SET_BIT(USB_MAMSK_REG, USB_M_RX_EV);
}
else
{
regs->epc_in = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask;
- USB->USB_TXMSK_REG |= 0x11 << (epnum - 1);
+ USB->USB_TXMSK_REG |= 0x11u << (epnum - 1);
REG_SET_BIT(USB_MAMSK_REG, USB_M_TX_EV);
}
}
@@ -974,8 +978,8 @@ void dcd_edpt_close_all (uint8_t rhport)
for (int epnum = 1; epnum < EP_MAX; ++epnum)
{
- dcd_edpt_close(0, epnum | TUSB_DIR_OUT);
- dcd_edpt_close(0, epnum | TUSB_DIR_IN);
+ dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_OUT));
+ dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_IN));
}
}
@@ -1001,7 +1005,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
{
regs->rxc = USB_USB_RXC1_REG_USB_FLUSH_Msk;
regs->epc_out = 0;
- USB->USB_RXMSK_REG &= ~(0x11 << (epnum - 1));
+ USB->USB_RXMSK_REG &= ~(0x11u << (epnum - 1));
// Release DMA if needed
if (_dcd.dma_ep[TUSB_DIR_OUT] == epnum)
{
@@ -1013,7 +1017,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
{
regs->txc = USB_USB_TXC1_REG_USB_FLUSH_Msk;
regs->epc_in = 0;
- USB->USB_TXMSK_REG &= ~(0x11 << (epnum - 1));
+ USB->USB_TXMSK_REG &= ~(0x11u << (epnum - 1));
// Release DMA if needed
if (_dcd.dma_ep[TUSB_DIR_IN] == epnum)
{
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index d329285e9..1d1280bf4 100644
--- a/src/portable/mentor/musb/dcd_musb.c
+++ b/src/portable/mentor/musb/dcd_musb.c
@@ -50,36 +50,133 @@
* MACRO TYPEDEF CONSTANT ENUM DECLARATION
*------------------------------------------------------------------*/
-#define REQUEST_TYPE_INVALID (0xFFu)
-
typedef union {
volatile uint8_t u8;
volatile uint16_t u16;
volatile uint32_t u32;
} hw_fifo_t;
-typedef struct TU_ATTR_PACKED
-{
- void *buf; /* the start address of a transfer data buffer */
+typedef struct {
+ union {
+ uint8_t *buf; /* the start address of a transfer data buffer */
+ tu_fifo_t *fifo;
+ };
uint16_t length; /* the number of bytes in the buffer */
uint16_t remaining; /* the number of bytes remaining in the buffer */
+ bool armed; /* true while a transfer is posted */
+ bool use_fifo; /* true: buf is tu_fifo_t*; false: buf is plain byte pointer. */
} pipe_state_t;
-typedef struct
-{
- union {
- tusb_control_request_t setup_packet;
- uint32_t setup_buffer[2];
- };
- uint16_t remaining_ctrl; /* The number of bytes remaining in data stage of control transfer. */
- int8_t status_out;
- pipe_state_t pipe0;
- pipe_state_t pipe[2][TUP_DCD_ENDPOINT_MAX-1]; /* pipe[direction][endpoint number - 1] */
- uint16_t pipe_buf_is_fifo[2]; /* Bitmap. Each bit means whether 1:TU_FIFO or 0:POD. */
+// Pipe array layout (N = TUP_DCD_ENDPOINT_MAX). EP0 has its own scalars in
+// dcd_data_t and does not occupy a pipe slot.
+// One-direction-only IPs (CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY=1):
+// [0..N-2] : EP1..N-1 (single slot per endpoint)
+// Bidirectional-capable IPs:
+// [0..N-2 ] : EP1..N-1 OUT
+// [N-1..2*N-3 ] : EP1..N-1 IN
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ #define MUSB_PIPE_COUNT (TUP_DCD_ENDPOINT_MAX - 1u)
+#else
+ #define MUSB_PIPE_COUNT (2u * (TUP_DCD_ENDPOINT_MAX - 1u))
+#endif
+
+enum {
+ PIPE0_STATE_IDLE = 0, // no active control transfer
+ PIPE0_STATE_DATA_IN, // DATA IN stage
+ PIPE0_STATE_DATA_OUT, // DATA OUT stage
+ PIPE0_STATE_STATUS_IN, // STATUS IN โ€” device sends IN-ZLP; awaits send-ACK IRQ
+ PIPE0_STATE_STATUS_OUT, // post-DATAEND, neither edpt0_xfer(STATUS OUT) nor confirmation IRQ has happened yet
+ PIPE0_STATE_STATUS_OUT_PENDING_XFER, // edpt0_xfer(STATUS OUT) called first; the confirmation IRQ fires xfer_complete
+ PIPE0_STATE_STATUS_OUT_PENDING_IRQ, // confirmation IRQ seen (or synthesized) first; edpt0_xfer(STATUS OUT) fires xfer_complete
+};
+
+// EP0 control-transfer state (own scalars, not a pipe[] slot).
+typedef struct {
+ uint8_t *buf; // DATA OUT drain target (only valid while EP0 is in DATA OUT stage)
+ uint16_t xact_len; // DATA IN chunk length armed via edpt0_xfer; reported in its xfer_complete (OUT reports count0)
+ uint16_t remain_wlength; // bytes remaining in the control transfer's DATA stage
+ uint8_t state;
+ uint8_t pending_addr; // new USB address latched by dcd_set_address; applied when STATUS IN completes
+ bool rxrdy_consumed; // RxPktRdy left set in hw for an already-consumed packet (NAK flow control);
+ // RXRDY events are stale while set. Cleared when RXRDYC is written.
+ bool deferred_setup_valid;
+ uint32_t deferred_setup[2]; // raw SETUP words, replayed via pipe0_start_setup
+} pipe0_state_t;
+
+typedef struct {
+ pipe0_state_t pipe0;
+ pipe_state_t pipe[MUSB_PIPE_COUNT];
} dcd_data_t;
static dcd_data_t _dcd;
+// Read the 8-byte SETUP packet (2 words) from the EP0 FIFO into setup[]. Does not ack RxPktRdy.
+static bool pipe0_read_setup(musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr, uint32_t setup[2]) {
+ TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0);
+ setup[0] = musb_regs->fifo[0];
+ setup[1] = musb_regs->fifo[0];
+ return true;
+}
+
+static void pipe0_start_setup(uint8_t rhport, musb_ep_csr_t* ep_csr,
+ const uint32_t setup[2], bool is_isr) {
+ tusb_control_request_t const* req = (tusb_control_request_t const*) setup;
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ pipe0->remain_wlength = req->wLength;
+
+ if (req->wLength == 0) {
+ // Leave RXRDY set; edpt0_xfer(STATUS IN) acks it together with DATAEND.
+ pipe0->state = PIPE0_STATE_STATUS_IN;
+ pipe0->rxrdy_consumed = true;
+ } else {
+ if (req->bmRequestType & TUSB_DIR_IN_MASK) {
+ pipe0->state = PIPE0_STATE_DATA_IN;
+ // On a deferred replay the packet's RXRDY stays parked until the edpt0_xfer(DATA IN) arm
+ // acks it โ€” a stale latched EP0 IRQ in between is gated by rxrdy_consumed.
+ if (!pipe0->rxrdy_consumed) {
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+ }
+ } else {
+ // If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data.
+ // Deliberate deviation from the databook's canonical flow (ack right after unload),
+ // used as NAK flow control until usbd arms the drain buffer.
+ pipe0->state = PIPE0_STATE_DATA_OUT;
+ pipe0->rxrdy_consumed = true;
+ }
+ }
+
+ dcd_event_setup_received(rhport, (const uint8_t *) setup, is_isr);
+}
+
+// Replay a previously deferred SETUP, if any.
+static void pipe0_try_deferred_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, bool is_isr) {
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ if (!pipe0->deferred_setup_valid) {
+ return;
+ }
+
+ pipe0->deferred_setup_valid = false;
+ pipe0_start_setup(rhport, ep_csr, pipe0->deferred_setup, is_isr);
+}
+
+// Last DATA packet: wLength satisfied, or a short packet (incl. ZLP) ends the stage.
+TU_ATTR_ALWAYS_INLINE static inline bool pipe0_data_stage_done(uint16_t xfer_len) {
+ return _dcd.pipe0.remain_wlength == 0 || xfer_len < CFG_TUD_ENDPOINT0_SIZE;
+}
+
+// EP0 must not call this โ€” it has its own scalars in dcd_data_t.
+TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_dir_t epdir) {
+ size_t idx = epnum - 1u;
+#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
+ (void) epdir;
+#else
+ if (epdir == TUSB_DIR_IN) {
+ idx += TUP_DCD_ENDPOINT_MAX - 1u;
+ }
+#endif
+ return &_dcd.pipe[idx];
+}
+
//--------------------------------------------------------------------
// HW FIFO Helper
// Note: Index register is already set by caller
@@ -110,7 +207,6 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne
TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps,
bool double_packet) {
- (void) epnum;
uint8_t ffsize = hwfifo_byte2size(mps);
mps = 8 << ffsize; // round up to the next power of 2
@@ -123,6 +219,13 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign
musb->fifo_addr[is_rx] = alloced_fifo_bytes / 8;
musb->fifo_size[is_rx] = ffsize;
+ volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable;
+ if (double_packet) {
+ *dp_disable &= ~(1u << epnum);
+ } else {
+ *dp_disable |= (1u << epnum);
+ }
+
alloced_fifo_bytes += mps;
return true;
}
@@ -136,18 +239,29 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne
TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps,
bool double_packet) {
- (void) epnum; (void) mps;
- if (!double_packet) {
- #if defined(TUP_USBIP_MUSB_ADI)
- musb->indexed_csr.maxp_csr[is_rx].csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET(is_rx);
- #else
- if (is_rx) {
- musb->rx_doulbe_packet_disable |= 1u << epnum;
- } else {
- musb->tx_double_packet_disable |= 1u << epnum;
- }
- #endif
+ (void) mps;
+
+ #if defined(TUP_USBIP_MUSB_ADI)
+ // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B.
+ // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies.
+ // Force single-buffered on EP < 8 even if the caller requested DPB.
+ if (epnum < 8 && (musb->power & MUSB_POWER_HSMODE)) {
+ double_packet = false;
+ }
+ volatile uint8_t* csrh = &musb->indexed_csr.maxp_csr[is_rx].csrh;
+ if (double_packet) {
+ *csrh &= ~MUSB_CSRH_DISABLE_DOUBLE_PACKET;
+ } else {
+ *csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET;
}
+ #else
+ volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable;
+ if (double_packet) {
+ *dp_disable &= ~(1u << epnum);
+ } else {
+ *dp_disable |= (1u << epnum);
+ }
+ #endif
return true;
}
@@ -167,315 +281,359 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigne
}
}
-static void process_setup_packet(uint8_t rhport) {
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
-
- // Read setup packet
- _dcd.setup_buffer[0] = musb_regs->fifo[0];
- _dcd.setup_buffer[1] = musb_regs->fifo[0];
-
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- dcd_event_setup_received(rhport, (const uint8_t*)(uintptr_t)&_dcd.setup_packet, true);
-
- const unsigned len = _dcd.setup_packet.wLength;
- _dcd.remaining_ctrl = len;
- const unsigned dir_in = tu_edpt_dir(_dcd.setup_packet.bmRequestType);
- /* Clear RX FIFO and reverse the transaction direction */
- if (len && dir_in) {
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+// write to txfifo using pipe_state_t info
+static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) {
+ musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr;
+ const uint16_t mps = ep_csr->tx_maxp & MUSB_TXMAXP_PACKET_SIZE_M;
+ const uint16_t xact_len = tu_min16(mps, pipe->remaining);
+ volatile void *hwfifo = &musb_regs->fifo[epnum];
+ if (xact_len) {
+ if (pipe->use_fifo) {
+ tu_hwfifo_write_from_fifo(hwfifo, pipe->fifo, xact_len, NULL);
+ } else {
+ tu_hwfifo_write(hwfifo, pipe->buf, xact_len, NULL);
+ pipe->buf += xact_len;
+ }
+ pipe->remaining -= xact_len;
}
+ ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY;
}
-static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) {
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1];
- const unsigned rem = pipe->remaining;
+// Called from the TX interrupt. If the last queued packet finished the transfer,
+// signal completion; otherwise queue the next packet.
+static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) {
+ musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
+ const uint_fast8_t csrl = ep_csr->tx_csrl;
+ if (csrl & MUSB_TXCSRL1_STALLED) {
+ ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN);
+ return; // sent STALL, do nothing
+ }
- if (rem == 0 && pipe->length > 0) {
+ pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN);
+ if (pipe->remaining > 0) {
+ pipe_write(musb_regs, pipe, epnum);
+ } else {
+ // All bytes have been loaded into the FIFO. With double-packet buffering a
+ // second packet may still be waiting in the FIFO when this IRQ fires (the
+ // hardware signals TXRDY clear as soon as a slot frees, not when the wire
+ // transfer finishes). Defer completion until FIFONE == 0 so we don't emit
+ // a duplicate xfer_complete before the final packet has been sent.
+ if (csrl & MUSB_TXCSRL1_FIFONE) {
+ return;
+ }
+ const uint16_t xferred_len = pipe->length;
pipe->buf = NULL;
- return true;
+ pipe->armed = false;
+ dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, TUSB_DIR_IN), xferred_len, XFER_RESULT_SUCCESS, true);
}
+}
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
- const unsigned mps = ep_csr->tx_maxp;
- const unsigned len = TU_MIN(mps, rem);
- void *buf = pipe->buf;
- volatile void *fifo_ptr = &musb_regs->fifo[epnum];
- // TU_LOG1(" %p mps %d len %d rem %d\r\n", buf, mps, len, rem);
- if (len) {
- if (_dcd.pipe_buf_is_fifo[TUSB_DIR_IN] & TU_BIT(epnum_minus1)) {
- tu_hwfifo_write_from_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL);
+// Drain one packet from the Rx FIFO into pipe->buf/fifo, update pipe state, and
+// release the FIFO slot by clearing RXRDY. return true if short packet
+static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) {
+ musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; // index already set in process_epout_isr()
+ const uint16_t mps = ep_csr->rx_maxp & MUSB_RXMAXP_PACKET_SIZE_M;
+ const uint16_t rx_count = ep_csr->rx_count;
+ const uint16_t xact_len = tu_min16(tu_min16(pipe->remaining, mps), rx_count);
+ volatile void *hwfifo = &musb_regs->fifo[epnum];
+ if (xact_len) {
+ if (pipe->use_fifo) {
+ tu_hwfifo_read_to_fifo(hwfifo, pipe->fifo, xact_len, NULL);
} else {
- tu_hwfifo_write(fifo_ptr, buf, len, NULL);
- pipe->buf = (uint8_t*)buf + len;
+ tu_hwfifo_read(hwfifo, pipe->buf, xact_len, NULL);
+ pipe->buf += xact_len;
}
- pipe->remaining = rem - len;
+ pipe->remaining -= xact_len;
}
- ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY;
- // TU_LOG1(" TXCSRL%d = %x %d\r\n", epnum, ep_csr->tx_csrl, rem - len);
- return false;
+ ep_csr->rx_csrl = 0; /* Clear RXRDY - release this FIFO slot */
+
+ return (xact_len < mps);
}
-static bool handle_xfer_out(uint8_t rhport, uint_fast8_t ep_addr)
-{
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1];
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
+static void process_epout_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, bool is_isr) {
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
- // TU_LOG1(" RXCSRL%d = %x\r\n", epnum_minus1 + 1, ep_csr->rx_csrl);
+ if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
+ ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
+ return; // sent STALL, do nothing
+ }
- TU_ASSERT(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY);
+ // Fail gracefully. Spurious interrupt.
+ if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) {
+ return;
+ }
- const unsigned mps = ep_csr->rx_maxp;
- const unsigned rem = pipe->remaining;
- const unsigned vld = ep_csr->rx_count;
- const unsigned len = TU_MIN(TU_MIN(rem, mps), vld);
- void *buf = pipe->buf;
- volatile void *fifo_ptr = &musb_regs->fifo[epnum];
- if (len) {
- if (_dcd.pipe_buf_is_fifo[TUSB_DIR_OUT] & TU_BIT(epnum_minus1)) {
- tu_hwfifo_read_to_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL);
- } else {
- tu_hwfifo_read(fifo_ptr, buf, len, NULL);
- pipe->buf = (uint8_t*)buf + len;
- }
- pipe->remaining = rem - len;
+ pipe_state_t *pipe = pipe_get(epnum, TUSB_DIR_OUT);
+ if (!pipe->armed) {
+ // Packet is already ACK'd by hardware and sitting in the Rx FIFO, but no transfer is
+ // posted. Do NOT flush (per MUSB spec ยง3.3.11 FlushFIFO) - that would silently drop
+ // acknowledged data. Mask this endpoint's Rx interrupt so the ISR stops re-firing;
+ // the FIFO stays occupied so hardware NAKs further OUT tokens (natural backpressure).
+ // The next dcd_edpt_xfer() on this endpoint will drain the staged packet.
+ musb_regs->intr_rxen &= (uint16_t) ~TU_BIT(epnum);
+ return;
}
- if ((len < mps) || (rem == len)) {
+
+ const bool is_short = pipe_read(musb_regs, pipe, epnum);
+
+ // Transfer completes on a short packet or when the rx buffer is filled.
+ if (is_short || pipe->remaining == 0) {
+ const uint16_t xferred_len = pipe->length - pipe->remaining;
pipe->buf = NULL;
- return NULL != buf;
+ pipe->armed = false;
+ dcd_event_xfer_complete(rhport, epnum, xferred_len, XFER_RESULT_SUCCESS, is_isr);
}
- ep_csr->rx_csrl = 0; /* Clear RXRDY bit */
- return false;
}
-static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
-{
- unsigned epnum = tu_edpt_number(ep_addr);
- unsigned epnum_minus1 = epnum - 1;
- unsigned dir_in = tu_edpt_dir(ep_addr);
+static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t total_bytes, bool use_fifo, bool is_isr) {
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir_in = tu_edpt_dir(ep_addr);
+
+ pipe_state_t *pipe = pipe_get(epnum, dir_in);
+ if (use_fifo) {
+ pipe->fifo = (tu_fifo_t *)buffer;
+ } else {
+ pipe->buf = (uint8_t *)buffer;
+ }
+ pipe->length = total_bytes;
+ pipe->remaining = total_bytes;
+ pipe->use_fifo = use_fifo;
+ pipe->armed = true;
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epnum_minus1];
- pipe->buf = buffer;
- pipe->length = total_bytes;
- pipe->remaining = total_bytes;
+ musb_regs_t *musb_regs = MUSB_REGS(rhport);
+ musb_ep_csr_t *ep_csr = get_ep_csr(musb_regs, epnum);
if (dir_in) {
- handle_xfer_in(rhport, ep_addr);
+ pipe_write(musb_regs, pipe, epnum);
} else {
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum);
- if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) ep_csr->rx_csrl = 0;
+ // Re-enable Rx interrupt (may have been masked by the no-buffer path in process_epout_isr)
+ musb_regs->intr_rxen |= (uint16_t)TU_BIT(epnum);
+
+ // Drain any packet staged in the Rx FIFO from a prior no-buffer interrupt.
+ // process_epout_isr() fires dcd_event_xfer_complete() itself if the drain completes.
+ if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) {
+ process_epout_isr(rhport, musb_regs, epnum, is_isr);
+ }
}
return true;
}
-static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes)
-{
- (void)rhport;
- TU_ASSERT(total_bytes <= 64); /* Current implementation supports for only up to 64 bytes. */
+static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ TU_ASSERT(total_bytes <= CFG_TUD_ENDPOINT0_SIZE); /* EP0 only supports 1 packet per dcd_edpt_xfer()*/
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- const unsigned req = _dcd.setup_packet.bmRequestType;
- TU_ASSERT(req != REQUEST_TYPE_INVALID || total_bytes == 0);
-
- if (req == REQUEST_TYPE_INVALID || _dcd.status_out) {
- /* STATUS OUT stage.
- * MUSB controller automatically handles STATUS OUT packets without
- * software helps. We do not have to do anything. And STATUS stage
- * may have already finished and received the next setup packet
- * without calling this function, so we have no choice but to
- * invoke the callback function of status packet here. */
- // TU_LOG1(" STATUS OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.status_out = 0;
- if (req == REQUEST_TYPE_INVALID) {
- dcd_event_xfer_complete(rhport, ep_addr, total_bytes, XFER_RESULT_SUCCESS, false);
- } else {
- /* The next setup packet has already been received, it aborts
- * invoking callback function to avoid confusing TUSB stack. */
- TU_LOG1("Drop CONTROL_STAGE_ACK\r\n");
- }
- return true;
- }
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
const unsigned dir_in = tu_edpt_dir(ep_addr);
- if (tu_edpt_dir(req) == dir_in) { /* DATA stage */
- TU_ASSERT(total_bytes <= _dcd.remaining_ctrl);
- const unsigned rem = _dcd.remaining_ctrl;
- const unsigned len = TU_MIN(TU_MIN(rem, 64), total_bytes);
- volatile void *fifo_ptr = &musb_regs->fifo[0];
- if (dir_in) {
- tu_hwfifo_write(fifo_ptr, buffer, len, NULL);
- _dcd.pipe0.buf = buffer + len;
- _dcd.pipe0.length = len;
- _dcd.pipe0.remaining = 0;
-
- _dcd.remaining_ctrl = rem - len;
- if ((len < 64) || (rem == len)) {
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; /* Change to STATUS/SETUP stage */
- _dcd.status_out = 1;
- /* Flush TX FIFO and reverse the transaction direction. */
+ switch (pipe0->state) {
+ // DATA stage exits on its last packet, so state matches the call direction here.
+ case PIPE0_STATE_DATA_IN:
+ TU_ASSERT(dir_in);
+ pipe0->xact_len = total_bytes;
+ if (pipe0->rxrdy_consumed) { // replayed SETUP: ack its parked RXRDY before loading the FIFO
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+ pipe0->rxrdy_consumed = false;
+ }
+ tu_hwfifo_write(&musb_regs->fifo[0], buffer, total_bytes, NULL);
+ pipe0->remain_wlength -= total_bytes;
+ // Add DATAEND on the last packet to end the data stage.
+ if (pipe0_data_stage_done(total_bytes)) {
ep_csr->csr0l = MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND;
} else {
- ep_csr->csr0l = MUSB_CSRL0_TXRDY; /* Flush TX FIFO to return ACK. */
+ ep_csr->csr0l = MUSB_CSRL0_TXRDY;
}
- // TU_LOG1(" IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- } else {
- // TU_LOG1(" OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.pipe0.buf = buffer;
- _dcd.pipe0.length = len;
- _dcd.pipe0.remaining = len;
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC; /* Clear RX FIFO to return ACK. */
- }
- } else if (dir_in) {
- // TU_LOG1(" STATUS IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l);
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- /* Clear RX FIFO and reverse the transaction direction */
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
+ break;
+
+ case PIPE0_STATE_DATA_OUT:
+ TU_ASSERT(!dir_in);
+ pipe0->xact_len = total_bytes;
+ pipe0->buf = buffer; // arm drain target, ack RXRDY so host can send DATA OUT
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+ pipe0->rxrdy_consumed = false;
+ break;
+
+ case PIPE0_STATE_STATUS_IN:
+ TU_ASSERT(dir_in && total_bytes == 0); // only STATUS IN allowed
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
+ pipe0->rxrdy_consumed = false;
+ break;
+
+ case PIPE0_STATE_STATUS_OUT:
+ TU_ASSERT(!dir_in && total_bytes == 0); // only STATUS OUT allowed
+ // First event of the STATUS OUT pair โ€” wait for the IRQ to fire complete.
+ pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_XFER;
+ break;
+
+ case PIPE0_STATE_STATUS_OUT_PENDING_IRQ:
+ // Second event โ€” IRQ already arrived, fire complete now. The old transfer is retired here,
+ // so a deferred SETUP can be replayed safely.
+ pipe0->state = PIPE0_STATE_IDLE;
+ dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, is_isr);
+ pipe0_try_deferred_setup(rhport, ep_csr, is_isr);
+ break;
+
+ default: break;
}
+
return true;
}
-static void process_ep0(uint8_t rhport)
-{
+// Advance EP0's status-stage state machine on a tail event: the csrl==0 confirmation IRQ, or such a
+// confirmation combined with a new SETUP (caller sets deferred_setup_valid first). ISR context only.
+static void pipe0_process_xfer_state_isr(uint8_t rhport, musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr) {
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ switch (pipe0->state) {
+ case PIPE0_STATE_DATA_IN:
+ if (pipe0_data_stage_done(pipe0->xact_len)) {
+ if (pipe0->deferred_setup_valid) {
+ pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ; // status confirm coalesced with deferred SETUP
+ } else {
+ pipe0->state = PIPE0_STATE_STATUS_OUT; // await host's STATUS-OUT ZLP IRQ
+ }
+ }
+ dcd_event_xfer_complete(rhport, TU_EP0_IN, pipe0->xact_len, XFER_RESULT_SUCCESS, true);
+ break;
+
+ case PIPE0_STATE_STATUS_OUT:
+ // Confirmation seen โ€” await edpt0_xfer(STATUS OUT) to fire complete.
+ pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ;
+ break;
+
+ case PIPE0_STATE_STATUS_OUT_PENDING_XFER:
+ // edpt0_xfer(STATUS OUT) already called โ€” fire complete and replay now.
+ pipe0->state = PIPE0_STATE_IDLE;
+ dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true);
+ pipe0_try_deferred_setup(rhport, ep_csr, true);
+ break;
+
+ case PIPE0_STATE_STATUS_OUT_PENDING_IRQ:
+ // Confirmation already accounted for โ€” the pairing edpt0_xfer(STATUS OUT) fires complete.
+ break;
+
+ case PIPE0_STATE_STATUS_IN:
+ if (pipe0->pending_addr) {
+ musb_regs->faddr = pipe0->pending_addr;
+ pipe0->pending_addr = 0;
+ }
+ pipe0->state = PIPE0_STATE_IDLE;
+ dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true);
+ pipe0_try_deferred_setup(rhport, ep_csr, true);
+ break;
+
+ default: break;
+ }
+}
+
+// 21.1.5: endpoint 0 service routine as peripheral
+static void process_ep0_isr(uint8_t rhport) {
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
uint_fast8_t csrl = ep_csr->csr0l;
- // TU_LOG1(" EP0 ep_csr->csr0l = %x\r\n", csrl);
- // 21.1.5: endpoint 0 service routine as peripheral
-
+ // 21.1.5: SentStall and SetupEnd must be checked before anything else.
if (csrl & MUSB_CSRL0_STALLED) {
- /* Returned STALL packet to HOST. */
- ep_csr->csr0l = 0; /* Clear STALL */
+ ep_csr->csr0l = 0;
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->deferred_setup_valid = false;
+ pipe0->rxrdy_consumed = false;
return;
}
- unsigned req = _dcd.setup_packet.bmRequestType;
if (csrl & MUSB_CSRL0_SETEND) {
- TU_LOG1(" ABORT by the next packets\r\n");
+ // Host aborted the current control transfer (new SETUP or premature STATUS).
+ // do nothing, it is probably another setup packet, usbd will reset its state.
ep_csr->csr0l = MUSB_CSRL0_SETENDC;
- if (req != REQUEST_TYPE_INVALID && _dcd.pipe0.buf) {
- /* DATA stage was aborted by receiving STATUS or SETUP packet. */
- _dcd.pipe0.buf = NULL;
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- dcd_event_xfer_complete(rhport,
- req & TUSB_DIR_IN_MASK,
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->deferred_setup_valid = false;
+ pipe0->rxrdy_consumed = false;
+ if (!(csrl & MUSB_CSRL0_RXRDY)) {
+ return; /* no SETUP waiting behind it */
}
- req = REQUEST_TYPE_INVALID;
- if (!(csrl & MUSB_CSRL0_RXRDY)) return; /* Received SETUP packet */
}
+ // Receive Data (Setup or OUT)
if (csrl & MUSB_CSRL0_RXRDY) {
- /* Received SETUP or DATA OUT packet */
- if (req == REQUEST_TYPE_INVALID) {
- /* SETUP */
- TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0,);
- process_setup_packet(rhport);
- return;
+ if (pipe0->rxrdy_consumed) {
+ return; // stale latched IRQ: this RXRDY's packet was already drained
}
- if (_dcd.pipe0.buf) {
- /* DATA OUT */
- const unsigned vld = ep_csr->count0;
- const unsigned rem = _dcd.pipe0.remaining;
- const unsigned len = TU_MIN(TU_MIN(rem, 64), vld);
- volatile void *fifo_ptr = &musb_regs->fifo[0];
- tu_hwfifo_read(fifo_ptr, _dcd.pipe0.buf, len, NULL);
+ switch (pipe0->state) {
+ case PIPE0_STATE_IDLE: {
+ uint32_t setup[2];
+ TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, setup), );
+ pipe0_start_setup(rhport, ep_csr, setup, true);
+ break;
+ }
- _dcd.pipe0.remaining = rem - len;
- _dcd.remaining_ctrl -= len;
+ case PIPE0_STATE_DATA_OUT: {
+ // EP0 OUT is single-packet (TU_ASSERT total_bytes <= EP0_SIZE in edpt0_xfer)
+ // so the whole packet drains in one shot.
+ const uint16_t count0 = ep_csr->count0;
+ if (count0) {
+ TU_ASSERT(pipe0->buf, );
+ tu_hwfifo_read(&musb_regs->fifo[0], pipe0->buf, count0, NULL);
+ pipe0->remain_wlength -= tu_min16(count0, pipe0->remain_wlength); // clamp: host may overrun
+ }
+ // RXRDY stays set until the next edpt0_xfer arm acks it (NAK flow control):
+ // edpt0_xfer(DATA OUT) for a mid-stream packet, edpt0_xfer(STATUS IN) for the last.
+ pipe0->rxrdy_consumed = true;
+ if (pipe0_data_stage_done(count0)) {
+ pipe0->state = PIPE0_STATE_STATUS_IN;
+ }
+ dcd_event_xfer_complete(rhport, TU_EP0_OUT, count0, XFER_RESULT_SUCCESS, true);
+ break;
+ }
- _dcd.pipe0.buf = NULL;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_OUT),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
- }
- return;
- }
+ // New SETUP arrived while the old control transfer's tail events are still in flight (IRQs
+ // combined under high CPU load): the old transfer's status confirm and this SETUP land together.
+ case PIPE0_STATE_DATA_IN:
+ case PIPE0_STATE_STATUS_OUT:
+ case PIPE0_STATE_STATUS_OUT_PENDING_XFER:
+ case PIPE0_STATE_STATUS_OUT_PENDING_IRQ:
+ case PIPE0_STATE_STATUS_IN:
+ // Save it, then finish the old transfer's tail event; deferred_setup_valid makes
+ // pipe0_process_xfer_state_isr() synthesize the coalesced status confirm and replay the SETUP
+ // once the old transfer is retired. Its RXRDY stays parked so a stale IRQ can't re-process it.
+ TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, pipe0->deferred_setup), );
+ pipe0->deferred_setup_valid = true;
+ pipe0->rxrdy_consumed = true;
+ pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr);
+ break;
- /* When CSRL0 is zero, it means that completion of sending a any length packet
- * or receiving a zero length packet. */
- if (req != REQUEST_TYPE_INVALID && !tu_edpt_dir(req)) {
- /* STATUS IN */
- if (*(const uint16_t*)(uintptr_t)&_dcd.setup_packet == 0x0500) {
- /* The address must be changed on completion of the control transfer. */
- musb_regs->faddr = (uint8_t)_dcd.setup_packet.wValue;
+ default: break;
}
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_IN),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
+
return;
}
- if (_dcd.pipe0.buf) {
- /* DATA IN */
- _dcd.pipe0.buf = NULL;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(0, TUSB_DIR_IN),
- _dcd.pipe0.length - _dcd.pipe0.remaining,
- XFER_RESULT_SUCCESS, true);
- }
-}
-static void process_edpt_n(uint8_t rhport, uint_fast8_t ep_addr)
-{
- bool completed;
- const unsigned dir_in = tu_edpt_dir(ep_addr);
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned epn_minus1 = epn - 1;
-
- musb_regs_t* musb_regs = MUSB_REGS(rhport);
- musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn);
- if (dir_in) {
- // TU_LOG1(" TX CSRL%d = %x\r\n", epn, ep_csr->tx_csrl);
- if (ep_csr->tx_csrl & MUSB_TXCSRL1_STALLED) {
- ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN);
- return;
- }
- completed = handle_xfer_in(rhport, ep_addr);
- } else {
- // TU_LOG1(" RX CSRL%d = %x\r\n", epn, ep_csr->rx_csrl);
- if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
- ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
- return;
- }
- completed = handle_xfer_out(rhport, ep_addr);
+ if (csrl & MUSB_CSRL0_DATAEND) {
+ // Last DATA IN chunk / STATUS IN arm wrote TXRDY|DATAEND and the status stage has not completed
+ // yet โ€” nothing to service. DataEnd is CPU-set-only per the CSR access table; whether it ever
+ // reads back 1 is vendor-dependent (on cores where it reads 0 this guard is dead code).
+ return;
}
- if (completed) {
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epn_minus1];
- dcd_event_xfer_complete(rhport, ep_addr,
- pipe->length - pipe->remaining,
- XFER_RESULT_SUCCESS, true);
- }
+ /* When CSRL0 is zero, it means that either
+ * - completion of sending any length packet TxPktRdy clear
+ * - or status stage is complete (ZLP) after DataEnd is set */
+ pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr);
}
// Upon BUS RESET is detected, hardware havs already done:
// faddr = 0, index = 0, flushes all ep fifos, clears all ep csr, enabled all ep interrupts
-static void process_bus_reset(uint8_t rhport) {
+static void process_bus_reset_isr(uint8_t rhport) {
musb_regs_t* musb = MUSB_REGS(rhport);
#if MUSB_CFG_DYNAMIC_FIFO
alloced_fifo_bytes = CFG_TUD_ENDPOINT0_SIZE;
#endif
- /* When bmRequestType is REQUEST_TYPE_INVALID(0xFF), a control transfer state is SETUP or STATUS stage. */
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- _dcd.status_out = 0;
- /* When pipe0.buf has not NULL, DATA stage works in progress. */
- _dcd.pipe0.buf = NULL;
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->buf = NULL;
+ pipe0->xact_len = 0;
+ pipe0->remain_wlength = 0;
+ pipe0->deferred_setup_valid = false;
+ pipe0->rxrdy_consumed = false;
musb->intr_txen = 1; /* Enable only EP0 */
musb->intr_rxen = 0;
@@ -494,7 +652,7 @@ static void process_bus_reset(uint8_t rhport) {
*------------------------------------------------------------------*/
#if CFG_TUSB_DEBUG >= MUSB_DEBUG
-void print_musb_info(musb_regs_t* musb_regs) {
+static void print_musb_info(musb_regs_t* musb_regs) {
// print version, epinfo, raminfo, config_data0, fifo_size
TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor);
TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num);
@@ -537,18 +695,22 @@ void dcd_int_disable(uint8_t rhport) {
musb_dcd_int_disable(rhport);
}
-// Receive Set Address request, mcu port must also include status IN response
+// Receive Set Address request. Stash the new address here; hardware faddr is
+// latched from pending_addr in process_ep0_isr once the STATUS IN completes (per
+// USB spec, address must only take effect after the status stage).
void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
{
- (void)dev_addr;
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.length = 0;
- _dcd.pipe0.remaining = 0;
- /* Clear RX FIFO to return ACK. */
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ pipe0->pending_addr = dev_addr;
+ pipe0->buf = NULL;
+ pipe0->xact_len = 0;
+ pipe0->state = PIPE0_STATE_STATUS_IN;
+ /* Send STATUS IN ZLP with DATAEND; host ACK fires the confirmation IRQ. */
ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
+ pipe0->rxrdy_consumed = false;
}
// Wake up host
@@ -588,39 +750,36 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// Endpoint API
//--------------------------------------------------------------------+
-// static void edpt_setup(musb_regs_t* musb, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_size){
-// const unsigned epn = tu_edpt_number(ep_addr);
-// const unsigned dir_in = tu_edpt_dir(ep_addr);
-// }
// Configure endpoint's registers according to descriptor
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) {
const unsigned ep_addr = ep_desc->bEndpointAddress;
const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir_in = tu_edpt_dir(ep_addr);
+ const tusb_dir_t epdir = tu_edpt_dir(ep_addr);
const unsigned mps = tu_edpt_packet_size(ep_desc);
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epn - 1];
+ pipe_state_t *pipe = pipe_get(epn, epdir);
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn);
- const uint8_t is_rx = 1 - dir_in;
+ const uint8_t is_rx = (1 - epdir);
musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx];
maxp_csr->maxp = mps;
maxp_csr->csrh = 0;
#if MUSB_CFG_SHARED_FIFO
- if (dir_in) {
+ if (epdir) {
maxp_csr->csrh |= MUSB_CSRH_TX_MODE;
}
#endif
hwfifo_flush(musb, epn, is_rx, true);
- TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, false));
+ TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK));
musb->intren_ep[is_rx] |= TU_BIT(epn);
return true;
@@ -639,16 +798,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) {
const unsigned ep_addr = ep_desc->bEndpointAddress;
const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir_in = tu_edpt_dir(ep_addr);
+ const tusb_dir_t dir_in = tu_edpt_dir(ep_addr);
const unsigned mps = tu_edpt_packet_size(ep_desc);
unsigned const ie = musb_dcd_get_int_enable(rhport);
musb_dcd_int_disable(rhport);
- pipe_state_t *pipe = &_dcd.pipe[dir_in][epn - 1];
+ pipe_state_t *pipe = pipe_get(epn, dir_in);
pipe->buf = NULL;
pipe->length = 0;
pipe->remaining = 0;
+ pipe->armed = false;
musb_regs_t* musb = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn);
@@ -706,22 +866,22 @@ void dcd_edpt_close_all(uint8_t rhport)
// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
{
- (void) is_isr;
(void)rhport;
bool ret;
- // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes);
unsigned const epnum = tu_edpt_number(ep_addr);
unsigned const ie = musb_dcd_get_int_enable(rhport);
musb_dcd_int_disable(rhport);
if (epnum) {
- _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] &= ~TU_BIT(epnum - 1);
- ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes);
+ ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes, false, is_isr);
} else {
- ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes);
+ (void) is_isr;
+ ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes, is_isr);
}
- if (ie) musb_dcd_int_enable(rhport);
+ if (ie) {
+ musb_dcd_int_enable(rhport);
+ }
return ret;
}
@@ -729,16 +889,13 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
// - optional, however, must be listed in usbd.c
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr)
{
- (void) is_isr;
(void)rhport;
bool ret;
- // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes);
unsigned const epnum = tu_edpt_number(ep_addr);
TU_ASSERT(epnum);
unsigned const ie = musb_dcd_get_int_enable(rhport);
musb_dcd_int_disable(rhport);
- _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] |= TU_BIT(epnum - 1);
- ret = edpt_n_xfer(rhport, ep_addr, (uint8_t*)ff, total_bytes);
+ ret = edpt_n_xfer(rhport, ep_addr, ff, total_bytes, true, is_isr);
if (ie) musb_dcd_int_enable(rhport);
return ret;
}
@@ -753,14 +910,27 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn);
if (0 == epn) {
- if (!ep_addr) { /* Ignore EP80 */
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
- _dcd.pipe0.buf = NULL;
- ep_csr->csr0l = MUSB_CSRL0_STALL;
+ if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 IN */
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->buf = NULL;
+ if (pipe0->deferred_setup_valid) {
+ // A deferred SETUP means the stalled transfer already ended on the wire and the host's next
+ // request was ACKed โ€” SendStall would hit that innocent request. Replay it instead of stalling.
+ pipe0_try_deferred_setup(rhport, ep_csr, false);
+ } else {
+ // Forcing EP0 to IDLE: any RXRDY parked by the aborted transfer's flow control is stale,
+ // clear it so the next SETUP IRQ is not gated off.
+ pipe0->rxrdy_consumed = false;
+ ep_csr->csr0l = MUSB_CSRL0_STALL;
+ }
}
} else {
- const uint8_t is_rx = 1 - tu_edpt_dir(ep_addr);
+ const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr);
+ const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u);
ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_SEND_STALL(is_rx);
+ pipe_state_t* pipe = pipe_get(epn, ep_dir);
+ pipe->armed = false;
}
if (ie) musb_dcd_int_enable(rhport);
@@ -805,7 +975,7 @@ void dcd_int_handler(uint8_t rhport) {
dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
}
if (intr_usb & MUSB_IS_RESET) {
- process_bus_reset(rhport);
+ process_bus_reset_isr(rhport);
}
if (intr_usb & MUSB_IS_RESUME) {
dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
@@ -815,21 +985,35 @@ void dcd_int_handler(uint8_t rhport) {
}
intr_tx &= musb_regs->intr_txen; /* Clear disabled interrupts */
- if (intr_tx & TU_BIT(0)) {
- process_ep0(rhport);
- intr_tx &= ~TU_BIT(0);
- }
+
while (intr_tx) {
- unsigned const num = __builtin_ctz(intr_tx);
- process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_IN));
- intr_tx &= ~TU_BIT(num);
+ const unsigned epnum = __builtin_ctz(intr_tx);
+ if (epnum == 0) {
+ process_ep0_isr(rhport); // EP0 has its own state machine (control transfers)
+ } else {
+ process_epin_isr(rhport, musb_regs, epnum);
+ }
+ intr_tx &= ~TU_BIT(epnum);
+
+ // Double packet endpoint: TxPktRdy is clear, and interrupt is generated immediately when 1st packet is written.
+ // Also catches EP0 SETUP arriving during bulk processing.
+ uint_fast8_t new_intr_tx = musb_regs->intr_tx;
+ new_intr_tx &= musb_regs->intr_txen;
+
+ intr_tx |= new_intr_tx;
}
intr_rx &= musb_regs->intr_rxen; /* Clear disabled interrupts */
while (intr_rx) {
- unsigned const num = __builtin_ctz(intr_rx);
- process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_OUT));
- intr_rx &= ~TU_BIT(num);
+ unsigned const epnum = __builtin_ctz(intr_rx);
+ process_epout_isr(rhport, musb_regs, epnum, true);
+ intr_rx &= ~TU_BIT(epnum);
+
+ // Double packet endpoint: RxPktRdy is set and interrupt is generated immediately if 2nd packet is received
+ uint_fast8_t new_intr_rx = musb_regs->intr_rx;
+ new_intr_rx &= musb_regs->intr_rxen;
+
+ intr_rx |= new_intr_rx;
}
musb_regs->index = saved_index; // restore endpoint index
diff --git a/src/portable/mentor/musb/musb_max32.h b/src/portable/mentor/musb/musb_max32.h
index 599de2ca1..134b47122 100644
--- a/src/portable/mentor/musb/musb_max32.h
+++ b/src/portable/mentor/musb/musb_max32.h
@@ -47,7 +47,7 @@ extern "C" {
#define MUSB_CFG_SHARED_FIFO 1 // shared FIFO for TX and RX endpoints
#define MUSB_CFG_DYNAMIC_FIFO 0 // dynamic EP FIFO sizing
-const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS };
+static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS };
#if CFG_TUD_ENABLED
#define USBHS_M31_CLOCK_RECOVERY
diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h
index 68e89d77d..deaea8017 100644
--- a/src/portable/mentor/musb/musb_ti.h
+++ b/src/portable/mentor/musb/musb_ti.h
@@ -49,7 +49,7 @@
#define MUSB_CFG_DYNAMIC_FIFO 1
#define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096
-const uintptr_t MUSB_BASES[] = { USB0_BASE };
+static const uintptr_t MUSB_BASES[] = { USB0_BASE };
// Header supports both device and host modes. Only include what's necessary
#if CFG_TUD_ENABLED
diff --git a/src/portable/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h
index b2f6492fa..3d3c3c834 100644
--- a/src/portable/mentor/musb/musb_type.h
+++ b/src/portable/mentor/musb/musb_type.h
@@ -300,7 +300,7 @@ TU_VERIFY_STATIC(sizeof(musb_regs_t) == 0x350, "size is not correct");
// Helper
//--------------------------------------------------------------------+
TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_regs, unsigned epnum) {
- musb_regs->index = epnum;
+ musb_regs->index = (uint8_t)epnum;
return &musb_regs->indexed_csr;
}
@@ -336,7 +336,7 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_
#define MUSB_CSRL_CLEAR_DATA_TOGGLE(_rx) (1u << ((_rx) ? 7 : 6))
// 0x13, 0x17: TX/RX CSRH
-#define MUSB_CSRH_DISABLE_DOUBLE_PACKET(_rx) (1u << 1)
+#define MUSB_CSRH_DISABLE_DOUBLE_PACKET (1u << 1)
#define MUSB_CSRH_TX_MODE (1u << 5) // 1 = TX, 0 = RX. only relevant for SHARED FIFO
#define MUSB_CSRH_ISO (1u << 6)
@@ -568,6 +568,16 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_
//*****************************************************************************
//
+// The following are defines for the bit fields in the MUSB_O_TXMAXP / MUSB_O_RXMAXP
+// registers. Bits [10:0] carry the maximum packet size; bits [15:11] carry
+// numpackminus1 (HB-iso / HS-bulk multiplier - 1).
+//
+//*****************************************************************************
+#define MUSB_TXMAXP_PACKET_SIZE_M 0x07FFu
+#define MUSB_RXMAXP_PACKET_SIZE_M 0x07FFu
+
+//*****************************************************************************
+//
// The following are defines for the bit fields in the MUSB_O_TXCSRL1 register.
//
//*****************************************************************************
diff --git a/src/portable/microchip/samg/dcd_samg.c b/src/portable/microchip/samg/dcd_samg.c
index 4115eecc5..f8980b775 100644
--- a/src/portable/microchip/samg/dcd_samg.c
+++ b/src/portable/microchip/samg/dcd_samg.c
@@ -352,8 +352,8 @@ void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr)
csr_clear(epnum, UDP_CSR_FORCESTALL_Msk);
// must also reset EP to clear data toggle
- UDP->UDP_RST_EP |= (1 << epnum);
- UDP->UDP_RST_EP &= ~(1 << epnum);
+ UDP->UDP_RST_EP |= (1u << epnum);
+ UDP->UDP_RST_EP &= ~(1u << epnum);
}
//--------------------------------------------------------------------+
diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c
index 8a41c4790..befbaa338 100644
--- a/src/portable/nordic/nrf5x/dcd_nrf5x.c
+++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c
@@ -36,6 +36,8 @@
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wcast-align"
#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "nrf.h"
@@ -461,7 +463,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to
xfer->actual_len = 0;
// Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage
- bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE));
+ bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir((uint8_t)NRF_USBD->BMREQUESTTYPE));
if (control_status) {
// The nRF doesn't interrupt on status transmit so we queue up a success response.
@@ -1047,6 +1049,12 @@ void tusb_hal_nrf_power_event(uint32_t event) {
NVIC_EnableIRQ(USBD_IRQn);
}
+ // Ensure HFCLK is requested in the current context. The hfclk_enable() in
+ // USB_EVT_DETECTED may have been pre-SoftDevice. After Softdevice is
+ // enabled, HFXO is physically off again. So any caller that fires
+ // USB_EVT_READY post-SD would hang here.
+ hfclk_enable();
+
// Wait for HFCLK
while (!hfclk_running()) {}
diff --git a/src/portable/nuvoton/nuc120/dcd_nuc120.c b/src/portable/nuvoton/nuc120/dcd_nuc120.c
index d9a0e3fa8..2edb1bc7a 100644
--- a/src/portable/nuvoton/nuc120/dcd_nuc120.c
+++ b/src/portable/nuvoton/nuc120/dcd_nuc120.c
@@ -253,13 +253,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
/* allocate buffer from USB RAM */
ep->BUFSEG = bufseg_addr;
- bufseg_addr += size;
+ bufseg_addr += (uint32_t)size;
TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE);
/* construct USB Configuration Register value and then write it */
@@ -435,7 +435,7 @@ void dcd_int_handler(uint8_t rhport)
/* given ACK from host has happened, we can now set the address (if not already done) */
if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) USBD->FADDR = assigned_address;
- uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD;
+ uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD;
active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size);
@@ -453,7 +453,7 @@ void dcd_int_handler(uint8_t rhport)
{
USBD->INTSTS = mask;
- uint16_t const available_bytes = ep->MXPLD;
+ uint16_t const available_bytes = (uint16_t)ep->MXPLD;
uint8_t const ep_addr = decode_ep_addr(ep);
bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK);
diff --git a/src/portable/nuvoton/nuc121/dcd_nuc121.c b/src/portable/nuvoton/nuc121/dcd_nuc121.c
index 42fb58a0a..008c9df6b 100644
--- a/src/portable/nuvoton/nuc121/dcd_nuc121.c
+++ b/src/portable/nuvoton/nuc121/dcd_nuc121.c
@@ -42,6 +42,8 @@
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "NuMicro.h"
@@ -291,7 +293,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
@@ -478,7 +480,7 @@ void dcd_int_handler(uint8_t rhport)
{
if (status & USBD_INTSTS_EPEVT0_Msk) /* PERIPH_EP0 (EP0_IN) event: this is treated separately from the rest */
{
- uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD;
+ uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD;
active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size);
@@ -496,7 +498,7 @@ void dcd_int_handler(uint8_t rhport)
{
USBD->INTSTS = mask;
- uint16_t const available_bytes = ep->MXPLD;
+ uint16_t const available_bytes = (uint16_t)ep->MXPLD;
uint8_t const ep_addr = decode_ep_addr(ep);
bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK);
diff --git a/src/portable/nuvoton/nuc505/dcd_nuc505.c b/src/portable/nuvoton/nuc505/dcd_nuc505.c
index ca17d6251..a0f3d4c3f 100644
--- a/src/portable/nuvoton/nuc505/dcd_nuc505.c
+++ b/src/portable/nuvoton/nuc505/dcd_nuc505.c
@@ -42,6 +42,8 @@
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wredundant-decls"
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include "NUC505Series.h"
@@ -338,13 +340,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
/* mine the data for the information we need */
int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress);
- int const size = tu_edpt_packet_size(p_endpoint_desc);
+ uint16_t const size = tu_edpt_packet_size(p_endpoint_desc);
tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer;
struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP];
/* allocate buffer from USB RAM */
ep->EPBUFSTART = bufseg_addr;
- bufseg_addr += size;
+ bufseg_addr += (uint32_t)size;
ep->EPBUFEND = bufseg_addr - 1;
TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE);
diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
index 349229c8d..2840c6d5e 100644
--- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
+++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
@@ -131,7 +131,7 @@ static uint8_t sie_read (uint8_t cmd_code)
//--------------------------------------------------------------------+
static inline uint8_t ep_addr2idx(uint8_t ep_addr)
{
- return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0);
+ return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0));
}
static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
@@ -243,7 +243,7 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
static inline uint8_t byte2dword(uint8_t bytes)
{
- return (bytes + 3) / 4; // length in dwords
+ return (uint8_t)((bytes + 3) / 4); // length in dwords
}
static void control_ep_write(void const * buffer, uint8_t len)
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index 5f4a441dc..8adf0f840 100644
--- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
+++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
@@ -243,7 +243,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer)
}
TU_ATTR_ALWAYS_INLINE static inline uint8_t ep_addr2id(uint8_t ep_addr) {
- return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0);
+ return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0));
}
TU_ATTR_ALWAYS_INLINE static inline bool ep_is_iso(ep_cmd_sts_t* ep_cs, bool is_highspeed) {
@@ -539,8 +539,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) {
uint16_t buf_nbytes;
if ( rhport_is_highspeed(rhport) ) {
- buf_offset = ep_cs->buffer_hs.offset;
- buf_nbytes = ep_cs->buffer_hs.nbytes;
+ buf_offset = (uint16_t)ep_cs->buffer_hs.offset;
+ buf_nbytes = (uint16_t)ep_cs->buffer_hs.nbytes;
#if TU_CHECK_MCU(OPT_MCU_LPC54)
// LPC54 Errata USB.2: In USB high-speed device mode, the NBytes field is not correct after BULK IN transfer
@@ -550,8 +550,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) {
}
#endif
} else {
- buf_offset = ep_cs->buffer_fs.offset;
- buf_nbytes = ep_cs->buffer_fs.nbytes;
+ buf_offset = (uint16_t)ep_cs->buffer_fs.offset;
+ buf_nbytes = (uint16_t)ep_cs->buffer_fs.nbytes;
}
xfer_dma->xferred_bytes += xfer_dma->nbytes - buf_nbytes;
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index 240e6c727..2b6bbc43b 100644
--- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
@@ -69,63 +69,69 @@ TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8
return hw_endpoint_get(num, dir);
}
-// main processing for dcd_edpt_iso_activate
-static void hw_endpoint_init(hw_endpoint_t *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- ep->ep_addr = ep_addr;
- ep->next_pid = 0u;
- ep->wMaxPacketSize = wMaxPacketSize;
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ if (epnum == 0) {
+ // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
+ return NULL;
+ }
+ struct usb_device_dpram_ep_ctrl *ep_ctrl = &usb_dpram->ep_ctrl[epnum - 1];
+ return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_dir_t dir) {
+ struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum];
+ return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out;
+}
+
+// Init and enable endpoint
+static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, bool ep_enabled) {
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ ep->ep_addr = ep_addr;
+ ep->next_pid = 0u;
+ ep->max_packet_size = wMaxPacketSize;
// Clear existing buffer control state
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- *buf_ctrl_reg = 0;
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
// allocated hw buffer
- const uint8_t epnum = tu_edpt_number(ep_addr);
if (epnum == 0) {
- // Buffer offset is fixed (also double buffered)
- ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0];
+ // Buffer offset is fixed (2 buffer allocated).
+ // Note: Only single buffer for EP since Double buffered RX can be troublesome with future data.
+ ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0];
} else {
+ uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+
// round up size to multiple of 64
uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64);
// double buffered Bulk endpoint
if (transfer_type == TUSB_XFER_BULK) {
size *= 2u;
-
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ if (dir == TUSB_DIR_IN) {
ep->e15_bulk_in = true;
}
#endif
}
// assign buffer
- ep->hw_data_buf = hw_buffer_ptr;
+ ep->dpram_buf = hw_buffer_ptr;
hw_buffer_ptr += size;
- hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
- pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->hw_data_buf);
- }
-}
-
-static void hw_endpoint_enable(hw_endpoint_t *ep, uint8_t transfer_type) {
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_device(ep);
- // Set endpoint control register to enable (EP0 has no endpoint control register)
- if (ctrl_reg != NULL) {
- const uint32_t ctrl_value =
- EP_CTRL_ENABLE_BITS | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf);
- *ctrl_reg = ctrl_value;
- }
-}
+ ep_ctrl |= hw_data_offset(ep->dpram_buf);
+ if (ep_enabled) {
+ ep_ctrl |= EP_CTRL_ENABLE_BITS;
+ }
-// Init and enable endpoint
-static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) {
- const uint8_t epnum = tu_edpt_number(ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ *get_ep_ctrl(epnum, dir) = ep_ctrl;
- hw_endpoint_init(ep, ep_addr, wMaxPacketSize, transfer_type);
- hw_endpoint_enable(ep, transfer_type);
+ hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data));
+ pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf);
+ }
}
static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
@@ -141,14 +147,9 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
while ((usb_hw->abort_done & abort_mask) != abort_mask) {}
}
- uint32_t buf_ctrl = USB_BUF_CTRL_SEL; // reset to buffer 0
- if (ep->next_pid) {
- buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
- }
-
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
- hw_endpoint_reset_transfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0; // clear buffer control
+ rp2usb_reset_transfer(ep);
if (rp2040_chip_version() >= 2) {
usb_hw_clear->abort_done = abort_mask;
@@ -157,29 +158,33 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) {
}
static void __tusb_irq_path_func(handle_hw_buff_status)(void) {
- uint32_t remaining_buffers = usb_hw->buf_status;
- pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers);
- uint bit = 1u;
- for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) {
- if (remaining_buffers & bit) {
- // clear this in advance
- usb_hw_clear->buf_status = bit;
+ uint32_t buf_status = usb_hw->buf_status;
+ pico_trace("buf_status = 0x%08lx\r\n", buf_status);
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t i = (uint8_t) __builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(i);
+
+ // IN transfer for even i, OUT transfer for odd i
+ const uint8_t epnum = i >> 1u;
+ const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
- // IN transfer for even i, OUT transfer for odd i
- const uint8_t epnum = i >> 1u;
- const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN;
- hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & bit) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; // before clear buf_status
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- // Notify usbd
+ if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id, dir == TUSB_DIR_OUT)) {
const uint16_t xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true);
}
- remaining_buffers &= ~bit;
}
- bit <<= 1u;
}
}
@@ -189,7 +194,7 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) {
for (uint8_t dir = 0; dir < 2; dir++) {
struct hw_endpoint *ep = hw_endpoint_get(0, dir);
ep->next_pid = 1u;
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs
}
}
@@ -211,52 +216,25 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) {
static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
const uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
if (status & USB_INTF_DEV_SOF_BITS) {
- bool keep_sof_alive = false;
-
- handled |= USB_INTF_DEV_SOF_BITS;
-
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- // Errata 15 workaround for Device Bulk-In endpoint
- e15_last_sof = time_us_32();
-
- for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
- struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
-
- // Active Bulk IN endpoint requires SOF
- if (ep->e15_bulk_in && ep->active) {
- keep_sof_alive = true;
-
- hw_endpoint_lock_update(ep, 1);
- if (ep->pending) {
- ep->pending = 0;
- hw_endpoint_start_next_buffer(ep);
- }
- hw_endpoint_lock_update(ep, -1);
- }
- }
-#endif
+ uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD
- // disable SOF interrupt if it is used for RESUME in remote wakeup
- if (!keep_sof_alive && !_sof_enable) {
- usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
- }
+ #if CFG_TUSB_RP2_ERRATA_E15
+ e15_last_sof = time_us_32(); // timing critical
+ #endif
- dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true);
+ dcd_event_sof(0, sof_count, true);
}
// xfer events are handled before setup req. So if a transfer completes immediately
// before closing the EP, the events will be delivered in same order.
if (status & USB_INTS_BUFF_STATUS_BITS) {
- handled |= USB_INTS_BUFF_STATUS_BITS;
handle_hw_buff_status();
}
if (status & USB_INTS_SETUP_REQ_BITS) {
- handled |= USB_INTS_SETUP_REQ_BITS;
- uint8_t const* setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet);
+ const uint8_t *setup = remove_volatile_cast(const uint8_t *, &usb_dpram->setup_packet);
// reset pid to both 1 (data and ack)
reset_ep0();
@@ -266,19 +244,68 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS;
}
-#if FORCE_VBUS_DETECT == 0
+ // Errata 15 workaround for Device Bulk-In endpoint, must be after BUF_STATUS interrupt to sync buf control first
+ if (status & USB_INTF_DEV_SOF_BITS) {
+ bool keep_sof_alive = false;
+
+ #if CFG_TUSB_RP2_ERRATA_E15
+ for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) {
+ struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN);
+
+ // Active Bulk IN endpoint requires SOF
+ if (ep->e15_bulk_in && ep->state >= EPSTATE_ACTIVE) {
+ keep_sof_alive = true;
+ hw_endpoint_lock_update(ep, 1);
+
+ if (ep->state == EPSTATE_PENDING) {
+ ep->state = EPSTATE_ACTIVE;
+
+ io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN);
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32;
+
+ // Check each buffer half: idle when both FULL and AVAIL are clear.
+ // Use 16-bit writes to avoid clobbering the other half (DPSRAM concurrent access).
+ enum {
+ BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL
+ };
+
+ const bool buf0_idle = !(buf_reg16[0] & BUSY_MASK);
+ const bool buf1_idle = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK);
+
+ if (buf0_idle && buf1_idle) {
+ // both are idle, start fresh
+ io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg32, false);
+ } else if (buf0_idle) {
+ uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false);
+ bufctrl_write16(buf_reg16, buf0);
+ } else if (buf1_idle) {
+ uint16_t buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false);
+ bufctrl_write16(buf_reg16 + 1, buf1);
+ }
+ }
+
+ hw_endpoint_lock_update(ep, -1);
+ }
+ }
+ #endif
+
+ // disable SOF interrupt if it is used for RESUME in remote wakeup
+ if (!keep_sof_alive && !_sof_enable) {
+ usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS;
+ }
+ }
+
+ #if FORCE_VBUS_DETECT == 0
// Since we force VBUS detect On, device will always think it is connected and
// couldn't distinguish between disconnect and suspend
if (status & USB_INTS_DEV_CONN_DIS_BITS) {
- handled |= USB_INTS_DEV_CONN_DIS_BITS;
-
if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) {
// Connected: nothing to do
} else {
// Disconnected
dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true);
}
-
usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS;
}
#endif
@@ -286,9 +313,6 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
// SE0 for 2.5 us or more (will last at least 10ms)
if (status & USB_INTS_BUS_RESET_BITS) {
pico_trace("BUS RESET\r\n");
-
- handled |= USB_INTS_BUS_RESET_BITS;
-
usb_hw->dev_addr_ctrl = 0;
reset_non_control_endpoints();
dcd_event_bus_reset(0, TUSB_SPEED_FULL, true);
@@ -311,20 +335,15 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) {
* being disconnected and suspended.
*/
if (status & USB_INTS_DEV_SUSPEND_BITS) {
- handled |= USB_INTS_DEV_SUSPEND_BITS;
dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS;
}
if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) {
- handled |= USB_INTS_DEV_RESUME_FROM_HOST_BITS;
dcd_event_bus_signal(0, DCD_EVENT_RESUME, true);
usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS;
}
- if (status ^ handled) {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
- }
}
/*------------------------------------------------------------------*/
@@ -340,7 +359,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
(void) rh_init;
assert(rhport == 0);
- TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version());
+ // TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version());
// Reset hardware to default state
rp2usb_init();
@@ -354,8 +373,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Init control endpoints
tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t));
- hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL);
- hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL);
+ hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL, false);
+ hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL, false);
// Init non-control endpoints
reset_non_control_endpoints();
@@ -436,7 +455,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
if (en) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
}
-#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if !CFG_TUSB_RP2_ERRATA_E15
else {
// Don't clear immediately if the SOF workaround is in use.
// The SOF handler will conditionally disable the interrupt.
@@ -461,7 +480,7 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* req
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
(void) rhport;
const uint8_t xfer_type = desc_edpt->bmAttributes.xfer;
- hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type);
+ hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type, true);
return true;
}
@@ -469,8 +488,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
(void)rhport;
- struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_init(ep, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS);
+ hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false);
return true;
}
@@ -480,14 +498,18 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc)
const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress);
const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress);
struct hw_endpoint *ep = hw_endpoint_get(epnum, dir);
- TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously
+ TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously
- if (ep->active) {
+ if (ep->state == EPSTATE_ACTIVE) {
hw_endpoint_abort_xfer(ep); // abort any pending transfer
}
- ep->wMaxPacketSize = ep_desc->wMaxPacketSize;
+ ep->max_packet_size = ep_desc->wMaxPacketSize;
- hw_endpoint_enable(ep, TUSB_XFER_ISOCHRONOUS);
+ // enable endpoint
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ if (ep_reg != NULL) {
+ *ep_reg |= EP_CTRL_ENABLE_BITS;
+ }
return true;
}
@@ -500,18 +522,27 @@ void dcd_edpt_close_all(uint8_t rhport) {
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, buffer, NULL, total_bytes);
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes);
return true;
}
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) {
(void)rhport;
(void)is_isr;
- hw_endpoint_t *ep = hw_endpoint_get_by_addr(ep_addr);
- hw_endpoint_xfer_start(ep, NULL, ff, total_bytes);
+ hw_endpoint_t *ep = hw_endpoint_get(epnum, dir);
+ io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes);
return true;
}
+#endif
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
(void)rhport;
@@ -524,21 +555,22 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS;
}
- // stall and clear current pending buffer, may need to use EP_ABORT
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ // abort first then stall and clear current pending buffer
+ hw_endpoint_abort_xfer(ep);
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = USB_BUF_CTRL_STALL;
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
(void) rhport;
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
- if (tu_edpt_number(ep_addr)) {
- struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr);
-
- // clear stall also reset toggle to DATA0, ready for next transfer
- ep->next_pid = 0;
- io_rw_32 *buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_clear_mask(buf_ctrl_reg, USB_BUF_CTRL_STALL);
+ if (epnum != 0) {
+ struct hw_endpoint* ep = hw_endpoint_get(epnum, dir);
+ ep->next_pid = 0; // reset data toggle
+ io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir);
+ *buf_reg = 0;
}
}
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 06c0ce340..064834efb 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -1,3 +1,4 @@
+
/*
* The MIT License (MIT)
*
@@ -29,48 +30,65 @@
#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421
-#include "pico.h"
-#include "rp2040_usb.h"
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
+ #include "pico.h"
-#include "host/hcd.h"
-#include "host/usbh.h"
+ #if defined(PICO_RP2350) && PICO_RP2350 == 1
+ #define HAS_STOP_EPX_ON_NAK
+ #endif
// port 0 is native USB port, other is counted as software PIO
-#define RHPORT_NATIVE 0
+ #define RHPORT_NATIVE 0
+
+ //--------------------------------------------------------------------+
+ // INCLUDE
+ //--------------------------------------------------------------------+
+ #include "rp2040_usb.h"
+ #include "osal/osal.h"
+
+ #include "host/hcd.h"
+ #include "host/usbh.h"
//--------------------------------------------------------------------+
-// Low level rp2040 controller functions
+//
//--------------------------------------------------------------------+
-#ifndef PICO_USB_HOST_INTERRUPT_ENDPOINTS
-#define PICO_USB_HOST_INTERRUPT_ENDPOINTS (USB_MAX_ENDPOINTS - 1)
-#endif
-static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, "");
-
// Host mode uses one shared endpoint register for non-interrupt endpoint
-static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS];
-#define epx (ep_pool[0])
+static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS];
+static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint
+
+ #ifndef HAS_STOP_EPX_ON_NAK
+static volatile bool epx_switch_request = false;
+ #endif
+
+enum {
+ SIE_CTRL_SPEED_DISCONNECT = 0,
+ SIE_CTRL_SPEED_LOW = 1,
+ SIE_CTRL_SPEED_FULL = 2,
+};
-// Flags we set by default in sie_ctrl (we add other bits on top)
enum {
- SIE_CTRL_BASE = USB_SIE_CTRL_SOF_EN_BITS | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS |
- USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS
+ EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data)
};
-static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
- uint8_t num = tu_edpt_number(ep_addr);
- if (num == 0) {
- return &epx;
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
+static hw_endpoint_t *edpt_alloc(void) {
+ for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->max_packet_size == 0) {
+ return ep;
+ }
}
+ return NULL;
+}
- for (uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- struct hw_endpoint *ep = &ep_pool[i];
- if (ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr)) {
+static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) {
+ for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) &&
+ (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) {
return ep;
}
}
@@ -78,6 +96,18 @@ static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) {
return NULL;
}
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl;
+}
+
+TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) {
+ return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl;
+}
+
+//--------------------------------------------------------------------+
+//
+//--------------------------------------------------------------------+
+
TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) {
return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB;
}
@@ -88,253 +118,300 @@ TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) {
return hcd_port_speed_get(0) != tuh_speed_get(dev_addr);
}
-static void __tusb_irq_path_func(hw_xfer_complete)(struct hw_endpoint *ep, xfer_result_t xfer_result) {
- // Mark transfer as done before we tell the tinyusb stack
- uint8_t dev_addr = ep->dev_addr;
- uint8_t ep_addr = ep->ep_addr;
- uint xferred_len = ep->xferred_len;
- hw_endpoint_reset_transfer(ep);
- hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, xfer_result, true);
+//--------------------------------------------------------------------+
+// EPX
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) {
+ uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
}
-static void __tusb_irq_path_func(handle_hwbuf_status_bit)(uint bit, struct hw_endpoint *ep) {
- usb_hw_clear->buf_status = bit;
- const bool done = hw_endpoint_xfer_continue(ep);
- if (done) {
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
+static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits
+ if (send_setup) {
+ sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS;
+ } else {
+ sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS);
+ }
+ if (need_pre) {
+ sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS;
}
+
+ // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit
+ // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".!
+ // We write everything except the START_TRANS bit first, then wait some cycles.
+ usb_hw->sie_ctrl = sie_ctrl;
+ busy_wait_at_least_cycles(12);
+ usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS;
}
-static void __tusb_irq_path_func(handle_hwbuf_status)(void) {
- uint32_t buf_status = usb_hw->buf_status;
- pico_trace("buf_status 0x%08lx\n", buf_status);
+// prepare epx_ctrl register for new endpoint
+TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) {
+ usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB);
+}
- // Check EPX first
- uint32_t bit = 1u;
- if (buf_status & bit) {
- buf_status &= ~bit;
- struct hw_endpoint * ep = &epx;
- handle_hwbuf_status_bit(bit, ep);
- }
+// Save buffer context for EPX preemption (called after STOP_TRANS).
+// Undo PID toggle and buffer accounting for buffers NOT completed on the wire.
+// A buffer completed on wire means: controller reached STATUS phase (ACK received).
+// OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed)
+// IN completed: FULL set to 1 in STATUS phase (was 0 when armed)
+// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0)
+static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) {
+ uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl;
+ const bool is_out = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT);
- // Check "interrupt" (asynchronous) endpoints for both IN and OUT
- for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && buf_status; i++) {
- // EPX is bit 0 & 1
- // IEP1 IN is bit 2
- // IEP1 OUT is bit 3
- // IEP2 IN is bit 4
- // IEP2 OUT is bit 5
- // IEP3 IN is bit 6
- // IEP3 OUT is bit 7
- // etc
- for (uint j = 0; j < 2; j++) {
- bit = 1 << (i * 2 + j);
- if (buf_status & bit) {
- buf_status &= ~bit;
- handle_hwbuf_status_bit(bit, &ep_pool[i]);
+ do {
+ const uint16_t bc16 = (uint16_t)buf_ctrl;
+ if (bc16) {
+ const bool avail = (bc16 & USB_BUF_CTRL_AVAIL);
+ const bool full = (bc16 & USB_BUF_CTRL_FULL);
+ if (avail || (is_out ? full : !full)) {
+ const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK;
+ ep->remaining_len += buf_len;
+ ep->next_pid ^= 1u;
+ if (is_out) {
+ ep->user_buf -= buf_len;
+ }
}
}
- }
- if (buf_status) {
- panic("Unhandled buffer %d\n", buf_status);
- }
-}
+ if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) {
+ buf_ctrl >>= 16;
+ } else {
+ buf_ctrl = 0;
+ }
+ } while (buf_ctrl > 0);
-static void __tusb_irq_path_func(hw_trans_complete)(void)
-{
- if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS)
- {
- pico_trace("Sent setup packet\n");
- struct hw_endpoint *ep = &epx;
- assert(ep->active);
- // Set transferred length to 8 for a setup packet
- ep->xferred_len = 8;
- hw_xfer_complete(ep, XFER_RESULT_SUCCESS);
- }
- else
- {
- // Don't care. Will handle this in buff status
- return;
- }
+ usbh_dpram->epx_buf_ctrl = 0;
+
+ ep->state = EPSTATE_PENDING;
}
-static void __tusb_irq_path_func(hcd_rp2040_irq)(void)
-{
- uint32_t status = usb_hw->ints;
- uint32_t handled = 0;
+// switch epx to new endpoint and start the transfer
+static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) {
+ const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP);
- if ( status & USB_INTS_HOST_CONN_DIS_BITS )
- {
- handled |= USB_INTS_HOST_CONN_DIS_BITS;
+ epx = ep; // switch pointer
+ ep->state = EPSTATE_ACTIVE;
- if ( dev_speed() )
- {
- hcd_event_device_attach(RHPORT_NATIVE, true);
- }
- else
- {
- hcd_event_device_remove(RHPORT_NATIVE, true);
- }
+ if (is_setup) {
+ // panic("new setup \n");
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, false, ep->need_pre);
+ } else {
+ const bool is_rx = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // Clear speed change interrupt
- usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
- }
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
- if ( status & USB_INTS_STALL_BITS )
- {
- // We have rx'd a stall from the device
- // NOTE THIS SHOULD HAVE PRIORITY OVER BUFF_STATUS
- // AND TRANS_COMPLETE as the stall is an alternative response
- // to one of those events
- pico_trace("Stall REC\n");
- handled |= USB_INTS_STALL_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
- hw_xfer_complete(&epx, XFER_RESULT_STALLED);
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(is_setup, is_rx, ep->need_pre);
}
+}
- if ( status & USB_INTS_BUFF_STATUS_BITS )
- {
- handled |= USB_INTS_BUFF_STATUS_BITS;
- TU_LOG(2, "Buffer complete\r\n");
- handle_hwbuf_status();
+// Round-robin find next pending ep after current epx
+static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) {
+ const uint cur_idx = (uint)(cur_ep - &ep_pool[0]);
+ for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
-
- if ( status & USB_INTS_TRANS_COMPLETE_BITS )
- {
- handled |= USB_INTS_TRANS_COMPLETE_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
- TU_LOG(2, "Transfer complete\r\n");
- hw_trans_complete();
+ for (uint i = 0; i < cur_idx; i++) {
+ if (ep_pool[i].state >= EPSTATE_PENDING) {
+ return &ep_pool[i];
+ }
}
+ return NULL;
+}
- if ( status & USB_INTS_ERROR_RX_TIMEOUT_BITS )
- {
- handled |= USB_INTS_ERROR_RX_TIMEOUT_BITS;
- usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- }
- if ( status & USB_INTS_ERROR_DATA_SEQ_BITS )
- {
- usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
- TU_LOG(3, " Seq Error: [0] = 0x%04u [1] = 0x%04x\r\n", tu_u32_low16(*hwbuf_ctrl_reg_host(&epx)),
- tu_u32_high16(*hwbuf_ctrl_reg_host(&epx)));
- panic("Data Seq Error \n");
- }
+//--------------------------------------------------------------------+
+// Interrupt handlers
+//--------------------------------------------------------------------+
+static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) {
+ // Mark transfer as done before we tell the tinyusb stack
+ uint32_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true);
- if ( status ^ handled )
- {
- panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled));
+ // Carry more transfer on epx
+ if (is_more) {
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_switch_ep(next_ep);
+ }
}
}
-void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
- (void) rhport;
- (void) in_isr;
- hcd_rp2040_irq();
-}
+static void __tusb_irq_path_func(handle_buf_status_isr)(void) {
+ pico_trace("buf_status 0x%08lx\n", buf_status);
+ enum {
+ BUF_STATUS_EPX = 1u
+ };
-static struct hw_endpoint *_next_free_interrupt_ep(void)
-{
- struct hw_endpoint * ep = NULL;
- for ( uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ )
- {
- ep = &ep_pool[i];
- if ( !ep->configured )
- {
- // Will be configured by hw_endpoint_init / hw_endpoint_allocate
- ep->interrupt_num = (uint8_t) (i - 1);
- return ep;
+ // Check EPX first (bit 0).
+ // Double-buffered: if both buffers completed at once, buf_status re-sets
+ // immediately after clearing (datasheet Table 406). Process the second buffer too.
+ while (usb_hw->buf_status & BUF_STATUS_EPX) {
+ const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0;
+ usb_hw_clear->buf_status = 1u; // clear
+
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
+ #ifndef HAS_STOP_EPX_ON_NAK
+ // Any packet completion (mid-transfer or final) means data is flowing.
+ // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints.
+ epx_switch_request = false;
+ #endif
+ if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) {
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
}
}
- return ep;
-}
-static hw_endpoint_t *hw_endpoint_allocate(uint8_t transfer_type) {
- hw_endpoint_t *ep = NULL;
+ // Check "interrupt" (asynchronous) endpoints for both IN and OUT
+ uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX;
+ while (buf_status) {
+ // ctz/clz is faster than loop which has only a few bit set in general
+ const uint8_t idx = (uint8_t)__builtin_ctz(buf_status);
+ const uint32_t bit = TU_BIT(idx);
+ usb_hw_clear->buf_status = bit;
+ buf_status &= ~bit;
- if (transfer_type == TUSB_XFER_CONTROL) {
- ep = &epx;
- ep->hw_data_buf = &usbh_dpram->epx_data[0];
- } else {
- // Note: even though datasheet name these "Interrupt" endpoints. These are actually
- // "Asynchronous" endpoints and can be used for other type such as: Bulk (ISO need confirmation)
- ep = _next_free_interrupt_ep();
- pico_info("Allocate %s ep %d\n", tu_edpt_type_str(transfer_type), ep->interrupt_num);
- assert(ep);
- // 0 for epx (double buffered): TODO increase to 1024 for ISO
- // 2x64 for intep0
- // 3x64 for intep1
- // etc
- ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)];
+ // IN transfer for even i, OUT transfer for odd i
+ // EPX is bit 0. Bit 1 is not used
+ // IEP1 IN/OUT is bit 2, 3
+ // IEP2 IN/OUT is bit 4, 5 etc
+ const uint8_t epnum = idx >> 1u;
+ for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) {
+ hw_endpoint_t *ep = &ep_pool[e];
+ if (ep->interrupt_num == epnum) {
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN);
+ if (done) {
+ xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false);
+ }
+ break;
+ }
+ }
}
-
- return ep;
}
-static void hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize,
- uint8_t transfer_type, uint8_t bmInterval) {
- // Already has data buffer, endpoint control, and buffer control allocated at this point
- assert(ep->hw_data_buf);
+static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
+ const uint32_t status = usb_hw->ints;
- uint8_t const num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ if (status & USB_INTS_HOST_CONN_DIS_BITS) {
+ uint8_t speed = dev_speed();
+ if (speed == SIE_CTRL_SPEED_DISCONNECT) {
+ hcd_event_device_remove(RHPORT_NATIVE, true);
+ } else {
+ if (speed == SIE_CTRL_SPEED_LOW) {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS;
+ } else {
+ usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS;
+ }
+ hcd_event_device_attach(RHPORT_NATIVE, true);
+ }
+ usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS;
+ }
- ep->ep_addr = ep_addr;
- ep->dev_addr = dev_addr;
+ if (status & USB_INTS_STALL_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS;
+ xfer_complete_isr(epx, XFER_RESULT_STALLED, true);
+ }
- // Response to a setup packet on EP0 starts with pid of 1
- ep->next_pid = (num == 0 ? 1u : 0u);
- ep->wMaxPacketSize = wMaxPacketSize;
+ if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS;
- pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, transfer_type);
- pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf);
- uint dpram_offset = hw_data_offset(ep->hw_data_buf);
- // Bits 0-5 should be 0
- assert(!(dpram_offset & 0b111111));
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
- // Fill in endpoint control register with buffer offset
- uint32_t ctrl_value = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
- ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset;
- if (bmInterval) {
- ctrl_value |= (uint32_t)((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time
+ // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe.
+ if (epx->state == EPSTATE_ACTIVE) {
+ xfer_complete_isr(epx, XFER_RESULT_FAILED, false);
+ }
}
- io_rw_32 *ctrl_reg = hwep_ctrl_reg_host(ep);
- *ctrl_reg = ctrl_value;
- pico_trace("endpoint control (0x%p) <- 0x%lx\n", ctrl_reg, ctrl_value);
- ep->configured = true;
+ if (status & USB_INTS_TRANS_COMPLETE_BITS) {
+ // only applies for epx, interrupt endpoint does not seem to raise this
+ usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS;
+ if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) {
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK;
+ usb_hw->sie_ctrl = sie_ctrl; // clear setup bit
+ epx->xferred_len = 8;
+ xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true);
+ }
+ }
- if (ep != &epx) {
- // Endpoint has its own addr_endp and interrupt bits to be setup!
- // This is an interrupt/async endpoint. so need to set up ADDR_ENDP register with:
- // - device address
- // - endpoint number / direction
- // - preamble
- uint32_t reg = (uint32_t)(dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (status & USB_INTS_BUFF_STATUS_BITS) {
+ handle_buf_status_isr();
+ }
- if (dir == TUSB_DIR_OUT) {
- reg |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing
+ // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr.
+ #ifdef HAS_STOP_EPX_ON_NAK
+ if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS;
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep != NULL) {
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre);
}
-
- if (need_pre(dev_addr)) {
- reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ #else
+ // RP2040: on SOF, switch EPX if another endpoint is pending.
+ // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is
+ // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means
+ // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch.
+ // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking.
+ if (status & USB_INTS_HOST_SOF_BITS) {
+ (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep == NULL) {
+ usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
+ usb_hw->nak_poll = USB_NAK_POLL_RESET;
+ epx_switch_request = false;
+ } else if (epx->state == EPSTATE_ACTIVE) {
+ if (epx_switch_request) {
+ // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch.
+ epx_switch_request = false;
+ sie_stop_xfer();
+ epx_save_context(epx);
+ epx_switch_ep(next_ep);
+ } else {
+ epx_switch_request = true;
+ }
}
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = reg;
-
- // Finally, enable interrupt that endpoint
- usb_hw_set->int_ep_ctrl = 1 << (ep->interrupt_num + 1);
+ }
+ #endif
- // If it's an interrupt endpoint we need to set up the buffer control register
+ if (status & USB_INTS_ERROR_DATA_SEQ_BITS) {
+ usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS;
+ panic("Data Seq Error \n");
}
}
+void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) {
+ (void)rhport;
+ (void)in_isr;
+ hcd_rp2040_irq();
+}
+
//--------------------------------------------------------------------+
// HCD API
//--------------------------------------------------------------------+
-bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
pico_trace("hcd_init %d\n", rhport);
assert(rhport == 0);
@@ -346,7 +423,6 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Remove shared irq if it was previously added so as not to fill up shared irq slots
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
-
irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY);
// clear epx and interrupt eps
@@ -354,97 +430,83 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// Enable in host mode with SOF / Keep alive on
usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS;
- usb_hw->sie_ctrl = SIE_CTRL_BASE;
- usb_hw->inte = USB_INTE_BUFF_STATUS_BITS |
- USB_INTE_HOST_CONN_DIS_BITS |
- USB_INTE_HOST_RESUME_BITS |
- USB_INTE_STALL_BITS |
- USB_INTE_TRANS_COMPLETE_BITS |
- USB_INTE_ERROR_RX_TIMEOUT_BITS |
- USB_INTE_ERROR_DATA_SEQ_BITS ;
+ usb_hw->sie_ctrl = SIE_CTRL_BASE;
+ usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS |
+ USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS |
+ USB_INTE_ERROR_DATA_SEQ_BITS;
+
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS;
+ #endif
return true;
}
bool hcd_deinit(uint8_t rhport) {
- (void) rhport;
-
+ (void)rhport;
irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq);
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-
return true;
}
-void hcd_port_reset(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_reset\n");
- assert(rhport == 0);
+void hcd_port_reset(uint8_t rhport) {
+ (void)rhport;
// TODO: Nothing to do here yet. Perhaps need to reset some state?
}
-void hcd_port_reset_end(uint8_t rhport)
-{
- (void) rhport;
+void hcd_port_reset_end(uint8_t rhport) {
+ (void)rhport;
}
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void) rhport;
- pico_trace("hcd_port_connect_status\n");
- assert(rhport == 0);
+bool hcd_port_connect_status(uint8_t rhport) {
+ (void)rhport;
return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS;
}
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void) rhport;
- assert(rhport == 0);
-
- // TODO: Should enumval this register
- switch ( dev_speed() )
- {
- case 1:
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ (void)rhport;
+ switch (dev_speed()) {
+ case SIE_CTRL_SPEED_LOW:
return TUSB_SPEED_LOW;
- case 2:
+ case SIE_CTRL_SPEED_FULL:
return TUSB_SPEED_FULL;
default:
- panic("Invalid speed\n");
- // return TUSB_SPEED_INVALID;
+ return TUSB_SPEED_INVALID;
}
}
// Close all opened endpoint belong to this device
void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
- pico_trace("hcd_device_close %d\n", dev_addr);
- (void) rhport;
+ (void)rhport;
- // reset epx if it is currently active with unplugged device
- if (epx.configured && epx.active && epx.dev_addr == dev_addr) {
- epx.configured = false;
- *hwep_ctrl_reg_host(&epx) = 0;
- *hwbuf_ctrl_reg_host(&epx) = 0;
- hw_endpoint_reset_transfer(&epx);
+ if (dev_addr == 0) {
+ return; // address 0 is for device enumeration
}
- // dev0 only has ep0
- if (dev_addr != 0) {
- for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) {
- hw_endpoint_t *ep = &ep_pool[i];
- if (ep->dev_addr == dev_addr && ep->configured) {
- // in case it is an interrupt endpoint, disable it
- usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1));
- usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0;
+ rp2usb_critical_enter();
- // unconfigure the endpoint
- ep->configured = false;
- *hwep_ctrl_reg_host(ep) = 0;
- *hwbuf_ctrl_reg_host(ep) = 0;
- hw_endpoint_reset_transfer(ep);
+ for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) {
+ hw_endpoint_t *ep = &ep_pool[i];
+ if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) {
+ ep->state = EPSTATE_IDLE; // clear any pending transfer
+
+ if (ep->interrupt_num > 0) {
+ // disable interrupt endpoint
+ usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0;
+
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ *buf_reg = 0;
+ *ep_reg = 0;
}
+
+ ep->max_packet_size = 0; // mark as unused
}
}
+
+ rp2usb_critical_exit();
}
uint32_t hcd_frame_number(uint8_t rhport) {
@@ -469,122 +531,177 @@ void hcd_int_disable(uint8_t rhport) {
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
(void)rhport;
pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress);
- hw_endpoint_t *ep = hw_endpoint_allocate(ep_desc->bmAttributes.xfer);
+ hw_endpoint_t *ep;
+ if (dev_addr == 0) {
+ ep = &ep_pool[0];
+ } else {
+ ep = edpt_alloc();
+ }
TU_ASSERT(ep);
- hw_endpoint_init(ep, dev_addr, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer,
- ep_desc->bInterval);
+ const uint8_t ep_addr = ep_desc->bEndpointAddress;
+ const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc);
+
+ ep->max_packet_size = max_packet_size;
+ ep->ep_addr = ep_addr;
+ ep->dev_addr = dev_addr;
+ ep->transfer_type = ep_desc->bmAttributes.xfer;
+ ep->need_pre = need_pre(dev_addr);
+ ep->next_pid = 0u;
+
+ if (ep->transfer_type != TUSB_XFER_INTERRUPT) {
+ ep->dpram_buf = usbh_dpram->epx_data;
+ } else {
+ // from 15 interrupt endpoints pool
+ uint8_t int_idx;
+ for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) {
+ if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) {
+ ep->interrupt_num = int_idx + 1;
+ break;
+ }
+ }
+ assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS);
+ assert(ep_desc->bInterval > 0);
+
+ //------------- dpram buf -------------//
+ // 15x64 last bytes of DPRAM for interrupt endpoint buffers
+ ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u);
+ uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
+ (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) |
+ ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB);
+ usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl;
+
+ //------------- address control -------------//
+ const uint8_t epnum = tu_edpt_number(ep_addr);
+ uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB));
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS;
+ }
+ if (ep->need_pre) {
+ addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS;
+ }
+ usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl;
+
+ // Finally, activate interrupt endpoint
+ usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num);
+ }
return true;
}
bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
- (void) rhport; (void) daddr; (void) ep_addr;
+ (void)rhport;
+ (void)daddr;
+ (void)ep_addr;
return false; // TODO not implemented yet
}
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
- (void) rhport;
-
- pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen);
-
- const uint8_t ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr);
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
+ // TODO not implemented yet
+ return false;
+}
- // Get appropriate ep. Either EPX or interrupt endpoint
- struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr);
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
+ (void)rhport;
+ hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr);
TU_ASSERT(ep);
- // EP should be inactive
- assert(!ep->active);
-
- // Control endpoint can change direction 0x00 <-> 0x80
- if (ep_addr != ep->ep_addr) {
- assert(ep_num == 0);
+ if (ep->interrupt_num > 0) {
+ // For interrupt endpoint control and buffer is already configured
+ // Note: Interrupt is single buffered only
+ io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num);
+ io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen);
+ } else {
+ // Control transfer data and status stages always start with DATA1, regardless of
+ // whether the direction changed since the previous stage. SET_REPORT (and any other
+ // host-to-device class request with an OUT data stage) keeps the same direction
+ // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset
+ // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage
+ // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(),
+ // which strict devices treat as a protocol violation and disconnect.
+ if (tu_edpt_number(ep_addr) == 0) {
+ ep->ep_addr = ep_addr;
+ ep->next_pid = 1;
+ }
- // Direction has flipped on endpoint control so re init it but with same properties
- hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, TUSB_XFER_CONTROL, 0);
- }
+ // If EPX is busy with another transfer, mark as pending
+ rp2usb_critical_enter();
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->user_buf = buffer;
+ ep->remaining_len = buflen;
+ ep->state = EPSTATE_PENDING;
- // If a normal transfer (non-interrupt) then initiate using
- // sie ctrl registers. Otherwise, interrupt ep registers should
- // already be configured
- if (ep == &epx) {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ // Only enable SOF round-robin for non-control endpoints
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl;
+ io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
- // That has set up buffer control, endpoint control etc
- // for host we have to initiate the transfer
- usb_hw->dev_addr_ctrl = (uint32_t) (dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB));
+ epx = ep;
- uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE |
- (ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS) |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
- } else {
- hw_endpoint_xfer_start(ep, buffer, NULL, buflen);
+ epx_ctrl_prepare(ep->transfer_type);
+ rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl
+ usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB));
+ sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
}
return true;
}
-bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- // TODO not implemented yet
- return false;
-}
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
+ (void)rhport;
+
+ hw_endpoint_t *ep = edpt_find(dev_addr, 0x00);
+ TU_ASSERT(ep);
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
+ rp2usb_critical_enter();
- // Copy data into setup packet buffer
+ // Copy data into setup packet buffer (usbh only schedules one setup at a time)
for (uint8_t i = 0; i < 8; i++) {
usbh_dpram->setup_packet[i] = setup_packet[i];
}
- // Configure EP0 struct with setup info for the trans complete
- hw_endpoint_t *ep = hw_endpoint_allocate((uint8_t)TUSB_XFER_CONTROL);
- TU_ASSERT(ep);
-
- // EPX should be inactive
- assert(!ep->active);
-
- // EP0 out
- hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0);
- assert(ep->configured);
-
+ ep->ep_addr = 0; // setup is OUT
ep->remaining_len = 8;
- ep->active = true;
-
- // Set device address
- usb_hw->dev_addr_ctrl = dev_addr;
+ ep->xferred_len = 0;
- // Set pre if we are a low speed device on full speed hub
- uint32_t const flags = SIE_CTRL_BASE | USB_SIE_CTRL_SEND_SETUP_BITS | USB_SIE_CTRL_START_TRANS_BITS |
- (need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0);
+ // If EPX is busy, mark as pending setup (DPRAM already has the packet)
+ if (epx->state == EPSTATE_ACTIVE) {
+ ep->state = EPSTATE_PENDING_SETUP;
+ #ifdef HAS_STOP_EPX_ON_NAK
+ usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
+ #else
+ usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB);
+ usb_hw_set->inte = USB_INTE_HOST_SOF_BITS;
+ #endif
+ } else {
+ epx = ep;
+ ep->state = EPSTATE_ACTIVE;
- // START_TRANS bit on SIE_CTRL seems to exhibit the same behavior as the AVAILABLE bit
- // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".
- // We write everything except the START_TRANS bit first, then wait some cycles.
- usb_hw->sie_ctrl = flags & ~USB_SIE_CTRL_START_TRANS_BITS;
- busy_wait_at_least_cycles(12);
- usb_hw->sie_ctrl = flags;
+ usb_hw->dev_addr_ctrl = ep->dev_addr;
+ sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre);
+ }
+ rp2usb_critical_exit();
return true;
}
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
+ (void)rhport;
+ (void)dev_addr;
+ (void)ep_addr;
panic("hcd_clear_stall");
// return true;
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index 3b65f57a4..83ac48ec2 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.c
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c
@@ -27,32 +27,42 @@
#include "tusb_option.h"
-#if CFG_TUSB_MCU == OPT_MCU_RP2040
+#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED)
-#include <stdlib.h>
-#include "rp2040_usb.h"
+ #include <stdlib.h>
+ #include "rp2040_usb.h"
+
+ #include "device/dcd.h"
+ #include "host/hcd.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTOTYPE
//--------------------------------------------------------------------+
-static void sync_xfer(hw_endpoint_t *ep);
+ #if CFG_TUSB_RP2_ERRATA_E15
+static bool e15_is_critical_frame_period(void);
+ #endif
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
-static bool e15_is_critical_frame_period(struct hw_endpoint *ep);
- #else
- #define e15_is_critical_frame_period(x) (false)
+ #if CFG_TUSB_RP2_ERRATA_E2
+static uint8_t rp2040_chipversion = 2;
#endif
+critical_section_t rp2usb_lock;
+
//--------------------------------------------------------------------+
// Implementation
//--------------------------------------------------------------------+
-// Provide own byte by byte memcpy as not all copies are aligned
+// Provide own byte by byte memcpy as not all copies are aligned.
+// Use volatile to prevent compiler from widening to 16/32-bit accesses
+// which cause hard fault on RP2350 when dst/src point to USB DPRAM.
static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) {
+ volatile uint8_t *vdst = dst;
+ const volatile uint8_t *vsrc = src;
while (n--) {
- *dst++ = *src++;
+ *vdst++ = *vsrc++;
}
}
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) {
(void)access_mode;
unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len);
@@ -62,216 +72,222 @@ void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, co
(void)access_mode;
unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len);
}
+ #endif
void rp2usb_init(void) {
// Reset usb controller
reset_block(RESETS_RESET_USBCTRL_BITS);
unreset_block_wait(RESETS_RESET_USBCTRL_BITS);
-#ifdef __GNUC__
- // Clear any previous state just in case
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Warray-bounds"
-#if __GNUC__ > 6
-#pragma GCC diagnostic ignored "-Wstringop-overflow"
-#endif
-#endif
+ #ifdef __GNUC__
+ // Clear any previous state just in case
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Warray-bounds"
+ #if __GNUC__ > 6
+ #pragma GCC diagnostic ignored "-Wstringop-overflow"
+ #endif
+ #endif
memset(usb_dpram, 0, sizeof(*usb_dpram));
-#ifdef __GNUC__
-#pragma GCC diagnostic pop
-#endif
+ #ifdef __GNUC__
+ #pragma GCC diagnostic pop
+ #endif
// Mux the controller to the onboard usb phy
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
+ #if CFG_TUSB_RP2_ERRATA_E2
+ rp2040_chipversion = rp2040_chip_version();
+ #endif
+
TU_LOG2_INT(sizeof(hw_endpoint_t));
+
+ critical_section_init(&rp2usb_lock);
}
-void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) {
- ep->active = false;
+void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) {
+ ep->state = EPSTATE_IDLE;
ep->remaining_len = 0;
- ep->xferred_len = 0;
- ep->user_buf = 0;
+ ep->xferred_len = 0;
+ ep->user_buf = 0;
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ ep->is_xfer_fifo = false;
+#endif
}
-void __tusb_irq_path_func(hwbuf_ctrl_update)(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask) {
- const bool is_host = rp2usb_is_host_mode();
- uint32_t value = 0;
- uint32_t buf_ctrl = *buf_ctrl_reg;
+void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) {
+ const uint32_t current = *buf_reg;
+ const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16);
+ if (current & value & avail_mask) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg);
+ }
+ *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first
- if (and_mask) {
- value = buf_ctrl & and_mask;
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control,
+ // wait for USB controller to see the update before setting AVAILABLE.
+ // Don't need delay in host mode as host is in charge of when to start the transaction.
+ if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
+ }
+ *buf_reg = value; // then set AVAILABLE bit last
}
+}
- if (or_mask) {
- value |= or_mask;
- if (or_mask & USB_BUF_CTRL_AVAIL) {
- if (buf_ctrl & USB_BUF_CTRL_AVAIL) {
- panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_ctrl_reg);
- }
- *buf_ctrl_reg = value & ~USB_BUF_CTRL_AVAIL;
+void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) {
+ const uint16_t current = *buf_reg16;
+ if (current & value & USB_BUF_CTRL_AVAIL) {
+ panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16);
+ }
+ *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first
- // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, we need to
- // wait at least 1/48 mhz (usb clock), 12 cycles should be good for 48*12Mhz = 576Mhz.
- // Don't need delay in host mode as host is in charge
- if (!is_host) {
- busy_wait_at_least_cycles(12);
- }
+ // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access
+ if (value & USB_BUF_CTRL_AVAIL) {
+ if (!rp2usb_is_host_mode()) {
+ busy_wait_at_least_cycles(12);
}
+ *buf_reg16 = value; // then set AVAILABLE bit last
}
-
- *buf_ctrl_reg = value;
}
// prepare buffer, move data if tx, return buffer control
-static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id, bool is_rx) {
- const uint16_t buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize);
- ep->remaining_len = (uint16_t) (ep->remaining_len - buflen);
-
- uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) {
+ const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size);
+ ep->remaining_len -= buflen;
- // PID
- buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
+ uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL;
+ if (ep->next_pid) {
+ buf_ctrl |= USB_BUF_CTRL_DATA1_PID;
+ }
ep->next_pid ^= 1u;
if (!is_rx) {
if (buflen) {
- // Copy data from user buffer/fifo to hw buffer
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
+ // Copy data from user buffer/fifo to hw buffer
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_write_from_fifo(hw_buf, ep->user_fifo, buflen, NULL);
- } else {
- unaligned_memcpy(hw_buf, ep->user_buf, buflen);
+ tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL);
+ } else
+ #endif
+ {
+ unaligned_memcpy(dpram_buf, ep->user_buf, buflen);
ep->user_buf += buflen;
}
}
- // Mark as full
buf_ctrl |= USB_BUF_CTRL_FULL;
}
- // Is this the last buffer? Only really matters for host mode. Will trigger
- // the trans complete irq but also stop it polling. We only really care about
- // trans complete for setup packets being sent
+ // Is this the last buffer? Will trigger the trans complete irq but also stop it polling.
+ // This is used to detect setup packets being sent in host mode
if (ep->remaining_len == 0) {
buf_ctrl |= USB_BUF_CTRL_LAST;
}
- if (buf_id) {
- buf_ctrl = buf_ctrl << 16;
- }
-
return buf_ctrl;
}
-// Prepare buffer control register value
-void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) {
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
- bool is_rx;
- bool is_host = false;
- io_rw_32 *ep_ctrl_reg;
- io_rw_32 *buf_ctrl_reg;
+// Start transaction on hw buffer
+void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) {
+ // always compute and start with buffer 0
+ uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL;
+ // Note: device EP0 does not have an endpoint control register
+ if (ep_reg != NULL) {
+ uint32_t ep_ctrl = *ep_reg;
#if CFG_TUH_ENABLED
- is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
+ const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0);
+ #else
+ const bool force_single = false;
#endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
- }
-
- // always compute and start with buffer 0
- uint32_t buf_ctrl = prepare_ep_buffer(ep, 0, is_rx) | USB_BUF_CTRL_SEL;
-
- // EP0 has no endpoint control register, also usbd only schedule 1 packet at a time (single buffer)
- if (ep_ctrl_reg != NULL) {
- uint32_t ep_ctrl = *ep_ctrl_reg;
-
- // For now: skip double buffered for RX e.g OUT endpoint in Device mode, since host could send < 64 bytes and cause
- // short packet on buffer0
- // NOTE: this could happen to Host mode IN endpoint Also, Host mode "interrupt" endpoint hardware is only single
- // buffered,
- // NOTE2: Currently Host bulk is implemented using "interrupt" endpoint
- const bool force_single = (!is_host && is_rx) || (is_host && tu_edpt_number(ep->ep_addr) != 0);
if (ep->remaining_len && !force_single) {
// Use buffer 1 (double buffered) if there is still data
- // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt)
-
- buf_ctrl |= prepare_ep_buffer(ep, 1, is_rx);
-
- // Set endpoint control double buffered bit if needed
- ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER;
- ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER;
+ buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16;
+ ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS;
} else {
- // Single buffered since 1 is enough
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector
+ ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS;
}
-
- *ep_ctrl_reg = ep_ctrl;
+ *ep_reg = ep_ctrl;
}
- TU_LOG(3, " Prepare BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-
- // Finally, write to buffer_control which will trigger the transfer
- // the next time the controller polls this dpram address
- hwbuf_ctrl_set(buf_ctrl_reg, buf_ctrl);
+ // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint
+ bufctrl_write32(buf_reg, buf_ctrl);
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len) {
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len) {
+ (void)ff;
hw_endpoint_lock_update(ep, 1);
- if (ep->active) {
- // TODO: Is this acceptable for interrupt packets?
+ if (ep->state == EPSTATE_ACTIVE) {
TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr);
- hw_endpoint_reset_transfer(ep);
+ rp2usb_reset_transfer(ep);
}
// Fill in info now that we're kicking off the hw
ep->remaining_len = total_len;
- ep->xferred_len = 0;
- ep->active = true;
+ ep->xferred_len = 0;
+ ep->state = EPSTATE_ACTIVE;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ff != NULL) {
ep->user_fifo = ff;
ep->is_xfer_fifo = true;
- } else {
- ep->user_buf = buffer;
+ } else
+ #endif
+ {
+ ep->user_buf = buffer;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
ep->is_xfer_fifo = false;
+ #endif
}
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN));
+
+ #if CFG_TUD_ENABLED
+ if (!is_host && ep->future_len > 0) {
+ // Device only: previous short-packet abort saved data from the other buffer
+ const uint8_t future_len = ep->future_len;
+ memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len);
+ ep->xferred_len += future_len;
+ ep->remaining_len -= future_len;
+ ep->user_buf += future_len;
+ ep->future_len = 0;
+ ep->future_bufid = 0;
+
+ if (ep->remaining_len == 0) {
+ const uint16_t xferred_len = ep->xferred_len;
+ rp2usb_reset_transfer(ep);
+ dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false);
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
+ }
+
+ #if CFG_TUSB_RP2_ERRATA_E15
if (ep->e15_bulk_in) {
usb_hw_set->inte = USB_INTS_DEV_SOF_BITS;
- }
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1; // skip transfer if we are in critical frame period
- } else
- #endif
- {
- hw_endpoint_start_next_buffer(ep);
+ // skip transfer if we are in critical frame period
+ if (e15_is_critical_frame_period()) {
+ ep->state = EPSTATE_PENDING;
+ hw_endpoint_lock_update(ep, -1);
+ return;
+ }
}
+ #endif // CFG_TUSB_RP2_ERRATA_E15
+ #endif // CFG_TUD_ENABLED
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx);
hw_endpoint_lock_update(ep, -1);
}
// sync endpoint buffer and return transferred bytes
-static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32 *buf_ctrl_reg, uint8_t buf_id,
- bool is_rx) {
- uint32_t buf_ctrl = *buf_ctrl_reg;
- if (buf_id) {
- buf_ctrl = buf_ctrl >> 16;
- }
-
+static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl,
+ uint8_t *dpram_buf) {
const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK;
if (!is_rx) {
@@ -282,20 +298,21 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
// If we have received some data, so can increase the length
// we have received AFTER we have copied it to the user buffer at the appropriate offset
assert(buf_ctrl & USB_BUF_CTRL_FULL);
-
- uint8_t *hw_buf = ep->hw_data_buf + buf_id * 64;
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
if (ep->is_xfer_fifo) {
// not in sram, may mess up timing with E15 workaround
- tu_hwfifo_read_to_fifo(hw_buf, ep->user_fifo, xferred_bytes, NULL);
- } else {
- unaligned_memcpy(ep->user_buf, hw_buf, xferred_bytes);
+ tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL);
+ } else
+ #endif
+ {
+ unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes);
ep->user_buf += xferred_bytes;
}
}
ep->xferred_len += xferred_bytes;
// Short packet
- if (xferred_bytes < ep->wMaxPacketSize) {
+ if (xferred_bytes < ep->max_packet_size) {
// Reduce total length as this is last packet
ep->remaining_len = 0;
}
@@ -303,109 +320,126 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(hw_endpoint_t *ep, io_rw_32
return xferred_bytes;
}
-// Update hw endpoint struct with info from hardware after a buff status interrupt
-static void __tusb_irq_path_func(sync_xfer)(hw_endpoint_t *ep) {
- // const uint8_t ep_num = tu_edpt_number(ep->ep_addr);
- const tusb_dir_t dir = tu_edpt_dir(ep->ep_addr);
-
- io_rw_32 *buf_ctrl_reg;
- io_rw_32 *ep_ctrl_reg;
- bool is_rx;
+// Returns true if transfer is complete.
+// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered).
+bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id,
+ bool is_rx) {
+ hw_endpoint_lock_update(ep, 1);
- #if CFG_TUH_ENABLED
- const bool is_host = rp2usb_is_host_mode();
- if (is_host) {
- buf_ctrl_reg = hwbuf_ctrl_reg_host(ep);
- ep_ctrl_reg = hwep_ctrl_reg_host(ep);
- is_rx = (dir == TUSB_DIR_IN);
- } else
- #endif
- {
- buf_ctrl_reg = hwbuf_ctrl_reg_device(ep);
- ep_ctrl_reg = hwep_ctrl_reg_device(ep);
- is_rx = (dir == TUSB_DIR_OUT);
+ if (ep->state == EPSTATE_IDLE) {
+ // probably land here due to short packet on rx with double buffered
+ hw_endpoint_lock_update(ep, -1);
+ return false;
}
- TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(*buf_ctrl_reg),
- tu_u32_high16(*buf_ctrl_reg));
- uint16_t buf0_bytes = sync_ep_buffer(ep, buf_ctrl_reg, 0, is_rx); // always sync buffer 0
+ const bool is_host = rp2usb_is_host_mode();
+ const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
- // sync buffer 1 if double buffered
- if (ep_ctrl_reg != NULL && (*ep_ctrl_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS) {
- if (buf0_bytes == ep->wMaxPacketSize) {
- // sync buffer 1 if not short packet
- sync_ep_buffer(ep, buf_ctrl_reg, 1, is_rx);
- } else {
- // short packet on buffer 0
- // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example
- // At this time (currently trigger per 2 buffer), the buffer1 is probably filled with data from
- // the next transfer (not current one). For now we disable double buffered for device OUT
- // NOTE this could happen to Host IN
-#if 0
- uint8_t const ep_num = tu_edpt_number(ep->ep_addr);
- uint8_t const dir = (uint8_t) tu_edpt_dir(ep->ep_addr);
- uint8_t const ep_id = 2*ep_num + (dir ? 0 : 1);
-
- // abort queued transfer on buffer 1
- usb_hw->abort |= TU_BIT(ep_id);
-
- while ( !(usb_hw->abort_done & TU_BIT(ep_id)) ) {}
+ // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed.
+ // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to
+ // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1
+ // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered.
+ uint8_t *dpram_buf = ep->dpram_buf;
+ if (buf_id) {
+ #if CFG_TUSB_RP2_ERRATA_E4
+ if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0
+ #endif
+ {
+ dpram_buf += 64; // buf1 offset
+ }
+ }
- uint32_t ep_ctrl = *ep->endpoint_control;
- ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER);
- ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER;
+ io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg;
+ uint16_t buf_ctrl16 = *(buf_reg16 + buf_id);
- io_rw_32 *buf_ctrl_reg = is_host ? hwbuf_ctrl_reg_host(ep) : hwbuf_ctrl_reg_device(ep);
- hwbuf_ctrl_set(buf_ctrl_reg, 0);
+ const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf);
+ const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST;
+ const bool is_short = xact_bytes < ep->max_packet_size;
+ const bool is_done = is_short || is_last;
- usb_hw->abort &= ~TU_BIT(ep_id);
+ // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control.
+ // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed.
+ // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer.
+ // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4
+ // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale)
+ if (is_short && is_double && is_rx && !is_last) {
+ const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint
- TU_LOG(3, "----SHORT PACKET buffer0 on EP %02X:\r\n", ep->ep_addr);
- TU_LOG(3, " BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl));
-#endif
+ if (is_host) {
+ // host stop current transfer, not safe, can be racing
+ const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
+ } else {
+ // device abort current transfer
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_set->abort = abort_bit;
+ while ((usb_hw->abort_done & abort_bit) != abort_bit) {}
+ }
}
- }
-}
-// Returns true if transfer is complete
-bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
- hw_endpoint_lock_update(ep, 1);
+ // After abort, check if the other buffer received valid data
+ io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1);
+ const uint16_t buf_ctrl16_other = *buf_reg16_other;
+ if (buf_ctrl16_other & USB_BUF_CTRL_FULL) {
+ // Data already sent into this buffer. Save it for the next transfer.
+ // buff_status will be clear by the next run
+ #if CFG_TUD_ENABLED
+ if (!is_host) {
+ ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK);
+ ep->future_bufid = buf_id ^ 1;
+ }
+ #endif
+ } else {
+ ep->next_pid ^= 1u; // roll back pid if aborted
+ }
- // Part way through a transfer
- if (!ep->active) {
- panic("Can't continue xfer on inactive ep %02X", ep->ep_addr);
- }
+ *buf_reg = 0; // reset buffer control
- sync_xfer(ep); // Update EP struct from hardware state
+ if (!is_host) {
+ #if CFG_TUSB_RP2_ERRATA_E2
+ if (rp2040_chipversion >= 2)
+ #endif
+ {
+ usb_hw_clear->abort_done = abort_bit;
+ usb_hw_clear->abort = abort_bit;
+ }
+ }
- // Now we have synced our state with the hardware. Is there more data to transfer?
- // If we are done then notify tinyusb
- if (ep->remaining_len == 0) {
- pico_trace("Completed transfer of %d bytes on ep %02X\r\n", ep->xferred_len, ep->ep_addr);
- // Notify caller we are done so it can notify the tinyusb stack
hw_endpoint_lock_update(ep, -1);
return true;
- } else {
- #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- if (e15_is_critical_frame_period(ep)) {
- ep->pending = 1;
+ }
+
+ if (!is_done && ep->remaining_len > 0) {
+ #if CFG_TUSB_RP2_ERRATA_E15
+ const bool need_e15 = ep->e15_bulk_in;
+ if (need_e15 && e15_is_critical_frame_period()) {
+ // mark as pending if matches E15 condition
+ ep->state = EPSTATE_PENDING;
+ } else if (need_e15 && ep->state == EPSTATE_PENDING) {
+ // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it
+ // do nothing
} else
#endif
{
- hw_endpoint_start_next_buffer(ep);
+ // ping-pong: arm the completed buffer with new data
+ const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx);
+ bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new);
}
}
hw_endpoint_lock_update(ep, -1);
- // More work to do
- return false;
+ return is_done;
}
//--------------------------------------------------------------------+
// Errata 15
//--------------------------------------------------------------------+
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #if CFG_TUSB_RP2_ERRATA_E15
// E15 is fixed with RP2350
/* Don't mark IN buffers as available during the last 200us of a full-speed
@@ -426,25 +460,19 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) {
volatile uint32_t e15_last_sof = 0;
-// check if we need to apply Errata 15 workaround : i.e
-// Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame
-static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) {
- if (!ep->e15_bulk_in) {
- return false;
- }
-
+// check if it is currently in critical frame period i.e 20% of last usb frame
+static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) {
/* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point.
* The device state machine cannot recover from receiving an incorrect PID
- * when it is expecting an ACK.
- */
+ * when it is expecting an ACK. */
uint32_t delta = time_us_32() - e15_last_sof;
if (delta < 800 || delta > 998) {
return false;
}
- TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
- e15_last_sof);
+ // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(),
+ // e15_last_sof);
return true;
}
-#endif // TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+ #endif
#endif
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h
index c03dc34b2..8ebc3e3fc 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.h
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h
@@ -7,6 +7,8 @@
#include "hardware/resets.h"
#include "hardware/timer.h"
+#include "pico/critical_section.h"
+
#include "common/tusb_common.h"
#include "osal/osal.h"
#include "common/tusb_fifo.h"
@@ -15,42 +17,66 @@
#error TinyUSB device and host mode not supported at the same time
#endif
-// E5 and E15 only apply to RP2040
#if defined(PICO_RP2040) && PICO_RP2040 == 1
- // RP2040 E5: USB device fails to exit RESET state on busy USB bus.
+ // RP2040-E2 USB device endpoint abort is not cleared.
+ #define CFG_TUSB_RP2_ERRATA_E2 1
+
+ // RP2040-E4: USB host writes to upper half of buffer status in single buffered mode.
+ #define CFG_TUSB_RP2_ERRATA_E4 1
+
+ // RP2040-E5: USB device fails to exit RESET state on busy USB bus.
#if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX)
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX
#endif
- // RP2040 E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
- #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX PICO_RP2040_USB_DEVICE_UFRAME_FIX
+ // RP2040-E15: USB Device controller will hang if certain bus errors occur during an IN transfer.
+ #ifndef CFG_TUSB_RP2_ERRATA_E15
+ #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && PICO_RP2040_USB_DEVICE_UFRAME_FIX)
+ #elif defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX)
+ #endif
#endif
#endif
+#ifndef CFG_TUSB_RP2_ERRATA_E2
+ #define CFG_TUSB_RP2_ERRATA_E2 0
+#endif
+
+#ifndef CFG_TUSB_RP2_ERRATA_E4
+ #define CFG_TUSB_RP2_ERRATA_E4 0
+#endif
+
#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX
#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0
#endif
-#ifndef TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- #define TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX 0
+#ifndef CFG_TUSB_RP2_ERRATA_E15
+ #define CFG_TUSB_RP2_ERRATA_E15 0
#endif
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
#undef PICO_RP2040_USB_FAST_IRQ
#define PICO_RP2040_USB_FAST_IRQ 1
#endif
#ifndef PICO_RP2040_USB_FAST_IRQ
-#define PICO_RP2040_USB_FAST_IRQ 0
+ #define PICO_RP2040_USB_FAST_IRQ 0
#endif
#if PICO_RP2040_USB_FAST_IRQ
-#define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
+ #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x)
#else
-#define __tusb_irq_path_func(x) x
+ #define __tusb_irq_path_func(x) x
#endif
+// Flags we set by default in sie_ctrl (we add other bits on top)
+enum {
+ SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS,
+ SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS |
+ USB_SIE_CTRL_KEEP_ALIVE_EN_BITS
+};
+
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
@@ -60,26 +86,43 @@
#define pico_info(...) TU_LOG(2, __VA_ARGS__)
#define pico_trace(...) TU_LOG(3, __VA_ARGS__)
+enum {
+ EPSTATE_IDLE = 0,
+ EPSTATE_ACTIVE,
+ EPSTATE_PENDING,
+ EPSTATE_PENDING_SETUP
+};
+
// Hardware information per endpoint
typedef struct hw_endpoint {
uint8_t ep_addr;
uint8_t next_pid;
- bool active; // transferring data
- bool is_xfer_fifo; // transfer using fifo
+ uint8_t state;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
- bool e15_bulk_in; // Errata15 device bulk in
- uint8_t pending; // Transfer scheduled but not active
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ bool is_xfer_fifo; // transfer using fifo
+#endif
+
+#if CFG_TUD_ENABLED
+ uint8_t future_bufid; // which buffer holds next data
+ uint8_t future_len; // next data len
+#endif
+
+#if CFG_TUSB_RP2_ERRATA_E15
+ bool e15_bulk_in; // Errata15 device bulk in
#endif
#if CFG_TUH_ENABLED
- bool configured; // Is this a valid struct
uint8_t dev_addr;
- uint8_t interrupt_num; // for host interrupt endpoints
+ uint8_t interrupt_num; // 1-15 for interrupt endpoints
+ struct TU_ATTR_PACKED {
+ uint8_t transfer_type : 2;
+ uint8_t need_pre : 1; // preamble for low-speed device behind full speed hub
+ };
#endif
- uint16_t wMaxPacketSize;
- uint8_t *hw_data_buf; // Buffer pointer in usb dpram
+ uint16_t max_packet_size; // max packet size also indicates configured
+ uint8_t *dpram_buf; // Buffer pointer in usb dpram
// transfer info
union {
@@ -91,7 +134,7 @@ typedef struct hw_endpoint {
} hw_endpoint_t;
-#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX
+#if CFG_TUSB_RP2_ERRATA_E15
extern volatile uint32_t e15_last_sof;
#endif
@@ -102,69 +145,39 @@ TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) {
return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false;
}
-void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, tu_fifo_t *ff, uint16_t total_len);
-bool hw_endpoint_xfer_continue(struct hw_endpoint *ep);
-void hw_endpoint_reset_transfer(struct hw_endpoint *ep);
-void hw_endpoint_start_next_buffer(struct hw_endpoint *ep);
-
-TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint * ep, __unused int delta) {
- // todo add critsec as necessary to prevent issues between worker and IRQ...
- // note that this is perhaps as simple as disabling IRQs because it would make
- // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
-}
-
-// #if CFG_TUD_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_device(struct hw_endpoint *ep) {
- uint8_t const epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- if (epnum == 0) {
- // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled
- return NULL;
- }
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_ctrl[epnum - 1].in : &usb_dpram->ep_ctrl[epnum - 1].out;
-}
+extern critical_section_t rp2usb_lock;
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_device(struct hw_endpoint *ep) {
- const uint8_t epnum = tu_edpt_number(ep->ep_addr);
- const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr);
- return (dir == TUSB_DIR_IN) ? &usb_dpram->ep_buf_ctrl[epnum].in : &usb_dpram->ep_buf_ctrl[epnum].out;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) {
+ critical_section_enter_blocking(&rp2usb_lock);
}
-// #endif
-
-#if CFG_TUH_ENABLED
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwep_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_ctrl;
- }
- return &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl;
-}
-
-TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *hwbuf_ctrl_reg_host(struct hw_endpoint *ep) {
- if (tu_edpt_number(ep->ep_addr) == 0) {
- return &usbh_dpram->epx_buf_ctrl;
- }
- return &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl;
+TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) {
+ critical_section_exit(&rp2usb_lock);
}
-#endif
//--------------------------------------------------------------------+
-//
+// Hardware Endpoint
//--------------------------------------------------------------------+
-void hwbuf_ctrl_update(io_rw_32 *buf_ctrl_reg, uint32_t and_mask, uint32_t or_mask);
+void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff,
+ uint16_t total_len);
+bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx);
+void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx);
+void rp2usb_reset_transfer(hw_endpoint_t *ep);
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, 0, value);
-}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_set_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, value);
+TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint *ep, __unused int delta) {
+ // todo add critsec as necessary to prevent issues between worker and IRQ...
+ // note that this is perhaps as simple as disabling IRQs because it would make
+ // sense to have worker and IRQ on same core, however I think using critsec is about equivalent.
}
-TU_ATTR_ALWAYS_INLINE static inline void hwbuf_ctrl_clear_mask(io_rw_32 *buf_ctrl_reg, uint32_t value) {
- hwbuf_ctrl_update(buf_ctrl_reg, ~value, 0);
-}
+//--------------------------------------------------------------------+
+// Hardware Buffer
+//--------------------------------------------------------------------+
+void bufctrl_write32(io_rw_32 *buf_reg, uint32_t value);
+void bufctrl_write16(io_rw_16 *buf_reg16, uint16_t value);
+uint16_t bufctrl_prepare16(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx);
-static inline uintptr_t hw_data_offset(uint8_t *buf) {
+TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) {
// Remove usb base from buffer pointer
return (uintptr_t)buf ^ (uintptr_t)usb_dpram;
}
diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c
index e2a51a5ca..adbb53787 100644
--- a/src/portable/renesas/rusb2/dcd_rusb2.c
+++ b/src/portable/renesas/rusb2/dcd_rusb2.c
@@ -93,7 +93,9 @@ static unsigned find_pipe(unsigned xfer_type) {
const uint8_t idx_last = pipe_idx_arr[xfer_type][1];
for (int i = idx_last; i >= idx_first; i--) {
- if (0 == _dcd.pipe[i].ep) return i;
+ if (0 == _dcd.pipe[i].ep) {
+ return (unsigned)i;
+ }
}
return 0;
@@ -117,10 +119,10 @@ static volatile reg_pipetre_t* get_pipetre(rusb2_reg_t *rusb, unsigned num) {
static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) {
rusb2_reg_t *rusb = RUSB2_REG(rhport);
- const unsigned epn = tu_edpt_number(ep_addr);
+ const unsigned epn = tu_edpt_number((uint8_t)ep_addr);
if (epn) {
- const unsigned dir = tu_edpt_dir(ep_addr);
+ const unsigned dir = tu_edpt_dir((uint8_t)ep_addr);
const unsigned num = _dcd.ep[dir][epn];
return get_pipectr(rusb, num);
} else {
@@ -129,11 +131,11 @@ static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) {
}
static uint16_t edpt0_max_packet_size(rusb2_reg_t* rusb) {
- return rusb->DCPMAXP_b.MXPS;
+ return (uint16_t)rusb->DCPMAXP_b.MXPS;
}
static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) {
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
return rusb->PIPEMAXP;
}
@@ -285,7 +287,7 @@ static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num)
const uint16_t mps = edpt_max_packet_size(rusb, num);
pipe_wait_for_ready(rusb, num);
- const uint16_t vld = rusb->D0FIFOCTR_b.DTLN;
+ const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN;
const uint16_t len = tu_min16(tu_min16(rem, mps), vld);
void *buf = pipe->buf;
@@ -498,7 +500,7 @@ static void process_bus_reset(uint8_t rhport)
volatile uint16_t *ctr = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_CTR[0]));
volatile uint16_t *tre = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_TR[0].E));
- for (int i = 1; i <= 5; ++i) {
+ for (uint16_t i = 1; i <= 5; ++i) {
rusb->PIPESEL = i;
rusb->PIPECFG = 0;
*ctr = RUSB2_PIPE_CTR_ACLRM_Msk;
@@ -508,7 +510,7 @@ static void process_bus_reset(uint8_t rhport)
tre += 2;
}
- for (int i = 6; i <= 9; ++i) {
+ for (uint16_t i = 6; i <= 9; ++i) {
rusb->PIPESEL = i;
rusb->PIPECFG = 0;
*ctr = RUSB2_PIPE_CTR_ACLRM_Msk;
@@ -542,7 +544,7 @@ static void process_bus_reset(uint8_t rhport)
static void process_set_address(uint8_t rhport)
{
rusb2_reg_t* rusb = RUSB2_REG(rhport);
- const uint16_t addr = rusb->USBADDR_b.USBADDR;
+ const uint16_t addr = (uint16_t)rusb->USBADDR_b.USBADDR;
if (!addr) {
return;
}
@@ -706,7 +708,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc)
(void)rhport;
rusb2_reg_t * rusb = RUSB2_REG(rhport);
- const unsigned ep_addr = ep_desc->bEndpointAddress;
+ const uint8_t ep_addr = ep_desc->bEndpointAddress;
const unsigned epn = tu_edpt_number(ep_addr);
const unsigned dir = tu_edpt_dir(ep_addr);
const unsigned xfer = ep_desc->bmAttributes.xfer;
@@ -770,8 +772,10 @@ void dcd_edpt_close_all(uint8_t rhport)
dcd_int_disable(rhport);
while (--i) { /* Close all pipes except 0 */
const unsigned ep_addr = _dcd.pipe[i].ep;
- if (!ep_addr) continue;
- dcd_edpt_close(rhport, ep_addr);
+ if (!ep_addr) {
+ continue;
+ }
+ dcd_edpt_close(rhport, (uint8_t)ep_addr);
}
dcd_int_enable(rhport);
}
@@ -783,10 +787,10 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
const unsigned dir = tu_edpt_dir(ep_addr);
const unsigned num = _dcd.ep[dir][epn];
- rusb->BRDYENB &= ~TU_BIT(num);
+ rusb->BRDYENB &= (uint16_t)~TU_BIT(num);
volatile uint16_t *ctr = get_pipectr(rusb, num);
*ctr = 0;
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
rusb->PIPECFG = 0;
_dcd.pipe[num].ep = 0;
_dcd.ep[dir][epn] = 0;
@@ -860,7 +864,7 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
*ctr = RUSB2_PIPE_CTR_PID_BUF;
} else {
const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)];
- rusb->PIPESEL = num;
+ rusb->PIPESEL = (uint16_t)num;
if (rusb->PIPECFG_b.TYPE != 1) {
*ctr = RUSB2_PIPE_CTR_PID_BUF;
}
diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index 13d1a6cb4..6f7f490a8 100644
--- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
@@ -41,6 +41,7 @@
* F302xB/C, F303xB/C, F373 512 byte buffer; no internal D+ pull-up
* F302x6/8, F302xD/E2, F303xD/E 1024 byte buffer; no internal D+ pull-up
* C0 2048 byte buffer; 32-bit bus; host mode
+ * C5 2048 byte buffer; 32-bit bus; host mode
* G0 2048 byte buffer; 32-bit bus; host mode
* G4 1024 byte buffer
* H5 2048 byte buffer; 32-bit bus; host mode
@@ -259,7 +260,7 @@ static void handle_ctr_tx(uint32_t ep_id) {
}
if (xfer->total_len != xfer->queued_len) {
- dcd_transmit_packet(xfer, ep_id);
+ dcd_transmit_packet(xfer, (uint16_t)ep_id);
} else {
dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
}
@@ -267,7 +268,7 @@ static void handle_ctr_tx(uint32_t ep_id) {
static void handle_ctr_setup(uint32_t ep_id) {
uint16_t rx_count = btable_get_count(ep_id, BTABLE_BUF_RX);
- uint16_t rx_addr = btable_get_addr(ep_id, BTABLE_BUF_RX);
+ uint16_t rx_addr = (uint16_t)btable_get_addr(ep_id, BTABLE_BUF_RX);
uint8_t setup_packet[8] TU_ATTR_ALIGNED(4);
tu_hwfifo_read(PMA_BUF_AT(rx_addr), setup_packet, rx_count, NULL);
@@ -342,7 +343,7 @@ void dcd_int_handler(uint8_t rhport) {
uint32_t int_status = FSDEV_REG->ISTR;
/* Put SOF flag at the beginning of ISR in case to get least amount of jitter if it is used for timing purposes */
- if (int_status & U_ISTR_SOF) {
+ if ((int_status & U_ISTR_SOF) && (FSDEV_REG->CNTR & U_CNTR_SOFM)) {
FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF;
dcd_event_sof(0, FSDEV_REG->FNR & U_FNR_FN, true);
}
@@ -393,26 +394,8 @@ void dcd_int_handler(uint8_t rhport) {
const uint32_t ep_reg = ep_read(ep_id);
if (ep_reg & U_EP_CTR_RX) {
- #ifdef CFG_TUSB_FSDEV_32BIT
- /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
- * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
- * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers
- * Description:
- * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM
- * accesses have completed. If the software responds quickly to the interrupt, the full buffer contents may not be
- * correct. Workaround:
- * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay
- * should be 800 ns in Full Speed mode and 6.4 ฮผs in Low Speed mode
- * - Since H5 can run up to 250Mhz -> 1 cycle = 4ns. Per errata, we need to wait 200 cycles. Though executing code
- * also takes time, so we'll wait 60 cycles (count = 20).
- * - Since Low Speed mode is not supported/popular, we will ignore it for now.
- *
- * Note: this errata may also apply to G0, U5, H5 etc.
- */
- volatile uint32_t cycle_count = 20; // defined as PCD_RX_PMA_CNT in stm32 hal_driver
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
+ #if defined(TUP_USBIP_FSDEV_STM32) && defined(CFG_TUSB_FSDEV_32BIT)
+ fsdev_btable_workaround_delay(false);
#endif
if (ep_reg & U_EP_SETUP) {
@@ -531,8 +514,8 @@ void edpt0_open(uint8_t rhport) {
xfer_status[0][1].max_packet_size = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][1].ep_idx = 0;
- uint16_t pma_addr0 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
- uint16_t pma_addr1 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
+ uint16_t pma_addr0 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
+ uint16_t pma_addr1 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false);
btable_set_addr(0, BTABLE_BUF_RX, pma_addr0);
btable_set_addr(0, BTABLE_BUF_TX, pma_addr1);
@@ -574,7 +557,7 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
}
/* Create a packet memory buffer area. */
- uint16_t pma_addr = dcd_pma_alloc(packet_size, false);
+ uint16_t pma_addr = (uint16_t)dcd_pma_alloc(packet_size, false);
btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr);
xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir);
@@ -612,7 +595,7 @@ void dcd_edpt_close_all(uint8_t rhport) {
dcd_int_enable(rhport);
// Reset PMA allocation
- ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * CFG_TUD_ENDPPOINT_MAX + 2 * CFG_TUD_ENDPOINT0_SIZE;
+ ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT + 2 * CFG_TUD_ENDPOINT0_SIZE;
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
@@ -624,17 +607,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
#if CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP != 0
uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, true);
- uint16_t pma_addr2 = pma_addr >> 16;
+ uint16_t pma_addr2 = (uint16_t)(pma_addr >> 16);
#else
uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, false);
- uint16_t pma_addr2 = pma_addr;
+ uint16_t pma_addr2 = (uint16_t)pma_addr;
#endif
#if FSDEV_USE_SBUF_ISO == 0
- btable_set_addr(ep_idx, 0, pma_addr);
+ btable_set_addr(ep_idx, 0, (uint16_t)pma_addr);
btable_set_addr(ep_idx, 1, pma_addr2);
#else
- btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr);
+ btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, (uint16_t)pma_addr);
(void)pma_addr2;
#endif
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c
index 003bcd069..7c4572a1e 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.c
+++ b/src/portable/st/stm32_fsdev/fsdev_common.c
@@ -81,11 +81,11 @@ uint16_t pma_align_buffer_size(uint16_t size, uint8_t* blsize, uint8_t* num_bloc
if (size > 62) {
block_in_bytes = 32;
*blsize = 1;
- *num_block = tu_div_ceil(size, 32);
+ *num_block = (uint8_t)tu_div_ceil(size, 32);
} else {
block_in_bytes = 2;
*blsize = 0;
- *num_block = tu_div_ceil(size, 2);
+ *num_block = (uint8_t)tu_div_ceil(size, 2);
}
return (*num_block) * block_in_bytes;
diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h
index 140ff1d61..af84b8b97 100644
--- a/src/portable/st/stm32_fsdev/fsdev_common.h
+++ b/src/portable/st/stm32_fsdev/fsdev_common.h
@@ -345,7 +345,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ep_write_clear_ctr(uint32_t ep_id, tusb
uint32_t reg = FSDEV_REG->ep[ep_id].reg;
reg |= U_EP_CTR_TX | U_EP_CTR_RX;
reg &= U_EPREG_MASK;
- reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0 : 8)));
+ reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0u : 8u)));
ep_write(ep_id, reg, false);
}
@@ -378,7 +378,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ch_write_clear_ctr(uint32_t ch_id, tusb
uint32_t reg = FSDEV_REG->ep[ch_id].reg;
reg |= U_EP_CTR_TX | U_EP_CTR_RX;
reg &= U_EPREG_MASK;
- reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8 : 0)));
+ reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8u : 0u)));
ep_write(ch_id, reg, false);
}
diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h
index a63592c5d..b15c95302 100644
--- a/src/portable/st/stm32_fsdev/fsdev_stm32.h
+++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h
@@ -36,6 +36,10 @@
#include "stm32c0xx.h"
#define FSDEV_HAS_SBUF_ISO 1
+#elif CFG_TUSB_MCU == OPT_MCU_STM32C5
+ #include "stm32c5xx.h"
+ #define FSDEV_HAS_SBUF_ISO 1
+
#elif CFG_TUSB_MCU == OPT_MCU_STM32F0
#include "stm32f0xx.h"
#define FSDEV_HAS_SBUF_ISO 0
@@ -177,7 +181,7 @@ static const IRQn_Type fsdev_irq[] = {
USB_IRQn,
#elif TU_CHECK_MCU(OPT_MCU_STM32L5, OPT_MCU_STM32U3)
USB_FS_IRQn,
- #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32H5, OPT_MCU_STM32U0)
+ #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32C5, OPT_MCU_STM32H5, OPT_MCU_STM32U0)
USB_DRD_FS_IRQn,
#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
#ifdef STM32G0B0xx
@@ -252,6 +256,64 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) {
}
//--------------------------------------------------------------------+
+// STM32 FSDEV PMA Buffer Description Table errata workaround
+//--------------------------------------------------------------------+
+
+#ifdef CFG_TUSB_FSDEV_32BIT
+/* Errata: Buffer description table update completes after CTR interrupt triggers
+ * https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
+ * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
+ *
+ * CTR may trigger before final PMA SRAM accesses complete on OUT transfers.
+ * Insert delay before reading PMA count/data.
+ * Max CPU frequency in Hz, used to derive conservative FSDEV PMA delay defaults.
+ */
+#if CFG_TUSB_MCU == OPT_MCU_STM32H5
+ #define FSDEV_STM32_CPU_HZ 250000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
+ #define FSDEV_STM32_CPU_HZ 160000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U3
+ #define FSDEV_STM32_CPU_HZ 96000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32U0
+ #define FSDEV_STM32_CPU_HZ 56000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
+ #define FSDEV_STM32_CPU_HZ 64000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32C0
+ #define FSDEV_STM32_CPU_HZ 48000000U
+#elif CFG_TUSB_MCU == OPT_MCU_STM32C5
+ #define FSDEV_STM32_CPU_HZ 144000000U
+#endif
+
+// 11 cycles / 800ns = ~13750000 cycles per second, used to derive conservative FSDEV PMA delay defaults
+#ifndef CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT
+ #define CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 13750000U)
+#endif
+
+// 11 cycles / 6.4us = ~1718750 cycles per second, used to derive conservative FSDEV PMA delay defaults
+#ifndef CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT
+ #define CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 1718750U)
+#endif
+
+/**
+ * LDR from SP-relative: 2 cycles
+ * SUBS: 1 cycle
+ * STR to SP-relative: 2 cycles
+ * LDR from SP-relative: 2 cycles
+ * CMP: 1 cycle
+ * BNE:
+ * taken: 3 cycles total (often shown as 1 + pipeline refill)
+ * not taken: 1 cycle
+ * Total cycles if delay is needed: 11 cycles
+ */
+TU_ATTR_ALWAYS_INLINE static inline void fsdev_btable_workaround_delay(bool low_speed) {
+ volatile uint32_t cycle_count = low_speed ? CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT : CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT;
+ while (cycle_count > 0U) {
+ cycle_count--;
+ }
+}
+#endif
+
+//--------------------------------------------------------------------+
// Connect / Disconnect
//--------------------------------------------------------------------+
diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
index dd86b052a..f9201651a 100644
--- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
+++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c
@@ -38,8 +38,7 @@
#include "tusb_option.h"
-#if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && \
- TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32G0, OPT_MCU_STM32H5, OPT_MCU_STM32U5)
+#if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && defined(TUP_USBIP_FSDEV_DRD)
#include "host/hcd.h"
#include "host/usbh.h"
@@ -59,20 +58,6 @@
TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index");
-#if CFG_TUSB_MCU == OPT_MCU_STM32H5
- #define CPU_FREQUENCY_MHZ 250U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
- #define CPU_FREQUENCY_MHZ 160U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32U3
- #define CPU_FREQUENCY_MHZ 96U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
- #define CPU_FREQUENCY_MHZ 64U
-#elif CFG_TUSB_MCU == OPT_MCU_STM32C0
- #define CPU_FREQUENCY_MHZ 48U
-#else
- #error "CPU_FREQUENCY_MHZ not defined for this STM32 MCU"
-#endif
-
enum {
HCD_XFER_ERROR_MAX = 3,
HCD_XFER_NAK_MAX = 15,
@@ -166,35 +151,9 @@ static inline void channel_write_status(uint8_t ch_id, uint32_t ch_reg, tusb_dir
}
static inline uint16_t channel_get_rx_count(uint8_t ch_id) {
- /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf
- * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf
- * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers
- * Description:
- * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM accesses
- * have completed. If the software responds quickly to the interrupt, the full buffer contents may not be correct.
- * Workaround:
- * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay
- * should be 800 ns in Full Speed mode and 6.4 ฮผs in Low Speed mode
- *
- * Note: this errata may also apply to G0, U5, H5 etc.
- *
- * We choose the delay count based on max CPU frequency (in MHz) to ensure the delay is at least the required time.
- */
-
uint32_t ch_reg = ch_read(ch_id);
- if (FSDEV_REG->ISTR & U_ISTR_LS_DCONN || ch_reg & U_EP_LSEP) {
- // Low speed mode: 6.4 us delay -> about 2 cycles per MHz
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ * 2U;
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
- } else {
- // Full speed mode: 800 ns delay -> about 0.25 cycles per MHz
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U;
- while (cycle_count > 0U) {
- cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare)
- }
- }
+ const bool is_low_speed = (FSDEV_REG->ISTR & U_ISTR_LS_DCONN) || (ch_reg & U_EP_LSEP);
+ fsdev_btable_workaround_delay(is_low_speed);
return btable_get_count(ch_id, BTABLE_BUF_RX);
}
@@ -238,11 +197,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
// If DCON_STAT is already set, the controller sometimes misses the initial connection interrupt
if (FSDEV_REG->ISTR & U_ISTR_DCON_STAT) {
- // Wait DP/DM stabilize time
- volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U;
- while (cycle_count > 0U) {
- cycle_count--;
- }
+ tusb_time_delay_ms_api(2);
port_status_handler(rhport, false);
}
@@ -672,10 +627,8 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet
// Clear stall, data toggle is also reset to DATA0
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- uint8_t const ep_id = endpoint_find(dev_addr, 0);
+ uint8_t const ep_id = endpoint_find(dev_addr, ep_addr);
TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8);
hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id];
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 8685ec6dc..e1a2f6cf2 100644
--- a/src/portable/synopsys/dwc2/dcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/dcd_dwc2.c
@@ -73,8 +73,15 @@ typedef struct {
static dcd_data_t _dcd_data;
+// DMA receives up to 3 back-to-back SETUP packets (3 x 8 bytes), Slave mode only needs 1 packet (8 bytes)
+#if CFG_TUD_DWC2_DMA_ENABLE
+ #define DWC2_SETUP_BUFFER_SIZE 24
+#else
+ #define DWC2_SETUP_BUFFER_SIZE 8
+#endif
+
CFG_TUD_MEM_SECTION static struct {
- TUD_EPBUF_DEF(setup_packet, 8);
+ TUD_EPBUF_DEF(setup_buffer, DWC2_SETUP_BUFFER_SIZE);
} _dcd_usbbuf;
static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT;
@@ -136,9 +143,9 @@ static void dma_setup_prepare(uint8_t rhport) {
}
}
- // Receive only 1 packet
- dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos);
- dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet;
+ // Receive back-to-back setup packets
+ dwc2->epout[0].doeptsiz = (3 << DOEPTSIZ_STUPCNT_Pos);
+ dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_buffer;
dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP;
}
@@ -148,22 +155,23 @@ static void dma_setup_prepare(uint8_t rhport) {
/* Device Data FIFO scheme
+ The controller has a single SPRAM of otg_dfifo_depth 32-bit words shared between all FIFOs and optional DMA metadata.
+ otg_dfifo_depth = ghwcfg3.dfifo_depth + EP_LOC_CNT. It is split up into:
- The FIFO is split up into
- - EPInfo: for storing DMA metadata, only required when use DMA. Maximum size is called
- EP_LOC_CNT = ep_fifo_size - ghwcfg3.dfifo_depth. For value less than EP_LOC_CNT, gdfifocfg must be configured before
- gahbcfg.dmaen is set
- - Buffer mode: 1 word per endpoint direction
- - Scatter/Gather DMA: 4 words per endpoint direction
+ - EPInfo: for storing DMA address registers (DxEPDMAn), only required when DMA is used.
+ gdfifocfg.EPINFOBASE and gdfifocfg.GDFIFOCfg must be configured before gahbcfg.dmaen is set.
+ The number of words needed per endpoint direction depends on the DMA mode used at runtime:
+ - Buffer DMA mode: 1 word per endpoint direction
+ - Scatter/Gather DMA mode: 4 words per endpoint direction
- TX FIFO: one fifo for each IN endpoint. Size is dynamic depending on packet size, starting from top with EP0 IN.
- Shared RX FIFO: a shared fifo for all OUT endpoints. Typically, can hold up to 2 packets of the largest EP size.
- We allocated TX FIFO from top to bottom (using top pointer), this to allow the RX FIFO to grow dynamically which is
+ We allocate TX FIFOs from top to bottom (using a top pointer), this to allow the RX FIFO to grow dynamically, which is
possible since the free space is located between the RX and TX FIFOs.
- ---------------- ep_fifo_size
- | DxEPIDMAn |
- |-------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
+ --------------- otg_dfifo_depth
+ | EPInfo | DxEPDMAn (DMA only, sized per runtime DMA mode)
+ |-------------|-- gdfifocfg.EPINFOBASE (start of EPInfo; FIFO space sized by GDFIFOCFG)
| IN FIFO 0 | control EP
|-------------|
| IN FIFO 1 |
@@ -184,13 +192,13 @@ static void dma_setup_prepare(uint8_t rhport) {
- 13 for setup packets + control words (up to 3 setup packets).
- 1 for global NAK (not required/used here).
- Largest-EPsize/4 + 1. (FS: 64 bytes, HS: 512 bytes). Recommended is "2 x (Largest-EPsize/4 + 1)"
- - 2 for each used OUT endpoint
+ - 2 for each used OUT endpoint.
- Therefore GRXFSIZ = 13 + 1 + 2 x (Largest-EPsize/4 + 1) + 2 x EPOUTnum
+ Therefore, GRXFSIZ = 13 + 1 + 2 x (Largest-EPsize/4 + 1) + 2 x EPOUTnum
*/
TU_ATTR_ALWAYS_INLINE static inline uint16_t calc_device_grxfsiz(uint16_t largest_ep_size, uint8_t ep_count) {
- return 13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count;
+ return (uint16_t)(13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count);
}
static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, bool is_bulk) {
@@ -202,7 +210,7 @@ static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, b
TU_ASSERT(epnum < ep_count);
- uint16_t fifo_size = tu_div_ceil(packet_size, 4);
+ uint16_t fifo_size = (uint16_t)tu_div_ceil(packet_size, 4);
if (dir == TUSB_DIR_OUT) {
// Calculate required size of RX FIFO
const uint16_t new_sz = calc_device_grxfsiz(4 * fifo_size, ep_count);
@@ -249,7 +257,7 @@ static void dfifo_device_init(uint8_t rhport) {
// Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per endpoint direction
const bool is_dma = dma_device_enabled(dwc2);
- _dcd_data.dfifo_top = dwc2_controller->ep_fifo_size/4;
+ _dcd_data.dfifo_top = dwc2_controller->otg_dfifo_depth;
if (is_dma) {
_dcd_data.dfifo_top -= 2 * dwc2_controller->ep_count;
}
@@ -345,6 +353,11 @@ static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) {
dwc2->dctl |= DCTL_CGONAK;
}
}
+
+ // Clear ActEP
+ if (!stall && epnum != 0) {
+ dep->ctl &= ~EPCTL_USBAEP;
+ }
}
// Since this function returns void, it is not possible to return a boolean success message
@@ -365,7 +378,7 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin
num_packets = 1;
} else {
total_bytes = xfer->total_len;
- num_packets = tu_div_ceil(total_bytes, xfer->max_size);
+ num_packets = (uint16_t)tu_div_ceil(total_bytes, xfer->max_size);
if (num_packets == 0) {
num_packets = 1; // zero length packet still count as 1
}
@@ -436,12 +449,12 @@ bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
}
bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
- dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+ dwc2_clock_init(rhport, rh_init->role);
tu_memclr(&_dcd_data, sizeof(_dcd_data));
// Core Initialization
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, TUD_OPT_HIGH_SPEED);
const bool is_dma = dma_device_enabled(dwc2);
TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma));
@@ -535,8 +548,9 @@ void dcd_remote_wakeup(uint8_t rhport) {
void dcd_connect(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
-#ifdef TUP_USBIP_DWC2_ESP32
- // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31)
+ // S31 is excluded at compile time (no USB_WRAP peripheral).
+ // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY.
if (rhport == 0) {
usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
conf.pad_pull_override = 0;
@@ -554,8 +568,9 @@ void dcd_connect(uint8_t rhport) {
void dcd_disconnect(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
-#ifdef TUP_USBIP_DWC2_ESP32
- // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY
+#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31)
+ // S31 is excluded at compile time (no USB_WRAP peripheral).
+ // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY.
if (rhport == 0) {
usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf;
conf.pad_pull_override = 1;
@@ -785,13 +800,15 @@ static void handle_bus_reset(uint8_t rhport) {
xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
+ uint32_t gintmsk = GINTMSK_OTGINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM;
if(dma_device_enabled(dwc2)) {
+ gintmsk |= GINTMSK_OEPINT;
dma_setup_prepare(rhport);
} else {
dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
}
- dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM;
+ dwc2->gintmsk |= gintmsk;
}
static void handle_enum_done(uint8_t rhport) {
@@ -875,31 +892,47 @@ static void handle_rxflvl_irq(uint8_t rhport) {
dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const volatile uint32_t* rx_fifo = dwc2->fifo[0];
+ // DWC2 v3.10a (e.g. STM32L476) emits an extra EP0 RX_COMPLETE that is NOT a real OUT data transfer completion, in two
+ // situations - each flagged by a DOEPINT bit set on that word:
+ // - DOEPINT.STPKTRX (Setup Packet Received): pushed between SETUP_RX and SETUP_DONE of every control transfer.
+ // - DOEPINT.STSPHSRX (Status Phase Received for control write): pushed after the OUT data stage when the host
+ // starts the IN status phase.
+ // Both are dropped in the RX_COMPLETE case below, clearing the flag (W1C) so a latched STSPHSRX
+ // does not block the core from emitting the next SETUP_DONE. usbd still processes the real OUT data
+ // and queues the IN status ZLP itself - the core does not auto-complete the control-write status.
+ const bool quirk_v310a = (dwc2->gsnpsid == DWC2_CORE_REV_3_10a);
+
// Pop control word off FIFO
const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp};
+ const uint8_t packet_status = grxstsp.packet_status;
const uint8_t epnum = grxstsp.ep_ch_num;
dwc2_dep_t* epout = &dwc2->epout[epnum];
- switch (grxstsp.packet_status) {
+ switch (packet_status) {
case GRXSTS_PKTSTS_GLOBAL_OUT_NAK:
// Global OUT NAK: do nothing
break;
case GRXSTS_PKTSTS_SETUP_RX: {
// Setup packet received
- uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet;
+ uint32_t * setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer;
// We can receive up to three setup packets in succession, but only the last one is valid.
setup[0] = (*rx_fifo);
setup[1] = (*rx_fifo);
break;
}
- case GRXSTS_PKTSTS_SETUP_DONE:
- // Setup packet done:
- // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq()
+ case GRXSTS_PKTSTS_SETUP_DONE: {
+ // Pop this word causes the Setup interrupt
epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos);
+ epout->doepint = DOEPINT_SETUP | DOEPINT_STPKTRX; // Clear SETUP interrupt, required for core to re-write this control word
+ if (edpt_is_enabled(&dwc2->epin[0])) {
+ edpt_disable(rhport, 0x80, false);
+ }
+ dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true);
break;
+ }
case GRXSTS_PKTSTS_RX_DATA: {
// Out packet received
@@ -927,41 +960,31 @@ static void handle_rxflvl_irq(uint8_t rhport) {
break;
}
- case GRXSTS_PKTSTS_RX_COMPLETE:
- // Out packet done
- // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on
- // the specified OUT endpoint which will be handled by handle_epout_irq()
- break;
+ case GRXSTS_PKTSTS_RX_COMPLETE: {
+ // Pop this word causes the xfer complete interrupt
+ const uint32_t doepint = epout->doepint;
+ epout->doepint = DOEPINT_XFRC;
- default: break; // nothing to do
- }
-}
-
-static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) {
- if (doepint_bm.setup_phase_done) {
- // Cleanup previous pending EP0 IN transfer if any
- dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0];
- if (edpt_is_enabled(epin0)) {
- edpt_disable(rhport, 0x80, false);
- }
- dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true);
- return;
- }
+ // v3.10a quirk (see top of function): the extra RX_COMPLETE flagged with Setup Packet Received (STPKTRX) or
+ // Status Phase Received for control write (STSPHSRX) is not a real OUT completion. Drop it
+ if (quirk_v310a) {
+ if (doepint & (DOEPINT_STPKTRX | DOEPINT_STSPHSRX)) {
+ epout->doepint = DOEPINT_STPKTRX | DOEPINT_STSPHSRX;
+ break;
+ }
+ }
- // Normal OUT transfer complete
- if (doepint_bm.xfer_complete) {
- // only handle data skip if it is setup or status related
- // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they
- // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done
- if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) {
xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT);
- if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) {
- // EP0 can only handle one packet, Schedule another packet to be received.
- edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT);
+ if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) {
+ // EP0 can only handle one packet, schedule another packet to be received.
+ edpt_schedule_packets(rhport, 0, TUSB_DIR_OUT);
} else {
dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true);
}
+ break;
}
+
+ default: break; // nothing to do
}
}
@@ -1000,13 +1023,23 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi
if (doepint_bm.setup_phase_done) {
// Cleanup previous pending EP0 IN transfer if any
- dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0];
+ dwc2_dep_t* epin0 = &dwc2->epin[0];
+ dwc2_dep_t* epout0 = &dwc2->epout[0];
if (edpt_is_enabled(epin0)) {
edpt_disable(rhport, 0x80, false);
}
- dma_setup_prepare(rhport);
- dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8);
- dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true);
+
+ dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, sizeof(_dcd_usbbuf.setup_buffer));
+
+ // DOEPDMA0 has advanced past the last received SETUP packet; back up one packet to the latest valid one
+ // (Programming Guide v4.20a section 9.1.2.1: "DOEPDMAn-8 provides the pointer to the last valid SETUP data")
+ tusb_control_request_t *setup_packet = (tusb_control_request_t *) (uintptr_t) (epout0->doepdma - sizeof(tusb_control_request_t));
+ dcd_event_setup_received(rhport, (uint8_t*)setup_packet, true);
+
+ // Prepare EP0 for next setup if this setup has no data stage
+ if (setup_packet->wLength == 0) {
+ dma_setup_prepare(rhport);
+ }
return;
}
@@ -1027,9 +1060,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi
const uint16_t remain = tsiz.xfer_size;
xfer->total_len -= remain;
- // this is ZLP, so prepare EP0 for next setup
- // TODO use status phase rx
- if(epnum == 0 && xfer->total_len == 0) {
+ // prepare EP0 for next setup
+ if(epnum == 0) {
dma_setup_prepare(rhport);
}
@@ -1048,9 +1080,6 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin
// EP0 can only handle one packet. Schedule another packet to be transmitted.
edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN);
} else {
- if(epnum == 0) {
- dma_setup_prepare(rhport);
- }
dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true);
}
}
@@ -1091,7 +1120,7 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) {
if (dir == TUSB_DIR_IN) {
handle_epin_slave(rhport, epnum, intr.diepint_bm);
} else {
- handle_epout_slave(rhport, epnum, intr.doepint_bm);
+ // epout is handled in handle_rxflvl_irq
}
#endif
}
@@ -1128,8 +1157,9 @@ static void handle_incomplete_iso_in(uint8_t rhport) {
}
epin->diepctl = depctl.value;
} else {
- // too many retries, give up
+ // too many retries, give up, but keep endpoint activated
edpt_disable(rhport, epnum | TUSB_DIR_IN_MASK, false);
+ epin->diepctl |= DIEPCTL_USBAEP;
dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true);
}
}
@@ -1198,7 +1228,7 @@ void dcd_int_handler(uint8_t rhport) {
dwc2->gotgint = otg_int;
}
- if(gintsts & GINTSTS_SOF) {
+ if(gintsts & GINTSTS_SOF && dwc2->gintmsk & GINTMSK_SOFM) {
dwc2->gintsts = GINTSTS_SOF;
dwc2->gintmsk |= GINTMSK_USBSUSPM;
const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos;
@@ -1211,6 +1241,12 @@ void dcd_int_handler(uint8_t rhport) {
dcd_event_sof(rhport, frame, true);
}
+ // IN endpoint interrupt handling.
+ if (gintsts & GINTSTS_IEPINT) {
+ // IEPINT bit read-only, clear using DIEPINTn
+ handle_ep_irq(rhport, TUSB_DIR_IN);
+ }
+
#if CFG_TUD_DWC2_SLAVE_ENABLE
// RxFIFO non-empty interrupt handling.
if (gintsts & GINTSTS_RXFLVL) {
@@ -1225,17 +1261,13 @@ void dcd_int_handler(uint8_t rhport) {
}
#endif
+#if CFG_TUD_DWC2_DMA_ENABLE
// OUT endpoint interrupt handling.
if (gintsts & GINTSTS_OEPINT) {
// OEPINT is read-only, clear using DOEPINTn
handle_ep_irq(rhport, TUSB_DIR_OUT);
}
-
- // IN endpoint interrupt handling.
- if (gintsts & GINTSTS_IEPINT) {
- // IEPINT bit read-only, clear using DIEPINTn
- handle_ep_irq(rhport, TUSB_DIR_IN);
- }
+#endif
// Incomplete isochronous IN transfer interrupt handling.
if (gintsts & GINTSTS_IISOIXFR) {
diff --git a/src/portable/synopsys/dwc2/dwc2_at32.h b/src/portable/synopsys/dwc2/dwc2_at32.h
index fa6d10c12..85fa9b20b 100644
--- a/src/portable/synopsys/dwc2/dwc2_at32.h
+++ b/src/portable/synopsys/dwc2/dwc2_at32.h
@@ -32,38 +32,38 @@
#if CFG_TUSB_MCU == OPT_MCU_AT32F415
#include <at32f415.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F435_437
#include <at32f435_437.h>
- #define OTG1_FIFO_SIZE 1280
- #define OTG2_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
+ #define OTG2_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define OTG2_IRQn OTGFS2_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#define DWC2_OTG2_REG_BASE 0x40040000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F423
#include <at32f423.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F402_405
#include <at32f402_405.h>
- #define OTG1_FIFO_SIZE 1280
- #define OTG2_FIFO_SIZE 4096
+ #define OTG1_DFIFO_DEPTH 320
+ #define OTG2_DFIFO_DEPTH 1024
#define OTG1_IRQn OTGFS1_IRQn
#define OTG2_IRQn OTGHS_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#define DWC2_OTG2_REG_BASE 0x40040000UL //OTGHS
#elif CFG_TUSB_MCU == OPT_MCU_AT32F425
#include <at32f425.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#elif CFG_TUSB_MCU == OPT_MCU_AT32F45X
#include <at32f45x.h>
- #define OTG1_FIFO_SIZE 1280
+ #define OTG1_DFIFO_DEPTH 320
#define OTG1_IRQn OTGFS1_IRQn
#define DWC2_OTG1_REG_BASE 0x50000000UL
#endif
@@ -73,12 +73,18 @@ extern "C" {
#endif
static const dwc2_controller_t _dwc2_controller[] = {
- {.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG1_FIFO_SIZE},
+ {.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG1_DFIFO_DEPTH},
#if defined DWC2_OTG2_REG_BASE
- {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = OTG2_FIFO_SIZE}
+ {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG2_DFIFO_DEPTH}
#endif
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
(void) role;
const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum;
diff --git a/src/portable/synopsys/dwc2/dwc2_bcm.h b/src/portable/synopsys/dwc2/dwc2_bcm.h
index 00842bba2..91cae821e 100644
--- a/src/portable/synopsys/dwc2/dwc2_bcm.h
+++ b/src/portable/synopsys/dwc2/dwc2_bcm.h
@@ -39,13 +39,19 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = USB_OTG_GLOBAL_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 16384 }
+ { .reg_base = USB_OTG_GLOBAL_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 4096 }
};
#define dcache_clean(_addr, _size) data_clean(_addr, _size)
#define dcache_invalidate(_addr, _size) data_invalidate(_addr, _size)
#define dcache_clean_invalidate(_addr, _size) data_clean_and_invalidate(_addr, _size)
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_common.h b/src/portable/synopsys/dwc2/dwc2_common.h
index 9f28ab2e0..5c4798d21 100644
--- a/src/portable/synopsys/dwc2/dwc2_common.h
+++ b/src/portable/synopsys/dwc2/dwc2_common.h
@@ -38,13 +38,15 @@
#include "host/hcd.h"
#endif
-// Following symbols must be defined by port header
-// - _dwc2_controller[]: array of controllers
-// - DWC2_EP_MAX: largest EP counts of all controllers
-// - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
-// - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
-// - dwc2_dcd_int_enable/dwc2_dcd_int_disable
-// - dwc2_remote_wakeup_delay
+/* Following symbols must be defined by port header
+ - _dwc2_controller[]: array of controllers
+ - DWC2_EP_MAX: largest EP counts of all controllers
+ - dwc2_clock_init(): clock init call before
+ - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset
+ - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core
+ - dwc2_dcd_int_enable/dwc2_dcd_int_disable
+ - dwc2_remote_wakeup_delay
+*/
#if defined(TUP_USBIP_DWC2_STM32)
#include "dwc2_stm32.h"
diff --git a/src/portable/synopsys/dwc2/dwc2_efm32.h b/src/portable/synopsys/dwc2/dwc2_efm32.h
index e1cb7c769..063360873 100644
--- a/src/portable/synopsys/dwc2/dwc2_efm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_efm32.h
@@ -40,9 +40,15 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = DWC2_REG_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 2048 }
+ { .reg_base = DWC2_REG_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 }
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h
index ff9f216bd..436f8dc30 100644
--- a/src/portable/synopsys/dwc2/dwc2_esp32.h
+++ b/src/portable/synopsys/dwc2/dwc2_esp32.h
@@ -37,14 +37,18 @@
#include "esp_intr_alloc.h"
#include "soc/periph_defs.h"
+
+// ESP32-S31 does not have USB_WRAP peripheral (HS-only with UTMI PHY)
+#if !TU_CHECK_MCU(OPT_MCU_ESP32S31)
#include "soc/usb_wrap_struct.h"
+#endif
#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
#define DWC2_FS_REG_BASE 0x60080000UL
#define DWC2_EP_MAX 7
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }
+ { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }
};
#elif TU_CHECK_MCU(OPT_MCU_ESP32H4)
@@ -61,7 +65,7 @@ static const dwc2_controller_t _dwc2_controller[] = {
#define DWC2_EP_MAX 7
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 }
+ { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }
};
#elif TU_CHECK_MCU(OPT_MCU_ESP32P4)
@@ -72,8 +76,16 @@ static const dwc2_controller_t _dwc2_controller[] = {
// On ESP32 for consistency we associate
// - Port0 to OTG_FS, and Port1 to OTG_HS
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .ep_fifo_size = 1024 },
- { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .ep_fifo_size = 4096 }
+ { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 },
+ { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 }
+};
+
+#elif TU_CHECK_MCU(OPT_MCU_ESP32S31)
+#define DWC2_HS_REG_BASE 0x20300000UL
+#define DWC2_EP_MAX 16
+
+static const dwc2_controller_t _dwc2_controller[] = {
+ { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTGHS_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 }
};
#endif
@@ -97,6 +109,12 @@ static void dwc2_int_handler_wrap(void* arg) {
#endif
}
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
if (enabled) {
esp_intr_alloc(_dwc2_controller[rhport].irqnum, ESP_INTR_FLAG_LOWMED,
diff --git a/src/portable/synopsys/dwc2/dwc2_gd32.h b/src/portable/synopsys/dwc2/dwc2_gd32.h
index ccbf93a76..28c3fde5e 100644
--- a/src/portable/synopsys/dwc2/dwc2_gd32.h
+++ b/src/portable/synopsys/dwc2/dwc2_gd32.h
@@ -37,7 +37,7 @@
static const dwc2_controller_t _dwc2_controller[] =
{
- { .reg_base = DWC2_REG_BASE, .irqnum = 86, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 1280 }
+ { .reg_base = DWC2_REG_BASE, .irqnum = 86, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 320 }
};
extern uint32_t SystemCoreClock;
@@ -57,6 +57,12 @@ static inline void __eclic_disable_interrupt (uint32_t irq){
*(volatile uint8_t*)(ECLIC_INTERRUPT_ENABLE_BASE + (irq * 4)) = 0;
}
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/dwc2_info.md b/src/portable/synopsys/dwc2/dwc2_info.md
index f83007b8c..205684e4b 100644
--- a/src/portable/synopsys/dwc2/dwc2_info.md
+++ b/src/portable/synopsys/dwc2/dwc2_info.md
@@ -1,58 +1,58 @@
-| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 |
-|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------|
-| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 |
-| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 |
-| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W |
-| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 |
-| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 |
-| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP |
-| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only |
-| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub |
-| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a |
-| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a |
-| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
-| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 |
-| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
-| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
-| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
-| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 |
-| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 |
-| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 |
-| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 |
-| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
-| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
-| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
-| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
-| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
-| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 |
-| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 |
-| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
-| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
-| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
-| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit |
-| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
-| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
-| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
-| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
-| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
+| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54H20 | nRF54LM20 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 |
+|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------|
+| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 |
+| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54500B | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 |
+| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 5.00b | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W |
+| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 |
+| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x22AFFC52 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 |
+| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP |
+| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only |
+| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub |
+| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a |
+| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a |
+| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
+| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 |
+| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
+| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
+| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 |
+| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 |
+| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x0BE0C0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 |
+| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 |
+| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 |
+| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
+| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
+| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
+| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
+| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
+| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 3040 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 |
+| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x3E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 |
+| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
+| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
+| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit |
+| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
+| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
+| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 |
+| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
+| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
diff --git a/src/portable/synopsys/dwc2/dwc2_info.py b/src/portable/synopsys/dwc2/dwc2_info.py
index e6601f482..8ee04e179 100755
--- a/src/portable/synopsys/dwc2/dwc2_info.py
+++ b/src/portable/synopsys/dwc2/dwc2_info.py
@@ -9,13 +9,14 @@ import pandas as pd
dwc2_reg_list = ['GUID', 'GSNPSID', 'GHWCFG1', 'GHWCFG2', 'GHWCFG3', 'GHWCFG4']
dwc2_reg_value = {
'AT32 F405 FS': [0x00002000, 0x4F54400A, 0x00000000, 0x228FDD00, 0x020004E8, 0x1FF0A020],
- 'AT32 F405 HS': [0x00000000, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020],
+ 'AT32 F405 HS': [0, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020],
'AT32 F415': [0x00001000, 0x4F54400A, 0x00000000, 0x228DCD00, 0x020004E8, 0x0F],
'BCM2711 (Pi4)': [0x2708A000, 0x4F54280A, 0, 0x228DDD50, 0xFF000E8, 0x1FF00020],
'EFM32GG': [0, 0x4F54330A, 0, 0x228F5910, 0x01F204E8, 0x1BF08030],
'ESP32-S2/S3': [0, 0x4F54400A, 0, 0x224DD930, 0x0C804B5, 0xD3F0A030],
'ESP32-P4': [0, 0x4F54400A, 0, 0x215FFFD0, 0x03805EB5, 0xDFF1A030],
- 'nRF54': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60],
+ 'nRF54H20': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60], # base on Preliminary Datasheet v0.7
+ 'nRF54LM20': [0, 0x4F54500B, 0x00000000, 0x22AFFC52, 0x0BE0C0E8, 0x3E10AA60],
# ST sort by GUID
'ST F407/429 HS': [0x1100, 0x4F54281A, 0, 0x229ED590, 0x03F403E8, 0x17F00030],
'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030],
diff --git a/src/portable/synopsys/dwc2/dwc2_nrf.h b/src/portable/synopsys/dwc2/dwc2_nrf.h
index 51f2d684f..a1bf692f8 100644
--- a/src/portable/synopsys/dwc2/dwc2_nrf.h
+++ b/src/portable/synopsys/dwc2/dwc2_nrf.h
@@ -22,22 +22,88 @@
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
+ *
+ * Modification
+ * - Gabriel Koppenstein add nRF54LM20 support
*/
#ifndef TUSB_DWC2_NRF_H
#define TUSB_DWC2_NRF_H
+// NRF is device only without OTG support
#include "nrf.h"
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+#include <soc/nrfx_coredep.h>
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
#define DWC2_EP_MAX 16
+// Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping)
+#if defined(NRF54LM20A_ENGA_XXAA)
+ #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE)
+#else
+ #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE0)
+#endif
+
static const dwc2_controller_t _dwc2_controller[] = {
- { .reg_base = NRF_USBHSCORE0_NS_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .ep_fifo_size = 12288 },
+ {.reg_base = DWC2_REG_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .otg_dfifo_depth = 3072},
};
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
(void) rhport;
(void) role;
- (void) enabled;
+
+ #if defined(NRF54LM20A_ENGA_XXAA)
+ // Start the USB voltage regulator
+ NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger;
+
+ // Based on Zephyr usbhs_enable_core() in drivers/usb/udc/udc_dwc2_vendor_quirks.h
+ // Step 1: Power up core only (PHY not yet)
+ NRF_USBHS->ENABLE = USBHS_ENABLE_CORE_Msk;
+
+ // Step 2: Override ID=Device (bit 31), and temporarily override VBUSVALID
+ NRF_USBHS->PHY.OVERRIDEVALUES = (USBHS_PHY_OVERRIDEVALUES_ID_Device << USBHS_PHY_OVERRIDEVALUES_ID_Pos);
+ NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk;
+
+ // Step 3: Release PHY power-on reset by enabling PHY
+ NRF_USBHS->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk;
+
+ // Step 4: Wait 45us for PHY clock to start
+ nrfx_coredep_delay_us(45);
+
+ // Step 5: Release DWC2 reset
+ NRF_USBHS->TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger;
+
+ // Step 6: Wait for clock to start to avoid hang on too early register read
+ nrfx_coredep_delay_us(2);
+
+ // Step 7: Clear VBUSVALID override (keep ID=Device override)
+ // DWC2 is now in Non-Driving opmode; D+ pull-up will activate when DWC2 clears DCTL SftDiscon
+ NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk;
+
+ // Barrier: USBHS wrapper (0x5005A000) and USBHSCORE (0x50020000) are separate
+ // peripheral blocks. Ensure the ENABLE/TASKS_START writes have propagated from
+ // the Cortex-M33 write buffer to hardware before anyone reads DWC2 core regs.
+ __DSB();
+ #endif
+}
+
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
+ (void)rhport;
+ (void)role;
+ if (enabled) {
+ NVIC_EnableIRQ(USBHS_IRQn);
+ } else {
+ NVIC_DisableIRQ(USBHS_IRQn);
+ }
}
#define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true)
@@ -47,21 +113,38 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) {
}
// MCU specific PHY init, called BEFORE core reset
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
// MCU specific PHY deinit, disable PHY power
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
// MCU specific PHY update, it is called AFTER init() and core reset
-TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) {
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) {
(void)dwc2;
(void)hs_phy_type;
}
+// nRF54 Cortex-M33 has no D-cache, provide no-op stubs for DMA mode
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void *addr, uint32_t data_size) {
+ (void)addr;
+ (void)data_size;
+ return true;
+}
+
#endif
diff --git a/src/portable/synopsys/dwc2/dwc2_stm32.h b/src/portable/synopsys/dwc2/dwc2_stm32.h
index 259ad21b9..126632fec 100644
--- a/src/portable/synopsys/dwc2/dwc2_stm32.h
+++ b/src/portable/synopsys/dwc2/dwc2_stm32.h
@@ -31,36 +31,36 @@
extern "C" {
#endif
-// EP_MAX : Max number of bi-directional endpoints including EP0
-// EP_FIFO_SIZE : Size of dedicated USB SRAM
+// EP_MAX : Max number of bi-directional endpoints including EP0
+// DFIFO_DEPTH_FS/HS : DFIFO depth in 32-bit words (OTG_DFIFO_DEPTH)
#if CFG_TUSB_MCU == OPT_MCU_STM32F1
#include "stm32f1xx.h"
#define EP_MAX_FS 4
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#elif CFG_TUSB_MCU == OPT_MCU_STM32F2
#include "stm32f2xx.h"
#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32F4
#include "stm32f4xx.h"
#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS
- #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32H7
#include "stm32h7xx.h"
#define EP_MAX_FS 9
- #define EP_FIFO_SIZE_FS 4096
+ #define DFIFO_DEPTH_FS 1024
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
// NOTE: H7 with only 1 USB port: H72x / H73x / H7Ax / H7Bx
// USB_OTG_FS_PERIPH_BASE and OTG_FS_IRQn not defined
@@ -72,18 +72,18 @@ extern "C" {
#elif CFG_TUSB_MCU == OPT_MCU_STM32H7RS
#include "stm32h7rsxx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32N6
#include "stm32n6xx.h"
#define EP_MAX_FS 9
- #define EP_FIFO_SIZE_FS 4096
+ #define DFIFO_DEPTH_FS 1024
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#define USB_OTG_FS_PERIPH_BASE USB1_OTG_HS_BASE
#define OTG_FS_IRQn USB1_OTG_HS_IRQn
@@ -94,15 +94,15 @@ extern "C" {
#elif CFG_TUSB_MCU == OPT_MCU_STM32F7
#include "stm32f7xx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#elif CFG_TUSB_MCU == OPT_MCU_STM32L4
#include "stm32l4xx.h"
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
#include "stm32u5xx.h"
@@ -110,11 +110,11 @@ extern "C" {
#ifdef USB_OTG_FS
#define USB_OTG_FS_PERIPH_BASE USB_OTG_FS_BASE
#define EP_MAX_FS 6
- #define EP_FIFO_SIZE_FS 1280
+ #define DFIFO_DEPTH_FS 320
#else
#define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#endif
#elif CFG_TUSB_MCU == OPT_MCU_STM32WBA
@@ -131,7 +131,7 @@ extern "C" {
#define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE_NS
#define OTG_HS_IRQn USB_OTG_HS_IRQn
#define EP_MAX_HS 9
- #define EP_FIFO_SIZE_HS 4096
+ #define DFIFO_DEPTH_HS 1024
#else
#error "Unsupported MCUs"
#endif
@@ -147,11 +147,11 @@ extern "C" {
// - Port0 to OTG_FS, and Port1 to OTG_HS
static const dwc2_controller_t _dwc2_controller[] = {
#ifdef USB_OTG_FS_PERIPH_BASE
- { .reg_base = USB_OTG_FS_PERIPH_BASE, .irqnum = OTG_FS_IRQn, .ep_count = EP_MAX_FS, .ep_fifo_size = EP_FIFO_SIZE_FS },
+ { .reg_base = USB_OTG_FS_PERIPH_BASE, .irqnum = OTG_FS_IRQn, .ep_count = EP_MAX_FS, .otg_dfifo_depth = DFIFO_DEPTH_FS },
#endif
#ifdef USB_OTG_HS_PERIPH_BASE
- { .reg_base = USB_OTG_HS_PERIPH_BASE, .irqnum = OTG_HS_IRQn, .ep_count = EP_MAX_HS, .ep_fifo_size = EP_FIFO_SIZE_HS },
+ { .reg_base = USB_OTG_HS_PERIPH_BASE, .irqnum = OTG_HS_IRQn, .ep_count = EP_MAX_HS, .otg_dfifo_depth = DFIFO_DEPTH_HS },
#endif
};
@@ -162,6 +162,12 @@ static const dwc2_controller_t _dwc2_controller[] = {
// SystemCoreClock is already included by family header
// extern uint32_t SystemCoreClock;
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) {
(void) role;
const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum;
diff --git a/src/portable/synopsys/dwc2/dwc2_type.h b/src/portable/synopsys/dwc2/dwc2_type.h
index 596bd0b34..7c03a4a91 100644
--- a/src/portable/synopsys/dwc2/dwc2_type.h
+++ b/src/portable/synopsys/dwc2/dwc2_type.h
@@ -47,7 +47,7 @@ typedef struct
uint32_t irqnum;
uint8_t ep_count;
uint8_t ep_in_count;
- uint32_t ep_fifo_size;
+ uint16_t otg_dfifo_depth; // total SPRAM in 32-bit words = ghwcfg3.dfifo_depth + EP_LOC_CNT
}dwc2_controller_t;
// DWC OTG HW Release versions
@@ -63,6 +63,7 @@ typedef struct
#define DWC2_CORE_REV_4_00a 0x4f54400a
#define DWC2_CORE_REV_4_11a 0x4f54411a
#define DWC2_CORE_REV_4_20a 0x4f54420a
+#define DWC2_CORE_REV_5_00b 0x4F54500b
#define DWC2_FS_IOT_REV_1_00a 0x5531100a
#define DWC2_HS_IOT_REV_1_00a 0x5532100a
#define DWC2_CORE_REV_MASK 0x0000ffff
diff --git a/src/portable/synopsys/dwc2/dwc2_xmc.h b/src/portable/synopsys/dwc2/dwc2_xmc.h
index aca3873df..85b2a2633 100644
--- a/src/portable/synopsys/dwc2/dwc2_xmc.h
+++ b/src/portable/synopsys/dwc2/dwc2_xmc.h
@@ -39,9 +39,15 @@
static const dwc2_controller_t _dwc2_controller[] =
{
// Note: XMC has some custom control registers before DWC registers
- { .reg_base = USB0_BASE, .irqnum = USB0_0_IRQn, .ep_count = DWC2_EP_MAX, .ep_fifo_size = 2048 }
+ { .reg_base = USB0_BASE, .irqnum = USB0_0_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 }
};
+// MCU specific to enable dwc2 clock/power before any access to register
+TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) {
+ (void) rhport;
+ (void) role;
+}
+
TU_ATTR_ALWAYS_INLINE
static inline void dwc2_dcd_int_enable(uint8_t rhport)
{
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index e12e44a41..84a0c6afd 100644
--- a/src/portable/synopsys/dwc2/hcd_dwc2.c
+++ b/src/portable/synopsys/dwc2/hcd_dwc2.c
@@ -104,6 +104,7 @@ typedef struct {
uint16_t xferred_bytes; // bytes that accumulate transferred though USB bus for the whole hcd_edpt_xfer(), which can
// be composed of multiple channel_xfer_start() (retry with NAK/NYET)
uint16_t fifo_bytes; // bytes written/read from/to FIFO (may not be transferred on USB bus).
+ uint8_t retry_disabled; // 1: channel was disabled to throttle a split retry (NAK in / XactErr out); re-arm on its halt
} hcd_xfer_t;
typedef struct {
@@ -323,9 +324,9 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t pa
We allocated TX FIFO from top to bottom (using top pointer), this to allow the RX FIFO to grow dynamically which is
possible since the free space is located between the RX and TX FIFOs.
- ----------------- ep_fifo_size
- | HCDMAn |
- |--------------|-- gdfifocfg.EPINFOBASE (max is ghwcfg3.dfifo_depth)
+ ----------------- otg_dfifo_depth
+ | HCDMAn | (DMA only, sized per runtime DMA mode)
+ |--------------|-- gdfifocfg.EPINFOBASE (= gdfifocfg.GDFIFOCfg)
| Non-Periodic |
| TX FIFO |
|--------------|--- GNPTXFSIZ.addr (fixed size)
@@ -358,15 +359,22 @@ static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) {
// Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per channel
const bool is_dma = dma_host_enabled(dwc2);
- uint16_t dfifo_top = dwc2_controller->ep_fifo_size/4;
+ uint16_t dfifo_top = dwc2_controller->otg_dfifo_depth;
if (is_dma) {
dfifo_top -= ghwcfg2.num_host_ch;
}
// fixed allocation for now, improve later:
- // - ptx_largest is limited to 256 for FS since most FS core only has 1024 bytes total
- uint32_t nptx_largest = is_hs_phy ? TUSB_EPSIZE_BULK_HS / 4 : TUSB_EPSIZE_BULK_FS / 4;
- uint32_t ptx_largest = is_hs_phy ? TUSB_EPSIZE_ISO_HS_MAX / 4 : 256 / 4;
+ // - ptx_largest is limited to 64 words for FS since most FS core only has 256-320 words total
+ uint32_t nptx_largest;
+ uint32_t ptx_largest;
+ if (is_hs_phy) {
+ nptx_largest = TUSB_EPSIZE_BULK_HS / 4;
+ ptx_largest = TUSB_EPSIZE_ISO_HS_MAX / 4;
+ } else {
+ nptx_largest = TUSB_EPSIZE_BULK_FS / 4;
+ ptx_largest = 256 / 4;
+ }
uint16_t nptxfsiz = 2 * nptx_largest;
uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch;
@@ -400,10 +408,12 @@ bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) {
// Initialize controller to host mode
bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- dwc2_regs_t* dwc2 = DWC2_REG(rhport);
+ dwc2_clock_init(rhport, rh_init->role);
+
tu_memclr(&_hcd_data, sizeof(_hcd_data));
// Core Initialization
+ dwc2_regs_t* dwc2 = DWC2_REG(rhport);
const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, _tuh_cfg.use_hs_phy);
const bool is_dma = dma_host_enabled(dwc2);
TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma));
@@ -894,9 +904,6 @@ static void handle_rxflvl_irq(uint8_t rhport) {
// return true if there is still pending data and need more ISR
static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
- // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
- const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
-
const uint8_t max_channel = dwc2_channel_count(dwc2);
for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
dwc2_channel_t* channel = &dwc2->channel[ch_id];
@@ -914,6 +921,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
// skip if there is not enough space in FIFO and RequestQueue.
// Packet's last word written to FIFO will trigger a request queue
+ // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
+ const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) {
return true;
}
@@ -1129,7 +1138,16 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
// TU_LOG1("in hcint = %02lX\r\n", hcint);
if (hcint & HCINT_HALTED) {
- if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) {
+ if (xfer->retry_disabled) {
+ // Halt from our split-NAK throttle disable (below): re-arm the start-split, or let teardown finish
+ // if the endpoint is closing. Programming Guide 3.5 "Halting a Channel" (p73).
+ xfer->retry_disabled = 0;
+ if (xfer->closing) {
+ is_done = true;
+ } else {
+ channel_send_in_token(dwc2, channel);
+ }
+ } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) {
const uint16_t remain_bytes = (uint16_t) hctsiz.xfer_size;
const uint16_t remain_packets = hctsiz.packet_count;
const uint16_t actual_len = edpt->buflen - remain_bytes;
@@ -1195,7 +1213,15 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci
channel->hcintmsk &= ~(HCINT_NAK | HCINT_DATATOGGLE_ERR);
hcsplt.split_compl = 0; // restart with start-split
channel->hcsplt = hcsplt.value;
- channel_xfer_in_retry(dwc2, ch_id, hcint);
+ // Persistent split bulk/control IN NAK (e.g. idle polled endpoint): re-enabling immediately storms
+ // the ISR and starves the task. Disable + re-arm on the resulting halt to throttle (like the slave
+ // path); no frame deferral. Programming Guide 3.5 (p73) Note permits disable on NAK/FrmOvrn splits.
+ if ((hcint & HCINT_NAK) && hcsplt.split_en && !channel_is_periodic(channel->hcchar)) {
+ xfer->retry_disabled = 1;
+ channel_disable(dwc2, channel);
+ } else {
+ channel_xfer_in_retry(dwc2, ch_id, hcint);
+ }
} else if (hcint & HCINT_FARME_OVERRUN) {
// retry start-split in next binterval
channel_xfer_in_retry(dwc2, ch_id, hcint);
@@ -1220,7 +1246,16 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
// TU_LOG1("out hcint = %02lX\r\n", hcint);
if (hcint & HCINT_HALTED) {
- if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) {
+ if (xfer->retry_disabled) {
+ // Halt from our split-XactErr throttle disable (below): re-issue the start-split (pointers already
+ // rewound), giving the hub TT a recovery gap. Programming Guide 3.5 "Halting a Channel" (p73).
+ xfer->retry_disabled = 0;
+ if (xfer->closing) {
+ is_done = true;
+ } else {
+ channel_xfer_start(dwc2, ch_id);
+ }
+ } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) {
is_done = true;
xfer->err_count = 0;
if (hcint & HCINT_XFER_COMPLETE) {
@@ -1243,9 +1278,17 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
xfer->result = XFER_RESULT_FAILED;
is_done = true;
} else {
- // clean up transfer so far and start again
+ // Rewind, then retry the start-split. Non-periodic SPLIT throttles via channel_disable + re-arm on
+ // the halt (immediate re-fire exhausts the retry budget; the disable gives the hub TT a recovery
+ // gap, like slave). Periodic split is excluded: channel_disable() is a no-op for it, so the halt
+ // never fires and the channel would wedge. Non-split re-inits immediately (Programming Guide 5.1.2.3).
channel_xfer_out_wrapup(dwc2, ch_id);
- channel_xfer_start(dwc2, ch_id);
+ if (hcsplt.split_en && !channel_is_periodic(channel->hcchar)) {
+ xfer->retry_disabled = 1;
+ channel_disable(dwc2, channel);
+ } else {
+ channel_xfer_start(dwc2, ch_id);
+ }
}
}
} else if (hcint & HCINT_NYET) {
@@ -1263,6 +1306,12 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc
channel->hcsplt = hcsplt.value;
channel->hcchar |= HCCHAR_CHENA;
}
+ } else if ((hcint & HCINT_NAK) && hcsplt.split_en) {
+ // Split OUT NAK: rewind + retry the start-split, else the channel stalls (Programming Guide 5.1.4.2).
+ // Non-split OUT NAK is core-handled (5.1.2.2), so this is split-only.
+ xfer->err_count = 0;
+ channel_xfer_out_wrapup(dwc2, ch_id);
+ channel_xfer_start(dwc2, ch_id);
}
if (xfer->closing == 1) {
diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h
index 68be64f5e..7ffdc6cef 100644
--- a/src/portable/wch/ch32_usbfs_reg.h
+++ b/src/portable/wch/ch32_usbfs_reg.h
@@ -39,59 +39,92 @@
#include <ch32f20x.h>
#elif CFG_TUSB_MCU == OPT_MCU_CH32V103
#include <ch32v10x.h>
+ // Newer-IP layout (separate UEPn_TX_CTRL/UEPn_RX_CTRL). The older IP (CH32V103) has a single
+ // combined control register at the UEPn_TX_CTRL offset, with UEPn_RX_CTRL reserved; the union
+ // exposes that same byte as UEPn_CTRL. Offsets are byte offsets from the peripheral base.
+ // TODO unify into a single struct shared by all WCH USBFS parts.
typedef struct
{
- __IO uint8_t BASE_CTRL;
- __IO uint8_t UDEV_CTRL;
- __IO uint8_t INT_EN;
- __IO uint8_t DEV_ADDR;
- __IO uint8_t Reserve0;
- __IO uint8_t MIS_ST;
- __IO uint8_t INT_FG;
- __IO uint8_t INT_ST;
- __IO uint32_t RX_LEN;
- __IO uint8_t UEP4_1_MOD;
- __IO uint8_t UEP2_3_MOD;
- __IO uint8_t UEP5_6_MOD;
- __IO uint8_t UEP7_MOD;
- __IO uint32_t UEP0_DMA;
- __IO uint32_t UEP1_DMA;
- __IO uint32_t UEP2_DMA;
- __IO uint32_t UEP3_DMA;
- __IO uint32_t UEP4_DMA;
- __IO uint32_t UEP5_DMA;
- __IO uint32_t UEP6_DMA;
- __IO uint32_t UEP7_DMA;
- __IO uint16_t UEP0_TX_LEN;
- __IO uint8_t UEP0_TX_CTRL;
- __IO uint8_t UEP0_RX_CTRL;
- __IO uint16_t UEP1_TX_LEN;
- __IO uint8_t UEP1_TX_CTRL;
- __IO uint8_t UEP1_RX_CTRL;
- __IO uint16_t UEP2_TX_LEN;
- __IO uint8_t UEP2_TX_CTRL;
- __IO uint8_t UEP2_RX_CTRL;
- __IO uint16_t UEP3_TX_LEN;
- __IO uint8_t UEP3_TX_CTRL;
- __IO uint8_t UEP3_RX_CTRL;
- __IO uint16_t UEP4_TX_LEN;
- __IO uint8_t UEP4_TX_CTRL;
- __IO uint8_t UEP4_RX_CTRL;
- __IO uint16_t UEP5_TX_LEN;
- __IO uint8_t UEP5_TX_CTRL;
- __IO uint8_t UEP5_RX_CTRL;
- __IO uint16_t UEP6_TX_LEN;
- __IO uint8_t UEP6_TX_CTRL;
- __IO uint8_t UEP6_RX_CTRL;
- __IO uint16_t UEP7_TX_LEN;
- __IO uint8_t UEP7_TX_CTRL;
- __IO uint8_t UEP7_RX_CTRL;
- __IO uint32_t Reserve1;
- __IO uint32_t OTG_CR;
- __IO uint32_t OTG_SR;
+ __IO uint8_t BASE_CTRL; // 0x00
+ __IO uint8_t UDEV_CTRL; // 0x01
+ __IO uint8_t INT_EN; // 0x02
+ __IO uint8_t DEV_ADDR; // 0x03
+ __IO uint8_t Reserve0; // 0x04
+ __IO uint8_t MIS_ST; // 0x05
+ __IO uint8_t INT_FG; // 0x06
+ __IO uint8_t INT_ST; // 0x07
+ __IO uint32_t RX_LEN; // 0x08
+ __IO uint8_t UEP4_1_MOD; // 0x0C
+ __IO uint8_t UEP2_3_MOD; // 0x0D
+ __IO uint8_t UEP5_6_MOD; // 0x0E
+ __IO uint8_t UEP7_MOD; // 0x0F
+ __IO uint32_t UEP0_DMA; // 0x10
+ __IO uint32_t UEP1_DMA; // 0x14
+ __IO uint32_t UEP2_DMA; // 0x18
+ __IO uint32_t UEP3_DMA; // 0x1C
+ __IO uint32_t UEP4_DMA; // 0x20
+ __IO uint32_t UEP5_DMA; // 0x24
+ __IO uint32_t UEP6_DMA; // 0x28
+ __IO uint32_t UEP7_DMA; // 0x2C
+ __IO uint16_t UEP0_TX_LEN; // 0x30
+ union {
+ __IO uint8_t UEP0_TX_CTRL;
+ __IO uint8_t UEP0_CTRL;
+ }; // 0x32 (TX_CTRL: IN | CTRL: combined)
+ __IO uint8_t UEP0_RX_CTRL; // 0x33 (OUT ctrl; reserved on combined IP)
+ __IO uint16_t UEP1_TX_LEN; // 0x34
+ union {
+ __IO uint8_t UEP1_TX_CTRL;
+ __IO uint8_t UEP1_CTRL;
+ }; // 0x36
+ __IO uint8_t UEP1_RX_CTRL; // 0x37
+ __IO uint16_t UEP2_TX_LEN; // 0x38
+ union {
+ __IO uint8_t UEP2_TX_CTRL;
+ __IO uint8_t UEP2_CTRL;
+ }; // 0x3A
+ __IO uint8_t UEP2_RX_CTRL; // 0x3B
+ __IO uint16_t UEP3_TX_LEN; // 0x3C
+ union {
+ __IO uint8_t UEP3_TX_CTRL;
+ __IO uint8_t UEP3_CTRL;
+ }; // 0x3E
+ __IO uint8_t UEP3_RX_CTRL; // 0x3F
+ __IO uint16_t UEP4_TX_LEN; // 0x40
+ union {
+ __IO uint8_t UEP4_TX_CTRL;
+ __IO uint8_t UEP4_CTRL;
+ }; // 0x42
+ __IO uint8_t UEP4_RX_CTRL; // 0x43
+ __IO uint16_t UEP5_TX_LEN; // 0x44
+ union {
+ __IO uint8_t UEP5_TX_CTRL;
+ __IO uint8_t UEP5_CTRL;
+ }; // 0x46
+ __IO uint8_t UEP5_RX_CTRL; // 0x47
+ __IO uint16_t UEP6_TX_LEN; // 0x48
+ union {
+ __IO uint8_t UEP6_TX_CTRL;
+ __IO uint8_t UEP6_CTRL;
+ }; // 0x4A
+ __IO uint8_t UEP6_RX_CTRL; // 0x4B
+ __IO uint16_t UEP7_TX_LEN; // 0x4C
+ union {
+ __IO uint8_t UEP7_TX_CTRL;
+ __IO uint8_t UEP7_CTRL;
+ }; // 0x4E
+ __IO uint8_t UEP7_RX_CTRL; // 0x4F
+ __IO uint32_t Reserve1; // 0x50
+ __IO uint32_t OTG_CR; // 0x54
+ __IO uint32_t OTG_SR; // 0x58
} USBOTG_FS_TypeDef;
#define USBOTG_FS ((USBOTG_FS_TypeDef *) 0x40023400)
+
+ // CH32V103 has the older USBFS IP: a single combined control register per endpoint
+ // (UEPn_CTRL) instead of separate TX_CTRL/RX_CTRL bytes. The struct's UEPn_TX_CTRL field
+ // aliases that combined register (same address); UEPn_RX_CTRL maps to unused padding.
+ #define CH32_USBFS_EP_CTRL_COMBINED 1
#elif CFG_TUSB_MCU == OPT_MCU_CH32V20X
#include <ch32v20x.h>
#elif CFG_TUSB_MCU == OPT_MCU_CH32V307
@@ -166,6 +199,17 @@
#define USBFS_EP_R_RES_NAK (2 << 0)
#define USBFS_EP_R_RES_STALL (3 << 0)
+#ifdef CH32_USBFS_EP_CTRL_COMBINED
+// Combined per-endpoint control register (older IP, e.g. CH32V103): IN response in
+// bits [1:0], OUT response in bits [3:2], shared auto-toggle, separate IN/OUT toggle.
+#define USBFS_EPC_T_RES_MASK 0x03
+#define USBFS_EPC_R_RES_MASK 0x0C
+#define USBFS_EPC_R_RES_SHIFT 2
+#define USBFS_EPC_AUTO_TOG 0x10
+#define USBFS_EPC_T_TOG 0x40
+#define USBFS_EPC_R_TOG 0x80
+#endif
+
// token PID
#define PID_OUT 0
#define PID_SOF 1
diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c
index 5cd25e33e..dae31da91 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -29,29 +29,75 @@
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS
-#include "device/dcd.h"
-#include "ch32_usbfs_reg.h"
+ #include "device/dcd.h"
+ #include "ch32_usbfs_reg.h"
-/* private defines */
-#define EP_MAX (8)
+ /* private defines */
+ #define EP_MAX (8)
-#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
-#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
-#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
-#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+ #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep])
+ #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep])
+ #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep])
+ #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep])
+
+// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate
+// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined
+// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared.
+// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them.
+#ifdef CH32_USBFS_EP_CTRL_COMBINED
+ #define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register
+
+ static inline uint8_t ep_tx_to_comb(uint8_t v) {
+ uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings
+ if (v & USBFS_EP_T_TOG) { c |= USBFS_EPC_T_TOG; }
+ if (v & USBFS_EP_T_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; }
+ return c;
+ }
+ static inline uint8_t ep_rx_to_comb(uint8_t v) {
+ uint8_t c = (uint8_t) ((v & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT); // OUT response -> bits [3:2]
+ if (v & USBFS_EP_R_TOG) { c |= USBFS_EPC_R_TOG; }
+ if (v & USBFS_EP_R_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; }
+ return c;
+ }
+ // Set IN side (response/toggle/auto-tog), preserving the OUT response + OUT toggle.
+ static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_R_RES_MASK | USBFS_EPC_R_TOG)) | ep_tx_to_comb(v));
+ }
+ // Set OUT side, preserving the IN response + IN toggle.
+ static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_T_RES_MASK | USBFS_EPC_T_TOG)) | ep_rx_to_comb(v));
+ }
+ static inline void ep_tx_set_response(uint8_t ep, uint8_t res) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_T_RES_MASK) | (res & USBFS_EP_T_RES_MASK));
+ }
+ static inline void ep_rx_set_response(uint8_t ep, uint8_t res) {
+ EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_R_RES_MASK) | ((res & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT));
+ }
+ #define EP0_SETUP_RX_TOG USBFS_EP_R_TOG // combined IP: data/status stage after SETUP is DATA1
+#else
+ static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { EP_TX_CTRL(ep) = v; }
+ static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { EP_RX_CTRL(ep) = v; }
+ static inline void ep_tx_set_response(uint8_t ep, uint8_t res) {
+ EP_TX_CTRL(ep) = (uint8_t) ((EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | res);
+ }
+ static inline void ep_rx_set_response(uint8_t ep, uint8_t res) {
+ EP_RX_CTRL(ep) = (uint8_t) ((EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | res);
+ }
+ #define EP0_SETUP_RX_TOG 0
+#endif
/* private data */
struct usb_xfer {
- bool valid;
- uint8_t* buffer;
- size_t len;
- size_t processed_len;
- size_t max_size;
+ bool valid;
+ uint8_t *buffer;
+ size_t len;
+ size_t processed_len;
+ size_t max_size;
};
static struct {
- bool ep0_tog;
- bool isochronous[EP_MAX];
+ bool ep0_tog;
+ bool isochronous[EP_MAX];
struct usb_xfer xfer[EP_MAX][2];
TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64];
TU_ATTR_ALIGNED(4) struct {
@@ -64,7 +110,7 @@ static struct {
/* private helpers */
static void update_in(uint8_t rhport, uint8_t ep, bool force) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_IN];
if (xfer->valid) {
if (force || xfer->len) {
size_t len = TU_MIN(xfer->max_size, xfer->len);
@@ -81,25 +127,27 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) {
EP_TX_LEN(ep) = len;
if (ep == 0) {
- EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0);
- data.ep0_tog = !data.ep0_tog;
+ ep_tx_ctrl_set(0, USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0));
+ data.ep0_tog = !data.ep0_tog;
} else if (data.isochronous[ep]) {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET;
+ ep_tx_set_response(ep, USBFS_EP_T_RES_NYET);
} else {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_ACK;
+ ep_tx_set_response(ep, USBFS_EP_T_RES_ACK);
}
} else {
xfer->valid = false;
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
- dcd_event_xfer_complete(
- rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len,
- XFER_RESULT_SUCCESS, true);
+ if (ep == 0) {
+ ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0));
+ } else if (!data.isochronous[ep]) {
+ ep_tx_set_response(ep, USBFS_EP_T_RES_NAK);
+ }
+ dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true);
}
}
}
static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
- struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT];
+ struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT];
if (xfer->valid) {
size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len));
if (ep == 3) {
@@ -117,41 +165,49 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) {
}
if (ep == 0) {
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ ep_rx_set_response(0, USBFS_EP_R_RES_NAK);
+ } else {
+ uint8_t rx_res =
+ data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK);
+ ep_rx_set_response(ep, rx_res);
}
}
}
+static void reset_ep_ctrls(void) {
+ for (uint8_t ep = 1; ep < EP_MAX; ep++) {
+ EP_DMA(ep) = (uint32_t)&data.buffer[ep][0];
+ EP_TX_LEN(ep) = 0;
+ ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET);
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET);
+ }
+ EP_DMA(3) = (uint32_t)&data.ep3_buffer.out[0];
+}
+
/* public functions */
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
// init registers
USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN;
USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN;
- USBOTG_FS->DEV_ADDR = 0x00;
+ USBOTG_FS->DEV_ADDR = 0x00;
USBOTG_FS->INT_FG = 0xFF;
USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND;
// setup endpoint 0
- EP_DMA(0) = (uint32_t) &data.buffer[0][0];
- EP_TX_LEN(0) = 0;
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ EP_DMA(0) = (uint32_t)&data.buffer[0][0];
+ EP_TX_LEN(0) = 0;
+ ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
+ ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
// enable other endpoints but NAK everything
USBOTG_FS->UEP4_1_MOD = 0xCC;
USBOTG_FS->UEP2_3_MOD = 0xCC;
USBOTG_FS->UEP5_6_MOD = 0xCC;
- USBOTG_FS->UEP7_MOD = 0x0C;
+ USBOTG_FS->UEP7_MOD = 0x0C;
- for (uint8_t ep = 1; ep < EP_MAX; ep++) {
- EP_DMA(ep) = (uint32_t) &data.buffer[ep][0];
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
- }
- EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0];
+ reset_ep_ctrls();
dcd_connect(rhport);
@@ -159,15 +215,15 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
uint8_t status = USBOTG_FS->INT_FG;
if (status & USBFS_INT_FG_TRANSFER) {
- uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
- uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST);
+ uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST);
+ uint16_t rx_len = USBOTG_FS->RX_LEN;
switch (token) {
case PID_OUT: {
- uint16_t rx_len = USBOTG_FS->RX_LEN;
update_out(rhport, ep, rx_len);
break;
}
@@ -178,25 +234,32 @@ void dcd_int_handler(uint8_t rhport) {
case PID_SETUP:
// setup clears stall
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK);
+ data.ep0_tog = true;
+
+ const tusb_control_request_t *setup = (const tusb_control_request_t *)&data.buffer[0][TUSB_DIR_OUT][0];
+ // EP0_SETUP_RX_TOG arms the data/status stage at DATA1 on the combined-control IP
+ ep_rx_ctrl_set(0, ((setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK) | EP0_SETUP_RX_TOG);
- data.ep0_tog = true;
dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
break;
}
USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER;
} else if (status & USBFS_INT_FG_BUS_RST) {
- data.ep0_tog = true;
+ data.ep0_tog = true;
data.xfer[0][TUSB_DIR_OUT].max_size = 64;
- data.xfer[0][TUSB_DIR_IN].max_size = 64;
+ data.xfer[0][TUSB_DIR_IN].max_size = 64;
- //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
- dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true);
+ // dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ // true);
+ dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL,
+ true);
USBOTG_FS->DEV_ADDR = 0x00;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
+
+ reset_ep_ctrls();
USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST;
} else if (status & USBFS_INT_FG_SUSPEND) {
@@ -207,148 +270,147 @@ void dcd_int_handler(uint8_t rhport) {
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHD_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHD_IRQn);
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
void dcd_connect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN;
}
void dcd_disconnect(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
- (void) en;
+ (void)rhport;
+ (void)en;
// TODO implement later
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue;
}
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress);
uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress);
TU_ASSERT(ep < EP_MAX);
- data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS;
data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep);
if (ep != 0) {
if (dir == TUSB_DIR_OUT) {
- if (data.isochronous[ep]) {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
- } else {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK;
- }
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK;
+ ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
return true;
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
// TODO optional
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void) rhport;
- (void) ep_addr;
+ (void)rhport;
+ (void)ep_addr;
(void)largest_packet_size;
- return false;
+ uint8_t ep = tu_edpt_number(ep_addr);
+ uint8_t dir = tu_edpt_dir(ep_addr);
+
+ data.isochronous[ep] = true;
+ data.xfer[ep][dir].max_size = largest_packet_size;
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
(void)rhport;
(void)desc_ep;
- return false;
+ return true;
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
- struct usb_xfer* xfer = &data.xfer[ep][dir];
+ struct usb_xfer *xfer = &data.xfer[ep][dir];
dcd_int_disable(rhport);
- xfer->valid = true;
- xfer->buffer = buffer;
- xfer->len = total_bytes;
+ xfer->valid = true;
+ xfer->buffer = buffer;
+ xfer->len = total_bytes;
xfer->processed_len = 0;
dcd_int_enable(rhport);
if (dir == TUSB_DIR_IN) {
update_in(rhport, ep, true);
+ } else {
+ uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK;
+ ep_rx_set_response(ep, rx_res);
}
return true;
}
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
if (ep == 0) {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL;
+ ep_rx_ctrl_set(0, USBFS_EP_R_RES_STALL);
} else {
- EP_TX_LEN(0) = 0;
- EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL;
+ EP_TX_LEN(0) = 0;
+ ep_tx_ctrl_set(0, USBFS_EP_T_RES_STALL);
}
} else {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | USBFS_EP_R_RES_STALL;
+ ep_rx_set_response(ep, USBFS_EP_R_RES_STALL);
} else {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | USBFS_EP_T_RES_STALL;
+ ep_tx_set_response(ep, USBFS_EP_T_RES_STALL);
}
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
- uint8_t ep = tu_edpt_number(ep_addr);
+ (void)rhport;
+ uint8_t ep = tu_edpt_number(ep_addr);
uint8_t dir = tu_edpt_dir(ep_addr);
if (ep == 0) {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK;
+ ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK);
}
} else {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK;
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK;
+ ep_tx_ctrl_set(ep, USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK);
}
}
}
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index 11734de37..ea3b052ad 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -24,7 +24,6 @@
*
* This file is part of the TinyUSB stack.
*/
-
#include "tusb_option.h"
#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \
@@ -37,138 +36,182 @@
#define EP_MAX 16
typedef struct {
- uint8_t* buffer;
+ uint8_t *buffer;
uint16_t total_len;
uint16_t queued_len;
uint16_t max_size;
- bool is_last_packet;
- bool is_iso;
+ bool is_iso;
+ bool valid;
} xfer_ctl_t;
-typedef enum {
- EP_RESPONSE_ACK,
- EP_RESPONSE_NAK,
-} ep_response_list_t;
-
-#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
+ #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir]
static xfer_ctl_t xfer_status[EP_MAX][2];
-#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
-#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
-#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
-#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
+ #define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2)
+ #define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4)
+ #define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4)
+ #define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2)
-#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
-#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
+ #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1))
+ #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1))
/* Endpoint Buffer */
TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE];
+static bool ep0_tog;
+static bool ep_data_tog[EP_MAX][2];
-static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) {
+static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) {
if (ep_dir == TUSB_DIR_IN) {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (EP_TX_LEN(ep_num) == 0) {
- EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1;
- } else {
- EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1;
- }
- }
- }
- if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG;
- } else {
- EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response;
- }
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_TOG_MASK)) |
+ (data1 ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
- if (ep_num == 0) {
- if (response_type == EP_RESPONSE_ACK) {
- if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) {
- EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1;
- }
- } else {
- EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1;
- }
- }
- EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_TOG_MASK)) |
+ (data1 ? USBHS_EP_R_TOG_1 : USBHS_EP_R_TOG_0);
}
}
-static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) {
- if (ep_dir == TUSB_DIR_IN) {
- uint16_t remaining = xfer->total_len - xfer->queued_len;
- uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size);
+static void queue_in_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t tx_len = TU_MIN(remaining, xfer->max_size);
+
+ if (ep_num == 0) {
+ memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], tx_len);
+ } else {
+ EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ }
+
+ EP_TX_LEN(ep_num) = tx_len;
+ xfer->queued_len += tx_len;
+
+ if (ep_num == 0) {
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_ACK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
+ ep0_tog = !ep0_tog;
+ } else if (xfer->is_iso) {
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_IN, ep_data_tog[ep_num][TUSB_DIR_IN]);
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK;
+ }
+}
+
+static void queue_out_packet(uint8_t ep_num, xfer_ctl_t* xfer) {
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t rx_len = TU_MIN(remaining, xfer->max_size);
+
+ if (ep_num > 0) {
+ EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_RX_MAX_LEN(ep_num) = rx_len;
+ }
+
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ } else if (xfer->is_iso) {
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NYET;
+ } else {
+ set_ep_toggle(ep_num, TUSB_DIR_OUT, ep_data_tog[ep_num][TUSB_DIR_OUT]);
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK;
+ }
+}
+static void update_in(uint8_t rhport, uint8_t ep_num, bool force) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN);
+ if (!xfer->valid) {
+ return;
+ }
+
+ if (!force && ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_IN] = !ep_data_tog[ep_num][TUSB_DIR_IN];
+ }
+
+ if (force || (xfer->total_len > xfer->queued_len)) {
+ queue_in_packet(ep_num, xfer);
+ } else {
+ xfer->valid = false;
if (ep_num == 0) {
- memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size);
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
} else {
- EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
+ EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
+}
- EP_TX_LEN(ep_num) = next_tx_size;
- xfer->queued_len += next_tx_size;
- if (xfer->queued_len == xfer->total_len) {
- xfer->is_last_packet = true;
- }
- if (xfer->is_iso == true) {
- /* Enable EP to generate ISA_ACT interrupt */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
- }
- } else { /* TUSB_DIR_OUT */
- uint16_t left_to_receive = xfer->total_len - xfer->queued_len;
- uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive);
+static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) {
+ xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT);
+ if (!xfer->valid) {
+ return;
+ }
+
+ uint16_t remaining = xfer->total_len - xfer->queued_len;
+ uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size));
+
+ if (ep_num == 0) {
+ memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len);
+ }
- if (max_possible_rx_size == left_to_receive) {
- xfer->is_last_packet = true;
+ xfer->queued_len += len;
+
+ if (ep_num != 0 && !xfer->is_iso) {
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = !ep_data_tog[ep_num][TUSB_DIR_OUT];
+ }
+
+ if ((xfer->queued_len == xfer->total_len) || (len < xfer->max_size)) {
+ xfer->valid = false;
+ if (ep_num == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NAK;
}
+ dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true);
+ }
- if (ep_num > 0) {
- EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len];
- EP_RX_MAX_LEN(ep_num) = max_possible_rx_size;
+ if (ep_num != 0) {
+ if (xfer->valid) {
+ queue_out_packet(ep_num, xfer);
+ } else {
+ uint8_t rx_res = xfer->is_iso ? USBHS_EP_R_RES_NYET : USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | rx_res;
}
}
- ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK);
}
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
+bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rhport;
+ (void)rh_init;
memset(&xfer_status, 0, sizeof(xfer_status));
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
USBHSD->HOST_CTRL = 0x00;
USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM;
USBHSD->CONTROL = 0;
-#if TUD_OPT_HIGH_SPEED
+ #if TUD_OPT_HIGH_SPEED
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED;
-#else
- #error OPT_MODE_FULL_SPEED not currently supported on CH32
+ #else
+ #error OPT_MODE_FULL_SPEED not currently supported on CH32
USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED;
-#endif
+ #endif
USBHSD->INT_EN = 0;
- USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN;
+ USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN;
USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
- USBHSD->ENDP_TYPE = 0x00;
- USBHSD->BUF_MODE = 0x00;
+ USBHSD->ENDP_TYPE = 0x00;
+ USBHSD->BUF_MODE = 0x00;
for (int ep = 0; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
- USBHSD->UEP0_DMA = (uint32_t) ep0_buffer;
- USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
+ USBHSD->UEP0_DMA = (uint32_t)ep0_buffer;
+ USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE;
xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE;
- xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
+ xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE;
USBHSD->DEV_AD = 0;
USBHSD->CONTROL |= USBHS_DEV_PU_EN;
@@ -177,22 +220,24 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
}
void dcd_int_enable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_EnableIRQ(USBHS_IRQn);
}
void dcd_int_disable(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
NVIC_DisableIRQ(USBHS_IRQn);
}
void dcd_edpt_close_all(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
+
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
for (size_t ep = 1; ep < EP_MAX; ep++) {
- EP_TX_LEN(ep) = 0;
- EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK;
- EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_LEN(ep) = 0;
+ EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep) = 0;
}
@@ -201,18 +246,18 @@ void dcd_edpt_close_all(uint8_t rhport) {
}
void dcd_set_address(uint8_t rhport, uint8_t dev_addr) {
- (void) dev_addr;
+ (void)dev_addr;
// Response with zlp status
dcd_edpt_xfer(rhport, 0x80, NULL, 0, false);
}
void dcd_remote_wakeup(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
}
void dcd_sof_enable(uint8_t rhport, bool en) {
- (void) rhport;
+ (void)rhport;
if (en) {
USBHSD->INT_EN |= USBHS_SOF_ACT_EN;
} else {
@@ -220,24 +265,19 @@ void dcd_sof_enable(uint8_t rhport, bool en) {
}
}
-void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) {
- (void) rhport;
-
+void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) {
+ (void)rhport;
if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE &&
- request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD &&
- request->bRequest == TUSB_REQ_SET_ADDRESS) {
- USBHSD->DEV_AD = (uint8_t) request->wValue;
+ request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) {
+ USBHSD->DEV_AD = (uint8_t)request->wValue;
}
-
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
}
-bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
- (void) rhport;
+bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) {
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
- tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
+ const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress);
+ const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress);
TU_ASSERT(ep_num < EP_MAX);
@@ -245,13 +285,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
return true;
}
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->max_size = tu_edpt_packet_size(desc_edpt);
+ ep_data_tog[ep_num][dir] = false;
xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS);
if (dir == TUSB_DIR_OUT) {
USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num);
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num);
}
@@ -259,31 +300,31 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) {
} else {
if (xfer->is_iso == true) {
USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num);
- } else {
- /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
}
- EP_TX_LEN(ep_num) = 0;
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num);
+ EP_TX_LEN(ep_num) = 0;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
}
return true;
}
void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
EP_RX_MAX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num);
- } else { // TUSB_DIR_IN
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
- EP_TX_LEN(ep_num) = 0;
+ } else { // TUSB_DIR_IN
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ EP_TX_LEN(ep_num) = 0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num);
USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
}
@@ -305,128 +346,120 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep)
#endif
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL;
} else {
- EP_TX_LEN(0) = 0;
+ EP_TX_LEN(ep_num) = 0;
EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL;
}
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) {
- (void) rhport;
+ (void)rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_OUT] = false;
} else {
- EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ ep_data_tog[ep_num][TUSB_DIR_IN] = false;
}
}
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) {
- (void) is_isr;
- (void) rhport;
- uint8_t const ep_num = tu_edpt_number(ep_addr);
- tusb_dir_t const dir = tu_edpt_dir(ep_addr);
+bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
+ (void)is_isr;
+ (void)rhport;
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const tusb_dir_t dir = tu_edpt_dir(ep_addr);
+
+ xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir);
+ xfer->buffer = buffer;
+ xfer->total_len = total_bytes;
+ xfer->queued_len = 0;
+ xfer->valid = true;
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir);
- xfer->buffer = buffer;
- xfer->total_len = total_bytes;
- xfer->queued_len = 0;
- xfer->is_last_packet = false;
+ if (ep_num == 0 && dir == TUSB_DIR_OUT) {
+ if (total_bytes == 0) {
+ EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_TOG_MASK)) | USBHS_EP_R_TOG_1;
+ } else {
+ EP_RX_CTRL(0) ^= USBHS_EP_R_TOG_1;
+ }
+ }
- xfer_data_packet(ep_num, dir, xfer);
+ if (dir == TUSB_DIR_IN) {
+ update_in(rhport, ep_num, true);
+ } else {
+ queue_out_packet(ep_num, xfer);
+ }
return true;
}
void dcd_int_handler(uint8_t rhport) {
- (void) rhport;
+ (void)rhport;
- uint8_t int_flag = USBHSD->INT_FG;
+ uint8_t int_flag = USBHSD->INT_FG;
uint8_t int_status = USBHSD->INT_ST;
- if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) {
- uint8_t const token = int_status & MASK_UIS_TOKEN;
+ if (int_flag & USBHS_TRANSFER_FLAG) {
+ const uint8_t token = int_status & MASK_UIS_TOKEN;
+ const uint8_t ep_num = int_status & MASK_UIS_ENDP;
+ const uint16_t len = USBHSD->RX_LEN;
if (token == USBHS_TOKEN_PID_SOF) {
uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK;
dcd_event_sof(rhport, frame_count, true);
- }else {
- uint8_t const ep_num = int_status & MASK_UIS_ENDP;
- tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT;
- uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir);
- xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir);
-
- if (token == USBHS_TOKEN_PID_OUT) {
- uint16_t rx_len = USBHSD->RX_LEN;
-
- if (ep_num == 0) {
- memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len);
- }
-
- xfer->queued_len += rx_len;
- if (rx_len < xfer->max_size) {
- xfer->is_last_packet = true;
- }
- } else if (token == USBHS_TOKEN_PID_IN) {
- if (xfer->is_iso && xfer->is_last_packet) {
- /* Disable EP to avoid ISO_ACT interrupt generation */
- USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num);
- } else {
- // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet
- }
- }
-
- if (xfer->is_last_packet == true) {
- ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK);
- dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true);
- } else {
- /* prepare next part of packet to xref */
- xfer_data_packet(ep_num, ep_dir, xfer);
- }
+ } else if (token == USBHS_TOKEN_PID_OUT) {
+ update_out(rhport, ep_num, len);
+ } else if (token == USBHS_TOKEN_PID_IN) {
+ update_in(rhport, ep_num, false);
}
-
- USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */
+ USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */
} else if (int_flag & USBHS_SETUP_FLAG) {
- ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK);
- ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK);
+ tusb_control_request_t const* setup =
+ (tusb_control_request_t const*) ep0_buffer;
+ ep0_tog = true;
+ EP_RX_CTRL(0) = (setup->wLength == 0) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK;
+
dcd_event_setup_received(0, ep0_buffer, true);
USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */
} else if (int_flag & USBHS_BUS_RST_FLAG) {
// TODO CH32 does not detect actual speed at this time (should be known at end of reset)
// This interrupt probably triggered at start of bus reset
-// tusb_speed_t actual_speed;
-// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
-// case USBHS_SPEED_TYPE_HIGH:
-// actual_speed = TUSB_SPEED_HIGH;
-// break;
-// case USBHS_SPEED_TYPE_FULL:
-// actual_speed = TUSB_SPEED_FULL;
-// break;
-// case USBHS_SPEED_TYPE_LOW:
-// actual_speed = TUSB_SPEED_LOW;
-// break;
-// default:
-// TU_ASSERT(0,);
-// break;
-// }
-// dcd_event_bus_reset(0, actual_speed, true);
+ // tusb_speed_t actual_speed;
+ // switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){
+ // case USBHS_SPEED_TYPE_HIGH:
+ // actual_speed = TUSB_SPEED_HIGH;
+ // break;
+ // case USBHS_SPEED_TYPE_FULL:
+ // actual_speed = TUSB_SPEED_FULL;
+ // break;
+ // case USBHS_SPEED_TYPE_LOW:
+ // actual_speed = TUSB_SPEED_LOW;
+ // break;
+ // default:
+ // TU_ASSERT(0,);
+ // break;
+ // }
+ // dcd_event_bus_reset(0, actual_speed, true);
dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true);
USBHSD->DEV_AD = 0;
- EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
- EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
+ memset(ep_data_tog, 0, sizeof(ep_data_tog));
+ ep0_tog = true;
+ EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0;
+ EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0;
USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */
} else if (int_flag & USBHS_SUSPEND_FLAG) {
@@ -434,6 +467,9 @@ void dcd_int_handler(uint8_t rhport) {
dcd_event_handler(&event, true);
USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */
+ } else {
+ // Unhandled interrupt
+ USBHSD->INT_FG = int_flag; /* Clear all flags */
}
}
#endif