
#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/cia.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>
#include <resources/cia.h>
#include <stdio.h>

static struct CIA *ciaa = (struct CIA *) 0x00BFE001L;
static struct CIA *ciab = (struct CIA *) 0x00BFD000L;

long CIA_Seconds = 0;
long CIA_Microseconds = 0;

/* timeslice is 46911 intervals.  Each interval is 1.397 microseconds,
 * this should correspond to a timing interval of 65536 microseconds */
#define CIA_TIME_SLICE ((unsigned short) 46911)

static struct Interrupt
   CIATimerInterrupt,
   *OldCIAInterrupt = (struct Interrupt *)-1;

static struct Library *CIAResource = NULL;

#define ciatlo ciaa->ciatalo
#define ciathi ciaa->ciatahi
#define ciacr  ciaa->ciacra
#define CIAINTBIT CIAICRB_TA
#define CLEAR 0


/* this is the actual interrupt routine.  since +p 32-bit model is used,
 * no special dinking around is necessary to get a C routine to run as
 * an interrupt.
 */
VOID CIAInterrupt()
{
	/* increment saved microseconds by number generated between CIA
	 * interrupts, and if we passed a million, increment seconds */
	CIA_Microseconds += 65536;
	if (CIA_Microseconds > 1000000)
	{
		CIA_Seconds++;
		CIA_Microseconds -= 1000000;
	}
}

/* start the timer, clear pending interrupts, and enable timer A
 * Interrupts */
void StartCIATimer()
{
	ciacr &= ~(CIACRAF_RUNMODE);    /* set it to reload on overflow */
	ciacr |= (CIACRAF_LOAD | CIAICRF_TA);
	SetICR(CIAResource,CLEAR|CIAICRF_TA);
	AbleICR(CIAResource, CIAICRF_SETCLR | CIAICRF_TA);
}

void StopCIATimer()
{
	AbleICR(CIAResource, CLEAR | CIAICRF_TA);
	ciacr &= ~CIACRAF_START;
}

/* set period between timer increments */
void SetCIATimer(micros)
unsigned short micros;
{
	ciatlo = micros & 0xff;
	ciathi = micros >> 8;
}

/* stop the timer and remove its interrupt vector */
void EndCIATimer()
{
	if (OldCIAInterrupt == NULL)
	{
		StopCIATimer();
		RemICRVector(CIAResource, CIAINTBIT, &CIATimerInterrupt);
	}
}

BOOL BeginCIATimer()
{
	extern struct Interrupt *AddICRVector();

	/* Open the CIA resource */
	if ((CIAResource = (struct Library *)OpenResource(CIAANAME)) == NULL)
	   return(FALSE);

	CIATimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
	CIATimerInterrupt.is_Node.ln_Pri = 127;
	CIATimerInterrupt.is_Code = CIAInterrupt;

	/* install interrupt */
	if ((OldCIAInterrupt = AddICRVector(CIAResource,CIAINTBIT,&CIATimerInterrupt)) != NULL)
	   {
	   EndCIATimer();
	   return(FALSE);
	   }

	SetCIATimer(CIA_TIME_SLICE);

	StartCIATimer();
	return(TRUE);
}

/* return the elapsed real time in seconds and microseconds since the
 * cia timer interrupt handler was installed.
 *
 * ElapsedTime(&secs,&microsecs);
 *
 * with the chosen timeslice interval, every timer interrupt represents
 * 65536 microseconds, so the count of interrupts received can be shifted
 * and or'ed in.  The thing that needs scaling is the timer count we
 * read from the hardware registers.  It's range of 0 - 46911 1.397
 * microsecond ticks must be changed to a range of 0 - 65535 1.0
 * microsecond ticks, which is done below
 *
 * note the code should really read the lo count register again after
 * reading the high one and comparing them to be sure it didn't wrap
 * in between reads
 *
 * interrupts are off during this to reduce the possibility of a problem
 * with the counter interrupt coming between the cia reads and the big tick
 * read, and because it's short it's no biggie, but again it should
 * really do more
 */
void ElapsedTime(sec_ptr,usec_ptr)
int *sec_ptr,*usec_ptr;
{
	register long seconds, microseconds;
	register long ciahi, cialo;

	Disable();
	ciahi = ciathi;
	cialo = ciatlo;
	seconds = CIA_Seconds;
	microseconds = CIA_Microseconds;
	Enable();
	/* total microseconds is CIA_BigTicks * 65536 + timerval * 1.397 */
	/* to multiply the timer ticks * 1.397, you can multiply by 1430
	 * and divide by 1024 (or shift right by 10, get it?)
	 */
	ciahi = CIA_TIME_SLICE - ((ciahi << 8) + cialo);
	ciahi = ((ciahi * 1430) >> 10) & 0xffff;

	microseconds += ciahi;
	if (microseconds > 1000000)
	{
		microseconds -= 1000000;
		seconds++;
	}

	*sec_ptr = seconds;
	*usec_ptr = microseconds;
	return;
}

