/*******************************************************************************
 * (c) Copyright 2007-2023 Microchip FPGA Embedded Systems Solutions.
 * 
 * SPDX-License-Identifier: MIT
 *
 * @file core_uart_apb.c
 * @author Microchip FPGA Embedded Systems Solutions
 * @brief CoreUARTapb driver implementation. See file "core_uart_apb.h" for 
 * description of the functions implemented in this file.
 * 
 */

#include "coreuartapb_regs.h"
#include "core_uart_apb.h"

#ifdef __cplusplus
extern "C" {
#endif

#define NULL_INSTANCE ( ( UART_instance_t* ) 0 )
#define NULL_BUFFER   ( ( uint8_t* ) 0 )

#define MAX_LINE_CONFIG     ( ( uint8_t )( DATA_8_BITS | ODD_PARITY ) )
#define MAX_BAUD_VALUE      ( ( uint16_t )( 0x1FFF ) )
#define STATUS_ERROR_MASK   ( ( uint8_t )( STATUS_PARITYERR_MASK | \
                                           STATUS_OVERFLOW_MASK  | \
                                           STATUS_FRAMERR_MASK ) )
#define BAUDVALUE_LSB ( (uint16_t) (0x00FF) )
#define BAUDVALUE_MSB ( (uint16_t) (0xFF00) )
#define BAUDVALUE_SHIFT ( (uint8_t) (5) )

#define STATUS_ERROR_OFFSET STATUS_PARITYERR_SHIFT 

/***************************************************************************//**
 * UART_init()
 * See "core_uart_apb.h" for details of how to use this function.
 */
void
UART_init
(
    UART_instance_t * this_uart,
    addr_t base_addr,
    uint16_t baud_value,
    uint8_t line_config
)
{
    uint8_t rx_full;
    
    HAL_ASSERT( this_uart != NULL_INSTANCE )
    HAL_ASSERT( line_config <= MAX_LINE_CONFIG )
    HAL_ASSERT( baud_value <= MAX_BAUD_VALUE )

    if( ( this_uart != NULL_INSTANCE ) &&
        ( line_config <= MAX_LINE_CONFIG ) &&
        ( baud_value <= MAX_BAUD_VALUE ) )
    {
        /*
         * Store lower 8-bits of baud value in CTRL1.
         */
        HAL_set_8bit_reg( base_addr, CTRL1, (uint_fast8_t)(baud_value &
                                                       BAUDVALUE_LSB ) );
    
        /*
         * Extract higher 5-bits of baud value and store in higher 5-bits 
         * of CTRL2, along with line configuration in lower 3 three bits.
         */
        HAL_set_8bit_reg( base_addr, CTRL2, (uint_fast8_t)line_config | 
                                           (uint_fast8_t)((baud_value &
                                   BAUDVALUE_MSB) >> BAUDVALUE_SHIFT ) );
    
        this_uart->base_address = base_addr;
#ifndef NDEBUG
        {
            uint8_t  config;
            uint8_t  temp;
            uint16_t baud_val;
            baud_val = HAL_get_8bit_reg( this_uart->base_address, CTRL1 );
            config =  HAL_get_8bit_reg( this_uart->base_address, CTRL2 );
            /*
             * To resolve operator precedence between & and <<
             */
            temp =  ( config  &  (uint8_t)(CTRL2_BAUDVALUE_MASK ) );
            baud_val |= (uint16_t)( (uint16_t)(temp) << BAUDVALUE_SHIFT );
            config &= (uint8_t)(~CTRL2_BAUDVALUE_MASK);
            HAL_ASSERT( baud_val == baud_value );
            HAL_ASSERT( config == line_config );
        }        
#endif
        
        /*
         * Flush the receive FIFO of data that may have been received before the
         * driver was initialized.
         */
        rx_full = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                    STATUS_RXFULL_MASK;
        while ( rx_full )
        {
            HAL_get_8bit_reg( this_uart->base_address, RXDATA );
            rx_full = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                        STATUS_RXFULL_MASK;
        }

        /*
         * Clear status of the UART instance.
         */
        this_uart->status = (uint8_t)0;
    }
}

/***************************************************************************//**
 * UART_send()
 * See "core_uart_apb.h" for details of how to use this function.
 */
void
UART_send
(
    UART_instance_t * this_uart,
    const uint8_t * tx_buffer,
    size_t tx_size
)
{
    size_t char_idx;
    uint8_t tx_ready;

    HAL_ASSERT( this_uart != NULL_INSTANCE )
    HAL_ASSERT( tx_buffer != NULL_BUFFER )
    HAL_ASSERT( tx_size > 0 )
      
    if( (this_uart != NULL_INSTANCE) &&
        (tx_buffer != NULL_BUFFER)   &&
        (tx_size > (size_t)0) )
    {
        for ( char_idx = (size_t)0; char_idx < tx_size; char_idx++ )
        {
            /* Wait for UART to become ready to transmit. */
            do {
                tx_ready = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                              STATUS_TXRDY_MASK;
            } while ( !tx_ready );
            /* Send next character in the buffer. */
            HAL_set_8bit_reg( this_uart->base_address, TXDATA,
                              (uint_fast8_t)tx_buffer[char_idx] );
        }
    }
}

/***************************************************************************//**
 * UART_fill_tx_fifo()
 * See "core_uart_apb.h" for details of how to use this function.
 */
size_t
UART_fill_tx_fifo
(
    UART_instance_t * this_uart,
    const uint8_t * tx_buffer,
    size_t tx_size
)
{
    uint8_t tx_ready;
    size_t size_sent = 0u;
    
    HAL_ASSERT( this_uart != NULL_INSTANCE )
    HAL_ASSERT( tx_buffer != NULL_BUFFER )
    HAL_ASSERT( tx_size > 0 )
      
    /* Fill the UART's Tx FIFO until the FIFO is full or the complete input 
     * buffer has been written. */
    if( (this_uart != NULL_INSTANCE) &&
        (tx_buffer != NULL_BUFFER)   &&
        (tx_size > 0u) )
    {
        tx_ready = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                      STATUS_TXRDY_MASK;
        if ( tx_ready )
        {
            do {
                HAL_set_8bit_reg( this_uart->base_address, TXDATA,
                                  (uint_fast8_t)tx_buffer[size_sent] );
                size_sent++;
                tx_ready = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                              STATUS_TXRDY_MASK;
            } while ( (tx_ready) && ( size_sent < tx_size ) );
        }
    }    
    return size_sent;
}

/***************************************************************************//**
 * UART_get_rx()
 * See "core_uart_apb.h" for details of how to use this function.
 */
size_t
UART_get_rx
(
    UART_instance_t * this_uart,
    uint8_t * rx_buffer,
    size_t buff_size
)
{
    uint8_t new_status;
    uint8_t rx_full;
    size_t rx_idx = 0u;
    
    HAL_ASSERT( this_uart != NULL_INSTANCE )
    HAL_ASSERT( rx_buffer != NULL_BUFFER )
    HAL_ASSERT( buff_size > 0 )
      
    if( (this_uart != NULL_INSTANCE) &&
        (rx_buffer != NULL_BUFFER)   &&
        (buff_size > 0u) )
    {
        rx_idx = 0u;
        new_status = HAL_get_8bit_reg( this_uart->base_address, STATUS );
        this_uart->status |= new_status;
        rx_full = new_status & STATUS_RXFULL_MASK;
        while ( ( rx_full ) && ( rx_idx < buff_size ) )
        {
            rx_buffer[rx_idx] = HAL_get_8bit_reg( this_uart->base_address,
                                                  RXDATA );
            rx_idx++;
            new_status = HAL_get_8bit_reg( this_uart->base_address, STATUS );
            this_uart->status |= new_status;
            rx_full = new_status & STATUS_RXFULL_MASK;
        }
    }
    return rx_idx;
}

/***************************************************************************//**
 * UART_polled_tx_string()
 * See "core_uart_apb.h" for details of how to use this function.
 */
void 
UART_polled_tx_string
( 
    UART_instance_t * this_uart, 
    const uint8_t * p_sz_string
)
{
    uint32_t char_idx;
    uint8_t tx_ready;

    HAL_ASSERT( this_uart != NULL_INSTANCE )
    HAL_ASSERT( p_sz_string != NULL_BUFFER )
    
    if( ( this_uart != NULL_INSTANCE ) && ( p_sz_string != NULL_BUFFER ) )
    {
        char_idx = 0U;
        while( 0U != p_sz_string[char_idx] )
        {
            /* Wait for UART to become ready to transmit. */
            do {
                tx_ready = HAL_get_8bit_reg( this_uart->base_address, STATUS ) &
                                                              STATUS_TXRDY_MASK;
            } while ( !tx_ready );
            /* Send next character in the buffer. */
            HAL_set_8bit_reg( this_uart->base_address, TXDATA,
                              (uint_fast8_t)p_sz_string[char_idx] );
            char_idx++;
        }
    }
}

/***************************************************************************//**
 * UART_get_rx_status()
 * See "core_uart_apb.h" for details of how to use this function.
 */
uint8_t
UART_get_rx_status
(
    UART_instance_t * this_uart
)
{
    uint8_t status = UART_APB_INVALID_PARAM;

    HAL_ASSERT( this_uart != NULL_INSTANCE )
    /*
     * Extract UART error status and place in lower bits of "status".
     * Bit 0 - Parity error status
     * Bit 1 - Overflow error status
     * Bit 2 - Frame error status
     */
    if( this_uart != NULL_INSTANCE )
    {
        status = ( ( this_uart->status & STATUS_ERROR_MASK ) >> 
                                          STATUS_ERROR_OFFSET );
        /*
         * Clear the sticky status for this instance.
         */
        this_uart->status = (uint8_t)0;
    }
    return status;
}

#ifdef __cplusplus
}
#endif
