diff --git a/src/device/dcd.h b/src/device/dcd.h index 8bfad9b7..66767c1f 100644 --- a/src/device/dcd.h +++ b/src/device/dcd.h @@ -152,6 +152,7 @@ bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); // clear stall, data toggle is also reset to DATA0 +// This API never calls with control endpoints, since it is auto cleared when receiving setup packet void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); //--------------------------------------------------------------------+ diff --git a/src/device/usbd.c b/src/device/usbd.c index e8a50352..8b4f4e7e 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -1220,6 +1220,9 @@ bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) { (void) rhport; + // TODO add this check later, also make sure we don't starve an out endpoint while suspending + // TU_VERIFY(tud_ready()); + uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); @@ -1273,6 +1276,9 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); + // TODO skip ready() check for now since enumeration also use this API + // TU_VERIFY(tud_ready()); + TU_LOG2(" Queue EP %02X with %u bytes ...\r\n", ep_addr, total_bytes); // Attempt to transfer on a busy endpoint, sound like an race condition ! diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index f78a932f..49284e92 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -37,6 +37,11 @@ #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. +// Note: won't work if change to 0 (for now) +#define FORCE_VBUS_DETECT 1 + /*------------------------------------------------------------------*/ /* Low level controller *------------------------------------------------------------------*/ @@ -48,27 +53,27 @@ static uint8_t *next_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] = {0}; +static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; static inline struct hw_endpoint *hw_endpoint_get_by_num(uint8_t num, tusb_dir_t dir) { - return &hw_endpoints[num][dir]; + return &hw_endpoints[num][dir]; } static 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); + 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); } -static void _hw_endpoint_alloc(struct hw_endpoint *ep) +static void _hw_endpoint_alloc(struct hw_endpoint *ep, uint8_t transfer_type) { // size must be multiple of 64 uint16_t size = tu_div_ceil(ep->wMaxPacketSize, 64) * 64u; - // double buffered for Control and Bulk endpoint - if ( ep->transfer_type == TUSB_XFER_CONTROL || ep->transfer_type == TUSB_XFER_BULK) + // double buffered Bulk endpoint + if ( transfer_type == TUSB_XFER_BULK ) { size *= 2u; } @@ -80,81 +85,14 @@ static void _hw_endpoint_alloc(struct hw_endpoint *ep) uint dpram_offset = hw_data_offset(ep->hw_data_buf); assert(hw_data_offset(next_buffer_ptr) <= USB_DPRAM_MAX); - pico_info("Alloced %d bytes at offset 0x%x (0x%p) for ep %d %s\n", - size, - dpram_offset, - ep->hw_data_buf, - tu_edpt_number(ep->ep_addr), - ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + pico_info(" Alloced %d bytes at offset 0x%x (0x%p)\r\n", size, dpram_offset, ep->hw_data_buf); // Fill in endpoint control register with buffer offset - uint32_t const reg = EP_CTRL_ENABLE_BITS | (ep->transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; + uint32_t const reg = EP_CTRL_ENABLE_BITS | (transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; *ep->endpoint_control = reg; } -static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) -{ - const uint8_t num = tu_edpt_number(ep_addr); - const tusb_dir_t dir = tu_edpt_dir(ep_addr); - - ep->ep_addr = ep_addr; - - // For device, IN is a tx transfer and OUT is an rx transfer - ep->rx = (dir == TUSB_DIR_OUT); - - // 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; - - // Every endpoint has a buffer control register in dpram - if (dir == TUSB_DIR_IN) - { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].in; - } - else - { - ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].out; - } - - // Clear existing buffer control state - *ep->buffer_control = 0; - - if (num == 0) - { - // EP0 has no endpoint control register because - // the buffer offsets are fixed - ep->endpoint_control = NULL; - - // Buffer offset is fixed - 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; - } - - // Now if it hasn't already been done - //alloc a buffer and fill in endpoint control register - // TODO device may change configuration (dynamic), should clear and reallocate - if(!(ep->configured)) - { - _hw_endpoint_alloc(ep); - } - } - - ep->configured = true; -} - #if 0 // todo unused static void _hw_endpoint_close(struct hw_endpoint *ep) { @@ -174,10 +112,61 @@ static void hw_endpoint_close(uint8_t ep_addr) } #endif -static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t bmAttributes) +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); - _hw_endpoint_init(ep, ep_addr, wMaxPacketSize, bmAttributes); + 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); + + ep->ep_addr = ep_addr; + + // For device, IN is a tx transfer and OUT is an rx transfer + ep->rx = (dir == TUSB_DIR_OUT); + + // 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; + + // Every endpoint has a buffer control register in dpram + if ( dir == TUSB_DIR_IN ) + { + ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].in; + } + else + { + ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].out; + } + + // Clear existing buffer control state + *ep->buffer_control = 0; + + if ( num == 0 ) + { + // EP0 has no endpoint control register because + // the buffer offsets are fixed + ep->endpoint_control = NULL; + + // 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; + } + + // alloc a buffer and fill in endpoint control register + _hw_endpoint_alloc(ep, transfer_type); + } } static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) @@ -222,55 +211,22 @@ static void reset_ep0(void) { struct hw_endpoint *ep = hw_endpoint_get_by_addr(addrs[i]); ep->next_pid = 1u; - ep->stalled = 0; } } -static void ep0_0len_status(void) +static void reset_all_endpoints(void) { - // Send 0len complete response on EP0 IN - reset_ep0(); - hw_endpoint_xfer(0x80, NULL, 0); -} + memset(hw_endpoints, 0, sizeof(hw_endpoints)); + next_buffer_ptr = &usb_dpram->epx_data[0]; -static void _hw_endpoint_stall(struct hw_endpoint *ep) -{ - assert(!ep->stalled); - if (tu_edpt_number(ep->ep_addr) == 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->ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; - } - _hw_endpoint_buffer_control_set_mask32(ep, USB_BUF_CTRL_STALL); - ep->stalled = true; -} - -static void hw_endpoint_stall(uint8_t ep_addr) -{ - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); - _hw_endpoint_stall(ep); -} - -static void _hw_endpoint_clear_stall(struct hw_endpoint *ep) -{ - if (tu_edpt_number(ep->ep_addr) == 0) - { - // Probably already been cleared but no harm - usb_hw_clear->ep_stall_arm = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; - } - _hw_endpoint_buffer_control_clear_mask32(ep, USB_BUF_CTRL_STALL); - ep->stalled = false; -} - -static void hw_endpoint_clear_stall(uint8_t ep_addr) -{ - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); - _hw_endpoint_clear_stall(ep); + // Init Control endpoint out & in + hw_endpoint_init(0x0, 64, TUSB_XFER_CONTROL); + hw_endpoint_init(0x80, 64, TUSB_XFER_CONTROL); } static void dcd_rp2040_irq(void) { - uint32_t status = usb_hw->ints; + uint32_t const status = usb_hw->ints; uint32_t handled = 0; if (status & USB_INTS_SETUP_REQ_BITS) @@ -290,7 +246,9 @@ static void dcd_rp2040_irq(void) hw_handle_buff_status(); } - // SE0 for 2 us or more, usually together with Bus Reset +#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; @@ -306,13 +264,17 @@ static void dcd_rp2040_irq(void) usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; } +#endif - // SE0 for 2.5 us or more + // SE0 for 2.5 us or more (will last at least 10ms) if (status & USB_INTS_BUS_RESET_BITS) { pico_trace("BUS RESET\n"); - usb_hw->dev_addr_ctrl = 0; + handled |= USB_INTS_BUS_RESET_BITS; + + usb_hw->dev_addr_ctrl = 0; + reset_all_endpoints(); dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; @@ -322,9 +284,6 @@ static void dcd_rp2040_irq(void) #endif } -#if 0 - // TODO Enable SUSPEND & RESUME interrupt and test later on with/without VBUS detection - /* 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 @@ -346,7 +305,6 @@ static void dcd_rp2040_irq(void) dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS; } -#endif if (status ^ handled) { @@ -364,36 +322,38 @@ static void dcd_rp2040_irq(void) /*------------------------------------------------------------------*/ /* Controller API *------------------------------------------------------------------*/ + void dcd_init (uint8_t rhport) { - pico_trace("dcd_init %d\n", rhport); - assert(rhport == 0); + pico_trace("dcd_init %d\n", rhport); + assert(rhport == 0); - // Reset hardware to default state - rp2040_usb_init(); + // Reset hardware to default state + rp2040_usb_init(); - irq_set_exclusive_handler(USBCTRL_IRQ, dcd_rp2040_irq); - memset(hw_endpoints, 0, sizeof(hw_endpoints)); - next_buffer_ptr = &usb_dpram->epx_data[0]; +#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 - // EP0 always exists so init it now - // EP0 OUT - hw_endpoint_init(0x0, 64, TUSB_XFER_CONTROL); - // EP0 IN - hw_endpoint_init(0x80, 64, TUSB_XFER_CONTROL); + irq_set_exclusive_handler(USBCTRL_IRQ, dcd_rp2040_irq); - // Initializes the USB peripheral for device mode and enables it. - // Don't need to enable the pull up here. Force VBUS - usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; + // reset endpoints + reset_all_endpoints(); - // Enable individual controller IRQS here. Processor interrupt enable will be used - // for the global interrupt enable... - // TODO Enable SUSPEND & RESUME interrupt - usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; - usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | - USB_INTS_DEV_CONN_DIS_BITS /* | USB_INTS_DEV_SUSPEND_BITS | USB_INTS_DEV_RESUME_FROM_HOST_BITS */ ; + // Initializes the USB peripheral for device mode and enables it. + // Don't need to enable the pull up here. Force VBUS + usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; - dcd_connect(rhport); + // Enable individual controller IRQS here. Processor interrupt enable will be used + // for the global interrupt enable... + // Note: Force VBUS detect cause disconnection not detectable + usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; + usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | + USB_INTS_DEV_SUSPEND_BITS | USB_INTS_DEV_RESUME_FROM_HOST_BITS | + (FORCE_VBUS_DETECT ? 0 : USB_INTS_DEV_CONN_DIS_BITS); + + dcd_connect(rhport); } void dcd_int_enable(uint8_t rhport) @@ -410,11 +370,13 @@ void dcd_int_disable(uint8_t rhport) void dcd_set_address (uint8_t rhport, uint8_t dev_addr) { - pico_trace("dcd_set_address %d %d\n", rhport, dev_addr); - assert(rhport == 0); + pico_trace("dcd_set_address %d %d\n", rhport, dev_addr); + assert(rhport == 0); - // Can't set device address in hardware until status xfer has complete - ep0_0len_status(); + // Can't set device address in hardware until status xfer has complete + // Send 0len complete response on EP0 IN + reset_ep0(); + hw_endpoint_xfer(0x80, NULL, 0); } void dcd_remote_wakeup(uint8_t rhport) @@ -461,7 +423,6 @@ void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * re bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) { - pico_info("dcd_edpt_open %02x\n", desc_edpt->bEndpointAddress); assert(rhport == 0); hw_endpoint_init(desc_edpt->bEndpointAddress, desc_edpt->wMaxPacketSize.size, desc_edpt->bmAttributes.xfer); return true; @@ -474,20 +435,37 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t return true; } -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - pico_trace("dcd_edpt_stall %02x\n", ep_addr); - assert(rhport == 0); - hw_endpoint_stall(ep_addr); + pico_trace("dcd_edpt_stall %02x\n", ep_addr); + assert(rhport == 0); + + if ( tu_edpt_number(ep_addr) == 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; + } + + struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + + // TODO check with double buffered + _hw_endpoint_buffer_control_set_mask32(ep, USB_BUF_CTRL_STALL); } -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { - pico_trace("dcd_edpt_clear_stall %02x\n", ep_addr); - assert(rhport == 0); - hw_endpoint_clear_stall(ep_addr); -} + pico_trace("dcd_edpt_clear_stall %02x\n", ep_addr); + assert(rhport == 0); + if (tu_edpt_number(ep_addr)) + { + struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + + // clear stall also reset toggle to DATA0 + // TODO check with double buffered + _hw_endpoint_buffer_control_clear_mask32(ep, USB_BUF_CTRL_STALL | USB_BUF_CTRL_DATA1_PID); + } +} void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) { @@ -500,8 +478,8 @@ void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) void dcd_int_handler(uint8_t rhport) { - (void) rhport; - dcd_rp2040_irq(); + (void) rhport; + dcd_rp2040_irq(); } #endif diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 40a0805c..2c007ddd 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -61,8 +61,8 @@ static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS]; // 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 + 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 }; static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr) @@ -360,6 +360,9 @@ bool hcd_init(uint8_t rhport) // Reset any previous state rp2040_usb_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; + irq_set_exclusive_handler(USBCTRL_IRQ, hcd_rp2040_irq); // clear epx and interrupt eps diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index dc12e6d8..43554d28 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -62,11 +62,7 @@ void rp2040_usb_init(void) memset(usb_dpram, 0, sizeof(*usb_dpram)); // Mux the controller to the onboard usb phy - usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; - - // Force VBUS detect so the device thinks it is plugged into a host - // TODO support VBUs detect - usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; + usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; } void hw_endpoint_reset_transfer(struct hw_endpoint *ep) diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index 514152cd..5570a731 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -42,7 +42,7 @@ struct hw_endpoint // Buffer pointer in usb dpram uint8_t *hw_data_buf; - // Have we been stalled + // Have we been stalled TODO remove later bool stalled; // Current transfer information