/* timer1.c */

#include <stdlib.h>
#include "kernel.h"
#include "swis.h"
#include "timer1.h"

static int timer_type = -1;

// This version uses OS_MonotonicTime.
static uint32_t mono_t0;

// This version uses the module TimerMod.
#define Timer_Start 0x490C0
#define Timer_Stop  0x490C1
#define Timer_Value 0x490C2

#define MPEG_RATE 90000 // Power up setting of the system clock rate in Hz
#define DIVISOR (1<<13) // A power of 2 for speed

static uint32_t rate = MPEG_RATE;
static uint32_t ratecs = MPEG_RATE / 100;
static uint32_t multiplier = (MPEG_RATE * DIVISOR) / 1000000;

/* The scale factor for converting the microsecond output from TimerMod to mpeg
 * clock units is (multiplier/DIVISOR). This should be 90000/1000000 requiring a
 * division. A power of 2 divisor is much faster using a shift but the calculation
 * is no longer exact due to integer truncation.
 * For a rate of 90000, a divisor of 1<<13 makes the scale factor 737/8192 and
 * leaves a gap of 35 counts every second, good enough for our purposes.
 * Multiplier values larger than 4294 will cause an (unsigned)overflow.
 * A gap of 90 results in an error increasing from zero to 1ms every second.
 */

static int rtc, rtc0, count, count0, pausertc, pausecount;

/**
 * Sets the type of hardware timer use to read time.
 */
void timer_selecthardware(int type)
{
  int class = 0;

  timer_type = type;
  if (type != TIMER_OS)
  {
    // Ionyx?
    if (!_swix(OS_Hardware, _INR(8,9), 1, 12))
      return;
    // IOC Timer?
    _swix(OS_ReadSysInfo, _IN(0)|_OUT(0), 8, &class);
    if (class >= 7)
      timer_type = TIMER_OS;
  }
}

/**
 * Returns the current timer rate.
 */
uint32_t timer_getrate(void)
{
  return rate;
}

/**
 * Returns the timer equivqlent of x centiseconds.
 */
uint32_t timer_getcs(uint32_t cs)
{
  return ratecs * cs;
}

/**
 * Adjusts the mpeg clock time constant. The video playback speed is directly
 * proportional to this clock, but the audio playback speed is unaffected.
 * Changing the time constant will alter the video playback speed relative to
 * the audio.
 *
 * @param  new_rate  New mpeg clock time constant.
 */
void timer_setrate(uint32_t new_rate)
{
  rate = new_rate;
  ratecs = rate / 100;
  multiplier = (new_rate * DIVISOR) / 1000000;
}

/**
 * Returns monotonic time, by default in units of 1/90KHz (11.11us)
 * note. 32 bits of 1/90KHz = 13.25 hours.
 * Timer_Value returns: regs.r[0] = seconds
 *                      regs.r[1] = remainder in microseconds (0..999999)
 */
static uint32_t mpeg_clock(void)
{
  switch (timer_type)
  {
    case TIMER_OS:
    {
      int32_t t1;

      _swix(OS_ReadMonotonicTime, _OUT(0), &t1);

      return ((t1 - mono_t0) * 900);
    }
    break;
    case TIMER_MOD:
    {
      uint32_t sec, micro;

      _swix(Timer_Value, _OUTR(0,1), &sec, &micro);

      return (sec * rate + (micro * multiplier) / DIVISOR);
    }
    break;
    default:
      return 0;
  }
}

/**
 * Allocate and presets all the timers.
 * Starts ticking the main timer but the rtc timer remains paused.
 */
void timer_install(void)
{
  int i;

  if (timer_type == TIMER_OS)
    _swix(OS_ReadMonotonicTime, _OUT(0), &mono_t0);

  // reset counters
  for (i = 0; i < 9; i++)
    fn_time[i] = 0;
  // TimerStart is not called, so that other users are not affected
  pausecount = 0;
  pausertc = 1;
  count0 = mpeg_clock();
  count = rtc0 = rtc = 0;
}

/**
 * Releases the timers.
 */
void timer_uninstall(void)
{
}

/**
 * Resets the rtc timer.
 */
void timer_resetrtc(void)
{
  pausertc = 1;
  rtc0 = rtc = 0;
}

/**
 * Returns the time elapsed since the timers installation.
 * The timer remains blocked during pauses.
 */
uint32_t timer_gettime(void)
{
  if(!pausecount)
    count = mpeg_clock() - count0;
  return count;
}

/**
 * Returns the rtc time elapsed since the timers installation.
 * The timer remains blocked during pauses.
 */
uint32_t timer_getrtc(void)
{
  if(!pausertc)
    rtc = mpeg_clock() - rtc0;
  return rtc;
}

/**
 * Alters the current value of the rtc and unpauses it.
 *
 * @param  t  New rtc value.
 */
void timer_setrtc(uint32_t t)
{
  rtc = t;
  rtc0 = mpeg_clock() - rtc;
  pausertc = 0;
}

/**
 * Pauses or starts ticking both timers.
 *
 * @param  on  non-zero to pause, 0 to start ticking.
 */
void timer_pause(int on)
{
  if (!on)
  {
    int t = mpeg_clock();

    if (pausecount)
      count0 = t - count;
    if (pausertc)
      rtc0 = t - rtc;
  }
  pausertc = pausecount = on;
}

/**
 * Pauses or starts ticking general timer.
 *
 * @param  on  non-zero to pause, 0 to start ticking.
 */
void timer_pausetime(int on)
{
  if (on)
  {
    // pause also rtc
    pausertc = on;
  }
  else if (pausecount)
  {
    // restart timer count
    count0 = mpeg_clock() - count;
  }

  pausecount = on;
}

/**
 * Pauses or starts ticking rtc.
 *
 * @param  on  non-zero to pause, 0 to start ticking.
 */
void timer_pausertc(int on)
{
  if (!on)
  {
    int t = mpeg_clock();

    // restart also timer count
    if (pausecount)
    {
      count0 = t - count;
      pausecount = on;
    }
    // restart rtc count
    if (pausertc)
      rtc0 = t - rtc;
  }
  pausertc = on;
}
