
/* TIMER - Amiga CIA Timer Control Software

  originally by Paul Higginbottom, Public Domain

  hacked on by karl to produce a monotonically increasing microsecond
  clock, 12/30/88, Public Domain

  second version, released 4/27/89, incorporates changes by Bob (Kodiak)
  Burns, Brian P. Dickson and C. Harald Koch.  Specifically, Bob pointed
  out that the hardware manual was wrong and CIA B Timer B was the free
  timer, Brian P. Dickson provided greater accuracy by using 1.396825 for
  the time constant and determining the time constants based on that
  number.  Finally, C. Harald Koch found a bug in which the wrong flag
  was used to initialize a control bit, this made it work for CIA A but
  not for CIA B.

  cc +p ciatimer.c
  ln ciatimer.o -lcl32

  To start the timer, execute BeginCIATimer()

  cc +p ciatimer.c
  ln ciatimer.o -lcl32

	By providing a solid, high-accuracy realtime clock, this code
	provides a way for timer-releated code that needs to run at
	specific realtimes, like a SMUS player, MIDI sequencer, etc,
	to compensate for delays in their execution caused by interrupts,
	cycle stealing by the blitter, etc.

	What you do is keep track of when in realtime you next want to 
	run (by adding time intervals to a time returned by ElapsedTime
	when you start, then when you're ready to set up your timer.device
	MICROHZ delay timer, call ElapsedTime and calculate the difference 
	in seconds and microseconds as your arguments for your timer.device
	request.

	The routine ElapsedTime gets the time by getting the number of
	55873 microsecond ticks that the handler has seen and retrieving
	the 0-40000 number of 1.396825 microsecond tisks from the CIA timer
	registers, scaling them to 1.000 microsecond ticks and returning
	the shifted-and-or'ed result.

	Note that what we really want is an improved timer.device where a
	flag in the timer request could say  "schedule me at this microsecond-
	resolution time of day seconds and microseconds" instead of only
	"schedule me in this many seconds and microseconds."

	When the CIA interrupt handler is installed, other tasks need a
	way to get the count maintained by the timer routine, too.

	This release of the code supports multiple readers.

	There is a sample main routine at the end of this program, run the
	program in a window to start this guy as a timer.  Control-C within
	the window to get it to exit.

	Run the ciafinder program from the CLI in another window and it will
	locate the interrupt data for the timer interrupt maintained by the
	ciatimer task and give you the elapsed time for your time calculation
	in this way.
*/

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

#include "ciatimer.h"

struct CIA_Time CIA_CurrentTime = {0, 0};

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

static struct Library *CIAResource = NULL;

#define ciatlo ciab.ciatblo
#define ciathi ciab.ciatbhi
#define ciacr ciab.ciacrb
#define CIAINTBIT CIAICRB_TB
#define CLEAR 0

void CIAInterrupt()
{
	CIA_CurrentTime.CIA_Microseconds += CIA_TIME_QUANTITY;
	if (CIA_CurrentTime.CIA_Microseconds > 1000000)
	{
		CIA_CurrentTime.CIA_Seconds++;
		CIA_CurrentTime.CIA_Microseconds -= 1000000;
	}
}

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

void StopCIATimer()
{
	AbleICR(CIAResource, CLEAR | CIAICRF_TB);
	ciacr &= ~CIACRBF_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 */
EndCIATimer()
{
	if (OldCIAInterrupt == NULL)
	{
		StopCIATimer();
		RemICRVector(CIAResource, CIAINTBIT, &CIATimerInterrupt);
	}
}

BOOL BeginCIATimer()
{
	/* Open the CIA resource */
	if ((CIAResource = (struct Library *)OpenResource(CIABNAME)) == NULL)
	{
		fprintf(stderr,"cia periodic timer startup couldn't open cia resource\n");
		return(0);
	}

	CIATimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
	CIATimerInterrupt.is_Node.ln_Pri = 127;
	CIATimerInterrupt.is_Node.ln_Name =  CIATIMER_INTERRUPT_NAME;
	CIATimerInterrupt.is_Code = CIAInterrupt;
	CIATimerInterrupt.is_Data = (APTR)&CIA_CurrentTime;

	/* install interrupt */
	if ((OldCIAInterrupt = AddICRVector(CIAResource,CIAINTBIT,&CIATimerInterrupt)) != NULL)
	{
		fprintf(stderr,"cia timer interrupt already in use by '%s'",OldCIAInterrupt->is_Node.ln_Name);
		EndCIATimer();
		return(0);
	}

	SetCIATimer(CIA_TIME_SLICE);

	StartCIATimer();
	return(TRUE);
}

main()
{
	if (BeginCIATimer())
	{
		Wait(SIGBREAKF_CTRL_C);
	}
	else 
		exit(1);

	EndCIATimer();
	exit(0);
}
