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


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


namespace eCxx {


//
// Helper macros
//
#define WAIT_RTC_CTRLABUSY()    do { Application::yield(); } while(RTC.STATUS    & RTC_CTRLABUSY_bm)
#define WAIT_RTC_PITCTRLABUSY() do { Application::yield(); } while(RTC.PITSTATUS & RTC_CTRLABUSY_bm)


//
// A simple AVR real-time counter wrapper
//
static eCxx::VoidCallback rtcOverflowHandler = nullptr;
static eCxx::VoidCallback rtcCompareHandler  = nullptr;
static eCxx::VoidCallback rtcPeriodicHandler = nullptr;


void RealTimeCounter::setClock(ClockSource source, ClockPrescaler prescaler)
{
    RTC.CLKSEL = static_cast<uint8_t>(source);

    WAIT_RTC_CTRLABUSY();
    RTC.CTRLA &= ~RTC_PRESCALER_gm;

    WAIT_RTC_CTRLABUSY();
    RTC.CTRLA |= static_cast<uint8_t>(prescaler) << RTC_PRESCALER_gp;
}


void RealTimeCounter::setCrystalFrequencyCalibrationValue(int8_t value)
{
    // Disable calibration
    if(!value) {
        WAIT_RTC_CTRLABUSY();
        RTC.CTRLA &= ~RTC_CORREN_bm;
        return;
    }

    // Positive value
    if(value > 0) {
        WAIT_RTC_CTRLABUSY();
        RTC.CALIB  = value & 0x7F;
        RTC.CTRLA |= RTC_CORREN_bm;
    }

    // Negative value
    WAIT_RTC_CTRLABUSY();
    RTC.CALIB  = RTC_SIGN_bm | ( (-value) & 0x7F );
    RTC.CTRLA |= RTC_CORREN_bm;
}


void RealTimeCounter::start()
{
    WAIT_RTC_CTRLABUSY();
    RTC.CTRLA |= RTC_RTCEN_bm;
}


void RealTimeCounter::stop()
{
    WAIT_RTC_CTRLABUSY();
    RTC.CTRLA &= ~RTC_RTCEN_bm;
}


uint16_t RealTimeCounter::getRunningCounterValue()
{
    uint8_t low;
    uint8_t high;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        low  = RTC.CNTL;
        high = RTC.CNTH;
    }

    return (static_cast<uint16_t>(high) << 8 ) | low;
}


void RealTimeCounter::setOverflowPeriod(uint16_t value)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        RTC.PERL = static_cast<uint8_t>(value &  0x00FF);
        RTC.PERH = static_cast<uint8_t>(value >> 8     );
    }
}


void RealTimeCounter::attachOverflowInterrupt(eCxx::VoidCallback userFunc)
{
    rtcOverflowHandler = userFunc;
    RTC.INTCTRL |= RTC_OVF_bm;
}


void RealTimeCounter::detachOverflowInterrupt()
{
    RTC.INTCTRL &= ~RTC_OVF_bm;
    rtcOverflowHandler = nullptr;
}


void RealTimeCounter::setCompareMatchValue(uint16_t value)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        RTC.CMPL = static_cast<uint8_t>(value &  0x00FF);
        RTC.CMPH = static_cast<uint8_t>(value >> 8     );
    }
}


void RealTimeCounter::attachCompareMatchInterrupt(eCxx::VoidCallback userFunc)
{
    rtcCompareHandler = userFunc;
    RTC.INTCTRL |= RTC_CMP_bm;
}


void RealTimeCounter::detachCompareMatchInterrupt()
{
    RTC.INTCTRL &= ~RTC_CMP_bm;
    rtcCompareHandler = nullptr;
}


void RealTimeCounter::setPeriodicInterruptCycle(PICycle cycle)
{
    WAIT_RTC_PITCTRLABUSY();
    RTC.PITCTRLA &= ~(RTC_PERIOD_gm | RTC_PITEN_bm);

    WAIT_RTC_PITCTRLABUSY();
    RTC.PITCTRLA |= (static_cast<uint8_t>(cycle) << RTC_PERIOD_gp) | RTC_PITEN_bm;
}


void RealTimeCounter::attachPeriodicInterrupt(eCxx::VoidCallback userFunc)
{
    rtcPeriodicHandler = userFunc;
    RTC.PITINTCTRL |= RTC_PI_bm;
}


void RealTimeCounter::detachPeriodicInterrupt()
{
    RTC.PITINTCTRL &= ~RTC_PI_bm;
    rtcPeriodicHandler = nullptr;
}


//
// Real-time counter interrupt vectors
//
ISR(RTC_CNT_vect)
{
    // Get the interrupt flags
    const uint8_t intFlags = RTC.INTFLAGS;

    // Overflow
    if(intFlags & RTC_OVF_bm) {
        // Call the user callback if specified
        if(rtcOverflowHandler) (*rtcOverflowHandler)();
        // Clear the interrupt flag
        RTC.INTFLAGS |= RTC_OVF_bm;
    }

    // Compare-match
    if(intFlags & RTC_CMP_bm) {
        // Call the user callback if specified
        if(rtcCompareHandler) (*rtcCompareHandler)();
        // Clear the interrupt flag
        RTC.INTFLAGS |= RTC_CMP_bm;
    }
}


ISR(RTC_PIT_vect)
{
    // Call the user callback if specified
    if(rtcPeriodicHandler) (*rtcPeriodicHandler)();

    // Clear the interrupt flag
    RTC.PITINTFLAGS |= RTC_PI_bm;
}


} // namespace eCxx
