summaryrefslogtreecommitdiff
path: root/src/portable/nxp
diff options
context:
space:
mode:
Diffstat (limited to 'src/portable/nxp')
-rw-r--r--src/portable/nxp/khci/dcd_khci.c560
-rw-r--r--src/portable/nxp/khci/hcd_khci.c628
-rw-r--r--src/portable/nxp/lpc17_40/dcd_lpc17_40.c281
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c189
4 files changed, 391 insertions, 1267 deletions
diff --git a/src/portable/nxp/khci/dcd_khci.c b/src/portable/nxp/khci/dcd_khci.c
deleted file mode 100644
index 9f61bd236..000000000
--- a/src/portable/nxp/khci/dcd_khci.c
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2020 Koji Kitayama
- * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if CFG_TUD_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS)
-
-#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS
- #include "fsl_device_registers.h"
- #define KHCI USB0
-#else
- #error "MCU is not supported"
-#endif
-
-#include "device/dcd.h"
-
-//--------------------------------------------------------------------+
-// MACRO TYPEDEF CONSTANT ENUM DECLARATION
-//--------------------------------------------------------------------+
-
-enum {
- TOK_PID_OUT = 0x1u,
- TOK_PID_IN = 0x9u,
- TOK_PID_SETUP = 0xDu,
-};
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t head;
- struct {
- union {
- struct {
- uint16_t : 2;
- __IO uint16_t tok_pid : 4;
- uint16_t data : 1;
- __IO uint16_t own : 1;
- uint16_t : 8;
- };
- struct {
- uint16_t : 2;
- uint16_t bdt_stall : 1;
- uint16_t dts : 1;
- uint16_t ninc : 1;
- uint16_t keep : 1;
- uint16_t : 10;
- };
- };
- __IO uint16_t bc : 10;
- uint16_t : 6;
- };
- };
- uint8_t *addr;
-}buffer_descriptor_t;
-
-TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t state;
- struct {
- uint32_t max_packet_size :11;
- uint32_t : 5;
- uint32_t odd : 1;
- uint32_t :15;
- };
- };
- uint16_t length;
- uint16_t remaining;
-}endpoint_state_t;
-
-TU_VERIFY_STATIC( sizeof(endpoint_state_t) == 8, "size is not correct" );
-
-typedef struct
-{
- union {
- /* [#EP][OUT,IN][EVEN,ODD] */
- buffer_descriptor_t bdt[16][2][2];
- uint16_t bda[512];
- };
- TU_ATTR_ALIGNED(4) union {
- endpoint_state_t endpoint[16][2];
- endpoint_state_t endpoint_unified[16 * 2];
- };
- uint8_t setup_packet[8];
- uint8_t addr;
-}dcd_data_t;
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-// BDT(Buffer Descriptor Table) must be 256-byte aligned
-CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(512) static dcd_data_t _dcd;
-
-TU_VERIFY_STATIC( sizeof(_dcd.bdt) == 512, "size is not correct" );
-
-static void prepare_next_setup_packet(uint8_t rhport)
-{
- const unsigned out_odd = _dcd.endpoint[0][0].odd;
- const unsigned in_odd = _dcd.endpoint[0][1].odd;
- TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, );
-
- _dcd.bdt[0][0][out_odd].data = 0;
- _dcd.bdt[0][0][out_odd ^ 1].data = 1;
- _dcd.bdt[0][1][in_odd].data = 1;
- _dcd.bdt[0][1][in_odd ^ 1].data = 0;
- dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT),
- _dcd.setup_packet, sizeof(_dcd.setup_packet), false);
-}
-
-static void process_stall(uint8_t rhport)
-{
- for (int i = 0; i < 16; ++i) {
- unsigned const endpt = KHCI->ENDPOINT[i].ENDPT;
-
- if (endpt & USB_ENDPT_EPSTALL_MASK) {
- // prepare next setup if endpoint0
- if ( i == 0 ) prepare_next_setup_packet(rhport);
-
- // clear stall bit
- KHCI->ENDPOINT[i].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK;
- }
- }
-}
-
-static void process_tokdne(uint8_t rhport)
-{
- const unsigned s = KHCI->STAT;
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
-
- uint8_t const epnum = (s >> USB_STAT_ENDP_SHIFT);
- uint8_t const dir = (s & USB_STAT_TX_MASK) >> USB_STAT_TX_SHIFT;
- unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0;
-
- buffer_descriptor_t *bd = (buffer_descriptor_t *)&_dcd.bda[s];
- endpoint_state_t *ep = &_dcd.endpoint_unified[s >> 3];
-
- /* fetch pid before discarded by the next steps */
- const unsigned pid = bd->tok_pid;
-
- /* reset values for a next transfer */
- bd->bdt_stall = 0;
- bd->dts = 1;
- bd->ninc = 0;
- bd->keep = 0;
- /* update the odd variable to prepare for the next transfer */
- ep->odd = odd ^ 1;
- if (pid == TOK_PID_SETUP) {
- dcd_event_setup_received(rhport, bd->addr, true);
- KHCI->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK;
- return;
- }
-
- const unsigned bc = bd->bc;
- const unsigned remaining = ep->remaining - bc;
- if (remaining && bc == ep->max_packet_size) {
- /* continue the transferring consecutive data */
- ep->remaining = remaining;
- const int next_remaining = remaining - ep->max_packet_size;
- if (next_remaining > 0) {
- /* prepare to the after next transfer */
- bd->addr += ep->max_packet_size * 2;
- bd->bc = next_remaining > ep->max_packet_size ? ep->max_packet_size: next_remaining;
- __DSB();
- bd->own = 1; /* the own bit must set after addr */
- }
- return;
- }
- const unsigned length = ep->length;
- dcd_event_xfer_complete(rhport,
- tu_edpt_addr(epnum, dir),
- length - remaining, XFER_RESULT_SUCCESS, true);
- if (0 == epnum && 0 == length) {
- /* After completion a ZLP of control transfer,
- * it prepares for the next steup transfer. */
- if (_dcd.addr) {
- /* When the transfer was the SetAddress,
- * the device address should be updated here. */
- KHCI->ADDR = _dcd.addr;
- _dcd.addr = 0;
- }
- prepare_next_setup_packet(rhport);
- }
-}
-
-static void process_bus_reset(uint8_t rhport)
-{
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL |= USB_CTL_ODDRST_MASK;
- KHCI->ADDR = 0;
- KHCI->INTEN = USB_INTEN_USBRSTEN_MASK | USB_INTEN_TOKDNEEN_MASK | USB_INTEN_SLEEPEN_MASK |
- USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK;
-
- KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
- for (unsigned i = 1; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
- }
- buffer_descriptor_t *bd = _dcd.bdt[0][0];
- for (unsigned i = 0; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) {
- bd->head = 0;
- }
- const endpoint_state_t ep0 = {
- .max_packet_size = CFG_TUD_ENDPOINT0_SIZE,
- .odd = 0,
- .length = 0,
- .remaining = 0,
- };
- _dcd.endpoint[0][0] = ep0;
- _dcd.endpoint[0][1] = ep0;
- tu_memclr(_dcd.endpoint[1], sizeof(_dcd.endpoint) - sizeof(_dcd.endpoint[0]));
- _dcd.addr = 0;
- prepare_next_setup_packet(rhport);
- KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
- dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true);
-}
-
-static void process_bus_sleep(uint8_t rhport)
-{
- // Enable resume & disable suspend interrupt
- const unsigned inten = KHCI->INTEN;
-
- KHCI->INTEN = (inten & ~USB_INTEN_SLEEPEN_MASK) | USB_INTEN_RESUMEEN_MASK;
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESMEN_MASK;
- KHCI->USBCTRL |= USB_USBCTRL_SUSP_MASK;
-
- dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
-}
-
-static void process_bus_resume(uint8_t rhport)
-{
- // Enable suspend & disable resume interrupt
- const unsigned inten = KHCI->INTEN;
-
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; // will also clear USB_USBTRC0_USB_RESUME_INT_MASK
- KHCI->USBTRC0 &= ~USB_USBTRC0_USBRESMEN_MASK;
- KHCI->INTEN = (inten & ~USB_INTEN_RESUMEEN_MASK) | USB_INTEN_SLEEPEN_MASK;
-
- dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true);
-}
-
-/*------------------------------------------------------------------*/
-/* Device API
- *------------------------------------------------------------------*/
-bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
-
- // save crystal-less setting (if available)
- #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1
- uint32_t clk_recover_irc_en = KHCI->CLK_RECOVER_IRC_EN;
- uint32_t clk_recover_ctrl = KHCI->CLK_RECOVER_CTRL;
- #endif
-
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
- while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
-
- // restore crystal-less setting (if available)
- #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1
- KHCI->CLK_RECOVER_IRC_EN = clk_recover_irc_en;
- KHCI->CLK_RECOVER_CTRL |= clk_recover_ctrl;
- #endif
-
- tu_memclr(&_dcd, sizeof(_dcd));
- KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
- KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_dcd.bdt >> 8);
- KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_dcd.bdt >> 16);
- KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_dcd.bdt >> 24);
-
- KHCI->INTEN = USB_INTEN_USBRSTEN_MASK;
-
- dcd_connect(rhport);
- NVIC_ClearPendingIRQ(USB0_IRQn);
-
- return true;
-}
-
-void dcd_int_enable(uint8_t rhport)
-{
- (void) rhport;
- NVIC_EnableIRQ(USB0_IRQn);
-}
-
-void dcd_int_disable(uint8_t rhport)
-{
- (void) rhport;
- NVIC_DisableIRQ(USB0_IRQn);
-}
-
-void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
-{
- _dcd.addr = dev_addr & 0x7F;
- /* Response with status first before changing device address */
- dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false);
-}
-
-void dcd_remote_wakeup(uint8_t rhport)
-{
- (void) rhport;
-
- KHCI->CTL |= USB_CTL_RESUME_MASK;
-
- unsigned cnt = SystemCoreClock / 1000;
- while (cnt--) __NOP();
-
- KHCI->CTL &= ~USB_CTL_RESUME_MASK;
-}
-
-void dcd_connect(uint8_t rhport)
-{
- (void) rhport;
- KHCI->USBCTRL = 0;
- KHCI->CONTROL |= USB_CONTROL_DPPULLUPNONOTG_MASK;
- KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
-}
-
-void dcd_disconnect(uint8_t rhport)
-{
- (void) rhport;
- KHCI->CTL = 0;
- KHCI->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK;
-}
-
-void dcd_sof_enable(uint8_t rhport, bool en)
-{
- (void) rhport;
- (void) en;
-
- // TODO implement later
-}
-
-//--------------------------------------------------------------------+
-// Endpoint API
-//--------------------------------------------------------------------+
-static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size, tusb_xfer_type_t xfer) {
- (void)rhport;
-
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir = tu_edpt_dir(ep_addr);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
- const unsigned odd = ep->odd;
- buffer_descriptor_t *bd = _dcd.bdt[epn][dir];
-
- /* No support for control transfer */
- TU_ASSERT(epn && (xfer != TUSB_XFER_CONTROL));
-
- ep->max_packet_size = max_packet_size;
- unsigned val = USB_ENDPT_EPCTLDIS_MASK;
- val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0;
- val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK;
- KHCI->ENDPOINT[epn].ENDPT |= val;
-
- if (xfer != TUSB_XFER_ISOCHRONOUS) {
- bd[odd].dts = 1;
- bd[odd].data = 0;
- bd[odd ^ 1].dts = 1;
- bd[odd ^ 1].data = 1;
- }
-
- return true;
-}
-
-bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) {
- return edpt_open(rhport, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer);
-}
-
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- return edpt_open(rhport, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS);
-}
-
-bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) {
- const unsigned epn = tu_edpt_number(ep_desc->bEndpointAddress);
- const unsigned dir = tu_edpt_dir(ep_desc->bEndpointAddress);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
-
- dcd_int_disable(rhport);
- ep->max_packet_size = tu_edpt_packet_size(ep_desc);
- dcd_int_enable(rhport);
-
- return true;
-}
-
-void dcd_edpt_close_all(uint8_t rhport)
-{
- (void) rhport;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- for (unsigned i = 1; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
- }
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- buffer_descriptor_t *bd = _dcd.bdt[1][0];
- for (unsigned i = 2; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) {
- bd->head = 0;
- }
- endpoint_state_t *ep = &_dcd.endpoint[1][0];
- for (unsigned i = 2; i < sizeof(_dcd.endpoint)/sizeof(*ep); ++i, ++ep) {
- /* Clear except the odd */
- ep->max_packet_size = 0;
- ep->length = 0;
- ep->remaining = 0;
- }
-}
-
-bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
-{
- (void) rhport;
- (void) is_isr;
- const unsigned epn = tu_edpt_number(ep_addr);
- const unsigned dir = tu_edpt_dir(ep_addr);
- endpoint_state_t *ep = &_dcd.endpoint[epn][dir];
- buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][ep->odd];
- TU_ASSERT(0 == bd->own);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- ep->length = total_bytes;
- ep->remaining = total_bytes;
-
- const unsigned mps = ep->max_packet_size;
- if (total_bytes > mps) {
- buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1;
- /* When total_bytes is greater than the max packet size,
- * it prepares to the next transfer to avoid NAK in advance. */
- next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps;
- next->addr = buffer + mps;
- next->own = 1;
- }
- bd->bc = total_bytes >= mps ? mps: total_bytes;
- bd->addr = buffer;
- __DSB();
- bd->own = 1; /* This bit must be set last */
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- return true;
-}
-
-void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- const unsigned epn = tu_edpt_number(ep_addr);
-
- if (0 == epn) {
- KHCI->ENDPOINT[epn].ENDPT |= USB_ENDPT_EPSTALL_MASK;
- } else {
- const unsigned dir = tu_edpt_dir(ep_addr);
- const unsigned odd = _dcd.endpoint[epn][dir].odd;
- buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][odd];
- TU_ASSERT(0 == bd->own,);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- bd->bdt_stall = 1;
- __DSB();
- bd->own = 1; /* This bit must be set last */
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- }
-}
-
-void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
- const unsigned epn = tu_edpt_number(ep_addr);
- TU_VERIFY(epn,);
- const unsigned dir = tu_edpt_dir(ep_addr);
- const unsigned odd = _dcd.endpoint[epn][dir].odd;
- buffer_descriptor_t *bd = _dcd.bdt[epn][dir];
- TU_VERIFY(bd[odd].own,);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
-
- bd[odd].own = 0;
- __DSB();
-
- // clear stall
- bd[odd].bdt_stall = 0;
-
- // Reset data toggle
- bd[odd ].data = 0;
- bd[odd ^ 1].data = 1;
-
- // We already cleared this in ISR, but just clear it here to be safe
- const unsigned endpt = KHCI->ENDPOINT[epn].ENDPT;
- if (endpt & USB_ENDPT_EPSTALL_MASK) {
- KHCI->ENDPOINT[epn].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK;
- }
-
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
-}
-
-//--------------------------------------------------------------------+
-// ISR
-//--------------------------------------------------------------------+
-void dcd_int_handler(uint8_t rhport)
-{
- uint32_t is = KHCI->ISTAT;
- uint32_t msk = KHCI->INTEN;
-
- // clear non-enabled interrupts
- KHCI->ISTAT = is & ~msk;
- is &= msk;
-
- if (is & USB_ISTAT_ERROR_MASK) {
- /* TODO: */
- uint32_t es = KHCI->ERRSTAT;
- KHCI->ERRSTAT = es;
- KHCI->ISTAT = is; /* discard any pending events */
- }
-
- if (is & USB_ISTAT_USBRST_MASK) {
- KHCI->ISTAT = is; /* discard any pending events */
- process_bus_reset(rhport);
- }
-
- if (is & USB_ISTAT_SLEEP_MASK) {
- // TU_LOG3("Suspend: "); TU_LOG2_HEX(is);
-
- // Note Host usually has extra delay after bus reset (without SOF), which could falsely
- // detected as Sleep event. Though usbd has debouncing logic so we are good
- KHCI->ISTAT = USB_ISTAT_SLEEP_MASK;
- process_bus_sleep(rhport);
- }
-
-#if 0 // ISTAT_RESUME never trigger, probably for host mode ?
- if (is & USB_ISTAT_RESUME_MASK) {
- // TU_LOG2("ISTAT Resume: "); TU_LOG2_HEX(is);
- KHCI->ISTAT = USB_ISTAT_RESUME_MASK;
- process_bus_resume(rhport);
- }
-#endif
-
- if (KHCI->USBTRC0 & USB_USBTRC0_USB_RESUME_INT_MASK) {
- // TU_LOG2("USBTRC0 Resume: "); TU_LOG2_HEX(is); TU_LOG2_HEX(KHCI->USBTRC0);
- process_bus_resume(rhport);
- }
-
- if (is & USB_ISTAT_SOFTOK_MASK) {
- KHCI->ISTAT = USB_ISTAT_SOFTOK_MASK;
- dcd_event_sof(rhport, tu_u16(KHCI->FRMNUMH, KHCI->FRMNUML), true);
- }
-
- if (is & USB_ISTAT_STALL_MASK) {
- KHCI->ISTAT = USB_ISTAT_STALL_MASK;
- process_stall(rhport);
- }
-
- if (is & USB_ISTAT_TOKDNE_MASK) {
- process_tokdne(rhport);
- }
-}
-#endif
diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/nxp/khci/hcd_khci.c
deleted file mode 100644
index 209940656..000000000
--- a/src/portable/nxp/khci/hcd_khci.c
+++ /dev/null
@@ -1,628 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama
- * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org)
- * SPDX-License-Identifier: MIT
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS)
-
-#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS
- #include "fsl_device_registers.h"
- #define KHCI USB0
-#else
- #error "MCU is not supported"
-#endif
-
-#include "host/hcd.h"
-#include "host/usbh.h"
-
-//--------------------------------------------------------------------+
-// MACRO TYPEDEF CONSTANT ENUM DECLARATION
-//--------------------------------------------------------------------+
-
-enum {
- TOK_PID_OUT = 0x1u,
- TOK_PID_IN = 0x9u,
- TOK_PID_SETUP = 0xDu,
- TOK_PID_DATA0 = 0x3u,
- TOK_PID_DATA1 = 0xbu,
- TOK_PID_ACK = 0x2u,
- TOK_PID_STALL = 0xeu,
- TOK_PID_NAK = 0xau,
- TOK_PID_BUSTO = 0x0u,
- TOK_PID_ERR = 0xfu,
-};
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t head;
- struct {
- union {
- struct {
- uint16_t : 2;
- __IO uint16_t tok_pid : 4;
- uint16_t data : 1;
- __IO uint16_t own : 1;
- uint16_t : 8;
- };
- struct {
- uint16_t : 2;
- uint16_t bdt_stall : 1;
- uint16_t dts : 1;
- uint16_t ninc : 1;
- uint16_t keep : 1;
- uint16_t : 10;
- };
- };
- __IO uint16_t bc : 10;
- uint16_t : 6;
- };
- };
- uint8_t *addr;
-}buffer_descriptor_t;
-
-TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" );
-
-typedef struct TU_ATTR_PACKED
-{
- union {
- uint32_t state;
- struct {
- uint32_t pipenum:16;
- uint32_t odd : 1;
- uint32_t : 0;
- };
- };
- uint8_t *buffer;
- uint16_t length;
- uint16_t remaining;
-} endpoint_state_t;
-
-typedef struct TU_ATTR_PACKED
-{
- uint8_t dev_addr;
- uint8_t ep_addr;
- uint16_t max_packet_size;
- union {
- uint8_t flags;
- struct {
- uint8_t data : 1;
- uint8_t xfer : 2;
- uint8_t : 0;
- };
- };
- uint8_t *buffer;
- uint16_t length;
- uint16_t remaining;
-} pipe_state_t;
-
-
-typedef struct
-{
- union {
- /* [OUT,IN][EVEN,ODD] */
- buffer_descriptor_t bdt[2][2];
- uint16_t bda[2*2];
- };
- endpoint_state_t endpoint[2];
- pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2];
- uint32_t in_progress; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe is in progress */
- uint32_t pending; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe will be resume the next frame */
- bool need_reset; /* The device has not been reset after connection. */
-} hcd_data_t;
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-// BDT(Buffer Descriptor Table) must be 256-byte aligned
-CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd;
-//CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024];
-
-static int find_pipe(uint8_t dev_addr, uint8_t ep_addr)
-{
- /* Find the target pipe */
- int num;
- for (num = 0; num < CFG_TUH_ENDPOINT_MAX * 2; ++num) {
- pipe_state_t *p = &_hcd.pipe[num];
- if ((p->dev_addr == dev_addr) && (p->ep_addr == ep_addr))
- return num;
- }
- return -1;
-}
-
-static int prepare_packets(int pipenum)
-{
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
- unsigned const dir_tx = tu_edpt_dir(pipe->ep_addr) ? 0 : 1;
- endpoint_state_t *ep = &_hcd.endpoint[dir_tx];
- unsigned const odd = ep->odd;
- buffer_descriptor_t *bd = _hcd.bdt[dir_tx];
- TU_ASSERT(0 == bd[odd].own, -1);
-
- // TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data);
-
- ep->pipenum = pipenum;
-
- bd[odd ].data = pipe->data;
- bd[odd ^ 1].data = pipe->data ^ 1;
- bd[odd ^ 1].own = 0;
- /* reset values for a next transfer */
-
- int num_tokens = 0; /* The number of prepared packets */
- unsigned const mps = pipe->max_packet_size;
- unsigned const rem = pipe->remaining;
- if (rem > mps) {
- /* When total_bytes is greater than the max packet size,
- * it prepares to the next transfer to avoid NAK in advance. */
- bd[odd ^ 1].bc = rem >= 2 * mps ? mps: rem - mps;
- bd[odd ^ 1].addr = pipe->buffer + mps;
- bd[odd ^ 1].own = 1;
- if (dir_tx) ++num_tokens;
- }
- bd[odd].bc = rem >= mps ? mps: rem;
- bd[odd].addr = pipe->buffer;
- __DSB();
- bd[odd].own = 1; /* This bit must be set last */
- ++num_tokens;
- return num_tokens;
-}
-
-static int select_next_pipenum(int pipenum)
-{
- unsigned wip = _hcd.in_progress & ~_hcd.pending;
- if (!wip) return -1;
- unsigned msk = TU_GENMASK(31, pipenum);
- int next = __builtin_ctz(wip & msk);
- if (next) return next;
- msk = TU_GENMASK(pipenum, 0);
- next = __builtin_ctz(wip & msk);
- return next;
-}
-
-/* When transfer is completed, return true. */
-static bool continue_transfer(int pipenum, buffer_descriptor_t *bd)
-{
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
- unsigned const bc = bd->bc;
- unsigned const rem = pipe->remaining - bc;
-
- pipe->remaining = rem;
- if (rem && bc == pipe->max_packet_size) {
- int const next_rem = rem - pipe->max_packet_size;
- if (next_rem > 0) {
- /* Prepare to the after next transfer */
- bd->addr += pipe->max_packet_size * 2;
- bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem;
- __DSB();
- bd->own = 1; /* This bit must be set last */
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = KHCI->TOKEN; /* Queue the same token as the last */
- } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = KHCI->TOKEN;
- }
- return true;
- }
- pipe->data = bd->data ^ 1;
- return false;
-}
-
-static bool resume_transfer(int pipenum)
-{
- int num_tokens = prepare_packets(pipenum);
- TU_ASSERT(0 <= num_tokens);
-
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
-
- unsigned flags = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
- flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
- switch (pipe->xfer) {
- case TUSB_XFER_CONTROL:
- flags |= USB_ENDPT_EPHSHK_MASK;
- break;
- case TUSB_XFER_ISOCHRONOUS:
- flags |= USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK;
- break;
- default:
- flags |= USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK;
- break;
- }
- // TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags);
-
- KHCI->ENDPOINT[0].ENDPT = flags;
- KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr;
-
- unsigned const token = tu_edpt_number(pipe->ep_addr) |
- ((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT);
- do {
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = token;
- } while (--num_tokens);
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- return true;
-}
-
-static void suspend_transfer(int pipenum, buffer_descriptor_t *bd)
-{
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
- pipe->buffer = bd->addr;
- pipe->data = bd->data ^ 1;
- if ((TUSB_XFER_INTERRUPT == pipe->xfer) ||
- (TUSB_XFER_BULK == pipe->xfer)) {
- _hcd.pending |= TU_BIT(pipenum);
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
- }
-}
-
-static void process_tokdne(uint8_t rhport)
-{
- (void)rhport;
- const unsigned s = KHCI->STAT;
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
- uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN;
- unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0;
-
- buffer_descriptor_t *bd = (buffer_descriptor_t *)&_hcd.bda[s];
- endpoint_state_t *ep = &_hcd.endpoint[s >> 3];
-
- /* fetch status before discarded by the next steps */
- const unsigned pid = bd->tok_pid;
-
- /* reset values for a next transfer */
- bd->bdt_stall = 0;
- bd->dts = 1;
- bd->ninc = 0;
- bd->keep = 0;
- /* Update the odd variable to prepare for the next transfer */
- ep->odd = odd ^ 1;
-
- int pipenum = ep->pipenum;
- int next_pipenum;
- // TU_LOG1("TOKDNE %x PID %x pipe %d\r\n", s, pid, pipenum);
-
- xfer_result_t result;
- switch (pid) {
- default:
- if (continue_transfer(pipenum, bd))
- return;
- result = XFER_RESULT_SUCCESS;
- break;
- case TOK_PID_NAK:
- suspend_transfer(pipenum, bd);
- next_pipenum = select_next_pipenum(pipenum);
- if (0 <= next_pipenum)
- resume_transfer(next_pipenum);
- return;
- case TOK_PID_STALL:
- result = XFER_RESULT_STALLED;
- break;
- case TOK_PID_ERR: /* mismatch toggle bit */
- case TOK_PID_BUSTO:
- result = XFER_RESULT_FAILED;
- break;
- }
- _hcd.in_progress &= ~TU_BIT(pipenum);
- pipe_state_t *pipe = &_hcd.pipe[ep->pipenum];
- hcd_event_xfer_complete(pipe->dev_addr,
- tu_edpt_addr(KHCI->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in),
- pipe->length - pipe->remaining,
- result, true);
- next_pipenum = select_next_pipenum(pipenum);
- if (0 <= next_pipenum)
- resume_transfer(next_pipenum);
-}
-
-static void process_attach(uint8_t rhport)
-{
- unsigned ctl = KHCI->CTL;
- if (!(ctl & USB_CTL_JSTATE_MASK)) {
- /* The attached device is a low speed device. */
- KHCI->ADDR = USB_ADDR_LSEN_MASK;
- KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_HOSTWOHUB_MASK;
- }
- hcd_event_device_attach(rhport, true);
-}
-
-static void process_bus_reset(uint8_t rhport)
-{
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK;
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
- KHCI->ADDR = 0;
- KHCI->ENDPOINT[0].ENDPT = 0;
-
- hcd_event_device_remove(rhport, true);
-
- _hcd.in_progress = 0;
- _hcd.pending = 0;
- buffer_descriptor_t *bd = &_hcd.bdt[0][0];
- for (unsigned i = 0; i < 2; ++i, ++bd) {
- bd->head = 0;
- }
-}
-
-/*------------------------------------------------------------------*/
-/* Host API
- *------------------------------------------------------------------*/
-bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
- (void) rhport;
- (void) rh_init;
- KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
- while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
-
- tu_memclr(&_hcd, sizeof(_hcd));
- KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
- KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8);
- KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16);
- KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24);
-
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL |= USB_CTL_ODDRST_MASK;
- for (unsigned i = 0; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
- }
- KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
-
- KHCI->SOFTHLD = 74; /* for 64-byte packets */
- // KHCI->SOFTHLD = 144; /* for low speed 8-byte packets */
- KHCI->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK;
- KHCI->USBCTRL = USB_USBCTRL_PDE_MASK;
-
- NVIC_ClearPendingIRQ(USB0_IRQn);
- KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK |
- USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK;
- KHCI->ERREN = 0xff;
-
- return true;
-}
-
-void hcd_int_enable(uint8_t rhport)
-{
- (void)rhport;
- NVIC_EnableIRQ(USB0_IRQn);
-}
-
-void hcd_int_disable(uint8_t rhport)
-{
- (void)rhport;
- NVIC_DisableIRQ(USB0_IRQn);
-}
-
-uint32_t hcd_frame_number(uint8_t rhport)
-{
- (void)rhport;
- /* The device must be reset at least once after connection
- * in order to start the frame counter. */
- if (_hcd.need_reset) hcd_port_reset(rhport);
- uint32_t frmnum = KHCI->FRMNUML;
- frmnum |= KHCI->FRMNUMH << 8u;
- return frmnum;
-}
-
-/*--------------------------------------------------------------------+
- * Port API
- *--------------------------------------------------------------------+ */
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void)rhport;
- if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK)
- return true;
- return false;
-}
-
-void hcd_port_reset(uint8_t rhport)
-{
- (void)rhport;
- KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
- KHCI->CTL |= USB_CTL_RESET_MASK;
- unsigned cnt = SystemCoreClock / 100;
- while (cnt--) __NOP();
- KHCI->CTL &= ~USB_CTL_RESET_MASK;
- KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
- _hcd.need_reset = false;
-}
-
-void hcd_port_reset_end(uint8_t rhport) {
- (void) rhport;
-}
-
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void)rhport;
- tusb_speed_t speed = TUSB_SPEED_FULL;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- if (KHCI->ADDR & USB_ADDR_LSEN_MASK)
- speed = TUSB_SPEED_LOW;
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- return speed;
-}
-
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
-{
- (void)rhport;
- const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- pipe_state_t *p = &_hcd.pipe[0];
- pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2];
- for (;p != end; ++p) {
- if (p->dev_addr == dev_addr)
- tu_memclr(p, sizeof(*p));
- }
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
-}
-
-//--------------------------------------------------------------------+
-// Endpoints API
-//--------------------------------------------------------------------+
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void)rhport;
- // TU_LOG1("SETUP %u\r\n", dev_addr);
- TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(0)));
-
- int pipenum = find_pipe(dev_addr, 0);
- if (pipenum < 0) return false;
-
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
- pipe[0].data = 0;
- pipe[0].buffer = (uint8_t*)(uintptr_t)setup_packet;
- pipe[0].length = 8;
- pipe[0].remaining = 8;
- pipe[1].data = 1;
-
- if (1 != prepare_packets(pipenum))
- return false;
-
- _hcd.in_progress |= TU_BIT(pipenum);
-
- unsigned hostwohub = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
- KHCI->ENDPOINT[0].ENDPT = hostwohub |
- USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
- KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | dev_addr;
- while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT);
- return true;
-}
-
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- (void)rhport;
- uint8_t const ep_addr = ep_desc->bEndpointAddress;
- // TU_LOG1("O %u %x\r\n", dev_addr, ep_addr);
- /* Find a free pipe */
- pipe_state_t *p = &_hcd.pipe[0];
- pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2];
- if (dev_addr || ep_addr) {
- p += 2;
- for (; p < end && (p->dev_addr || p->ep_addr); ++p) ;
- if (p == end) return false;
- }
- p->dev_addr = dev_addr;
- p->ep_addr = ep_addr;
- p->max_packet_size = ep_desc->wMaxPacketSize;
- p->xfer = ep_desc->bmAttributes.xfer;
- p->data = 0;
- if (!ep_addr) {
- /* Open one more pipe for Control IN transfer */
- TU_ASSERT(TUSB_XFER_CONTROL == p->xfer);
- pipe_state_t *q = p + 1;
- TU_ASSERT(!q->dev_addr && !q->ep_addr);
- q->dev_addr = dev_addr;
- q->ep_addr = tu_edpt_addr(0, TUSB_DIR_IN);
- q->max_packet_size = ep_desc->wMaxPacketSize;
- q->xfer = ep_desc->bmAttributes.xfer;
- q->data = 1;
- }
- return true;
-}
-
-bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) {
- (void) rhport; (void) daddr; (void) ep_addr;
- return false; // TODO not implemented yet
-}
-
-/* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long.
- * DMA writes data in 4 byte unit */
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
-{
- (void)rhport;
- // TU_LOG1("X %u %x %x %d\r\n", dev_addr, ep_addr, (uintptr_t)buffer, buflen);
-
- int pipenum = find_pipe(dev_addr, ep_addr);
- TU_ASSERT(0 <= pipenum);
-
- TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum)));
- unsigned const ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
- pipe_state_t *pipe = &_hcd.pipe[pipenum];
- pipe->buffer = buffer;
- pipe->length = buflen;
- pipe->remaining = buflen;
- _hcd.in_progress |= TU_BIT(pipenum);
- _hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
- return true;
-}
-
-bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- (void) dev_addr;
- (void) ep_addr;
- // TODO not implemented yet
- return false;
-}
-
-bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
- (void) rhport;
- if (!tu_edpt_number(ep_addr)) return true;
- int num = find_pipe(dev_addr, ep_addr);
- if (num < 0) return false;
- pipe_state_t *p = &_hcd.pipe[num];
- p->data = 0; /* Reset data toggle */
- return true;
-}
-
-/*--------------------------------------------------------------------+
- * ISR
- *--------------------------------------------------------------------+*/
-void hcd_int_handler(uint8_t rhport, bool in_isr)
-{
- (void) in_isr;
- uint32_t is = KHCI->ISTAT;
- uint32_t msk = KHCI->INTEN;
-
- // TU_LOG1("S %lx\r\n", is);
-
- /* clear disabled interrupts */
- KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
- is &= msk;
-
- if (is & USB_ISTAT_ERROR_MASK) {
- unsigned err = KHCI->ERRSTAT;
- if (err) {
- TU_LOG1(" ERR %x\r\n", err);
- KHCI->ERRSTAT = err;
- } else {
- KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK;
- }
- }
-
- if (is & USB_ISTAT_USBRST_MASK) {
- KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK;
- process_bus_reset(rhport);
- return;
- }
- if (is & USB_ISTAT_ATTACH_MASK) {
- KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK;
- _hcd.need_reset = true;
- process_attach(rhport);
- return;
- }
- if (is & USB_ISTAT_STALL_MASK) {
- KHCI->ISTAT = USB_ISTAT_STALL_MASK;
- }
- if (is & USB_ISTAT_SOFTOK_MASK) {
- msk &= ~USB_ISTAT_SOFTOK_MASK;
- KHCI->INTEN = msk;
- if (_hcd.pending) {
- int pipenum = __builtin_ctz(_hcd.pending);
- _hcd.pending = 0;
- if (!(is & USB_ISTAT_TOKDNE_MASK))
- resume_transfer(pipenum);
- }
- }
- if (is & USB_ISTAT_TOKDNE_MASK) {
- process_tokdne(rhport);
- }
-}
-
-#endif
diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
index 182710016..b577d0e9f 100644
--- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
+++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
@@ -19,6 +19,12 @@
//--------------------------------------------------------------------+
#define DCD_ENDPOINT_MAX 32
+// The iso machinery (5th DD word + packet-size memory) costs USB RAM on every build;
+// compile it only when a class that can open an iso endpoint is enabled. Keep this in
+// sync with the classes that actually arm an iso endpoint: audio, video, BTH (voice),
+// and vendor (its optional CFG_TUD_VENDOR_EP_ISO_* endpoints, exercised by usbtest).
+#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR || CFG_TUD_BTH)
+
typedef struct TU_ATTR_ALIGNED(4)
{
//------------- Word 0 -------------//
@@ -48,11 +54,37 @@ typedef struct TU_ATTR_ALIGNED(4)
volatile uint16_t present_count; // For non-iso : The number of bytes transferred by the DMA engine
// For iso : number of packets
+#if DCD_ISO_ENABLED
//------------- Word 4 -------------//
- // uint32_t iso_packet_size_addr; // iso only, can be omitted for non-iso
+ volatile uint32_t iso_packet_size_addr; // iso only: pointer into iso packet-size memory,
+ // advanced by hardware after each packet
+#endif
}dma_desc_t;
-TU_VERIFY_STATIC( sizeof(dma_desc_t) == 16, "size is not correct"); // TODO not support ISO for now
+TU_VERIFY_STATIC( sizeof(dma_desc_t) == (DCD_ISO_ENABLED ? 20 : 16), "size is not correct");
+
+// Hardware fixes endpoint type by number: 3, 6, 9, 12 are the iso-capable ones.
+// Constant per ep_id (= 2*epnum + dir) — unlike dd->isochronous, which dcd_edpt_xfer
+// transiently zeroes while rebuilding the DD, this is safe to dispatch on from the ISR.
+// TU_ATTR_UNUSED: every caller is under #if DCD_ISO_ENABLED, so non-iso builds don't
+// reference it and clang -Wunused-function (fatal) would otherwise reject the build.
+TU_ATTR_UNUSED TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) {
+ uint8_t const epnum = (uint8_t)(ep_id >> 1);
+ return (epnum % 3) == 0 && (epnum != 0) && (epnum != 15);
+}
+
+#if DCD_ISO_ENABLED
+// Isochronous packet-size memory (UM10562 12.15.6.3): one word per packet.
+// IN : software fills Packet_length (bits 15:0), 0 = ZLP
+// OUT: hardware writes Frame_number (31:17) | Packet_valid (16) | Packet_length (15:0)
+// Iso-capable endpoint numbers are 3, 6, 9, 12 -> 8 slots (x2 directions).
+// One packet moves per FRAME, so a deep queue only adds latency: 8 frames is plenty.
+#define ISO_MAX_PACKETS 8
+#define ISO_SLOT_COUNT 8
+TU_ATTR_ALWAYS_INLINE static inline uint8_t iso_slot(uint8_t ep_id) {
+ return (uint8_t)(((ep_id / 6) - 1) * 2 + (ep_id & 1)); // ep_id = 2*epnum + dir, epnum in {3,6,9,12}
+}
+#endif
typedef struct
{
@@ -66,11 +98,17 @@ typedef struct
{
uint8_t* out_buffer;
uint8_t out_bytes;
+ volatile bool out_queued; // an OUT xfer is queued; out_buffer may legitimately be NULL (status ZLP)
volatile bool out_received; // indicate if data is already received in endpoint
uint8_t in_bytes;
} control;
+#if DCD_ISO_ENABLED
+ // iso packet-size memory, must be DMA-reachable like the DDs
+ volatile uint32_t iso_psize[ISO_SLOT_COUNT][ISO_MAX_PACKETS];
+#endif
+
} dcd_data_t;
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd;
@@ -79,6 +117,29 @@ CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd;
//--------------------------------------------------------------------+
// SIE Command
//--------------------------------------------------------------------+
+
+// The SIE command protocol (CmdCode + CCEMPTY/CDFULL handshake) and the
+// slave-mode Ctrl/RxData/TxData registers are shared between thread-mode API
+// calls and dcd_int_handler, and are not reentrant: an ISR preempting a
+// thread-mode SIE sequence consumes its handshake flags and overwrites
+// CmdCode (symptom: EP0 wedges/answers stale data right after SET_INTERFACE
+// stall/clear-stall bursts overlapping bulk EOT interrupts). Mask only the
+// USB interrupt around those sequences; safe to nest, including from the ISR.
+static inline bool usb_irq_lock(void)
+{
+ bool const enabled = NVIC_GetEnableIRQ(USB_IRQn) != 0;
+ if (enabled)
+ {
+ NVIC_DisableIRQ(USB_IRQn); // CMSIS already ends this with DSB+ISB
+ }
+ return enabled;
+}
+
+static inline void usb_irq_unlock(bool enabled)
+{
+ if (enabled) NVIC_EnableIRQ(USB_IRQn);
+}
+
static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data)
{
LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK);
@@ -92,19 +153,28 @@ static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data)
static void sie_write (uint8_t cmd_code, uint8_t data_len, uint8_t data)
{
+ bool const lock = usb_irq_lock();
+
sie_cmd_code(SIE_CMDPHASE_COMMAND, cmd_code);
if (data_len)
{
sie_cmd_code(SIE_CMDPHASE_WRITE, data);
}
+
+ usb_irq_unlock(lock);
}
static uint8_t sie_read (uint8_t cmd_code)
{
+ bool const lock = usb_irq_lock();
+
sie_cmd_code(SIE_CMDPHASE_COMMAND , cmd_code);
sie_cmd_code(SIE_CMDPHASE_READ , cmd_code);
- return (uint8_t) LPC_USB->CmdData;
+ uint8_t const data = (uint8_t) LPC_USB->CmdData;
+
+ usb_irq_unlock(lock);
+ return data;
}
//--------------------------------------------------------------------+
@@ -117,6 +187,11 @@ static inline uint8_t ep_addr2idx(uint8_t ep_addr)
static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
{
+ // ReEp RMW + the EP_RLZED handshake share DevIntSt with the ISR: a bus reset
+ // from dcd_int_handler writes DevIntClr = 0xFFFFFFFF and would consume the
+ // flag this spin waits on, hanging it forever -> same lock as the SIE paths.
+ bool const lock = usb_irq_lock();
+
// follows example in 11.10.4.2
LPC_USB->ReEp |= TU_BIT(ep_id);
LPC_USB->EpInd = ep_id; // select index before setting packet size
@@ -124,6 +199,8 @@ static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size)
while ((LPC_USB->DevIntSt & DEV_INT_ENDPOINT_REALIZED_MASK) == 0) {}
LPC_USB->DevIntClr = DEV_INT_ENDPOINT_REALIZED_MASK;
+
+ usb_irq_unlock(lock);
}
@@ -230,6 +307,7 @@ static inline uint8_t byte2dword(uint8_t bytes)
static void control_ep_write(void const * buffer, uint8_t len)
{
uint32_t const * buf32 = (uint32_t const *) buffer;
+ bool const lock = usb_irq_lock(); // Ctrl/TxData + SIE sequence must not interleave with the ISR
LPC_USB->Ctrl = USBCTRL_WRITE_ENABLE_MASK; // logical endpoint = 0
LPC_USB->TxPLen = (uint32_t) len;
@@ -245,10 +323,14 @@ static void control_ep_write(void const * buffer, uint8_t len)
// select control IN & validate the endpoint
sie_write(SIE_CMDCODE_ENDPOINT_SELECT+1, 0, 0);
sie_write(SIE_CMDCODE_BUFFER_VALIDATE , 0, 0);
+
+ usb_irq_unlock(lock);
}
static uint8_t control_ep_read(void * buffer, uint8_t len)
{
+ bool const lock = usb_irq_lock(); // Ctrl/RxData + SIE sequence must not interleave with the ISR
+
LPC_USB->Ctrl = USBCTRL_READ_ENABLE_MASK; // logical endpoint = 0
while ((LPC_USB->RxPLen & USBRXPLEN_PACKET_READY_MASK) == 0) {} // TODO blocking, should have timeout
@@ -267,6 +349,7 @@ static uint8_t control_ep_read(void * buffer, uint8_t len)
sie_write(SIE_CMDCODE_ENDPOINT_SELECT+0, 0, 0);
sie_write(SIE_CMDCODE_BUFFER_CLEAR , 0, 0);
+ usb_irq_unlock(lock);
return len;
}
@@ -281,8 +364,9 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress);
uint8_t const ep_id = ep_addr2idx(p_endpoint_desc->bEndpointAddress);
- // Endpoint type is fixed to endpoint number
- // 1: interrupt, 2: Bulk, 3: Iso and so on
+ // Endpoint type is fixed to endpoint number (1 interrupt, 2 bulk, 3 iso, ...).
+ // Iso endpoints are armed via dcd_edpt_iso_alloc/activate, never through here
+ // (TUP_DCD_EDPT_ISO_ALLOC is defined for this IP), so only bulk/interrupt land here.
switch ( p_endpoint_desc->bmAttributes.xfer )
{
case TUSB_XFER_INTERRUPT:
@@ -293,10 +377,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
TU_ASSERT((epnum % 3) == 2 || (epnum == 15));
break;
- case TUSB_XFER_ISOCHRONOUS:
- TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15));
- break;
-
default:
break;
}
@@ -307,9 +387,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
//------------- first DD prepare -------------//
dma_desc_t* const dd = &_dcd.dd[ep_id];
- tu_memclr(dd, sizeof(dma_desc_t));
-
- dd->isochronous = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) ? 1 : 0;
+ tu_memclr(dd, sizeof(dma_desc_t)); // non-iso: isochronous stays 0
dd->max_packet_size = ep_size;
dd->retired = 1; // invalid at first
@@ -319,16 +397,54 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
}
bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+#if DCD_ISO_ENABLED
(void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
+ uint8_t const ep_id = ep_addr2idx(ep_addr);
+
+ // hardware fixes iso to endpoint numbers 3, 6, 9, 12
+ TU_ASSERT(ep_id_is_iso(ep_id));
+ TU_ASSERT(largest_packet_size > 0);
+
+ set_ep_size(ep_id, largest_packet_size);
+
+ dma_desc_t* const dd = &_dcd.dd[ep_id];
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->isochronous = 1;
+ dd->max_packet_size = largest_packet_size;
+ dd->retired = 1; // invalid at first
+
+ sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0);
+ return true;
+#else
+ (void)rhport; (void)ep_addr; (void)largest_packet_size;
return false;
+#endif
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
+#if DCD_ISO_ENABLED
(void)rhport;
- (void)desc_ep;
+ uint8_t const ep_id = ep_addr2idx(desc_ep->bEndpointAddress);
+ dma_desc_t* const dd = &_dcd.dd[ep_id];
+
+ // same fixed-number rule as alloc: without it a rejected-but-ignored alloc (classes
+ // discard that return) would set isochronous on a non-iso ep_id and underflow iso_slot()
+ TU_ASSERT(ep_id_is_iso(ep_id));
+
+ // kill any armed transfer from a previous alternate setting
+ LPC_USB->EpDMADis = TU_BIT(ep_id);
+ _dcd.udca[ep_id] = NULL;
+
+ dd->isochronous = 1;
+ dd->max_packet_size = tu_edpt_packet_size(desc_ep);
+ dd->retired = 1;
+
+ sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0);
+ return true;
+#else
+ (void)rhport; (void)desc_ep;
return false;
+#endif
}
void dcd_edpt_close_all (uint8_t rhport)
@@ -369,19 +485,28 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t
control_ep_write(buffer, len);
}else
{
+ // guard the out_received/out_buffer handshake against the EP0 OUT ISR
+ bool const lock = usb_irq_lock();
+
if ( _dcd.control.out_received )
{
// Already received the DATA OUT packet
_dcd.control.out_received = false;
- _dcd.control.out_buffer = NULL;
- _dcd.control.out_bytes = 0;
uint8_t received = control_ep_read(buffer, len);
+ // event queued with in_isr=true, which skips the queue's own locking: keep the
+ // USB IRQ masked across it, or a real ISR completion could interleave the write
dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true);
+ usb_irq_unlock(lock);
}else
{
+ // buffer is NULL for a status-stage ZLP: signal the pending xfer explicitly,
+ // NOT via out_buffer != NULL — a NULL-buffer queue mistaken for "nothing queued"
+ // leaves out_received stale and poisons the next control OUT data stage.
_dcd.control.out_buffer = buffer;
_dcd.control.out_bytes = len;
+ _dcd.control.out_queued = true;
+ usb_irq_unlock(lock);
}
}
@@ -406,26 +531,68 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t
uint16_t const ep_size = dd->max_packet_size;
uint8_t is_iso = dd->isochronous;
- tu_memclr(dd, sizeof(dma_desc_t));
- dd->isochronous = is_iso;
- dd->max_packet_size = ep_size;
- dd->buffer = (uint32_t) buffer;
- dd->buflen = total_bytes;
+#if DCD_ISO_ENABLED
+ if ( is_iso )
+ {
+ // iso: buflen counts packets; per-packet sizes live in the packet-size memory.
+ // One packet moves per frame (UM10562 12.15.6: DMA request is raised for
+ // DMA-enabled iso endpoints on every FRAME interrupt, both directions).
+ // Validate BEFORE touching the DD: bailing out mid-rebuild would leave a
+ // zeroed (retired=0 -> serviceable) descriptor armed for the frame engine.
+ TU_ASSERT(ep_size > 0);
+ uint16_t const packets = (total_bytes > 0) ? (uint16_t) tu_div_ceil(total_bytes, ep_size) : 1;
+ TU_ASSERT(packets <= ISO_MAX_PACKETS);
+
+ uint8_t const slot = iso_slot(ep_id);
+ uint16_t remain = total_bytes;
+ for ( uint16_t i = 0; i < packets; i++ )
+ {
+ uint16_t const pkt_len = tu_min16(remain, ep_size);
+ // IN: length to send (0 = ZLP). OUT: hardware writes back
+ // Frame_number|Packet_valid|Packet_length -- prefill 0 so a frame the
+ // hardware never wrote (missed/invalid) cannot read back as data.
+ _dcd.iso_psize[slot][i] = (ep_id & 1) ? pkt_len : 0;
+ remain = (uint16_t)(remain - pkt_len);
+ }
- _dcd.udca[ep_id] = dd;
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->isochronous = 1;
+ dd->max_packet_size = ep_size;
+ dd->buffer = (uint32_t) buffer;
+ dd->buflen = packets;
+ dd->iso_packet_size_addr = (uint32_t) &_dcd.iso_psize[slot][0];
- if ( ep_id % 2 )
+ _dcd.udca[ep_id] = dd;
+ LPC_USB->EpDMAEn = TU_BIT(ep_id); // frame-triggered: no DMARSet, no EpIntEn
+ }
+ else
+#else
+ (void) is_iso;
+#endif
{
- // Clear EP interrupt before Enable DMA
- LPC_USB->EpIntEn &= ~TU_BIT(ep_id);
- LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ tu_memclr(dd, sizeof(dma_desc_t));
+ dd->max_packet_size = ep_size;
+ dd->buffer = (uint32_t) buffer;
+ dd->buflen = total_bytes;
- // endpoint IN need to actively raise DMA request
- LPC_USB->DMARSet = TU_BIT(ep_id);
- }else
- {
- // Enable DMA
- LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ _dcd.udca[ep_id] = dd;
+
+ if ( ep_id % 2 )
+ {
+ // Clear EP interrupt before Enable DMA
+ // EpIntEn read-modify-write races the ISR's own RMWs -> lock
+ bool const lock = usb_irq_lock();
+ LPC_USB->EpIntEn &= ~TU_BIT(ep_id);
+ LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ usb_irq_unlock(lock);
+
+ // endpoint IN need to actively raise DMA request
+ LPC_USB->DMARSet = TU_BIT(ep_id);
+ }else
+ {
+ // Enable DMA
+ LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ }
}
return true;
@@ -451,13 +618,20 @@ static void control_xfer_isr(uint8_t rhport, uint32_t ep_int_status)
uint8_t setup_packet[8];
control_ep_read(setup_packet, 8); // TODO read before clear setup above
+ // a new SETUP voids any half-finished control state
+ _dcd.control.out_queued = false;
+ _dcd.control.out_received = false;
+ _dcd.control.out_buffer = NULL;
+ _dcd.control.out_bytes = 0;
+
dcd_event_setup_received(rhport, setup_packet, true);
}
- else if ( _dcd.control.out_buffer )
+ else if ( _dcd.control.out_queued )
{
- // software queued transfer previously
+ // software queued transfer previously (out_buffer NULL = status ZLP)
uint8_t received = control_ep_read(_dcd.control.out_buffer, _dcd.control.out_bytes);
+ _dcd.control.out_queued = false;
_dcd.control.out_buffer = NULL;
_dcd.control.out_bytes = 0;
@@ -513,7 +687,32 @@ static void dd_complete_isr(uint8_t rhport, uint8_t ep_id)
uint8_t result = (dd->status == DD_STATUS_NORMAL || dd->status == DD_STATUS_DATA_UNDERUN) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED;
uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0);
- dcd_event_xfer_complete(rhport, ep_addr, dd->present_count, result, true);
+ uint32_t xferred_bytes;
+#if DCD_ISO_ENABLED
+ if ( ep_id_is_iso(ep_id) )
+ {
+ // present_count is in packets; actual byte counts are in the packet-size memory
+ // (IN: as programmed by us, OUT: Packet_length written back by hardware,
+ // guarded by Packet_valid -- a frame with no packet must count as 0)
+ uint8_t const slot = iso_slot(ep_id);
+ uint16_t const packets = tu_min16(dd->present_count, ISO_MAX_PACKETS);
+ xferred_bytes = 0;
+ for (uint16_t i = 0; i < packets; i++)
+ {
+ uint32_t const psize = _dcd.iso_psize[slot][i];
+ if ( (ep_id & 1) || (psize & TU_BIT(16)) )
+ {
+ xferred_bytes += (psize & 0xFFFFu);
+ }
+ }
+ }
+ else
+#endif
+ {
+ xferred_bytes = dd->present_count;
+ }
+
+ dcd_event_xfer_complete(rhport, ep_addr, (uint16_t) xferred_bytes, result, true);
}
// main USB IRQ handler
@@ -569,6 +768,16 @@ void dcd_int_handler(uint8_t rhport)
{
if ( tu_bit_test(eot, ep_id) )
{
+ // dispatch on the hardware's fixed ep-number/type map, NOT dd->isochronous:
+ // thread-mode dcd_edpt_xfer transiently zeroes the DD while rebuilding it
+#if DCD_ISO_ENABLED
+ if ( ep_id_is_iso(ep_id) )
+ {
+ // iso: last packet already left with its frame; complete both directions here
+ dd_complete_isr(rhport, ep_id);
+ }
+ else
+#endif
if ( ep_id & 0x01 )
{
// IN enable EpInt for end of usb transfer
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index aa0307d25..42f6750b1 100644
--- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
+++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
@@ -87,6 +87,10 @@ enum {
DEVCMDSTAT_SUSPEND_CHANGE_MASK = TU_BIT(25),
DEVCMDSTAT_RESET_CHANGE_MASK = TU_BIT(26),
DEVCMDSTAT_VBUS_DEBOUNCED_MASK = TU_BIT(28),
+
+ // write-1-to-clear latches
+ DEVCMDSTAT_W1C_MASK = DEVCMDSTAT_SETUP_RECEIVED_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK |
+ DEVCMDSTAT_SUSPEND_CHANGE_MASK | DEVCMDSTAT_RESET_CHANGE_MASK,
};
enum {
@@ -158,6 +162,10 @@ typedef struct
// - 55 usb0 (FS) has 5x2 endpoints, usb1 (HS) has 6x2 endpoints
#define MAX_EP_PAIRS 6
+// Bounded spin waiting for hardware to clear an EPSKIP bit when retiring a still-armed endpoint on
+// reopen (dcd_edpt_open). Hardware clears it within a (micro)frame; the guard only avoids a hang.
+#define IP3511_EPSKIP_SPIN 100000u
+
// NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering.
// current_td is used to keep track of number of remaining & xferred bytes of the current request.
typedef struct
@@ -167,7 +175,9 @@ typedef struct
ep_cmd_sts_t ep[2*MAX_EP_PAIRS][2];
xfer_dma_t dma[2*MAX_EP_PAIRS];
- TU_ATTR_ALIGNED(64) uint8_t setup_packet[8];
+ // volatile: the controller DMAs a new setup packet into this buffer as soon as the SETUP
+ // latch is cleared, so reads of it must stay ordered against the register accesses around them
+ TU_ATTR_ALIGNED(64) volatile uint8_t setup_packet[8];
}dcd_data_t;
// EP list must be 256-byte aligned
@@ -176,8 +186,12 @@ typedef struct
// Use CFG_TUD_MEM_SECTION to place it accordingly.
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(256) static dcd_data_t _dcd;
-// Dummy buffer to fix ZLPs overwriting the buffer (probably an USB/DMA controller bug)
-// TODO find way to save memory
+// Dummy buffer to fix ZLPs overwriting the buffer: Errata LPC55S6x USB.5 / LPC55S2x USB.4 - the
+// HS device controller always DMA-writes OUT data in 8-byte units, so up to 7 bytes land past the
+// received length. This redirects the ZLP case; the general short-OUT case is unhandled here
+// (TinyUSB's own endpoint buffers are sized/aligned so the spill stays inside them, but a tight
+// caller buffer can be overrun by up to 7 bytes - the SDK's documented workaround is a bounce
+// buffer). TODO find way to save memory
CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(64) static uint8_t dummy[8];
//--------------------------------------------------------------------+
@@ -217,7 +231,7 @@ static const dcd_controller_t _dcd_controller[] = {
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
-TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer) {
+TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const volatile * buffer) {
uint32_t addr = (uint32_t) buffer;
TU_ASSERT( (addr & 0x3f) == 0, 0 );
return ( (addr >> 6) & 0xFFFFUL ) ;
@@ -243,6 +257,16 @@ TU_ATTR_ALWAYS_INLINE static inline bool rhport_is_highspeed(uint8_t rhport) {
return _dcd_controller[rhport].is_highspeed;
}
+
+// DEVCMDSTAT mixes RW fields with write-1-to-clear latches (SETUP + the 3 change bits): a blind
+// RMW writes a pending latch back as 1 and silently clears it (a SETUP eaten this way strands
+// EP0). Mask the latches on every update; pass one in set_mask only to clear it.
+TU_ATTR_ALWAYS_INLINE static inline void devcmdstat_update(dcd_registers_t* dcd_reg,
+ uint32_t clear_mask, uint32_t set_mask) {
+ const uint32_t v = dcd_reg->DEVCMDSTAT & ~(DEVCMDSTAT_W1C_MASK | clear_mask);
+ dcd_reg->DEVCMDSTAT = v | set_mask;
+}
+
//--------------------------------------------------------------------+
// CONTROLLER API
//--------------------------------------------------------------------+
@@ -280,8 +304,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment
dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt
dcd_reg->INTEN = INT_DEVICE_STATUS_MASK;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK |
- DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK;
+ // deliberately clear every latch (incl. a SETUP left by a bootloader/warm start) for a
+ // deterministic init state
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK |
+ DEVCMDSTAT_W1C_MASK);
NVIC_ClearPendingIRQ(_dcd_controller[rhport].irqnum);
@@ -305,8 +331,7 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr)
// Response with status first before changing device address
dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false);
- dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_ADDR_MASK;
- dcd_reg->DEVCMDSTAT |= dev_addr;
+ devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_ADDR_MASK, dev_addr);
}
void dcd_remote_wakeup(uint8_t rhport)
@@ -317,13 +342,13 @@ void dcd_remote_wakeup(uint8_t rhport)
void dcd_connect(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_CONNECT_MASK;
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_CONNECT_MASK);
}
void dcd_disconnect(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_CONNECT_MASK;
+ devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_CONNECT_MASK, 0);
}
void dcd_sof_enable(uint8_t rhport, bool en)
@@ -337,13 +362,37 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// DCD Endpoint Port
//--------------------------------------------------------------------+
+// Retire a still-armed (Active) endpoint before reconfiguring it (reopen across SET_INTERFACE).
+// UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for hardware to clear the bit, then Active is
+// safe to clear. EPSKIP raises the endpoint interrupt as it clears Active, delivered as a
+// (partial) transfer completion. Here that is sanctioned — usbd_edpt_close() documents "in
+// progress transfers may be delivered after this call", and that completion is what clears the
+// stale usbd busy flag (ISO_ALLOC close is a no-op) so the class can re-arm the reopened
+// endpoint. NOT for the stall/iso-activate paths: there the class re-arms from the completion
+// callback and the endpoint ends up Active+Stall, which never sends a STALL handshake (usbtest
+// case 13 regression on LPC11u37) — those paths must clear Active directly instead.
+// Bounded: hardware clears EPSKIP within a (micro)frame.
+static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) {
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) {
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->EPSKIP |= TU_BIT(ep_id);
+ uint32_t guard = IP3511_EPSKIP_SPIN;
+ while ( (dcd_reg->EPSKIP & TU_BIT(ep_id)) && guard-- ) {}
+ }
+ ep_cs[0].cmd_sts.active = ep_cs[1].cmd_sts.active = 0;
+}
+
void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr)
{
(void) rhport;
-
// TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around
uint8_t const ep_id = ep_addr2id(ep_addr);
- _dcd.ep[ep_id][0].cmd_sts.stall = 1;
+ // Clear Active directly before setting Stall (no EPSKIP — see edpt_skip_active): the hardware
+ // services an armed buffer instead of returning STALL, so a halt requested while a transfer is
+ // queued would not actually stall the endpoint (usbtest case 13).
+ _dcd.ep[ep_id][0].cmd_sts.active = 0;
+ _dcd.ep[ep_id][0].cmd_sts.stall = 1;
}
void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
@@ -352,9 +401,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
uint8_t const ep_id = ep_addr2id(ep_addr);
+ // Preserve rf_tv: for non-control endpoints it is a TYPE bit, not the toggle value (UM11126:
+ // T=1 + RF 1/0 = interrupt/iso). Zeroing it here turned HS periodic interrupt endpoints into
+ // isochronous - no handshake on OUT, dead IN (usbtest cases 25/26 on lpc55 HS port).
+ // TODO implement the Errata LPC546xx USB.13 work-around (same semantics in UM11126): with RF/TV preserved at 1, TR
+ // loads the toggle from TV, so an HS interrupt endpoint restarts on DATA1 after clear-halt and
+ // the host discards one packet as a retransmission. The documented workaround needs an
+ // interrupt-on-NAK state machine (park as generic TR=1/TV=0, wait for a NAKed token to latch
+ // toggle 0 via EPTOGGLE, restore the type) - deferred; one lost packet beats the fully broken
+ // endpoint the old rf_tv clear caused.
_dcd.ep[ep_id][0].cmd_sts.stall = 0;
_dcd.ep[ep_id][0].cmd_sts.toggle_reset = 1;
- _dcd.ep[ep_id][0].cmd_sts.rf_tv = 0;
}
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
@@ -362,9 +419,15 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
//------------- Prepare Queue Head -------------//
uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress);
ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- // Check if endpoint is available
- TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
+ // usbd_edpt_close() is a no-op on ISO_ALLOC ports, so an endpoint a class closed then reopened
+ // across SET_INTERFACE (e.g. the video notification or audio streaming endpoint) is still armed
+ // here rather than disabled. Retire it (edpt_skip_active) before reconfiguring.
+ if ( !(ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable) ) {
+ edpt_skip_active(rhport, ep_id);
+ ep_cs[0].cmd_sts.disable = ep_cs[1].cmd_sts.disable = 1;
+ }
edpt_reset(rhport, ep_id);
@@ -389,7 +452,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc)
}
// Enable EP interrupt
- dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
dcd_reg->INTEN |= TU_BIT(ep_id);
return true;
@@ -399,34 +461,40 @@ void dcd_edpt_close_all (uint8_t rhport)
{
for (uint8_t ep_id = 0; ep_id < 2*_dcd_controller[rhport].ep_pairs; ++ep_id)
{
- _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
+ _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][1].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
_dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1;
}
}
-void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr)
-{
- (void) rhport;
-
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void) largest_packet_size;
+ // Reserve the endpoint command/status entry once (persists across altsetting changes); the
+ // buffer pointer is filled per-transfer, so nothing to pre-allocate. Mirrors the ISO branch of
+ // dcd_edpt_open().
uint8_t ep_id = ep_addr2id(ep_addr);
- _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual)
- _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1;
-}
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable );
-#if 0
-bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
- (void)rhport;
- (void)ep_addr;
- (void)largest_packet_size;
- return false;
+ edpt_reset(rhport, ep_id);
+ ep_cs[0].cmd_sts.type = 1; // ISO
+
+ dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
+ dcd_reg->INTEN |= TU_BIT(ep_id);
+ return true;
}
bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
+ // (Re)activate on altsetting selection: abort a transfer still armed from the previous
+ // altsetting (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the
+ // fresh transfer the class queues), clear stall and reset the data toggle. Direct Active=0, not
+ // EPSKIP (see edpt_skip_active). The class re-arms via dcd_edpt_xfer().
+ uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress);
+ ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id);
+ ep_cs[0].cmd_sts.active = 0;
+ ep_cs[1].cmd_sts.active = 0;
+ dcd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress);
+ return true;
}
-#endif
static void prepare_ep_xfer(uint8_t rhport, uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) {
uint16_t nbytes;
@@ -499,7 +567,7 @@ static void bus_reset(uint8_t rhport)
dcd_reg->EPSKIP = 0xFFFFFFFF;
dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; // clear setup received interrupt
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); // clear setup received interrupt
dcd_reg->INTEN = INT_DEVICE_STATUS_MASK | TU_BIT(0) | TU_BIT(1); // enable device status & control endpoints
}
@@ -558,18 +626,25 @@ void dcd_int_handler(uint8_t rhport)
{
dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs;
- uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT;
-
uint32_t int_status = dcd_reg->INTSTAT;
- int_status &= dcd_reg->INTEN;
+ int_status &= dcd_reg->INTEN;
dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt
if (int_status == 0) return;
+ // Snapshot after the INTSTAT ack: latch bits persist (RWC) so nothing is lost, while the reverse
+ // order could consume INTSTAT bit0 for a SETUP not yet visible in the snapshot - stranding the
+ // SETUP (INTSTAT is edge-latched) and feeding bit0 to process_xfer_isr as a bogus completion.
+ uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT;
+
//------------- Device Status -------------//
if ( int_status & INT_DEVICE_STATUS_MASK )
{
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK;
+ // clear only the change latches observed in the snapshot: one latched by hardware between the
+ // snapshot and this write would be acknowledged unseen (its DEV_INT re-latches and dispatches
+ // next pass instead)
+ devcmdstat_update(dcd_reg, 0, cmd_stat &
+ (DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK));
if ( cmd_stat & DEVCMDSTAT_RESET_CHANGE_MASK) // bus reset
{
@@ -614,15 +689,43 @@ void dcd_int_handler(uint8_t rhport)
_dcd.ep[0][0].cmd_sts.active = _dcd.ep[1][0].cmd_sts.active = 0;
_dcd.ep[0][0].cmd_sts.stall = _dcd.ep[1][0].cmd_sts.stall = 0;
- dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK;
+ // UM flow: ack the latch FIRST, then read the payload. This IP has no setup lockout, so a
+ // back-to-back SETUP can overwrite _dcd.setup_packet at any time - but with the latch already
+ // released, any such overwrite re-latches SETUP_RECEIVED and is redelivered (worst case a
+ // superseded duplicate, absorbed by usbd's queued-setup counter). The reverse order can
+ // consume the newer SETUP's latch unseen and lose it.
+ devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK);
- dcd_event_setup_received(rhport, _dcd.setup_packet, true);
+ // UM11126 Fig 163 (control EP0 flowchart) requires clearing the EP0IN interrupt here: a
+ // control IN completion latched before this SETUP must not reach usbd after it, where it
+ // would be applied to the new request and arm its status stage early. EP0OUT goes with it -
+ // bit0 is set by SETUP reception too, and left set it would replay next pass as a phantom
+ // completion. Neither can discard live work: the SETUP latch NAKs all EP0 traffic until the
+ // update above, and both EP0 Active bits were cleared a few lines up.
+ dcd_reg->INTSTAT = TU_BIT(0) | TU_BIT(1);
+
+ // Copied a byte at a time rather than with memcpy: C orders volatile accesses only against
+ // each other, so a non-volatile copy of this buffer may be sunk below the guard read that
+ // follows - gcc does exactly that at -O2 and -O3, leaving only -Os correct.
+ uint8_t setup_copy[8];
+ for (uint8_t i = 0; i < sizeof(setup_copy); i++) {
+ setup_copy[i] = _dcd.setup_packet[i];
+ }
+
+ // a SETUP that raced in after the acks (its bit0 consumed above) makes this copy suspect:
+ // its latch is visible again, so re-raise the endpoint interrupt and let the next pass
+ // deliver the newer payload rather than passing up bytes that may be torn between the two
+ if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) {
+ dcd_reg->INTSETSTAT = TU_BIT(0);
+ } else {
+ dcd_event_setup_received(rhport, setup_copy, true);
+ }
// keep waiting for next setup
prepare_setup_packet(rhport);
- // clear bit0
- int_status = tu_bit_clear(int_status, 0);
+ // drop both EP0 bits: acked above, and neither belongs to the request this SETUP starts
+ int_status &= ~(TU_BIT(0) | TU_BIT(1));
}
// Endpoint transfer complete interrupt