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


#include "SFBHWTimer.h"
#include "lpc/LPC23xx.h" // For hardware register definitions 
#include "lpc/irq.h"     // For TIMER0_INT, 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
  uv32 & pclkSel;        // The PCLK_SEL register CCLK : PCLK ratio

  typedef void (*TimerInterruptHandler)();

  TimerInterruptHandler handler; // The interrupt handling function

  u8 selPos;             // The position in pclkSel to set the ratio
  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, PCLKSEL0, Timer0Handler,  2, TIMER0_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR1_BASE_ADDR, PCTIM1, PCLKSEL0, Timer1Handler,  4, TIMER1_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR2_BASE_ADDR, PCTIM2, PCLKSEL1, Timer2Handler, 12, TIMER2_INT, MEDIUM_PRIORITY },
  { (uv32*) TMR3_BASE_ADDR, PCTIM3, PCLKSEL1, Timer3Handler, 14, TIMER3_INT, MEDIUM_PRIORITY },
  { (uv32*) PWM1_BASE_ADDR, PCPWM1, PCLKSEL0, Timer4Handler, 12, PWM0_1_INT, MEDIUM_PRIORITY }
};

SFBHWTimer::SFBHWTimer(u8 index) 
  : 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;
}

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

  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

  // Select PCLK == CCLK/8 == 9MHz
  bits = tdesc.pclkSel;           // Get PCLK select bits
  BIT_PAIR_AT_POSITION(bits,3,tdesc.selPos); // PCLK 3 == CCLK/8
  tdesc.pclkSel = bits;           // Update

  // 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;

  // Default tick 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::setTickTime(u32 usecsPerTick) {
  API_ASSERT_NONZERO(usecsPerTick);

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

  // Set prescaler to make usecsPerTick from 1/9 usecsPerTick (==9MHz)
  REGISTER_OFFSET(timerBaseAddr,TxPR) = usecsPerTick*9-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;

}

