/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: usb-drv-as3525.c 25996 2010-05-13 14:07:59Z ranma $
 *
 * Copyright © 2009 Rafaël Carré
 *
 * 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.
 *
 ****************************************************************************/

#include "system.h"
#include "usb.h"
#include "usb_drv.h"
#include "as3525.h"
#include "clock-target.h"
#include "ascodec.h"
#include "as3514.h"
#include <stdbool.h>
#include "panic.h"
/*#define LOGF_ENABLE*/
#define LOGF_ENABLE
#include "logf.h"
#include "usb_ch9.h"
#include "usb_core.h"
#include "string.h"

#define USB_NUM_EPS         4

typedef struct {
    volatile unsigned long offset[4096];
} __regbase;

/*
 * This generates better code.
 * Stripped object size with __regbase construct:  5192
 * Stripped object size with *((volatile int)(x)): 5228
 */
#define USB_REG(x)    ((__regbase *)(USB_BASE))->offset[(x)>>2]

/* 4 input endpoints */
#define USB_IEP_CTRL(i)         USB_REG(0x0000 + i*0x20)
#define USB_IEP_STS(i)          USB_REG(0x0004 + i*0x20)
#define USB_IEP_TXFSIZE(i)      USB_REG(0x0008 + i*0x20)
#define USB_IEP_MPS(i)          USB_REG(0x000C + i*0x20)
#define USB_IEP_DESC_PTR(i)     USB_REG(0x0014 + i*0x20)
#define USB_IEP_STS_MASK(i)     USB_REG(0x0018 + i*0x20)

/* 4 output endpoints */
#define USB_OEP_CTRL(i)         USB_REG(0x0200 + i*0x20)
#define USB_OEP_STS(i)          USB_REG(0x0204 + i*0x20)
#define USB_OEP_RXFR(i)         USB_REG(0x0208 + i*0x20)
#define USB_OEP_MPS(i)          USB_REG(0x020C + i*0x20)
#define USB_OEP_SUP_PTR(i)      USB_REG(0x0210 + i*0x20)
#define USB_OEP_DESC_PTR(i)     USB_REG(0x0214 + i*0x20)
#define USB_OEP_STS_MASK(i)     USB_REG(0x0218 + i*0x20)

/* more general macro */
/* d: true => IN, false => OUT */
#define USB_EP_CTRL(i,d)        USB_REG(0x0000 + i*0x20 + (!d)*0x0200)
#define USB_EP_STS(i,d)         USB_REG(0x0004 + i*0x20 + (!d)*0x0200)
#define USB_EP_TXFSIZE(i,d)     USB_REG(0x0008 + i*0x20 + (!d)*0x0200)
#define USB_EP_MPS(i,d)         USB_REG(0x000C + i*0x20 + (!d)*0x0200)
#define USB_EP_DESC_PTR(i,d)    USB_REG(0x0014 + i*0x20 + (!d)*0x0200)
#define USB_EP_STS_MASK(i,d)    USB_REG(0x0018 + i*0x20 + (!d)*0x0200)

#define USB_DEV_CFG             USB_REG(0x0400)
#define USB_DEV_CTRL            USB_REG(0x0404)
#define USB_DEV_STS             USB_REG(0x0408)
#define USB_DEV_INTR            USB_REG(0x040C)
#define USB_DEV_INTR_MASK       USB_REG(0x0410)
#define USB_DEV_EP_INTR         USB_REG(0x0414)
#define USB_DEV_EP_INTR_MASK    USB_REG(0x0418)

/* NOTE: Not written to in OF, most lied in host mode? */
#define USB_PHY_EP0_INFO        USB_REG(0x0504)
#define USB_PHY_EP1_INFO        USB_REG(0x0508)
#define USB_PHY_EP2_INFO        USB_REG(0x050C)
#define USB_PHY_EP3_INFO        USB_REG(0x0510)
#define USB_PHY_EP4_INFO        USB_REG(0x0514)
#define USB_PHY_EP5_INFO        USB_REG(0x0518)

/* 4 channels */
#define USB_HOST_CH_SPLT(i)     USB_REG(0x1000 + i*0x20)
#define USB_HOST_CH_STS(i)      USB_REG(0x1004 + i*0x20)
#define USB_HOST_CH_TXFSIZE(i)  USB_REG(0x1008 + i*0x20)
#define USB_HOST_CH_REQ(i)      USB_REG(0x100C + i*0x20)
#define USB_HOST_CH_PER_INFO(i) USB_REG(0x1010 + i*0x20)
#define USB_HOST_CH_DESC_PTR(i) USB_REG(0x1014 + i*0x20)
#define USB_HOST_CH_STS_MASK(i) USB_REG(0x1018 + i*0x20)

#define USB_HOST_CFG            USB_REG(0x1400)
#define USB_HOST_CTRL           USB_REG(0x1404)
#define USB_HOST_INTR           USB_REG(0x140C)
#define USB_HOST_INTR_MASK      USB_REG(0x1410)
#define USB_HOST_CH_INTR        USB_REG(0x1414)
#define USB_HOST_CH_INTR_MASK   USB_REG(0x1418)
#define USB_HOST_FRAME_INT      USB_REG(0x141C)
#define USB_HOST_FRAME_REM      USB_REG(0x1420)
#define USB_HOST_FRAME_NUM      USB_REG(0x1424)

#define USB_HOST_PORT0_CTRL_STS USB_REG(0x1500)

#define USB_OTG_CSR             USB_REG(0x2000)
#define USB_I2C_CSR             USB_REG(0x2004)
#define USB_GPIO_CSR            USB_REG(0x2008)
#define USB_SNPSID_CSR          USB_REG(0x200C)
#define USB_USERID_CSR          USB_REG(0x2010)
#define USB_USER_CONF1          USB_REG(0x2014)
#define USB_USER_CONF2          USB_REG(0x2018)
#define USB_USER_CONF3          USB_REG(0x201C)
#define USB_USER_CONF4          USB_REG(0x2020)
/* USER_CONF5 seems to the same as USBt least on read */
#define USB_USER_CONF5          USB_REG(0x2024)

/* write bits 31..16 */
#define USB_GPIO_IDDIG_SEL        (1<<30)
#define USB_GPIO_FS_DATA_EXT      (1<<29)
#define USB_GPIO_FS_SE0_EXT       (1<<28)
#define USB_GPIO_FS_XCVR_OWNER    (1<<27)
#define USB_GPIO_TX_ENABLE_N      (1<<26)
#define USB_GPIO_TX_BIT_STUFF_EN  (1<<25)
#define USB_GPIO_BSESSVLD_EXT     (1<<24)
#define USB_GPIO_ASESSVLD_EXT     (1<<23)
#define USB_GPIO_VBUS_VLD_EXT     (1<<22)
#define USB_GPIO_VBUS_VLD_EXT_SEL (1<<21)
#define USB_GPIO_XO_ON            (1<<20)
#define USB_GPIO_CLK_SEL11        (3<<18)
#define USB_GPIO_CLK_SEL10        (2<<18)
#define USB_GPIO_CLK_SEL01        (1<<18)
#define USB_GPIO_CLK_SEL00        (0<<18)
#define USB_GPIO_XO_EXT_CLK_ENBN  (1<<17)
#define USB_GPIO_XO_REFCLK_ENB    (1<<16)
/* readronly bits 15..0 */
#define USB_GPIO_PHY_VBUSDRV      (1<< 1)
#define USB_GPIO_HS_INTR          (1<< 0)

/* Device Control Register and bit fields */
#define USB_DEV_CTRL_REMOTE_WAKEUP      0x00000001  // set remote wake-up signal
#define USB_DEV_CTRL_RDE                0x00000004  // receive dma enable
#define USB_DEV_CTRL_DESC_UPDATE        0x00000010  // descriptor update
#define USB_DEV_CTRL_THRES_ENABLE       0x00000080  // threshold enable
#define USB_DEV_CTRL_BURST_CONTROL      0x00000100  // burst control
#define USB_DEV_CTRL_SOFT_DISCONN       0x00000400  // soft disconnect
#define USB_DEV_CTRL_APCSR_DONE         0x00002000  // app Prog CSR Done
#define USB_DEV_CTRL_MASK_BURST_LEN     0x000f0000  // mask for burst length
#define USB_DEV_CTRL_MASK_THRESHOLD_LEN 0xff000000  // mask for threshold length

/* settings of burst length for maskBurstLen_c field */
#define USB_DEV_CTRL_BLEN_1DWORD        0x00000000
#define USB_DEV_CTRL_BLEN_2DWORDS       0x00010000
#define USB_DEV_CTRL_BLEN_4DWORDS       0x00020000
#define USB_DEV_CTRL_BLEN_8DWORDS       0x00030000
#define USB_DEV_CTRL_BLEN_16DWORDS      0x00040000
#define USB_DEV_CTRL_BLEN_32DWORDS      0x00050000
#define USB_DEV_CTRL_BLEN_64DWORDS      0x00060000
#define USB_DEV_CTRL_BLEN_128DWORDS     0x00070000
#define USB_DEV_CTRL_BLEN_256DWORDS     0x00080000
#define USB_DEV_CTRL_BLEN_512DWORDS     0x00090000

/* settings of threshold length for maskThresholdLen_c field */
#define USB_DEV_CTRL_TLEN_1DWORD        0x00000000
#define USB_DEV_CTRL_TLEN_HALFMAXSIZE   0x01000000
#define USB_DEV_CTRL_TLEN_4THMAXSIZE    0x02000000
#define USB_DEV_CTRL_TLEN_8THMAXSIZE    0x03000000

#define USB_DEV_CFG_HS                  0x00000000
#define USB_DEV_CFG_FS                  0x00000001 /* 30 or 60MHz */
#define USB_DEV_CFG_LS                  0x00000002
#define USB_DEV_CFG_FS_48               0x00000003 /* 48MHz */
#define USB_DEV_CFG_REMOTE_WAKEUP       0x00000004
#define USB_DEV_CFG_SELF_POWERED        0x00000008
#define USB_DEV_CFG_SYNC_FRAME          0x00000010
#define USB_DEV_CFG_PI_16BIT            0x00000000
#define USB_DEV_CFG_PI_8BIT             0x00000020
#define USB_DEV_CFG_UNI_DIR             0x00000000
#define USB_DEV_CFG_BI_DIR              0x00000040
#define USB_DEV_CFG_STAT_ACK            0x00000000
#define USB_DEV_CFG_STAT_STALL          0x00000080

/* Device Status Register and bit fields */
#define USB_DEV_STS_MASK_CFG            0x0000000f
#define USB_DEV_STS_MASK_IF             0x000000f0
#define USB_DEV_STS_MASK_ALT_SET        0x00000f00
#define USB_DEV_STS_SUSPEND_STAT        0x00001000
#define USB_DEV_STS_MASK_SPD            0x00006000 /* Enumerated Speed */
#define USB_DEV_STS_SPD_HS                0x00000000
#define USB_DEV_STS_SPD_FS                0x00002000
#define USB_DEV_STS_SPD_LS                0x00004000
#define USB_DEV_STS_RXF_EMPTY           0x00008000
#define USB_DEV_STS_MASK_FRM_NUM        0xfffc0000 /* SOF frame number */


/* Device Intr Register and bit fields */
#define USB_DEV_INTR_SET_CONFIG         0x00000001  /* set configuration cmd rcvd */
#define USB_DEV_INTR_SET_INTERFACE      0x00000002  /* set interface command rcvd */
#define USB_DEV_INTR_EARLY_SUSPEND      0x00000004  /* idle on usb for 3ms */
#define USB_DEV_INTR_USB_RESET          0x00000008  /* usb bus reset req */
#define USB_DEV_INTR_USB_SUSPEND        0x00000010  /* usb bus suspend req */
#define USB_DEV_INTR_SOF                0x00000020  /* SOF seen on bus */
#define USB_DEV_INTR_ENUM_DONE          0x00000040  /* usb speed enum done */
#define USB_DEV_INTR_OTG                0x00000080  /* otg status change? */
#define USB_DEV_INTR_BUSERR             0x00000080  /* AHB DMA error */

/* EP Control Register Fields */
#define USB_EP_CTRL_STALL               0x00000001
#define USB_EP_CTRL_FLUSH               0x00000002     /* EP In data fifo Flush */
#define USB_EP_CTRL_SNOOP_MODE          0x00000004     // snoop mode for out endpoint
#define USB_EP_CTRL_PD                  0x00000008     /* EP Poll Demand */
#define USB_EP_CTRL_EPTYPE_MASK         0x00000030     // bit 5-4: endpoint types
#define USB_EP_TYPE_CONTROL               0x00000000   // control endpoint
#define USB_EP_TYPE_ISO                   0x00000010   // isochronous endpoint
#define USB_EP_TYPE_BULK                  0x00000020   // bulk endpoint
#define USB_EP_TYPE_INTERRUPT             0x00000030   // interrupt endpoint
#define USB_EP_CTRL_NAK                 0x00000040     /* EP NAK Status */
#define USB_EP_CTRL_SNAK                0x00000080     /* EP Set NAK Bit */
#define USB_EP_CTRL_CNAK                0x00000100     /* EP Clr NAK Bit */
#define USB_EP_CTRL_ACT                 0x00000400     /* EP Clr NAK Bit */

/* bit fields in EP Status Register */
#define USB_EP_STAT_OUT_RCVD            0x00000010     /* OUT token received */
#define USB_EP_STAT_SETUP_RCVD          0x00000020     /* SETUP token received */
#define USB_EP_STAT_IN                  0x00000040     /* IN token received? */
#define USB_EP_STAT_BNA                 0x00000080     /* Buffer Not Available */
#define USB_EP_STAT_BUFF_ERROR          0x00000100
#define USB_EP_STAT_HERR                0x00000200     /* Host Error */
#define USB_EP_STAT_AHB_ERROR           0x00000200
#define USB_EP_STAT_TDC                 0x00000400     /* Transmit DMA Complete */

/*-------------------------*/
/* DMA Related Definitions */
/*-------------------------*/

/* dma status */
#define USB_DMA_DESC_BUFF_STS           0x80000000    /* Buffer Status */
#define USB_DMA_DESC_BS_HST_RDY         0x80000000    /* Host Ready */
#define USB_DMA_DESC_BS_DMA_DONE        0x00000000    /* DMA Done */
#define USB_DMA_DESC_ZERO_LEN           0x40000000    /* zero length packet */
#define USB_DMA_DESC_EARY_INTR          0x20000000    /* early interrupt */
#define USB_DMA_DESC_RXTX_STS           0x10000000
#define USB_DMA_DESC_RTS_SUCC           0x00000000    /* Success */
#define USB_DMA_DESC_RTS_BUFERR         0x10000000    /* Buffer Error */
#define USB_DMA_DESC_LAST               0x08000000
#define USB_DMA_DESC_MASK_FRAM_NUM      0x07ff0000    // bits 26-16: frame number for iso
#define USB_DMA_DESC_RXTX_BYTES         0x0000FFFF

/* setup descriptor */
#define SETUP_MASK_CONFIG_STAT          0x0fff0000
#define SETUP_MASK_CONFIG_NUM           0x0f000000
#define SETUP_MASK_IF_NUM               0x00f00000
#define SETUP_MASK_ALT_SETNUM           0x000f0000

#define EP_STATE_ALLOCATED              0x00000001
#define EP_STATE_BUSY                   0x00000002
#define EP_STATE_ASYNC                  0x00000004

struct usb_dev_dma_desc {
    int   status;
    int   resv;
    void  *data_ptr;
    void  *next_desc;
};

struct usb_dev_setup_buf {
    int   status;
    int   resv;
    int   data1; /* first 4 bytes of data */
    int   data2; /* last 4 bytes of data */
};

struct usb_endpoint
{
    void *buf;
    unsigned int len;
    volatile unsigned int state;
    int rc;
    struct wakeup complete;
    struct usb_dev_dma_desc *uc_desc;
};

static struct usb_endpoint endpoints[USB_NUM_EPS][2];

/*
 * dma/setup descriptors and buffers should avoid sharing
 * a cacheline with other data.
 * dmadescs may share with each other, since we only access them uncached.
 */
static struct usb_dev_dma_desc dmadescs[USB_NUM_EPS][2] __attribute__((aligned(32)));

static struct usb_dev_setup_buf setup_desc;
static struct usb_dev_setup_buf dummy; /* to keep rx_buf out of this cacheline */

static char rx_buf[1024];
static char tx_buf[1024];

void usb_attach(void)
{
    usb_enable(true);
}

static inline void usb_delay(int delay)
{   //TUNEME : delay is in milliseconds
    delay <<= 14;
    while(delay--) ;
}

static void usb_phy_on(void)
{
    /* PHY clock */
    CGU_USB = 1<<5 /* enable */
        | (CLK_DIV(AS3525_PLLB_FREQ, 48000000) / 2) << 2
        | 2; /* source = PLLB */

    /* UVDD on */
    ascodec_write(AS3517_USB_UTIL, ascodec_read(AS3517_USB_UTIL) | (1<<4));
    usb_delay(100);

    /* reset */
    CCU_SRC = CCU_SRC_USB_AHB_EN|CCU_SRC_USB_PHY_EN;
    CCU_SRL = CCU_SRL_MAGIC_NUMBER;
    usb_delay(1);
    CCU_SRC = CCU_SRC_USB_AHB_EN;
    usb_delay(1);
    CCU_SRC = CCU_SRL = 0;

    USB_GPIO_CSR = USB_GPIO_TX_ENABLE_N
                 | USB_GPIO_TX_BIT_STUFF_EN
                 | USB_GPIO_XO_ON
                 | USB_GPIO_CLK_SEL10; /* 0x06180000; */
}

static void usb_phy_suspend(void)
{
    USB_GPIO_CSR |= USB_GPIO_ASESSVLD_EXT |
                    USB_GPIO_BSESSVLD_EXT |
                    USB_GPIO_VBUS_VLD_EXT;
    usb_delay(3);
    USB_GPIO_CSR |= USB_GPIO_VBUS_VLD_EXT_SEL;
    usb_delay(10);
}

static void usb_phy_resume(void)
{
    USB_GPIO_CSR &= ~(USB_GPIO_ASESSVLD_EXT |
                      USB_GPIO_BSESSVLD_EXT |
                      USB_GPIO_VBUS_VLD_EXT);
    usb_delay(3);
    USB_GPIO_CSR &= ~USB_GPIO_VBUS_VLD_EXT_SEL;
    usb_delay(10);
}

static void setup_desc_init(struct usb_dev_setup_buf *desc)
{
    struct usb_dev_setup_buf *uc_desc = UNCACHED_ADDR(desc);

    uc_desc->status = USB_DMA_DESC_BS_HST_RDY;
    uc_desc->resv   = 0xffffffff;
    uc_desc->data1  = 0xffffffff;
    uc_desc->data2  = 0xffffffff;
}

static void dma_desc_init(int ep, int dir)
{
    struct usb_dev_dma_desc *desc = &dmadescs[ep][dir];
    struct usb_dev_dma_desc *uc_desc = UNCACHED_ADDR(desc);

    endpoints[ep][dir].uc_desc = uc_desc;

    if (dir == 0) {
        uc_desc->status    = USB_DMA_DESC_BS_DMA_DONE | USB_DMA_DESC_LAST | 0x40;
        uc_desc->resv      = 0xffffffff;
        uc_desc->data_ptr  = tx_buf;
        uc_desc->next_desc = /* &desc[0] */ 0;
    } else {
        uc_desc->status    = USB_DMA_DESC_BS_HST_RDY | /*USB_DMA_DESC_LAST |*/ 0x40;
        uc_desc->resv      = 0xffffffff;
        uc_desc->data_ptr  = rx_buf;
        uc_desc->next_desc = desc;
    }
}

static void reset_endpoints(int init)
{
    int i;

    /*
     * OUT EP 2 is an alias for OUT EP 0 on this HW!
     *
     * Resonates with "3 bidirectional- plus 1 in-endpoints in device mode"
     * from the datasheet, but why ep2 and not ep3?
     *
     * Reserve it here so we will skip over it in request_endpoint().
     */
    endpoints[2][1].state |= EP_STATE_ALLOCATED;

    for(i = 0; i < USB_NUM_EPS; i++) {
        int mps = i == 0 ? 64 : 2048; /* For HS */

        if (init) {
            endpoints[i][0].state = 0;
            wakeup_init(&endpoints[i][0].complete);

            if (i != 2) { /* Skip the OUT EP0 alias */
                endpoints[i][1].state = 0;
                wakeup_init(&endpoints[i][1].complete);
                USB_OEP_SUP_PTR(i)    = 0;
            }
        }

        dma_desc_init(i, 0);
        USB_IEP_CTRL    (i) = USB_EP_CTRL_FLUSH|USB_EP_CTRL_SNAK;
        USB_IEP_MPS     (i) = mps;
        /* We don't care about the 'IN token received' event */
        USB_IEP_STS_MASK(i) = USB_EP_STAT_IN; /* OF: 0x840 */
        USB_IEP_TXFSIZE (i) = 0x20;
        USB_IEP_STS     (i) = 0xffffffff; /* clear status */
        USB_IEP_DESC_PTR(i) = 0;

        if (i != 2) { /* Skip the OUT EP0 alias */
            dma_desc_init(i, 1);
            USB_OEP_CTRL    (i) = USB_EP_CTRL_FLUSH|USB_EP_CTRL_SNAK;
            USB_OEP_MPS     (i) = 0x08000000|mps;
            USB_OEP_STS_MASK(i) = 0x0000; /* OF: 0x1800 */
            USB_OEP_RXFR    (i) = 0x00;
            USB_OEP_STS     (i) = 0xffffffff; /* clear status */
            USB_OEP_DESC_PTR(i) = 0;
        }
    }

    setup_desc_init(&setup_desc);
    USB_OEP_SUP_PTR(0)    = (int)&setup_desc;
}

void usb_drv_init(void)
{
    logf("usb_drv_init() !!!!\n");

    /* length regulator: normal operation */
    ascodec_write(AS3514_CVDD_DCDC3, ascodec_read(AS3514_CVDD_DCDC3) | 1<<2);

    /* AHB part */
    CGU_PERI |= CGU_USB_CLOCK_ENABLE;

    /* reset AHB */
    CCU_SRC = CCU_SRC_USB_AHB_EN;
    CCU_SRL = CCU_SRL_MAGIC_NUMBER;
    usb_delay(1);
    CCU_SRC = CCU_SRL = 0;

    USB_GPIO_CSR = USB_GPIO_TX_ENABLE_N
                 | USB_GPIO_TX_BIT_STUFF_EN
                 | USB_GPIO_XO_ON
                 | USB_GPIO_CLK_SEL10; /* 0x06180000; */

    /* bug workaround according to linux patch */
    USB_DEV_CFG = (USB_DEV_CFG & ~3) | 1; /* full speed */

    /* enable soft disconnect */
    USB_DEV_CTRL |= USB_DEV_CTRL_SOFT_DISCONN;

    usb_phy_on();
    usb_phy_suspend();
    USB_DEV_CTRL |= USB_DEV_CTRL_SOFT_DISCONN;

    /* We don't care about OTG or SOF events */
    /* Right now we don't handle suspend or reset, so mask those too */
    USB_DEV_INTR_MASK = USB_DEV_INTR_OTG |
                        USB_DEV_INTR_SOF |
                        USB_DEV_INTR_USB_SUSPEND |
                        USB_DEV_INTR_EARLY_SUSPEND;

    USB_DEV_CFG = USB_DEV_CFG_STAT_ACK     |
                  USB_DEV_CFG_UNI_DIR      |
                  USB_DEV_CFG_PI_16BIT     |
                  USB_DEV_CFG_HS           |
                  USB_DEV_CFG_SELF_POWERED |
                  0x20200;

    USB_DEV_CTRL = USB_DEV_CTRL_BLEN_1DWORD  |
                   USB_DEV_CTRL_DESC_UPDATE  |
                   USB_DEV_CTRL_THRES_ENABLE |
                   USB_DEV_CTRL_RDE          |
                   0x04000000;

    USB_DEV_EP_INTR_MASK &= ~((1<<0) | (1<<16));    /* ep 0 */
    USB_DEV_EP_INTR_MASK = 0; /* FIXME: test */

    reset_endpoints(1);

    /* clear pending interrupts */
    USB_DEV_EP_INTR = 0xffffffff;
    USB_DEV_INTR    = 0xffffffff;

    VIC_INT_ENABLE = INTERRUPT_USB;

    usb_phy_resume();
    USB_DEV_CTRL &= ~USB_DEV_CTRL_SOFT_DISCONN;

    USB_GPIO_CSR = USB_GPIO_TX_ENABLE_N
                 | USB_GPIO_TX_BIT_STUFF_EN
                 | USB_GPIO_XO_ON
                 | USB_GPIO_HS_INTR
                 | USB_GPIO_CLK_SEL10; /* 0x06180000; */

}

void usb_drv_exit(void)
{
    USB_DEV_CTRL |= (1<<10); /* soft disconnect */
    VIC_INT_EN_CLEAR = INTERRUPT_USB;
#if 0
    CGU_USB &= ~(1<<5);
    CGU_PERI &= ~CGU_USB_CLOCK_ENABLE;
    /* Disable UVDD generating LDO */
    ascodec_write(AS3517_USB_UTIL, ascodec_read(AS3517_USB_UTIL) & ~(1<<4));
#endif
    logf("usb_drv_exit() !!!!\n");
}

int usb_drv_port_speed(void)
{
    return (USB_DEV_STS & USB_DEV_STS_MASK_SPD) ? 0 : 1;
}

int usb_drv_request_endpoint(int type, int dir)
{
    int d = dir == USB_DIR_IN ? 0 : 1;
    int i = 1; /* skip the control EP */

    for(; i < USB_NUM_EPS; i++) {
        if (endpoints[i][d].state & EP_STATE_ALLOCATED)
            continue;

        endpoints[i][d].state |= EP_STATE_ALLOCATED;

        if (dir == USB_DIR_IN) {
            USB_IEP_CTRL(i) = USB_EP_CTRL_FLUSH |
                              USB_EP_CTRL_SNAK  |
                              USB_EP_CTRL_ACT   |
                              (type << 4);
            USB_DEV_EP_INTR_MASK &= ~(1<<i);
        } else {
            USB_OEP_CTRL(i) = USB_EP_CTRL_FLUSH |
                              USB_EP_CTRL_SNAK  |
                              USB_EP_CTRL_ACT   |
                              (type << 4);
            USB_DEV_EP_INTR_MASK &= ~(1<<(16+i));
        }
        /* logf("usb_drv_request_endpoint(%d, %d): returning %02x\n", type, dir, i | dir); */
        return i | dir;
    }

    logf("usb_drv_request_endpoint(%d, %d): no free endpoint found\n", type, dir);
    return -1;
}

void usb_drv_release_endpoint(int ep)
{
    int i = ep & 0x7f;
    int d = ep & USB_DIR_IN ? 0 : 1;

    if (i >= USB_NUM_EPS)
        return;
    /*
     * Check for control EP and ignore it.
     * Unfortunately the usb core calls
     * usb_drv_release_endpoint() for ep=0..(USB_NUM_ENDPOINTS-1),
     * but doesn't request a new control EP after that...
     */
    if (i == 0 || /* Don't mask control EP */
        (i == 2 && d == 1)) /* See reset_endpoints(), EP2_OUT == EP0_OUT */
        return;

    if (!(endpoints[i][d].state & EP_STATE_ALLOCATED))
        return;

    /* logf("usb_drv_release_endpoint(%d, %d)\n", i, d); */
    endpoints[i][d].state = 0;
    USB_DEV_EP_INTR_MASK |= (1<<(16*d+i));
    USB_EP_CTRL(i, !d) = USB_EP_CTRL_FLUSH | USB_EP_CTRL_SNAK;
}

void usb_drv_cancel_all_transfers(void)
{
    logf("usb_drv_cancel_all_transfers()\n");
    return;

    int flags = disable_irq_save();
    reset_endpoints(0);
    restore_irq(flags);
}

static void *virt_to_bus(void *addr)
{
    unsigned int x = (long)addr;

    x -= (x & 0x40000000) >> 2; /* fix uncached address */

    if (x >= IRAM_ORIG) { /* iram address */
        x -= IRAM_ORIG;
    }
    /* logf("V2B: %08x -> %08x\n", (int)addr, x); */

    return (void*)x;
}

int usb_drv_recv(int ep, void *ptr, int len)
{
    struct usb_dev_dma_desc *uc_desc = endpoints[ep][1].uc_desc;

    ep &= 0x7f;
    logf("usb_drv_recv(%d,%x,%d)\n", ep, (int)ptr, len);

    if ((int)ptr & 31) {
        logf("addr %08x not aligned!\n", (int)ptr);
    }

    endpoints[ep][1].state |= EP_STATE_BUSY;
    endpoints[ep][1].len = len;
    endpoints[ep][1].rc = -1;

    /* remove data buffer from cache */
    invalidate_dcache();
    /* DMA setup */
    uc_desc->status    = USB_DMA_DESC_BS_HST_RDY |
                         USB_DMA_DESC_LAST |
                         len;
    uc_desc->resv      = 0xffffffff;
    uc_desc->next_desc = 0;
    if (len == 0) {
        uc_desc->status   |= USB_DMA_DESC_ZERO_LEN;
        uc_desc->data_ptr  = 0;
    } else {
        uc_desc->data_ptr  = virt_to_bus(ptr);
    }
    USB_OEP_DESC_PTR(ep) = (int)&dmadescs[ep][1];
    USB_OEP_STS(ep)      = USB_EP_STAT_OUT_RCVD; /* clear status */
    USB_OEP_CTRL(ep) |= USB_EP_CTRL_CNAK;

    return 0;
}

static char hexbuf[1025];
static char hextab[16] = "0123456789abcdef";

char *make_hex(char *data, int len)
{
    int i;
    if (!((int)data & 0x40000000))
        data = UNCACHED_ADDR(data); /* don't pollute the cache */

    if (len > 512)
        len = 512;

    for (i=0; i<len; i++) {
        hexbuf[2*i  ] = hextab[(unsigned char)data[i] >> 4 ];
        hexbuf[2*i+1] = hextab[(unsigned char)data[i] & 0xf];
    }
    hexbuf[2*i] = 0;

    return hexbuf;
}

void ep_send(int ep, void *ptr, int len)
{
    struct usb_dev_dma_desc *uc_desc = endpoints[ep][0].uc_desc;
    int i;

    endpoints[ep][0].state |= EP_STATE_BUSY;
    endpoints[ep][0].len = len;
    endpoints[ep][0].rc = -1;

    USB_IEP_CTRL(ep)     |= USB_EP_CTRL_CNAK;

    /* TEST: delay a little here */
    for (i=0; i<1000; i++) asm volatile ("nop\n");

    /* Make sure data is committed to memory */
    clean_dcache();

    logf("xx%s\n", make_hex(ptr, len));

    uc_desc->status    = USB_DMA_DESC_BS_HST_RDY |
                         USB_DMA_DESC_LAST |
                         len;
    if (len == 0)
        uc_desc->status |= USB_DMA_DESC_ZERO_LEN;

    uc_desc->resv      = 0xffffffff;
    uc_desc->next_desc = 0;
    uc_desc->data_ptr  = virt_to_bus(ptr);

    USB_IEP_CTRL(ep)     |= USB_EP_CTRL_FLUSH;
    USB_IEP_DESC_PTR(ep) = (int)&dmadescs[ep][0];
    USB_IEP_STS(ep)      = 0xffffffff; /* clear status */
    /* start transfer */
    USB_IEP_CTRL(ep)     |= USB_EP_CTRL_PD;
}

int usb_drv_send(int ep, void *ptr, int len)
{
    logf("usb_drv_send(%d,%x,%d): ", ep, (int)ptr, len);

    ep &= 0x7f;
    ep_send(ep, ptr, len);
    while (endpoints[ep][0].state & EP_STATE_BUSY)
        /* DEBUG: Busywait for now... */
        /*wakeup_wait(&endpoints[ep][0].complete, TIMEOUT_BLOCK)*/;

    /* logf("usb_drv_send(): returning %d\n", endpoints[ep][0].rc); */
    return endpoints[ep][0].rc;
}

int usb_drv_send_nonblocking(int ep, void *ptr, int len)
{
    logf("usb_drv_send_nonblocking(%d,%x,%d): ", ep, (int)ptr, len);
    ep &= 0x7f;
    endpoints[ep][0].state |= EP_STATE_ASYNC;
    ep_send(ep, ptr, len);
    return 0;
}

static void handle_in_ep(int ep)
{
    int ep_sts = USB_IEP_STS(ep) & ~USB_IEP_STS_MASK(ep);
    int ack_sts = ep_sts;

    if (ep > 3)
        return;

    if (ep_sts & USB_EP_STAT_TDC) {
        ep_sts &= ~USB_EP_STAT_TDC;
        /* OF does SNAK and FLUSH at once here */
        USB_IEP_CTRL(ep) |= USB_EP_CTRL_SNAK|USB_EP_CTRL_FLUSH;
        endpoints[ep][0].state &= ~EP_STATE_BUSY;
        endpoints[ep][0].rc = 0;
        logf("EP%d %stx done len %x stat %08x\n",
             ep, endpoints[ep][0].state & EP_STATE_ASYNC ? "async " :"",
             endpoints[ep][0].len,
             endpoints[ep][0].uc_desc->status);
        if (endpoints[ep][0].state & EP_STATE_ASYNC) {
            endpoints[ep][0].state &= ~EP_STATE_ASYNC;
            usb_core_transfer_complete(ep, USB_DIR_IN, 0, endpoints[ep][0].len);
        } else {
            wakeup_signal(&endpoints[ep][0].complete);
        }
    }

    if (ep_sts) {
        logf("ep%d IN, hwstat %lx, epstat %x\n", ep, USB_IEP_STS(ep), endpoints[ep][0].state);
        panicf("ep%d IN 0x%x", ep, ep_sts);
    }

    /* HW automatically disables RDE, re-enable it */
    /* But this an IN ep, I wonder... */
    USB_DEV_CTRL |= USB_DEV_CTRL_RDE;

    /* ACK late for debugging */
    USB_IEP_STS(ep) = ack_sts; /* ack */
}

static void handle_out_ep(int ep)
{
    struct usb_ctrlrequest *req = (void*)UNCACHED_ADDR(&setup_desc.data1);
    int ep_sts = USB_OEP_STS(ep) & ~USB_OEP_STS_MASK(ep);
    int ack_sts = ep_sts;
    struct usb_dev_dma_desc *uc_desc = endpoints[ep][1].uc_desc;

    if (ep > 3)
        return;

    if (ep_sts & USB_EP_STAT_OUT_RCVD) {
        int dma_sts = uc_desc->status;
        int dma_len = dma_sts & 0xffff;
        int dma_frm = (dma_sts >> 16) & 0x7ff;
        int dma_mst = dma_sts & 0xf8000000;

        if (!(dma_sts & USB_DMA_DESC_ZERO_LEN)) {
             logf("EP%d OUT token, st:%08x len:%d frm:%x data=%s\n", ep,
                 dma_mst, dma_len, dma_frm, make_hex(uc_desc->data_ptr, dma_len));
             /*
              * If parts of the just dmaed range are in cache, dump them now.
              */
             dump_dcache_range(uc_desc->data_ptr, dma_len);
        } else{
             logf("EP%d OUT token, st:%08x len:%d frm:%x\n", ep,
                 dma_mst, dma_len, dma_frm);
        }

        if (endpoints[ep][1].state & EP_STATE_BUSY) {
            endpoints[ep][1].state &= ~EP_STATE_BUSY;
            endpoints[ep][1].rc = 0;
            usb_core_transfer_complete(ep, 0, 0, endpoints[ep][0].len);
        }

        USB_OEP_CTRL(ep) |= USB_EP_CTRL_SNAK; /* make sure NAK is set */

        ep_sts &= ~USB_EP_STAT_OUT_RCVD;
    }

    if (ep_sts & USB_EP_STAT_SETUP_RCVD) {
        static struct usb_ctrlrequest req_copy;

        req_copy = *req;
        logf("t%ld:got SETUP packet: type=%d req=%d val=%d ind=%d len=%d\n",
             current_tick,
             req->bRequestType,
             req->bRequest,
             req->wValue,
             req->wIndex,
             req->wLength);

        usb_core_control_request(&req_copy);
        setup_desc_init(&setup_desc);

        ep_sts &= ~USB_EP_STAT_SETUP_RCVD;
    }

    if (ep_sts) {
        logf("ep%d OUT, status %x\n", ep, ep_sts);
        panicf("ep%d OUT 0x%x", ep, ep_sts);
    }

    /* HW automatically sets NAK and disables RDE, clear and re-enable it */
    /* THEORY: Because we only set up one DMA buffer... */
/*    USB_OEP_CTRL(ep) |= USB_EP_CTRL_CNAK; */
    USB_DEV_CTRL |= USB_DEV_CTRL_RDE;

    /* ACK late for debugging */
    USB_OEP_STS(ep) = ack_sts; /* ACK */
}

/* interrupt service routine */
void INT_USB(void)
{
    int ep = USB_DEV_EP_INTR & ~USB_DEV_EP_INTR_MASK;
    int ack_ep = ep;
    int intr = USB_DEV_INTR & ~USB_DEV_INTR_MASK;
    int ack_intr = intr;

    /*
    logf("t%ld:INT_USB(ep=%x[r:%lx,m:%lx],intr=%x[r:%lx,m:%lx])\n",
         current_tick, ep, USB_DEV_EP_INTR, USB_DEV_EP_INTR_MASK,
         intr, USB_DEV_INTR, USB_DEV_INTR_MASK);
    */

    /* GPIOA_PIN(5) ^= 0xff; */

    while (ep) {
        int onebit = 31-__builtin_clz(ep);
        /*logf("ep:%x,onebit:%d\n", ep, onebit);*/
        if (onebit < 16) handle_in_ep(onebit);
        else handle_out_ep(onebit-16);
        ep &= ~(1 << onebit);
    }

    if(intr)
    {
        if (intr & USB_DEV_INTR_SET_INTERFACE) {/* SET_INTERFACE received */
            intr &= ~USB_DEV_INTR_SET_INTERFACE;
            logf("set interface\n");
            panicf("set interface");
        }
        if (intr & USB_DEV_INTR_SET_CONFIG) {/* SET_CONFIGURATION received */
            /*
             * This is handled in HW, we have to fake a request here for usb_core
             */
            static struct usb_ctrlrequest set_config = {
            bRequestType: USB_TYPE_STANDARD | USB_RECIP_DEVICE,
            bRequest: USB_REQ_SET_CONFIGURATION,
            wValue: 0,
            wIndex: 0,
            wLength: 0,
            };

            intr &= ~USB_DEV_INTR_SET_CONFIG;
            logf("set config\n");

            set_config.wValue = USB_DEV_STS & USB_DEV_STS_MASK_CFG;
            usb_core_control_request(&set_config);

            /* Tell the HW we handled the request */
            USB_DEV_CTRL |= USB_DEV_CTRL_APCSR_DONE;
        }
        if (intr & USB_DEV_INTR_EARLY_SUSPEND) {/* idle >3ms detected */
            intr &= ~USB_DEV_INTR_EARLY_SUSPEND;
            logf("usb idle\n");
        }
        if (intr & USB_DEV_INTR_USB_RESET) {/* usb reset from host? */
            intr &= ~USB_DEV_INTR_USB_RESET;
            logf("usb reset\n");
            reset_endpoints(1);
            usb_core_bus_reset();
        }
        if (intr & USB_DEV_INTR_USB_SUSPEND) {/* suspend req from host? */
            intr &= ~USB_DEV_INTR_USB_SUSPEND;
            logf("usb suspend\n");
        }
        if (intr & USB_DEV_INTR_SOF) {/* sof received */
            intr &= ~USB_DEV_INTR_SOF;
            logf("sof\n");
        }
        if (intr & USB_DEV_INTR_ENUM_DONE) {/* speed enumeration complete */
            int spd = USB_DEV_STS & USB_DEV_STS_MASK_SPD;  /* Enumerated Speed */

            intr &= ~USB_DEV_INTR_ENUM_DONE;
            logf("speed enum complete: ");
            if (spd == USB_DEV_STS_SPD_HS) logf("hs\n");
            if (spd == USB_DEV_STS_SPD_FS) logf("fs\n");
            if (spd == USB_DEV_STS_SPD_LS) logf("ls\n");

            USB_PHY_EP0_INFO = 0x00200000;
            USB_DEV_CTRL |= USB_DEV_CTRL_APCSR_DONE;
            USB_IEP_CTRL(0) |= USB_EP_CTRL_ACT;
            USB_OEP_CTRL(0) |= USB_EP_CTRL_ACT;
#if 0
            USB_IEP_CTRL(0) |= USB_EP_CTRL_CNAK|USB_EP_CTRL_FLUSH; /* clear NAK */
            USB_OEP_CTRL(0) |= USB_EP_CTRL_CNAK|USB_EP_CTRL_FLUSH; /* clear NAK */
#endif
        }
        if (intr & USB_DEV_INTR_BUSERR) {
            panicf("usb dma bus error");
            intr &= ~USB_DEV_INTR_BUSERR;
        }
        if (intr)
            panicf("usb devirq 0x%x", intr);
    }

#if 0
    if (USB_OEP_CTRL(0) & USB_EP_CTRL_NAK) {
        logf("EP0 nak set? clearing!\n");
        USB_OEP_CTRL(0) |= USB_EP_CTRL_CNAK; /* clear NAK */
    }
#endif

    if (!(USB_DEV_CTRL & USB_DEV_CTRL_RDE)){
        logf("re-enabling receive DMA\n");
        USB_DEV_CTRL |= USB_DEV_CTRL_RDE;
    }

    /* ACK late for debugging */
    USB_DEV_EP_INTR = ack_ep; /* ack EP intr */
    USB_DEV_INTR = ack_intr; /* ack interrupt */
}

/* (not essential? , not implemented in usb-tcc.c) */
void usb_drv_set_test_mode(int mode)
{
    (void)mode;
}

/* handled internally by controller */
void usb_drv_set_address(int address)
{
    (void)address;
}

void usb_drv_stall(int ep, bool stall, bool in)
{
   if (stall) USB_EP_CTRL(ep, in) |= USB_EP_CTRL_STALL;
   else USB_EP_CTRL(ep, in) &= ~USB_EP_CTRL_STALL;
}

bool usb_drv_stalled(int ep, bool in)
{
    return USB_EP_CTRL(ep, in) & USB_EP_CTRL_STALL ? true : false;
}