/*
 * Kneron CMSIS Driver
 *
 * Copyright (C) 2019 Kneron, Inc. All rights reserved.
 *
 */

#include "cmsis_os2.h" // CMSIS RTOS header file
#include "Driver_USBH.h"
#include "kdrv_cmsis_core.h"
#include "regbase.h"
#include "io.h"

#include "kmdw_memory.h"
#include <string.h>

#include "base.h"

//#define USBH_DRV_DBG // turn on this can help trace code, a lot of code-size needed
//#define USBH_DRV_ERR // turn on this can add some more error checking, slightly code-size needed
#define USBH_DRV_FULL // turn on this to get full set of functions, some code-sie needed

#if defined(USBH_DRV_DBG) | defined(USBH_DRV_ERR)
#include "kdrv_uart.h"
#endif

/*  Register read/write macros */

// FIXME: copied from kdp_usbd_reg.h
#define EHCI_USBCMD 0x10
/* USBSTS bit fields */
#define ASCH_EN BIT5
#define PSCH_EN BIT4
#define HC_RESET BIT1
#define RS BIT0
/* end */

#define EHCI_USBSTS 0x14
/* USBSTS bit fields */
#define ASCH_STS BIT15
#define PSCH_STS BIT14
#define HCHalted BIT12
#define INT_OAA BIT5
#define H_SYSERR BIT4
#define FRL_ROL BIT3
#define PO_CHG_DET BIT2
#define USBERR_INT BIT1
#define USB_INT BIT0
/* end */

#define EHCI_USBINTR 0x18
/* USBINTR bit fields */
#define H_SYSERR_EN BIT4
#define PO_CHG_INT_EN BIT2
#define USBERR_INT_EN BIT1
#define USB_INT_EN BIT0
/* end */

#define EHCI_FRINDEX 0x1C

#define EHCI_PERIODIC_LIST_ADDR 0x24
#define EHCI_ASYNC_LIST_ADDR 0x28

#define EHCI_PORTSC 0x30
/* USBINTR bit fields */
#define PO_RESET BIT8
#define PO_SUSP BIT7
#define F_PO_RESM BIT6
#define PO_EN BIT2
#define CONN_CHG BIT1
#define CONN_STS BIT0
/* end */

#define REG_OTG_CSR 0x80
#define A_BUS_DROP BIT5
#define A_BUS_REQ BIT4

#define REG_OTG_ISR 0x84
#define OTG_OVC_RW1C BIT10

#define REG_GLB_INT 0xC4

#define REG_DEV_CTL 0x100

// PHY Test Mode Selector Register (0x114)
#define REG_PHY_TST 0x114
#define TST_JSTA BIT0

#define UsbRegRead(reg_offset) inw(USB_FOTG210_PA_BASE + reg_offset)
#define UsbRegWrite(reg_offset, val) outw(USB_FOTG210_PA_BASE + reg_offset, val)
#define UsbRegMaskedSet(reg_offset, val) masked_outw(USB_FOTG210_PA_BASE + reg_offset, val, val)
#define UsbRegMaskedClr(reg_offset, val) masked_outw(USB_FOTG210_PA_BASE + reg_offset, 0, val)

/* USB Host Driver */

#define ARM_USBH_DRV_VERSION ARM_DRIVER_VERSION_MAJOR_MINOR(2, 0) /* driver version */

typedef enum
{
    ASYNC_SCHEDULE = ASCH_EN,
    PERIODIC_SCHEDULE = PSCH_EN,
} EHCI_SCHEDULE_TYPE;

/* Queue Head */
#define QH_NUM 10
#define QH_REAL_SIZE 48 // EHCI actual size
#define QH_ALLOC_IZE 64 // for 32-byte alignment and some meta-data usage

#define qTD_NUM 50
#define qTD_REAL_SIZE 32  // EHCI actual size
#define qTD_ALLOC_SIZE 64 // for 32-byte alignment and some meta-data usage

#define MAX_PIPES QH_NUM

#define FRAME_LIST_SIZE 1024
#define FRAME_LIST_ROLL_MASK (FRAME_LIST_SIZE - 1)

#define iTD_MAX_NUM 1024
#define iTD_SIZE 64

// for QH, qTD and iTD link pointer dwords
struct Link_Pointer_DWord
{
    uint32_t terminate : 1;
    uint32_t type : 2; //  valid only for QH and iTD
    uint32_t reserved : 2;
    uint32_t ptr_address : 27;
};

// qTD DWORD 2
struct Status_DWord
{
    uint32_t status : 8;
    uint32_t PID : 2;
    uint32_t CERR : 2;
    uint32_t c_page : 3;
    uint32_t ioc : 1;
    uint32_t total_bytes_txfer : 15;
    uint32_t dt : 1;
};

// qTD Buffer Pointer DWORD
struct Buffer_Pointer_DWord
{
    uint32_t cur_offset : 12;  // only valid for DWORD 3
    uint32_t ptr_address : 20; // 4KBytes-aligned addrees
};

// qTD data structure
typedef struct __attribute__((__packed__))
{
    // DWORD 0 : Next qTD Pointer
    struct Link_Pointer_DWord next_qtd;

    // DWORD 1 : Alternate Next qTD Pointer
    struct Link_Pointer_DWord alt_next_qtd;

    // DWORD 2 : Status, PID Code, Error Counter, Current Page, Interrupt On Complete, Total Bytes to Transfer, Data Toggle
    struct Status_DWord status_dword;

    // DWORD 3~7 : Buffer Pointer pages and current offset
    struct Buffer_Pointer_DWord buf_ptr_dword[5];

    // ======== below is for internal metada ========
    uint32_t in_use;
    uint32_t wanted_txfer_bytes; // record the wanted txfer bytes
    // ==============================================

} Q_TD;

// iTD Transaction DWORD
struct Transaction_DWord
{
    uint32_t offset : 12;
    uint32_t pg : 3;
    uint32_t ioc : 1;
    uint32_t length : 12;
    uint32_t status : 4;
};

// iTD Buffer Pointer Page DWORD (generic)
struct Buffer_Page_DWord
{
    uint32_t misc : 12;        // for page 0~2, use differnt structs
    uint32_t ptr_address : 20; // 4KBytes-aligned addrees
};

// iTD Buffer Pointer Page 0 DWORD
struct Buffer_Page_0_DWord
{
    uint32_t dev_addr : 7;
    uint32_t reserved : 1;
    uint32_t endpoint : 4;
    uint32_t ptr_address : 20; // 4KBytes-aligned addrees
};

// iTD Buffer Pointer Page 1 DWORD
struct Buffer_Page_1_DWord
{
    uint32_t max_packet_size : 11;
    uint32_t direction : 1;
    uint32_t ptr_address : 20; // 4KBytes-aligned addrees
};

// iTD Buffer Pointer Page 2 DWORD
struct Buffer_Page_2_DWord
{
    uint32_t mult : 2; // valid values are 1~3, indidcates number of transaction per micro-frame
    uint32_t reserved : 10;
    uint32_t ptr_address : 20; // 4KBytes-aligned addrees
};

// iTD data structure
typedef struct __attribute__((__packed__))
{
    // DWORD 0 : Next qTD Pointer
    struct Link_Pointer_DWord next_link; // QH or iTD

    // DWORD 1~8 : Transaction DWORDs
    struct Transaction_DWord trans_dword[8];

    // DWORD 9~15 : Buffer Pointer pages and others
    struct Buffer_Page_DWord buf_page_dword[7];

} I_TD;

// QH DWORD 1
struct Device_Address_DWord
{
    uint32_t dev_addr : 7;
    uint32_t inactivate : 1;
    uint32_t endpoint : 4;
    uint32_t endpt_speed : 2;
    uint32_t dtc : 1;
    uint32_t H_reclamation : 1;
    uint32_t max_packet_length : 11;
    uint32_t C_endpt_flag : 1;
    uint32_t RL_Nak_count : 4;
};

// QH DWORD 2
struct Mask_DWord
{
    uint32_t S_mask : 8;
    uint32_t C_mask : 8;
    uint32_t hub_addr : 7;
    uint32_t port_number : 7;
    uint32_t mult : 2;
};

// Queue Head data structure
typedef struct __attribute__((__packed__))
{
    // DWORD 0
    struct Link_Pointer_DWord next_qh;

    // DWORD 1
    struct Device_Address_DWord device_dword;

    // DWORD 2
    struct Mask_DWord mask_dword;

    // DWORD 3 : Current qTD Pointer
    uint32_t cur_qtd_ptr;

    // DWORD 4~11 : overlay of qTD
    uint32_t overlay_next_qtd;        // qTD dword 0
    uint32_t overlay_alternate_qtd;   // qTD dword 1
    uint32_t overlay_dword_others[6]; // qTD dword 2~7, dont care

    // ======== below is for internal metada ========
    uint32_t prev_link_ptr;     // make it double linked list
    uint32_t all_txfered_bytes; // record all txfered bytes
    uint8_t is_txfering;        // mark it '1' if this QH is doing transfer, otherwise '0'
    uint8_t in_use;
    uint8_t endpoint_type; // control/bulk/interrupt/isochronous
    // ==============================================

} Queue_Head;

static bool hw_initialized = false;
static uint32_t QH_addr[QH_NUM];
static uint32_t qTD_addr[qTD_NUM];
static uint32_t iTD_addr[iTD_MAX_NUM];
static uint32_t *periodic_frame_list = 0;

static ARM_USBH_SignalPortEvent_t notify_port_event_cb = 0;
static ARM_USBH_SignalPipeEvent_t notify_pipe_event_cb = 0;

#ifdef KNERON_USBH_MDW

typedef uint32_t (*ARM_USBH_ISOCH_ITD_WORK)();
static uint32_t usbh_handle_iTD_work(void);

static ARM_USBH_ISOCH_DATA_CALLBACK notify_isoch_data_cb = 0;
static ARM_USBH_ISOCH_ITD_WORK isoch_process_itd_work = usbh_handle_iTD_work; // it can be replaced by user bottom-half callback

// below are all for iTD management
static uint32_t isoch_running_flag = 0;     // ISOCH periodic schelde running flag
static uint32_t isoch_next_insert_pos = 0;  // next inserted pos for iTD
static uint32_t isoch_next_process_pos = 0; // next processing pos for iTD
static uint8_t isoch_trans_interval = 0;    // transaction interval in a iTD
static uint32_t isoch_txfer_len = 0;        // total transfer length
static uint8_t isoch_iTD_interval = 0;      // iTD interval in the periodic frame list
static uint8_t isoch_IOC_pos = 0;           // position of the IOC flag
static uint32_t isoch_iTD_num = 0;          // number of iTD

#endif

/* Driver Version */
static const ARM_DRIVER_VERSION usbh_driver_version = {
    ARM_USBH_API_VERSION,
    ARM_USBH_DRV_VERSION};

/* Driver Capabilities */
static const ARM_USBH_CAPABILITIES usbh_driver_capabilities = {
    0x0001U, /* Root HUB available Ports Mask   */
    0U,      /* Automatic SPLIT packet handling */
    1U,      /* Signal Connect event */
    1U,      /* Signal Disconnect event */
    0U,      /* Signal Overcurrent event */
    0U       /* Reserved */
};

static void fotg210_ehci_USBCMD_schedule_set_enable(EHCI_SCHEDULE_TYPE schedule_type, bool enable)
{

    uint32_t usbsts = UsbRegRead(EHCI_USBSTS);

    uint32_t bit_check;
    if (schedule_type == ASCH_EN)
        bit_check = BIT15;
    else
        bit_check = BIT14;

    if (enable)
    {
        if (usbsts & bit_check)
            return;

        UsbRegMaskedSet(EHCI_USBCMD, schedule_type);

        // polling wait for status change
        while ((UsbRegRead(EHCI_USBSTS) & bit_check) == 0)
            ;
    }
    else
    {
        if ((usbsts & bit_check) == 0)
            return;

        UsbRegMaskedClr(EHCI_USBCMD, schedule_type);

        // polling wait for status change
        while ((UsbRegRead(EHCI_USBSTS) & bit_check) != 0)
            ;
    }
}

static void fotg210_ehci_USBCMD_HC_run()
{
    // According to EHCI spec, doing USBCMD Run only if USBSTS is in HCHalted
    if (UsbRegRead(EHCI_USBSTS) & HCHalted)
    {
        UsbRegMaskedSet(EHCI_USBCMD, RS);

        // polling wait for !HCHalted
        while ((UsbRegRead(EHCI_USBSTS) & HCHalted))
            ;
    }
}

static void fotg210_ehci_USBCMD_HC_stop()
{
    if (!(UsbRegRead(EHCI_USBSTS) & HCHalted))
    {
        // first disable all scheduling works
        fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);
        fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);

        UsbRegMaskedClr(EHCI_USBCMD, RS);

        // polling wait for HCHalted
        while (!(UsbRegRead(EHCI_USBSTS) & HCHalted))
            ;
    }
}

static void fotg210_ehci_data_struct_from_ddr()
{
    // alloacte a pool of memory from DDR

    uint32_t ehci_struct_ddr_addr = 0;                                                                      // all data structures begin address in DDR
    uint32_t needed_size = QH_NUM * QH_ALLOC_IZE + qTD_NUM * qTD_ALLOC_SIZE + iTD_MAX_NUM * iTD_SIZE + 128; // 128 just for more space

    ehci_struct_ddr_addr = kmdw_ddr_reserve(needed_size) & (~0x1F);

    memset((void *)ehci_struct_ddr_addr, 0, needed_size);

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s() allocate ddr mem %d bytes\n", __FUNCTION__, needed_size);
    kmdw_printf("@@ Q_TD size %d, Queue Head size %d\n", sizeof(Q_TD), sizeof(Queue_Head));
#endif

    QH_addr[0] = ehci_struct_ddr_addr;
    for (int i = 1; i < QH_NUM; i++)
    {
        QH_addr[i] = QH_addr[i - 1] + QH_ALLOC_IZE;
    }

    qTD_addr[0] = QH_addr[QH_NUM - 1] + QH_ALLOC_IZE;
    for (int i = 1; i < qTD_NUM; i++)
    {
        qTD_addr[i] = qTD_addr[i - 1] + qTD_ALLOC_SIZE;
    }

    iTD_addr[0] = qTD_addr[QH_NUM - 1] + qTD_ALLOC_SIZE;
    for (int i = 1; i < iTD_MAX_NUM; i++)
    {
        iTD_addr[i] = iTD_addr[i - 1] + iTD_SIZE;
    }

    periodic_frame_list = (uint32_t *)kmdw_ddr_reserve(2 * sizeof(uint32_t) * FRAME_LIST_SIZE); // FIXME: double the allocated size due to 4-KB alignment
    periodic_frame_list = (uint32_t *)(((uint32_t)periodic_frame_list & (~0xFFF)) + 0x1000);   // now make periodic_frame_list begin from 4-KB boundray address

    for (int i = 0; i < FRAME_LIST_SIZE; i++)
    {
        periodic_frame_list[i] = 0x1; // mark T-bit as 1 indicating invalid pointers
    }
}

// USB host ISR
static void usbh_driver_isr(void)
{
    // read the USB Status register
    uint32_t usbsts = UsbRegRead(EHCI_USBSTS);

    // USB transaction completeion
    if (usbsts & USB_INT)
    {
        uint32_t txfer_complt = 0; // is any transfer completed ?

#ifdef KNERON_USBH_MDW

        // CMSIS middleware does not support ISOCH transfer
        if (isoch_running_flag)
        {
            txfer_complt = isoch_process_itd_work();
        }

#endif

        // scan all QHs
        for (int i = 0; i < QH_NUM; i++)
        {
            Queue_Head *qh = (Queue_Head *)QH_addr[i];
            if (qh->is_txfering)
            {
                Q_TD *qTD = (Q_TD *)(qh->cur_qtd_ptr & (~0x1F));

                if (qTD && qTD->in_use == true)
                {
                    uint8_t status_bits = qTD->status_dword.status;
                    if (status_bits <= 0x1) // 0 or 1
                    {
                        qh->is_txfering = false;
                        qTD->in_use = false;
                        qh->all_txfered_bytes += (qTD->wanted_txfer_bytes - qTD->status_dword.total_bytes_txfer);

                        // notify middleware of the completeion
                        notify_pipe_event_cb((uint32_t)qh, ARM_USBH_EVENT_TRANSFER_COMPLETE);

                        txfer_complt = 1;
                    }
                    else if (status_bits == 0x80) // still in Active ? guess DDR update slower ?
                    {
                        // A workaround, we skip this interrupt
                        // just do nothing, keep 'txfer_complt = 0'
                    }
                    else
                    {
#ifdef USBH_DRV_ERR
                        kmdw_printf("-- QH 0x%p Q_TD 0x%p error, status 0x%x, CERR 0x%x, in_use %d\n", qh, qTD, qTD->status_dword.status, qTD->status_dword.CERR, qTD->in_use);
#endif
                        txfer_complt = 1;
                    }
                }
            }
        }

        if (txfer_complt > 0) // a workaround for ZLP interrupt coming prior to status bit update in DDR
            UsbRegMaskedSet(EHCI_USBSTS, USB_INT);
    }

    // USB transaction error
    if (usbsts & USBERR_INT)
    {
#ifdef USBH_DRV_DBG
        kmdw_printf("-- transaction error\n");
#endif
        UsbRegMaskedSet(EHCI_USBSTS, USBERR_INT);
	//		UsbRegMaskedSet(EHCI_USBCMD, HC_RESET);
    }

    // port change detected
    if (usbsts & PO_CHG_DET)
    {
#ifdef USBH_DRV_DBG
        kmdw_printf("-- port change detected\n");
#endif

        // read the PORTSC
        uint32_t portsc = UsbRegRead(EHCI_PORTSC);

#ifdef USBH_DRV_DBG
        kmdw_printf("@@ %s(), portsc: 0x%x\n", __FUNCTION__, portsc);
#endif
        // check if connect status change in PORTSC
        if (portsc & CONN_CHG)
        {
            // write 1 to clear bit
            UsbRegMaskedSet(EHCI_PORTSC, CONN_CHG);

#ifdef USBH_DRV_DBG
            kmdw_printf("-- notify ARM_USBH_EVENT_CONNECT/DISCONNECT\n");
#endif

            // call back for USB middleware
            if (portsc & CONN_STS) {
                notify_port_event_cb(0, ARM_USBH_EVENT_CONNECT);
						}
            else {
                notify_port_event_cb(0, ARM_USBH_EVENT_DISCONNECT);
            }
        }
        // Port changed in USBSTS but no connection change in PORTSC
        // so guess it could be a port reset
        else
        {
#ifdef USBH_DRV_DBG
            kmdw_printf("-- notify ARM_USBH_EVENT_RESET\n");
#endif
            notify_port_event_cb(0, ARM_USBH_EVENT_RESET);
        }

        UsbRegMaskedSet(EHCI_USBSTS, PO_CHG_DET);
    }

    // host system error
    if (usbsts & H_SYSERR)
    {
/* serious error here */
#ifdef USBH_DRV_DBG
        kmdw_printf("--  host system error\n");
#endif

        UsbRegMaskedSet(EHCI_USBSTS, H_SYSERR);
    }

    // async schedule advanced
    if (usbsts & INT_OAA)
    {
#ifdef USBH_DRV_DBG
        kmdw_printf("-- async schedule advanced\n");
#endif

        UsbRegMaskedSet(EHCI_USBSTS, INT_OAA);
    }

    //NVIC_EnableIRQ(OTG_SBS_3_IRQ);
}

//
// Functions
//

// done
static ARM_DRIVER_VERSION usbh_driver_get_version(void)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif
    return usbh_driver_version;
}

// done
static ARM_USBH_CAPABILITIES usbh_driver_get_capabilities(void)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif
    return usbh_driver_capabilities;
}

static int32_t usbh_driver_initialize(ARM_USBH_SignalPortEvent_t cb_port_event, ARM_USBH_SignalPipeEvent_t cb_pipe_event)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    if (hw_initialized)
    {
        return ARM_DRIVER_OK;
    }

    // ask for DDR memory for EHCI data structures
    fotg210_ehci_data_struct_from_ddr();

    notify_port_event_cb = cb_port_event;
    notify_pipe_event_cb = cb_pipe_event;

    NVIC_SetVector(OTG_SBS_3_IRQ, (uint32_t)usbh_driver_isr);

    // Clear and Enable IRQ
    NVIC_ClearPendingIRQ(OTG_SBS_3_IRQ);
    NVIC_EnableIRQ(OTG_SBS_3_IRQ);

    // set up only host interrupt
    UsbRegWrite(REG_GLB_INT, 0x3);

    // enable chip
    UsbRegMaskedSet(REG_DEV_CTL, BIT5);

    // USBCMD - reset host controller
    UsbRegMaskedSet(EHCI_USBCMD, HC_RESET);

    // USBCMD - polling to wait for the reset complete done
    while ((UsbRegRead(EHCI_USBCMD) & HC_RESET) > 0)
        ;

    // enable EHCI USBINTR
    UsbRegWrite(EHCI_USBINTR, H_SYSERR_EN | PO_CHG_INT_EN | USBERR_INT_EN | USB_INT_EN);

    // zero Async List Addr before first QH is inserted
    UsbRegWrite(EHCI_ASYNC_LIST_ADDR, 0x0);

    // init periodic Aist Addr
    UsbRegWrite(EHCI_PERIODIC_LIST_ADDR, (uint32_t)&periodic_frame_list[0]);

    hw_initialized = true;

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_uninitialize(void)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    // FIXME: cannot free DDR memory

    return ARM_DRIVER_ERROR;
}

static int32_t usbh_driver_power_control(ARM_POWER_STATE state)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    switch (state)
    {
    case ARM_POWER_OFF:
        break;

    case ARM_POWER_LOW:
        break;

    case ARM_POWER_FULL:

        // USBCMD - Run HC
        fotg210_ehci_USBCMD_HC_run();

        break;

    default:
        return ARM_DRIVER_ERROR_UNSUPPORTED;
    }

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_vbus_on_off(uint8_t port, bool vbus)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    if (vbus == true)
    {
        // turn on VBUS
        UsbRegMaskedClr(REG_OTG_CSR, A_BUS_DROP);
        UsbRegMaskedSet(REG_OTG_CSR, A_BUS_REQ);
    }
    else
    {
        // turn off VBUS
        UsbRegMaskedClr(REG_OTG_CSR, A_BUS_REQ);
        UsbRegMaskedSet(REG_OTG_CSR, A_BUS_DROP);
    }

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_port_reset(uint8_t port)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    //NVIC_DisableIRQ(OTG_SBS_3_IRQ);

    // stop host controller
    fotg210_ehci_USBCMD_HC_stop();

    // port disable
    UsbRegMaskedClr(EHCI_PORTSC, PO_EN);

    // holding port reset for 55 ms
    UsbRegMaskedSet(EHCI_PORTSC, PO_RESET);
    osDelay(55);
    UsbRegMaskedClr(EHCI_PORTSC, PO_RESET);

    // run host controller again
    fotg210_ehci_USBCMD_HC_run();

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_port_suspend(uint8_t port)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    // make sure HC is running
    if (UsbRegRead(EHCI_USBSTS) & HCHalted)
        return ARM_DRIVER_ERROR;

    // make sure port is enabled
    if (!(UsbRegRead(EHCI_PORTSC) & PO_EN))
        return ARM_DRIVER_ERROR;

    // disable all scheduling works
    fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);
    fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);

    fotg210_ehci_USBCMD_HC_stop(); // due to Faraday

    // suspend port according to EHCI spec
    UsbRegMaskedSet(EHCI_PORTSC, PO_SUSP);

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_port_resume(uint8_t port)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    // make sure port is suspending
    if (!(UsbRegRead(EHCI_PORTSC) & PO_SUSP))
        return ARM_DRIVER_ERROR;

    osDelay(10); // due to Faraday

    // resume port according to EHCI spec
    UsbRegMaskedSet(EHCI_PORTSC, F_PO_RESM);

    osDelay(20); // due to Faraday

    UsbRegMaskedClr(EHCI_PORTSC, F_PO_RESM); // due to Faraday ??

    fotg210_ehci_USBCMD_HC_run(); // due to Faraday

    // PORTSC - polling to wait for the exit from suspend status
    while ((UsbRegRead(EHCI_PORTSC) & PO_SUSP))
        ;

    fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
    fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);

    return ARM_DRIVER_OK;
}

static ARM_USBH_PORT_STATE usbh_driver_port_get_state(uint8_t port)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    ARM_USBH_PORT_STATE port_state;

    port_state.connected = UsbRegRead(EHCI_PORTSC) & CONN_STS;
    port_state.overcurrent = !!(UsbRegRead(REG_OTG_ISR) & OTG_OVC_RW1C);

    // refer to the register OTG_CSR in FOTG210 bloack data sheet
    uint32_t speed = (UsbRegRead(REG_OTG_CSR) >> 22) & 0x3;
    switch (speed)
    {
    case 0x00:
        port_state.speed = ARM_USB_SPEED_FULL;
        break;
    case 0x01:
        port_state.speed = ARM_USB_SPEED_LOW;
        break;
    case 0x02:
        port_state.speed = ARM_USB_SPEED_HIGH;
        break;
    }

    return port_state;
}

#define HORI_LINK_TPYE_iTD 0x0
#define HORI_LINK_TPYE_QH 0x1

// this pipe create is only for Control/Bulk/Interrupt transfer
// so 'ep_type' can only be ARM_USB_ENDPOINT_CONTROL, ARM_USB_ENDPOINT_BULK or ARM_USB_ENDPOINT_INTERRUPT
static ARM_USBH_PIPE_HANDLE usbh_driver_pipe_create(uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr,
                                                    uint8_t hub_port, uint8_t ep_addr, uint8_t ep_type,
                                                    uint16_t ep_max_packet_size, uint8_t ep_interval)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s() ep_addr 0x%x ep_type 0x%x ep_max_packet %u ep_interval %u\n", __FUNCTION__, ep_addr, ep_type, ep_max_packet_size, ep_interval);
#endif

    // insert a new QH into the async scheudle list without Q_TD

    Queue_Head *qh = 0;
    uint32_t idx;

    // find a free QH
    for (idx = 0; idx < QH_NUM; idx++)
    {
        qh = (Queue_Head *)QH_addr[idx];
        // check in-use or not
        if (qh->in_use == false)
        {
            // fill up the QH
            memset(qh, 0, QH_REAL_SIZE);
            qh = (Queue_Head *)QH_addr[idx];
            qh->in_use = true;
            break;
        }
    }

#ifdef USBH_DRV_ERR
    if (idx >= QH_NUM)
    {
        kmdw_printf("@@ %s() no more QH !!!\n");
        return NULL;
    }
#endif

    // DWORD 0

    // DWORD 1
    qh->device_dword.dev_addr = dev_addr;
    qh->device_dword.endpoint = ep_addr & 0xF;
    qh->device_dword.endpt_speed = dev_speed;
    qh->device_dword.dtc = 1;           // ????
    qh->device_dword.H_reclamation = 0; // ????
    qh->device_dword.max_packet_length = ep_max_packet_size;

    if (ep_addr != 0x0)
        qh->device_dword.dtc = 0; // test

    // DWORD 2
    qh->mask_dword.hub_addr = hub_addr;
    qh->mask_dword.port_number = hub_port;

    // Q_TD overlay
    qh->overlay_next_qtd |= 0x1;    // T-bit = 1
    qh->overlay_alternate_qtd |= 1; // T-bit = 1

    // internal use
    qh->all_txfered_bytes = 0;
    qh->endpoint_type = ep_type;

    if (ep_type == ARM_USB_ENDPOINT_CONTROL || ep_type == ARM_USB_ENDPOINT_BULK)
    {

        // let's add this QH into the first position
        if (UsbRegRead(EHCI_ASYNC_LIST_ADDR) == 0x0)
        {
            // the very first QH
            qh->next_qh.type = HORI_LINK_TPYE_QH; // QH type
            //qh->next_qh.terminate = 0; // default
            qh->next_qh.ptr_address = ((uint32_t)qh >> 5);
            qh->prev_link_ptr = (uint32_t)qh;
            UsbRegWrite(EHCI_ASYNC_LIST_ADDR, (uint32_t)qh);
        }
        else
        {
            // Insert the new QH into the first position
            Queue_Head *qh_cur = (Queue_Head *)UsbRegRead(EHCI_ASYNC_LIST_ADDR);
            Queue_Head *qh_next = (Queue_Head *)(qh_cur->next_qh.ptr_address << 5);

            qh->next_qh.ptr_address = ((uint32_t)qh_next >> 5);
            qh->prev_link_ptr = (uint32_t)qh_cur;

            qh_next->prev_link_ptr = (uint32_t)qh;
            qh_cur->next_qh.ptr_address = ((uint32_t)qh >> 5);
        }

        // enable schedules only if dev_speed = High Speed (0x2), FIXME
        if (dev_speed == ARM_USB_SPEED_HIGH)
            fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
    }
    else if (ep_type == ARM_USB_ENDPOINT_INTERRUPT) // FIXME: now it supports only one QH
    {
        if (ep_interval < 4)
            ep_interval = 4; // FIXME, we support minimum interval is 1ms

        uint32_t intv_per_ms = (0x1 << (ep_interval - 4));

        qh->next_qh.type = HORI_LINK_TPYE_QH; // QH type
        qh->next_qh.terminate = 1;            // no other QH
        qh->mask_dword.mult = 0x1;
        qh->mask_dword.S_mask = 0x1; // indicate that trigger at Frindex 000b

        for (int i = 0; i < FRAME_LIST_SIZE; i += intv_per_ms)
        {
#ifdef USBH_DRV_DBG
            // check T-bit
            if (periodic_frame_list[i] != 0x1)
                kmdw_printf("warning: this frame list link pointer is occupied!! idx = %u\n", i);
#endif

            periodic_frame_list[i] = (uint32_t)qh | (HORI_LINK_TPYE_QH << 1); // setting Frame List Link Pointer T-bit as '0'
        }

        // enable schedules only if dev_speed = High Speed (0x2), FIXME
        if (dev_speed == ARM_USB_SPEED_HIGH)
            fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
    }
    else // Isochronous transfer
    {
    }

    return (uint32_t)qh;
}

static int32_t usbh_driver_pipe_modify(ARM_USBH_PIPE_HANDLE pipe_hndl, uint8_t dev_addr,
                                       uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint16_t ep_max_packet_size)
{
    // assume only control transfer do this

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

    Queue_Head *qh = (Queue_Head *)pipe_hndl;

    // DWORD 0
    qh->next_qh.type = 0x1; // QH type

    // DWORD 1
    qh->device_dword.dev_addr = dev_addr;
    qh->device_dword.endpt_speed = dev_speed;
    qh->device_dword.max_packet_length = ep_max_packet_size;

    // DWORD 2
    qh->mask_dword.hub_addr = hub_addr;
    qh->mask_dword.port_number = hub_port;

    // enable schedules only if dev_speed = High Speed (0x2), FIXME
    if (dev_speed == ARM_USB_SPEED_HIGH)
    {
        fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
        fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
    }

    return ARM_DRIVER_OK;
}

//  for data SRAM, the address must be remapped, FIXME!!!!!!
uint32_t scpu_remap_addr(uint32_t addr)
{
    uint32_t tmp;

    if ((addr & (SdRAM_MEM_BASE)) == SdRAM_MEM_BASE)
    {
        tmp = ((addr) & (~0x10000000)) | 0x20000000;
        return tmp;
    }
    return addr;
}

static int32_t usbh_driver_pipe_transfer(ARM_USBH_PIPE_HANDLE pipe_hndl, uint32_t packet, uint8_t *data, uint32_t num)
{

    Queue_Head *qh = (Queue_Head *)pipe_hndl;

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s() pipe_hndl %p packet 0x%x data %p num %u\n", __FUNCTION__, (void *)pipe_hndl, packet, data, num);

    if (qh->is_txfering)
    {
        kmdw_printf("@@ error: pipe 0x%p is already doing transfer, %x\n", (void *)pipe_hndl, qh->is_txfering);
        return ARM_DRIVER_ERROR;
    }
#endif

    Q_TD *qTD = 0;
    uint32_t qtd_idx;

    // find a free Q_TD
    for (qtd_idx = 0; qtd_idx < qTD_NUM; qtd_idx++)
    {
        qTD = (Q_TD *)qTD_addr[qtd_idx];
        // check in-use or not
        if (qTD->in_use == false)
        {
            memset(qTD, 0, qTD_REAL_SIZE);
            qTD->in_use = true;
            break;
        }
    }

    // no more Q_TD available
    if (qtd_idx >= qTD_NUM)
        return ARM_DRIVER_ERROR;

    qTD->next_qtd.terminate = 1;
    qTD->alt_next_qtd.terminate = 1;

    switch (packet & ARM_USBH_PACKET_TOKEN_Msk)
    {
    case ARM_USBH_PACKET_OUT:
        qTD->status_dword.PID = 0x0;
        break;
    case ARM_USBH_PACKET_IN:
        qTD->status_dword.PID = 0x1;
        break;
    case ARM_USBH_PACKET_SETUP:
        qTD->status_dword.PID = 0x2;
        break;
    }

    switch (packet & ARM_USBH_PACKET_DATA_Msk)
    {
    case ARM_USBH_PACKET_DATA0:
        qTD->status_dword.dt = 0;
        break;
    case ARM_USBH_PACKET_DATA1:
        qTD->status_dword.dt = 1;
        break;
    }

    qTD->status_dword.CERR = 0x0; // FIXME: it 0x3 OK ?
    qTD->status_dword.c_page = 0;
    qTD->status_dword.ioc = 1;
    qTD->status_dword.total_bytes_txfer = num;

    // internal use, not part of EHCI
    qTD->wanted_txfer_bytes = num;

    // set up data 4K address
    uint32_t data_addr = scpu_remap_addr((uint32_t)data);

    // get buf 0 offset
    qTD->buf_ptr_dword[0].cur_offset = data_addr & 0xFFF;

    // make it as 4K page unit
    data_addr >>= 12;

    // whatever the txfer size is, we here fill up all buffer pointer fields
    for (int i = 0; i < 5; i++)
        qTD->buf_ptr_dword[i].ptr_address = data_addr + i;

    // then insert this Q_TD into pipe QH

    qh->overlay_next_qtd = (uint32_t)(qTD) & (~0x1F);
    qh->overlay_alternate_qtd = (uint32_t)(qTD) & (~0x1F);

    qh->all_txfered_bytes = 0; // FIXME: clear 0 here is ok ?

    qh->is_txfering = true; // internal use to mark it is doing transfer

    // set Activate bit, HC shall begin the transaction very soon
    qTD->status_dword.status = 0x80;

    return ARM_DRIVER_OK;
}

static uint32_t usbh_driver_pipe_transfer_get_result(ARM_USBH_PIPE_HANDLE pipe_hndl)
{
    Queue_Head *qh = (Queue_Head *)pipe_hndl;

    uint32_t txfer_bytes = qh->all_txfered_bytes;

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s() bytes: %d\n", __FUNCTION__, txfer_bytes);
#endif
    return txfer_bytes;
}

static int32_t usbh_driver_pipe_transfer_abort(ARM_USBH_PIPE_HANDLE pipe_hndl)
{
    // FIXME: this function implementation is not complete

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

#ifdef USBH_DRV_FULL

    Queue_Head *qh = (Queue_Head *)pipe_hndl;

    if (qh->endpoint_type != ARM_USB_ENDPOINT_ISOCHRONOUS)
    {
        // handle QH and qTD
        if (qh->is_txfering) // then there is a running qTD
        {
            Q_TD *qTD = (Q_TD *)(qh->overlay_next_qtd & (~0x1F));

            // clear Active bit
            qTD->status_dword.status &= (~0x80);

            qh->is_txfering = false;
            qTD->in_use = false;
        }
    }
    else
    {
        // handle iTD
    }

    return ARM_DRIVER_OK;
#else
    return ARM_DRIVER_ERROR;
#endif
}

static int32_t usbh_driver_pipe_reset(ARM_USBH_PIPE_HANDLE pipe_hndl)
{
    // FIXME: do the same as usbh_driver_pipe_transfer_abort() ?

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

#ifdef USBH_DRV_FULL
    return usbh_driver_pipe_transfer_abort(pipe_hndl);
#else
    return ARM_DRIVER_ERROR;
#endif
}

static int32_t usbh_driver_pipe_delete(ARM_USBH_PIPE_HANDLE pipe_hndl)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

#ifdef USBH_DRV_FULL

    Queue_Head *qh = (Queue_Head *)pipe_hndl;

    // report error if this pipe is still doing transfers or it is already out of schedule
    // user should invoke usbh_driver_pipe_transfer_abort() before this
    if (qh->is_txfering || !qh->in_use)
        return ARM_DRIVER_ERROR;

    // first check the pipe (endpoint) is async or periodic
    if (qh->endpoint_type == ARM_USB_ENDPOINT_CONTROL || ARM_USB_ENDPOINT_BULK)
    {
        // async schedule

        // stop async schedule for safety, FIXME: really need this ?
        fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);

        Queue_Head *qh_next = (Queue_Head *)(qh->next_qh.ptr_address << 5);

        if (qh_next == qh)
        {
            // in this case only it is the last QH in the scheudle
            // clear the ASYNCLISTADDR register
            UsbRegWrite(EHCI_ASYNC_LIST_ADDR, 0x0);
        }
        else
        {
            // remove it from schedule linked list
            Queue_Head *qh_prev = (Queue_Head *)(qh->prev_link_ptr);
            qh_prev->next_qh.ptr_address = ((uint32_t)qh_next) >> 5;
            qh_next->prev_link_ptr = (uint32_t)qh_prev;

            if (UsbRegRead(EHCI_ASYNC_LIST_ADDR) == (uint32_t)qh)
                UsbRegWrite(EHCI_ASYNC_LIST_ADDR, (uint32_t)qh_next);

            // re-enable the async schedule
            fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
        }

        qh->in_use = false;
    }
    else
    {
        // periodic scheudle
        // ARM_USB_ENDPOINT_ISOCHRONOUS or ARM_USB_ENDPOINT_INTERRUPT

        // stop periodic schedule for safety, FIXME: really need this ?
        fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);

        // FIXME: for now only one endpoint in the periodic schedule is allowed
        // FIXME: below way of handling is not enough

        // search all existence of this QH from periodic scheudle
        for (int i = 0; i < FRAME_LIST_SIZE; i++)
            periodic_frame_list[i] = 0x1;

        qh->in_use = false;
    }

    return ARM_DRIVER_OK;
#else
    return ARM_DRIVER_ERROR;
#endif
}

static uint16_t usbh_driver_get_frame_number(void)
{
#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s()\n", __FUNCTION__);
#endif

#ifdef USBH_DRV_FULL
    return (UsbRegRead(EHCI_FRINDEX) & 0x1FFF >> 3);
#else
    return 0;
#endif
}

// code under this define is a extenion set only for Kneron's own USBH middlware
#ifdef KNERON_USBH_MDW

// use this for Isochronous endpoint
// 'ep_addr' should also include direction bit
// 'ep_max_packet_mult' is directly from the 'wMaxPacketSize' of an Isoch endpoint descriptor
// NOTE: this driver supports only one isoch endpoint
static ARM_USBH_PIPE_HANDLE usbh_driver_pipe_isoch_create(uint8_t dev_addr, uint8_t ep_addr,
                                                          uint16_t max_packet_size, uint8_t mult, uint8_t ep_interval,
                                                          uint8_t *buf, uint32_t buf_size)
{

    // set up first iTD
    I_TD *iTD_0 = (I_TD *)iTD_addr[0];

    // clear all iTDs
    memset(iTD_0, 0, sizeof(I_TD));

    iTD_0->next_link.terminate = 1;

    struct Buffer_Page_0_DWord *dev_dword = (struct Buffer_Page_0_DWord *)&(iTD_0->buf_page_dword[0]);
    dev_dword->dev_addr = dev_addr;
    dev_dword->endpoint = ep_addr & 0xF;

    struct Buffer_Page_1_DWord *maxpack_dword = (struct Buffer_Page_1_DWord *)&(iTD_0->buf_page_dword[1]);
    maxpack_dword->direction = !!(ep_addr & 0x80);
    maxpack_dword->max_packet_size = max_packet_size; // USB 2.0 spec Table 9-13 and EHCI 1.0 spec Table 3-5

    struct Buffer_Page_2_DWord *mult_dword = (struct Buffer_Page_2_DWord *)&(iTD_0->buf_page_dword[2]);
    mult_dword->mult = mult; // USB 2.0 spec Table 9-13 and EHCI 1.0 spec Table 3-6

    // calcualte intervals
    isoch_trans_interval = (1 << (ep_interval - 1));      // SOF interval, USB 2.0 spec Table 9-13
    isoch_iTD_interval = (isoch_trans_interval >> 4) + 1; // calculate interval of iTD, min is 1

    // calculate needed buffer size for a iTD
    uint8_t num_of_sof;
    switch (isoch_trans_interval)
    {
    case 1:
        num_of_sof = 8;
        isoch_IOC_pos = 7;
        break;
    case 2:
        num_of_sof = 4;
        isoch_IOC_pos = 6;
        break;
    case 4:
        num_of_sof = 2;
        isoch_IOC_pos = 4;
        break;
    default:
        num_of_sof = 1;
        isoch_IOC_pos = 4;
        break;
    }

    isoch_txfer_len = (max_packet_size * mult); // for example: 1020 x 3
    uint32_t buf_per_iTD = (num_of_sof * isoch_txfer_len);

    isoch_iTD_num = (buf_size / buf_per_iTD); // now we know how many iTDs needed

    int i;
    // let's use PG field to indicate if this transaction is needed according to isoch_trans_interval
    for (i = 0; i < 8; i++)
        iTD_0->trans_dword[i].pg = 0x7; // 0x7 to indicate NOT in-use

    for (i = 0; i < 8; i += isoch_trans_interval)
    {
        iTD_0->trans_dword[i].length = isoch_txfer_len;
        iTD_0->trans_dword[i].pg = 0;
        iTD_0->trans_dword[i].status = 0x8; // pre-activate it
    }
    // only 1 ioc in a iTD
    iTD_0->trans_dword[isoch_IOC_pos].ioc = 1; // set interrupt only for last transaction, interrtup per 1 ms

    // error checking: isoch_iTD_num must <= iTD_MAX_NUM

    uint32_t data_addr = (uint32_t)buf;

    // memory copy to all other iTDs
    for (i = 0; i < isoch_iTD_num; i++)
    {
        I_TD *iTD = (I_TD *)iTD_addr[i];
        memcpy(iTD, iTD_0, sizeof(I_TD));

        // layout memory
        iTD->trans_dword[0].offset = data_addr & 0xFFF;
        iTD->trans_dword[0].pg = 0;
        iTD->buf_page_dword[0].ptr_address = (data_addr & (~0xFFF)) >> 12;

        uint8_t cp = 0; // curent page looking at

        for (int t = 1; t < 8; t += isoch_trans_interval)
        {
            data_addr += isoch_txfer_len;

            iTD->trans_dword[t].offset = data_addr & 0xFFF;

            uint32_t buf_ptr = (data_addr & (~0xFFF)) >> 12;
            if (buf_ptr != iTD->buf_page_dword[cp].ptr_address)
            {
                cp++;
                iTD->buf_page_dword[cp].ptr_address = buf_ptr;
            }
            iTD->trans_dword[t].pg = cp;
        }

        // below is kind of workaround, give a plus page
        if (cp < 6)
            iTD->buf_page_dword[cp + 1].ptr_address = 1 + iTD->buf_page_dword[cp].ptr_address;

        data_addr += isoch_txfer_len;
    }

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s() iTD_0 0x%p num %d buf 0x%p\n", __FUNCTION__, iTD_0, isoch_iTD_num, buf);
#endif

    return 0x1000F; // just return a value to represent the handle
}

// FIXME, for now it has conflits with interrupt transfer
static int32_t usbh_driver_pipe_isoch_start(ARM_USBH_PIPE_HANDLE pipe_hndl, ARM_USBH_ISOCH_DATA_CALLBACK isoch_data_cb)
{
    // pipe_hndl is not used because it supports only one ISOCH endpoint

    fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);

    isoch_next_process_pos = usbh_driver_get_frame_number() + 50; // insert iTD after 50 ms position
    isoch_next_process_pos &= FRAME_LIST_ROLL_MASK;

    isoch_next_insert_pos = isoch_next_process_pos;

    for (int i = 0; i < isoch_iTD_num; i++)
    {
        periodic_frame_list[isoch_next_insert_pos] = (uint32_t)iTD_addr[i]; // FIXME: should also take care of other iTD or QH here
        isoch_next_insert_pos += isoch_iTD_interval;
        isoch_next_insert_pos &= FRAME_LIST_ROLL_MASK;
    }

#ifdef USBH_DRV_DBG
    kmdw_printf("@@ %s(),isoch_iTD_num %d isoch_next_process_pos %d isoch_next_insert_pos = %d\n", __FUNCTION__, isoch_iTD_num, isoch_next_process_pos, isoch_next_insert_pos);
#endif

    fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);

    isoch_running_flag = 1; // FIXME

    // at least one of callback should not be NULL
    notify_isoch_data_cb = isoch_data_cb;

    return ARM_DRIVER_OK;
}

static int32_t usbh_driver_pipe_isoch_stop(ARM_USBH_PIPE_HANDLE pipe_hndl)
{
    // pipe_hndl is not used because it supports only one ISOCH endpoint

    fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);

    // just clear the frame link list
    for (int i = 0; i < FRAME_LIST_SIZE; i++)
    {
        periodic_frame_list[i] = 0x1; // mark T-bit as 1 indicating invalid pointers
    }

    isoch_running_flag = 0;

  //  fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
    return ARM_DRIVER_OK;
}

uint32_t usbh_handle_iTD_work(void)
{
    uint32_t handle_count = 0;

    // scan some number of iTDs, FIXME: why 100
    for (int32_t r = 0; r < 100; r++)
    {

        I_TD *iTD = (I_TD *)periodic_frame_list[isoch_next_process_pos];

        // find the completed iTD
        if ((periodic_frame_list[isoch_next_process_pos] & 0x3) == 0x0 &&
            iTD->trans_dword[isoch_IOC_pos].ioc == 0x1 &&
            iTD->trans_dword[isoch_IOC_pos].status == 0x0)
        {
            periodic_frame_list[isoch_next_process_pos] = 0x1; // set T-bit as 1 to dequeue this iTD

            for (int t = 0; t < 8; t += isoch_trans_interval)
            {
#ifdef USBH_DRV_ERR
                if (iTD->trans_dword[t].status != 0)
                    kmdw_printf("iTD %d trans %d error status = 0x%x\n", isoch_next_process_pos, t, iTD->trans_dword[t].status);
#endif
                // callback to uppper layer middleware
                uint8_t pg = iTD->trans_dword[t].pg;
                uint32_t ptr_base = iTD->buf_page_dword[pg].ptr_address;
                uint32_t ptr_offset = iTD->trans_dword[t].offset;
                uint32_t *ptr_buf = (uint32_t *)((ptr_base << 12) | ptr_offset);

                notify_isoch_data_cb(ptr_buf, iTD->trans_dword[t].length);

                iTD->trans_dword[t].length = isoch_txfer_len;
                iTD->trans_dword[t].status = 0x8; // pre-activate it
            }

            // inser iTD in the back, FIXME: there should a better policy
            periodic_frame_list[isoch_next_insert_pos] = (uint32_t)iTD;
            isoch_next_insert_pos += isoch_iTD_interval;
            isoch_next_insert_pos &= FRAME_LIST_ROLL_MASK;

            handle_count++;
        }

        isoch_next_process_pos += isoch_iTD_interval;
        isoch_next_process_pos &= FRAME_LIST_ROLL_MASK;
    }

    return handle_count;
}

// this function is optional, it is for registering bottom-half callback function
static ARM_USBH_ISOCH_ITD_WORK_FUNC usbh_driver_pipe_isoch_enable_bh(ARM_USBH_PIPE_HANDLE pipe_hndl, ARM_USBH_ISOCH_BH_CALLBACK isoch_bf_callback)
{
    // pipe_hndl is not used because it supports only one ISOCH endpoint

    isoch_process_itd_work = isoch_bf_callback;
    return usbh_handle_iTD_work;
}

#endif

// Structure exported by driver
ARM_DRIVER_USBH Driver_USBH0 = {
    usbh_driver_get_version,
    usbh_driver_get_capabilities,
    usbh_driver_initialize,
    usbh_driver_uninitialize,
    usbh_driver_power_control,
    usbh_driver_vbus_on_off,
    usbh_driver_port_reset,
    usbh_driver_port_suspend,
    usbh_driver_port_resume,
    usbh_driver_port_get_state,
    usbh_driver_pipe_create,
    usbh_driver_pipe_modify,
    usbh_driver_pipe_delete,
    usbh_driver_pipe_reset,
    usbh_driver_pipe_transfer,
    usbh_driver_pipe_transfer_get_result,
    usbh_driver_pipe_transfer_abort,
    usbh_driver_get_frame_number,
#ifdef KNERON_USBH_MDW
    usbh_driver_pipe_isoch_create,
    usbh_driver_pipe_isoch_start,
    usbh_driver_pipe_isoch_stop,
    usbh_driver_pipe_isoch_enable_bh
#endif
};
