/*
 * 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 {


//
// A simple AVR timer/counter type B wrapper
//
#ifdef TCB1
static eCxx::VoidCallback tcbInterruptHandler1 = nullptr;
#endif

#ifdef TCB2
static eCxx::VoidCallback tcbInterruptHandler2 = nullptr;
#endif

#ifdef TCB3
static eCxx::VoidCallback tcbInterruptHandler3 = nullptr;
#endif


void TimerCounterB::setClock(ClockSource source)
{
    _tcb->CTRLA &= ~TCB_CLKSEL_gm;
    _tcb->CTRLA |= static_cast<uint8_t>(source) << TCB_CLKSEL_gp;
}


void TimerCounterB::setMode(Mode mode)
{
    _tcb->CTRLB &= ~TCB_CNTMODE_gm;
    _tcb->CTRLB |= static_cast<uint8_t>(mode) << TCB_CNTMODE_gp;
}


void TimerCounterB::start()
{ _tcb->CTRLA |= TCB_ENABLE_bm;  }


void TimerCounterB::stop()
{ _tcb->CTRLA &= ~TCB_ENABLE_bm;  }


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

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        low  = _tcb->CNTL;
        high = _tcb->CNTH;
    }

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


uint16_t TimerCounterB::getPeriod()
{
    uint8_t low;
    uint8_t high;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        low  = _tcb->CCMPL;
        high = _tcb->CCMPH;
    }

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


void TimerCounterB::setPeriod(uint16_t value)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _tcb->CCMPL = static_cast<uint8_t>(value &  0x00FF);
        _tcb->CCMPH = static_cast<uint8_t>(value >> 8     );
    }
}


bool TimerCounterB::setPMOutput(PWMOutput pwmOutput)
{
    // Output to default pin
    if(pwmOutput == PWMOutput::Default) {
        // Check if there is a default PWM output pin
        if(_pwmPinDef == Application::InvalidPin) return false;
        // Setup port multiplexer
        PORTMUX.TCBROUTEA &= ~_pwmPortMuxBM;
        // Enable PWM output
        _tcb->CTRLB |= TCB_CCMPEN_bm;
        // Configure output pin
        _pwmPin = _pwmPinDef;
        Application::setPinMode(_pwmPin, Application::PinMode::Output);
    }

    // Output to alternate pin
    else if(pwmOutput == PWMOutput::Alternate) {
        // Check if there is an alternate PWM output pin
        if(_pwmPinAlt == Application::InvalidPin) return false;
        // Setup port multiplexer
        PORTMUX.TCBROUTEA |= _pwmPortMuxBM;
        // Enable PWM output
        _tcb->CTRLB |= TCB_CCMPEN_bm;
        // Configure output pin
        _pwmPin = _pwmPinAlt;
        Application::setPinMode(_pwmPin, Application::PinMode::Output);
    }

    // Disable output
    else { // PWMOutput::Disabled
        // Check if PWM output was enabled
        if(_pwmPin == Application::InvalidPin) return false;
        // Disable PWM output
        _tcb->CTRLB &= ~TCB_CCMPEN_bm;
        // Disable output pin
        Application::setPinMode(_pwmPin, Application::PinMode::Input);
        _pwmPin = Application::InvalidPin;
    }

    // Done
    return true;
}


void TimerCounterB::setPWMPeriodAndDutyCycle(uint8_t period, uint8_t duty)
{
    if(duty > period) duty = period;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        _tcb->CCMPL = period;
        _tcb->CCMPH = duty;
    }
}


void TimerCounterB::setPWMDutyCycle(uint8_t value)
{ _tcb->CCMPH = value; }


void TimerCounterB::attachInterrupt(eCxx::VoidCallback userFunc)
{
#ifdef TCB1
    if(_tcb == &TCB1) tcbInterruptHandler1 = userFunc;
#endif
#ifdef TCB2
    if(_tcb == &TCB2) tcbInterruptHandler2 = userFunc;
#endif
#ifdef TCB3
    if(_tcb == &TCB3) tcbInterruptHandler3 = userFunc;
#endif

    _tcb->INTCTRL |= TCB_CAPT_bm;
}


void TimerCounterB::detachInterrupt()
{
    _tcb->INTCTRL &= ~TCB_CAPT_bm;

#ifdef TCB1
    if(_tcb == &TCB1) tcbInterruptHandler1 = nullptr;
#endif
#ifdef TCB2
    if(_tcb == &TCB2) tcbInterruptHandler2 = nullptr;
#endif
#ifdef TCB3
    if(_tcb == &TCB3) tcbInterruptHandler3 = nullptr;
#endif
}


//
// Timer/counter type B interrupt vectors
//
#ifdef TCB1_INT_vect
ISR(TCB1_INT_vect)
{
    // Call the user callback if specified
    if(tcbInterruptHandler1) (*tcbInterruptHandler1)();

    // Clear the interrupt flag
    TCB1.INTFLAGS |= TCB_CAPT_bm;
}
#endif


#ifdef TCB2_INT_vect
ISR(TCB2_INT_vect)
{
    // Call the user callback if specified
    if(tcbInterruptHandler2) (*tcbInterruptHandler2)();

    // Clear the interrupt flag
    TCB2.INTFLAGS |= TCB_CAPT_bm;
}
#endif


#ifdef TCB3_INT_vect
ISR(TCB3_INT_vect)
{
    // Call the user callback if specified
    if(tcbInterruptHandler3) (*tcbInterruptHandler3)();

    // Clear the interrupt flag
    TCB3.INTFLAGS |= TCB_CAPT_bm;
}
#endif


} // namespace eCxx
