diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index d5a105a29..ab1185102 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -244,128 +244,133 @@ static void __tusb_irq_path_func(reset_non_control_endpoints)(void) static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { - uint32_t const status = usb_hw->ints; - uint32_t handled = 0; + uint32_t const status = usb_hw->ints; + uint32_t handled = 0; - if (status & USB_INTF_DEV_SOF_BITS) - { - bool keep_sof_alive = false; + if ( status & USB_INTF_DEV_SOF_BITS ) + { + bool keep_sof_alive = false; - handled |= USB_INTF_DEV_SOF_BITS; + handled |= USB_INTF_DEV_SOF_BITS; #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - last_sof = time_us_32(); + // Errata 15 Walkaround for Device Bulk-In endpoint + e15_last_sof = time_us_32(); - for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) + 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 ) { - struct hw_endpoint *ep = hw_endpoint_get_by_num(i, TUSB_DIR_IN); + keep_sof_alive = true; + hw_endpoint_lock_update(ep, 1); - // Bulk IN endpoint in a transfer? - if (rp2040_ep_needs_sof(ep) && ep->active) keep_sof_alive = true; - // Deferred enable? - if (ep->pending) + if ( ep->pending ) { - hw_endpoint_start_next_buffer(ep); 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; + // 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; - dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true); - } + dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, 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(); - } + // 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(); + } - if (status & USB_INTS_SETUP_REQ_BITS) - { - handled |= USB_INTS_SETUP_REQ_BITS; - uint8_t const *setup = (uint8_t const *)&usb_dpram->setup_packet; + if ( status & USB_INTS_SETUP_REQ_BITS ) + { + handled |= USB_INTS_SETUP_REQ_BITS; + uint8_t const * setup = (uint8_t const*) &usb_dpram->setup_packet; - // reset pid to both 1 (data and ack) - reset_ep0_pid(); + // reset pid to both 1 (data and ack) + reset_ep0_pid(); - // Pass setup packet to tiny usb - dcd_event_setup_received(0, setup, true); - usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; - } + // Pass setup packet to tiny usb + dcd_event_setup_received(0, setup, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_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) + // 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 ) { - handled |= USB_INTS_DEV_CONN_DIS_BITS; - - if ( usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS ) - { - // Connected: nothing to do - }else - { - // Disconnected - dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); - } - - usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; + // Connected: nothing to do + }else + { + // Disconnected + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); } + + usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; + } #endif - // SE0 for 2.5 us or more (will last at least 10ms) - if (status & USB_INTS_BUS_RESET_BITS) - { - pico_trace("BUS RESET\n"); + // SE0 for 2.5 us or more (will last at least 10ms) + if ( status & USB_INTS_BUS_RESET_BITS ) + { + pico_trace("BUS RESET\n"); - handled |= USB_INTS_BUS_RESET_BITS; + 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; + 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 - // Only run enumeration walk-around if pull up is enabled - if ( usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS ) rp2040_usb_device_enumeration_fix(); + // Only run enumeration walk-around if pull up is enabled + 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) - * If you enable the suspend interrupt, it is likely you will see a suspend interrupt when - * the device is first connected but the bus is idle. The bus can be idle for a few ms before - * the host begins sending start of frame packets. You will also see a suspend interrupt - * when the device is disconnected if you do not have a VBUS detect circuit connected. This is - * because without VBUS detection, it is impossible to tell the difference between - * 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; - } + /* Note from pico datasheet 4.1.2.6.4 (v1.2) + * If you enable the suspend interrupt, it is likely you will see a suspend interrupt when + * the device is first connected but the bus is idle. The bus can be idle for a few ms before + * the host begins sending start of frame packets. You will also see a suspend interrupt + * when the device is disconnected if you do not have a VBUS detect circuit connected. This is + * because without VBUS detection, it is impossible to tell the difference between + * 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 & 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)); - } + if ( status ^ handled ) + { + panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); + } } #define USB_INTS_ERROR_BITS ( \ diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index b7790e0c1..d25e6c8fb 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -51,37 +51,41 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) //--------------------------------------------------------------------+ #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX -volatile uint32_t last_sof = 0; +// Errata 15 Walkaround for Device Bulk-In endpoint to avoid schedule an transfer +// within last 20% of an USB frame. -bool rp2040_critical_frame_period(struct hw_endpoint *ep) +volatile uint32_t e15_last_sof = 0; + +// check if Errata 15 walkround is needed for this endpoint +static bool __tusb_irq_path_func(e15_is_bulkin_ep) (struct hw_endpoint *ep) { - uint32_t delta; + return (!is_host_mode() && tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN && + ep->transfer_type == TUSB_XFER_BULK); +} - if (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) return false; - - if (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT || - ep->transfer_type == TUSB_XFER_INTERRUPT || - ep->transfer_type == TUSB_XFER_ISOCHRONOUS) - { - return false; - } +// check if we need to apply Errata 15 workaround: ie.g +// Enpoint 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)); /* 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. */ - delta = time_us_32() - last_sof; + uint32_t delta = time_us_32() - e15_last_sof; if (delta < 800 || delta > 998) { return false; } - TU_LOG(3, "Avoiding sof %u now %lu last %lu\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, now, last_sof); + TU_LOG(3, "Avoiding sof %u now %lu last %lu\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), e15_last_sof); return true; } -bool rp2040_ep_needs_sof(struct hw_endpoint *ep) { - return (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN && - ep->transfer_type == TUSB_XFER_BULK); -} +#else + +#define e15_is_bulkin_ep(x) false +#define e15_is_critical_frame_period(x) false + #endif void rp2040_usb_init(void) @@ -249,17 +253,17 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t to ep->active = true; ep->user_buf = buffer; - if (rp2040_ep_needs_sof(ep)) + if ( e15_is_bulkin_ep(ep) ) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; } - if(!rp2040_critical_frame_period(ep)) - { - hw_endpoint_start_next_buffer(ep); - } else + if ( e15_is_critical_frame_period(ep) ) { ep->pending = 1; + } else + { + hw_endpoint_start_next_buffer(ep); } hw_endpoint_lock_update(ep, -1); @@ -356,6 +360,7 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync) (struct hw_endpoint *ep bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep) { hw_endpoint_lock_update(ep, 1); + // Part way through a transfer if (!ep->active) { @@ -377,10 +382,12 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep) } else { - if(!rp2040_critical_frame_period(ep)) { - hw_endpoint_start_next_buffer(ep); - } else { + if ( e15_is_critical_frame_period(ep) ) + { ep->pending = 1; + } else + { + hw_endpoint_start_next_buffer(ep); } } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index d41abfee0..a06407f23 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -93,14 +93,8 @@ typedef struct hw_endpoint } hw_endpoint_t; -#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX -#define rp2040_critical_frame_period(x) false -#define rp2040_ep_needs_sof(x) false -#else -extern volatile uint32_t last_sof; - -bool rp2040_critical_frame_period(struct hw_endpoint *ep); -bool rp2040_ep_needs_sof(struct hw_endpoint *ep); +#if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX +extern volatile uint32_t e15_last_sof; #endif void rp2040_usb_init(void);