Przeglądaj źródła

add musb host porting

sakumisu 3 lat temu
rodzic
commit
0c39b0a2bd
1 zmienionych plików z 1051 dodań i 0 usunięć
  1. 1051 0
      port/musb/usb_hc_musb.c

+ 1051 - 0
port/musb/usb_hc_musb.c

@@ -0,0 +1,1051 @@
+#include "usbh_core.h"
+#include "usb_musb_reg.h"
+
+#ifdef USB_MUSB_SUNXI
+
+#define SUNXI_SRAMC_BASE 0x01c00000
+#define SUNXI_USB0_BASE  0x01c13000
+
+#define USBC_REG_o_PHYCTL 0x0404
+
+#ifndef USB_BASE
+#define USB_BASE (SUNXI_USB0_BASE)
+#endif
+
+#ifndef USBH_IRQHandler
+#define USBH_IRQHandler USB_INT_Handler //use actual usb irq name instead
+void USBH_IRQHandler(int, void *);
+#endif
+
+#define MUSB_IND_TXMAP_OFFSET      0x80
+#define MUSB_IND_TXCSRL_OFFSET     0x82
+#define MUSB_IND_TXCSRH_OFFSET     0x83
+#define MUSB_IND_RXMAP_OFFSET      0x84
+#define MUSB_IND_RXCSRL_OFFSET     0x86
+#define MUSB_IND_RXCSRH_OFFSET     0x87
+#define MUSB_IND_RXCOUNT_OFFSET    0x88
+#define MUSB_IND_TXTYPE_OFFSET     0x8C
+#define MUSB_IND_TXINTERVAL_OFFSET 0x8D
+#define MUSB_IND_RXTYPE_OFFSET     0x8E
+#define MUSB_IND_RXINTERVAL_OFFSET 0x8F
+#define MUSB_FIFO_OFFSET           0x00
+#else
+#ifndef USBH_IRQHandler
+#define USBH_IRQHandler USB_INT_Handler
+#endif
+
+#ifndef USB_BASE
+#define USB_BASE (0x40086400UL)
+#endif
+
+#define MUSB_IND_TXMAP_OFFSET      0x10
+#define MUSB_IND_TXCSRL_OFFSET     0x12
+#define MUSB_IND_TXCSRH_OFFSET     0x13
+#define MUSB_IND_RXMAP_OFFSET      0x14
+#define MUSB_IND_RXCSRL_OFFSET     0x16
+#define MUSB_IND_RXCSRH_OFFSET     0x17
+#define MUSB_IND_RXCOUNT_OFFSET    0x18
+#define MUSB_IND_TXTYPE_OFFSET     0x1A
+#define MUSB_IND_TXINTERVAL_OFFSET 0x1B
+#define MUSB_IND_RXTYPE_OFFSET     0x1C
+#define MUSB_IND_RXINTERVAL_OFFSET 0x1D
+#define MUSB_FIFO_OFFSET           0x20
+#endif
+
+#ifndef CONFIG_USBHOST_CHANNELS
+#define CONFIG_USBHOST_CHANNELS 8
+#endif
+
+#define USB ((USB0_Type *)USB_BASE)
+
+#define HWREG(x) \
+    (*((volatile uint32_t *)(x)))
+#define HWREGH(x) \
+    (*((volatile uint16_t *)(x)))
+#define HWREGB(x) \
+    (*((volatile uint8_t *)(x)))
+
+#define USB_TXMAPx_BASE       (USB_BASE + MUSB_IND_TXMAP_OFFSET)
+#define USB_RXMAPx_BASE       (USB_BASE + MUSB_IND_RXMAP_OFFSET)
+#define USB_TXCSRLx_BASE      (USB_BASE + MUSB_IND_TXCSRL_OFFSET)
+#define USB_RXCSRLx_BASE      (USB_BASE + MUSB_IND_RXCSRL_OFFSET)
+#define USB_TXCSRHx_BASE      (USB_BASE + MUSB_IND_TXCSRH_OFFSET)
+#define USB_RXCSRHx_BASE      (USB_BASE + MUSB_IND_RXCSRH_OFFSET)
+#define USB_RXCOUNTx_BASE     (USB_BASE + MUSB_IND_RXCOUNT_OFFSET)
+#define USB_FIFO_BASE(ep_idx) (USB_BASE + MUSB_FIFO_OFFSET + 0x4 * ep_idx)
+#define USB_TXTYPEx_BASE      (USB_BASE + MUSB_IND_TXTYPE_OFFSET)
+#define USB_RXTYPEx_BASE      (USB_BASE + MUSB_IND_RXTYPE_OFFSET)
+#define USB_TXINTERVALx_BASE  (USB_BASE + MUSB_IND_TXINTERVAL_OFFSET)
+#define USB_RXINTERVALx_BASE  (USB_BASE + MUSB_IND_RXINTERVAL_OFFSET)
+
+#define USB_TXADDR_BASE(ep_idx)    (&USB->TXFUNCADDR0 + 0x8 * ep_idx)
+#define USB_TXHUBADDR_BASE(ep_idx) (&USB->TXFUNCADDR0 + 0x8 * ep_idx + 2)
+#define USB_TXHUBPORT_BASE(ep_idx) (&USB->TXFUNCADDR0 + 0x8 * ep_idx + 3)
+#define USB_RXADDR_BASE(ep_idx)    (&USB->TXFUNCADDR0 + 0x8 * ep_idx + 4)
+#define USB_RXHUBADDR_BASE(ep_idx) (&USB->TXFUNCADDR0 + 0x8 * ep_idx + 6)
+#define USB_RXHUBPORT_BASE(ep_idx) (&USB->TXFUNCADDR0 + 0x8 * ep_idx + 7)
+
+typedef enum {
+    USB_EP0_STATE_SETUP = 0x0, /**< SETUP DATA */
+    USB_EP0_STATE_IN_DATA,     /**< IN DATA */
+    USB_EP0_STATE_IN_STATUS,   /**< IN status*/
+    USB_EP0_STATE_OUT_DATA,    /**< OUT DATA */
+    USB_EP0_STATE_OUT_STATUS,  /**< OUT status */
+    USB_EP0_STATE_IN_DATA_C,   /**< IN status*/
+    USB_EP0_STATE_IN_STATUS_C, /**< IN DATA */
+} ep0_state_t;
+
+struct usb_musb_chan {
+    usb_osal_sem_t waitsem; /* Channel wait semaphore */
+    volatile bool waiter;   /* True: Thread is waiting for a channel event */
+    volatile int result;    /* The result of the transfer */
+    bool inuse;             /* True: This channel is "in use" */
+    uint16_t mps;
+    uint8_t interval;
+    uint8_t hubaddr;
+    uint8_t hubport;
+    uint8_t *buffer;
+    uint32_t buflen;
+    bool in;                /* True: IN endpoint */
+    volatile uint16_t xfrd; /* Bytes transferred (at end of transfer) */
+#ifdef CONFIG_USBHOST_ASYNCH
+    usbh_asynch_callback_t callback; /* Transfer complete callback */
+    void *arg;                       /* Argument that accompanies the callback */
+#endif
+};
+
+struct usb_musb_priv {
+    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 */
+    volatile uint32_t fifo_size_offset;
+    struct usb_musb_chan chan[CONFIG_USBHOST_CHANNELS];
+} g_usbhost;
+
+volatile uint8_t usb_ep0_state = USB_EP0_STATE_SETUP;
+
+/* get current active ep */
+static uint8_t USBC_GetActiveEp(void)
+{
+    return USB->EPIDX;
+}
+
+/* set the active ep */
+static void USBC_SelectActiveEp(uint8_t ep_index)
+{
+    USB->EPIDX = ep_index;
+}
+
+static void usb_musb_fifo_flush(uint8_t ep)
+{
+    uint8_t ep_idx = ep & 0x7f;
+    if (ep_idx == 0) {
+        if ((HWREGB(USB_TXCSRLx_BASE) & (USB_CSRL0_RXRDY | USB_CSRL0_TXRDY)) != 0)
+            HWREGB(USB_RXCSRLx_BASE) |= USB_CSRH0_FLUSH;
+    } else {
+        if (ep & 0x80) {
+            if (HWREGB(USB_TXCSRLx_BASE) & USB_TXCSRL1_TXRDY)
+                HWREGB(USB_TXCSRLx_BASE) |= USB_TXCSRL1_FLUSH;
+        } else {
+            if (HWREGB(USB_RXCSRLx_BASE) & USB_RXCSRL1_RXRDY)
+                HWREGB(USB_RXCSRLx_BASE) |= USB_RXCSRL1_FLUSH;
+        }
+    }
+}
+
+void usb_musb_ep_status_clear(uint32_t ep_idx, uint32_t flags)
+{
+    if (ep_idx == USB_EP_0) {
+        HWREGB(USB_TXCSRLx_BASE) &= ~flags;
+    } else {
+        HWREGB(USB_TXCSRLx_BASE) &= ~flags;
+        HWREGB(USB_RXCSRLx_BASE) &= ~flags;
+    }
+}
+
+static void usb_musb_write_packet(uint8_t ep_idx, uint8_t *buffer, uint16_t len)
+{
+    uint32_t *buf32;
+    uint8_t *buf8;
+    uint32_t count32;
+    uint32_t count8;
+    int i;
+
+    if ((uint32_t)buffer & 0x03) {
+        buf8 = buffer;
+        for (i = 0; i < len; i++) {
+            HWREGB(USB_FIFO_BASE(ep_idx)) = *buf8++;
+        }
+    } else {
+        count32 = len >> 2;
+        count8 = len & 0x03;
+
+        buf32 = (uint32_t *)buffer;
+
+        while (count32--) {
+            HWREG(USB_FIFO_BASE(ep_idx)) = *buf32++;
+        }
+
+        buf8 = (uint8_t *)buf32;
+
+        while (count8--) {
+            HWREGB(USB_FIFO_BASE(ep_idx)) = *buf8++;
+        }
+    }
+}
+
+static void usb_musb_read_packet(uint8_t ep_idx, uint8_t *buffer, uint16_t len)
+{
+    uint32_t *buf32;
+    uint8_t *buf8;
+    uint32_t count32;
+    uint32_t count8;
+    int i;
+
+    if ((uint32_t)buffer & 0x03) {
+        buf8 = buffer;
+        for (i = 0; i < len; i++) {
+            *buf8++ = HWREGB(USB_FIFO_BASE(ep_idx));
+        }
+    } else {
+        count32 = len >> 2;
+        count8 = len & 0x03;
+
+        buf32 = (uint32_t *)buffer;
+
+        while (count32--) {
+            *buf32++ = HWREG(USB_FIFO_BASE(ep_idx));
+        }
+
+        buf8 = (uint8_t *)buf32;
+
+        while (count8--) {
+            *buf8++ = HWREGB(USB_FIFO_BASE(ep_idx));
+        }
+    }
+}
+
+static uint32_t usb_musb_get_fifo_size(uint16_t mps, uint16_t *used)
+{
+    uint32_t size;
+
+    for (uint8_t i = USB_TXFIFOSZ_SIZE_8; i <= USB_TXFIFOSZ_SIZE_2048; i++) {
+        size = (8 << i);
+        if (mps <= size) {
+            *used = size;
+            return i;
+        }
+    }
+
+    *used = 0;
+    return USB_TXFIFOSZ_SIZE_8;
+}
+
+/****************************************************************************
+ * Name: usb_synopsys_chan_alloc
+ *
+ * Description:
+ *   Allocate a channel.
+ *
+ ****************************************************************************/
+
+static int usb_musb_chan_alloc(struct usb_musb_priv *priv)
+{
+    int chidx;
+
+    /* Search the table of channels */
+
+    for (chidx = 2; 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_musb_chan_free(struct usb_musb_priv *priv, int chidx)
+{
+    /* Mark the channel available */
+    priv->chan[chidx].inuse = false;
+}
+
+/****************************************************************************
+ * Name: usb_synopsys_chan_freeall
+ *
+ * Description:
+ *   Free all channels.
+ *
+ ****************************************************************************/
+
+static inline void usb_musb_chan_freeall(struct usb_musb_priv *priv)
+{
+    uint8_t chidx;
+
+    /* Free all host channels */
+
+    for (chidx = 2; chidx < CONFIG_USBHOST_CHANNELS; chidx++) {
+        usb_musb_chan_free(priv, chidx);
+    }
+}
+
+/****************************************************************************
+ * Name: usb_musb_chan_waitsetup
+ *
+ * Description:
+ *   Set the request for the transfer complete event well BEFORE enabling
+ *   the transfer (as soon as we are absolutely committed to the 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!
+ *
+ * Assumptions:
+ *   Called from a normal thread context BEFORE the transfer has been
+ *   started.
+ *
+ ****************************************************************************/
+
+static int usb_musb_chan_waitsetup(struct usb_musb_priv *priv,
+                                   struct usb_musb_chan *chan)
+{
+    uint32_t flags;
+    int ret = -ENODEV;
+
+    flags = usb_osal_enter_critical_section();
+
+    /* Is the device still connected? */
+
+    if (priv->connected) {
+        /* Yes.. then set waiter to indicate that we expect to be informed
+       * when either (1) the device is disconnected, or (2) the transfer
+       * completed.
+       */
+
+        chan->waiter = true;
+        chan->result = -EBUSY;
+        chan->xfrd = 0;
+#ifdef CONFIG_USBHOST_ASYNCH
+        chan->callback = NULL;
+        chan->arg = NULL;
+#endif
+        ret = 0;
+    }
+    usb_osal_leave_critical_section(flags);
+    return ret;
+}
+
+/****************************************************************************
+ * Name: usb_musb_chan_asynchsetup
+ *
+ * Description:
+ *   Set the request for the transfer complete 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!
+ *
+ * Assumptions:
+ *   Might be called from the level of an interrupt handler
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_USBHOST_ASYNCH
+static int usb_musb_chan_asynchsetup(struct usb_musb_priv *priv,
+                                     struct usb_musb_chan *chan,
+                                     usbh_asynch_callback_t callback, void *arg)
+{
+    uint32_t flags;
+    int ret = -ENODEV;
+
+    flags = usb_osal_enter_critical_section();
+    /* Is the device still connected? */
+
+    if (priv->connected) {
+        /* Yes.. then set waiter to indicate that we expect to be informed
+       * when either (1) the device is disconnected, or (2) the transfer
+       * completed.
+       */
+
+        chan->waiter = false;
+        chan->result = -EBUSY;
+        chan->xfrd = 0;
+        chan->callback = callback;
+        chan->arg = arg;
+        ret = 0;
+    }
+
+    usb_osal_leave_critical_section(flags);
+    return ret;
+}
+#endif
+
+/****************************************************************************
+ * Name: usb_musb_chan_wait
+ *
+ * Description:
+ *   Wait for a transfer on a channel to complete.
+ *
+ * Assumptions:
+ *   Called from a normal thread context
+ *
+ ****************************************************************************/
+
+static int usb_musb_chan_wait(struct usb_musb_priv *priv, struct usb_musb_chan *chan)
+{
+    int ret;
+
+    /* Loop, testing for an end of transfer condition.  The channel 'result'
+   * was set to EBUSY and 'waiter' was set to true before the transfer;
+   * 'waiter' will be set to false and 'result' will be set appropriately
+   * when the transfer is completed.
+   */
+
+    if (chan->waiter) {
+        ret = usb_osal_sem_take(chan->waitsem);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    /* The transfer is complete re-enable interrupts and return the result */
+    ret = chan->result;
+    return ret;
+}
+
+/****************************************************************************
+ * Name: usb_musb_chan_wakeup
+ *
+ * Description:
+ *   A channel transfer has completed... wakeup any threads waiting for the
+ *   transfer to complete.
+ *
+ * Assumptions:
+ *   This function is called from the transfer complete interrupt handler for
+ *   the channel.  Interrupts are disabled.
+ *
+ ****************************************************************************/
+
+static void usb_musb_chan_wakeup(struct usb_musb_priv *priv, struct usb_musb_chan *chan)
+{
+    usbh_asynch_callback_t callback;
+    void *arg;
+    int nbytes;
+
+    /* Is the transfer complete? */
+
+    if (chan->result != -EBUSY) {
+        /* Is there a thread waiting for this transfer to complete? */
+
+        if (chan->waiter) {
+            /* Wake'em up! */
+            chan->waiter = false;
+            usb_osal_sem_give(chan->waitsem);
+        }
+#ifdef CONFIG_USBHOST_ASYNCH
+        /* No.. is an asynchronous callback expected when the transfer
+       * completes?
+       */
+        else if (chan->callback) {
+            /* Handle continuation of IN/OUT pipes */
+            if (chan->in) {
+                callback = chan->callback;
+                arg = chan->arg;
+                nbytes = chan->xfrd;
+                chan->callback = NULL;
+                chan->arg = NULL;
+                if (chan->result < 0) {
+                    nbytes = chan->result;
+                }
+
+                callback(arg, nbytes);
+
+            } else {
+                callback = chan->callback;
+                arg = chan->arg;
+                nbytes = chan->xfrd;
+                chan->callback = NULL;
+                chan->arg = NULL;
+                if (chan->result < 0) {
+                    nbytes = chan->result;
+                }
+
+                callback(arg, nbytes);
+            }
+        }
+#endif
+    }
+}
+
+__WEAK void usb_hc_low_level_init(void)
+{
+}
+
+int usb_hc_init(void)
+{
+    g_usbhost.connected = 0;
+    g_usbhost.pscwait = 0;
+    g_usbhost.fifo_size_offset = 0;
+
+    usb_hc_low_level_init();
+
+    g_usbhost.exclsem = usb_osal_mutex_create();
+
+    USB->IE = USB_IE_RESET | USB_IE_CONN | USB_IE_DISCON |
+              USB_IE_RESUME | USB_IE_SUSPND |
+              USB_IE_BABBLE | USB_IE_SESREQ | USB_IE_VBUSERR;
+
+    USB->TXIE = USB_TXIE_EP0;
+    USB->RXIE = 0;
+
+    USB->DEVCTL |= USB_DEVCTL_SESSION;
+    return 0;
+}
+
+int usbh_reset_port(const uint8_t port)
+{
+    USB->POWER |= USB_POWER_RESET;
+    usb_osal_msleep(20);
+    USB->POWER &= ~(USB_POWER_RESET);
+    usb_osal_msleep(20);
+    return 0;
+}
+
+uint8_t usbh_get_port_speed(const uint8_t port)
+{
+    uint8_t speed;
+
+    if (USB->POWER & USB_POWER_HSMODE)
+        speed = USB_SPEED_HIGH;
+    else if (USB->DEVCTL & USB_DEVCTL_FSDEV)
+        speed = USB_SPEED_FULL;
+    else if (USB->DEVCTL & USB_DEVCTL_LSDEV)
+        speed = USB_SPEED_LOW;
+
+    return speed;
+}
+
+int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed)
+{
+    int ret;
+    uint8_t ep0 = (uint8_t)ep;
+    struct usb_musb_chan *chan;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    USBC_SelectActiveEp(ep0);
+    HWREGB(USB_TXADDR_BASE(ep0)) = dev_addr;
+
+    if (speed == USB_SPEED_HIGH) {
+        USB->TYPE0 = USB_TYPE0_SPEED_HIGH;
+    } else if (speed == USB_SPEED_FULL) {
+        USB->TYPE0 = USB_TYPE0_SPEED_FULL;
+    } else if (speed == USB_SPEED_LOW) {
+        USB->TYPE0 = USB_TYPE0_SPEED_LOW;
+    }
+
+    chan = &g_usbhost.chan[ep0];
+    chan->mps = 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 usbh_hubport *hport;
+    struct usb_musb_chan *chan;
+    uint32_t chidx;
+    uint16_t used = 0;
+    uint8_t fifo_size;
+
+    hport = ep_cfg->hport;
+
+    uint32_t old_index = USBC_GetActiveEp();
+
+    if (ep_cfg->ep_type == USB_ENDPOINT_TYPE_CONTROL) {
+        if (hport->parent == NULL) {
+            chan = &g_usbhost.chan[0];
+            memset(chan, 0, sizeof(struct usb_musb_chan));
+
+            chan->waitsem = usb_osal_sem_create(0);
+
+            USBC_SelectActiveEp(0);
+
+            HWREGB(USB_TXINTERVALx_BASE) = 0;
+            HWREGB(USB_TXHUBADDR_BASE(0)) = 0;
+            HWREGB(USB_TXHUBPORT_BASE(0)) = 0;
+
+            *ep = (usbh_epinfo_t)0;
+        } else {
+        }
+    } else {
+        chidx = usb_musb_chan_alloc(&g_usbhost);
+
+        chan = &g_usbhost.chan[chidx];
+        memset(chan, 0, sizeof(struct usb_musb_chan));
+        chan->inuse = true;
+        chan->interval = ep_cfg->ep_interval;
+        chan->mps = ep_cfg->ep_mps;
+
+        chan->waitsem = usb_osal_sem_create(0);
+
+        USBC_SelectActiveEp(chidx);
+
+        uint32_t temp = ep_cfg->ep_addr & 0x7f;
+
+        if (hport->speed == USB_SPEED_HIGH) {
+            temp |= USB_TXTYPE1_SPEED_HIGH;
+        } else if (hport->speed == USB_SPEED_FULL) {
+            temp |= USB_TXTYPE1_SPEED_FULL;
+        } else if (hport->speed == USB_SPEED_LOW) {
+            temp |= USB_TXTYPE1_SPEED_LOW;
+        }
+
+        switch (ep_cfg->ep_type) {
+            case 0x00:
+                temp |= USB_TXTYPE1_PROTO_CTRL;
+                break;
+            case 0x01:
+                temp |= USB_TXTYPE1_PROTO_ISOC;
+                break;
+            case 0x02:
+                temp |= USB_TXTYPE1_PROTO_BULK;
+                break;
+            case 0x03:
+                temp |= USB_TXTYPE1_PROTO_INT;
+                break;
+        }
+
+        if (ep_cfg->ep_addr & 0x80) {
+            chan->in = 1;
+
+            HWREGB(USB_RXADDR_BASE(chidx)) = hport->dev_addr;
+            HWREGB(USB_RXHUBADDR_BASE(chidx)) = 0;
+            HWREGB(USB_RXHUBPORT_BASE(chidx)) = 0;
+
+            HWREGB(USB_RXTYPEx_BASE) = temp;
+            HWREGH(USB_RXMAPx_BASE) = ep_cfg->ep_mps;
+            HWREGB(USB_RXINTERVALx_BASE) = ep_cfg->ep_interval;
+
+            HWREGB(USB_RXCSRHx_BASE) = 0;
+            HWREGB(USB_RXCSRLx_BASE) |= USB_RXCSRL1_CLRDT;
+
+            usb_musb_ep_status_clear(chidx, USB_HOST_IN_STATUS);
+
+            fifo_size = usb_musb_get_fifo_size(ep_cfg->ep_mps, &used);
+
+            USB->RXFIFOADD = g_usbhost.fifo_size_offset >> 3;
+            USB->RXFIFOSZ = fifo_size & 0x0f;
+
+            g_usbhost.fifo_size_offset += used;
+
+            USB->RXIE |= (1 << chidx);
+        } else {
+            chan->in = 0;
+            HWREGB(USB_TXADDR_BASE(chidx)) = hport->dev_addr;
+            HWREGB(USB_TXHUBADDR_BASE(chidx)) = 0;
+            HWREGB(USB_TXHUBPORT_BASE(chidx)) = 0;
+
+            HWREGB(USB_TXTYPEx_BASE) = temp;
+            HWREGH(USB_TXMAPx_BASE) = ep_cfg->ep_mps;
+            HWREGB(USB_TXINTERVALx_BASE) = ep_cfg->ep_interval;
+
+            HWREGB(USB_TXCSRHx_BASE) = 0;
+            usb_musb_ep_status_clear(chidx, USB_HOST_OUT_STATUS);
+            HWREGB(USB_TXCSRLx_BASE) |= USB_TXCSRL1_CLRDT;
+
+            fifo_size = usb_musb_get_fifo_size(ep_cfg->ep_mps, &used);
+
+            USB->TXFIFOADD = g_usbhost.fifo_size_offset >> 3;
+            USB->TXFIFOSZ = fifo_size & 0x0f;
+
+            g_usbhost.fifo_size_offset += used;
+
+            USB->TXIE |= (1 << chidx);
+        }
+
+        *ep = (usbh_epinfo_t)chidx;
+    }
+
+    USBC_SelectActiveEp(old_index);
+    return 0;
+}
+
+int usbh_ep_free(usbh_epinfo_t ep)
+{
+    usb_musb_chan_free(&g_usbhost, (int)ep);
+    usb_osal_sem_delete(g_usbhost.chan[(int)ep].waitsem);
+    return 0;
+}
+
+int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
+{
+    int ret;
+    struct usb_musb_chan *chan;
+    uint8_t ep0 = (uint8_t)ep;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    chan = &g_usbhost.chan[0];
+
+    usb_musb_chan_waitsetup(&g_usbhost, chan);
+
+    usb_musb_write_packet(0, (uint8_t *)setup, 8);
+
+    if (setup->wLength && buffer) {
+        if (setup->bmRequestType & 0x80) {
+            usb_ep0_state = USB_EP0_STATE_IN_DATA;
+        } else {
+            usb_ep0_state = USB_EP0_STATE_OUT_DATA;
+        }
+        chan->buffer = buffer;
+        chan->buflen = setup->wLength;
+    } else {
+        usb_ep0_state = USB_EP0_STATE_IN_STATUS;
+    }
+
+    USBC_SelectActiveEp(0);
+    USB->CSRL0 = USB_CSRL0_TXRDY | USB_CSRL0_SETUP;
+
+    ret = usb_musb_chan_wait(&g_usbhost, chan);
+
+    if (ret < 0) {
+        goto errout_with_mutex;
+    }
+
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return 0;
+errout_with_mutex:
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return ret;
+}
+
+int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int ret;
+    struct usb_musb_chan *chan;
+    uint8_t chidx = (uint8_t)ep;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    chan = &g_usbhost.chan[chidx];
+    usb_musb_chan_waitsetup(&g_usbhost, chan);
+
+    if (chan->in) {
+        chan->buffer = buffer;
+        chan->buflen = buflen;
+        USBC_SelectActiveEp(chidx);
+        HWREGB(USB_RXCSRLx_BASE) = USB_RXCSRL1_REQPKT;
+    } else {
+        chan->buffer = buffer;
+        chan->buflen = buflen;
+        if (buflen > chan->mps) {
+            buflen = chan->mps;
+        }
+
+        usb_musb_write_packet(chidx, (uint8_t *)buffer, buflen);
+        USBC_SelectActiveEp(chidx);
+        HWREGB(USB_TXCSRLx_BASE) = USB_TXCSRL1_TXRDY;
+        chan->buffer += buflen;
+        chan->buflen -= buflen;
+    }
+    ret = usb_musb_chan_wait(&g_usbhost, chan);
+    if (ret < 0) {
+        goto errout_with_mutex;
+    }
+    usb_osal_mutex_give(g_usbhost.exclsem);
+
+    return buflen;
+errout_with_mutex:
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return ret;
+}
+
+int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
+{
+    int ret;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    usb_osal_mutex_give(g_usbhost.exclsem);
+    return ret;
+}
+
+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;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    usb_osal_mutex_give(g_usbhost.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;
+
+    ret = usb_osal_mutex_take(g_usbhost.exclsem);
+    if (ret < 0) {
+        return ret;
+    }
+
+    usb_osal_mutex_give(g_usbhost.exclsem);
+
+    return ret;
+}
+
+int usb_ep_cancel(usbh_epinfo_t ep)
+{
+    return 0;
+}
+
+void handle_ep0(void)
+{
+    uint8_t ep0_status = USB->CSRL0;
+
+    if (ep0_status & USB_HOST_EP0_ERROR) {
+        usb_musb_ep_status_clear(0, USB_HOST_EP0_ERROR);
+        usb_musb_fifo_flush(0);
+        usb_ep0_state = USB_EP0_STATE_SETUP;
+        g_usbhost.chan[0].result = -EIO;
+        goto chan_wait;
+    }
+
+    if (ep0_status & USB_HOST_EP0_RX_STALL) {
+        usb_musb_ep_status_clear(0, ep0_status & USB_HOST_IN_STATUS);
+        usb_ep0_state = USB_EP0_STATE_SETUP;
+        g_usbhost.chan[0].result = -EPERM;
+        goto chan_wait;
+    }
+    switch (usb_ep0_state) {
+        case USB_EP0_STATE_SETUP:
+            break;
+        case USB_EP0_STATE_IN_DATA:
+            USB->CSRL0 = USB_RXCSRL1_REQPKT;
+            usb_ep0_state = USB_EP0_STATE_IN_DATA_C;
+
+            break;
+        case USB_EP0_STATE_IN_DATA_C:
+            if (USB->CSRL0 & USB_CSRL0_RXRDY) {
+                uint32_t size = g_usbhost.chan[0].buflen;
+                if (size > g_usbhost.chan[0].mps) {
+                    size = g_usbhost.chan[0].mps;
+                }
+
+                size = MIN(size, USB->COUNT0);
+
+                usb_musb_read_packet(0, g_usbhost.chan[0].buffer, size);
+                USB->CSRL0 &= ~USB_CSRL0_RXRDY;
+
+                g_usbhost.chan[0].buffer += size;
+                g_usbhost.chan[0].buflen -= size;
+
+                if ((size < g_usbhost.chan[0].mps) || (g_usbhost.chan[0].buflen == 0)) {
+                    usb_ep0_state = USB_EP0_STATE_IN_STATUS_C;
+                    USB->CSRL0 = USB_CSRL0_TXRDY | USB_CSRL0_STATUS;
+                } else {
+                    USB->CSRL0 = USB_RXCSRL1_REQPKT;
+                }
+            }
+            break;
+        case USB_EP0_STATE_IN_STATUS_C:
+            if (ep0_status & (USB_HOST_EP0_RXPKTRDY | USB_HOST_EP0_STATUS)) {
+                usb_musb_ep_status_clear(0, (USB_HOST_EP0_RXPKTRDY | USB_HOST_EP0_STATUS));
+            }
+
+            usb_ep0_state = USB_EP0_STATE_SETUP;
+            g_usbhost.chan[0].result = 0;
+            goto chan_wait;
+
+            break;
+        case USB_EP0_STATE_IN_STATUS:
+            USB->CSRL0 = USB_CSRL0_REQPKT | USB_CSRL0_STATUS;
+            usb_ep0_state = USB_EP0_STATE_IN_STATUS_C;
+            break;
+
+        case USB_EP0_STATE_OUT_DATA: {
+            uint32_t size = g_usbhost.chan[0].buflen;
+
+            if (size > g_usbhost.chan[0].mps) {
+                size = g_usbhost.chan[0].mps;
+            }
+
+            usb_musb_write_packet(0, g_usbhost.chan[0].buffer, size);
+
+            g_usbhost.chan[0].buffer += size;
+            g_usbhost.chan[0].buflen -= size;
+
+            if (size == g_usbhost.chan[0].mps) {
+                USB->CSRL0 = USB_CSRL0_TXRDY;
+            } else {
+                USB->CSRL0 = USB_CSRL0_TXRDY;
+                usb_ep0_state = USB_EP0_STATE_IN_STATUS;
+            }
+        }
+
+        break;
+    }
+    return;
+chan_wait:
+    usb_musb_chan_wakeup(&g_usbhost, &g_usbhost.chan[0]);
+}
+void USBH_IRQHandler(void)
+{
+    uint32_t is;
+    uint32_t txis;
+    uint32_t rxis;
+    uint32_t ep_status;
+    struct usb_musb_chan *chan;
+    uint8_t chidx;
+    uint8_t old_ep_idx;
+
+    is = USB->IS;
+    txis = USB->TXIS;
+    rxis = USB->RXIS;
+
+    old_ep_idx = USBC_GetActiveEp();
+    if (is & USB_IS_CONN) {
+        if (!g_usbhost.connected) {
+            g_usbhost.connected = true;
+            extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
+            usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1);
+        }
+    }
+
+    if (is & USB_IS_DISCON) {
+        if (g_usbhost.connected) {
+            g_usbhost.connected = false;
+
+            for (uint8_t chnum = 0; chnum < CONFIG_USBHOST_CHANNELS; chnum++) {
+                if (g_usbhost.chan[chnum].waiter) {
+                    /* Wake'em up! */
+                    g_usbhost.chan[chnum].waiter = false;
+                    usb_osal_sem_give(g_usbhost.chan[chnum].waitsem);
+                }
+            }
+
+            extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
+            usbh_event_notify_handler(USBH_EVENT_REMOVED, 1);
+        }
+    }
+
+    if (is & USB_IS_SOF) {
+    }
+
+    txis &= USB->TXIE;
+    /* Handle EP0 interrupt */
+    if (txis & USB_TXIE_EP0) {
+        txis &= ~USB_TXIE_EP0;
+        USB->TXIS = USB_TXIE_EP0; // clear isr flag
+        USBC_SelectActiveEp(0);
+        handle_ep0();
+    }
+
+    while (txis) {
+        chidx = __builtin_ctz(txis);
+        txis &= ~(1 << chidx);
+        USB->TXIS = (1 << chidx); // clear isr flag
+
+        chan = &g_usbhost.chan[chidx];
+
+        USBC_SelectActiveEp(chidx);
+
+        ep_status = HWREGB(USB_TXCSRLx_BASE);
+        usb_musb_ep_status_clear(chidx, ep_status | USB_HOST_OUT_STATUS);
+
+        if (ep_status & USB_HOST_OUT_STALL) {
+            chan->result = -EPERM;
+            usb_musb_ep_status_clear(chidx, USB_HOST_OUT_STALL);
+            usb_musb_chan_wakeup(&g_usbhost, chan);
+        } else if (ep_status & USB_HOST_OUT_ERROR) {
+            chan->result = -EIO;
+            usb_musb_ep_status_clear(chidx, USB_HOST_OUT_ERROR);
+            usb_musb_chan_wakeup(&g_usbhost, chan);
+        } else {
+            uint32_t size = chan->buflen;
+
+            if (size) {
+                if (size > chan->mps) {
+                    size = chan->mps;
+                }
+                usb_musb_write_packet(chidx, chan->buffer, size);
+                HWREGB(USB_TXCSRLx_BASE) = USB_TXCSRL1_TXRDY;
+                chan->buffer += size;
+                chan->buflen -= size;
+
+            } else {
+                chan->result = 0;
+                usb_musb_chan_wakeup(&g_usbhost, chan);
+            }
+        }
+    }
+
+    rxis &= USB->RXIE;
+    while (rxis) {
+        chidx = __builtin_ctz(rxis);
+        rxis &= ~(1 << chidx);
+        USB->RXIS = (1 << chidx); // clear isr flag
+        chan = &g_usbhost.chan[chidx];
+
+        USBC_SelectActiveEp(chidx);
+
+        ep_status = HWREGB(USB_RXCSRLx_BASE);
+
+        if (ep_status & USB_HOST_IN_STALL) {
+            usb_musb_ep_status_clear(chidx, USB_HOST_IN_STALL);
+            usb_musb_chan_wakeup(&g_usbhost, chan);
+        } else if (ep_status & USB_HOST_IN_ERROR) {
+            usb_musb_ep_status_clear(chidx, USB_HOST_IN_ERROR);
+            usb_musb_chan_wakeup(&g_usbhost, chan);
+        } else if (ep_status & USB_RXCSRL1_RXRDY) {
+            uint32_t size = chan->buflen;
+            if (size > chan->mps) {
+                size = chan->mps;
+            }
+            size = MIN(size, HWREG(USB_RXCOUNTx_BASE));
+
+            usb_musb_read_packet(chidx, chan->buffer, size);
+            HWREGB(USB_RXCSRLx_BASE) &= ~USB_RXCSRL1_RXRDY;
+            chan->buffer += size;
+            chan->buflen -= size;
+
+            if (chan->buflen == 0) {
+                chan->result = 0;
+                usb_musb_chan_wakeup(&g_usbhost, chan);
+            } else {
+                HWREGB(USB_RXCSRLx_BASE) = USB_RXCSRL1_REQPKT;
+            }
+        }
+    }
+    USBC_SelectActiveEp(old_ep_idx);
+}