/*
 * Copyright (c) 2021 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

/*---------------------------------------------------------------------
 * Include
 *---------------------------------------------------------------------
 */
#include "hpm_usb_drv.h"
#include "hpm_misc.h"
#include "hpm_soc_feature.h"
#include "hpm_common.h"
/*---------------------------------------------------------------------
 * Macro Enum Declaration
 *---------------------------------------------------------------------
 */

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

/*---------------------------------------------------------------------
 * Internal API
 *---------------------------------------------------------------------
 */

/* De-initialize USB phy */
static void usb_phy_deinit(USB_Type *ptr)
{
    uint32_t status;

    ptr->PHY_CTRL1 &= ~USB_PHY_CTRL1_UTMI_OTG_SUSPENDM_MASK;       /* clear otg_suspendm */

    ptr->PHY_CTRL1 &= ~USB_PHY_CTRL1_UTMI_CFG_RST_N_MASK;          /* clear cfg_rst_n */

    ptr->OTG_CTRL0 |= USB_OTG_CTRL0_OTG_UTMI_RESET_SW_MASK;        /* set otg_utmi_reset_sw for naneng usbphy */

    for (volatile uint32_t i = 0; i < USB_PHY_INIT_DELAY_COUNT; i++) {
        (void)ptr->PHY_CTRL1;                                      /* used for delay, at least 1us */
    }

    ptr->OTG_CTRL0 &= ~USB_OTG_CTRL0_OTG_UTMI_SUSPENDM_SW_MASK;     /* clear otg_utmi_suspend_m for naneng usbphy */

    do {
        status = USB_OTG_CTRL0_OTG_UTMI_RESET_SW_GET(ptr->OTG_CTRL0); /* wait for reset status */
    } while (status == 0);
}

/*---------------------------------------------------------------------
 * Driver API
 *---------------------------------------------------------------------
 */
/* Initialize USB phy */
void usb_phy_init(USB_Type *ptr, bool host)
{
    uint32_t status;

    usb_phy_deinit(ptr);
    usb_phy_enable_dp_dm_pulldown(ptr);

    ptr->OTG_CTRL0 |= USB_OTG_CTRL0_OTG_UTMI_SUSPENDM_SW_MASK;        /* set otg_utmi_suspend_m for naneng usbphy */

    for (volatile uint32_t i = 0; i < USB_PHY_INIT_DELAY_COUNT; i++) {
        (void)ptr->PHY_CTRL1;                                         /* used for delay, at least 1us */
    }

    ptr->OTG_CTRL0 &= ~USB_OTG_CTRL0_OTG_UTMI_RESET_SW_MASK;          /* clear otg_utmi_reset_sw for naneng usbphy */

    ptr->OTG_CTRL0 &= ~USB_OTG_CTRL0_OTG_WKDPDMCHG_EN_MASK;           /* Disable dp/dm wakeup */

    /* otg utmi clock detection */
    ptr->PHY_STATUS |= USB_PHY_STATUS_UTMI_CLK_VALID_MASK;            /* write 1 to clear valid status */
    do {
        status = USB_PHY_STATUS_UTMI_CLK_VALID_GET(ptr->PHY_STATUS);  /* get utmi clock status */
    } while (status == 0);

    ptr->PHY_CTRL0 |= USB_PHY_CTRL0_OP_MODE_SUSPENDM_ENJ_MASK;        /* set suspendm_enj */

    ptr->PHY_CTRL1 |= USB_PHY_CTRL1_UTMI_CFG_RST_N_MASK;              /* set cfg_rst_n */

    if (host) {
        ptr->PHY_CTRL1 |= USB_PHY_CTRL1_UTMI_OTG_SUSPENDM_MASK;       /* set otg_suspendm, enable high speed device disconect detect */
    }
}

void usb_dcd_bus_reset(USB_Type *ptr, uint16_t ep0_max_packet_size)
{
    (void) ep0_max_packet_size;
    /* 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 < USB_SOC_DCD_MAX_ENDPOINT_COUNT; i++) {
        ptr->ENDPTCTRL[i] = USB_ENDPTCTRL_TXT_SET(usb_xfer_bulk) | USB_ENDPTCTRL_RXT_SET(usb_xfer_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) {
    }
}

void usb_dcd_init(USB_Type *ptr)
{
    /* Initialize USB phy */
    usb_phy_init(ptr, false);

    /* Stop */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;

    /* 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;

#if defined(CONFIG_USB_DEVICE_FS) || defined(CONFIG_USB_DEVICE_FORCE_FULL_SPEED)
    /* Set usb forced to full speed mode */
    ptr->PORTSC1 |= USB_PORTSC1_PFSC_MASK;
#endif

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

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

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

void usb_dcd_deinit(USB_Type *ptr)
{
    /* Stop */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;

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

    /* De-initialize USB phy */
    usb_phy_deinit(ptr);

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

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

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

/* Connect by enabling internal pull-up resistor on D+/D- */
void usb_dcd_connect(USB_Type *ptr)
{
    ptr->USBCMD |= USB_USBCMD_RS_MASK;
}

/* Disconnect by disabling internal pull-up resistor on D+/D- */
void usb_dcd_disconnect(USB_Type *ptr)
{
    /* Stop */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;

    /* Pullup DP to make the phy switch into full speed mode */
    ptr->USBCMD |= USB_USBCMD_RS_MASK;

    /* Clear the sof flag */
    ptr->USBSTS |= USB_USBSTS_SRI_MASK;

    /* Wait a SOF (It will not be a dead loop even usb cable is not connected.) */
    while (USB_USBSTS_SRI_GET(ptr->USBSTS) == 0) {
    }

    /* Disconnect */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;
}

/*---------------------------------------------------------------------
 * Endpoint API
 *---------------------------------------------------------------------
 */
void usb_dcd_edpt_open(USB_Type *ptr, usb_endpoint_config_t *config)
{
    uint8_t const epnum  = config->ep_addr & 0x0f;
    uint8_t const dir = (config->ep_addr & 0x80) >> 7;

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

uint8_t usb_dcd_edpt_get_type(USB_Type *ptr, uint8_t ep_addr)
{
    uint8_t const epnum  = ep_addr & 0x0f;
    uint8_t const dir = (ep_addr & 0x80) >> 7;
    uint32_t temp =  ptr->ENDPTCTRL[epnum];

    return dir ? USB_ENDPTCTRL_TXT_GET(temp) : USB_ENDPTCTRL_RXT_GET(temp);
}

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

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

void usb_dcd_edpt_stall(USB_Type *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);
}

void usb_dcd_edpt_clear_stall(USB_Type *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));
}

bool usb_dcd_edpt_check_stall(USB_Type *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;
}

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

    uint32_t primebit = HPM_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_xfer_bulk << 2) << (dir ? 16 : 0);
}

void usb_dcd_remote_wakeup(USB_Type *ptr)
{
    usb_force_port_resume(ptr);
}

bool usb_hcd_init(USB_Type *ptr, uint32_t int_mask, uint16_t framelist_size)
{
    uint8_t framelist_size_bf = 0;

    if (framelist_size > USB_SOC_HCD_FRAMELIST_MAX_ELEMENTS || framelist_size == 0) {
        return false;
    }

    framelist_size_bf = 10 - get_first_set_bit_from_lsb(framelist_size);

    if (framelist_size != (1 << get_first_set_bit_from_lsb(framelist_size))) {
        return false;
    }

    usb_phy_init(ptr, true);

    /* Stop */
    ptr->USBCMD &= ~USB_USBCMD_RS_MASK;

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

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

    /* 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;

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

    /* USB INT Register */
    ptr->USBSTS = ptr->USBSTS;
    ptr->USBINTR |= int_mask;

    /* USB CMD Register */
    ptr->USBCMD = USB_USBCMD_ASE_MASK | USB_USBCMD_PSE_MASK
                | USB_USBCMD_FS_2_SET(framelist_size_bf >> 2)
                | USB_USBCMD_FS_1_SET(framelist_size_bf);

    return true;
}

void usb_hcd_port_reset(USB_Type *ptr)
{
    if (usb_phy_get_line_state(ptr) == usb_line_state2) {
        ptr->PORTSC1 |= USB_PORTSC1_STS_MASK;
    } else {
        ptr->PORTSC1 &= ~USB_PORTSC1_STS_MASK;
    }

    ptr->PORTSC1 &= ~USB_PORTSC1_PE_MASK;
    ptr->PORTSC1 |=  USB_PORTSC1_PR_MASK;

    /* wait until port reset sequence is completed */
    while (USB_PORTSC1_PR_GET(ptr->PORTSC1)) {
    }
}
