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


#ifndef ECXX_PLATFORM_AVR_UARTX_INLINE_H
#define ECXX_PLATFORM_AVR_UARTX_INLINE_H


namespace eCxx {


//
// Private inline member functions of the AVR UART class
//
inline UART::UART(volatile USART_t* USART, uint8_t rxPin, uint8_t txPin, uint8_t dreVecNum, uint8_t portMux)
: _USART         (USART)
, _rxPin         (rxPin)
, _txPin         (txPin)
, _dreVecNum     (dreVecNum)
, _portMux       (portMux)
, _dreIntElevated(false)
, _prevLV1IntVec (0)
{}


inline void UART::rxComplete_isr()
{
    uint8_t c;

    // Check for parity error
    if(_USART->RXDATAH & USART_PERR_bm) {
        _USART->RXDATAL;
        return;
    }
    // Store the data
    else {
        c = _USART->RXDATAL;
        _rxBuff.put(c);
    }

#ifdef __JTAG2UPDI_WORKAROUND_MAGIC__
    //
    // Workaround for Thinary ATmega4808 board where JTAG2UPDI will stop working when UART0 is opened;
    // this code checks the bytes received from UART0 for a special magic number sequence
    //
    // Please see "Makefile.platform.avr" for the details on the macro's content
    //

    // No need to continue if this is not UART0's ISR
    if(_dreVecNum != ECXX_USART0_DRE_VEC_NUM) return;

    // Check for the special magic number sequence
    static constexpr uint8_t magic[ ] = { __JTAG2UPDI_WORKAROUND_MAGIC__ };
    static           uint8_t check[3] = { 0xFF, 0xFF, 0xFF };

    static_assert(
        sizeof(magic) == sizeof(check),
        "macro value substituting __JTAG2UPDI_WORKAROUND_MAGIC__ does not produce an array of 3 numbers"
    );

    check[0] = check[1];
    check[1] = check[2];
    check[2] = c;

    if( check[0] != magic[0] || check[1] != magic[1] || check[2] != magic[2] ) return;

    // Disable all interrupts
    cli();

    // Disable the UART and configure the Rx and Tx pins as input
    ECXX_USART0_USART->CTRLB &= ~(USART_RXEN_bm | USART_TXEN_bm);
    ECXX_USART0_PORT.DIRCLR   = ECXX_USART0_PORT_BM;

    // Loop forever, waiting for programming
    for(;;) wdt_reset();
#endif
}


inline void UART::udrEmpty_isr()
{
    // Retrieve data from the Tx buffer
    uint8_t c;

    if( !_txBuff.get(c) ) {
        // Disable this interrupt if no more data can be retrieved
        _USART->CTRLA &= ~USART_DREIE_bm;
        return;
    }

    // Send the data
    _USART->TXDATAL = c;

    // Clear the TXCIF bit to ensure flush() will not return until the byte(s) are really sent
    _USART->STATUS = USART_TXCIF_bm;

    // Check if the Tx buffer is empty
    if( _txBuff.empty() ) {
        // Disable this interrupt if the Tx buffer is empty
        _USART->CTRLA &= ~USART_DREIE_bm;
        // Restore the 'Data Register Empty' interrupt priority level to normal as needed
        if(_dreIntElevated) {
            CPUINT.LVL1VEC  = _prevLV1IntVec;
            _dreIntElevated = false;
        }
    }
}


} // namespace eCxx


#endif
