diff --git a/examples/Video/video_capture/.metro_m0:usbstack=tinyusb.test.skip b/examples/Video/video_capture/.metro_m0:usbstack=tinyusb.test.skip new file mode 100644 index 00000000..52a3f993 --- /dev/null +++ b/examples/Video/video_capture/.metro_m0:usbstack=tinyusb.test.skip @@ -0,0 +1 @@ +The video capture example exceeds the Metro M0 RAM capacity. diff --git a/src/arduino/Adafruit_USBD_CDC.cpp b/src/arduino/Adafruit_USBD_CDC.cpp index 11c5673e..9fdccc2a 100644 --- a/src/arduino/Adafruit_USBD_CDC.cpp +++ b/src/arduino/Adafruit_USBD_CDC.cpp @@ -266,11 +266,18 @@ int Adafruit_USBD_CDC::availableForWrite(void) { extern "C" { +// Optional application observer. The Arduino CDC wrapper retains ownership of +// tud_cdc_line_state_cb() so 1200-baud bootloader touch remains intact. +__attribute__((weak)) void tud_cdc_line_state_app_cb(uint8_t instance, bool dtr, + bool rts) { + (void)instance; + (void)dtr; + (void)rts; +} + // Invoked when cdc when line state changed e.g connected/disconnected // Use to reset to DFU when disconnect with 1200 bps void tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) { - (void)rts; - // DTR = false is counted as disconnected if (!dtr) { // touch1200 only with first CDC instance (Serial) @@ -283,6 +290,8 @@ void tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) { } } } + + tud_cdc_line_state_app_cb(instance, dtr, rts); } } diff --git a/src/arduino/Adafruit_USBH_Host.cpp b/src/arduino/Adafruit_USBH_Host.cpp index cdba7732..a3a7da7c 100644 --- a/src/arduino/Adafruit_USBH_Host.cpp +++ b/src/arduino/Adafruit_USBH_Host.cpp @@ -273,11 +273,20 @@ void tuh_max3421_int_api(uint8_t rhport, bool enabled) { (void)host; #ifdef ARDUINO_ARCH_SAMD - //--- SAMD51 ---// -#ifdef __SAMD51__ +//--- SAMD51/SAME51 ---// +#if TU_CHECK_MCU(OPT_MCU_SAMD51) const IRQn_Type irq = (IRQn_Type)(EIC_0_IRQn + g_APinDescription[host->_intr].ulExtInt); + if (enabled) { + NVIC_EnableIRQ(irq); + } else { + NVIC_DisableIRQ(irq); + } +#elif TU_CHECK_MCU(OPT_MCU_SAME5X) + const IRQn_Type irq = + (IRQn_Type)(EIC_EXTINT_0_IRQn + g_APinDescription[host->_intr].ulExtInt); + if (enabled) { NVIC_EnableIRQ(irq); } else { diff --git a/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp b/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp index f6fe45e0..44659691 100644 --- a/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp +++ b/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp @@ -37,7 +37,7 @@ //--------------------------------------------------------------------+ extern "C" { -#if CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X +#if TU_CHECK_MCU(OPT_MCU_SAMD51) // SAMD51 void USB_0_Handler(void) { tud_int_handler(0); } @@ -45,7 +45,15 @@ void USB_1_Handler(void) { tud_int_handler(0); } void USB_2_Handler(void) { tud_int_handler(0); } void USB_3_Handler(void) { tud_int_handler(0); } -#elif CFG_TUSB_MCU == OPT_MCU_SAMD21 +#elif TU_CHECK_MCU(OPT_MCU_SAME5X) + +// SAME53/54 +void USB_OTHER_Handler(void) { tud_int_handler(0); } +void USB_SOF_HSOF_Handler(void) { tud_int_handler(0); } +void USB_TRCPT0_Handler(void) { tud_int_handler(0); } +void USB_TRCPT1_Handler(void) { tud_int_handler(0); } + +#elif TU_CHECK_MCU(OPT_MCU_SAMD21) // SAMD21 void USB_Handler(void) { tud_int_handler(0); } @@ -61,7 +69,7 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) { (void)rhport; /* Enable USB clock */ -#if defined(__SAMD51__) +#if TU_CHECK_MCU(OPT_MCU_SAMD51) MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB; @@ -84,6 +92,29 @@ void TinyUSB_Port_InitDevice(uint8_t rhport) { NVIC_SetPriority(USB_1_IRQn, 0UL); NVIC_SetPriority(USB_2_IRQn, 0UL); NVIC_SetPriority(USB_3_IRQn, 0UL); +#elif TU_CHECK_MCU(OPT_MCU_SAME5X) + MCLK_REGS->MCLK_APBBMASK |= MCLK_APBBMASK_USB_Msk; + MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_USB_Msk; + + // Set up the USB DP/DN pins + PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA24H_USB_DM] |= PORT_PINCFG_PMUXEN_Msk; + PORT_REGS->GROUP[0].PORT_PMUX[PIN_PA24H_USB_DM / 2] &= + ~(0xF << (4 * (PIN_PA24H_USB_DM & 0x01u))); + PORT_REGS->GROUP[0].PORT_PMUX[PIN_PA24H_USB_DM / 2] |= + MUX_PA24H_USB_DM << (4 * (PIN_PA24H_USB_DM & 0x01u)); + PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA25H_USB_DP] |= PORT_PINCFG_PMUXEN_Msk; + PORT_REGS->GROUP[0].PORT_PMUX[PIN_PA25H_USB_DP / 2] &= + ~(0xF << (4 * (PIN_PA25H_USB_DP & 0x01u))); + PORT_REGS->GROUP[0].PORT_PMUX[PIN_PA25H_USB_DP / 2] |= + MUX_PA25H_USB_DP << (4 * (PIN_PA25H_USB_DP & 0x01u)); + + GCLK_REGS->GCLK_PCHCTRL[USB_GCLK_ID] = + GCLK_PCHCTRL_GEN_GCLK1 | GCLK_PCHCTRL_CHEN_Msk; + + NVIC_SetPriority(USB_OTHER_IRQn, 0UL); + NVIC_SetPriority(USB_SOF_HSOF_IRQn, 0UL); + NVIC_SetPriority(USB_TRCPT0_IRQn, 0UL); + NVIC_SetPriority(USB_TRCPT1_IRQn, 0UL); #else PM->APBBMASK.reg |= PM_APBBMASK_USB; @@ -126,7 +157,7 @@ void TinyUSB_Port_EnterDFU(void) { } uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { -#ifdef __SAMD51__ +#if TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) uint32_t *id_addresses[4] = {(uint32_t *)0x008061FC, (uint32_t *)0x00806010, (uint32_t *)0x00806014, (uint32_t *)0x00806018}; #else // samd21 diff --git a/src/arduino/ports/samd/tusb_config_samd.h b/src/arduino/ports/samd/tusb_config_samd.h index d2276d12..f4875022 100644 --- a/src/arduino/ports/samd/tusb_config_samd.h +++ b/src/arduino/ports/samd/tusb_config_samd.h @@ -32,8 +32,10 @@ extern "C" { //-------------------------------------------------------------------- // COMMON CONFIGURATION //-------------------------------------------------------------------- -#ifdef __SAMD51__ +#if defined(__SAMD51__) || defined(__SAME51__) #define CFG_TUSB_MCU OPT_MCU_SAMD51 +#elif defined(__SAME53__) || defined(__SAME54__) +#define CFG_TUSB_MCU OPT_MCU_SAME5X #else #define CFG_TUSB_MCU OPT_MCU_SAMD21 #endif diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c index c499756b..cb2e6de5 100644 --- a/src/class/cdc/cdc_device.c +++ b/src/class/cdc/cdc_device.c @@ -56,6 +56,12 @@ typedef struct { tu_edpt_stream_t tx_stream; tu_edpt_stream_t rx_stream; + bool direct_mode; + bool direct_tx_active; + bool direct_rx_active; + const void* direct_tx_buffer; + void* direct_rx_buffer; + uint8_t tx_ff_buf[CFG_TUD_CDC_TX_BUFSIZE]; uint8_t rx_ff_buf[CFG_TUD_CDC_RX_BUFSIZE]; } cdcd_interface_t; @@ -92,6 +98,22 @@ TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf) { (void)itf; } +TU_ATTR_WEAK void tud_cdc_direct_tx_complete_cb(uint8_t itf, const void* buffer, uint32_t xferred_bytes, + xfer_result_t result) { + (void)itf; + (void)buffer; + (void)xferred_bytes; + (void)result; +} + +TU_ATTR_WEAK void tud_cdc_direct_rx_complete_cb(uint8_t itf, void* buffer, uint32_t xferred_bytes, + xfer_result_t result) { + (void)itf; + (void)buffer; + (void)xferred_bytes; + (void)result; +} + TU_ATTR_WEAK void tud_cdc_notify_complete_cb(uint8_t itf) { (void)itf; } @@ -183,28 +205,85 @@ void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted) { _cdcd_itf[itf].wanted_char = wanted; } +bool tud_cdc_n_set_direct_mode(uint8_t itf, bool enabled) { + TU_VERIFY(itf < CFG_TUD_CDC); + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!tu_edpt_stream_is_opened(&p_cdc->tx_stream)); + TU_VERIFY(!tu_edpt_stream_is_opened(&p_cdc->rx_stream)); + TU_VERIFY(!p_cdc->direct_tx_active && !p_cdc->direct_rx_active); + p_cdc->direct_mode = enabled; + return true; +} + +bool tud_cdc_n_direct_write(uint8_t itf, const void* buffer, uint32_t length) { + TU_VERIFY(itf < CFG_TUD_CDC); + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(p_cdc->direct_mode && tud_ready()); + TU_VERIFY(tu_edpt_stream_is_opened(&p_cdc->tx_stream)); + TU_VERIFY(buffer != NULL || length == 0); + TU_VERIFY(length <= UINT16_MAX); + TU_VERIFY(!p_cdc->direct_tx_active); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->tx_stream.ep_addr)); + + p_cdc->direct_tx_active = true; + p_cdc->direct_tx_buffer = buffer; + if (!usbd_edpt_xfer(p_cdc->rhport, p_cdc->tx_stream.ep_addr, (uint8_t*)(uintptr_t)buffer, + (uint16_t) length, false)) { + // usbd_edpt_xfer() clears BUSY and CLAIMED when the DCD rejects submission. + p_cdc->direct_tx_active = false; + p_cdc->direct_tx_buffer = NULL; + return false; + } + return true; +} + +bool tud_cdc_n_direct_read(uint8_t itf, void* buffer, uint32_t length) { + TU_VERIFY(itf < CFG_TUD_CDC); + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(p_cdc->direct_mode && tud_ready()); + TU_VERIFY(tu_edpt_stream_is_opened(&p_cdc->rx_stream)); + TU_VERIFY(buffer != NULL && length > 0); + TU_VERIFY(length <= UINT16_MAX); + TU_VERIFY(!p_cdc->direct_rx_active); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->rx_stream.ep_addr)); + + p_cdc->direct_rx_active = true; + p_cdc->direct_rx_buffer = buffer; + if (!usbd_edpt_xfer(p_cdc->rhport, p_cdc->rx_stream.ep_addr, buffer, (uint16_t) length, false)) { + // usbd_edpt_xfer() clears BUSY and CLAIMED when the DCD rejects submission. + p_cdc->direct_rx_active = false; + p_cdc->direct_rx_buffer = NULL; + return false; + } + return true; +} + //--------------------------------------------------------------------+ // READ API //--------------------------------------------------------------------+ uint32_t tud_cdc_n_available(uint8_t itf) { TU_VERIFY(itf < CFG_TUD_CDC, 0); + TU_VERIFY(!_cdcd_itf[itf].direct_mode, 0); return tu_edpt_stream_read_available(&_cdcd_itf[itf].rx_stream); } uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) { TU_VERIFY(itf < CFG_TUD_CDC, 0); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode, 0); return tu_edpt_stream_read(&p_cdc->rx_stream, buffer, bufsize); } bool tud_cdc_n_peek(uint8_t itf, uint8_t *chr) { TU_VERIFY(itf < CFG_TUD_CDC); + TU_VERIFY(!_cdcd_itf[itf].direct_mode); return tu_edpt_stream_peek(&_cdcd_itf[itf].rx_stream, chr); } void tud_cdc_n_read_flush(uint8_t itf) { TU_VERIFY(itf < CFG_TUD_CDC, ); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode, ); tu_edpt_stream_clear(&p_cdc->rx_stream); tu_edpt_stream_read_xfer(&p_cdc->rx_stream); } @@ -215,24 +294,28 @@ void tud_cdc_n_read_flush(uint8_t itf) { uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) { TU_VERIFY(itf < CFG_TUD_CDC, 0); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode, 0); return tu_edpt_stream_write(&p_cdc->tx_stream, buffer, bufsize); } uint32_t tud_cdc_n_write_flush(uint8_t itf) { TU_VERIFY(itf < CFG_TUD_CDC, 0); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode, 0); return tu_edpt_stream_write_xfer(&p_cdc->tx_stream); } uint32_t tud_cdc_n_write_available(uint8_t itf) { TU_VERIFY(itf < CFG_TUD_CDC, 0); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode, 0); return tu_edpt_stream_write_available(&p_cdc->tx_stream); } bool tud_cdc_n_write_clear(uint8_t itf) { TU_VERIFY(itf < CFG_TUD_CDC); cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(!p_cdc->direct_mode); tu_edpt_stream_clear(&p_cdc->tx_stream); return true; } @@ -286,6 +369,11 @@ void cdcd_reset(uint8_t rhport) { cdcd_interface_t* p_cdc = &_cdcd_itf[i]; tu_memclr(p_cdc, ITF_MEM_RESET_SIZE); + p_cdc->direct_tx_active = false; + p_cdc->direct_rx_active = false; + p_cdc->direct_tx_buffer = NULL; + p_cdc->direct_rx_buffer = NULL; + tu_fifo_set_overwritable(&p_cdc->tx_stream.ff, CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED); // back to default tu_edpt_stream_close(&p_cdc->rx_stream); tu_edpt_stream_close(&p_cdc->tx_stream); @@ -361,7 +449,9 @@ uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16 tu_edpt_stream_clear(stream_rx); #endif - TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare for incoming data + if (!p_cdc->direct_mode) { + TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare for incoming data + } } } @@ -469,6 +559,15 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ // Received new data, move to fifo if (ep_addr == stream_rx->ep_addr) { + if (p_cdc->direct_mode) { + TU_ASSERT(p_cdc->direct_rx_active); + void* buffer = p_cdc->direct_rx_buffer; + p_cdc->direct_rx_active = false; + p_cdc->direct_rx_buffer = NULL; + tud_cdc_direct_rx_complete_cb(itf, buffer, xferred_bytes, result); + return true; + } + tu_edpt_stream_read_xfer_complete(stream_rx, xferred_bytes); // Check for wanted char and invoke wanted callback @@ -515,6 +614,15 @@ bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_ // Data sent to host, we continue to fetch from tx fifo to send. // Note: This will cause incorrect baudrate set in line coding. Though maybe the baudrate is not really important! if (ep_addr == stream_tx->ep_addr) { + if (p_cdc->direct_mode) { + TU_ASSERT(p_cdc->direct_tx_active); + const void* buffer = p_cdc->direct_tx_buffer; + p_cdc->direct_tx_active = false; + p_cdc->direct_tx_buffer = NULL; + tud_cdc_direct_tx_complete_cb(itf, buffer, xferred_bytes, result); + return true; + } + tud_cdc_tx_complete_cb(itf); // invoke callback to possibly refill tx fifo if (0 == tu_edpt_stream_write_xfer(stream_tx)) { diff --git a/src/class/cdc/cdc_device.h b/src/class/cdc/cdc_device.h index 9ac6bc58..b4e34238 100644 --- a/src/class/cdc/cdc_device.h +++ b/src/class/cdc/cdc_device.h @@ -116,6 +116,26 @@ void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding); // Set special character that will trigger tud_cdc_rx_wanted_cb() callback on receiving void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted); +// Select direct endpoint transfers for this CDC interface. This task-context +// API must be called before the interface is opened by enumeration and must not +// race another API call for the same interface. Direct mode bypasses the CDC +// FIFOs: the caller-owned buffer is passed unchanged to the device controller +// and must remain valid until its completion callback or a device reset. While +// direct mode is enabled, FIFO read/write/status APIs fail and return their +// normal failure value (zero or false); that result does not mean FIFO state. +bool tud_cdc_n_set_direct_mode(uint8_t itf, bool enabled); + +// Submit one caller-owned fixed container directly to the CDC bulk endpoint. +// FIFO APIs and direct APIs are mutually exclusive on a direct-mode interface. +// These are task-context APIs and must not race another API call for the same +// interface. Only one transfer in each direction may be active at a time. +// Lengths greater than UINT16_MAX are rejected because the device endpoint +// transfer API uses a 16-bit transfer length. A false return means no callback +// will occur. If endpoint submission fails after a successful claim, +// usbd_edpt_xfer() releases the claim before returning false. +bool tud_cdc_n_direct_write(uint8_t itf, const void* buffer, uint32_t length); +bool tud_cdc_n_direct_read(uint8_t itf, void* buffer, uint32_t length); + // Get the number of bytes available for reading uint32_t tud_cdc_n_available(uint8_t itf); @@ -222,6 +242,18 @@ TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_set_wanted_char(char wanted) { tud_cdc_n_set_wanted_char(0, wanted); } +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_set_direct_mode(bool enabled) { + return tud_cdc_n_set_direct_mode(0, enabled); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_direct_write(const void* buffer, uint32_t length) { + return tud_cdc_n_direct_write(0, buffer, length); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_direct_read(void* buffer, uint32_t length) { + return tud_cdc_n_direct_read(0, buffer, length); +} + TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_available(void) { return tud_cdc_n_available(0); } @@ -279,6 +311,13 @@ void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); // Invoked when a TX is complete and therefore space becomes available in TX buffer void tud_cdc_tx_complete_cb(uint8_t itf); +// Invoked when a direct-mode transfer completes. The buffer is the exact +// caller-owned pointer submitted to tud_cdc_n_direct_write/read(). +void tud_cdc_direct_tx_complete_cb(uint8_t itf, const void* buffer, uint32_t xferred_bytes, + xfer_result_t result); +void tud_cdc_direct_rx_complete_cb(uint8_t itf, void* buffer, uint32_t xferred_bytes, + xfer_result_t result); + // Invoked when a notification is sent to host void tud_cdc_notify_complete_cb(uint8_t itf); diff --git a/src/portable/microchip/samd/dcd_samd.c b/src/portable/microchip/samd/dcd_samd.c index cdfb00e3..0066e38a 100644 --- a/src/portable/microchip/samd/dcd_samd.c +++ b/src/portable/microchip/samd/dcd_samd.c @@ -28,7 +28,8 @@ #if CFG_TUD_ENABLED && TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X, OPT_MCU_SAMD51, OPT_MCU_SAME5X) -#include "sam.h" +#include +#include "same5x_usb.h" #include "device/dcd.h" /*------------------------------------------------------------------*/ @@ -103,7 +104,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } -#if TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) +#if TU_CHECK_MCU(OPT_MCU_SAMD51) void dcd_int_enable(uint8_t rhport) { (void) rhport; NVIC_EnableIRQ(USB_0_IRQn); @@ -120,6 +121,23 @@ void dcd_int_disable(uint8_t rhport) { NVIC_DisableIRQ(USB_0_IRQn); } +#elif TU_CHECK_MCU(OPT_MCU_SAME5X) +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USB_OTHER_IRQn); + NVIC_EnableIRQ(USB_SOF_HSOF_IRQn); + NVIC_EnableIRQ(USB_TRCPT0_IRQn); + NVIC_EnableIRQ(USB_TRCPT1_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USB_TRCPT1_IRQn); + NVIC_DisableIRQ(USB_TRCPT0_IRQn); + NVIC_DisableIRQ(USB_SOF_HSOF_IRQn); + NVIC_DisableIRQ(USB_OTHER_IRQn); +} + #elif TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X) void dcd_int_enable(uint8_t rhport) { (void) rhport; @@ -348,9 +366,12 @@ static void maybe_transfer_complete(void) { UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_IN]; uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; - dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, total_transfer_size, XFER_RESULT_SUCCESS, true); - + // SAME5x exposes transfer completion through split IRQ vectors. Clear + // the hardware source before publishing the software event so another + // pending USB vector cannot report the same bank completion twice. ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1; + + dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, total_transfer_size, XFER_RESULT_SUCCESS, true); } // Handle OUT completions @@ -358,9 +379,11 @@ static void maybe_transfer_complete(void) { UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_OUT]; uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; - dcd_event_xfer_complete(0, epnum, total_transfer_size, XFER_RESULT_SUCCESS, true); - + // See the IN path above. OUT direct transfers rely on exactly one + // completion to retire direct_rx_active and return the caller buffer. ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0; + + dcd_event_xfer_complete(0, epnum, total_transfer_size, XFER_RESULT_SUCCESS, true); } } } diff --git a/src/portable/microchip/samd/hcd_samd.c b/src/portable/microchip/samd/hcd_samd.c index 0de7ddeb..26725acd 100644 --- a/src/portable/microchip/samd/hcd_samd.c +++ b/src/portable/microchip/samd/hcd_samd.c @@ -30,7 +30,8 @@ TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X, OPT_MCU_SAMD51, OPT_MCU_SAME5X) #include "host/hcd.h" -#include "sam.h" +#include +#include "same5x_usb.h" /*------------------------------------------------------------------*/ /* MACRO TYPEDEF CONSTANT ENUM @@ -425,7 +426,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } -#if TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) +#if TU_CHECK_MCU(OPT_MCU_SAMD51) // Enable USB interrupt void hcd_int_enable(uint8_t rhport) @@ -447,6 +448,28 @@ void hcd_int_disable(uint8_t rhport) NVIC_DisableIRQ(USB_0_IRQn); } +#elif TU_CHECK_MCU(OPT_MCU_SAME5X) + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) +{ + (void) rhport; + NVIC_EnableIRQ(USB_OTHER_IRQn); + NVIC_EnableIRQ(USB_SOF_HSOF_IRQn); + NVIC_EnableIRQ(USB_TRCPT0_IRQn); + NVIC_EnableIRQ(USB_TRCPT1_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) +{ + (void) rhport; + NVIC_DisableIRQ(USB_TRCPT1_IRQn); + NVIC_DisableIRQ(USB_TRCPT0_IRQn); + NVIC_DisableIRQ(USB_SOF_HSOF_IRQn); + NVIC_DisableIRQ(USB_OTHER_IRQn); +} + #elif TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X) // Enable USB interrupt diff --git a/src/portable/microchip/samd/same5x_usb.h b/src/portable/microchip/samd/same5x_usb.h new file mode 100644 index 00000000..5dcb9f74 --- /dev/null +++ b/src/portable/microchip/samd/same5x_usb.h @@ -0,0 +1,84 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Chris Rabel + * + * 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. + */ + +#ifndef _SAME5X_USB_H_ +#define _SAME5X_USB_H_ + +#if defined(__GNUC__) +#pragma GCC system_header +#endif +#include "tusb_option.h" + +#if TU_CHECK_MCU(OPT_MCU_SAME5X) + +#ifndef _Ul +#define _Ul(x) x##U +#endif + +#ifndef TUSB_SAME5X_COMPAT_ROREG8 +#define TUSB_SAME5X_COMPAT_ROREG8 1 +typedef volatile const uint8_t RoReg8; +#endif + +#if defined(__has_include) +#if __has_include("samd51/include/component/usb.h") +#include "samd51/include/component/usb.h" +#else +#error "SAME5x USB support requires SAMD51 USB component headers on the include path." +#endif +#else +#include "samd51/include/component/usb.h" +#endif + +#if !defined(NVMCTRL_SW0) && defined(SW0_ADDR) +#define NVMCTRL_SW0 SW0_ADDR +#elif !defined(NVMCTRL_SW0) && defined(SW0_FUSES_BASE_ADDRESS) +#define NVMCTRL_SW0 SW0_FUSES_BASE_ADDRESS +#endif + +#ifndef USB_FUSES_TRANSN_ADDR +#define USB_FUSES_TRANSN_ADDR (NVMCTRL_SW0 + 4) +#define USB_FUSES_TRANSN_Pos 0 +#define USB_FUSES_TRANSN_Msk (_Ul(0x1F) << USB_FUSES_TRANSN_Pos) +#endif + +#ifndef USB_FUSES_TRANSP_ADDR +#define USB_FUSES_TRANSP_ADDR (NVMCTRL_SW0 + 4) +#define USB_FUSES_TRANSP_Pos 5 +#define USB_FUSES_TRANSP_Msk (_Ul(0x1F) << USB_FUSES_TRANSP_Pos) +#endif + +#ifndef USB_FUSES_TRIM_ADDR +#define USB_FUSES_TRIM_ADDR (NVMCTRL_SW0 + 4) +#define USB_FUSES_TRIM_Pos 10 +#define USB_FUSES_TRIM_Msk (_Ul(0x7) << USB_FUSES_TRIM_Pos) +#endif + +#if !defined(USB) && defined(USB_REGS) +#define USB ((Usb *)USB_REGS) +#endif + +#endif + +#endif