/*!
    \file  usbd_int.c
    \brief USB device power interrupt routines
*/

/*
    Copyright (C) 2017 GigaDevice

    2017-02-10, V1.0.0, firmware for GD32F30x
*/

#include "usbd_int.h"

extern uint32_t g_interrupt_mask;
extern __IO uint8_t g_suspend_enabled;
extern __IO uint8_t g_remote_wakeup_on;
extern __IO uint8_t g_ESOF_count;

#ifdef LPM_ENABLED
__IO uint32_t L1_remote_wakeup = 0U;
__IO uint32_t L1_resume = 0U;
__IO uint32_t besl = 0U;
#endif /* LPM_ENABLED */

static uint8_t  usbd_intf_lpst    (usbd_core_handle_struct *pudev);
static uint8_t  usbd_intf_sof     (usbd_core_handle_struct *pudev);
static uint8_t  usbd_intf_esof    (usbd_core_handle_struct *pudev);
static uint8_t  usbd_intf_reset   (usbd_core_handle_struct *pudev);
static uint8_t  usbd_intf_suspend (usbd_core_handle_struct *pudev);
static uint8_t  usbd_intf_wakeup  (usbd_core_handle_struct *pudev);

/*!
    \brief      USB interrupt events service routine
    \param[in]  none
    \param[out] none
    \retval     none
*/
void  usbd_isr (void)
{
    __IO uint16_t interrupt_flag = 0U;
    __IO uint16_t ctlr = 0U;

    interrupt_flag = USBD_REG_GET(USBD_INTF);

    if (g_interrupt_mask & INTF_STIF & interrupt_flag) {
        /* the endpoint successful transfer interrupt service */
        usbd_intf_lpst(&usb_device_dev);
    }

    if (g_interrupt_mask & INTF_WKUPIF & interrupt_flag) {
        /* clear wakeup interrupt flag in INTF */
        USBD_REG_SET(USBD_INTF, (uint16_t)CLR_WKUPIF);

        /* USB wakeup interrupt handle */
        usbd_intf_wakeup(&usb_device_dev);

#ifdef LPM_ENABLED
        /* clear L1 remote wakeup flag */
        L1_remote_wakeup = 0;
#endif /* LPM_ENABLED */
    }

    if (g_interrupt_mask & INTF_SPSIF & interrupt_flag) {
        if(!(USBD_REG_GET(USBD_CTL) & CTL_RSREQ)) {
            /* process library core layer suspend routine*/
            usbd_intf_suspend(&usb_device_dev);

            /* clear of suspend interrupt flag bit must be done after setting of CTLR_SETSPS */
            USBD_REG_SET(USBD_INTF, (uint16_t)CLR_SPSIF);
        }
    }

    if (g_interrupt_mask & INTF_SOFIF & interrupt_flag) {
        /* clear SOF interrupt flag in INTF */
        USBD_REG_SET(USBD_INTF, (uint16_t)CLR_SOFIF);

        /* USB SOF interrupt handle */
        usbd_intf_sof(&usb_device_dev);
    }

    if (g_interrupt_mask & INTF_ESOFIF & interrupt_flag) {
        /* clear ESOF interrupt flag in INTF */
        USBD_REG_SET(USBD_INTF, (uint16_t)CLR_ESOFIF);

        /* USB ESOF interrupt handle */
        usbd_intf_esof(&usb_device_dev);
    }

    if (g_interrupt_mask & INTF_RSTIF & interrupt_flag) {
        /* clear reset interrupt flag in INTF */
        USBD_REG_SET(USBD_INTF, (uint16_t)CLR_RSTIF);

        /* USB reset interrupt handle */
        usbd_intf_reset(&usb_device_dev);
    }

#ifdef LPM_ENABLED
    if (g_interrupt_mask & INTF_L1REQ & interrupt_flag) {
        /* clear L1 ST bit in LPM INTF */
        USBD_REG_SET(USBD_INTF, CLR_L1REQ);

        /* read BESL field from subendpoint0 register which coressponds to HIRD parameter in LPM spec */
        besl = (USBD_REG_GET(USBD_LPMCS) & LPMCS_BLSTAT) >> 4;

        /* read BREMOTEWAKE bit from subendpoint0 register which corresponding to bRemoteWake bit in LPM request */
        L1_remote_wakeup = (USBD_REG_GET(USBD_LPMCS) & LPMCS_REMWK) >> 8;

        /* process USB device core layer suspend routine */
        /* enter USB model in suspend and system in low power mode (DEEP_SLEEP mode) */
        usbd_intf_suspend(&usb_device_dev);
    }
#endif /* LPM_ENABLED */
}

/*!
    \brief      handle USB low priority successful transfer event 
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_lpst (usbd_core_handle_struct *pudev)
{
    uint8_t ep_num = 0U;

    __IO uint16_t int_status = 0U;
    __IO uint16_t ep_value = 0U;

    usb_ep_struct *ep = NULL;

    /* wait till interrupts are not pending */
    while (0U != ((int_status = USBD_REG_GET(USBD_INTF)) & (uint16_t)INTF_STIF)) {
        /* get endpoint number and the value of control and state register */
        ep_num = (uint8_t)(int_status & INTF_EPNUM);
        ep_value = USBD_REG_GET(USBD_EPxCS(ep_num));

        if (0U == (int_status & INTF_DIR)) {
            /* handle the in direction transaction */

            ep = &(pudev->in_ep[ep_num]);

            if (0U != (ep_value & EPxCS_TX_ST)) {
                /* clear successful transmit interrupt flag */
                USBD_ENDP_TX_STAT_CLEAR(ep_num);

                /* just handle single buffer situation */
                ep->trs_count = (pbuf_reg + ep_num)->tx_count & EPTCNT_CNT;

                /* maybe mutiple packets */
                ep->trs_buf += ep->trs_count;

                usbd_in_transaction(pudev, ep_num);
            }
        } else {
            /* handle the out direction transaction */

            uint16_t count = 0U;

            ep = &(pudev->out_ep[ep_num]);

            if (0U != (ep_value & EPxCS_RX_ST)) {
                /* clear successful receive interrupt flag */
                USBD_ENDP_RX_STAT_CLEAR(ep_num);

                count = (pbuf_reg + ep_num)->rx_count & (uint16_t)EPRCNT_CNT;

                if (0U != count) {
                    if (0U != (ep_value & EPxCS_SETUP)) {
                        /* handle setup packet */
                        usbd_ep_data_read(&(pudev->setup_packet[0]), pbuf_reg->rx_addr, count);

                        /* enter setup status */
                        usbd_setup_transaction(pudev);

                        return USBD_OK;
                    } else {
                        usbd_ep_data_read(ep->trs_buf, (pbuf_reg + ep_num)->rx_addr, count);
                    }
                }

                /* maybe mutiple packets */
                ep->trs_count += count;
                ep->trs_buf += count;
                ep->trs_len -= count;

                if ((0U == ep->trs_len) || (count < ep->maxpacket)) {
                    /* enter data OUT status */
                    usbd_out_transaction(pudev, ep_num);

                    ep->trs_count = 0U;
                } else {
                    usbd_ep_rx(pudev, ep_num, ep->trs_buf, (uint16_t)ep->trs_len);
                }
            }
        }   
    }

    return USBD_OK;
}

/*!
    \brief      handle USB SOF event
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_sof (usbd_core_handle_struct *pudev)
{
    /* if necessary, user can add code here */
    if (NULL != usbd_int_fops) {
        usbd_int_fops->SOF(pudev);
    }

    return USBD_OK;
}

/*!
    \brief      handle USB expect SOF event
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_esof (usbd_core_handle_struct *pudev)
{
    /* control resume time by ESOFs */
    if (g_ESOF_count > 0U) {
        g_ESOF_count--;

        if (0U == g_ESOF_count) {
            if (1U == g_remote_wakeup_on) {
                USBD_REG_SET(USBD_CTL, USBD_REG_GET(USBD_CTL) & ~CTL_RSREQ);

                g_remote_wakeup_on = 0U;
            } else {
                USBD_REG_SET(USBD_CTL, USBD_REG_GET(USBD_CTL) | CTL_RSREQ);

                g_ESOF_count = 3U;
                g_remote_wakeup_on = 1U;
            }
        }
    }

    return USBD_OK;
}

/*!
    \brief      handle USB reset event
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_reset (usbd_core_handle_struct *pudev)
{
    uint8_t i;

    g_free_buf_addr = ENDP_BUF_ADDR;

    /* configure endpoint 0 buffer */
    pbuf_reg->tx_addr = (uint16_t)g_free_buf_addr;
    g_free_buf_addr += USBD_EP0_MAX_SIZE;

    pbuf_reg->rx_addr = (uint16_t)g_free_buf_addr;
    g_free_buf_addr += USBD_EP0_MAX_SIZE;

    /* configure endpoint 0 Rx count */
    if (USBD_EP0_MAX_SIZE > 62U) {
        pbuf_reg->rx_count = ((USBD_EP0_MAX_SIZE << 5U) - 1U) | 0x8000U;
    } else {
        pbuf_reg->rx_count = ((USBD_EP0_MAX_SIZE + 1U) & ~1U) << 9U;
    }

    pudev->in_ep[EP0].maxpacket = USBD_EP0_MAX_SIZE;
    pudev->out_ep[EP0].maxpacket = USBD_EP0_MAX_SIZE;

    /* set endpoints address */
    for (i = 0U; i < EP_COUNT; i++)
    {
        _EP_ADDR_SET(i, i);
    }

    USBD_REG_SET(USBD_EPxCS(EP0), EP_CONTROL | EPRX_VALID | EPTX_NAK);

    /* set device address as default address 0 */
    USBD_REG_SET(USBD_DADDR, DADDR_USBEN);

    pudev->status = USBD_DEFAULT;

    return USBD_OK;
}

/*!
    \brief      handle USB suspend event
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_suspend (usbd_core_handle_struct *pudev)
{
    /* store the device current status */
    pudev->prev_status = pudev->status;

    /* set device in suspended state */
    pudev->status = USBD_SUSPENDED;

    /* enter USB in suspend and mcu system in low power mode */
    if (g_suspend_enabled) {
        usbd_suspend();
    } else {
        /* if not possible then resume after xx ms */
        g_ESOF_count = 3U;
    }

    return USBD_OK;
}

/*!
    \brief      handle USB wakeup event
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t  usbd_intf_wakeup (usbd_core_handle_struct *pudev)
{
    /* restore the old status */
    pudev->status = pudev->prev_status;

#ifdef LPM_ENABLED
    if ((0U == g_remote_wakeup_on) && (0U == L1_resume)) {
        resume_mcu();
    } else if (1U == g_remote_wakeup_on) {
        /* no operation */
    } else {
        L1_resume = 0U;
    }
#else
    if (0U == g_remote_wakeup_on) {
        resume_mcu();
    }
#endif /* LPM_ENABLED */

    return USBD_OK;
}
