/* timer.c */

/* Copyright (c) 1988 David J. Arendash, Inc. */

/* This file contains general purpose routines for the handling of the
   timer device.  As the ROM Kernal Manual examples are buggy, I based these
   on those in the Programmer's Guide to the Amiga by Robert A. Peck.
   They use standard IO requests rather than timerrequests.  On the other
   hand, they work.

   You may want to break this file up to library-ize these routines.

   Feel free to do anything you want with this stuff.  I only ask that
	1.  Don't delete these comments
	2.  Follow this indentation and punctuation, as it is the least
	    annoying and easiest to follow, all pinheads aside.
*/

#include "exec/types.h"
#include "exec/nodes.h"
#include "exec/lists.h"
#include "exec/memory.h"
#include "exec/interrupts.h"
#include "exec/ports.h"
#include "exec/libraries.h"
#include "exec/tasks.h"
#include "exec/io.h"
#include "exec/devices.h"
#include "devices/timer.h"
#include <stdio.h>	/* used in debugging */

#define SECS io_Actual
#define MICROS io_Length

APTR TimerBase;

extern struct MsgPort  *CreatePort();
extern struct IOStdReq *CreateStdIO();


/* set up the Timer Device (just once) */
long InitTimerDevice (tr, unit)
struct IOStdReq *tr;
long            unit;
{
   long error;

   error = OpenDevice (TIMERNAME, unit, tr, 0L);
   if (error)
      return (NULL);

   TimerBase = tr->io_Device;
   return (1L);
}

/* Duplicate a timer.  Useful for generating multiple requests */
DupTimer (orginal, duplicate)
struct IOStdReq *orginal, *duplicate;
{
   duplicate->io_Device = orginal->io_Device;
   duplicate->io_Unit = orginal->io_Unit;
}

/* create a new timer request. does not start the timer */
struct IOStdReq *CreateTimer ()
{
   long error;

   struct MsgPort *timerport;
   struct IOStdReq *timermsg;

   timerport = CreatePort (0L, 0L);

   if (timerport == NULL)
      return (NULL);

   timermsg = CreateStdIO (timerport);
   return (timermsg);
}

/* everything needed to do a time delay (like Wait)
   unit is UNIT_MICROHZ or UNIT_VBLANK
   secs is seconds
   micros is microseconds
*/
TimeDelay (unit, secs, micros)
long unit, secs, micros;
{
   long precise;
   struct IOStdReq *tr;

   /* choose best unit (?)  (from the book) */
   if (secs < 0 || unit == UNIT_MICROHZ)
      unit = UNIT_MICROHZ;
   else
      unit = UNIT_VBLANK;

   tr = CreateTimer ();
   if (tr == NULL)
      return (-1);

   if (!InitTimerDevice (tr, unit))
      return (-1);

   tr->SECS = secs;
   tr->MICROS = micros;
   tr->io_Command = TR_ADDREQUEST;
   /* wait for timer to timeout */
   DoIO (tr);

   CloseDevice (tr);
   DeleteTimer (tr);
   return (0);
}

/* Queue (start) a timer using seconds and microseconds
   tr is the thing returned from CreateTimer() */
QTimer_sm (tr,secs,micros)
struct IOStdReq *tr;
{
   tr->SECS = secs;
   tr->MICROS = micros;
   tr->io_Command = TR_ADDREQUEST;
   SendIO (tr);
   /* returns immediately, use CheckTimer to find out when it's done */
}

/* Queue (start) a timer using a timeval structure.
   tr is the thing returned from CreateTimer() */
QTimer_tv (tr,tv)
struct IOStdReq *tr;
struct timeval *tv;
{
   tr->SECS = tv->tv_secs;
   tr->MICROS = tv->tv_micro;
   tr->io_Command = TR_ADDREQUEST;
   SendIO (tr);
   /* returns immediately, use CheckTimer to find out when it's done */
}

/* Check to see if a timer is done. Returns 1 if done, 0 if not done */
CheckTimer (tr)
struct IOStdReq *tr;
{
   long result;

   /* see if timed out; if so, reply to it */
   result = (int) CheckIO (tr);
   if (result)
      GetMsg (tr->io_Message.mn_ReplyPort);
   return (result);
}

/* stop a timer whether it is done or not
   best do this to all timers before CloseDevice() */
AbortTimer (tr)
struct IOStdReq *tr;
{
   AbortIO (tr);
}

/* set new system time in seconds since 12:00 AM, 1/1/78 */
SetNewTime (secs)
LONG secs;
{
   struct IOStdReq *tr;

   tr = CreateTimer (UNIT_MICROHZ);
   if (tr == 0)
      return (-1);

   tr->io_Command = TR_SETSYSTIME;
   tr->SECS = secs;
   tr->MICROS = 0;
   DoIO (tr);

   CloseDevice (tr);
   DeleteTimer (tr);
   return (0);
}

/* fill a timeval structure with system time in seconds and microseconds
   since 12:00 AM, 1/1/78 */ 
GetSysTime (tv)
struct timeval *tv;
{
   struct IOStdReq *tr;

   tr = CreateTimer (UNIT_MICROHZ);
   if (tr == 0)
      return (-1);

   tr->io_Command = TR_GETSYSTIME;
   DoIO (tr);

   CloseDevice (tr);
   DeleteTimer (tr);
   return (0);
}

/* remove a timer from existance */
DeleteTimer (tr)
struct IOStdReq *tr;
{
   struct MsgPort *tp;

   if (tr)
   {
      tp = tr->mn_ReplyPort;
      if (tp)
         DeletePort (tp);
      DeleteStdIO (tr);
   }
}
