/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2009 by Karl Kurbjun
 * Portions Copyright (C) 2007 by Catalin Patulea
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
/*#define LOGF_ENABLE*/

#include "system.h"
#include "config.h"
#include "string.h"
#include "usb_ch9.h"
#include "usb_core.h"
#include "kernel.h"
#include "panic.h"
#include "usb_drv.h"
#include "logf.h"

#include "cpu.h"
#include "ata.h"
#include "usb.h"
#include "m66591.h"

/*******************************************************************************
 *  These are the driver specific defines.
 ******************************************************************************/

/* This define is primarily intended for testing, using HISPEED all the time
 *  should be acceptable since the device should down-train if the host does not
 *  support HISPEED.
 */
#define HISPEED

/* Right now sending blocks till the full transfer has completed. The driver
 *  will work without USB_TRAN_BLOCK set, but it is more than 50% slower.
 *  The driver is more "Proper" without USB_TRAN_BLOCK defined so if you start
 *  having freezeups or trouble using USB undefine this option.
 */
#define USB_TRAN_BLOCK

/*******************************************************************************
 * The following functions are all helpers which should not be called directly
 *  from the USB stack.  They should only be called by eachother, or the USB
 *  stack visible functions.
 ******************************************************************************/

struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS]; /* filled in usb_drv_init */
uint8_t usb_drv_ep_specs_flags = USB_ENDPOINT_SPEC_IO_EXCLUSIVE;

static volatile unsigned short * pipe_ctrl_addr(int pipe);
static void pipe_handshake(int pipe, int handshake);
static void pipe_c_select (int pipe, bool dir);
#if !defined(USB_TRAN_BLOCK)
static int pipe_buffer_size (int pipe);
#endif
static int pipe_maxpack_size (int pipe);
static void control_received(void);
static void transfer_complete(int endpoint);
static int mxx_transmit_receive(int endpoint);
static int mxx_queue(int endpoint, void * ptr, int length, bool send,
    bool wait);

struct M66591_epstat {
    unsigned char dir;      /* endpoint direction */
    char *buf;              /* user buffer to store data */
    int length;             /* how match data will fit */
    volatile int count;     /* actual data count */
    bool waiting;           /* is there data to transfer? */
} ;

static struct M66591_epstat M66591_eps[USB_NUM_ENDPOINTS];

/* This function is used to return the control address for each pipe */
static volatile unsigned short * pipe_ctrl_addr(int pipe) {
    if(pipe==0) {
        return &M66591_DCPCTRL;
    } else {
        return &M66591_PIPECTRL1 + (pipe-1);
    }
}

static void pipe_init(int pipe) {
    volatile unsigned short *pipe_cfg;
    pipe_cfg = pipe_ctrl_addr(pipe);

    *pipe_cfg |= 1<<9;      /* ACLR */
    *pipe_cfg &= ~(1<<9);   /* Force de-assertion */
    *pipe_cfg |= 1<<8;      /* SQCLR */
}

/* This function sets the pipe/endpoint handshake */
static void pipe_handshake(int pipe, int handshake) {
    handshake&=0x03;

    if(handshake == PIPE_SHAKE_STALL) {
        if( *(pipe_ctrl_addr(pipe)) & 0x03 ) {
            *(pipe_ctrl_addr(pipe)) = 0x03;
        } else {
            *(pipe_ctrl_addr(pipe)) = 0x02;
        }
    } else {
        *(pipe_ctrl_addr(pipe)) = handshake;
    }
}

/* This function chooses the pipe desired and waits the required time before
 *  warites/reads are valid */
static void pipe_c_select (int pipe, bool dir) {
    M66591_CPORT_CTRL0 = pipe | (1<<10) | (dir<<5);

    // Wait for the Pipe to be valid;
    udelay(2);
}

#if !defined(USB_TRAN_BLOCK)
/* This returns the maximum buffer size of each pipe.  On this device the size
 *  is fixed.
 */
static int pipe_buffer_size (int pipe) {
    switch(pipe) {
    case 0:
        return 256;
    case 1:
    case 2:
        if(M66591_PIPE_CFGWND & (1<<9) )
            return 1024;
        else
            return 512;
    case 3:
    case 4:
        return 512;
    case 5:
    case 6:
        return 64;
    default:
        return 0;
    }
}
#endif

/* This function returns the maximum packet size for each endpoint/pipe.  The
 *  max packet size is dependent on whether the device is running High or Full
 *  speed.
 */
static int pipe_maxpack_size (int pipe) {
    if( (M66591_HSFS & 0xFF) == 0x03 ) { /* Device is running Highspeed */
        switch(pipe) {
        case 0:
            /* DCP max packet size is configurable */
            return M66591_DCP_MXPKSZ;
        case 1:
        case 2:
        case 3:
        case 4:
            return 512;
        case 5:
        case 6:
            return 64;
        default:
            return 0;
        }
    } else {            /* Device is running Full speed */
        switch(pipe) {
        case 0:
            /* DCP max packet size is configurable */
            return M66591_DCP_MXPKSZ;
        case 1:
        case 2:
        case 3:
        case 4:
            return 64;
        case 5:
        case 6:
            return 64;
        default:
            return 0;
        }
    }
}

/* This is a helper function that is only called from the interupt handler.  It
 *  copies the control packet information from the PHY and notifies the stack.
 */
static void control_received(void) {
    /* copy setup data from packet */
    static struct usb_ctrlrequest temp;

    memcpy(&temp, (unsigned char*)&M66591_USB_REQ0, 8);

    logf("mxx: bReqType=0x%02x bReq=0x%02x wVal=0x%04x"
        " wIdx=0x%04x wLen=0x%04x",
        temp.bRequestType, temp.bRequest, temp.wValue,
        temp.wIndex, temp.wLength);

    /* acknowledge packet recieved (clear valid) */
    M66591_INTSTAT_MAIN &= ~(1<<3);

    usb_core_setup_received(&temp);
}

/* This is a helper function, it is used to notife the stack that a transfer is
 *  done.
 */
static void transfer_complete(int endpoint) {
    M66591_INTCFG_EMP &= ~(1 << endpoint);
    logf("mxx: ep %d transfer complete", endpoint);
    int temp=M66591_eps[endpoint].dir ? USB_DIR_IN : USB_DIR_OUT;
    usb_core_transfer_complete(endpoint, temp, 0,
        M66591_eps[endpoint].count);
}

/* This is the main transmit routine that is typically called from the interrupt
 *  handler (the queue function calls it in some situations)
 */
static int mxx_transmit_receive(int endpoint) {
    logf("mxx: do start");

    /* Only the lower 15 bits of the endpoint correlate to the pipe number.
     *  For example pipe 2 will corelate to endpoint 0x82, so the upper bits
     * need to be masked out.
     */
    endpoint &= 0x7F;

    int i;      /* Used as a loop counter */
    int length; /* Used in transfers to determine the amount to send/receive */

    bool send=M66591_eps[endpoint].dir;

    /* This is used as the internal buffer pointer */
    unsigned short *ptrs;

    /* Choose the pipe that data is being transfered on */
    pipe_c_select(endpoint, send);

    /* Check to see if the endpoint is ready and give it some time to become
     *  ready.  If it runs out of time exit out as an error.
     */
    i = 0;
    while (!(M66591_CPORT_CTRL1&(1<<13))) {
        if (i++ > 100000) {
            logf("mxx: FIFO %d not ready", endpoint);
            return -1;
        }
    }

    /* Write to FIFO */
    if(send) {
        int maxpack=pipe_maxpack_size(endpoint);
#if defined(USB_TRAN_BLOCK)
        length = M66591_eps[endpoint].length;
#else
        int bufsize=pipe_buffer_size(endpoint);
        length=MIN(M66591_eps[endpoint].length - M66591_eps[endpoint].count,
            bufsize);
#endif

        /* Calculate the position in the buffer, all transfers should be 2-byte
         *  aligned till the last packet or short packet.
         */
        ptrs = (unsigned short *)(M66591_eps[endpoint].buf
            + M66591_eps[endpoint].count);

        /* Check if the buffer is alligned */
        if( LIKELY(((int)ptrs) & 0x01) == 0 )
        {
            /* Start sending data in 16-bit words (fast) */
            for (i = 0; i < (length>>1); i++) {
#if defined(USB_TRAN_BLOCK)
                /* This wait is dangerous in the event that something happens
                 *  to the PHY pipe where it never becomes ready again, should
                 *  probably add a timeout, and ideally completely remove.
                 */
                while(!(M66591_CPORT_CTRL1&(1<<13))){};
#endif

                M66591_CPORT = *ptrs++;
                M66591_eps[endpoint].count+=2;
            }

            /* If the length is odd, send the last byte after setting the byte
             *  width of the FIFO.
             */
            if(length & 0x01) {
                /* Unset MBW (8-bit transfer) */
                M66591_CPORT_CTRL0 &= ~(1<<10);
                M66591_CPORT = *((unsigned char *)ptrs);
                M66591_eps[endpoint].count++;
            }
        }
        else
        {
            /* The buffer is mis-aligned - data needs to be organized first.
             *  This is slower than the above method.
             */
            unsigned short sbuf;
            unsigned char *ptrc = (unsigned char*)ptrs;

            /* Start sending data in 16-bit words */
            for (i = 0; i < (length>>1); i++) {
#if defined(USB_TRAN_BLOCK)
                /* This wait is dangerous in the event that something happens
                 *  to the PHY pipe where it never becomes ready again, should
                 *  probably add a timeout, and ideally completely remove.
                 */
                while(!(M66591_CPORT_CTRL1&(1<<13))){};
#endif

                /* These are mis-aligned accesses so the data nees to be
                 *  arranged.
                 */
                sbuf = (*(ptrc+1) << 8) | *ptrc;
                ptrc += 2;

                M66591_CPORT = sbuf;
                M66591_eps[endpoint].count+=2;
            }

            /* If the length is odd, send the last byte after setting the byte
             *  width of the FIFO.
             */
            if(length & 0x01) {
                /* Unset MBW (8-bit transfer) */
                M66591_CPORT_CTRL0 &= ~(1<<10);
                M66591_CPORT = *ptrc;
                M66591_eps[endpoint].count++;
            }
        }

        /* If the transfer is complete set up interrupts to notify when FIFO is
         *  EMPTY, disable READY and let the handler know that there is nothing
         *  left to transfer on this pipe.
         */
        if(M66591_eps[endpoint].count == M66591_eps[endpoint].length) {
            /* Enable Empty flag */
            M66591_INTCFG_EMP |= 1 << endpoint;
            /* Disable ready flag */
            M66591_INTCFG_RDY &= ~(1 << endpoint);
            /* Nothing left to transfer */
            M66591_eps[endpoint].waiting=false;
        } else {
            /* There is still data to transfer, make sure READY is enabled */
            M66591_INTCFG_RDY |= 1 << endpoint;
        }

        /* Set BVAL if length is not a multiple of the maximum packet size */
        if( (length == 0) || (length % maxpack != 0) ) {
            logf("mxx: do set BVAL");
            M66591_CPORT_CTRL1 |= (1<<15) | ((length == 0) << 14);
        }
    } else {
        /* Read data from FIFO */

        /* Read the number of bytes that the PHY received */
        int receive_length=M66591_CPORT_CTRL1 & 0x03FF;

        /* The number of bytes to actually read is either what's left of the
         *  amount requested, or the amount that the PHY received.  Choose the
         *  smaller of the two.
         */
        length = MIN(M66591_eps[endpoint].length - M66591_eps[endpoint].count,
            receive_length);

        /* If the length is zero, just clear the buffer as specified in the
         *  datasheet.  Otherwise read in the data (in 16-bit pieces */
        if(length==0) {
            /* Set the BCLR bit */
            M66591_CPORT_CTRL1 |= 1<<14;
        } else {
            /* Set the position in the buffer */
            ptrs = (unsigned short *)(M66591_eps[endpoint].buf
                + M66591_eps[endpoint].count);

            /* Read in the data (buffer size should be even).  The PHY cannot
             *  switch from 16-bit mode to 8-bit mode on an OUT buffer.
             */
            for (i = 0; i < ((length+1)>>1); i++) {
                *ptrs++ = M66591_CPORT;
                M66591_eps[endpoint].count+=2;
            }
        }

        /* If the length was odd subtract 1 from the count */
        M66591_eps[endpoint].count -= (length&0x01);

        /* If the requested size of data was received, or the data received was
         *  less than the maximum packet size end the transfer.
         */
        if( (M66591_eps[endpoint].count == M66591_eps[endpoint].length)
            || (length % pipe_maxpack_size(endpoint)) ) {

            /* If the host tries to send anything else the FIFO is not ready/
             *  enabled yet (NAK).
             */
            pipe_handshake(endpoint, PIPE_SHAKE_NAK);
            /* Tell the interrupt handler that transfer is complete. */
            M66591_eps[endpoint].waiting=false;
            /* Disable ready */
            M66591_INTCFG_RDY &= ~(1 << endpoint);

            /* Let the stack know that the transfer is complete */
            if(endpoint!=0)
                transfer_complete(endpoint);
        }
    }

    logf("mxx: do done ep %d %s len: %d cnt: %d", endpoint,
        send ? "out" : "in", length, M66591_eps[endpoint].count);

    return 0;
}

/* This function is used to start transfers.  It is a helper function for the
 *  usb_drv_send_nonblocking, usb_drv_send, and usb_drv_receive functions.
 *
 * The functionality for wait needs to be added.  Currently the driver is
 *  always used in a blocking mode(USB_TRAN_BLOCK) so it is not required.
 */
static int mxx_queue(int endpoint, void * ptr, int length, bool send,
    bool wait)
{
#if defined(USB_TRAN_BLOCK) && !defined(LOGF_ENABLE)
    (void) wait;
#endif

    /* Disable IRQs */
    int flags = disable_irq_save();

    /* Only the lower 15 bits of the endpoint correlate to the pipe number.
     *  For example pipe 2 will corelate to endpoint 0x82, so the upper bits
     *  need to be masked out.
     */
    endpoint &= 0x7F;

    /* Initialize the enpoint status registers used for the transfer */
    M66591_eps[endpoint].buf=ptr;
    M66591_eps[endpoint].length=length;
    M66591_eps[endpoint].count=0;
    M66591_eps[endpoint].dir=send;
    M66591_eps[endpoint].waiting=true;

    logf("mxx: queue ep %d %s, len: %d, wait: %d",
        endpoint, send ? "out" : "in", length, wait);

    /* Pick the pipe that communications are happening on */
    pipe_c_select(endpoint, send);

    /* All transfers start with a BUF handshake */
    pipe_handshake(endpoint, PIPE_SHAKE_BUF);

    /* This USB PHY takes care of control completion packets by setting the
     *  CCPL bit in EP0 (endpoint 0, or DCP).  If the control state is "write no
     *  data tranfer" then we just need to set the CCPL bit (hopefully)
     *  regardless of what the stack said to send.
     */
    int control_state = (M66591_INTSTAT_MAIN & 0x07);
    if(endpoint==0 && control_state==CTRL_WTND) {
        logf("mxx: queue ep 0 ctls: 5, set ccpl");

        /* Set CCPL */
        M66591_DCPCTRL |= 1<<2;
    } else {
        /* This is the standard case for transmitting data */
        if(send) {
            /* If the pipe is not ready don't try and send right away; instead
             *  just set the READY interrupt so that the handler can initiate
             *  the transfer.
             */
            if((M66591_CPORT_CTRL1&(1<<13))) {
                mxx_transmit_receive(endpoint);
            } else {
                M66591_INTCFG_RDY |= 1 << endpoint;
            }

            if(length==0) {
                transfer_complete(endpoint);
            }
        } else {
            /* When receiving data, just enable the ready interrupt, the PHY
             *  will trigger it and then the reads can start.
             */
            M66591_INTCFG_RDY |= 1 << endpoint;
        }
    }

    /* Re-enable IRQs */
    restore_irq(flags);
    return 0;
}

/*******************************************************************************
 * This is the interrupt handler for this driver.  It should be called from the
 *  target interrupt handler routine (eg. GPIO3 on M:Robe 500).
 ******************************************************************************/
void USB_DEVICE(void)  __attribute__ ((section(".icode")));
void USB_DEVICE(void) {
    int pipe_restore=M66591_CPORT_CTRL0;
    logf("\nmxx: INT BEGIN tick: %d", (int) current_tick);

    logf("mxx: sMAIN0: 0x%04x, sRDY: 0x%04x",
        M66591_INTSTAT_MAIN, M66591_INTSTAT_RDY);
    logf("mxx:  sNRDY: 0x%04x, sEMP: 0x%04x",
        M66591_INTSTAT_NRDY, M66591_INTSTAT_EMP);

    /* VBUS (connected) interrupt */
    while ( M66591_INTSTAT_MAIN & (1<<15) ) {
        M66591_INTSTAT_MAIN &= ~(1<<15);

        /* If device is not clocked, interrupt flag must be set manually */
        if ( !(M66591_TRN_CTRL & (1<<10)) ) {
            M66591_INTSTAT_MAIN |= (1<<15);
        }
    }

    /* Resume interrupt: This is not used. Extra logic needs to be added similar
    *  to the VBUS interrupt incase the PHY clock is not running.
    */
    if(M66591_INTSTAT_MAIN & (1<<14)) {
        M66591_INTSTAT_MAIN &= ~(1<<14);
        logf("mxx: RESUME");
    }

    /* Device state transition interrupt: Not used, but useful for debugging */
    if(M66591_INTSTAT_MAIN & (1<<12)) {
        M66591_INTSTAT_MAIN &= ~(1<<12);
        logf("mxx: DEV state CHANGE=%d",
        ((M66591_INTSTAT_MAIN & (0x07<<4)) >> 4) );
    }

    /* Control transfer stage interrupt */
    if(M66591_INTSTAT_MAIN & (1<<11)) {
        M66591_INTSTAT_MAIN &= ~(1<<11);
        int control_state = (M66591_INTSTAT_MAIN & 0x07);

        logf("mxx: CTRT with CTSQ=%d", control_state);

        switch ( control_state ) {
        case CTRL_IDLE:
            transfer_complete(0);
            break;
        case CTRL_RTDS:
        case CTRL_WTDS:
        case CTRL_WTND:
            /* If data is not valid stop */
            if(!(M66591_INTSTAT_MAIN & (1<<3)) ) {
                logf("mxx: CTRT interrupt but VALID is false");
                break;
            }
            control_received();
            break;
        case CTRL_RTSS:
        case CTRL_WTSS:
            pipe_handshake(0, PIPE_SHAKE_BUF);
            M66591_DCPCTRL |= 1<<2; /* Set CCPL */
            break;
        default:
            logf("mxx: CTRT with unknown CTSQ");
            break;
        }
    }

    /* FIFO EMPTY interrupt: when this happens the transfer should be complete.
     *  When the interrupt occurs notify the stack.
     */
    if(M66591_INTSTAT_MAIN & (1<<10)) {
        int i;
        logf("mxx: INT EMPTY: 0x%04x", M66591_INTSTAT_EMP);

        for(i=0; i<USB_NUM_ENDPOINTS; i++) {
            if(M66591_INTSTAT_EMP&(1<<i)) {
                /* Clear the empty flag */
                M66591_INTSTAT_EMP=~(1<<i);
                /* Notify the stack */
                transfer_complete(i);
            }
        }
    }

    /* FIFO NOT READY interrupt: This is not used, but included incase the
     *  interrupt is endabled.
     */
    if(M66591_INTSTAT_MAIN & (1<<9)) {
        logf("mxx: INT NOT READY: 0x%04x", M66591_INTSTAT_NRDY);
        M66591_INTSTAT_NRDY = 0;
    }

    /* FIFO READY interrupt: This just initiates transfers if they are needed */
    if(M66591_INTSTAT_MAIN & (1<<8)) {
        int i;
        logf("mxx: INT READY: 0x%04x", M66591_INTSTAT_RDY);

        for(i=0; i<USB_NUM_ENDPOINTS; i++) {
            /* Was this endpoint ready and waiting */
            if(M66591_INTSTAT_RDY&(1<<i) && M66591_eps[i].waiting) {
                /* Clear the ready flag */
                M66591_INTSTAT_RDY=~(1<<i);
                /* It was ready and waiting so start a transfer */
                mxx_transmit_receive(i);
            }
        }
    }

    /* Make sure that the INTStatus register is completely cleared. */
    M66591_INTSTAT_MAIN = 0;

    /* Restore the pipe state before the interrupt occured */
    M66591_CPORT_CTRL0=pipe_restore;
    logf("mxx: INT END\n");
}

/*******************************************************************************
 *  The following functions are all called by and visible to the USB stack.
 ******************************************************************************/

/* The M55691 handles this automatically, nothing to do */
void usb_drv_set_address(int address) {
    (void) address;
}

/* This function sets the standard test modes, it is not required, but might as
 *  well implement it since the hardware supports it
 */
void usb_drv_set_test_mode(int mode) {
    /* This sets the test bits and assumes that mode is from 0 to 0x04 */
    M66591_TESTMODE &= 0x0007;
    M66591_TESTMODE |= mode;
}

void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) {
    /* FIXME: support max packet size override */
    const int epnum = EP_NUM(ep);
    const int epdir = EP_DIR(ep);
    const int type = ctx->type[epnum][epdir];

    int pipecfg = 0;

    if(type == USB_ENDPOINT_XFER_BULK) {
        /* Enable double buffer mode (only used for ep 1 and 2) */
        pipecfg |= 1<<9 | 1<<8;
    } else if(type == USB_ENDPOINT_XFER_BULK) {
        pipecfg |= 1<<13;
    } else {
        panicf("mxx: unsupported type %d", type);
    }

    if (epdir == DIR_IN) {
        pipecfg |= (1<<4);
    }

    M66591_eps[epnum].dir = epdir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT;

    M66591_PIPE_CFGSEL = epnum;

    /* Enable pipe (15) */
    pipecfg |= 1<<15;

    pipe_handshake(epnum, PIPE_SHAKE_NAK);

    /* Setup the flags */
    M66591_PIPE_CFGWND=pipecfg;

    pipe_init(epnum);

    logf("mxx: ep req ep#: %d config: 0x%04x", epnum, M66591_PIPE_CFGWND);
}

/* Used by stack to tell the helper functions that the pipe is not in use */
void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) {
    (void)ctx;

    int num = ep & USB_ENDPOINT_NUMBER_MASK;

    if (num < 1 || num > USB_NUM_ENDPOINTS) {
        return;
    }

    int flags = disable_irq_save();

    logf("mxx: ep %d release", num);

    M66591_eps[num].dir = -1;

    restore_irq(flags);
}

/* Periodically called to check if a cable was plugged into the device */
inline int usb_detect(void)
{
    if(M66591_INTSTAT_MAIN&(1<<7))
        return USB_INSERTED;
    else
        return USB_EXTRACTED;
}

void usb_enable(bool on) {
    logf("mxx: %s: %s", __FUNCTION__, on ? "true" : "false");
    if (on)
        usb_core_init();
    else
        usb_core_exit();
}

/* This is where the driver stuff starts */
void usb_drv_init(void) {
    logf("mxx: Device Init");

    M66591_PIN_CFG1         = 0x8000; /* Drive Current: 3.3V setting */
    M66591_PIN_CFG2         = 0x0000;

    M66591_TRN_CTRL         = 0x8000; /* External 48 MHz clock */
    M66591_TRN_CTRL         |=0x0001;

    M66591_INTCFG_MAIN      |=0x8000; /* Enable VBUS interrupt */

    /* Fill endpoint spec table FIXME: should be done in usb_drv_startup() */
    usb_drv_ep_specs[0].type[DIR_OUT] = USB_ENDPOINT_XFER_CONTROL;
    usb_drv_ep_specs[0].type[DIR_IN] = USB_ENDPOINT_XFER_CONTROL;
    for(int i = 1; i < 5; i += 1) {
        usb_drv_ep_specs[i].type[DIR_OUT] = USB_ENDPOINT_XFER_BULK;
        usb_drv_ep_specs[i].type[DIR_IN] = USB_ENDPOINT_XFER_BULK;
    }
    for(int i = 5; i < USB_NUM_ENDPOINTS; i += 1) {
        usb_drv_ep_specs[i].type[DIR_OUT] = USB_ENDPOINT_XFER_INT;
        usb_drv_ep_specs[i].type[DIR_IN] = USB_ENDPOINT_XFER_INT;
    }
}

/* fully enable driver */
void usb_attach(void) {
    int i;

    /* Reset Endpoint states */
    for(i=0; i<USB_NUM_ENDPOINTS; i++) {
        M66591_eps[i].dir = -1;
        M66591_eps[i].buf = (char *) 0;
        M66591_eps[i].length = 0;
        M66591_eps[i].count = 0;
        M66591_eps[i].waiting = false;
    }

    /* Issue a h/w reset */
    usb_init_device();
    usb_core_init();

    /* USB Attach Process: This follows the flow diagram in the M66591GP
     *  Reference Manual Rev 1.00, p. 77 */

#if defined(HISPEED)
    /* Run Hi-Speed */
    M66591_TRN_CTRL |= 1<<7;
#else
    /* Run Full-Speed */
    M66591_TRN_CTRL &= ~(1<<7);
#endif

    /* Enable oscillation buffer XCKE */
    M66591_TRN_CTRL |= (1<<13);

    udelay(1500);

    /* Enable reference clock, PLL RCKE */
    M66591_TRN_CTRL |= (3<<11);

    udelay(9);

    /* Enable internal clock supply SCKE */
    M66591_TRN_CTRL |= (1<<10);

    /* Disable PIPE ready interrupts */
    M66591_INTCFG_RDY = 0;

    /* Disable PIPE not-ready interrupts */
    M66591_INTCFG_NRDY = 0;

    /* Disable PIPE empyt/size error interrupts */
    M66591_INTCFG_EMP = 0;

    /* Enable all interrupts except NOT READY, RESUME, and VBUS */
    M66591_INTCFG_MAIN = 0x1DFF;

    pipe_c_select(0, false);

    /* Enable continuous transfer mode on the DCP */
    M66591_DCP_CNTMD |= (1<<8);

    /* Set the threshold that the PHY will automatically transmit from EP0 */
    M66591_DCP_CTRLEN = 256;

    pipe_handshake(0, PIPE_SHAKE_NAK);

    /* Set the Max packet size to 64 */
    M66591_DCP_MXPKSZ = 64;

    /* Attach notification to PC (D+ pull-up) */
    M66591_TRN_CTRL |= (1<<4);

    logf("mxx: attached");
}

void usb_drv_exit(void) {
    /* USB Detach Process: This follows the flow diagram in the M66591GP
     *  Reference Manual Rev 1.00, p. 78.
     */

    /* Detach notification to PC (disable D+ pull-up) */
    M66591_TRN_CTRL &= ~(1<<4);

    /* Software reset */
    M66591_TRN_CTRL &= ~0x01;

    /* Disable internal clock supply */
    M66591_TRN_CTRL &= ~(1<<10);
    udelay(3);

    /* Disable PLL */
    M66591_TRN_CTRL &= ~(1<<11);
    udelay(3);

    /* Disable internal reference clock */
    M66591_TRN_CTRL &= ~(1<<12);
    udelay(3);

    /* Disable oscillation buffer, reenable USB operation */
    M66591_TRN_CTRL &= ~(1<<13);

    M66591_TRN_CTRL |= 0x01;

    logf("mxx: detached");
}

/* This function begins a transmit (on an IN endpoint), it should not block
 *  so the actual transmit is done in the interrupt handler.
 */
int usb_drv_send_nonblocking(int endpoint, void* ptr, int length)
{
    return mxx_queue(endpoint, ptr, length, true, false);
}

/* This function begins a transmit (on an IN endpoint), it does not block
 *  so the actual transmit is done in the interrupt handler.
 */
int usb_drv_send(int endpoint, void* ptr, int length)
{
    return mxx_queue(endpoint, ptr, length, true, true);
}

/* This function begins a receive (on an OUT endpoint), it should not block
 *  so the actual receive is done in the interrupt handler.
 */
int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length)
{
    return mxx_queue(endpoint, ptr, length, false, false);
}

/* This function checks the reset handshake speed status
 *  (Fullspeed or Highspeed)
 */
int usb_drv_port_speed(void)
{
    int handshake = (M66591_HSFS & 0xFF);

    if( handshake == 0x02) {
        return 0; /* Handshook at Full-Speed */
    } else if( handshake == 0x03) {
        return 1; /* Handshook at Hi-Speed */
    } else {
        return -1; /* Error, handshake may not be complete */
    }
}

/* This function checks if the endpoint is stalled (error).  I am not sure what
 *  the "in" variable is intended for.
 */
bool usb_drv_stalled(int endpoint,bool in)
{
    (void) in;

    bool stalled = (*(pipe_ctrl_addr(endpoint)) & (0x02)) ? true : false;

    logf("mxx: stall?: %s ep: %d", stalled ? "true" : "false", endpoint);

    if(stalled) {
        return true;
    } else {
        return false;
    }
}

/* This function stalls/unstalls the endpoint.  Stalls only happen on error so
 *  if the endpoint is functioning properly this should not be called.  I am
 *  not sure what the "in" variable is intended for.
 */
void usb_drv_stall(int endpoint, bool stall,bool in)
{
    (void) in;

    logf("mxx: stall - ep: %d", endpoint);

    if(stall) {
        /* Stall the pipe (host needs to intervene/error) */
        pipe_handshake(endpoint, PIPE_SHAKE_STALL);
    } else {
        /* Setting this to a NAK, not sure if it is appropriate */
        pipe_handshake(endpoint, PIPE_SHAKE_NAK);
    }
}

/* !!!!!!!!!!This function is likely incomplete!!!!!!!!!!!!!! */
void usb_drv_cancel_all_transfers(void)
{
    int endpoint;
    int flags;

    logf("mxx: %s", __func__);

    flags = disable_irq_save();
    for (endpoint = 0; endpoint < USB_NUM_ENDPOINTS; endpoint++) {
        if (M66591_eps[endpoint].buf) {
            M66591_eps[endpoint].buf = NULL;
        }
    }

    restore_irq(flags);
}
