/*
 * Copyright (c) 2021-2024 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
/*
 * Copyright (c) 2024, sakumisu
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include "usbd_core.h"
#include "usb_chipidea_reg.h"

#define USB_OTG_DEV ((CHIPIDEA_TypeDef *)g_usbdev_bus[busid].reg_base)

#define CHIPIDEA_BITSMASK(val, offset) ((uint32_t)(val) << (offset))
#define QTD_COUNT_EACH_ENDPOINT        (8U)

/* ENDPTCTRL */
enum {
    ENDPTCTRL_STALL = CHIPIDEA_BITSMASK(1, 0),
    ENDPTCTRL_TYPE = CHIPIDEA_BITSMASK(3, 2),
    ENDPTCTRL_TOGGLE_INHIBIT = CHIPIDEA_BITSMASK(1, 5),
    ENDPTCTRL_TOGGLE_RESET = CHIPIDEA_BITSMASK(1, 6),
    ENDPTCTRL_ENABLE = CHIPIDEA_BITSMASK(1, 7),
};

/* USBSTS, USBINTR */
enum {
    intr_usb = CHIPIDEA_BITSMASK(1, 0),
    intr_error = CHIPIDEA_BITSMASK(1, 1),
    intr_port_change = CHIPIDEA_BITSMASK(1, 2),
    intr_reset = CHIPIDEA_BITSMASK(1, 6),
    intr_sof = CHIPIDEA_BITSMASK(1, 7),
    intr_suspend = CHIPIDEA_BITSMASK(1, 8),
    intr_nak = CHIPIDEA_BITSMASK(1, 16)
};

/* Queue Transfer Descriptor */
typedef struct {
    /* Word 0: Next QTD Pointer */
    volatile uint32_t next; /* Next link pointer This field contains the physical memory address of the next dTD to be processed */

    /* Word 1: qTQ Token */
    volatile uint32_t                   : 3;
    volatile uint32_t xact_err          : 1;
    volatile uint32_t                   : 1;
    volatile uint32_t buffer_err        : 1;
    volatile uint32_t halted            : 1;
    volatile uint32_t active            : 1;
    volatile uint32_t                   : 2;
    volatile uint32_t iso_mult_override : 2; /* This field can be used for transmit ISOs to override the MULT field in the dQH. This field must be zero for all packet types that are not transmit-ISO. */
    volatile uint32_t                   : 3;
    volatile uint32_t int_on_complete   : 1;
    volatile uint32_t total_bytes       : 15;
    volatile uint32_t                   : 0;

    /* Word 2-6: Buffer Page Pointer List, Each element in the list is a 4K page aligned, physical memory address. The lower 12 bits in each pointer are reserved (except for the first one) as each memory pointer must reference the start of a 4K page */
    volatile uint32_t buffer[5];

    /*------------- DCD Area -------------*/
    volatile uint16_t expected_bytes;
    volatile uint8_t reserved[2];
} dcd_qtd_t;

/* Queue Head */
typedef struct {
    /* Word 0: Capabilities and Characteristics */

    volatile uint32_t                         : 15; /* Number of packets executed per transaction descriptor 00 - Execute N transactions as demonstrated by the USB variable length protocol where N is computed using Max_packet_length and the Total_bytes field in the dTD. 01 - Execute one transaction 10 - Execute two transactions 11 - Execute three transactions Remark: Non-isochronous endpoints must set MULT = 00. Remark: Isochronous endpoints must set MULT = 01, 10, or 11 as needed. */
    volatile uint32_t int_on_setup            : 1;  /* Interrupt on setup This bit is used on control type endpoints to indicate if USBINT is set in response to a setup being received. */
    volatile uint32_t max_packet_size         : 11; /* This directly corresponds to the maximum packet size of the associated endpoint (wMaxPacketSize) */
    volatile uint32_t                         : 2;
    volatile uint32_t zero_length_termination : 1; /* This bit is used for non-isochronous endpoints to indicate when a zero-length packet is received to terminate transfers in case the total transfer length is “multiple”. 0 - Enable zero-length packet to terminate transfers equal to a multiple of Max_packet_length (default). 1 - Disable zero-length packet on transfers that are equal in length to a multiple Max_packet_length. */
    volatile uint32_t iso_mult                : 2;
    volatile uint32_t                         : 0;

    /* Word 1: Current qTD Pointer */
    volatile uint32_t qtd_addr;

    /* Word 2-9: Transfer Overlay */
    volatile dcd_qtd_t qtd_overlay;

    /* Word 10-11: Setup request (control OUT only) */
    volatile struct usb_setup_packet setup_request;

    /*--------------------------------------------------------------------
     * Due to the fact QHD is 64 bytes aligned but occupies only 48 bytes
     * thus there are 16 bytes padding free that we can make use of.
     *--------------------------------------------------------------------
     */
    volatile uint8_t reserved[16];
} dcd_qhd_t;

typedef struct {
    dcd_qhd_t qhd[CONFIG_USBDEV_EP_NUM * 2];
    dcd_qtd_t qtd[CONFIG_USBDEV_EP_NUM * 2 * QTD_COUNT_EACH_ENDPOINT];
} dcd_data_t;

/* Endpoint state */
struct chipidea_ep_state {
    uint16_t ep_mps;    /* Endpoint max packet size */
    uint8_t ep_type;    /* Endpoint type */
    uint8_t ep_stalled; /* Endpoint stall flag */
    uint8_t ep_enable;  /* Endpoint enable */
    uint8_t *xfer_buf;
    uint32_t xfer_len;
    uint32_t actual_xfer_len;
};

/* Driver state */
struct chipidea_udc {
    dcd_data_t *dcd_data;
    bool is_suspend;
    struct chipidea_ep_state in_ep[CONFIG_USBDEV_EP_NUM];  /*!< IN endpoint parameters*/
    struct chipidea_ep_state out_ep[CONFIG_USBDEV_EP_NUM]; /*!< OUT endpoint parameters */
} g_chipidea_udc[CONFIG_USBDEV_MAX_BUS];

static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data0;
#if CONFIG_USBDEV_MAX_BUS == 2
static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data1;
#endif

static dcd_data_t *g_dcd_data[CONFIG_USBDEV_MAX_BUS] = {
    &_dcd_data0,
#if CONFIG_USBDEV_MAX_BUS == 2
    &_dcd_data1
#endif
};

/* Index to bit position in register */
static inline uint8_t ep_idx2bit(uint8_t ep_idx)
{
    return ep_idx / 2 + ((ep_idx % 2) ? 16 : 0);
}

static void __chipidea_bus_reset(CHIPIDEA_TypeDef *ptr)
{
    /* The reset value for all endpoint types is the control endpoint. If one endpoint
     * direction is enabled and the paired endpoint of opposite direction is disabled, then the
     * endpoint type of the unused direction must be changed from the control type to any other
     * type (e.g. bulk). Leaving an un-configured endpoint control will cause undefined behavior
     * for the data PID tracking on the active endpoint.
     */

    for (uint32_t i = 1; i < CONFIG_USBDEV_EP_NUM; i++) {
        ptr->ENDPTCTRL[i] = USB_ENDPTCTRL_TXT_SET(USB_ENDPOINT_TYPE_BULK) | USB_ENDPTCTRL_RXT_SET(USB_ENDPOINT_TYPE_BULK);
    }

    /* Clear All Registers */
    ptr->ENDPTNAK = ptr->ENDPTNAK;
    ptr->ENDPTNAKEN = 0;
    ptr->USBSTS = ptr->USBSTS;
    ptr->ENDPTSETUPSTAT = ptr->ENDPTSETUPSTAT;
    ptr->ENDPTCOMPLETE = ptr->ENDPTCOMPLETE;

    while (ptr->ENDPTPRIME) {
    }
    ptr->ENDPTFLUSH = 0xFFFFFFFF;
    while (ptr->ENDPTFLUSH) {
    }
}

static void chipidea_init(CHIPIDEA_TypeDef *ptr)
{
    /* Reset controller */
    ptr->USBCMD |= USB_USBCMD_RST_MASK;
    while (USB_USBCMD_RST_GET(ptr->USBCMD)) {
    }

    /* Set mode to device, must be set immediately after reset */
    ptr->USBMODE &= ~USB_USBMODE_CM_MASK;
    ptr->USBMODE |= USB_USBMODE_CM_SET(2);

    /* Disable setup lockout, please refer to "Control Endpoint Operation" section in RM. */
    ptr->USBMODE &= ~USB_USBMODE_SLOM_MASK;

    /* Set the endian */
    ptr->USBMODE &= ~USB_USBMODE_ES_MASK;

    /* Set parallel interface signal */
    ptr->PORTSC1 &= ~USB_PORTSC1_STS_MASK;

    /* Set parallel transceiver width */
    ptr->PORTSC1 &= ~USB_PORTSC1_PTW_MASK;

    /* Set usb forced to full speed mode */
    //ptr->PORTSC1 |= USB_PORTSC1_PFSC_MASK;

    /* Not use interrupt threshold. */
    ptr->USBCMD &= ~USB_USBCMD_ITC_MASK;

    /* Enable VBUS discharge */
    ptr->OTGSC |= USB_OTGSC_VD_MASK;
}

static void chipidea_deinit(CHIPIDEA_TypeDef *ptr)
{
    /* Stop */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;

    /* Reset controller */
    ptr->USBCMD |= USB_USBCMD_RST_MASK;
    while (USB_USBCMD_RST_GET(ptr->USBCMD)) {
    }

    /* Reset endpoint list address register */
    ptr->ENDPTLISTADDR = 0;

    /* Reset status register */
    ptr->USBSTS = ptr->USBSTS;

    /* Reset interrupt enable register */
    ptr->USBINTR = 0;
}

/*---------------------------------------------------------------------
 * Endpoint API
 *---------------------------------------------------------------------
 */
static void __chipidea_edpt_open(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr, uint8_t ep_type)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;

    /* Enable EP Control */
    uint32_t temp = ptr->ENDPTCTRL[epnum];
    temp &= ~((0x03 << 2) << (dir ? 16 : 0));
    temp |= ((ep_type << 2) | ENDPTCTRL_ENABLE | ENDPTCTRL_TOGGLE_RESET) << (dir ? 16 : 0);
    ptr->ENDPTCTRL[epnum] = temp;
}

static void chipidea_edpt_xfer(CHIPIDEA_TypeDef *ptr, uint8_t ep_idx)
{
    uint32_t offset = ep_idx / 2 + ((ep_idx % 2) ? 16 : 0);

    /* Start transfer */
    ptr->ENDPTPRIME = 1 << offset;
}

static void chipidea_edpt_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;

    ptr->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0);
}

static void chipidea_edpt_clear_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;

    /* data toggle also need to be reset */
    ptr->ENDPTCTRL[epnum] |= ENDPTCTRL_TOGGLE_RESET << (dir ? 16 : 0);
    ptr->ENDPTCTRL[epnum] &= ~(ENDPTCTRL_STALL << (dir ? 16 : 0));
}

static bool chipidea_edpt_check_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;

    return (ptr->ENDPTCTRL[epnum] & (ENDPTCTRL_STALL << (dir ? 16 : 0))) ? true : false;
}

static void chipidea_edpt_close(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;

    uint32_t primebit = CHIPIDEA_BITSMASK(1, epnum) << (dir ? 16 : 0);

    /* Flush the endpoint to stop a transfer. */
    do {
        /* Set the corresponding bit(s) in the ENDPTFLUSH register */
        ptr->ENDPTFLUSH |= primebit;

        /* Wait until all bits in the ENDPTFLUSH register are cleared. */
        while (0U != (ptr->ENDPTFLUSH & primebit)) {
        }
        /*
         * Read the ENDPTSTAT register to ensure that for all endpoints
         * commanded to be flushed, that the corresponding bits
         * are now cleared.
         */
    } while (0U != (ptr->ENDPTSTAT & primebit));

    /* Disable the endpoint */
    ptr->ENDPTCTRL[epnum] &= ~((ENDPTCTRL_TYPE | ENDPTCTRL_ENABLE | ENDPTCTRL_STALL) << (dir ? 16 : 0));
    ptr->ENDPTCTRL[epnum] |= (USB_ENDPOINT_TYPE_BULK << 2) << (dir ? 16 : 0);
}

/* Initialize qtd */
static void usb_qtd_init(dcd_qtd_t *p_qtd, void *data_ptr, uint16_t total_bytes)
{
    memset(p_qtd, 0, sizeof(dcd_qtd_t));

    p_qtd->next = 1;
    p_qtd->active = 1;
    p_qtd->total_bytes = p_qtd->expected_bytes = total_bytes;

    if (data_ptr != NULL) {
        p_qtd->buffer[0] = (uint32_t)data_ptr;
        for (uint8_t i = 1; i < 5; i++) {
            p_qtd->buffer[i] = ((p_qtd->buffer[i - 1]) & 0xFFFFF000UL) + 4096U;
        }
    }
}

static dcd_qhd_t *chipidea_qhd_get(uint8_t busid, uint8_t ep_idx)
{
    dcd_data_t *dcd_data;

    dcd_data = g_chipidea_udc[busid].dcd_data;
    return &dcd_data->qhd[ep_idx];
}

static dcd_qtd_t *chipidea_qtd_get(uint8_t busid, uint8_t ep_idx)
{
    dcd_data_t *dcd_data;

    dcd_data = g_chipidea_udc[busid].dcd_data;
    return &dcd_data->qtd[ep_idx * QTD_COUNT_EACH_ENDPOINT];
}

static void chipidea_bus_reset(uint8_t busid, uint16_t ep0_max_packet_size)
{
    dcd_data_t *dcd_data;

    dcd_data = g_chipidea_udc[busid].dcd_data;
    __chipidea_bus_reset(USB_OTG_DEV);

    /* Queue Head & Queue TD */
    memset(dcd_data, 0, sizeof(dcd_data_t));

    /* Set up Control Endpoints (0 OUT, 1 IN) */
    dcd_data->qhd[0].zero_length_termination = dcd_data->qhd[1].zero_length_termination = 1;
    dcd_data->qhd[0].max_packet_size = dcd_data->qhd[1].max_packet_size = ep0_max_packet_size;
    dcd_data->qhd[0].qtd_overlay.next = dcd_data->qhd[1].qtd_overlay.next = 1;

    /* OUT only */
    dcd_data->qhd[0].int_on_setup = 1;
}

static void chipidea_edpt_open(uint8_t busid, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_mps)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;
    uint8_t const ep_idx = 2 * epnum + dir;
    dcd_data_t *dcd_data;
    dcd_qhd_t *p_qhd;

    /* Prepare Queue Head */
    dcd_data = g_chipidea_udc[busid].dcd_data;
    p_qhd = &dcd_data->qhd[ep_idx];
    memset(p_qhd, 0, sizeof(dcd_qhd_t));

    p_qhd->zero_length_termination = 1;
    p_qhd->max_packet_size = ep_mps & 0x7FFu;
    p_qhd->qtd_overlay.next = 1;
    if (ep_type == USB_ENDPOINT_TYPE_ISOCHRONOUS) {
        p_qhd->iso_mult = ((ep_mps >> 11u) & 0x3u) + 1u;
    }

    __chipidea_edpt_open(USB_OTG_DEV, ep_addr, ep_type);
}

static bool chipidea_start_xfer(uint8_t busid, uint8_t ep_addr, uint8_t *buffer, uint32_t total_bytes)
{
    uint8_t const epnum = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;
    uint8_t const ep_idx = 2 * epnum + dir;
    uint8_t qtd_num;
    uint8_t i;
    uint32_t xfer_len;
    dcd_qhd_t *p_qhd;
    dcd_qtd_t *p_qtd;
    dcd_qtd_t *first_p_qtd = NULL;
    dcd_qtd_t *prev_p_qtd = NULL;
    dcd_data_t *dcd_data;

    dcd_data = g_chipidea_udc[busid].dcd_data;

    if (epnum == 0) {
        /* follows UM Setup packet handling using setup lockout mechanism
         * wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out
         */
        while (USB_OTG_DEV->ENDPTSETUPSTAT & CHIPIDEA_BITSMASK(1, 0)) {
        }
    }

    qtd_num = (total_bytes + 0x3fff) / 0x4000;
    if (qtd_num > QTD_COUNT_EACH_ENDPOINT) {
        return false;
    }

    if (buffer != NULL) {
        buffer = (uint8_t *)buffer;
    }
    p_qhd = &dcd_data->qhd[ep_idx];
    i = 0;
    do {
        p_qtd = &dcd_data->qtd[ep_idx * QTD_COUNT_EACH_ENDPOINT + i];
        i++;

        if (total_bytes > 0x4000) {
            xfer_len = 0x4000;
            total_bytes -= 0x4000;
        } else {
            xfer_len = total_bytes;
            total_bytes = 0;
        }

        usb_qtd_init(p_qtd, (void *)buffer, xfer_len);
        if (total_bytes == 0) {
            p_qtd->int_on_complete = true;
        }
        buffer += xfer_len;

        if (prev_p_qtd) {
            prev_p_qtd->next = (uint32_t)p_qtd;
        } else {
            first_p_qtd = p_qtd;
        }
        prev_p_qtd = p_qtd;
    } while (total_bytes > 0);

    p_qhd->qtd_overlay.next = (uint32_t)first_p_qtd; /* link qtd to qhd */

    chipidea_edpt_xfer(USB_OTG_DEV, ep_idx);

    return true;
}

__WEAK void usb_dc_low_level_init(uint8_t busid)
{
}

__WEAK void usb_dc_low_level_deinit(uint8_t busid)
{
}

int usb_dc_init(uint8_t busid)
{
    uint32_t int_mask;
    int_mask = (USB_USBINTR_UE_MASK | USB_USBINTR_UEE_MASK | USB_USBINTR_SLE_MASK |
                USB_USBINTR_PCE_MASK | USB_USBINTR_URE_MASK);

    usb_dc_low_level_init(busid);

    memset(&g_chipidea_udc[busid], 0, sizeof(struct chipidea_udc));
    g_chipidea_udc[busid].dcd_data = g_dcd_data[busid];
    memset(g_chipidea_udc[busid].dcd_data, 0, sizeof(dcd_data_t));

    chipidea_init(USB_OTG_DEV);

    /* Set endpoint list address */
    USB_OTG_DEV->ENDPTLISTADDR = ((uint32_t)g_chipidea_udc[busid].dcd_data->qhd) & USB_ENDPTLISTADDR_EPBASE_MASK;

    /* Clear status */
    USB_OTG_DEV->USBSTS = USB_OTG_DEV->USBSTS;

#ifdef CONFIG_USBDEV_SOF_ENABLE
    int_mask |= USB_USBINTR_SRE_MASK;
#endif

    /* Enable interrupt mask */
    USB_OTG_DEV->USBINTR |= int_mask;

    /* Connect by enabling internal pull-up resistor on D+/D- */
    USB_OTG_DEV->USBCMD |= USB_USBCMD_RS_MASK;
    return 0;
}

int usb_dc_deinit(uint8_t busid)
{
    chipidea_deinit(USB_OTG_DEV);

    for (uint32_t i = 0; i < CONFIG_USBDEV_EP_NUM; i++) {
        chipidea_edpt_close(USB_OTG_DEV, (i | 0x80));
        chipidea_edpt_close(USB_OTG_DEV, (i | 0x00));
    }

    usb_dc_low_level_deinit(busid);
    return 0;
}

int usbd_set_address(uint8_t busid, const uint8_t addr)
{
    USB_OTG_DEV->DEVICEADDR = USB_DEVICEADDR_USBADR_SET(addr) | USB_DEVICEADDR_USBADRA_MASK;
    return 0;
}

int usbd_set_remote_wakeup(uint8_t busid)
{
    if (!USB_PORTSC1_SUSP_GET(USB_OTG_DEV->PORTSC1)) {
        return -1;
    }

    USB_OTG_DEV->PORTSC1 |= USB_PORTSC1_FPR_MASK;
    while (USB_OTG_DEV->PORTSC1 & USB_PORTSC1_FPR_MASK) {
    }

    return 0;
}

uint8_t usbd_get_port_speed(uint8_t busid)
{
    uint8_t speed;

    speed = USB_PORTSC1_PSPD_GET(USB_OTG_DEV->PORTSC1);

    if (speed == 0x00) {
        return USB_SPEED_FULL;
    }
    if (speed == 0x01) {
        return USB_SPEED_LOW;
    }
    if (speed == 0x02) {
        return USB_SPEED_HIGH;
    }

    return 0;
}

int usbd_ep_open(uint8_t busid, const struct usb_endpoint_descriptor *ep)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep->bEndpointAddress);

    USB_ASSERT_MSG(ep_idx < CONFIG_USBDEV_EP_NUM, "Ep addr %02x overflow", ep->bEndpointAddress);

    chipidea_edpt_open(busid, ep->bEndpointAddress, USB_GET_ENDPOINT_TYPE(ep->bmAttributes), ep->wMaxPacketSize);

    if (USB_EP_DIR_IS_OUT(ep->bEndpointAddress)) {
        g_chipidea_udc[busid].out_ep[ep_idx].ep_mps = USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize);
        g_chipidea_udc[busid].out_ep[ep_idx].ep_type = USB_GET_ENDPOINT_TYPE(ep->bmAttributes);
        g_chipidea_udc[busid].out_ep[ep_idx].ep_enable = true;
    } else {
        g_chipidea_udc[busid].in_ep[ep_idx].ep_mps = USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize);
        g_chipidea_udc[busid].in_ep[ep_idx].ep_type = USB_GET_ENDPOINT_TYPE(ep->bmAttributes);
        g_chipidea_udc[busid].in_ep[ep_idx].ep_enable = true;
    }

    return 0;
}

int usbd_ep_close(uint8_t busid, const uint8_t ep)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    if (USB_EP_DIR_IS_OUT(ep)) {
        g_chipidea_udc[busid].out_ep[ep_idx].ep_enable = false;
    } else {
        g_chipidea_udc[busid].in_ep[ep_idx].ep_enable = false;
    }

    chipidea_edpt_close(USB_OTG_DEV, ep);

    return 0;
}

int usbd_ep_set_stall(uint8_t busid, const uint8_t ep)
{
    chipidea_edpt_stall(USB_OTG_DEV, ep);
    return 0;
}

int usbd_ep_clear_stall(uint8_t busid, const uint8_t ep)
{
    chipidea_edpt_clear_stall(USB_OTG_DEV, ep);
    return 0;
}

int usbd_ep_is_stalled(uint8_t busid, const uint8_t ep, uint8_t *stalled)
{
    *stalled = chipidea_edpt_check_stall(USB_OTG_DEV, ep);
    return 0;
}

int usbd_ep_start_write(uint8_t busid, const uint8_t ep, const uint8_t *data, uint32_t data_len)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    if (!data && data_len) {
        return -1;
    }
    if (!g_chipidea_udc[busid].in_ep[ep_idx].ep_enable) {
        return -2;
    }

    g_chipidea_udc[busid].in_ep[ep_idx].xfer_buf = (uint8_t *)data;
    g_chipidea_udc[busid].in_ep[ep_idx].xfer_len = data_len;
    g_chipidea_udc[busid].in_ep[ep_idx].actual_xfer_len = 0;

    chipidea_start_xfer(busid, ep, (uint8_t *)data, data_len);

    return 0;
}

int usbd_ep_start_read(uint8_t busid, const uint8_t ep, uint8_t *data, uint32_t data_len)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    if (!data && data_len) {
        return -1;
    }
    if (!g_chipidea_udc[busid].out_ep[ep_idx].ep_enable) {
        return -2;
    }

    g_chipidea_udc[busid].out_ep[ep_idx].xfer_buf = (uint8_t *)data;
    g_chipidea_udc[busid].out_ep[ep_idx].xfer_len = data_len;
    g_chipidea_udc[busid].out_ep[ep_idx].actual_xfer_len = 0;

    chipidea_start_xfer(busid, ep, data, data_len);

    return 0;
}

void USBD_IRQHandler(uint8_t busid)
{
    uint32_t int_status;
    uint32_t transfer_len;
    bool ep_cb_req;

    /* Acknowledge handled interrupt */
    int_status = USB_OTG_DEV->USBSTS;
    int_status &= USB_OTG_DEV->USBINTR;
    USB_OTG_DEV->USBSTS = int_status;

    if (int_status & intr_error) {
        USB_LOG_ERR("usbd intr error!\r\n");
    }

#ifdef CONFIG_USBDEV_SOF_ENABLE
    if (int_status & intr_sof) {
        usbd_event_sof_handler(busid);
    }
#endif

    if (int_status & intr_reset) {
        g_chipidea_udc[busid].is_suspend = false;
        memset(g_chipidea_udc[busid].in_ep, 0, sizeof(struct chipidea_ep_state) * CONFIG_USBDEV_EP_NUM);
        memset(g_chipidea_udc[busid].out_ep, 0, sizeof(struct chipidea_ep_state) * CONFIG_USBDEV_EP_NUM);
        usbd_event_reset_handler(busid);
        chipidea_bus_reset(busid, 64);
    }

    if (int_status & intr_suspend) {
        if (USB_PORTSC1_SUSP_GET(USB_OTG_DEV->PORTSC1)) {
            /* Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. */
            if (USB_DEVICEADDR_USBADR_GET(USB_OTG_DEV->DEVICEADDR)) {
                g_chipidea_udc[busid].is_suspend = true;
                usbd_event_suspend_handler(busid);
            }
        } else {
        }
    }

    if (int_status & intr_port_change) {
        if (!USB_PORTSC1_CCS_GET(USB_OTG_DEV->PORTSC1)) {
            usbd_event_disconnect_handler(busid);
        } else {
            if (g_chipidea_udc[busid].is_suspend) {
                g_chipidea_udc[busid].is_suspend = false;
                usbd_event_resume_handler(busid);
            }
            usbd_event_connect_handler(busid);
        }
    }

    if (int_status & intr_usb) {
        uint32_t const edpt_complete = USB_OTG_DEV->ENDPTCOMPLETE;
        uint32_t edpt_setup_status = USB_OTG_DEV->ENDPTSETUPSTAT;

        if (edpt_complete) {
            USB_OTG_DEV->ENDPTCOMPLETE = edpt_complete;
            for (uint8_t ep_idx = 0; ep_idx < (CONFIG_USBDEV_EP_NUM * 2); ep_idx++) {
                if (edpt_complete & (1 << ep_idx2bit(ep_idx))) {
                    transfer_len = 0;
                    ep_cb_req = true;

                    /* Failed QTD also get ENDPTCOMPLETE set */
                    dcd_qtd_t *p_qtd = chipidea_qtd_get(busid, ep_idx);
                    while (1) {
                        if (p_qtd->halted || p_qtd->xact_err || p_qtd->buffer_err) {
                            USB_LOG_ERR("usbd transfer error!\r\n");
                            ep_cb_req = false;
                            break;
                        } else if (p_qtd->active) {
                            ep_cb_req = false;
                            break;
                        } else {
                            transfer_len += p_qtd->expected_bytes - p_qtd->total_bytes;
                        }

                        if (p_qtd->next == 1) {
                            break;
                        } else {
                            p_qtd = (dcd_qtd_t *)p_qtd->next;
                        }
                    }

                    if (ep_cb_req) {
                        uint8_t const ep_addr = (ep_idx / 2) | ((ep_idx & 0x01) ? 0x80 : 0);
                        if (ep_addr & 0x80) {
                            usbd_event_ep_in_complete_handler(busid, ep_addr, transfer_len);
                        } else {
                            usbd_event_ep_out_complete_handler(busid, ep_addr, transfer_len);
                        }
                    }
                }
            }
        }

        if (edpt_setup_status) {
            /*------------- Set up Received -------------*/
            USB_OTG_DEV->ENDPTSETUPSTAT = edpt_setup_status;
            dcd_qhd_t *qhd0 = chipidea_qhd_get(busid, 0);
            usbd_event_ep0_setup_complete_handler(busid, (uint8_t *)&qhd0->setup_request);
        }
    }
}