/************************************************************
 *
 * Alcuin: A HP48 emulator for Amiga Computer
 *
 * Copyright (c) 1995 by Stephan Schupfer <schupfer@sbox.tu-graz.ac.at>
 *
 */

#include <stdio.h>
#include <devices/timer.h>

#include "hp48.h"
#include "timer.h"
#include "romio.h"
#include "crc.h"

/*#define DEBUG_TIMER 1*/
/*#define DEBUG_TIMER_ADJUST 1*/

typedef struct x48_timer_t {
  word_1  run;
  word_64 start;
  word_64 stop;
  word_64 value;
} x48_timer_t;

static x48_timer_t timers[NR_TIMERS];

/***********************************************
 * Ticks for THU 01.01.1970 00:00:00 (UNIX)
 *
 * word_64 unix_0_time  = { 0x0001cf2e, 0x8f800000 };
 *
 * Ticks for THU 01.01.1978 00:00:00 (AMIGA)
 * (1978 + 480 extratage - Schaltjahr)
 */

word_64 amiga_0_time  = { 0x0001d110, 0x17600000 };
word_64 ticks_10_min = { 0x0, 0x00b40000 };

/*
 * Will be in saturn_t in the future
 */
word_64 set_0_time = { 0x0, 0x0 };

/*
 * Calculated as (unix_0_time + set_0_time)
 */
word_64 time_offset = { 0x0, 0x0 };

#define RAM_BASE_SX	0x70000
#define ACCESSTIME_SX	(0x70052 - RAM_BASE_SX)
#define ACCESSCRC_SX	(0x7005F - RAM_BASE_SX)
#define TIMEOUT_SX	(0x70063 - RAM_BASE_SX)
#define TIMEOUTCLK_SX	(0x70070 - RAM_BASE_SX)

#define RAM_BASE_GX	0x80000
#define ACCESSTIME_GX	(0x80058 - RAM_BASE_GX)
#define ACCESSCRC_GX	(0x80065 - RAM_BASE_GX)
#define TIMEOUT_GX	(0x80069 - RAM_BASE_GX)
#define TIMEOUTCLK_GX	(0x80076 - RAM_BASE_GX)

#define add_64(res,a,b)\
{\
  unsigned long t = (a).lo;\
  ((res)->lo = (a).lo + (b).lo);\
  ((res)->hi = (a).hi + (b).hi);\
  if (t > (res)->lo) (res)->hi++;\
}

#define sub_64(res,a,b)\
{\
  unsigned long t = (a).lo;\
  ((res)->lo = (a).lo - (b).lo);\
  ((res)->hi = (a).hi - (b).hi);\
  if (t < (res)->lo) (res)->hi--;\
}

struct timerequest *TimerIO;
struct MsgPort *TimerMP;
struct Message *TimerMSG;
int timeop=1;
word_20	  accesstime_loc, timeout_loc;
word_20	  accesscrc_loc, timeoutclk_loc;

/*********************************************************************/
void opentime()
{
if (!(TimerMP =(struct MsgPort *) CreatePort(0,0)))
	stirb ("Can't create %s.",101,"MessagePort");
if (!(TimerIO = (struct timerequest *) CreateExtIO(TimerMP,sizeof(struct timerequest))))
	stirb ("Can't create %s.",101,"TimeRequest");
if (timeop=OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerIO,0L))
	stirb ("Can't open %s.",100,"timer.device");
}
/*********************************************************************/
void closetime()
{
if (!timeop)	CloseDevice((struct IORequest *) TimerIO);
if (TimerIO) 	DeleteExtIO(TimerIO);
if (TimerMP)	DeletePort(TimerMP);
}

/*********************************************************************/
void init_time(void)
{
if (opt_gx) {
	accesstime_loc = ACCESSTIME_GX;
	accesscrc_loc = ACCESSCRC_GX;
	timeout_loc = TIMEOUT_GX;
	timeoutclk_loc = TIMEOUTCLK_GX;
	}
else {
	accesstime_loc = ACCESSTIME_SX;
	accesscrc_loc = ACCESSCRC_SX;
	timeout_loc = TIMEOUT_SX;
	timeoutclk_loc = TIMEOUTCLK_SX;
	}
}

/*********************************************************************/
static void systime ()
{
TimerIO->tr_node.io_Command = TR_GETSYSTIME;
DoIO((struct IORequest *) TimerIO);
}

/*********************************************************************
 *
 * Set ACCESSTIME: (on startup)
 *
 * 1. TICKS = 8092 * gettimeofday()  (UNIX System Time)
 * 2. TICKS += unix_0_time           (TICKS for 1.1.1970, 0:00)
 * 3. TICKS += set_0_time            (Time adjustment from User)
 * 4. TICKS += saturn.timer2         (Timer 2 from last run)
 * 5. Write this into ACCESSTIME
 * 6. Calculate CRC for 13 Nibbles
 * 7. Write this into ACCESSCRC
 * 8. Prevent AutoOff by setting TIMEOUT
 *
 */
void set_accesstime(void)
{
word_64	  ticks, timeout, timer2;
word_16	  crc;
word_4	  val;
int		  i;

systime();
ticks.hi = (TimerIO->tr_time.tv_secs >> 19);
ticks.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
time_offset.lo = amiga_0_time.lo;
time_offset.hi = amiga_0_time.hi;
add_64(&time_offset, time_offset, set_0_time);
add_64(&ticks, ticks, time_offset);
timer2.lo = saturn_timer2;
if (saturn_timer2 & 0x80000000) timer2.hi = 0xffffffff;
else timer2.hi = 0x0;
add_64(&ticks, ticks, timer2);
timeout.lo = ticks.lo;
timeout.hi = ticks.hi;
crc = 0x0;
for (i = 0; i < 13; i++) {
	val = ticks.lo & 0xf;
      do_crc(crc,val);
      saturn_ram[accesstime_loc + i] = val;
      ticks.lo >>= 4;
      ticks.lo |= (ticks.hi & 0xf) << 28;
      ticks.hi >>= 4;
    }
for (i = 0; i < 4; i++) {
	saturn_ram[accesscrc_loc + i] = crc & 0xf;
	crc >>= 4;
	}
add_64(&timeout, timeout, ticks_10_min);
for (i = 0; i < 13; i++) {
	val = timeout.lo & 0xf;
	do_crc(crc,val);
	saturn_ram[timeout_loc + i] = val;
	timeout.lo >>= 4;
	timeout.lo |= (timeout.hi & 0xf) << 28;
	timeout.hi >>= 4;
	}
saturn_ram[timeoutclk_loc] = 0xf;
}

/*********************************************************************/
/*long diff_timer(word_64 *t1, word_64 *t2)
{
return (t1->lo - t2->lo);
}
*/
/*********************************************************************/
void start_timer(int timer)
{
if (timers[timer].run) return;
systime();
timers[timer].run = 1;
if (timer == T1_TIMER) {
	timers[timer].start.hi = (TimerIO->tr_time.tv_secs >> 23);
	timers[timer].start.lo = (TimerIO->tr_time.tv_secs << 9) | (TimerIO->tr_time.tv_micro / 62500);
	}
else {
	timers[timer].start.hi = (TimerIO->tr_time.tv_secs >> 19);
	timers[timer].start.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
	}
#ifdef DEBUG_TIMER
  fprintf(stderr, "Timer[%d] start at 0x%.8lx%.8lx\n", timer,
          timers[timer].start.hi, timers[timer].start.lo);
#endif
}

/*********************************************************************/
void restart_timer(int timer)
{
timers[timer].start.lo = timers[timer].start.hi = 0;
timers[timer].stop.lo = timers[timer].stop.hi = 0;
timers[timer].value.lo = timers[timer].value.hi = 0;
systime();
timers[timer].run = 1;
if (timer == T1_TIMER) {
	timers[timer].start.hi = (TimerIO->tr_time.tv_secs >> 23);
	timers[timer].start.lo = (TimerIO->tr_time.tv_secs << 9) | (TimerIO->tr_time.tv_micro / 62500);
	}
else {
	timers[timer].start.hi = (TimerIO->tr_time.tv_secs >> 19);
	timers[timer].start.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
	}
#ifdef DEBUG_TIMER
  fprintf(stderr, "Timer[%d] restart at 0x%.8lx%.8lx\n", timer,
          timers[timer].start.hi, timers[timer].start.lo);
#endif
}

/*********************************************************************/
void stop_timer(int timer)
{
word_64 run;

if (!timers[timer].run) return;
systime();
timers[timer].run = 0;
if (timer == T1_TIMER) {
	timers[timer].stop.hi = (TimerIO->tr_time.tv_secs >> 23);
	timers[timer].stop.lo = (TimerIO->tr_time.tv_secs << 9) | (TimerIO->tr_time.tv_micro / 62500);
	}
else {
	timers[timer].stop.hi = (TimerIO->tr_time.tv_secs >> 19);
	timers[timer].stop.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
	}
sub_64(&run, timers[timer].stop, timers[timer].start);
add_64(&timers[timer].value, timers[timer].value, run);
#ifdef DEBUG_TIMER
  fprintf(stderr, "Timer[%d] stop at 0x%.8lx%.8lx, value 0x%.8lx%.8lx\n",
          timer, timers[timer].stop.hi, timers[timer].stop.lo,
          timers[timer].value.hi, timers[timer].value.lo);
#endif
}

/*********************************************************************/
void reset_timer(int timer)
{
timers[timer].run = 0;
timers[timer].start.lo = timers[timer].start.hi = 0;
timers[timer].stop.lo = timers[timer].stop.hi = 0;
timers[timer].value.lo = timers[timer].value.hi = 0;
#ifdef DEBUG_TIMER
  fprintf(stderr, "Timer[%d] reset\n", timer);
#endif
}

/*********************************************************************/
word_64 get_timer(int timer)
{
word_64	run,stop;

if (timers[timer].run) {
	systime();
	if (timer == T1_TIMER) {
		stop.hi = (TimerIO->tr_time.tv_secs >> 23);
		stop.lo = (TimerIO->tr_time.tv_secs << 9) | (TimerIO->tr_time.tv_micro / 62500);
		}
	else {
		stop.hi = (TimerIO->tr_time.tv_secs >> 19);
		stop.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
		}
    sub_64(&run, stop, timers[timer].start);
    add_64(&timers[timer].value, timers[timer].value, run);
	}
return timers[timer].value;
}

/*********************************************************************
 *
 * Calculate TIMER 2 Ticks:
 *
 * 1. TICKS = 8092 * gettimeofday()  (UNIX System Time)
 * 2. TICKS += unix_0_time           (TICKS for 1.1.1970, 0:00)
 * 3. TICKS += set_0_time            (Time adjustment from User)
 * 4. Get value of ACCESSTIME
 * 5. Return (ACCESSTIME - TICKS)
 *
 */

t1_t2_ticks get_t1_t2(void)
{
word_64		run,stop;
t1_t2_ticks	ticks;
word_64		access_time;
word_64		adj_time;
word_64		diff_time;
word_64		delta;
word_20		at;
int			i;

systime();
if (timers[T1_TIMER].run) {
	stop.hi = (TimerIO->tr_time.tv_secs >> 23);
	stop.lo = (TimerIO->tr_time.tv_secs << 9) | (TimerIO->tr_time.tv_micro / 62500);
    sub_64(&run, stop, timers[T1_TIMER].start);
    add_64(&timers[T1_TIMER].value, timers[T1_TIMER].value, run);
	}
ticks.t1_ticks = timers[T1_TIMER].value.lo;
stop.hi = (TimerIO->tr_time.tv_secs >> 19);
stop.lo = (TimerIO->tr_time.tv_secs << 13) | ((TimerIO->tr_time.tv_micro << 7) / 15625);
add_64(&stop,stop,time_offset);
/*
access_time.lo = 0x0;
access_time.hi = 0x0;
for (i = 13 - 1; i >= 0; i--) {
	access_time.hi <<= 4;
	access_time.hi |= ((access_time.lo >> 28) & 0xf);
	access_time.lo <<= 4;
	access_time.lo |= ((int)saturn_ram[accesstime_loc + i] & 0xf);
	}
*/
at=accesstime_loc;
access_time.lo = saturn_ram[at++];
access_time.lo |= saturn_ram[at++] << 4;
access_time.lo |= saturn_ram[at++] << 8;
access_time.lo |= saturn_ram[at++] << 12;
access_time.lo |= saturn_ram[at++] << 16;
access_time.lo |= saturn_ram[at++] << 20;
access_time.lo |= saturn_ram[at++] << 24;
access_time.lo |= saturn_ram[at++] << 28;
access_time.hi = saturn_ram[at++];
access_time.hi |= saturn_ram[at++] << 4;
access_time.hi |= saturn_ram[at++] << 8;
access_time.hi |= saturn_ram[at++] << 12;
access_time.hi |= saturn_ram[at] << 16;
sub_64(&access_time,access_time,stop);
if (adj_time_pending) {
	if ((saturn_timer2 >= 0 && (access_time.lo & 0x80000000)) || ((unsigned long)saturn_timer2 > access_time.lo)) {
		ticks.t2_ticks = access_time.lo;
		}
	else {
		ticks.t2_ticks = saturn_timer2;
		saturn_t2_tick++;
		}
	return ticks;
	}
diff_time.lo = saturn_timer2;
if (saturn_timer2 & 0x80000000) diff_time.hi = 0xffffffff;
else diff_time.hi = 0x0;
adj_time.lo = access_time.lo;
adj_time.hi = access_time.hi;
sub_64(&adj_time,adj_time,diff_time);
if (adj_time.hi & 0x8000000) {
	delta.lo = 0x0;
	delta.hi = 0x0;
	sub_64(&delta,delta,adj_time);
	}
else {
	delta.lo = adj_time.lo;
	delta.hi = adj_time.hi;
	}

if (delta.hi != 0 || (delta.lo > 0x3c000))	{
	add_64(&set_0_time,set_0_time,adj_time);
	add_64(&time_offset,time_offset,adj_time);
	sub_64(&access_time,access_time,adj_time);
	}
if ((saturn_timer2 >= 0 && (access_time.lo & 0x80000000)) || ((unsigned long)saturn_timer2 > access_time.lo)) {
	ticks.t2_ticks = access_time.lo;
	}
else {
	ticks.t2_ticks = saturn_timer2;
	saturn_t2_tick++;
	}
return ticks;
}

/*********************************************************************/
