/*
Copyright 2003/2004, Freie Universitaet Berlin. All rights reserved.

These sources were developed at the Freie Universität Berlin, Computer
Systems and Telematics group.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- 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.
 
- Neither the name of Freie Universitaet Berlin (FUB) 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 FUB and the contributors on an "as is"
basis, without any representations or warranties of any kind, express
or implied including, but not limited to, representations or
warranties of non-infringement, merchantability or fitness for a
particular purpose. In no event shall FUB 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 implementation was developed by the CST group at the FUB.
Contributors: Bjoern Lichtblau

For documentation and questions please use the web site
http://www.scatterweb.net and the mailinglist
scatterweb@lists.spline.inf.fu-berlin.de (subscription via the Website).
Berlin, 2003/2004/2005
*/

/** \file timer.c
 ** \ingroup Firmware
 ** \brief Initialization of the hardware timer, implementation of software timers and DCO setup&checker.
 **  
 **/
 
#include "ScatterWeb.System.h"

////////////////////////////////////////////////////////////////////////////////
/** \name Software timers
 **
 ** With theses function you can implement delays asynchronously. Just give a
 ** time and a pointer to a function which should get called when the time has elapsed. 
 **
 ** The timer entries are kept in a fixed size array (::MAX_TIMERS, ::timers, ::numberTimers).
 ** The entry at position 0 will always contain the next timer to expire.
 ** The ticks in the timer entries are not decreased with every system tick but
 ** first accumulated into ::_ticks until one of two things happen:
 **
 ** - ::_ticks becomes greater than ::timers[0].ticks, what means that at least ::timers[0] has elapsed
 **   (if no timer is running ::timers[0].ticks is set to 0xFFFF, so this will never happen then).
 **   The interrupt routine sets ::callTimer to 1 and ::timer_heartbeat will execute. It
 **   calls ::_consolidate and all elapsed timers (where ticks==0).
 ** - An overflow when adding a new timer is detected. For example a timer with 50000 ticks was added.
 **   _ticks has run up to 30000 and then a new timer with 40000 ticks comes in. To get the correct
 **   offset for the timer it has to be added internally with 30000+40000=70000 ticks, but that is
 **   greater than maximum integer value 65535. ::_consolidate is called which decreases first timer
 **   to 20000, ::_ticks to 0, the second timer is then added with 40000 ticks.
 **
 ** ::_consolidate reads ::_ticks and subtracts the value from all ::timers[i].ticks (or sets them to 0 if they are
 ** already smaller) and ::_ticks itself. This is a safe way to synchronize with the interrupt and better than
 ** setting ::_ticks to 0, because if ::_ticks is just increased by the interrupt in between read and write
 ** the single tick will not get lost.          
 */    
//@{

typedef struct {
  UINT16 ticks;   ///< ::timers[i].ticks - ::_ticks is the number of ticks until timer fires.
  fp_timer fp;          ///< Function pointer to call when elapsed.  
  UINT16 data;    ///< User data which is passed to the function fp.
} timerEntry;
timerEntry timers[MAX_TIMERS]; ///< Table of timer entries.

volatile UINT16 _ticks; ///< Currently accumulated ticks which are not yet subtracted from the timers entries. Increased by Timer_A0 Interrupt and decreased by _consolidate.
UINT16 numberTimers;    ///< Current number of entires in ::timers, ::timers[0] .. ::timers[::numberTimers-1] are valid entries.
//UINT8 callTimer;      ///< The interrupt routine flags here that ::_ticks > ::timers[0].ticks, what means that at least ::timers[0] has elapsed.

/** \brief Initialization of DCO, TimerA, TimerB and software timer table.
 **
 ** This method first calls SetDCO and initializes variables for timers.
 ** Timer_A is initialized to run from the divided 32,768 Hz Crystal at 4096 Hz.
 ** Timer_B is initialized to run from the self-regulated DCO at 4,505600 MHz.
 **   
 ** Timer_A0 is used to generate an interrupt every 4 ticks once a 1024/second (near to a millisecond.)
 ** Timer_A1(-3) is used by watchdog.c to implement a software watchdog.
 ** Timer_B0 is used by recir.c to capture RC5 signals every 890 us.  
 ** Timer_B1(-6) is used to measure the DCO continously with TB6.
 ** 
 */ 
bool Timers_init(){
  UINT16 i;
  // DCO Init
  System_setDCO();
  // Timer Software Init
  for(i=0; i<MAX_TIMERS; i++){ timers[i].ticks = 0; timers[i].fp = NULL; timers[i].data = 0; }
  timers[0].ticks = 0xFFFF;
  numberTimers = 0;
  ////////////////////////////////// Init TIMERA to run continously from the divided ACLK (4096 Hz) and setup TA0 for system ticks.
  TACTL = TASSEL0 + TACLR ;       // Clr timer, select ACLK.
  TACCTL0 = CCIE;                 // Compare mode: enable interrupt.
  TACCR0 = 4;                     // Interrupt every 4 ticks @4096 Hz => 1024 Hz.
  TACTL |= MC1;                   // Start timer in continous mode.
  ////////////////////////////////// Init TIMERB to run continously from DCO at 4,505606 Hz and setup TB6 for DCO checking.
  TBCTL = TBSSEL1 + TBCLR;        // Clr timer, select SMCLK-
  TBCTL |= MC1;                   // Start Timer B in continuous mode
  if(Configuration.firmwareFlags & FF_DCOCHECKER){
    extern unsigned int dcoCap;
    extern void checkDCO();
    TBCCTL6 = CCIS_1 + CM_2 + CAP;    // Set TB6 to capture CCIB (which is ACLK on TB6) on one edge, clears CCIFG
    //////////////////////////////////// Await the first cap to init dcoCap.
    while(!(TBCCTL6 & CCIFG)) _NOP(); // Wait polling for first capture.
    dcoCap = TBCCR6;                  // Save first capture like in isr.
    TBCCTL6 &= ~(CCIFG);              // Clear ifg like in isr:
    TBCCTL6 |= CCIE;                  // Enable tb6 interrupt.
    Timers_add(100, checkDCO, 0xFFFF);
  } else {
    TBCCTL6 = 0x0000;
  }
  return True;
}

/// Reads and decrements ticks and decrements all timers[i].ticks accordingly.
void _consolidate(){
  UINT16 i;
  // Good and safe way to synchronize with the interrupt without disabling it:
  // First read ticks and then subtract it. Both operations are atomic, if
  // they get interrupted in between no tick will get lost, it will stay
  // in ticks due to the subtract.
  UINT16 ticks = _ticks;
  _ticks -= ticks;
  // Now subtract the ticks from all timers, timers with ticks=0 are expired.
  for(i=0; i<numberTimers; i++){
    if(timers[i].ticks > ticks) timers[i].ticks -= ticks;
    else                        timers[i].ticks = 0; // expired
  }
}

/** \brief Timer heartbeat function called by main loop() as often as possible.
 **
 */   
bool Timers_eventHandler(){
  UINT16 i = 0;
  // Consolidate timer list: subtract timed ticks
  _consolidate();
  while(i<numberTimers){ // Call and remove all expired timers
    if(timers[i].ticks==0){
      // todo maybe better
      fp_timer fp = timers[i].fp;
      UINT16 data = timers[i].data;
      //log(DEBUG, "timer expired: pos: %i adr: %x\r\n", i, timers[i].fp);
      timers[i] = timers[numberTimers-1];
      numberTimers--;
      fp(data);   
    }
    else i++;
  }
  // If timers empty set ticks to 0xFFFF so callTimer will never be set.
  if(numberTimers==0) timers[0].ticks = 0xFFFF;
  // else if more than one find the new min
  else if(numberTimers>1){
    UINT16 min = 0;
    for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
    if(min != 0){ // If min is other than 0 swap them
      timerEntry te = timers[0];
      timers[0] = timers[min];
      timers[min] = te; 
    }
  }
  // If no timer expired until now reset callTimer, else let it stay.
  if(_ticks <= timers[0].ticks) runModule &= ~MF_TIMER;
  return True;
}

/// Strictly adds a timer, also if it exists already. If ticks are passed, the given functions is called.
bool Timers_add(UINT16 t, fp_timer fp, UINT16 data){
  // Timers empty, first entry.
  if(numberTimers==0){
    _ticks = 0; // Reset ticks.
    timers[0].ticks = t;
    timers[0].fp = fp;
    timers[0].data = data;
    numberTimers++;
    //log(DEBUG, "first timer added: t: %i adr: %x\r\n", t, fp);
    return True;
  }
  // Timers running, add another one.
  else if(numberTimers<MAX_TIMERS){ 
    UINT16 incticks = t + _ticks; // If this timer is just added in it has to get this value, check it for overflow.
    // Can the timer be added by adding its ticks with _ticks? Then no consolidation is needed.
    if(incticks<t){ // Overflow, we will first have to consolidate the list by subtracting _ticks.
      _consolidate();
      incticks = t + _ticks; // If really a tick occured just in between these few lines this tick will be accounted here. _ticks should be never more than 1 !   
    }
    if(incticks < timers[0].ticks){ // New entry is the smallest, put entry 0 away and save this there.
      timers[numberTimers] = timers[0];
      
      timers[0].ticks = incticks;
      timers[0].fp = fp;
      timers[0].data = data;
    }
    else { // Entry 0 is still the smallest, save this on new place.
      timers[numberTimers].ticks = incticks;
      timers[numberTimers].fp = fp;
      timers[numberTimers].data = data;
    }
    numberTimers++;
    //log(DEBUG, "timer added: t: %i adr: %x\r\n", incticks, fp);
    return True;
  }
  // No place found to add => timers full  
  else {
    if(Configuration.panicFlags & PANIC_ON_FULL_TIMERS) System_panic("Timers FULL(%x)", fp);
    else Comm_log(LOW, "timers full!! not added adr: %x\r\npos ticks fp\r\n", fp);
  }
  return False;
}

/// Adds or (if it already exists)  resets a timer.
bool Timers_reset(UINT16 t, fp_timer fp, UINT16 data){
  UINT16 i, j;
  for(j=0; j<numberTimers; j++){
    if((timers[j].fp==fp) && (timers[j].data==data)){
      UINT16 incticks = t + _ticks; // If without consolidation it has to get this value, check it for overflow.
      // Can the timer be added by adding its ticks with _ticks? Then no consolidation is needed.
      if(incticks<t){ // Overflow, we will first have to consolidate the list by subtracting _ticks.
        _consolidate();
        incticks = t + _ticks; // If really a tick occured just in between these few lines this tick will be accounted here. _ticks should be never more than 1 !   
      }
      timers[j].ticks = incticks;
      if(j==0 && numberTimers > 1){ // if it was the first one and more than 1 timer remain search new min and set it
        UINT16 min = 0;
        for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
        if(min != 0){ // If min is other than 0 swap them
          timerEntry te = timers[0];
          timers[0] = timers[min];
          timers[min] = te; 
        }        
      }
      else if(timers[j].ticks < timers[0].ticks){ // if not the first and smaller swap them
        timerEntry te = timers[0];
        timers[0] = timers[j];
        timers[j] = te;       
      }
      //log(DEBUG, "timer reset: t: %i adr: %x\r\n", incticks, fp);
      return True;
    }
  }
  Timers_add(t, fp, data);
  return False;
}

/// Removes timer if data is matching!!
bool Timers_remove(fp_timer fp, UINT16 data){
  UINT16 i, j;
  for(j=0; j<numberTimers; j++){
    if((timers[j].fp==fp) && (timers[j].data==data /*|| data==0xFFFF*/)){
      // remove it
      timers[j] = timers[numberTimers-1];
      numberTimers--;
      if(j==0 && numberTimers > 1){ // if it was the first one and more than 1 timer remain search new min and set it
        UINT16 min = 0;
        for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
        if(min != 0){ // If min is other than 0 swap them
          timerEntry te = timers[0];
          timers[0] = timers[min];
          timers[min] = te; 
        }        
      }
      //log(DEBUG, "timer removed: adr: %x\r\n", fp);
      return True;
    }
  }
  //log(DEBUG, "timer not removed: adr: %x\r\n", fp);
  return False;
}

/// Main system tick. Every 4 ticks @ 4096 Hz 
/// currentTime and _ticks get increased.
interrupt(TIMERA0_VECTOR) Timer_A0 (){
  extern volatile time_t currentTime; // OK, access is exlusive.
  TACCR0 += 4; // Fire timer again in a 1024/second.
  // Software Timers.
  if(++_ticks > timers[0].ticks) WAKEUPLPM1(Timer_A0, MF_TIMER);
  // System time.
  if(++currentTime.millis>=1024){
    currentTime.millis=0;
    currentTime.secs++;
  }
}

//@}

/// \name Timer commands
/// \ingroup Commands
//@{

/// tmr <br> Reads timer table.
COMMAND(tmr, SERIALONLY){
  UINT16 i;
  Comm_print("_ticks: %i t[0].ticks: %i\r\n", _ticks, timers[0].ticks);
  for(i=0; i<numberTimers; i++) Comm_print("%i: FP:%x T:%i D:%x\r\n", i, timers[i].fp, timers[i].ticks, timers[i].data);
}

//@}
