/*
 * 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_SPECIFIC_APPLICATION_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_CPP


#include <eCxx/Platform/AVRX/Logic.h>
#include <eCxx/Platform/AVRX/PinMapping.h>

#include "../AVRCommon/Internal.h"

#include "UART_Inline.h"


//
// Include the AVR TCB0 ISR routine implementation
//
#define INCLUDE_ECXX_PLATFORM_AVRX_ISR_TCB0_CPP
#include "ISR_TCB0.cpp"


namespace eCxx {


//
// Internal constants
//
static constexpr uint8_t ActivityIndicatorLED_Pin = AVR::BuiltinLED;


//
// Disable the watchdog timer early during program startup
//
void __disable_wdt_on_program_startup() __ECXX_ATTR_NAKED __ECXX_ATTR_SEC_INIT3 __ECXX_ATTR_USED;
void __disable_wdt_on_program_startup()
{
    RSTCTRL.RSTFR = 0;

    while(WDT.STATUS & WDT_SYNCBUSY_bm);
    _PROTECTED_WRITE(WDT.CTRLA, PERIOD_OFF_gc);
    while(WDT.STATUS & WDT_SYNCBUSY_bm);
}


//
// Instantiate the UART object(s)
//
#ifdef ECXX_USART0_USART

inline UART* uart0_instance()
{
    static UART _uart0(ECXX_USART0_USART, ECXX_USART0_RX, ECXX_USART0_TX, ECXX_USART0_DRE_VEC_NUM, ECXX_USART0_PORT_MUX);
    return &_uart0;
};

inline void uart0_rxComplete_isr()
{ uart0_instance()->rxComplete_isr(); }

inline void uart0_udrEmpty_isr()
{ uart0_instance()->udrEmpty_isr(); }

#endif


#ifdef ECXX_USART1_USART

inline UART* uart1_instance()
{
    static UART _uart1(ECXX_USART1_USART, ECXX_USART1_RX, ECXX_USART1_TX, ECXX_USART1_DRE_VEC_NUM, ECXX_USART1_PORT_MUX);
    return &_uart1;
};

inline void uart1_rxComplete_isr()
{ uart1_instance()->rxComplete_isr(); }


inline void uart1_udrEmpty_isr()
{ uart1_instance()->udrEmpty_isr(); }

#endif


#ifdef ECXX_USART2_USART

inline UART* uart2_instance()
{
    static UART _uart2(ECXX_USART2_USART, ECXX_USART2_RX, ECXX_USART2_TX, ECXX_USART2_DRE_VEC_NUM, ECXX_USART2_PORT_MUX);
    return &_uart2;
};

inline void uart2_rxComplete_isr()
{ uart2_instance()->rxComplete_isr(); }


inline void uart2_udrEmpty_isr()
{ uart2_instance()->udrEmpty_isr(); }

#endif


//
// Instantiate the SPI object)
//
inline SPI& spi_instance()
{
    static SPI _spi;
    return _spi;
};


//
// A standard AVR application
//
uint8_t Application::_wdtCtrlA;


void Application::disablePWMOutput(uint8_t pin)
{
    // Check the pin number
    if(pin < minPWMPinNumber || pin > maxPWMPinNumber) return;

    // Get and check the timer number
    const uint8_t timer = pinNumberToPWMTimer(pin);
    if(timer == ECXX_NOT_PWM) return;

    // Enable PWM output and set its duty cycle (process according to the timer)
    switch(timer) {
        case ECXX_TIMERA0 : {
            // Get the bit position
            const uint8_t bitPos = pinNumberToBitPosition(pin);
            // Disable PWM output
            switch(bitPos) {
                case 0 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_LCMP0EN_bm; break;
                case 1 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_LCMP1EN_bm; break;
                case 2 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_LCMP2EN_bm; break;
                case 3 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_HCMP0EN_bm; break;
                case 4 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_HCMP1EN_bm; break;
                case 5 : TCA0.SPLIT.CTRLB &= ~TCA_SPLIT_HCMP2EN_bm; break;
            }
        }
        break;
    }
}


void Application::getSoftUARTBitDelayUS(uint16_t& txBit, uint16_t& rxBit, uint16_t& rxCtr, uint32_t baudrate) noexcept
{
    // Get the CPU frequency
    constexpr uint32_t cpuFreq = Application::getCPUFrequency();

    // Each loop in the __delay_loop4() function will take 4 cycles to execute
    baudrate *= 4;

    // Calculate the bit delay
    uint16_t bitDelay = ( cpuFreq + baudrate / 2 ) / baudrate;

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

    // Store the bit delay
    txBit = bitDelay - 4; // 4 = approximated overhead factor
    rxBit = bitDelay - 4; // 4 = approximated overhead factor
    rxCtr = bitDelay / 8;
}


void Application::setPinMode(uint8_t pin, PinMode mode)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the bitmask and port structure
    const uint8_t bm   = pinNumberToBitmask(pin);
          PORT    port = pinNumberToPort   (pin);

    // Output mode
    if(mode == PinMode::Output) {
        // Configure direction as output
        port->DIRSET = bm;
    }
    // Input mode
    else {
        // Get the bit position and control register
        const uint8_t bitPos = pinNumberToBitPosition(pin);
              REG8    creg   = getPinControlRegister (port, bitPos);
        // Configure direction as input
        port->DIRCLR = bm;
        // Enable/disable pull-up
        if(mode == PinMode::InputPullUp) *creg |=  PORT_PULLUPEN_bm;
        else                             *creg &= ~PORT_PULLUPEN_bm;
    }

    // Disable PWM output on the pin as needed
    if(_pwmEnabled) disablePWMOutput(pin);
}


void Application::setPinInvertedIOMode(uint8_t pin, bool enabled)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the control register
    REG8 creg = getPinControlRegister( pinNumberToPort(pin), pinNumberToBitPosition(pin) );

    // Enable/disable inverted I/O mode
    if(enabled) *creg |=  PORT_INVEN_bm;
    else        *creg &= ~PORT_INVEN_bm;
}


void Application::digitalWrite(uint8_t pin, bool bit)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the bitmask and port structure
    const uint8_t bm   = pinNumberToBitmask(pin);
          PORT    port = pinNumberToPort   (pin);

    // Write bit
    if(bit) port->OUTSET = bm;
    else    port->OUTCLR = bm;
}


bool Application::digitalRead(uint8_t pin)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return false;

    // Get the bitmask and port structure
    const uint8_t bm   = pinNumberToBitmask(pin);
          PORT    port = pinNumberToPort   (pin);

    // Read bit
    return port->IN & bm;
}


void Application::analogWrite(uint8_t pin, uint8_t val)
{
    // Check if PWM is not enabled
    if(!_pwmEnabled) return;

    // Check the pin number
    if(pin < minPWMPinNumber || pin > maxPWMPinNumber) return;

    // Get and check the timer number
    const uint8_t timer = pinNumberToPWMTimer(pin);
    if(timer == ECXX_NOT_PWM) return;

    // Enable PWM output and set its duty cycle (process according to the timer)
    switch(timer) {
        case ECXX_TIMERA0 : {
            // Get the bit position and the compare buffer register
            const uint8_t bitPos = pinNumberToBitPosition(pin);
            // Set duty cycle and enable PWM output
            switch(bitPos) {
                case 0 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP0EN_bm;
                    TCA0.SPLIT.LCMP0  = val;
                    break;
                case 1 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP1EN_bm;
                    TCA0.SPLIT.LCMP1  = val;
                    break;
                case 2 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP2EN_bm;
                    TCA0.SPLIT.LCMP2  = val;
                    break;
                case 3 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP0EN_bm;
                    TCA0.SPLIT.HCMP0  = val;
                    break;
                case 4 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP1EN_bm;
                    TCA0.SPLIT.HCMP1  = val;
                    break;
                case 5 :
                    TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP2EN_bm;
                    TCA0.SPLIT.HCMP2  = val;
                    break;
            }
        }
        break;
    }
}


uint16_t Application::analogRead(uint8_t pin)
{
    // For saving the ADC settings
    uint8_t adc0SCtrl;
    uint8_t adc0CtrlC;
    uint8_t vrefCtrlA;

    // Set the ADC channel
    if(pin == AVR::A_TempSenseK) {
        // Save the ADC settings
        adc0SCtrl = ADC0.SAMPCTRL;
        adc0CtrlC = ADC0.CTRLC;
        vrefCtrlA = VREF.CTRLA;
        // Use the maximum sample length
        ADC0.SAMPCTRL |= ADC_SAMPLEN_gm;
        // Temperature reading must be done using the internal 1.10V reference
        ADC0.CTRLC &= ~ADC_REFSEL_gm;
        ADC0.CTRLC |=  ADC_REFSEL_INTREF_gc;
        VREF.CTRLA &= ~VREF_ADC0REFSEL_gm;
        VREF.CTRLA |=  VREF_ADC0REFSEL_1V1_gc;
        // Temperature sensor
        ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc;
    }
    else if(pin == AVR::A_DACRef0) {
        // DAC reference in AC0
        ADC0.MUXPOS = ADC_MUXPOS_DACREF_gc;
    }
    else if(pin >= minADCPinNumber && pin <= maxADCPinNumber) {
        // Get and check the ADC channel
        const uint8_t adcChn = pinNumberToADCChannel(pin);
        if(adcChn == ECXX_NOT_ADC) return 0;
        // ADC input pin
        ADC0.MUXPOS = (adcChn << ADC_MUXPOS_gp);
    }
    else {
        // Invalid pin
        return 0;
    }

    // Start the conversion
    ADC0.COMMAND = ADC_STCONV_bm;

    // Wait until the conversion is completed
    while( !(ADC0.INTFLAGS & ADC_RESRDY_bm) ) Application::yield();

    // Get the result
    const uint8_t low  = ADC0.RESL;
    const uint8_t high = ADC0.RESH;

    // Calculate the ADC value
    const uint16_t adcValue = ( static_cast<uint16_t>(high) << 8 ) | low;

    // Return the result - temperature sensor
    if(pin == AVR::A_TempSenseK) {
        // Restore the ADC settings
        ADC0.SAMPCTRL = adc0SCtrl;
        ADC0.CTRLC    = adc0CtrlC;
        VREF.CTRLA    = vrefCtrlA;
        // Calculate and return the temperature in Kelvin
        const  int8_t sigrowOffset = SIGROW.TEMPSENSE1;
        const uint8_t sigrowGain   = SIGROW.TEMPSENSE0;
        return ( ( static_cast<uint32_t>(adcValue - sigrowOffset) * sigrowGain + 0x80 ) * 100 ) >> 8;
    }

    // Return the result - others
    return adcValue;
}


void Application::setADCReferenceVoltage(ADCVRef adcReferenceVoltage) noexcept
{
    // Clear previous settings
    ADC0.CTRLC &= ~ADC_REFSEL_gm;
    VREF.CTRLA &= ~VREF_ADC0REFSEL_gm;

    // Use smaller sampling capacitance size
    ADC0.CTRLC |= ADC_SAMPCAP_bm;

    // Set new settings
    switch(adcReferenceVoltage) {
        case ADCVRef::ExternalAREF :
            ADC0.CTRLC |= ADC_REFSEL_VREFA_gc;
            break;

        case ADCVRef::Internal0V55 :
            ADC0.CTRLC &= ~ADC_SAMPCAP_bm; // Use larger sampling capacitance size
            ADC0.CTRLC |=  ADC_REFSEL_INTREF_gc;
            VREF.CTRLA |=  VREF_ADC0REFSEL_0V55_gc;
            break;

        case ADCVRef::Internal1V10 :
            ADC0.CTRLC |= ADC_REFSEL_INTREF_gc;
            VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc;
            break;

        case ADCVRef::Internal1V50 :
            ADC0.CTRLC |= ADC_REFSEL_INTREF_gc;
            VREF.CTRLA |= VREF_ADC0REFSEL_1V5_gc;
            break;

        case ADCVRef::Internal2V50 :
            ADC0.CTRLC |= ADC_REFSEL_INTREF_gc;
            VREF.CTRLA |= VREF_ADC0REFSEL_2V5_gc;
            break;

        case ADCVRef::Internal4V34 :
            ADC0.CTRLC |= ADC_REFSEL_INTREF_gc;
            VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc;
            break;

        // ADCVRef::ExternalAVCC
        default:
            ADC0.CTRLC |= ADC_REFSEL_VDDREF_gc;
            break;
    }
}


bool Application::writeUserRow(uint8_t index, uint8_t value)
{
    // Check the index
    if(index >= UserRowSize) return false;

    // Write the value
    while( !eeprom_is_ready() ) Application::yield();
    eeprom_update_byte( const_cast<uint8_t*>(&USERROW.USERROW0) - EEPROM_START + index, value );

    // The operation above should always be successful
    return true;

}


bool Application::readUserRow(uint8_t index, uint8_t& value)
{
    // Check the index
    if(index >= UserRowSize) return false;

    // Read the value
    while( !eeprom_is_ready() ) Application::yield();
    value = eeprom_read_byte( const_cast<uint8_t*>(&USERROW.USERROW0 - EEPROM_START + index) );

    // The operation above should always be successful
    return true;
}


uint32_t Application::micros() noexcept
{
    uint32_t m;
    uint16_t t;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        m = t0OVFCount;
        t = TCB0.CNT;

        // In case interrupt occurring during the function call
        if(TCB0.INTFLAGS & TCB_CAPT_bm) {
            ++m;
            t = TCB0.CNT;
        }
    }

    // TCB0 prescaler is 2 ; TOP is 8191 (2 ^ 13 - 1)
    return ( (m << 13) + t ) * 2 / (_cpuFrequency / 1000000UL);
}


uint32_t Application::millis() noexcept
{
    uint32_t m;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        m = t0Millis;
    }

    return m;
}


SPI& Application::spi()
{ return spi_instance(); }


TWIMaster& Application::twiMaster()
{ return TWIMaster::instance(); }


TWISlave& Application::twiSlave()
{ return TWISlave::instance(); }


Application::OWIMaster& Application::owiMaster()
{
    static OWIMaster _owimaster;
    return _owimaster;
}


bool Application::attachInterruptArg(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode)
{ return AVR::attachInterruptArg(pin, userFunc, arg, mode); }


bool Application::detachInterrupt(uint8_t pin)
{ return AVR::detachInterrupt(pin); }


UART* Application::uart0()
{
#ifdef ECXX_USART0_USART
    return uart0_instance();
#else
    return nullptr;
#endif
}


UART* Application::uart1()
{
#ifdef ECXX_USART1_USART
    return uart1_instance();
#else
    return nullptr;
#endif
}


UART* Application::uart2()
{
#ifdef ECXX_USART2_USART
    return uart2_instance();
#else
    return nullptr;
#endif
}


Logic& Application::logic()
{
    static Logic _logic;
    return _logic;
}


AnalogComparator& Application::analogComparator()
{
    static AnalogComparator _ac;
    return _ac;
}


TimerCounterB* Application::tcb1()
{
#ifdef TCB1
    static TimerCounterB _tcb1(&TCB1, PORTMUX_TCB1_bm, AVR::TCB1_OutDef, AVR::TCB1_OutAlt);
    return &_tcb1;
#else
    return nullptr;
#endif
}


TimerCounterB* Application::tcb2()
{
#ifdef TCB2
    static TimerCounterB _tcb2(&TCB2, PORTMUX_TCB2_bm, AVR::TCB2_OutDef, AVR::TCB2_OutAlt);
    return &_tcb2;
#else
    return nullptr;
#endif
}


TimerCounterB* Application::tcb3()
{
#ifdef TCB3
    static TimerCounterB _tcb3(&TCB3, PORTMUX_TCB3_bm, AVR::TCB3_OutDef, AVR::TCB3_OutAlt);
    return &_tcb3;
#else
    return nullptr;
#endif
}


//
// A standard AVR application - fast-digital-read/write
//
void Application::_preparePinFDR(_PinFDR& pinFDR, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Store the configurations
    pinFDR._port = pinNumberToPort   (pin);
    pinFDR._bm   = pinNumberToBitmask(pin);
}


void Application::_preparePinFDW(_PinFDW& pinFDW, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Store the configurations
    pinFDW._port = pinNumberToPort   (pin);
    pinFDW._bm   = pinNumberToBitmask(pin);
}


void Application::_preparePinFDX(_PinFDX& pinFDX, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Store the configurations
    pinFDX._port      = pinNumberToPort   (pin);
    pinFDX._ctrl      = getPinControlRegister( pinFDX._port, pinNumberToBitPosition(pin) );
    pinFDX._bm        = pinNumberToBitmask(pin);
    pinFDX._usePullUp = true;

    // Initialize the pin as input with pull-up
    pinFDX.setReadMode();
}


//
// A standard AVR application - low level OWI functions
//
// # Using only AVR's internal pull-up resistor (without adding external pull-up resistor),
//   would result in an unstable and even unusable OWI bus.
// # However, if the internal pull-up is not enabled, then the system would hung if we try
//   to search the bus for slave device(s) while nothing is actually connected to it.
#define ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP

/*
 * This code is written based on the information from:
 *     [AN126] 1-Wire Communication Through Software
 *     https://pdfserv.maximintegrated.com/en/an/AN126.pdf
 */
bool Application::owiBegin(OWIData& owiData, uint8_t pin)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return false;

    // Get the bitmask and port structure
    owiData.bitmask = pinNumberToBitmask(pin);
    owiData.port    = pinNumberToPort   (pin);

    // Get the bit position and control register
    const uint8_t bitPos = pinNumberToBitPosition(pin);
    owiData.creg = getPinControlRegister(owiData.port, bitPos);

    // Configure direction as input
    owiData.port->DIRCLR = owiData.bitmask;

#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
    // Enable pull-up
    *owiData.creg |= PORT_PULLUPEN_bm;
#else
    // Disable pull-up
    *owiData.creg &= ~PORT_PULLUPEN_bm;
#endif

    // Disable PWM output on the pin as needed
    if(_pwmEnabled) disablePWMOutput(pin);

    // Reset and check for the presence of at least one slave (reset twice, just in case)
        owiReset(owiData);
    if( owiReset(owiData) ) return true;

    // No slave was found
    owiEnd(owiData);
    return false;
}


void Application::owiEnd(OWIData& owiData)
{
    // Configure direction as input
    owiData.port->DIRCLR = owiData.bitmask;

    // Disable pull-up
    *owiData.creg &= ~PORT_PULLUPEN_bm;

    // Clear the OWI data
    owiData.port    = nullptr;
    owiData.creg    = nullptr;
    owiData.bitmask = 0;
}


bool Application::owiReset(const OWIData& owiData)
{
    // Reset bus and check for the presence of any slave
    bool bit = true;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Delay
        delayUS(OWIMaster::Delay_G);

        // Drive bus low and delay
        owiData.port->OUTCLR = owiData.bitmask;
        owiData.port->DIRSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_H);

        // Release bus and delay
        owiData.port->DIRCLR = owiData.bitmask;
        owiData.port->OUTSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_I);

        // Sample bus and delay
        bit = owiData.port->IN & owiData.bitmask;
        delayUS(OWIMaster::Delay_J);
    }

    // Return true if there is at least one slave present
    return !bit;
}


void Application::owiWriteBit0(const OWIData& owiData)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        owiData.port->OUTCLR = owiData.bitmask;
        owiData.port->DIRSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_C);

        // Release bus and delay
        owiData.port->DIRCLR = owiData.bitmask;
        owiData.port->OUTSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_D);
    }
}


void Application::owiWriteBit1(const OWIData& owiData)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        owiData.port->OUTCLR = owiData.bitmask;
        owiData.port->DIRSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_A);

        // Release bus and delay
        owiData.port->DIRCLR = owiData.bitmask;
        owiData.port->OUTSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_B);
    }
}


bool Application::owiReadBit(const OWIData& owiData)
{
    // Read bit
    bool bit = false;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        owiData.port->OUTCLR = owiData.bitmask;
        owiData.port->DIRSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_A);

        // Release bus and delay
        owiData.port->DIRCLR = owiData.bitmask;
        owiData.port->OUTSET = owiData.bitmask;
        delayUS(OWIMaster::Delay_E);

        // Sample bus and delay
        bit = owiData.port->IN & owiData.bitmask;
        delayUS(OWIMaster::Delay_F);
    }

    // Return the result
    return bit;
}


void Application::powerDown() noexcept
{
    // Disable watchdog timer
    wdt_reset();

    while(WDT.STATUS & WDT_SYNCBUSY_bm);
    _PROTECTED_WRITE(WDT.CTRLA, PERIOD_OFF_gc);
    while(WDT.STATUS & WDT_SYNCBUSY_bm);

    // Power-down
    SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc | SLPCTRL_SEN_bm;
    __asm__ __volatile__ ( "sleep\n\t" );
}


void Application::restoreOnWakeUp() noexcept
{
    // Reconfigure watchdog timer
    _PROTECTED_WRITE(WDT.CTRLA, _wdtCtrlA);
    wdt_reset();
}


//
// A standard AVR application - initialization
//
void Application::initialize(const Application::Config& config)
{
    // Initialize variables
    _pwmEnabled = config.enablePWM;

    t0MillisInc = config.timer0MillisInc;
    t0FractFInc = config.timer0FractFInc;

    // Select internal 20 MHz / 16 MHz oscillator
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);

    // Initialize main clock divider
    #define MCLK_DIV1  (0x00                                )
    #define MCLK_DIV2  (CLKCTRL_PDIV_2X_gc  | CLKCTRL_PEN_bm)
    #define MCLK_DIV4  (CLKCTRL_PDIV_4X_gc  | CLKCTRL_PEN_bm)
    #define MCLK_DIV8  (CLKCTRL_PDIV_8X_gc  | CLKCTRL_PEN_bm)
    #define MCLK_DIV10 (CLKCTRL_PDIV_10X_gc | CLKCTRL_PEN_bm)
    #define MCLK_DIV16 (CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm)

    #if (F_CPU == 20000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV1 ); // Assume FUSE.OSCCFG[1:0] = 2 (20 MHz)
    #elif (F_CPU == 10000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV2 ); // Assume FUSE.OSCCFG[1:0] = 2 (20 MHz)
    #elif (F_CPU == 5000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV4 ); // Assume FUSE.OSCCFG[1:0] = 2 (20 MHz)
    #elif (F_CPU == 2500000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV8 ); // Assume FUSE.OSCCFG[1:0] = 2 (20 MHz)
    #elif (F_CPU == 1250000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV16); // Assume FUSE.OSCCFG[1:0] = 2 (20 MHz)
    #elif (F_CPU == 16000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV1 ); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #elif (F_CPU == 8000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV2 ); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #elif (F_CPU == 4000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV4 ); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #elif (F_CPU == 2000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV8 ); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #elif (F_CPU == 1600000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV10); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #elif (F_CPU == 1000000UL)
        _PROTECTED_WRITE( CLKCTRL_MCLKCTRLB, MCLK_DIV16); // Assume FUSE.OSCCFG[1:0] = 1 (16 MHz)
    #else
        #error "unsupported CPU frequency"
    #endif

    // Configure the activity indicator LED and turn it off
    setPinMode(ActivityIndicatorLED_Pin, PinMode::Output);
    setActivityIndicatorLED(false);

    // Route all Communication port signals to their default pins
    PORTMUX.USARTROUTEA  = 0;
    PORTMUX.TWISPIROUTEA = 0;

    // Initialize TCA0 for PWM so that for each 1 MHZ CPU clock, it will produce ~244 Hz PWM frequency
    PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTD_gc;                               // Route signal to PD [0:5]
    TCA0.SPLIT.CTRLD  = TCA_SINGLE_SPLITM_bm;                                // Enable split mode
    TCA0.SPLIT.LPER   = 0xFF;                                                // Set the maximum 8-bit period
    TCA0.SPLIT.HPER   = 0xFF;                                                // Set the maximum 8-bit period
    TCA0.SPLIT.LCMP0  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.HCMP0  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.LCMP1  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.HCMP1  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.LCMP2  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.HCMP2  = 0x80;                                                // Set 50% duty cycle on start
    TCA0.SPLIT.CTRLA  = (TCA_SPLIT_CLKSEL_DIV16_gc) | (TCA_SPLIT_ENABLE_bm); // Set prescaler to 8 and enable the timer

    // Initialize TCB0 for TIMER0_INTERNAL
    TCB0.CTRLB    = TCB_CNTMODE_INT_gc;                        // Periodic interrupt mode
    TCB0.CCMP     = 8191;                                      // TOP value
    TCB0.INTCTRL |= TCB_CAPT_bm;                               // Enable timer interrupt
    TCB0.CTRLA    = (TCB_CLKSEL_CLKDIV2_gc) | (TCB_ENABLE_bm); // Set prescaler to 2 and enable the timer

    // Set the ADC prescaler so that the ADC will be clocked at a frequency
    // that will produce results with acceptable accuracy
    #if (F_CPU == 20000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV128_gc; //  20    MHz / 128 = 156.25 kHz
    #elif (F_CPU == 10000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV64_gc;  //  10    MHz /  64 = 156.25 kHz
    #elif (F_CPU == 5000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV32_gc;  //   5    MHz /  32 = 156.25 kHz
    #elif (F_CPU == 2500000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV16_gc;  //   2.5  MHz /  16 = 156.25 kHz
    #elif (F_CPU == 1250000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV8_gc;   //   1.25 MHz /   8 = 156.25 kHz
    #elif (F_CPU == 16000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV128_gc; //  16    MHz / 128 = 125    kHz
    #elif (F_CPU == 8000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV64_gc;  //   8    MHz /  64 = 125    kHz
    #elif (F_CPU == 4000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV32_gc;  //   4    MHz /  32 = 125    kHz
    #elif (F_CPU == 2000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV16_gc;  //   2    MHz /  16 = 125    kHz
    #elif (F_CPU == 1600000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV8_gc;   //   1.6  MHz /   8 = 200    kHz
    #elif (F_CPU == 1000000UL)
        ADC0.CTRLC |= ADC_PRESC_DIV8_gc;   //   1    MHz /   8 = 125    kHz
    #else
        #error "unsupported CPU frequency"
    #endif

    // Use the default ADC reference voltage
    setADCReferenceVoltage();

    // Set other ADC settings and enable the ADC
    ADC0.CTRLD  |= ADC_INITDLY_DLY32_gc; // Initialization delay
    ADC0.CTRLA  |= ADC_RESSEL_10BIT_gc;  // 10-bit resolution
    ADC0.CTRLA  |= ADC_ENABLE_bm;        // Enable the ADC

    // Set AC0's DAC voltage reference to maximum AVCC
    VREF.CTRLA &= ~VREF_AC0REFSEL_gm;
    VREF.CTRLA |=  VREF_AC0REFSEL_AVDD_gc;
    AC0.DACREF  =  255;

    // Configure watchdog timer
    while(WDT.STATUS & WDT_SYNCBUSY_bm);
    switch(config.watchdogTimeout) {
        case Application::Config::WDTO::_15mS  : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_16CLK_gc ); break;
        case Application::Config::WDTO::_30mS  : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_32CLK_gc ); break;
        case Application::Config::WDTO::_60mS  : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_64CLK_gc ); break;
        case Application::Config::WDTO::_120mS : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_128CLK_gc); break;
        case Application::Config::WDTO::_250mS : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_256CLK_gc); break;
        case Application::Config::WDTO::_500mS : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_512CLK_gc); break;
        case Application::Config::WDTO::_1S    : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_1KCLK_gc ); break;
        case Application::Config::WDTO::_2S    : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_2KCLK_gc ); break;
        case Application::Config::WDTO::_4S    : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_4KCLK_gc ); break;
        case Application::Config::WDTO::_8S    : _PROTECTED_WRITE(WDT.CTRLA, PERIOD_8KCLK_gc ); break;
        default                                :                                                break;
    }
    while(WDT.STATUS & WDT_SYNCBUSY_bm);
    wdt_reset();

    _wdtCtrlA = WDT.CTRLA; // Save the configuration

    // Enable global interrupt
    sei();

    // This small delay allows for:
    //     # AVRDUDE to finish (close the programmer) before the user program starts
    //       (it is a workaround for a bug in Thinary ATmega4808 board).
    //     # Recovery, in case the MCU freezes and holds the main UART open. With this
    //       delay, the user can reset and then upload a new program (or erase the flash).
    //       Without this delay, the user program will immediately start again and may
    //       freeze the MCU again.
    for(uint8_t i = 0; i < 5; ++i) {
        setActivityIndicatorLED(true ); delayMS( 40);
        setActivityIndicatorLED(false); delayMS( 40);
        setActivityIndicatorLED(true ); delayMS( 40);
        setActivityIndicatorLED(false); delayMS( 40);
        setActivityIndicatorLED(true ); delayMS( 40);
        setActivityIndicatorLED(false); delayMS( 40);
                                        delayMS(160);
    }
}


} // namespace eCxx


//
// Include the common implementation file
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_APPLICATION_CPP
#include "../AVRCommon/Application.cpp"


//
// Include the UART wrapper implementation and the ISR routines implementation
//
#define INCLUDE_ECXX_PLATFORM_AVRX_UART_CPP
#include "UART.cpp"

#define INCLUDE_ECXX_PLATFORM_AVRX_ISR_UART_CPP
#include "ISR_UART.cpp"


//
// Include the SPI wrapper implementation
//
#define INCLUDE_ECXX_PLATFORM_AVRX_SPI_CPP
#include "SPI.cpp"


//
// Include the TWI-master and TWI-slave wrapper implementation
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_TWIMASTER_CPP
#include "TWIMaster.cpp"

#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_TWISLAVE_CPP
#include "TWISlave.cpp"
