Преглед на файлове

add ehci and synopsys hcd driver

sakumisu преди 4 години
родител
ревизия
b5964103e2
променени са 4 файла, в които са добавени 4533 реда и са изтрити 0 реда
  1. 213 0
      common/usb_hc.h
  2. 2833 0
      port/ehci/usb_ehci.c
  3. 1006 0
      port/ehci/usb_ehci.h
  4. 481 0
      port/synopsys/usb_hc_synopsys.c

+ 213 - 0
common/usb_hc.h

@@ -0,0 +1,213 @@
+/**
+ * @file usb_hc.h
+ * @brief
+ *
+ * Copyright (c) 2022 sakumisu
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _USB_HC_H
+#define _USB_HC_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*usbh_asynch_callback_t)(void *arg, int nbytes);
+typedef void *usbh_epinfo_t;
+/**
+ * @brief USB Endpoint Configuration.
+ *
+ * Structure containing the USB endpoint configuration.
+ */
+struct usbh_endpoint_cfg {
+    struct usbh_hubport *hport;
+    /** The number associated with the EP in the device
+     *  configuration structure
+     *       IN  EP = 0x80 | \<endpoint number\>
+     *       OUT EP = 0x00 | \<endpoint number\>
+     */
+    uint8_t ep_addr;
+    /** Endpoint Transfer Type.
+     * May be Bulk, Interrupt, Control or Isochronous
+     */
+    uint8_t ep_type;
+    uint8_t ep_interval;
+    /** Endpoint max packet size */
+    uint16_t ep_mps;
+};
+
+/**
+ * @brief USB Host Core Layer API
+ * @defgroup _usb_host_core_api USB Host Core API
+ * @{
+ */
+
+/**
+ * @brief usb host controller hardware init.
+ *
+ * @return int
+ */
+int usb_hc_init(void);
+
+/**
+ * @brief reset roothub port
+ *
+ * @param port port index
+ * @return int
+ */
+int usbh_reset_port(const uint8_t port);
+
+/**
+ * @brief get roothub port speed
+ *
+ * @param port port index
+ * @return return 0 means USB_SPEED_LOW, 1 means USB_SPEED_FULL and 2 means USB_SPEED_HIGH.
+ */
+uint8_t usbh_get_port_speed(const uint8_t port);
+
+/**
+ * @brief reconfig control endpoint.
+ *
+ * @param ep A memory location provided by the caller.
+ * @param dev_addr device address.
+ * @param ep_mps control endpoint max packet size.
+ * @param speed port speed
+ * @return On success will return 0, and others indicate fail.
+ */
+int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed);
+
+/**
+ * @brief Allocate and config endpoint
+ *
+ * @param ep A memory location provided by the caller in which to save the allocated endpoint info.
+ * @param ep_cfg Describes the endpoint info to be allocated.
+ * @return  On success will return 0, and others indicate fail.
+ */
+int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg);
+
+/**
+ * @brief Free a memory in which saves endpoint info.
+ *
+ * @param ep A memory location provided by the caller in which to free the allocated endpoint info.
+ * @return On success will return 0, and others indicate fail.
+ */
+int usbh_ep_free(usbh_epinfo_t ep);
+
+/**
+ * @brief Perform a control transfer.
+ * This is a blocking method; this method will not return until the transfer has completed.
+ *
+ * @param ep The control endpoint to send/receive the control request.
+ * @param setup Setup packet to be sent.
+ * @param buffer buffer used for sending the request and for returning any responses.  This buffer must be large enough to hold the length value
+ *  in the request description.
+ * @return On success will return 0, and others indicate fail.
+ */
+int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer);
+
+/**
+ * @brief  Process a request to handle a transfer descriptor.  This method will
+ * enqueue the transfer request and wait for it to complete.  Only one transfer may be queued;
+ * This is a blocking method; this method will not return until the transfer has completed.
+ *
+ * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
+ * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
+ * @param buflen The length of the data to be sent or received.
+ * @return On success, a non-negative value is returned that indicates the number
+ *   of bytes successfully transferred.  On a failure, a negated errno value
+ *   is returned that indicates the nature of the failure:
+ *
+ *     EAGAIN - If devices NAKs the transfer (or NYET or other error where
+ *              it may be appropriate to restart the entire transaction).
+ *     EPERM  - If the endpoint stalls
+ *     EIO    - On a TX or data toggle error
+ *     EPIPE  - Overrun errors
+ *
+ */
+int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen);
+
+/**
+ * @brief  Process a request to handle a transfer descriptor.  This method will
+ * enqueue the transfer request and wait for it to complete.  Only one transfer may be queued;
+ * This is a blocking method; this method will not return until the transfer has completed.
+ *
+ * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
+ * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
+ * @param buflen The length of the data to be sent or received.
+ * @return On success, a non-negative value is returned that indicates the number
+ *   of bytes successfully transferred.  On a failure, a negated errno value
+ *   is returned that indicates the nature of the failure:
+ *
+ *     EAGAIN - If devices NAKs the transfer (or NYET or other error where
+ *              it may be appropriate to restart the entire transaction).
+ *     EPERM  - If the endpoint stalls
+ *     EIO    - On a TX or data toggle error
+ *     EPIPE  - Overrun errors
+ *
+ */
+int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen);
+
+/**
+ * @brief Process a request to handle a transfer asynchronously.  This method
+ * will enqueue the transfer request and return immediately.  Only one transfer may be queued on a given endpoint
+ * When the transfer completes, the callback will be invoked with the provided argument.
+ *
+ * This method is useful for receiving interrupt transfers which may come infrequently.
+ *
+ * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
+ * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
+ * @param buflen The length of the data to be sent or received.
+ * @param callback This function will be called when the transfer completes.
+ * @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes.
+ *
+ * @return On success will return 0, and others indicate fail.
+ */
+int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg);
+
+/**
+ * @brief Process a request to handle a transfer asynchronously.  This method
+ * will enqueue the transfer request and return immediately.  Only one transfer may be queued on a given endpoint
+ * When the transfer completes, the callback will be invoked with the provided argument.
+ *
+ * This method is useful for receiving interrupt transfers which may come infrequently.
+ *
+ * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
+ * @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
+ * @param buflen The length of the data to be sent or received.
+ * @param callback This function will be called when the transfer completes.
+ * @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes.
+ *
+ * @return On success will return 0, and others indicate fail.
+ */
+int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg);
+
+/**
+ * @brief Cancel any pending syncrhonous or asynchronous transfer on an endpoint.
+ *
+ * @param ep The IN or OUT endpoint descriptor for the device endpoint on which to cancel.
+ * @return On success will return 0, and others indicate fail.
+ */
+int usb_ep_cancel(usbh_epinfo_t ep);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 2833 - 0
port/ehci/usb_ehci.c

@@ -0,0 +1,2833 @@
+#include "usbh_core.h"
+#include "usb_ehci.h"
+
+#define DEBUGASSERT(f)
+
+#define BL_USBOTG_HCCR_BASE (0x20072000)
+#define BL_USBOTG_HCOR_BASE (0x20072000 + 0x10)
+
+/* Configurable number of Queue Head (QH) structures.  The default is one per
+ * Root hub port plus one for EP0.
+ */
+
+#ifndef CONFIG_USB_EHCI_QH_NUM
+#define CONFIG_USB_EHCI_QH_NUM (CONFIG_USBHOST_RHPORTS + 1)
+#endif
+
+/* Configurable number of Queue Element Transfer Descriptor (qTDs).  The
+ * default is one per root hub plus three from EP0.
+ */
+
+#ifndef CONFIG_USB_EHCI_QTD_NUM
+#define CONFIG_USB_EHCI_QTD_NUM (CONFIG_USBHOST_RHPORTS + 3)
+#endif
+
+/* Registers ****************************************************************/
+
+/* Traditionally, NuttX specifies register locations using individual
+ * register offsets from a base address.  That tradition is broken here and,
+ * instead, register blocks are represented as structures.  This is done here
+ * because, in principle, EHCI operational register address may not be known
+ * at compile time; the operational registers lie at an offset specified in
+ * the 'caplength' byte of the Host Controller Capability Registers.
+ *
+ * However, for the case of the LPC43 EHCI, we know apriori that locations
+ * of these register blocks.
+ */
+
+/* Host Controller Capability Registers */
+
+#define HCCR ((struct ehci_hccr_s *)BL_USBOTG_HCCR_BASE)
+
+/* Host Controller Operational Registers */
+
+#define HCOR ((volatile struct ehci_hcor_s *)BL_USBOTG_HCOR_BASE)
+
+/* Interrupts ***************************************************************/
+
+/* This is the set of interrupts handled by this driver */
+
+#define EHCI_HANDLED_INTS (EHCI_INT_USBINT | EHCI_INT_USBERRINT | \
+                           EHCI_INT_PORTSC | EHCI_INT_SYSERROR |  \
+                           EHCI_INT_AAINT)
+
+/* The periodic frame list is a 4K-page aligned array of Frame List Link
+ * pointers. The length of the frame list may be programmable.  The
+ * programmability of the periodic frame list is exported to system software
+ * via the HCCPARAMS register. If non-programmable, the length is 1024
+ * elements. If programmable, the length can be selected by system software
+ * as one of 256, 512, or 1024 elements.
+ */
+
+#define FRAME_LIST_SIZE 1024
+
+/* DMA **********************************************************************/
+
+/* For now, we are assuming an identity mapping between physical and virtual
+ * address spaces.
+ */
+
+#define usb_ehci_physramaddr(a) (a)
+#define usb_ehci_virtramaddr(a) (a)
+
+/* Port numbers */
+// #define RHPNDX(rh) (rh)
+// #define RHPORT(rh) (RHPNDX(rh) + 1)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Internal representation of the EHCI Queue Head (QH) */
+
+struct usb_ehci_epinfo_s;
+struct usb_ehci_qh_s {
+    /* Fields visible to hardware */
+
+    struct ehci_qh_s hw; /* Hardware representation of the queue head */
+
+    /* Internal fields used by the EHCI driver */
+
+    struct usb_ehci_epinfo_s *epinfo; /* Endpoint used for the transfer */
+    uint32_t fqp;                     /* First qTD in the list (physical address) */
+    uint8_t pad[8];                   /* Padding to assure 32-byte alignment */
+};
+
+/* Internal representation of the EHCI Queue Element Transfer Descriptor
+ * (qTD)
+ */
+
+struct usb_ehci_qtd_s {
+    /* Fields visible to hardware */
+
+    struct ehci_qtd_s hw; /* Hardware representation of the queue head */
+
+    /* Internal fields used by the EHCI driver */
+};
+
+/* The following is used to manage lists of free QHs and qTDs */
+
+struct usb_ehci_list_s {
+    struct usb_ehci_list_s *flink; /* Link to next entry in the list */
+                                   /* Variable length entry data follows */
+};
+
+/* List traversal callout functions */
+
+typedef int (*foreach_qh_t)(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg);
+typedef int (*foreach_qtd_t)(struct usb_ehci_qtd_s *qtd, uint32_t **bp, void *arg);
+
+/* This structure describes one endpoint. */
+
+struct usb_ehci_epinfo_s {
+    uint8_t epno    : 7; /* Endpoint number */
+    uint8_t dirin   : 1; /* 1:IN endpoint 0:OUT endpoint */
+    uint8_t devaddr : 7; /* Device address */
+    uint8_t toggle  : 1; /* Next data toggle */
+#ifndef CONFIG_USBHOST_INT_DISABLE
+    uint8_t interval; /* Polling interval */
+#endif
+    uint8_t status;          /* Retained token status bits (for debug purposes) */
+    volatile bool iocwait;   /* TRUE: Thread is waiting for transfer completion */
+    uint16_t maxpacket : 11; /* Maximum packet size */
+    uint16_t xfrtype   : 2;  /* See USB_EP_ATTR_XFER_* definitions in usb.h */
+    uint16_t speed     : 2;  /* See USB_*_SPEED definitions in ehci.h */
+    int result;              /* The result of the transfer */
+    uint32_t xfrd;           /* On completion, will hold the number of bytes transferred */
+
+    usb_osal_sem_t iocsem; /* Semaphore used to wait for transfer completion */
+#ifdef CONFIG_USBHOST_ASYNCH
+    usbh_asynch_callback_t callback; /* Transfer complete callback */
+    void *arg;                       /* Argument that accompanies the callback */
+#endif
+    struct usbh_hubport *hport;
+};
+
+/* This structure retains the overall state of the USB host controller */
+
+struct usb_ehci_s {
+    volatile bool connected;  /* Connected to device */
+    usb_osal_mutex_t exclsem; /* Support mutually exclusive access */
+    struct usb_work work;
+
+    struct usb_ehci_list_s *qhfree;  /* List of free Queue Head (QH) structures */
+    struct usb_ehci_list_s *qtdfree; /* List of free Queue Element Transfer Descriptor (qTD) */
+
+    struct usb_ehci_epinfo_s ep0[CONFIG_USBHOST_RHPORTS]; /* EP0 endpoint info */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Register operations ******************************************************/
+
+static uint16_t usb_ehci_read16(const uint8_t *addr);
+static uint32_t usb_ehci_read32(const uint8_t *addr);
+#if 0 /* Not used */
+static void usb_ehci_write16(uint16_t memval, uint8_t *addr);
+static void usb_ehci_write32(uint32_t memval, uint8_t *addr);
+#endif
+
+#ifdef CONFIG_ENDIAN_BIG
+static uint16_t usb_ehci_swap16(uint16_t value);
+static uint32_t usb_ehci_swap32(uint32_t value);
+#else
+#define usb_ehci_swap16(value) (value)
+#define usb_ehci_swap32(value) (value)
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* In this driver implementation, support is provided for only a single
+ * USB device.  All status information can be simply retained in a single
+ * global instance.
+ */
+
+/* Maps USB chapter 9 speed to EHCI speed */
+
+static const uint8_t g_ehci_speed[4] = {
+    0, EHCI_LOW_SPEED, EHCI_FULL_SPEED, EHCI_HIGH_SPEED
+};
+
+/* In this driver implementation, support is provided for only a single
+ * USB device.  All status information can be simply retained in a single
+ * global instance.
+ */
+
+static struct usb_ehci_s g_ehci;
+
+/* The head of the asynchronous queue */
+
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_asynchead __attribute__((aligned(32)));
+
+/* The head of the periodic queue */
+
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_intrhead __attribute__((aligned(32)));
+
+/* Queue Head (QH) pool */
+
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_qhpool[CONFIG_USB_EHCI_QH_NUM] __attribute__((aligned(32)));
+
+/* Queue Element Transfer Descriptor (qTD) pool */
+
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qtd_s g_qtdpool[CONFIG_USB_EHCI_QTD_NUM] __attribute__((aligned(32)));
+
+/* The frame list */
+USB_NOCACHE_RAM_SECTION uint32_t g_framelist[FRAME_LIST_SIZE] __attribute__((aligned(4096)));
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: usb_ehci_read16
+ *
+ * Description:
+ *   Read 16-bit little endian data
+ *
+ ****************************************************************************/
+
+static uint16_t usb_ehci_read16(const uint8_t *addr)
+{
+#ifdef CONFIG_ENDIAN_BIG
+    return (uint16_t)addr[0] << 8 | (uint16_t)addr[1];
+#else
+    return (uint16_t)addr[1] << 8 | (uint16_t)addr[0];
+#endif
+}
+
+/****************************************************************************
+ * Name: usb_ehci_read32
+ *
+ * Description:
+ *   Read 32-bit little endian data
+ *
+ ****************************************************************************/
+
+static inline uint32_t usb_ehci_read32(const uint8_t *addr)
+{
+#ifdef CONFIG_ENDIAN_BIG
+    return (uint32_t)usb_ehci_read16(&addr[0]) << 16 |
+           (uint32_t)usb_ehci_read16(&addr[2]);
+#else
+    return (uint32_t)usb_ehci_read16(&addr[2]) << 16 |
+           (uint32_t)usb_ehci_read16(&addr[0]);
+#endif
+}
+
+/****************************************************************************
+ * Name: usb_ehci_swap16
+ *
+ * Description:
+ *   Swap bytes on a 16-bit value
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_ENDIAN_BIG
+static uint16_t usb_ehci_swap16(uint16_t value)
+{
+    return ((value >> 8) & 0xff) | ((value & 0xff) << 8);
+}
+#endif
+
+/****************************************************************************
+ * Name: usb_ehci_swap32
+ *
+ * Description:
+ *   Swap bytes on a 32-bit value
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_ENDIAN_BIG
+static uint32_t usb_ehci_swap32(uint32_t value)
+{
+    return (uint32_t)usb_ehci_swap16((uint16_t)((value >> 16) & 0xffff)) |
+           (uint32_t)usb_ehci_swap16((uint16_t)(value & 0xffff)) << 16;
+}
+#endif
+
+static inline void usb_ehci_putreg(uint32_t regval, volatile uint32_t *regaddr)
+{
+    *regaddr = regval;
+}
+
+static inline uint32_t usb_ehci_getreg(volatile uint32_t *regaddr)
+{
+    return *regaddr;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_alloc
+ *
+ * Description:
+ *   Allocate a Queue Head (QH) structure by removing it from the free list
+ *
+ * Assumption:  Caller holds the exclsem
+ *
+ ****************************************************************************/
+
+static struct usb_ehci_qh_s *usb_ehci_qh_alloc(void)
+{
+    struct usb_ehci_qh_s *qh;
+
+    /* Remove the QH structure from the freelist */
+
+    qh = (struct usb_ehci_qh_s *)g_ehci.qhfree;
+    if (qh) {
+        g_ehci.qhfree = ((struct usb_ehci_list_s *)qh)->flink;
+        memset(qh, 0, sizeof(struct usb_ehci_qh_s));
+    }
+
+    return qh;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_free
+ *
+ * Description:
+ *   Free a Queue Head (QH) structure by returning it to the free list
+ *
+ * Assumption:  Caller holds the exclsem
+ *
+ ****************************************************************************/
+
+static void usb_ehci_qh_free(struct usb_ehci_qh_s *qh)
+{
+    struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qh;
+
+    /* Put the QH structure back into the free list */
+
+    entry->flink = g_ehci.qhfree;
+    g_ehci.qhfree = entry;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_alloc
+ *
+ * Description:
+ *   Allocate a Queue Element Transfer Descriptor (qTD) by removing it from
+ *   the free list
+ *
+ * Assumption:  Caller holds the exclsem
+ *
+ ****************************************************************************/
+
+static struct usb_ehci_qtd_s *usb_ehci_qtd_alloc(void)
+{
+    struct usb_ehci_qtd_s *qtd;
+
+    /* Remove the qTD from the freelist */
+
+    qtd = (struct usb_ehci_qtd_s *)g_ehci.qtdfree;
+    if (qtd) {
+        g_ehci.qtdfree = ((struct usb_ehci_list_s *)qtd)->flink;
+        memset(qtd, 0, sizeof(struct usb_ehci_qtd_s));
+    }
+
+    return qtd;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_free
+ *
+ * Description:
+ *   Free a Queue Element Transfer Descriptor (qTD) by returning it to the
+ *   free list
+ *
+ * Assumption:  Caller holds the exclsem
+ *
+ ****************************************************************************/
+
+static void usb_ehci_qtd_free(struct usb_ehci_qtd_s *qtd)
+{
+    struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qtd;
+
+    /* Put the qTD back into the free list */
+
+    entry->flink = g_ehci.qtdfree;
+    g_ehci.qtdfree = entry;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_foreach
+ *
+ * Description:
+ *   Give the first entry in a list of Queue Head (QH) structures, call the
+ *   handler for each QH structure in the list (including the one at the head
+ *   of the list).
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qh_foreach(struct usb_ehci_qh_s *qh, uint32_t **bp,
+                               foreach_qh_t handler, void *arg)
+{
+    struct usb_ehci_qh_s *next;
+    uintptr_t physaddr;
+    int ret;
+
+    DEBUGASSERT(qh && handler);
+    while (qh) {
+        /* Is this the end of the list?  Check the horizontal link pointer
+       * (HLP) terminate (T) bit.  If T==1, then the HLP address is not
+       * valid.
+       */
+
+        physaddr = usb_ehci_swap32(qh->hw.hlp);
+
+        if ((physaddr & QH_HLP_T) != 0) {
+            /* Set the next pointer to NULL.  This will terminate the loop. */
+
+            next = NULL;
+        }
+
+        /* Is the next QH the asynchronous list head which will always be at
+       * the end of the asynchronous queue?
+       */
+
+        else if (usb_ehci_virtramaddr(physaddr & QH_HLP_MASK) ==
+                 (uintptr_t)&g_asynchead) {
+            /* That will also terminate the loop */
+
+            next = NULL;
+        }
+
+        /* Otherwise, there is a QH structure after this one that describes
+       * another transaction.
+       */
+
+        else {
+            physaddr = usb_ehci_swap32(qh->hw.hlp) & QH_HLP_MASK;
+            next = (struct usb_ehci_qh_s *)usb_ehci_virtramaddr(physaddr);
+        }
+
+        /* Perform the user action on this entry.  The action might result in
+       * unlinking the entry!  But that is okay because we already have the
+       * next QH pointer.
+       *
+       * Notice that we do not manage the back pointer (bp).  If the callout
+       * uses it, it must update it as necessary.
+       */
+
+        ret = handler(qh, bp, arg);
+
+        /* If the handler returns any non-zero value, then terminate the
+       * traversal early.
+       */
+
+        if (ret != 0) {
+            return ret;
+        }
+
+        /* Set up to visit the next entry */
+
+        qh = next;
+    }
+
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_foreach
+ *
+ * Description:
+ *   Give a Queue Head (QH) instance, call the handler for each qTD structure
+ *   in the queue.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qtd_foreach(struct usb_ehci_qh_s *qh, foreach_qtd_t handler,
+                                void *arg)
+{
+    struct usb_ehci_qtd_s *qtd;
+    struct usb_ehci_qtd_s *next;
+    uintptr_t physaddr;
+    uint32_t *bp;
+    int ret;
+
+    DEBUGASSERT(qh && handler);
+
+    /* Handle the special case where the queue is empty */
+
+    bp = &qh->fqp;                   /* Start of qTDs in original list */
+    physaddr = usb_ehci_swap32(*bp); /* Physical address of first qTD in CPU order */
+
+    if ((physaddr & QTD_NQP_T) != 0) {
+        return 0;
+    }
+
+    /* Start with the first qTD in the list */
+
+    qtd = (struct usb_ehci_qtd_s *)usb_ehci_virtramaddr(physaddr);
+    next = NULL;
+
+    /* And loop until we encounter the end of the qTD list */
+
+    while (qtd) {
+        /* Is this the end of the list?  Check the next qTD pointer (NQP)
+       * terminate (T) bit.  If T==1, then the NQP address is not valid.
+       */
+
+        if ((usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_T) != 0) {
+            /* Set the next pointer to NULL.  This will terminate the loop. */
+
+            next = NULL;
+        } else {
+            physaddr = usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_NTEP_MASK;
+            next = (struct usb_ehci_qtd_s *)usb_ehci_virtramaddr(physaddr);
+        }
+
+        /* Perform the user action on this entry.  The action might result in
+       * unlinking the entry!  But that is okay because we already have the
+       * next qTD pointer.
+       *
+       * Notice that we do not manage the back pointer (bp).  If the call-
+       * out uses it, it must update it as necessary.
+       */
+
+        ret = handler(qtd, &bp, arg);
+
+        /* If the handler returns any non-zero value, then terminate the
+       * traversal early.
+       */
+
+        if (ret != 0) {
+            return ret;
+        }
+
+        /* Set up to visit the next entry */
+
+        qtd = next;
+    }
+
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_discard
+ *
+ * Description:
+ *   This is a usb_ehci_qtd_foreach callback.  It simply unlinks the QTD,
+ *   updates the back pointer, and frees the QTD structure.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qtd_discard(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
+                                void *arg)
+{
+    DEBUGASSERT(qtd && bp && *bp);
+
+    /* Remove the qTD from the list by updating the forward pointer to skip
+   * around this qTD.  We do not change that pointer because are repeatedly
+   * removing the aTD at the head of the QH list.
+   */
+
+    **bp = qtd->hw.nqp;
+
+    /* Then free the qTD */
+
+    usb_ehci_qtd_free(qtd);
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_discard
+ *
+ * Description:
+ *   Free the Queue Head (QH) and all qTD's attached to the QH.
+ *
+ * Assumptions:
+ *   The QH structure itself has already been unlinked from whatever list it
+ *   may have been in.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qh_discard(struct usb_ehci_qh_s *qh)
+{
+    int ret;
+
+    DEBUGASSERT(qh);
+
+    /* Free all of the qTD's attached to the QH */
+
+    ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_discard, NULL);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
+    }
+
+    /* Then free the QH itself */
+
+    usb_ehci_qh_free(qh);
+    return ret;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_speed
+ *
+ * Description:
+ *   Map a speed enumeration value per Chapter 9 of the USB specification to
+ *   the speed enumeration required in the EHCI queue head.
+ *
+ ****************************************************************************/
+
+static inline uint8_t usb_ehci_speed(uint8_t usbspeed)
+{
+    DEBUGASSERT(usbspeed >= USB_SPEED_LOW && usbspeed <= USB_SPEED_HIGH);
+    return g_ehci_speed[usbspeed];
+}
+
+static struct usb_ehci_qh_s *usb_ehci_qh_create(struct usb_ehci_epinfo_s *epinfo)
+{
+    struct usb_ehci_qh_s *qh;
+    uint32_t regval;
+    uint8_t hubaddr;
+    uint8_t hubport;
+    struct usb_ehci_epinfo_s *ep0info;
+    struct usbh_hubport *rhport;
+
+    rhport = epinfo->hport;
+
+    while (rhport->parent != NULL) {
+        rhport = rhport->parent->parent;
+    }
+    ep0info = (struct usb_ehci_epinfo_s *)rhport->ep0;
+    /* Allocate a new queue head structure */
+
+    qh = usb_ehci_qh_alloc();
+    if (qh == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_QHALLOC_FAILED, 0);
+        return NULL;
+    }
+
+    /* Save the endpoint information with the QH itself */
+
+    qh->epinfo = epinfo;
+
+    /* Write QH endpoint characteristics:
+   *
+   * FIELD    DESCRIPTION                     VALUE/SOURCE
+   * -------- ------------------------------- --------------------
+   * DEVADDR  Device address                  Endpoint structure
+   * I        Inactivate on Next Transaction  0
+   * ENDPT    Endpoint number                 Endpoint structure
+   * EPS      Endpoint speed                  Endpoint structure
+   * DTC      Data toggle control             1
+   * MAXPKT   Max packet size                 Endpoint structure
+   * C        Control endpoint                Calculated
+   * RL       NAK count reloaded              0
+   */
+
+    regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) |
+             ((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) |
+             ((uint32_t)usb_ehci_speed(epinfo->speed) << QH_EPCHAR_EPS_SHIFT) |
+             QH_EPCHAR_DTC |
+             ((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) |
+             ((uint32_t)0 << QH_EPCHAR_RL_SHIFT);
+
+    /* Paragraph 3.6.3: "Control Endpoint Flag (C). If the QH.EPS field
+    * indicates the endpoint is not a high-speed device, and the endpoint
+    * is an control endpoint, then software must set this bit to a one.
+    * Otherwise it should always set this bit to a zero."
+    */
+
+    if (epinfo->speed != USB_SPEED_HIGH &&
+        epinfo->xfrtype == USB_ENDPOINT_TYPE_CONTROL) {
+        regval |= QH_EPCHAR_C;
+    }
+
+    /* Save the endpoint characteristics word with the correct byte order */
+
+    qh->hw.epchar = usb_ehci_swap32(regval);
+
+    /* Write QH endpoint capabilities
+    *
+    * FIELD    DESCRIPTION                     VALUE/SOURCE
+    * -------- ------------------------------- --------------------
+    * SSMASK   Interrupt Schedule Mask         Depends on epinfo->xfrtype
+    * SCMASK   Split Completion Mask           0
+    * HUBADDR  Hub Address                     roothub port devaddr
+    * PORT     Port number                     RH port index
+    * MULT     High band width multiplier      1
+    */
+
+    hubaddr = ep0info->devaddr;
+    hubport = rhport->port;
+
+    regval = ((uint32_t)hubaddr << QH_EPCAPS_HUBADDR_SHIFT) |
+             ((uint32_t)hubport << QH_EPCAPS_PORT_SHIFT) |
+             ((uint32_t)1 << QH_EPCAPS_MULT_SHIFT);
+
+#ifndef CONFIG_USBHOST_INT_DISABLE
+    if (epinfo->xfrtype == USB_ENDPOINT_TYPE_INTERRUPT) {
+        /* Here, the S-Mask field in the queue head is set to 1, indicating
+        * that the transaction for the endpoint should be executed on the bus
+        * during micro-frame 0 of the frame.
+        *
+        * REVISIT: The polling interval should be controlled by the which
+        * entry is the framelist holds the QH pointer for a given micro-frame
+        * and the QH pointer should be replicated for different polling rates.
+        * This implementation currently just sets all frame_list entry to
+        * all the same interrupt queue.  That should work but will not give
+        * any control over polling rates.
+        */
+        // #warning REVISIT
+
+        regval |= ((uint32_t)1 << QH_EPCAPS_SSMASK_SHIFT);
+    }
+#endif
+
+    qh->hw.epcaps = usb_ehci_swap32(regval);
+
+    /* Mark this as the end of this list.  This will be overwritten if/when the
+    * next qTD is added to the queue.
+    */
+
+    qh->hw.hlp = usb_ehci_swap32(QH_HLP_T);
+    qh->hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
+    qh->hw.overlay.alt = usb_ehci_swap32(QH_AQP_T);
+    return qh;
+}
+
+static int usb_ehci_qtd_addbpl(struct usb_ehci_qtd_s *qtd, const void *buffer, size_t buflen)
+{
+    uint32_t physaddr;
+    uint32_t nbytes;
+    uint32_t next;
+    int ndx;
+
+    /* Loop, adding the aligned physical addresses of the buffer to the buffer
+    * page list.  Only the first entry need not be aligned (because only the
+    * first entry has the offset field). The subsequent entries must begin on
+    * 4KB address boundaries.
+    */
+
+    physaddr = (uint32_t)usb_ehci_physramaddr((uintptr_t)buffer);
+
+    for (ndx = 0; ndx < 5; ndx++) {
+        /* Write the physical address of the buffer into the qTD buffer
+       * pointer list.
+       */
+
+        qtd->hw.bpl[ndx] = usb_ehci_swap32(physaddr);
+
+        /* Get the next buffer pointer (in the case where we will have to
+        * transfer more then one chunk).  This buffer must be aligned to a
+        * 4KB address boundary.
+        */
+
+        next = (physaddr + 4096) & ~4095;
+
+        /* How many bytes were included in the last buffer?  Was it the whole
+        * thing?
+        */
+
+        nbytes = next - physaddr;
+        if (nbytes >= buflen) {
+            /* Yes... it was the whole thing.  Break out of the loop early. */
+
+            break;
+        }
+
+        /* Adjust the buffer length and physical address for the next time
+        * through the loop.
+        */
+
+        buflen -= nbytes;
+        physaddr = next;
+    }
+
+    /* Handle the case of a huge buffer > 4*4KB = 16KB */
+
+    if (ndx >= 5) {
+        //usbhost_trace1(EHCI_TRACE1_BUFTOOBIG, buflen);
+        return -EFBIG;
+    }
+
+    return 0;
+}
+
+static struct usb_ehci_qtd_s *usb_ehci_qtd_setupphase(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup)
+{
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t regval;
+    int ret;
+
+    /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+    qtd = usb_ehci_qtd_alloc();
+    if (qtd == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0);
+        return NULL;
+    }
+
+    /* Mark this as the end of the list (this will be overwritten if another
+    * qTD is added after this one).
+    */
+
+    qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
+    qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
+
+    /* Write qTD token:
+    *
+    * FIELD    DESCRIPTION                     VALUE/SOURCE
+    * -------- ------------------------------- --------------------
+    * STATUS   Status                          QTD_TOKEN_ACTIVE
+    * PID      PID Code                        QTD_TOKEN_PID_SETUP
+    * CERR     Error Counter                   3
+    * CPAGE    Current Page                    0
+    * IOC      Interrupt on complete           0
+    * NBYTES   Total Bytes to Transfer         8
+    * TOGGLE   Data Toggle                     0
+    */
+
+    regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP |
+             ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
+             ((uint32_t)8 << QTD_TOKEN_NBYTES_SHIFT);
+
+    qtd->hw.token = usb_ehci_swap32(regval);
+
+    /* Add the buffer data */
+    ret = usb_ehci_qtd_addbpl(qtd, (uint8_t *)setup, 8);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret);
+        usb_ehci_qtd_free(qtd);
+        return NULL;
+    }
+
+    /* Add the data transfer size to the count in the epinfo structure */
+    epinfo->xfrd += 8;
+
+    return qtd;
+}
+
+static struct usb_ehci_qtd_s *usb_ehci_qtd_dataphase(struct usb_ehci_epinfo_s *epinfo, void *buffer, int buflen, uint32_t tokenbits)
+{
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t regval;
+    int ret;
+
+    /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+    qtd = usb_ehci_qtd_alloc();
+    if (qtd == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_DATAQTDALLOC_FAILED, 0);
+        return NULL;
+    }
+
+    /* Mark this as the end of the list (this will be overwritten if another
+    * qTD is added after this one).
+    */
+
+    qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
+    qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
+
+    /* Write qTD token:
+    *
+    * FIELD    DESCRIPTION                     VALUE/SOURCE
+    * -------- ------------------------------- --------------------
+    * STATUS   Status                          QTD_TOKEN_ACTIVE
+    * PID      PID Code                        Contained in tokenbits
+    * CERR     Error Counter                   3
+    * CPAGE    Current Page                    0
+    * IOC      Interrupt on complete           Contained in tokenbits
+    * NBYTES   Total Bytes to Transfer         buflen
+    * TOGGLE   Data Toggle                     Contained in tokenbits
+    */
+
+    regval = tokenbits | QTD_TOKEN_ACTIVE |
+             ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
+             ((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT);
+
+    qtd->hw.token = usb_ehci_swap32(regval);
+
+    ret = usb_ehci_qtd_addbpl(qtd, buffer, buflen);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret);
+        usb_ehci_qtd_free(qtd);
+        return NULL;
+    }
+
+    /* Add the data transfer size to the count in the epinfo structure */
+    epinfo->xfrd += buflen;
+
+    return qtd;
+}
+
+static struct usb_ehci_qtd_s *usb_ehci_qtd_statusphase(uint32_t tokenbits)
+{
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t regval;
+
+    /* Allocate a new Queue Element Transfer Descriptor (qTD) */
+
+    qtd = usb_ehci_qtd_alloc();
+    if (qtd == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0);
+        return NULL;
+    }
+
+    /* Mark this as the end of the list (this will be overwritten if another
+    * qTD is added after this one).
+    */
+
+    qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
+    qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
+
+    /* Write qTD token:
+    *
+    * FIELD    DESCRIPTION                     VALUE/SOURCE
+    * -------- ------------------------------- --------------------
+    * STATUS   Status                          QTD_TOKEN_ACTIVE
+    * PID      PID Code                        Contained in tokenbits
+    * CERR     Error Counter                   3
+    * CPAGE    Current Page                    0
+    * IOC      Interrupt on complete           QTD_TOKEN_IOC
+    * NBYTES   Total Bytes to Transfer         0
+    * TOGGLE   Data Toggle                     Contained in tokenbits
+    */
+
+    regval = tokenbits | QTD_TOKEN_ACTIVE | QTD_TOKEN_IOC |
+             ((uint32_t)3 << QTD_TOKEN_CERR_SHIFT);
+
+    qtd->hw.token = usb_ehci_swap32(regval);
+
+    return qtd;
+}
+
+static void usb_ehci_qh_enqueue(struct usb_ehci_qh_s *qhead, struct usb_ehci_qh_s *qh)
+{
+    uintptr_t physaddr;
+
+    /* Set the internal fqp field.  When we transverse the QH list later,
+    * we need to know the correct place to start because the overlay may no
+    * longer point to the first qTD entry.
+    */
+
+    qh->fqp = qh->hw.overlay.nqp;
+    //usb_ehci_qh_dump(qh, NULL, NULL);
+
+    /* Add the new QH to the head of the asynchronous queue list.
+    *
+    * First, attach the old head as the new QH HLP and flush the new QH and
+    * its attached qTDs to RAM.
+    */
+
+    qh->hw.hlp = qhead->hw.hlp;
+
+    /* Then set the new QH as the first QH in the asynchronous queue */
+
+    physaddr = (uintptr_t)usb_ehci_physramaddr((uintptr_t)qh);
+    qhead->hw.hlp = usb_ehci_swap32(physaddr | QH_HLP_TYP_QH);
+}
+
+static int usb_ehci_control_setup(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup, uint8_t *buffer, uint32_t buflen)
+{
+    struct usb_ehci_qh_s *qh;
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t tokenbits;
+    uintptr_t physaddr;
+    uint32_t *flink;
+    uint32_t *alt;
+    uint32_t toggle;
+    bool dirin = false;
+    int ret;
+
+    /* Create and initialize a Queue Head (QH) structure for this transfer */
+    qh = usb_ehci_qh_create(epinfo);
+    if (qh == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
+        return -ENOMEM;
+    }
+
+    /* Initialize the QH link and get the next data toggle (not used for SETUP
+    * transfers)
+    */
+
+    flink = &qh->hw.overlay.nqp;
+    toggle = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
+    ret = -EIO;
+
+    /* Is there an EP0 SETUP request?  If so, we will queue two or three qTDs:
+    *
+    *   1) One for the SETUP phase,
+    *   2) One for the DATA phase (if there is data), and
+    *   3) One for the STATUS phase.
+    */
+
+    /* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP
+    * phase of the request sequence.
+    */
+    {
+        qtd = usb_ehci_qtd_setupphase(epinfo, setup);
+        if (qtd == NULL) {
+            //usbhost_trace1(EHCI_TRACE1_QTDSETUP_FAILED, 0);
+            ret = -ENOMEM;
+            goto errout_with_qh;
+        }
+        /* Link the new qTD to the QH head. */
+
+        physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
+        *flink = usb_ehci_swap32(physaddr);
+
+        /* Get the new forward link pointer and data toggle */
+
+        flink = &qtd->hw.nqp;
+        toggle = QTD_TOKEN_TOGGLE;
+    }
+    /* A buffer may or may be supplied with an EP0 SETUP transfer.  A buffer
+    * will always be present for normal endpoint data transfers.
+    */
+
+    alt = NULL;
+
+    if (buffer != NULL && buflen > 0) {
+        /* Extra TOKEN bits include the data toggle, the data PID, and if
+        * there is no request, an indication to interrupt at the end of this
+        * transfer.
+        */
+
+        tokenbits = toggle;
+
+        /* Get the data token direction.
+        *
+        * If this is a SETUP request, use the direction contained in the
+        * request.  The IOC bit is not set.
+        */
+        if ((setup->bmRequestType & 0x80) == 0x80) {
+            tokenbits |= QTD_TOKEN_PID_IN;
+            dirin = true;
+        } else {
+            tokenbits |= QTD_TOKEN_PID_OUT;
+            dirin = false;
+        }
+
+        /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
+        * buffer.
+        */
+
+        qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
+        if (qtd == NULL) {
+            //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
+            ret = -ENOMEM;
+            goto errout_with_qh;
+        }
+
+        /* Link the new qTD to either QH head of the SETUP qTD. */
+        physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
+        *flink = usb_ehci_swap32(physaddr);
+
+        /* Set the forward link pointer to this new qTD */
+
+        flink = &qtd->hw.nqp;
+
+        /* If this was an IN transfer, then setup a pointer alternate link.
+        * The EHCI hardware will use this link if a short packet is received.
+        */
+
+        if (dirin) {
+            alt = &qtd->hw.alt;
+        }
+    }
+
+    {
+        /* Extra TOKEN bits include the data toggle and the correct data PID. */
+
+        tokenbits = toggle;
+
+        /* The status phase direction is the opposite of the data phase.  If
+        * this is an IN request, then we received the buffer and we will send
+        * the zero length packet handshake.
+        */
+        if ((setup->bmRequestType & 0x80) == 0x80) {
+            tokenbits |= QTD_TOKEN_PID_OUT;
+        } else {
+            /* Otherwise, this in an OUT request.  We send the buffer and we expect
+            * to receive the NULL packet handshake.
+            */
+            tokenbits |= QTD_TOKEN_PID_IN;
+        }
+
+        /* Allocate a new Queue Element Transfer Descriptor (qTD) for the
+        * status
+        */
+        qtd = usb_ehci_qtd_statusphase(tokenbits);
+        if (qtd == NULL) {
+            //usbhost_trace1(EHCI_TRACE1_QTDSTATUS_FAILED, 0);
+            ret = -ENOMEM;
+            goto errout_with_qh;
+        }
+
+        /* Link the new qTD to either the SETUP or data qTD. */
+        physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
+        *flink = usb_ehci_swap32(physaddr);
+
+        /* In an IN data qTD was also enqueued, then linked the data qTD's
+        * alternate pointer to this STATUS phase qTD in order to handle short
+        * transfers.
+        */
+
+        if (alt) {
+            *alt = usb_ehci_swap32(physaddr);
+        }
+    }
+    /* Add the new QH to the head of the asynchronous queue list */
+    usb_ehci_qh_enqueue(&g_asynchead, qh);
+
+    return 0;
+
+errout_with_qh:
+    /* Clean-up after an error */
+    usb_ehci_qh_discard(qh);
+    return ret;
+}
+
+static int usb_ehci_bulk_setup(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen)
+{
+    struct usb_ehci_qh_s *qh;
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t tokenbits;
+    uintptr_t physaddr;
+    int ret;
+
+    /* Create and initialize a Queue Head (QH) structure for this transfer */
+    qh = usb_ehci_qh_create(epinfo);
+    if (qh == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
+        return -ENOMEM;
+    }
+
+    /* Initialize the QH link and get the next data toggle */
+    tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
+    ret = -EIO;
+
+    if (buffer != NULL && buflen > 0) {
+        /* Get the direction from the epinfo structure.  Since this is not an EP0 SETUP request,
+        * nothing follows the data and we want the IOC interrupt when the data transfer completes.
+        */
+        if (epinfo->dirin) {
+            tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC);
+        } else {
+            tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC);
+        }
+
+        /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
+        * buffer.
+        */
+
+        qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
+        if (qtd == NULL) {
+            //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
+            ret = -ENOMEM;
+            goto errout_with_qh;
+        }
+
+        /* Link the new qTD to the QH. */
+        physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
+        qh->hw.overlay.nqp = usb_ehci_swap32(physaddr);
+    }
+
+    /* Add the new QH to the head of the asynchronous queue list */
+    usb_ehci_qh_enqueue(&g_asynchead, qh);
+    return 0;
+
+errout_with_qh:
+    usb_ehci_qh_discard(qh);
+    return ret;
+}
+
+static int usb_ehci_intr_setup(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen)
+{
+    struct usb_ehci_qh_s *qh;
+    struct usb_ehci_qtd_s *qtd;
+    uint32_t tokenbits;
+    uintptr_t physaddr;
+    uint32_t regval;
+    int ret;
+
+    /* Create and initialize a Queue Head (QH) structure for this transfer */
+    qh = usb_ehci_qh_create(epinfo);
+    if (qh == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
+        return -ENOMEM;
+    }
+
+    /* Initialize the QH link and get the next data toggle */
+    tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
+
+    /* Get the direction from the epinfo structure.  Since this is not an EP0 SETUP request,
+    * nothing follows the data and we want the IOC interrupt when the data transfer completes.
+    */
+    if (epinfo->dirin) {
+        tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC);
+    } else {
+        tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC);
+    }
+
+    /* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
+    * buffer.
+    */
+
+    qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
+    if (qtd == NULL) {
+        //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
+        ret = -ENOMEM;
+        goto errout_with_qh;
+    }
+
+    /* Link the new qTD to the QH. */
+    physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
+    qh->hw.overlay.nqp = usb_ehci_swap32(physaddr);
+
+    /* Disable the periodic schedule */
+    regval = usb_ehci_getreg(&HCOR->usbcmd);
+    regval &= ~EHCI_USBCMD_PSEN;
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+
+    /* Add the new QH to the head of the interrupt transfer list */
+    usb_ehci_qh_enqueue(&g_intrhead, qh);
+
+    /* Re-enable the periodic schedule */
+    regval |= EHCI_USBCMD_PSEN;
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+    return 0;
+
+errout_with_qh:
+    usb_ehci_qh_discard(qh);
+    return ret;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_ioc_setup
+ *
+ * Description:
+ *   Set the request for the IOC event well BEFORE enabling the transfer (as
+ *   soon as we are absolutely committed to the to avoid transfer).  We do
+ *   this to minimize race conditions.  This logic would have to be expanded
+ *   if we want to have more than one packet in flight at a time!
+ *
+ * Assumption:  The caller holds tex EHCI exclsem
+ *
+ ****************************************************************************/
+
+static int usb_ehci_ioc_setup(struct usb_ehci_epinfo_s *epinfo)
+{
+    uint32_t flags;
+    int ret = -ENODEV;
+
+    DEBUGASSERT(rhport && epinfo && !epinfo->iocwait);
+#ifdef CONFIG_USBHOST_ASYNCH
+    DEBUGASSERT(epinfo->callback == NULL);
+#endif
+
+    /* Is the device still connected? */
+
+    flags = usb_osal_enter_critical_section();
+    if (g_ehci.connected) {
+        /* Then set iocwait to indicate that we expect to be informed when
+        * either (1) the device is disconnected, or (2) the transfer
+        * completed.
+        */
+
+        epinfo->iocwait = true;  /* We want to be awakened by IOC interrupt */
+        epinfo->status = 0;      /* No status yet */
+        epinfo->xfrd = 0;        /* Nothing transferred yet */
+        epinfo->result = -EBUSY; /* Transfer in progress */
+#ifdef CONFIG_USBHOST_ASYNCH
+        epinfo->callback = NULL; /* No asynchronous callback */
+        epinfo->arg = NULL;
+#endif
+        ret = 0; /* We are good to go */
+    }
+    usb_osal_leave_critical_section(flags);
+    return ret;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_ioc_async_setup
+ *
+ * Description:
+ *   Setup to receive an asynchronous notification when a transfer completes.
+ *
+ * Input Parameters:
+ *   epinfo - The IN or OUT endpoint descriptor for the device endpoint on
+ *      which the transfer will be performed.
+ *   callback - The function to be called when the completes
+ *   arg - An arbitrary argument that will be provided with the callback.
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Called from the interrupt level
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST_ASYNCH
+static int usb_ehci_ioc_async_setup(struct usb_ehci_epinfo_s *epinfo, usbh_asynch_callback_t callback, void *arg)
+{
+    uint32_t flags;
+    int ret = -ENODEV;
+
+    DEBUGASSERT(rhport && epinfo && !epinfo->iocwait);
+#ifdef CONFIG_USBHOST_ASYNCH
+    DEBUGASSERT(epinfo->callback == NULL);
+#endif
+
+    /* Is the device still connected? */
+
+    flags = usb_osal_enter_critical_section();
+    if (g_ehci.connected) {
+        /* Then set iocwait to indicate that we expect to be informed when
+        * either (1) the device is disconnected, or (2) the transfer
+        * completed.
+        */
+
+        epinfo->iocwait = false; /* We want to be awakened by IOC interrupt */
+        epinfo->status = 0;      /* No status yet */
+        epinfo->xfrd = 0;        /* Nothing transferred yet */
+        epinfo->result = -EBUSY; /* Transfer in progress */
+#ifdef CONFIG_USBHOST_ASYNCH
+        epinfo->callback = callback; /* No asynchronous callback */
+        epinfo->arg = arg;
+#endif
+        ret = 0; /* We are good to go */
+    }
+
+    usb_osal_leave_critical_section(flags);
+    return ret;
+}
+#endif
+
+/****************************************************************************
+ * Name: usb_ehci_asynch_completion
+ *
+ * Description:
+ *   This function is called at the interrupt level when an asynchronous
+ *   transfer completes.  It performs the pending callback.
+ *
+ * Input Parameters:
+ *   epinfo - The IN or OUT endpoint descriptor for the device endpoint on
+ *      which the transfer was performed.
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Called from the interrupt level
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST_ASYNCH
+static void usb_ehci_asynch_completion(struct usb_ehci_epinfo_s *epinfo)
+{
+    usbh_asynch_callback_t callback;
+    int nbytes;
+    void *arg;
+    int result;
+
+    DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false &&
+                epinfo->callback != NULL);
+
+    /* Extract and reset the callback info */
+
+    callback = epinfo->callback;
+    arg = epinfo->arg;
+    result = epinfo->result;
+    nbytes = epinfo->xfrd;
+
+    epinfo->callback = NULL;
+    epinfo->arg = NULL;
+    epinfo->result = 0;
+    epinfo->iocwait = false;
+
+    /* Then perform the callback.  Provide the number of bytes successfully
+   * transferred or the negated errno value in the event of a failure.
+   */
+
+    if (result < 0) {
+        nbytes = (int)result;
+    }
+
+    callback(arg, nbytes);
+}
+#endif
+
+/****************************************************************************
+ * Name: usb_ehci_ioc_wait
+ *
+ * Description:
+ *   Wait for the IOC event.
+ *
+ * Assumption:  The caller does *NOT* hold the EHCI exclsem.  That would
+ * cause a deadlock when the bottom-half, worker thread needs to take the
+ * semaphore.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_ioc_wait(struct usb_ehci_epinfo_s *epinfo)
+{
+    int ret = 0;
+
+    /* Wait for the IOC event.  Loop to handle any false alarm semaphore
+    * counts.  Return an error if the task is canceled.
+    */
+
+    while (epinfo->iocwait) {
+        ret = usb_osal_sem_take(epinfo->iocsem);
+        if (ret < 0) {
+            break;
+        }
+    }
+
+    return ret < 0 ? ret : epinfo->result;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_transfer_wait
+ *
+ * Description:
+ *   Wait for an IN or OUT transfer to complete.
+ *
+ * Assumption:  The caller holds the EHCI exclsem.  The caller must be aware
+ *   that the EHCI exclsem will released while waiting for the transfer to
+ *   complete, but will be re-acquired when before returning.  The state of
+ *   EHCI resources could be very different upon return.
+ *
+ * Returned Value:
+ *   On success, this function returns the number of bytes actually
+ *   transferred.  For control transfers, this size includes the size of the
+ *   control request plus the size of the data (which could be short); for
+ *   bulk transfers, this will be the number of data bytes transfers (which
+ *   could be short).
+ *
+ ****************************************************************************/
+
+static int usb_ehci_transfer_wait(struct usb_ehci_epinfo_s *epinfo)
+{
+    int ret;
+    int ret2;
+
+    /* Release the EHCI semaphore while we wait.  Other threads need the
+    * opportunity to access the EHCI resources while we wait.
+    *
+    * REVISIT:  Is this safe?  NO.  This is a bug and needs rethinking.
+    * We need to lock all of the port-resources (not EHCI common) until
+    * the transfer is complete.  But we can't use the common EHCI exclsem
+    * or we will deadlock while waiting (because the working thread that
+    * wakes this thread up needs the exclsem).
+    */
+
+    /* REVISIT */
+
+    usb_osal_mutex_give(g_ehci.exclsem);
+
+    /* Wait for the IOC completion event */
+
+    ret = usb_ehci_ioc_wait(epinfo);
+
+    /* Re-acquire the EHCI semaphore.  The caller expects to be holding
+   * this upon return.
+   */
+
+    ret2 = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret2 < 0) {
+        ret = ret2;
+    }
+
+    /* Did usb_ehci_ioc_wait() or usb_osal_mutex_take()report an
+   * error?
+   */
+
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_TRANSFER_FAILED, -ret);
+        epinfo->iocwait = false;
+        return ret;
+    }
+
+    /* Transfer completed successfully.  Return the number of bytes
+    * transferred.
+    */
+
+    return epinfo->xfrd;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_ioccheck
+ *
+ * Description:
+ *   This function is a usb_ehci_qtd_foreach() callback function.  It services
+ *   one qTD in the asynchronous queue.  It removes all of the qTD
+ *   structures that are no longer active.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qtd_ioccheck(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
+                                 void *arg)
+{
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg;
+    DEBUGASSERT(qtd && epinfo);
+
+    //usb_ehci_qtd_print(qtd);
+
+    /* Remove the qTD from the list
+    *
+    * NOTE that we don't check if the qTD is active nor do we check if there
+    * are any errors reported in the qTD.  If the transfer halted due to
+    * an error, then qTDs in the list after the error qTD will still appear
+    * to be active.
+    */
+
+    **bp = qtd->hw.nqp;
+
+    /* Subtract the number of bytes left untransferred.  The epinfo->xfrd
+    * field is initialized to the total number of bytes to be transferred
+    * (all qTDs in the list).  We subtract out the number of untransferred
+    * bytes on each transfer and the final result will be the number of bytes
+    * actually transferred.
+    */
+
+    epinfo->xfrd -= (usb_ehci_swap32(qtd->hw.token) & QTD_TOKEN_NBYTES_MASK) >>
+                    QTD_TOKEN_NBYTES_SHIFT;
+
+    /* Release this QH by returning it to the free list */
+
+    usb_ehci_qtd_free(qtd);
+
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_ioccheck
+ *
+ * Description:
+ *   This function is a usb_ehci_qh_foreach() callback function.  It services
+ *   one QH in the asynchronous queue.  It check all attached qTD structures
+ *   and remove all of the structures that are no longer active.  if all of
+ *   the qTD structures are removed, then QH itself will also be removed.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_qh_ioccheck(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg)
+{
+    struct usb_ehci_epinfo_s *epinfo;
+    uint32_t token;
+    int ret;
+
+    DEBUGASSERT(qh && bp);
+
+    //usb_ehci_qh_print(qh);
+
+    /* Get the endpoint info pointer from the extended QH data.  Only the
+    * g_asynchead QH can have a NULL epinfo field.
+    */
+
+    epinfo = qh->epinfo;
+    DEBUGASSERT(epinfo);
+
+    /* Paragraph 3.6.3:  "The nine DWords in [the Transfer Overlay] area
+    * represent a transaction working space for the host controller.  The
+    * general operational model is that the host controller can detect
+    * whether the overlay area contains a description of an active transfer.
+    * If it does not contain an active transfer, then it follows the Queue
+    * Head Horizontal Link Pointer to the next queue head.  The host
+    * controller will never follow the Next Transfer Queue Element or
+    * Alternate Queue Element pointers unless it is actively attempting to
+    * advance the queue ..."
+    */
+
+    /* Is the qTD still active? */
+
+    token = usb_ehci_swap32(qh->hw.overlay.token);
+    //usbhost_vtrace2(EHCI_VTRACE2_IOCCHECK, epinfo->epno, token);
+
+    if ((token & QH_TOKEN_ACTIVE) != 0) {
+        /* Yes... we cannot process the QH while it is still active.  Return
+       * zero to visit the next QH in the list.
+       */
+        *bp = &qh->hw.hlp;
+        return 0;
+    }
+
+    /* Remove all active, attached qTD structures from the inactive QH */
+    ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_ioccheck, (void *)qh->epinfo);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
+    }
+    /* If there is no longer anything attached to the QH, then remove it from
+    * the asynchronous queue.
+    */
+
+    if ((usb_ehci_swap32(qh->fqp) & QTD_NQP_T) != 0) {
+        /* Set the forward link of the previous QH to point to the next
+        * QH in the list.
+        */
+
+        **bp = qh->hw.hlp;
+
+        /* Check for errors, update the data toggle */
+
+        if ((token & QH_TOKEN_ERRORS) == 0) {
+            /* No errors.. Save the last data toggle value */
+
+            epinfo->toggle = (token >> QTD_TOKEN_TOGGLE_SHIFT) & 1;
+
+            /* Report success */
+
+            epinfo->status = 0;
+            epinfo->result = 0;
+        } else {
+            /* An error occurred */
+
+            epinfo->status = (token & QH_TOKEN_STATUS_MASK) >>
+                             QH_TOKEN_STATUS_SHIFT;
+
+            /* The HALT condition is set on a variety of conditions:  babble,
+            * error counter countdown to zero, or a STALL.  If we can rule
+            * out babble (babble bit not set) and if the error counter is
+            * non-zero, then we can assume a STALL. In this case, we return
+            * -PERM to inform the class driver of the stall condition.
+            */
+
+            if ((token & (QH_TOKEN_BABBLE | QH_TOKEN_HALTED)) ==
+                    QH_TOKEN_HALTED &&
+                (token & QH_TOKEN_CERR_MASK) != 0) {
+                /* It is a stall,  Note that the data toggle is reset
+               * after the stall.
+               */
+
+                //usbhost_trace2(EHCI_TRACE2_EPSTALLED, epinfo->epno, token);
+                epinfo->result = -EPERM;
+                epinfo->toggle = 0;
+            } else {
+                /* Otherwise, it is some kind of data transfer error */
+
+                //usbhost_trace2(EHCI_TRACE2_EPIOERROR, epinfo->epno, token);
+                epinfo->result = -EIO;
+            }
+        }
+
+        /* Is there a thread waiting for this transfer to complete? */
+        if (epinfo->iocwait) {
+            /* Yes... wake it up */
+            epinfo->iocwait = false;
+            /* TODO */
+            // clang-format off
+            for (size_t i = 0; i < 3200; i++) {
+                __asm volatile("nop" ::: "memory");
+            }
+            // clang-format on
+            usb_osal_sem_give(epinfo->iocsem);
+        }
+#ifdef CONFIG_USBHOST_ASYNCH
+        /* No.. Is there a pending asynchronous transfer? */
+
+        else if (epinfo->callback != NULL) {
+            /* Yes.. perform the callback */
+
+            usb_ehci_asynch_completion(epinfo);
+        }
+#endif
+
+        /* Then release this QH by returning it to the free list */
+        usb_ehci_qh_free(qh);
+    } else {
+        /* Otherwise, the horizontal link pointer of this QH will become the
+        * next back pointer.
+        */
+
+        *bp = &qh->hw.hlp;
+    }
+
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qtd_cancel
+ *
+ * Description:
+ *   This function is a usb_ehci_qtd_foreach() callback function.  It removes
+ *   each qTD attached to a QH.
+ *
+ ****************************************************************************/
+static int usb_ehci_qtd_cancel(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
+                               void *arg)
+{
+    DEBUGASSERT(qtd != NULL && bp != NULL);
+
+    //usb_ehci_qtd_print(qtd);
+
+    /* Remove the qTD from the list
+    *
+    * NOTE that we don't check if the qTD is active nor do we check if there
+    * are any errors reported in the qTD.  If the transfer halted due to
+    * an error, then qTDs in the list after the error qTD will still appear
+    * to be active.
+    *
+    * REVISIT: There is a race condition here that needs to be resolved.
+    */
+
+    **bp = qtd->hw.nqp;
+
+    /* Release this QH by returning it to the free list */
+
+    usb_ehci_qtd_free(qtd);
+    return 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_qh_cancel
+ *
+ * Description:
+ *   This function is a imxrt_qh_foreach() callback function.  It cancels
+ *   one QH in the asynchronous queue.  It will remove all attached qTD
+ *   structures and remove all of the structures that are no longer active.
+ *   Then QH itself will also be removed.
+ *
+ ****************************************************************************/
+static int usb_ehci_qh_cancel(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg)
+{
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg;
+    uint32_t regval;
+    int ret;
+
+    DEBUGASSERT(qh != NULL && bp != NULL && epinfo != NULL);
+
+    //usb_ehci_qh_print(qh);
+
+    /* Check if this is the QH that we are looking for */
+
+    if (qh->epinfo == epinfo) {
+        /* No... keep looking */
+
+        return 0;
+    }
+
+    /* Disable both the asynchronous and period schedules */
+
+    regval = usb_ehci_getreg(&HCOR->usbcmd);
+    usb_ehci_putreg(regval & ~(EHCI_USBCMD_ASEN | EHCI_USBCMD_PSEN),
+                    &HCOR->usbcmd);
+
+    /* Remove the QH from the list
+    *
+    * NOTE that we don't check if the qTD is active nor do we check if there
+    * are any errors reported in the qTD.  If the transfer halted due to
+    * an error, then qTDs in the list after the error qTD will still appear
+    * to be active.
+    *
+    * REVISIT: There is a race condition here that needs to be resolved.
+    */
+
+    **bp = qh->hw.hlp;
+
+    /* Re-enable the schedules (if they were enabled before. */
+
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+
+    /* Remove all active, attached qTD structures from the removed QH */
+
+    ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_cancel, NULL);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
+    }
+
+    /* Then release this QH by returning it to the free list.  Return 1
+    * to stop the traverse without an error.
+    */
+
+    usb_ehci_qh_free(qh);
+    return 1;
+}
+
+static inline void usb_ehci_ioc_bottomhalf(void)
+{
+    struct usb_ehci_qh_s *qh;
+    uint32_t *bp;
+    int ret;
+
+    /* Set the back pointer to the forward qTD pointer of the asynchronous
+    * queue head.
+    */
+
+    bp = (uint32_t *)&g_asynchead.hw.hlp;
+    qh = (struct usb_ehci_qh_s *)
+        usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
+
+    /* If the asynchronous queue is empty, then the forward point in the
+    * asynchronous queue head will point back to the queue head.
+    */
+    if (qh && qh != &g_asynchead) {
+        /* Then traverse and operate on every QH and qTD in the asynchronous
+        * queue
+        */
+        usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL);
+    }
+#ifndef CONFIG_USBHOST_INT_DISABLE
+    /* Check the Interrupt Queue */
+
+    /* Set the back pointer to the forward qTD pointer of the asynchronous
+    * queue head.
+    */
+
+    bp = (uint32_t *)&g_intrhead.hw.hlp;
+    qh = (struct usb_ehci_qh_s *)
+        usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
+    if (qh) {
+        /* Then traverse and operate on every QH and qTD in the asynchronous
+        * queue.
+        */
+
+        ret = usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL);
+        if (ret < 0) {
+            //usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret);
+        }
+    }
+#endif
+}
+
+extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
+
+/****************************************************************************
+ * Name: usb_ehci_portsc_bottomhalf
+ *
+ * Description:
+ *   EHCI Port Change Detect "Bottom Half" interrupt handler
+ *
+ *  "The Host Controller sets this bit to a one when any port for which the
+ *   Port Owner bit is set to zero ... has a change bit transition from a
+ *   zero to a one or a Force Port Resume bit transition from a zero to a
+ *   one as a result of a J-K transition detected on a suspended port.
+ *   This bit will also be set as a result of the Connect Status Change
+ *   being set to a one after system software has relinquished ownership of
+ *   a connected port by writing a one to a port's Port Owner bit...
+ *
+ *  "This bit is allowed to be maintained in the Auxiliary power well.
+ *   Alternatively, it is also acceptable that on a D3 to D0 transition of
+ *   the EHCI HC device, this bit is loaded with the OR of all of the PORTSC
+ *   change bits (including: Force port resume, over-current change,
+ *   enable/disable change and connect status change)."
+ *
+ ****************************************************************************/
+static inline void usb_ehci_portsc_bottomhalf(void)
+{
+    uint32_t portsc;
+    int rhpndx;
+
+    /* Handle root hub status change on each root port */
+
+    for (rhpndx = 0; rhpndx < CONFIG_USBHOST_RHPORTS; rhpndx++) {
+        portsc = usb_ehci_getreg(&HCOR->portsc[rhpndx]);
+
+        //usbhost_vtrace2(EHCI_VTRACE2_PORTSC, rhpndx + 1, portsc);
+
+        /* Handle port connection status change (CSC) events */
+        if ((portsc & EHCI_PORTSC_CSC) != 0) {
+            //usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CSC, portsc);
+
+            /* Check current connect status */
+            if ((portsc & EHCI_PORTSC_CCS) != 0) {
+                /* Connected ... Did we just become connected? */
+                g_ehci.connected = 1;
+                usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1);
+            } else {
+                g_ehci.connected = 0;
+                usbh_event_notify_handler(USBH_EVENT_REMOVED, 1);
+            }
+        }
+
+        /* Clear all pending port interrupt sources by writing a '1' to the
+        * corresponding bit in the PORTSC register.  In addition, we need
+        * to preserve the values of all R/W bits (RO bits don't matter)
+        */
+        usb_ehci_putreg(portsc, &HCOR->portsc[rhpndx]);
+    }
+}
+
+/****************************************************************************
+ * Name: usb_ehci_reset
+ *
+ * Description:
+ *   Set the HCRESET bit in the USBCMD register to reset the EHCI hardware.
+ *
+ *   Table 2-9. USBCMD - USB Command Register Bit Definitions
+ *
+ *    "Host Controller Reset (HCRESET) ... This control bit is used by
+ *     software to reset the host controller. The effects of this on Root
+ *     Hub registers are similar to a Chip Hardware Reset.
+ *
+ *    "When software writes a one to this bit, the Host Controller resets its
+ *     internal pipelines, timers, counters, state machines, etc. to their
+ *     initial value. Any transaction currently in progress on USB is
+ *     immediately terminated. A USB reset is not driven on downstream
+ *     ports.
+ *
+ *    "PCI Configuration registers are not affected by this reset. All
+ *     operational registers, including port registers and port state
+ *     machines are set to their initial values. Port ownership reverts
+ *     to the companion host controller(s)... Software must reinitialize
+ *     the host controller ... in order to return the host controller to
+ *     an operational state.
+ *
+ *    "This bit is set to zero by the Host Controller when the reset process
+ *     is complete. Software cannot terminate the reset process early by
+ *     writing a zero to this register. Software should not set this bit to
+ *     a one when the HCHalted bit in the USBSTS register is a zero.
+ *     Attempting to reset an actively running host controller will result
+ *     in undefined behavior."
+ *
+ * Input Parameters:
+ *   None.
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned
+ *   on failure.
+ *
+ * Assumptions:
+ * - Called during the initialization of the EHCI.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_reset(void)
+{
+    uint32_t regval;
+    unsigned int timeout;
+
+    /* Make sure that the EHCI is halted:  "When [the Run/Stop] bit is set to
+    * 0, the Host Controller completes the current transaction on the USB and
+    * then halts. The HC Halted bit in the status register indicates when the
+    * Host Controller has finished the transaction and has entered the
+    * stopped state..."
+    */
+
+    usb_ehci_putreg(0, &HCOR->usbcmd);
+
+    /* "... Software should not set [HCRESET] to a one when the HCHalted bit in
+    *  the USBSTS register is a zero. Attempting to reset an actively running
+    *  host controller will result in undefined behavior."
+    */
+
+    timeout = 0;
+    do {
+        /* Wait one microsecond and update the timeout counter */
+
+        usb_osal_msleep(1);
+        timeout++;
+
+        /* Get the current value of the USBSTS register.  This loop will
+        * terminate when either the timeout exceeds one millisecond or when
+        * the HCHalted bit is no longer set in the USBSTS register.
+        */
+
+        regval = usb_ehci_getreg(&HCOR->usbsts);
+    } while (((regval & EHCI_USBSTS_HALTED) == 0) && (timeout < 1000));
+
+    /* Is the EHCI still running?  Did we timeout? */
+
+    if ((regval & EHCI_USBSTS_HALTED) == 0) {
+        //usbhost_trace1(EHCI_TRACE1_HCHALTED_TIMEOUT, regval);
+        return -ETIMEDOUT;
+    }
+
+    /* Now we can set the HCReset bit in the USBCMD register to
+    * initiate the reset
+    */
+
+    regval = usb_ehci_getreg(&HCOR->usbcmd);
+    regval |= EHCI_USBCMD_HCRESET;
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+
+    /* Wait for the HCReset bit to become clear */
+
+    do {
+        /* Wait five microsecondw and update the timeout counter */
+
+        usb_osal_msleep(5);
+        timeout += 5;
+
+        /* Get the current value of the USBCMD register.  This loop will
+        * terminate when either the timeout exceeds one second or when the
+        * HCReset bit is no longer set in the USBSTS register.
+        */
+
+        regval = usb_ehci_getreg(&HCOR->usbcmd);
+    } while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000));
+
+    /* Return either success or a timeout */
+
+    return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : 0;
+}
+
+/****************************************************************************
+ * Name: usb_ehci_wait_usbsts
+ *
+ * Description:
+ *   Wait for either (1) a field in the USBSTS register to take a specific
+ *   value, (2) for a timeout to occur, or (3) a error to occur.  Return
+ *   a value to indicate which terminated the wait.
+ *
+ ****************************************************************************/
+
+static int usb_ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, unsigned int delay)
+{
+    uint32_t regval;
+    unsigned int timeout;
+
+    timeout = 0;
+    do {
+        /* Wait 5usec before trying again */
+
+        usb_osal_msleep(5);
+        timeout += 5;
+
+        /* Read the USBSTS register and check for a system error */
+
+        regval = usb_ehci_getreg(&HCOR->usbsts);
+        if ((regval & EHCI_INT_SYSERROR) != 0) {
+            //usbhost_trace1(EHCI_TRACE1_SYSTEMERROR, regval);
+            return -EIO;
+        }
+
+        /* Mask out the bits of interest */
+
+        regval &= maskbits;
+
+        /* Loop until the masked bits take the specified value or until a
+       * timeout occurs.
+       */
+    } while (regval != donebits && timeout < delay);
+
+    /* We got here because either the waited for condition or a timeout
+    * occurred.  Return a value to indicate which.
+    */
+
+    return (regval == donebits) ? 0 : -ETIMEDOUT;
+}
+
+__WEAK void usb_ehci_pre_hw_init(void)
+{
+}
+
+__WEAK void usb_ehci_last_hw_init(void)
+{
+}
+
+int usb_hc_init(void)
+{
+    int ret;
+    uint32_t regval;
+    uintptr_t physaddr1;
+    uintptr_t physaddr2;
+
+    /* Initialize the list of free Queue Head (QH) structures */
+
+    for (uint8_t i = 0; i < CONFIG_USB_EHCI_QH_NUM; i++) {
+        /* Put the QH structure in a free list */
+
+        usb_ehci_qh_free(&g_qhpool[i]);
+    }
+    /* Initialize the list of free Queue Head (QH) structures */
+
+    for (uint8_t i = 0; i < CONFIG_USB_EHCI_QTD_NUM; i++) {
+        /* Put the QH structure in a free list */
+
+        usb_ehci_qtd_free(&g_qtdpool[i]);
+    }
+
+    /* Initialize the head of the asynchronous queue/reclamation list.
+    *
+    * "In order to communicate with devices via the asynchronous schedule,
+    *  system software must write the ASYNDLISTADDR register with the address
+    *  of a control or bulk queue head. Software must then enable the
+    *  asynchronous schedule by writing a one to the Asynchronous Schedule
+    *  Enable bit in the USBCMD register. In order to communicate with devices
+    *  via the periodic schedule, system software must enable the periodic
+    *  schedule by writing a one to the Periodic Schedule Enable bit in the
+    *  USBCMD register. Note that the schedules can be turned on before the
+    *  first port is reset (and enabled)."
+    */
+
+    memset(&g_asynchead, 0, sizeof(struct usb_ehci_qh_s));
+    physaddr1 = usb_ehci_physramaddr((uintptr_t)&g_asynchead);
+    g_asynchead.hw.hlp = usb_ehci_swap32(physaddr1 | QH_HLP_TYP_QH);
+    g_asynchead.hw.epchar = usb_ehci_swap32(QH_EPCHAR_H |
+                                            QH_EPCHAR_EPS_FULL);
+    g_asynchead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
+    g_asynchead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T);
+    g_asynchead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED);
+    g_asynchead.fqp = usb_ehci_swap32(QTD_NQP_T);
+
+    g_ehci.exclsem = usb_osal_mutex_create();
+
+    /* Initialize the head of the periodic list.  Since Isochronous
+    * endpoints are not not yet supported, each element of the
+    * frame list is initialized to point to the Interrupt Queue
+    * Head (g_intrhead).
+    */
+
+    memset(&g_intrhead, 0, sizeof(struct usb_ehci_qh_s));
+    g_intrhead.hw.hlp = usb_ehci_swap32(QH_HLP_T);
+    g_intrhead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
+    g_intrhead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T);
+    g_intrhead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED);
+    g_intrhead.hw.epcaps = usb_ehci_swap32(QH_EPCAPS_SSMASK(1));
+
+    /* Attach the periodic QH to Period Frame List */
+
+    physaddr2 = usb_ehci_physramaddr((uintptr_t)&g_intrhead);
+    for (uint32_t i = 0; i < FRAME_LIST_SIZE; i++) {
+        g_framelist[i] = usb_ehci_swap32(physaddr2) | PFL_TYP_QH;
+    }
+
+    /* Set the Periodic Frame List Base Address. */
+    physaddr2 = usb_ehci_physramaddr((uintptr_t)g_framelist);
+
+    usb_ehci_pre_hw_init();
+    /* Host Controller Initialization. Paragraph 4.1 */
+
+    /* Reset the EHCI hardware */
+    ret = usb_ehci_reset();
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_RESET_FAILED, -ret);
+        return -1;
+    }
+
+    /* Disable all interrupts */
+    usb_ehci_putreg(0, &HCOR->usbintr);
+
+    /* Clear pending interrupts.  Bits in the USBSTS register are cleared by
+    * writing a '1' to the corresponding bit.
+    */
+    usb_ehci_putreg(EHCI_INT_ALLINTS, &HCOR->usbsts);
+
+#if defined(CONFIG_USB_EHCI_INFO_ENABLE)
+    /* Show the EHCI version */
+    uint16_t regval16 = usb_ehci_swap16(HCCR->hciversion);
+    //usbhost_vtrace2(EHCI_VTRACE2_HCIVERSION, regval16 >> 8, regval16 & 0xff);
+
+    /* Verify that the correct number of ports is reported */
+    regval = usb_ehci_getreg(&HCCR->hcsparams);
+    uint8_t nports = (regval & EHCI_HCSPARAMS_NPORTS_MASK) >> EHCI_HCSPARAMS_NPORTS_SHIFT;
+
+    //usbhost_vtrace2(EHCI_VTRACE2_HCSPARAMS, nports, regval);
+    //DEBUGASSERT(nports == LPC43_EHCI_NRHPORT);
+
+    /* Show the HCCPARAMS register */
+    regval = usb_ehci_getreg(&HCCR->hccparams);
+    //usbhost_vtrace1(EHCI_VTRACE1_HCCPARAMS, regval);
+#endif
+    /* Set the Current Asynchronous List Address. */
+    usb_ehci_putreg(usb_ehci_swap32(physaddr1), &HCOR->asynclistaddr);
+    /* Set the Periodic Frame List Base Address. */
+    usb_ehci_putreg(usb_ehci_swap32(physaddr2), &HCOR->periodiclistbase);
+
+    /* Enable the asynchronous schedule and, possibly enable the periodic
+    * schedule and set the frame list size.
+    */
+    regval = usb_ehci_getreg(&HCOR->usbcmd);
+    regval &= ~(EHCI_USBCMD_HCRESET | EHCI_USBCMD_FLSIZE_MASK |
+                EHCI_USBCMD_PSEN | EHCI_USBCMD_ASEN | EHCI_USBCMD_IAADB);
+    regval |= EHCI_USBCMD_ASEN;
+
+#ifndef CONFIG_USBHOST_INT_DISABLE
+    regval |= EHCI_USBCMD_PSEN;
+#if FRAME_LIST_SIZE == 1024
+    regval |= EHCI_USBCMD_FLSIZE_1024;
+#elif FRAME_LIST_SIZE == 512
+    regval |= EHCI_USBCMD_FLSIZE_512;
+#elif FRAME_LIST_SIZE == 512
+    regval |= EHCI_USBCMD_FLSIZE_256;
+#else
+#error Unsupported frame size list size
+#endif
+#endif
+
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+
+    /* Start the host controller by setting the RUN bit in the
+    * USBCMD register.
+    */
+    regval = usb_ehci_getreg(&HCOR->usbcmd);
+    regval |= EHCI_USBCMD_RUN;
+    usb_ehci_putreg(regval, &HCOR->usbcmd);
+
+    /* Route all ports to this host controller by setting the CONFIG flag. */
+    // regval = usb_ehci_getreg(&HCOR->configflag);
+    // regval |= EHCI_CONFIGFLAG;
+    // usb_ehci_putreg(regval, &HCOR->configflag);
+
+    /* Wait for the EHCI to run (i.e., no longer report halted) */
+    ret = usb_ehci_wait_usbsts(EHCI_USBSTS_HALTED, 0, 100 * 1000);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_RUN_FAILED, usb_ehci_getreg(&HCOR->usbsts));
+        return -2;
+    }
+
+    /* Enable EHCI interrupts.  Interrupts are still disabled at the level of
+    * the interrupt controller.
+    */
+    usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);
+
+    usb_ehci_last_hw_init();
+    return ret;
+}
+
+int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed)
+{
+    struct usb_ehci_epinfo_s *epinfo;
+    int ret;
+
+    epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo != NULL && ep_mps < 2048);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    epinfo->devaddr = dev_addr;
+    epinfo->speed = speed;
+    epinfo->maxpacket = ep_mps;
+
+    usb_osal_mutex_give(g_ehci.exclsem);
+
+    return ret;
+}
+
+int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
+{
+    // int ret;
+    struct usb_ehci_epinfo_s *epinfo;
+    struct usbh_hubport *hport;
+
+    DEBUGASSERT(ep_cfg != NULL && ep_cfg->hport != NULL);
+
+    // ret = usb_osal_mutex_take(g_ehci.exclsem);
+    // if (ret < 0) {
+    //     return ret;
+    // }
+
+    hport = ep_cfg->hport;
+
+    /* new roothub ep info */
+    if (((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) && (hport->parent == NULL)) {
+        epinfo = &g_ehci.ep0[hport->port - 1];
+    } else {
+        /* new exteranl hub ep info */
+        epinfo = usb_malloc(sizeof(struct usb_ehci_epinfo_s));
+        memset(epinfo, 0, sizeof(struct usb_ehci_epinfo_s));
+        if (epinfo == NULL) {
+            return -1;
+        }
+    }
+
+    epinfo->epno = ep_cfg->ep_addr & 0x7f;
+    epinfo->dirin = (ep_cfg->ep_addr & 0x80) ? 1 : 0;
+    epinfo->devaddr = hport->dev_addr;
+#ifndef CONFIG_USBHOST_INT_DISABLE
+    epinfo->interval = ep_cfg->ep_interval;
+#endif
+    epinfo->maxpacket = ep_cfg->ep_mps;
+    epinfo->xfrtype = ep_cfg->ep_type;
+    epinfo->speed = hport->speed;
+    epinfo->hport = hport;
+
+    epinfo->iocsem = usb_osal_sem_create(0);
+
+    *ep = epinfo;
+
+    //usb_osal_mutex_give(g_ehci.exclsem);
+
+    return 0;
+}
+
+int usbh_ep_free(usbh_epinfo_t ep)
+{
+    int ret;
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    usb_osal_sem_delete(epinfo->iocsem);
+    usb_free(epinfo);
+
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return 0;
+}
+
+int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
+{
+    int nbytes;
+    int ret;
+
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo);
+
+    /* A buffer may or may be supplied with an EP0 SETUP transfer.  A buffer
+    * will always be present for normal endpoint data transfers.
+    */
+
+    DEBUGASSERT(setup || buffer);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Set the request for the IOC event well BEFORE initiating the transfer. */
+    ret = usb_ehci_ioc_setup(epinfo);
+    if (ret != 0) {
+        //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
+        goto errout_with_sem;
+    }
+
+    ret = usb_ehci_control_setup(epinfo, setup, buffer, setup->wLength);
+    if (ret < 0) {
+        goto errout_with_iocwait;
+    }
+
+    /* And wait for the transfer to complete */
+    nbytes = usb_ehci_transfer_wait(epinfo);
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return nbytes >= 0 ? 0 : (int)nbytes;
+
+errout_with_iocwait:
+    epinfo->iocwait = false;
+errout_with_sem:
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return ret;
+}
+
+int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int nbytes;
+    int ret;
+
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo && buffer && buflen > 0);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = usb_ehci_ioc_setup(epinfo);
+    if (ret != 0) {
+        //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
+        goto errout_with_sem;
+    }
+
+    ret = usb_ehci_bulk_setup(epinfo, buffer, buflen);
+    if (ret < 0) {
+        goto errout_with_iocwait;
+    }
+
+    /* And wait for the transfer to complete */
+    nbytes = usb_ehci_transfer_wait(epinfo);
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return nbytes;
+
+errout_with_iocwait:
+    epinfo->iocwait = false;
+errout_with_sem:
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return ret;
+}
+
+int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int nbytes;
+    int ret;
+
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo && buffer && buflen > 0);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = usb_ehci_ioc_setup(epinfo);
+
+    if (ret != 0) {
+        //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
+        goto errout_with_sem;
+    }
+
+    ret = usb_ehci_intr_setup(epinfo, buffer, buflen);
+    if (ret < 0) {
+        goto errout_with_iocwait;
+    }
+
+    /* And wait for the transfer to complete */
+    nbytes = usb_ehci_transfer_wait(epinfo);
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return nbytes;
+
+errout_with_iocwait:
+    epinfo->iocwait = false;
+errout_with_sem:
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return ret;
+}
+#ifdef CONFIG_USBHOST_ASYNCH
+int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
+{
+    int ret;
+
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo && buffer && buflen > 0);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Set the request for the callback well BEFORE initiating the transfer. */
+    ret = usb_ehci_ioc_async_setup(epinfo, callback, arg);
+    if (ret != 0) {
+        goto errout_with_sem;
+    }
+
+    /* Check for errors in the setup of the transfer */
+    ret = usb_ehci_bulk_setup(epinfo, buffer, buflen);
+    if (ret < 0) {
+        goto errout_with_callback;
+    }
+
+    /* The transfer is in progress */
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return 0;
+
+errout_with_callback:
+    epinfo->callback = NULL;
+    epinfo->arg = NULL;
+errout_with_sem:
+    usb_osal_mutex_give(&g_ehci.exclsem);
+    return ret;
+}
+
+int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
+{
+    int ret;
+
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+
+    DEBUGASSERT(epinfo && buffer && buflen > 0);
+
+    ret = usb_osal_mutex_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Set the request for the callback well BEFORE initiating the transfer. */
+    ret = usb_ehci_ioc_async_setup(epinfo, callback, arg);
+    if (ret != 0) {
+        goto errout_with_sem;
+    }
+
+    /* Check for errors in the setup of the transfer */
+    ret = usb_ehci_intr_setup(epinfo, buffer, buflen);
+    if (ret < 0) {
+        goto errout_with_callback;
+    }
+
+    /* The transfer is in progress */
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return 0;
+
+errout_with_callback:
+    epinfo->callback = NULL;
+    epinfo->arg = NULL;
+errout_with_sem:
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return ret;
+}
+#endif
+/****************************************************************************
+ * Name: usb_ehci_cancel
+ *
+ * Description:
+ *   Cancel a pending transfer on an endpoint.  Canceled synchronous or
+ *   asynchronous transfer will complete normally with the error -ESHUTDOWN.
+ *
+ * Input Parameters:
+ *   drvr - The USB host driver instance obtained as a parameter from the
+ *          call to the class create() method.
+ *   ep   - The IN or OUT endpoint descriptor for the device endpoint on
+ *          which an asynchronous transfer should be transferred.
+ *
+ * Returned Value:
+ *   On success, zero (OK) is returned. On a failure, a negated errno value
+ *   is returned indicating the nature of the failure
+ *
+ ****************************************************************************/
+
+int usb_ep_cancel(usbh_epinfo_t ep)
+{
+    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
+    struct usb_ehci_qh_s *qh;
+#ifdef CONFIG_USBHOST_ASYNCH
+    usbh_asynch_callback_t callback;
+    void *arg;
+#endif
+    uint32_t *bp;
+    uint32_t flags;
+    bool iocwait;
+    int ret;
+
+    DEBUGASSERT(epinfo);
+
+    /* We must have exclusive access to the EHCI hardware and data structures.
+   * This will prevent servicing any transfer completion events while we
+   * perform the cancellation, but will not prevent DMA-related race
+   * conditions.
+   *
+   * REVISIT: This won't work.  This function must be callable from the
+   * interrupt level.
+   */
+
+    ret = usb_osal_sem_take(g_ehci.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Sample and reset all transfer termination information.  This will
+   * prevent any callbacks from occurring while are performing the
+   * cancellation.  The transfer may still be in progress, however, so this
+   * does not eliminate other DMA-related race conditions.
+   */
+
+    flags = usb_osal_enter_critical_section();
+#ifdef CONFIG_USBHOST_ASYNCH
+    callback = epinfo->callback;
+    arg = epinfo->arg;
+#endif
+    iocwait = epinfo->iocwait;
+
+#ifdef CONFIG_USBHOST_ASYNCH
+    epinfo->callback = NULL;
+    epinfo->arg = NULL;
+#endif
+    epinfo->iocwait = false;
+
+    /* This will prevent any callbacks from occurring while are performing
+   * the cancellation.  The transfer may still be in progress, however, so
+   * this does not eliminate other DMA-related race conditions.
+   */
+
+    epinfo->callback = NULL;
+    epinfo->arg = NULL;
+    usb_osal_leave_critical_section(flags);
+
+    /* Bail if there is no transfer in progress for this endpoint */
+
+#ifdef CONFIG_USBHOST_ASYNCH
+    if (callback == NULL && !iocwait)
+#else
+    if (!iocwait)
+#endif
+    {
+        ret = 0;
+        goto errout_with_sem;
+    }
+
+    /* Handle the cancellation according to the type of the transfer */
+
+    switch (epinfo->xfrtype) {
+        case USB_ENDPOINT_TYPE_CONTROL:
+        case USB_ENDPOINT_TYPE_BULK: {
+            /* Get the horizontal pointer from the head of the asynchronous
+           * queue.
+           */
+
+            bp = (uint32_t *)&g_asynchead.hw.hlp;
+            qh = (struct usb_ehci_qh_s *)
+                usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
+
+            /* If the asynchronous queue is empty, then the forward point in
+           * the asynchronous queue head will point back to the queue
+           * head.
+           */
+
+            if (qh && qh != &g_asynchead) {
+                /* Claim that we successfully cancelled the transfer */
+
+                ret = 0;
+                goto exit_terminate;
+            }
+        } break;
+
+#ifndef CONFIG_USBHOST_INT_DISABLE
+        case USB_ENDPOINT_TYPE_INTERRUPT: {
+            /* Get the horizontal pointer from the head of the interrupt
+           * queue.
+           */
+
+            bp = (uint32_t *)&g_intrhead.hw.hlp;
+            qh = (struct usb_ehci_qh_s *)
+                usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
+            if (qh) {
+                /* if the queue is empty, then just claim that we successfully
+               * cancelled the transfer.
+               */
+
+                ret = 0;
+                goto exit_terminate;
+            }
+        } break;
+#endif
+
+#ifndef CONFIG_USBHOST_ISOC_DISABLE
+        case USB_ENDPOINT_TYPE_ISOCHRONOUS:
+#warning "Isochronous endpoint support not emplemented"
+#endif
+        default:
+            //usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype);
+            ret = -ENOSYS;
+            goto errout_with_sem;
+    }
+
+    /* Find and remove the QH.  There are four possibilities:
+    *
+    * 1)  The transfer has already completed and the QH is no longer in the
+    *     list.  In this case, sam_hq_foreach will return zero
+    * 2a) The transfer is not active and still pending.  It was removed from
+    *     the list and sam_hq_foreach will return one.
+    * 2b) The is active but not yet complete.  This is currently handled the
+    *     same as 2a).  REVISIT: This needs to be fixed.
+    * 3)  Some bad happened and sam_hq_foreach returned an error code < 0.
+    */
+
+    ret = usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_cancel, epinfo);
+    if (ret < 0) {
+        //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
+    }
+
+    /* Was there a pending synchronous transfer? */
+
+exit_terminate:
+    epinfo->result = -ESHUTDOWN;
+#ifdef CONFIG_USBHOST_ASYNCH
+    if (iocwait) {
+        /* Yes... wake it up */
+
+        DEBUGASSERT(callback == NULL);
+        usb_osal_sem_give(epinfo->iocsem);
+    }
+
+    /* No.. Is there a pending asynchronous transfer? */
+
+    else /* if (callback != NULL) */
+    {
+        /* Yes.. perform the callback */
+
+        callback(arg, -ESHUTDOWN);
+    }
+
+#else
+    /* Wake up the waiting thread */
+
+    usb_osal_sem_give(epinfo->iocsem);
+#endif
+
+errout_with_sem:
+    usb_osal_mutex_give(g_ehci.exclsem);
+    return ret;
+}
+
+static void usb_ehci_bottomhalf(void *arg)
+{
+    uint32_t pending = (uint32_t)arg;
+    /* We need to have exclusive access to the EHCI data structures.  Waiting
+    * here is not a good thing to do on the worker thread, but there is no
+    * real option (other than to reschedule and delay).
+    */
+
+    usb_osal_mutex_take(g_ehci.exclsem);
+
+    /* Handle all unmasked interrupt sources */
+    /* USB Interrupt (USBINT)
+        *
+        *  "The Host Controller sets this bit to 1 on the completion of a USB
+        *   transaction, which results in the retirement of a Transfer Descriptor
+        *   that had its IOC bit set.
+        *
+        *  "The Host Controller also sets this bit to 1 when a short packet is
+        *   detected (actual number of bytes received was less than the expected
+        *   number of bytes)."
+        *
+        * USB Error Interrupt (USBERRINT)
+        *
+        *  "The Host Controller sets this bit to 1 when completion of a USB
+        *   transaction results in an error condition (e.g., error counter
+        *   underflow). If the TD on which the error interrupt occurred also
+        *   had its IOC bit set, both this bit and USBINT bit are set. ..."
+        *
+        * We do the same thing in either case:  Traverse the asynchronous queue
+        * and remove all of the transfers that are no longer active.
+        */
+    if ((pending & (EHCI_INT_USBINT | EHCI_INT_USBERRINT)) != 0) {
+        if ((pending & EHCI_INT_USBERRINT) != 0) {
+            //usbhost_trace1(EHCI_TRACE1_USBERR_INTR, pending);
+        } else {
+            //usbhost_vtrace1(EHCI_VTRACE1_USBINTR, pending);
+        }
+
+        usb_ehci_ioc_bottomhalf();
+    }
+    /* Port Change Detect
+        *
+        *  "The Host Controller sets this bit to a one when any port for which
+        *   the Port Owner bit is set to zero ... has a change bit transition
+        *   from a zero to a one or a Force Port Resume bit transition from a zero
+        *   to a one as a result of a J-K transition detected on a suspended port.
+        *   This bit will also be set as a result of the Connect Status Change
+        *   being set to a one after system software has relinquished ownership
+        *    of a connected port by writing a one to a port's Port Owner bit...
+        *
+        *  "This bit is allowed to be maintained in the Auxiliary power well.
+        *   Alternatively, it is also acceptable that on a D3 to D0 transition
+        *   of the EHCI HC device, this bit is loaded with the OR of all of the
+        *   PORTSC change bits (including: Force port resume, over-current change,
+        *   enable/disable change and connect status change)."
+        */
+    if ((pending & EHCI_INT_PORTSC) != 0) {
+        usb_ehci_portsc_bottomhalf();
+    }
+    /* Frame List Rollover
+        *
+        *  "The Host Controller sets this bit to a one when the Frame List Index
+        *   ... rolls over from its maximum value to zero. The exact value at
+        *   which the rollover occurs depends on the frame list size. For example,
+        *   if the frame list size (as programmed in the Frame List Size field of
+        *   the USBCMD register) is 1024, the Frame Index Register rolls over
+        *   every time FRINDEX[13] toggles. Similarly, if the size is 512, the
+        *   Host Controller sets this bit to a one every time FRINDEX[12]
+        *   toggles."
+        */
+
+#if 0 /* Not used */
+        if ((pending & EHCI_INT_FLROLL) != 0)
+            {
+
+            }
+#endif
+    /* Host System Error
+        *
+        *  "The Host Controller sets this bit to 1 when a serious error occurs
+        *   during a host system access involving the Host Controller module. ...
+        *   When this error occurs, the Host Controller clears the Run/Stop bit
+        *   in the Command register to prevent further execution of the scheduled
+        *   TDs."
+        */
+
+    if ((pending & EHCI_INT_SYSERROR) != 0) {
+        //usbhost_trace1(EHCI_TRACE1_SYSERR_INTR, 0);
+    }
+    /* Interrupt on Async Advance
+        *
+        *  "System software can force the host controller to issue an interrupt
+        *   the next time the host controller advances the asynchronous schedule
+        *   by writing a one to the Interrupt on Async Advance Doorbell bit in
+        *   the USBCMD register. This status bit indicates the assertion of that
+        *   interrupt source."
+        */
+
+    if ((pending & EHCI_INT_AAINT) != 0) {
+        //usbhost_vtrace1(EHCI_VTRACE1_AAINTR, 0);
+    }
+    /* Re-enable relevant EHCI interrupts.  Interrupts should still be enabled
+    * at the level of the interrupt controller.
+    */
+
+    usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);
+
+    /* We are done with the EHCI structures */
+    usb_osal_mutex_give(g_ehci.exclsem);
+}
+
+void usb_ehci_interrupt(void)
+{
+    uint32_t usbsts;
+    uint32_t pending;
+    uint32_t regval;
+
+    /* Read Interrupt Status and mask out interrupts that are not enabled. */
+
+    usbsts = usb_ehci_getreg(&HCOR->usbsts);
+    regval = usb_ehci_getreg(&HCOR->usbintr);
+
+    /* Handle all unmasked interrupt sources */
+    pending = usbsts & regval;
+
+    if (pending != 0) {
+
+        /* Schedule interrupt handling work for the high priority worker
+        * thread so that we are not pressed for time and so that we can
+        * interrupt with other USB threads gracefully.
+        *
+        * The worker should be available now because we implement a handshake
+        * by controlling the EHCI interrupts.
+        */
+
+        usb_workqueue_submit(&g_hpworkq, &g_ehci.work, usb_ehci_bottomhalf, (void *)pending, 0);
+        /* Disable further EHCI interrupts so that we do not overrun the work
+        * queue.
+        */
+        usb_ehci_putreg(0, &HCOR->usbintr);
+
+        /* Clear all pending status bits by writing the value of the pending
+        * interrupt bits back to the status register.
+        */
+        usb_ehci_putreg(usbsts & EHCI_INT_ALLINTS, &HCOR->usbsts);
+    }
+}

+ 1006 - 0
port/ehci/usb_ehci.h

@@ -0,0 +1,1006 @@
+/****************************************************************************
+ * include/nuttx/usb/ehci.h
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_USB_EHCI_H
+#define __INCLUDE_NUTTX_USB_EHCI_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* General definitions ******************************************************/
+
+/* Endpoint speed values as used in endpoint characteristics field.
+ * NOTE:  These values are *NOT* the same as the SPEED definitions in usb.h.
+ */
+
+#define EHCI_FULL_SPEED               (0) /* Full-Speed (12Mbs) */
+#define EHCI_LOW_SPEED                (1) /* Low-Speed (1.5Mbs) */
+#define EHCI_HIGH_SPEED               (2) /* High-Speed (480 Mb/s) */
+
+#define EHCI_DIR_IN                   (1) /* Direction IN: Peripheral to host */
+#define EHCI_DIR_OUT                  (0) /* Direction OUT: Host to peripheral */
+
+/* PCI Configuration Space Register Offsets *********************************/
+
+/* Paragraph 2.1 */
+
+/* 0x0009-0x000b: Class Code */
+
+#define EHCI_PCI_CLASSC_OFFSET         0x0009
+
+/* 0x0010-0x0013: Base Address to Memory-mapped Host Controller Register
+ * Space
+ */
+
+#define EHCI_PCIUSBBASE_OFFSET         0x0010
+
+/* 0x0060: Serial Bus Release Number */
+
+#define EHCI_PCI_SBRN_OFFSET           0x0060
+
+/* 0x0061: Frame Length Adjustment Register */
+
+#define EHCI_PCI_FLADJ_OFFSET          0x0061
+
+/* 0x0062-0x0063: Port wake capabilities register (OPTIONAL) */
+
+#define EHCI_PCI_PORTWAKECAP_OFFSET    0x0062
+
+/* EECP+0x0000: USB Legacy Support EHCI Extended Capability Register */
+
+#define EHCI_PCI_USBLEGSUP_OFFSET      0x0000
+
+/* EECP+0x0000: USB Legacy Support Control and Status Register */
+
+#define EHCI_PCI_USBLEGCTLSTS_OFFSET   0x0004
+
+/* Host Controller Capability Register Offsets ******************************/
+
+/* Paragraph 2.2 */
+
+#define EHCI_CAPLENGTH_OFFSET          0x0000     /* Core Capability Register Length */
+                                                  /* 0x0001 Reserved  */
+#define EHCI_HCIVERSION_OFFSET         0x0002     /* Core Interface Version Number */
+#define EHCI_HCSPARAMS_OFFSET          0x0004     /* Core Structural Parameters */
+#define EHCI_HCCPARAMS_OFFSET          0x0008     /* Core Capability Parameters */
+#define EHCI_HCSP_PORTROUTE_OFFSET     0x000c     /* Core Companion Port Route Description */
+
+/* Host Controller Operational Register Offsets *****************************/
+
+/* Paragraph 2.3 */
+
+#define EHCI_USBCMD_OFFSET             0x0000     /* USB Command */
+#define EHCI_USBSTS_OFFSET             0x0004     /* USB Status */
+#define EHCI_USBINTR_OFFSET            0x0008     /* USB Interrupt Enable */
+#define EHCI_FRINDEX_OFFSET            0x000c     /* USB Frame Index */
+#define EHCI_CTRLDSSEGMENT_OFFSET      0x0010     /* 4G Segment Selector */
+#define EHCI_PERIODICLISTBASE_OFFSET   0x0014     /* Frame List Base Address */
+#define EHCI_ASYNCLISTADDR_OFFSET      0x0018     /* Next Asynchronous List Address */
+                                                  /* 0x001c-0x003f: Reserved */
+#define EHCI_CONFIGFLAG_OFFSET         0x0040     /* Configured Flag Register */
+
+/* Port Status/Control, Port 1-n */
+
+#define EHCI_PORTSC_OFFSET(n)          (0x0044 + ((n-1) << 2))
+#define EHCI_PORTSC1_OFFSET            0x0044     /* Port Status/Control, Port 1 */
+#define EHCI_PORTSC2_OFFSET            0x0048     /* Port Status/Control, Port 2 */
+#define EHCI_PORTSC3_OFFSET            0x004c     /* Port Status/Control, Port 3 */
+#define EHCI_PORTSC4_OFFSET            0x0050     /* Port Status/Control, Port 4 */
+#define EHCI_PORTSC5_OFFSET            0x0054     /* Port Status/Control, Port 5 */
+#define EHCI_PORTSC6_OFFSET            0x0058     /* Port Status/Control, Port 6 */
+#define EHCI_PORTSC7_OFFSET            0x005c     /* Port Status/Control, Port 7 */
+#define EHCI_PORTSC8_OFFSET            0x0060     /* Port Status/Control, Port 8 */
+#define EHCI_PORTSC9_OFFSET            0x0064     /* Port Status/Control, Port 9 */
+#define EHCI_PORTSC10_OFFSET           0x0068     /* Port Status/Control, Port 10 */
+#define EHCI_PORTSC11_OFFSET           0x006c     /* Port Status/Control, Port 11 */
+#define EHCI_PORTSC12_OFFSET           0x0070     /* Port Status/Control, Port 12 */
+#define EHCI_PORTSC13_OFFSET           0x0074     /* Port Status/Control, Port 13 */
+#define EHCI_PORTSC14_OFFSET           0x0078     /* Port Status/Control, Port 14 */
+#define EHCI_PORTSC15_OFFSET           0x007c     /* Port Status/Control, Port 15 */
+
+/* Debug Register Offsets ***************************************************/
+
+/* Paragraph C.3 */
+
+#define EHCI_DEBUG_PCS_OFFSET          0x0000     /* Debug Port Control/Status Register */
+#define EHCI_DEBUG_USBPIDS_OFFSET      0x0004     /* Debug USB PIDs Register */
+#define EHCI_DEBUG_DATA0_OFFSET        0x0008     /* Debug Data Buffer 0 Register [31:0]  */
+#define EHCI_DEBUG_DATA1_OFFSET        0x000c     /* Debug Data Buffer 1 Register [63:32]  */
+#define EHCI_DEBUG_DEVADDR_OFFSET      0x0010     /* Debug Device Address Register */
+
+/* PCI Configuration Space Register Bit Definitions *************************/
+
+/* 0x0009-0x000b: Class Code.  Paragraph 2.1.2 */
+
+#define EHCI_PCI_PI_SHIFT              (0)        /* Bits 0-7: Programming Interface */
+#define EHCI_PCI_PI_MASK               (0xff << EHCI_PCI_PI_SHIFT)
+#  define EHCI_PCI_PI                  (0x20 << EHCI_PCI_PI_SHIFT)
+#define EHCI_PCI_SCC_SHIFT             (8)        /* Bits 8-15: Sub-Class Code */
+#define EHCI_PCI_SCC_MASK              (0xff << EHCI_PCI_SCC_SHIFT)
+#    define EHCI_PCI_SCC               (0x03 << EHCI_PCI_SCC_SHIFT)
+#define EHCI_PCI_BASEC_SHIFT           (16)       /* Base Class Code */
+#define EHCI_PCI_BASEC_MASK            (0xff << EHCI_PCI_BASEC_SHIFT)
+#    define EHCI_PCI_BASEC             (0x0c << EHCI_PCI_BASEC_SHIFT)
+#define EHCI_PCI_CLASSC                0x000c0320 /* Default value (little endian) */
+
+/* 0x0010-0x0013: Base Address to Memory-mapped Host Controller Register
+ * Space.  Paragraph 2.1.3
+ */
+
+                                                  /* Bit 0: Reserved */
+
+#define EHCI_PCIUSBBASE_TYPE_SHIFT     (0)        /* Bits 1-2: Type */
+#define EHCI_PCIUSBBASE_TYPE_MASK      (3 << EHCI_PCIUSBBASE_TYPE_SHIFT)
+#  define EHCI_PCIUSBBASE_TYPE_32BIT   (3 << EHCI_PCIUSBBASE_TYPE_SHIFT) /* 32-bit addressing */
+#  define EHCI_PCIUSBBASE_TYPE_64BIT   (3 << EHCI_PCIUSBBASE_TYPE_SHIFT) /* 64-bit addressing */
+
+                                                  /* Bits 3-7: Reserved */
+#define EHCI_PCIUSBBASE_BASE_SHIFT     (8)        /* Bits 8-31: Base address */
+#define EHCI_PCIUSBBASE_BASE_MASK      (0xffffff00)
+
+/* 0x0060: Serial Bus Release Number.  Paragraph 2.1.4 */
+
+#define EHCI_PCI_SBRN_MASK             0xff       /* Bits 0-7: Serial Bus Release Number */
+
+/* 0x0061: Frame Length Adjustment Register.  Paragraph 2.1.5 */
+
+#define EHCI_PCI_FLADJ_SHIFT           (0)        /* Bit 0-5: Frame Length Timing Value */
+#define EHCI_PCI_FLADJ_MASK            (0x3f >> EHCI_PCI_FLADJ_SHIFT)
+                                                  /* Bits 6-7: Reserved */
+
+/* 0x0062-0x0063: Port wake capabilities register (OPTIONAL).
+ *  Paragraph 2.1.6
+ */
+
+#define EHCI_PCI_PORTWAKECAP_MASK      (0xffff)
+
+/* EECP+0x0000: USB Legacy Support EHCI Extended Capability Register.
+ *  Paragraph 2.1.7
+ */
+
+#define EHCI_PCI_USBLEGSUP_CAPID_SHIFT (0)        /* Bits 0-7 Capability ID */
+#define EHCI_PCI_USBLEGSUP_CAPID_MASK  (0xff << EHCI_PCI_USBLEGSUP_CAPID_SHIFT)
+#define EHCI_PCI_USBLEGSUP_NEECP_SHIFT (8)        /* Bits 8-15: Next EHCI Extended Capability Pointer */
+#define EHCI_PCI_USBLEGSUP_NEECP_MASK  (0xff << EHCI_PCI_USBLEGSUP_NEECP_SHIFT)
+#define EHCI_PCI_USBLEGSUP_BOWN        (1 << 16)  /* Bit 16: HC BIOS Owned Semaphore */
+                                                  /* Bits 17-23: Reserved */
+#define EHCI_PCI_USBLEGSUP_OSOWN       (1 << 24)  /* Bit 24: HC OS Owned Semaphore */
+                                                  /* Bits 25-31: Reserved */
+
+/* EECP+0x0000: USB Legacy Support Control and Status Register.
+ * Paragraph 2.1.8
+ */
+
+#define EHCI_PCI_USBLEGCTLSTS_USBCMPEN (1 << 0)   /* Bit 0:  USB SMI Enable */
+#define EHCI_PCI_USBLEGCTLSTS_USBERREN (1 << 1)   /* Bit 1:  SMI on USB Error Enable */
+#define EHCI_PCI_USBLEGCTLSTS_PCHEN    (1 << 2)   /* Bit 2:  SMI on Port Change Enable */
+#define EHCI_PCI_USBLEGCTLSTS_FLREN    (1 << 3)   /* Bit 3:  SMI on Frame List Rollover Enable */
+#define EHCI_PCI_USBLEGCTLSTS_HSEEN    (1 << 4)   /* Bit 4:  SMI on Host System Error Enable */
+#define EHCI_PCI_USBLEGCTLSTS_AAEN     (1 << 5)   /* Bit 5:  SMI on Async Advance Enable */
+                                                  /* Bits 6-12: Reserved */
+#define EHCI_PCI_USBLEGCTLSTS_OOEN     (1 << 13)  /* Bit 13: SMI on OS Ownership Enable */
+#define EHCI_PCI_USBLEGCTLSTS_PCEN     (1 << 14)  /* Bit 14: SMI on PCI Command Enable */
+#define EHCI_PCI_USBLEGCTLSTS_BAREN    (1 << 15)  /* Bit 15: SMI on BAR Enable */
+#define EHCI_PCI_USBLEGCTLSTS_USBCMP   (1 << 16)  /* Bit 16: SMI on USB Complete */
+#define EHCI_PCI_USBLEGCTLSTS_USBERR   (1 << 17)  /* Bit 17: SMI on USB Error */
+#define EHCI_PCI_USBLEGCTLSTS_PCH      (1 << 18)  /* Bit 18: SMI on Port Change Detect */
+#define EHCI_PCI_USBLEGCTLSTS_FLR      (1 << 19)  /* Bit 19: SMI on Frame List Rollover */
+#define EHCI_PCI_USBLEGCTLSTS_HSE      (1 << 20)  /* Bit 20: SMI on Host System Error */
+#define EHCI_PCI_USBLEGCTLSTS_AA       (1 << 21)  /* Bit 21: SMI on Async Advance */
+                                                  /* Bits 22-28: Reserved */
+#define EHCI_PCI_USBLEGCTLSTS_OO       (1 << 29)  /* Bit 29: SMI on OS Ownership Change */
+#define EHCI_PCI_USBLEGCTLSTS_PC       (1 << 30)  /* Bit 30: SMI on PCI Command */
+#define EHCI_PCI_USBLEGCTLSTS_BAR      (1 << 31)  /* Bit 31: SMI on BAR */
+
+/* Host Controller Capability Register Bit Definitions **********************/
+
+/* Paragraph 2.2 */
+
+/* Core Capability Register Length. Paragraph 2.2.1. 8-bit length. */
+
+/* Core Interface Version Number. Paragraph 2.2.2.  Two byte BCD encoding */
+
+/* Core Structural Parameters. Paragraph 2.2.3 */
+
+#define EHCI_HCSPARAMS_NPORTS_SHIFT    (0)        /* Bit 0-3: Number of physical downstream ports */
+#define EHCI_HCSPARAMS_NPORTS_MASK     (15 << EHCI_HCSPARAMS_NPORTS_SHIFT)
+#define EHCI_HCSPARAMS_PPC             (1 << 4)   /* Bit 4: Port Power Control */
+                                                  /* Bits 5-6: Reserved */
+#define EHCI_HCSPARAMS_PRR             (1 << 7)   /* Bit 7: Port Routing Rules */
+#define EHCI_HCSPARAMS_NPCC_SHIFT      (8)        /* Bit 8-11: Number of Ports per Companion Controller */
+#define EHCI_HCSPARAMS_NPCC_MASK       (15 << EHCI_HCSPARAMS_NPCC_SHIFT)
+#define EHCI_HCSPARAMS_NCC_SHIFT       (12)       /* Bit 12-15: Number of Companion Controllers */
+#define EHCI_HCSPARAMS_NCC_MASK        (15 << EHCI_HCSPARAMS_NCC_SHIFT)
+#define EHCI_HCSPARAMS_PIND            (1 << 16)  /* Bit 16: Port Indicators */
+                                                  /* Bits 17-19: Reserved */
+#define EHCI_HCSPARAMS_DBGPORT_SHIFT   (20)       /* Bit 20-23: Debug Port Number */
+#define EHCI_HCSPARAMS_DBGPORT_MASK    (15 << EHCI_HCSPARAMS_DBGPORT_SHIFT)
+                                                  /* Bits 24-31: Reserved */
+
+/* Core Capability Parameters. Paragraph 2.2.4 */
+
+#define EHCI_HCCPARAMS_64BIT           (1 << 0)   /* Bit 0: 64-bit Addressing Capability */
+#define EHCI_HCCPARAMS_PFLF            (1 << 1)   /* Bit 1: Programmable Frame List Flag */
+#define EHCI_HCCPARAMS_ASPC            (1 << 2)   /* Bit 2: Asynchronous Schedule Park Capability */
+                                                  /* Bit 3: Reserved */
+#define EHCI_HCCPARAMS_IST_SHIFT       (4)        /* Bits 4-7: Isochronous Scheduling Threshold */
+#define EHCI_HCCPARAMS_IST_MASK        (15 << EHCI_HCCPARAMS_IST_SHIFT)
+#define EHCI_HCCPARAMS_EECP_SHIFT      (8)        /* Bits 8-15: EHCI Extended Capabilities Pointer */
+#define EHCI_HCCPARAMS_EECP_MASK       (0xff << EHCI_HCCPARAMS_EECP_SHIFT)
+                                                  /* Bits 16-31: Reserved */
+
+/* Core Companion Port Route Description.
+ * Paragraph 2.2.5. 15 x 4-bit array (60 bits)
+ */
+
+/* Host Controller Operational Register Bit Definitions *********************/
+
+/* Paragraph 2.3 */
+
+/* USB Command. Paragraph 2.3.1 */
+
+#define EHCI_USBCMD_RUN                (1 << 0)   /* Bit 0: Run/Stop */
+#define EHCI_USBCMD_HCRESET            (1 << 1)   /* Bit 1: Host Controller Reset */
+#define EHCI_USBCMD_FLSIZE_SHIFT       (2)        /* Bits 2-3: Frame List Size */
+#define EHCI_USBCMD_FLSIZE_MASK        (3 << EHCI_USBCMD_FLSIZE_SHIFT)
+#  define EHCI_USBCMD_FLSIZE_1024      (0 << EHCI_USBCMD_FLSIZE_SHIFT) /* 1024 elements (4096 bytes) */
+#  define EHCI_USBCMD_FLSIZE_512       (1 << EHCI_USBCMD_FLSIZE_SHIFT) /* 512 elements (2048 bytes) */
+#  define EHCI_USBCMD_FLSIZE_256       (2 << EHCI_USBCMD_FLSIZE_SHIFT) /* 256 elements (1024 bytes) */
+
+#define EHCI_USBCMD_PSEN               (1 << 4)   /* Bit 4: Periodic Schedule Enable */
+#define EHCI_USBCMD_ASEN               (1 << 5)   /* Bit 5: Asynchronous Schedule Enable */
+#define EHCI_USBCMD_IAADB              (1 << 6)   /* Bit 6: Interrupt on Async Advance Doorbell */
+#define EHCI_USBCMD_LRESET             (1 << 7)   /* Bit 7: Light Host Controller Reset */
+#define EHCI_USBCMD_PARKCNT_SHIFT      (8)        /* Bits 8-9: Asynchronous Schedule Park Mode Count */
+#define EHCI_USBCMD_PARKCNT_MASK       (3 << EHCI_USBCMD_PARKCNT_SHIFT)
+                                                  /* Bit 10: Reserved */
+#define EHCI_USBCMD_PARK               (1 << 11)  /* Bit 11: Asynchronous Schedule Park Mode Enable */
+                                                  /* Bits 12-15: Reserved */
+#define EHCI_USBCMD_ITHRE_SHIFT        (16)       /* Bits 16-23: Interrupt Threshold Control */
+#define EHCI_USBCMD_ITHRE_MASK         (0xff << EHCI_USBCMD_ITHRE_SHIFT)
+#  define EHCI_USBCMD_ITHRE_1MF        (0x01 << EHCI_USBCMD_ITHRE_SHIFT) /* 1 micro-frame */
+#  define EHCI_USBCMD_ITHRE_2MF        (0x02 << EHCI_USBCMD_ITHRE_SHIFT) /* 2 micro-frames */
+#  define EHCI_USBCMD_ITHRE_4MF        (0x04 << EHCI_USBCMD_ITHRE_SHIFT) /* 4 micro-frames */
+#  define EHCI_USBCMD_ITHRE_8MF        (0x08 << EHCI_USBCMD_ITHRE_SHIFT) /* 8 micro-frames (default, 1 ms) */
+#  define EHCI_USBCMD_ITHRE_16MF       (0x10 << EHCI_USBCMD_ITHRE_SHIFT) /* 16 micro-frames (2 ms) */
+#  define EHCI_USBCMD_ITHRE_32MF       (0x20 << EHCI_USBCMD_ITHRE_SHIFT) /* 32 micro-frames (4 ms) */
+#  define EHCI_USBCMD_ITHRE_64MF       (0x40 << EHCI_USBCMD_ITHRE_SHIFT) /* 64 micro-frames (8 ms) */
+
+                                                  /* Bits 24-31: Reserved */
+
+/* USB Status. Paragraph 2.3.2 */
+
+/* USB Interrupt Enable. Paragraph 2.3.3 */
+
+#define EHCI_INT_USBINT                (1 << 0)   /* Bit 0:  USB Interrupt */
+#define EHCI_INT_USBERRINT             (1 << 1)   /* Bit 1:  USB Error Interrupt */
+#define EHCI_INT_PORTSC                (1 << 2)   /* Bit 2:  Port Change Detect */
+#define EHCI_INT_FLROLL                (1 << 3)   /* Bit 3:  Frame List Rollover */
+#define EHCI_INT_SYSERROR              (1 << 4)   /* Bit 4:  Host System Error */
+#define EHCI_INT_AAINT                 (1 << 5)   /* Bit 5:  Interrupt on Async Advance */
+#define EHCI_INT_ALLINTS               (0x3f)     /* Bits 0-5:  All interrupts */
+                                                  /* Bits 6-11: Reserved */
+#define EHCI_USBSTS_HALTED             (1 << 12)  /* Bit 12: HC Halted */
+#define EHCI_USBSTS_RECLAM             (1 << 13)  /* Bit 13: Reclamation */
+#define EHCI_USBSTS_PSS                (1 << 14)  /* Bit 14: Periodic Schedule Status */
+#define EHCI_USBSTS_ASS                (1 << 15)  /* Bit 15: Asynchronous Schedule Status */
+                                                  /* Bits 16-31: Reserved */
+
+/* USB Frame Index. Paragraph 2.3.4 */
+
+#define EHCI_FRINDEX_MASK              (0x1fff)   /* Bits 0-13: Frame index */
+                                                  /* Bits 14-31: Reserved */
+
+/* 4G Segment Selector.
+ * Paragraph 2.3.5,  Bits[64:32] of data structure addresses
+ */
+
+/* Frame List Base Address. Paragraph 2.3.6 */
+
+                                                    /* Bits 0-11: Reserved */
+#define EHCI_PERIODICLISTBASE_MASK     (0xfffff000) /* Bits 12-31: Base Address (Low) */
+
+/* Next Asynchronous List Address. Paragraph 2.3.7 */
+
+                                                    /* Bits 0-4: Reserved */
+#define EHCI_ASYNCLISTADDR_MASK        (0xffffffe0) /* Bits 5-31: Link Pointer Low (LPL) */
+
+/* Configured Flag Register. Paragraph 2.3.8 */
+
+#define EHCI_CONFIGFLAG              (1 << 0)   /* Bit 0: Configure Flag */
+                                                /* Bits 1-31: Reserved */
+
+/* Port Status/Control, Port 1-n. Paragraph 2.3.9 */
+
+#define EHCI_PORTSC_CCS                (1 << 0)   /* Bit 0: Current Connect Status */
+#define EHCI_PORTSC_CSC                (1 << 1)   /* Bit 1: Connect Status Change */
+#define EHCI_PORTSC_PE                 (1 << 2)   /* Bit 2: Port Enable */
+#define EHCI_PORTSC_PEC                (1 << 3)   /* Bit 3: Port Enable/Disable Change */
+#define EHCI_PORTSC_OCA                (1 << 4)   /* Bit 4: Over-current Active */
+#define EHCI_PORTSC_OCC                (1 << 5)   /* Bit 5: Over-current Change */
+#define EHCI_PORTSC_RESUME             (1 << 6)   /* Bit 6: Force Port Resume */
+#define EHCI_PORTSC_SUSPEND            (1 << 7)   /* Bit 7: Suspend */
+#define EHCI_PORTSC_RESET              (1 << 8)   /* Bit 8: Port Reset */
+                                                  /* Bit 9: Reserved */
+#define EHCI_PORTSC_LSTATUS_SHIFT      (10)       /* Bits 10-11: Line Status */
+#define EHCI_PORTSC_LSTATUS_MASK       (3 << EHCI_PORTSC_LSTATUS_SHIFT)
+#  define EHCI_PORTSC_LSTATUS_SE0      (0 << EHCI_PORTSC_LSTATUS_SHIFT) /* SE0 Not Low-speed device, perform EHCI reset */
+#  define EHCI_PORTSC_LSTATUS_KSTATE   (1 << EHCI_PORTSC_LSTATUS_SHIFT) /* K-state Low-speed device, release ownership of port */
+#  define EHCI_PORTSC_LSTATUS_JSTATE   (2 << EHCI_PORTSC_LSTATUS_SHIFT) /* J-state Not Low-speed device, perform EHCI reset */
+
+#define EHCI_PORTSC_PP                 (1 << 12)  /* Bit 12: Port Power */
+#define EHCI_PORTSC_OWNER              (1 << 13)  /* Bit 13: Port Owner */
+#define EHCI_PORTSC_PIC_SHIFT          (14)       /* Bits 14-15: Port Indicator Control */
+#define EHCI_PORTSC_PIC_MASK           (3 << EHCI_PORTSC_PIC_SHIFT)
+#  define EHCI_PORTSC_PIC_OFF          (0 << EHCI_PORTSC_PIC_SHIFT) /* Port indicators are off */
+#  define EHCI_PORTSC_PIC_AMBER        (1 << EHCI_PORTSC_PIC_SHIFT) /* Amber */
+#  define EHCI_PORTSC_PIC_GREEN        (2 << EHCI_PORTSC_PIC_SHIFT) /* Green */
+
+#define EHCI_PORTSC_PTC_SHIFT          (16)       /* Bits 16-19: Port Test Control */
+#define EHCI_PORTSC_PTC_MASK           (15 << EHCI_PORTSC_PTC_SHIFT)
+#  define EHCI_PORTSC_PTC_DISABLED     (0 << EHCI_PORTSC_PTC_SHIFT) /* Test mode not enabled */
+#  define EHCI_PORTSC_PTC_JSTATE       (1 << EHCI_PORTSC_PTC_SHIFT) /* Test J_STATE */
+#  define EHCI_PORTSC_PTC_KSTATE       (2 << EHCI_PORTSC_PTC_SHIFT) /* Test K_STATE */
+#  define EHCI_PORTSC_PTC_SE0NAK       (3 << EHCI_PORTSC_PTC_SHIFT) /* Test SE0_NAK */
+#  define EHCI_PORTSC_PTC_PACKET       (4 << EHCI_PORTSC_PTC_SHIFT) /* Test Packet */
+#  define EHCI_PORTSC_PTC_ENABLE       (5 << EHCI_PORTSC_PTC_SHIFT) /* Test FORCE_ENABLE */
+
+#define EHCI_PORTSC_WKCCNTE            (1 << 20)  /* Bit 20: Wake on Connect Enable */
+#define EHCI_PORTSC_WKDSCNNTE          (1 << 21)  /* Bit 21: Wake on Disconnect Enable */
+#define EHCI_PORTSC_WKOCE              (1 << 22)  /* Bit 22: Wake on Over-current Enable */
+                                                  /* Bits 23-31: Reserved */
+
+#define EHCI_PORTSC_ALLINTS            (EHCI_PORTSC_CSC | EHCI_PORTSC_PEC | \
+                                        EHCI_PORTSC_OCC | EHCI_PORTSC_RESUME)
+
+/* Debug Register Bit Definitions *******************************************/
+
+/* Debug Port Control/Status Register.  Paragraph C.3.1 */
+
+#define EHCI_DEBUG_PCS_LENGTH_SHIFT    (0)        /* Bits 0-3: Data Length */
+#define EHCI_DEBUG_PCS_LENGTH_MASK     (15 << EHCI_DEBUG_PCS_LENGTH_SHIFT)
+#define EHCI_DEBUG_PCS_WRITE           (1 << 4)   /* Bit 6:  Write/Read# */
+#define EHCI_DEBUG_PCS_GO              (1 << 5)   /* Bit 5:  Go */
+#define EHCI_DEBUG_PCS_ERROR           (1 << 6)   /* Bit 6:  Error/Good# */
+#define EHCI_DEBUG_PCS_EXCEPTION_SHIFT (17)       /* Bits 7-9: Exception */
+#define EHCI_DEBUG_PCS_EXCEPTION_MASK  (7 << EHCI_DEBUG_PCS_EXCEPTION_SHIFT)
+#define EHCI_DEBUG_PCS_INUSE           (1 << 10)  /* Bit 10: In Use */
+                                                  /* Bits 11-15: Reserved */
+#define EHCI_DEBUG_PCS_DONE            (1 << 16)  /* Bit 16: Done */
+                                                  /* Bits 17-27: Reserved */
+#define EHCI_DEBUG_PCS_ENABLED         (1 << 28)  /* Bit 28: Enabled */
+                                                  /* Bit 29: Reserved */
+#define EHCI_DEBUG_PCS_OWNER           (1 << 30)  /* Bit 30: Owner */
+                                                  /* Bit 31: Reserved */
+
+/* Debug USB PIDs Register.  Paragraph C.3.2 */
+
+#define EHCI_DEBUG_USBPIDS_TKPID_SHIFT (0)        /* Bits 0-7: Token PID */
+#define EHCI_DEBUG_USBPIDS_TKPID_MASK  (0xff << EHCI_DEBUG_USBPIDS_TKPID_SHIFT)
+#define EHCI_DEBUG_USBPIDS_SPID_SHIFT  (8)        /* Bits 8-15: Sent PID */
+#define EHCI_DEBUG_USBPIDS_SPID_MASK   (0xff << EHCI_DEBUG_USBPIDS_SPID_SHIFT)
+#define EHCI_DEBUG_USBPIDS_RPID_SHIFT  (16)       /* Bits 16-23: Received PID */
+#define EHCI_DEBUG_USBPIDS_RPID_MASK   (0xff << EHCI_DEBUG_USBPIDS_RPID_SHIFT)
+                                                  /* Bits 24-31: Reserved */
+
+/* Debug Data Buffer 0/1 Register [64:0].
+ * Paragreph C.3.3.  64 bits of data.
+ */
+
+/* Debug Device Address Register.  Paragraph C.3.4 */
+
+#define EHCI_DEBUG_DEVADDR_ENDPT_SHIFT (0)        /* Bit 0-3: USB Endpoint */
+#define EHCI_DEBUG_DEVADDR_ENDPT_MASK  (15 << EHCI_DEBUG_DEVADDR_ENDPT_SHIFT)
+                                                  /* Bits 4-7: Reserved */
+#define EHCI_DEBUG_DEVADDR_ADDR_SHIFT  (8)        /* Bits 8-14: USB Address */
+#define EHCI_DEBUG_DEVADDR_ADDR_MASK   (0x7f << EHCI_DEBUG_DEVADDR_ADDR_SHIFT)
+                                                  /* Bits 15-31: Reserved */
+
+/* Data Structures **********************************************************/
+
+/* Paragraph 3 */
+
+/* Periodic Frame List. Paragraph 3.1 */
+
+#define PFL_T                          (1 << 0)   /* Bit 0: Terminate, Link pointer invalid */
+#define PFL_TYP_SHIFT                  (1)        /* Bits 1-2: Type */
+#define PFL_TYP_MASK                   (3 << PFL_TYP_SHIFT)
+#  define PFL_TYP_ITD                  (0 << PFL_TYP_SHIFT) /* Isochronous Transfer Descriptor */
+#  define PFL_TYP_QH                   (1 << PFL_TYP_SHIFT) /* Queue Head */
+#  define PFL_TYP_SITD                 (2 << PFL_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */
+#  define PFL_TYP_FSTN                 (3 << PFL_TYP_SHIFT) /* Frame Span Traversal Node */
+
+                                                    /* Bits 3-4: zero */
+#define PFL_MASK                       (0xffffffe0) /* Bits 5-31:  Frame List Link Pointer */
+
+/* Aysnchronous List Queue Head Pointer.
+ * Paragraph 3.2. Circular list of queue heads
+ */
+
+/* Isochronous (High-Speed) Transfer Descriptor (iTD). Paragraph 3.3 */
+
+/* iTD Next Link Pointer. Paragraph 3.3.1 */
+
+#define ITD_NLP_T                      (1 << 0)   /* Bit 0: Terminate, Link pointer invalid */
+#define ITD_NLP_TYP_SHIFT              (1)        /* Bits 1-2: Type */
+#define ITD_NLP_TYP_MASK               (3 << ITD_NLP_TYP_SHIFT)
+#  define ITD_NLP_TYP_ITD              (0 << ITD_NLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */
+#  define ITD_NLP_TYP_QH               (1 << ITD_NLP_TYP_SHIFT) /* Queue Head */
+#  define ITD_NLP_TYP_SITD             (2 << ITD_NLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */
+#  define ITD_NLP_TYP_FSTN             (3 << ITD_NLP_TYP_SHIFT) /* Frame Span Traversal Node */
+
+                                                    /* Bits 3-4: zero */
+#define ITD_NLP_MASK                   (0xffffffe0) /* Bits 5-31:  Frame List Link Pointer */
+
+/* iTD Transaction Status and Control List. Paragraph 3.3.2 */
+
+#define ITD_TRAN_XOFFS_SHIFT           (0)        /* Bits 0-11: Transaction X offset */
+#define ITD_TRAN_XOFFS_MASK            (0xfff << ITD_TRAN_XOFFS_SHIFT)
+#define ITD_TRAN_PG_SHIFT              (12)       /* Bits 12-14: Page select */
+#define ITD_TRAN_PG_MASK               (7 << ITD_TRAN_PG_SHIFT)
+#define ITD_TRAN_IOC                   (1 << 15)  /* Bit 15:  Interrupt On Comp */
+#define ITD_TRAN_LENGTH_SHIFT          (16)       /* Bits 16-27:  Transaction length */
+#define ITD_TRAN_LENGTH_MASK           (0xfff << ITD_TRAN_LENGTH_SHIFT)
+#define ITD_TRAN_STATUS_SHIFT          (28)       /* Bits 28-31:  Transaction status */
+#define ITD_TRAN_STATUS_MASK           (15 << ITD_TRAN_STATUS_SHIFT)
+#  define ITD_TRAN_STATUS_XACTERR      (1 << 28)  /* Bit 28: Transaction error */
+#  define ITD_TRAN_STATUS_BABBLE       (1 << 29)  /* Bit 29: Babble Detected */
+#  define ITD_TRAN_STATUS_DBERROR      (1 << 30)  /* Bit 30: Data Buffer Error */
+#  define ITD_TRAN_STATUS_ACTIVE       (1 << 31)  /* Bit 28: Transaction error */
+
+/* iTD Buffer Page Pointer List. Paragraph 3.3.4 */
+
+/* iTD Buffer Pointer Page 0. Table 3-4 */
+
+#define ITD_BUFPTR0_DEVADDR_SHIFT      (0)        /* Bits 0-6: Device Address */
+#define ITD_BUFPTR0_DEVADDR_MASK       (0x7f << ITD_BUFPTR0_DEVADDR_SHIFT)
+                                                  /* Bit 7: Reserved */
+#define ITD_BUFPTR0_ENDPT_SHIFT        (8)        /* Bits 8-11: Endpoint Number */
+#define ITD_BUFPTR0_ENDPT_MASK         (15 << ITD_BUFPTR0_ENDPT_SHIFT)
+
+/* iTD Buffer Pointer Page 1. Table 3-5 */
+
+#define ITD_BUFPTR1_MAXPKT_SHIFT       (0)        /* Bits 0-10: Maximum Packet Size */
+#define ITD_BUFPTR1_MAXPKT_MASK        (0x7ff << ITD_BUFPTR1_MAXPKT_SHIFT)
+#define ITD_BUFPTR1_DIRIN              (1 << 11)  /* Bit 11: Direction 1=IN */
+#define ITD_BUFPTR1_DIROUT             (0)        /* Bit 11: Direction 0=OUT */
+
+/* iTD Buffer Pointer Page 2. Table 3-6 */
+
+#define ITD_BUFPTR2_MULTI_SHIFT        (0)        /* Bits 0-1: Multi */
+#define ITD_BUFPTR2_MULTI_MASK         (3 << ITD_BUFPTR2_MULTI_SHIFT)
+#  define ITD_BUFPTR2_MULTI_1          (1 << ITD_BUFPTR2_MULTI_SHIFT) /* One transaction per micro-frame */
+#  define ITD_BUFPTR2_MULTI_2          (2 << ITD_BUFPTR2_MULTI_SHIFT) /* Two transactions per micro-frame */
+#  define ITD_BUFPTR2_MULTI_3          (3 << ITD_BUFPTR2_MULTI_SHIFT) /* Three transactions per micro-frame */
+
+                                                  /* Bits 2-11: Reserved */
+
+/* iTD Buffer Pointer Page 3-6. Table 3-7 */
+
+                                                  /* Bits 0-11: Reserved */
+
+/* iTD Buffer Pointer All Pages */
+
+#define ITD_BUFPTR_MASK                (0xfffff000) /* Bits 12-31: Buffer Pointer */
+
+/* Split Transaction Isochronous Transfer Descriptor (siTD).
+ * Paragraph 3.4
+ */
+
+/* siTD Next Link Pointer. Paragraph 3.4.1 */
+
+#define SITD_NLP_T                     (1 << 0)   /* Bit 0: Terminate, Link pointer invalid */
+#define SITD_NLP_TYP_SHIFT             (1)        /* Bits 1-2: Type */
+#define SITD_NLP_TYP_MASK              (3 << SITD_NLP_TYP_SHIFT)
+#  define SITD_NLP_TYP_ITD             (0 << SITD_NLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */
+#  define SITD_NLP_TYP_QH              (1 << SITD_NLP_TYP_SHIFT) /* Queue Head */
+#  define SITD_NLP_TYP_SITD            (2 << SITD_NLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */
+#  define SITD_NLP_TYP_FSTN            (3 << SITD_NLP_TYP_SHIFT) /* Frame Span Traversal Node */
+
+                                                    /* Bits 3-4: zero */
+#define SITD_NLP_MASK                  (0xffffffe0) /* Bits 5-31: Frame List Link Pointer */
+
+/* siTD Endpoint Capabilities/Characteristics.
+ * Paragraph 3.4.2
+ */
+
+/* Endpoint and Transaction Translator Characteristics. Table 3-9 */
+
+#define SITD_EPCHAR_DEVADDR_SHIFT      (0)        /* Bitx 0-6: Device Address */
+#define SITD_EPCHAR_DEVADDR_MASK       (0x7f << SITD_EPCHAR_DEVADDR_SHIFT)
+                                                  /* Bits 7: Reserved */
+#define SITD_EPCHAR_ENDPT_SHIFT        (8)        /* Bitx 8-11: Endpoint Number */
+#define SITD_EPCHAR_ENDPT_MASK         (15 << SITD_EPCHAR_ENDPT_SHIFT)
+                                                  /* Bits 12-15: Reserved */
+#define SITD_EPCHAR_HUBADDR_SHIFT      (16)       /* Bitx 16-22: Hub Address */
+#define SITD_EPCHAR_HUBADDR_MASK       (0x7f << SITD_EPCHAR_HUBADDR_SHIFT)
+                                                  /* Bit 23: Reserved */
+#define SITD_EPCHAR_DIRIN              (1 << 31)  /* Bit 31: Direction 1=IN */
+#define SITD_EPCHAR_DIROUT             (0)        /* Bit 31: Direction 0=OUT */
+
+/* Micro-frame Schedule Control. Table 3-10 */
+
+#define SITD_FMSCHED_SSMASK_SHIFT      (0)        /* Bitx 0-7: Split Start Mask (�Frame S-mask) */
+#define SITD_FMSCHED_SSMASK_MASK       (0xff << SITD_FMSCHED_SSMASK_SHIFT)
+#  define SITD_FMSCHED_SSMASK(n)       ((n) << SITD_FMSCHED_SSMASK_SHIFT)
+#define SITD_FMSCHED_SCMASK_SHIFT      (8)        /* Bitx 8-15: Split Completion Mask (�Frame C-Mask) */
+#define SITD_FMSCHED_SCMASK_MASK       (0xff << SITD_FMSCHED_SCMASK_SHIFT)
+#  define SITD_FMSCHED_SCMASK(n)       ((n) << SITD_FMSCHED_SCMASK_SHIFT)
+                                                  /* Bits 16-31: Reserved */
+
+/* siTD Transfer State. Paragraph 3.4.3 */
+
+#define SITD_XFRSTATE_STATUS_SHIFT     (0)        /* Bits 0-7: Status */
+#define SITD_XFRSTATE_STATUS_MASK      (0xff << SITD_XFRSTATE_STATUS_SHIFT)
+#define SITD_XFRSTATE_CPROGMASK_SHIFT  (8)        /* Bits 8-15: �Frame Complete-split Progress Mask  */
+#define SITD_XFRSTATE_CPROGMASK_MASK   (0xff << SITD_XFRSTATE_CPROGMASK_SHIFT)
+#define SITD_XFRSTATE_NBYTES_SHIFT     (16)       /* Bits 16-25: Total Bytes To Transfer */
+#define SITD_XFRSTATE_NBYTES_MASK      (0x3ff << SITD_XFRSTATE_NBYTES_SHIFT)
+                                                  /* Bits 26-29: Reserved */
+#define SITD_XFRSTATE_P                (1 << 30)  /* Bit 30: Page Select */
+#define SITD_XFRSTATE_IOC              (1 << 31)  /* Bit 31: Interrupt On Complete */
+
+/* siTD Buffer Pointer List.
+ * Paragraph 3.4.4
+ */
+
+/* Page 0 */
+
+#define SITD_BUFPTR0_OFFSET_SHIFT      (0)        /* Bits 0-11: Current Offset */
+#define SITD_BUFPTR0_OFFSET_MASK       (0xff << SITD_BUFPTR0_OFFSET_SHIFT)
+
+/* Page 1 */
+
+#define SITD_BUFPTR1_TCOUNT_SHIFT      (0)        /* Bits 0-2: Transaction count */
+#define SITD_BUFPTR1_TCOUNT_MASK       (7 << SITD_BUFPTR1_TCOUNT_SHIFT)
+#define SITD_BUFPTR1_TP_SHIFT          (33)       /* Bits 3-4: Transaction position */
+#define SITD_BUFPTR1_TP_MASK           (3 << SITD_BUFPTR1_TP_SHIFT)
+#  define SITD_BUFPTR1_TP_ENTIRE       (0 << SITD_BUFPTR1_TP_SHIFT) /* Entire full-speed transaction data payload. */
+#  define SITD_BUFPTR1_TP_BEGIN        (1 << SITD_BUFPTR1_TP_SHIFT) /* This is the first data payload */
+#  define SITD_BUFPTR1_TP_MID          (2 << SITD_BUFPTR1_TP_SHIFT) /* This the middle payload */
+#  define SITD_BUFPTR1_TP_END          (3 << SITD_BUFPTR1_TP_SHIFT) /* This is the last payload */
+
+                                                  /* Bits 5-11: Reserved */
+
+/* All pages */
+
+#define SITD_BUFPTR_MASK               (0xfffff000) /* Bits 12-31: Buffer Pointer List */
+
+/* Queue Element Transfer Descriptor (qTD).
+ * Paragraph 3.5
+ */
+
+/* Next qTD Pointer.
+ * Paragraph 3.5.1
+ */
+
+#define QTD_NQP_T                      (1 << 0)   /* Bit 0: Terminate */
+                                                  /* Bits 1-4: Reserved */
+#define QTD_NQP_NTEP_SHIFT             (5)        /* Bits 5-31: Next Transfer Element Pointer */
+#define QTD_NQP_NTEP_MASK              (0xffffffe0)
+
+/* Alternate Next qTD Pointer.
+ * Paragraph 3.5.2
+ */
+
+#define QTD_AQP_T                      (1 << 0)   /* Bit 0: Terminate */
+                                                  /* Bits 1-4: Reserved */
+#define QTD_AQP_NTEP_SHIFT             (5)        /* Bits 5-31: Next Transfer Element Pointer */
+#define QTD_AQP_NTEP_MASK              (0xffffffe0)
+
+/* qTD Token.
+ * Paragraph 3.5.3
+ */
+
+#define QTD_TOKEN_STATUS_SHIFT         (0)        /* Bits 0-7: Status */
+#define QTD_TOKEN_STATUS_MASK          (0xff << QTD_TOKEN_STATUS_SHIFT)
+#  define QTD_TOKEN_P                  (1 << 0)   /* Bit 0 Ping State  */
+#  define QTD_TOKEN_ERR                (1 << 0)   /* Bit 0 Error */
+#  define QTD_TOKEN_SPLITXSTATE        (1 << 1)   /* Bit 1 Split Transaction State */
+#  define QTD_TOKEN_MMF                (1 << 2)   /* Bit 2 Missed Micro-Frame */
+#  define QTD_TOKEN_XACTERR            (1 << 3)   /* Bit 3 Transaction Error */
+#  define QTD_TOKEN_BABBLE             (1 << 4)   /* Bit 4 Babble Detected */
+#  define QTD_TOKEN_DBERR              (1 << 5)   /* Bit 5 Data Buffer Error */
+#  define QTD_TOKEN_HALTED             (1 << 6)   /* Bit 6 Halted */
+#  define QTD_TOKEN_ACTIVE             (1 << 7)   /* Bit 7 Active */
+#  define QTD_TOKEN_ERRORS             (0x78 << QTD_TOKEN_STATUS_SHIFT)
+#define QTD_TOKEN_PID_SHIFT            (8)        /* Bits 8-9: PID Code */
+#define QTD_TOKEN_PID_MASK             (3 << QTD_TOKEN_PID_SHIFT)
+#  define QTD_TOKEN_PID_OUT            (0 << QTD_TOKEN_PID_SHIFT) /* OUT Token generates token (E1H) */
+#  define QTD_TOKEN_PID_IN             (1 << QTD_TOKEN_PID_SHIFT) /* IN Token generates token (69H) */
+#  define QTD_TOKEN_PID_SETUP          (2 << QTD_TOKEN_PID_SHIFT) /* SETUP Token generates token (2DH) */
+
+#define QTD_TOKEN_CERR_SHIFT           (10)       /* Bits 10-11: Error Counter */
+#define QTD_TOKEN_CERR_MASK            (3 << QTD_TOKEN_CERR_SHIFT)
+#define QTD_TOKEN_CPAGE_SHIFT          (12)       /* Bits 12-14: Current Page */
+#define QTD_TOKEN_CPAGE_MASK           (7 << QTD_TOKEN_CPAGE_SHIFT)
+#define QTD_TOKEN_IOC                  (1 << 15)  /* Bit 15: Interrupt On Complete */
+#define QTD_TOKEN_NBYTES_SHIFT         (16)       /* Bits 16-30: Total Bytes to Transfer */
+#define QTD_TOKEN_NBYTES_MASK          (0x7fff << QTD_TOKEN_NBYTES_SHIFT)
+#define QTD_TOKEN_TOGGLE_SHIFT         (31)       /* Bit 31: Data Toggle */
+#define QTD_TOKEN_TOGGLE               (1 << 31)  /* Bit 31: Data Toggle */
+
+/* qTD Buffer Page Pointer List.
+ * Paragraph 3.5.4
+ */
+
+/* Page 0 */
+
+#define QTD_BUFPTR0_OFFFSET_SHIFT      (0)        /* Bits 0-11: Current Offset */
+#define QTD_BUFPTR0_OFFFSET_MASK       (0xfff << QTD_BUFPTR0_OFFFSET_SHIFT)
+
+/* Other pages */
+
+                                                  /* Bits 0-11: Reserved */
+
+/* All pages */
+
+#define QTD_BUFPTR_SHIFT               (12)       /* Bits 12-31: Buffer Pointer List */
+#define QTD_BUFPTR_MASK                (0xfffff000)
+
+/* Queue Head. Paragraph 3.6 */
+
+/* Queue Head Horizontal Link Pointer.
+ * Paragraph 3.6.1
+ */
+
+#define QH_HLP_T                       (1 << 0)   /* Bit 0: Terminate, QH HL pointer invalid */
+#define QH_HLP_TYP_SHIFT               (1)        /* Bits 1-2: Type */
+#define QH_HLP_TYP_MASK                (3 << QH_HLP_TYP_SHIFT)
+#  define QH_HLP_TYP_ITD               (0 << QH_HLP_TYP_SHIFT) /* Isochronous Transfer Descriptor */
+#  define QH_HLP_TYP_QH                (1 << QH_HLP_TYP_SHIFT) /* Queue Head */
+#  define QH_HLP_TYP_SITD              (2 << QH_HLP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */
+#  define QH_HLP_TYP_FSTN              (3 << QH_HLP_TYP_SHIFT) /* Frame Span Traversal Node */
+
+                                                    /* Bits 3-4: Reserved */
+#define QH_HLP_MASK                    (0xffffffe0) /* Bits 5-31: Queue Head Horizontal Link Pointer */
+
+/* Endpoint Capabilities/Characteristics. Paragraph 3.6.2 */
+
+/* Endpoint Characteristics: Queue Head DWord. Table 3-19 */
+
+#define QH_EPCHAR_DEVADDR_SHIFT        (0)        /* Bitx 0-6: Device Address */
+#define QH_EPCHAR_DEVADDR_MASK         (0x7f << QH_EPCHAR_DEVADDR_SHIFT)
+#define QH_EPCHAR_I                    (1 << 7)   /* Bit 7: Inactivate on Next Transaction */
+#define QH_EPCHAR_ENDPT_SHIFT          (8)        /* Bitx 8-11: Endpoint Number */
+#define QH_EPCHAR_ENDPT_MASK           (15 << QH_EPCHAR_ENDPT_SHIFT)
+#define QH_EPCHAR_EPS_SHIFT            (12)       /* Bitx 12-13: Endpoint Speed */
+#define QH_EPCHAR_EPS_MASK             (3 << QH_EPCHAR_EPS_SHIFT)
+#  define QH_EPCHAR_EPS_FULL           (0 << QH_EPCHAR_EPS_SHIFT) /* Full-Speed (12Mbs) */
+#  define QH_EPCHAR_EPS_LOW            (1 << QH_EPCHAR_EPS_SHIFT) /* Low-Speed (1.5Mbs) */
+#  define QH_EPCHAR_EPS_HIGH           (2 << QH_EPCHAR_EPS_SHIFT) /* High-Speed (480 Mb/s) */
+
+#define QH_EPCHAR_DTC                  (1 << 14)  /* Bit 14: Data Toggle Control */
+#define QH_EPCHAR_H                    (1 << 15)  /* Bit 15: Head of Reclamation List Flag */
+#define QH_EPCHAR_MAXPKT_SHIFT         (16)       /* Bitx 16-26: Maximum Packet Length */
+#define QH_EPCHAR_MAXPKT_MASK          (0x7ff << QH_EPCHAR_MAXPKT_SHIFT)
+#define QH_EPCHAR_C                    (1 << 27)  /* Bit 27: Control Endpoint Flag */
+#define QH_EPCHAR_RL_SHIFT             (28)       /* Bitx 28-31: Nak Count Reload */
+#define QH_EPCHAR_RL_MASK              (15 << QH_EPCHAR_RL_SHIFT)
+
+/* Endpoint Capabilities: Queue Head DWord 2. Table 3-20 */
+
+#define QH_EPCAPS_SSMASK_SHIFT         (0)        /* Bitx 0-7: Interrupt Schedule Mask (�Frame S-mask) */
+#define QH_EPCAPS_SSMASK_MASK          (0xff << QH_EPCAPS_SSMASK_SHIFT)
+#  define QH_EPCAPS_SSMASK(n)          ((n) <<  QH_EPCAPS_SSMASK_SHIFT)
+#define QH_EPCAPS_SCMASK_SHIFT         (8)        /* Bitx 8-15: Split Completion Mask (�Frame C-Mask) */
+#define QH_EPCAPS_SCMASK_MASK          (0xff << QH_EPCAPS_SCMASK_SHIFT)
+#  define QH_EPCAPS_SCMASK(n)          ((n) << QH_EPCAPS_SCMASK_SHIFT)
+#define QH_EPCAPS_HUBADDR_SHIFT        (16)       /* Bitx 16-22: Hub Address */
+#define QH_EPCAPS_HUBADDR_MASK         (0x7f << QH_EPCAPS_HUBADDR_SHIFT)
+#  define QH_EPCAPS_HUBADDR(n)         ((n) << QH_EPCAPS_HUBADDR_SHIFT)
+#define QH_EPCAPS_PORT_SHIFT           (23)       /* Bit 23-29: Port Number */
+#define QH_EPCAPS_PORT_MASK            (0x7f << QH_EPCAPS_PORT_SHIFT)
+#  define QH_EPCAPS_PORT(n)            ((n) << QH_EPCAPS_PORT_SHIFT)
+#define QH_EPCAPS_MULT_SHIFT           (30)       /* Bit 30-31: High-Bandwidth Pipe Multiplier */
+#define QH_EPCAPS_MULT_MASK            (3 << QH_EPCAPS_MULT_SHIFT)
+#  define QH_EPCAPS_MULT(n)            ((n) << QH_EPCAPS_MULT_SHIFT)
+
+/* Current qTD Link Pointer.  Table 3-21 */
+
+#define QH_CQP_NTEP_SHIFT              (5)        /* Bits 5-31: Next Transfer Element Pointer */
+#define QH_CQP_NTEP_MASK               (0xffffffe0)
+
+/* Transfer Overlay.  Paragraph 3.6.3
+ *
+ * NOTES:
+ * 1. Same as the field of the same name in struct ehci_qtd_s
+ * 2. Similar to the field of the same name in struct ehci_qtd_s, but with
+ *    some additional bitfields.
+ */
+
+/* Next qTD Pointer (NOTE 1) */
+
+#define QH_NQP_T                       (1 << 0)   /* Bit 0: Terminate */
+                                                  /* Bits 1-4: Reserved */
+#define QH_NQP_NTEP_SHIFT              (5)        /* Bits 5-31: Next Transfer Element Pointer */
+#define QH_NQP_NTEP_MASK               (0xffffffe0)
+
+/* Alternate Next qTD Pointer.  Table 3.7 (NOTE 2) */
+
+#define QH_AQP_T                       (1 << 0)   /* Bit 0: Terminate */
+#define QH_AQP_NAKCNT                  (1)        /* Bits 1-4: Nak Counter */
+#define QH_AQP_NTEP_SHIFT              (5)        /* Bits 5-31: Next Transfer Element Pointer */
+#define QH_AQP_NTEP_MASK               (0xffffffe0)
+
+/* qTD Token (NOTE 1) */
+
+#define QH_TOKEN_STATUS_SHIFT          (0)        /* Bits 0-7: Status */
+#define QH_TOKEN_STATUS_MASK           (0xff << QH_TOKEN_STATUS_SHIFT)
+#  define QH_TOKEN_P                   (1 << 0)   /* Bit 0 Ping State  */
+#  define QH_TOKEN_ERR                 (1 << 0)   /* Bit 0 Error */
+#  define QH_TOKEN_SPLITXSTATE         (1 << 1)   /* Bit 1 Split Transaction State */
+#  define QH_TOKEN_MMF                 (1 << 2)   /* Bit 2 Missed Micro-Frame */
+#  define QH_TOKEN_XACTERR             (1 << 3)   /* Bit 3 Transaction Error */
+#  define QH_TOKEN_BABBLE              (1 << 4)   /* Bit 4 Babble Detected */
+#  define QH_TOKEN_DBERR               (1 << 5)   /* Bit 5 Data Buffer Error */
+#  define QH_TOKEN_HALTED              (1 << 6)   /* Bit 6 Halted */
+#  define QH_TOKEN_ACTIVE              (1 << 7)   /* Bit 7 Active */
+#  define QH_TOKEN_ERRORS              (0x78 << QH_TOKEN_STATUS_SHIFT)
+#define QH_TOKEN_PID_SHIFT             (8)        /* Bits 8-9: PID Code */
+#define QH_TOKEN_PID_MASK              (3 << QH_TOKEN_PID_SHIFT)
+#  define QH_TOKEN_PID_OUT             (0 << QH_TOKEN_PID_SHIFT) /* OUT Token generates token (E1H) */
+#  define QH_TOKEN_PID_IN              (1 << QH_TOKEN_PID_SHIFT) /* IN Token generates token (69H) */
+#  define QH_TOKEN_PID_SETUP           (2 << QH_TOKEN_PID_SHIFT) /* SETUP Token generates token (2DH) */
+
+#define QH_TOKEN_CERR_SHIFT            (10)       /* Bits 10-11: Error Counter */
+#define QH_TOKEN_CERR_MASK             (3 << QH_TOKEN_CERR_SHIFT)
+#define QH_TOKEN_CPAGE_SHIFT           (12)       /* Bits 12-14: Current Page */
+#define QH_TOKEN_CPAGE_MASK            (7 << QH_TOKEN_CPAGE_SHIFT)
+#define QH_TOKEN_IOC                   (1 << 15)  /* Bit 15: Interrupt On Complete */
+#define QH_TOKEN_NBYTES_SHIFT          (16)       /* Bits 16-30: Total Bytes to Transfer */
+#define QH_TOKEN_NBYTES_MASK           (0x7fff << QH_TOKEN_NBYTES_SHIFT)
+#define QH_TOKEN_TOGGLE_SHIFT          (31)       /* Bit 31: Data Toggle */
+#define QH_TOKEN_TOGGLE                (1 << 31)  /* Bit 31: Data Toggle */
+
+/* Buffer Page Pointer List (NOTE 2) */
+
+/* Page 0 */
+
+#define QH_BUFPTR0_OFFFSET_SHIFT      (0)        /* Bits 0-11: Current Offset */
+#define QH_BUFPTR0_OFFFSET_MASK       (0xfff << QH_BUFPTR0_OFFFSET_SHIFT)
+
+/* Page 1. Table 3.22 */
+
+#define QH_BUFPTR1_CPROGMASK_SHIFT    (0)        /* Bits 0-7: Split-transaction Complete-split Progress */
+#define QH_BUFPTR1_CPROGMASK_MASK     (0xff << QH_BUFPTR1_CPROGMASK_SHIFT)
+                                                  /* Bits 8-11: Reserved */
+
+/* Page 2. Table 3.22 */
+
+#define QH_BUFPTR2_FRAMETAG_SHIFT     (0)        /* Bits 0-4: Split-transaction Frame Tag */
+#define QH_BUFPTR2_FRAMETAG_MASK      (31 << QH_BUFPTR2_FRAMETAG_SHIFT)
+#define QH_BUFPTR2_SBYTES_SHIFT       (5)        /* Bits 5-11: S-bytes */
+#define QH_BUFPTR2_SBYTES_MASK        (0x7f << QH_BUFPTR2_SBYTES_SHIFT)
+
+/* Other pages */
+
+                                                  /* Bits 0-11: Reserved */
+
+/* All pages */
+
+#define QH_BUFPTR_SHIFT               (12)       /* Bits 12-31: Buffer Pointer List */
+#define QH_BUFPTR_MASK                (0xfffff000)
+
+/* Periodic Frame Span Traversal Node (STN). Paragrap 3.7 */
+
+/* FSTN Normal Path Pointer. Paragraph 3.7.1 */
+
+#define FSTN_NPP_T                     (1 << 0)   /* Bit 0: Terminate. 1=Link Pointer not valid */
+#define FSTN_NPP_TYP_SHIFT             (1)        /* Bits 1-2: Type */
+#define FSTN_NPP_TYP_MASK              (3 << FSTN_NPP_TYP_SHIFT)
+#  define FSTN_NPP_TYP_ITD             (0 << FSTN_NPP_TYP_SHIFT) /* Isochronous Transfer Descriptor */
+#  define FSTN_NPP_TYP_QH              (1 << FSTN_NPP_TYP_SHIFT) /* Queue Head */
+#  define FSTN_NPP_TYP_SITD            (2 << FSTN_NPP_TYP_SHIFT) /* Split Transaction Isochronous Transfer Descriptor */
+#  define FSTN_NPP_TYP_FSTN            (3 << FSTN_NPP_TYP_SHIFT) /* Frame Span Traversal Node */
+
+                                                  /* Bits 3-4: Reserved */
+#define FSTN_NPP_NPLP_SHIFT            (5)        /* Bits 5-31: Normal Path Link Pointer */
+#define FSTN_NPP_NPLP_MASK             (0xffffffe0)
+
+/* FSTN Back Path Link Pointer. Paragraph 3.7.2 */
+
+#define FSTN_BPP_T                     (1 << 0)   /* Bit 0: Terminate. 1=Link Pointer not valid */
+#define FSTN_BPP_TYP_SHIFT             (1)        /* Bits 1-2: Type */
+#define FSTN_BPP_TYP_MASK              (3 << FSTN_BPP_TYP_SHIFT)
+#  define FSTN_BPP_TYP_QH              (1 << FSTN_BPP_TYP_SHIFT) /* Queue Head */
+
+                                                  /* Bits 3-4: Reserved */
+#define FSTN_BPP_BPLP_SHIFT            (5)        /* Bits 5-31: Back Path Link Pointer */
+#define FSTN_BPP_BPLP_MASK             (0xffffffe0)
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* Registers ****************************************************************/
+
+/* Since the operational registers are not known a compile time, representing
+ * register blocks with structures is more convenient than using individual
+ * register offsets.
+ */
+
+/* Host Controller Capability Registers.
+ * This register block must be positioned at a well known address.
+ */
+
+struct ehci_hccr_s
+{
+  uint8_t  caplength;        /* 0x00: Capability Register Length */
+  uint8_t  reserved;
+  uint16_t hciversion;       /* 0x02: Interface Version Number */
+  uint32_t hcsparams;        /* 0x04: Structural Parameters */
+  uint32_t hccparams;        /* 0x08: Capability Parameters */
+  uint8_t  hcspportrt[8];    /* 0x0c: Companion Port Route Description */
+};
+
+/* Host Controller Operational Registers.
+ * This register block is positioned at an offset of 'caplength' from the
+ * beginning of the Host Controller Capability Registers.
+ */
+
+struct ehci_hcor_s
+{
+  uint32_t usbcmd;           /* 0x00: USB Command */
+  uint32_t usbsts;           /* 0x04: USB Status */
+  uint32_t usbintr;          /* 0x08: USB Interrupt Enable */
+  uint32_t frindex;          /* 0x0c: USB Frame Index */
+  uint32_t ctrldssegment;    /* 0x10: 4G Segment Selector */
+  uint32_t periodiclistbase; /* 0x14: Frame List Base Address */
+  uint32_t asynclistaddr;    /* 0x18: Next Asynchronous List Address */
+  // uint32_t reserved[9];
+  uint32_t configflag;       /* 0x40: Configured Flag Register */
+  uint32_t portsc[15];       /* 0x44: Port Status/Control */
+};
+
+/* USB2 Debug Port Register Interface.
+ *  This register block is normally found via the PCI capabalities.
+ * In non-PCI implementions, you need apriori information about the
+ * location of these registers.
+ */
+
+struct ehci_debug_s
+{
+  uint32_t psc;              /* 0x00: Debug Port Control/Status Register */
+  uint32_t pids;             /* 0x04: Debug USB PIDs Register */
+  uint32_t data[2];          /* 0x08: Debug Data buffer Registers */
+  uint32_t addr;             /* 0x10: Device Address Register */
+};
+
+/* Data Structures **********************************************************/
+
+/* Paragraph 3 */
+
+/* Periodic Frame List.
+ * Paragraph 3.1.  An array of pointers.
+ */
+
+/* Aysnchronous List Queue Head Pointer.
+ * Paragraph 3.2. Circular list of queue heads
+ */
+
+/* Isochronous (High-Speed) Transfer Descriptor (iTD).
+ * Paragraph 3.3.  Must be aligned to 32-byte boundaries.
+ */
+
+struct ehci_itd_s
+{
+  uint32_t nlp;                              /* 0x00-0x03: Next link pointer */
+  uint32_t trans[8];                         /* 0x04-0x23: Transaction Status and Control List */
+  uint32_t bpl[7];                           /* 0x24-0x3c: Buffer Page Pointer List */
+};
+
+#define SIZEOF_EHCI_ITD_S (64)               /* 16*sizeof(uint32_t) */
+
+/* Split Transaction Isochronous Transfer Descriptor (siTD). Paragraph 3.4 */
+
+struct ehci_sitd_s
+{
+  uint32_t nlp;                              /* 0x00-0x03: Next link pointer */
+  uint32_t epchar;                           /* 0x04-0x07: Endpoint and Transaction Translator Characteristics */
+  uint32_t fmsched;                          /* 0x08-0x0b: Micro-frame Schedule Control */
+  uint32_t xfrstate;                         /* 0x0c-0x0f: Transfer Status and Control */
+  uint32_t bpl[2];                           /* 0x10-0x17: Buffer Pointer List */
+  uint32_t blp;                              /* 0x18-0x1b: Back link pointer */
+};
+
+#define SIZEOF_EHCI_SITD_S (28)              /* 7*sizeof(uint32_t) */
+
+/* Queue Element Transfer Descriptor (qTD). Paragraph 3.5 */
+
+/* 32-bit version.  See EHCI Appendix B for the 64-bit version. */
+
+struct ehci_qtd_s
+{
+  uint32_t nqp;                              /* 0x00-0x03: Next qTD Pointer */
+  uint32_t alt;                              /* 0x04-0x07: Alternate Next qTD Pointer */
+  uint32_t token;                            /* 0x08-0x0b: qTD Token */
+  uint32_t bpl[5];                           /* 0x0c-0x1c: Buffer Page Pointer List */
+};
+
+#define SIZEOF_EHCI_QTD_S (32)               /* 8*sizeof(uint32_t) */
+
+/* Queue Head. Paragraph 3.6
+ *
+ * NOTE:
+ * 1. Same as the field of the same name in struct ehci_qtd_s
+ * 2. Similar to the field of the same name in struct ehci_qtd_s,
+ *    but with some additional bitfields.
+ */
+
+struct ehci_overlay_s
+{
+  uint32_t nqp;                              /* 0x00-0x03: Next qTD Pointer (NOTE 1) */
+  uint32_t alt;                              /* 0x04-0x07: Alternate Next qTD Pointer (NOTE 2) */
+  uint32_t token;                            /* 0x08-0x0b: qTD Token (NOTE 1) */
+  uint32_t bpl[5];                           /* 0x0c-0x1c: Buffer Page Pointer List (NOTE 2) */
+};
+
+#define SIZEOF_EHCI_OVERLAY (32)             /* 8*sizeof(uint32_t) */
+
+struct ehci_qh_s
+{
+  uint32_t hlp;                              /* 0x00-0x03: Queue Head Horizontal Link Pointer */
+  uint32_t epchar;                           /* 0x04-0x07: Endpoint Characteristics */
+  uint32_t epcaps;                           /* 0x08-0x0b: Endpoint Capabilities */
+  uint32_t cqp;                              /* 0x0c-0x0f: Current qTD Pointer */
+  struct ehci_overlay_s overlay;             /* 0x10-0x2c: Transfer overlay */
+};
+
+#define SIZEOF_EHCI_QH (48)             /* 4*sizeof(uint32_t) + SIZEOF_EHCI_OVERLAY */
+
+/* Periodic Frame Span Traversal Node (STN). Paragrap 3.7 */
+
+struct ehci_fstn_s
+{
+  uint32_t npp;                              /* 0x00-0x03: Normal Path Pointer */
+  uint32_t bpp;                              /* 0x04-0x07: Back Path Link Pointer */
+};
+
+#define SIZEOF_EHCI_FSTN_S (8)               /* 2*sizeof(uint32_t) */
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+#endif /* __INCLUDE_NUTTX_USB_EHCI_H */

+ 481 - 0
port/synopsys/usb_hc_synopsys.c

@@ -0,0 +1,481 @@
+#include "usbh_core.h"
+#include "stm32f4xx_hal.h"
+
+#define USBH_PID_SETUP 0U
+#define USBH_PID_DATA  1U
+
+#define USB_SNOPSYS_RETRY_COUNT 5
+
+#ifndef CONFIG_USBHOST_CHANNELS
+#define CONFIG_USBHOST_CHANNELS 12 /* Number of host channels */
+#endif
+
+/* This structure retains the state of one host channel.  NOTE: Since there
+ * is only one channel operation active at a time, some of the fields in
+ * in the structure could be moved in struct stm32_ubhost_s to achieve
+ * some memory savings.
+ */
+
+struct usb_synopsys_chan {
+    usb_osal_sem_t waitsem; /* Channel wait semaphore */
+    uint8_t chidx;          /* Channel index */
+    bool inuse;             /* True: This channel is "in use" */
+#ifdef CONFIG_USBHOST_ASYNCH
+    usbh_asynch_callback_t callback; /* Transfer complete callback */
+    void *arg;                       /* Argument that accompanies the callback */
+#endif
+};
+
+/* A channel represents on uni-directional endpoint.  So, in the case of the
+ * bi-directional, control endpoint, there must be two channels to represent
+ * the endpoint.
+ */
+
+struct usb_synopsys_ctrlinfo {
+    uint8_t inndx;  /* EP0 IN control channel index */
+    uint8_t outndx; /* EP0 OUT control channel index */
+};
+
+struct usb_synopsys_priv {
+    HCD_HandleTypeDef *handle;
+    volatile bool connected; /* Connected to device */
+    volatile bool pscwait;   /* True: Thread is waiting for a port event */
+    usb_osal_sem_t exclsem;  /* Support mutually exclusive access */
+    struct usb_synopsys_chan chan[CONFIG_USBHOST_CHANNELS];
+} g_usbhost;
+
+/****************************************************************************
+ * Name: usb_synopsys_chan_alloc
+ *
+ * Description:
+ *   Allocate a channel.
+ *
+ ****************************************************************************/
+
+static int usb_synopsys_chan_alloc(struct usb_synopsys_priv *priv)
+{
+    int chidx;
+
+    /* Search the table of channels */
+
+    for (chidx = 0; chidx < CONFIG_USBHOST_CHANNELS; chidx++) {
+        /* Is this channel available? */
+        if (!priv->chan[chidx].inuse) {
+            /* Yes... make it "in use" and return the index */
+
+            priv->chan[chidx].inuse = true;
+            return chidx;
+        }
+    }
+
+    /* All of the channels are "in-use" */
+
+    return -EBUSY;
+}
+
+/****************************************************************************
+ * Name: usb_synopsys_chan_free
+ *
+ * Description:
+ *   Free a previoiusly allocated channel.
+ *
+ ****************************************************************************/
+
+static void usb_synopsys_chan_free(struct usb_synopsys_priv *priv, int chidx)
+{
+    /* Halt the channel */
+    HAL_HCD_HC_Halt(priv->handle, chidx);
+    /* Mark the channel available */
+
+    priv->chan[chidx].inuse = false;
+}
+
+__WEAK void usb_hc_low_level_init(void)
+{
+}
+
+int usb_hc_init(void)
+{
+    memset(&g_usbhost, 0, sizeof(struct usb_synopsys_priv));
+#ifdef CONFIG_USB_HS
+    extern HCD_HandleTypeDef hhcd_USB_OTG_HS;
+    g_usbhost.handle = &hhcd_USB_OTG_HS;
+#else
+    extern HCD_HandleTypeDef hhcd_USB_OTG_FS;
+    g_usbhost.handle = &hhcd_USB_OTG_FS;
+#endif
+
+    g_usbhost.exclsem = usb_osal_mutex_create();
+
+    for (uint8_t i = 0; i < CONFIG_USBHOST_CHANNELS; i++) {
+        struct usb_synopsys_chan *chan = &g_usbhost.chan[i];
+
+        chan->chidx = i;
+
+        /* The waitsem semaphore is used for signaling and, hence, should not
+       * have priority inheritance enabled.
+       */
+        chan->waitsem = usb_osal_sem_create(0);
+    }
+    usb_hc_low_level_init();
+    HAL_HCD_Start(g_usbhost.handle);
+    return 0;
+}
+
+int usbh_reset_port(const uint8_t port)
+{
+    HAL_HCD_ResetPort(g_usbhost.handle);
+    return 0;
+}
+
+uint8_t usbh_get_port_speed(const uint8_t port)
+{
+    if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 1) {
+        return USB_SPEED_FULL;
+    } else if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 2)
+        return USB_SPEED_LOW;
+    else
+        return USB_SPEED_HIGH;
+}
+
+int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed)
+{
+    struct usb_synopsys_ctrlinfo *ep0info;
+    int ret;
+
+    ep0info = (struct usb_synopsys_ctrlinfo *)ep;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (speed == USB_SPEED_FULL) {
+        speed = HCD_SPEED_FULL;
+    } else if (speed == USB_SPEED_LOW) {
+        speed = HCD_SPEED_LOW;
+    }
+
+    ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->outndx, 0x00, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps);
+    ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->inndx, 0x80, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps);
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return ret;
+}
+
+int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
+{
+    struct usb_synopsys_ctrlinfo *ep0;
+    struct usbh_hubport *hport;
+    int chidx;
+    int ret;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    hport = ep_cfg->hport;
+
+    if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) {
+        ep0 = usb_malloc(sizeof(struct usb_synopsys_ctrlinfo));
+
+        ep0->outndx = usb_synopsys_chan_alloc(&g_usbhost);
+        ep0->inndx = usb_synopsys_chan_alloc(&g_usbhost);
+
+        HAL_HCD_HC_Init(g_usbhost.handle, ep0->outndx, 0x00, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps);
+        HAL_HCD_HC_Init(g_usbhost.handle, ep0->inndx, 0x80, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps);
+
+        *ep = (usbh_epinfo_t)ep0;
+
+    } else {
+        chidx = usb_synopsys_chan_alloc(&g_usbhost);
+        HAL_HCD_HC_Init(g_usbhost.handle, chidx, ep_cfg->ep_addr, hport->dev_addr, hport->speed, ep_cfg->ep_type, ep_cfg->ep_mps);
+        if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_BULK) {
+            if (g_usbhost.handle->hc[chidx].ep_is_in) {
+                g_usbhost.handle->hc[chidx].toggle_in = 0;
+            } else {
+                g_usbhost.handle->hc[chidx].toggle_out = 0;
+            }
+        }
+        *ep = (usbh_epinfo_t)chidx;
+    }
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return 0;
+}
+int usbh_ep_free(usbh_epinfo_t ep)
+{
+    return 0;
+}
+
+int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
+{
+    uint8_t retries;
+    int ret;
+    struct usb_synopsys_ctrlinfo *ep0info = (struct usb_synopsys_ctrlinfo *)ep;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+    for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+        ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                       ep0info->outndx,  /* Pipe index       */
+                                       0,                /* Direction : OUT  */
+                                       0,                /* EP type          */
+                                       USBH_PID_SETUP,   /* Type Data        */
+                                       (uint8_t *)setup, /* data buffer      */
+                                       8,                /* data length      */
+                                       0);               /* do ping (HS Only)*/
+
+        ret = usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+
+        if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
+            break;
+        }
+    }
+
+    if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+        goto urb_timeout;
+    }
+    if (setup->wLength && buffer) {
+        if (setup->bmRequestType & 0x80) {
+            for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+                ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                               ep0info->inndx, /* Pipe index       */
+                                               1,              /* Direction : IN  */
+                                               0,              /* EP type          */
+                                               USBH_PID_DATA,  /* Type Data        */
+                                               buffer,         /* data buffer      */
+                                               setup->wLength, /* data length      */
+                                               0);             /* do ping (HS Only)*/
+                usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
+                if (ret < 0) {
+                    return ret;
+                }
+
+                if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
+                    break;
+                }
+            }
+            if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+                goto urb_timeout;
+            }
+            for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+                ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                               ep0info->outndx, /* Pipe index       */
+                                               0,               /* Direction : OUT  */
+                                               0,               /* EP type          */
+                                               USBH_PID_DATA,   /* Type Data        */
+                                               NULL,            /* data buffer      */
+                                               0,               /* data length      */
+                                               0);              /* do ping (HS Only)*/
+                usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
+                if (ret < 0) {
+                    return ret;
+                }
+
+                if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
+                    break;
+                }
+            }
+            if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+                goto urb_timeout;
+            }
+        } else {
+            for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+                ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                               ep0info->outndx, /* Pipe index       */
+                                               0,               /* Direction : OUT  */
+                                               0,               /* EP type          */
+                                               USBH_PID_DATA,   /* Type Data        */
+                                               buffer,          /* data buffer      */
+                                               setup->wLength,  /* data length      */
+                                               0);              /* do ping (HS Only)*/
+
+                usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
+                if (ret < 0) {
+                    return ret;
+                }
+
+                if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
+                    break;
+                }
+            }
+
+            if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+                goto urb_timeout;
+            }
+            for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+                ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                               ep0info->inndx, /* Pipe index       */
+                                               1,              /* Direction : IN  */
+                                               0,              /* EP type          */
+                                               USBH_PID_DATA,  /* Type Data        */
+                                               NULL,           /* data buffer      */
+                                               0,              /* data length      */
+                                               0);             /* do ping (HS Only)*/
+                usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
+                if (ret < 0) {
+                    return ret;
+                }
+
+                if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
+                    break;
+                }
+            }
+
+            if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+                goto urb_timeout;
+            }
+        }
+    } else {
+        for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
+            ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                           ep0info->inndx, /* Pipe index       */
+                                           1,              /* Direction : IN  */
+                                           0,              /* EP type          */
+                                           USBH_PID_DATA,  /* Type Data        */
+                                           NULL,           /* data buffer      */
+                                           0,              /* data length      */
+                                           0);             /* do ping (HS Only)*/
+            usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
+            if (ret < 0) {
+                return ret;
+            }
+            usb_osal_msleep(10);
+            if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
+                break;
+            }
+        }
+        if (retries >= USB_SNOPSYS_RETRY_COUNT) {
+            goto urb_timeout;
+        }
+    }
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return 0;
+urb_timeout:
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return -ETIMEDOUT;
+}
+int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int ret;
+    uint8_t chidx = (uint8_t)ep;
+
+    if (g_usbhost.handle->hc[chidx].ep_is_in) {
+        ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                       chidx,         /* Pipe index       */
+                                       1,             /* Direction : IN  */
+                                       2,             /* EP type          */
+                                       USBH_PID_DATA, /* Type Data        */
+                                       buffer,        /* data buffer      */
+                                       buflen,        /* data length      */
+                                       0);            /* do ping (HS Only)*/
+
+        usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+        return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
+    } else {
+        ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                       chidx,         /* Pipe index       */
+                                       0,             /* Direction : OUT  */
+                                       2,             /* EP type          */
+                                       USBH_PID_DATA, /* Type Data        */
+                                       buffer,        /* data buffer      */
+                                       buflen,        /* data length      */
+                                       0);            /* do ping (HS Only)*/
+        usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+        return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
+    }
+    return -1;
+}
+
+int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int ret;
+    uint8_t chidx = (uint8_t)ep;
+
+    if (g_usbhost.handle->hc[chidx].ep_is_in) {
+        ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                       chidx,         /* Pipe index       */
+                                       1,             /* Direction : IN  */
+                                       3,             /* EP type          */
+                                       USBH_PID_DATA, /* Type Data        */
+                                       buffer,        /* data buffer      */
+                                       buflen,        /* data length      */
+                                       0);            /* do ping (HS Only)*/
+
+        usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+        return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
+    } else {
+        ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
+                                       chidx,         /* Pipe index       */
+                                       0,             /* Direction : OUT  */
+                                       3,             /* EP type          */
+                                       USBH_PID_DATA, /* Type Data        */
+                                       buffer,        /* data buffer      */
+                                       buflen,        /* data length      */
+                                       0);            /* do ping (HS Only)*/
+        usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+        return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
+    }
+    return -1;
+}
+
+int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
+{
+    return 0;
+}
+
+int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
+{
+    return 0;
+}
+
+int usb_ep_cancel(usbh_epinfo_t ep)
+{
+    return 0;
+}
+
+void HAL_Delay(uint32_t Delay)
+{
+    usb_osal_msleep(Delay);
+}
+
+void HAL_HCD_Connect_Callback(HCD_HandleTypeDef *hhcd)
+{
+    g_usbhost.connected = true;
+    extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
+    usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1);
+}
+
+/**
+  * @brief  SOF callback.
+  * @param  hhcd: HCD handle
+  * @retval None
+  */
+void HAL_HCD_Disconnect_Callback(HCD_HandleTypeDef *hhcd)
+{
+    g_usbhost.connected = false;
+    extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
+    usbh_event_notify_handler(USBH_EVENT_REMOVED, 1);
+}
+
+void HAL_HCD_HC_NotifyURBChange_Callback(HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state)
+{
+    usb_osal_sem_give(g_usbhost.chan[chnum].waitsem);
+}