hcd: add NXP IP3516

Signed-off-by: HiFiPhile <admin@hifiphile.com>
This commit is contained in:
HiFiPhile
2025-12-10 21:37:00 +01:00
committed by Zixun LI
parent 2b9a778621
commit e86e894a23
3 changed files with 977 additions and 0 deletions

View File

@ -73,11 +73,13 @@
#elif TU_CHECK_MCU(OPT_MCU_LPC54)
// TODO USB0 has 5, USB1 has 6
#define TUP_USBIP_IP3511
#define TUP_USBIP_IP3516
#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

View File

@ -0,0 +1,787 @@
/*
* 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 CFG_TUSB_MCU == OPT_MCU_LPC55
#include "fsl_device_registers.h"
#else
#error "Unsupported MCUs"
#endif
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF
//--------------------------------------------------------------------+
#define USBHSH_PORTSC1_W1C_MASK (USBHSH_PORTSC1_CSC_MASK | USBHSH_PORTSC1_PEDC_MASK | USBHSH_PORTSC1_OCC_MASK)
//--------------------------------------------------------------------+
// Proprietary Transfer Descriptor
//--------------------------------------------------------------------+
CFG_TUD_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_asyc(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_asyc(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
uint32_t start_uframe =
(USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
start_uframe) {}
// 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
uint32_t start_uframe =
(USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT;
while (((USBHSH->FLADJ_FRINDEX & USBHSH_FLADJ_FRINDEX_FRINDEX_MASK) >> USBHSH_FLADJ_FRINDEX_FRINDEX_SHIFT) ==
start_uframe) {}
// 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 TUSB_INDEX_INVALID_8;
}
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 != TUSB_INDEX_INVALID_8);
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 relauched 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 == 2) {
// 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 0:
return TUSB_SPEED_LOW;
case 1:
return TUSB_SPEED_FULL;
case 2:
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;
return ((uframe & (_hcd_data.uframe_length - 1) + _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_asyc(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_asyc(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 != TUSB_INDEX_INVALID_8);
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

View File

@ -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)
//--------------------------------------------------------------------+
// OHCI 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_ */