summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-30 12:19:20 +0700
committerhathach <[email protected]>2026-03-30 13:02:17 +0700
commit31ec5a7d3afdb79c95cfc6eeaf9659066d83fd69 (patch)
treee64699716a590f067b615c6030bee1800a2e2344
parentdfcd271400c31e15a7d38b319c65173c3e4227c7 (diff)
hcd rp2 add double buffered for control endpoint transfers.
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c42
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c34
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h7
3 files changed, 39 insertions, 44 deletions
diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
index 31bbfc0be..3a99e7275 100644
--- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c
+++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c
@@ -55,12 +55,6 @@
static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS];
static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint
-// 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
-};
enum {
SIE_CTRL_SPEED_DISCONNECT = 0,
@@ -187,7 +181,7 @@ static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) {
io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl;
epx_ctrl_prepare(ep);
- rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT);
+ rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx, ep->transfer_type == TUSB_XFER_INTERRUPT);
usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB));
sie_start_xfer(false, is_rx, ep->need_pre); // start transfer
@@ -340,26 +334,20 @@ static void __tusb_irq_path_func(hcd_rp2040_irq)(void) {
// RP2040: on SOF, stop and switch if there's a pending ep
if (status & USB_INTS_HOST_SOF_BITS) {
(void)usb_hw->sof_rd; // clear SOF by reading SOF_RD
- if (epx->active && tu_edpt_number(epx->ep_addr) != 0) {
- hw_endpoint_t *next_ep = epx_next_pending(epx);
- if (next_ep) {
+ hw_endpoint_t *next_ep = epx_next_pending(epx);
+ if (next_ep == NULL) {
+ // no more pending --> disable SOF
+ usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
+ usb_hw->nak_poll = USB_NAK_POLL_RESET;
+ } else {
+ // stop transfer if is active
+ if (epx->active) {
usb_hw_set->sie_ctrl = USB_SIE_CTRL_STOP_TRANS_BITS;
while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {}
- busy_wait_at_least_cycles(12);
- if (usb_hw->buf_status & 1u) {
- usb_hw->nak_poll = USB_NAK_POLL_RESET;
- handle_buf_status_isr();
- } else {
- epx_switch_ep(next_ep);
- }
- } else {
- usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
- usb_hw->nak_poll = USB_NAK_POLL_RESET;
}
- } else if (!epx_next_pending(epx)) {
- // EPX is on control endpoint or inactive — disable SOF if nothing pending
- usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS;
- usb_hw->nak_poll = USB_NAK_POLL_RESET;
+
+ epx_save_context();
+ epx_switch_ep(next_ep);
}
}
#endif
@@ -602,10 +590,8 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b
usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS;
#else
// Only enable SOF round-robin for non-control endpoints
- if (tu_edpt_number(epx->ep_addr) != 0) {
- 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;
- }
+ 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 {
const uint8_t ep_num = tu_edpt_number(ep->ep_addr);
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index 49c948af6..ffb5fcbc6 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.c
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c
@@ -283,7 +283,7 @@ void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, u
const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN));
#if CFG_TUH_ENABLED
- const bool force_single = (is_host && (is_rx || ep->transfer_type == TUSB_XFER_INTERRUPT));
+ const bool force_single = (is_host && ep->transfer_type == TUSB_XFER_INTERRUPT);
#else
const bool force_single = false;
#endif
@@ -339,23 +339,17 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_
return false;
}
- const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
const bool is_host = rp2usb_is_host_mode();
-
- #if CFG_TUSB_RP2_ERRATA_E4
- const bool need_e4_fix = (is_host && !is_double);
- #endif
+ const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS));
// 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 that controller can execute an extra packet after writing to BUF1
- // since it leave BUF0 intact, which can be poll before buf_status interrupt is trigger.
- // Workaround for the side effect, we will enable double-buffered for rx but only prepare 1 buf at a time.
+ // 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 (!need_e4_fix) // incorrect buf_id, buffer pointer is still buf0
+ if (!(is_host && !is_double)) // incorrect buf_id, buffer data is still buf0
#endif
{
dpram_buf += 64; // buf1 offset
@@ -368,16 +362,24 @@ bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_
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 || (buf_ctrl16 & USB_BUF_CTRL_LAST);
+ const bool is_done = is_short || is_last;
- // Short packet on rx with double buffer: abort the other half (if not last) and reset double-buffer state.
+ // 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.
- // After abort, zero buf_ctrl.
- // Note: Host mode we cannot save next transfer data due to shared epx --> force single
+ // 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) {
#if CFG_TUH_ENABLED
- if (is_host) {}
+ if (is_host) {
+ // stop current transfer
+ uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK;
+ sie_ctrl |= USB_SIE_CTRL_STOP_TRANS_BITS;
+ usb_hw->sie_ctrl = sie_ctrl;
+ // maybe wait until STOP_TRANS bit is clear
+
+ *buf_reg = 0; // reset buffer control
+ }
#endif
#if CFG_TUD_ENABLED
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h
index c4c7e625e..2ba9d018e 100644
--- a/src/portable/raspberrypi/rp2040/rp2040_usb.h
+++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h
@@ -70,6 +70,13 @@
#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
+};
+
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+