summaryrefslogtreecommitdiff
path: root/src/portable
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-18 15:55:55 +0700
committerhathach <[email protected]>2026-06-18 15:55:55 +0700
commit4498f65c460874ae64306d01638f52285394efc5 (patch)
treeb8c36311a2cd5c41d0583ca9b3be92b1f870b89e /src/portable
parent072c02e3684e886a93681ecb37f830be5b8c6b53 (diff)
parent941d63e39a529593ee86caf7ea85e04839680d59 (diff)
Merge remote-tracking branch 'origin/master' into pr-3618
Diffstat (limited to 'src/portable')
-rw-r--r--src/portable/mentor/musb/dcd_musb.c367
-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/st/stm32_fsdev/dcd_stm32_fsdev.c3
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_stm32.h39
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c135
-rw-r--r--src/portable/synopsys/dwc2/hcd_dwc2.c51
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h140
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c95
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c2
10 files changed, 565 insertions, 271 deletions
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index 56429ac1f..1d1280bf4 100644
--- a/src/portable/mentor/musb/dcd_musb.c
+++ b/src/portable/mentor/musb/dcd_musb.c
@@ -82,25 +82,88 @@ typedef struct {
enum {
PIPE0_STATE_IDLE = 0, // no active control transfer
- PIPE0_STATE_DATA, // DATA stage (IN or OUT — direction implied by CSR/dir)
+ 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, // one of {edpt0_xfer(STATUS OUT), confirmation IRQ} has happened; the other fires xfer_complete
+ 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 {
- struct {
- uint8_t *buf; // DATA OUT drain target (only valid while EP0 is in DATA OUT stage)
- uint16_t xact_len; // chunk length most recently armed via edpt0_xfer; reported in xfer_complete
- 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
- } pipe0;
+ 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;
@@ -238,7 +301,7 @@ static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum
// Called from the TX interrupt. If the last queued packet finished the transfer,
// signal completion; otherwise queue the next packet.
-static void process_epin(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) {
+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) {
@@ -268,7 +331,7 @@ static void process_epin(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum)
// 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()
+ 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);
@@ -287,7 +350,7 @@ static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum)
return (xact_len < mps);
}
-static void process_epout(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, bool is_isr) {
+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);
if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) {
ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER);
@@ -323,7 +386,7 @@ static void process_epout(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum,
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 unsigned dir_in = tu_edpt_dir(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) {
@@ -342,13 +405,13 @@ static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t
if (dir_in) {
pipe_write(musb_regs, pipe, epnum);
} else {
- // Re-enable Rx interrupt (may have been masked by the no-buffer path in process_epout)
+ // 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() fires dcd_event_xfer_complete() itself if the drain completes.
+ // process_epout_isr() fires dcd_event_xfer_complete() itself if the drain completes.
if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) {
- process_epout(rhport, musb_regs, epnum, is_isr);
+ process_epout_isr(rhport, musb_regs, epnum, is_isr);
}
}
return true;
@@ -358,44 +421,54 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_
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);
+ pipe0_state_t* pipe0 = &_dcd.pipe0;
const unsigned dir_in = tu_edpt_dir(ep_addr);
- switch (_dcd.pipe0.state) {
- case PIPE0_STATE_DATA: {
- _dcd.pipe0.xact_len = total_bytes;
- if (dir_in) {
- // DATA IN: load FIFO, set TXRDY. Add DATAEND on the last chunk
- // (ep0_remain_datalen == 0 after this load) to end the data stage.
- tu_hwfifo_write(&musb_regs->fifo[0], buffer, total_bytes, NULL);
- _dcd.pipe0.remain_wlength -= total_bytes;
- if (_dcd.pipe0.remain_wlength == 0) {
- ep_csr->csr0l = MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND;
- } else {
- ep_csr->csr0l = MUSB_CSRL0_TXRDY;
- }
- } else {
- // DATA OUT: arm drain target, ack RXRDY so host can send DATA OUT.
- _dcd.pipe0.buf = buffer;
+ 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;
}
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.
- _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT_PENDING;
+ pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_XFER;
break;
- case PIPE0_STATE_STATUS_OUT_PENDING:
- // Second event — IRQ already arrived, fire complete now.
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
+ 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;
@@ -404,15 +477,65 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_
return true;
}
+// 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(uint8_t rhport) {
+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;
+ // 21.1.5: SentStall and SetupEnd must be checked before anything else.
if (csrl & MUSB_CSRL0_STALLED) {
ep_csr->csr0l = 0;
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
+ pipe0->state = PIPE0_STATE_IDLE;
+ pipe0->deferred_setup_valid = false;
+ pipe0->rxrdy_consumed = false;
return;
}
@@ -420,7 +543,9 @@ static void process_ep0(uint8_t rhport) {
// 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;
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
+ 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 */
}
@@ -428,105 +553,87 @@ static void process_ep0(uint8_t rhport) {
// Receive Data (Setup or OUT)
if (csrl & MUSB_CSRL0_RXRDY) {
- const uint16_t count0 = ep_csr->count0;
- switch (_dcd.pipe0.state) {
- case PIPE0_STATE_IDLE:
- TU_ASSERT(sizeof(tusb_control_request_t) == count0, );
- union {
- tusb_control_request_t req;
- uint32_t u32[2];
- } setup_packet;
- setup_packet.u32[0] = musb_regs->fifo[0];
- setup_packet.u32[1] = musb_regs->fifo[0];
-
- _dcd.pipe0.remain_wlength = setup_packet.req.wLength;
-
- if (setup_packet.req.wLength == 0) {
- _dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
- } else {
- _dcd.pipe0.state = PIPE0_STATE_DATA;
- // If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data.
- if (setup_packet.req.bmRequestType & TUSB_DIR_IN_MASK) {
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
- }
- }
- dcd_event_setup_received(rhport, (const uint8_t *)&setup_packet.req, true);
+ if (pipe0->rxrdy_consumed) {
+ return; // stale latched IRQ: this RXRDY's packet was already drained
+ }
+ 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;
+ }
- case PIPE0_STATE_DATA: {
+ 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_hwfifo_read(&musb_regs->fifo[0], _dcd.pipe0.buf, count0, NULL);
- _dcd.pipe0.remain_wlength -= 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
}
- if (_dcd.pipe0.remain_wlength == 0) {
- // last packet: change state and leave RXRDY for edpt0_xfer(STATUS IN) to ack
- _dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
- } else {
- ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+ // 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;
}
+ // 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;
+
default: break;
}
return;
}
+ 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;
+ }
+
/* 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 */
- switch (_dcd.pipe0.state) {
- case PIPE0_STATE_DATA:
- // csrl == 0 in DATA state = TXRDY just cleared, i.e. a DATA IN packet was successfully sent. If the just-sent
- // packet was the last (DATAEND was set when ep0_remain_datalen hit zero), transition
- // to STATUS_OUT to await the host's STATUS-OUT ZLP confirmation IRQ.
- if (_dcd.pipe0.remain_wlength == 0) {
- _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT;
- }
- dcd_event_xfer_complete(rhport, TU_EP0_IN, _dcd.pipe0.xact_len, XFER_RESULT_SUCCESS, true);
- break;
-
- case PIPE0_STATE_STATUS_OUT:
- // First event of the STATUS OUT pair — wait for edpt0_xfer(STATUS OUT) to fire complete.
- _dcd.pipe0.state = PIPE0_STATE_STATUS_OUT_PENDING;
- break;
-
- case PIPE0_STATE_STATUS_OUT_PENDING:
- // Second event — edpt0_xfer(STATUS OUT) already called, fire complete now.
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
- dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true);
- break;
-
- case PIPE0_STATE_STATUS_IN:
- if (_dcd.pipe0.pending_addr) {
- musb_regs->faddr = _dcd.pipe0.pending_addr;
- _dcd.pipe0.pending_addr = 0;
- }
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
- dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true);
- break;
-
- default: break;
- }
+ 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
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.xact_len = 0;
- _dcd.pipe0.remain_wlength = 0;
+ 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;
@@ -589,19 +696,21 @@ void dcd_int_disable(uint8_t rhport) {
}
// Receive Set Address request. Stash the new address here; hardware faddr is
-// latched from pending_addr in process_ep0 once the STATUS IN completes (per
+// 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)
{
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
- _dcd.pipe0.pending_addr = dev_addr;
- _dcd.pipe0.buf = NULL;
- _dcd.pipe0.xact_len = 0;
- _dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
+ 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
@@ -646,7 +755,7 @@ void dcd_sof_enable(uint8_t rhport, bool en)
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 epdir = 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 = pipe_get(epn, epdir);
@@ -689,7 +798,7 @@ 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);
@@ -801,10 +910,20 @@ 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 == TU_EP0_OUT) { /* Ignore EP0 OUT */
- _dcd.pipe0.state = PIPE0_STATE_IDLE;
- _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 tusb_dir_t ep_dir = tu_edpt_dir(ep_addr);
@@ -856,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);
@@ -870,9 +989,9 @@ void dcd_int_handler(uint8_t rhport) {
while (intr_tx) {
const unsigned epnum = __builtin_ctz(intr_tx);
if (epnum == 0) {
- process_ep0(rhport); // EP0 has its own state machine (control transfers)
+ process_ep0_isr(rhport); // EP0 has its own state machine (control transfers)
} else {
- process_epin(rhport, musb_regs, epnum);
+ process_epin_isr(rhport, musb_regs, epnum);
}
intr_tx &= ~TU_BIT(epnum);
@@ -887,7 +1006,7 @@ void dcd_int_handler(uint8_t rhport) {
intr_rx &= musb_regs->intr_rxen; /* Clear disabled interrupts */
while (intr_rx) {
unsigned const epnum = __builtin_ctz(intr_rx);
- process_epout(rhport, musb_regs, epnum, true);
+ 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
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/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c
index 41da3ddd0..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
@@ -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);
}
diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h
index 070aa00ec..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
@@ -262,30 +266,45 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) {
*
* CTR may trigger before final PMA SRAM accesses complete on OUT transfers.
* Insert delay before reading PMA count/data.
- * Max CPU frequency in MHz, used to derive conservative FSDEV PMA delay defaults.
+ * Max CPU frequency in Hz, used to derive conservative FSDEV PMA delay defaults.
*/
#if CFG_TUSB_MCU == OPT_MCU_STM32H5
- #define FSDEV_STM32_CPU_MHZ 250U
+ #define FSDEV_STM32_CPU_HZ 250000000U
#elif CFG_TUSB_MCU == OPT_MCU_STM32U5
- #define FSDEV_STM32_CPU_MHZ 160U
+ #define FSDEV_STM32_CPU_HZ 160000000U
#elif CFG_TUSB_MCU == OPT_MCU_STM32U3
- #define FSDEV_STM32_CPU_MHZ 96U
+ #define FSDEV_STM32_CPU_HZ 96000000U
#elif CFG_TUSB_MCU == OPT_MCU_STM32U0
- #define FSDEV_STM32_CPU_MHZ 56U
+ #define FSDEV_STM32_CPU_HZ 56000000U
#elif CFG_TUSB_MCU == OPT_MCU_STM32G0
- #define FSDEV_STM32_CPU_MHZ 64U
+ #define FSDEV_STM32_CPU_HZ 64000000U
#elif CFG_TUSB_MCU == OPT_MCU_STM32C0
- #define FSDEV_STM32_CPU_MHZ 48U
+ #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_MHZ / 4U)
+ #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_MHZ * 2U)
+ #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) {
diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 1d0ef45d2..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;
}
@@ -793,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) {
@@ -883,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
@@ -935,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
}
}
@@ -1008,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;
}
@@ -1035,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);
}
@@ -1056,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);
}
}
@@ -1099,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
}
@@ -1207,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;
@@ -1220,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) {
@@ -1234,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/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c
index 9ea5f33c5..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 {
@@ -1137,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;
@@ -1203,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);
@@ -1228,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) {
@@ -1251,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) {
@@ -1271,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 af0f17785..dae31da91 100644
--- a/src/portable/wch/dcd_ch32_usbfs.c
+++ b/src/portable/wch/dcd_ch32_usbfs.c
@@ -40,6 +40,52 @@
#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;
@@ -81,19 +127,19 @@ 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);
+ 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;
if (ep == 0) {
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 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_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK;
+ 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);
}
@@ -119,11 +165,11 @@ 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_NAK;
+ 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_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res;
+ ep_rx_set_response(ep, rx_res);
}
}
}
@@ -132,8 +178,8 @@ 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(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET;
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET;
+ 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];
}
@@ -152,8 +198,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
// 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_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;
@@ -188,11 +234,12 @@ void dcd_int_handler(uint8_t rhport) {
case PID_SETUP:
// setup clears stall
- EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK;
+ 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];
- EP_RX_CTRL(0) = (setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK;
+ // 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);
dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true);
break;
@@ -210,7 +257,7 @@ void dcd_int_handler(uint8_t rhport) {
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();
@@ -277,9 +324,9 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
if (ep != 0) {
if (dir == TUSB_DIR_OUT) {
- EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_T_RES_NAK;
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- 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;
@@ -326,7 +373,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to
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_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | rx_res;
+ ep_rx_set_response(ep, rx_res);
}
return true;
}
@@ -337,16 +384,16 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t 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_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);
}
}
}
@@ -357,13 +404,13 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t 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) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK;
+ ep_rx_ctrl_set(ep, USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK);
} else {
- 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);
}
}
}
diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c
index a6dd5bb79..ea3b052ad 100644
--- a/src/portable/wch/dcd_ch32_usbhs.c
+++ b/src/portable/wch/dcd_ch32_usbhs.c
@@ -354,7 +354,7 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t 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;
}
}