diff options
Diffstat (limited to 'tinyusb/src/portable/raspberrypi/rp2040')
-rwxr-xr-x | tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c | 485 | ||||
-rwxr-xr-x | tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c | 564 | ||||
-rwxr-xr-x | tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c | 326 | ||||
-rwxr-xr-x | tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.h | 147 |
4 files changed, 1522 insertions, 0 deletions
diff --git a/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c new file mode 100755 index 00000000..49284e92 --- /dev/null +++ b/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -0,0 +1,485 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if TUSB_OPT_DEVICE_ENABLED && CFG_TUSB_MCU == OPT_MCU_RP2040 + +#include "pico.h" +#include "rp2040_usb.h" + +#if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX +#include "pico/fix/rp2040_usb_device_enumeration.h" +#endif + +#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 + *------------------------------------------------------------------*/ + +#define usb_hw_set hw_set_alias(usb_hw) +#define usb_hw_clear hw_clear_alias(usb_hw) + +// Init these in dcd_init +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]; + +static inline struct hw_endpoint *hw_endpoint_get_by_num(uint8_t num, tusb_dir_t 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); +} + +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 Bulk endpoint + if ( transfer_type == TUSB_XFER_BULK ) + { + size *= 2u; + } + + ep->hw_data_buf = next_buffer_ptr; + next_buffer_ptr += size; + + assert(((uintptr_t )next_buffer_ptr & 0b111111u) == 0); + 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)\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 | (transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; + + *ep->endpoint_control = reg; +} + +#if 0 // todo unused +static void _hw_endpoint_close(struct hw_endpoint *ep) +{ + // Clear hardware registers and then zero the struct + // Clears endpoint enable + *ep->endpoint_control = 0; + // Clears buffer available, etc + *ep->buffer_control = 0; + // Clear any endpoint state + memset(ep, 0, sizeof(struct hw_endpoint)); +} + +static void hw_endpoint_close(uint8_t ep_addr) +{ + struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + _hw_endpoint_close(ep); +} +#endif + +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); + + 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) +{ + struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + hw_endpoint_xfer_start(ep, buffer, total_bytes); +} + +static void hw_handle_buff_status(void) +{ + uint32_t remaining_buffers = usb_hw->buf_status; + pico_trace("buf_status 0x%08x\n", remaining_buffers); + uint bit = 1u; + for (uint i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) + { + if (remaining_buffers & bit) + { + // clear this in advance + usb_hw_clear->buf_status = bit; + // IN transfer for even i, OUT transfer for odd i + struct hw_endpoint *ep = hw_endpoint_get_by_num(i >> 1u, !(i & 1u)); + // Continue xfer + bool done = hw_endpoint_xfer_continue(ep); + if (done) + { + // Notify + dcd_event_xfer_complete(0, ep->ep_addr, ep->xferred_len, XFER_RESULT_SUCCESS, true); + hw_endpoint_reset_transfer(ep); + } + remaining_buffers &= ~bit; + } + bit <<= 1u; + } +} + +static 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 + uint8_t addrs[] = {0x0, 0x80}; + for (uint i = 0 ; i < TU_ARRAY_SIZE(addrs); i++) + { + struct hw_endpoint *ep = hw_endpoint_get_by_addr(addrs[i]); + ep->next_pid = 1u; + } +} + +static void reset_all_endpoints(void) +{ + memset(hw_endpoints, 0, sizeof(hw_endpoints)); + next_buffer_ptr = &usb_dpram->epx_data[0]; + + // 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 const status = usb_hw->ints; + uint32_t handled = 0; + + if (status & USB_INTS_SETUP_REQ_BITS) + { + handled |= USB_INTS_SETUP_REQ_BITS; + uint8_t const *setup = (uint8_t const *)&usb_dpram->setup_packet; + // Clear stall bits and reset pid + reset_ep0(); + // 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 (status & USB_INTS_BUFF_STATUS_BITS) + { + handled |= USB_INTS_BUFF_STATUS_BITS; + hw_handle_buff_status(); + } + +#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 ) + { + // 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"); + + 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; + +#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(); +#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; + } + + 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 + *------------------------------------------------------------------*/ + +void dcd_init (uint8_t rhport) +{ + pico_trace("dcd_init %d\n", rhport); + assert(rhport == 0); + + // Reset hardware to default state + rp2040_usb_init(); + +#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 + + irq_set_exclusive_handler(USBCTRL_IRQ, dcd_rp2040_irq); + + // reset endpoints + reset_all_endpoints(); + + // 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; + + // 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) +{ + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, true); +} + +void dcd_int_disable(uint8_t rhport) +{ + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, false); +} + +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); + + // 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) +{ + pico_info("dcd_remote_wakeup %d\n", rhport); + assert(rhport == 0); + usb_hw_set->sie_ctrl = USB_SIE_CTRL_RESUME_BITS; +} + +// disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + pico_info("dcd_disconnect %d\n", rhport); + assert(rhport == 0); + usb_hw_clear->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; +} + +// connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + pico_info("dcd_connect %d\n", rhport); + assert(rhport == 0); + usb_hw_set->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; +} + +/*------------------------------------------------------------------*/ +/* DCD Endpoint port + *------------------------------------------------------------------*/ + +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) +{ + (void) rhport; + + if ( request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS ) + { + pico_trace("Set HW address %d\n", request->wValue); + usb_hw->dev_addr_ctrl = (uint8_t) request->wValue; + } + + reset_ep0(); +} + +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) +{ + assert(rhport == 0); + hw_endpoint_init(desc_edpt->bEndpointAddress, desc_edpt->wMaxPacketSize.size, desc_edpt->bmAttributes.xfer); + return true; +} + +bool dcd_edpt_xfer(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); + return true; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t 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) +{ + 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) +{ + (void) rhport; + (void) ep_addr; + + // usbd.c says: In progress transfers on this EP may be delivered after this call + pico_trace("dcd_edpt_close %02x\n", ep_addr); +} + +void dcd_int_handler(uint8_t rhport) +{ + (void) rhport; + dcd_rp2040_irq(); +} + +#endif diff --git a/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c new file mode 100755 index 00000000..e51dfac2 --- /dev/null +++ b/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -0,0 +1,564 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if TUSB_OPT_HOST_ENABLED && CFG_TUSB_MCU == OPT_MCU_RP2040 + +#include "pico.h" +#include "rp2040_usb.h" + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "osal/osal.h" + +#include "host/hcd.h" +#include "host/usbh.h" + +#define ROOT_PORT 0 + +//--------------------------------------------------------------------+ +// 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]) + +#define usb_hw_set hw_set_alias(usb_hw) +#define usb_hw_clear hw_clear_alias(usb_hw) + +// 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 +}; + +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; + + 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; + } + + return NULL; +} + +static inline uint8_t dev_speed(void) +{ + return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB; +} + +static 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 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); +} + +static void _handle_buff_status_bit(uint bit, struct hw_endpoint *ep) +{ + usb_hw_clear->buf_status = bit; + bool done = hw_endpoint_xfer_continue(ep); + if (done) + { + hw_xfer_complete(ep, XFER_RESULT_SUCCESS); + } +} + +static void hw_handle_buff_status(void) +{ + uint32_t remaining_buffers = usb_hw->buf_status; + pico_trace("buf_status 0x%08x\n", remaining_buffers); + + // Check EPX first + uint bit = 0b1; + if (remaining_buffers & bit) + { + remaining_buffers &= ~bit; + struct hw_endpoint *ep = &epx; + + 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: "); + } + TU_LOG_HEX(3, ep_ctrl); + + _handle_buff_status_bit(bit, ep); + } + + // Check interrupt endpoints + for (uint i = 1; i <= USB_HOST_INTERRUPT_ENDPOINTS && remaining_buffers; i++) + { + // EPX is bit 0 + // IEP1 is bit 2 + // IEP2 is bit 4 + // IEP3 is bit 6 + // etc + bit = 1 << (i*2); + + if (remaining_buffers & bit) + { + remaining_buffers &= ~bit; + _handle_buff_status_bit(bit, &ep_pool[i]); + } + } + + if (remaining_buffers) + { + panic("Unhandled buffer %d\n", remaining_buffers); + } +} + +static void hw_trans_complete(void) +{ + struct hw_endpoint *ep = &epx; + assert(ep->active); + + if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) + { + pico_trace("Sent setup packet\n"); + hw_xfer_complete(ep, XFER_RESULT_SUCCESS); + } + else + { + // Don't care. Will handle this in buff status + return; + } +} + +static void hcd_rp2040_irq(void) +{ + uint32_t status = usb_hw->ints; + uint32_t handled = 0; + + if (status & USB_INTS_HOST_CONN_DIS_BITS) + { + handled |= USB_INTS_HOST_CONN_DIS_BITS; + + if (dev_speed()) + { + hcd_event_device_attach(ROOT_PORT, true); + } + else + { + hcd_event_device_remove(ROOT_PORT, true); + } + + // Clear speed change interrupt + usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; + } + + if (status & USB_INTS_BUFF_STATUS_BITS) + { + handled |= USB_INTS_BUFF_STATUS_BITS; + TU_LOG(2, "Buffer complete\n"); + // print_bufctrl32(*epx.buffer_control); + hw_handle_buff_status(); + } + + 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\n"); + hw_trans_complete(); + } + + if (status & USB_INTS_STALL_BITS) + { + // We have rx'd a stall from the device + 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); + } + + 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; + print_bufctrl32(*epx.buffer_control); + panic("Data Seq Error \n"); + } + + if (status ^ handled) + { + panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); + } +} + +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 = i - 1; + return ep; + } + } + return ep; +} + +static struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type) +{ + struct hw_endpoint *ep = NULL; + + if (transfer_type == TUSB_XFER_INTERRUPT) + { + ep = _next_free_interrupt_ep(); + pico_info("Allocate interrupt ep %d\n", 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]; + } + + return ep; +} + +static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint 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); + + uint8_t const num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + ep->ep_addr = ep_addr; + ep->dev_addr = dev_addr; + + // For host, IN to host == RX, anything else rx == false + ep->rx = (dir == TUSB_DIR_IN); + + // 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; + + pico_trace("hw_endpoint_init dev %d ep %d %s xfer %d\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)], ep->transfer_type); + pico_trace("dev %d ep %d %s setup buffer @ 0x%p\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(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)); + + // 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; + ep_reg |= bmInterval ? (bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB : 0; + *ep->endpoint_control = ep_reg; + pico_trace("endpoint control (0x%p) <- 0x%x\n", ep->endpoint_control, ep_reg); + ep->configured = true; + + if (bmInterval) + { + // This is an interrupt endpoint + // so need to set up interrupt endpoint address control register with: + // device address + // endpoint number / direction + // preamble + uint32_t reg = dev_addr | (num << USB_ADDR_ENDP1_ENDPOINT_LSB); + // Assert the interrupt endpoint is IN_TO_HOST + // TODO Interrupt can also be OUT + assert(dir == TUSB_DIR_IN); + + if (need_pre(dev_addr)) + { + reg |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; + } + 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); + + // If it's an interrupt endpoint we need to set up the buffer control + // register + } +} + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(uint8_t rhport) +{ + pico_trace("hcd_init %d\n", rhport); + assert(rhport == 0); + + // 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 + memset(&ep_pool, 0, sizeof(ep_pool)); + + // 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 ; + + return true; +} + +void hcd_port_reset(uint8_t rhport) +{ + pico_trace("hcd_port_reset\n"); + assert(rhport == 0); + // TODO: Nothing to do here yet. Perhaps need to reset some state? +} + +bool hcd_port_connect_status(uint8_t rhport) +{ + pico_trace("hcd_port_connect_status\n"); + assert(rhport == 0); + return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + assert(rhport == 0); + // TODO: Should enumval this register + switch (dev_speed()) + { + case 1: + return TUSB_SPEED_LOW; + case 2: + return TUSB_SPEED_FULL; + default: + panic("Invalid speed\n"); + return TUSB_SPEED_INVALID; + } +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + (void) rhport; + (void) dev_addr; + + pico_trace("hcd_device_close %d\n", dev_addr); +} + +uint32_t hcd_frame_number(uint8_t rhport) +{ + (void) rhport; + return usb_hw->sof_rd; +} + +void hcd_int_enable(uint8_t rhport) +{ + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, true); +} + +void hcd_int_disable(uint8_t 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; + + 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_init(ep, + dev_addr, + ep_desc->bEndpointAddress, + ep_desc->wMaxPacketSize.size, + ep_desc->bmAttributes.xfer, + ep_desc->bInterval); + + return true; +} + +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); + + // Get appropriate ep. Either EPX or interrupt endpoint + struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); + assert(ep); + + // Control endpoint can change direction 0x00 <-> 0x80 + if ( ep_addr != ep->ep_addr ) + { + assert(ep_num == 0); + + // 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); + } + + // 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); + + // That has set up buffer control, endpoint control etc + // for host we have to initiate the transfer + usb_hw->dev_addr_ctrl = 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); + // Set pre if we are a low speed device on full speed hub + flags |= need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0; + + usb_hw->sie_ctrl = flags; + }else + { + hw_endpoint_xfer_start(ep, buffer, buflen); + } + + return true; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + (void) rhport; + + // Copy data into setup packet buffer + memcpy((void*)&usbh_dpram->setup_packet[0], setup_packet, 8); + + // Configure EP0 struct with setup info for the trans complete + struct hw_endpoint *ep = _hw_endpoint_allocate(0); + + // EP0 out + _hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0); + assert(ep->configured); + + ep->remaining_len = 8; + ep->active = true; + + // Set device address + usb_hw->dev_addr_ctrl = dev_addr; + + // 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); + + usb_hw->sie_ctrl = flags; + + return true; +} + + +//bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) +//{ +// // EPX is shared, so multiple device addresses and endpoint addresses share that +// // so if any transfer is active on epx, we are busy. Interrupt endpoints have their own +// // EPX so ep->active will only be busy if there is a pending transfer on that interrupt endpoint +// // on that device +// pico_trace("hcd_edpt_busy dev addr %d ep_addr 0x%x\n", dev_addr, ep_addr); +// struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); +// assert(ep); +// bool busy = ep->active; +// pico_trace("busy == %d\n", busy); +// return busy; +//} + +bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr) +{ + (void) dev_addr; + (void) ep_addr; + + panic("hcd_clear_stall"); + return true; +} + +#endif diff --git a/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c b/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c new file mode 100755 index 00000000..43554d28 --- /dev/null +++ b/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -0,0 +1,326 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 + +#include <stdlib.h> +#include "rp2040_usb.h" + +// Direction strings for debug +const char *ep_dir_string[] = { + "out", + "in", +}; + +static inline void _hw_endpoint_lock_update(struct hw_endpoint *ep, 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 void _hw_endpoint_xfer_sync(struct hw_endpoint *ep); +static void _hw_endpoint_start_next_buffer(struct hw_endpoint *ep); + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +void rp2040_usb_init(void) +{ + // Reset usb controller + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + // Clear any previous state just in case + memset(usb_hw, 0, sizeof(*usb_hw)); + 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; +} + +void hw_endpoint_reset_transfer(struct hw_endpoint *ep) +{ + ep->stalled = false; + ep->active = false; + ep->remaining_len = 0; + ep->xferred_len = 0; + ep->user_buf = 0; +} + +void _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; + } + if (or_mask) { + value |= or_mask; + if (or_mask & USB_BUF_CTRL_AVAIL) { + if (*ep->buffer_control & USB_BUF_CTRL_AVAIL) { + panic("ep %d %s was already available", tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + } + *ep->buffer_control = value & ~USB_BUF_CTRL_AVAIL; + // 12 cycle delay.. (should be good for 48*12Mhz = 576Mhz) + // Don't need delay in host mode as host is in charge +#if !TUSB_OPT_HOST_ENABLED + __asm volatile ( + "b 1f\n" + "1: b 1f\n" + "1: b 1f\n" + "1: b 1f\n" + "1: b 1f\n" + "1: b 1f\n" + "1:\n" + : : : "memory"); +#endif + } + } + *ep->buffer_control = value; +} + +// prepare buffer, return buffer control +static uint32_t 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 -= buflen; + + uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; + + // PID + buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; + ep->next_pid ^= 1u; + + if ( !ep->rx ) + { + // Copy data from user buffer to hw buffer + memcpy(ep->hw_data_buf + buf_id*64, 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 + 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 +static void _hw_endpoint_start_next_buffer(struct hw_endpoint *ep) +{ + uint32_t ep_ctrl = *ep->endpoint_control; + + // 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 Device mode, OUT endpoint since + // host could send < 64 bytes and cause short packet on buffer0 + // NOTE this could happen to Host mode IN endpoint + bool const force_single = !(usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) && !tu_edpt_dir(ep->ep_addr); + + 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) + + 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; + } + + *ep->endpoint_control = ep_ctrl; + + TU_LOG(3, "Prepare Buffer Control:\r\n"); + print_bufctrl32(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); +} + +void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t total_len) +{ + _hw_endpoint_lock_update(ep, 1); + + if ( ep->active ) + { + // TODO: Is this acceptable for interrupt packets? + TU_LOG(1, "WARN: starting new transfer on already active ep %d %s\n", tu_edpt_number(ep->ep_addr), + ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + + hw_endpoint_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; + + _hw_endpoint_start_next_buffer(ep); + _hw_endpoint_lock_update(ep, -1); +} + +// sync endpoint buffer and return transferred bytes +static uint16_t 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; + + uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + + if ( !ep->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 += 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); + + memcpy(ep->user_buf, ep->hw_data_buf + buf_id*64, xferred_bytes); + ep->xferred_len += xferred_bytes; + ep->user_buf += xferred_bytes; + } + + // Short packet + if (xferred_bytes < ep->wMaxPacketSize) + { + pico_trace("Short rx transfer on buffer %d with %u bytes\n", buf_id, xferred_bytes); + // Reduce total length as this is last packet + ep->remaining_len = 0; + } + + return xferred_bytes; +} + +static void _hw_endpoint_xfer_sync (struct hw_endpoint *ep) +{ + // Update hw endpoint struct with info from hardware + // after a buff status interrupt + + uint32_t buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); + TU_LOG(3, "_hw_endpoint_xfer_sync:\r\n"); + print_bufctrl32(buf_ctrl); + + // always sync buffer 0 + uint16_t buf0_bytes = sync_ep_buffer(ep, 0); + + // 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); + + // abort queued transfer on buffer 1 + usb_hw->abort |= TU_BIT(ep_id); + + while ( !(usb_hw->abort_done & TU_BIT(ep_id)) ) {} + + 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; + + _hw_endpoint_buffer_control_set_value32(ep, 0); + + usb_hw->abort &= ~TU_BIT(ep_id); + + TU_LOG(3, "----SHORT PACKET buffer0 on EP %02X:\r\n", ep->ep_addr); + print_bufctrl32(buf_ctrl); +#endif + } + } +} + +// Returns true if transfer is complete +bool hw_endpoint_xfer_continue(struct hw_endpoint *ep) +{ + _hw_endpoint_lock_update(ep, 1); + // Part way through a transfer + if (!ep->active) + { + panic("Can't continue xfer on inactive ep %d %s", tu_edpt_number(ep->ep_addr), ep_dir_string); + } + + // Update EP struct from hardware state + _hw_endpoint_xfer_sync(ep); + + // 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 %d %s\n", + ep->xferred_len, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + // Notify caller we are done so it can notify the tinyusb stack + _hw_endpoint_lock_update(ep, -1); + return true; + } + else + { + _hw_endpoint_start_next_buffer(ep); + } + + _hw_endpoint_lock_update(ep, -1); + // More work to do + return false; +} + +#endif diff --git a/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.h b/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.h new file mode 100755 index 00000000..5570a731 --- /dev/null +++ b/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -0,0 +1,147 @@ +#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" + +#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 + + +#define pico_info(...) TU_LOG(2, __VA_ARGS__) +#define pico_trace(...) TU_LOG(3, __VA_ARGS__) + +// Hardware information per endpoint +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; + + // 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; + + // Have we been stalled TODO remove later + bool stalled; + + // Current transfer information + bool active; + uint16_t remaining_len; + uint16_t xferred_len; + + // User buffer in main memory + uint8_t *user_buf; + + // Data needed from EP descriptor + uint16_t wMaxPacketSize; + + // Interrupt, bulk, etc + uint8_t transfer_type; + +#if TUSB_OPT_HOST_ENABLED + // Only needed for host + uint8_t dev_addr; + + // If interrupt endpoint + uint8_t interrupt_num; +#endif +}; + +void rp2040_usb_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_buffer_control_update32(struct hw_endpoint *ep, uint32_t and_mask, uint32_t or_mask); +static inline uint32_t _hw_endpoint_buffer_control_get_value32(struct hw_endpoint *ep) { + return *ep->buffer_control; +} +static inline void _hw_endpoint_buffer_control_set_value32(struct hw_endpoint *ep, uint32_t value) { + return _hw_endpoint_buffer_control_update32(ep, 0, value); +} +static inline void _hw_endpoint_buffer_control_set_mask32(struct hw_endpoint *ep, uint32_t value) { + return _hw_endpoint_buffer_control_update32(ep, ~value, value); +} +static inline void _hw_endpoint_buffer_control_clear_mask32(struct hw_endpoint *ep, uint32_t value) { + return _hw_endpoint_buffer_control_update32(ep, ~value, 0); +} + +static inline uintptr_t hw_data_offset(uint8_t *buf) +{ + // Remove usb base from buffer pointer + return (uintptr_t)buf ^ (uintptr_t)usb_dpram; +} + +extern const char *ep_dir_string[]; + +typedef union TU_ATTR_PACKED +{ + uint16_t u16; + struct TU_ATTR_PACKED + { + uint16_t xfer_len : 10; + uint16_t available : 1; + uint16_t stall : 1; + uint16_t reset_bufsel : 1; + uint16_t data_toggle : 1; + uint16_t last_buf : 1; + uint16_t full : 1; + }; +} rp2040_buffer_control_t; + +TU_VERIFY_STATIC(sizeof(rp2040_buffer_control_t) == 2, "size is not correct"); + +#if CFG_TUSB_DEBUG >= 3 +static inline void print_bufctrl16(uint32_t u16) +{ + rp2040_buffer_control_t bufctrl = { + .u16 = u16 + }; + + TU_LOG(3, "len = %u, available = %u, full = %u, last = %u, stall = %u, reset = %u, toggle = %u\r\n", + bufctrl.xfer_len, bufctrl.available, bufctrl.full, bufctrl.last_buf, bufctrl.stall, bufctrl.reset_bufsel, bufctrl.data_toggle); +} + +static inline void print_bufctrl32(uint32_t u32) +{ + uint16_t u16; + + u16 = u32 >> 16; + TU_LOG(3, " Buffer Control 1 0x%x: ", u16); + print_bufctrl16(u16); + + u16 = u32 & 0x0000ffff; + TU_LOG(3, " Buffer Control 0 0x%x: ", u16); + print_bufctrl16(u16); +} + +#else + +#define print_bufctrl16(u16) +#define print_bufctrl32(u32) + +#endif + +#endif |