summaryrefslogtreecommitdiff
path: root/src/host
diff options
context:
space:
mode:
authorhathach <[email protected]>2020-03-22 17:19:57 +0700
committerhathach <[email protected]>2020-03-22 17:19:57 +0700
commit4f871063fd33db2b55c6f78130c723a8854e0b11 (patch)
treeddc78bd68c16a0895e0a7b4f9d1aff6380a20484 /src/host
parent1ab3a1035c997e66b2014e8edfb92e10202cd988 (diff)
parente47f9921570c75afd3288407601897c5b283f043 (diff)
Merge branch 'master' into cr1901-msp430f5529
Diffstat (limited to 'src/host')
-rw-r--r--src/host/ehci/ehci.c1712
-rw-r--r--src/host/hcd.h293
-rw-r--r--src/host/hub.c500
-rw-r--r--src/host/hub.h388
-rw-r--r--src/host/ohci/ohci.c1302
-rw-r--r--src/host/ohci/ohci.h582
-rw-r--r--src/host/usbh.c1352
-rw-r--r--src/host/usbh.h3
8 files changed, 3068 insertions, 3064 deletions
diff --git a/src/host/ehci/ehci.c b/src/host/ehci/ehci.c
index 63e87dd54..9ddffaf2c 100644
--- a/src/host/ehci/ehci.c
+++ b/src/host/ehci/ehci.c
@@ -1,856 +1,856 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "common/tusb_common.h"
-
-#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX)
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
-
-#include "../hcd.h"
-#include "../usbh_hcd.h"
-#include "ehci.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-// Periodic frame list must be 4K alignment
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4096) static ehci_data_t ehci_data;
-
-// EHCI portable
-uint32_t hcd_ehci_register_addr(uint8_t rhport);
-
-//--------------------------------------------------------------------+
-// PROTOTYPE
-//--------------------------------------------------------------------+
-static inline ehci_link_t* get_period_head(uint8_t rhport, uint8_t interval_ms)
-{
- (void) rhport;
- return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min8(EHCI_FRAMELIST_SIZE, interval_ms) ) ];
-}
-
-static inline ehci_qhd_t* qhd_control(uint8_t dev_addr)
-{
- return &ehci_data.control[dev_addr].qhd;
-}
-
-static inline ehci_qhd_t* qhd_async_head(uint8_t rhport)
-{
- (void) rhport;
- return qhd_control(0); // control qhd of dev0 is used as async head
-}
-
-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);
-
-// determine if a queue head has bus-related error
-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, tusb_desc_endpoint_t const * ep_desc);
-
-static inline ehci_qtd_t* qtd_find_free (void);
-static inline ehci_qtd_t* qtd_next (ehci_qtd_t const * p_qtd);
-static inline void qtd_insert_to_qhd (ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new);
-static inline void qtd_remove_1st_from_qhd (ehci_qhd_t *p_qhd);
-static void qtd_init (ehci_qtd_t* p_qtd, void* 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 *p_link_pointer);
-
-static bool ehci_init (uint8_t rhport);
-
-//--------------------------------------------------------------------+
-// HCD API
-//--------------------------------------------------------------------+
-bool hcd_init(void)
-{
- tu_memclr(&ehci_data, sizeof(ehci_data_t));
- return ehci_init(TUH_OPT_RHPORT);
-}
-
-void hcd_port_reset(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_registers_t* regs = ehci_data.regs;
-
- regs->portsc_bm.port_enabled = 0; // disable port before reset
- regs->portsc_bm.port_reset = 1;
-}
-
-bool hcd_port_connect_status(uint8_t rhport)
-{
- (void) rhport;
- return ehci_data.regs->portsc_bm.current_connect_status;
-}
-
-tusb_speed_t hcd_port_speed_get(uint8_t rhport)
-{
- (void) rhport;
- return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed
-}
-
-static void list_remove_qhd_by_addr(ehci_link_t* list_head, uint8_t dev_addr)
-{
- for(ehci_link_t* prev = list_head;
- !prev->terminate && (tu_align32(prev->address) != (uint32_t) list_head);
- prev = list_next(prev) )
- {
- // TODO check type for ISO iTD and siTD
- ehci_qhd_t* qhd = (ehci_qhd_t*) list_next(prev);
- if ( qhd->dev_addr == dev_addr )
- {
- // TODO deactive all TD, wait for QHD to inactive before removal
- prev->address = qhd->next.address;
-
- // EHCI 4.8.2 link the removed qhd 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;
- }
- }
- }
-}
-
-// Close all opened endpoint belong to this device
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
-{
- // skip dev0
- if (dev_addr == 0) return;
-
- // Remove from async list
- list_remove_qhd_by_addr( (ehci_link_t*) qhd_async_head(rhport), dev_addr );
-
- // Remove from all interval period list
- for(uint8_t i = 0; i < TU_ARRAY_SZIE(ehci_data.period_head_arr); i++)
- {
- list_remove_qhd_by_addr( (ehci_link_t*) &ehci_data.period_head_arr[i], dev_addr);
- }
-
- // Async doorbell (EHCI 4.8.2 for operational details)
- ehci_data.regs->command_bm.async_adv_doorbell = 1;
-}
-
-// EHCI controller init
-static bool ehci_init(uint8_t rhport)
-{
- ehci_data.regs = (ehci_registers_t* ) hcd_ehci_register_addr(rhport);
-
- ehci_registers_t* regs = ehci_data.regs;
-
- //------------- CTRLDSSEGMENT Register (skip) -------------//
- //------------- USB INT Register -------------//
- regs->inten = 0; // 1. disable all the interrupt
- regs->status = EHCI_INT_MASK_ALL; // 2. clear all status
-
- regs->inten = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | EHCI_INT_MASK_ASYNC_ADVANCE |
- EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC ;
-
- //------------- Asynchronous List -------------//
- ehci_qhd_t * const async_head = qhd_async_head(rhport);
- tu_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_QTYPE_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_addr = (uint32_t) async_head;
-
- //------------- 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[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, 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_QTYPE_QHD;
- }
-
- for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2)
- {
- list_insert(framelist + i, get_period_head(rhport, 2), EHCI_QTYPE_QHD);
- }
-
- for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4)
- {
- list_insert(framelist + i, get_period_head(rhport, 4), EHCI_QTYPE_QHD);
- }
-
- list_insert(framelist+3, get_period_head(rhport, 8), EHCI_QTYPE_QHD);
-
- period_1ms->terminate = 1;
-
- regs->periodic_list_base = (uint32_t) framelist;
-
- //------------- TT Control (NXP only) -------------//
- 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
- | ((EHCI_CFG_FRAMELIST_SIZE_BITS & TU_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_bm.port_power = 1; // enable port power
-
- return true;
-}
-
-#if 0
-static void ehci_stop(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_registers_t* regs = ehci_data.regs;
-
- regs->command_bm.run_stop = 0;
-
- // USB Spec: controller has to stop within 16 uframe = 2 frames
- while( regs->status_bm.hc_halted == 0 ) {}
-}
-#endif
-
-//--------------------------------------------------------------------+
-// CONTROL PIPE API
-//--------------------------------------------------------------------+
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
-{
- (void) rhport;
-
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
-
- // FIXME control only for now
- if ( epnum == 0 )
- {
- ehci_qhd_t* qhd = qhd_control(dev_addr);
- ehci_qtd_t* qtd = qtd_control(dev_addr);
-
- qtd_init(qtd, buffer, buflen);
-
- // first first data toggle is always 1 (data & setup stage)
- qtd->data_toggle = 1;
- qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT;
- qtd->int_on_complete = 1;
- qtd->next.terminate = 1;
-
- // sw region
- qhd->p_qtd_list_head = qtd;
- qhd->p_qtd_list_tail = qtd;
-
- // attach TD
- qhd->qtd_overlay.next.address = (uint32_t) qtd;
- }
-
- return true;
-}
-
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
-
- ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd;
- ehci_qtd_t* td = &ehci_data.control[dev_addr].qtd;
-
- qtd_init(td, (void*) setup_packet, 8);
- td->pid = EHCI_PID_SETUP;
- td->int_on_complete = 1;
- td->next.terminate = 1;
-
- // sw region
- qhd->p_qtd_list_head = td;
- qhd->p_qtd_list_tail = td;
-
- // attach TD
- qhd->qtd_overlay.next.address = (uint32_t) td;
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// BULK/INT/ISO PIPE API
-//--------------------------------------------------------------------+
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- (void) rhport;
-
- // TODO not support ISO yet
- TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
-
- //------------- Prepare Queue Head -------------//
- ehci_qhd_t * p_qhd;
-
- if ( ep_desc->bEndpointAddress == 0 )
- {
- p_qhd = qhd_control(dev_addr);
- }else
- {
- p_qhd = qhd_find_free();
- }
- TU_ASSERT(p_qhd);
-
- qhd_init(p_qhd, dev_addr, ep_desc);
-
- // control of dev0 is always present as async head
- if ( dev_addr == 0 ) return true;
-
- // Insert to list
- ehci_link_t * list_head;
-
- switch (ep_desc->bmAttributes.xfer)
- {
- case TUSB_XFER_CONTROL:
- case TUSB_XFER_BULK:
- list_head = (ehci_link_t*) qhd_async_head(rhport);
- break;
-
- case TUSB_XFER_INTERRUPT:
- list_head = get_period_head(rhport, p_qhd->interval_ms);
- break;
-
- case TUSB_XFER_ISOCHRONOUS:
- // TODO iso is not supported
- break;
-
- default: break;
- }
-
- // TODO might need to disable async/period list
- list_insert( list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD);
-
- return true;
-}
-
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
-{
- //------------- set up QTD -------------//
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- ehci_qtd_t *p_qtd = qtd_find_free();
-
- TU_ASSERT(p_qtd);
-
- qtd_init(p_qtd, buffer, total_bytes);
- p_qtd->pid = p_qhd->pid;
-
- //------------- insert TD to TD list -------------//
- qtd_insert_to_qhd(p_qhd, p_qtd);
-
- return true;
-}
-
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- TU_ASSERT ( hcd_pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes) );
-
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
-
- 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 head QTD to QHD start transferring
-
- return true;
-}
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL);
-}
-
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd);
-}
-
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
-{
- ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
- p_qhd->qtd_overlay.halted = 0;
- // TODO reset data toggle ?
- return true;
-}
-
-//--------------------------------------------------------------------+
-// EHCI Interrupt Handler
-//--------------------------------------------------------------------+
-
-// async_advance is handshake between usb stack & ehci controller.
-// This isr mean it is safe to modify previously removed queue head from async list.
-// In tinyusb, queue head is only removed when device is unplugged.
-static void async_advance_isr(uint8_t rhport)
-{
- (void) rhport;
-
- ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
- for(uint32_t i = 0; i < HCD_MAX_ENDPOINT; i++)
- {
- if ( qhd_pool[i].removing )
- {
- qhd_pool[i].removing = 0;
- qhd_pool[i].used = 0;
- }
- }
-}
-
-static void port_connect_status_change_isr(uint8_t hostid)
-{
- // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device
- if (ehci_data.regs->portsc_bm.current_connect_status)
- {
- hcd_port_reset(hostid);
- hcd_event_device_attach(hostid);
- }else // device unplugged
- {
- hcd_event_device_remove(hostid);
- }
-}
-
-static void qhd_xfer_complete_isr(ehci_qhd_t * 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)
- {
- // 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
- hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), XFER_RESULT_SUCCESS, p_qhd->total_xferred_bytes);
- p_qhd->total_xferred_bytes = 0;
- }
- }
-}
-
-static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
-{
- 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);
- }while(p_qhd != async_head); // async list traversal, stop if loop around
-}
-
-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 == tu_align32(next_item.address)) &&
- max_loop < (HCD_MAX_ENDPOINT + EHCI_MAX_ITD + EHCI_MAX_SITD)*CFG_TUSB_HOST_DEVICE_MAX)
- {
- switch ( next_item.type )
- {
- case EHCI_QTYPE_QHD:
- {
- ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
- if ( !p_qhd_int->qtd_overlay.halted )
- {
- qhd_xfer_complete_isr(p_qhd_int);
- }
- }
- break;
-
- case EHCI_QTYPE_ITD: // TODO support hs/fs ISO
- case EHCI_QTYPE_SITD:
- case EHCI_QTYPE_FSTN:
-
- default: break;
- }
-
- next_item = *list_next(&next_item);
- max_loop++;
- }
-}
-
-static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd)
-{
- if ( (p_qhd->dev_addr != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL
- qhd_has_xact_error(p_qhd) )
- {
- // current qhd has error in transaction
- xfer_result_t error_event;
-
- // no error bits are set, endpoint is halted due to STALL
- error_event = qhd_has_xact_error(p_qhd) ? XFER_RESULT_FAILED : XFER_RESULT_STALLED;
-
- p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
-
-// if ( XFER_RESULT_FAILED == error_event ) TU_BREAKPOINT(); // TODO skip unplugged device
-
- p_qhd->p_qtd_list_head->used = 0; // free QTD
- qtd_remove_1st_from_qhd(p_qhd);
-
- if ( 0 == p_qhd->ep_number )
- {
- // 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 = qtd_control(p_qhd->dev_addr);
- p_setup->used = 0;
- }
-
- // call USBH callback
- hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), error_event, p_qhd->total_xferred_bytes);
-
- p_qhd->total_xferred_bytes = 0;
- }
-}
-
-static void xfer_error_isr(uint8_t hostid)
-{
- //------------- async list -------------//
- ehci_qhd_t * const async_head = qhd_async_head(hostid);
- ehci_qhd_t *p_qhd = async_head;
- do
- {
- qhd_xfer_error_isr( p_qhd );
- p_qhd = qhd_next(p_qhd);
- }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(hostid, 1);
- for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2)
- {
- 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 == tu_align32(next_item.address)) )
- {
- switch ( next_item.type )
- {
- case EHCI_QTYPE_QHD:
- {
- ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
- qhd_xfer_error_isr(p_qhd_int);
- }
- break;
-
- // TODO support hs/fs ISO
- case EHCI_QTYPE_ITD:
- case EHCI_QTYPE_SITD:
- case EHCI_QTYPE_FSTN:
- default: break;
- }
-
- next_item = *list_next(&next_item);
- }
- }
-}
-
-//------------- Host Controller Driver's Interrupt Handler -------------//
-void hal_hcd_isr(uint8_t rhport)
-{
- ehci_registers_t* regs = ehci_data.regs;
-
- uint32_t int_status = regs->status;
- int_status &= regs->inten;
-
- regs->status |= 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_bm.connect_status_change)
- {
- port_connect_status_change_isr(rhport);
- }
-
- regs->portsc |= port_status; // Acknowledge change bits in portsc
- }
-
- if (int_status & EHCI_INT_MASK_ERROR)
- {
- xfer_error_isr(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) );
- }
-
- if (int_status & EHCI_INT_MASK_NXP_PERIODIC)
- {
- for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2)
- {
- period_list_xfer_complete_isr( rhport, i );
- }
- }
-
- //------------- 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(rhport);
- }
-}
-
-//--------------------------------------------------------------------+
-// HELPER
-//--------------------------------------------------------------------+
-
-
-//------------- queue head helper -------------//
-static inline ehci_qhd_t* qhd_find_free (void)
-{
- for (uint32_t i=0; i<HCD_MAX_ENDPOINT; 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);
-}
-
-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;
-
- for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
- {
- if ( (qhd_pool[i].dev_addr == dev_addr) &&
- ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) )
- {
- return &qhd_pool[i];
- }
- }
-
- return NULL;
-}
-
-//------------- TD helper -------------//
-static inline ehci_qtd_t* qtd_find_free(void)
-{
- for (uint32_t i=0; i<HCD_MAX_XFER; i++)
- {
- if ( !ehci_data.qtd_pool[i].used ) return &ehci_data.qtd_pool[i];
- }
-
- return NULL;
-}
-
-static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd )
-{
- return (ehci_qtd_t*) tu_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, 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)
- if (dev_addr != 0)
- {
- tu_memclr(p_qhd, sizeof(ehci_qhd_t));
- }
-
- uint8_t const xfer_type = ep_desc->bmAttributes.xfer;
- uint8_t const interval = ep_desc->bInterval;
-
- p_qhd->dev_addr = dev_addr;
- p_qhd->fl_inactive_next_xact = 0;
- p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress);
- p_qhd->ep_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_packet_size = ep_desc->wMaxPacketSize.size;
- p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0;
- p_qhd->nak_reload = 0;
-
- // Bulk/Control -> smask = cmask = 0
- // TODO Isochronous
- if (TUSB_XFER_INTERRUPT == xfer_type)
- {
- if (TUSB_SPEED_HIGH == p_qhd->ep_speed)
- {
- TU_ASSERT( interval <= 16, );
- if ( interval < 4) // sub milisecond interval
- {
- p_qhd->interval_ms = 0;
- p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) :
- (interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100);
- }else
- {
- p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 );
- p_qhd->int_smask = TU_BIT(interval % 8);
- }
- }else
- {
- TU_ASSERT( 0 != interval, );
- // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes
- p_qhd->int_smask = 0x01;
- p_qhd->fl_int_cmask = TU_BIN8(11100);
- p_qhd->interval_ms = interval;
- }
- }else
- {
- p_qhd->int_smask = p_qhd->fl_int_cmask = 0;
- }
-
- p_qhd->fl_hub_addr = _usbh_devices[dev_addr].hub_addr;
- p_qhd->fl_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->removing = 0;
- p_qhd->p_qtd_list_head = NULL;
- p_qhd->p_qtd_list_tail = NULL;
- p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? 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->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT)
- {
- p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11
- }
-}
-
-static void qtd_init(ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes)
-{
- tu_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->err_count = 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] = (uint32_t) buffer;
- for(uint8_t i=1; i<5; i++)
- {
- p_qtd->buffer[i] |= tu_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*) tu_align32(p_link_pointer->address);
-}
-
-#endif
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "common/tusb_common.h"
+
+#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX)
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "osal/osal.h"
+
+#include "../hcd.h"
+#include "../usbh_hcd.h"
+#include "ehci.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+// Periodic frame list must be 4K alignment
+CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4096) static ehci_data_t ehci_data;
+
+// EHCI portable
+uint32_t hcd_ehci_register_addr(uint8_t rhport);
+
+//--------------------------------------------------------------------+
+// PROTOTYPE
+//--------------------------------------------------------------------+
+static inline ehci_link_t* get_period_head(uint8_t rhport, uint8_t interval_ms)
+{
+ (void) rhport;
+ return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min8(EHCI_FRAMELIST_SIZE, interval_ms) ) ];
+}
+
+static inline ehci_qhd_t* qhd_control(uint8_t dev_addr)
+{
+ return &ehci_data.control[dev_addr].qhd;
+}
+
+static inline ehci_qhd_t* qhd_async_head(uint8_t rhport)
+{
+ (void) rhport;
+ return qhd_control(0); // control qhd of dev0 is used as async head
+}
+
+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);
+
+// determine if a queue head has bus-related error
+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, tusb_desc_endpoint_t const * ep_desc);
+
+static inline ehci_qtd_t* qtd_find_free (void);
+static inline ehci_qtd_t* qtd_next (ehci_qtd_t const * p_qtd);
+static inline void qtd_insert_to_qhd (ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new);
+static inline void qtd_remove_1st_from_qhd (ehci_qhd_t *p_qhd);
+static void qtd_init (ehci_qtd_t* p_qtd, void* 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 *p_link_pointer);
+
+static bool ehci_init (uint8_t rhport);
+
+//--------------------------------------------------------------------+
+// HCD API
+//--------------------------------------------------------------------+
+bool hcd_init(void)
+{
+ tu_memclr(&ehci_data, sizeof(ehci_data_t));
+ return ehci_init(TUH_OPT_RHPORT);
+}
+
+void hcd_port_reset(uint8_t rhport)
+{
+ (void) rhport;
+
+ ehci_registers_t* regs = ehci_data.regs;
+
+ regs->portsc_bm.port_enabled = 0; // disable port before reset
+ regs->portsc_bm.port_reset = 1;
+}
+
+bool hcd_port_connect_status(uint8_t rhport)
+{
+ (void) rhport;
+ return ehci_data.regs->portsc_bm.current_connect_status;
+}
+
+tusb_speed_t hcd_port_speed_get(uint8_t rhport)
+{
+ (void) rhport;
+ return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed
+}
+
+static void list_remove_qhd_by_addr(ehci_link_t* list_head, uint8_t dev_addr)
+{
+ for(ehci_link_t* prev = list_head;
+ !prev->terminate && (tu_align32(prev->address) != (uint32_t) list_head);
+ prev = list_next(prev) )
+ {
+ // TODO check type for ISO iTD and siTD
+ ehci_qhd_t* qhd = (ehci_qhd_t*) list_next(prev);
+ if ( qhd->dev_addr == dev_addr )
+ {
+ // TODO deactive all TD, wait for QHD to inactive before removal
+ prev->address = qhd->next.address;
+
+ // EHCI 4.8.2 link the removed qhd 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;
+ }
+ }
+ }
+}
+
+// Close all opened endpoint belong to this device
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
+{
+ // skip dev0
+ if (dev_addr == 0) return;
+
+ // Remove from async list
+ list_remove_qhd_by_addr( (ehci_link_t*) qhd_async_head(rhport), dev_addr );
+
+ // Remove from all interval period list
+ for(uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++)
+ {
+ list_remove_qhd_by_addr( (ehci_link_t*) &ehci_data.period_head_arr[i], dev_addr);
+ }
+
+ // Async doorbell (EHCI 4.8.2 for operational details)
+ ehci_data.regs->command_bm.async_adv_doorbell = 1;
+}
+
+// EHCI controller init
+static bool ehci_init(uint8_t rhport)
+{
+ ehci_data.regs = (ehci_registers_t* ) hcd_ehci_register_addr(rhport);
+
+ ehci_registers_t* regs = ehci_data.regs;
+
+ //------------- CTRLDSSEGMENT Register (skip) -------------//
+ //------------- USB INT Register -------------//
+ regs->inten = 0; // 1. disable all the interrupt
+ regs->status = EHCI_INT_MASK_ALL; // 2. clear all status
+
+ regs->inten = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | EHCI_INT_MASK_ASYNC_ADVANCE |
+ EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC ;
+
+ //------------- Asynchronous List -------------//
+ ehci_qhd_t * const async_head = qhd_async_head(rhport);
+ tu_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_QTYPE_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_addr = (uint32_t) async_head;
+
+ //------------- 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[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, 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_QTYPE_QHD;
+ }
+
+ for(uint32_t i=0; i<EHCI_FRAMELIST_SIZE; i+=2)
+ {
+ list_insert(framelist + i, get_period_head(rhport, 2), EHCI_QTYPE_QHD);
+ }
+
+ for(uint32_t i=1; i<EHCI_FRAMELIST_SIZE; i+=4)
+ {
+ list_insert(framelist + i, get_period_head(rhport, 4), EHCI_QTYPE_QHD);
+ }
+
+ list_insert(framelist+3, get_period_head(rhport, 8), EHCI_QTYPE_QHD);
+
+ period_1ms->terminate = 1;
+
+ regs->periodic_list_base = (uint32_t) framelist;
+
+ //------------- TT Control (NXP only) -------------//
+ 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
+ | ((EHCI_CFG_FRAMELIST_SIZE_BITS & TU_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_bm.port_power = 1; // enable port power
+
+ return true;
+}
+
+#if 0
+static void ehci_stop(uint8_t rhport)
+{
+ (void) rhport;
+
+ ehci_registers_t* regs = ehci_data.regs;
+
+ regs->command_bm.run_stop = 0;
+
+ // USB Spec: controller has to stop within 16 uframe = 2 frames
+ while( regs->status_bm.hc_halted == 0 ) {}
+}
+#endif
+
+//--------------------------------------------------------------------+
+// CONTROL PIPE API
+//--------------------------------------------------------------------+
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
+{
+ (void) rhport;
+
+ uint8_t const epnum = tu_edpt_number(ep_addr);
+ uint8_t const dir = tu_edpt_dir(ep_addr);
+
+ // FIXME control only for now
+ if ( epnum == 0 )
+ {
+ ehci_qhd_t* qhd = qhd_control(dev_addr);
+ ehci_qtd_t* qtd = qtd_control(dev_addr);
+
+ qtd_init(qtd, buffer, buflen);
+
+ // first first data toggle is always 1 (data & setup stage)
+ qtd->data_toggle = 1;
+ qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT;
+ qtd->int_on_complete = 1;
+ qtd->next.terminate = 1;
+
+ // sw region
+ qhd->p_qtd_list_head = qtd;
+ qhd->p_qtd_list_tail = qtd;
+
+ // attach TD
+ qhd->qtd_overlay.next.address = (uint32_t) qtd;
+ }
+
+ return true;
+}
+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
+{
+ (void) rhport;
+
+ ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd;
+ ehci_qtd_t* td = &ehci_data.control[dev_addr].qtd;
+
+ qtd_init(td, (void*) setup_packet, 8);
+ td->pid = EHCI_PID_SETUP;
+ td->int_on_complete = 1;
+ td->next.terminate = 1;
+
+ // sw region
+ qhd->p_qtd_list_head = td;
+ qhd->p_qtd_list_tail = td;
+
+ // attach TD
+ qhd->qtd_overlay.next.address = (uint32_t) td;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// BULK/INT/ISO PIPE API
+//--------------------------------------------------------------------+
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
+{
+ (void) rhport;
+
+ // TODO not support ISO yet
+ TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
+
+ //------------- Prepare Queue Head -------------//
+ ehci_qhd_t * p_qhd;
+
+ if ( ep_desc->bEndpointAddress == 0 )
+ {
+ p_qhd = qhd_control(dev_addr);
+ }else
+ {
+ p_qhd = qhd_find_free();
+ }
+ TU_ASSERT(p_qhd);
+
+ qhd_init(p_qhd, dev_addr, ep_desc);
+
+ // control of dev0 is always present as async head
+ if ( dev_addr == 0 ) return true;
+
+ // Insert to list
+ ehci_link_t * list_head;
+
+ switch (ep_desc->bmAttributes.xfer)
+ {
+ case TUSB_XFER_CONTROL:
+ case TUSB_XFER_BULK:
+ list_head = (ehci_link_t*) qhd_async_head(rhport);
+ break;
+
+ case TUSB_XFER_INTERRUPT:
+ list_head = get_period_head(rhport, p_qhd->interval_ms);
+ break;
+
+ case TUSB_XFER_ISOCHRONOUS:
+ // TODO iso is not supported
+ break;
+
+ default: break;
+ }
+
+ // TODO might need to disable async/period list
+ list_insert( list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD);
+
+ return true;
+}
+
+bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
+{
+ //------------- set up QTD -------------//
+ ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
+ ehci_qtd_t *p_qtd = qtd_find_free();
+
+ TU_ASSERT(p_qtd);
+
+ qtd_init(p_qtd, buffer, total_bytes);
+ p_qtd->pid = p_qhd->pid;
+
+ //------------- insert TD to TD list -------------//
+ qtd_insert_to_qhd(p_qhd, p_qtd);
+
+ return true;
+}
+
+bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
+{
+ TU_ASSERT ( hcd_pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes) );
+
+ ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
+
+ 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 head QTD to QHD start transferring
+
+ return true;
+}
+
+bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
+ return !p_qhd->qtd_overlay.halted && (p_qhd->p_qtd_list_head != NULL);
+}
+
+bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
+ return p_qhd->qtd_overlay.halted && !qhd_has_xact_error(p_qhd);
+}
+
+bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
+ p_qhd->qtd_overlay.halted = 0;
+ // TODO reset data toggle ?
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// EHCI Interrupt Handler
+//--------------------------------------------------------------------+
+
+// async_advance is handshake between usb stack & ehci controller.
+// This isr mean it is safe to modify previously removed queue head from async list.
+// In tinyusb, queue head is only removed when device is unplugged.
+static void async_advance_isr(uint8_t rhport)
+{
+ (void) rhport;
+
+ ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
+ for(uint32_t i = 0; i < HCD_MAX_ENDPOINT; i++)
+ {
+ if ( qhd_pool[i].removing )
+ {
+ qhd_pool[i].removing = 0;
+ qhd_pool[i].used = 0;
+ }
+ }
+}
+
+static void port_connect_status_change_isr(uint8_t hostid)
+{
+ // NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device
+ if (ehci_data.regs->portsc_bm.current_connect_status)
+ {
+ hcd_port_reset(hostid);
+ hcd_event_device_attach(hostid);
+ }else // device unplugged
+ {
+ hcd_event_device_remove(hostid);
+ }
+}
+
+static void qhd_xfer_complete_isr(ehci_qhd_t * 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)
+ {
+ // 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
+ hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), XFER_RESULT_SUCCESS, p_qhd->total_xferred_bytes);
+ p_qhd->total_xferred_bytes = 0;
+ }
+ }
+}
+
+static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
+{
+ 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);
+ }while(p_qhd != async_head); // async list traversal, stop if loop around
+}
+
+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 == tu_align32(next_item.address)) &&
+ max_loop < (HCD_MAX_ENDPOINT + EHCI_MAX_ITD + EHCI_MAX_SITD)*CFG_TUSB_HOST_DEVICE_MAX)
+ {
+ switch ( next_item.type )
+ {
+ case EHCI_QTYPE_QHD:
+ {
+ ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
+ if ( !p_qhd_int->qtd_overlay.halted )
+ {
+ qhd_xfer_complete_isr(p_qhd_int);
+ }
+ }
+ break;
+
+ case EHCI_QTYPE_ITD: // TODO support hs/fs ISO
+ case EHCI_QTYPE_SITD:
+ case EHCI_QTYPE_FSTN:
+
+ default: break;
+ }
+
+ next_item = *list_next(&next_item);
+ max_loop++;
+ }
+}
+
+static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd)
+{
+ if ( (p_qhd->dev_addr != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL
+ qhd_has_xact_error(p_qhd) )
+ {
+ // current qhd has error in transaction
+ xfer_result_t error_event;
+
+ // no error bits are set, endpoint is halted due to STALL
+ error_event = qhd_has_xact_error(p_qhd) ? XFER_RESULT_FAILED : XFER_RESULT_STALLED;
+
+ p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
+
+// if ( XFER_RESULT_FAILED == error_event ) TU_BREAKPOINT(); // TODO skip unplugged device
+
+ p_qhd->p_qtd_list_head->used = 0; // free QTD
+ qtd_remove_1st_from_qhd(p_qhd);
+
+ if ( 0 == p_qhd->ep_number )
+ {
+ // 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 = qtd_control(p_qhd->dev_addr);
+ p_setup->used = 0;
+ }
+
+ // call USBH callback
+ hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), error_event, p_qhd->total_xferred_bytes);
+
+ p_qhd->total_xferred_bytes = 0;
+ }
+}
+
+static void xfer_error_isr(uint8_t hostid)
+{
+ //------------- async list -------------//
+ ehci_qhd_t * const async_head = qhd_async_head(hostid);
+ ehci_qhd_t *p_qhd = async_head;
+ do
+ {
+ qhd_xfer_error_isr( p_qhd );
+ p_qhd = qhd_next(p_qhd);
+ }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(hostid, 1);
+ for (uint8_t interval_ms=1; interval_ms <= EHCI_FRAMELIST_SIZE; interval_ms *= 2)
+ {
+ 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 == tu_align32(next_item.address)) )
+ {
+ switch ( next_item.type )
+ {
+ case EHCI_QTYPE_QHD:
+ {
+ ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
+ qhd_xfer_error_isr(p_qhd_int);
+ }
+ break;
+
+ // TODO support hs/fs ISO
+ case EHCI_QTYPE_ITD:
+ case EHCI_QTYPE_SITD:
+ case EHCI_QTYPE_FSTN:
+ default: break;
+ }
+
+ next_item = *list_next(&next_item);
+ }
+ }
+}
+
+//------------- Host Controller Driver's Interrupt Handler -------------//
+void hcd_isr(uint8_t rhport)
+{
+ ehci_registers_t* regs = ehci_data.regs;
+
+ uint32_t int_status = regs->status;
+ int_status &= regs->inten;
+
+ regs->status |= 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_bm.connect_status_change)
+ {
+ port_connect_status_change_isr(rhport);
+ }
+
+ regs->portsc |= port_status; // Acknowledge change bits in portsc
+ }
+
+ if (int_status & EHCI_INT_MASK_ERROR)
+ {
+ xfer_error_isr(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) );
+ }
+
+ if (int_status & EHCI_INT_MASK_NXP_PERIODIC)
+ {
+ for (uint8_t i=1; i <= EHCI_FRAMELIST_SIZE; i *= 2)
+ {
+ period_list_xfer_complete_isr( rhport, i );
+ }
+ }
+
+ //------------- 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(rhport);
+ }
+}
+
+//--------------------------------------------------------------------+
+// HELPER
+//--------------------------------------------------------------------+
+
+
+//------------- queue head helper -------------//
+static inline ehci_qhd_t* qhd_find_free (void)
+{
+ for (uint32_t i=0; i<HCD_MAX_ENDPOINT; 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);
+}
+
+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;
+
+ for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
+ {
+ if ( (qhd_pool[i].dev_addr == dev_addr) &&
+ ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) )
+ {
+ return &qhd_pool[i];
+ }
+ }
+
+ return NULL;
+}
+
+//------------- TD helper -------------//
+static inline ehci_qtd_t* qtd_find_free(void)
+{
+ for (uint32_t i=0; i<HCD_MAX_XFER; i++)
+ {
+ if ( !ehci_data.qtd_pool[i].used ) return &ehci_data.qtd_pool[i];
+ }
+
+ return NULL;
+}
+
+static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd )
+{
+ return (ehci_qtd_t*) tu_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, 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)
+ if (dev_addr != 0)
+ {
+ tu_memclr(p_qhd, sizeof(ehci_qhd_t));
+ }
+
+ uint8_t const xfer_type = ep_desc->bmAttributes.xfer;
+ uint8_t const interval = ep_desc->bInterval;
+
+ p_qhd->dev_addr = dev_addr;
+ p_qhd->fl_inactive_next_xact = 0;
+ p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress);
+ p_qhd->ep_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_packet_size = ep_desc->wMaxPacketSize.size;
+ p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0;
+ p_qhd->nak_reload = 0;
+
+ // Bulk/Control -> smask = cmask = 0
+ // TODO Isochronous
+ if (TUSB_XFER_INTERRUPT == xfer_type)
+ {
+ if (TUSB_SPEED_HIGH == p_qhd->ep_speed)
+ {
+ TU_ASSERT( interval <= 16, );
+ if ( interval < 4) // sub milisecond interval
+ {
+ p_qhd->interval_ms = 0;
+ p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) :
+ (interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100);
+ }else
+ {
+ p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 );
+ p_qhd->int_smask = TU_BIT(interval % 8);
+ }
+ }else
+ {
+ TU_ASSERT( 0 != interval, );
+ // Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes
+ p_qhd->int_smask = 0x01;
+ p_qhd->fl_int_cmask = TU_BIN8(11100);
+ p_qhd->interval_ms = interval;
+ }
+ }else
+ {
+ p_qhd->int_smask = p_qhd->fl_int_cmask = 0;
+ }
+
+ p_qhd->fl_hub_addr = _usbh_devices[dev_addr].hub_addr;
+ p_qhd->fl_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->removing = 0;
+ p_qhd->p_qtd_list_head = NULL;
+ p_qhd->p_qtd_list_tail = NULL;
+ p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? 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->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT)
+ {
+ p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11
+ }
+}
+
+static void qtd_init(ehci_qtd_t* p_qtd, void* buffer, uint16_t total_bytes)
+{
+ tu_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->err_count = 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] = (uint32_t) buffer;
+ for(uint8_t i=1; i<5; i++)
+ {
+ p_qtd->buffer[i] |= tu_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*) tu_align32(p_link_pointer->address);
+}
+
+#endif
diff --git a/src/host/hcd.h b/src/host/hcd.h
index 86e7d664b..a39e7fe16 100644
--- a/src/host/hcd.h
+++ b/src/host/hcd.h
@@ -1,146 +1,147 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup group_usbh
- * \defgroup Group_HCD Host Controller Driver (HCD)
- * @{ */
-
-#ifndef _TUSB_HCD_H_
-#define _TUSB_HCD_H_
-
-#include <common/tusb_common.h>
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
- //--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-typedef enum
-{
- HCD_EVENT_DEVICE_ATTACH,
- HCD_EVENT_DEVICE_REMOVE,
- HCD_EVENT_XFER_COMPLETE,
-} hcd_eventid_t;
-
-typedef struct
-{
- uint8_t rhport;
- uint8_t event_id;
-
- union
- {
- struct
- {
- uint8_t hub_addr;
- uint8_t hub_port;
- } attach, remove;
-
- struct
- {
- uint8_t ep_addr;
- uint8_t result;
- uint32_t len;
- } xfer_complete;
- };
-
-} hcd_event_t;
-
-#if TUSB_OPT_HOST_ENABLED
-// Max number of endpoints per device
-enum {
- HCD_MAX_ENDPOINT = CFG_TUSB_HOST_DEVICE_MAX*(CFG_TUH_HUB + CFG_TUH_HID_KEYBOARD + CFG_TUH_HID_MOUSE + CFG_TUSB_HOST_HID_GENERIC +
- CFG_TUH_MSC*2 + CFG_TUH_CDC*3),
-
- HCD_MAX_XFER = HCD_MAX_ENDPOINT*2,
-};
-
-//#define HCD_MAX_ENDPOINT 16
-//#define HCD_MAX_XFER 16
-#endif
-
-//--------------------------------------------------------------------+
-// HCD API
-//--------------------------------------------------------------------+
-bool hcd_init(void);
-void hcd_int_enable (uint8_t rhport);
-void hcd_int_disable(uint8_t rhport);
-
-// PORT API
-/// return the current connect status of roothub port
-bool hcd_port_connect_status(uint8_t hostid);
-void hcd_port_reset(uint8_t hostid);
-tusb_speed_t hcd_port_speed_get(uint8_t hostid);
-
-// HCD closes all opened endpoints belong to this device
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// Event function
-//--------------------------------------------------------------------+
-void hcd_event_handler(hcd_event_t const* event, bool in_isr);
-
-// Helper to send device attach event
-void hcd_event_device_attach(uint8_t rhport);
-
-// Helper to send device removal event
-void hcd_event_device_remove(uint8_t rhport);
-
-// Helper to send USB transfer event
-void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
-
-//--------------------------------------------------------------------+
-// Endpoints API
-//--------------------------------------------------------------------+
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]);
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr);
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr);
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr);
-
-// TODO merge with pipe_xfer
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen);
-
-//--------------------------------------------------------------------+
-// PIPE API
-//--------------------------------------------------------------------+
-// TODO control xfer should be used via usbh layer
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes); // only queue, not transferring yet
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete);
-
-#if 0
-tusb_error_t hcd_pipe_cancel();
-#endif
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_HCD_H_ */
-
-/// @}
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+/** \ingroup group_usbh
+ * \defgroup Group_HCD Host Controller Driver (HCD)
+ * @{ */
+
+#ifndef _TUSB_HCD_H_
+#define _TUSB_HCD_H_
+
+#include <common/tusb_common.h>
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+ //--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+typedef enum
+{
+ HCD_EVENT_DEVICE_ATTACH,
+ HCD_EVENT_DEVICE_REMOVE,
+ HCD_EVENT_XFER_COMPLETE,
+} hcd_eventid_t;
+
+typedef struct
+{
+ uint8_t rhport;
+ uint8_t event_id;
+
+ union
+ {
+ struct
+ {
+ uint8_t hub_addr;
+ uint8_t hub_port;
+ } attach, remove;
+
+ struct
+ {
+ uint8_t ep_addr;
+ uint8_t result;
+ uint32_t len;
+ } xfer_complete;
+ };
+
+} hcd_event_t;
+
+#if TUSB_OPT_HOST_ENABLED
+// Max number of endpoints per device
+enum {
+ HCD_MAX_ENDPOINT = CFG_TUSB_HOST_DEVICE_MAX*(CFG_TUH_HUB + CFG_TUH_HID_KEYBOARD + CFG_TUH_HID_MOUSE + CFG_TUSB_HOST_HID_GENERIC +
+ CFG_TUH_MSC*2 + CFG_TUH_CDC*3),
+
+ HCD_MAX_XFER = HCD_MAX_ENDPOINT*2,
+};
+
+//#define HCD_MAX_ENDPOINT 16
+//#define HCD_MAX_XFER 16
+#endif
+
+//--------------------------------------------------------------------+
+// HCD API
+//--------------------------------------------------------------------+
+bool hcd_init(void);
+void hcd_isr(uint8_t hostid);
+void hcd_int_enable (uint8_t rhport);
+void hcd_int_disable(uint8_t rhport);
+
+// PORT API
+/// return the current connect status of roothub port
+bool hcd_port_connect_status(uint8_t hostid);
+void hcd_port_reset(uint8_t hostid);
+tusb_speed_t hcd_port_speed_get(uint8_t hostid);
+
+// HCD closes all opened endpoints belong to this device
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr);
+
+//--------------------------------------------------------------------+
+// Event function
+//--------------------------------------------------------------------+
+void hcd_event_handler(hcd_event_t const* event, bool in_isr);
+
+// Helper to send device attach event
+void hcd_event_device_attach(uint8_t rhport);
+
+// Helper to send device removal event
+void hcd_event_device_remove(uint8_t rhport);
+
+// Helper to send USB transfer event
+void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+
+//--------------------------------------------------------------------+
+// Endpoints API
+//--------------------------------------------------------------------+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]);
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
+
+bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr);
+bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr);
+bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr);
+
+// TODO merge with pipe_xfer
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen);
+
+//--------------------------------------------------------------------+
+// PIPE API
+//--------------------------------------------------------------------+
+// TODO control xfer should be used via usbh layer
+bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes); // only queue, not transferring yet
+bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete);
+
+#if 0
+tusb_error_t hcd_pipe_cancel();
+#endif
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_HCD_H_ */
+
+/// @}
diff --git a/src/host/hub.c b/src/host/hub.c
index a2d4e8429..00450b704 100644
--- a/src/host/hub.c
+++ b/src/host/hub.c
@@ -1,250 +1,250 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "tusb_option.h"
-
-#if (TUSB_OPT_HOST_ENABLED && CFG_TUH_HUB)
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "hub.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-typedef struct
-{
- uint8_t itf_num;
- uint8_t ep_status;
- uint8_t port_number;
- uint8_t status_change; // data from status change interrupt endpoint
-}usbh_hub_t;
-
-CFG_TUSB_MEM_SECTION static usbh_hub_t hub_data[CFG_TUSB_HOST_DEVICE_MAX];
-TU_ATTR_ALIGNED(4) CFG_TUSB_MEM_SECTION static uint8_t hub_enum_buffer[sizeof(descriptor_hub_desc_t)];
-
-//OSAL_SEM_DEF(hub_enum_semaphore);
-//static osal_semaphore_handle_t hub_enum_sem_hdl;
-
-//--------------------------------------------------------------------+
-// HUB
-//--------------------------------------------------------------------+
-bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature)
-{
- TU_ASSERT(HUB_FEATURE_PORT_CONNECTION_CHANGE <= feature && feature <= HUB_FEATURE_PORT_RESET_CHANGE);
-
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_CLEAR_FEATURE,
- .wValue = feature,
- .wIndex = hub_port,
- .wLength = 0
- };
-
- //------------- Clear Port Feature request -------------//
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
-
- //------------- Get Port Status to check if feature is cleared -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = hub_port,
- .wLength = 4
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
-
- //------------- Check if feature is cleared -------------//
- hub_port_status_response_t * p_port_status;
- p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
-
- TU_ASSERT( !tu_bit_test(p_port_status->status_change.value, feature-16) );
-
- return true;
-}
-
-bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port)
-{
- enum { RESET_DELAY = 200 }; // USB specs say only 50ms but many devices require much longer
-
- //------------- Set Port Reset -------------//
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_SET_FEATURE,
- .wValue = HUB_FEATURE_PORT_RESET,
- .wIndex = hub_port,
- .wLength = 0
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
-
- osal_task_delay(RESET_DELAY); // TODO Hub wait for Status Endpoint on Reset Change
-
- //------------- Get Port Status to check if port is enabled, powered and reset_change -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = hub_port,
- .wLength = 4
- };
-
- TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
-
- hub_port_status_response_t * p_port_status;
- p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
-
- TU_ASSERT ( p_port_status->status_change.reset && p_port_status->status_current.connect_status &&
- p_port_status->status_current.port_power && p_port_status->status_current.port_enable);
-
- return true;
-}
-
-// can only get the speed RIGHT AFTER hub_port_reset_subtask call
-tusb_speed_t hub_port_get_speed(void)
-{
- hub_port_status_response_t * p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
- return (p_port_status->status_current.high_speed_device_attached) ? TUSB_SPEED_HIGH :
- (p_port_status->status_current.low_speed_device_attached ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
-}
-
-//--------------------------------------------------------------------+
-// CLASS-USBH API (don't require to verify parameters)
-//--------------------------------------------------------------------+
-void hub_init(void)
-{
- tu_memclr(hub_data, CFG_TUSB_HOST_DEVICE_MAX*sizeof(usbh_hub_t));
-// hub_enum_sem_hdl = osal_semaphore_create( OSAL_SEM_REF(hub_enum_semaphore) );
-}
-
-bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length)
-{
- // not support multiple TT yet
- if ( itf_desc->bInterfaceProtocol > 1 ) return false;
-
- //------------- Open Interrupt Status Pipe -------------//
- tusb_desc_endpoint_t const *ep_desc;
- ep_desc = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc);
-
- TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType);
- TU_ASSERT(TUSB_XFER_INTERRUPT == ep_desc->bmAttributes.xfer);
-
- TU_ASSERT(hcd_edpt_open(rhport, dev_addr, ep_desc));
-
- hub_data[dev_addr-1].itf_num = itf_desc->bInterfaceNumber;
- hub_data[dev_addr-1].ep_status = ep_desc->bEndpointAddress;
-
- (*p_length) = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t);
-
- //------------- Get Hub Descriptor -------------//
- tusb_control_request_t request = {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_DESCRIPTOR,
- .wValue = 0,
- .wIndex = 0,
- .wLength = sizeof(descriptor_hub_desc_t)
- };
-
- TU_ASSERT( usbh_control_xfer( dev_addr, &request, hub_enum_buffer ) );
-
- // only care about this field in hub descriptor
- hub_data[dev_addr-1].port_number = ((descriptor_hub_desc_t*) hub_enum_buffer)->bNbrPorts;
-
- //------------- Set Port_Power on all ports -------------//
- // TODO may only power port with attached
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
- .bRequest = HUB_REQUEST_SET_FEATURE,
- .wValue = HUB_FEATURE_PORT_POWER,
- .wIndex = 0,
- .wLength = 0
- };
-
- for(uint8_t i=1; i <= hub_data[dev_addr-1].port_number; i++)
- {
- request.wIndex = i;
- TU_ASSERT( usbh_control_xfer( dev_addr, &request, NULL ) );
- }
-
- //------------- Queue the initial Status endpoint transfer -------------//
- TU_ASSERT( hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true) );
-
- return true;
-}
-
-// is the response of interrupt endpoint polling
-#include "usbh_hcd.h" // FIXME remove
-void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
-{
- (void) xferred_bytes; // TODO can be more than 1 for hub with lots of ports
- (void) ep_addr;
-
- usbh_hub_t * p_hub = &hub_data[dev_addr-1];
-
- if ( event == XFER_RESULT_SUCCESS )
- {
- for (uint8_t port=1; port <= p_hub->port_number; port++)
- {
- // TODO HUB ignore bit0 hub_status_change
- if ( tu_bit_test(p_hub->status_change, port) )
- {
- hcd_event_t event =
- {
- .rhport = _usbh_devices[dev_addr].rhport,
- .event_id = HCD_EVENT_DEVICE_ATTACH
- };
-
- event.attach.hub_addr = dev_addr;
- event.attach.hub_port = port;
-
- hcd_event_handler(&event, true);
- break; // handle one port at a time, next port if any will be handled in the next cycle
- }
- }
- // NOTE: next status transfer is queued by usbh.c after handling this request
- }
- else
- {
- // TODO [HUB] check if hub is still plugged before polling status endpoint since failed usually mean hub unplugged
-// TU_ASSERT ( hub_status_pipe_queue(dev_addr) );
- }
-}
-
-void hub_close(uint8_t dev_addr)
-{
- tu_memclr(&hub_data[dev_addr-1], sizeof(usbh_hub_t));
-// osal_semaphore_reset(hub_enum_sem_hdl);
-}
-
-bool hub_status_pipe_queue(uint8_t dev_addr)
-{
- return hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true);
-}
-
-
-#endif
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (TUSB_OPT_HOST_ENABLED && CFG_TUH_HUB)
+
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "hub.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+typedef struct
+{
+ uint8_t itf_num;
+ uint8_t ep_status;
+ uint8_t port_number;
+ uint8_t status_change; // data from status change interrupt endpoint
+}usbh_hub_t;
+
+CFG_TUSB_MEM_SECTION static usbh_hub_t hub_data[CFG_TUSB_HOST_DEVICE_MAX];
+TU_ATTR_ALIGNED(4) CFG_TUSB_MEM_SECTION static uint8_t hub_enum_buffer[sizeof(descriptor_hub_desc_t)];
+
+//OSAL_SEM_DEF(hub_enum_semaphore);
+//static osal_semaphore_handle_t hub_enum_sem_hdl;
+
+//--------------------------------------------------------------------+
+// HUB
+//--------------------------------------------------------------------+
+bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature)
+{
+ TU_ASSERT(HUB_FEATURE_PORT_CONNECTION_CHANGE <= feature && feature <= HUB_FEATURE_PORT_RESET_CHANGE);
+
+ tusb_control_request_t request = {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
+ .bRequest = HUB_REQUEST_CLEAR_FEATURE,
+ .wValue = feature,
+ .wIndex = hub_port,
+ .wLength = 0
+ };
+
+ //------------- Clear Port Feature request -------------//
+ TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
+
+ //------------- Get Port Status to check if feature is cleared -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
+ .bRequest = HUB_REQUEST_GET_STATUS,
+ .wValue = 0,
+ .wIndex = hub_port,
+ .wLength = 4
+ };
+
+ TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
+
+ //------------- Check if feature is cleared -------------//
+ hub_port_status_response_t * p_port_status;
+ p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
+
+ TU_ASSERT( !tu_bit_test(p_port_status->status_change.value, feature-16) );
+
+ return true;
+}
+
+bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port)
+{
+ enum { RESET_DELAY = 200 }; // USB specs say only 50ms but many devices require much longer
+
+ //------------- Set Port Reset -------------//
+ tusb_control_request_t request = {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
+ .bRequest = HUB_REQUEST_SET_FEATURE,
+ .wValue = HUB_FEATURE_PORT_RESET,
+ .wIndex = hub_port,
+ .wLength = 0
+ };
+
+ TU_ASSERT( usbh_control_xfer( hub_addr, &request, NULL ) );
+
+ osal_task_delay(RESET_DELAY); // TODO Hub wait for Status Endpoint on Reset Change
+
+ //------------- Get Port Status to check if port is enabled, powered and reset_change -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
+ .bRequest = HUB_REQUEST_GET_STATUS,
+ .wValue = 0,
+ .wIndex = hub_port,
+ .wLength = 4
+ };
+
+ TU_ASSERT( usbh_control_xfer( hub_addr, &request, hub_enum_buffer ) );
+
+ hub_port_status_response_t * p_port_status;
+ p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
+
+ TU_ASSERT ( p_port_status->status_change.reset && p_port_status->status_current.connect_status &&
+ p_port_status->status_current.port_power && p_port_status->status_current.port_enable);
+
+ return true;
+}
+
+// can only get the speed RIGHT AFTER hub_port_reset_subtask call
+tusb_speed_t hub_port_get_speed(void)
+{
+ hub_port_status_response_t * p_port_status = (hub_port_status_response_t *) hub_enum_buffer;
+ return (p_port_status->status_current.high_speed_device_attached) ? TUSB_SPEED_HIGH :
+ (p_port_status->status_current.low_speed_device_attached ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
+}
+
+//--------------------------------------------------------------------+
+// CLASS-USBH API (don't require to verify parameters)
+//--------------------------------------------------------------------+
+void hub_init(void)
+{
+ tu_memclr(hub_data, CFG_TUSB_HOST_DEVICE_MAX*sizeof(usbh_hub_t));
+// hub_enum_sem_hdl = osal_semaphore_create( OSAL_SEM_REF(hub_enum_semaphore) );
+}
+
+bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length)
+{
+ // not support multiple TT yet
+ if ( itf_desc->bInterfaceProtocol > 1 ) return false;
+
+ //------------- Open Interrupt Status Pipe -------------//
+ tusb_desc_endpoint_t const *ep_desc;
+ ep_desc = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc);
+
+ TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType);
+ TU_ASSERT(TUSB_XFER_INTERRUPT == ep_desc->bmAttributes.xfer);
+
+ TU_ASSERT(hcd_edpt_open(rhport, dev_addr, ep_desc));
+
+ hub_data[dev_addr-1].itf_num = itf_desc->bInterfaceNumber;
+ hub_data[dev_addr-1].ep_status = ep_desc->bEndpointAddress;
+
+ (*p_length) = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t);
+
+ //------------- Get Hub Descriptor -------------//
+ tusb_control_request_t request = {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
+ .bRequest = HUB_REQUEST_GET_DESCRIPTOR,
+ .wValue = 0,
+ .wIndex = 0,
+ .wLength = sizeof(descriptor_hub_desc_t)
+ };
+
+ TU_ASSERT( usbh_control_xfer( dev_addr, &request, hub_enum_buffer ) );
+
+ // only care about this field in hub descriptor
+ hub_data[dev_addr-1].port_number = ((descriptor_hub_desc_t*) hub_enum_buffer)->bNbrPorts;
+
+ //------------- Set Port_Power on all ports -------------//
+ // TODO may only power port with attached
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT },
+ .bRequest = HUB_REQUEST_SET_FEATURE,
+ .wValue = HUB_FEATURE_PORT_POWER,
+ .wIndex = 0,
+ .wLength = 0
+ };
+
+ for(uint8_t i=1; i <= hub_data[dev_addr-1].port_number; i++)
+ {
+ request.wIndex = i;
+ TU_ASSERT( usbh_control_xfer( dev_addr, &request, NULL ) );
+ }
+
+ //------------- Queue the initial Status endpoint transfer -------------//
+ TU_ASSERT( hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true) );
+
+ return true;
+}
+
+// is the response of interrupt endpoint polling
+#include "usbh_hcd.h" // FIXME remove
+void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
+{
+ (void) xferred_bytes; // TODO can be more than 1 for hub with lots of ports
+ (void) ep_addr;
+
+ usbh_hub_t * p_hub = &hub_data[dev_addr-1];
+
+ if ( event == XFER_RESULT_SUCCESS )
+ {
+ for (uint8_t port=1; port <= p_hub->port_number; port++)
+ {
+ // TODO HUB ignore bit0 hub_status_change
+ if ( tu_bit_test(p_hub->status_change, port) )
+ {
+ hcd_event_t event =
+ {
+ .rhport = _usbh_devices[dev_addr].rhport,
+ .event_id = HCD_EVENT_DEVICE_ATTACH
+ };
+
+ event.attach.hub_addr = dev_addr;
+ event.attach.hub_port = port;
+
+ hcd_event_handler(&event, true);
+ break; // handle one port at a time, next port if any will be handled in the next cycle
+ }
+ }
+ // NOTE: next status transfer is queued by usbh.c after handling this request
+ }
+ else
+ {
+ // TODO [HUB] check if hub is still plugged before polling status endpoint since failed usually mean hub unplugged
+// TU_ASSERT ( hub_status_pipe_queue(dev_addr) );
+ }
+}
+
+void hub_close(uint8_t dev_addr)
+{
+ tu_memclr(&hub_data[dev_addr-1], sizeof(usbh_hub_t));
+// osal_semaphore_reset(hub_enum_sem_hdl);
+}
+
+bool hub_status_pipe_queue(uint8_t dev_addr)
+{
+ return hcd_pipe_xfer(dev_addr, hub_data[dev_addr-1].ep_status, &hub_data[dev_addr-1].status_change, 1, true);
+}
+
+
+#endif
diff --git a/src/host/hub.h b/src/host/hub.h
index 51d33d4e5..621356315 100644
--- a/src/host/hub.h
+++ b/src/host/hub.h
@@ -1,194 +1,194 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup group_class
- * \defgroup ClassDriver_Hub Hub (Host only)
- * \details Like most PC's OS, Hub support is completely hidden from Application. In fact, application cannot determine whether
- * a device is mounted directly via roothub or via a hub's port. All Hub-related procedures are performed and managed
- * by tinyusb stack. Unless you are trying to develop the stack itself, there are nothing else can be used by Application.
- * \note Due to my laziness, only 1-level of Hub is supported. In other way, the stack cannot mount a hub via another hub.
- * @{
- */
-
-#ifndef _TUSB_HUB_H_
-#define _TUSB_HUB_H_
-
-#include <common/tusb_common.h>
-#include "usbh.h"
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-//D1...D0: Logical Power Switching Mode
-//00: Ganged power switching (all ports’power at
-//once)
-//01: Individual port power switching
-//1X: Reserved. Used only on 1.0 compliant hubs
-//that implement no power switching
-//D2: Identifies a Compound Device
-//0: Hub is not part of a compound device.
-//1: Hub is part of a compound device.
-//D4...D3: Over-current Protection Mode
-//00: Global Over-current Protection. The hub
-//reports over-current as a summation of all
-//ports’current draw, without a breakdown of
-//individual port over-current status.
-//01: Individual Port Over-current Protection. The
-//hub reports over-current on a per-port basis.
-//Each port has an over-current status.
-//1X: No Over-current Protection. This option is
-//allowed only for bus-powered hubs that do not
-//implement over-current protection.
-//
-//D6...D5: TT Think TIme
-//00: TT requires at most 8 FS bit times of inter
-//transaction gap on a full-/low-speed
-//downstream bus.
-//01: TT requires at most 16 FS bit times.
-//10: TT requires at most 24 FS bit times.
-//11: TT requires at most 32 FS bit times.
-//D7: Port Indicators Supported
-//0: Port Indicators are not supported on its
-//downstream facing ports and the
-//PORT_INDICATOR request has no effect.
-//1: Port Indicators are supported on its
-//downstream facing ports and the
-//PORT_INDICATOR request controls the
-//indicators. See Section 11.5.3.
-//D15...D8: Reserved
-
-typedef struct TU_ATTR_PACKED{
- uint8_t bLength ; ///< Size of descriptor
- uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
- uint8_t bNbrPorts;
- uint16_t wHubCharacteristics;
- uint8_t bPwrOn2PwrGood;
- uint8_t bHubContrCurrent;
- uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1)
- uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff
-} descriptor_hub_desc_t;
-
-TU_VERIFY_STATIC( sizeof(descriptor_hub_desc_t) == 9, "size is not correct");
-
-enum {
- HUB_REQUEST_GET_STATUS = 0 ,
- HUB_REQUEST_CLEAR_FEATURE = 1 ,
-
- HUB_REQUEST_SET_FEATURE = 3 ,
-
- HUB_REQUEST_GET_DESCRIPTOR = 6 ,
- HUB_REQUEST_SET_DESCRIPTOR = 7 ,
- HUB_REQUEST_CLEAR_TT_BUFFER = 8 ,
- HUB_REQUEST_RESET_TT = 9 ,
- HUB_REQUEST_GET_TT_STATE = 10 ,
- HUB_REQUEST_STOP_TT = 11
-};
-
-enum {
- HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0,
- HUB_FEATURE_HUB_OVER_CURRENT_CHANGE
-};
-
-enum{
- HUB_FEATURE_PORT_CONNECTION = 0,
- HUB_FEATURE_PORT_ENABLE = 1,
- HUB_FEATURE_PORT_SUSPEND = 2,
- HUB_FEATURE_PORT_OVER_CURRENT = 3,
- HUB_FEATURE_PORT_RESET = 4,
-
- HUB_FEATURE_PORT_POWER = 8,
- HUB_FEATURE_PORT_LOW_SPEED = 9,
-
- HUB_FEATURE_PORT_CONNECTION_CHANGE = 16,
- HUB_FEATURE_PORT_ENABLE_CHANGE = 17,
- HUB_FEATURE_PORT_SUSPEND_CHANGE = 18,
- HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19,
- HUB_FEATURE_PORT_RESET_CHANGE = 20,
- HUB_FEATURE_PORT_TEST = 21,
- HUB_FEATURE_PORT_INDICATOR = 22
-};
-
-// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub)
-typedef struct {
- union{
- struct TU_ATTR_PACKED {
- uint16_t local_power_source : 1;
- uint16_t over_current : 1;
- uint16_t : 14;
- };
-
- uint16_t value;
- } status, status_change;
-} hub_status_response_t;
-
-TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct");
-
-// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num
-typedef struct {
- union {
- struct TU_ATTR_PACKED {
- uint16_t connect_status : 1;
- uint16_t port_enable : 1;
- uint16_t suspend : 1;
- uint16_t over_current : 1;
- uint16_t reset : 1;
-
- uint16_t : 3;
- uint16_t port_power : 1;
- uint16_t low_speed_device_attached : 1;
- uint16_t high_speed_device_attached : 1;
- uint16_t port_test_mode : 1;
- uint16_t port_indicator_control : 1;
- uint16_t : 0;
- };
-
- uint16_t value;
- } status_current, status_change;
-} hub_port_status_response_t;
-
-TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct");
-
-bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port);
-bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature);
-tusb_speed_t hub_port_get_speed(void);
-bool hub_status_pipe_queue(uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// Internal Class Driver API
-//--------------------------------------------------------------------+
-void hub_init(void);
-bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length);
-void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
-void hub_close(uint8_t dev_addr);
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_HUB_H_ */
-
-/** @} */
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+/** \ingroup group_class
+ * \defgroup ClassDriver_Hub Hub (Host only)
+ * \details Like most PC's OS, Hub support is completely hidden from Application. In fact, application cannot determine whether
+ * a device is mounted directly via roothub or via a hub's port. All Hub-related procedures are performed and managed
+ * by tinyusb stack. Unless you are trying to develop the stack itself, there are nothing else can be used by Application.
+ * \note Due to my laziness, only 1-level of Hub is supported. In other way, the stack cannot mount a hub via another hub.
+ * @{
+ */
+
+#ifndef _TUSB_HUB_H_
+#define _TUSB_HUB_H_
+
+#include <common/tusb_common.h>
+#include "usbh.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//D1...D0: Logical Power Switching Mode
+//00: Ganged power switching (all ports’power at
+//once)
+//01: Individual port power switching
+//1X: Reserved. Used only on 1.0 compliant hubs
+//that implement no power switching
+//D2: Identifies a Compound Device
+//0: Hub is not part of a compound device.
+//1: Hub is part of a compound device.
+//D4...D3: Over-current Protection Mode
+//00: Global Over-current Protection. The hub
+//reports over-current as a summation of all
+//ports’current draw, without a breakdown of
+//individual port over-current status.
+//01: Individual Port Over-current Protection. The
+//hub reports over-current on a per-port basis.
+//Each port has an over-current status.
+//1X: No Over-current Protection. This option is
+//allowed only for bus-powered hubs that do not
+//implement over-current protection.
+//
+//D6...D5: TT Think TIme
+//00: TT requires at most 8 FS bit times of inter
+//transaction gap on a full-/low-speed
+//downstream bus.
+//01: TT requires at most 16 FS bit times.
+//10: TT requires at most 24 FS bit times.
+//11: TT requires at most 32 FS bit times.
+//D7: Port Indicators Supported
+//0: Port Indicators are not supported on its
+//downstream facing ports and the
+//PORT_INDICATOR request has no effect.
+//1: Port Indicators are supported on its
+//downstream facing ports and the
+//PORT_INDICATOR request controls the
+//indicators. See Section 11.5.3.
+//D15...D8: Reserved
+
+typedef struct TU_ATTR_PACKED{
+ uint8_t bLength ; ///< Size of descriptor
+ uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
+ uint8_t bNbrPorts;
+ uint16_t wHubCharacteristics;
+ uint8_t bPwrOn2PwrGood;
+ uint8_t bHubContrCurrent;
+ uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1)
+ uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff
+} descriptor_hub_desc_t;
+
+TU_VERIFY_STATIC( sizeof(descriptor_hub_desc_t) == 9, "size is not correct");
+
+enum {
+ HUB_REQUEST_GET_STATUS = 0 ,
+ HUB_REQUEST_CLEAR_FEATURE = 1 ,
+
+ HUB_REQUEST_SET_FEATURE = 3 ,
+
+ HUB_REQUEST_GET_DESCRIPTOR = 6 ,
+ HUB_REQUEST_SET_DESCRIPTOR = 7 ,
+ HUB_REQUEST_CLEAR_TT_BUFFER = 8 ,
+ HUB_REQUEST_RESET_TT = 9 ,
+ HUB_REQUEST_GET_TT_STATE = 10 ,
+ HUB_REQUEST_STOP_TT = 11
+};
+
+enum {
+ HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0,
+ HUB_FEATURE_HUB_OVER_CURRENT_CHANGE
+};
+
+enum{
+ HUB_FEATURE_PORT_CONNECTION = 0,
+ HUB_FEATURE_PORT_ENABLE = 1,
+ HUB_FEATURE_PORT_SUSPEND = 2,
+ HUB_FEATURE_PORT_OVER_CURRENT = 3,
+ HUB_FEATURE_PORT_RESET = 4,
+
+ HUB_FEATURE_PORT_POWER = 8,
+ HUB_FEATURE_PORT_LOW_SPEED = 9,
+
+ HUB_FEATURE_PORT_CONNECTION_CHANGE = 16,
+ HUB_FEATURE_PORT_ENABLE_CHANGE = 17,
+ HUB_FEATURE_PORT_SUSPEND_CHANGE = 18,
+ HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19,
+ HUB_FEATURE_PORT_RESET_CHANGE = 20,
+ HUB_FEATURE_PORT_TEST = 21,
+ HUB_FEATURE_PORT_INDICATOR = 22
+};
+
+// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub)
+typedef struct {
+ union{
+ struct TU_ATTR_PACKED {
+ uint16_t local_power_source : 1;
+ uint16_t over_current : 1;
+ uint16_t : 14;
+ };
+
+ uint16_t value;
+ } status, status_change;
+} hub_status_response_t;
+
+TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct");
+
+// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num
+typedef struct {
+ union {
+ struct TU_ATTR_PACKED {
+ uint16_t connect_status : 1;
+ uint16_t port_enable : 1;
+ uint16_t suspend : 1;
+ uint16_t over_current : 1;
+ uint16_t reset : 1;
+
+ uint16_t : 3;
+ uint16_t port_power : 1;
+ uint16_t low_speed_device_attached : 1;
+ uint16_t high_speed_device_attached : 1;
+ uint16_t port_test_mode : 1;
+ uint16_t port_indicator_control : 1;
+ uint16_t : 0;
+ };
+
+ uint16_t value;
+ } status_current, status_change;
+} hub_port_status_response_t;
+
+TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct");
+
+bool hub_port_reset_subtask(uint8_t hub_addr, uint8_t hub_port);
+bool hub_port_clear_feature_subtask(uint8_t hub_addr, uint8_t hub_port, uint8_t feature);
+tusb_speed_t hub_port_get_speed(void);
+bool hub_status_pipe_queue(uint8_t dev_addr);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void hub_init(void);
+bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t *p_length);
+void hub_isr(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes);
+void hub_close(uint8_t dev_addr);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_HUB_H_ */
+
+/** @} */
diff --git a/src/host/ohci/ohci.c b/src/host/ohci/ohci.c
index ea553baa2..547622edf 100644
--- a/src/host/ohci/ohci.c
+++ b/src/host/ohci/ohci.c
@@ -1,651 +1,651 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include <common/tusb_common.h>
-
-#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX)
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "osal/osal.h"
-
-#include "../hcd.h"
-#include "../usbh_hcd.h"
-#include "ohci.h"
-
-// TODO remove
-#include "chip.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-#define OHCI_REG ((ohci_registers_t *) LPC_USB_BASE)
-
-enum {
- OHCI_CONTROL_FUNCSTATE_RESET = 0,
- OHCI_CONTROL_FUNCSTATE_RESUME,
- OHCI_CONTROL_FUNCSTATE_OPERATIONAL,
- OHCI_CONTROL_FUNCSTATE_SUSPEND
-};
-
-enum {
- OHCI_CONTROL_CONTROL_BULK_RATIO = 3, ///< This specifies the service ratio between Control and Bulk EDs. 0 = 1:1, 3 = 4:1
- OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK = TU_BIT(2),
- OHCI_CONTROL_LIST_ISOCHRONOUS_ENABLE_MASK = TU_BIT(3),
- OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK = TU_BIT(4),
- OHCI_CONTROL_LIST_BULK_ENABLE_MASK = TU_BIT(5),
-};
-
-enum {
- OHCI_FMINTERVAL_FI = 0x2EDF, // 7.3.1 nominal (reset) value
- OHCI_FMINTERVAL_FSMPS = (6*(OHCI_FMINTERVAL_FI-210)) / 7, // 5.4 calculated based on maximum overhead + bit stuffing
-};
-
-enum {
- OHCI_PERIODIC_START = 0x3E67
-};
-
-enum {
- OHCI_INT_SCHEDULING_OVERUN_MASK = TU_BIT(0),
- OHCI_INT_WRITEBACK_DONEHEAD_MASK = TU_BIT(1),
- OHCI_INT_SOF_MASK = TU_BIT(2),
- OHCI_INT_RESUME_DETECTED_MASK = TU_BIT(3),
- OHCI_INT_UNRECOVERABLE_ERROR_MASK = TU_BIT(4),
- OHCI_INT_FRAME_OVERFLOW_MASK = TU_BIT(5),
- OHCI_INT_RHPORT_STATUS_CHANGE_MASK = TU_BIT(6),
-
- OHCI_INT_OWNERSHIP_CHANGE_MASK = TU_BIT(30),
- OHCI_INT_MASTER_ENABLE_MASK = TU_BIT(31),
-};
-
-enum {
- OHCI_RHPORT_CURRENT_CONNECT_STATUS_MASK = TU_BIT(0),
- OHCI_RHPORT_PORT_ENABLE_STATUS_MASK = TU_BIT(1),
- OHCI_RHPORT_PORT_SUSPEND_STATUS_MASK = TU_BIT(2),
- OHCI_RHPORT_PORT_OVER_CURRENT_INDICATOR_MASK = TU_BIT(3),
- OHCI_RHPORT_PORT_RESET_STATUS_MASK = TU_BIT(4), ///< write '1' to reset port
-
- OHCI_RHPORT_PORT_POWER_STATUS_MASK = TU_BIT(8),
- OHCI_RHPORT_LOW_SPEED_DEVICE_ATTACHED_MASK = TU_BIT(9),
-
- OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK = TU_BIT(16),
- OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK = TU_BIT(17),
- OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK = TU_BIT(18),
- OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK = TU_BIT(19),
- OHCI_RHPORT_PORT_RESET_CHANGE_MASK = TU_BIT(20),
-
- OHCI_RHPORT_ALL_CHANGE_MASK = OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK | OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK |
- OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK | OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK | OHCI_RHPORT_PORT_RESET_CHANGE_MASK
-};
-
-enum {
- OHCI_CCODE_NO_ERROR = 0,
- OHCI_CCODE_CRC = 1,
- OHCI_CCODE_BIT_STUFFING = 2,
- OHCI_CCODE_DATA_TOGGLE_MISMATCH = 3,
- OHCI_CCODE_STALL = 4,
- OHCI_CCODE_DEVICE_NOT_RESPONDING = 5,
- OHCI_CCODE_PID_CHECK_FAILURE = 6,
- OHCI_CCODE_UNEXPECTED_PID = 7,
- OHCI_CCODE_DATA_OVERRUN = 8,
- OHCI_CCODE_DATA_UNDERRUN = 9,
- OHCI_CCODE_BUFFER_OVERRUN = 12,
- OHCI_CCODE_BUFFER_UNDERRUN = 13,
- OHCI_CCODE_NOT_ACCESSED = 14,
-};
-
-enum {
- OHCI_INT_ON_COMPLETE_YES = 0,
- OHCI_INT_ON_COMPLETE_NO = TU_BIN8(111)
-};
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(256) static ohci_data_t ohci_data;
-
-static ohci_ed_t * const p_ed_head[] =
-{
- [TUSB_XFER_CONTROL] = &ohci_data.control[0].ed,
- [TUSB_XFER_BULK ] = &ohci_data.bulk_head_ed,
- [TUSB_XFER_INTERRUPT] = &ohci_data.period_head_ed,
- [TUSB_XFER_ISOCHRONOUS] = NULL // TODO Isochronous
-};
-
-static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed);
-static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr);
-
-//--------------------------------------------------------------------+
-// USBH-HCD API
-//--------------------------------------------------------------------+
-// Initialization according to 5.1.1.4
-bool hcd_init(void)
-{
- //------------- Data Structure init -------------//
- tu_memclr(&ohci_data, sizeof(ohci_data_t));
- for(uint8_t i=0; i<32; i++)
- { // assign all interrupt pointes to period head ed
- ohci_data.hcca.interrupt_table[i] = (uint32_t) &ohci_data.period_head_ed;
- }
-
- ohci_data.control[0].ed.skip = 1;
- ohci_data.bulk_head_ed.skip = 1;
- ohci_data.period_head_ed.skip = 1;
-
- // reset controller
- OHCI_REG->command_status_bit.controller_reset = 1;
- while( OHCI_REG->command_status_bit.controller_reset ) {} // should not take longer than 10 us
-
- //------------- init ohci registers -------------//
- OHCI_REG->control_head_ed = (uint32_t) &ohci_data.control[0].ed;
- OHCI_REG->bulk_head_ed = (uint32_t) &ohci_data.bulk_head_ed;
- OHCI_REG->hcca = (uint32_t) &ohci_data.hcca;
-
- OHCI_REG->interrupt_disable = OHCI_REG->interrupt_enable; // disable all interrupts
- OHCI_REG->interrupt_status = OHCI_REG->interrupt_status; // clear current set bits
- OHCI_REG->interrupt_enable = OHCI_INT_WRITEBACK_DONEHEAD_MASK | OHCI_INT_RESUME_DETECTED_MASK |
- OHCI_INT_UNRECOVERABLE_ERROR_MASK | /*OHCI_INT_FRAME_OVERFLOW_MASK |*/ OHCI_INT_RHPORT_STATUS_CHANGE_MASK |
- OHCI_INT_MASTER_ENABLE_MASK;
-
- OHCI_REG->control |= OHCI_CONTROL_CONTROL_BULK_RATIO | OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK |
- OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous
-
- OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI;
- OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval
-
- OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power)
- OHCI_REG->rh_status_bit.local_power_status_change = 1; // set global power for ports
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// PORT API
-//--------------------------------------------------------------------+
-void hcd_port_reset(uint8_t hostid)
-{
- (void) hostid;
- OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
-}
-
-bool hcd_port_connect_status(uint8_t hostid)
-{
- (void) hostid;
- return OHCI_REG->rhport_status_bit[0].current_connect_status;
-}
-
-tusb_speed_t hcd_port_speed_get(uint8_t hostid)
-{
- (void) hostid;
- return OHCI_REG->rhport_status_bit[0].low_speed_device_attached ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
-}
-
-// endpoints are tied to an address, which only reclaim after a long delay when enumerating
-// thus there is no need to make sure ED is not in HC's cahed as it will not for sure
-void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
-{
- // TODO OHCI
- (void) rhport;
-
- // addr0 serves as static head --> only set skip bit
- if ( dev_addr == 0 )
- {
- ohci_data.control[0].ed.skip = 1;
- }else
- {
- // remove control
- ed_list_remove_by_addr( p_ed_head[TUSB_XFER_CONTROL], dev_addr);
-
- // remove bulk
- ed_list_remove_by_addr(p_ed_head[TUSB_XFER_BULK], dev_addr);
-
- // remove interrupt
- ed_list_remove_by_addr(p_ed_head[TUSB_XFER_INTERRUPT], dev_addr);
-
- // TODO remove ISO
- }
-}
-
-//--------------------------------------------------------------------+
-// Controller API
-//--------------------------------------------------------------------+
-
-//--------------------------------------------------------------------+
-// CONTROL PIPE API
-//--------------------------------------------------------------------+
-static inline tusb_xfer_type_t ed_get_xfer_type(ohci_ed_t const * const p_ed)
-{
- return (p_ed->ep_number == 0 ) ? TUSB_XFER_CONTROL :
- (p_ed->is_iso ) ? TUSB_XFER_ISOCHRONOUS :
- (p_ed->is_interrupt_xfer ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK;
-}
-
-static void ed_init(ohci_ed_t *p_ed, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval)
-{
- (void) interval;
-
- // address 0 is used as async head, which always on the list --> cannot be cleared
- if (dev_addr != 0)
- {
- tu_memclr(p_ed, sizeof(ohci_ed_t));
- }
-
- p_ed->dev_addr = dev_addr;
- p_ed->ep_number = endpoint_addr & 0x0F;
- p_ed->pid = (xfer_type == TUSB_XFER_CONTROL) ? OHCI_PID_SETUP : ( (endpoint_addr & TUSB_DIR_IN_MASK) ? OHCI_PID_IN : OHCI_PID_OUT );
- p_ed->speed = _usbh_devices[dev_addr].speed;
- p_ed->is_iso = (xfer_type == TUSB_XFER_ISOCHRONOUS) ? 1 : 0;
- p_ed->max_packet_size = max_packet_size;
-
- p_ed->used = 1;
- p_ed->is_interrupt_xfer = (xfer_type == TUSB_XFER_INTERRUPT ? 1 : 0);
-}
-
-static void gtd_init(ohci_gtd_t* p_td, void* data_ptr, uint16_t total_bytes)
-{
- tu_memclr(p_td, sizeof(ohci_gtd_t));
-
- p_td->used = 1;
- p_td->expected_bytes = total_bytes;
-
- p_td->buffer_rounding = 1; // less than queued length is not a error
- p_td->delay_interrupt = OHCI_INT_ON_COMPLETE_NO;
- p_td->condition_code = OHCI_CCODE_NOT_ACCESSED;
-
- p_td->current_buffer_pointer = data_ptr;
- p_td->buffer_end = total_bytes ? (((uint8_t*) data_ptr) + total_bytes-1) : NULL;
-}
-
-bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
-{
- (void) rhport;
-
- ohci_ed_t* p_ed = &ohci_data.control[dev_addr].ed;
- ohci_gtd_t *p_setup = &ohci_data.control[dev_addr].gtd;
-
- gtd_init(p_setup, (void*) setup_packet, 8);
- p_setup->index = dev_addr;
- p_setup->pid = OHCI_PID_SETUP;
- p_setup->data_toggle = TU_BIN8(10); // DATA0
- p_setup->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- //------------- Attach TDs list to Control Endpoint -------------//
- p_ed->td_head.address = (uint32_t) p_setup;
-
- OHCI_REG->command_status_bit.control_list_filled = 1;
-
- return true;
-}
-
-bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
-{
- (void) rhport;
-
- uint8_t const epnum = tu_edpt_number(ep_addr);
- uint8_t const dir = tu_edpt_dir(ep_addr);
-
- // FIXME control only for now
- if ( epnum == 0 )
- {
- ohci_ed_t* const p_ed = &ohci_data.control[dev_addr].ed;
- ohci_gtd_t *p_data = &ohci_data.control[dev_addr].gtd;
-
- gtd_init(p_data, buffer, buflen);
-
- p_data->index = dev_addr;
- p_data->pid = dir ? OHCI_PID_IN : OHCI_PID_OUT;
- p_data->data_toggle = TU_BIN8(11); // DATA1
- p_data->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- p_ed->td_head.address = (uint32_t) p_data;
-
- OHCI_REG->command_status_bit.control_list_filled = 1;
- }
-
- return true;
-}
-
-//--------------------------------------------------------------------+
-// BULK/INT/ISO PIPE API
-//--------------------------------------------------------------------+
-static inline ohci_ed_t * ed_from_addr(uint8_t dev_addr, uint8_t ep_addr)
-{
- if ( tu_edpt_number(ep_addr) == 0 ) return &ohci_data.control[dev_addr].ed;
-
- ohci_ed_t* ed_pool = ohci_data.ed_pool;
-
- for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
- {
- if ( (ed_pool[i].dev_addr == dev_addr) &&
- ep_addr == tu_edpt_addr(ed_pool[i].ep_number, ed_pool[i].pid == OHCI_PID_IN) )
- {
- return &ed_pool[i];
- }
- }
-
- return NULL;
-}
-
-static inline ohci_ed_t * ed_find_free(void)
-{
- ohci_ed_t* ed_pool = ohci_data.ed_pool;
-
- for(uint8_t i = 0; i < HCD_MAX_ENDPOINT; i++)
- {
- if ( !ed_pool[i].used ) return &ed_pool[i];
- }
-
- return NULL;
-}
-
-static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed)
-{
- p_ed->next = p_pre->next;
- p_pre->next = (uint32_t) p_ed;
-}
-
-static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr)
-{
- ohci_ed_t* p_prev = p_head;
-
- while( p_prev->next )
- {
- ohci_ed_t* ed = (ohci_ed_t*) p_prev->next;
-
- if (ed->dev_addr == dev_addr)
- {
- // unlink ed
- p_prev->next = ed->next;
-
- // point the removed ED's next pointer to list head to make sure HC can always safely move away from this ED
- ed->next = (uint32_t) p_head;
- ed->used = 0;
- }
-
- // check next valid since we could remove it
- if (p_prev->next) p_prev = (ohci_ed_t*) p_prev->next;
- }
-}
-
-bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
-{
- (void) rhport;
-
- // TODO iso support
- TU_ASSERT(ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
-
- //------------- Prepare Queue Head -------------//
- ohci_ed_t * p_ed;
-
- if ( ep_desc->bEndpointAddress == 0 )
- {
- p_ed = &ohci_data.control[dev_addr].ed;
- }else
- {
- p_ed = ed_find_free();
- }
- TU_ASSERT(p_ed);
-
- ed_init( p_ed, dev_addr, ep_desc->wMaxPacketSize.size, ep_desc->bEndpointAddress,
- ep_desc->bmAttributes.xfer, ep_desc->bInterval );
-
- // control of dev0 is used as static async head
- if ( dev_addr == 0 )
- {
- p_ed->skip = 0; // only need to clear skip bit
- return true;
- }
-
- ed_list_insert( p_ed_head[ep_desc->bmAttributes.xfer], p_ed );
-
- return true;
-}
-
-static ohci_gtd_t * gtd_find_free(void)
-{
- for(uint8_t i=0; i < HCD_MAX_XFER; i++)
- {
- if ( !ohci_data.gtd_pool[i].used ) return &ohci_data.gtd_pool[i];
- }
-
- return NULL;
-}
-
-static void td_insert_to_ed(ohci_ed_t* p_ed, ohci_gtd_t * p_gtd)
-{
- // tail is always NULL
- if ( tu_align16(p_ed->td_head.address) == 0 )
- { // TD queue is empty --> head = TD
- p_ed->td_head.address |= (uint32_t) p_gtd;
- }
- else
- { // TODO currently only support queue up to 2 TD each endpoint at a time
- ((ohci_gtd_t*) tu_align16(p_ed->td_head.address))->next = (uint32_t) p_gtd;
- }
-}
-
-static bool pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- ohci_ed_t* const p_ed = ed_from_addr(dev_addr, ep_addr);
-
- // not support ISO yet
- TU_VERIFY ( !p_ed->is_iso );
-
- ohci_gtd_t * const p_gtd = gtd_find_free();
- TU_ASSERT(p_gtd); // not enough gtd
-
- gtd_init(p_gtd, buffer, total_bytes);
- p_gtd->index = p_ed-ohci_data.ed_pool;
-
- if ( int_on_complete ) p_gtd->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
-
- td_insert_to_ed(p_ed, p_gtd);
-
- return true;
-}
-
-bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
-{
- return pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, false);
-}
-
-bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
-{
- (void) int_on_complete;
- TU_ASSERT( pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, true) );
-
- tusb_xfer_type_t xfer_type = ed_get_xfer_type( ed_from_addr(dev_addr, ep_addr) );
-
- if (TUSB_XFER_BULK == xfer_type) OHCI_REG->command_status_bit.bulk_list_filled = 1;
-
- return true;
-}
-
-bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
- return tu_align16(p_ed->td_head.address) != tu_align16(p_ed->td_tail);
-}
-
-bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
- return p_ed->td_head.halted && p_ed->is_stalled;
-}
-
-bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
-{
- ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr);
-
- p_ed->is_stalled = 0;
- p_ed->td_tail &= 0x0Ful; // set tail pointer back to NULL
-
- p_ed->td_head.toggle = 0; // reset data toggle
- p_ed->td_head.halted = 0;
-
- if ( TUSB_XFER_BULK == ed_get_xfer_type(p_ed) ) OHCI_REG->command_status_bit.bulk_list_filled = 1;
-
- return true;
-}
-
-
-//--------------------------------------------------------------------+
-// OHCI Interrupt Handler
-//--------------------------------------------------------------------+
-static ohci_td_item_t* list_reverse(ohci_td_item_t* td_head)
-{
- ohci_td_item_t* td_reverse_head = NULL;
-
- while(td_head != NULL)
- {
- uint32_t next = td_head->next;
-
- // make current's item become reverse's first item
- td_head->next = (uint32_t) td_reverse_head;
- td_reverse_head = td_head;
-
- td_head = (ohci_td_item_t*) next; // advance to next item
- }
-
- return td_reverse_head;
-}
-
-static inline bool gtd_is_control(ohci_gtd_t const * const p_qtd)
-{
- return ((uint32_t) p_qtd) < ((uint32_t) ohci_data.gtd_pool); // check ohci_data_t for memory layout
-}
-
-static inline ohci_ed_t* gtd_get_ed(ohci_gtd_t const * const p_qtd)
-{
- if ( gtd_is_control(p_qtd) )
- {
- return &ohci_data.control[p_qtd->index].ed;
- }else
- {
- return &ohci_data.ed_pool[p_qtd->index];
- }
-}
-
-static inline uint32_t gtd_xfer_byte_left(uint32_t buffer_end, uint32_t current_buffer)
-{ // 5.2.9 OHCI sample code
- return (tu_align4k(buffer_end ^ current_buffer) ? 0x1000 : 0) +
- tu_offset4k(buffer_end) - tu_offset4k(current_buffer) + 1;
-}
-
-static void done_queue_isr(uint8_t hostid)
-{
- (void) hostid;
-
- // done head is written in reversed order of completion --> need to reverse the done queue first
- ohci_td_item_t* td_head = list_reverse ( (ohci_td_item_t*) tu_align16(ohci_data.hcca.done_head) );
-
- while( td_head != NULL )
- {
- // TODO check if td_head is iso td
- //------------- Non ISO transfer -------------//
- ohci_gtd_t * const p_qtd = (ohci_gtd_t *) td_head;
- xfer_result_t const event = (p_qtd->condition_code == OHCI_CCODE_NO_ERROR) ? XFER_RESULT_SUCCESS :
- (p_qtd->condition_code == OHCI_CCODE_STALL) ? XFER_RESULT_STALLED : XFER_RESULT_FAILED;
-
- p_qtd->used = 0; // free TD
- if ( (p_qtd->delay_interrupt == OHCI_INT_ON_COMPLETE_YES) || (event != XFER_RESULT_SUCCESS) )
- {
- ohci_ed_t * const p_ed = gtd_get_ed(p_qtd);
-
- uint32_t const xferred_bytes = p_qtd->expected_bytes - gtd_xfer_byte_left((uint32_t) p_qtd->buffer_end, (uint32_t) p_qtd->current_buffer_pointer);
-
- // NOTE Assuming the current list is BULK and there is no other EDs in the list has queued TDs.
- // When there is a error resulting this ED is halted, and this EP still has other queued TD
- // --> the Bulk list only has this halted EP queueing TDs (remaining)
- // --> Bulk list will be considered as not empty by HC !!! while there is no attempt transaction on this list
- // --> HC will not process Control list (due to service ratio when Bulk list not empty)
- // To walk-around this, the halted ED will have TailP = HeadP (empty list condition), when clearing halt
- // the TailP must be set back to NULL for processing remaining TDs
- if ((event != XFER_RESULT_SUCCESS))
- {
- p_ed->td_tail &= 0x0Ful;
- p_ed->td_tail |= tu_align16(p_ed->td_head.address); // mark halted EP as empty queue
- if ( event == XFER_RESULT_STALLED ) p_ed->is_stalled = 1;
- }
-
- hcd_event_xfer_complete(p_ed->dev_addr,
- tu_edpt_addr(p_ed->ep_number, p_ed->pid == OHCI_PID_IN),
- event, xferred_bytes);
- }
-
- td_head = (ohci_td_item_t*) td_head->next;
- }
-}
-
-void hal_hcd_isr(uint8_t hostid)
-{
- uint32_t const int_en = OHCI_REG->interrupt_enable;
- uint32_t const int_status = OHCI_REG->interrupt_status & int_en;
-
- if (int_status == 0) return;
-
- //------------- RootHub status -------------//
- if ( int_status & OHCI_INT_RHPORT_STATUS_CHANGE_MASK )
- {
- uint32_t const rhport_status = OHCI_REG->rhport_status[0] & OHCI_RHPORT_ALL_CHANGE_MASK;
-
- // TODO dual port is not yet supported
- if ( rhport_status & OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK )
- {
- // TODO check if remote wake-up
- if ( OHCI_REG->rhport_status_bit[0].current_connect_status )
- {
- // TODO reset port immediately, without this controller will got 2-3 (debouncing connection status change)
- OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
- hcd_event_device_attach(0);
- }else
- {
- hcd_event_device_remove(0);
- }
- }
-
- if ( rhport_status & OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK)
- {
-
- }
-
- OHCI_REG->rhport_status[0] = rhport_status; // acknowledge all interrupt
- }
-
- //------------- Transfer Complete -------------//
- if ( int_status & OHCI_INT_WRITEBACK_DONEHEAD_MASK)
- {
- done_queue_isr(hostid);
- }
-
- OHCI_REG->interrupt_status = int_status; // Acknowledge handled interrupt
-}
-//--------------------------------------------------------------------+
-// HELPER
-//--------------------------------------------------------------------+
-
-
-#endif
-
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include <common/tusb_common.h>
+
+#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX)
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "osal/osal.h"
+
+#include "../hcd.h"
+#include "../usbh_hcd.h"
+#include "ohci.h"
+
+// TODO remove
+#include "chip.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+#define OHCI_REG ((ohci_registers_t *) LPC_USB_BASE)
+
+enum {
+ OHCI_CONTROL_FUNCSTATE_RESET = 0,
+ OHCI_CONTROL_FUNCSTATE_RESUME,
+ OHCI_CONTROL_FUNCSTATE_OPERATIONAL,
+ OHCI_CONTROL_FUNCSTATE_SUSPEND
+};
+
+enum {
+ OHCI_CONTROL_CONTROL_BULK_RATIO = 3, ///< This specifies the service ratio between Control and Bulk EDs. 0 = 1:1, 3 = 4:1
+ OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK = TU_BIT(2),
+ OHCI_CONTROL_LIST_ISOCHRONOUS_ENABLE_MASK = TU_BIT(3),
+ OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK = TU_BIT(4),
+ OHCI_CONTROL_LIST_BULK_ENABLE_MASK = TU_BIT(5),
+};
+
+enum {
+ OHCI_FMINTERVAL_FI = 0x2EDF, // 7.3.1 nominal (reset) value
+ OHCI_FMINTERVAL_FSMPS = (6*(OHCI_FMINTERVAL_FI-210)) / 7, // 5.4 calculated based on maximum overhead + bit stuffing
+};
+
+enum {
+ OHCI_PERIODIC_START = 0x3E67
+};
+
+enum {
+ OHCI_INT_SCHEDULING_OVERUN_MASK = TU_BIT(0),
+ OHCI_INT_WRITEBACK_DONEHEAD_MASK = TU_BIT(1),
+ OHCI_INT_SOF_MASK = TU_BIT(2),
+ OHCI_INT_RESUME_DETECTED_MASK = TU_BIT(3),
+ OHCI_INT_UNRECOVERABLE_ERROR_MASK = TU_BIT(4),
+ OHCI_INT_FRAME_OVERFLOW_MASK = TU_BIT(5),
+ OHCI_INT_RHPORT_STATUS_CHANGE_MASK = TU_BIT(6),
+
+ OHCI_INT_OWNERSHIP_CHANGE_MASK = TU_BIT(30),
+ OHCI_INT_MASTER_ENABLE_MASK = TU_BIT(31),
+};
+
+enum {
+ OHCI_RHPORT_CURRENT_CONNECT_STATUS_MASK = TU_BIT(0),
+ OHCI_RHPORT_PORT_ENABLE_STATUS_MASK = TU_BIT(1),
+ OHCI_RHPORT_PORT_SUSPEND_STATUS_MASK = TU_BIT(2),
+ OHCI_RHPORT_PORT_OVER_CURRENT_INDICATOR_MASK = TU_BIT(3),
+ OHCI_RHPORT_PORT_RESET_STATUS_MASK = TU_BIT(4), ///< write '1' to reset port
+
+ OHCI_RHPORT_PORT_POWER_STATUS_MASK = TU_BIT(8),
+ OHCI_RHPORT_LOW_SPEED_DEVICE_ATTACHED_MASK = TU_BIT(9),
+
+ OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK = TU_BIT(16),
+ OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK = TU_BIT(17),
+ OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK = TU_BIT(18),
+ OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK = TU_BIT(19),
+ OHCI_RHPORT_PORT_RESET_CHANGE_MASK = TU_BIT(20),
+
+ OHCI_RHPORT_ALL_CHANGE_MASK = OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK | OHCI_RHPORT_PORT_ENABLE_CHANGE_MASK |
+ OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK | OHCI_RHPORT_OVER_CURRENT_CHANGE_MASK | OHCI_RHPORT_PORT_RESET_CHANGE_MASK
+};
+
+enum {
+ OHCI_CCODE_NO_ERROR = 0,
+ OHCI_CCODE_CRC = 1,
+ OHCI_CCODE_BIT_STUFFING = 2,
+ OHCI_CCODE_DATA_TOGGLE_MISMATCH = 3,
+ OHCI_CCODE_STALL = 4,
+ OHCI_CCODE_DEVICE_NOT_RESPONDING = 5,
+ OHCI_CCODE_PID_CHECK_FAILURE = 6,
+ OHCI_CCODE_UNEXPECTED_PID = 7,
+ OHCI_CCODE_DATA_OVERRUN = 8,
+ OHCI_CCODE_DATA_UNDERRUN = 9,
+ OHCI_CCODE_BUFFER_OVERRUN = 12,
+ OHCI_CCODE_BUFFER_UNDERRUN = 13,
+ OHCI_CCODE_NOT_ACCESSED = 14,
+};
+
+enum {
+ OHCI_INT_ON_COMPLETE_YES = 0,
+ OHCI_INT_ON_COMPLETE_NO = TU_BIN8(111)
+};
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(256) static ohci_data_t ohci_data;
+
+static ohci_ed_t * const p_ed_head[] =
+{
+ [TUSB_XFER_CONTROL] = &ohci_data.control[0].ed,
+ [TUSB_XFER_BULK ] = &ohci_data.bulk_head_ed,
+ [TUSB_XFER_INTERRUPT] = &ohci_data.period_head_ed,
+ [TUSB_XFER_ISOCHRONOUS] = NULL // TODO Isochronous
+};
+
+static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed);
+static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr);
+
+//--------------------------------------------------------------------+
+// USBH-HCD API
+//--------------------------------------------------------------------+
+// Initialization according to 5.1.1.4
+bool hcd_init(void)
+{
+ //------------- Data Structure init -------------//
+ tu_memclr(&ohci_data, sizeof(ohci_data_t));
+ for(uint8_t i=0; i<32; i++)
+ { // assign all interrupt pointes to period head ed
+ ohci_data.hcca.interrupt_table[i] = (uint32_t) &ohci_data.period_head_ed;
+ }
+
+ ohci_data.control[0].ed.skip = 1;
+ ohci_data.bulk_head_ed.skip = 1;
+ ohci_data.period_head_ed.skip = 1;
+
+ // reset controller
+ OHCI_REG->command_status_bit.controller_reset = 1;
+ while( OHCI_REG->command_status_bit.controller_reset ) {} // should not take longer than 10 us
+
+ //------------- init ohci registers -------------//
+ OHCI_REG->control_head_ed = (uint32_t) &ohci_data.control[0].ed;
+ OHCI_REG->bulk_head_ed = (uint32_t) &ohci_data.bulk_head_ed;
+ OHCI_REG->hcca = (uint32_t) &ohci_data.hcca;
+
+ OHCI_REG->interrupt_disable = OHCI_REG->interrupt_enable; // disable all interrupts
+ OHCI_REG->interrupt_status = OHCI_REG->interrupt_status; // clear current set bits
+ OHCI_REG->interrupt_enable = OHCI_INT_WRITEBACK_DONEHEAD_MASK | OHCI_INT_RESUME_DETECTED_MASK |
+ OHCI_INT_UNRECOVERABLE_ERROR_MASK | /*OHCI_INT_FRAME_OVERFLOW_MASK |*/ OHCI_INT_RHPORT_STATUS_CHANGE_MASK |
+ OHCI_INT_MASTER_ENABLE_MASK;
+
+ OHCI_REG->control |= OHCI_CONTROL_CONTROL_BULK_RATIO | OHCI_CONTROL_LIST_CONTROL_ENABLE_MASK |
+ OHCI_CONTROL_LIST_BULK_ENABLE_MASK | OHCI_CONTROL_LIST_PERIODIC_ENABLE_MASK; // TODO Isochronous
+
+ OHCI_REG->frame_interval = (OHCI_FMINTERVAL_FSMPS << 16) | OHCI_FMINTERVAL_FI;
+ OHCI_REG->periodic_start = (OHCI_FMINTERVAL_FI * 9) / 10; // Periodic start is 90% of frame interval
+
+ OHCI_REG->control_bit.hc_functional_state = OHCI_CONTROL_FUNCSTATE_OPERATIONAL; // make HC's state to operational state TODO use this to suspend (save power)
+ OHCI_REG->rh_status_bit.local_power_status_change = 1; // set global power for ports
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// PORT API
+//--------------------------------------------------------------------+
+void hcd_port_reset(uint8_t hostid)
+{
+ (void) hostid;
+ OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
+}
+
+bool hcd_port_connect_status(uint8_t hostid)
+{
+ (void) hostid;
+ return OHCI_REG->rhport_status_bit[0].current_connect_status;
+}
+
+tusb_speed_t hcd_port_speed_get(uint8_t hostid)
+{
+ (void) hostid;
+ return OHCI_REG->rhport_status_bit[0].low_speed_device_attached ? TUSB_SPEED_LOW : TUSB_SPEED_FULL;
+}
+
+// endpoints are tied to an address, which only reclaim after a long delay when enumerating
+// thus there is no need to make sure ED is not in HC's cahed as it will not for sure
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
+{
+ // TODO OHCI
+ (void) rhport;
+
+ // addr0 serves as static head --> only set skip bit
+ if ( dev_addr == 0 )
+ {
+ ohci_data.control[0].ed.skip = 1;
+ }else
+ {
+ // remove control
+ ed_list_remove_by_addr( p_ed_head[TUSB_XFER_CONTROL], dev_addr);
+
+ // remove bulk
+ ed_list_remove_by_addr(p_ed_head[TUSB_XFER_BULK], dev_addr);
+
+ // remove interrupt
+ ed_list_remove_by_addr(p_ed_head[TUSB_XFER_INTERRUPT], dev_addr);
+
+ // TODO remove ISO
+ }
+}
+
+//--------------------------------------------------------------------+
+// Controller API
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// CONTROL PIPE API
+//--------------------------------------------------------------------+
+static inline tusb_xfer_type_t ed_get_xfer_type(ohci_ed_t const * const p_ed)
+{
+ return (p_ed->ep_number == 0 ) ? TUSB_XFER_CONTROL :
+ (p_ed->is_iso ) ? TUSB_XFER_ISOCHRONOUS :
+ (p_ed->is_interrupt_xfer ) ? TUSB_XFER_INTERRUPT : TUSB_XFER_BULK;
+}
+
+static void ed_init(ohci_ed_t *p_ed, uint8_t dev_addr, uint16_t max_packet_size, uint8_t endpoint_addr, uint8_t xfer_type, uint8_t interval)
+{
+ (void) interval;
+
+ // address 0 is used as async head, which always on the list --> cannot be cleared
+ if (dev_addr != 0)
+ {
+ tu_memclr(p_ed, sizeof(ohci_ed_t));
+ }
+
+ p_ed->dev_addr = dev_addr;
+ p_ed->ep_number = endpoint_addr & 0x0F;
+ p_ed->pid = (xfer_type == TUSB_XFER_CONTROL) ? OHCI_PID_SETUP : ( (endpoint_addr & TUSB_DIR_IN_MASK) ? OHCI_PID_IN : OHCI_PID_OUT );
+ p_ed->speed = _usbh_devices[dev_addr].speed;
+ p_ed->is_iso = (xfer_type == TUSB_XFER_ISOCHRONOUS) ? 1 : 0;
+ p_ed->max_packet_size = max_packet_size;
+
+ p_ed->used = 1;
+ p_ed->is_interrupt_xfer = (xfer_type == TUSB_XFER_INTERRUPT ? 1 : 0);
+}
+
+static void gtd_init(ohci_gtd_t* p_td, void* data_ptr, uint16_t total_bytes)
+{
+ tu_memclr(p_td, sizeof(ohci_gtd_t));
+
+ p_td->used = 1;
+ p_td->expected_bytes = total_bytes;
+
+ p_td->buffer_rounding = 1; // less than queued length is not a error
+ p_td->delay_interrupt = OHCI_INT_ON_COMPLETE_NO;
+ p_td->condition_code = OHCI_CCODE_NOT_ACCESSED;
+
+ p_td->current_buffer_pointer = data_ptr;
+ p_td->buffer_end = total_bytes ? (((uint8_t*) data_ptr) + total_bytes-1) : NULL;
+}
+
+bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
+{
+ (void) rhport;
+
+ ohci_ed_t* p_ed = &ohci_data.control[dev_addr].ed;
+ ohci_gtd_t *p_setup = &ohci_data.control[dev_addr].gtd;
+
+ gtd_init(p_setup, (void*) setup_packet, 8);
+ p_setup->index = dev_addr;
+ p_setup->pid = OHCI_PID_SETUP;
+ p_setup->data_toggle = TU_BIN8(10); // DATA0
+ p_setup->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
+
+ //------------- Attach TDs list to Control Endpoint -------------//
+ p_ed->td_head.address = (uint32_t) p_setup;
+
+ OHCI_REG->command_status_bit.control_list_filled = 1;
+
+ return true;
+}
+
+bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
+{
+ (void) rhport;
+
+ uint8_t const epnum = tu_edpt_number(ep_addr);
+ uint8_t const dir = tu_edpt_dir(ep_addr);
+
+ // FIXME control only for now
+ if ( epnum == 0 )
+ {
+ ohci_ed_t* const p_ed = &ohci_data.control[dev_addr].ed;
+ ohci_gtd_t *p_data = &ohci_data.control[dev_addr].gtd;
+
+ gtd_init(p_data, buffer, buflen);
+
+ p_data->index = dev_addr;
+ p_data->pid = dir ? OHCI_PID_IN : OHCI_PID_OUT;
+ p_data->data_toggle = TU_BIN8(11); // DATA1
+ p_data->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
+
+ p_ed->td_head.address = (uint32_t) p_data;
+
+ OHCI_REG->command_status_bit.control_list_filled = 1;
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// BULK/INT/ISO PIPE API
+//--------------------------------------------------------------------+
+static inline ohci_ed_t * ed_from_addr(uint8_t dev_addr, uint8_t ep_addr)
+{
+ if ( tu_edpt_number(ep_addr) == 0 ) return &ohci_data.control[dev_addr].ed;
+
+ ohci_ed_t* ed_pool = ohci_data.ed_pool;
+
+ for(uint32_t i=0; i<HCD_MAX_ENDPOINT; i++)
+ {
+ if ( (ed_pool[i].dev_addr == dev_addr) &&
+ ep_addr == tu_edpt_addr(ed_pool[i].ep_number, ed_pool[i].pid == OHCI_PID_IN) )
+ {
+ return &ed_pool[i];
+ }
+ }
+
+ return NULL;
+}
+
+static inline ohci_ed_t * ed_find_free(void)
+{
+ ohci_ed_t* ed_pool = ohci_data.ed_pool;
+
+ for(uint8_t i = 0; i < HCD_MAX_ENDPOINT; i++)
+ {
+ if ( !ed_pool[i].used ) return &ed_pool[i];
+ }
+
+ return NULL;
+}
+
+static void ed_list_insert(ohci_ed_t * p_pre, ohci_ed_t * p_ed)
+{
+ p_ed->next = p_pre->next;
+ p_pre->next = (uint32_t) p_ed;
+}
+
+static void ed_list_remove_by_addr(ohci_ed_t * p_head, uint8_t dev_addr)
+{
+ ohci_ed_t* p_prev = p_head;
+
+ while( p_prev->next )
+ {
+ ohci_ed_t* ed = (ohci_ed_t*) p_prev->next;
+
+ if (ed->dev_addr == dev_addr)
+ {
+ // unlink ed
+ p_prev->next = ed->next;
+
+ // point the removed ED's next pointer to list head to make sure HC can always safely move away from this ED
+ ed->next = (uint32_t) p_head;
+ ed->used = 0;
+ }
+
+ // check next valid since we could remove it
+ if (p_prev->next) p_prev = (ohci_ed_t*) p_prev->next;
+ }
+}
+
+bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
+{
+ (void) rhport;
+
+ // TODO iso support
+ TU_ASSERT(ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
+
+ //------------- Prepare Queue Head -------------//
+ ohci_ed_t * p_ed;
+
+ if ( ep_desc->bEndpointAddress == 0 )
+ {
+ p_ed = &ohci_data.control[dev_addr].ed;
+ }else
+ {
+ p_ed = ed_find_free();
+ }
+ TU_ASSERT(p_ed);
+
+ ed_init( p_ed, dev_addr, ep_desc->wMaxPacketSize.size, ep_desc->bEndpointAddress,
+ ep_desc->bmAttributes.xfer, ep_desc->bInterval );
+
+ // control of dev0 is used as static async head
+ if ( dev_addr == 0 )
+ {
+ p_ed->skip = 0; // only need to clear skip bit
+ return true;
+ }
+
+ ed_list_insert( p_ed_head[ep_desc->bmAttributes.xfer], p_ed );
+
+ return true;
+}
+
+static ohci_gtd_t * gtd_find_free(void)
+{
+ for(uint8_t i=0; i < HCD_MAX_XFER; i++)
+ {
+ if ( !ohci_data.gtd_pool[i].used ) return &ohci_data.gtd_pool[i];
+ }
+
+ return NULL;
+}
+
+static void td_insert_to_ed(ohci_ed_t* p_ed, ohci_gtd_t * p_gtd)
+{
+ // tail is always NULL
+ if ( tu_align16(p_ed->td_head.address) == 0 )
+ { // TD queue is empty --> head = TD
+ p_ed->td_head.address |= (uint32_t) p_gtd;
+ }
+ else
+ { // TODO currently only support queue up to 2 TD each endpoint at a time
+ ((ohci_gtd_t*) tu_align16(p_ed->td_head.address))->next = (uint32_t) p_gtd;
+ }
+}
+
+static bool pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
+{
+ ohci_ed_t* const p_ed = ed_from_addr(dev_addr, ep_addr);
+
+ // not support ISO yet
+ TU_VERIFY ( !p_ed->is_iso );
+
+ ohci_gtd_t * const p_gtd = gtd_find_free();
+ TU_ASSERT(p_gtd); // not enough gtd
+
+ gtd_init(p_gtd, buffer, total_bytes);
+ p_gtd->index = p_ed-ohci_data.ed_pool;
+
+ if ( int_on_complete ) p_gtd->delay_interrupt = OHCI_INT_ON_COMPLETE_YES;
+
+ td_insert_to_ed(p_ed, p_gtd);
+
+ return true;
+}
+
+bool hcd_pipe_queue_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes)
+{
+ return pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, false);
+}
+
+bool hcd_pipe_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t buffer[], uint16_t total_bytes, bool int_on_complete)
+{
+ (void) int_on_complete;
+ TU_ASSERT( pipe_queue_xfer(dev_addr, ep_addr, buffer, total_bytes, true) );
+
+ tusb_xfer_type_t xfer_type = ed_get_xfer_type( ed_from_addr(dev_addr, ep_addr) );
+
+ if (TUSB_XFER_BULK == xfer_type) OHCI_REG->command_status_bit.bulk_list_filled = 1;
+
+ return true;
+}
+
+bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
+ return tu_align16(p_ed->td_head.address) != tu_align16(p_ed->td_tail);
+}
+
+bool hcd_edpt_stalled(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ohci_ed_t const * const p_ed = ed_from_addr(dev_addr, ep_addr);
+ return p_ed->td_head.halted && p_ed->is_stalled;
+}
+
+bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
+{
+ ohci_ed_t * const p_ed = ed_from_addr(dev_addr, ep_addr);
+
+ p_ed->is_stalled = 0;
+ p_ed->td_tail &= 0x0Ful; // set tail pointer back to NULL
+
+ p_ed->td_head.toggle = 0; // reset data toggle
+ p_ed->td_head.halted = 0;
+
+ if ( TUSB_XFER_BULK == ed_get_xfer_type(p_ed) ) OHCI_REG->command_status_bit.bulk_list_filled = 1;
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------+
+// OHCI Interrupt Handler
+//--------------------------------------------------------------------+
+static ohci_td_item_t* list_reverse(ohci_td_item_t* td_head)
+{
+ ohci_td_item_t* td_reverse_head = NULL;
+
+ while(td_head != NULL)
+ {
+ uint32_t next = td_head->next;
+
+ // make current's item become reverse's first item
+ td_head->next = (uint32_t) td_reverse_head;
+ td_reverse_head = td_head;
+
+ td_head = (ohci_td_item_t*) next; // advance to next item
+ }
+
+ return td_reverse_head;
+}
+
+static inline bool gtd_is_control(ohci_gtd_t const * const p_qtd)
+{
+ return ((uint32_t) p_qtd) < ((uint32_t) ohci_data.gtd_pool); // check ohci_data_t for memory layout
+}
+
+static inline ohci_ed_t* gtd_get_ed(ohci_gtd_t const * const p_qtd)
+{
+ if ( gtd_is_control(p_qtd) )
+ {
+ return &ohci_data.control[p_qtd->index].ed;
+ }else
+ {
+ return &ohci_data.ed_pool[p_qtd->index];
+ }
+}
+
+static inline uint32_t gtd_xfer_byte_left(uint32_t buffer_end, uint32_t current_buffer)
+{ // 5.2.9 OHCI sample code
+ return (tu_align4k(buffer_end ^ current_buffer) ? 0x1000 : 0) +
+ tu_offset4k(buffer_end) - tu_offset4k(current_buffer) + 1;
+}
+
+static void done_queue_isr(uint8_t hostid)
+{
+ (void) hostid;
+
+ // done head is written in reversed order of completion --> need to reverse the done queue first
+ ohci_td_item_t* td_head = list_reverse ( (ohci_td_item_t*) tu_align16(ohci_data.hcca.done_head) );
+
+ while( td_head != NULL )
+ {
+ // TODO check if td_head is iso td
+ //------------- Non ISO transfer -------------//
+ ohci_gtd_t * const p_qtd = (ohci_gtd_t *) td_head;
+ xfer_result_t const event = (p_qtd->condition_code == OHCI_CCODE_NO_ERROR) ? XFER_RESULT_SUCCESS :
+ (p_qtd->condition_code == OHCI_CCODE_STALL) ? XFER_RESULT_STALLED : XFER_RESULT_FAILED;
+
+ p_qtd->used = 0; // free TD
+ if ( (p_qtd->delay_interrupt == OHCI_INT_ON_COMPLETE_YES) || (event != XFER_RESULT_SUCCESS) )
+ {
+ ohci_ed_t * const p_ed = gtd_get_ed(p_qtd);
+
+ uint32_t const xferred_bytes = p_qtd->expected_bytes - gtd_xfer_byte_left((uint32_t) p_qtd->buffer_end, (uint32_t) p_qtd->current_buffer_pointer);
+
+ // NOTE Assuming the current list is BULK and there is no other EDs in the list has queued TDs.
+ // When there is a error resulting this ED is halted, and this EP still has other queued TD
+ // --> the Bulk list only has this halted EP queueing TDs (remaining)
+ // --> Bulk list will be considered as not empty by HC !!! while there is no attempt transaction on this list
+ // --> HC will not process Control list (due to service ratio when Bulk list not empty)
+ // To walk-around this, the halted ED will have TailP = HeadP (empty list condition), when clearing halt
+ // the TailP must be set back to NULL for processing remaining TDs
+ if ((event != XFER_RESULT_SUCCESS))
+ {
+ p_ed->td_tail &= 0x0Ful;
+ p_ed->td_tail |= tu_align16(p_ed->td_head.address); // mark halted EP as empty queue
+ if ( event == XFER_RESULT_STALLED ) p_ed->is_stalled = 1;
+ }
+
+ hcd_event_xfer_complete(p_ed->dev_addr,
+ tu_edpt_addr(p_ed->ep_number, p_ed->pid == OHCI_PID_IN),
+ event, xferred_bytes);
+ }
+
+ td_head = (ohci_td_item_t*) td_head->next;
+ }
+}
+
+void hcd_isr(uint8_t hostid)
+{
+ uint32_t const int_en = OHCI_REG->interrupt_enable;
+ uint32_t const int_status = OHCI_REG->interrupt_status & int_en;
+
+ if (int_status == 0) return;
+
+ //------------- RootHub status -------------//
+ if ( int_status & OHCI_INT_RHPORT_STATUS_CHANGE_MASK )
+ {
+ uint32_t const rhport_status = OHCI_REG->rhport_status[0] & OHCI_RHPORT_ALL_CHANGE_MASK;
+
+ // TODO dual port is not yet supported
+ if ( rhport_status & OHCI_RHPORT_CONNECT_STATUS_CHANGE_MASK )
+ {
+ // TODO check if remote wake-up
+ if ( OHCI_REG->rhport_status_bit[0].current_connect_status )
+ {
+ // TODO reset port immediately, without this controller will got 2-3 (debouncing connection status change)
+ OHCI_REG->rhport_status[0] = OHCI_RHPORT_PORT_RESET_STATUS_MASK;
+ hcd_event_device_attach(0);
+ }else
+ {
+ hcd_event_device_remove(0);
+ }
+ }
+
+ if ( rhport_status & OHCI_RHPORT_PORT_SUSPEND_CHANGE_MASK)
+ {
+
+ }
+
+ OHCI_REG->rhport_status[0] = rhport_status; // acknowledge all interrupt
+ }
+
+ //------------- Transfer Complete -------------//
+ if ( int_status & OHCI_INT_WRITEBACK_DONEHEAD_MASK)
+ {
+ done_queue_isr(hostid);
+ }
+
+ OHCI_REG->interrupt_status = int_status; // Acknowledge handled interrupt
+}
+//--------------------------------------------------------------------+
+// HELPER
+//--------------------------------------------------------------------+
+
+
+#endif
+
diff --git a/src/host/ohci/ohci.h b/src/host/ohci/ohci.h
index a8aae251b..6f6ef9676 100644
--- a/src/host/ohci/ohci.h
+++ b/src/host/ohci/ohci.h
@@ -1,291 +1,291 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-/** \ingroup Group_HCD
- * @{
- * \defgroup OHCI
- * \brief OHCI driver. All documents sources mentioned here (eg section 3.5) is referring to OHCI Specs unless state otherwise
- * @{ */
-
-#ifndef _TUSB_OHCI_H_
-#define _TUSB_OHCI_H_
-
-#ifdef __cplusplus
- extern "C" {
-#endif
-
-#include "common/tusb_common.h"
-
-//--------------------------------------------------------------------+
-// OHCI CONFIGURATION & CONSTANTS
-//--------------------------------------------------------------------+
-#define HOST_HCD_XFER_INTERRUPT // TODO interrupt is used widely, should always be enalbed
-#define OHCI_PERIODIC_LIST (defined HOST_HCD_XFER_INTERRUPT || defined HOST_HCD_XFER_ISOCHRONOUS)
-
-// TODO merge OHCI with EHCI
-enum {
- OHCI_MAX_ITD = 4
-};
-
-enum {
- OHCI_PID_SETUP = 0,
- OHCI_PID_OUT,
- OHCI_PID_IN,
-};
-
-//--------------------------------------------------------------------+
-// OHCI Data Structure
-//--------------------------------------------------------------------+
-typedef struct {
- uint32_t interrupt_table[32];
- volatile uint16_t frame_number;
- volatile uint16_t frame_pad;
- volatile uint32_t done_head;
- uint8_t reserved[116+4]; // TODO try to make use of this area if possible, extra 4 byte to make the whole struct size = 256
-}ohci_hcca_t; // TU_ATTR_ALIGNED(256)
-
-TU_VERIFY_STATIC( sizeof(ohci_hcca_t) == 256, "size is not correct" );
-
-typedef struct {
- uint32_t reserved[2];
- volatile uint32_t next;
- uint32_t reserved2;
-}ohci_td_item_t;
-
-
-typedef struct TU_ATTR_ALIGNED(16)
-{
- // Word 0
- uint32_t used : 1;
- uint32_t index : 4; // endpoint index the td belongs to, or device address in case of control xfer
- uint32_t expected_bytes : 13; // TODO available for hcd
-
- uint32_t buffer_rounding : 1;
- uint32_t pid : 2;
- uint32_t delay_interrupt : 3;
- volatile uint32_t data_toggle : 2;
- volatile uint32_t error_count : 2;
- volatile uint32_t condition_code : 4;
-
- // Word 1
- volatile uint8_t* current_buffer_pointer;
-
- // Word 2 : next TD
- volatile uint32_t next;
-
- // Word 3
- uint8_t* buffer_end;
-} ohci_gtd_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_gtd_t) == 16, "size is not correct" );
-
-typedef struct TU_ATTR_ALIGNED(16)
-{
- // Word 0
- uint32_t dev_addr : 7;
- uint32_t ep_number : 4;
- uint32_t pid : 2; // 00b from TD, 01b Out, 10b In
- uint32_t speed : 1;
- uint32_t skip : 1;
- uint32_t is_iso : 1;
- uint32_t max_packet_size : 11;
- // HCD: make use of 5 reserved bits
- uint32_t used : 1;
- uint32_t is_interrupt_xfer : 1;
- uint32_t is_stalled : 1;
- uint32_t : 2;
-
- // Word 1
- uint32_t td_tail;
-
- // Word 2
- volatile union {
- uint32_t address;
- struct {
- uint32_t halted : 1;
- uint32_t toggle : 1;
- uint32_t : 30;
- };
- }td_head;
-
- // Word 3: next ED
- uint32_t next;
-} ohci_ed_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_ed_t) == 16, "size is not correct" );
-
-typedef struct TU_ATTR_ALIGNED(32)
-{
- /*---------- Word 1 ----------*/
- uint32_t starting_frame : 16;
- uint32_t : 5; // can be used
- uint32_t delay_interrupt : 3;
- uint32_t frame_count : 3;
- uint32_t : 1; // can be used
- volatile uint32_t condition_code : 4;
-
- /*---------- Word 2 ----------*/
- uint32_t buffer_page0; // 12 lsb bits can be used
-
- /*---------- Word 3 ----------*/
- volatile uint32_t next;
-
- /*---------- Word 4 ----------*/
- uint32_t buffer_end;
-
- /*---------- Word 5-8 ----------*/
- volatile uint16_t offset_packetstatus[8];
-} ochi_itd_t;
-
-TU_VERIFY_STATIC( sizeof(ochi_itd_t) == 32, "size is not correct" );
-
-// structure with member alignment required from large to small
-typedef struct TU_ATTR_ALIGNED(256)
-{
- ohci_hcca_t hcca;
-
- ohci_ed_t bulk_head_ed; // static bulk head (dummy)
- ohci_ed_t period_head_ed; // static periodic list head (dummy)
-
- // control endpoints has reserved resources
- struct {
- ohci_ed_t ed;
- ohci_gtd_t gtd;
- }control[CFG_TUSB_HOST_DEVICE_MAX+1];
-
- // ochi_itd_t itd[OHCI_MAX_ITD]; // itd requires alignment of 32
- ohci_ed_t ed_pool[HCD_MAX_ENDPOINT];
- ohci_gtd_t gtd_pool[HCD_MAX_XFER];
-
-} ohci_data_t;
-
-//--------------------------------------------------------------------+
-// OHCI Operational Register
-//--------------------------------------------------------------------+
-
-
-//--------------------------------------------------------------------+
-// OHCI Data Organization
-//--------------------------------------------------------------------+
-typedef volatile struct
-{
- uint32_t revision;
-
- union {
- uint32_t control;
- struct {
- uint32_t control_bulk_service_ratio : 2;
- uint32_t periodic_list_enable : 1;
- uint32_t isochronous_enable : 1;
- uint32_t control_list_enable : 1;
- uint32_t bulk_list_enable : 1;
- uint32_t hc_functional_state : 2;
- uint32_t interrupt_routing : 1;
- uint32_t remote_wakeup_connected : 1;
- uint32_t remote_wakeup_enale : 1;
- uint32_t : 0;
- }control_bit;
- };
-
- union {
- uint32_t command_status;
- struct {
- uint32_t controller_reset : 1;
- uint32_t control_list_filled : 1;
- uint32_t bulk_list_filled : 1;
- uint32_t ownership_change_request : 1;
- uint32_t : 12;
- uint32_t scheduling_overrun_count : 2;
- }command_status_bit;
- };
-
- uint32_t interrupt_status;
- uint32_t interrupt_enable;
- uint32_t interrupt_disable;
-
- uint32_t hcca;
- uint32_t period_current_ed;
- uint32_t control_head_ed;
- uint32_t control_current_ed;
- uint32_t bulk_head_ed;
- uint32_t bulk_current_ed;
- uint32_t done_head;
-
- uint32_t frame_interval;
- uint32_t frame_remaining;
- uint32_t frame_number;
- uint32_t periodic_start;
- uint32_t lowspeed_threshold;
-
- uint32_t rh_descriptorA;
- uint32_t rh_descriptorB;
-
- union {
- uint32_t rh_status;
- struct {
- uint32_t local_power_status : 1; // read Local Power Status; write: Clear Global Power
- uint32_t over_current_indicator : 1;
- uint32_t : 13;
- uint32_t device_remote_wakeup_enable : 1;
- uint32_t local_power_status_change : 1;
- uint32_t over_current_indicator_change : 1;
- uint32_t : 13;
- uint32_t clear_remote_wakeup_enable : 1;
- }rh_status_bit;
- };
-
- union {
- uint32_t rhport_status[2]; // TODO NXP OHCI controller only has 2 ports
- struct {
- uint32_t current_connect_status : 1;
- uint32_t port_enable_status : 1;
- uint32_t port_suspend_status : 1;
- uint32_t port_over_current_indicator : 1;
- uint32_t port_reset_status : 1;
- uint32_t : 3;
- uint32_t port_power_status : 1;
- uint32_t low_speed_device_attached : 1;
- uint32_t : 6;
- uint32_t connect_status_change : 1;
- uint32_t port_enable_status_change : 1;
- uint32_t port_suspend_status_change : 1;
- uint32_t port_over_current_indicator_change : 1;
- uint32_t port_reset_status_change : 1;
- uint32_t : 0;
- }rhport_status_bit[2];
- };
-}ohci_registers_t;
-
-TU_VERIFY_STATIC( sizeof(ohci_registers_t) == 0x5c, "size is not correct");
-
-#ifdef __cplusplus
- }
-#endif
-
-#endif /* _TUSB_OHCI_H_ */
-
-/** @} */
-/** @} */
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+/** \ingroup Group_HCD
+ * @{
+ * \defgroup OHCI
+ * \brief OHCI driver. All documents sources mentioned here (eg section 3.5) is referring to OHCI Specs unless state otherwise
+ * @{ */
+
+#ifndef _TUSB_OHCI_H_
+#define _TUSB_OHCI_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "common/tusb_common.h"
+
+//--------------------------------------------------------------------+
+// OHCI CONFIGURATION & CONSTANTS
+//--------------------------------------------------------------------+
+#define HOST_HCD_XFER_INTERRUPT // TODO interrupt is used widely, should always be enalbed
+#define OHCI_PERIODIC_LIST (defined HOST_HCD_XFER_INTERRUPT || defined HOST_HCD_XFER_ISOCHRONOUS)
+
+// TODO merge OHCI with EHCI
+enum {
+ OHCI_MAX_ITD = 4
+};
+
+enum {
+ OHCI_PID_SETUP = 0,
+ OHCI_PID_OUT,
+ OHCI_PID_IN,
+};
+
+//--------------------------------------------------------------------+
+// OHCI Data Structure
+//--------------------------------------------------------------------+
+typedef struct {
+ uint32_t interrupt_table[32];
+ volatile uint16_t frame_number;
+ volatile uint16_t frame_pad;
+ volatile uint32_t done_head;
+ uint8_t reserved[116+4]; // TODO try to make use of this area if possible, extra 4 byte to make the whole struct size = 256
+}ohci_hcca_t; // TU_ATTR_ALIGNED(256)
+
+TU_VERIFY_STATIC( sizeof(ohci_hcca_t) == 256, "size is not correct" );
+
+typedef struct {
+ uint32_t reserved[2];
+ volatile uint32_t next;
+ uint32_t reserved2;
+}ohci_td_item_t;
+
+
+typedef struct TU_ATTR_ALIGNED(16)
+{
+ // Word 0
+ uint32_t used : 1;
+ uint32_t index : 4; // endpoint index the td belongs to, or device address in case of control xfer
+ uint32_t expected_bytes : 13; // TODO available for hcd
+
+ uint32_t buffer_rounding : 1;
+ uint32_t pid : 2;
+ uint32_t delay_interrupt : 3;
+ volatile uint32_t data_toggle : 2;
+ volatile uint32_t error_count : 2;
+ volatile uint32_t condition_code : 4;
+
+ // Word 1
+ volatile uint8_t* current_buffer_pointer;
+
+ // Word 2 : next TD
+ volatile uint32_t next;
+
+ // Word 3
+ uint8_t* buffer_end;
+} ohci_gtd_t;
+
+TU_VERIFY_STATIC( sizeof(ohci_gtd_t) == 16, "size is not correct" );
+
+typedef struct TU_ATTR_ALIGNED(16)
+{
+ // Word 0
+ uint32_t dev_addr : 7;
+ uint32_t ep_number : 4;
+ uint32_t pid : 2; // 00b from TD, 01b Out, 10b In
+ uint32_t speed : 1;
+ uint32_t skip : 1;
+ uint32_t is_iso : 1;
+ uint32_t max_packet_size : 11;
+ // HCD: make use of 5 reserved bits
+ uint32_t used : 1;
+ uint32_t is_interrupt_xfer : 1;
+ uint32_t is_stalled : 1;
+ uint32_t : 2;
+
+ // Word 1
+ uint32_t td_tail;
+
+ // Word 2
+ volatile union {
+ uint32_t address;
+ struct {
+ uint32_t halted : 1;
+ uint32_t toggle : 1;
+ uint32_t : 30;
+ };
+ }td_head;
+
+ // Word 3: next ED
+ uint32_t next;
+} ohci_ed_t;
+
+TU_VERIFY_STATIC( sizeof(ohci_ed_t) == 16, "size is not correct" );
+
+typedef struct TU_ATTR_ALIGNED(32)
+{
+ /*---------- Word 1 ----------*/
+ uint32_t starting_frame : 16;
+ uint32_t : 5; // can be used
+ uint32_t delay_interrupt : 3;
+ uint32_t frame_count : 3;
+ uint32_t : 1; // can be used
+ volatile uint32_t condition_code : 4;
+
+ /*---------- Word 2 ----------*/
+ uint32_t buffer_page0; // 12 lsb bits can be used
+
+ /*---------- Word 3 ----------*/
+ volatile uint32_t next;
+
+ /*---------- Word 4 ----------*/
+ uint32_t buffer_end;
+
+ /*---------- Word 5-8 ----------*/
+ volatile uint16_t offset_packetstatus[8];
+} ochi_itd_t;
+
+TU_VERIFY_STATIC( sizeof(ochi_itd_t) == 32, "size is not correct" );
+
+// structure with member alignment required from large to small
+typedef struct TU_ATTR_ALIGNED(256)
+{
+ ohci_hcca_t hcca;
+
+ ohci_ed_t bulk_head_ed; // static bulk head (dummy)
+ ohci_ed_t period_head_ed; // static periodic list head (dummy)
+
+ // control endpoints has reserved resources
+ struct {
+ ohci_ed_t ed;
+ ohci_gtd_t gtd;
+ }control[CFG_TUSB_HOST_DEVICE_MAX+1];
+
+ // ochi_itd_t itd[OHCI_MAX_ITD]; // itd requires alignment of 32
+ ohci_ed_t ed_pool[HCD_MAX_ENDPOINT];
+ ohci_gtd_t gtd_pool[HCD_MAX_XFER];
+
+} ohci_data_t;
+
+//--------------------------------------------------------------------+
+// OHCI Operational Register
+//--------------------------------------------------------------------+
+
+
+//--------------------------------------------------------------------+
+// OHCI Data Organization
+//--------------------------------------------------------------------+
+typedef volatile struct
+{
+ uint32_t revision;
+
+ union {
+ uint32_t control;
+ struct {
+ uint32_t control_bulk_service_ratio : 2;
+ uint32_t periodic_list_enable : 1;
+ uint32_t isochronous_enable : 1;
+ uint32_t control_list_enable : 1;
+ uint32_t bulk_list_enable : 1;
+ uint32_t hc_functional_state : 2;
+ uint32_t interrupt_routing : 1;
+ uint32_t remote_wakeup_connected : 1;
+ uint32_t remote_wakeup_enale : 1;
+ uint32_t : 0;
+ }control_bit;
+ };
+
+ union {
+ uint32_t command_status;
+ struct {
+ uint32_t controller_reset : 1;
+ uint32_t control_list_filled : 1;
+ uint32_t bulk_list_filled : 1;
+ uint32_t ownership_change_request : 1;
+ uint32_t : 12;
+ uint32_t scheduling_overrun_count : 2;
+ }command_status_bit;
+ };
+
+ uint32_t interrupt_status;
+ uint32_t interrupt_enable;
+ uint32_t interrupt_disable;
+
+ uint32_t hcca;
+ uint32_t period_current_ed;
+ uint32_t control_head_ed;
+ uint32_t control_current_ed;
+ uint32_t bulk_head_ed;
+ uint32_t bulk_current_ed;
+ uint32_t done_head;
+
+ uint32_t frame_interval;
+ uint32_t frame_remaining;
+ uint32_t frame_number;
+ uint32_t periodic_start;
+ uint32_t lowspeed_threshold;
+
+ uint32_t rh_descriptorA;
+ uint32_t rh_descriptorB;
+
+ union {
+ uint32_t rh_status;
+ struct {
+ uint32_t local_power_status : 1; // read Local Power Status; write: Clear Global Power
+ uint32_t over_current_indicator : 1;
+ uint32_t : 13;
+ uint32_t device_remote_wakeup_enable : 1;
+ uint32_t local_power_status_change : 1;
+ uint32_t over_current_indicator_change : 1;
+ uint32_t : 13;
+ uint32_t clear_remote_wakeup_enable : 1;
+ }rh_status_bit;
+ };
+
+ union {
+ uint32_t rhport_status[2]; // TODO NXP OHCI controller only has 2 ports
+ struct {
+ uint32_t current_connect_status : 1;
+ uint32_t port_enable_status : 1;
+ uint32_t port_suspend_status : 1;
+ uint32_t port_over_current_indicator : 1;
+ uint32_t port_reset_status : 1;
+ uint32_t : 3;
+ uint32_t port_power_status : 1;
+ uint32_t low_speed_device_attached : 1;
+ uint32_t : 6;
+ uint32_t connect_status_change : 1;
+ uint32_t port_enable_status_change : 1;
+ uint32_t port_suspend_status_change : 1;
+ uint32_t port_over_current_indicator_change : 1;
+ uint32_t port_reset_status_change : 1;
+ uint32_t : 0;
+ }rhport_status_bit[2];
+ };
+}ohci_registers_t;
+
+TU_VERIFY_STATIC( sizeof(ohci_registers_t) == 0x5c, "size is not correct");
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_OHCI_H_ */
+
+/** @} */
+/** @} */
diff --git a/src/host/usbh.c b/src/host/usbh.c
index 42a99f3d2..f1b88259c 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -1,676 +1,676 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) 2019 Ha Thach (tinyusb.org)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * This file is part of the TinyUSB stack.
- */
-
-#include "common/tusb_common.h"
-
-#if TUSB_OPT_HOST_ENABLED
-
-#ifndef CFG_TUH_TASK_QUEUE_SZ
-#define CFG_TUH_TASK_QUEUE_SZ 16
-#endif
-
-//--------------------------------------------------------------------+
-// INCLUDE
-//--------------------------------------------------------------------+
-#include "tusb.h"
-#include "hub.h"
-#include "usbh_hcd.h"
-
-//--------------------------------------------------------------------+
-// MACRO CONSTANT TYPEDEF
-//--------------------------------------------------------------------+
-static host_class_driver_t const usbh_class_drivers[] =
-{
- #if CFG_TUH_CDC
- {
- .class_code = TUSB_CLASS_CDC,
- .init = cdch_init,
- .open = cdch_open,
- .isr = cdch_isr,
- .close = cdch_close
- },
- #endif
-
- #if CFG_TUH_MSC
- {
- .class_code = TUSB_CLASS_MSC,
- .init = msch_init,
- .open = msch_open,
- .isr = msch_isr,
- .close = msch_close
- },
- #endif
-
- #if HOST_CLASS_HID
- {
- .class_code = TUSB_CLASS_HID,
- .init = hidh_init,
- .open = hidh_open_subtask,
- .isr = hidh_isr,
- .close = hidh_close
- },
- #endif
-
- #if CFG_TUH_HUB
- {
- .class_code = TUSB_CLASS_HUB,
- .init = hub_init,
- .open = hub_open,
- .isr = hub_isr,
- .close = hub_close
- },
- #endif
-
- #if CFG_TUH_VENDOR
- {
- .class_code = TUSB_CLASS_VENDOR_SPECIFIC,
- .init = cush_init,
- .open = cush_open_subtask,
- .isr = cush_isr,
- .close = cush_close
- }
- #endif
-};
-
-enum { USBH_CLASS_DRIVER_COUNT = TU_ARRAY_SZIE(usbh_class_drivers) };
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
-
-// including zero-address
-CFG_TUSB_MEM_SECTION usbh_device_t _usbh_devices[CFG_TUSB_HOST_DEVICE_MAX+1];
-
-// Event queue
-// role device/host is used by OS NONE for mutex (disable usb isr) only
-OSAL_QUEUE_DEF(OPT_MODE_HOST, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
-static osal_queue_t _usbh_q;
-
-CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _usbh_ctrl_buf[CFG_TUSB_HOST_ENUM_BUFFER_SIZE];
-
-//------------- Reporter Task Data -------------//
-
-//------------- Helper Function Prototypes -------------//
-static inline uint8_t get_new_address(void);
-static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc);
-static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id);
-
-//--------------------------------------------------------------------+
-// PUBLIC API (Parameter Verification is required)
-//--------------------------------------------------------------------+
-tusb_device_state_t tuh_device_get_state (uint8_t const dev_addr)
-{
- TU_ASSERT( dev_addr <= CFG_TUSB_HOST_DEVICE_MAX, TUSB_DEVICE_STATE_UNPLUG);
- return (tusb_device_state_t) _usbh_devices[dev_addr].state;
-}
-
-//--------------------------------------------------------------------+
-// CLASS-USBD API (don't require to verify parameters)
-//--------------------------------------------------------------------+
-bool usbh_init(void)
-{
- tu_memclr(_usbh_devices, sizeof(usbh_device_t)*(CFG_TUSB_HOST_DEVICE_MAX+1));
-
- //------------- Enumeration & Reporter Task init -------------//
- _usbh_q = osal_queue_create( &_usbh_qdef );
- TU_ASSERT(_usbh_q != NULL);
-
- //------------- Semaphore, Mutex for Control Pipe -------------//
- for(uint8_t i=0; i<CFG_TUSB_HOST_DEVICE_MAX+1; i++) // including address zero
- {
- usbh_device_t * const dev = &_usbh_devices[i];
-
- dev->control.sem_hdl = osal_semaphore_create(&dev->control.sem_def);
- TU_ASSERT(dev->control.sem_hdl != NULL);
-
- dev->control.mutex_hdl = osal_mutex_create(&dev->control.mutex_def);
- TU_ASSERT(dev->control.mutex_hdl != NULL);
-
- memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
- memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
- }
-
- // Class drivers init
- for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].init();
-
- TU_ASSERT(hcd_init());
- hcd_int_enable(TUH_OPT_RHPORT);
-
- return true;
-}
-
-//------------- USBH control transfer -------------//
-bool usbh_control_xfer (uint8_t dev_addr, tusb_control_request_t* request, uint8_t* data)
-{
- usbh_device_t* dev = &_usbh_devices[dev_addr];
- const uint8_t rhport = dev->rhport;
-
- TU_ASSERT(osal_mutex_lock(dev->control.mutex_hdl, OSAL_TIMEOUT_NORMAL));
-
- dev->control.request = *request;
- dev->control.pipe_status = 0;
-
- // Setup Stage
- hcd_setup_send(rhport, dev_addr, (uint8_t*) &dev->control.request);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
-
- // Data stage : first data toggle is always 1
- if ( request->wLength )
- {
- hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), data, request->wLength);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
- }
-
- // Status : data toggle is always 1
- hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0);
- TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
-
- osal_mutex_unlock(dev->control.mutex_hdl);
-
- if ( XFER_RESULT_STALLED == dev->control.pipe_status ) return false;
- if ( XFER_RESULT_FAILED == dev->control.pipe_status ) return false;
-
- return true;
-}
-
-tusb_error_t usbh_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size)
-{
- osal_semaphore_reset( _usbh_devices[dev_addr].control.sem_hdl );
- //osal_mutex_reset( usbh_devices[dev_addr].control.mutex_hdl );
-
- tusb_desc_endpoint_t ep0_desc =
- {
- .bLength = sizeof(tusb_desc_endpoint_t),
- .bDescriptorType = TUSB_DESC_ENDPOINT,
- .bEndpointAddress = 0,
- .bmAttributes = { .xfer = TUSB_XFER_CONTROL },
- .wMaxPacketSize = { .size = max_packet_size },
- .bInterval = 0
- };
-
- hcd_edpt_open(_usbh_devices[dev_addr].rhport, dev_addr, &ep0_desc);
-
- return TUSB_ERROR_NONE;
-}
-
-//--------------------------------------------------------------------+
-// USBH-HCD ISR/Callback API
-//--------------------------------------------------------------------+
-// interrupt caused by a TD (with IOC=1) in pipe of class class_code
-void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
-{
- usbh_device_t* dev = &_usbh_devices[ dev_addr ];
-
- if (0 == tu_edpt_number(ep_addr))
- {
- dev->control.pipe_status = event;
-// usbh_devices[ pipe_hdl.dev_addr ].control.xferred_bytes = xferred_bytes; not yet neccessary
- osal_semaphore_post( dev->control.sem_hdl, true );
- }
- else
- {
- uint8_t drv_id = dev->ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)];
- TU_ASSERT(drv_id < USBH_CLASS_DRIVER_COUNT, );
-
- if (usbh_class_drivers[drv_id].isr)
- {
- usbh_class_drivers[drv_id].isr(dev_addr, ep_addr, event, xferred_bytes);
- }
- else
- {
- TU_BREAKPOINT(); // something wrong, no one claims the isr's source
- }
- }
-}
-
-void hcd_event_device_attach(uint8_t rhport)
-{
- hcd_event_t event =
- {
- .rhport = rhport,
- .event_id = HCD_EVENT_DEVICE_ATTACH
- };
-
- event.attach.hub_addr = 0;
- event.attach.hub_port = 0;
-
- hcd_event_handler(&event, true);
-}
-
-void hcd_event_handler(hcd_event_t const* event, bool in_isr)
-{
- switch (event->event_id)
- {
- default:
- osal_queue_send(_usbh_q, event, in_isr);
- break;
- }
-}
-
-void hcd_event_device_remove(uint8_t hostid)
-{
- hcd_event_t event =
- {
- .rhport = hostid,
- .event_id = HCD_EVENT_DEVICE_REMOVE
- };
-
- event.attach.hub_addr = 0;
- event.attach.hub_port = 0;
-
- hcd_event_handler(&event, true);
-}
-
-
-// a device unplugged on hostid, hub_addr, hub_port
-// return true if found and unmounted device, false if cannot find
-static void usbh_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port)
-{
- //------------- find the all devices (star-network) under port that is unplugged -------------//
- for (uint8_t dev_addr = 0; dev_addr <= CFG_TUSB_HOST_DEVICE_MAX; dev_addr ++)
- {
- usbh_device_t* dev = &_usbh_devices[dev_addr];
-
- // TODO Hub multiple level
- if (dev->rhport == rhport &&
- (hub_addr == 0 || dev->hub_addr == hub_addr) && // hub_addr == 0 & hub_port == 0 means roothub
- (hub_port == 0 || dev->hub_port == hub_port) &&
- dev->state != TUSB_DEVICE_STATE_UNPLUG)
- {
- // Invoke callback before close driver
- if (tuh_umount_cb) tuh_umount_cb(dev_addr);
-
- // Close class driver
- for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].close(dev_addr);
-
- memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
- memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
-
- hcd_device_close(rhport, dev_addr);
-
- dev->state = TUSB_DEVICE_STATE_UNPLUG;
- }
- }
-}
-
-//--------------------------------------------------------------------+
-// ENUMERATION TASK
-//--------------------------------------------------------------------+
-
-bool enum_task(hcd_event_t* event)
-{
- enum {
-#if 1
- // FIXME ohci LPC1769 xpresso + debugging to have 1st control xfer to work, some kind of timing or ohci driver issue !!!
- POWER_STABLE_DELAY = 100,
- RESET_DELAY = 500
-#else
- POWER_STABLE_DELAY = 500,
- RESET_DELAY = 200, // USB specs say only 50ms but many devices require much longer
-#endif
- };
-
- // for OSAL_NONE local variable won't retain value after blocking service sem_wait/queue_recv
- static uint8_t configure_selected = 1; // TODO move
-
- usbh_device_t* dev0 = &_usbh_devices[0];
- tusb_control_request_t request;
-
- dev0->rhport = event->rhport; // TODO refractor integrate to device_pool
- dev0->hub_addr = event->attach.hub_addr;
- dev0->hub_port = event->attach.hub_port;
- dev0->state = TUSB_DEVICE_STATE_UNPLUG;
-
- //------------- connected/disconnected directly with roothub -------------//
- if ( dev0->hub_addr == 0)
- {
- if( hcd_port_connect_status(dev0->rhport) )
- {
- // connection event
- osal_task_delay(POWER_STABLE_DELAY); // wait until device is stable. Increase this if the first 8 bytes is failed to get
-
- // exit if device unplugged while delaying
- if ( !hcd_port_connect_status(dev0->rhport) ) return true;
-
- hcd_port_reset( dev0->rhport ); // port must be reset to have correct speed operation
- osal_task_delay(RESET_DELAY);
-
- dev0->speed = hcd_port_speed_get( dev0->rhport );
- }
- else
- {
- // disconnection event
- usbh_device_unplugged(dev0->rhport, 0, 0);
- return true; // restart task
- }
- }
- #if CFG_TUH_HUB
- //------------- connected/disconnected via hub -------------//
- else
- {
- //------------- Get Port Status -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
- .bRequest = HUB_REQUEST_GET_STATUS,
- .wValue = 0,
- .wIndex = dev0->hub_port,
- .wLength = 4
- };
- // TODO hub refractor
- TU_VERIFY_HDLR( usbh_control_xfer( dev0->hub_addr, &request, _usbh_ctrl_buf ), hub_status_pipe_queue( dev0->hub_addr) );
-
- // Acknowledge Port Connection Change
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_CONNECTION_CHANGE);
-
- hub_port_status_response_t * p_port_status;
- p_port_status = ((hub_port_status_response_t *) _usbh_ctrl_buf);
-
- if ( ! p_port_status->status_change.connect_status ) return true; // only handle connection change
-
- if ( ! p_port_status->status_current.connect_status )
- {
- // Disconnection event
- usbh_device_unplugged(dev0->rhport, dev0->hub_addr, dev0->hub_port);
-
- (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
- return true; // restart task
- }
- else
- {
- // Connection Event
- TU_VERIFY_HDLR(hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port),
- hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
-
- dev0->speed = hub_port_get_speed();
-
- // Acknowledge Port Reset Change
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
- }
- }
- #endif
-
- TU_ASSERT_ERR( usbh_pipe_control_open(0, 8) );
-
- //------------- Get first 8 bytes of device descriptor to get Control Endpoint Size -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = TUSB_DESC_DEVICE << 8,
- .wIndex = 0,
- .wLength = 8
- };
- bool is_ok = usbh_control_xfer(0, &request, _usbh_ctrl_buf);
-
- //------------- Reset device again before Set Address -------------//
- if (dev0->hub_addr == 0)
- {
- // connected directly to roothub
- TU_ASSERT(is_ok); // TODO some slow device is observed to fail the very fist controller xfer, can try more times
- hcd_port_reset( dev0->rhport ); // reset port after 8 byte descriptor
- osal_task_delay(RESET_DELAY);
- }
- #if CFG_TUH_HUB
- else
- {
- // connected via a hub
- TU_VERIFY_HDLR(is_ok, hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
-
- if ( hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port) )
- {
- // Acknowledge Port Reset Change if Reset Successful
- hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
- }
-
- (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
- }
- #endif
-
- //------------- Set new address -------------//
- uint8_t const new_addr = get_new_address();
- TU_ASSERT(new_addr <= CFG_TUSB_HOST_DEVICE_MAX); // TODO notify application we reach max devices
-
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
- .bRequest = TUSB_REQ_SET_ADDRESS,
- .wValue = new_addr,
- .wIndex = 0,
- .wLength = 0
- };
- TU_ASSERT(usbh_control_xfer(0, &request, NULL));
-
- //------------- update port info & close control pipe of addr0 -------------//
- usbh_device_t* new_dev = &_usbh_devices[new_addr];
- new_dev->rhport = dev0->rhport;
- new_dev->hub_addr = dev0->hub_addr;
- new_dev->hub_port = dev0->hub_port;
- new_dev->speed = dev0->speed;
-
- hcd_device_close(dev0->rhport, 0); // close device 0
- dev0->state = TUSB_DEVICE_STATE_UNPLUG;
-
- // open control pipe for new address
- TU_ASSERT_ERR ( usbh_pipe_control_open(new_addr, ((tusb_desc_device_t*) _usbh_ctrl_buf)->bMaxPacketSize0 ) );
-
- //------------- Get full device descriptor -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = TUSB_DESC_DEVICE << 8,
- .wIndex = 0,
- .wLength = 18
- };
- TU_ASSERT(usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
-
- // update device info TODO alignment issue
- new_dev->vendor_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idVendor;
- new_dev->product_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idProduct;
- new_dev->configure_count = ((tusb_desc_device_t*) _usbh_ctrl_buf)->bNumConfigurations;
-
- configure_selected = get_configure_number_for_device((tusb_desc_device_t*) _usbh_ctrl_buf);
- TU_ASSERT(configure_selected <= new_dev->configure_count); // TODO notify application when invalid configuration
-
- //------------- Get 9 bytes of configuration descriptor -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
- .bRequest = TUSB_REQ_GET_DESCRIPTOR,
- .wValue = (TUSB_DESC_CONFIGURATION << 8) | (configure_selected - 1),
- .wIndex = 0,
- .wLength = 9
- };
- TU_ASSERT( usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
-
- // TODO not enough buffer to hold configuration descriptor
- TU_ASSERT( CFG_TUSB_HOST_ENUM_BUFFER_SIZE >= ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength );
-
- //------------- Get full configuration descriptor -------------//
- request.wLength = ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength; // full length
- TU_ASSERT( usbh_control_xfer( new_addr, &request, _usbh_ctrl_buf ) );
-
- // update configuration info
- new_dev->interface_count = ((tusb_desc_configuration_t*) _usbh_ctrl_buf)->bNumInterfaces;
-
- //------------- Set Configure -------------//
- request = (tusb_control_request_t ) {
- .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
- .bRequest = TUSB_REQ_SET_CONFIGURATION,
- .wValue = configure_selected,
- .wIndex = 0,
- .wLength = 0
- };
- TU_ASSERT(usbh_control_xfer( new_addr, &request, NULL ));
-
- new_dev->state = TUSB_DEVICE_STATE_CONFIGURED;
-
- //------------- TODO Get String Descriptors -------------//
-
- //------------- parse configuration & install drivers -------------//
- uint8_t const* p_desc = _usbh_ctrl_buf + sizeof(tusb_desc_configuration_t);
-
- // parse each interfaces
- while( p_desc < _usbh_ctrl_buf + ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength )
- {
- // skip until we see interface descriptor
- if ( TUSB_DESC_INTERFACE != tu_desc_type(p_desc) )
- {
- p_desc = tu_desc_next(p_desc); // skip the descriptor, increase by the descriptor's length
- }else
- {
- tusb_desc_interface_t* desc_itf = (tusb_desc_interface_t*) p_desc;
-
- // Check if class is supported
- uint8_t drv_id;
- for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
- {
- if ( usbh_class_drivers[drv_id].class_code == desc_itf->bInterfaceClass ) break;
- }
-
- if( drv_id >= USBH_CLASS_DRIVER_COUNT )
- {
- // skip unsupported class
- p_desc = tu_desc_next(p_desc);
- }
- else
- {
- // Interface number must not be used already TODO alternate interface
- TU_ASSERT( new_dev->itf2drv[desc_itf->bInterfaceNumber] == 0xff );
- new_dev->itf2drv[desc_itf->bInterfaceNumber] = drv_id;
-
- if (desc_itf->bInterfaceClass == TUSB_CLASS_HUB && new_dev->hub_addr != 0)
- {
- // TODO Attach hub to Hub is not currently supported
- // skip this interface
- p_desc = tu_desc_next(p_desc);
- }
- else
- {
- uint16_t itf_len = 0;
-
- if ( usbh_class_drivers[drv_id].open(new_dev->rhport, new_addr, desc_itf, &itf_len) )
- {
- mark_interface_endpoint(new_dev->ep2drv, p_desc, itf_len, drv_id);
- }
-
- TU_ASSERT( itf_len >= sizeof(tusb_desc_interface_t) );
- p_desc += itf_len;
- }
- }
- }
- }
-
- if (tuh_mount_cb) tuh_mount_cb(new_addr);
-
- return true;
-}
-
-/* USB Host Driver task
- * This top level thread manages all host controller event and delegates events to class-specific drivers.
- * This should be called periodically within the mainloop or rtos thread.
- *
- @code
- int main(void)
- {
- application_init();
- tusb_init();
-
- while(1) // the mainloop
- {
- application_code();
-
- tuh_task(); // tinyusb host task
- }
- }
- @endcode
- */
-void tuh_task(void)
-{
- // Skip if stack is not initialized
- if ( !tusb_inited() ) return;
-
- // Loop until there is no more events in the queue
- while (1)
- {
- hcd_event_t event;
- if ( !osal_queue_receive(_usbh_q, &event) ) return;
-
- switch (event.event_id)
- {
- case HCD_EVENT_DEVICE_ATTACH:
- case HCD_EVENT_DEVICE_REMOVE:
- enum_task(&event);
- break;
-
- default: break;
- }
- }
-}
-
-//--------------------------------------------------------------------+
-// INTERNAL HELPER
-//--------------------------------------------------------------------+
-static inline uint8_t get_new_address(void)
-{
- for (uint8_t addr=1; addr <= CFG_TUSB_HOST_DEVICE_MAX; addr++)
- {
- if (_usbh_devices[addr].state == TUSB_DEVICE_STATE_UNPLUG) return addr;
- }
- return CFG_TUSB_HOST_DEVICE_MAX+1;
-}
-
-static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc)
-{
- uint8_t config_num = 1;
-
- // invoke callback to ask user which configuration to select
- if (tuh_device_attached_cb)
- {
- config_num = tu_min8(1, tuh_device_attached_cb(dev_desc) );
- }
-
- return config_num;
-}
-
-// Helper marking endpoint of interface belongs to class driver
-// TODO merge with usbd
-static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id)
-{
- uint16_t len = 0;
-
- while( len < desc_len )
- {
- if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
- {
- uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress;
-
- ep2drv[ tu_edpt_number(ep_addr) ][ tu_edpt_dir(ep_addr) ] = driver_id;
- }
-
- len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
- }
-}
-
-
-#endif
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "common/tusb_common.h"
+
+#if TUSB_OPT_HOST_ENABLED
+
+#ifndef CFG_TUH_TASK_QUEUE_SZ
+#define CFG_TUH_TASK_QUEUE_SZ 16
+#endif
+
+//--------------------------------------------------------------------+
+// INCLUDE
+//--------------------------------------------------------------------+
+#include "tusb.h"
+#include "hub.h"
+#include "usbh_hcd.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+static host_class_driver_t const usbh_class_drivers[] =
+{
+ #if CFG_TUH_CDC
+ {
+ .class_code = TUSB_CLASS_CDC,
+ .init = cdch_init,
+ .open = cdch_open,
+ .isr = cdch_isr,
+ .close = cdch_close
+ },
+ #endif
+
+ #if CFG_TUH_MSC
+ {
+ .class_code = TUSB_CLASS_MSC,
+ .init = msch_init,
+ .open = msch_open,
+ .isr = msch_isr,
+ .close = msch_close
+ },
+ #endif
+
+ #if HOST_CLASS_HID
+ {
+ .class_code = TUSB_CLASS_HID,
+ .init = hidh_init,
+ .open = hidh_open_subtask,
+ .isr = hidh_isr,
+ .close = hidh_close
+ },
+ #endif
+
+ #if CFG_TUH_HUB
+ {
+ .class_code = TUSB_CLASS_HUB,
+ .init = hub_init,
+ .open = hub_open,
+ .isr = hub_isr,
+ .close = hub_close
+ },
+ #endif
+
+ #if CFG_TUH_VENDOR
+ {
+ .class_code = TUSB_CLASS_VENDOR_SPECIFIC,
+ .init = cush_init,
+ .open = cush_open_subtask,
+ .isr = cush_isr,
+ .close = cush_close
+ }
+ #endif
+};
+
+enum { USBH_CLASS_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) };
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+
+// including zero-address
+CFG_TUSB_MEM_SECTION usbh_device_t _usbh_devices[CFG_TUSB_HOST_DEVICE_MAX+1];
+
+// Event queue
+// role device/host is used by OS NONE for mutex (disable usb isr) only
+OSAL_QUEUE_DEF(OPT_MODE_HOST, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t);
+static osal_queue_t _usbh_q;
+
+CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _usbh_ctrl_buf[CFG_TUSB_HOST_ENUM_BUFFER_SIZE];
+
+//------------- Reporter Task Data -------------//
+
+//------------- Helper Function Prototypes -------------//
+static inline uint8_t get_new_address(void);
+static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc);
+static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id);
+
+//--------------------------------------------------------------------+
+// PUBLIC API (Parameter Verification is required)
+//--------------------------------------------------------------------+
+tusb_device_state_t tuh_device_get_state (uint8_t const dev_addr)
+{
+ TU_ASSERT( dev_addr <= CFG_TUSB_HOST_DEVICE_MAX, TUSB_DEVICE_STATE_UNPLUG);
+ return (tusb_device_state_t) _usbh_devices[dev_addr].state;
+}
+
+//--------------------------------------------------------------------+
+// CLASS-USBD API (don't require to verify parameters)
+//--------------------------------------------------------------------+
+bool usbh_init(void)
+{
+ tu_memclr(_usbh_devices, sizeof(usbh_device_t)*(CFG_TUSB_HOST_DEVICE_MAX+1));
+
+ //------------- Enumeration & Reporter Task init -------------//
+ _usbh_q = osal_queue_create( &_usbh_qdef );
+ TU_ASSERT(_usbh_q != NULL);
+
+ //------------- Semaphore, Mutex for Control Pipe -------------//
+ for(uint8_t i=0; i<CFG_TUSB_HOST_DEVICE_MAX+1; i++) // including address zero
+ {
+ usbh_device_t * const dev = &_usbh_devices[i];
+
+ dev->control.sem_hdl = osal_semaphore_create(&dev->control.sem_def);
+ TU_ASSERT(dev->control.sem_hdl != NULL);
+
+ dev->control.mutex_hdl = osal_mutex_create(&dev->control.mutex_def);
+ TU_ASSERT(dev->control.mutex_hdl != NULL);
+
+ memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
+ memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
+ }
+
+ // Class drivers init
+ for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].init();
+
+ TU_ASSERT(hcd_init());
+ hcd_int_enable(TUH_OPT_RHPORT);
+
+ return true;
+}
+
+//------------- USBH control transfer -------------//
+bool usbh_control_xfer (uint8_t dev_addr, tusb_control_request_t* request, uint8_t* data)
+{
+ usbh_device_t* dev = &_usbh_devices[dev_addr];
+ const uint8_t rhport = dev->rhport;
+
+ TU_ASSERT(osal_mutex_lock(dev->control.mutex_hdl, OSAL_TIMEOUT_NORMAL));
+
+ dev->control.request = *request;
+ dev->control.pipe_status = 0;
+
+ // Setup Stage
+ hcd_setup_send(rhport, dev_addr, (uint8_t*) &dev->control.request);
+ TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
+
+ // Data stage : first data toggle is always 1
+ if ( request->wLength )
+ {
+ hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), data, request->wLength);
+ TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
+ }
+
+ // Status : data toggle is always 1
+ hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0);
+ TU_VERIFY(osal_semaphore_wait(dev->control.sem_hdl, OSAL_TIMEOUT_NORMAL));
+
+ osal_mutex_unlock(dev->control.mutex_hdl);
+
+ if ( XFER_RESULT_STALLED == dev->control.pipe_status ) return false;
+ if ( XFER_RESULT_FAILED == dev->control.pipe_status ) return false;
+
+ return true;
+}
+
+tusb_error_t usbh_pipe_control_open(uint8_t dev_addr, uint8_t max_packet_size)
+{
+ osal_semaphore_reset( _usbh_devices[dev_addr].control.sem_hdl );
+ //osal_mutex_reset( usbh_devices[dev_addr].control.mutex_hdl );
+
+ tusb_desc_endpoint_t ep0_desc =
+ {
+ .bLength = sizeof(tusb_desc_endpoint_t),
+ .bDescriptorType = TUSB_DESC_ENDPOINT,
+ .bEndpointAddress = 0,
+ .bmAttributes = { .xfer = TUSB_XFER_CONTROL },
+ .wMaxPacketSize = { .size = max_packet_size },
+ .bInterval = 0
+ };
+
+ hcd_edpt_open(_usbh_devices[dev_addr].rhport, dev_addr, &ep0_desc);
+
+ return TUSB_ERROR_NONE;
+}
+
+//--------------------------------------------------------------------+
+// USBH-HCD ISR/Callback API
+//--------------------------------------------------------------------+
+// interrupt caused by a TD (with IOC=1) in pipe of class class_code
+void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
+{
+ usbh_device_t* dev = &_usbh_devices[ dev_addr ];
+
+ if (0 == tu_edpt_number(ep_addr))
+ {
+ dev->control.pipe_status = event;
+// usbh_devices[ pipe_hdl.dev_addr ].control.xferred_bytes = xferred_bytes; not yet neccessary
+ osal_semaphore_post( dev->control.sem_hdl, true );
+ }
+ else
+ {
+ uint8_t drv_id = dev->ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)];
+ TU_ASSERT(drv_id < USBH_CLASS_DRIVER_COUNT, );
+
+ if (usbh_class_drivers[drv_id].isr)
+ {
+ usbh_class_drivers[drv_id].isr(dev_addr, ep_addr, event, xferred_bytes);
+ }
+ else
+ {
+ TU_BREAKPOINT(); // something wrong, no one claims the isr's source
+ }
+ }
+}
+
+void hcd_event_device_attach(uint8_t rhport)
+{
+ hcd_event_t event =
+ {
+ .rhport = rhport,
+ .event_id = HCD_EVENT_DEVICE_ATTACH
+ };
+
+ event.attach.hub_addr = 0;
+ event.attach.hub_port = 0;
+
+ hcd_event_handler(&event, true);
+}
+
+void hcd_event_handler(hcd_event_t const* event, bool in_isr)
+{
+ switch (event->event_id)
+ {
+ default:
+ osal_queue_send(_usbh_q, event, in_isr);
+ break;
+ }
+}
+
+void hcd_event_device_remove(uint8_t hostid)
+{
+ hcd_event_t event =
+ {
+ .rhport = hostid,
+ .event_id = HCD_EVENT_DEVICE_REMOVE
+ };
+
+ event.attach.hub_addr = 0;
+ event.attach.hub_port = 0;
+
+ hcd_event_handler(&event, true);
+}
+
+
+// a device unplugged on hostid, hub_addr, hub_port
+// return true if found and unmounted device, false if cannot find
+static void usbh_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port)
+{
+ //------------- find the all devices (star-network) under port that is unplugged -------------//
+ for (uint8_t dev_addr = 0; dev_addr <= CFG_TUSB_HOST_DEVICE_MAX; dev_addr ++)
+ {
+ usbh_device_t* dev = &_usbh_devices[dev_addr];
+
+ // TODO Hub multiple level
+ if (dev->rhport == rhport &&
+ (hub_addr == 0 || dev->hub_addr == hub_addr) && // hub_addr == 0 & hub_port == 0 means roothub
+ (hub_port == 0 || dev->hub_port == hub_port) &&
+ dev->state != TUSB_DEVICE_STATE_UNPLUG)
+ {
+ // Invoke callback before close driver
+ if (tuh_umount_cb) tuh_umount_cb(dev_addr);
+
+ // Close class driver
+ for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) usbh_class_drivers[drv_id].close(dev_addr);
+
+ memset(dev->itf2drv, 0xff, sizeof(dev->itf2drv)); // invalid mapping
+ memset(dev->ep2drv , 0xff, sizeof(dev->ep2drv )); // invalid mapping
+
+ hcd_device_close(rhport, dev_addr);
+
+ dev->state = TUSB_DEVICE_STATE_UNPLUG;
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// ENUMERATION TASK
+//--------------------------------------------------------------------+
+
+bool enum_task(hcd_event_t* event)
+{
+ enum {
+#if 1
+ // FIXME ohci LPC1769 xpresso + debugging to have 1st control xfer to work, some kind of timing or ohci driver issue !!!
+ POWER_STABLE_DELAY = 100,
+ RESET_DELAY = 500
+#else
+ POWER_STABLE_DELAY = 500,
+ RESET_DELAY = 200, // USB specs say only 50ms but many devices require much longer
+#endif
+ };
+
+ // for OSAL_NONE local variable won't retain value after blocking service sem_wait/queue_recv
+ static uint8_t configure_selected = 1; // TODO move
+
+ usbh_device_t* dev0 = &_usbh_devices[0];
+ tusb_control_request_t request;
+
+ dev0->rhport = event->rhport; // TODO refractor integrate to device_pool
+ dev0->hub_addr = event->attach.hub_addr;
+ dev0->hub_port = event->attach.hub_port;
+ dev0->state = TUSB_DEVICE_STATE_UNPLUG;
+
+ //------------- connected/disconnected directly with roothub -------------//
+ if ( dev0->hub_addr == 0)
+ {
+ if( hcd_port_connect_status(dev0->rhport) )
+ {
+ // connection event
+ osal_task_delay(POWER_STABLE_DELAY); // wait until device is stable. Increase this if the first 8 bytes is failed to get
+
+ // exit if device unplugged while delaying
+ if ( !hcd_port_connect_status(dev0->rhport) ) return true;
+
+ hcd_port_reset( dev0->rhport ); // port must be reset to have correct speed operation
+ osal_task_delay(RESET_DELAY);
+
+ dev0->speed = hcd_port_speed_get( dev0->rhport );
+ }
+ else
+ {
+ // disconnection event
+ usbh_device_unplugged(dev0->rhport, 0, 0);
+ return true; // restart task
+ }
+ }
+ #if CFG_TUH_HUB
+ //------------- connected/disconnected via hub -------------//
+ else
+ {
+ //------------- Get Port Status -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_OTHER, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN },
+ .bRequest = HUB_REQUEST_GET_STATUS,
+ .wValue = 0,
+ .wIndex = dev0->hub_port,
+ .wLength = 4
+ };
+ // TODO hub refractor
+ TU_VERIFY_HDLR( usbh_control_xfer( dev0->hub_addr, &request, _usbh_ctrl_buf ), hub_status_pipe_queue( dev0->hub_addr) );
+
+ // Acknowledge Port Connection Change
+ hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_CONNECTION_CHANGE);
+
+ hub_port_status_response_t * p_port_status;
+ p_port_status = ((hub_port_status_response_t *) _usbh_ctrl_buf);
+
+ if ( ! p_port_status->status_change.connect_status ) return true; // only handle connection change
+
+ if ( ! p_port_status->status_current.connect_status )
+ {
+ // Disconnection event
+ usbh_device_unplugged(dev0->rhport, dev0->hub_addr, dev0->hub_port);
+
+ (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
+ return true; // restart task
+ }
+ else
+ {
+ // Connection Event
+ TU_VERIFY_HDLR(hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port),
+ hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
+
+ dev0->speed = hub_port_get_speed();
+
+ // Acknowledge Port Reset Change
+ hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
+ }
+ }
+ #endif
+
+ TU_ASSERT_ERR( usbh_pipe_control_open(0, 8) );
+
+ //------------- Get first 8 bytes of device descriptor to get Control Endpoint Size -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
+ .bRequest = TUSB_REQ_GET_DESCRIPTOR,
+ .wValue = TUSB_DESC_DEVICE << 8,
+ .wIndex = 0,
+ .wLength = 8
+ };
+ bool is_ok = usbh_control_xfer(0, &request, _usbh_ctrl_buf);
+
+ //------------- Reset device again before Set Address -------------//
+ if (dev0->hub_addr == 0)
+ {
+ // connected directly to roothub
+ TU_ASSERT(is_ok); // TODO some slow device is observed to fail the very fist controller xfer, can try more times
+ hcd_port_reset( dev0->rhport ); // reset port after 8 byte descriptor
+ osal_task_delay(RESET_DELAY);
+ }
+ #if CFG_TUH_HUB
+ else
+ {
+ // connected via a hub
+ TU_VERIFY_HDLR(is_ok, hub_status_pipe_queue( dev0->hub_addr) ); // TODO hub refractor
+
+ if ( hub_port_reset_subtask(dev0->hub_addr, dev0->hub_port) )
+ {
+ // Acknowledge Port Reset Change if Reset Successful
+ hub_port_clear_feature_subtask(dev0->hub_addr, dev0->hub_port, HUB_FEATURE_PORT_RESET_CHANGE);
+ }
+
+ (void) hub_status_pipe_queue( dev0->hub_addr ); // done with hub, waiting for next data on status pipe
+ }
+ #endif
+
+ //------------- Set new address -------------//
+ uint8_t const new_addr = get_new_address();
+ TU_ASSERT(new_addr <= CFG_TUSB_HOST_DEVICE_MAX); // TODO notify application we reach max devices
+
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
+ .bRequest = TUSB_REQ_SET_ADDRESS,
+ .wValue = new_addr,
+ .wIndex = 0,
+ .wLength = 0
+ };
+ TU_ASSERT(usbh_control_xfer(0, &request, NULL));
+
+ //------------- update port info & close control pipe of addr0 -------------//
+ usbh_device_t* new_dev = &_usbh_devices[new_addr];
+ new_dev->rhport = dev0->rhport;
+ new_dev->hub_addr = dev0->hub_addr;
+ new_dev->hub_port = dev0->hub_port;
+ new_dev->speed = dev0->speed;
+
+ hcd_device_close(dev0->rhport, 0); // close device 0
+ dev0->state = TUSB_DEVICE_STATE_UNPLUG;
+
+ // open control pipe for new address
+ TU_ASSERT_ERR ( usbh_pipe_control_open(new_addr, ((tusb_desc_device_t*) _usbh_ctrl_buf)->bMaxPacketSize0 ) );
+
+ //------------- Get full device descriptor -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
+ .bRequest = TUSB_REQ_GET_DESCRIPTOR,
+ .wValue = TUSB_DESC_DEVICE << 8,
+ .wIndex = 0,
+ .wLength = 18
+ };
+ TU_ASSERT(usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
+
+ // update device info TODO alignment issue
+ new_dev->vendor_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idVendor;
+ new_dev->product_id = ((tusb_desc_device_t*) _usbh_ctrl_buf)->idProduct;
+ new_dev->configure_count = ((tusb_desc_device_t*) _usbh_ctrl_buf)->bNumConfigurations;
+
+ configure_selected = get_configure_number_for_device((tusb_desc_device_t*) _usbh_ctrl_buf);
+ TU_ASSERT(configure_selected <= new_dev->configure_count); // TODO notify application when invalid configuration
+
+ //------------- Get 9 bytes of configuration descriptor -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN },
+ .bRequest = TUSB_REQ_GET_DESCRIPTOR,
+ .wValue = (TUSB_DESC_CONFIGURATION << 8) | (configure_selected - 1),
+ .wIndex = 0,
+ .wLength = 9
+ };
+ TU_ASSERT( usbh_control_xfer(new_addr, &request, _usbh_ctrl_buf));
+
+ // TODO not enough buffer to hold configuration descriptor
+ TU_ASSERT( CFG_TUSB_HOST_ENUM_BUFFER_SIZE >= ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength );
+
+ //------------- Get full configuration descriptor -------------//
+ request.wLength = ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength; // full length
+ TU_ASSERT( usbh_control_xfer( new_addr, &request, _usbh_ctrl_buf ) );
+
+ // update configuration info
+ new_dev->interface_count = ((tusb_desc_configuration_t*) _usbh_ctrl_buf)->bNumInterfaces;
+
+ //------------- Set Configure -------------//
+ request = (tusb_control_request_t ) {
+ .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_OUT },
+ .bRequest = TUSB_REQ_SET_CONFIGURATION,
+ .wValue = configure_selected,
+ .wIndex = 0,
+ .wLength = 0
+ };
+ TU_ASSERT(usbh_control_xfer( new_addr, &request, NULL ));
+
+ new_dev->state = TUSB_DEVICE_STATE_CONFIGURED;
+
+ //------------- TODO Get String Descriptors -------------//
+
+ //------------- parse configuration & install drivers -------------//
+ uint8_t const* p_desc = _usbh_ctrl_buf + sizeof(tusb_desc_configuration_t);
+
+ // parse each interfaces
+ while( p_desc < _usbh_ctrl_buf + ((tusb_desc_configuration_t*)_usbh_ctrl_buf)->wTotalLength )
+ {
+ // skip until we see interface descriptor
+ if ( TUSB_DESC_INTERFACE != tu_desc_type(p_desc) )
+ {
+ p_desc = tu_desc_next(p_desc); // skip the descriptor, increase by the descriptor's length
+ }else
+ {
+ tusb_desc_interface_t* desc_itf = (tusb_desc_interface_t*) p_desc;
+
+ // Check if class is supported
+ uint8_t drv_id;
+ for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++)
+ {
+ if ( usbh_class_drivers[drv_id].class_code == desc_itf->bInterfaceClass ) break;
+ }
+
+ if( drv_id >= USBH_CLASS_DRIVER_COUNT )
+ {
+ // skip unsupported class
+ p_desc = tu_desc_next(p_desc);
+ }
+ else
+ {
+ // Interface number must not be used already TODO alternate interface
+ TU_ASSERT( new_dev->itf2drv[desc_itf->bInterfaceNumber] == 0xff );
+ new_dev->itf2drv[desc_itf->bInterfaceNumber] = drv_id;
+
+ if (desc_itf->bInterfaceClass == TUSB_CLASS_HUB && new_dev->hub_addr != 0)
+ {
+ // TODO Attach hub to Hub is not currently supported
+ // skip this interface
+ p_desc = tu_desc_next(p_desc);
+ }
+ else
+ {
+ uint16_t itf_len = 0;
+
+ if ( usbh_class_drivers[drv_id].open(new_dev->rhport, new_addr, desc_itf, &itf_len) )
+ {
+ mark_interface_endpoint(new_dev->ep2drv, p_desc, itf_len, drv_id);
+ }
+
+ TU_ASSERT( itf_len >= sizeof(tusb_desc_interface_t) );
+ p_desc += itf_len;
+ }
+ }
+ }
+ }
+
+ if (tuh_mount_cb) tuh_mount_cb(new_addr);
+
+ return true;
+}
+
+/* USB Host Driver task
+ * This top level thread manages all host controller event and delegates events to class-specific drivers.
+ * This should be called periodically within the mainloop or rtos thread.
+ *
+ @code
+ int main(void)
+ {
+ application_init();
+ tusb_init();
+
+ while(1) // the mainloop
+ {
+ application_code();
+
+ tuh_task(); // tinyusb host task
+ }
+ }
+ @endcode
+ */
+void tuh_task(void)
+{
+ // Skip if stack is not initialized
+ if ( !tusb_inited() ) return;
+
+ // Loop until there is no more events in the queue
+ while (1)
+ {
+ hcd_event_t event;
+ if ( !osal_queue_receive(_usbh_q, &event) ) return;
+
+ switch (event.event_id)
+ {
+ case HCD_EVENT_DEVICE_ATTACH:
+ case HCD_EVENT_DEVICE_REMOVE:
+ enum_task(&event);
+ break;
+
+ default: break;
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// INTERNAL HELPER
+//--------------------------------------------------------------------+
+static inline uint8_t get_new_address(void)
+{
+ for (uint8_t addr=1; addr <= CFG_TUSB_HOST_DEVICE_MAX; addr++)
+ {
+ if (_usbh_devices[addr].state == TUSB_DEVICE_STATE_UNPLUG) return addr;
+ }
+ return CFG_TUSB_HOST_DEVICE_MAX+1;
+}
+
+static inline uint8_t get_configure_number_for_device(tusb_desc_device_t* dev_desc)
+{
+ uint8_t config_num = 1;
+
+ // invoke callback to ask user which configuration to select
+ if (tuh_device_attached_cb)
+ {
+ config_num = tu_min8(1, tuh_device_attached_cb(dev_desc) );
+ }
+
+ return config_num;
+}
+
+// Helper marking endpoint of interface belongs to class driver
+// TODO merge with usbd
+static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id)
+{
+ uint16_t len = 0;
+
+ while( len < desc_len )
+ {
+ if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
+ {
+ uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress;
+
+ ep2drv[ tu_edpt_number(ep_addr) ][ tu_edpt_dir(ep_addr) ] = driver_id;
+ }
+
+ len += tu_desc_len(p_desc);
+ p_desc = tu_desc_next(p_desc);
+ }
+}
+
+
+#endif
diff --git a/src/host/usbh.h b/src/host/usbh.h
index 311b2514d..42a2bd095 100644
--- a/src/host/usbh.h
+++ b/src/host/usbh.h
@@ -68,6 +68,9 @@ typedef struct {
//--------------------------------------------------------------------+
void tuh_task(void);
+// Interrupt handler, name alias to HCD
+#define tuh_isr hcd_isr
+
tusb_device_state_t tuh_device_get_state (uint8_t dev_addr);
static inline bool tuh_device_is_configured(uint8_t dev_addr)
{