/*
 * Copyright (c) 2014, Ralf Schlatterbeck Open Source Consulting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 */

/**
 * \defgroup hardware timer
 *
 * This module wraps hardware timers of AVR microcontrollers.
 * Currently we only support 16-bit timers. The main focus is currently
 * on PWM generation. But input capture and interrupt routines are on
 * the TODO list, see below. We currently support the AVR ATmega128RFA1
 * so this should be generalized to supported timers of other AVR
 * microcontrollers.
 *
 * Datasheet references in the following refer to ATmega128RFA1 data sheet
 *
 * TODO: Allow input capture.
 * TODO: Allow definition of interrupt routine; check if merkur board
 *       supports necessary pins.
 * TODO: Generalize for 8-bit timers.
 * TODO: Check other AVR microcontrollers and the supported timers.
 *
 * @{
 */

/**
 * \file
 *        Header file for hardware timer of AVR microcontrollers
 * \author
 *        Ralf Schlatterbeck <rsc@runtux.com>
 *
 */

#ifndef hw_timer_h
#define hw_timer_h

#include "contiki.h"
#include "rtimer-arch.h"

#ifndef PLAT_TIMER
#define PLAT_TIMER -1
#endif

/*
 * All routines return a negative number for error and 0 for success.
 * The negative return value indicates the error.
 */
#define HWT_ERR_INVALID_TIMER   (-1)
#define HWT_ERR_INVALID_WGM     (-2)
#define HWT_ERR_INVALID_COM     (-3)
#define HWT_ERR_INVALID_CLOCK   (-4)
#define HWT_ERR_INVALID_CHANNEL (-5)

/*
 * Timer waveform generation modes (WGM), see data sheet
 * chapter 18 "16-bit Timer/Counter (Timer/Counter 1,3,4, and 5)
 * 18.9 "Modes of Operation", in particular Table 18-5
 */
#define HWT_WGM_NORMAL                    0
#define HWT_WGM_PWM_PHASE_8_BIT           1
#define HWT_WGM_PWM_PHASE_9_BIT           2
#define HWT_WGM_PWM_PHASE_10_BIT          3
#define HWT_WGM_CTC_OCRA                  4
#define HWT_WGM_PWM_FAST_8_BIT            5
#define HWT_WGM_PWM_FAST_9_BIT            6
#define HWT_WGM_PWM_FAST_10_BIT           7
#define HWT_WGM_PWM_PHASE_FRQ_ICR         8
#define HWT_WGM_PWM_PHASE_FRQ_OCRA        9
#define HWT_WGM_PWM_PHASE_ICR            10
#define HWT_WGM_PWM_PHASE_OCRA           11
#define HWT_WGM_CTC_ICR                  12
#define HWT_WGM_RESERVED                 13
#define HWT_WGM_PWM_FAST_ICR             14
#define HWT_WGM_PWM_FAST_OCRA            15
#define HWT_WGM_MASK                     15
#define HWT_WGM_MASK_LOW                  3
#define HWT_WGM_MASK_HIGH                (HWT_WGM_MASK - HWT_WGM_MASK_LOW)
#define HWT_WGM_SHIFT_LOW                0
#define HWT_WGM_SHIFT_HIGH               1

/*
 * Timer compare output modes (COM), 
 * chapter 18 "16-bit Timer/Counter (Timer/Counter 1,3,4, and 5)
 * 18.8 "Compare Match Output Unit", in particular Tables 18-2,3,4
 */
#define HWT_COM_NORMAL 0
#define HWT_COM_TOGGLE 1
#define HWT_COM_CLEAR  2
#define HWT_COM_SET    3
#define HWT_COM_MASK   3

/*
 * Clock select, clock can be off, use prescaler or external clock
 * source on Tn pin. See Table 18-11 (for Timer 1 but this is the same
 * for all the timers).
 */
#define HWT_CLOCK_OFF             0
#define HWT_CLOCK_PRESCALER_1     1
#define HWT_CLOCK_PRESCALER_8     2
#define HWT_CLOCK_PRESCALER_64    3
#define HWT_CLOCK_PRESCALER_256   4
#define HWT_CLOCK_PRESCALER_1024  5
#define HWT_CLOCK_EXTERN_FALLING  6
#define HWT_CLOCK_EXTERN_RISING   7
#define HWT_CLOCK_MASK            7

/*
 * Timer channels A B C
 */
#define HWT_CHANNEL_A 0
#define HWT_CHANNEL_B 1
#define HWT_CHANNEL_C 2
#define HWT_CHANNEL_D 3
#define HWT_CHANNEL_MASK 3

/* The following macros are defined for timer values 1,3,4,5 */
#define HWT_ICR(t) \
    ((t)<4?((t)==1?(&ICR1) :(&ICR3)) :((t)==4?(&ICR4) :(&ICR5)))
#define HWT_OCRA(t) \
    ((t)<4?((t)==1?(&OCR1A):(&OCR3A)):((t)==4?(&OCR4A):(&OCR5A)))
#define HWT_OCRB(t) \
    ((t)<4?((t)==1?(&OCR1B):(&OCR3B)):((t)==4?(&OCR4B):(&OCR5B)))
#define HWT_OCRC(t) \
    ((t)<4?((t)==1?(&OCR1C):(&OCR3C)):((t)==4?(&OCR4C):(&OCR5C)))

#define HWT_OCR(t,c)                             \
  ( (c)==HWT_CHANNEL_A                           \
  ? (HWT_OCRA(t))                                \
  : ((c)==HWT_CHANNEL_B?HWT_OCRB(t):HWT_OCRC(t)) \
  )

#define HWT_TCCRA(t)               \
    ( (t)<4                        \
    ? ((t)==1?(&TCCR1A):(&TCCR3A)) \
    : ((t)==4?(&TCCR4A):(&TCCR5A)) \
    )
#define HWT_TCCRB(t)               \
    ( (t)<4                        \
    ? ((t)==1?(&TCCR1B):(&TCCR3B)) \
    : ((t)==4?(&TCCR4B):(&TCCR5B)) \
    )
#define HWT_TCCRC(t)               \
    ( (t)<4                        \
    ? ((t)==1?(&TCCR1C):(&TCCR3C)) \
    : ((t)==4?(&TCCR4C):(&TCCR5C)) \
    )
#define HWT_TCNT(t)                \
    ( (t)<4                        \
    ? ((t)==1?(&TCNT1) :(&TCNT3))  \
    : ((t)==4?(&TCNT4) :(&TCNT5))  \
    )

#define HWT_CLR_COM(timer, channel)                                  \
    (*HWT_TCCRA (timer) &= ~(HWT_COM_MASK << (6 - 2 * (channel))))

#define HWT_SET_COM(timer, channel, com)                             \
    ( HWT_CLR_COM (timer, channel)                                   \
    , (*HWT_TCCRA (timer) |=  ((com)        << (6 - 2 * (channel)))) \
    )

#define HWT_CHECK_TIMER(timer)                                                 \
  do {                                                                         \
    if ((timer) == 0 || (timer) == 2 || (timer) == PLAT_TIMER || (timer) > 5) {\
      return HWT_ERR_INVALID_TIMER;                                            \
    }                                                                          \
  } while (0)

#define HWT_CHECK_CHANNEL(chan)         \
  do {                                  \
    if ((chan) > HWT_CHANNEL_C) {       \
      return HWT_ERR_INVALID_CHANNEL;   \
    }                                   \
  } while (0)

#define HWT_PWM_FAST              0
#define HWT_PWM_PHASE_CORRECT     1
#define HWT_PWM_PHASE_FRQ_CORRECT 2

/**
 * \brief Initialize the hardware timer with the given settings
 * \param timer: Timer to use
 * \param wgm: waveform generation mode to use, see definitions
 * \param clock: Prescaler or external clock settings
 * \param maxt: Maximum counter value, not used for fixed modes, this
 *        sets ICRn for the ICR modes and OCRnA for the OCR modes
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 *
 * The initial compare output mode is set to HWT_COM_NORMAL (off) for
 * all outputs (pwm disabled).
 *
 * Note that this sets the compare output mode COM registers to 0,
 * turning off PWM on outputs.
 */
static inline int8_t
hwtimer_ini (uint8_t timer, uint8_t wgm, uint8_t clock, uint16_t maxt)
{
  int8_t i;
  HWT_CHECK_TIMER (timer);
  if (wgm > HWT_WGM_MASK || wgm == HWT_WGM_RESERVED) {
    return HWT_ERR_INVALID_WGM;
  }
  if (clock > HWT_CLOCK_MASK) {
    return HWT_ERR_INVALID_CLOCK;
  }
  /* Turn off clock, no need to disable interrupt */
  *HWT_TCCRB (timer) &= ~HWT_CLOCK_MASK;

  *HWT_TCCRA (timer) &= ~(HWT_WGM_MASK_LOW         << HWT_WGM_SHIFT_LOW);
  *HWT_TCCRA (timer) |= ((wgm & HWT_WGM_MASK_LOW)  << HWT_WGM_SHIFT_LOW);
  *HWT_TCCRB (timer) &= ~(HWT_WGM_MASK_HIGH        << HWT_WGM_SHIFT_HIGH);
  *HWT_TCCRB (timer) |= ((wgm & HWT_WGM_MASK_HIGH) << HWT_WGM_SHIFT_HIGH);

  for (i=0; i<3; i++) {
    HWT_CLR_COM (timer, i);
  }

  if (  wgm == HWT_WGM_PWM_PHASE_FRQ_ICR
     || wgm == HWT_WGM_PWM_PHASE_ICR
     || wgm == HWT_WGM_CTC_ICR
     || wgm == HWT_WGM_PWM_FAST_ICR
     )
  {
    *HWT_ICR (timer) = maxt;
  }

  if (  wgm == HWT_WGM_CTC_OCRA
     || wgm == HWT_WGM_PWM_PHASE_FRQ_OCRA
     || wgm == HWT_WGM_PWM_PHASE_OCRA
     || wgm == HWT_WGM_PWM_FAST_OCRA
     )
  {
    *HWT_OCRA (timer) = maxt;
  }

  /* Set clock, finally */
  *HWT_TCCRB (timer) |= clock;
  return 0;
}

/* Needed for implementation */
#define HWT_PERIOD_MAX_ (0xFFFFFFFF / (F_CPU / 1000000))
/* for 16-bit timer: */
#define HWT_TICKS_MAX_  0xFFFF
#define HWT_TICKS_MIN_  0xFF

/**
 * \brief Convenience function to initialize hardware timer for PWM
 * \param timer: Timer to use
 * \param pwm_type: See HWT_PWM* macros
 * \param period_us: Period of the timer in µs
 * \param ocra: Use OCRnA register if set, ICRn otherwise
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 *
 * This function can be called instead of hwtimer_ini and sets up the
 * timer for one of the PWM modes. There are fast, phase-correct and
 * phase- and frequency correct modes, refer to the datasheet for
 * semantics.
 *
 * The function tries to initialize the timer to a mode that doesn't use
 * one of the internal registers OCRnA or ICRn for specifying the upper
 * bound of the counter. For fast PWM and phase-correct PWM there are
 * fixed 8-, 9-, and 10-bit modes that can be used if the computed value
 * fits one of these setups.
 *
 * We try to get the *maximum* prescaler that still permits a tick
 * resolution of at least 8 bit. This will not work for very high
 * frequencies.
 *
 * If the specified period is too large to fit into a 16-bit timer we
 * take the maximum period that is still possible, this may be
 * substatially higher than specified.
 *
 * Note that when using OCRnA for the upper bound of the counter, the
 * pin associated with this register can not be used for PWM. Instead it
 * can be used to change the period.
 */
static inline int8_t
hwtimer_pwm_ini (uint8_t timer, uint32_t period_us, uint8_t pwm_type, uint8_t ocra)
{
  uint32_t ticks = 0;
  uint8_t  clock = HWT_CLOCK_PRESCALER_1024;
  uint8_t  wgm   = HWT_WGM_NORMAL;
  HWT_CHECK_TIMER (timer);
  if (period_us > HWT_PERIOD_MAX_) {
    period_us = HWT_PERIOD_MAX_;
  }
  ticks = (F_CPU / 1000000) * period_us;
  /* Non-fast PWM modes have half the frequency */
  if (pwm_type != HWT_PWM_FAST) {
    ticks >>= 1;
  }

  /*
   * Divisors are 1, 8, 64, 256, 1024, shifts between these are
   * 3, 3, 2, 2, respectively. We modify `ticks` in place, the AVR can
   * shift only one bit in one instruction, so shifting isn't cheap.
   * We try to get the *maximum* prescaler that still permits a tick
   * resolution of at least 8 bit.
   */
  if (ticks <= (HWT_TICKS_MIN_ << 3)) {
    clock = HWT_CLOCK_PRESCALER_1;
  }
  else if ((ticks >>= 3) <= (HWT_TICKS_MIN_ << 3)) {
    clock = HWT_CLOCK_PRESCALER_8;
  }
  else if ((ticks >>= 3) <= (HWT_TICKS_MIN_ << 2)) {
    clock = HWT_CLOCK_PRESCALER_64;
  }
  else if ((ticks >>= 2) <= (HWT_TICKS_MIN_ << 2)) {
    clock = HWT_CLOCK_PRESCALER_256;
  }
  else if ((ticks >>= 2) > HWT_TICKS_MAX_) {
    ticks = HWT_TICKS_MAX_;
  }
  switch (pwm_type) {
    case HWT_PWM_FAST:
      wgm = ocra ? HWT_WGM_PWM_FAST_OCRA : HWT_WGM_PWM_FAST_ICR;
      break;
    case HWT_PWM_PHASE_CORRECT:
      wgm = ocra ? HWT_WGM_PWM_PHASE_OCRA : HWT_WGM_PWM_PHASE_ICR;
      break;
    case HWT_PWM_PHASE_FRQ_CORRECT:
    default:
      wgm = ocra ? HWT_WGM_PWM_PHASE_FRQ_OCRA : HWT_WGM_PWM_PHASE_FRQ_ICR;
      break;
  }
  /* Special 8- 9- 10-bit modes */
  if (pwm_type == HWT_PWM_FAST || pwm_type == HWT_PWM_PHASE_CORRECT) {
    if (ticks == 0xFF) {
      wgm = (pwm_type == HWT_PWM_FAST)
          ? HWT_WGM_PWM_FAST_8_BIT
          : HWT_WGM_PWM_PHASE_8_BIT;
    }
    else if (ticks == 0x1FF) {
      wgm = (pwm_type == HWT_PWM_FAST)
          ? HWT_WGM_PWM_FAST_9_BIT
          : HWT_WGM_PWM_PHASE_9_BIT;
    }
    else if (ticks == 0x3FF) {
      wgm = (pwm_type == HWT_PWM_FAST)
          ? HWT_WGM_PWM_FAST_10_BIT
          : HWT_WGM_PWM_PHASE_10_BIT;
    }
  }
  return hwtimer_ini (timer, wgm, clock, ticks);
}

/*
 * Simple init macro for sane default values
 */
#define hwtimer_pwm_ini_simple (timer, period_us) \
  hwtimer_pwm_ini ((timer), HWT_PWM_PHASE_CORRECT, (period_us), 0)

/**
 * \brief  Maximum timer value usable in hwtimer_set_pwm
 * \param  timer: Timer to use
 * \return max. timer value according to current timer setup
 *         negative value if wrong timer given
 *         a positive value is guaranteed to fit into 16 bit unsigned.
 */
static inline int32_t hwtimer_pwm_max_ticks (uint8_t timer)
{
  uint8_t wgm = 0;
  HWT_CHECK_TIMER (timer);
  wgm = ((*HWT_TCCRA (timer) >> HWT_WGM_SHIFT_LOW)  & HWT_WGM_MASK_LOW)
      | ((*HWT_TCCRB (timer) >> HWT_WGM_SHIFT_HIGH) & HWT_WGM_MASK_HIGH)
      ;
  switch (wgm) {
    case HWT_WGM_PWM_PHASE_8_BIT:
    case HWT_WGM_PWM_FAST_8_BIT:
      return 0xFF;
    case HWT_WGM_PWM_PHASE_9_BIT:
    case HWT_WGM_PWM_FAST_9_BIT:
      return 0x1FF;
    case HWT_WGM_PWM_PHASE_10_BIT:
    case HWT_WGM_PWM_FAST_10_BIT:
      return 0x3FF;
    case HWT_WGM_CTC_OCRA:
    case HWT_WGM_PWM_PHASE_FRQ_OCRA:
    case HWT_WGM_PWM_PHASE_OCRA:
    case HWT_WGM_PWM_FAST_OCRA:
      return *HWT_OCRA (timer);
    case HWT_WGM_PWM_PHASE_FRQ_ICR:
    case HWT_WGM_PWM_PHASE_ICR:
    case HWT_WGM_CTC_ICR:
    case HWT_WGM_PWM_FAST_ICR:
      return *HWT_ICR (timer);
    case HWT_WGM_NORMAL:
      return 0xFFFF;
  }
  return HWT_ERR_INVALID_WGM;
}

/*
 * The following functions are defined inline to allow for compiler
 * optimizations if some of the parameters are constant.
 */

/**
 * \brief  Set PWM duty cycle
 * \param  timer: Timer to use
 * \param  channel: Channel to use, see HWT_CHANNEL definitions
 * \param  pwm: Duty cycle
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 *
 * Note that the available range for the duty cycle depends on the timer
 * setup and the chosen mode.
 */
static inline int8_t
hwtimer_set_pwm (uint8_t timer, uint8_t channel, uint16_t pwm)
{
  uint8_t sreg = 0;
  HWT_CHECK_TIMER (timer);
  HWT_CHECK_CHANNEL (channel);
  sreg = SREG;
  cli ();
  *HWT_OCR (timer, channel) = pwm;
  SREG = sreg;
  return 0;
}

/**
 * \brief  Set compare output mode
 * \param  timer: Timer to use
 * \param  channel: Channel to use, see HWT_CHANNEL definitions
 * \param  com: compare output mode for given channel
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 */
static inline int8_t
hwtimer_set_com (uint8_t timer, uint8_t channel, uint8_t com)
{
  HWT_CHECK_TIMER (timer);
  HWT_CHECK_CHANNEL (channel);
  if (com > HWT_COM_MASK) {
    return HWT_ERR_INVALID_COM;
  }
  if (com) {
    HWT_SET_COM (timer, channel, com);
  } else {
    HWT_CLR_COM (timer, channel);
  }
  return 0;
}

/**
 * \brief  Convenience function for setting compare output mode for PWM
 * \param  timer: Timer to use
 * \param  channel: Channel to use, see HWT_CHANNEL definitions
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 */
static inline int8_t
hwtimer_pwm_enable (uint8_t timer, uint8_t channel)
{
  return hwtimer_set_com (timer, channel, HWT_COM_CLEAR);
}

/**
 * \brief  Convenience function for inverse compare output mode for PWM
 * \param  timer: Timer to use
 * \param  channel: Channel to use, see HWT_CHANNEL definitions
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 */
static inline int8_t
hwtimer_pwm_inverse (uint8_t timer, uint8_t channel)
{
  return hwtimer_set_com (timer, channel, HWT_COM_SET);
}

/**
 * \brief  Convenience function for setting compare output mode to off
 * \param  timer: Timer to use
 * \param  channel: Channel to use, see HWT_CHANNEL definitions
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 */
static inline int8_t
hwtimer_pwm_disable (uint8_t timer, uint8_t channel)
{
  return hwtimer_set_com (timer, channel, HWT_COM_NORMAL);
}

/**
 * \brief Turn off the clock
 * \param timer: Timer to use
 * \return see HWT_ERR definitions for return codes, returns 0 if ok
 */
static inline int8_t
hwtimer_fin (uint8_t timer)
{
  HWT_CHECK_TIMER (timer);
  *HWT_TCCRB (timer) &= ~HWT_CLOCK_MASK;
  *HWT_TCCRB (timer) |= HWT_CLOCK_OFF; /* technically not necessary this is 0 */
  return 0;
}

#endif /* hw_timer_h */

/*
 * ex:ts=8:et:sw=2
 */

/** @} */