/**************************************************************************//**
 * @file     usbd.c
 * @version  V1.00
 * $Revision: 1 $
 * $Date: 14/08/06 11:56a $
 * @brief    N572 series USBD driver source file
 *
 * @note
 * Copyright (C) 2014 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/

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

#ifdef __cplusplus
extern "C"
{
#endif

/** @addtogroup N572_Device_Driver N572 Device Driver
  @{
*/

/** @addtogroup N572_USBD_Driver USBD Driver
  @{
*/

/** @addtogroup N572_USBD_EXPORTED_FUNCTIONS USBD Exported Functions
  @{
*/
	

/* Global variables for Control Pipe */
uint8_t g_usbd_SetupPacket[8] = {0};

static volatile uint8_t *g_usbd_CtrlInPointer = 0;
static volatile uint32_t g_usbd_CtrlInSize = 0;
static volatile uint8_t *g_usbd_CtrlOutPointer = 0;
static volatile uint32_t g_usbd_CtrlOutSize = 0;
static volatile uint32_t g_usbd_CtrlOutSizeLimit = 0;
static volatile uint32_t g_usbd_UsbAddr = 0;
static volatile uint32_t g_usbd_UsbConfig = 0;
static volatile uint32_t g_usbd_CtrlMaxPktSize = 8;
static volatile uint32_t g_usbd_UsbAltInterface = 0;

S_USBD_INFO_T *g_usbd_sInfo;

CLASS_REQ g_usbd_pfnClassRequest = NULL;
SET_INTERFACE_REQ g_usbd_pfnSetInterface = NULL;

/**
  * @brief      This function makes USBD module to be ready to use
  * @param[in]  param           The structure of USBD information.
  * @param[in]  pfnClassReq     USB Class request callback function.
  * @param[in]  pfnSetInterface USB Set Interface request callback function.
  * @return     None
  * @details    This function will enable USB controller, USB PHY transceiver and pull-up resistor of USB_D+ pin. USB PHY will drive SE0 to bus.
  */
void USBD_Open(S_USBD_INFO_T *param, CLASS_REQ pfnClassReq, SET_INTERFACE_REQ pfnSetInterface)
{
    g_usbd_sInfo = param;
    g_usbd_pfnClassRequest = pfnClassReq;
    g_usbd_pfnSetInterface = pfnSetInterface;

    /* get EP0 maximum packet size */
    g_usbd_CtrlMaxPktSize = g_usbd_sInfo->gu8DevDesc[7];

    /* Initial USB engine */
    USBD->ATTR = 0x7D0;
    /* Force SE0 */
    USBD_SET_SE0();
}

/**
  * @brief    This function makes USB host to recognize the device
  * @return   None
  * @details  Enable WAKEUP, FLDET, USB and BUS interrupts. Disable software-disconnect function after 100ms delay with SysTick timer.
  */
void USBD_Start(void)
{
    /* Disable software-disconnect function */
    USBD_CLR_SE0();

    /* Clear USB-related interrupts before enable interrupt */
    USBD_CLR_INT_FLAG(USBD_INT_BUS | USBD_INT_USB | USBD_INT_FLDET | USBD_INT_WAKEUP);

    /* Enable USB-related interrupts. */
    UBSD_ENABLE_INT(USBD_INT_BUS | USBD_INT_USB | USBD_INT_FLDET | USBD_INT_WAKEUP);
}

/**
  * @brief      Get the received SETUP packet
  * @param[in]  buf A buffer used to store 8-byte SETUP packet.
  * @return     None
  * @details    Store SETUP packet to a user-specified buffer.
  */
void USBD_GetSetupPacket(uint8_t *buf)
{
    USBD_MemCopy(buf, g_usbd_SetupPacket, 8);
}

/**
  * @brief    Process SETUP packet
  * @return   None
  * @details  Parse SETUP packet and perform the corresponding action.
  */
void USBD_ProcessSetupPacket(void)
{
    /* Get SETUP packet from USB buffer */
    USBD_MemCopy(g_usbd_SetupPacket, (uint8_t *)USBD_BUF_BASE, 8);
    /* Check the request type */
    switch(g_usbd_SetupPacket[0] & 0x60)
    {
    case REQ_STANDARD:   // Standard
    {
        USBD_StandardRequest();
        break;
    }
    case REQ_CLASS:   // Class
    {
        if(g_usbd_pfnClassRequest != NULL)
        {
            g_usbd_pfnClassRequest();
        }
        break;
    }
    case REQ_VENDOR:   // Vendor
    {
        break;
    }
    default:   // reserved
    {
        /* Setup error, stall the device */
        USBD_SetStall(EP0);
        USBD_SetStall(EP1);
        break;
    }
    }
}

/**
  * @brief    Process GetDescriptor request
  * @return   None
  * @details  Parse GetDescriptor request and perform the corresponding action.
  */
void USBD_GetDescriptor(void)
{
    uint32_t u32Len;

    u32Len = 0;
    u32Len = g_usbd_SetupPacket[7];
    u32Len <<= 8;
    u32Len += g_usbd_SetupPacket[6];

    switch(g_usbd_SetupPacket[3])
    {
    // Get Device Descriptor
    case DESC_DEVICE:
    {
        u32Len = ( (u32Len<LEN_DEVICE)? u32Len:LEN_DEVICE );
        USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8DevDesc, u32Len);
        USBD_PrepareCtrlOut(0, 0);
        break;
    }
    // Get Configuration Descriptor
    case DESC_CONFIG:
    {
        uint32_t u32TotalLen;

        u32TotalLen = g_usbd_sInfo->gu8ConfigDesc[3];
        u32TotalLen = g_usbd_sInfo->gu8ConfigDesc[2] + (u32TotalLen << 8);

        u32Len = ( (u32Len<u32TotalLen)? u32Len:u32TotalLen );
        USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8ConfigDesc, u32Len);
        USBD_PrepareCtrlOut(0, 0);
        break;
    }
    // Get HID Descriptor
    case DESC_HID:
    {
        u32Len = ( (u32Len<LEN_HID)? u32Len:LEN_HID );
        USBD_PrepareCtrlIn((uint8_t *)&g_usbd_sInfo->gu8ConfigDesc[LEN_CONFIG + LEN_INTERFACE], u32Len);
        USBD_PrepareCtrlOut(0, 0);
        break;
    }
    // Get Report Descriptor
    case DESC_HID_RPT:
    {
        USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8HidReportDesc, u32Len);
        USBD_PrepareCtrlOut(0, 0);
        break;
    }
    // Get String Descriptor
    case DESC_STRING:
    {
        // Get Language
        if(g_usbd_SetupPacket[2] == 0)
        {
            u32Len = ( (u32Len<4)? u32Len:4 );
            USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8StrLangDesc, u32Len);
            USBD_PrepareCtrlOut(0, 0);
        }
        else
        {
            // Get String Descriptor
            switch(g_usbd_SetupPacket[2])
            {
            case 1:
            {
                u32Len = ( (u32Len<g_usbd_sInfo->gu8StrVendorDesc[0])? u32Len:g_usbd_sInfo->gu8StrVendorDesc[0] );
                USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8StrVendorDesc, u32Len);
                USBD_PrepareCtrlOut(0, 0);
                break;
            }
            case 2:
            {
                u32Len = ( (u32Len<g_usbd_sInfo->gu8StrProductDesc[0])? u32Len:g_usbd_sInfo->gu8StrProductDesc[0] );
                USBD_PrepareCtrlIn((uint8_t *)g_usbd_sInfo->gu8StrProductDesc, u32Len);
                USBD_PrepareCtrlOut(0, 0);
                break;
            }
            default:
                // Not support. Reply STALL.
                USBD_SetStall(EP0);
                USBD_SetStall(EP1);
                break;
            }
        }
        break;
    }
    default:
        // Not support. Reply STALL.
        USBD_SetStall(EP0);
        USBD_SetStall(EP1);
        break;
    }
}

/**
  * @brief    Process standard request
  * @return   None
  * @details  Parse standard request and perform the corresponding action.
  */
void USBD_StandardRequest(void)
{
    /* clear global variables for new request */
    g_usbd_CtrlInPointer = 0;
    g_usbd_CtrlInSize = 0;

    if(g_usbd_SetupPacket[0] & 0x80)    /* request data transfer direction */
    {
        // Device to host
        switch(g_usbd_SetupPacket[1])
        {
			case GET_CONFIGURATION:
			{
				// Return current configuration setting
				/* Data stage */
				M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = g_usbd_UsbConfig;
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 1);
				/* Status stage */
				USBD_PrepareCtrlOut(0, 0);
				break;
			}
			case GET_DESCRIPTOR:
			{
				USBD_GetDescriptor();
				break;
			}
			case GET_INTERFACE:
			{
				// Return current interface setting
				/* Data stage */
				M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = g_usbd_UsbAltInterface;
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 1);
				/* Status stage */
				USBD_PrepareCtrlOut(0, 0);
				break;
			}
			case GET_STATUS:
			{
				// Device
				if(g_usbd_SetupPacket[0] == 0x80)
				{
					if(g_usbd_sInfo->gu8ConfigDesc[7] & 0x40)
						M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = 1;  // Self-Powered
					else
						M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = 0;  // bus-Powered
				}
				// Interface
				else if(g_usbd_SetupPacket[0] == 0x81)
					M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = 0;
				// Endpoint
				else if(g_usbd_SetupPacket[0] == 0x82)
				{
					uint8_t ep = g_usbd_SetupPacket[4] & 0xF;
					M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)) = USBD_GetStall(ep) ? 1 : 0;
				}

				M8(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0) + 1) = 0;
				/* Data stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 2);
				/* Status stage */
				USBD_PrepareCtrlOut(0, 0);
				break;
			}
			default:
			{
				/* Setup error, stall the device */
				USBD_SetStall(EP0);
				USBD_SetStall(EP1);
				break;
			}
        }
    }
    else
    {
        // Host to device
        switch(g_usbd_SetupPacket[1])
        {
			case CLEAR_FEATURE:
			{
				if(g_usbd_SetupPacket[2] == FEATURE_ENDPOINT_HALT)
					USBD_ClearStall(g_usbd_SetupPacket[4] & 0xF);
				/* Status stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 0);
				break;
			}
			case SET_ADDRESS:
			{
				g_usbd_UsbAddr = g_usbd_SetupPacket[2];

				// DATA IN for end of setup
				/* Status Stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 0);
				break;
			}
			case SET_CONFIGURATION:
			{
				g_usbd_UsbConfig = g_usbd_SetupPacket[2];

				// DATA IN for end of setup
				/* Status stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 0);
				break;
			}
			case SET_FEATURE:
			{
				if(g_usbd_SetupPacket[2] == FEATURE_ENDPOINT_HALT)
					USBD_SetStall(g_usbd_SetupPacket[4] & 0xF);
				/* Status stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 0);
				break;
			}
			case SET_INTERFACE:
			{
				g_usbd_UsbAltInterface = g_usbd_SetupPacket[2];
				if(g_usbd_pfnSetInterface != NULL)
					g_usbd_pfnSetInterface();
				/* Status stage */
				USBD_SET_DATA1(EP0);
				USBD_SET_PAYLOAD_LEN(EP0, 0);
				break;
			}
			default:
			{
				/* Setup error, stall the device */
				USBD_SetStall(EP0);
				USBD_SetStall(EP1);
				break;
			}
        }
    }
}

/**
  * @brief      Prepare the first Control IN pipe
  * @param[in]  pu8Buf  The pointer of data sent to USB host.
  * @param[in]  u32Size The IN transfer size.
  * @return     None
  * @details    Prepare data for Control IN transfer.
  */
void USBD_PrepareCtrlIn(uint8_t *pu8Buf, uint32_t u32Size)
{
    if(u32Size > g_usbd_CtrlMaxPktSize)
    {
        // Data size > MXPLD
        g_usbd_CtrlInPointer = pu8Buf + g_usbd_CtrlMaxPktSize;
        g_usbd_CtrlInSize = u32Size - g_usbd_CtrlMaxPktSize;
        USBD_SET_DATA1(EP0);
        USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)), pu8Buf, g_usbd_CtrlMaxPktSize);
        USBD_SET_PAYLOAD_LEN(EP0, g_usbd_CtrlMaxPktSize);
    }
    else
    {
        // Data size <= MXPLD
        g_usbd_CtrlInPointer = 0;
        g_usbd_CtrlInSize = 0;
        USBD_SET_DATA1(EP0);
        USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)), pu8Buf, u32Size);
        USBD_SET_PAYLOAD_LEN(EP0, u32Size);
    }
}

/**
  * @brief    Repeat Control IN pipe
  * @return   None
  * @details  This function processes the remained data of Control IN transfer.
  */
void USBD_CtrlIn(void)
{
    if(g_usbd_CtrlInSize)
    {
        // Process remained data
        if(g_usbd_CtrlInSize > g_usbd_CtrlMaxPktSize)
        {
            // Data size > MXPLD
            USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)), (uint8_t *)g_usbd_CtrlInPointer, g_usbd_CtrlMaxPktSize);
            USBD_SET_PAYLOAD_LEN(EP0, g_usbd_CtrlMaxPktSize);
            g_usbd_CtrlInPointer += g_usbd_CtrlMaxPktSize;
            g_usbd_CtrlInSize -= g_usbd_CtrlMaxPktSize;
        }
        else
        {
            // Data size <= MXPLD
            USBD_MemCopy((uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP0)), (uint8_t *)g_usbd_CtrlInPointer, g_usbd_CtrlInSize);
            USBD_SET_PAYLOAD_LEN(EP0, g_usbd_CtrlInSize);
            g_usbd_CtrlInPointer = 0;
            g_usbd_CtrlInSize = 0;
        }
    }
    else
    {
        // In ACK for Set address
        if((g_usbd_SetupPacket[0] == 0) && (g_usbd_SetupPacket[1] == 5))
        {
            if((USBD_GET_ADDR() != g_usbd_UsbAddr) && (USBD_GET_ADDR() == 0))
            {
                USBD_SET_ADDR(g_usbd_UsbAddr);
            }
        }
        // No more data for IN token
        USBD_SET_PAYLOAD_LEN(EP0, 0);
    }
}

/**
  * @brief      Prepare the first Control OUT pipe
  * @param[in]  pu8Buf  The pointer of data received from USB host.
  * @param[in]  u32Size The OUT transfer size.
  * @return     None
  * @details    This function is used to prepare the first Control OUT transfer.
  */
void USBD_PrepareCtrlOut(uint8_t *pu8Buf, uint32_t u32Size)
{
    g_usbd_CtrlOutPointer = pu8Buf;
    g_usbd_CtrlOutSize = 0;
    g_usbd_CtrlOutSizeLimit = u32Size;
    USBD_SET_PAYLOAD_LEN(EP1, g_usbd_CtrlMaxPktSize);
}

/**
  * @brief    Repeat Control OUT pipe
  * @return   None
  * @details  This function processes the successive Control OUT transfer.
  */
void USBD_CtrlOut(void)
{
    uint32_t u32Size;

    if(g_usbd_CtrlOutSize < g_usbd_CtrlOutSizeLimit)
    {
        u32Size = USBD_GET_PAYLOAD_LEN(EP1);
        USBD_MemCopy((uint8_t *)g_usbd_CtrlOutPointer, (uint8_t *)(USBD_BUF_BASE + USBD_GET_EP_BUF_ADDR(EP1)), u32Size);
        g_usbd_CtrlOutPointer += u32Size;
        g_usbd_CtrlOutSize += u32Size;
        
        if(g_usbd_CtrlOutSize < g_usbd_CtrlOutSizeLimit)
            USBD_SET_PAYLOAD_LEN(EP1, g_usbd_CtrlMaxPktSize);
    }
}

/**
  * @brief    Reset software flags
  * @return   None
  * @details  This function resets all variables for protocol and resets USB device address to 0.
  *
  */
void USBD_SwReset(void)
{
    // Reset all variables for protocol
    g_usbd_CtrlInPointer = 0;
    g_usbd_CtrlInSize = 0;
    g_usbd_CtrlOutPointer = 0;
    g_usbd_CtrlOutSize = 0;
    g_usbd_CtrlOutSizeLimit = 0;
    memset(g_usbd_SetupPacket, 0, 8);

    // Reset USB device address
    USBD_SET_ADDR(0);
}

/*@}*/ /* end of group N572_USBD_EXPORTED_FUNCTIONS */

/*@}*/ /* end of group N572_USBD_Driver */

/*@}*/ /* end of group N572_Device_Driver */

#ifdef __cplusplus
}
#endif

/*** (C) COPYRIGHT 2014 Nuvoton Technology Corp. ***/
