summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2026-03-10 21:44:40 +0700
committerGitHub <[email protected]>2026-03-10 21:44:40 +0700
commit7249c65b2a5b995ff7c2b45c1171f218b9d71112 (patch)
treec3ecb27d7e2b72d91e56bc3f7becf2e2a7ff2e0e /src
parent14b0d7964e409bd46e9593f1002cd6b5da146c26 (diff)
parenta82ba9b884ae880a472314f04d1a2a3a7e6469f7 (diff)
Merge pull request #3530 from hathach/hcd_ip3516
Add IP3516 HCD support
Diffstat (limited to 'src')
-rw-r--r--src/common/tusb_mcu.h11
-rw-r--r--src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c821
-rw-r--r--src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h188
-rw-r--r--src/portable/ohci/ohci.c3
4 files changed, 1022 insertions, 1 deletions
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 32f7eb557..edc7ecc3e 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -71,13 +71,24 @@
#define TUP_DCD_ENDPOINT_MAX 5
#elif TU_CHECK_MCU(OPT_MCU_LPC54)
+ #include "fsl_device_registers.h"
+
// TODO USB0 has 5, USB1 has 6
#define TUP_USBIP_IP3511
+
+ #if !defined(LPC54114_cm4_SERIES) && !defined(LPC54114_cm0plus_SERIES)
+ #define TUP_USBIP_IP3516
+ #define TUP_USBIP_OHCI
+ #define TUP_USBIP_OHCI_NXP
+ #define TUP_OHCI_RHPORTS 1 // 1 downstream port
+ #endif
+
#define TUP_DCD_ENDPOINT_MAX 6
#elif TU_CHECK_MCU(OPT_MCU_LPC55)
// TODO USB0 has 5, USB1 has 6
#define TUP_USBIP_IP3511
+ #define TUP_USBIP_IP3516
#define TUP_USBIP_OHCI
#define TUP_USBIP_OHCI_NXP
#define TUP_OHCI_RHPORTS 1 // 1 downstream port
diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c
new file mode 100644
index 000000000..c9810f51c
--- /dev/null
+++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.c
@@ -0,0 +1,821 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 HiFiPhile (Zixun LI)
+ *
+ * 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_TUH_ENABLED && defined(TUP_USBIP_IP3516)
+
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "common/tusb_common.h"
+#include "host/hcd.h"
+#include "host/usbh.h"
+#include "hcd_lpc_ip3516.h"
+
+#if TU_CHECK_MCU(OPT_MCU_LPC55, OPT_MCU_LPC54)
+ #include "fsl_device_registers.h"
+#else
+ #error "Unsupported MCUs"
+#endif
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+#if TU_CHECK_MCU(OPT_MCU_LPC54)
+ #define ATLPTD ATL_PTD_BASE_ADDR
+ #define INTPTD INT_PTD_BASE_ADDR
+ #define ISOPTD ISO_PTD_BASE_ADDR
+ #define ATLPTDD ATL_PTD_DONE_MAP
+ #define INTPTDD INT_PTD_DONE_MAP
+ #define ISOPTDD ISO_PTD_DONE_MAP
+ #define ATLPTDS ATL_PTD_SKIP_MAP
+ #define INTPTDS INT_PTD_SKIP_MAP
+ #define ISOPTDS ISO_PTD_SKIP_MAP
+ #define DATAPAYLOAD DATA_PAYLOAD_BASE_ADDR
+ #define LASTPTD LAST_PTD_INUSE
+
+ #define USBHSH_ATLPTD_ATL_BASE_MASK USBHSH_ATL_PTD_BASE_ADDR_ATL_BASE_MASK
+ #define USBHSH_INTPTD_INT_BASE_MASK USBHSH_INT_PTD_BASE_ADDR_INT_BASE_MASK
+ #define USBHSH_ISOPTD_ISO_BASE_MASK USBHSH_ISO_PTD_BASE_ADDR_ISO_BASE_MASK
+
+ #define USBHSH_DATAPAYLOAD_DAT_BASE_MASK USBHSH_DATA_PAYLOAD_BASE_ADDR_DAT_BASE_MASK
+
+ #define USBHSH_LASTPTD_ATL_LAST USBHSH_LAST_PTD_INUSE_ATL_LAST
+ #define USBHSH_LASTPTD_INT_LAST USBHSH_LAST_PTD_INUSE_INT_LAST
+ #define USBHSH_LASTPTD_ISO_LAST USBHSH_LAST_PTD_INUSE_ISO_LAST
+#endif
+
+#define USBHSH_PORTSC1_W1C_MASK (USBHSH_PORTSC1_CSC_MASK | USBHSH_PORTSC1_PEDC_MASK | USBHSH_PORTSC1_OCC_MASK)
+
+#define IP3516_PSPD_LOW 0
+#define IP3516_PSPD_FULL 1
+#define IP3516_PSPD_HIGH 2
+
+//--------------------------------------------------------------------+
+// Proprietary Transfer Descriptor
+//--------------------------------------------------------------------+
+
+CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(1024) static ip3516_ptd_t _ptd;
+
+static struct {
+ uint32_t uframe_number;
+ uint32_t uframe_length;
+ bool attached; // Track attachment state to avoid duplicate events, sometimes high-speed disconnection detector is not reliable
+} _hcd_data;
+
+//--------------------------------------------------------------------+
+// Helper Functions
+//--------------------------------------------------------------------+
+
+static inline bool is_ptd_free(const ptd_ctrl1_t ctrl1) {
+ return ctrl1.mps == 0;
+}
+
+static inline bool is_xfer_async(tusb_xfer_type_t xfer_type) {
+ return (xfer_type == TUSB_XFER_CONTROL || xfer_type == TUSB_XFER_BULK);
+}
+
+static inline void ptd_clear_state(ptd_state_t *state) {
+ ptd_state_t local = {.value = 0};
+ local.ep_type = state->ep_type; // preserve ep_type
+ local.token = state->token; // preserve token
+ local.data_toggle = state->data_toggle; // preserve data_toggle
+ *state = local;
+}
+
+static inline uint8_t ptd_find_free(tusb_xfer_type_t xfer_type) {
+ uint8_t max_count;
+ intptr_t ptd_array;
+
+ switch (xfer_type) {
+ case TUSB_XFER_CONTROL:
+ case TUSB_XFER_BULK:
+ max_count = IP3516_ATL_NUM;
+ ptd_array = (intptr_t)&_ptd.atl;
+ break;
+
+ case TUSB_XFER_INTERRUPT:
+ max_count = IP3516_PTL_NUM;
+ ptd_array = (intptr_t)&_ptd.intr;
+ break;
+
+ case TUSB_XFER_ISOCHRONOUS:
+ max_count = IP3516_PTL_NUM;
+ ptd_array = (intptr_t)&_ptd.iso;
+ break;
+
+ default:
+ return TUSB_INDEX_INVALID_8;
+ }
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ // For ATL: stride is sizeof(ip3516_atl_t) = 16 bytes = 4 words
+ // For PTL: stride is sizeof(ip3516_ptl_t) = 32 bytes = 8 words
+ uint8_t stride = is_xfer_async(xfer_type) ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t);
+ ptd_ctrl1_t *ctrl1 = (ptd_ctrl1_t *)(ptd_array + i * stride);
+
+ if (is_ptd_free(*ctrl1)) {
+ return i;
+ }
+ }
+
+ return TUSB_INDEX_INVALID_8; // No free PTD found
+}
+
+// Close all PTDs associated with a specific device address
+static void close_ptds_by_device(uint8_t dev_addr, intptr_t ptd_array, uint8_t max_count, uint8_t stride,
+ volatile uint32_t *skip_reg) {
+
+ uint32_t skip_mask = 0;
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+
+ if (!is_ptd_free(*ptd_ctrl1) && ptd_ctrl2->dev_addr == dev_addr) {
+ *skip_reg |= (1 << i);
+ skip_mask |= (1 << i);
+ }
+ }
+
+ if (skip_mask) {
+ // Wait 1 uframe for PTDs to be inactive (with timeout)
+ uint32_t start_uframe =
+ (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uint32_t timeout = 10000;
+ while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
+ start_uframe && timeout > 0) {
+ timeout--;
+ }
+
+ // Clear PTDs
+ for (uint8_t i = 0; i < max_count; i++) {
+ if (skip_mask & (1 << i)) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ tu_memclr((void *)ptd_ptr, stride);
+ }
+ }
+
+ // Clear skip bits
+ *skip_reg &= ~skip_mask;
+ }
+}
+
+// Check if a PTD matches the given endpoint criteria
+static bool ptd_matches(intptr_t ptd_ptr, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) {
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ if (is_ptd_free(*ptd_ctrl1)) {
+ return false;
+ }
+
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ if (ptd_ctrl2->dev_addr != dev_addr || ptd_ctrl2->ep_num != ep_num) {
+ return false;
+ }
+
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ bool is_control = (ptd_state->ep_type == TUSB_XFER_CONTROL);
+
+ // For control endpoint, match both IN and OUT directions
+ if (is_control) {
+ return true;
+ }
+
+ if (ep_dir == TUSB_DIR_IN && ptd_state->token == IP3516_PTD_TOKEN_IN) {
+ return true;
+ }
+
+ if (ep_dir == TUSB_DIR_OUT && ptd_state->token == IP3516_PTD_TOKEN_OUT) {
+ return true;
+ }
+
+ return false;
+}
+
+// Find and close a specific PTD
+static bool find_and_close_ptd(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir, intptr_t ptd_array, uint8_t max_count,
+ uint8_t stride, volatile uint32_t *skip_reg) {
+ for (uint8_t i = 0; i < max_count; i++) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ if (skip_reg) {
+ *skip_reg |= (1 << i);
+
+ // Wait 1 uframe for PTD to be inactive (with timeout)
+ uint32_t start_uframe =
+ (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uint32_t timeout = 10000;
+ while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
+ start_uframe && timeout > 0) {
+ timeout--;
+ }
+
+ // Just clear state
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ ptd_clear_state(ptd_state);
+ ptd_ctrl1->valid = 0;
+
+ *skip_reg &= ~(1 << i);
+ } else {
+ // Clear PTD
+ tu_memclr((void *)ptd_ptr, stride);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+// Find an opened PTD
+static intptr_t find_opened_ptd(uint8_t dev_addr, uint8_t ep_addr) {
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ for (uint8_t i = 0; i < IP3516_ATL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.atl[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ // Search in INT
+ for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.intr[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ // Search in ISO
+ for (uint8_t i = 0; i < IP3516_PTL_NUM; i++) {
+ intptr_t ptd_ptr = (intptr_t)&_ptd.iso[i];
+ if (ptd_matches(ptd_ptr, dev_addr, ep_num, ep_dir)) {
+ return ptd_ptr;
+ }
+ }
+
+ return 0;
+}
+
+static bool edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen, bool is_setup) {
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr);
+ TU_ASSERT(ptd_ptr != 0);
+
+ ptd_ctrl1_t *ptd_ctrl1 = (ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ ptd_data_t *ptd_data = (ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ // Setup data buffer and length
+ ptd_data->data_addr = (uint32_t)(uintptr_t)buffer & IP3516_PTD_DATA_ADDR_MASK;
+ ptd_data->xfer_len = buflen;
+
+ // Clear previous state
+ ptd_clear_state(ptd_state);
+
+ // Set token for EP0
+ if (ep_num == 0) {
+ if (is_setup) {
+ ptd_state->token = IP3516_PTD_TOKEN_SETUP;
+ ptd_state->data_toggle = 0;
+ } else {
+ ptd_state->token = (ep_dir == TUSB_DIR_IN) ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT;
+ ptd_state->data_toggle = 1;
+ }
+ }
+
+ // Interrupt split transfer needs to be relaunched manually if NAKed
+ if (ptd_ctrl2->split && ptd_state->ep_type == TUSB_XFER_INTERRUPT) {
+ ptd_ctrl2->reload = 0x0f;
+ ptd_state->nak_cnt = 0x0f;
+ }
+
+ // Activate PTD
+ ptd_ctrl1->valid = 1;
+ ptd_state->active = 1;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Controller API
+//--------------------------------------------------------------------+
+
+// Initialize controller to host mode
+bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) {
+ (void)rh_init;
+ (void)rhport;
+
+ // Reset controller
+ USBHSH->USBCMD |= USBHSH_USBCMD_HCRESET_MASK;
+ while (USBHSH->USBCMD & USBHSH_USBCMD_HCRESET_MASK) {}
+
+ USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK;
+
+ tu_memclr(&_ptd, sizeof(_ptd));
+ tu_varclr(&_hcd_data);
+
+ // Set base addresses
+ USBHSH->ATLPTD = (uint32_t)&_ptd.atl & USBHSH_ATLPTD_ATL_BASE_MASK;
+ USBHSH->INTPTD = (uint32_t)&_ptd.intr & USBHSH_INTPTD_INT_BASE_MASK;
+ USBHSH->ISOPTD = (uint32_t)&_ptd.iso & USBHSH_ISOPTD_ISO_BASE_MASK;
+ USBHSH->DATAPAYLOAD = (uint32_t)&_ptd & USBHSH_DATAPAYLOAD_DAT_BASE_MASK;
+
+ // Turn on power switch
+ if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) {
+ USBHSH->PORTSC1 |= USBHSH_PORTSC1_PP_MASK;
+ }
+
+ // Get frame list size
+ uint32_t fls = (USBHSH->USBCMD & USBHSH_USBCMD_FLS_MASK) >> USBHSH_USBCMD_FLS_SHIFT;
+ _hcd_data.uframe_length = 8192 >> fls;
+
+ // Clear pending interrupts
+ USBHSH->USBSTS = 0xFFFFFFFF;
+
+ // Enable interrupts
+ USBHSH->USBINTR = USBHSH_USBINTR_ATL_IRQ_E_MASK | USBHSH_USBINTR_INT_IRQ_E_MASK | USBHSH_USBINTR_ISO_IRQ_E_MASK |
+ USBHSH_USBINTR_PCDE_MASK | USBHSH_USBINTR_FLRE_MASK;
+
+
+ // Enable all PTDs
+ USBHSH->LASTPTD = USBHSH_LASTPTD_ATL_LAST(IP3516_ATL_NUM - 1) | USBHSH_LASTPTD_INT_LAST(IP3516_PTL_NUM - 1) |
+ USBHSH_LASTPTD_ISO_LAST(IP3516_PTL_NUM - 1);
+
+ // Enable controller
+ USBHSH->USBCMD = USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK;
+
+ return true;
+}
+
+// Enable USB interrupt
+void hcd_int_enable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_EnableIRQ(USB1_IRQn);
+}
+
+// Disable USB interrupt
+void hcd_int_disable(uint8_t rhport) {
+ (void)rhport;
+ NVIC_DisableIRQ(USB1_IRQn);
+}
+
+bool hcd_deinit(uint8_t rhport) {
+ (void)rhport;
+
+ // Disable interrupts
+ USBHSH->USBINTR = 0;
+ USBHSH->USBSTS = 0xFFFFFFFF;
+
+ // Disable controller
+ USBHSH->USBCMD &= ~(USBHSH_USBCMD_ATL_EN_MASK | USBHSH_USBCMD_INT_EN_MASK | USBHSH_USBCMD_ISO_EN_MASK | USBHSH_USBCMD_RS_MASK);
+
+ // Turn off power switch
+ if (USBHSH->HCSPARAMS & USBHSH_HCSPARAMS_PPC_MASK) {
+ USBHSH->PORTSC1 &= ~USBHSH_PORTSC1_PP_MASK;
+ }
+
+ // Connect PHY to device mode
+ USBHSH->PORTMODE = USBHSH_PORTMODE_SW_CTRL_PDCOM_MASK | USBHSH_PORTMODE_DEV_ENABLE_MASK;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Port API
+//--------------------------------------------------------------------+
+
+// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete.
+// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence.
+void hcd_port_reset(uint8_t rhport) {
+ (void)rhport;
+ uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK;
+ USBHSH->PORTSC1 = status | USBHSH_PORTSC1_PR_MASK;
+}
+
+// Complete bus reset sequence, may be required by some controllers
+void hcd_port_reset_end(uint8_t rhport) {
+ (void)rhport;
+ uint32_t status = USBHSH->PORTSC1 & ~USBHSH_PORTSC1_W1C_MASK;
+ USBHSH->PORTSC1 = status & ~USBHSH_PORTSC1_PR_MASK;
+ while (USBHSH->PORTSC1 & USBHSH_PORTSC1_PR_MASK) {}
+#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
+ uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT;
+ if (pspd == IP3516_PSPD_HIGH) {
+ // enable phy disconnection for high speed
+ USBPHY->CTRL |= USBPHY_CTRL_ENHOSTDISCONDETECT_MASK;
+ }
+#endif
+}
+
+// Get the current connect status of roothub port
+bool hcd_port_connect_status(uint8_t rhport) {
+ (void)rhport;
+ return (USBHSH->PORTSC1 & USBHSH_PORTSC1_CCS_MASK) ? true : false;
+}
+
+// Get port link speed
+tusb_speed_t hcd_port_speed_get(uint8_t rhport) {
+ (void)rhport;
+ uint32_t pspd = (USBHSH->PORTSC1 & USBHSH_PORTSC1_PSPD_MASK) >> USBHSH_PORTSC1_PSPD_SHIFT;
+ switch (pspd) {
+ case IP3516_PSPD_LOW:
+ return TUSB_SPEED_LOW;
+ case IP3516_PSPD_FULL:
+ return TUSB_SPEED_FULL;
+ case IP3516_PSPD_HIGH:
+ return TUSB_SPEED_HIGH;
+ default:
+ return TUSB_SPEED_INVALID;
+ }
+}
+
+// Get frame number (1ms)
+uint32_t hcd_frame_number(uint8_t rhport) {
+ (void)rhport;
+ uint32_t uframe = (USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
+ uframe &= (_hcd_data.uframe_length - 1);
+ return (uframe + _hcd_data.uframe_number) >> 3;
+}
+
+// HCD closes all opened endpoints belong to this device
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr) {
+ (void)rhport;
+
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), &USBHSH->ATLPTDS);
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->INTPTDS);
+ close_ptds_by_device(dev_addr, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), &USBHSH->ISOPTDS);
+}
+
+//--------------------------------------------------------------------+
+// Endpoints API
+//--------------------------------------------------------------------+
+
+static inline intptr_t get_ptd_from_index(tusb_xfer_type_t xfer_type, uint8_t ptd_index) {
+ if (is_xfer_async(xfer_type)) {
+ return (intptr_t)&_ptd.atl[ptd_index];
+ } else {
+ if (xfer_type == TUSB_XFER_INTERRUPT) {
+ return (intptr_t)&_ptd.intr[ptd_index];
+ } else {
+ return (intptr_t)&_ptd.iso[ptd_index];
+ }
+ }
+}
+
+
+// Open an endpoint
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) {
+ (void)rhport;
+
+ const uint8_t ep_num = tu_edpt_number(ep_desc->bEndpointAddress);
+ const tusb_xfer_type_t xfer_type = (tusb_xfer_type_t)ep_desc->bmAttributes.xfer;
+
+ tuh_bus_info_t bus_info;
+ tuh_bus_info_get(dev_addr, &bus_info);
+
+ // Find a free PTD
+ uint8_t ptd_index = ptd_find_free(xfer_type);
+ TU_ASSERT(ptd_index != TUSB_INDEX_INVALID_8);
+
+ // Configure PTD
+ intptr_t ptd_ptr = get_ptd_from_index(xfer_type, ptd_index);
+ volatile ptd_ctrl1_t *ctrl1 = (volatile ptd_ctrl1_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl1));
+ volatile ptd_ctrl2_t *ctrl2 = (volatile ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ volatile ptd_data_t *data = (volatile ptd_data_t *)(ptd_ptr + offsetof(ip3516_atl_t, data));
+ volatile ptd_state_t *state = (volatile ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ // Initialize PTD fields
+ ctrl1->mps = ep_desc->wMaxPacketSize;
+ ctrl1->mult = 1;
+
+ ctrl2->dev_addr = dev_addr;
+ ctrl2->ep_num = ep_num;
+ ctrl2->speed = bus_info.speed == TUSB_SPEED_LOW ? 2 : 0;
+ ctrl2->hub_addr = bus_info.hub_addr;
+ ctrl2->hub_port = bus_info.hub_port;
+ ctrl2->split = (hcd_port_speed_get(rhport) == TUSB_SPEED_HIGH) && (bus_info.speed != TUSB_SPEED_HIGH) ? 1 : 0;
+
+ data->intr = 1;
+
+ state->ep_type = (uint32_t)xfer_type;
+ state->token = tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN ? IP3516_PTD_TOKEN_IN : IP3516_PTD_TOKEN_OUT;
+
+ if (!is_xfer_async(xfer_type)) {
+ ip3516_ptl_t *ptd = (ip3516_ptl_t *)ptd_ptr;
+
+ uint32_t uframe_interval;
+ if (bus_info.speed == TUSB_SPEED_HIGH) {
+ uframe_interval = 1 << (ep_desc->bInterval - 1);
+ } else {
+ uframe_interval = ep_desc->bInterval << 3;
+ // round down to nearest power of 2
+ uframe_interval = 1 << tu_log2(uframe_interval);
+ }
+ uframe_interval = tu_min32(uframe_interval, IP3516_MAX_UFRAME);
+
+ // uframe_active is an 8-bit mask, where each bit corresponds to a micro-frame within a 1ms frame.
+ // A '1' indicates the endpoint should be polled in that micro-frame.
+ // For example:
+ // Interval 1 (poll every u-frame) -> mask is 0b11111111 (0xFF)
+ // Interval 2 (poll every 2nd u-frame, e.g., 0, 2, 4, 6) -> mask is 0b10101010 (0xAA)
+ // Interval 4 (poll every 4th u-frame, e.g., 0, 4) -> mask is 0b10001000 (0x88)
+ // Interval 8 (poll every 8th u-frame, e.g., 0) -> mask is 0b10000000 (0x80)
+ switch (uframe_interval) {
+ case 1:
+ ptd->status.uframe_active = 0xFF;
+ break;
+ case 2:
+ ptd->status.uframe_active = 0xAA;
+ break;
+ case 4:
+ ptd->status.uframe_active = 0x11;
+ break;
+ case 8:
+ ptd->status.uframe_active = 0x01;
+ break;
+ default:
+ // For intervals > 8, we poll once per frame (every 8 u-frames) and use ctrl1.uframe to skip frames.
+ ptd->status.uframe_active = 0x01;
+ if (uframe_interval >= 16) {
+ ctrl1->uframe = tu_log2(uframe_interval) - 3;
+ }
+ break;
+ }
+
+ if (ctrl2->split) {
+ // 11.18.1 Best Case Full-Speed Budget
+ //
+ // A microframe of time allows at most 187.5 raw bytes of signaling on a full-speed bus.
+ // The best case full-speed budget assumes that 188 full-speed bytes occur in each microframe.
+ //
+ // A 1 ms frame subdivided into microframes of budget time:
+ //
+ // Microframes Y_0 Y_1 Y_2 Y_3 Y_4 Y_5 Y_6 Y_7
+ // Max wire time 187.5 187.5 187.5 187.5 187.5 187.5 32
+ // Best case wire budget 188 188 188 188 188 188 29
+ //
+ // 11.18.4 Host Split Transaction Scheduling Requirements
+ //
+ // 1. The host must never schedule a start-split in microframe Y_6.
+ // 2. For isochronous OUT full-speed transactions, for each microframe in which the transaction is
+ // budgeted, the host must schedule a 188 (or the remaining data size) data byte start-split transaction.
+ // For isochronous IN and interrupt IN/OUT full-/low-speed transactions, a single start-split must be
+ // scheduled in the microframe before the transaction is budgeted to start on the full-/low-speed bus.
+ // 3. For isochronous OUT full-speed transactions, the host must never schedule a complete-split. The
+ // TT response to a complete-split for an isochronous OUT is undefined.
+ // For interrupt IN/OUT full-/low-speed transactions, the host must schedule a complete-split
+ // transaction in each of the two microframes following the first microframe in which the full-/low-
+ // speed transaction is budgeted. An additional complete-split must also be scheduled in the third
+ // following microframe unless the full-/low-speed transaction was budgeted to start in microframe Y_6
+ // For isochronous IN full-speed transactions, for each microframe in which the full-speed transaction
+ // is budgeted, a complete-split must be scheduled for each following microframe.
+ // Also, determine the last microframe in which a complete-split is scheduled, call it L.
+ // If L is less than Y_6, schedule additional complete-splits in microframe L+1 and L+2.
+ // If L is equal to Y_6, schedule one complete-split in microframe Y_7.
+ //
+ // TODO: Implement budget check scheduling
+ // Otherwise, it may cause bus contention with other split transfers
+ // Here we simply start interrupt transfers for Y_0 and Y1 and isochronous transfers for Y_2
+ if (xfer_type == TUSB_XFER_ISOCHRONOUS) {
+ const uint8_t ss_slot = 2; // Start-split slot
+ const uint8_t slots = (ep_desc->wMaxPacketSize + 187) / 188;
+ const tusb_dir_t ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress);
+ if (ep_dir == TUSB_DIR_IN) {
+ if (ep_desc->wMaxPacketSize > 192) {
+ ctrl1->mps = 192;
+ }
+ ptd->status.uframe_active = 1 << ss_slot;
+ for (uint8_t i = 0; i < slots; i++) {
+ ptd->iso_in_0.uframe_complete |= 1 << (2 + ss_slot + i);
+ }
+ // Schedule additional complete-splits if needed
+ uint8_t last_complete = ss_slot + slots + 1;
+ if (last_complete < 6) {
+ ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 1);
+ ptd->iso_in_0.uframe_complete |= 1 << (ss_slot + last_complete + 2);
+ } else if (last_complete == 6) {
+ ptd->iso_in_0.uframe_complete |= 1 << 7;
+ }
+ } else {
+ if (ep_desc->wMaxPacketSize > 188) {
+ ctrl1->mps = 188;
+ }
+ for (uint8_t i = 0; i < slots; i++) {
+ ptd->status.uframe_active |= 1 << (ss_slot + i);
+ }
+ }
+ } else {
+ // Start-split slot, jigging to avoid bus contention: EP odd -> Y_1, EP even -> Y_0
+ const uint8_t ss_slot = ep_num & 0x01;
+ ptd->status.uframe_active = 1 << ss_slot;
+ // Complete-split slots: next 3 u-frames
+ ptd->iso_in_0.uframe_complete = 0x1c << ss_slot;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Close an opened endpoint
+bool hcd_edpt_close(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t), NULL)) {
+ return true;
+ }
+
+ // Search in INT
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) {
+ return true;
+ }
+
+ // Search in ISO
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t), NULL)) {
+ return true;
+ }
+
+ return false;
+}
+
+// Submit a transfer on an endpoint
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) {
+ (void)rhport;
+
+ return edpt_xfer(dev_addr, ep_addr, buffer, buflen, false);
+}
+
+// Abort a queued transfer. Note: it can only abort transfer that has not been started
+// Return true if a queued transfer is aborted, false if there is no transfer to abort
+bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+
+ const uint8_t ep_num = tu_edpt_number(ep_addr);
+ const uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ // Search in ATL
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.atl, IP3516_ATL_NUM, sizeof(ip3516_atl_t),
+ &USBHSH->ATLPTDS)) {
+ return true;
+ }
+
+ // Search in INT
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.intr, IP3516_PTL_NUM, sizeof(ip3516_ptl_t),
+ &USBHSH->INTPTDS)) {
+ return true;
+ }
+
+ // Search in ISO
+ if (find_and_close_ptd(dev_addr, ep_num, ep_dir, (intptr_t)&_ptd.iso, IP3516_PTL_NUM, sizeof(ip3516_ptl_t),
+ &USBHSH->ISOPTDS)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) {
+ (void)rhport;
+
+ return edpt_xfer(dev_addr, 0x00, (uint8_t *)(uintptr_t)setup_packet, 8, true);
+}
+
+bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
+ (void)rhport;
+
+ intptr_t ptd_ptr = find_opened_ptd(dev_addr, ep_addr);
+ TU_ASSERT(ptd_ptr != 0);
+
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+ ptd_clear_state(ptd_state);
+ ptd_state->data_toggle = 0; // reset data toggle to DATA0
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Interrupt Handler
+//--------------------------------------------------------------------+
+
+// Handle port status change event
+static inline void handle_port_status_change(uint8_t rhport) {
+ const uint32_t status = USBHSH->PORTSC1;
+
+ if (status & USBHSH_PORTSC1_CSC_MASK) {
+ if (status & USBHSH_PORTSC1_CCS_MASK && !_hcd_data.attached) {
+ _hcd_data.attached = true;
+ hcd_event_device_attach(rhport, true);
+ } else {
+ _hcd_data.attached = false;
+ hcd_event_device_remove(rhport, true);
+ #if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
+ // disable phy disconnection for high speed
+ USBPHY->CTRL &= ~USBPHY_CTRL_ENHOSTDISCONDETECT_MASK;
+ #endif
+ }
+ }
+
+ USBHSH->PORTSC1 |= status & USBHSH_PORTSC1_W1C_MASK;
+}
+
+// Handle PTD done interrupt
+static inline void handle_ptd_done(uint32_t done_status, intptr_t ptd_array, bool is_async) {
+ uint8_t max_count = is_async ? IP3516_ATL_NUM : IP3516_PTL_NUM;
+ uint8_t stride = is_async ? sizeof(ip3516_atl_t) : sizeof(ip3516_ptl_t);
+
+ for (uint8_t i = 0; i < max_count; i++) {
+ if (done_status & (1 << i)) {
+ intptr_t ptd_ptr = ptd_array + i * stride;
+ ptd_ctrl2_t *ptd_ctrl2 = (ptd_ctrl2_t *)(ptd_ptr + offsetof(ip3516_atl_t, ctrl2));
+ ptd_state_t *ptd_state = (ptd_state_t *)(ptd_ptr + offsetof(ip3516_atl_t, state));
+
+ xfer_result_t result;
+ if (ptd_state->halt) {
+ result = XFER_RESULT_STALLED;
+ } else if (ptd_state->error || ptd_state->babble) {
+ result = XFER_RESULT_FAILED;
+ } else {
+ result = XFER_RESULT_SUCCESS;
+ }
+
+ uint8_t ep_addr = ptd_ctrl2->ep_num | (ptd_state->token == IP3516_PTD_TOKEN_IN ? 0x80 : 0x00);
+
+ hcd_event_xfer_complete(ptd_ctrl2->dev_addr, ep_addr, ptd_state->xferred_len, result, true);
+ }
+ }
+}
+
+void hcd_int_handler(uint8_t rhport, bool in_isr) {
+ (void)in_isr;
+
+ uint32_t int_status = USBHSH->USBSTS;
+ USBHSH->USBSTS = int_status; // clear interrupt status
+
+ // Port Change Detect
+ if (int_status & USBHSH_USBSTS_PCD_MASK) {
+ handle_port_status_change(rhport);
+ }
+
+ // Frame List Rollover
+ if (int_status & USBHSH_USBSTS_FLR_MASK) {
+ _hcd_data.uframe_number += _hcd_data.uframe_length;
+ }
+
+ // ATL done
+ if (int_status & USBHSH_USBSTS_ATL_IRQ_MASK) {
+ uint32_t done_status = USBHSH->ATLPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.atl, true);
+ USBHSH->ATLPTDD = done_status;
+ }
+
+ // INT done
+ if (int_status & USBHSH_USBSTS_INT_IRQ_MASK) {
+ uint32_t done_status = USBHSH->INTPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.intr, false);
+ USBHSH->INTPTDD = done_status;
+ }
+
+ // ISO done
+ if (int_status & USBHSH_USBSTS_ISO_IRQ_MASK) {
+ uint32_t done_status = USBHSH->ISOPTDD;
+ handle_ptd_done(done_status, (intptr_t)&_ptd.iso, false);
+ USBHSH->ISOPTDD = done_status;
+ }
+}
+
+#endif
diff --git a/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h
new file mode 100644
index 000000000..5214c18ae
--- /dev/null
+++ b/src/portable/nxp/lpc_ip3516/hcd_lpc_ip3516.h
@@ -0,0 +1,188 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 HiFiPhile (Zixun LI)
+ *
+ * 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.
+ */
+
+#ifndef TUSB_HCD_IP3516_H_
+#define TUSB_HCD_IP3516_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// IP3516 CONFIGURATION & CONSTANTS
+//--------------------------------------------------------------------+
+
+#define IP3516_ATL_NUM 32
+#define IP3516_PTL_NUM 32
+
+#define IP3516_PTD_TOKEN_OUT 0x00U
+#define IP3516_PTD_TOKEN_IN 0x01U
+#define IP3516_PTD_TOKEN_SETUP 0x02U
+
+#define IP3516_PTD_EPTYPE_OUT 0x00U
+#define IP3516_PTD_EPTYPE_IN 0x01U
+#define IP3516_PTD_EPTYPE_SETUP 0x02U
+
+#define IP3516_PTD_MAX_TRANSFER_LENGTH 0x7FFFU
+
+#define IP3516_PTD_DATA_ADDR_MASK 0xFFFFU
+
+#define IP3516_MAX_UFRAME (1UL << 8)
+
+#define IP3516_PERIODIC_TRANSFER_GAP (3U)
+#define IP3516_ISO_MULTIPLE_TRANSFER (8U)
+
+
+//--------------------------------------------------------------------+
+// IP3516 PTD Data Structure
+//--------------------------------------------------------------------+
+
+// Control Word 1
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t valid : 1;
+ uint32_t next_ptd : 5;
+ uint32_t : 1;
+ uint32_t jump : 1;
+ uint32_t uframe : 8;
+ uint32_t mps : 11;
+ uint32_t : 1;
+ uint32_t mult : 2;
+ uint32_t : 2;
+ };
+} ptd_ctrl1_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_ctrl1_t) == 4, "size is not correct");
+
+// Control Word 2
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t ep_num : 4;
+ uint32_t dev_addr : 7;
+ uint32_t split : 1;
+ uint32_t reload : 4;
+ uint32_t speed : 2;
+ uint32_t hub_port : 7;
+ uint32_t hub_addr : 7;
+ };
+} ptd_ctrl2_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_ctrl2_t) == 4, "size is not correct");
+
+// Data Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t xfer_len : 15;
+ uint32_t intr : 1;
+ uint32_t data_addr : 16;
+ };
+} ptd_data_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_data_t) == 4, "size is not correct");
+
+// State Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t xferred_len : 15;
+ uint32_t token : 2;
+ uint32_t ep_type : 2;
+ uint32_t nak_cnt : 4;
+ uint32_t err_cnt : 2;
+ uint32_t data_toggle : 1;
+ uint32_t ping : 1;
+ uint32_t start_complete : 1;
+ uint32_t error : 1;
+ uint32_t babble : 1;
+ uint32_t halt : 1;
+ uint32_t active : 1;
+ };
+} ptd_state_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_state_t) == 4, "size is not correct");
+
+// Status Word
+typedef union {
+ uint32_t value;
+ struct {
+ uint32_t uframe_active : 8;
+ uint32_t iso_status0 : 3;
+ uint32_t iso_status1 : 3;
+ uint32_t iso_status2 : 3;
+ uint32_t iso_status3 : 3;
+ uint32_t iso_status4 : 3;
+ uint32_t iso_status5 : 3;
+ uint32_t iso_status6 : 3;
+ uint32_t iso_status7 : 3;
+ };
+} ptd_status_t;
+
+TU_VERIFY_STATIC(sizeof(ptd_status_t) == 4, "size is not correct");
+
+// ATL (Asynchronous Transfer List) structure
+typedef volatile struct {
+ ptd_ctrl1_t ctrl1;
+ ptd_ctrl2_t ctrl2;
+ ptd_data_t data;
+ ptd_state_t state;
+} ip3516_atl_t;
+
+TU_VERIFY_STATIC(sizeof(ip3516_atl_t) == 16, "size is not correct");
+
+// PTL (Periodic Transfer List) structure
+typedef volatile struct {
+ ptd_ctrl1_t ctrl1;
+ ptd_ctrl2_t ctrl2;
+ ptd_data_t data;
+ ptd_state_t state;
+ ptd_status_t status;
+ union {
+ uint32_t value;
+ struct {
+ uint32_t uframe_complete : 8;
+ uint32_t spl_iso_in_0 : 24;
+ };
+ } iso_in_0;
+ uint32_t iso_in_1;
+ uint32_t iso_in_2;
+} ip3516_ptl_t;
+
+TU_VERIFY_STATIC(sizeof(ip3516_ptl_t) == 32, "size is not correct");
+
+// Proprietary Transfer Descriptor
+typedef struct {
+ ip3516_ptl_t intr[IP3516_PTL_NUM];
+ ip3516_ptl_t iso[IP3516_PTL_NUM];
+ ip3516_atl_t atl[IP3516_ATL_NUM];
+} ip3516_ptd_t;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* TUSB_HCD_IP3516_H_ */
diff --git a/src/portable/ohci/ohci.c b/src/portable/ohci/ohci.c
index 5ca093506..4b248d9e6 100644
--- a/src/portable/ohci/ohci.c
+++ b/src/portable/ohci/ohci.c
@@ -243,7 +243,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous
OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI;
- OHCI_REG->frame_interval ^= (1 << 31); //Must toggle when frame_interval is updated.
+ OHCI_REG->frame_interval ^= (1ul << 31); //Must toggle when frame_interval is updated.
OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval
OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power)
@@ -557,6 +557,7 @@ bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
(void) rhport;
ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr);
+ TU_ASSERT(p_ed);
ohci_ed_word2_t td_head = p_ed->td_head;
td_head.toggle = 0; // reset data toggle