#include "usbh_core.h"
#include "usb_ehci.h"

#ifndef USBH_IRQHandler
#define USBH_IRQHandler USBH_IRQHandler
#endif

#define DEBUGASSERT(f)

/* 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 *)CONFIG_USB_EHCI_HCCR_BASE)

/* Host Controller Operational Registers */

#define HCOR ((volatile struct ehci_hcor_s *)CONFIG_USB_EHCI_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)

/****************************************************************************
 * 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) */
#if (__riscv_xlen == 64)
    uint8_t pad[4];                   /* Padding to assure 32-byte alignment */
#else
    uint8_t pad[8];                   /* Padding to assure 32-byte alignment */
#endif
};

/* 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
    bool inuse;
    uint8_t status;           /* Retained token status bits (for debug purposes) */
    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 */
    volatile bool iocwait;    /* TRUE: Thread is waiting for transfer completion */
    usb_osal_sem_t iocsem;    /* Semaphore used to wait for transfer completion */
    usb_osal_mutex_t exclsem; /* Support mutually exclusive access */
#ifdef CONFIG_USBHOST_ASYNCH
    usbh_asynch_callback_t callback; /* Transfer complete callback */
    void *arg;                       /* Argument that accompanies the callback */
#endif
    struct usbh_hubport *hport;
    struct usb_ehci_qh_s *qh;
};

/* This structure retains the overall state of the USB host controller */

struct ehci_hcd {
    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) */
    __attribute__((aligned(32))) struct usb_ehci_qh_s qhpool[CONFIG_USB_EHCI_QH_NUM];    /* Queue Head (QH) pool */
    __attribute__((aligned(32))) struct usb_ehci_qtd_s qtdpool[CONFIG_USB_EHCI_QTD_NUM]; /* Queue Element Transfer Descriptor (qTD) pool */
    struct usb_ehci_epinfo_s chan[CONFIG_USBHOST_PIPE_NUM];
};

/****************************************************************************
 * 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

#ifdef CONFIG_USB_DCACHE_ENABLE
void usb_ehci_dcache_clean(uintptr_t addr, uint32_t len);
void usb_ehci_dcache_invalidate(uintptr_t addr, uint32_t len);
void usb_ehci_dcache_clean_invalidate(uintptr_t addr, uint32_t len);
#else
#define usb_ehci_dcache_clean(addr, len)
#define usb_ehci_dcache_invalidate(addr, len)
#define usb_ehci_dcache_clean_invalidate(addr, len)
#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 ehci_hcd g_ehci_hcd;

/* The head of the asynchronous queue */

static struct usb_ehci_qh_s g_asynchead __attribute__((aligned(32)));

/* The head of the periodic queue */

static struct usb_ehci_qh_s g_intrhead __attribute__((aligned(32)));

/* The frame list */
static 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;
}

static int usb_ehci_chan_alloc(void)
{
    int chidx;

    /* Search the table of channels */

    for (chidx = 0; chidx < CONFIG_USBHOST_PIPE_NUM; chidx++) {
        /* Is this channel available? */
        if (!g_ehci_hcd.chan[chidx].inuse) {
            /* Yes... make it "in use" and return the index */

            g_ehci_hcd.chan[chidx].inuse = true;
            return chidx;
        }
    }

    /* All of the channels are "in-use" */

    return -EBUSY;
}

static void usb_ehci_chan_free(struct usb_ehci_epinfo_s *chan)
{
    /* Mark the channel available */
    chan->inuse = false;
}
/****************************************************************************
 * 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_hcd.qhfree;
    if (qh) {
        g_ehci_hcd.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_hcd.qhfree;
    g_ehci_hcd.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_hcd.qtdfree;
    if (qtd) {
        g_ehci_hcd.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_hcd.qtdfree;
    g_ehci_hcd.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) {
    }

    /* Then free the QH itself */

    usb_ehci_qh_free(qh);
    return ret;
}
#ifdef CONFIG_USB_DCACHE_ENABLE
/****************************************************************************
 * Name: usb_ehci_qtd_flush
 *
 * Description:
 *   This is a callback from usb_ehci_qtd_foreach.  It simply flushes D-cache
 *   for address range of the qTD entry.
 *
 ****************************************************************************/

static int usb_ehci_qtd_flush(struct usb_ehci_qtd_s *qtd, uint32_t **bp, void *arg)
{
    /* Flush the D-Cache, i.e., make the contents of the memory match the
    * contents of the D-Cache in the specified address range and invalidate
    * the D-Cache to force re-loading of the data from memory when next
    * accessed.
    */

    usb_ehci_dcache_clean_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));

    return 0;
}

/****************************************************************************
 * Name: usb_ehci_qh_flush
 *
 * Description:
 *   Invalidate the Queue Head and all qTD entries in the queue.
 *
 ****************************************************************************/

static int usb_ehci_qh_flush(struct usb_ehci_qh_s *qh)
{
    /* Flush the QH first.  This will write the contents of the D-cache to RAM
    * and invalidate the contents of the D-cache so that the next access will
    * be reloaded from D-Cache.
    */

    usb_ehci_dcache_clean_invalidate((uintptr_t)&qh->hw, sizeof(struct usb_ehci_qh_s));

    /* Then flush all of the qTD entries in the queue */

    return usb_ehci_qtd_foreach(qh, usb_ehci_qtd_flush, NULL);
}
#else
#define usb_ehci_qtd_flush(qtd, bp, arg)
#define usb_ehci_qtd_flush(qh)
#endif
/****************************************************************************
 * 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) {
        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) {
        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;

    usb_ehci_dcache_clean_invalidate((uintptr_t)buffer, buflen);
    /* 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) {
        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) {
        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) {
        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) {
        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) {
        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) {
        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;

    /* 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;
    usb_ehci_qh_flush(qh);
    /* 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);
    usb_ehci_dcache_clean((uintptr_t)&qhead->hw, sizeof(struct usb_ehci_qh_s));
}

static int usb_ehci_control_init(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;

    /* Create and initialize a Queue Head (QH) structure for this transfer */
    qh = usb_ehci_qh_create(epinfo);
    if (qh == NULL) {
        return -ENOMEM;
    }

    epinfo->qh = qh;
    /* 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;

    /* 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) {
            return -ENOMEM;
        }
        /* 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) {
            return -ENOMEM;
        }

        /* 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) {
            return -ENOMEM;
        }

        /* 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;
}

static int usb_ehci_bulk_init(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;

    /* Create and initialize a Queue Head (QH) structure for this transfer */
    qh = usb_ehci_qh_create(epinfo);
    if (qh == NULL) {
        return -ENOMEM;
    }

    epinfo->qh = qh;

    /* Initialize the QH link and get the next data toggle */
    tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;

    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) {
            return -ENOMEM;
        }

        /* 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;
}

static int usb_ehci_intr_init(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;

    /* Create and initialize a Queue Head (QH) structure for this transfer */
    qh = usb_ehci_qh_create(epinfo);
    if (qh == NULL) {
        return -ENOMEM;
    }

    epinfo->qh = qh;

    /* 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) {
        return -ENOMEM;
    }

    /* 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;
}

/****************************************************************************
 * 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 (epinfo->hport->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 (epinfo->hport->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_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, uint32_t timeout)
{
    int ret = 0;

    /* Wait for the IOC completion event */
    if (epinfo->iocwait) {
        ret = usb_osal_sem_take(epinfo->iocsem, timeout);
        if (ret < 0) {
            return ret;
        }
    }

    ret = epinfo->result;

    if (ret < 0) {
        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_dcache_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));

    /* 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_dcache_invalidate((uintptr_t)&qh->hw, sizeof(struct ehci_qh_s));

    /* 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);

    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) {
    }
    /* 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;
        usb_ehci_dcache_clean((uintptr_t)*bp, sizeof(uint32_t));
        /* 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.
                */

                epinfo->result = -EPERM;
                epinfo->toggle = 0;
            } else {
                /* Otherwise, it is some kind of data transfer error */

                epinfo->result = -EIO;
            }
        }

        /* Is there a thread waiting for this transfer to complete? */
        if (epinfo->iocwait) {
            /* Yes... wake it up */
            epinfo->iocwait = false;
            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_dcache_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));

    /* 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_dcache_invalidate((uintptr_t)&qh->hw, sizeof(struct usb_ehci_qh_s));

    /* 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;
    usb_ehci_dcache_clean((uintptr_t)*bp, sizeof(uint32_t));
    /* 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) {
    }

    /* 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;

    usb_ehci_dcache_invalidate((uintptr_t)&g_asynchead.hw, sizeof(struct usb_ehci_qh_s));
    /* 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 */

    usb_ehci_dcache_invalidate((uintptr_t)&g_intrhead.hw, sizeof(struct usb_ehci_qh_s));
    /* 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) {
        }
    }
#endif
}

/****************************************************************************
 * 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]);

        /* Handle port connection status change (CSC) events */
        if ((portsc & EHCI_PORTSC_CSC) != 0) {
            if ((portsc & EHCI_PORTSC_CCS) == EHCI_PORTSC_CCS) {
                /* Connected ... Did we just become connected? */
                usbh_event_notify_handler(USBH_EVENT_CONNECTED, 1);
            } else {
                for (uint8_t chidx = 0; chidx < CONFIG_USBHOST_PIPE_NUM; chidx++) {
                    struct usb_ehci_epinfo_s *epinfo = &g_ehci_hcd.chan[chidx];
                    if (epinfo->iocwait) {
                        epinfo->iocwait = false;
                        usb_osal_sem_give(epinfo->iocsem);
                    }
                }
                usbh_event_notify_handler(USBH_EVENT_DISCONNECTED, 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) {
        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) {
            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_hc_low_level_init(void)
{
}

int usb_hc_sw_init(void)
{
    memset(&g_ehci_hcd, 0, sizeof(struct ehci_hcd));

    /* 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_ehci_hcd.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_ehci_hcd.qtdpool[i]);
    }

    for (uint8_t chidx = 0; chidx < CONFIG_USB_EHCI_QH_NUM; chidx++) {
        struct usb_ehci_epinfo_s *epinfo;

        epinfo = &g_ehci_hcd.chan[chidx];
        epinfo->iocsem = usb_osal_sem_create(0);
        epinfo->exclsem = usb_osal_mutex_create();
    }

    return 0;
}

int usb_hc_hw_init(void)
{
    int ret;
    uint32_t regval;
    uintptr_t physaddr1;
    uintptr_t physaddr2;

    /* 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);

    usb_ehci_dcache_clean((uintptr_t)&g_asynchead.hw, sizeof(struct usb_ehci_qh_s));

    /* 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_dcache_clean((uintptr_t)&g_intrhead.hw, sizeof(struct usb_ehci_qh_s));
    usb_ehci_dcache_clean((uintptr_t)g_framelist, FRAME_LIST_SIZE * sizeof(uint32_t));

    usb_hc_low_level_init();
    /* Host Controller Initialization. Paragraph 4.1 */

    /* Reset the EHCI hardware */
    ret = usb_ehci_reset();
    if (ret < 0) {
        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);
    USB_LOG_INFO("EHCI HCIVERSION %x.%02x\r\n", 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;
    USB_LOG_INFO("EHCI nports=%d, HCSPARAMS=%04x\r\n", nports, regval);

    /* Show the HCCPARAMS register */
    regval = usb_ehci_getreg(&HCCR->hccparams);
    USB_LOG_INFO("EHCI HCCPARAMS=%06x\r\n", 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 == 256
    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. */
#ifdef CONFIG_USB_EHCI_CONFIGFLAG
    regval = usb_ehci_getreg(&HCOR->configflag);
    regval |= EHCI_CONFIGFLAG;
    usb_ehci_putreg(regval, &HCOR->configflag);
#endif
    /* 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) {
        return -2;
    }

    /* Enable EHCI interrupts.  Interrupts are still disabled at the level of
    * the interrupt controller.
    */
    usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);

    return ret;
}

bool usbh_get_port_connect_status(const uint8_t port)
{
    uint32_t portsc;
    portsc = usb_ehci_getreg(&HCOR->portsc[port - 1]);
    if ((portsc & EHCI_PORTSC_CCS) == EHCI_PORTSC_CCS) {
        return true;
    } else {
        return false;
    }
}

int usbh_reset_port(const uint8_t port)
{
    uint32_t timeout = 0;
    uint32_t regval;
    regval = usb_ehci_getreg(&HCOR->portsc[port - 1]);
    regval &= ~EHCI_PORTSC_PE;
    regval |= EHCI_PORTSC_RESET;
    usb_ehci_putreg(regval, &HCOR->portsc[port - 1]);

    usb_osal_msleep(55);

    regval = usb_ehci_getreg(&HCOR->portsc[port - 1]);
    regval &= ~EHCI_PORTSC_RESET;
    usb_ehci_putreg(regval, &HCOR->portsc[port - 1]);

    /* Wait for the port reset to complete
    *
    * Paragraph 2.3.9:
    *
    *  "Note that when software writes a zero to this bit there may be a
    *   delay before the bit status changes to a zero. The bit status will
    *   not read as a zero until after the reset has completed. If the port
    *   is in high-speed mode after reset is complete, the host controller
    *   will automatically enable this port (e.g. set the Port Enable bit
    *   to a one). A host controller must terminate the reset and stabilize
    *   the state of the port within 2 milliseconds of software transitioning
    *   this bit from a one to a zero ..."
    */

    while ((usb_ehci_getreg(&HCOR->portsc[port - 1]) & EHCI_PORTSC_RESET) != 0) {
        usb_osal_msleep(1);
        timeout++;
        if (timeout > 100) {
            return -ETIMEDOUT;
        }
    }

    return 0;
}

__WEAK uint8_t usbh_get_port_speed(const uint8_t port)
{
    /* Defined by individual manufacturers */
    return 0;
}

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(epinfo->exclsem);
    if (ret < 0) {
        return ret;
    }

    epinfo->devaddr = dev_addr;
    epinfo->speed = speed;
    epinfo->maxpacket = ep_mps;

    usb_osal_mutex_give(epinfo->exclsem);

    return ret;
}

int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
{
    struct usb_ehci_epinfo_s *epinfo;
    struct usbh_hubport *hport;
    usb_osal_sem_t iocsem;
    usb_osal_mutex_t exclsem;
    int chidx;

    DEBUGASSERT(ep_cfg != NULL && ep_cfg->hport != NULL);

    hport = ep_cfg->hport;

    chidx = usb_ehci_chan_alloc();

    epinfo = &g_ehci_hcd.chan[chidx];

    iocsem = epinfo->iocsem;
    exclsem = epinfo->exclsem;

    memset(epinfo, 0, sizeof(struct usb_ehci_epinfo_s));

    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;

    /* restore variable */
    epinfo->inuse = true;
    epinfo->iocsem = iocsem;
    epinfo->exclsem = exclsem;

    *ep = (usbh_epinfo_t)epinfo;

    return 0;
}

int usbh_ep_free(usbh_epinfo_t ep)
{
    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;

    usb_ehci_chan_free(epinfo);

    return 0;
}

int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
{
    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(epinfo->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) {
        goto errout_with_setup;
    }

    ret = usb_ehci_control_init(epinfo, setup, buffer, setup->wLength);
    if (ret < 0) {
        goto errout_with_iocwait;
    }

    /* And wait for the transfer to complete */
    ret = usb_ehci_transfer_wait(epinfo, CONFIG_USBHOST_CONTROL_TRANSFER_TIMEOUT);
    if (ret < 0) {
        goto errout_with_iocwait;
    }

    usb_osal_mutex_give(epinfo->exclsem);
    return ret;

errout_with_iocwait:
    epinfo->iocwait = false;
    /* Clean-up after an error */
    if (epinfo->qh) {
        usb_ehci_qh_discard(epinfo->qh);
        epinfo->qh = NULL;
    }
errout_with_setup:
    usb_osal_mutex_give(epinfo->exclsem);
    return ret;
}

int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
    int ret;

    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;

    DEBUGASSERT(epinfo && buffer && buflen > 0);

    ret = usb_osal_mutex_take(epinfo->exclsem);
    if (ret < 0) {
        return ret;
    }

    ret = usb_ehci_ioc_setup(epinfo);
    if (ret < 0) {
        goto errout_with_setup;
    }

    ret = usb_ehci_bulk_init(epinfo, buffer, buflen);
    if (ret < 0) {
        goto errout_with_iocwait;
    }

    /* And wait for the transfer to complete */
    ret = usb_ehci_transfer_wait(epinfo, timeout);
    if (ret < 0) {
        goto errout_with_iocwait;
    }
    usb_osal_mutex_give(epinfo->exclsem);
    return ret;

errout_with_iocwait:
    epinfo->iocwait = false;
    /* Clean-up after an error */
    if (epinfo->qh) {
        usb_ehci_qh_discard(epinfo->qh);
        epinfo->qh = NULL;
    }
errout_with_setup:
    usb_osal_mutex_give(epinfo->exclsem);
    return ret;
}

int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
    int ret;

    struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;

    DEBUGASSERT(epinfo && buffer && buflen > 0);

    ret = usb_osal_mutex_take(epinfo->exclsem);
    if (ret < 0) {
        return ret;
    }

    ret = usb_ehci_ioc_setup(epinfo);
    if (ret < 0) {
        goto errout_with_setup;
    }

    ret = usb_ehci_intr_init(epinfo, buffer, buflen);
    if (ret < 0) {
        goto errout_with_iocwait;
    }

    /* And wait for the transfer to complete */
    ret = usb_ehci_transfer_wait(epinfo, timeout);
    if (ret < 0) {
        goto errout_with_iocwait;
    }
    usb_osal_mutex_give(epinfo->exclsem);
    return ret;

errout_with_iocwait:
    epinfo->iocwait = false;
    /* Clean-up after an error */
    if (epinfo->qh) {
        usb_ehci_qh_discard(epinfo->qh);
        epinfo->qh = NULL;
    }
errout_with_setup:
    usb_osal_mutex_give(epinfo->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(epinfo->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_setup;
    }

    /* Check for errors in the setup of the transfer */
    ret = usb_ehci_bulk_init(epinfo, buffer, buflen);
    if (ret < 0) {
        goto errout_with_qh;
    }

    /* The transfer is in progress */
    usb_osal_mutex_give(epinfo->exclsem);
    return 0;

errout_with_qh:
    /* Clean-up after an error */
    if (epinfo->qh) {
        usb_ehci_qh_discard(epinfo->qh);
        epinfo->qh = NULL;
    }
errout_with_setup:
    epinfo->callback = NULL;
    epinfo->arg = NULL;
    usb_osal_mutex_give(epinfo->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(epinfo->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_setup;
    }

    /* Check for errors in the setup of the transfer */
    ret = usb_ehci_intr_init(epinfo, buffer, buflen);
    if (ret < 0) {
        goto errout_with_qh;
    }

    /* The transfer is in progress */
    usb_osal_mutex_give(epinfo->exclsem);
    return 0;

errout_with_qh:
    /* Clean-up after an error */
    if (epinfo->qh) {
        usb_ehci_qh_discard(epinfo->qh);
        epinfo->qh = NULL;
    }
errout_with_setup:
    epinfo->callback = NULL;
    epinfo->arg = NULL;
    usb_osal_mutex_give(epinfo->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_mutex_take(epinfo->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

        case USB_ENDPOINT_TYPE_ISOCHRONOUS:
        default:
            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) {
    }

    /* 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(epinfo->exclsem);
    return ret;
}

static void usb_ehci_bottomhalf(void *arg)
{
    uint32_t pending = (uint32_t)arg;

    /* 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) {
        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) {
    }
    /* 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) {
    }
}

void USBH_IRQHandler(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) {
        /* 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);

        usb_ehci_bottomhalf((void *)pending);

        /* 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);
    }
}