/***************************************************************************//**
* \file CDC_drv.c
* \version 3.20
*
* \brief
*  This file contains the Endpoint 0 Driver for the USBFS Component.  
*
********************************************************************************
* \copyright
* Copyright 2008-2016, Cypress Semiconductor Corporation.  All rights reserved.
* You may use this file only in accordance with the license, terms, conditions,
* disclaimers, and limitations in the end user license agreement accompanying
* the software package with which this file was provided.
*******************************************************************************/

#include "CDC_pvt.h"
#include "cyapicallbacks.h"


/***************************************
* Global data allocation
***************************************/

volatile T_CDC_EP_CTL_BLOCK CDC_EP[CDC_MAX_EP];

/** Contains the current configuration number, which is set by the host using a 
 * SET_CONFIGURATION request. This variable is initialized to zero in 
 * USBFS_InitComponent() API and can be read by the USBFS_GetConfiguration() 
 * API.*/
volatile uint8 CDC_configuration;

/** Contains the current interface number.*/
volatile uint8 CDC_interfaceNumber;

/** This variable is set to one after SET_CONFIGURATION and SET_INTERFACE 
 *requests. It can be read by the USBFS_IsConfigurationChanged() API */
volatile uint8 CDC_configurationChanged;

/** Contains the current device address.*/
volatile uint8 CDC_deviceAddress;

/** This is a two-bit variable that contains power status in the bit 0 
 * (DEVICE_STATUS_BUS_POWERED or DEVICE_STATUS_SELF_POWERED) and remote wakeup 
 * status (DEVICE_STATUS_REMOTE_WAKEUP) in the bit 1. This variable is 
 * initialized to zero in USBFS_InitComponent() API, configured by the 
 * USBFS_SetPowerStatus() API. The remote wakeup status cannot be set using the 
 * API SetPowerStatus(). */
volatile uint8 CDC_deviceStatus;

volatile uint8 CDC_interfaceSetting[CDC_MAX_INTERFACES_NUMBER];
volatile uint8 CDC_interfaceSetting_last[CDC_MAX_INTERFACES_NUMBER];
volatile uint8 CDC_interfaceStatus[CDC_MAX_INTERFACES_NUMBER];

/** Contains the started device number. This variable is set by the 
 * USBFS_Start() or USBFS_InitComponent() APIs.*/
volatile uint8 CDC_device;

/** Initialized class array for each interface. It is used for handling Class 
 * specific requests depend on interface class. Different classes in multiple 
 * alternate settings are not supported.*/
const uint8 CYCODE *CDC_interfaceClass;


/***************************************
* Local data allocation
***************************************/

volatile uint8  CDC_ep0Toggle;
volatile uint8  CDC_lastPacketSize;

/** This variable is used by the communication functions to handle the current 
* transfer state.
* Initialized to TRANS_STATE_IDLE in the USBFS_InitComponent() API and after a 
* complete transfer in the status stage.
* Changed to the TRANS_STATE_CONTROL_READ or TRANS_STATE_CONTROL_WRITE in setup 
* transaction depending on the request type.
*/
volatile uint8  CDC_transferState;
volatile T_CDC_TD CDC_currentTD;
volatile uint8  CDC_ep0Mode;
volatile uint8  CDC_ep0Count;
volatile uint16 CDC_transferByteCount;


/*******************************************************************************
* Function Name: CDC_ep_0_Interrupt
****************************************************************************//**
*
*  This Interrupt Service Routine handles Endpoint 0 (Control Pipe) traffic.
*  It dispatches setup requests and handles the data and status stages.
*
*
*******************************************************************************/
CY_ISR(CDC_EP_0_ISR)
{
    uint8 tempReg;
    uint8 modifyReg;

#ifdef CDC_EP_0_ISR_ENTRY_CALLBACK
    CDC_EP_0_ISR_EntryCallback();
#endif /* (CDC_EP_0_ISR_ENTRY_CALLBACK) */
    
    tempReg = CDC_EP0_CR_REG;
    if ((tempReg & CDC_MODE_ACKD) != 0u)
    {
        modifyReg = 1u;
        if ((tempReg & CDC_MODE_SETUP_RCVD) != 0u)
        {
            if ((tempReg & CDC_MODE_MASK) != CDC_MODE_NAK_IN_OUT)
            {
                /* Mode not equal to NAK_IN_OUT: invalid setup */
                modifyReg = 0u;
            }
            else
            {
                CDC_HandleSetup();
                
                if ((CDC_ep0Mode & CDC_MODE_SETUP_RCVD) != 0u)
                {
                    /* SETUP bit set: exit without mode modificaiton */
                    modifyReg = 0u;
                }
            }
        }
        else if ((tempReg & CDC_MODE_IN_RCVD) != 0u)
        {
            CDC_HandleIN();
        }
        else if ((tempReg & CDC_MODE_OUT_RCVD) != 0u)
        {
            CDC_HandleOUT();
        }
        else
        {
            modifyReg = 0u;
        }
        
        /* Modify the EP0_CR register */
        if (modifyReg != 0u)
        {
            
            tempReg = CDC_EP0_CR_REG;
            
            /* Make sure that SETUP bit is cleared before modification */
            if ((tempReg & CDC_MODE_SETUP_RCVD) == 0u)
            {
                /* Update count register */
                tempReg = (uint8) CDC_ep0Toggle | CDC_ep0Count;
                CDC_EP0_CNT_REG = tempReg;
               
                /* Make sure that previous write operaiton was successful */
                if (tempReg == CDC_EP0_CNT_REG)
                {
                    /* Repeat until next successful write operation */
                    do
                    {
                        /* Init temporary variable */
                        modifyReg = CDC_ep0Mode;
                        
                        /* Unlock register */
                        tempReg = (uint8) (CDC_EP0_CR_REG & CDC_MODE_SETUP_RCVD);
                        
                        /* Check if SETUP bit is not set */
                        if (0u == tempReg)
                        {
                            /* Set the Mode Register  */
                            CDC_EP0_CR_REG = CDC_ep0Mode;
                            
                            /* Writing check */
                            modifyReg = CDC_EP0_CR_REG & CDC_MODE_MASK;
                        }
                    }
                    while (modifyReg != CDC_ep0Mode);
                }
            }
        }
    }

    CDC_ClearSieInterruptSource(CDC_INTR_SIE_EP0_INTR);
	
#ifdef CDC_EP_0_ISR_EXIT_CALLBACK
    CDC_EP_0_ISR_ExitCallback();
#endif /* (CDC_EP_0_ISR_EXIT_CALLBACK) */
}


/*******************************************************************************
* Function Name: CDC_HandleSetup
****************************************************************************//**
*
*  This Routine dispatches requests for the four USB request types
*
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_HandleSetup(void) 
{
    uint8 requestHandled;
    
    /* Clear register lock by SIE (read register) and clear setup bit 
    * (write any value in register).
    */
    requestHandled = (uint8) CDC_EP0_CR_REG;
    CDC_EP0_CR_REG = (uint8) requestHandled;
    requestHandled = (uint8) CDC_EP0_CR_REG;

    if ((requestHandled & CDC_MODE_SETUP_RCVD) != 0u)
    {
        /* SETUP bit is set: exit without mode modification. */
        CDC_ep0Mode = requestHandled;
    }
    else
    {
        /* In case the previous transfer did not complete, close it out */
        CDC_UpdateStatusBlock(CDC_XFER_PREMATURE);

        /* Check request type. */
        switch (CDC_bmRequestTypeReg & CDC_RQST_TYPE_MASK)
        {
            case CDC_RQST_TYPE_STD:
                requestHandled = CDC_HandleStandardRqst();
                break;
                
            case CDC_RQST_TYPE_CLS:
                requestHandled = CDC_DispatchClassRqst();
                break;
                
            case CDC_RQST_TYPE_VND:
                requestHandled = CDC_HandleVendorRqst();
                break;
                
            default:
                requestHandled = CDC_FALSE;
                break;
        }
        
        /* If request is not recognized. Stall endpoint 0 IN and OUT. */
        if (requestHandled == CDC_FALSE)
        {
            CDC_ep0Mode = CDC_MODE_STALL_IN_OUT;
        }
    }
}


/*******************************************************************************
* Function Name: CDC_HandleIN
****************************************************************************//**
*
*  This routine handles EP0 IN transfers.
*
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_HandleIN(void) 
{
    switch (CDC_transferState)
    {
        case CDC_TRANS_STATE_IDLE:
            break;
        
        case CDC_TRANS_STATE_CONTROL_READ:
            CDC_ControlReadDataStage();
            break;
            
        case CDC_TRANS_STATE_CONTROL_WRITE:
            CDC_ControlWriteStatusStage();
            break;
            
        case CDC_TRANS_STATE_NO_DATA_CONTROL:
            CDC_NoDataControlStatusStage();
            break;
            
        default:    /* there are no more states */
            break;
    }
}


/*******************************************************************************
* Function Name: CDC_HandleOUT
****************************************************************************//**
*
*  This routine handles EP0 OUT transfers.
*
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_HandleOUT(void) 
{
    switch (CDC_transferState)
    {
        case CDC_TRANS_STATE_IDLE:
            break;
        
        case CDC_TRANS_STATE_CONTROL_READ:
            CDC_ControlReadStatusStage();
            break;
            
        case CDC_TRANS_STATE_CONTROL_WRITE:
            CDC_ControlWriteDataStage();
            break;
            
        case CDC_TRANS_STATE_NO_DATA_CONTROL:
            /* Update the completion block */
            CDC_UpdateStatusBlock(CDC_XFER_ERROR);
            
            /* We expect no more data, so stall INs and OUTs */
            CDC_ep0Mode = CDC_MODE_STALL_IN_OUT;
            break;
            
        default:    
            /* There are no more states */
            break;
    }
}


/*******************************************************************************
* Function Name: CDC_LoadEP0
****************************************************************************//**
*
*  This routine loads the EP0 data registers for OUT transfers. It uses the
*  currentTD (previously initialized by the _InitControlWrite function and
*  updated for each OUT transfer, and the bLastPacketSize) to determine how
*  many uint8s to transfer on the current OUT.
*
*  If the number of uint8s remaining is zero and the last transfer was full,
*  we need to send a zero length packet.  Otherwise we send the minimum
*  of the control endpoint size (8) or remaining number of uint8s for the
*  transaction.
*
*
* \globalvars
*  CDC_transferByteCount - Update the transfer byte count from the
*     last transaction.
*  CDC_ep0Count - counts the data loaded to the SIE memory in
*     current packet.
*  CDC_lastPacketSize - remembers the USBFS_ep0Count value for the
*     next packet.
*  CDC_transferByteCount - sum of the previous bytes transferred
*     on previous packets(sum of USBFS_lastPacketSize)
*  CDC_ep0Toggle - inverted
*  CDC_ep0Mode  - prepare for mode register content.
*  CDC_transferState - set to TRANS_STATE_CONTROL_READ
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_LoadEP0(void) 
{
    uint8 ep0Count = 0u;

    /* Update the transfer byte count from the last transaction */
    CDC_transferByteCount += CDC_lastPacketSize;

    /* Now load the next transaction */
    while ((CDC_currentTD.count > 0u) && (ep0Count < 8u))
    {
        CDC_EP0_DR_BASE.epData[ep0Count] = (uint8) *CDC_currentTD.pData;
        CDC_currentTD.pData = &CDC_currentTD.pData[1u];
        ep0Count++;
        CDC_currentTD.count--;
    }

    /* Support zero-length packet */
    if ((CDC_lastPacketSize == 8u) || (ep0Count > 0u))
    {
        /* Update the data toggle */
        CDC_ep0Toggle ^= CDC_EP0_CNT_DATA_TOGGLE;
        /* Set the Mode Register  */
        CDC_ep0Mode = CDC_MODE_ACK_IN_STATUS_OUT;
        /* Update the state (or stay the same) */
        CDC_transferState = CDC_TRANS_STATE_CONTROL_READ;
    }
    else
    {
        /* Expect Status Stage Out */
        CDC_ep0Mode = CDC_MODE_STATUS_OUT_ONLY;
        /* Update the state (or stay the same) */
        CDC_transferState = CDC_TRANS_STATE_CONTROL_READ;
    }

    /* Save the packet size for next time */
    CDC_ep0Count =       (uint8) ep0Count;
    CDC_lastPacketSize = (uint8) ep0Count;
}


/*******************************************************************************
* Function Name: CDC_InitControlRead
****************************************************************************//**
*
*  Initialize a control read transaction. It is used to send data to the host.
*  The following global variables should be initialized before this function
*  called. To send zero length packet use InitZeroLengthControlTransfer
*  function.
*
*
* \return
*  requestHandled state.
*
* \globalvars
*  CDC_currentTD.count - counts of data to be sent.
*  CDC_currentTD.pData - data pointer.
*
* \reentrant
*  No.
*
*******************************************************************************/
uint8 CDC_InitControlRead(void) 
{
    uint16 xferCount;

    if (CDC_currentTD.count == 0u)
    {
        (void) CDC_InitZeroLengthControlTransfer();
    }
    else
    {
        /* Set up the state machine */
        CDC_transferState = CDC_TRANS_STATE_CONTROL_READ;
        
        /* Set the toggle, it gets updated in LoadEP */
        CDC_ep0Toggle = 0u;
        
        /* Initialize the Status Block */
        CDC_InitializeStatusBlock();
        
        xferCount = ((uint16)((uint16) CDC_lengthHiReg << 8u) | ((uint16) CDC_lengthLoReg));

        if (CDC_currentTD.count > xferCount)
        {
            CDC_currentTD.count = xferCount;
        }
        
        CDC_LoadEP0();
    }

    return (CDC_TRUE);
}


/*******************************************************************************
* Function Name: CDC_InitZeroLengthControlTransfer
****************************************************************************//**
*
*  Initialize a zero length data IN transfer.
*
* \return
*  requestHandled state.
*
* \globalvars
*  CDC_ep0Toggle - set to EP0_CNT_DATA_TOGGLE
*  CDC_ep0Mode  - prepare for mode register content.
*  CDC_transferState - set to TRANS_STATE_CONTROL_READ
*  CDC_ep0Count - cleared, means the zero-length packet.
*  CDC_lastPacketSize - cleared.
*
* \reentrant
*  No.
*
*******************************************************************************/
uint8 CDC_InitZeroLengthControlTransfer(void)
                                                
{
    /* Update the state */
    CDC_transferState = CDC_TRANS_STATE_CONTROL_READ;
    
    /* Set the data toggle */
    CDC_ep0Toggle = CDC_EP0_CNT_DATA_TOGGLE;
    
    /* Set the Mode Register  */
    CDC_ep0Mode = CDC_MODE_ACK_IN_STATUS_OUT;
    
    /* Save the packet size for next time */
    CDC_lastPacketSize = 0u;
    
    CDC_ep0Count = 0u;

    return (CDC_TRUE);
}


/*******************************************************************************
* Function Name: CDC_ControlReadDataStage
****************************************************************************//**
*
*  Handle the Data Stage of a control read transfer.
*
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_ControlReadDataStage(void) 

{
    CDC_LoadEP0();
}


/*******************************************************************************
* Function Name: CDC_ControlReadStatusStage
****************************************************************************//**
*
*  Handle the Status Stage of a control read transfer.
*
*
* \globalvars
*  CDC_USBFS_transferByteCount - updated with last packet size.
*  CDC_transferState - set to TRANS_STATE_IDLE.
*  CDC_ep0Mode  - set to MODE_STALL_IN_OUT.
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_ControlReadStatusStage(void) 
{
    /* Update the transfer byte count */
    CDC_transferByteCount += CDC_lastPacketSize;
    
    /* Go Idle */
    CDC_transferState = CDC_TRANS_STATE_IDLE;
    
    /* Update the completion block */
    CDC_UpdateStatusBlock(CDC_XFER_STATUS_ACK);
    
    /* We expect no more data, so stall INs and OUTs */
    CDC_ep0Mode = CDC_MODE_STALL_IN_OUT;
}


/*******************************************************************************
* Function Name: CDC_InitControlWrite
****************************************************************************//**
*
*  Initialize a control write transaction
*
* \return
*  requestHandled state.
*
* \globalvars
*  CDC_USBFS_transferState - set to TRANS_STATE_CONTROL_WRITE
*  CDC_ep0Toggle - set to EP0_CNT_DATA_TOGGLE
*  CDC_ep0Mode  - set to MODE_ACK_OUT_STATUS_IN
*
* \reentrant
*  No.
*
*******************************************************************************/
uint8 CDC_InitControlWrite(void) 
{
    uint16 xferCount;

    /* Set up the state machine */
    CDC_transferState = CDC_TRANS_STATE_CONTROL_WRITE;
    
    /* This might not be necessary */
    CDC_ep0Toggle = CDC_EP0_CNT_DATA_TOGGLE;
    
    /* Initialize the Status Block */
    CDC_InitializeStatusBlock();

    xferCount = ((uint16)((uint16) CDC_lengthHiReg << 8u) | ((uint16) CDC_lengthLoReg));

    if (CDC_currentTD.count > xferCount)
    {
        CDC_currentTD.count = xferCount;
    }

    /* Expect Data or Status Stage */
    CDC_ep0Mode = CDC_MODE_ACK_OUT_STATUS_IN;

    return(CDC_TRUE);
}


/*******************************************************************************
* Function Name: CDC_ControlWriteDataStage
****************************************************************************//**
*
*  Handle the Data Stage of a control write transfer
*       1. Get the data (We assume the destination was validated previously)
*       2. Update the count and data toggle
*       3. Update the mode register for the next transaction
*
*
* \globalvars
*  CDC_transferByteCount - Update the transfer byte count from the
*    last transaction.
*  CDC_ep0Count - counts the data loaded from the SIE memory
*    in current packet.
*  CDC_transferByteCount - sum of the previous bytes transferred
*    on previous packets(sum of USBFS_lastPacketSize)
*  CDC_ep0Toggle - inverted
*  CDC_ep0Mode  - set to MODE_ACK_OUT_STATUS_IN.
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_ControlWriteDataStage(void) 
{
    uint8 ep0Count;
    uint8 regIndex = 0u;

    ep0Count = (CDC_EP0_CNT_REG & CDC_EPX_CNT0_MASK) - CDC_EPX_CNTX_CRC_COUNT;

    CDC_transferByteCount += (uint8)ep0Count;

    while ((CDC_currentTD.count > 0u) && (ep0Count > 0u))
    {
        *CDC_currentTD.pData = (uint8) CDC_EP0_DR_BASE.epData[regIndex];
        CDC_currentTD.pData = &CDC_currentTD.pData[1u];
        regIndex++;
        ep0Count--;
        CDC_currentTD.count--;
    }
    
    CDC_ep0Count = (uint8)ep0Count;
    
    /* Update the data toggle */
    CDC_ep0Toggle ^= CDC_EP0_CNT_DATA_TOGGLE;
    
    /* Expect Data or Status Stage */
    CDC_ep0Mode = CDC_MODE_ACK_OUT_STATUS_IN;
}


/*******************************************************************************
* Function Name: CDC_ControlWriteStatusStage
****************************************************************************//**
*
*  Handle the Status Stage of a control write transfer
*
* \globalvars
*  CDC_transferState - set to TRANS_STATE_IDLE.
*  CDC_USBFS_ep0Mode  - set to MODE_STALL_IN_OUT.
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_ControlWriteStatusStage(void) 
{
    /* Go Idle */
    CDC_transferState = CDC_TRANS_STATE_IDLE;
    
    /* Update the completion block */    
    CDC_UpdateStatusBlock(CDC_XFER_STATUS_ACK);
    
    /* We expect no more data, so stall INs and OUTs */
    CDC_ep0Mode = CDC_MODE_STALL_IN_OUT;
}


/*******************************************************************************
* Function Name: CDC_InitNoDataControlTransfer
****************************************************************************//**
*
*  Initialize a no data control transfer
*
* \return
*  requestHandled state.
*
* \globalvars
*  CDC_transferState - set to TRANS_STATE_NO_DATA_CONTROL.
*  CDC_ep0Mode  - set to MODE_STATUS_IN_ONLY.
*  CDC_ep0Count - cleared.
*  CDC_ep0Toggle - set to EP0_CNT_DATA_TOGGLE
*
* \reentrant
*  No.
*
*******************************************************************************/
uint8 CDC_InitNoDataControlTransfer(void) 
{
    CDC_transferState = CDC_TRANS_STATE_NO_DATA_CONTROL;
    CDC_ep0Mode       = CDC_MODE_STATUS_IN_ONLY;
    CDC_ep0Toggle     = CDC_EP0_CNT_DATA_TOGGLE;
    CDC_ep0Count      = 0u;

    return (CDC_TRUE);
}


/*******************************************************************************
* Function Name: CDC_NoDataControlStatusStage
****************************************************************************//**
*  Handle the Status Stage of a no data control transfer.
*
*  SET_ADDRESS is special, since we need to receive the status stage with
*  the old address.
*
* \globalvars
*  CDC_transferState - set to TRANS_STATE_IDLE.
*  CDC_ep0Mode  - set to MODE_STALL_IN_OUT.
*  CDC_ep0Toggle - set to EP0_CNT_DATA_TOGGLE
*  CDC_deviceAddress - used to set new address and cleared
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_NoDataControlStatusStage(void) 
{
    if (0u != CDC_deviceAddress)
    {
        /* Update device address if we got new address. */
        CDC_CR0_REG = (uint8) CDC_deviceAddress | CDC_CR0_ENABLE;
        CDC_deviceAddress = 0u;
    }

    CDC_transferState = CDC_TRANS_STATE_IDLE;
    
    /* Update the completion block. */
    CDC_UpdateStatusBlock(CDC_XFER_STATUS_ACK);
    
    /* Stall IN and OUT, no more data is expected. */
    CDC_ep0Mode = CDC_MODE_STALL_IN_OUT;
}


/*******************************************************************************
* Function Name: CDC_UpdateStatusBlock
****************************************************************************//**
*
*  Update the Completion Status Block for a Request.  The block is updated
*  with the completion code the CDC_transferByteCount.  The
*  StatusBlock Pointer is set to NULL.
*
*  completionCode - status.
*
*
* \globalvars
*  CDC_currentTD.pStatusBlock->status - updated by the
*    completionCode parameter.
*  CDC_currentTD.pStatusBlock->length - updated.
*  CDC_currentTD.pStatusBlock - cleared.
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_UpdateStatusBlock(uint8 completionCode) 
{
    if (CDC_currentTD.pStatusBlock != NULL)
    {
        CDC_currentTD.pStatusBlock->status = completionCode;
        CDC_currentTD.pStatusBlock->length = CDC_transferByteCount;
        CDC_currentTD.pStatusBlock = NULL;
    }
}


/*******************************************************************************
* Function Name: CDC_InitializeStatusBlock
****************************************************************************//**
*
*  Initialize the Completion Status Block for a Request.  The completion
*  code is set to USB_XFER_IDLE.
*
*  Also, initializes CDC_transferByteCount.  Save some space,
*  this is the only consumer.
*
* \globalvars
*  CDC_currentTD.pStatusBlock->status - set to XFER_IDLE.
*  CDC_currentTD.pStatusBlock->length - cleared.
*  CDC_transferByteCount - cleared.
*
* \reentrant
*  No.
*
*******************************************************************************/
void CDC_InitializeStatusBlock(void) 
{
    CDC_transferByteCount = 0u;
    
    if (CDC_currentTD.pStatusBlock != NULL)
    {
        CDC_currentTD.pStatusBlock->status = CDC_XFER_IDLE;
        CDC_currentTD.pStatusBlock->length = 0u;
    }
}


/* [] END OF FILE */
