/*                                              -*- mode:C++; fill-column:100 -*-
  SFBHWTimer.cpp - Support for h/w-based timer interrupts
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

/*
  Theory of operation: 

  We don't (any longer) configure PCLKs in this code.  Instead, we use the default PCLK values
  given by the .pclkMHz fields of the clockParms array in clock.c.  Those values run a bit
  faster than we really need here, but it seems likely the added code simplicity outweighs
  trying to squeeze out a couple nanojoules by slowing the timer PCLKs a bit more.

  Then in setTickTime, we use the timer prescaler to knock the PCLK down to the given multiple
  of a microsecond.  (So larger PCLK values also means a slightly reduced maximum tick period
  -- but I just don't think people will be that concerned the <i>slowest</i> the timers can
  tick is now some 4 minutes per tick instead of 8 minutes per tick -- cutting the maximum lap
  time to only like 32K years per lap.)
 */
#include "SFBHWTimer.h"
#include "SFBProcessor.h"       // For SpeedChangeHandlerBlock
#include "lpc/LPC23xx.h"        // For hardware register definitions 
#include "lpc/irq.h"            // For TIMER0_INT, etc
#include "lpc/clock.h"          // For ClockConfiguration etc
#include "SFBAssert.h"

SFBHWTimer Timers[5] = {
  SFBHWTimer(0),
  SFBHWTimer(1),
  SFBHWTimer(2),
  SFBHWTimer(3),
  SFBHWTimer(4)
};
SFBHWTimer & Timer1 = Timers[1];
SFBHWTimer & Timer2 = Timers[2];
SFBHWTimer & Timer3 = Timers[3];
SFBHWTimer & Timer4 = Timers[4];
u32 SFBHWTimer::currentMatchIndex = 0;

void Timer0Handler() {  BUG_UNIMPLEMENTED(); }
void Timer1Handler() {  Timer1.interruptHandler(); }
void Timer2Handler() {  Timer2.interruptHandler(); }
void Timer3Handler() {  Timer3.interruptHandler(); }
void Timer4Handler() {  Timer4.interruptHandler(); }

/**
   \cond
 */
struct TimerInfo {
  uv32 * baseAddress;    // Timer register base address
  u32 pconMask;          // PCONP bit to power up the timer

  typedef void (*TimerInterruptHandler)();

  TimerInterruptHandler handler; // The interrupt handling function

  u8 interrupt;          // The interrupt number for this timer
  u8 priority;           // The default interrupt priority for this timer
};
/**
   \endcond
 */

const TimerInfo timerInfo[] = {
  { (uv32*) TMR0_BASE_ADDR, PCTIM0, Timer0Handler, TIMER0_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR1_BASE_ADDR, PCTIM1, Timer1Handler, TIMER1_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR2_BASE_ADDR, PCTIM2, Timer2Handler, TIMER2_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR3_BASE_ADDR, PCTIM3, Timer3Handler, TIMER3_INT, MEDIUM_PRIORITY },
  { (uv32*) PWM1_BASE_ADDR, PCPWM1, Timer4Handler, PWM0_1_INT, MEDIUM_PRIORITY }
};

SFBHWTimer::SFBHWTimer(u8 index) 
  : usecsPerTick(0),
    lapCounter(0),
    index(index) 
{
  API_ASSERT_MAX(index,sizeof(timerInfo)/sizeof(timerInfo[0]));
  for (u32 i = 0; i < 4; ++i)
    handlers[i] = 0;
}

void SFBHWTimer::end() {
  const TimerInfo & tdesc = timerInfo[index];

  ////
  // Disable the interrupt

  uninstall_irq(tdesc.interrupt);

  ////
  // Power down 

  PCONP &= ~tdesc.pconMask;

  ////
  // Mark as unused

  usecsPerTick = 0;
}

static void reinitHWTimers() {
  for (u32 i = 1; i < 5; ++i) 
    Timers[i].reinitForPCLK();
}

static SpeedChangeHandlerBlock speedChangeBlock = { reinitHWTimers, 0 }; // Cannot be const!

void SFBHWTimer::begin(u32 usecsPerTick, u32 ticksPerLap, bool start) {
  end();

  registerSpeedChangeHandlerBlock(&speedChangeBlock); // Ensure our callback is registered

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  u32 bits;

  PCONP |= tdesc.pconMask;    // heat up the timer

  // Disable and reset timer during setup
  REGISTER_OFFSET(timerBaseAddr,TxTCR) = 2; // TC and PR are disabled and reset

  // (We use the default PCLK (see clock.c:clockParms array) so no PCLK configuration here)

  // Timer mode: Count on rising edge of PCLK
  bits = REGISTER_OFFSET(timerBaseAddr,TxCTCR);
  BIT_PAIR_AT_POSITION(bits,0,0); // Timer mode
  REGISTER_OFFSET(timerBaseAddr,TxCTCR) = bits;

  // No laps completed yet
  lapCounter = 0;

  // Set up tick (thus marking timer active) and lap
  setTickTime(usecsPerTick);
  setLap(ticksPerLap);

  // Install the interrupt vector!
  install_irq(tdesc.interrupt, tdesc.handler, tdesc.priority);

  // Enable interrupt and reset on MR0
  REGISTER_OFFSET(tdesc.baseAddress,TxMCR) = TxMCR_MR0I|TxMCR_MR0R;

  // Start it or don't
  setRunning(start);
}

u32 SFBHWTimer::getTicks() {
  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  return REGISTER_OFFSET(timerBaseAddr,TxTC);
}

void SFBHWTimer::reinitForPCLK() {  
  if (!usecsPerTick) return;    // Not active
  setTickTime(usecsPerTick);    // Otherwise just compensate for possibly changed PCLK
}

void SFBHWTimer::setTickTime(u32 usecsPerTick) {
  API_ASSERT_NONZERO(usecsPerTick);

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  
  this->usecsPerTick = usecsPerTick; // Remember for clock speed changes

  const ClockConfiguration * cc = getCurrentClockConfiguration(); // Get clock info

  // Set prescaler to make usecsPerTick from the current PCLK
  REGISTER_OFFSET(timerBaseAddr,TxPR) = usecsPerTick*cc->pclkMHz-1;
}

void SFBHWTimer::setRunning(bool isRunning) {

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  u32 bits;

  if (isRunning) bits = 1;      // Clears reset (bit 2)
  else           bits = 0;      // either way
  REGISTER_OFFSET(timerBaseAddr,TxTCR) = bits;
}

void SFBHWTimer::reset(bool alsoStop) {
  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  u32 bits;

  bits = REGISTER_OFFSET(timerBaseAddr,TxTCR);
  REGISTER_OFFSET(timerBaseAddr,TxTCR) = 2|bits; // Add reset bit

  while (REGISTER_OFFSET(timerBaseAddr,TxTC) ||
         REGISTER_OFFSET(timerBaseAddr,TxPC)) {
    /* kill time until both TC and PC are clear (next PCLK) */
  }

  if (alsoStop) bits = 0;
  REGISTER_OFFSET(timerBaseAddr,TxTCR) = bits;

}

void SFBHWTimer::setLap(u32 ticks) {
  API_ASSERT_NONZERO(ticks); 

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;

  bool needReset = ticks-1 < REGISTER_OFFSET(timerBaseAddr,TxMR0);

  REGISTER_OFFSET(timerBaseAddr,TxMR0) = ticks-1;

  if (needReset)
    reset(false);
}

u32 SFBHWTimer::getLap() {
  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;

  return REGISTER_OFFSET(timerBaseAddr,TxMR0)+1;
}

u32 SFBHWTimer::getSplit(u32 match) {
  API_ASSERT_NONZERO(match);
  API_ASSERT_MAX(match,4);

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;

  // Match registers are 4 bytes apart counting up from MR0
  return REGISTER_OFFSET(timerBaseAddr,TxMR0+4*match);
}

HWTimerHandler SFBHWTimer::getSplitHandler(u32 match) {
  API_ASSERT_NONZERO(match);
  API_ASSERT_MAX(match,4);
  return handlers[match];
}

void SFBHWTimer::setLapHandler(HWTimerHandler handler) {
  handlers[0] = handler;    
}

HWTimerHandler SFBHWTimer::getLapHandler() {
  return handlers[0];    
}

void SFBHWTimer::setSplit(u32 match, u32 ticks) {
  API_ASSERT_NONZERO(match);
  API_ASSERT_MAX(match,4);

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;

  // Match registers are 4 bytes apart counting up from MR0
  REGISTER_OFFSET(timerBaseAddr,TxMR0+4*match) = ticks; // Update ticks
}

void SFBHWTimer::setSplitHandler(u32 match, u32 ticks, HWTimerHandler handler) {
  API_ASSERT_NONZERO(match);
  API_ASSERT_MAX(match,4);

  handlers[match] = handler;    

  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;
  u32 bits;

  u32 interruptBit = 1<<(3*match);
  bits = REGISTER_OFFSET(timerBaseAddr,TxMCR); // Get match control register
  if (handler) bits |= interruptBit;
  else         bits &= ~interruptBit;
  REGISTER_OFFSET(timerBaseAddr,TxMCR) = bits; // Update

  setSplit(match, ticks);
}

void SFBHWTimer::interruptHandler() {  
  const TimerInfo & tdesc = timerInfo[index];
  uv32 * timerBaseAddr = tdesc.baseAddress;

  // Dispatch on any match handlers, clearing bits as we go
  u32 matchBit = TxIR_MR0_Interrupt;
  for (currentMatchIndex = 0; currentMatchIndex < 4; ++currentMatchIndex) {

    if (REGISTER_OFFSET(timerBaseAddr,TxIR) & matchBit) {
      if (handlers[currentMatchIndex]) 
        handlers[currentMatchIndex]();

      REGISTER_OFFSET(timerBaseAddr,TxIR) = matchBit;
    }

    matchBit <<= 1;
  }

  // Finished another period
  ++lapCounter;

}

