/***************************************************************************//**
 * (c) Copyright 2012-2014 Microsemi SoC Products Group. All rights reserved.
 *
 * Smartfusion2 MSS USB Driver Stack
 *      USB Core Interface Layer (USB-CIFL)
 *          USBD-CIF driver
 *
 * USBD-CIF driver implementation:
 * This file implements MSS USB core initialization in device mode and
 * implements core interface function for the logical layer to control the
 * MSS USB core in USB Device mode.
 *
 * SVN $Revision: 6145 $
 * SVN $Date: 2014-02-18 19:11:44 +0530 (Tue, 18 Feb 2014) $
 */

#include "mss_usb_device_cif.h"
#include "mss_usb_core_regs.h"
#include "mss_usb_common_cif.h"
#include "mss_usb_device_reg_io.h"
#include "mss_usb_common_reg_io.h"
#include "../../CMSIS/mss_assert.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifdef MSS_USB_DEVICE_ENABLED

/***************************************************************************//**
 These pointers are used for holding the Receive transfer parametes (Buffer
 address, length) etc. This pointer will be updated in
 MSS_USBD_CIF_rx_ep_read_prepare() API and used in
 MSS_USB_CIF_handle_rx_ep_irq().
 */
volatile mss_usbd_cep_state_t cep_state;

/***************************************************************************//**
 This structure is used for tracking the Control endoint states..
 */
mss_usb_ep_t *gd_rx_ep[5];
mss_usb_ep_t *gd_tx_ep[5];

/***************************************************************************//**
 EXPORTED APIs
 ******************************************************************************/

/***************************************************************************//**
 This function initializes the MSS USB core in Device mode.
 */
void
MSS_USBD_CIF_init
(
    mss_usb_device_speed_t speed
)
{
    cep_state = MSS_USB_CTRL_EP_IDLE;

    /*TODO: Avoid resetting twice in DualRole mode*/
    MSS_USB_CIF_soft_reset();
    MSS_USB_CIF_clr_usb_irq_reg();
    NVIC_ClearPendingIRQ(USB_IRQn);

    /*Reset and Resume are by defualt enabled in INTRUSBE reg after soft reset*/
    NVIC_EnableIRQ(USB_IRQn);

    NVIC_ClearPendingIRQ(USB_DMA_IRQn);
    NVIC_EnableIRQ(USB_DMA_IRQn);

    MSS_USB_CIF_rx_ep_disable_irq_all();
    MSS_USB_CIF_tx_ep_disable_irq_all();

    if(speed == MSS_USB_DEVICE_FS)
    {
        MSS_USB_CIF_disable_hs_mode();
    }

    USB->C_T_HSBT = 0x01u;
}

/***************************************************************************//**
 Provides the information about the MSS USB core configuration
 */
void
MSS_USBD_CIF_get_hwcore_info
(
    mss_usb_core_info_t* hw_core
)
{
    MSS_USB_CIF_set_index_reg(MSS_USB_CEP);
    hw_core->core_max_nbr_of_tx_ep = (USB->EP_INFO & 0x0Fu);
    hw_core->core_max_nbr_of_rx_ep = ((USB->EP_INFO & 0xF0u) >> 4u);
    hw_core->core_ram_bus_width = (USB->RAM_INFO & 0x0Fu);
    hw_core->core_max_nbr_of_dma_chan = ((USB->RAM_INFO & 0xF0u) >> 4u);
    hw_core->core_WTCON = ((USB->LINK_INFO & 0xF0u) >> 4u);
    hw_core->core_WTID = (USB->LINK_INFO & 0x0Fu);
    hw_core->core_VPLEN = USB->VP_LEN;
    hw_core->core_HS_EOF1 = USB->HS_EOF1;
    hw_core->core_FS_EOF1 = USB->FS_EOF1;
    hw_core->core_LS_EOF1 = USB->LS_EOF1;
    hw_core->core_configdata = USB->INDEXED_CSR.DEVICE_EP0.CONFIG_DATA;
}

/***************************************************************************//**
 Configures the registers related to TX EP for data transfer operations as
 per the parameters provided by the upper layer.
 */
void
MSS_USBD_CIF_tx_ep_configure
(
    mss_usb_ep_t* device_ep
)
{
    MSS_USB_CIF_tx_ep_clr_underrun(device_ep->num);
    MSS_USB_CIF_tx_ep_clr_send_stall_bit(device_ep->num);
    MSS_USB_CIF_tx_ep_clr_stall_sent_bit(device_ep->num);

    switch(device_ep->xfr_type)
    {
        case MSS_USB_XFR_INTERRUPT:
            MSS_USB_CIF_tx_ep_disable_iso(device_ep->num);
            MSS_USB_CIF_tx_ep_clr_autoset(device_ep->num);
        break;

        case MSS_USB_XFR_BULK:
            MSS_USB_CIF_tx_ep_disable_iso(device_ep->num);
            if(device_ep->dma_enable == DMA_ENABLE)
                MSS_USB_CIF_tx_ep_set_autoset(device_ep->num);
        break;

        case MSS_USB_XFR_ISO:
            /*Error check and Data toggle is ignored in ISO transfers*/
            MSS_USB_CIF_tx_ep_enable_iso(device_ep->num);
            MSS_USB_CIF_tx_ep_clr_autoset(device_ep->num);
        break;

        default:
            ASSERT(0);
        break;
    }

    /*Do the common configuration for TX EP*/
    MSS_USB_CIF_tx_ep_configure(device_ep);
}

/***************************************************************************//**
 Configures the RX EP for data transfer operations as per the parameters
 provided by upper layer.
 */
void
MSS_USBD_CIF_rx_ep_configure
(
    mss_usb_ep_t* device_ep
)
{
    /*Device mode configurations*/
    MSS_USB_CIF_rx_ep_clr_overrun(device_ep->num);
    MSS_USB_CIF_rx_ep_clr_stall_sent_bit(device_ep->num);
    MSS_USB_CIF_rx_ep_clr_send_stall_bit(device_ep->num);

    switch(device_ep->xfr_type)
    {
        case MSS_USB_XFR_INTERRUPT:
            MSS_USB_CIF_rx_ep_disable_iso(device_ep->num);
            MSS_USB_CIF_rx_ep_disable_nyet(device_ep->num);
            MSS_USB_CIF_rx_ep_clr_autoclr(device_ep->num);
        break;

        case MSS_USB_XFR_BULK:
            MSS_USB_CIF_rx_ep_disable_iso(device_ep->num);
            MSS_USB_CIF_rx_ep_enable_nyet(device_ep->num);
            MSS_USB_CIF_rx_ep_set_autoclr(device_ep->num);
        break;

        case MSS_USB_XFR_ISO:
            MSS_USB_CIF_rx_ep_clr_autoclr(device_ep->num);
            MSS_USB_CIF_rx_ep_enable_iso(device_ep->num);
            MSS_USB_CIF_rx_ep_disable_nyet(device_ep->num);
        break;

        default:
            ASSERT(0);
        break;
    }

    /*Do the common configuration for RX EP*/
    MSS_USB_CIF_rx_ep_configure(device_ep);
}

/***************************************************************************//**
 Configures the Control EP for data transfer operations.
 */
void
MSS_USBD_CIF_cep_configure
(
    void
)
{
    /*Control transfers will be handled without DMA*/
    MSS_USB_CIF_set_index_reg(MSS_USB_CEP);
    MSS_USB_CIF_cep_reset_csr0_reg();
    MSS_USB_CIF_cep_enable_irq();
}

/***************************************************************************//**
 Prepares the Control EP for receving data as per the parameters provided by
 upper layer
 */
void
MSS_USBD_CIF_cep_rx_prepare
(
    mss_usb_ep_t* device_ep
)
{
    if(MSS_USB_CEP_IDLE == device_ep[MSS_USB_CEP].state)
    {
        cep_state = MSS_USB_CTRL_EP_IDLE;
    }
    else if((MSS_USB_CEP_SETUP == device_ep[MSS_USB_CEP].state) ||
           (MSS_USB_CEP_DATAOUT == device_ep[MSS_USB_CEP].state))
    {
        cep_state = MSS_USB_CTRL_EP_RX;
    }
}

/***************************************************************************//**
 Prepares the RX EP for receving data as per parameters provided by upper layer
 */
void
MSS_USBD_CIF_rx_ep_read_prepare
(
    mss_usb_ep_t* device_ep
)
{
    /*
     Store the endpoint info to calculate number of bytes transfered by
     DMA and to update the txn_length and xfr_length.
     */
    gd_rx_ep[(device_ep->num)] = device_ep;
    MSS_USB_CIF_rx_ep_enable_irq(device_ep->num);
    if((device_ep->dma_enable == DMA_ENABLE))
    {
        /*Make sure that address is Modulo-4.Bits D0-D1 are read only.*/
        ASSERT(!(((uint32_t)device_ep->buf_addr) & 0x00000002));
        MSS_USB_CIF_dma_write_addr(device_ep->dma_channel,
                                   (uint32_t)device_ep->buf_addr);

        /*
         DMA Count register will be loaded after receive interrupt occures.
         Mode need to be set everytime since M1 to M0 transition might have
         happened for "short packet".
         */
         if((device_ep->xfr_type == MSS_USB_XFR_BULK))
        {
            MSS_USB_CIF_rx_ep_set_dma_mode1(device_ep->num);
            MSS_USB_CIF_rx_ep_set_autoclr(device_ep->num);
            MSS_USB_CIF_rx_ep_enable_dma(device_ep->num);

            MSS_USB_CIF_dma_write_count(device_ep->dma_channel,
                                        device_ep->xfr_length);

            /*Handling single NULL packet reception*/
            if(0u != device_ep->xfr_length )
            {
                MSS_USB_CIF_dma_start_xfr(device_ep->dma_channel);
            }
        }
        else
        {
            MSS_USB_CIF_rx_ep_clr_autoclr(device_ep->num);
            MSS_USB_CIF_rx_ep_set_dma_mode0(device_ep->num);
            MSS_USB_CIF_rx_ep_disable_dma(device_ep->num);
        }
    }
}

/***************************************************************************//**
 Writes a data packet on EP0 in device mode(control endpoint).
 */
void
MSS_USBD_CIF_cep_write_pkt
(
    mss_usb_ep_t* device_ep
)
{
    if(device_ep->num == MSS_USB_CEP)
    {
        /* null buffer, xfr type, transaction type */
        if((device_ep->buf_addr != 0) && (device_ep->xfr_type == MSS_USB_XFR_CONTROL))
        {
            MSS_USB_CIF_load_tx_fifo(device_ep->num,
                                     device_ep->buf_addr,
                                     device_ep->txn_length);

            device_ep->txn_count = device_ep->txn_length;
            device_ep->xfr_count += device_ep->txn_length;
        }

        if(MSS_USB_CEP_DATAIN == device_ep->state)
        {
            cep_state = MSS_USB_CTRL_EP_TX;
        }

        if(device_ep->xfr_count < device_ep->xfr_length)
        {
            MSS_USB_CIF_cep_set_txpktrdy();
        }
        else if(device_ep->xfr_count >= device_ep->xfr_length)
        {
            MSS_USBD_CIF_cep_end_rdr();
        }
    }
}

/***************************************************************************//**
 Reads data packet arrived on EP0 in device mode(control endpoint).
 */
void
MSS_USBD_CIF_cep_read_pkt
(
    mss_usb_ep_t* device_ep
)
{
       volatile uint16_t received_count = 0u;
       MSS_USB_CIF_set_index_reg(MSS_USB_CEP);

    if((device_ep->num == MSS_USB_CEP) &&(device_ep->buf_addr != 0))
    {
        /*TODO: check stalled, null buffer, xfr type, transaction type */
        received_count = MSS_USB_CIF_cep_rx_byte_count();

        MSS_USB_CIF_read_rx_fifo(MSS_USB_CEP,
                                 device_ep->buf_addr,
                                 received_count);

        device_ep->xfr_count += received_count;
        device_ep->txn_count = received_count;
    }
}

#endif  //MSS_USB_DEVICE_ENABLED

#ifdef __cplusplus
}
#endif
