diff options
| author | hathach <[email protected]> | 2013-10-24 15:32:54 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2013-10-24 15:32:54 +0700 |
| commit | b71ffe3d5a7b97010b7ac2096271e03dcb35c2dd (patch) | |
| tree | 512a6798926f074a93701a10a31a6e6721b78e02 /tinyusb/host | |
| parent | 2076e57d7072bb77c116c3cea51f640081da4aab (diff) | |
change TUSB_CFG_CONTROLLER0/1_MODE to TUSB_CFG_CONTROLLER_0/1_MODE for clearance
adding configuration docs for tinyusb_config.h
document HID mouse & keyboard API
Diffstat (limited to 'tinyusb/host')
| -rw-r--r-- | tinyusb/host/ehci/ehci.c | 2196 |
1 files changed, 1098 insertions, 1098 deletions
diff --git a/tinyusb/host/ehci/ehci.c b/tinyusb/host/ehci/ehci.c index 378e5f736..428c2636f 100644 --- a/tinyusb/host/ehci/ehci.c +++ b/tinyusb/host/ehci/ehci.c @@ -1,1098 +1,1098 @@ -/**************************************************************************/ -/*! - @file ehci.c - @author hathach (tinyusb.org) - - @section LICENSE - - Software License Agreement (BSD License) - - Copyright (c) 2013, hathach (tinyusb.org) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holders nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - This file is part of the tinyusb stack. -*/ -/**************************************************************************/ - -#include "common/common.h" - -#if MODE_HOST_SUPPORTED && (MCU == MCU_LPC43XX || MCU == MCU_LPC18XX) -//--------------------------------------------------------------------+ -// INCLUDE -//--------------------------------------------------------------------+ -#include "hal/hal.h" -#include "osal/osal.h" -#include "common/timeout_timer.h" - -#include "../hcd.h" -#include "../usbh_hcd.h" -#include "ehci.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -STATIC_VAR ehci_data_t ehci_data TUSB_CFG_ATTR_USBRAM; - -#if EHCI_PERIODIC_LIST - - #if (TUSB_CFG_CONTROLLER0_MODE & TUSB_MODE_HOST) - ATTR_ALIGNED(4096) STATIC_VAR ehci_link_t period_frame_list0[EHCI_FRAMELIST_SIZE] TUSB_CFG_ATTR_USBRAM; - - #ifndef __ICCARM__ // IAR cannot able to determine the alignment with datalignment pragma - STATIC_ASSERT( ALIGN_OF(period_frame_list0) == 4096, "Period Framelist must be 4k alginment"); // validation - #endif - #endif - - #if (TUSB_CFG_CONTROLLER1_MODE & TUSB_MODE_HOST) - STATIC_VAR ehci_link_t period_frame_list1[EHCI_FRAMELIST_SIZE] ATTR_ALIGNED(4096) TUSB_CFG_ATTR_USBRAM; - - #ifndef __ICCARM__ // IAR cannot able to determine the alignment with datalignment pragma - STATIC_ASSERT( ALIGN_OF(period_frame_list1) == 4096, "Period Framelist must be 4k alginment"); // validation - #endif - #endif -#endif - -//------------- Validation -------------// -// TODO static assert for memory placement on some known MCU such as lpc43xx - -//--------------------------------------------------------------------+ -// PROTOTYPE -//--------------------------------------------------------------------+ -STATIC_ INLINE_ ehci_registers_t* get_operational_register(uint8_t hostid) ATTR_PURE ATTR_ALWAYS_INLINE ATTR_WARN_UNUSED_RESULT; -STATIC_ INLINE_ ehci_link_t* get_period_frame_list(uint8_t hostid) ATTR_PURE ATTR_ALWAYS_INLINE ATTR_WARN_UNUSED_RESULT; -STATIC_ INLINE_ uint8_t hostid_to_data_idx(uint8_t hostid) ATTR_ALWAYS_INLINE ATTR_CONST ATTR_WARN_UNUSED_RESULT; - -STATIC_ INLINE_ ehci_qhd_t* get_async_head(uint8_t hostid) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT; -STATIC_ INLINE_ ehci_link_t* get_period_head(uint8_t hostid, uint8_t interval_ms) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT; - -STATIC_ INLINE_ ehci_qhd_t* get_control_qhd(uint8_t dev_addr) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT; -STATIC_ INLINE_ ehci_qtd_t* get_control_qtds(uint8_t dev_addr) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT; - -static inline uint8_t qhd_get_index(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE; -static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE; -static inline ehci_qhd_t* qhd_find_free (uint8_t dev_addr) ATTR_PURE ATTR_ALWAYS_INLINE; -static inline tusb_xfer_type_t qhd_get_xfer_type(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE; -STATIC_ INLINE_ ehci_qhd_t* qhd_get_from_pipe_handle(pipe_handle_t pipe_hdl) ATTR_PURE ATTR_ALWAYS_INLINE; -static inline pipe_handle_t qhd_create_pipe_handle(ehci_qhd_t const * p_qhd, tusb_xfer_type_t xfer_type) ATTR_PURE ATTR_ALWAYS_INLINE; -// determine if a queue head has bus-related error -static inline bool qhd_has_xact_error(ehci_qhd_t * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE; -static inline bool qhd_has_xact_error(ehci_qhd_t * p_qhd) -{ - return ( p_qhd->qtd_overlay.buffer_err ||p_qhd->qtd_overlay.babble_err || p_qhd->qtd_overlay.xact_err ); - //p_qhd->qtd_overlay.non_hs_period_missed_uframe || p_qhd->qtd_overlay.pingstate_err TODO split transaction error -} - -static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval); - - -STATIC_ INLINE_ ehci_qtd_t* qtd_find_free(uint8_t dev_addr) ATTR_PURE ATTR_ALWAYS_INLINE; -static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd ) ATTR_PURE ATTR_ALWAYS_INLINE; -static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new) ATTR_ALWAYS_INLINE; -static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd) ATTR_ALWAYS_INLINE; -static void qtd_init(ehci_qtd_t* p_qtd, uint32_t data_ptr, uint16_t total_bytes); - -static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type) ATTR_ALWAYS_INLINE; -static inline ehci_link_t* list_next(ehci_link_t *p_link_pointer) ATTR_PURE ATTR_ALWAYS_INLINE; -static ehci_link_t* list_find_previous_item(ehci_link_t* p_head, ehci_link_t* p_current); -static tusb_error_t list_remove_qhd(ehci_link_t* p_head, ehci_link_t* p_remove); - -static tusb_error_t hcd_controller_init(uint8_t hostid) ATTR_WARN_UNUSED_RESULT; -static tusb_error_t hcd_controller_stop(uint8_t hostid) ATTR_WARN_UNUSED_RESULT; - -//--------------------------------------------------------------------+ -// USBH-HCD API -//--------------------------------------------------------------------+ -tusb_error_t hcd_init(void) -{ - //------------- Data Structure init -------------// - memclr_(&ehci_data, sizeof(ehci_data_t)); - - #if (TUSB_CFG_CONTROLLER0_MODE & TUSB_MODE_HOST) - ASSERT_STATUS (hcd_controller_init(0)); - #endif - - #if (TUSB_CFG_CONTROLLER1_MODE & TUSB_MODE_HOST) - ASSERT_STATUS (hcd_controller_init(1)); - #endif - - return TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// PORT API -//--------------------------------------------------------------------+ -void hcd_port_reset(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - - regs->portsc_bit.port_enable = 0; // disable port before reset - regs->portsc_bit.port_reset = 1; - -#ifndef _TEST_ - // NXP specific, port reset will automatically be 0 when reset sequence complete - // there is chance device is unplugged while reset sequence is not complete - while( regs->portsc_bit.port_reset) {} // TODO use task delay to remove blocking -#endif -} - -bool hcd_port_connect_status(uint8_t hostid) -{ - return get_operational_register(hostid)->portsc_bit.current_connect_status; -} - -tusb_speed_t hcd_port_speed_get(uint8_t hostid) -{ - return (tusb_speed_t) get_operational_register(hostid)->portsc_bit.nxp_port_speed; // NXP specific port speed -} - -//--------------------------------------------------------------------+ -// Controller API -//--------------------------------------------------------------------+ -static tusb_error_t hcd_controller_init(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - - //------------- CTRLDSSEGMENT Register (skip) -------------// - //------------- USB INT Register -------------// - regs->usb_int_enable = 0; // 1. disable all the interrupt -#ifndef _TEST_ // the fake controller does not have write-to-clear behavior - regs->usb_sts = EHCI_INT_MASK_ALL; // 2. clear all status -#endif - regs->usb_int_enable = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | -#if EHCI_PERIODIC_LIST - EHCI_INT_MASK_NXP_PERIODIC | -#endif - EHCI_INT_MASK_ASYNC_ADVANCE | EHCI_INT_MASK_NXP_ASYNC; - - //------------- Asynchronous List -------------// - ehci_qhd_t * const async_head = get_async_head(hostid); - memclr_(async_head, sizeof(ehci_qhd_t)); - - async_head->next.address = (uint32_t) async_head; // circular list, next is itself - async_head->next.type = EHCI_QUEUE_ELEMENT_QHD; - async_head->head_list_flag = 1; - async_head->qtd_overlay.halted = 1; // inactive most of time - async_head->qtd_overlay.next.terminate = 1; // TODO removed if verified - - regs->async_list_base = (uint32_t) async_head; - -#if EHCI_PERIODIC_LIST - //------------- Periodic List -------------// - // Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only - - for(uint32_t i=0; i<4; i++) - { - ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ][i].interrupt_smask = 1; // queue head in period list must have smask non-zero - ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ][i].qtd_overlay.halted = 1; // dummy node, always inactive - } - - ehci_link_t * const framelist = get_period_frame_list(hostid); - ehci_link_t * const period_1ms = get_period_head(hostid, 1); - // 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 - for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i++) - { - framelist[i].address = (uint32_t) period_1ms; - framelist[i].type = EHCI_QUEUE_ELEMENT_QHD; - } - - for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2) - { - list_insert(framelist + i, get_period_head(hostid, 2), EHCI_QUEUE_ELEMENT_QHD); - } - - for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4) - { - list_insert(framelist + i, get_period_head(hostid, 4), EHCI_QUEUE_ELEMENT_QHD); - } - - list_insert(framelist+3, get_period_head(hostid, 8), EHCI_QUEUE_ELEMENT_QHD); - - period_1ms->terminate = 1; - - regs->periodic_list_base = (uint32_t) framelist; -#else - regs->periodic_list_base = 0; -#endif - - //------------- TT Control (NXP only) -------------// - regs->tt_control = 0; - - //------------- USB CMD Register -------------// - - regs->usb_cmd |= BIT_(EHCI_USBCMD_POS_RUN_STOP) | BIT_(EHCI_USBCMD_POS_ASYNC_ENABLE) -#if EHCI_PERIODIC_LIST - | BIT_(EHCI_USBCMD_POS_PERIOD_ENABLE) // TODO enable period list only there is int/iso endpoint -#endif - | ((EHCI_CFG_FRAMELIST_SIZE_BITS & BIN8(011)) << EHCI_USBCMD_POS_FRAMELIST_SZIE) - | ((EHCI_CFG_FRAMELIST_SIZE_BITS >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB); - - //------------- ConfigFlag Register (skip) -------------// - - regs->portsc_bit.port_power = 1; // enable port power - - return TUSB_ERROR_NONE; -} - -static tusb_error_t hcd_controller_stop(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - timeout_timer_t timeout; - - regs->usb_cmd_bit.run_stop = 0; - - timeout_set(&timeout, 2); // USB Spec: controller has to stop within 16 uframe = 2 frames - while( regs->usb_sts_bit.hc_halted == 0 && !timeout_expired(&timeout)) {} - - return timeout_expired(&timeout) ? TUSB_ERROR_OSAL_TIMEOUT : TUSB_ERROR_NONE; -} - -tusb_error_t hcd_controller_reset(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - timeout_timer_t timeout; - -// NXP chip powered with non-host mode --> sts bit is not correctly reflected - regs->usb_cmd_bit.reset = 1; - - timeout_set(&timeout, 2); // should not take longer the time to stop controller - while( regs->usb_cmd_bit.reset && !timeout_expired(&timeout)) {} - - return timeout_expired(&timeout) ? TUSB_ERROR_OSAL_TIMEOUT : TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// CONTROL PIPE API -//--------------------------------------------------------------------+ -tusb_error_t hcd_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size) -{ - ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr); - - qhd_init(p_qhd, dev_addr, max_packet_size, 0, TUSB_XFER_CONTROL, 1); // TODO binterval of control is ignored - - if (dev_addr != 0) - { - //------------- insert to async list -------------// - // TODO might need to to disable async list first - list_insert( (ehci_link_t*) get_async_head(usbh_devices[dev_addr].core_id), - (ehci_link_t*) p_qhd, EHCI_QUEUE_ELEMENT_QHD); - } - - return TUSB_ERROR_NONE; -} - -tusb_error_t hcd_pipe_control_xfer(uint8_t dev_addr, tusb_control_request_t const * p_request, uint8_t data[]) -{ - ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr); - - ehci_qtd_t *p_setup = get_control_qtds(dev_addr); - ehci_qtd_t *p_data = p_setup + 1; - ehci_qtd_t *p_status = p_setup + 2; - - //------------- SETUP Phase -------------// - qtd_init(p_setup, (uint32_t) p_request, 8); - p_setup->pid = EHCI_PID_SETUP; - p_setup->next.address = (uint32_t) p_data; - - //------------- DATA Phase -------------// - if (p_request->wLength > 0) - { - qtd_init(p_data, (uint32_t) data, p_request->wLength); - p_data->data_toggle = 1; - p_data->pid = p_request->bmRequestType_bit.direction ? EHCI_PID_IN : EHCI_PID_OUT; - }else - { - p_data = p_setup; - } - p_data->next.address = (uint32_t) p_status; - - //------------- STATUS Phase -------------// - qtd_init(p_status, 0, 0); // zero-length data - p_status->int_on_complete = 1; - p_status->data_toggle = 1; - p_status->pid = p_request->bmRequestType_bit.direction ? EHCI_PID_OUT : EHCI_PID_IN; // reverse direction of data phase - p_status->next.terminate = 1; - - //------------- Attach TDs list to Control Endpoint -------------// - p_qhd->p_qtd_list_head = p_setup; - p_qhd->p_qtd_list_tail = p_status; - - p_qhd->qtd_overlay.next.address = (uint32_t) p_setup; - - return TUSB_ERROR_NONE; -} - -tusb_error_t hcd_pipe_control_close(uint8_t dev_addr) -{ - //------------- TODO pipe handle validate -------------// - ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr); - - p_qhd->is_removing = 1; - - if (dev_addr != 0) - { - ASSERT_STATUS( list_remove_qhd( (ehci_link_t*) get_async_head( usbh_devices[dev_addr].core_id ), - (ehci_link_t*) p_qhd) ); - } - - return TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// BULK/INT/ISO PIPE API -//--------------------------------------------------------------------+ -pipe_handle_t hcd_pipe_open(uint8_t dev_addr, tusb_descriptor_endpoint_t const * p_endpoint_desc, uint8_t class_code) -{ - pipe_handle_t const null_handle = { .dev_addr = 0, .xfer_type = 0, .index = 0 }; - - ASSERT(dev_addr > 0, null_handle); - - if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) - return null_handle; // TODO not support ISO yet - - //------------- Prepare Queue Head -------------// - ehci_qhd_t * const p_qhd = qhd_find_free(dev_addr); - ASSERT_PTR(p_qhd, null_handle); - - qhd_init( p_qhd, dev_addr, p_endpoint_desc->wMaxPacketSize.size, p_endpoint_desc->bEndpointAddress, - p_endpoint_desc->bmAttributes.xfer, p_endpoint_desc->bInterval ); - p_qhd->class_code = class_code; - - //------------- Insert to Async List -------------// - ehci_link_t * list_head; - - if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_BULK) - { - list_head = (ehci_link_t*) get_async_head(usbh_devices[dev_addr].core_id); - } - #if EHCI_PERIODIC_LIST // TODO refractor/group this together - else if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT) - { - list_head = get_period_head(usbh_devices[dev_addr].core_id, p_qhd->interval_ms); - } - #endif - - //------------- insert to async/period list TODO might need to disable async/period list -------------// - list_insert( list_head, - (ehci_link_t*) p_qhd, EHCI_QUEUE_ELEMENT_QHD); - - return (pipe_handle_t) { .dev_addr = dev_addr, .xfer_type = p_endpoint_desc->bmAttributes.xfer, .index = qhd_get_index(p_qhd) }; -} - -tusb_error_t hcd_pipe_queue_xfer(pipe_handle_t pipe_hdl, uint8_t buffer[], uint16_t total_bytes) -{ - //------------- TODO pipe handle validate -------------// - - //------------- set up QTD -------------// - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle(pipe_hdl); - ehci_qtd_t *p_qtd = qtd_find_free(pipe_hdl.dev_addr); - - ASSERT_PTR(p_qtd, TUSB_ERROR_EHCI_NOT_ENOUGH_QTD); - - qtd_init(p_qtd, (uint32_t) buffer, total_bytes); - p_qtd->pid = p_qhd->pid_non_control; - - //------------- insert TD to TD list -------------// - qtd_insert_to_qhd(p_qhd, p_qtd); - - return TUSB_ERROR_NONE; -} - -tusb_error_t hcd_pipe_xfer(pipe_handle_t pipe_hdl, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete) -{ - ASSERT_STATUS ( hcd_pipe_queue_xfer(pipe_hdl, buffer, total_bytes) ); - - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle(pipe_hdl); - - if ( int_on_complete ) - { // the just added qtd is pointed by list_tail - p_qhd->p_qtd_list_tail->int_on_complete = 1; - } - p_qhd->qtd_overlay.next.address = (uint32_t) p_qhd->p_qtd_list_head; // attach to queue head to start transferring - - return TUSB_ERROR_NONE; -} - -/// pipe_close should only be called as a part of unmount/safe-remove process -tusb_error_t hcd_pipe_close(pipe_handle_t pipe_hdl) -{ - ASSERT(pipe_hdl.dev_addr > 0, TUSB_ERROR_INVALID_PARA); - - ASSERT(pipe_hdl.xfer_type != TUSB_XFER_ISOCHRONOUS, TUSB_ERROR_INVALID_PARA); - - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - - // async list needs async advance handshake to make sure host controller has released cached data - // non-control does not use async advance, it will eventually free by control pipe close - // period list queue element is guarantee to be free in the next frame (1 ms) - p_qhd->is_removing = 1; // TODO redundant, only apply to control queue head - - if ( pipe_hdl.xfer_type == TUSB_XFER_BULK ) - { - ASSERT_STATUS( list_remove_qhd( - (ehci_link_t*) get_async_head( usbh_devices[pipe_hdl.dev_addr].core_id ), - (ehci_link_t*) p_qhd) ); - } - #if EHCI_PERIODIC_LIST // TODO refractor/group this together - else - { - ASSERT_STATUS( list_remove_qhd( - get_period_head( usbh_devices[pipe_hdl.dev_addr].core_id, p_qhd->interval_ms ), - (ehci_link_t*) p_qhd) ); - } - #endif - - return TUSB_ERROR_NONE; -} - -bool hcd_pipe_is_busy(pipe_handle_t pipe_hdl) -{ - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL); -} - -bool hcd_pipe_is_error(pipe_handle_t pipe_hdl) -{ - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - return p_qhd->qtd_overlay.halted; -} - -bool hcd_pipe_is_idle(pipe_handle_t pipe_hdl) -{ // TODO to be remove - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - return (p_qhd->p_qtd_list_head == NULL); -} - -bool hcd_pipe_is_stalled(pipe_handle_t pipe_hdl) -{ - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd); -} - -uint8_t hcd_pipe_get_endpoint_addr(pipe_handle_t pipe_hdl) -{ - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - return p_qhd->endpoint_number + ( (p_qhd->pid_non_control == EHCI_PID_IN) ? 0x80 : 0); -} - -tusb_error_t hcd_pipe_clear_stall(pipe_handle_t pipe_hdl) -{ - ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl ); - p_qhd->qtd_overlay.halted = 0; - - return TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// EHCI Interrupt Handler -//--------------------------------------------------------------------+ - -// async_advance is handshake between sw stack & ehci controller where ehci free all memory from an deleted queue head. -// In tinyusb, queue head is only removed when device is unplugged. So only control queue head is checked if removing -static void async_advance_isr(ehci_qhd_t * const async_head) -{ - // TODO do we need to close addr0 - if (async_head->is_removing) // closing control pipe of addr0 - { - async_head->is_removing = 0; - async_head->p_qtd_list_head = async_head->p_qtd_list_tail = NULL; - async_head->qtd_overlay.halted = 1; - - usbh_devices[0].state = TUSB_DEVICE_STATE_UNPLUG; - } - - for(uint8_t relative_dev_addr=0; relative_dev_addr < TUSB_CFG_HOST_DEVICE_MAX; relative_dev_addr++) - { - // check if control endpoint is removing - ehci_qhd_t *p_control_qhd = &ehci_data.device[relative_dev_addr].control.qhd; - if ( p_control_qhd->is_removing ) - { - p_control_qhd->is_removing = 0; - p_control_qhd->used = 0; - - // Host Controller has cleaned up its cached data for this device, set state to unplug - usbh_devices[relative_dev_addr+1].state = TUSB_DEVICE_STATE_UNPLUG; - - for (uint8_t i=0; i<EHCI_MAX_QHD; i++) // free all qhd - { - ehci_data.device[relative_dev_addr].qhd[i].used = 0; - ehci_data.device[relative_dev_addr].qhd[i].is_removing = 0; - } - for (uint8_t i=0; i<EHCI_MAX_QTD; i++) // free all qtd - { - ehci_data.device[relative_dev_addr].qtd[i].used = 0; - } - // TODO free all itd & sitd - } - } // end for device[] loop -} - -static void port_connect_status_change_isr(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - - // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device - if (regs->portsc_bit.current_connect_status) - { - usbh_hcd_rhport_plugged_isr(hostid); - }else // device unplugged - { - usbh_hcd_rhport_unplugged_isr(hostid); -// regs->usb_cmd_bit.advacne_async = 1; // Async doorbell check EHCI 4.8.2 for operational details - } -} - -// TODO refractor abtract later -void hcd_port_unplug(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - regs->usb_cmd_bit.advacne_async = 1; // Async doorbell check EHCI 4.8.2 for operational details -} - -static void qhd_xfer_complete_isr(ehci_qhd_t * p_qhd) -{ - uint8_t max_loop = 0; - tusb_xfer_type_t const xfer_type = qhd_get_xfer_type(p_qhd); - - // free all TDs from the head td to the first active TD - while(p_qhd->p_qtd_list_head != NULL && !p_qhd->p_qtd_list_head->active - && max_loop < EHCI_MAX_QTD) - { - // TD need to be freed and removed from qhd, before invoking callback - bool is_ioc = (p_qhd->p_qtd_list_head->int_on_complete != 0); - p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes; - - p_qhd->p_qtd_list_head->used = 0; // free QTD - qtd_remove_1st_from_qhd(p_qhd); - - if (is_ioc) // end of request - { // call USBH callback - usbh_xfer_isr( qhd_create_pipe_handle(p_qhd, xfer_type), - p_qhd->class_code, TUSB_EVENT_XFER_COMPLETE, - p_qhd->total_xferred_bytes - (xfer_type == TUSB_XFER_CONTROL ? 8 : 0) ); // subtract setup packet size if control, - p_qhd->total_xferred_bytes = 0; - } - - max_loop++; - } -} - -static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head) -{ - uint8_t max_loop = 0; - ehci_qhd_t *p_qhd = async_head; - do - { - if ( !p_qhd->qtd_overlay.halted ) // halted or error is processed in error isr - { - qhd_xfer_complete_isr(p_qhd); - } - p_qhd = qhd_next(p_qhd); - max_loop++; - }while(p_qhd != async_head && max_loop < EHCI_MAX_QHD); // async list traversal, stop if loop around - // TODO abstract max loop guard for async -} - -#if EHCI_PERIODIC_LIST // TODO refractor/group this together -static void period_list_xfer_complete_isr(uint8_t hostid, uint8_t interval_ms) -{ - uint8_t max_loop = 0; - uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1); - ehci_link_t next_item = * get_period_head(hostid, interval_ms); - - // TODO abstract max loop guard for period - while( !next_item.terminate && - !(interval_ms > 1 && period_1ms_addr == align32(next_item.address)) && - max_loop < (EHCI_MAX_QHD + EHCI_MAX_ITD + EHCI_MAX_SITD)) - { - switch ( next_item.type ) - { - case EHCI_QUEUE_ELEMENT_QHD: - { - ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) align32(next_item.address); - if ( !p_qhd_int->qtd_overlay.halted ) - { - qhd_xfer_complete_isr(p_qhd_int); - } - } - break; - - case EHCI_QUEUE_ELEMENT_ITD: - case EHCI_QUEUE_ELEMENT_SITD: - case EHCI_QUEUE_ELEMENT_FSTN: - default: - ASSERT (false, VOID_RETURN); // TODO support hs/fs ISO - break; - } - - next_item = *list_next(&next_item); - max_loop++; - } -} -#endif - -static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd) -{ - if ( (p_qhd->device_address != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL - qhd_has_xact_error(p_qhd) ) - { // current qhd has error in transaction - tusb_xfer_type_t const xfer_type = qhd_get_xfer_type(p_qhd); - tusb_event_t error_event; - - // no error bits are set, endpoint is halted due to STALL - error_event = qhd_has_xact_error(p_qhd) ? TUSB_EVENT_XFER_ERROR : TUSB_EVENT_XFER_STALLED; - - p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes; - - // TODO skip unplugged device - if ( TUSB_EVENT_XFER_ERROR == error_event ) hal_debugger_breakpoint(); - - p_qhd->p_qtd_list_head->used = 0; // free QTD - qtd_remove_1st_from_qhd(p_qhd); - - if ( TUSB_XFER_CONTROL == xfer_type ) - { - p_qhd->total_xferred_bytes -= min8_of(8, p_qhd->total_xferred_bytes); // subtract setup size - - // control cannot be halted --> clear all qtd list - p_qhd->p_qtd_list_head = NULL; - p_qhd->p_qtd_list_tail = NULL; - - p_qhd->qtd_overlay.next.terminate = 1; - p_qhd->qtd_overlay.alternate.terminate = 1; - p_qhd->qtd_overlay.halted = 0; - - ehci_qtd_t *p_setup = get_control_qtds(p_qhd->device_address); - ehci_qtd_t *p_data = p_setup + 1; - ehci_qtd_t *p_status = p_setup + 2; - - p_setup->used = p_data->used = p_status->used = 0; - } - - // call USBH callback - usbh_xfer_isr( qhd_create_pipe_handle(p_qhd, xfer_type), - p_qhd->class_code, error_event, - p_qhd->total_xferred_bytes); - - p_qhd->total_xferred_bytes = 0; - } -} - -static void xfer_error_isr(uint8_t hostid) -{ - //------------- async list -------------// - uint8_t max_loop = 0; - ehci_qhd_t * const async_head = get_async_head(hostid); - ehci_qhd_t *p_qhd = async_head; - do - { - qhd_xfer_error_isr( p_qhd ); - p_qhd = qhd_next(p_qhd); - max_loop++; - }while(p_qhd != async_head && max_loop < EHCI_MAX_QHD); // async list traversal, stop if loop around - - #if EHCI_PERIODIC_LIST - //------------- TODO refractor period list -------------// - uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1); - for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2) - { - uint8_t period_max_loop = 0; - ehci_link_t next_item = * get_period_head(hostid, interval_ms); - - // TODO abstract max loop guard for period - while( !next_item.terminate && - !(interval_ms > 1 && period_1ms_addr == align32(next_item.address)) && - period_max_loop < (EHCI_MAX_QHD + EHCI_MAX_ITD + EHCI_MAX_SITD)) - { - switch ( next_item.type ) - { - case EHCI_QUEUE_ELEMENT_QHD: - { - ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) align32(next_item.address); - qhd_xfer_error_isr(p_qhd_int); - } - break; - - case EHCI_QUEUE_ELEMENT_ITD: - case EHCI_QUEUE_ELEMENT_SITD: - case EHCI_QUEUE_ELEMENT_FSTN: - default: - ASSERT (false, VOID_RETURN); // TODO support hs/fs ISO - break; - } - - next_item = *list_next(&next_item); - period_max_loop++; - } - } - #endif -} - -//------------- Host Controller Driver's Interrupt Handler -------------// -void hcd_isr(uint8_t hostid) -{ - ehci_registers_t* const regs = get_operational_register(hostid); - - uint32_t int_status = regs->usb_sts; - int_status &= regs->usb_int_enable; - - regs->usb_sts |= int_status; // Acknowledge handled interrupt - - if (int_status == 0) - return; - - if (int_status & EHCI_INT_MASK_PORT_CHANGE) - { - uint32_t port_status = regs->portsc & EHCI_PORTSC_MASK_ALL; - - if (regs->portsc_bit.connect_status_change) - { - port_connect_status_change_isr(hostid); - } - - regs->portsc |= port_status; // Acknowledge change bits in portsc - } - - if (int_status & EHCI_INT_MASK_ERROR) - { - xfer_error_isr(hostid); - } - - //------------- some QTD/SITD/ITD with IOC set is completed -------------// - if (int_status & EHCI_INT_MASK_NXP_ASYNC) - { - async_list_xfer_complete_isr( get_async_head(hostid) ); - } - -#if EHCI_PERIODIC_LIST // TODO refractor/group this together - if (int_status & EHCI_INT_MASK_NXP_PERIODIC) - { - for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2) - { - period_list_xfer_complete_isr( hostid, i ); - } - } -#endif - - //------------- There is some removed async previously -------------// - if (int_status & EHCI_INT_MASK_ASYNC_ADVANCE) // need to place after EHCI_INT_MASK_NXP_ASYNC - { - async_advance_isr( get_async_head(hostid) ); - } -} - -//--------------------------------------------------------------------+ -// HELPER -//--------------------------------------------------------------------+ -STATIC_ INLINE_ ehci_registers_t* get_operational_register(uint8_t hostid) -{ - return (ehci_registers_t*) (hostid ? (&LPC_USB1->USBCMD_H) : (&LPC_USB0->USBCMD_H) ); -} - -#if EHCI_PERIODIC_LIST // TODO refractor/group this together -STATIC_ INLINE_ ehci_link_t* get_period_frame_list(uint8_t hostid) -{ - switch(hostid) - { -#if (TUSB_CFG_CONTROLLER0_MODE & TUSB_MODE_HOST) - case 0: - return period_frame_list0; -#endif - -#if (TUSB_CFG_CONTROLLER1_MODE & TUSB_MODE_HOST) - case 1: - return period_frame_list1; -#endif - - default: return NULL; - } -} -#endif - -STATIC_ INLINE_ uint8_t hostid_to_data_idx(uint8_t hostid) -{ - #if (CONTROLLER_HOST_NUMBER == 1) && (TUSB_CFG_CONTROLLER1_MODE & TUSB_MODE_HOST) - (void) hostid; - return 0; - #else - return hostid; - #endif -} - -//------------- queue head helper -------------// -STATIC_ INLINE_ ehci_qhd_t* get_async_head(uint8_t hostid) -{ - return &ehci_data.async_head[ hostid_to_data_idx(hostid) ]; -} - -#if EHCI_PERIODIC_LIST // TODO refractor/group this together -STATIC_ INLINE_ ehci_link_t* get_period_head(uint8_t hostid, uint8_t interval_ms) -{ - return (ehci_link_t*) (&ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ] - [ log2_of( min8_of(EHCI_FRAMELIST_SIZE, interval_ms) ) ] ); -} -#endif - -STATIC_ INLINE_ ehci_qhd_t* get_control_qhd(uint8_t dev_addr) -{ - return (dev_addr == 0) ? - get_async_head( usbh_devices[dev_addr].core_id ) : - &ehci_data.device[dev_addr-1].control.qhd; -} -STATIC_ INLINE_ ehci_qtd_t* get_control_qtds(uint8_t dev_addr) -{ - return (dev_addr == 0) ? - ehci_data.addr0_qtd : - ehci_data.device[ dev_addr-1 ].control.qtd; - -} - -static inline ehci_qhd_t* qhd_find_free (uint8_t dev_addr) -{ - uint8_t relative_address = dev_addr-1; - uint8_t index=0; - while( index<EHCI_MAX_QHD && ehci_data.device[relative_address].qhd[index].used ) - { - index++; - } - return (index < EHCI_MAX_QHD) ? &ehci_data.device[relative_address].qhd[index] : NULL; -} - -static inline uint8_t qhd_get_index(ehci_qhd_t const * p_qhd) -{ - return p_qhd - ehci_data.device[p_qhd->device_address-1].qhd; -} -static inline tusb_xfer_type_t qhd_get_xfer_type(ehci_qhd_t const * p_qhd) -{ - return ( p_qhd->endpoint_number == 0 ) ? TUSB_XFER_CONTROL : - ( p_qhd->interrupt_smask != 0 ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK; -} - -static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd) -{ - return (ehci_qhd_t*) align32(p_qhd->next.address); -} - -STATIC_ INLINE_ ehci_qhd_t* qhd_get_from_pipe_handle(pipe_handle_t pipe_hdl) -{ - return &ehci_data.device[ pipe_hdl.dev_addr-1 ].qhd[ pipe_hdl.index ]; -} - -static inline pipe_handle_t qhd_create_pipe_handle(ehci_qhd_t const * p_qhd, tusb_xfer_type_t xfer_type) -{ - pipe_handle_t pipe_hdl = { - .dev_addr = p_qhd->device_address, - .xfer_type = xfer_type - }; - - // TODO Isochronous transfer support - if (TUSB_XFER_CONTROL != xfer_type) // qhd index for control is meaningless - { - pipe_hdl.index = qhd_get_index(p_qhd); - } - - return pipe_hdl; -} - -//------------- TD helper -------------// -STATIC_ INLINE_ ehci_qtd_t* qtd_find_free(uint8_t dev_addr) -{ - uint8_t index=0; - while( index<EHCI_MAX_QTD && ehci_data.device[dev_addr-1].qtd[index].used ) - { - index++; - } - - return (index < EHCI_MAX_QTD) ? &ehci_data.device[dev_addr-1].qtd[index] : NULL; -} - -static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd ) -{ - return (ehci_qtd_t*) align32(p_qtd->next.address); -} - -static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd) -{ - if (p_qhd->p_qtd_list_head == p_qhd->p_qtd_list_tail) // last TD --> make it NULL - { - p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = NULL; - }else - { - p_qhd->p_qtd_list_head = qtd_next( p_qhd->p_qtd_list_head ); - } -} - -static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new) -{ - if (p_qhd->p_qtd_list_head == NULL) // empty list - { - p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = p_qtd_new; - }else - { - p_qhd->p_qtd_list_tail->next.address = (uint32_t) p_qtd_new; - p_qhd->p_qtd_list_tail = p_qtd_new; - } -} - -static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval) -{ - // address 0 uses async head, which always on the list --> cannot be cleared (ehci halted otherwise) - if (dev_addr != 0) - { - memclr_(p_qhd, sizeof(ehci_qhd_t)); - } - - p_qhd->device_address = dev_addr; - p_qhd->non_hs_period_inactive_next_xact = 0; - p_qhd->endpoint_number = endpoint_addr & 0x0F; - p_qhd->endpoint_speed = usbh_devices[dev_addr].speed; - p_qhd->data_toggle_control = (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0; - p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head - p_qhd->max_package_size = max_packet_size; - p_qhd->non_hs_control_endpoint = ((TUSB_XFER_CONTROL == xfer_type) && (p_qhd->endpoint_speed != TUSB_SPEED_HIGH)) ? 1 : 0; - p_qhd->nak_count_reload = 0; - - // Bulk/Control -> smask = cmask = 0 - // TODO Isochronous - if (TUSB_XFER_INTERRUPT == xfer_type) - { - if (TUSB_SPEED_HIGH == p_qhd->endpoint_speed) - { - ASSERT_INT_WITHIN(1, 16, interval, VOID_RETURN); - if ( interval < 4) // sub milisecond interval - { - p_qhd->interval_ms = 0; - p_qhd->interrupt_smask = (interval == 1) ? BIN8(11111111) : - (interval == 2) ? BIN8(10101010) : BIN8(01000100); - }else - { - p_qhd->interval_ms = (uint8_t) min16_of( 1 << (interval-4), 255 ); - p_qhd->interrupt_smask = BIT_(interval % 8); - } - }else - { - ASSERT( 0 != interval, VOID_RETURN); - // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes - p_qhd->interrupt_smask = 0x01; - p_qhd->non_hs_interrupt_cmask = BIN8(11100); - p_qhd->interval_ms = interval; - } - }else - { - p_qhd->interrupt_smask = p_qhd->non_hs_interrupt_cmask = 0; - } - - p_qhd->hub_address = usbh_devices[dev_addr].hub_addr; - p_qhd->hub_port = usbh_devices[dev_addr].hub_port; - p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet - - //------------- HCD Management Data -------------// - p_qhd->used = 1; - p_qhd->is_removing = 0; - p_qhd->p_qtd_list_head = NULL; - p_qhd->p_qtd_list_tail = NULL; - p_qhd->pid_non_control = (endpoint_addr & 0x80) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint - - //------------- active, but no TD list -------------// - p_qhd->qtd_overlay.halted = 0; - p_qhd->qtd_overlay.next.terminate = 1; - p_qhd->qtd_overlay.alternate.terminate = 1; - if (TUSB_XFER_BULK == xfer_type && p_qhd->endpoint_speed == TUSB_SPEED_HIGH && p_qhd->pid_non_control == EHCI_PID_OUT) - { - p_qhd->qtd_overlay.pingstate_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11 - } -} - -static void qtd_init(ehci_qtd_t* p_qtd, uint32_t data_ptr, uint16_t total_bytes) -{ - memclr_(p_qtd, sizeof(ehci_qtd_t)); - - p_qtd->used = 1; - - p_qtd->next.terminate = 1; // init to null - p_qtd->alternate.terminate = 1; // not used, always set to terminated - p_qtd->active = 1; - p_qtd->cerr = 3; // TODO 3 consecutive errors tolerance - p_qtd->data_toggle = 0; - p_qtd->total_bytes = total_bytes; - p_qtd->expected_bytes = total_bytes; - - p_qtd->buffer[0] = data_ptr; - for(uint8_t i=1; i<5; i++) - { - p_qtd->buffer[i] |= align4k( p_qtd->buffer[i-1] ) + 4096; - } -} - -//------------- List Managing Helper -------------// -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 *p_link_pointer) -{ - return (ehci_link_t*) align32(p_link_pointer->address); -} - -static ehci_link_t* list_find_previous_item(ehci_link_t* p_head, ehci_link_t* p_current) -{ - ehci_link_t *p_prev = p_head; - uint32_t max_loop = 0; - while( (align32(p_prev->address) != (uint32_t) p_head) && // not loop around - (align32(p_prev->address) != (uint32_t) p_current) && // not found yet - !p_prev->terminate && // not advanceable - max_loop < EHCI_MAX_QHD) - { - p_prev = list_next(p_prev); - max_loop++; - } - - return (align32(p_prev->address) != (uint32_t) p_head) ? p_prev : NULL; -} - -static tusb_error_t list_remove_qhd(ehci_link_t* p_head, ehci_link_t* p_remove) -{ - ehci_link_t *p_prev = list_find_previous_item(p_head, p_remove); - - ASSERT_PTR(p_prev, TUSB_ERROR_INVALID_PARA); - - p_prev->address = p_remove->address; - // EHCI 4.8.2 link the removing queue head to async/period head (which always reachable by Host Controller) - p_remove->address = ((uint32_t) p_head) | (EHCI_QUEUE_ELEMENT_QHD << 1); - - return TUSB_ERROR_NONE; -} - -#endif +/**************************************************************************/
+/*!
+ @file ehci.c
+ @author hathach (tinyusb.org)
+
+ @section LICENSE
+
+ Software License Agreement (BSD License)
+
+ Copyright (c) 2013, hathach (tinyusb.org)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holders nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ This file is part of the tinyusb stack.
+*/
+/**************************************************************************/
+
+#include "common/common.h"
+
+#if MODE_HOST_SUPPORTED && (MCU == MCU_LPC43XX || MCU == MCU_LPC18XX)
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "hal/hal.h"
+#include "osal/osal.h"
+#include "common/timeout_timer.h"
+
+#include "../hcd.h"
+#include "../usbh_hcd.h"
+#include "ehci.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+STATIC_VAR ehci_data_t ehci_data TUSB_CFG_ATTR_USBRAM;
+
+#if EHCI_PERIODIC_LIST
+
+ #if (TUSB_CFG_CONTROLLER_0_MODE & TUSB_MODE_HOST)
+ ATTR_ALIGNED(4096) STATIC_VAR ehci_link_t period_frame_list0[EHCI_FRAMELIST_SIZE] TUSB_CFG_ATTR_USBRAM;
+
+ #ifndef __ICCARM__ // IAR cannot able to determine the alignment with datalignment pragma
+ STATIC_ASSERT( ALIGN_OF(period_frame_list0) == 4096, "Period Framelist must be 4k alginment"); // validation
+ #endif
+ #endif
+
+ #if (TUSB_CFG_CONTROLLER_1_MODE & TUSB_MODE_HOST)
+ STATIC_VAR ehci_link_t period_frame_list1[EHCI_FRAMELIST_SIZE] ATTR_ALIGNED(4096) TUSB_CFG_ATTR_USBRAM;
+
+ #ifndef __ICCARM__ // IAR cannot able to determine the alignment with datalignment pragma
+ STATIC_ASSERT( ALIGN_OF(period_frame_list1) == 4096, "Period Framelist must be 4k alginment"); // validation
+ #endif
+ #endif
+#endif
+
+//------------- Validation -------------//
+// TODO static assert for memory placement on some known MCU such as lpc43xx
+
+//--------------------------------------------------------------------+
+// PROTOTYPE
+//--------------------------------------------------------------------+
+STATIC_ INLINE_ ehci_registers_t* get_operational_register(uint8_t hostid) ATTR_PURE ATTR_ALWAYS_INLINE ATTR_WARN_UNUSED_RESULT;
+STATIC_ INLINE_ ehci_link_t* get_period_frame_list(uint8_t hostid) ATTR_PURE ATTR_ALWAYS_INLINE ATTR_WARN_UNUSED_RESULT;
+STATIC_ INLINE_ uint8_t hostid_to_data_idx(uint8_t hostid) ATTR_ALWAYS_INLINE ATTR_CONST ATTR_WARN_UNUSED_RESULT;
+
+STATIC_ INLINE_ ehci_qhd_t* get_async_head(uint8_t hostid) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT;
+STATIC_ INLINE_ ehci_link_t* get_period_head(uint8_t hostid, uint8_t interval_ms) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT;
+
+STATIC_ INLINE_ ehci_qhd_t* get_control_qhd(uint8_t dev_addr) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT;
+STATIC_ INLINE_ ehci_qtd_t* get_control_qtds(uint8_t dev_addr) ATTR_ALWAYS_INLINE ATTR_PURE ATTR_WARN_UNUSED_RESULT;
+
+static inline uint8_t qhd_get_index(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE;
+static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE;
+static inline ehci_qhd_t* qhd_find_free (uint8_t dev_addr) ATTR_PURE ATTR_ALWAYS_INLINE;
+static inline tusb_xfer_type_t qhd_get_xfer_type(ehci_qhd_t const * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE;
+STATIC_ INLINE_ ehci_qhd_t* qhd_get_from_pipe_handle(pipe_handle_t pipe_hdl) ATTR_PURE ATTR_ALWAYS_INLINE;
+static inline pipe_handle_t qhd_create_pipe_handle(ehci_qhd_t const * p_qhd, tusb_xfer_type_t xfer_type) ATTR_PURE ATTR_ALWAYS_INLINE;
+// determine if a queue head has bus-related error
+static inline bool qhd_has_xact_error(ehci_qhd_t * p_qhd) ATTR_ALWAYS_INLINE ATTR_PURE;
+static inline bool qhd_has_xact_error(ehci_qhd_t * p_qhd)
+{
+ return ( p_qhd->qtd_overlay.buffer_err ||p_qhd->qtd_overlay.babble_err || p_qhd->qtd_overlay.xact_err );
+ //p_qhd->qtd_overlay.non_hs_period_missed_uframe || p_qhd->qtd_overlay.pingstate_err TODO split transaction error
+}
+
+static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval);
+
+
+STATIC_ INLINE_ ehci_qtd_t* qtd_find_free(uint8_t dev_addr) ATTR_PURE ATTR_ALWAYS_INLINE;
+static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd ) ATTR_PURE ATTR_ALWAYS_INLINE;
+static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new) ATTR_ALWAYS_INLINE;
+static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd) ATTR_ALWAYS_INLINE;
+static void qtd_init(ehci_qtd_t* p_qtd, uint32_t data_ptr, uint16_t total_bytes);
+
+static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type) ATTR_ALWAYS_INLINE;
+static inline ehci_link_t* list_next(ehci_link_t *p_link_pointer) ATTR_PURE ATTR_ALWAYS_INLINE;
+static ehci_link_t* list_find_previous_item(ehci_link_t* p_head, ehci_link_t* p_current);
+static tusb_error_t list_remove_qhd(ehci_link_t* p_head, ehci_link_t* p_remove);
+
+static tusb_error_t hcd_controller_init(uint8_t hostid) ATTR_WARN_UNUSED_RESULT;
+static tusb_error_t hcd_controller_stop(uint8_t hostid) ATTR_WARN_UNUSED_RESULT;
+
+//--------------------------------------------------------------------+
+// USBH-HCD API
+//--------------------------------------------------------------------+
+tusb_error_t hcd_init(void)
+{
+ //------------- Data Structure init -------------//
+ memclr_(&ehci_data, sizeof(ehci_data_t));
+
+ #if (TUSB_CFG_CONTROLLER_0_MODE & TUSB_MODE_HOST)
+ ASSERT_STATUS (hcd_controller_init(0));
+ #endif
+
+ #if (TUSB_CFG_CONTROLLER_1_MODE & TUSB_MODE_HOST)
+ ASSERT_STATUS (hcd_controller_init(1));
+ #endif
+
+ return TUSB_ERROR_NONE;
+}
+
+//--------------------------------------------------------------------+
+// PORT API
+//--------------------------------------------------------------------+
+void hcd_port_reset(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+
+ regs->portsc_bit.port_enable = 0; // disable port before reset
+ regs->portsc_bit.port_reset = 1;
+
+#ifndef _TEST_
+ // NXP specific, port reset will automatically be 0 when reset sequence complete
+ // there is chance device is unplugged while reset sequence is not complete
+ while( regs->portsc_bit.port_reset) {} // TODO use task delay to remove blocking
+#endif
+}
+
+bool hcd_port_connect_status(uint8_t hostid)
+{
+ return get_operational_register(hostid)->portsc_bit.current_connect_status;
+}
+
+tusb_speed_t hcd_port_speed_get(uint8_t hostid)
+{
+ return (tusb_speed_t) get_operational_register(hostid)->portsc_bit.nxp_port_speed; // NXP specific port speed
+}
+
+//--------------------------------------------------------------------+
+// Controller API
+//--------------------------------------------------------------------+
+static tusb_error_t hcd_controller_init(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+
+ //------------- CTRLDSSEGMENT Register (skip) -------------//
+ //------------- USB INT Register -------------//
+ regs->usb_int_enable = 0; // 1. disable all the interrupt
+#ifndef _TEST_ // the fake controller does not have write-to-clear behavior
+ regs->usb_sts = EHCI_INT_MASK_ALL; // 2. clear all status
+#endif
+ regs->usb_int_enable = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE |
+#if EHCI_PERIODIC_LIST
+ EHCI_INT_MASK_NXP_PERIODIC |
+#endif
+ EHCI_INT_MASK_ASYNC_ADVANCE | EHCI_INT_MASK_NXP_ASYNC;
+
+ //------------- Asynchronous List -------------//
+ ehci_qhd_t * const async_head = get_async_head(hostid);
+ memclr_(async_head, sizeof(ehci_qhd_t));
+
+ async_head->next.address = (uint32_t) async_head; // circular list, next is itself
+ async_head->next.type = EHCI_QUEUE_ELEMENT_QHD;
+ async_head->head_list_flag = 1;
+ async_head->qtd_overlay.halted = 1; // inactive most of time
+ async_head->qtd_overlay.next.terminate = 1; // TODO removed if verified
+
+ regs->async_list_base = (uint32_t) async_head;
+
+#if EHCI_PERIODIC_LIST
+ //------------- Periodic List -------------//
+ // Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only
+
+ for(uint32_t i=0; i<4; i++)
+ {
+ ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ][i].interrupt_smask = 1; // queue head in period list must have smask non-zero
+ ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ][i].qtd_overlay.halted = 1; // dummy node, always inactive
+ }
+
+ ehci_link_t * const framelist = get_period_frame_list(hostid);
+ ehci_link_t * const period_1ms = get_period_head(hostid, 1);
+ // 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
+ for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i++)
+ {
+ framelist[i].address = (uint32_t) period_1ms;
+ framelist[i].type = EHCI_QUEUE_ELEMENT_QHD;
+ }
+
+ for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2)
+ {
+ list_insert(framelist + i, get_period_head(hostid, 2), EHCI_QUEUE_ELEMENT_QHD);
+ }
+
+ for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4)
+ {
+ list_insert(framelist + i, get_period_head(hostid, 4), EHCI_QUEUE_ELEMENT_QHD);
+ }
+
+ list_insert(framelist+3, get_period_head(hostid, 8), EHCI_QUEUE_ELEMENT_QHD);
+
+ period_1ms->terminate = 1;
+
+ regs->periodic_list_base = (uint32_t) framelist;
+#else
+ regs->periodic_list_base = 0;
+#endif
+
+ //------------- TT Control (NXP only) -------------//
+ regs->tt_control = 0;
+
+ //------------- USB CMD Register -------------//
+
+ regs->usb_cmd |= BIT_(EHCI_USBCMD_POS_RUN_STOP) | BIT_(EHCI_USBCMD_POS_ASYNC_ENABLE)
+#if EHCI_PERIODIC_LIST
+ | BIT_(EHCI_USBCMD_POS_PERIOD_ENABLE) // TODO enable period list only there is int/iso endpoint
+#endif
+ | ((EHCI_CFG_FRAMELIST_SIZE_BITS & BIN8(011)) << EHCI_USBCMD_POS_FRAMELIST_SZIE)
+ | ((EHCI_CFG_FRAMELIST_SIZE_BITS >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB);
+
+ //------------- ConfigFlag Register (skip) -------------//
+
+ regs->portsc_bit.port_power = 1; // enable port power
+
+ return TUSB_ERROR_NONE;
+}
+
+static tusb_error_t hcd_controller_stop(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+ timeout_timer_t timeout;
+
+ regs->usb_cmd_bit.run_stop = 0;
+
+ timeout_set(&timeout, 2); // USB Spec: controller has to stop within 16 uframe = 2 frames
+ while( regs->usb_sts_bit.hc_halted == 0 && !timeout_expired(&timeout)) {}
+
+ return timeout_expired(&timeout) ? TUSB_ERROR_OSAL_TIMEOUT : TUSB_ERROR_NONE;
+}
+
+tusb_error_t hcd_controller_reset(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+ timeout_timer_t timeout;
+
+// NXP chip powered with non-host mode --> sts bit is not correctly reflected
+ regs->usb_cmd_bit.reset = 1;
+
+ timeout_set(&timeout, 2); // should not take longer the time to stop controller
+ while( regs->usb_cmd_bit.reset && !timeout_expired(&timeout)) {}
+
+ return timeout_expired(&timeout) ? TUSB_ERROR_OSAL_TIMEOUT : TUSB_ERROR_NONE;
+}
+
+//--------------------------------------------------------------------+
+// CONTROL PIPE API
+//--------------------------------------------------------------------+
+tusb_error_t hcd_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size)
+{
+ ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr);
+
+ qhd_init(p_qhd, dev_addr, max_packet_size, 0, TUSB_XFER_CONTROL, 1); // TODO binterval of control is ignored
+
+ if (dev_addr != 0)
+ {
+ //------------- insert to async list -------------//
+ // TODO might need to to disable async list first
+ list_insert( (ehci_link_t*) get_async_head(usbh_devices[dev_addr].core_id),
+ (ehci_link_t*) p_qhd, EHCI_QUEUE_ELEMENT_QHD);
+ }
+
+ return TUSB_ERROR_NONE;
+}
+
+tusb_error_t hcd_pipe_control_xfer(uint8_t dev_addr, tusb_control_request_t const * p_request, uint8_t data[])
+{
+ ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr);
+
+ ehci_qtd_t *p_setup = get_control_qtds(dev_addr);
+ ehci_qtd_t *p_data = p_setup + 1;
+ ehci_qtd_t *p_status = p_setup + 2;
+
+ //------------- SETUP Phase -------------//
+ qtd_init(p_setup, (uint32_t) p_request, 8);
+ p_setup->pid = EHCI_PID_SETUP;
+ p_setup->next.address = (uint32_t) p_data;
+
+ //------------- DATA Phase -------------//
+ if (p_request->wLength > 0)
+ {
+ qtd_init(p_data, (uint32_t) data, p_request->wLength);
+ p_data->data_toggle = 1;
+ p_data->pid = p_request->bmRequestType_bit.direction ? EHCI_PID_IN : EHCI_PID_OUT;
+ }else
+ {
+ p_data = p_setup;
+ }
+ p_data->next.address = (uint32_t) p_status;
+
+ //------------- STATUS Phase -------------//
+ qtd_init(p_status, 0, 0); // zero-length data
+ p_status->int_on_complete = 1;
+ p_status->data_toggle = 1;
+ p_status->pid = p_request->bmRequestType_bit.direction ? EHCI_PID_OUT : EHCI_PID_IN; // reverse direction of data phase
+ p_status->next.terminate = 1;
+
+ //------------- Attach TDs list to Control Endpoint -------------//
+ p_qhd->p_qtd_list_head = p_setup;
+ p_qhd->p_qtd_list_tail = p_status;
+
+ p_qhd->qtd_overlay.next.address = (uint32_t) p_setup;
+
+ return TUSB_ERROR_NONE;
+}
+
+tusb_error_t hcd_pipe_control_close(uint8_t dev_addr)
+{
+ //------------- TODO pipe handle validate -------------//
+ ehci_qhd_t * const p_qhd = get_control_qhd(dev_addr);
+
+ p_qhd->is_removing = 1;
+
+ if (dev_addr != 0)
+ {
+ ASSERT_STATUS( list_remove_qhd( (ehci_link_t*) get_async_head( usbh_devices[dev_addr].core_id ),
+ (ehci_link_t*) p_qhd) );
+ }
+
+ return TUSB_ERROR_NONE;
+}
+
+//--------------------------------------------------------------------+
+// BULK/INT/ISO PIPE API
+//--------------------------------------------------------------------+
+pipe_handle_t hcd_pipe_open(uint8_t dev_addr, tusb_descriptor_endpoint_t const * p_endpoint_desc, uint8_t class_code)
+{
+ pipe_handle_t const null_handle = { .dev_addr = 0, .xfer_type = 0, .index = 0 };
+
+ ASSERT(dev_addr > 0, null_handle);
+
+ if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS)
+ return null_handle; // TODO not support ISO yet
+
+ //------------- Prepare Queue Head -------------//
+ ehci_qhd_t * const p_qhd = qhd_find_free(dev_addr);
+ ASSERT_PTR(p_qhd, null_handle);
+
+ qhd_init( p_qhd, dev_addr, p_endpoint_desc->wMaxPacketSize.size, p_endpoint_desc->bEndpointAddress,
+ p_endpoint_desc->bmAttributes.xfer, p_endpoint_desc->bInterval );
+ p_qhd->class_code = class_code;
+
+ //------------- Insert to Async List -------------//
+ ehci_link_t * list_head;
+
+ if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_BULK)
+ {
+ list_head = (ehci_link_t*) get_async_head(usbh_devices[dev_addr].core_id);
+ }
+ #if EHCI_PERIODIC_LIST // TODO refractor/group this together
+ else if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT)
+ {
+ list_head = get_period_head(usbh_devices[dev_addr].core_id, p_qhd->interval_ms);
+ }
+ #endif
+
+ //------------- insert to async/period list TODO might need to disable async/period list -------------//
+ list_insert( list_head,
+ (ehci_link_t*) p_qhd, EHCI_QUEUE_ELEMENT_QHD);
+
+ return (pipe_handle_t) { .dev_addr = dev_addr, .xfer_type = p_endpoint_desc->bmAttributes.xfer, .index = qhd_get_index(p_qhd) };
+}
+
+tusb_error_t hcd_pipe_queue_xfer(pipe_handle_t pipe_hdl, uint8_t buffer[], uint16_t total_bytes)
+{
+ //------------- TODO pipe handle validate -------------//
+
+ //------------- set up QTD -------------//
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle(pipe_hdl);
+ ehci_qtd_t *p_qtd = qtd_find_free(pipe_hdl.dev_addr);
+
+ ASSERT_PTR(p_qtd, TUSB_ERROR_EHCI_NOT_ENOUGH_QTD);
+
+ qtd_init(p_qtd, (uint32_t) buffer, total_bytes);
+ p_qtd->pid = p_qhd->pid_non_control;
+
+ //------------- insert TD to TD list -------------//
+ qtd_insert_to_qhd(p_qhd, p_qtd);
+
+ return TUSB_ERROR_NONE;
+}
+
+tusb_error_t hcd_pipe_xfer(pipe_handle_t pipe_hdl, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
+{
+ ASSERT_STATUS ( hcd_pipe_queue_xfer(pipe_hdl, buffer, total_bytes) );
+
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle(pipe_hdl);
+
+ if ( int_on_complete )
+ { // the just added qtd is pointed by list_tail
+ p_qhd->p_qtd_list_tail->int_on_complete = 1;
+ }
+ p_qhd->qtd_overlay.next.address = (uint32_t) p_qhd->p_qtd_list_head; // attach to queue head to start transferring
+
+ return TUSB_ERROR_NONE;
+}
+
+/// pipe_close should only be called as a part of unmount/safe-remove process
+tusb_error_t hcd_pipe_close(pipe_handle_t pipe_hdl)
+{
+ ASSERT(pipe_hdl.dev_addr > 0, TUSB_ERROR_INVALID_PARA);
+
+ ASSERT(pipe_hdl.xfer_type != TUSB_XFER_ISOCHRONOUS, TUSB_ERROR_INVALID_PARA);
+
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+
+ // async list needs async advance handshake to make sure host controller has released cached data
+ // non-control does not use async advance, it will eventually free by control pipe close
+ // period list queue element is guarantee to be free in the next frame (1 ms)
+ p_qhd->is_removing = 1; // TODO redundant, only apply to control queue head
+
+ if ( pipe_hdl.xfer_type == TUSB_XFER_BULK )
+ {
+ ASSERT_STATUS( list_remove_qhd(
+ (ehci_link_t*) get_async_head( usbh_devices[pipe_hdl.dev_addr].core_id ),
+ (ehci_link_t*) p_qhd) );
+ }
+ #if EHCI_PERIODIC_LIST // TODO refractor/group this together
+ else
+ {
+ ASSERT_STATUS( list_remove_qhd(
+ get_period_head( usbh_devices[pipe_hdl.dev_addr].core_id, p_qhd->interval_ms ),
+ (ehci_link_t*) p_qhd) );
+ }
+ #endif
+
+ return TUSB_ERROR_NONE;
+}
+
+bool hcd_pipe_is_busy(pipe_handle_t pipe_hdl)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL);
+}
+
+bool hcd_pipe_is_error(pipe_handle_t pipe_hdl)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ return p_qhd->qtd_overlay.halted;
+}
+
+bool hcd_pipe_is_idle(pipe_handle_t pipe_hdl)
+{ // TODO to be remove
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ return (p_qhd->p_qtd_list_head == NULL);
+}
+
+bool hcd_pipe_is_stalled(pipe_handle_t pipe_hdl)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd);
+}
+
+uint8_t hcd_pipe_get_endpoint_addr(pipe_handle_t pipe_hdl)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ return p_qhd->endpoint_number + ( (p_qhd->pid_non_control == EHCI_PID_IN) ? 0x80 : 0);
+}
+
+tusb_error_t hcd_pipe_clear_stall(pipe_handle_t pipe_hdl)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_pipe_handle( pipe_hdl );
+ p_qhd->qtd_overlay.halted = 0;
+
+ return TUSB_ERROR_NONE;
+}
+
+//--------------------------------------------------------------------+
+// EHCI Interrupt Handler
+//--------------------------------------------------------------------+
+
+// async_advance is handshake between sw stack & ehci controller where ehci free all memory from an deleted queue head.
+// In tinyusb, queue head is only removed when device is unplugged. So only control queue head is checked if removing
+static void async_advance_isr(ehci_qhd_t * const async_head)
+{
+ // TODO do we need to close addr0
+ if (async_head->is_removing) // closing control pipe of addr0
+ {
+ async_head->is_removing = 0;
+ async_head->p_qtd_list_head = async_head->p_qtd_list_tail = NULL;
+ async_head->qtd_overlay.halted = 1;
+
+ usbh_devices[0].state = TUSB_DEVICE_STATE_UNPLUG;
+ }
+
+ for(uint8_t relative_dev_addr=0; relative_dev_addr < TUSB_CFG_HOST_DEVICE_MAX; relative_dev_addr++)
+ {
+ // check if control endpoint is removing
+ ehci_qhd_t *p_control_qhd = &ehci_data.device[relative_dev_addr].control.qhd;
+ if ( p_control_qhd->is_removing )
+ {
+ p_control_qhd->is_removing = 0;
+ p_control_qhd->used = 0;
+
+ // Host Controller has cleaned up its cached data for this device, set state to unplug
+ usbh_devices[relative_dev_addr+1].state = TUSB_DEVICE_STATE_UNPLUG;
+
+ for (uint8_t i=0; i<EHCI_MAX_QHD; i++) // free all qhd
+ {
+ ehci_data.device[relative_dev_addr].qhd[i].used = 0;
+ ehci_data.device[relative_dev_addr].qhd[i].is_removing = 0;
+ }
+ for (uint8_t i=0; i<EHCI_MAX_QTD; i++) // free all qtd
+ {
+ ehci_data.device[relative_dev_addr].qtd[i].used = 0;
+ }
+ // TODO free all itd & sitd
+ }
+ } // end for device[] loop
+}
+
+static void port_connect_status_change_isr(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+
+ // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device
+ if (regs->portsc_bit.current_connect_status)
+ {
+ usbh_hcd_rhport_plugged_isr(hostid);
+ }else // device unplugged
+ {
+ usbh_hcd_rhport_unplugged_isr(hostid);
+// regs->usb_cmd_bit.advacne_async = 1; // Async doorbell check EHCI 4.8.2 for operational details
+ }
+}
+
+// TODO refractor abtract later
+void hcd_port_unplug(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+ regs->usb_cmd_bit.advacne_async = 1; // Async doorbell check EHCI 4.8.2 for operational details
+}
+
+static void qhd_xfer_complete_isr(ehci_qhd_t * p_qhd)
+{
+ uint8_t max_loop = 0;
+ tusb_xfer_type_t const xfer_type = qhd_get_xfer_type(p_qhd);
+
+ // free all TDs from the head td to the first active TD
+ while(p_qhd->p_qtd_list_head != NULL && !p_qhd->p_qtd_list_head->active
+ && max_loop < EHCI_MAX_QTD)
+ {
+ // TD need to be freed and removed from qhd, before invoking callback
+ bool is_ioc = (p_qhd->p_qtd_list_head->int_on_complete != 0);
+ p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
+
+ p_qhd->p_qtd_list_head->used = 0; // free QTD
+ qtd_remove_1st_from_qhd(p_qhd);
+
+ if (is_ioc) // end of request
+ { // call USBH callback
+ usbh_xfer_isr( qhd_create_pipe_handle(p_qhd, xfer_type),
+ p_qhd->class_code, TUSB_EVENT_XFER_COMPLETE,
+ p_qhd->total_xferred_bytes - (xfer_type == TUSB_XFER_CONTROL ? 8 : 0) ); // subtract setup packet size if control,
+ p_qhd->total_xferred_bytes = 0;
+ }
+
+ max_loop++;
+ }
+}
+
+static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
+{
+ uint8_t max_loop = 0;
+ ehci_qhd_t *p_qhd = async_head;
+ do
+ {
+ if ( !p_qhd->qtd_overlay.halted ) // halted or error is processed in error isr
+ {
+ qhd_xfer_complete_isr(p_qhd);
+ }
+ p_qhd = qhd_next(p_qhd);
+ max_loop++;
+ }while(p_qhd != async_head && max_loop < EHCI_MAX_QHD); // async list traversal, stop if loop around
+ // TODO abstract max loop guard for async
+}
+
+#if EHCI_PERIODIC_LIST // TODO refractor/group this together
+static void period_list_xfer_complete_isr(uint8_t hostid, uint8_t interval_ms)
+{
+ uint8_t max_loop = 0;
+ uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1);
+ ehci_link_t next_item = * get_period_head(hostid, interval_ms);
+
+ // TODO abstract max loop guard for period
+ while( !next_item.terminate &&
+ !(interval_ms > 1 && period_1ms_addr == align32(next_item.address)) &&
+ max_loop < (EHCI_MAX_QHD + EHCI_MAX_ITD + EHCI_MAX_SITD))
+ {
+ switch ( next_item.type )
+ {
+ case EHCI_QUEUE_ELEMENT_QHD:
+ {
+ ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) align32(next_item.address);
+ if ( !p_qhd_int->qtd_overlay.halted )
+ {
+ qhd_xfer_complete_isr(p_qhd_int);
+ }
+ }
+ break;
+
+ case EHCI_QUEUE_ELEMENT_ITD:
+ case EHCI_QUEUE_ELEMENT_SITD:
+ case EHCI_QUEUE_ELEMENT_FSTN:
+ default:
+ ASSERT (false, VOID_RETURN); // TODO support hs/fs ISO
+ break;
+ }
+
+ next_item = *list_next(&next_item);
+ max_loop++;
+ }
+}
+#endif
+
+static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd)
+{
+ if ( (p_qhd->device_address != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL
+ qhd_has_xact_error(p_qhd) )
+ { // current qhd has error in transaction
+ tusb_xfer_type_t const xfer_type = qhd_get_xfer_type(p_qhd);
+ tusb_event_t error_event;
+
+ // no error bits are set, endpoint is halted due to STALL
+ error_event = qhd_has_xact_error(p_qhd) ? TUSB_EVENT_XFER_ERROR : TUSB_EVENT_XFER_STALLED;
+
+ p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
+
+ // TODO skip unplugged device
+ if ( TUSB_EVENT_XFER_ERROR == error_event ) hal_debugger_breakpoint();
+
+ p_qhd->p_qtd_list_head->used = 0; // free QTD
+ qtd_remove_1st_from_qhd(p_qhd);
+
+ if ( TUSB_XFER_CONTROL == xfer_type )
+ {
+ p_qhd->total_xferred_bytes -= min8_of(8, p_qhd->total_xferred_bytes); // subtract setup size
+
+ // control cannot be halted --> clear all qtd list
+ p_qhd->p_qtd_list_head = NULL;
+ p_qhd->p_qtd_list_tail = NULL;
+
+ p_qhd->qtd_overlay.next.terminate = 1;
+ p_qhd->qtd_overlay.alternate.terminate = 1;
+ p_qhd->qtd_overlay.halted = 0;
+
+ ehci_qtd_t *p_setup = get_control_qtds(p_qhd->device_address);
+ ehci_qtd_t *p_data = p_setup + 1;
+ ehci_qtd_t *p_status = p_setup + 2;
+
+ p_setup->used = p_data->used = p_status->used = 0;
+ }
+
+ // call USBH callback
+ usbh_xfer_isr( qhd_create_pipe_handle(p_qhd, xfer_type),
+ p_qhd->class_code, error_event,
+ p_qhd->total_xferred_bytes);
+
+ p_qhd->total_xferred_bytes = 0;
+ }
+}
+
+static void xfer_error_isr(uint8_t hostid)
+{
+ //------------- async list -------------//
+ uint8_t max_loop = 0;
+ ehci_qhd_t * const async_head = get_async_head(hostid);
+ ehci_qhd_t *p_qhd = async_head;
+ do
+ {
+ qhd_xfer_error_isr( p_qhd );
+ p_qhd = qhd_next(p_qhd);
+ max_loop++;
+ }while(p_qhd != async_head && max_loop < EHCI_MAX_QHD); // async list traversal, stop if loop around
+
+ #if EHCI_PERIODIC_LIST
+ //------------- TODO refractor period list -------------//
+ uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1);
+ for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2)
+ {
+ uint8_t period_max_loop = 0;
+ ehci_link_t next_item = * get_period_head(hostid, interval_ms);
+
+ // TODO abstract max loop guard for period
+ while( !next_item.terminate &&
+ !(interval_ms > 1 && period_1ms_addr == align32(next_item.address)) &&
+ period_max_loop < (EHCI_MAX_QHD + EHCI_MAX_ITD + EHCI_MAX_SITD))
+ {
+ switch ( next_item.type )
+ {
+ case EHCI_QUEUE_ELEMENT_QHD:
+ {
+ ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) align32(next_item.address);
+ qhd_xfer_error_isr(p_qhd_int);
+ }
+ break;
+
+ case EHCI_QUEUE_ELEMENT_ITD:
+ case EHCI_QUEUE_ELEMENT_SITD:
+ case EHCI_QUEUE_ELEMENT_FSTN:
+ default:
+ ASSERT (false, VOID_RETURN); // TODO support hs/fs ISO
+ break;
+ }
+
+ next_item = *list_next(&next_item);
+ period_max_loop++;
+ }
+ }
+ #endif
+}
+
+//------------- Host Controller Driver's Interrupt Handler -------------//
+void hcd_isr(uint8_t hostid)
+{
+ ehci_registers_t* const regs = get_operational_register(hostid);
+
+ uint32_t int_status = regs->usb_sts;
+ int_status &= regs->usb_int_enable;
+
+ regs->usb_sts |= int_status; // Acknowledge handled interrupt
+
+ if (int_status == 0)
+ return;
+
+ if (int_status & EHCI_INT_MASK_PORT_CHANGE)
+ {
+ uint32_t port_status = regs->portsc & EHCI_PORTSC_MASK_ALL;
+
+ if (regs->portsc_bit.connect_status_change)
+ {
+ port_connect_status_change_isr(hostid);
+ }
+
+ regs->portsc |= port_status; // Acknowledge change bits in portsc
+ }
+
+ if (int_status & EHCI_INT_MASK_ERROR)
+ {
+ xfer_error_isr(hostid);
+ }
+
+ //------------- some QTD/SITD/ITD with IOC set is completed -------------//
+ if (int_status & EHCI_INT_MASK_NXP_ASYNC)
+ {
+ async_list_xfer_complete_isr( get_async_head(hostid) );
+ }
+
+#if EHCI_PERIODIC_LIST // TODO refractor/group this together
+ if (int_status & EHCI_INT_MASK_NXP_PERIODIC)
+ {
+ for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2)
+ {
+ period_list_xfer_complete_isr( hostid, i );
+ }
+ }
+#endif
+
+ //------------- There is some removed async previously -------------//
+ if (int_status & EHCI_INT_MASK_ASYNC_ADVANCE) // need to place after EHCI_INT_MASK_NXP_ASYNC
+ {
+ async_advance_isr( get_async_head(hostid) );
+ }
+}
+
+//--------------------------------------------------------------------+
+// HELPER
+//--------------------------------------------------------------------+
+STATIC_ INLINE_ ehci_registers_t* get_operational_register(uint8_t hostid)
+{
+ return (ehci_registers_t*) (hostid ? (&LPC_USB1->USBCMD_H) : (&LPC_USB0->USBCMD_H) );
+}
+
+#if EHCI_PERIODIC_LIST // TODO refractor/group this together
+STATIC_ INLINE_ ehci_link_t* get_period_frame_list(uint8_t hostid)
+{
+ switch(hostid)
+ {
+#if (TUSB_CFG_CONTROLLER_0_MODE & TUSB_MODE_HOST)
+ case 0:
+ return period_frame_list0;
+#endif
+
+#if (TUSB_CFG_CONTROLLER_1_MODE & TUSB_MODE_HOST)
+ case 1:
+ return period_frame_list1;
+#endif
+
+ default: return NULL;
+ }
+}
+#endif
+
+STATIC_ INLINE_ uint8_t hostid_to_data_idx(uint8_t hostid)
+{
+ #if (CONTROLLER_HOST_NUMBER == 1) && (TUSB_CFG_CONTROLLER_1_MODE & TUSB_MODE_HOST)
+ (void) hostid;
+ return 0;
+ #else
+ return hostid;
+ #endif
+}
+
+//------------- queue head helper -------------//
+STATIC_ INLINE_ ehci_qhd_t* get_async_head(uint8_t hostid)
+{
+ return &ehci_data.async_head[ hostid_to_data_idx(hostid) ];
+}
+
+#if EHCI_PERIODIC_LIST // TODO refractor/group this together
+STATIC_ INLINE_ ehci_link_t* get_period_head(uint8_t hostid, uint8_t interval_ms)
+{
+ return (ehci_link_t*) (&ehci_data.period_head_arr[ hostid_to_data_idx(hostid) ]
+ [ log2_of( min8_of(EHCI_FRAMELIST_SIZE, interval_ms) ) ] );
+}
+#endif
+
+STATIC_ INLINE_ ehci_qhd_t* get_control_qhd(uint8_t dev_addr)
+{
+ return (dev_addr == 0) ?
+ get_async_head( usbh_devices[dev_addr].core_id ) :
+ &ehci_data.device[dev_addr-1].control.qhd;
+}
+STATIC_ INLINE_ ehci_qtd_t* get_control_qtds(uint8_t dev_addr)
+{
+ return (dev_addr == 0) ?
+ ehci_data.addr0_qtd :
+ ehci_data.device[ dev_addr-1 ].control.qtd;
+
+}
+
+static inline ehci_qhd_t* qhd_find_free (uint8_t dev_addr)
+{
+ uint8_t relative_address = dev_addr-1;
+ uint8_t index=0;
+ while( index<EHCI_MAX_QHD && ehci_data.device[relative_address].qhd[index].used )
+ {
+ index++;
+ }
+ return (index < EHCI_MAX_QHD) ? &ehci_data.device[relative_address].qhd[index] : NULL;
+}
+
+static inline uint8_t qhd_get_index(ehci_qhd_t const * p_qhd)
+{
+ return p_qhd - ehci_data.device[p_qhd->device_address-1].qhd;
+}
+static inline tusb_xfer_type_t qhd_get_xfer_type(ehci_qhd_t const * p_qhd)
+{
+ return ( p_qhd->endpoint_number == 0 ) ? TUSB_XFER_CONTROL :
+ ( p_qhd->interrupt_smask != 0 ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK;
+}
+
+static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd)
+{
+ return (ehci_qhd_t*) align32(p_qhd->next.address);
+}
+
+STATIC_ INLINE_ ehci_qhd_t* qhd_get_from_pipe_handle(pipe_handle_t pipe_hdl)
+{
+ return &ehci_data.device[ pipe_hdl.dev_addr-1 ].qhd[ pipe_hdl.index ];
+}
+
+static inline pipe_handle_t qhd_create_pipe_handle(ehci_qhd_t const * p_qhd, tusb_xfer_type_t xfer_type)
+{
+ pipe_handle_t pipe_hdl = {
+ .dev_addr = p_qhd->device_address,
+ .xfer_type = xfer_type
+ };
+
+ // TODO Isochronous transfer support
+ if (TUSB_XFER_CONTROL != xfer_type) // qhd index for control is meaningless
+ {
+ pipe_hdl.index = qhd_get_index(p_qhd);
+ }
+
+ return pipe_hdl;
+}
+
+//------------- TD helper -------------//
+STATIC_ INLINE_ ehci_qtd_t* qtd_find_free(uint8_t dev_addr)
+{
+ uint8_t index=0;
+ while( index<EHCI_MAX_QTD && ehci_data.device[dev_addr-1].qtd[index].used )
+ {
+ index++;
+ }
+
+ return (index < EHCI_MAX_QTD) ? &ehci_data.device[dev_addr-1].qtd[index] : NULL;
+}
+
+static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd )
+{
+ return (ehci_qtd_t*) align32(p_qtd->next.address);
+}
+
+static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd)
+{
+ if (p_qhd->p_qtd_list_head == p_qhd->p_qtd_list_tail) // last TD --> make it NULL
+ {
+ p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = NULL;
+ }else
+ {
+ p_qhd->p_qtd_list_head = qtd_next( p_qhd->p_qtd_list_head );
+ }
+}
+
+static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new)
+{
+ if (p_qhd->p_qtd_list_head == NULL) // empty list
+ {
+ p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = p_qtd_new;
+ }else
+ {
+ p_qhd->p_qtd_list_tail->next.address = (uint32_t) p_qtd_new;
+ p_qhd->p_qtd_list_tail = p_qtd_new;
+ }
+}
+
+static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval)
+{
+ // address 0 uses async head, which always on the list --> cannot be cleared (ehci halted otherwise)
+ if (dev_addr != 0)
+ {
+ memclr_(p_qhd, sizeof(ehci_qhd_t));
+ }
+
+ p_qhd->device_address = dev_addr;
+ p_qhd->non_hs_period_inactive_next_xact = 0;
+ p_qhd->endpoint_number = endpoint_addr & 0x0F;
+ p_qhd->endpoint_speed = usbh_devices[dev_addr].speed;
+ p_qhd->data_toggle_control = (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0;
+ p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head
+ p_qhd->max_package_size = max_packet_size;
+ p_qhd->non_hs_control_endpoint = ((TUSB_XFER_CONTROL == xfer_type) && (p_qhd->endpoint_speed != TUSB_SPEED_HIGH)) ? 1 : 0;
+ p_qhd->nak_count_reload = 0;
+
+ // Bulk/Control -> smask = cmask = 0
+ // TODO Isochronous
+ if (TUSB_XFER_INTERRUPT == xfer_type)
+ {
+ if (TUSB_SPEED_HIGH == p_qhd->endpoint_speed)
+ {
+ ASSERT_INT_WITHIN(1, 16, interval, VOID_RETURN);
+ if ( interval < 4) // sub milisecond interval
+ {
+ p_qhd->interval_ms = 0;
+ p_qhd->interrupt_smask = (interval == 1) ? BIN8(11111111) :
+ (interval == 2) ? BIN8(10101010) : BIN8(01000100);
+ }else
+ {
+ p_qhd->interval_ms = (uint8_t) min16_of( 1 << (interval-4), 255 );
+ p_qhd->interrupt_smask = BIT_(interval % 8);
+ }
+ }else
+ {
+ ASSERT( 0 != interval, VOID_RETURN);
+ // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes
+ p_qhd->interrupt_smask = 0x01;
+ p_qhd->non_hs_interrupt_cmask = BIN8(11100);
+ p_qhd->interval_ms = interval;
+ }
+ }else
+ {
+ p_qhd->interrupt_smask = p_qhd->non_hs_interrupt_cmask = 0;
+ }
+
+ p_qhd->hub_address = usbh_devices[dev_addr].hub_addr;
+ p_qhd->hub_port = usbh_devices[dev_addr].hub_port;
+ p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet
+
+ //------------- HCD Management Data -------------//
+ p_qhd->used = 1;
+ p_qhd->is_removing = 0;
+ p_qhd->p_qtd_list_head = NULL;
+ p_qhd->p_qtd_list_tail = NULL;
+ p_qhd->pid_non_control = (endpoint_addr & 0x80) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint
+
+ //------------- active, but no TD list -------------//
+ p_qhd->qtd_overlay.halted = 0;
+ p_qhd->qtd_overlay.next.terminate = 1;
+ p_qhd->qtd_overlay.alternate.terminate = 1;
+ if (TUSB_XFER_BULK == xfer_type && p_qhd->endpoint_speed == TUSB_SPEED_HIGH && p_qhd->pid_non_control == EHCI_PID_OUT)
+ {
+ p_qhd->qtd_overlay.pingstate_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11
+ }
+}
+
+static void qtd_init(ehci_qtd_t* p_qtd, uint32_t data_ptr, uint16_t total_bytes)
+{
+ memclr_(p_qtd, sizeof(ehci_qtd_t));
+
+ p_qtd->used = 1;
+
+ p_qtd->next.terminate = 1; // init to null
+ p_qtd->alternate.terminate = 1; // not used, always set to terminated
+ p_qtd->active = 1;
+ p_qtd->cerr = 3; // TODO 3 consecutive errors tolerance
+ p_qtd->data_toggle = 0;
+ p_qtd->total_bytes = total_bytes;
+ p_qtd->expected_bytes = total_bytes;
+
+ p_qtd->buffer[0] = data_ptr;
+ for(uint8_t i=1; i<5; i++)
+ {
+ p_qtd->buffer[i] |= align4k( p_qtd->buffer[i-1] ) + 4096;
+ }
+}
+
+//------------- List Managing Helper -------------//
+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 *p_link_pointer)
+{
+ return (ehci_link_t*) align32(p_link_pointer->address);
+}
+
+static ehci_link_t* list_find_previous_item(ehci_link_t* p_head, ehci_link_t* p_current)
+{
+ ehci_link_t *p_prev = p_head;
+ uint32_t max_loop = 0;
+ while( (align32(p_prev->address) != (uint32_t) p_head) && // not loop around
+ (align32(p_prev->address) != (uint32_t) p_current) && // not found yet
+ !p_prev->terminate && // not advanceable
+ max_loop < EHCI_MAX_QHD)
+ {
+ p_prev = list_next(p_prev);
+ max_loop++;
+ }
+
+ return (align32(p_prev->address) != (uint32_t) p_head) ? p_prev : NULL;
+}
+
+static tusb_error_t list_remove_qhd(ehci_link_t* p_head, ehci_link_t* p_remove)
+{
+ ehci_link_t *p_prev = list_find_previous_item(p_head, p_remove);
+
+ ASSERT_PTR(p_prev, TUSB_ERROR_INVALID_PARA);
+
+ p_prev->address = p_remove->address;
+ // EHCI 4.8.2 link the removing queue head to async/period head (which always reachable by Host Controller)
+ p_remove->address = ((uint32_t) p_head) | (EHCI_QUEUE_ELEMENT_QHD << 1);
+
+ return TUSB_ERROR_NONE;
+}
+
+#endif
|
