// ****************************************************************************
// INCLUDE FILES: Header files of modules referenced by this module
// ****************************************************************************
#include <uart.h>
#include <sysclk.h>
#include <pmc.h>
#include <UartDriver.h>
#include <string.h>


// ****************************************************************************
// PRIVATE TYPES: Private types used by this module only
// ****************************************************************************


// ****************************************************************************
// PRIVATE MACROS: Private #define directives used by this module only
// ****************************************************************************


// ****************************************************************************
// PRIVATE VARIABLES: Variables used by this module only (static)
// ****************************************************************************
static sam_uart_opt_t udUartOptions;
static uint8_t udRxBuff1[UD_RX_BUFF_SIZE_BYTES];
static uint8_t udRxBuff2[UD_RX_BUFF_SIZE_BYTES];
static uint8_t * udRxBuffActive = udRxBuff1;
static volatile uint16_t udRxBytes = 0;
static volatile uint8_t udRxBufferOverflow = 0;

#ifdef UD_DIAG_ENABLED

uint32_t udOvreCnt;
uint32_t udFrameCnt;
uint32_t udPareCnt;
uint32_t udOvrflwCnt;

#endif // UD_DIAG_ENABLED


// ****************************************************************************
// PRIVATE FUNCTIONS PROTOTYPES: Functions used by this module only (static)
// ****************************************************************************
static void UDresetRxBuffer(void);
static void UDswitchRxBuffer(void);


// ****************************************************************************
// PUBLIC VARIABLES: Public variables shared with other modules
// ****************************************************************************


// ****************************************************************************
// PRIVATE FUNCTIONS: Private functions used by this module only
// ****************************************************************************
// This function must be called with disabled global interrupts, or
// at least with disabled interrupts from UART0
static void UDresetRxBuffer(void)
{
    memset(udRxBuff1, 0, UD_RX_BUFF_SIZE_BYTES);
    memset(udRxBuff2, 0, UD_RX_BUFF_SIZE_BYTES);
    udRxBuffActive = udRxBuff1;
    udRxBytes = 0;
    udRxBufferOverflow = 0;
}

// This function must be called with disabled global interrupts, or
// at least with disabled interrupts from UART0, since the
// updated variables are modified within interrupt.
static void UDswitchRxBuffer(void)
{
    // Move to the beginning of the buffer
    udRxBytes = 0;
    // Clear the overflow information
    udRxBufferOverflow = 0;
    
    // Switch the Rx buffer
    if( udRxBuff1 == udRxBuffActive )
    {
        udRxBuffActive = udRxBuff2;
    }
    else
    {
        udRxBuffActive = udRxBuff1;
    }
}


// ****************************************************************************
// PUBLIC FUNCTIONS: Public functions shared with other modules
// ****************************************************************************
void UDinitialize( uint32_t baudRateBpS )
{
    // Prepare options for uart
    
    // Baud rate configurable by user
    udUartOptions.ul_baudrate = baudRateBpS;
    // Other settings fixed
    udUartOptions.ul_mck = sysclk_get_cpu_hz();
    // No Parity, Normal Mode
    udUartOptions.ul_mode = ( UART_MR_PAR_NO | UART_MR_CHMODE_NORMAL );
    
    // Enable clock for the UART0 periphery
    pmc_enable_periph_clk(ID_UART0);

    // Reset periphery
    uart_reset( UART0 );

    // Disable all interrupts
    uart_disable_interrupt(UART0, ( UART_IER_RXRDY  |
                                  UART_IER_TXRDY    |
                                  UART_IER_ENDRX    |
                                  UART_IER_ENDTX    | 
                                  UART_IER_OVRE     |
                                  UART_IER_FRAME    |
                                  UART_IER_PARE     |
                                  UART_IER_TXEMPTY  |
                                  UART_IER_TXBUFE   |
                                  UART_IER_RXBUFF )
                            );

    // Clean-up the Rx buffer
    UDresetRxBuffer();
    
    // Initialize and enable receiver and transmitter
    uart_init( UART0, &udUartOptions );
    
    // Enable Receiver interrupt within the UART0 periphery
    uart_enable_interrupt(UART0, UART_IER_RXRDY );

    // Clear possible pending interrupts of UART0 in interrupt controller
    NVIC_ClearPendingIRQ(UART0_IRQn);
    // Enable UART0 interrupt in interrupt controller
    NVIC_EnableIRQ( UART0_IRQn );
}

uint8_t * UDread( uint16_t * lengthBytes, uint8_t * overflow )
{
    irqflags_t irqFlags;
    uint8_t * data;

    // Data received since the last call of UDread
    // are provided to the caller
    data = udRxBuffActive;
      
    // Start of critical section    
    irqFlags = cpu_irq_save();

    // Inform the caller how many bytes were received
    *lengthBytes = udRxBytes;
    // Inform the caller if rx buffer overflow occurred
    *overflow = udRxBufferOverflow;

#ifdef UD_DIAG_ENABLED

    udOvrflwCnt += udRxBufferOverflow;    

#endif // UD_DIAG_ENABLED
    
    UDswitchRxBuffer();
    
    // End of critical section
    cpu_irq_restore( irqFlags );

    return data;
}

void UDflushRxBuffer( void )
{
    irqflags_t irqFlags;
    
    // Start of critical section
    irqFlags = cpu_irq_save();
   
    UDswitchRxBuffer();
    
    // End of critical section
    cpu_irq_restore( irqFlags );
}

// Interrupt service routine for UART0
void UART0_Handler(void)
{
    uint8_t rxByte;
    uint8_t status;

    // Read status register
    status = UART0->UART_SR;

#ifdef UD_DIAG_ENABLED
    
    // Clear status register
    UART0->UART_CR = UART_CR_RSTSTA;
    // Check for errors
    if( status & UART_SR_OVRE )
    {
        udOvreCnt++;
    }
    if( status & UART_SR_FRAME )
    {
        udFrameCnt++;
    }
    if( status & UART_SR_PARE )
    {
        udPareCnt++;
    }

#endif //UD_DIAG_ENABLED
    
    // Check Rx interrupt
    if( status & UART_SR_RXRDY )
    {
        // Byte received
        
        // Read the byte
        rxByte = UART0->UART_RHR;

        // Store the byte to the Rx buffer if not full
        if( udRxBytes < UD_RX_BUFF_SIZE_BYTES )
        {
            // Place in the buffer, store the byte there
            udRxBuffActive[udRxBytes++] = rxByte;
        }
        else
        {
            // Rx buffer is full, indicate overflow
            udRxBufferOverflow = 1;
        }
    }
}
