/* @file pal_usb.c
 *
 * @brief PAL USB related functions
 *
 * This file implements SAM3 USB related functions
 * and reception functions.
 *
 * $Id: pal_usb.c 27470 2011-06-24 05:24:36Z mahendran.p $
 *
 * @author    Atmel Corporation: http://www.atmel.com
 * @author    Support email: avr@atmel.com
 */
/*
 * Copyright (c) 2010, Atmel Corporation All rights reserved.
 *
 * Licensed under Atmel's Limited License Agreement --> EULA.txt
 */

#if ((defined USB0))
/* === Includes =========================================================*/
#include "pal_usb.h"
#include <Descriptors/CDCDSerialDriverDescriptors.h>
#include <usb/device/core/USBD.h>
#include <usb/device/cdc-serial/CDCDSerialDriver.h>
#include <usb/device/core/USBDDriver.h>
#include <usb/common/cdc/CDCLineCoding.h>
#include <usb/common/cdc/CDCGenericRequest.h>
#include <usb/common/cdc/CDCSetControlLineStateRequest.h>

/* === Globals =========================================================*/

/* Usb tempoary Transmission and receive buffers */
static uint8_t usb_0_tx_temp_buf[USB_TX_BUF_SIZE];
static uint8_t usb_0_rx_temp_buf[USB_RX_BUF_SIZE];

/* USB communication buffer */
static usb_communication_buffer_t usb_0_buffer;

/* Flag to indicate whether the transmission in progress or not */
volatile bool block_tx;

/* VBUS configuration based on the PIN USB VBUS*/
#if defined(PIN_USB_VBUS)
#define VBUS_CONFIGURE()  VBus_Configure()
static const pin_t pinVbus = PIN_USB_VBUS;
#else
#define VBUS_CONFIGURE()    USBD_Connect()
#endif

/* Returns the minimum between two values */
#define MIN(a, b)       ((a < b) ? a : b)

/* === Prototypes =========================================================*/
void usb0_tx_complete_handler(  void *pArg ,
                                unsigned char   status,
                                unsigned int    transferred,
                                unsigned int    remaining);

void usb0_rx_complete_handler(  void *pArg ,
                                unsigned char status,
                                unsigned int received,
                                unsigned int remaining);

/* === Implementation ======================================================= */


/**
 * @brief Configures USB clock
 */
void ConfigureUsbClock(void)
{
    PMC->PMC_USB = ((PMC_USB_USBDIV & (1<<8))    /* 2 */
                   | (PMC_USB_USBS & 1));        /* PLLB */
}


#if defined(PIN_USB_VBUS)
/**
 * @brief Handles VBUS interrupt
 *
 * @param pPin-pin descriptor of the interrupt
 */
static void ISR_Vbus(const pin_t *pPin)
{
    /* Check current level on VBus */
    if (pal_pio_get(&pinVbus))
    {
        USBD_Connect();
    }
    else
    {
        USBD_Disconnect();
    }
    pPin = pPin;
}


/**
 * @brief Configures the VBus pin to trigger an interrupt when the
 * level on that pin changes
 */
static void VBus_Configure( void )
{
    /*Configure PIO for usb */
    pal_pio_configure(&pinVbus, PIO_LISTSIZE(pinVbus));
    pal_pio_configure_interrupt(&pinVbus, ISR_Vbus);
    pal_pio_enable_interrupt(&pinVbus);

    /*  Check current level on VBus */
    if (pal_pio_get(&pinVbus))
    {
        /* if VBUS present, force the connect */
        USBD_Connect();
    }
    else
    {
        USBD_Disconnect();
    }
 }
#endif


/**
  * @brief Bytes free in the buffer
  *
  * This function computes the free buffer size in a cyclic buffer.
  *
  * @param head - index of the head
  * @param tail - index of the tail
  * @param max_buf_size	maximum size of the used buffer
  *
  * @return Number of bytes available in the buffers
  */
uint8_t get_buffer_size(uint8_t head, uint8_t tail, uint8_t max_buf_size)
{
    uint8_t size;
    if (tail >= head)
    {
        size = (max_buf_size) - (tail - head);
    }
    else
    {
        size = (head - 1) - tail;
    }

    return size;
}


/**
 * @brief Initializes USB 0
 *
 * This function initializes the USB0 Channel and the ring buffer parameters.
 *
 */
void sio_usb_0_init(void)
{
    /* Initialize the buffer parameters */
    usb_0_buffer.tx_buf_head = 0;
    usb_0_buffer.tx_buf_tail = 0;
    usb_0_buffer.rx_buf_head = 0;
    usb_0_buffer.rx_buf_tail = 0;
    usb_0_buffer.rx_count = 0;

    /* Set the flag to false during initialization */
    block_tx = false;

    /* Disconnect to if USB stick has been powered before*/
    USBD_Disconnect();

    /* Enable PLL for USB */
    ConfigureUsbClock();

    /* CDC serial driver initialization */
    CDCDSerialDriver_Initialize();

    /* connect if needed */
    VBUS_CONFIGURE();

    /* Wait until the state is changed to configured state*/
    while (USBD_GetState() < USBD_STATE_CONFIGURED);

    /* USB serial driver read is called to give the pointer to the data buffer
     to put received data */
    CDCDSerialDriver_Read(usb_0_rx_temp_buf, sizeof(usb_0_rx_temp_buf),
                      (TransferCallback) usb0_rx_complete_handler, 0);
}


/**
 * @brief Transmit data via USB 0
 *
 * This function transmits data via USB channel 0.
 *
 * @param data Pointer to the buffer where the data to be transmitted is present
 * @param length Number of bytes to be transmitted
 *
 * @return Number of bytes actually transmitted
 */

uint8_t sio_usb_0_tx(uint8_t *data, uint8_t length)
{

    uint8_t bytes_to_be_written = 0;
    uint8_t head = 0;
    uint8_t tail = 0;
    uint8_t size = 0;
    uint8_t back = 0;

    uint8_t tx_length = 0;
    uint8_t i = 0;

    /* Check whether the tranmission is ongoing or not before sending data */
    while (block_tx == true);

    /*
     * Initialize head and tail
     */
    head = usb_0_buffer.tx_buf_head;
    tail = usb_0_buffer.tx_buf_tail;

    size = get_buffer_size( usb_0_buffer.tx_buf_head,
                            usb_0_buffer.tx_buf_tail,
                            USB_TX_BUF_MAX_SIZE);
    if (size < length)
    {
        /* Not enough buffer space available. Use the remaining size. */
        bytes_to_be_written = size;
    }
    else
    {
        bytes_to_be_written = length;
    }

    /* Remember the number of bytes transmitted. */
    back = bytes_to_be_written;

    /* The data is copied to the transmit buffer. */
    while (bytes_to_be_written > 0)
    {
        usb_0_buffer.tx_buf[usb_0_buffer.tx_buf_tail] = *data;
        if ((USB_TX_BUF_MAX_SIZE - 1) == usb_0_buffer.tx_buf_tail)
        {
            /* Reached the end of buffer, revert back to beginning of buffer. */
            usb_0_buffer.tx_buf_tail = 0;
        }
        else
        {
            /*
             * Increment the index to point the next character to be
             * inserted.
             */
            usb_0_buffer.tx_buf_tail++;
        }

        bytes_to_be_written--;
        data++;
    }

    /*
     * Check whether there is a transmission ongoing. Otherwise write
     * data into the UART data register. Transmission of subsequent
     * bytes / data will be taken care in the ISR.
     */

    /*
     * Prepare a linear buffer for submitting data
    */
    head = usb_0_buffer.tx_buf_head;
    tail = usb_0_buffer.tx_buf_tail;

    while (head != tail)
    {
        usb_0_tx_temp_buf[i] = usb_0_buffer.tx_buf[head];
        /* cannot transmit more than USB_TX_MAX_SIZE at once */
        if (i == USB_TX_MAX_SIZE)
        break;
        if ((head) == (USB_TX_BUF_MAX_SIZE - 1))
        head = 0;
        else
        head++;
        i++;
    }

    tx_length = i;

    /* Set the flag to true before writing the data in usb driver*/
    block_tx = true;

    /* Send the data by calling USB driver write and wait for write success */
    while (CDCDSerialDriver_Write(usb_0_tx_temp_buf, tx_length,
            (TransferCallback) usb0_tx_complete_handler, 0)
            != USBD_STATUS_SUCCESS)	;

    return back;
}


/**
 * @brief Receives data from USB0
 *
 * This function receives data from USB0
 *
 * @param data pointer to the buffer where the received data will be stored
 * @param max_length maximum length of data to be received
 *
 * @return actual number of bytes received
 */

uint8_t sio_usb_0_rx(uint8_t *data, uint8_t max_length)
{
    uint8_t data_received = 0;
    uint8_t size = 0;
    if (usb_0_buffer.rx_count == 0)
    {
        /* usb receive buffer is empty. */
        return 0;
    }

    ENTER_CRITICAL_REGION();

    if (USB_RX_BUF_MAX_SIZE <= usb_0_buffer.rx_count)
    {
        /*
         * Bytes between head and tail are overwritten by new data.
         * The oldest data in buffer is the one to which the tail is
         * pointing. So reading operation should start from the tail.
         */
        usb_0_buffer.rx_buf_head = usb_0_buffer.rx_buf_tail;

        /*
         * This is a buffer overflow case. But still only bytes equivalent to
         * full buffer size are useful.
         */
        usb_0_buffer.rx_count = USB_RX_BUF_MAX_SIZE;

        /* Bytes received is more than or equal to buffer. */
        if (USB_RX_BUF_MAX_SIZE <= max_length)
        {
            /*
             * Requested receive length (max_length) is more than the
             * max size of receive buffer, but at max the full
             * buffer can be read.
             */
            max_length = USB_RX_BUF_MAX_SIZE;
        }
    }
    else
    {
        /* Bytes received is less than receive buffer maximum length. */
        if (max_length > usb_0_buffer.rx_count)
        {
            /*
             * Requested receive length (max_length) is more than the data
             * present in receive buffer. Hence only the number of bytes
             * present in receive buffer are read.
             */
            max_length = usb_0_buffer.rx_count;
        }
    }

    data_received = max_length;

    while (max_length > 0)
    {
        /* Start to copy from head. */
        *data = usb_0_buffer.rx_buf[usb_0_buffer.rx_buf_head];
        usb_0_buffer.rx_buf_head++;
        usb_0_buffer.rx_count--;
        data++;
        max_length--;
        if ((USB_RX_BUF_MAX_SIZE) == usb_0_buffer.rx_buf_head)
        {
            usb_0_buffer.rx_buf_head = 0;
        }
    }

    size = get_buffer_size( usb_0_buffer.rx_buf_head,
                            usb_0_buffer.rx_buf_tail,
                            USB_RX_BUF_MAX_SIZE
                          );

    LEAVE_CRITICAL_REGION();

    /* Read the buffer by calling USB driver read */
    if(size > 60)
    {
        CDCDSerialDriver_Read(  usb_0_rx_temp_buf,
                                sizeof(usb_0_rx_temp_buf),
                                (TransferCallback) usb0_rx_complete_handler,
                                0);
    }

    return data_received;
}


 /*
 * @brief This function is called when the USB transfer from the
 * device to the host is finished.
 *
 * @param pArg     Pointer to callback arguments.
 * @param status   USBD status.
 * @param transferred number of bytes transferred
 * @param remaining number of bytes remaining
 *
 */
void usb0_tx_complete_handler(  void *pArg ,
                                unsigned char   status,
                                unsigned int    transferred,
                                unsigned int    remaining)
{

    const unsigned int maxPackedSize = MIN(CHIP_USB_ENDPOINTS_MAXPACKETSIZE(CDCDSerialDriverDescriptors_DATAOUT),
                                    USBEndpointDescriptor_MAXBULKSIZE_FS);
    bool needZeroFrameCompletion = (maxPackedSize == transferred) && (0 == remaining);
    ENTER_CRITICAL_REGION();
    while (transferred--)
    {
        if ((USB_TX_BUF_MAX_SIZE - 1) == usb_0_buffer.tx_buf_head)
        {
            /* Reached the end of buffer, revert back to beginning of buffer. */
            usb_0_buffer.tx_buf_head = 0;
        }
        else
        {
            /*
             * * Increment the index to point the next character to be
             * * transmitted.
             * */
            usb_0_buffer.tx_buf_head++;
        }
    }

    if (usb_0_buffer.tx_buf_head != usb_0_buffer.tx_buf_tail)
    {
        /*
         * Prepare a linear buffer for submitting data
         * remaining data in the buffer
         */
        uint8_t tx_length = 0;
        uint8_t i = 0;
        uint8_t head = 0;
        uint8_t tail = 0;
        head = usb_0_buffer.tx_buf_head;
        tail = usb_0_buffer.tx_buf_tail;
        while (head != tail)
        {
            usb_0_tx_temp_buf[i] = usb_0_buffer.tx_buf[head];
            /* cannot transmit more than USB_TX_MAX_SIZE at once */
            if (i == (USB_TX_MAX_SIZE-1))
                break;
            if ((head) == (USB_TX_BUF_MAX_SIZE-1))
                head = 0;
            else
                head++;
            i++;
        }
        tx_length = i;
        /* Set the flag to true before writing the data in usb driver*/
        block_tx = true;
        /* Send the data by calling USB driver write and wait for success */
        while(CDCDSerialDriver_Write(usb_0_tx_temp_buf,
                            tx_length,
                            (TransferCallback) usb0_tx_complete_handler,
                            0)!= USBD_STATUS_SUCCESS);
    }
    else if ( needZeroFrameCompletion )
    {
        block_tx = true;
        /* Send the data by calling USB driver write and wait for success */
        while(CDCDSerialDriver_Write(usb_0_tx_temp_buf, 0,
          (TransferCallback) usb0_tx_complete_handler,0)!= USBD_STATUS_SUCCESS);
    }
    else
    {
        /* No more data for transmission */
        block_tx = false;
    }
    LEAVE_CRITICAL_REGION();
    /* Keep compilar happy*/
    pArg = pArg;
    status = status;
    remaining = remaining;
}


/*
 * @brief This function is called when the USB transfer from the host to the
 * device is finished
 *
 * @param pArg     Pointer to callback arguments.
 * @param status   USBD status.
 * @param transferred number of bytes transferred
 * @param remaining number of bytes remaining
 */

void usb0_rx_complete_handler(  void *pArg ,
                                unsigned char status,
                                unsigned int received,
                                unsigned int remaining)
{
    uint8_t tail = usb_0_buffer.rx_buf_tail;
    uint8_t i = 0;
    ENTER_CRITICAL_REGION();
    while (received--)
    {
        /* Count of bytes received through USB 0 channel is incremented. */
        usb_0_buffer.rx_count++;
        usb_0_buffer.rx_buf[tail] = usb_0_rx_temp_buf[i];
        i++;
        if ((USB_RX_BUF_MAX_SIZE - 1) == usb_0_buffer.rx_buf_tail)
        {
            /* Revert back to beginning of buffer after reaching end of buffer. */
            usb_0_buffer.rx_buf_tail = 0;
            tail = 0;
        }
        else
        {
            usb_0_buffer.rx_buf_tail++;
            tail++;
        }
    }
    LEAVE_CRITICAL_REGION();
    /* Keep compilar happy*/
    pArg = pArg;
    status = status;
    remaining = remaining;
}
#endif  /* ((defined USB0)) */
/* EOF */
