|
|
|
@ -48,8 +48,8 @@
|
|
|
|
|
#ifdef TUP_USBIP_CHIPIDEA_HS
|
|
|
|
|
// NXP Transdimension: 8 elements
|
|
|
|
|
#define FRAMELIST_SIZE_BIT_VALUE 7u
|
|
|
|
|
#define FRAMELIST_SIZE_USBCMD_VALUE (((FRAMELIST_SIZE_BIT_VALUE & 3) << EHCI_USBCMD_POS_FRAMELIST_SIZE) | \
|
|
|
|
|
((FRAMELIST_SIZE_BIT_VALUE >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB))
|
|
|
|
|
#define FRAMELIST_SIZE_USBCMD_VALUE (((FRAMELIST_SIZE_BIT_VALUE & 3) << EHCI_USBCMD_FRAMELIST_SIZE_SHIFT) | \
|
|
|
|
|
((FRAMELIST_SIZE_BIT_VALUE >> 2) << EHCI_USBCMD_CHIPIDEA_FRAMELIST_SIZE_MSB_SHIFT))
|
|
|
|
|
#else
|
|
|
|
|
// STD EHCI: 256 elements
|
|
|
|
|
#define FRAMELIST_SIZE_BIT_VALUE 2u
|
|
|
|
@ -126,55 +126,50 @@ static inline void print_intr(uint32_t intr) {
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
// PROTOTYPE
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
static inline ehci_link_t* get_period_head(uint8_t rhport, uint32_t interval_ms)
|
|
|
|
|
{
|
|
|
|
|
(void) rhport;
|
|
|
|
|
return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min32(FRAMELIST_SIZE, interval_ms) ) ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_control(uint8_t dev_addr)
|
|
|
|
|
{
|
|
|
|
|
return &ehci_data.control[dev_addr].qhd;
|
|
|
|
|
}
|
|
|
|
|
// weak dcache for non-cacheable MCU
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_clean(void const* addr, uint32_t data_size) { (void) addr; (void) data_size; return true; }
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_invalidate(void const* addr, uint32_t data_size) { (void) addr; (void) data_size; return true; }
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(void const* addr, uint32_t data_size) { (void) addr; (void) data_size; return true; }
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_async_head(uint8_t rhport)
|
|
|
|
|
{
|
|
|
|
|
(void) rhport;
|
|
|
|
|
// control qhd of dev0 is used as async head
|
|
|
|
|
return qhd_control(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline ehci_qtd_t* qtd_control(uint8_t dev_addr)
|
|
|
|
|
{
|
|
|
|
|
return &ehci_data.control[dev_addr].qtd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_next (ehci_qhd_t const * p_qhd);
|
|
|
|
|
static inline ehci_qhd_t* qhd_find_free (void);
|
|
|
|
|
static inline ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* qhd_control(uint8_t dev_addr);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* qhd_next (ehci_qhd_t const * p_qhd);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* qhd_find_free (void);
|
|
|
|
|
static ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr);
|
|
|
|
|
static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
|
|
|
|
|
static void qhd_attach_qtd(ehci_qhd_t *qhd, ehci_qtd_t *qtd);
|
|
|
|
|
static void qhd_remove_qtd(ehci_qhd_t *qhd);
|
|
|
|
|
|
|
|
|
|
static inline ehci_qtd_t* qtd_find_free (void);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t* qtd_control(uint8_t dev_addr);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t* qtd_find_free (void);
|
|
|
|
|
static void qtd_init (ehci_qtd_t* qtd, void const* buffer, uint16_t total_bytes);
|
|
|
|
|
|
|
|
|
|
static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type);
|
|
|
|
|
static inline ehci_link_t* list_next (ehci_link_t const *p_link);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_get_period_head(uint8_t rhport, uint32_t interval_ms);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* list_get_async_head(uint8_t rhport);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type);
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_next (ehci_link_t const *p_link);
|
|
|
|
|
static void list_remove_qhd_by_daddr(ehci_link_t* list_head, uint8_t dev_addr);
|
|
|
|
|
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_clean(void const* addr, uint32_t data_size) {
|
|
|
|
|
(void) addr; (void) data_size;
|
|
|
|
|
return true;
|
|
|
|
|
static void ehci_disable_schedule(ehci_registers_t* regs, bool is_period) {
|
|
|
|
|
// maybe have a timeout for status
|
|
|
|
|
if (is_period) {
|
|
|
|
|
regs->command_bm.periodic_enable = 0;
|
|
|
|
|
while(regs->status_bm.periodic_status) {}
|
|
|
|
|
} else {
|
|
|
|
|
regs->command_bm.async_enable = 0;
|
|
|
|
|
while(regs->status_bm.async_status) {} // should have a timeout
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_invalidate(void const* addr, uint32_t data_size) {
|
|
|
|
|
(void) addr; (void) data_size;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(void const* addr, uint32_t data_size) {
|
|
|
|
|
(void) addr; (void) data_size;
|
|
|
|
|
return true;
|
|
|
|
|
static void ehci_enable_schedule(ehci_registers_t* regs, bool is_period) {
|
|
|
|
|
// maybe have a timeout for status
|
|
|
|
|
if (is_period) {
|
|
|
|
|
regs->command_bm.periodic_enable = 1;
|
|
|
|
|
while ( 0 == regs->status_bm.periodic_status ) {}
|
|
|
|
|
} else {
|
|
|
|
|
regs->command_bm.async_enable = 1;
|
|
|
|
|
while( 0 == regs->status_bm.async_status ) {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
@ -231,43 +226,6 @@ tusb_speed_t hcd_port_speed_get(uint8_t rhport)
|
|
|
|
|
return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void list_remove_qhd_by_daddr(ehci_link_t* list_head, uint8_t dev_addr) {
|
|
|
|
|
ehci_link_t* prev = list_head;
|
|
|
|
|
|
|
|
|
|
while (prev && !prev->terminate) {
|
|
|
|
|
ehci_qhd_t* qhd = (ehci_qhd_t*) (uintptr_t) list_next(prev);
|
|
|
|
|
|
|
|
|
|
// done if loop back to head
|
|
|
|
|
if ( (uintptr_t) qhd == (uintptr_t) list_head) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( qhd->dev_addr == dev_addr ) {
|
|
|
|
|
// TODO deactivate all TD, wait for QHD to inactive before removal
|
|
|
|
|
prev->address = qhd->next.address;
|
|
|
|
|
|
|
|
|
|
// EHCI 4.8.2 link the removed qhd's next to async head (which always reachable by Host Controller)
|
|
|
|
|
qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1);
|
|
|
|
|
|
|
|
|
|
if ( qhd->int_smask )
|
|
|
|
|
{
|
|
|
|
|
// period list queue element is guarantee to be free in the next frame (1 ms)
|
|
|
|
|
qhd->used = 0;
|
|
|
|
|
}else
|
|
|
|
|
{
|
|
|
|
|
// async list use async advance handshake
|
|
|
|
|
// mark as removing, will completely re-usable when async advance isr occurs
|
|
|
|
|
qhd->removing = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hcd_dcache_clean(qhd, sizeof(ehci_qhd_t));
|
|
|
|
|
hcd_dcache_clean(prev, sizeof(ehci_qhd_t));
|
|
|
|
|
}else {
|
|
|
|
|
prev = list_next(prev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close all opened endpoint belong to this device
|
|
|
|
|
void hcd_device_close(uint8_t rhport, uint8_t daddr)
|
|
|
|
|
{
|
|
|
|
@ -277,7 +235,7 @@ void hcd_device_close(uint8_t rhport, uint8_t daddr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove from async list
|
|
|
|
|
list_remove_qhd_by_daddr((ehci_link_t *) qhd_async_head(rhport), daddr);
|
|
|
|
|
list_remove_qhd_by_daddr((ehci_link_t *) list_get_async_head(rhport), daddr);
|
|
|
|
|
|
|
|
|
|
// Remove from all interval period list
|
|
|
|
|
for(uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++) {
|
|
|
|
@ -289,37 +247,42 @@ void hcd_device_close(uint8_t rhport, uint8_t daddr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void init_periodic_list(uint8_t rhport) {
|
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
|
|
// Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only
|
|
|
|
|
for ( uint32_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++ ) {
|
|
|
|
|
ehci_data.period_head_arr[i].int_smask = 1; // queue head in period list must have smask non-zero
|
|
|
|
|
ehci_data.period_head_arr[i].qtd_overlay.halted = 1; // dummy node, always inactive
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ehci_link_t * const framelist = ehci_data.period_framelist;
|
|
|
|
|
ehci_link_t * const period_1ms = get_period_head(rhport, 1u);
|
|
|
|
|
|
|
|
|
|
// TODO EHCI_FRAMELIST_SIZE with other size than 8
|
|
|
|
|
// all links --> period_head_arr[0] (1ms)
|
|
|
|
|
// 0, 2, 4, 6 etc --> period_head_arr[1] (2ms)
|
|
|
|
|
// 1, 5 --> period_head_arr[2] (4ms)
|
|
|
|
|
// 3 --> period_head_arr[3] (8ms)
|
|
|
|
|
|
|
|
|
|
// TODO EHCI_FRAMELIST_SIZE with other size than 8
|
|
|
|
|
ehci_link_t * const framelist = ehci_data.period_framelist;
|
|
|
|
|
ehci_link_t * const head_1ms = (ehci_link_t *) &ehci_data.period_head_arr[0];
|
|
|
|
|
ehci_link_t * const head_2ms = (ehci_link_t *) &ehci_data.period_head_arr[1];
|
|
|
|
|
ehci_link_t * const head_4ms = (ehci_link_t *) &ehci_data.period_head_arr[2];
|
|
|
|
|
ehci_link_t * const head_8ms = (ehci_link_t *) &ehci_data.period_head_arr[3];
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < FRAMELIST_SIZE; i++) {
|
|
|
|
|
framelist[i].address = (uint32_t) period_1ms;
|
|
|
|
|
framelist[i].address = (uint32_t) head_1ms;
|
|
|
|
|
framelist[i].type = EHCI_QTYPE_QHD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < FRAMELIST_SIZE; i += 2) {
|
|
|
|
|
list_insert(framelist + i, get_period_head(rhport, 2u), EHCI_QTYPE_QHD);
|
|
|
|
|
list_insert(framelist + i, head_2ms, EHCI_QTYPE_QHD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 1; i < FRAMELIST_SIZE; i += 4) {
|
|
|
|
|
list_insert(framelist + i, get_period_head(rhport, 4u), EHCI_QTYPE_QHD);
|
|
|
|
|
list_insert(framelist + i, head_4ms, EHCI_QTYPE_QHD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_insert(framelist + 3, get_period_head(rhport, 8u), EHCI_QTYPE_QHD);
|
|
|
|
|
list_insert(framelist + 3, head_8ms, EHCI_QTYPE_QHD);
|
|
|
|
|
|
|
|
|
|
period_1ms->terminate = 1;
|
|
|
|
|
head_1ms->terminate = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
|
|
|
|
@ -348,7 +311,7 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
|
|
|
|
|
EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC | EHCI_INT_MASK_FRAMELIST_ROLLOVER;
|
|
|
|
|
|
|
|
|
|
//------------- Asynchronous List -------------//
|
|
|
|
|
ehci_qhd_t * const async_head = qhd_async_head(rhport);
|
|
|
|
|
ehci_qhd_t * const async_head = list_get_async_head(rhport);
|
|
|
|
|
tu_memclr(async_head, sizeof(ehci_qhd_t));
|
|
|
|
|
|
|
|
|
|
async_head->next.address = (uint32_t) async_head; // circular list, next is itself
|
|
|
|
@ -369,8 +332,7 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
|
|
|
|
|
regs->nxp_tt_control = 0;
|
|
|
|
|
|
|
|
|
|
//------------- USB CMD Register -------------//
|
|
|
|
|
regs->command |= TU_BIT(EHCI_USBCMD_POS_RUN_STOP) | TU_BIT(EHCI_USBCMD_POS_ASYNC_ENABLE) |
|
|
|
|
|
TU_BIT(EHCI_USBCMD_POS_PERIOD_ENABLE) | // TODO enable period list only there is int/iso endpoint
|
|
|
|
|
regs->command |= EHCI_USBCMD_RUN_STOP | EHCI_USBCMD_PERIOD_SCHEDULE_ENABLE | EHCI_USBCMD_ASYNC_SCHEDULE_ENABLE |
|
|
|
|
|
FRAMELIST_SIZE_USBCMD_VALUE;
|
|
|
|
|
|
|
|
|
|
//------------- ConfigFlag Register (skip) -------------//
|
|
|
|
@ -380,7 +342,7 @@ bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
|
|
|
|
|
if (ehci_data.cap_regs->hcsparams_bm.port_power_control) {
|
|
|
|
|
// mask out all change bits since they are Write 1 to clear
|
|
|
|
|
uint32_t portsc = (regs->portsc & ~EHCI_PORTSC_MASK_W1C);
|
|
|
|
|
portsc |= ECHI_PORTSC_MASK_PORT_POWER;
|
|
|
|
|
portsc |= EHCI_PORTSC_MASK_PORT_POWER;
|
|
|
|
|
|
|
|
|
|
regs->portsc = portsc;
|
|
|
|
|
}
|
|
|
|
@ -429,11 +391,11 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const
|
|
|
|
|
{
|
|
|
|
|
case TUSB_XFER_CONTROL:
|
|
|
|
|
case TUSB_XFER_BULK:
|
|
|
|
|
list_head = (ehci_link_t*) qhd_async_head(rhport);
|
|
|
|
|
list_head = (ehci_link_t*) list_get_async_head(rhport);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TUSB_XFER_INTERRUPT:
|
|
|
|
|
list_head = get_period_head(rhport, p_qhd->interval_ms);
|
|
|
|
|
list_head = list_get_period_head(rhport, p_qhd->interval_ms);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TUSB_XFER_ISOCHRONOUS:
|
|
|
|
@ -442,10 +404,8 @@ bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const
|
|
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ASSERT(list_head);
|
|
|
|
|
|
|
|
|
|
// TODO might need to disable async/period list
|
|
|
|
|
list_insert(list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD);
|
|
|
|
|
|
|
|
|
|
hcd_dcache_clean(p_qhd, sizeof(ehci_qhd_t));
|
|
|
|
@ -479,20 +439,17 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
|
|
|
|
|
uint8_t const epnum = tu_edpt_number(ep_addr);
|
|
|
|
|
uint8_t const dir = tu_edpt_dir(ep_addr);
|
|
|
|
|
|
|
|
|
|
ehci_qhd_t* qhd;
|
|
|
|
|
ehci_qhd_t* qhd = qhd_get_from_addr(dev_addr, ep_addr);
|
|
|
|
|
ehci_qtd_t* qtd;
|
|
|
|
|
|
|
|
|
|
if (epnum == 0) {
|
|
|
|
|
qhd = qhd_control(dev_addr);
|
|
|
|
|
qtd = qtd_control(dev_addr);
|
|
|
|
|
|
|
|
|
|
qtd_init(qtd, buffer, buflen);
|
|
|
|
|
|
|
|
|
|
// first data toggle is always 1 (data & setup stage)
|
|
|
|
|
qtd->data_toggle = 1;
|
|
|
|
|
qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT;
|
|
|
|
|
} else {
|
|
|
|
|
qhd = qhd_get_from_addr(dev_addr, ep_addr);
|
|
|
|
|
qtd = qtd_find_free();
|
|
|
|
|
TU_ASSERT(qtd);
|
|
|
|
|
|
|
|
|
@ -513,6 +470,38 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
|
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
|
|
// TODO ISO not supported yet
|
|
|
|
|
ehci_qhd_t* qhd = qhd_get_from_addr(dev_addr, ep_addr);
|
|
|
|
|
ehci_qtd_t * volatile qtd = qhd->attached_qtd;
|
|
|
|
|
TU_VERIFY(qtd != NULL); // no queued transfer
|
|
|
|
|
|
|
|
|
|
hcd_dcache_invalidate(qtd, sizeof(ehci_qtd_t));
|
|
|
|
|
TU_VERIFY(qtd->active); // transfer is already complete
|
|
|
|
|
|
|
|
|
|
// HC is still processing, disable HC list schedule before making changes
|
|
|
|
|
bool const is_period = (qhd->interval_ms > 0);
|
|
|
|
|
|
|
|
|
|
ehci_disable_schedule(ehci_data.regs, is_period);
|
|
|
|
|
|
|
|
|
|
// check active bit again just in case HC has just processed the TD
|
|
|
|
|
bool const still_active = qtd->active;
|
|
|
|
|
if (still_active) {
|
|
|
|
|
// remove TD from QH overlay
|
|
|
|
|
qhd->qtd_overlay.next.terminate = 1;
|
|
|
|
|
hcd_dcache_clean(qhd, sizeof(ehci_qhd_t));
|
|
|
|
|
|
|
|
|
|
// remove TD from QH software list
|
|
|
|
|
qhd_remove_qtd(qhd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ehci_enable_schedule(ehci_data.regs, is_period);
|
|
|
|
|
|
|
|
|
|
return still_active; // true if removed an active transfer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool hcd_edpt_clear_stall(uint8_t daddr, uint8_t ep_addr)
|
|
|
|
|
{
|
|
|
|
|
ehci_qhd_t *qhd = qhd_get_from_addr(daddr, ep_addr);
|
|
|
|
@ -560,8 +549,12 @@ void port_connect_status_change_isr(uint8_t rhport)
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline
|
|
|
|
|
void qhd_xfer_complete_isr(ehci_qhd_t * qhd) {
|
|
|
|
|
// examine TD attached to queue head
|
|
|
|
|
ehci_qtd_t * volatile qtd = (ehci_qtd_t * volatile) qhd->attached_qtd;
|
|
|
|
|
if (qtd == NULL) return; // no TD attached
|
|
|
|
|
ehci_qtd_t * volatile qtd = qhd->attached_qtd;
|
|
|
|
|
|
|
|
|
|
if (qtd == NULL) {
|
|
|
|
|
return; // no TD attached
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hcd_dcache_invalidate(qtd, sizeof(ehci_qtd_t));
|
|
|
|
|
|
|
|
|
|
// TD is still active, no need to process
|
|
|
|
@ -578,9 +571,7 @@ void qhd_xfer_complete_isr(ehci_qhd_t * qhd) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove and free TD before invoking callback
|
|
|
|
|
qhd->attached_qtd = NULL;
|
|
|
|
|
qhd->attached_buffer = 0;
|
|
|
|
|
qtd->used = 0; // free QTD
|
|
|
|
|
qhd_remove_qtd(qhd);
|
|
|
|
|
|
|
|
|
|
// notify usbh
|
|
|
|
|
uint8_t const ep_addr = tu_edpt_addr(qhd->ep_number, dir);
|
|
|
|
@ -607,8 +598,8 @@ void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline
|
|
|
|
|
void period_list_xfer_complete_isr(uint8_t rhport, uint32_t interval_ms)
|
|
|
|
|
{
|
|
|
|
|
uint32_t const period_1ms_addr = (uint32_t) get_period_head(rhport, 1u);
|
|
|
|
|
ehci_link_t next_link = * get_period_head(rhport, interval_ms);
|
|
|
|
|
uint32_t const period_1ms_addr = (uint32_t) list_get_period_head(rhport, 1u);
|
|
|
|
|
ehci_link_t next_link = *list_get_period_head(rhport, interval_ms);
|
|
|
|
|
|
|
|
|
|
while (!next_link.terminate) {
|
|
|
|
|
if (interval_ms > 1 && period_1ms_addr == tu_align32(next_link.address)) {
|
|
|
|
@ -683,9 +674,7 @@ void qhd_xfer_error_isr(ehci_qhd_t * qhd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove and free TD before invoking callback
|
|
|
|
|
qhd->attached_qtd = NULL;
|
|
|
|
|
qhd->attached_buffer = 0;
|
|
|
|
|
qtd->used = 0; // free QTD
|
|
|
|
|
qhd_remove_qtd(qhd);
|
|
|
|
|
|
|
|
|
|
if (0 == qhd->ep_number ) {
|
|
|
|
|
// control cannot be halted
|
|
|
|
@ -706,7 +695,7 @@ TU_ATTR_ALWAYS_INLINE static inline
|
|
|
|
|
void xfer_error_isr(uint8_t rhport)
|
|
|
|
|
{
|
|
|
|
|
//------------- async list -------------//
|
|
|
|
|
ehci_qhd_t * const async_head = qhd_async_head(rhport);
|
|
|
|
|
ehci_qhd_t * const async_head = list_get_async_head(rhport);
|
|
|
|
|
ehci_qhd_t *p_qhd = async_head;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
@ -716,10 +705,10 @@ void xfer_error_isr(uint8_t rhport)
|
|
|
|
|
}while(p_qhd != async_head); // async list traversal, stop if loop around
|
|
|
|
|
|
|
|
|
|
//------------- TODO refractor period list -------------//
|
|
|
|
|
uint32_t const period_1ms_addr = (uint32_t) get_period_head(rhport, 1u);
|
|
|
|
|
uint32_t const period_1ms_addr = (uint32_t) list_get_period_head(rhport, 1u);
|
|
|
|
|
for (uint32_t interval_ms=1; interval_ms <= FRAMELIST_SIZE; interval_ms *= 2)
|
|
|
|
|
{
|
|
|
|
|
ehci_link_t next_item = * get_period_head(rhport, interval_ms);
|
|
|
|
|
ehci_link_t next_item = *list_get_period_head(rhport, interval_ms);
|
|
|
|
|
|
|
|
|
|
// TODO abstract max loop guard for period
|
|
|
|
|
while( !next_item.terminate &&
|
|
|
|
@ -786,7 +775,7 @@ void hcd_int_handler(uint8_t rhport)
|
|
|
|
|
|
|
|
|
|
//------------- some QTD/SITD/ITD with IOC set is completed -------------//
|
|
|
|
|
if (int_status & EHCI_INT_MASK_NXP_ASYNC) {
|
|
|
|
|
async_list_xfer_complete_isr(qhd_async_head(rhport));
|
|
|
|
|
async_list_xfer_complete_isr(list_get_async_head(rhport));
|
|
|
|
|
regs->status = EHCI_INT_MASK_NXP_ASYNC; // Acknowledge
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -813,35 +802,103 @@ void hcd_int_handler(uint8_t rhport)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
// HELPER
|
|
|
|
|
// List Managing Helper
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// Get head of periodic list
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_get_period_head(uint8_t rhport, uint32_t interval_ms) {
|
|
|
|
|
(void) rhport;
|
|
|
|
|
return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min32(FRAMELIST_SIZE, interval_ms) ) ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------- queue head helper -------------//
|
|
|
|
|
static inline ehci_qhd_t* qhd_find_free (void)
|
|
|
|
|
// Get head of async list
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* list_get_async_head(uint8_t rhport) {
|
|
|
|
|
(void) rhport;
|
|
|
|
|
return qhd_control(0); // control qhd of dev0 is used as async head
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_link_t* list_next(ehci_link_t const *p_link) {
|
|
|
|
|
return (ehci_link_t*) tu_align32(p_link->address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type)
|
|
|
|
|
{
|
|
|
|
|
for (uint32_t i=0; i<QHD_MAX; i++)
|
|
|
|
|
{
|
|
|
|
|
new->address = current->address;
|
|
|
|
|
current->address = ((uint32_t) new) | (new_type << 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove all queue head belong to this device address
|
|
|
|
|
static void list_remove_qhd_by_daddr(ehci_link_t* list_head, uint8_t dev_addr) {
|
|
|
|
|
ehci_link_t* prev = list_head;
|
|
|
|
|
|
|
|
|
|
while (prev && !prev->terminate) {
|
|
|
|
|
ehci_qhd_t* qhd = (ehci_qhd_t*) (uintptr_t) list_next(prev);
|
|
|
|
|
|
|
|
|
|
// done if loop back to head
|
|
|
|
|
if ( (uintptr_t) qhd == (uintptr_t) list_head) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( qhd->dev_addr == dev_addr ) {
|
|
|
|
|
// TODO deactivate all TD, wait for QHD to inactive before removal
|
|
|
|
|
prev->address = qhd->next.address;
|
|
|
|
|
|
|
|
|
|
// EHCI 4.8.2 link the removed qhd's next to async head (which always reachable by Host Controller)
|
|
|
|
|
qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1);
|
|
|
|
|
|
|
|
|
|
if ( qhd->int_smask )
|
|
|
|
|
{
|
|
|
|
|
// period list queue element is guarantee to be free in the next frame (1 ms)
|
|
|
|
|
qhd->used = 0;
|
|
|
|
|
}else
|
|
|
|
|
{
|
|
|
|
|
// async list use async advance handshake
|
|
|
|
|
// mark as removing, will completely re-usable when async advance isr occurs
|
|
|
|
|
qhd->removing = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hcd_dcache_clean(qhd, sizeof(ehci_qhd_t));
|
|
|
|
|
hcd_dcache_clean(prev, sizeof(ehci_qhd_t));
|
|
|
|
|
}else {
|
|
|
|
|
prev = list_next(prev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
// Queue Header helper
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// Get queue head for control transfer (always available)
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t* qhd_control(uint8_t dev_addr) {
|
|
|
|
|
return &ehci_data.control[dev_addr].qhd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find a free queue head
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t *qhd_find_free(void) {
|
|
|
|
|
for ( uint32_t i = 0; i < QHD_MAX; i++ ) {
|
|
|
|
|
if ( !ehci_data.qhd_pool[i].used ) return &ehci_data.qhd_pool[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd)
|
|
|
|
|
{
|
|
|
|
|
return (ehci_qhd_t*) tu_align32(p_qhd->next.address);
|
|
|
|
|
// Next queue head link
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qhd_t *qhd_next(ehci_qhd_t const *p_qhd) {
|
|
|
|
|
return (ehci_qhd_t *) tu_align32(p_qhd->next.address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
|
|
{
|
|
|
|
|
ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
|
|
|
|
|
// Get queue head from device + endpoint address
|
|
|
|
|
static ehci_qhd_t *qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr) {
|
|
|
|
|
if ( 0 == tu_edpt_number(ep_addr) ) {
|
|
|
|
|
return qhd_control(dev_addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(uint32_t i=0; i<QHD_MAX; i++)
|
|
|
|
|
{
|
|
|
|
|
ehci_qhd_t *qhd_pool = ehci_data.qhd_pool;
|
|
|
|
|
|
|
|
|
|
for ( uint32_t i = 0; i < QHD_MAX; i++ ) {
|
|
|
|
|
if ( (qhd_pool[i].dev_addr == dev_addr) &&
|
|
|
|
|
ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) )
|
|
|
|
|
{
|
|
|
|
|
ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) ) {
|
|
|
|
|
return &qhd_pool[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -849,6 +906,7 @@ static inline ehci_qhd_t* qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Init queue head with endpoint descriptor
|
|
|
|
|
static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
|
|
|
|
|
{
|
|
|
|
|
// address 0 is used as async head, which always on the list --> cannot be cleared (ehci halted otherwise)
|
|
|
|
@ -923,6 +981,7 @@ static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t c
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attach a TD to queue head
|
|
|
|
|
static void qhd_attach_qtd(ehci_qhd_t *qhd, ehci_qtd_t *qtd) {
|
|
|
|
|
qhd->attached_qtd = qtd;
|
|
|
|
|
qhd->attached_buffer = qtd->buffer[0];
|
|
|
|
@ -934,9 +993,26 @@ static void qhd_attach_qtd(ehci_qhd_t *qhd, ehci_qtd_t *qtd) {
|
|
|
|
|
hcd_dcache_clean_invalidate(qhd, sizeof(ehci_qhd_t));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove an attached TD from queue head
|
|
|
|
|
static void qhd_remove_qtd(ehci_qhd_t *qhd) {
|
|
|
|
|
ehci_qtd_t * volatile qtd = qhd->attached_qtd;
|
|
|
|
|
|
|
|
|
|
//------------- TD helper -------------//
|
|
|
|
|
static inline ehci_qtd_t *qtd_find_free(void) {
|
|
|
|
|
qhd->attached_qtd = NULL;
|
|
|
|
|
qhd->attached_buffer = 0;
|
|
|
|
|
|
|
|
|
|
qtd->used = 0; // free QTD
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
// Queue TD helper
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
// Get TD for control transfer (always available)
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t* qtd_control(uint8_t dev_addr) {
|
|
|
|
|
return &ehci_data.control[dev_addr].qtd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TU_ATTR_ALWAYS_INLINE static inline ehci_qtd_t *qtd_find_free(void) {
|
|
|
|
|
for (uint32_t i = 0; i < QTD_MAX; i++) {
|
|
|
|
|
if (!ehci_data.qtd_pool[i].used) return &ehci_data.qtd_pool[i];
|
|
|
|
|
}
|
|
|
|
@ -964,18 +1040,4 @@ static void qtd_init(ehci_qtd_t* qtd, void const* buffer, uint16_t total_bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------- List Managing Helper -------------//
|
|
|
|
|
|
|
|
|
|
// insert at head
|
|
|
|
|
static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type)
|
|
|
|
|
{
|
|
|
|
|
new->address = current->address;
|
|
|
|
|
current->address = ((uint32_t) new) | (new_type << 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline ehci_link_t* list_next(ehci_link_t const *p_link)
|
|
|
|
|
{
|
|
|
|
|
return (ehci_link_t*) tu_align32(p_link->address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|