/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_PLATFORM_AVRX_UART_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_AVRX_UART_CPP


namespace eCxx {


//
// A simple AVR UART wrapper
//
void UART::_setBaudrate(uint32_t baudrate)
{
    // Get the CPU frequency
    constexpr uint32_t cpuFreq = Application::getCPUFrequency();

    // Try using 2X mode first
    int32_t baudSetting = ( ( (16 * cpuFreq) / baudrate ) + 1 ) / 2;

    // Error if the resulting value is too small
    if(baudSetting < 64) return;

    // If the result is larger than 65535, fallback to 1X mode (because BAUD is a 16-bit register)
    const bool use2XMode = (baudSetting <= 65535);

    if(!use2XMode) baudSetting = ( ( (8 * cpuFreq) / baudrate ) + 1 ) / 2;

    // Adjust the baud setting using the oscillator frequency error value
    if(FUSE.OSCCFG & FUSE_FREQSEL1)
        baudSetting += (baudSetting * SIGROW.OSC20ERR5V) / 1024;
    else
        baudSetting += (baudSetting * SIGROW.OSC16ERR5V) / 1024;

    // Set the baudrate
    if(use2XMode) {
        _USART->CTRLB &= ~USART_RXMODE_NORMAL_gc;
        _USART->CTRLB |=  USART_RXMODE_CLK2X_gc;
    }
    else {
        _USART->CTRLB &= ~USART_RXMODE_CLK2X_gc;
        _USART->CTRLB |=  USART_RXMODE_NORMAL_gc;
    }

    _USART->BAUD = static_cast<uint16_t>(baudSetting);
}


void UART::begin(uint32_t baudrate, DataFormat dataFormat)
{
    // No need to continue if the UART is already enabled
    if( _USART->CTRLB & (USART_RXEN_bm | USART_TXEN_bm) ) return;

    // Setup port multiplexer
    PORTMUX.USARTROUTEA |= _portMux;

    // Initialize UART
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Set the baudrate
        _setBaudrate(baudrate);

        // Set data format
        switch(dataFormat) {
            case DataFormat::_8N1 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_DISABLED_gc | USART_SBMODE_1BIT_gc;
                break;
            case DataFormat::_8N2 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_DISABLED_gc | USART_SBMODE_2BIT_gc;
                break;
            case DataFormat::_8E1 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_EVEN_gc     | USART_SBMODE_1BIT_gc;
                break;
            case DataFormat::_8E2 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_EVEN_gc     | USART_SBMODE_2BIT_gc;
                break;
            case DataFormat::_8O1 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_ODD_gc      | USART_SBMODE_1BIT_gc;
                break;
            case DataFormat::_8O2 :
                _USART->CTRLC = USART_CMODE_ASYNCHRONOUS_gc | USART_CHSIZE_8BIT_gc | USART_PMODE_ODD_gc      | USART_SBMODE_2BIT_gc;
                break;
        }

        // Configure interrupts
        _USART->CTRLA |=  USART_RXCIE_bm; // 'Rx Complete'
        _USART->CTRLA &= ~USART_DREIE_bm; // 'Data Register Empty'

        // Enable the UART
        // (eCxx does not support 'Multi-processor Communication Mode', hence MPCM is never set)
        _USART->CTRLB |= (USART_RXEN_bm | USART_TXEN_bm);

        // Configure pins
        Application::setPinMode(_rxPin, Application::PinMode::InputPullUp);
        Application::setPinMode(_txPin, Application::PinMode::Output     );
    }

    // Reset flag
    _txAtLeastOnce = false;
}


void UART::end()
{
    // No need to continue if the UART is currently disabled
    if( !( _USART->CTRLB & (USART_RXEN_bm | USART_TXEN_bm) ) ) return;

    // Ensure all outgoing data are transmitted
    flush();

    // Uninitialize UART
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Disable the UART
        _USART->CTRLB &= ~(USART_RXEN_bm | USART_TXEN_bm);

        // Disable interrupts
        _USART->CTRLA &= ~(USART_RXCIE_bm | USART_DREIE_bm);

        // Configure pins
        Application::setPinMode(_rxPin, Application::PinMode::Input);
        Application::setPinMode(_txPin, Application::PinMode::Input);
    }

    // Clear the Rx and Tx buffers
    _rxBuff.clear();
    _txBuff.clear();
}


bool UART::changeBaudrate(uint32_t baudrate)
{
    // Set the baudrate
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _setBaudrate(baudrate);
    }

    // Done
    return true;
}


bool UART::canRead() const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = !_rxBuff.empty();
    }

    return r;
}


bool UART::read(uint8_t& value)
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = _rxBuff.get(value);
    }

    return r;
}


bool UART::peek(uint8_t& value) const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = _rxBuff.peek(value);
    }

    return r;
}


bool UART::canWrite() const
{
    bool r;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        r = !_txBuff.full();
    }

    return r;
}


bool UART::write(uint8_t value)
{
    // Set flag
    _txAtLeastOnce = true;

    // If the Tx buffer and data register is empty, simply send the byte directly
    if( _txBuff.empty() && (_USART->STATUS & USART_DREIF_bm) ) {
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
        {
            // Send the data
            _USART->TXDATAL = value;
            // Clear the TXCIF bit to ensure flush() will not return until the byte(s) are really sent
            _USART->STATUS = USART_TXCIF_bm;
            // Disable the 'Data Register Empty' interrupt
            _USART->CTRLA &= ~USART_DREIE_bm;
        }
        return true;
    }

    // Elevate the 'Data Register Empty' interrupt priority level as needed
    if(CPUINT.STATUS & CPUINT_LVL0EX_bm) {
        _dreIntElevated = true;
        _prevLV1IntVec  = CPUINT.LVL1VEC;
        CPUINT.LVL1VEC  = _dreVecNum;
    }

    // If the output buffer is full, wait for the interrupt handler to empty it
    while(_txBuff.full()) {
        // If interrupt is disabled, send the data manually
        if( !(SREG & CPU_I_bm) || !(_USART->CTRLA & USART_DREIE_bm) ) {
            // Ensure the data register is empty before sending
            if(_USART->STATUS & USART_DREIF_bm) udrEmpty_isr();
        }
    }

    // Store the data and enable the 'Data Register Empty' interrupt
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _txBuff.put(value);
        _USART->CTRLA |= USART_DREIE_bm;
    }

    // Done
    return true;
}


void UART::flush()
{
    // No need to flush if no bytes have been written
    if(!_txAtLeastOnce) return;

    // Disable the 'Data Register Empty' interrupt
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _USART->CTRLA &= ~USART_DREIE_bm;
    }

    // There is no need to continue if the Tx buffer is empty
    if( _txBuff.empty() ) return;

    // Call the interrupt handler to manually send the data
    while( !_txBuff.empty() ) {
        Application::yield();
        if(_USART->STATUS & USART_DREIF_bm) udrEmpty_isr();
    }

    // Wait until the last byte is fully sent
    Application::yield();
    while( !(_USART->STATUS & USART_TXCIF_bm) );
}


} // namespace eCxx
