/*
  インターバルタイマー
  Satofumi KAMIMURA
  $Id: timer.c 360 2008-11-19 23:51:52Z satofumi $
*/

#include "sh7045lib.h"
#include "cpu_spec.h"


/*!
  \brief タイマーハンドラ情報の管理
*/
typedef struct {
  timerHandler_t callback;
  unsigned long interval;
  long interval_left;
} eachTimerHandler_t;


/*!
  \brief タイマーハンドラの統括管理
*/
typedef struct {
  int numHandler;
  unsigned long interval_usec;
  eachTimerHandler_t handler[TIMER_HANDLER_MAX];
} timerHandlerSet_t;
static timerHandlerSet_t Handler;


static unsigned long Usec = 0;
static unsigned long Sec = 0;


static void rawTimerHandler(void) {
  int i;
  eachTimerHandler_t *p;

  Usec += Handler.interval_usec;
  if (Usec >= 1000000) {
    Usec -= 1000000;
    ++Sec;
  }
  for (i = 0; i < Handler.numHandler; ++i) {
    p = &Handler.handler[i];
    p->interval_left -= Handler.interval_usec;
    while ((long)Handler.interval_usec > p->interval_left) {
      p->interval_left += p->interval;
      p->callback();
    }
  }
}


int set_timerHandler(timerHandler_t callback, int interval_usec) {
  if (Handler.numHandler >= TIMER_HANDLER_MAX) {
    return -1;
  }
  Handler.handler[Handler.numHandler].callback = callback;
  Handler.handler[Handler.numHandler].interval = interval_usec;
  Handler.handler[Handler.numHandler].interval_left = interval_usec;

  ++Handler.numHandler;
  return 0;
}


void unset_timerHandler(timerHandler_t callback) {
  int i;

  if (Handler.numHandler <= 1) {
    Handler.numHandler = 0;
  }

  for (i = 0; i < Handler.numHandler; ++i) {
    if (Handler.handler[i].callback == callback) {
      Handler.handler[i] = Handler.handler[Handler.numHandler];
      --Handler.numHandler;
      return;
    }
  }
}


void init_timer(int level) {
  Handler.numHandler = 0;
  Handler.interval_usec = ~0;

  MTU.TSTR.BYTE &= ~0x80;       /* stop MTU4 */
  MTU4.TCR.BYTE = 0x20;         /* use TGRA, 1/1clk */
  MTU4.TMDR.BYTE = 0x00;        /* normal timer mode */
  MTU4.TIOR.BYTE.H = 0x03;      /* toggle output */
  MTU.TOER.BYTE |= 0x02;        /* MTU4 output setting */

  INTC.IPRF.WORD &= ~0xf000;    /* interrupt level */
  INTC.IPRF.WORD |= level << 12;

  MTU4.TIER.BYTE |= 0x01;       /* enable TGRA interrupt */

  PFC.PECR1.WORD |= 0x0100;     /* use TIOC4A port */
  PFC.PEIOR.WORD |= 0x1000;
}


int start_timer(int usec) {
  unsigned long cnt = (MTU4_MSEC_CNT * usec) >> 10;
  if (cnt > 0xffff) {
    return -1;
  }
  Handler.interval_usec = usec;
  MTU4.TGRA = cnt;
  MTU4.TCNT = 0;
  MTU.TSTR.BYTE |= 0x80;

  return 0;
}


void stop_timer(void) {
  MTU.TSTR.BYTE &= ~0x80;
}


void getRawCount(unsigned short *cnt) {
  int level = get_imask();

  set_imask((INTC.IPRF.WORD >> 12) & 0xf);
  *cnt = MTU4.TCNT;
  set_imask(level);
}


void getTimestamp(unsigned long *msec, unsigned short *usec) {
  int level = get_imask();

  set_imask((INTC.IPRF.WORD >> 12) & 0xf);
  if (usec) {
    *usec = Usec % 1000;
  }
  if (msec) {
    *msec = Sec * 1000 + Usec / 1000;
  }
  set_imask(level);
}


#pragma interrupt
void tgi4a(void) {
  MTU4.TSR.BYTE &= ~0x01;
  rawTimerHandler();
}
