/**
 * @file hv_drv_UsbMusbHost.c
 * @brief This is musb controller host driver.
 * @details 
 * @author HiView SoC Software Team
 * @version 0.0.1
 * @date 20230417
 * @copyright Copyright(c),2022-9, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author                         <th>Date             <th>Details
 * <tr><td>HiView SoC Software Team   <td>20230417       <td>Create and first Version.
 * </table>
 **/

#include "hv_drv_UsbCoreUtils.h"
#include "hv_drv_UsbCh9.h"
#include "hv_drv_UsbCoreCommonUsb.h"
#include "hv_drv_UsbCoreDevice.h"
#include "hv_drv_UsbCoreUsb.h"
#include "hv_drv_UsbCoreHcd.h"
#include "hv_drv_UsbMusbRegs.h"
#include "hv_drv_UsbMusbHost.h"
#include "hv_drv_UsbMusbCore.h"
#include "hv_drv_UsbMusbDma.h"
#include "hv_drv_UsbMusbHsDma.h"


#define RX_STALL_IGNORE_MAX     3
#define ERROR_IGNORE_MAX  3

// enable all fifo data via register to CPU for TX/RX
#define RAPID_FIFO 0

/*
 * Load an endpoint's FIFO
 */
void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) {
    int i = 0;
    u8 fifo_addr = hw_ep->epnum * 4 + 0x20;
#if FIFO_USE_WORD
    u32* buf = (u32*)src;
    for(i = 0; i < len; i += 4) {
        musb_writel(fifo_addr,*buf++);
    }  
    if (len % 4) {
        u8 *buf8 = (u8*)buf;
        u8 remain = len % 4;
        u32 val = 0;
        for (i = 0; i < remain; i ++) {
            val = (val << 8) | *buf8++;
        }
        musb_writel(fifo_addr, val);
    }
#else
    const u8 * buf = src;
#if RAPID_FIFO
    usb_write_fifo(fifo_addr, buf, len);
#else
    for(i=0; i<len; i++) {
        musb_writeb(fifo_addr, *buf++);
    }
#endif
#endif
}

/*
 * Unload an endpoint's FIFO
 */
void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) {
    int i=0;
#if FIFO_USE_WORD
    u32 *buf = (UINT32 *)dst;
#else
    u8 *buf = dst;
#endif
    u8 fifo_addr = hw_ep->epnum * 4 + 0x20;
    musb_ep_select(hw_ep->epnum);
#if FIFO_USE_WORD
    for(i=0; i < len / 4; i++) {
        *buf++ = musb_readl(fifo_addr);
    }
    if (len % 4) {
        u8 *buf8 = (u8*)buf;
        u8 remain = len % 4;
        u32 val = musb_readl(fifo_addr);
        for (i = 0; i < remain; i ++) {
            *buf8 ++ = val & 0xFF;
            val >>= 8;
        }
    }
#else
#if RAPID_FIFO
    usb_read_fifo(fifo_addr, buf, len);
#else
    for(i=0; i<len; i++) {
        *buf++ = musb_readb(fifo_addr);
    }
#endif
#endif
}

static void musb_ep_program(struct musb *musb, u8 epnum,
                            struct urb *urb, int is_out,
                            u8 *buf, u32 offset, u32 len);

/*
 * Clear TX fifo. Needed to avoid BABBLE errors.
 */
static void musb_h_tx_flush_fifo(struct musb_hw_ep *ep) {
    u16 csr, lastcsr = 0;
    u16 retries = 100;

    musb_ep_select(ep->epnum);
    csr = musb_readw(MUSB_TXCSR);
    while (csr & MUSB_TXCSR_FIFONOTEMPTY) {
        if (csr != lastcsr) {
            USB_LOGE("Host TX FIFONOTEMPTY csr: 0x%04x\n", csr);
        }
        lastcsr = csr;

        csr = MUSB_TXCSR_MODE | MUSB_TXCSR_FLUSHFIFO;
        musb_writew(MUSB_TXCSR, csr);
        csr = musb_readw(MUSB_TXCSR);

        if (retries-- < 1) {
            USB_LOGE("Could not flush host TX%d fifo,"
                    "csr: 0x%04x\n", ep->epnum, csr);
            return;
        }
        USB_MSleep(1);
    }
}

static void musb_h_ep0_flush_fifo(struct musb_hw_ep *ep) {
    u16 csr;
    u16 retries = 5;

    musb_writeb(MUSB_INDEX,0);
    /* scrub any data left in the fifo */
    do {
        csr = musb_readw(MUSB_CSR0);
        if (!(csr & (MUSB_CSR0_TXPKTRDY | MUSB_CSR0_RXPKTRDY)))
            break;

        musb_writew(MUSB_TXCSR, MUSB_CSR0_FLUSHFIFO);
        csr = musb_readw(MUSB_CSR0);
        USB_MSleep(1);
    } while (--retries);

    if (!retries) {
        USB_LOGE("could not flush host tx %d fifo,"
                "csr:%04x\n",ep->epnum, csr);
    }

    /* and reset for the next transfer */
    musb_writew(MUSB_CSR0, 0);
}

/*
 * Start transmit. Caller is responsible for locking shared resources.
 * musb must be locked.
 */
INLINE void musb_h_tx_start(struct musb_hw_ep *ep) {
    u16 txcsr=0;

    if (ep->epnum) {
        txcsr = musb_readw(MUSB_TXCSR);
        txcsr |= MUSB_TXCSR_TXPKTRDY | MUSB_TXCSR_H_WZC_BITS;
        musb_writew(MUSB_TXCSR, txcsr);
    } else {
        txcsr = MUSB_CSR0_H_SETUPPKT | MUSB_CSR0_TXPKTRDY;
        musb_writew(MUSB_CSR0, txcsr);
    }
}

INLINE void musb_h_tx_dma_start(struct musb_hw_ep *ep) {
    u16 txcsr;

    txcsr = musb_readw(MUSB_TXCSR);
    txcsr |= MUSB_TXCSR_DMAENAB | MUSB_TXCSR_H_WZC_BITS;
    txcsr |= MUSB_TXCSR_DMAMODE;
    musb_writew(MUSB_TXCSR, txcsr);
}

static void
musb_start_urb(struct musb *musb, int is_in, struct musb_xfer *xfer) {
    u16     frame;
    u32     len = 0;
    struct urb  *urb = current_urb(xfer);
    void        *buf = urb->transfer_buffer;
    u32     offset = 0;
    struct  musb_hw_ep   *hw_ep = xfer->hw_ep;
    int     epnum = hw_ep->epnum;

    xfer->offset = 0;
    xfer->segsize = 0;
    xfer->rx_stall_cnt = 0;
    xfer->error_cnt = 0;

    switch (xfer->type) {
    case USB_ENDPOINT_XFER_CONTROL:
        is_in = 0;
        musb->ep0_stage = MUSB_EP0_START;
        buf = urb->setup_packet;
        len = 8;
        break;
    case USB_ENDPOINT_XFER_ISOC:
        offset = urb->iso_frame_desc[0].offset;
        len = urb->iso_frame_desc[0].length;
        break;
    default:
        if (urb->actual_length != 0) {
            USB_LOGI("urb actual length is not ZERO!!\n");
        }

        buf = (u8*)urb->transfer_buffer + urb->actual_length;
        len = urb->transfer_buffer_length - urb->actual_length;
    }
    hw_ep->xfer = xfer;
    musb_ep_program(musb, epnum, urb, !is_in, (u8*)buf, offset, len);

    if (is_in) {
        return;
    }
    
    switch (xfer->type) {
    case USB_ENDPOINT_XFER_ISOC:
    case USB_ENDPOINT_XFER_INT:
        frame = musb_readw(MUSB_FRAME);
        xfer->frame = 0;
        //frame = frame;  /* cheat compiler */
    default:
        if (!hw_ep->tx_channel) {
            musb_h_tx_start(hw_ep);
        } else {
            musb_h_tx_dma_start(hw_ep);
        }
    }
}

static u16 musb_h_flush_rx_fifo(struct musb_hw_ep *hw_ep, u16 csr) {
    csr |= MUSB_RXCSR_FLUSHFIFO | MUSB_RXCSR_RXPKTRDY;
    csr &= ~(MUSB_RXCSR_H_REQPKT
             | MUSB_RXCSR_H_AUTOREQ
             | MUSB_RXCSR_AUTOCLEAR);

    /* write 2x to allow double buffering */
    musb_writew(MUSB_RXCSR, csr);
    musb_writew(MUSB_RXCSR, csr);

    /* flush writebuffer */
    return musb_readw(MUSB_RXCSR);
}

/*
 * PIO RX for a packet (or part of it).
 */
static boolean
musb_host_packet_rx(struct musb *musb, struct urb *urb, u8 epnum) {
    boolean done = false;
    boolean do_flush = false;
    u8  *buf;
    u16 rx_count;
    u16 csr;
    u32 length;
    void    *buffer = urb->transfer_buffer;
    struct musb_hw_ep   *hw_ep = musb->endpoints + epnum;
    struct musb_xfer    *xfer = hw_ep->xfer;

    musb_ep_select(epnum);
    rx_count = musb_readw(MUSB_RXCOUNT);

    /* non-isoch */
    buf = (u8*)buffer + xfer->offset;
    length = urb->transfer_buffer_length - xfer->offset;

    if (rx_count > length) {
        if (urb->status == -EINPROGRESS)
            urb->status = -EOVERFLOW;
        do_flush = true;
    } else {
        length = rx_count;
    }

    urb->actual_length += length;
    xfer->offset += length;

    /* see if we are done */
    done = (urb->actual_length == urb->transfer_buffer_length)
           || (rx_count < xfer->maxpacket)
           || (urb->status != -EINPROGRESS);

    if (done && (urb->status == -EINPROGRESS)
            && (urb->transfer_flags & URB_SHORT_NOT_OK)
            && (urb->actual_length < urb->transfer_buffer_length)) {
        urb->status = -EREMOTEIO;
    }

    musb_read_fifo(hw_ep, length, buf);
    csr = musb_readw(MUSB_RXCSR);
    csr |= MUSB_RXCSR_H_WZC_BITS;
    if (unlikely(do_flush)) {
        (void) musb_h_flush_rx_fifo(hw_ep, csr);
    } else {
        csr &= ~(MUSB_RXCSR_RXPKTRDY | MUSB_RXCSR_H_REQPKT);
        if (!done)
            csr |= MUSB_RXCSR_H_REQPKT;
        musb_writew(MUSB_RXCSR, csr);
    }

    return done;
}

/* we don't always need to reinit a given side of an endpoint...
 * when we do, use tx/rx reinit routine and then construct a new CSR
 * to address data toggle, NYET, and DMA or PIO.
 *
 * it's possible that driver bugs (especially for DMA) or aborting a
 * transfer might have left the endpoint busier than it should be.
 * the busy/not-empty tests are basically paranoia.
 */
static void
musb_rx_reinit(struct musb *musb, struct musb_hw_ep *ep) {
    struct musb_xfer    *xfer = ep->xfer;
    u16 csr;

    /* NOTE:  we know the "rx" fifo reinit never triggers for ep0.
    * That always uses tx_reinit since ep0 repurposes TX register
    * offsets; the initial SETUP packet is also a kind of OUT.
    */
    
    /* if programmed for Tx, put it in RX mode */
    if (ep->is_shared_fifo) {
        csr = musb_readw(MUSB_TXCSR);
        if (csr & MUSB_TXCSR_MODE) {
            musb_h_tx_flush_fifo(ep);
            csr = musb_readw(MUSB_TXCSR);
            musb_writew(MUSB_TXCSR, csr | MUSB_TXCSR_FRCDATATOG);
        }

        /*
        * Clear the MODE bit (and everything else) to enable Rx.
        * NOTE: we mustn't clear the DMAMODE bit before DMAENAB.
        */
        if (csr & MUSB_TXCSR_DMAMODE)
            musb_writew(MUSB_TXCSR, MUSB_TXCSR_DMAMODE);

        musb_writew(MUSB_TXCSR, 0);

        /* scrub all previous state, clearing toggle */
    } else {
        csr = musb_readw(MUSB_RXCSR);
        if (csr & MUSB_RXCSR_RXPKTRDY) {
            (void) musb_h_flush_rx_fifo(ep, csr);
        } else {
            musb_writew_lo(MUSB_RXCSR, 0);
        }

        csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_H_AUTOREQ | MUSB_RXCSR_DMAENAB);
        musb_writew_hi(MUSB_RXCSR, csr);
        musb_writew(MUSB_RXCSR, 0);
    }

    musb_writeb(MUSB_FADDR, xfer->addr_reg);
    if (musb->is_multipoint) {
        musb_write_rxfunaddr(ep->epnum, xfer->addr_reg);
    }

    /* protocol/endpoint, interval/NAKlimit, i/o size */
    musb_writeb(MUSB_RXTYPE, xfer->type_reg);
    musb_writeb(MUSB_RXINTERVAL, xfer->intv_reg);
    /* NOTE: bulk combining rewrites high bits of maxpacket */
    /* Set RXMAXP with the FIFO size of the endpoint
    * to disable double buffer mode.
    */
    if (musb->hwvers < MUSB_HWVERS_2000)
        musb_writew(MUSB_RXMAXP, ep->max_packet_sz_rx);
    else
        musb_writew(MUSB_RXMAXP, xfer->maxpacket | ((xfer->hb_mult - 1) << 11));

    ep->rx_reinit = 0;
}

static boolean musb_tx_dma_program(struct dma_controller *dma,
                                   struct musb_hw_ep *hw_ep,
                                   struct urb *urb, u32 offset, u32 length) {
    struct dma_channel *channel = hw_ep->tx_channel;
    struct musb_xfer    *xfer = hw_ep->xfer;
    u16 pkt_size = xfer->maxpacket;
    u8 mode;
    u16 csr;
    u32 dma_addr;

    musb_ep_select(hw_ep->epnum);
    if (length > channel->max_len)
        length = channel->max_len;

    csr = musb_readw(MUSB_TXCSR);
    if (length > pkt_size) {
        mode = 1;
        csr |= MUSB_TXCSR_DMAMODE | MUSB_TXCSR_DMAENAB;
        /* autoset shouldn't be set in high bandwidth */
        if (xfer->hb_mult == 1)
            csr |= MUSB_TXCSR_AUTOSET;
    } else {
        mode = 0;
        csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAMODE);
        csr |= MUSB_TXCSR_DMAENAB; /* against programmer's guide */
    }

    channel->desired_mode = mode;
    musb_writew(MUSB_TXCSR, csr);
    dma_addr = urb->transfer_dma + offset;
    if (!dma->channel_program(channel, pkt_size, mode, dma_addr, length)) {
        dma->channel_release(channel);
        hw_ep->tx_channel = NULL;

        csr = musb_readw(MUSB_TXCSR);
        csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
        musb_writew(MUSB_TXCSR, csr | MUSB_TXCSR_H_WZC_BITS);
        return false;
    }
    return true;
}

static void musb_ep_program(struct musb *musb, u8 epnum,
                            struct urb *urb, int is_out,
                            u8 *buf, u32 offset, u32 len) {
    struct dma_controller *dma_controller;
    struct dma_channel  *dma_channel;
    struct musb_hw_ep   *hw_ep = musb->endpoints + epnum;
    struct musb_xfer    *xfer = hw_ep->xfer;

    u16     packet_sz = xfer->maxpacket;
    u8      use_dma = 1;
    u16     csr;

    musb_ep_select(epnum);

    /* candidate for DMA? */
    dma_controller = musb->dma_controller;
    xfer->segsize = USB_MIN((u32) packet_sz, len);
    if (len <= packet_sz) {
        use_dma = 0;
        if (is_out) {
            csr = musb_readw(MUSB_TXCSR);
            csr &= ~MUSB_TXCSR_DMAENAB;
            musb_writew(MUSB_TXCSR, csr);
        } else {
            csr = musb_readw(MUSB_RXCSR);
            csr &= ~MUSB_RXCSR_DMAENAB;
            musb_writew(MUSB_RXCSR, csr);
        }

        if (dma_controller) {
            if (is_out && hw_ep->tx_channel) {
                dma_controller->channel_release(hw_ep->tx_channel);
                hw_ep->tx_channel = NULL;
            }

            if (!is_out && hw_ep->rx_channel) {
                dma_controller->channel_release(hw_ep->rx_channel);
                hw_ep->rx_channel = NULL;
            }
            dma_controller = NULL;
        }
    }

    if (use_dma && dma_controller && epnum &&
            ((is_out && musb->is_tx_dma_used) ||
             (!is_out && musb->is_rx_dma_used))) {
        dma_channel = is_out ? hw_ep->tx_channel : hw_ep->rx_channel;

        if (!dma_channel) {
            dma_channel = dma_controller->channel_alloc(dma_controller, hw_ep, is_out);

            if (is_out)
                hw_ep->tx_channel = dma_channel;
            else
                hw_ep->rx_channel = dma_channel;
        }
    } else {
        dma_channel = NULL;
    }

    /* OUT/transmit/EP0 or IN/receive? */
    if (is_out) {
        u16 int_txe;
        u16 load_count;

        csr = musb_readw(MUSB_TXCSR);

        /* disable interrupt in case we flush */
        int_txe = musb_readw(MUSB_INTRTXE);
        musb_writew(MUSB_INTRTXE, int_txe & ~(1 << epnum));

        if (epnum) {
            if (!hw_ep->tx_double_buffered) {
                musb_h_tx_flush_fifo(hw_ep);
            }

            csr &= ~(MUSB_TXCSR_H_NAKTIMEOUT
                     | MUSB_TXCSR_AUTOSET
                     | MUSB_TXCSR_DMAENAB
                     | MUSB_TXCSR_FRCDATATOG
                     | MUSB_TXCSR_H_RXSTALL
                     | MUSB_TXCSR_H_ERROR
                     | MUSB_TXCSR_TXPKTRDY);
            csr |= MUSB_TXCSR_MODE;

            if (!hw_ep->tx_double_buffered) {
                u32 pkt_cnt;
                if (!usb_gettoggle(urb->dev, xfer->epnum, 1)) {
                    csr |= MUSB_TXCSR_CLRDATATOG;
                }

                pkt_cnt = len / packet_sz;
                pkt_cnt += (len % packet_sz) ? 1 : 0;
                if (pkt_cnt % 2) {
                    usb_dotoggle(urb->dev, xfer->epnum, 1);
                }
            }

            musb_writew(MUSB_TXCSR, csr);

            csr &= ~MUSB_TXCSR_DMAMODE;
            musb_writew(MUSB_TXCSR, csr);
            csr = musb_readw(MUSB_TXCSR);
        } else {
            musb_h_ep0_flush_fifo(hw_ep);
        }

        musb_writeb(MUSB_FADDR, xfer->addr_reg);

        if (musb->is_multipoint) {
            musb_write_txfunaddr(epnum, xfer->addr_reg);
        }

        if (epnum) {
            musb_writeb(MUSB_TXTYPE, xfer->type_reg);
            musb_writew(MUSB_TXMAXP, packet_sz);
            musb_writeb(MUSB_TXINTERVAL, xfer->intv_reg);
        } else {
            musb_writeb(MUSB_NAKLIMIT0, xfer->intv_reg);
            if (musb->is_multipoint)
                musb_writeb(MUSB_TYPE0,    xfer->type_reg);
        }
 
        load_count = xfer->segsize;

        if (dma_channel && musb_tx_dma_program(dma_controller,
                                               hw_ep, urb, offset, len)) {
            load_count = 0;
        }                               

        if (load_count) {
            /* PIO to load FIFO */
            musb_write_fifo(hw_ep, load_count, buf);
            if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) {
                /* not for data phase */
                csr |= MUSB_TXCSR_FRCDATATOG;
                musb_writew_hi(MUSB_TXCSR, csr);
            }
        }

        musb_writew(MUSB_INTRTXE, int_txe);
        /* IN/receive */
    } else {
        u8 dma_ok = 0;
        u32     rx_req_count = 0;
        csr = musb_readw(MUSB_RXCSR);

        if (hw_ep->rx_reinit) {
            musb_rx_reinit(musb, hw_ep);
            if (!usb_gettoggle(urb->dev, xfer->epnum, 0)) {
                csr = 0;
            } else if (csr != 0) {
            }
            rx_req_count = len / xfer->maxpacket;
            if (len % xfer->maxpacket)
                rx_req_count += 1;

            if (rx_req_count % 2) {
                usb_dotoggle(urb->dev, xfer->epnum, 0);
            }
        } else {
            csr = musb_readw(MUSB_RXCSR);

            if (csr & (MUSB_RXCSR_RXPKTRDY
                       | MUSB_RXCSR_DMAENAB
                       | MUSB_RXCSR_H_REQPKT)) {
                USB_LOGE("broken !rx_reinit, ep%d csr %04x\n",
                        hw_ep->epnum, csr);
            }
        }

        if (use_dma && dma_channel) {
            u32 dma_addr;
            MUSB_REQ_PKT_COUNT(epnum, rx_req_count);

            /* candidate for DMA */
            dma_channel->actual_len = 0L;

            musb_writew(MUSB_RXCSR, csr);
            csr = musb_readw(MUSB_RXCSR);
            if (csr != 0)
                USB_LOGE("invalid rx csr at start %04x, will be cleared\n", csr);

            dma_channel->desired_mode = 1;

            /* unless caller treats short rx transfers as
             * errors, we dare not queue multiple transfers.
             */
            dma_addr = urb->transfer_dma + offset;
            dma_ok = dma_controller->channel_program(dma_channel,
                     packet_sz, dma_channel->desired_mode,
                     dma_addr, len);

            if (!dma_ok) {
                dma_controller->channel_release(dma_channel);
                hw_ep->rx_channel = NULL;
                dma_channel = NULL;
            } else {
                csr |= MUSB_RXCSR_DMAENAB;
            }
        }
        csr |= MUSB_RXCSR_H_REQPKT;
        if (dma_ok) {
            csr |= MUSB_RXCSR_DMAMODE
                   | MUSB_RXCSR_AUTOCLEAR
                   | MUSB_RXCSR_H_AUTOREQ;
        }

        musb_writew(MUSB_RXCSR, csr);

        csr = musb_readw(MUSB_RXCSR);
    }
}

/*
 * Service the default endpoint (ep0) as host.
 * Return true until it's time to start the status stage.
 */
static boolean musb_h_ep0_continue(struct musb *musb, u16 len, struct urb *urb) {
    boolean        more = false;
    u8          *fifo_dest = NULL;
    u16         fifo_count = 0;
    struct musb_hw_ep   *hw_ep = musb->control_ep;
    struct musb_xfer    *xfer = hw_ep->xfer;
    struct usb_ctrlrequest  *request;

    switch (musb->ep0_stage) {
    case MUSB_EP0_IN:
        fifo_dest = (u8*)urb->transfer_buffer + urb->actual_length;
        fifo_count = USB_MIN(len, urb->transfer_buffer_length -urb->actual_length);

        if(fifo_count < len)
            urb->status = -EOVERFLOW;

        musb_read_fifo(hw_ep, fifo_count, fifo_dest);

        urb->actual_length += fifo_count;
        if(len < xfer->maxpacket) {
        /* always terminate on short read; it's
            * rarely reported as an error.
            */
        } else if(urb->actual_length < urb->transfer_buffer_length)
            more = true;
        break;
    case MUSB_EP0_START:
        request = (struct usb_ctrlrequest *) urb->setup_packet;

        if (!request->wLength) {
            break;
        } else if (request->bRequestType & USB_DIR_IN) {
            musb->ep0_stage = MUSB_EP0_IN;
            more = true;
            break;
        } else {
            musb->ep0_stage = MUSB_EP0_OUT;
            more = true;
        }
    /* FALLTHROUGH */
    case MUSB_EP0_OUT:
        fifo_count = USB_MIN(xfer->maxpacket, urb->transfer_buffer_length - urb->actual_length);
        if (fifo_count) {
            fifo_dest = (u8*)urb->transfer_buffer + urb->actual_length;

            musb_write_fifo(hw_ep, fifo_count, fifo_dest);

            urb->actual_length += fifo_count;
            more = true;
        }
        break;
    default:
        break;
    }

    return more;
}

static void musb_giveback(struct musb *musb,
                          struct urb *urb, int status) {
    HANDLE sc;
    usb_hcd_unlink_urb_from_ep(musb->hcd, urb);
    sc = spin_lock_irq();
    usb_hcd_giveback_urb(musb->hcd, urb, status);
    spin_unlock_irq(sc);
}

static void musb_advance_schedule(struct musb *musb, struct urb *urb,
                                  struct musb_hw_ep *hw_ep, int is_in) {
    struct musb_xfer    *xfer = hw_ep->xfer;
    int ready;
    int status;
    struct dma_controller *dma;
    if (!xfer) {
        return;
    }
    ready = xfer->is_ready;
    status = urb->status == -EINPROGRESS ? 0 : urb->status;

    xfer->is_ready = 0;
    musb_giveback(musb, urb, status);
    xfer->is_ready = ready;

    dma = musb->dma_controller;

    if (is_in) {
        hw_ep->rx_reinit = 1;
        if (hw_ep->rx_channel) {
            dma->channel_release(hw_ep->rx_channel);
            hw_ep->rx_channel = NULL;
        }
    } else {
        hw_ep->tx_reinit = 1;
        if (hw_ep->tx_channel) {
            dma->channel_release(hw_ep->tx_channel);
            hw_ep->tx_channel = NULL;
        }
    }

    hw_ep->xfer = NULL;
    xfer->hep->hcpriv = NULL;
    if(xfer != NULL)
    {
        USB_FREE(xfer);
        xfer = NULL;
    }
}

void musb_h_ep0_irq(struct musb *musb) {
    struct urb *urb;
    u16 csr, len = 0;
    int status = 0;
    struct musb_hw_ep   *hw_ep = musb->control_ep;
    struct musb_xfer    *xfer = hw_ep->xfer;
    boolean complete = false;

    /* ep0 only has one queue, "in" */
    urb = current_urb(xfer);

    musb_ep_select(0);
    csr = musb_readw(MUSB_CSR0);
    if (csr & MUSB_CSR0_RXPKTRDY) {
        len = musb_readb(MUSB_COUNT0);
    }

    /* if we just did status stage, we are done */
    if (MUSB_EP0_STATUS == musb->ep0_stage) {
        complete = true;
    }

    if (csr & MUSB_CSR0_H_ERR_BITS) {
        if (csr & MUSB_CSR0_H_RXSTALL) {
            status = -EPIPE;
        } else if (csr & MUSB_CSR0_H_ERROR) {
            status = -EPROTO;
        } else if (csr & MUSB_CSR0_H_NAKTIMEOUT) {
            musb_writew_lo(MUSB_CSR0, (csr & ~MUSB_CSR0_H_ERR_BITS));
            if (csr & MUSB_CSR0_H_REQPKT) {
                return;
            } else {
                musb_writew(MUSB_CSR0, 0);
            }
        }
    }

    if (status) {
        if (urb)
            urb->status = status;

        complete = true;

        /* use the proper sequence to abort the transfer */
        if (csr & MUSB_CSR0_H_REQPKT) {
            csr &= ~MUSB_CSR0_H_REQPKT;
            musb_writew_lo(MUSB_CSR0, csr);
            csr &= ~MUSB_CSR0_H_ERR_BITS;
            musb_writew_lo(MUSB_CSR0, csr);
        } else {
            musb_h_ep0_flush_fifo(hw_ep);
        }

        musb_writeb(MUSB_NAKLIMIT0, 0);
        /* clear it */
        musb_writew(MUSB_CSR0, 0);
    }

    if (unlikely(!urb)) {
        musb_h_ep0_flush_fifo(hw_ep);
        return;
    }

    if (!complete) {
        /* call common logic and prepare response */
        if (musb_h_ep0_continue(musb, len, urb)) {
            /* more packets required */
            csr = (MUSB_EP0_IN == musb->ep0_stage)
                  ?  MUSB_CSR0_H_REQPKT : MUSB_CSR0_TXPKTRDY;
        } else {
            /* data transfer complete; perform status phase */
            if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length)
                csr = MUSB_CSR0_H_STATUSPKT |MUSB_CSR0_H_REQPKT;
            else
                csr = MUSB_CSR0_H_STATUSPKT |MUSB_CSR0_TXPKTRDY;

            /* disable ping token in status phase. */
            csr |= MUSB_CSR0_H_DIS_PING;

            /* flag status stage */
            musb->ep0_stage = MUSB_EP0_STATUS;
        }
        musb_writew(MUSB_CSR0, csr);
    } else
        musb->ep0_stage = MUSB_EP0_IDLE;

    /* call completion handler if done */
    if (complete) {
        musb_advance_schedule(musb, urb, hw_ep, 1);
    }
}

/* Service a Tx-Available or dma completion irq for the endpoint */
void musb_host_tx(struct musb *musb, u8 epnum) {
    boolean    done = false;
    int     status = 0;
    struct musb_hw_ep   *hw_ep = musb->endpoints + epnum;
    struct musb_xfer    *xfer = hw_ep->xfer;
    struct urb  *urb = current_urb(xfer);
    struct dma_channel  *dma = hw_ep->tx_channel;
    u16 tx_csr;

    musb_ep_select(epnum);
    tx_csr = musb_readw(MUSB_TXCSR);
    if (!(urb && xfer->is_ready)) {
        return;
    }

    if (tx_csr & MUSB_TXCSR_H_RXSTALL) {
        xfer->rx_stall_cnt += 1;
        if (xfer->rx_stall_cnt >= RX_STALL_IGNORE_MAX) {
            status = -EPIPE;
            goto done;
        }
    } else {    /* fraudulent stall */
        xfer->rx_stall_cnt = 0;
    }

    if (tx_csr & MUSB_TXCSR_H_ERROR) {
        xfer->error_cnt += 1;
        if (xfer->error_cnt >= ERROR_IGNORE_MAX) {
            xfer->error_cnt = 0;

            status = -ETIMEDOUT;
            goto done;
        }
    } else {
        xfer->error_cnt = 0;
    }

    if (tx_csr & MUSB_TXCSR_H_ERR_BITS) {
        u8  with_dma = tx_csr & MUSB_TXCSR_DMAENAB ? 1 : 0;
        tx_csr &= ~MUSB_TXCSR_H_ERR_BITS;
        musb_writew_lo(MUSB_TXCSR, tx_csr);
        if (with_dma) {
            musb_h_tx_dma_start(hw_ep);
            return;
        } else if (tx_csr & MUSB_TXCSR_FIFONOTEMPTY) {
            musb_h_tx_start(hw_ep);
            return;
        }
    }

    if (dma && dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
        USB_ASSERT(0);      /* pray never happen */
    }

    musb_writew(MUSB_TXCSR, 0);
    xfer->offset += xfer->segsize;
    if (xfer->offset == urb->transfer_buffer_length) {
        done = true;
    } else if (xfer->segsize < xfer->maxpacket) {
        done = true;
    } else {    /* multi-packet transfer without dma. */
        USB_ASSERT(0);
    }

done:
    if (status) {
        if (dma && dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
            dma->status = MUSB_DMA_STATUS_CORE_ABORT;
            musb->dma_controller->channel_abort(dma);
            USB_ASSERT(0);      /* TODO */
        }

        musb_h_tx_flush_fifo(hw_ep);
        tx_csr &= ~(MUSB_TXCSR_AUTOSET
                    | MUSB_TXCSR_DMAENAB
                    | MUSB_TXCSR_H_ERR_BITS);

        musb_ep_select(epnum);
        musb_writew(MUSB_TXCSR, tx_csr);
        /* revisit may need to clear FLUSHFIFO */
        musb_writew(MUSB_TXCSR, tx_csr);
        musb_writeb(MUSB_TXINTERVAL, 0);
        done = true;
    }

    xfer->rx_stall_cnt = 0;
    xfer->error_cnt = 0;
    if (done) {
        urb->actual_length = xfer->offset;
        if (urb->status == -EINPROGRESS) {
            urb->status = status;
        }
        musb_advance_schedule(musb, urb, hw_ep, 0);
    }else{
        musb_write_fifo(hw_ep, xfer->segsize, urb->transfer_buffer + xfer->offset);
        musb_ep_select( epnum);
        musb_writew(MUSB_TXCSR,
                MUSB_TXCSR_H_WZC_BITS | MUSB_TXCSR_TXPKTRDY);
    }
}

void musb_host_tx_dma(struct musb *musb, u8 epnum) {
    struct musb_hw_ep   *hw_ep = musb->endpoints + epnum;
    struct musb_xfer    *xfer = hw_ep->xfer;
    struct urb  *urb = current_urb(xfer);
    struct dma_channel  *dma = hw_ep->tx_channel;
    u16 tx_csr;

    musb_ep_select(epnum);
    tx_csr = musb_readw(MUSB_TXCSR);
    if (!(urb && dma && xfer->is_ready)) {
        return;
    }

    if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
        USB_ASSERT(0);
    }

    if (tx_csr & (MUSB_TXCSR_FIFONOTEMPTY | MUSB_TXCSR_TXPKTRDY)) {
        if (xfer->segsize < xfer->maxpacket) {
            return;
        } else {
            USB_ASSERT(0);
        }
    }

    xfer->is_ready = 0;
    tx_csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
    musb_writew_hi(MUSB_TXCSR, tx_csr);

    tx_csr = musb_readw(MUSB_TXCSR);
    musb_writew_hi(MUSB_TXCSR, tx_csr & ~(MUSB_TXCSR_DMAMODE));
    musb_writew(MUSB_TXCSR, 0);

    urb->actual_length = dma->actual_len;
    urb->status = 0;

    /* TODO */
    musb_advance_schedule(musb, urb, hw_ep, 0);
}

/*
 * Service an RX interrupt for the given IN endpoint; docs cover bulk, iso,
 * and high-bandwidth IN transfer cases.
 */
void musb_host_rx(struct musb *musb, u8 epnum) {
    boolean done = false;
    u16 rx_csr;
    u16 rxcount;
    int status = 0;
    size_t  xfer_len = 0;
    struct musb_hw_ep *hw_ep = musb->endpoints + epnum;
    struct dma_channel  *dma = NULL;
    struct musb_xfer    *xfer = hw_ep->xfer;
    struct urb *urb;
    int with_dma;

    musb_ep_select(epnum);
    urb = current_urb(xfer);
    dma = is_dma_capable() ? hw_ep->rx_channel : NULL;

    rx_csr = musb_readw(MUSB_RXCSR);
    rxcount = musb_readw(MUSB_RXCOUNT);

    if (!(urb && xfer->is_ready)) {
        return;
    }

    if (rx_csr & MUSB_RXCSR_H_RXSTALL) {
        xfer->rx_stall_cnt += 1;
        /* indicate really stall */
        if (xfer->rx_stall_cnt >= RX_STALL_IGNORE_MAX) {
            status = -EPIPE;
            goto done;
        }
    } else {
        xfer->rx_stall_cnt = 0;
    }

    if (rx_csr & MUSB_RXCSR_H_ERROR) {
        xfer->error_cnt += 1;
        /* indicate really proto error */
        if (xfer->error_cnt >= ERROR_IGNORE_MAX) {
            musb_writeb(MUSB_RXINTERVAL, 0);
            xfer->error_cnt = 0;

            status = -EPROTO;
            goto done;
        }
    } else {
        xfer->error_cnt = 0;
    }

    with_dma = rx_csr & MUSB_RXCSR_DMAENAB ? 1 : 0;
    if (rx_csr & MUSB_RXCSR_H_ERR_BITS) {
        if (xfer->type == USB_ENDPOINT_XFER_INT) {
            musb_writew(MUSB_RXINTERVAL, 0);
            if (rx_csr & MUSB_RXCSR_H_RXSTALL) {
                status = -EPIPE;
            } else if (rx_csr & MUSB_RXCSR_H_ERROR) {
                status = -EPROTO;
            } else {
                status = -EBUSY;
            }
            goto done;
        }

        /* Clear error bits */
        rx_csr &= ~MUSB_RXCSR_H_ERR_BITS;
        musb_writew_lo(MUSB_RXCSR, rx_csr);

        rx_csr = musb_readw(MUSB_RXCSR);
        if (rx_csr & MUSB_RXCSR_RXPKTRDY) {
            USB_ASSERT(0); /* TODO */
            goto finish;
        }

        /* Set REQPKT */
        if (with_dma) {
            if (rx_csr & MUSB_RXCSR_RXPKTRDY)
                musb_writew_lo(MUSB_RXCSR, rx_csr | MUSB_RXCSR_FLUSHFIFO);
            if (unlikely(hw_ep->tx_double_buffered))
                musb_writew_lo(MUSB_RXCSR, rx_csr | MUSB_RXCSR_FLUSHFIFO);

            rx_csr |= (MUSB_RXCSR_H_AUTOREQ | MUSB_RXCSR_AUTOCLEAR);
        }
        rx_csr |= MUSB_RXCSR_H_REQPKT;
        musb_writew(MUSB_RXCSR, rx_csr);

        rx_csr = musb_readw(MUSB_RXCSR);
        return;
    }

    if (dma && with_dma) {
        xfer_len = dma->actual_len;
        if (rx_csr & MUSB_RXCSR_H_REQPKT) {
            USB_ASSERT(0);
        }

        if (rx_csr & MUSB_RXCSR_RXPKTRDY) {
            struct dma_controller   *c;

            rxcount = musb_readw(MUSB_RXCOUNT);
            c = musb->dma_controller;
            xfer_len = rxcount;

            c->channel_release(dma);
            hw_ep->rx_channel = NULL;
            dma = NULL;

            MUSB_REQ_PKT_COUNT(epnum, 0);
            rx_csr = musb_readw(MUSB_RXCSR);
            rx_csr &= ~(MUSB_RXCSR_DMAENAB
                        | MUSB_RXCSR_H_AUTOREQ
                        | MUSB_RXCSR_AUTOCLEAR);
            musb_writew(MUSB_RXCSR, rx_csr);

            done = true;
            goto done;
        }
    }

finish:

    /* we are expecting IN packets */
    done = musb_host_packet_rx(musb, urb, epnum);

done:
    if (status) {
        if (dma && dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
            dma->status = MUSB_DMA_STATUS_CORE_ABORT;
            musb->dma_controller->channel_abort(dma);
            xfer_len = dma->actual_len;
        }
        musb_h_flush_rx_fifo(hw_ep, MUSB_RXCSR_CLRDATATOG);
        musb_writeb(MUSB_RXINTERVAL, 0);
        done = true;
    }

    xfer->rx_stall_cnt = 0;
    xfer->error_cnt = 0;
    urb->actual_length += xfer_len;
    xfer->offset += xfer_len;
    if (done) {
        if (urb->status == -EINPROGRESS) {
            urb->status = status;
        }
        musb_advance_schedule(musb, urb, hw_ep, 1);
    }
}

void musb_host_rx_dma(struct musb *musb, u8 epnum) {
    struct musb_hw_ep *hw_ep = musb->endpoints + epnum;
    struct musb_xfer    *xfer = hw_ep->xfer;
    struct urb *urb = current_urb(xfer);
    struct dma_channel *dma = is_dma_capable() ? hw_ep->rx_channel : NULL;
    u16 rx_csr;
    u32 pkt_cnt;

    musb_ep_select(epnum);
    rx_csr = musb_readw(MUSB_RXCSR);
    pkt_cnt = musb_readl(0x300 + 4*epnum);
    //pkt_cnt = pkt_cnt;  /* cheat compiler */

    if (!(urb && dma && xfer->is_ready)) {
        return;
    }

    rx_csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_H_AUTOREQ | MUSB_RXCSR_DMAENAB);
    musb_writew_hi(MUSB_RXCSR, rx_csr);

    rx_csr &= ~MUSB_RXCSR_DMAMODE;
    musb_writew_hi(MUSB_RXCSR, rx_csr);

    if (rx_csr & MUSB_RXCSR_RXPKTRDY) {
        musb_writew_lo(MUSB_RXCSR, MUSB_RXCSR_FLUSHFIFO);
    }
    musb_writew_lo(MUSB_RXCSR, 0);

    xfer->offset += dma->actual_len;
    urb->actual_length = xfer->offset;
    urb->status = 0;
    musb_advance_schedule(musb, urb, hw_ep, 1);
}

/*
 * abort a transfer that's at the head of a hardware queue.
 * called with controller locked, irqs blocked
 * that hardware queue advances to the next transfer, unless prevented
 */
static int musb_cleanup_urb(struct urb *urb, struct musb_xfer *xfer) {
    struct musb_hw_ep   *ep = xfer->hw_ep;
    struct musb     *musb = ep->musb;
    u32 epnum = ep->epnum;
    int is_in = usb_pipein(urb->pipe);
    int status = 0;
    u16 csr;

    musb_ep_select(epnum);

    if (epnum && is_in) {
        u16 int_rxe = musb_readw(MUSB_INTRRXE);
        musb_writew(MUSB_INTRRXE, int_rxe & ~(1 << epnum));

        musb_writew(MUSB_RXCSR, 0);
        csr = musb_h_flush_rx_fifo(ep, 0);

        musb_writew(MUSB_INTRRXE, int_rxe);
    } else if (epnum) {
        u16 int_txe = musb_readw(MUSB_INTRTXE);
        musb_writew(MUSB_INTRTXE, int_txe & ~(1 << epnum));

        musb_h_tx_flush_fifo(ep);
        csr = musb_readw(MUSB_TXCSR);
        csr &= ~(MUSB_TXCSR_AUTOSET
                 | MUSB_TXCSR_DMAENAB
                 | MUSB_TXCSR_H_RXSTALL
                 | MUSB_TXCSR_H_NAKTIMEOUT
                 | MUSB_TXCSR_H_ERROR
                 | MUSB_TXCSR_TXPKTRDY);
        musb_writew(MUSB_TXCSR, csr);
        /* REVISIT may need to clear FLUSHFIFO ... */
        musb_writew(MUSB_TXCSR, csr);
        /* flush cpu writebuffer */
        csr = musb_readw(MUSB_TXCSR);
        musb_writew(MUSB_TXCSR, 0);

        musb_writew(MUSB_INTRTXE, int_txe);
    } else {
        musb_h_ep0_flush_fifo(ep);
    }

    if (is_dma_capable()) {
        struct dma_channel *dma;

        dma = is_in ? ep->rx_channel : ep->tx_channel;
        if (dma) {
            status = ep->musb->dma_controller->channel_abort(dma);
            urb->actual_length += dma->actual_len;
        }
    }

    musb_advance_schedule(musb, urb, ep, is_in);

    return status;
}

void musb_host_free(struct musb *musb) {
    usb_destroy_hcd(musb->hcd);

    musb->hcd->hcd_priv_size = 0;
    musb->hcd->hcd_priv = NULL;
    musb->hcd->uses_new_polling = 0;

    musb->hcd = NULL;
}

int musb_host_setup(struct musb *musb) {
    int ret = 0;
    struct usb_hcd *hcd = musb->hcd;
    hcd->power_budget = 2 * 250;
    hcd->product_desc = "musb root hub";
    ret = usb_add_hcd(hcd);

    return ret;
}

void musb_host_cleanup(struct musb *musb) {
    usb_remove_hcd(musb->hcd);
}

static int musb_h_start(struct usb_hcd *hcd) {
    struct musb *musb = hcd_to_musb(hcd);

    hcd->state = HC_STATE_RUNNING;
    musb->port1_status = 0;

    return 0;
}

static void musb_h_stop(struct usb_hcd *hcd) {
    struct musb *musb = hcd_to_musb(hcd);

    musb_stop(musb);
    hcd->state = HC_STATE_HALT;
}

static int musb_h_get_last_frame_number(struct usb_hcd *hcd) {
    return musb_readw(MUSB_FRAME);
}

static int musb_schedule(
    struct musb     *musb,
    struct musb_xfer *xfer,
    int     is_in) {
    int epnum;
    struct musb_hw_ep   *hw_ep = NULL;
    int diff, best_end, alter;
    struct urb *urb = current_urb(xfer);;

    if (xfer->type == USB_ENDPOINT_XFER_CONTROL) {
        hw_ep = musb->control_ep;
        goto success;
    }

    if (urb->preferable) {
        hw_ep = musb->endpoints + urb->preferable;
        if (!hw_ep->xfer) {
            goto success;
        } else {
            hw_ep->bundled = 0;
            urb->preferable = 0;
        }
    }

    alter = -1;
    best_end = -1;
    for (epnum = 1, hw_ep = musb->endpoints + 1;
            epnum < musb->nr_endpoints;
            epnum++, hw_ep++) {
        if (hw_ep->xfer != NULL) {
            continue;   /* busy */
        }
        if (hw_ep == musb->bulk_ep) {
            continue;
        }
        diff = is_in ?
               hw_ep->max_packet_sz_rx : hw_ep->max_packet_sz_tx;
        if (diff < xfer->maxpacket) {
            continue;
        }
        if (!hw_ep->bundled)
            best_end = epnum;
        alter = epnum;
    }

    /* use bulk reserved ep1 if no other ep is free. */
    if (xfer->type == USB_ENDPOINT_XFER_BULK && alter < 0) {
        hw_ep = musb->bulk_ep;  /* the worst choice */
        if (xfer->dev) {
            xfer->intv_reg = (USB_SPEED_HIGH == xfer->dev->speed) ? 8 : 4;
        }

        goto success;
    } else if (alter < 0) {
        return -ENOSPC;
    }

    if (best_end > 0) {
        urb->preferable = best_end;
        hw_ep = musb->endpoints + best_end;
        hw_ep->bundled = 1;
    } else {
        urb->preferable = 0;
        best_end = alter;
    }


    hw_ep = musb->endpoints + best_end;
success:
    xfer->hw_ep = hw_ep;
    xfer->hep->hcpriv = xfer;
    musb_start_urb(musb, is_in, xfer);
    return 0;
}

static int musb_urb_enqueue(
    struct usb_hcd  *hcd,
    struct urb      *urb) {
    struct musb *musb = hcd_to_musb(hcd);
    struct usb_host_endpoint *hep = urb->ep;
    struct musb_xfer *xfer;
    struct usb_endpoint_descriptor *epd = &hep->desc;
    int     ret;
    unsigned int type_reg, interval;
    HANDLE sc;

    if (!is_host_active(musb) || !musb->is_active) {
        return -ENODEV;
    }

    sc = spin_lock_irq();
    ret = usb_hcd_link_urb_to_ep(hcd, urb);
    xfer = ret ? NULL : (struct musb_xfer*)hep->hcpriv;
    if (xfer) {
        urb->hcpriv = xfer;
        /* should NEVER happen without a transfer queue. */
        USB_ASSERT(!xfer);
    }
    spin_unlock_irq(sc);

    if (xfer || ret) {
        return ret;
    }

    xfer = USB_MALLOC(sizeof (*xfer));
    if (!xfer) {
        sc = spin_lock_irq();
        usb_hcd_unlink_urb_from_ep(hcd, urb);
        spin_unlock_irq(sc);
        return -ENOMEM;
    }

    HV_MEMSET((void*)xfer, 0, sizeof(*xfer));

    xfer->hep = hep;
    xfer->dev = urb->dev;
    xfer->is_ready = 1;

    /* ctrl: 64 ``` bulk-high: 512 ``` bulk-full: 64 */
    xfer->maxpacket = usb_endpoint_maxp(epd);
    xfer->type = usb_endpoint_type(epd);

    xfer->hb_mult = 1 + ((xfer->maxpacket >> 11) & 0x03);
    if (xfer->hb_mult > 1) {
        int ok = (xfer->type == USB_ENDPOINT_XFER_ISOC);

        if (ok) {
            USB_ASSERT(0);
        }

        if (!ok) {
            ret = -EMSGSIZE;
            goto done;
        }
        xfer->maxpacket &= 0x7ff;
    }

    xfer->epnum = usb_endpoint_num(epd);
    xfer->addr_reg = ((u8) usb_pipedevice(urb->pipe)) & 0xff;
    type_reg = (xfer->type << 4) | xfer->epnum;

    switch (urb->dev->speed) {
    case USB_SPEED_LOW:
        type_reg |= 0xc0;
        break;
    case USB_SPEED_FULL:
        type_reg |= 0x80;
        break;
    default:
        type_reg |= 0x40;
    }
    xfer->type_reg = type_reg & 0xff;

    switch (xfer->type) {
    case USB_ENDPOINT_XFER_INT:
        if (urb->dev->speed <= USB_SPEED_FULL) {
            interval = USB_MAX(epd->bInterval, 1);
            break;
        }
    case USB_ENDPOINT_XFER_ISOC:
        interval = USB_MIN(epd->bInterval, 16);
        break;
    default:
        interval = 16;
    }
    xfer->intv_reg = interval;

    if (hep->hcpriv || !current_urb(xfer)) {
        if(xfer != NULL)
        {
            USB_FREE(xfer);
            xfer = NULL;
        }
        ret = 0;
    } else {
        ret = musb_schedule(musb, xfer,
                            epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK);
    }

    if (ret == 0) {
        sc = spin_lock_irq();
        urb->hcpriv = xfer;
        spin_unlock_irq(sc);
    }

done:
    if (ret != 0) {
        sc = spin_lock_irq();
        usb_hcd_unlink_urb_from_ep(hcd, urb);
        spin_unlock_irq(sc);
        if(xfer != NULL)
        {
            USB_FREE(xfer);
            xfer = NULL;
        }
    }
    return ret;
}

static int musb_urb_dequeue(
    struct usb_hcd  *hcd,
    struct urb      *urb,
    int status) {
    struct musb     *musb = hcd_to_musb(hcd);
    struct musb_xfer    *xfer;
    int ret;

    HANDLE sc = spin_lock_irq();
    ret = usb_hcd_check_unlink_urb(hcd, urb, status);
    if (ret) {
        goto done;
    }

    xfer = urb->hcpriv;
    if (!xfer) {
        goto done;
    }

    if (!xfer->is_ready) {
        musb_giveback(musb, urb, 0);

        xfer->hep->hcpriv = NULL;
        if(xfer != NULL)
        {
            USB_FREE(xfer);
            xfer = NULL;
        }
    } else {
        ret = musb_cleanup_urb(urb, xfer);
    }

done:
    spin_unlock_irq(sc);
    return ret;
}

static void musb_urb_free(struct usb_hcd *hcd, struct urb *urb) {
    struct musb *musb = hcd_to_musb(hcd);

    if (urb->preferable > 0 && urb->preferable < 16) {
        (musb->endpoints + urb->preferable)->bundled = 0;
    }
}

static void musb_repower_and_reset(struct usb_hcd *hcd) {
    struct musb *musb = hcd_to_musb(hcd);

    musb_interrput_switch(0);
    musb_platform_reset(musb);
    musb_interrput_switch(1);
}

static const struct hc_driver musb_hc_driver = {
    "musb hc driver", //const char  *description;   /* "ehci-hcd" etc */
    "musb hc product desc", //const char  *product_desc;  /* product/vendor string */
    HCD_MEMORY | HCD_USB2 | HCD_BH, //int flags;
    //HCD_MEMORY | HCD_USB11 | HCD_BH, //int flags;
    musb_h_start, //int (*start) (struct usb_hcd *hcd);
    musb_h_stop, //void (*stop) (struct usb_hcd *hcd);
    NULL, //int (*reset_device)(struct usb_hcd *, struct usb_device *);
    musb_h_get_last_frame_number, //int (*get_frame_number) (struct usb_hcd *hcd);
    musb_urb_enqueue, //int (*urb_enqueue) (struct usb_hcd *hcd, struct urb *urb);
    musb_urb_dequeue, //int (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb, int status);
    musb_hub_status_data, //int (*hub_status_data) (struct usb_hcd *hcd, char *buf);
    musb_hub_control, //int (*hub_control) (struct usb_hcd *hcd,u16 typeReq, u16 wValue, u16 wIndex,char *buf, u16 wLength);
    NULL, //void (*endpoint_reset) (struct usb_hcd *hcd,struct usb_host_endpoint *ep);
    NULL, //int (*update_hub_device)(struct usb_hcd *, struct usb_device *hdev, struct usb_tt *tt);
    musb_urb_free, //void (*urb_free) (struct usb_hcd *hcd, struct urb *urb);
    musb_repower_and_reset, //void (*repower_and_reset) (struct usb_hcd *hcd);
    musb_hub_port1_connect_state, //int (*hub_connect_state) (void);
};
int musb_host_alloc(struct musb *musb) {
    struct device *dev = musb->controller;

    musb->hcd = usb_create_hcd(&musb_hc_driver, dev);
    if (musb->hcd == NULL)
        return -EINVAL;

    musb->hcd->hcd_priv_size = sizeof(struct musb *);
    musb->hcd->hcd_priv = (void *)musb;
    musb->hcd->uses_new_polling = 1;

    return 0;
}
