diff options
Diffstat (limited to 'src/portable/raspberrypi')
| -rw-r--r-- | src/portable/raspberrypi/pio_usb/dcd_pio_usb.c | 21 | ||||
| -rw-r--r-- | src/portable/raspberrypi/pio_usb/hcd_pio_usb.c | 11 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/dcd_rp2040.c | 442 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/hcd_rp2040.c | 921 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.c | 512 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.h | 218 |
6 files changed, 1179 insertions, 946 deletions
diff --git a/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c b/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c index 60afbd435..78df2b6a6 100644 --- a/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c +++ b/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c @@ -76,7 +76,7 @@ void dcd_set_address (uint8_t rhport, uint8_t dev_addr) { // must be called before queuing status pio_usb_device_set_address(dev_addr); - dcd_edpt_xfer(rhport, 0x80, NULL, 0); + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); } // Wake up host @@ -113,16 +113,30 @@ void dcd_edpt_close_all (uint8_t rhport) (void) rhport; } +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + (void)ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} + // 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 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; endpoint_t *ep = pio_usb_device_get_endpoint_by_address(ep_addr); return pio_usb_ll_transfer_start(ep, buffer, total_bytes); } // Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack - 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 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) ep_addr; @@ -207,5 +221,4 @@ void __no_inline_not_in_flash_func(pio_usb_device_irq_handler)(uint8_t root_id) // clear all rport->ints &= ~ints; } - #endif diff --git a/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c b/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c index d59a2b4ee..90eb920e0 100644 --- a/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c +++ b/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c @@ -29,9 +29,20 @@ #if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && CFG_TUH_RPI_PIO_USB #include "pico.h" + #include "pio_usb.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + #include "pio_usb_ll.h" +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index a89c2f42b..2b6bbc43b 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -39,134 +39,117 @@ #include "device/dcd.h" // Current implementation force vbus detection as always present, causing device think it is always plugged into host. -// Therefore it cannot detect disconnect event, mistaken it as suspend. +// Therefore, it cannot detect disconnect event, mistaken it as suspend. // Note: won't work if change to 0 (for now) -#define FORCE_VBUS_DETECT 1 + #define FORCE_VBUS_DETECT 1 + + #define USB_INTS_ERROR_BITS \ + (USB_INTS_ERROR_DATA_SEQ_BITS | USB_INTS_ERROR_BIT_STUFF_BITS | USB_INTS_ERROR_CRC_BITS | \ + USB_INTS_ERROR_RX_OVERFLOW_BITS | USB_INTS_ERROR_RX_TIMEOUT_BITS) /*------------------------------------------------------------------*/ /* Low level controller *------------------------------------------------------------------*/ -// Init these in dcd_init -static uint8_t* next_buffer_ptr; +// HW buffer pointer from USB buffer space (max 3840 bytes) +static uint8_t *hw_buffer_ptr; // USB_MAX_ENDPOINTS Endpoints, direction TUSB_DIR_OUT for out and TUSB_DIR_IN for in. static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; -// SOF may be used by remote wakeup as RESUME, this indicate whether SOF is actually used by usbd +// SOF may be used by remote wakeup as RESUME, this indicates whether SOF is actually used by usbd static bool _sof_enable = false; -TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_num(uint8_t num, tusb_dir_t dir) { - return &hw_endpoints[num][dir]; +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get(uint8_t epnum, tusb_dir_t dir) { + return &hw_endpoints[epnum][dir]; } -TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_addr(uint8_t ep_addr) { - uint8_t num = tu_edpt_number(ep_addr); - tusb_dir_t dir = tu_edpt_dir(ep_addr); - return hw_endpoint_get_by_num(num, dir); +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8_t ep_addr) { + const uint8_t num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + return hw_endpoint_get(num, dir); } -// Allocate from the USB buffer space (max 3840 bytes) -static void hw_endpoint_alloc(struct hw_endpoint* ep, size_t size) { - // round up size to multiple of 64 - size = tu_round_up(ep->wMaxPacketSize, 64); - - // double buffered Bulk endpoint - if (ep->transfer_type == TUSB_XFER_BULK) { - size *= 2u; +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; } - - // assign buffer - ep->hw_data_buf = next_buffer_ptr; - next_buffer_ptr += size; - - hard_assert(next_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); + 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; } -// Enable endpoint -TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_enable(struct hw_endpoint* ep) { - uint32_t const reg = EP_CTRL_ENABLE_BITS | ((uint) ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->hw_data_buf); - *ep->endpoint_control = reg; +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; } -// main processing for dcd_edpt_iso_activate -static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - - const uint8_t num = tu_edpt_number(ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep_addr); +// 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); - ep->ep_addr = 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; - // For device, IN is a tx transfer and OUT is an rx transfer - ep->rx = (dir == TUSB_DIR_OUT); - - ep->next_pid = 0u; - ep->wMaxPacketSize = wMaxPacketSize; - ep->transfer_type = transfer_type; + // Clear existing buffer control state + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; - // Every endpoint has a buffer control register in dpram - if (dir == TUSB_DIR_IN) { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].in; + // allocated hw buffer + if (epnum == 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 { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].out; - } + uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB); - // Clear existing buffer control state - *ep->buffer_control = 0; + // round up size to multiple of 64 + uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64); - if (num == 0) { - // EP0 has no endpoint control register because the buffer offsets are fixed - ep->endpoint_control = NULL; + // double buffered Bulk endpoint + if (transfer_type == TUSB_XFER_BULK) { + size *= 2u; + #if CFG_TUSB_RP2_ERRATA_E15 + if (dir == TUSB_DIR_IN) { + ep->e15_bulk_in = true; + } + #endif + } - // Buffer offset is fixed (also double buffered) - ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0]; - } else { - // Set the endpoint control register (starts at EP1, hence num-1) - if (dir == TUSB_DIR_IN) { - ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].in; - } else { - ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].out; + // assign buffer + ep->dpram_buf = hw_buffer_ptr; + hw_buffer_ptr += size; + + ep_ctrl |= hw_data_offset(ep->dpram_buf); + if (ep_enabled) { + ep_ctrl |= EP_CTRL_ENABLE_BITS; } - } -} -// Init, allocate buffer and enable endpoint -static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_init(ep_addr, wMaxPacketSize, transfer_type); - const uint8_t num = tu_edpt_number(ep_addr); - if (num != 0) { - // EP0 is already enabled - hw_endpoint_alloc(ep, ep->wMaxPacketSize); - hw_endpoint_enable(ep); - } -} + *get_ep_ctrl(epnum, dir) = ep_ctrl; -static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_xfer_start(ep, buffer, total_bytes); + 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) { // Abort any pending transfer + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. - const uint8_t dir = tu_edpt_dir(ep->ep_addr); - const uint8_t epnum = tu_edpt_number(ep->ep_addr); - const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); if (rp2040_chip_version() >= 2) { usb_hw_set->abort = abort_mask; 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; - } - - _hw_endpoint_buffer_control_set_value32(ep, 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; @@ -174,29 +157,34 @@ static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { } } -static void __tusb_irq_path_func(hw_handle_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; +static void __tusb_irq_path_func(handle_hw_buff_status)(void) { + 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 - struct hw_endpoint* ep = hw_endpoint_get_by_num(i >> 1u, (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN); + // 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; - // Continue xfer - bool done = hw_endpoint_xfer_continue(ep); - if (done) { - // Notify + 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; } } @@ -204,9 +192,9 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { // If we have finished this transfer on EP0 set pid back to 1 for next // setup transfer. Also clear a stall in case for (uint8_t dir = 0; dir < 2; dir++) { - struct hw_endpoint* ep = hw_endpoint_get_by_num(0, 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 } } @@ -223,58 +211,30 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) { tu_memclr(hw_endpoints[1], sizeof(hw_endpoints) - 2 * sizeof(hw_endpoint_t)); // reclaim buffer space - next_buffer_ptr = &usb_dpram->epx_data[0]; + hw_buffer_ptr = &usb_dpram->epx_data[0]; } static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { - uint32_t const status = usb_hw->ints; - uint32_t handled = 0; + const uint32_t status = usb_hw->ints; 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_by_num(i, TUSB_DIR_IN); - - // Active Bulk IN endpoint requires SOF - if ((ep->transfer_type == TUSB_XFER_BULK) && ep->active) { - keep_sof_alive = true; + uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD - hw_endpoint_lock_update(ep, 1); - - // Deferred enable? - if (ep->pending) { - ep->pending = 0; - hw_endpoint_start_next_buffer(ep); - } - - 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 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; - hw_handle_buff_status(); + 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(); @@ -284,22 +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 ) - { + if (status & USB_INTS_DEV_CONN_DIS_BITS) { + if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) { // Connected: nothing to do - }else - { + } else { // Disconnected dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); } - usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; } #endif @@ -307,18 +313,17 @@ 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); usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; -#if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + #if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX // Only run enumeration workaround if pull up is enabled - if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) rp2040_usb_device_enumeration_fix(); -#endif + if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) { + rp2040_usb_device_enumeration_fix(); + } + #endif } /* Note from pico datasheet 4.1.2.6.4 (v1.2) @@ -330,29 +335,17 @@ 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)); - } } -#define USB_INTS_ERROR_BITS ( \ - USB_INTS_ERROR_DATA_SEQ_BITS | \ - USB_INTS_ERROR_BIT_STUFF_BITS | \ - USB_INTS_ERROR_CRC_BITS | \ - USB_INTS_ERROR_RX_OVERFLOW_BITS | \ - USB_INTS_ERROR_RX_TIMEOUT_BITS) - /*------------------------------------------------------------------*/ /* Controller API *------------------------------------------------------------------*/ @@ -366,12 +359,12 @@ 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 - rp2040_usb_init(); + rp2usb_init(); -#if FORCE_VBUS_DETECT + #if FORCE_VBUS_DETECT // Force VBUS detect so the device thinks it is plugged into a host usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; #endif @@ -380,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(); @@ -425,12 +418,11 @@ void dcd_int_disable(__unused uint8_t rhport) { irq_set_enabled(USBCTRL_IRQ, false); } -void dcd_set_address(__unused uint8_t rhport, __unused uint8_t dev_addr) { - assert(rhport == 0); - +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void)dev_addr; // Can't set device address in hardware until status xfer has complete // Send 0len complete response on EP0 IN - hw_endpoint_xfer(0x80, NULL, 0); + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); } void dcd_remote_wakeup(__unused uint8_t rhport) { @@ -463,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. @@ -475,7 +467,6 @@ void dcd_sof_enable(uint8_t rhport, bool en) { /*------------------------------------------------------------------*/ /* DCD Endpoint port *------------------------------------------------------------------*/ - void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { (void) rhport; @@ -489,74 +480,97 @@ 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; - TU_VERIFY(xfer_type != TUSB_XFER_ISOCHRONOUS); - 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; } // New API: Allocate packet buffer used by ISO endpoints // 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_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); - hw_endpoint_alloc(ep, largest_packet_size); + (void)rhport; + hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false); return true; } // New API: Configure and enable an ISO endpoint according to descriptor -bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { - (void) rhport; - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_desc->bEndpointAddress); - TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + 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->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->max_packet_size = ep_desc->wMaxPacketSize; - ep->wMaxPacketSize = ep_desc->wMaxPacketSize; - hw_endpoint_enable(ep); + // enable endpoint + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + if (ep_reg != NULL) { + *ep_reg |= EP_CTRL_ENABLE_BITS; + } return true; } void dcd_edpt_close_all(uint8_t rhport) { (void) rhport; - // may need to use EP Abort reset_non_control_endpoints(); } -bool dcd_edpt_xfer(__unused uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { - assert(rhport == 0); - hw_endpoint_xfer(ep_addr, buffer, total_bytes); +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; + 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(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; + (void)rhport; + 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); - if (tu_edpt_number(ep_addr) == 0) { + if (epnum == 0) { // A stall on EP0 has to be armed so it can be cleared on the next setup packet - usb_hw_set->ep_stall_arm = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS - : USB_EP_STALL_ARM_EP0_OUT_BITS; + usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; } - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - - // stall and clear current pending buffer - // may need to use EP_ABORT - _hw_endpoint_buffer_control_set_value32(ep, 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; - _hw_endpoint_buffer_control_clear_mask32(ep, 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 cd8c905d5..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,367 +30,399 @@ #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 "pico.h" -//--------------------------------------------------------------------+ -// INCLUDE -//--------------------------------------------------------------------+ -#include "osal/osal.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) ) return ep; +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; + } } return NULL; } -TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) -{ +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; } -TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) -{ +TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { // If this device is different to the speed of the root device // (i.e. is a low speed device on a full speed hub) then need pre 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_buff_status_bit)(uint bit, struct hw_endpoint *ep) -{ - usb_hw_clear->buf_status = bit; - // EP may have been stalled? - assert(ep->active); - 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(hw_handle_buff_status)(void) -{ - uint32_t remaining_buffers = usb_hw->buf_status; - pico_trace("buf_status 0x%08lx\n", remaining_buffers); +// 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 - uint bit = 0b1; - if ( remaining_buffers & bit ) - { - remaining_buffers &= ~bit; - struct hw_endpoint * ep = &epx; +// 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); - uint32_t ep_ctrl = *ep->endpoint_control; - if ( ep_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS ) - { - TU_LOG(3, "Double Buffered: "); - } - else - { - TU_LOG(3, "Single Buffered: "); + 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; + } + } } - TU_LOG_HEX(3, ep_ctrl); - _handle_buff_status_bit(bit, ep); - } - - // Check "interrupt" (asynchronous) endpoints for both IN and OUT - for ( uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && remaining_buffers; 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 ( remaining_buffers & bit ) - { - remaining_buffers &= ~bit; - _handle_buff_status_bit(bit, &ep_pool[i]); - } + if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) { + buf_ctrl >>= 16; + } else { + buf_ctrl = 0; } - } + } while (buf_ctrl > 0); - if ( remaining_buffers ) - { - panic("Unhandled buffer %d\n", remaining_buffers); - } -} + usbh_dpram->epx_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; - } + 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"); - hw_handle_buff_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(*epx.buffer_control), - tu_u32_high16(*epx.buffer_control)); - 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 struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type) -{ - struct hw_endpoint * 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 ) - { - // 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); - ep->buffer_control = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl; - ep->endpoint_control = &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl; - // 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)]; - } - else - { - ep = &epx; - ep->buffer_control = &usbh_dpram->epx_buf_ctrl; - ep->endpoint_control = &usbh_dpram->epx_ctrl; - ep->hw_data_buf = &usbh_dpram->epx_data[0]; + // 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->endpoint_control); - assert(ep->buffer_control); - 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); + } - // For host, IN to host == RX, anything else rx == false - ep->rx = (dir == TUSB_DIR_IN); + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; - // Response to a setup packet on EP0 starts with pid of 1 - ep->next_pid = (num == 0 ? 1u : 0u); - ep->wMaxPacketSize = wMaxPacketSize; - ep->transfer_type = transfer_type; + 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) {} - pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, ep->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)); + // 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); + } + } - // Fill in endpoint control register with buffer offset - uint32_t ep_reg = EP_CTRL_ENABLE_BITS - | EP_CTRL_INTERRUPT_PER_BUFFER - | (ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) - | dpram_offset; - if ( bmInterval ) - { - ep_reg |= (uint32_t) ((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + 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); + } } - *ep->endpoint_control = ep_reg; - pico_trace("endpoint control (0x%p) <- 0x%lx\n", ep->endpoint_control, ep_reg); - ep->configured = 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); // Reset any previous state - rp2040_usb_init(); + rp2usb_init(); // Force VBUS detect to always present, for now we assume vbus is always provided (without using VBUS En) usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; // 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 @@ -397,254 +430,278 @@ 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; - *epx.endpoint_control = 0; - *epx.buffer_control = 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(); + + 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; - // unconfigure the endpoint - ep->configured = false; - *ep->endpoint_control = 0; - *ep->buffer_control = 0; - hw_endpoint_reset_transfer(ep); + 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) -{ - (void) rhport; +uint32_t hcd_frame_number(uint8_t rhport) { + (void)rhport; return usb_hw->sof_rd; } -void hcd_int_enable(uint8_t rhport) -{ - (void) rhport; - assert(rhport == 0); +void hcd_int_enable(uint8_t rhport) { + (void)rhport; irq_set_enabled(USBCTRL_IRQ, true); } -void hcd_int_disable(uint8_t rhport) -{ - (void) rhport; +void hcd_int_disable(uint8_t rhport) { + (void)rhport; // todo we should check this is disabling from the correct core; note currently this is never called - assert(rhport == 0); irq_set_enabled(USBCTRL_IRQ, false); } //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ -bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ - (void) 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); - - // Allocated differently based on if it's an interrupt endpoint or not - struct hw_endpoint *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); - - uint8_t const 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); + 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; + } - // Control endpoint can change direction 0x00 <-> 0x80 - if ( ep_addr != ep->ep_addr ) - { - assert(ep_num == 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; - // Direction has flipped on endpoint control so re init it but with same properties - _hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, ep->transfer_type, 0); - } + #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; - // 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, buflen); + epx = ep; - // 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)); - - 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, 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; -bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) -{ - (void) rhport; + hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); + TU_ASSERT(ep); - // Copy data into setup packet buffer + rp2usb_critical_enter(); + + // 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 - struct hw_endpoint * 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; + ep->xferred_len = 0; - // Set device address - usb_hw->dev_addr_ctrl = dev_addr; + // 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; - // 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); - - // 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 9d0bd762d..83ac48ec2 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -27,224 +27,292 @@ #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 _hw_endpoint_xfer_sync(struct hw_endpoint* 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_bulkin_ep(struct hw_endpoint* ep); - static bool e15_is_critical_frame_period(struct hw_endpoint* ep); -#else - #define e15_is_bulkin_ep(x) (false) - #define e15_is_critical_frame_period(x) (false) -#endif + #if CFG_TUSB_RP2_ERRATA_E2 +static uint8_t rp2040_chipversion = 2; + #endif -// if usb hardware is in host mode -TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) { - return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; -} +critical_section_t rp2usb_lock; //--------------------------------------------------------------------+ // Implementation //--------------------------------------------------------------------+ -// Provide own byte by byte memcpy as not all copies are aligned -static void unaligned_memcpy(void *dst, const void *src, size_t n) { - uint8_t *dst_byte = (uint8_t*)dst; - const uint8_t *src_byte = (const uint8_t*)src; +// 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_byte++ = *src_byte++; + *vdst++ = *vsrc++; } } -void rp2040_usb_init(void) { + #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); +} + +void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { + (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(_hw_endpoint_buffer_control_update32)(struct hw_endpoint* ep, uint32_t and_mask, - uint32_t or_mask) { - uint32_t value = 0; - - if (and_mask) { - value = *ep->buffer_control & and_mask; +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 (or_mask) { - value |= or_mask; - if (or_mask & USB_BUF_CTRL_AVAIL) { - if (*ep->buffer_control & USB_BUF_CTRL_AVAIL) { - panic("ep %02X was already available", ep->ep_addr); - } - *ep->buffer_control = value & ~USB_BUF_CTRL_AVAIL; - // 4.1.2.5.1 Con-current access: 12 cycles (should be good for 48*12Mhz = 576Mhz) after write to buffer control - // Don't need delay in host mode as host is in charge - if (!is_host_mode()) { - busy_wait_at_least_cycles(12); - } + // 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 } - - *ep->buffer_control = value; } -// prepare buffer, return buffer control -static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep, uint8_t buf_id) { - uint16_t const buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); - ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); +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 - uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; + // 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 + } +} + +// prepare buffer, move data if tx, return buffer control +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 (!ep->rx) { - // Copy data from user buffer to hw buffer - unaligned_memcpy(ep->hw_data_buf + buf_id * 64, ep->user_buf, buflen); - ep->user_buf += buflen; + if (!is_rx) { + if (buflen) { + // 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(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) { - uint32_t ep_ctrl = *ep->endpoint_control; - +// 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 = prepare_ep_buffer(ep, 0) | USB_BUF_CTRL_SEL; - - // For now: skip double buffered for 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 - bool const is_host = is_host_mode(); - bool const force_single = (!is_host && !tu_edpt_dir(ep->ep_addr)) || - (is_host && tu_edpt_number(ep->ep_addr) != 0); + uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; - 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) + // Note: device EP0 does not have an endpoint control register + if (ep_reg != NULL) { + uint32_t ep_ctrl = *ep_reg; + #if CFG_TUH_ENABLED + const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + #else + const bool force_single = false; + #endif - buf_ctrl |= prepare_ep_buffer(ep, 1); - - // 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; - } 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; + if (ep->remaining_len && !force_single) { + // Use buffer 1 (double buffered) if there is still data + buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; + ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; + } else { + // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector + ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS; + } + *ep_reg = ep_ctrl; } - *ep->endpoint_control = 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 - _hw_endpoint_buffer_control_set_value32(ep, 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, 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->user_buf = buffer; + ep->xferred_len = 0; + ep->state = EPSTATE_ACTIVE; - if (e15_is_bulkin_ep(ep)) { - usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + if (ff != NULL) { + ep->user_fifo = ff; + ep->is_xfer_fifo = true; + } else + #endif + { + ep->user_buf = buffer; + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + ep->is_xfer_fifo = false; + #endif } - if (e15_is_critical_frame_period(ep)) { - ep->pending = 1; - } else { - hw_endpoint_start_next_buffer(ep); + 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; + + // 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)(struct hw_endpoint* ep, uint8_t buf_id) { - uint32_t buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); - 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; - uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; - - if (!ep->rx) { + if (!is_rx) { // We are continuing a transfer here. If we are TX, we have successfully // sent some data can increase the length we have sent assert(!(buf_ctrl & USB_BUF_CTRL_FULL)); - - ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); } else { // 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); - - unaligned_memcpy(ep->user_buf, ep->hw_data_buf + buf_id * 64, xferred_bytes); - ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); - ep->user_buf += xferred_bytes; + #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(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) { - pico_trace(" Short packet on buffer %d with %u bytes\r\n", buf_id, xferred_bytes); + if (xferred_bytes < ep->max_packet_size) { // Reduce total length as this is last packet ep->remaining_len = 0; } @@ -252,89 +320,127 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint* ep, uin return xferred_bytes; } -static void __tusb_irq_path_func(_hw_endpoint_xfer_sync)(struct hw_endpoint* ep) { - // Update hw endpoint struct with info from hardware - // after a buff status interrupt - - uint32_t __unused buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); - TU_LOG(3, " Sync BufCtrl: [0] = 0x%04x [1] = 0x%04x\r\n", tu_u32_low16(buf_ctrl), tu_u32_high16(buf_ctrl)); - - // always sync buffer 0 - uint16_t buf0_bytes = sync_ep_buffer(ep, 0); +// 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); - // sync buffer 1 if double buffered - if ((*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS) { - if (buf0_bytes == ep->wMaxPacketSize) { - // sync buffer 1 if not short packet - sync_ep_buffer(ep, 1); - } 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); + 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; + } - // abort queued transfer on buffer 1 - usb_hw->abort |= TU_BIT(ep_id); + const bool is_host = rp2usb_is_host_mode(); + const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); - 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); - _hw_endpoint_buffer_control_set_value32(ep, 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 - // Update EP struct from hardware state - _hw_endpoint_xfer_sync(ep); + 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 (e15_is_critical_frame_period(ep)) { - ep->pending = 1; - } else { - hw_endpoint_start_next_buffer(ep); + } + + 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 + { + // 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 frame. This avoids a situation seen with the USB2.0 hub on a Raspberry @@ -354,29 +460,19 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { volatile uint32_t e15_last_sof = 0; -// check if Errata 15 is needed for this endpoint i.e device bulk-in -static bool __tusb_irq_path_func(e15_is_bulkin_ep)(struct hw_endpoint* ep) { - return (!is_host_mode() && tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN && - ep->transfer_type == TUSB_XFER_BULK); -} - -// 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) { - TU_VERIFY(e15_is_bulkin_ep(ep)); - +// 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 d4d29a816..8ebc3e3fc 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -1,143 +1,185 @@ #ifndef RP2040_COMMON_H_ #define RP2040_COMMON_H_ -#if defined(RP2040_USB_HOST_MODE) && defined(RP2040_USB_DEVICE_MODE) -#error TinyUSB device and host mode not supported at the same time -#endif - -#include "common/tusb_common.h" - #include "pico.h" #include "hardware/structs/usb.h" #include "hardware/irq.h" #include "hardware/resets.h" #include "hardware/timer.h" -#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 +#include "pico/critical_section.h" + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#if defined(RP2040_USB_HOST_MODE) && defined(RP2040_USB_DEVICE_MODE) + #error TinyUSB device and host mode not supported at the same time +#endif + +#if defined(PICO_RP2040) && PICO_RP2040 == 1 + // 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. + #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 -#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 +#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0 #endif -#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX -#undef PICO_RP2040_USB_FAST_IRQ -#define PICO_RP2040_USB_FAST_IRQ 1 +#ifndef CFG_TUSB_RP2_ERRATA_E15 + #define CFG_TUSB_RP2_ERRATA_E15 0 +#endif + +#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 +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ #define usb_hw_set ((usb_hw_t *) hw_set_alias_untyped(usb_hw)) #define usb_hw_clear ((usb_hw_t *) hw_clear_alias_untyped(usb_hw)) #define pico_info(...) TU_LOG(2, __VA_ARGS__) #define pico_trace(...) TU_LOG(3, __VA_ARGS__) -// Hardware information per endpoint -typedef struct hw_endpoint -{ - // Is this a valid struct - bool configured; - - // Transfer direction (i.e. IN is rx for host but tx for device) - // allows us to common up transfer functions - bool rx; - - uint8_t ep_addr; - uint8_t next_pid; +enum { + EPSTATE_IDLE = 0, + EPSTATE_ACTIVE, + EPSTATE_PENDING, + EPSTATE_PENDING_SETUP +}; - // Endpoint control register - io_rw_32 *endpoint_control; - - // Buffer control register - io_rw_32 *buffer_control; - - // Buffer pointer in usb dpram - uint8_t *hw_data_buf; - - // User buffer in main memory - uint8_t *user_buf; - - // Current transfer information - uint16_t remaining_len; - uint16_t xferred_len; - - // Data needed from EP descriptor - uint16_t wMaxPacketSize; +// Hardware information per endpoint +typedef struct hw_endpoint { + uint8_t ep_addr; + uint8_t next_pid; + uint8_t state; - // Endpoint is in use - bool active; +#if CFG_TUD_EDPT_DEDICATED_HWFIFO + bool is_xfer_fifo; // transfer using fifo +#endif - // Interrupt, bulk, etc - uint8_t transfer_type; +#if CFG_TUD_ENABLED + uint8_t future_bufid; // which buffer holds next data + uint8_t future_len; // next data len +#endif - // Transfer scheduled but not active - uint8_t pending; +#if CFG_TUSB_RP2_ERRATA_E15 + bool e15_bulk_in; // Errata15 device bulk in +#endif #if CFG_TUH_ENABLED - // Only needed for host - uint8_t dev_addr; - - // If interrupt endpoint - uint8_t interrupt_num; + uint8_t dev_addr; + 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 max_packet_size; // max packet size also indicates configured + uint8_t *dpram_buf; // Buffer pointer in usb dpram + + // transfer info + union { + uint8_t *user_buf; // User buffer in main memory + tu_fifo_t *user_fifo; + }; + uint16_t remaining_len; + uint16_t xferred_len; + } 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 -void rp2040_usb_init(void); +void rp2usb_init(void); -void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, 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 usb hardware is in host mode +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_buffer_control_update32(struct hw_endpoint *ep, uint32_t and_mask, uint32_t or_mask); +extern critical_section_t rp2usb_lock; -TU_ATTR_ALWAYS_INLINE static inline uint32_t _hw_endpoint_buffer_control_get_value32 (struct hw_endpoint *ep) -{ - return *ep->buffer_control; +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) { + critical_section_enter_blocking(&rp2usb_lock); } - -TU_ATTR_ALWAYS_INLINE static inline void _hw_endpoint_buffer_control_set_value32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, 0, value); +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { + critical_section_exit(&rp2usb_lock); } -TU_ATTR_ALWAYS_INLINE static inline void _hw_endpoint_buffer_control_set_mask32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, ~value, value); -} +//--------------------------------------------------------------------+ +// Hardware Endpoint +//--------------------------------------------------------------------+ +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 _hw_endpoint_buffer_control_clear_mask32 (struct hw_endpoint *ep, uint32_t value) -{ - _hw_endpoint_buffer_control_update32(ep, ~value, 0); + +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. } -static inline uintptr_t hw_data_offset (uint8_t *buf) -{ +//--------------------------------------------------------------------+ +// 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); + +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; + return (uintptr_t)buf ^ (uintptr_t)usb_dpram; } -extern const char *ep_dir_string[]; - #endif |
