// Copyright 2011 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see .
//
// -----------------------------------------------------------------------------
//
// Timer/counter
#ifndef AVRLIBX_SYSTEM_TIMER_H_
#define AVRLIBX_SYSTEM_TIMER_H_
#include 
#include "avrlibx/avrlibx.h"
#include "avrlibx/io/gpio.h"
#include "avrlibx/system/event_system.h"
namespace avrlibx {
enum TimerMode {
  TIMER_MODE_NORMAL = 0,
  TIMER_MODE_FREQUENCY_GENERATOR = 1,
  TIMER_MODE_SINGLE_PWM = 3,
  TIMER_MODE_DUAL_PWM_T = 5,
  TIMER_MODE_DUAL_PWM_TB = 6,
  TIMER_MODE_DUAL_PWM_B = 7
};
enum TimerPrescaler {
  TIMER_PRESCALER_OFF = 0,
  TIMER_PRESCALER_CLK = 1,
  TIMER_PRESCALER_CLK_2 = 2,
  TIMER_PRESCALER_CLK_4 = 3,
  TIMER_PRESCALER_CLK_8 = 4,
  TIMER_PRESCALER_CLK_64 = 5,
  TIMER_PRESCALER_CLK_256 = 6,
  TIMER_PRESCALER_CLK_1024 = 7,
};
enum TimerChannel {
  TIMER_CHANNEL_A,
  TIMER_CHANNEL_B,
  TIMER_CHANNEL_C,
  TIMER_CHANNEL_D
};
enum TimerEventAction {
  TIMER_EVENT_ACTION_NONE = TC_EVACT_OFF_gc,
  TIMER_EVENT_ACTION_CAPTURE = TC_EVACT_CAPT_gc,
  TIMER_EVENT_ACTION_UPDOWN = TC_EVACT_UPDOWN_gc,
  TIMER_EVENT_ACTION_QDEC = TC_EVACT_QDEC_gc,
  TIMER_EVENT_ACTION_RESTART = TC_EVACT_RESTART_gc,
  TIMER_EVENT_ACTION_FRQ = TC_EVACT_FRW_gc,
  TIMER_EVENT_ACTION_PW = TC_EVACT_PW_gc
};
template struct TCWrapper { };
#define WRAP_TIMER(port, index) \
template<> \
struct TCWrapper { \
  static inline TC0_t& tc() { \
    return (TC0_t&)(TC ## port ## index); \
  } \
  static volatile inline uint16_t count() { \
    return TC ## port ## index ## _CNT; \
  } \
  static inline void set_count(uint16_t value) { \
    TC ## port ## index ## _CNT = value; \
  } \
  static inline uint16_t period() { \
    return TC ## port ## index ## _PERBUF; \
  } \
  static inline void set_period(uint16_t value) { \
    TC ## port ## index ## _PERBUF = value; \
  } \
  static inline uint8_t dma_tx_trigger() { \
    return DMA_CH_TRIGSRC_TC ## port ## index ## _OVF_gc; \
  } \
  static inline uint8_t overflow_event() { \
    return EVSYS_CHMUX_TC ## port ## index ## _OVF_gc; \
  } \
  template \
  static inline void set_channel(uint16_t value) { \
    if (channel == TIMER_CHANNEL_A) { \
      TC ## port ## index ## _CCABUF = value; \
    } else if (channel == TIMER_CHANNEL_B) { \
      TC ## port ## index ## _CCBBUF = value; \
    } else if (channel == TIMER_CHANNEL_C && !index) { \
      TC ## port ## 0_CCCBUF = value; \
    } else if (channel == TIMER_CHANNEL_D && !index) { \
      TC ## port ## 0_CCDBUF = value; \
    } \
  } \
  template \
  static inline uint16_t get_channel() { \
    if (channel == TIMER_CHANNEL_A) { \
      return TC ## port ## index ## _CCA; \
    } else if (channel == TIMER_CHANNEL_B) { \
      return TC ## port ## index ## _CCB; \
    } else if (channel == TIMER_CHANNEL_C && !index) { \
      return TC ## port ## 0_CCC; \
    } else if (channel == TIMER_CHANNEL_D && !index) { \
      return TC ## port ## 0_CCD; \
    } \
  } \
};
WRAP_TIMER(C, 0)
WRAP_TIMER(C, 1)
WRAP_TIMER(D, 0)
WRAP_TIMER(D, 1)
WRAP_TIMER(E, 0)
#ifdef TCE1
  WRAP_TIMER(E, 1)
#endif
#ifdef TCF0
  WRAP_TIMER(F, 0)
#endif
template
class Timer {
 public:
  typedef TCWrapper TC;
  static inline void set_prescaler(TimerPrescaler prescaler) {
    TC::tc().CTRLA = prescaler;
  }
  static inline void set_mode(TimerMode mode) {
    // Preserve Compare/capture enable and set mode.
    TC::tc().CTRLB = (TC::tc().CTRLB & 0xf0) | mode;
  }
   
  static volatile inline uint16_t count() {
    return TC::count();
  }
  static inline void set_count(uint16_t value) {
    TC::set_count(value);
  }
  
  static inline uint16_t period() {
    return TC::period();
  }
  static inline void set_period(uint16_t value) {
    TC::set_period(value);
  }
  
  static inline void Restart() {
    TC::tc().CTRLFSET = 8;
  }
  
  static inline void Bind(uint8_t channel, TimerEventAction event_action) {
    TC::tc().CTRLD = event_action | 0x08 | channel;
  }
  
  static inline void EnableCC(uint8_t channel) {
    TC::tc().CTRLB |= (16 << channel);
  }
  static inline void StopCC(uint8_t channel) {
    TC::tc().CTRLB &= ~(16 << channel);
  }
  
  static inline void set_pwm_resolution(uint8_t resolution) {
    TC::set_period((1 << static_cast(resolution) )- 1);
  }
  
  static inline void EnableOverflowInterrupt(uint8_t int_level) {
    TC::tc().INTCTRLA = (TC::tc().INTCTRLA & 0xfc) | int_level;
  }
  
  static inline void DisableOverflowInterrupt() {
    TC::tc().INTCTRLA = TC::tc().INTCTRLA & 0xfc;
  }
  
  static inline void EnableChannelInterrupt(
      uint8_t channel, uint8_t int_level) {
    uint8_t shift = channel << 2;
    uint8_t mask = (0x3) << shift;
    TC::tc().INTCTRLB = (TC::tc().INTCTRLB & ~mask) | (int_level << shift);
  }
  
  static inline void DisableChannelInterrupt(uint8_t channel) {
    uint8_t shift = channel << 2;
    uint8_t mask = (0x3) << shift;
    TC::tc().INTCTRLB = TC::tc().INTCTRLB & ~mask;
  }
  
  template
  static inline void set_channel(uint16_t value) {
    TC::template set_channel(value);
  }
  
  template
  static inline uint16_t get_channel() {
    return TC::template get_channel();
  }
  
  template
  static inline void EnableChannelInterrupt(uint8_t int_level) {
    uint8_t shift = channel << 2;
    uint8_t mask = (0x3) << shift;
    TC::tc().INTCTRLB = (TC::tc().INTCTRLB & ~mask) | (int_level << shift);
  }
  
  template
  static inline void DisableChannelInterrupt() {
    uint8_t shift = channel << 2;
    uint8_t mask = (0x3) << shift;
    TC::tc().INTCTRLB = TC::tc().INTCTRLB & ~mask;
  }
  
  static inline uint8_t dma_tx_trigger() {
    //
    // Horrible hack ahead!
    //
    // It looks like a timer overflow cannot be used as a DMA trigger. The
    // workaround for this is to use an Event as a proxy: set the event trigger
    // to be the timer overflow ; set the DMA trigger to be the event.
    // Here we use system event 0 for this purpose.
    EVSYS_CH0MUX = TC::overflow_event();
    return DMA_CH_TRIGSRC_EVSYS_CH0_gc;
  }
};
// To be tested (XMega-AU only)
template
class DualTimer {
 public:
  typedef TCWrapper TC;
  static inline void set_prescaler(TimerPrescaler prescaler) {
    TC::tc().CTRLE = 0x2;
    TC::tc().CTRLA = prescaler;
  }
  static inline void EnabledInterrupts(uint8_t level_1, uint8_t level_2) {
    TC::tc().INTCTRLA = (level_1 << 2) | level_2;
  }
  
  static inline void set_periods(uint8_t period_1, uint8_t period_2) {
    TC::tc().HPER = period_1;
    TC::tc().LPER = period_2;
  }
};
template
struct PWMPinToTimer { };
#define BIND_TIMER_TO_PWM_PIN(port, index, Channel, pin) \
template<> struct PWMPinToTimer { \
  typedef Timer T; \
  enum { channel = Channel }; \
}; \
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortC, 0, TIMER_CHANNEL_D, 3);
BIND_TIMER_TO_PWM_PIN(PortC, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortC, 1, TIMER_CHANNEL_B, 5);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortD, 0, TIMER_CHANNEL_D, 3);
BIND_TIMER_TO_PWM_PIN(PortD, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortD, 1, TIMER_CHANNEL_B, 5);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortE, 0, TIMER_CHANNEL_D, 3);
#ifdef TCE1
BIND_TIMER_TO_PWM_PIN(PortE, 1, TIMER_CHANNEL_A, 4);
BIND_TIMER_TO_PWM_PIN(PortE, 1, TIMER_CHANNEL_B, 5);
#endif
#ifdef TCF0
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_A, 0);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_B, 1);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_C, 2);
BIND_TIMER_TO_PWM_PIN(PortF, 0, TIMER_CHANNEL_D, 3);
#endif
template
class PWM {
 public:
  static inline void set_value(uint16_t value) {
    PWMPinToTimer::T::template \
        set_channel::channel>(value);
  }
  static inline uint16_t get_value() {
    return PWMPinToTimer::T::template \
    get_channel::channel>();
  }
  static inline void Init(uint8_t resolution) {
    typename PWMPinToTimer::T timer;
    timer.set_prescaler(TIMER_PRESCALER_CLK);
    timer.set_mode(TIMER_MODE_SINGLE_PWM);
    timer.set_pwm_resolution(resolution);
    Start();
  }
  static inline void EnableInterrupt(uint8_t level) {
    PWMPinToTimer::T::template \
        EnableChannelInterrupt::channel>(level);
  }
  static inline void DisableInterrupt() {
    PWMPinToTimer::T::template \
        DisableChannelInterrupt::channel>();
  }
  static inline void Start() {
    Gpio::set_direction(OUTPUT);
    PWMPinToTimer::T::EnableCC(PWMPinToTimer::channel);
  }
  static inline void Stop() {
    PWMPinToTimer::T::StopCC(PWMPinToTimer::channel);
  }
  static inline void Write(uint16_t value) { set_value(value); }
};
template
class InputCapture {
 public:
  template
  static inline void Init(bool frequency_measurement_mode) {
    Gpio gpio;
    // Set the GPIO to input with rising edge detection, and disable pull-up.
    gpio.set_direction(INPUT);
    gpio.set_sense(SENSE_MODE_RISING);
    gpio.Low();
    // The selected event source is the GPIO state change.
    EventSystemChannel::set_source(gpio.event());
    // Bind this event to the channel capture.
    PWMPinToTimer::T::Bind(
        event_channel,
        frequency_measurement_mode
            ? TIMER_EVENT_ACTION_FRQ
            : TIMER_EVENT_ACTION_CAPTURE);
    Start();
  }
  static inline void EnableInterrupt(uint8_t level) {
    PWMPinToTimer::T::template \
        EnableChannelInterrupt::channel>(level);
  }
  static inline void DisableInterrupt() {
    PWMPinToTimer::T::template \
        DisableChannelInterrupt::channel>();
  }
  static inline uint16_t get_value() {
    return PWMPinToTimer::T::template \
        get_channel::channel>();
  }
  static inline void Start() {
    PWMPinToTimer::T::EnableCC(PWMPinToTimer::channel);
  }
  static inline void Stop() {
    PWMPinToTimer::T::StopCC(PWMPinToTimer::channel);
  }
};
}  // namespace avrlibx
#endif  // AVRLIBX_SYSTEM_TIMER_H_