/*
 * PC/HW routine collection v1.5 for DOS/DJGPP
 *
 *  Copyright (C) 2002 - Daniel Borca
 *  Email : dborca@yahoo.com
 *  Web   : http://www.geocities.com/dborca
 */


#include <pc.h>
#include <string.h>

#include "pc_hw.h"

#define TIMER_IRQ 0

#define MAX_TIMERS 8

#define PIT_FREQ 0x1234DD

#define ADJUST(timer, basefreq) timer.counter = PIT_FREQ * timer.freq / SQR(basefreq)

#define unvolatile(__v, __t) __extension__ ({union { volatile __t __cp; __t __p; } __q; __q.__cp = __v; __q.__p;})

static int timer_installed;

typedef struct {
   volatile unsigned int counter, clock_ticks, freq;
   volatile PFUNC func;
   volatile void *parm;
} TIMER;

static TIMER timer_main, timer_func[MAX_TIMERS];


/* Desc: main timer callback
 *
 * In  : -
 * Out : 0 to bypass BIOS, 1 to chain to BIOS
 *
 * Note: -
 */
static int
timer ()
{
   int i;

   for (i = 0; i < MAX_TIMERS; i++) {
      TIMER *t = &timer_func[i];
      if (t->func) {
         t->clock_ticks += t->counter;
         if (t->clock_ticks >= timer_main.counter) {
            t->clock_ticks -= timer_main.counter;
            t->func(unvolatile(t->parm, void *));
         }
      }
   }

   timer_main.clock_ticks += timer_main.counter;
   if (timer_main.clock_ticks >= 0x10000) {
      timer_main.clock_ticks -= 0x10000;
      return 1;
   } else {
      outportb(0x20, 0x20);
      return 0;
   }
} ENDOFUNC(timer)


/* Desc: uninstall timer engine
 *
 * In  : -
 * Out : -
 *
 * Note: -
 */
void
pc_remove_timer (void)
{
   if (timer_installed) {
      timer_installed = FALSE;
      pc_clexit(pc_remove_timer);

      DISABLE();
      outportb(0x43, 0x34);
      outportb(0x40, 0);
      outportb(0x40, 0);
      ENABLE();

      pc_remove_irq(TIMER_IRQ);
   }
}


/* Desc: remove timerfunc
 *
 * In  : timerfunc id
 * Out : 0 if success
 *
 * Note: tries to relax the main timer whenever possible
 */
int
pc_remove_int (int fid)
{
   int i;
   unsigned int freq = 0;

   /* are we installed? */
   if (!timer_installed) {
      return -1;
   }

   /* sanity check */
   if ((fid < 0) || (fid >= MAX_TIMERS) || (timer_func[fid].func == NULL)) {
      return -1;
   }
   timer_func[fid].func = NULL;

   /* scan for maximum frequency */
   for (i = 0; i < MAX_TIMERS; i++) {
      TIMER *t = &timer_func[i];
      if (t->func) {
         if (freq < t->freq) {
            freq = t->freq;
         }
      }
   }

   /* if there are no callbacks left, cleanup */
   if (!freq) {
      pc_remove_timer();
      return 0;
   }

   /* if we just lowered the maximum frequency, try to relax the timer engine */
   if (freq < timer_main.freq) {
      unsigned int new_counter = PIT_FREQ / freq;

      DISABLE();

      for (i = 0; i < MAX_TIMERS; i++) {
         if (timer_func[i].func) {
            ADJUST(timer_func[i], freq);
         }
      }

      outportb(0x43, 0x34);
      outportb(0x40, (unsigned char)new_counter);
      outportb(0x40, (unsigned char)(new_counter>>8));
      timer_main.clock_ticks = 0;
      timer_main.counter = new_counter;
      timer_main.freq = freq;

      ENABLE();
   }
 
   return 0;
} ENDOFUNC(pc_remove_int)


/* Desc: adjust timerfunc
 *
 * In  : timerfunc id, new frequency (Hz)
 * Out : 0 if success
 *
 * Note: might change the main timer frequency
 */
int
pc_adjust_int (int fid, unsigned int freq)
{
   int i;

   /* are we installed? */
   if (!timer_installed) {
      return -1;
   }

   /* sanity check */
   if ((fid < 0) || (fid >= MAX_TIMERS) || (timer_func[fid].func == NULL)) {
      return -1;
   }
   timer_func[fid].freq = freq;

   /* scan for maximum frequency */
   freq = 0;
   for (i = 0; i < MAX_TIMERS; i++) {
      TIMER *t = &timer_func[i];
      if (t->func) {
         if (freq < t->freq) {
            freq = t->freq;
         }
      }
   }

   /* update main timer / sons to match highest frequency */
   DISABLE();

   /* using '>' is correct still (and avoids updating
    * the HW timer too often), but doesn't relax the timer!
    */
   if (freq != timer_main.freq) {
      unsigned int new_counter = PIT_FREQ / freq;

      for (i = 0; i < MAX_TIMERS; i++) {
         if (timer_func[i].func) {
            ADJUST(timer_func[i], freq);
         }
      }

      outportb(0x43, 0x34);
      outportb(0x40, (unsigned char)new_counter);
      outportb(0x40, (unsigned char)(new_counter>>8));
      timer_main.clock_ticks = 0;
      timer_main.counter = new_counter;
      timer_main.freq = freq;
   } else {
      ADJUST(timer_func[fid], timer_main.freq);
   }

   ENABLE();

   return 0;
} ENDOFUNC(pc_adjust_int)


/* Desc: install timer engine
 *
 * In  : -
 * Out : 0 for success
 *
 * Note: initial frequency is 18.2 Hz
 */
static int
install_timer (void)
{
   if (timer_installed || pc_install_irq(TIMER_IRQ, timer)) {
      return -1;
   } else {
      memset(timer_func, 0, sizeof(timer_func));

      LOCKDATA(timer_func);
      LOCKDATA(timer_main);
      LOCKFUNC(timer);
      LOCKFUNC(pc_adjust_int);
      LOCKFUNC(pc_remove_int);

      timer_main.counter = 0x10000;

      DISABLE();
      outportb(0x43, 0x34);
      outportb(0x40, 0);
      outportb(0x40, 0);
      timer_main.clock_ticks = 0;
      ENABLE();

      pc_atexit(pc_remove_timer);
      timer_installed = TRUE;
      return 0;
   }
}


/* Desc: install timerfunc
 *
 * In  : callback function, opaque pointer to be passed to callee, freq (Hz)
 * Out : timerfunc id (0 .. MAX_TIMERS-1)
 *
 * Note: returns -1 if error
 */
int
pc_install_int (PFUNC func, void *parm, unsigned int freq)
{
   int i;
   TIMER *t = NULL;

   /* ensure the timer engine is set up */
   if (!timer_installed) {
      if (install_timer()) {
         return -1;
      }
   }

   /* find an empty slot */
   for (i = 0; i < MAX_TIMERS; i++) {
       if (!timer_func[i].func) {
          t = &timer_func[i];
          break;
       }
   }
   if (t == NULL) {
      return -1;
   }

   DISABLE();

   t->func = func;
   t->parm = parm;
   t->freq = freq;
   t->clock_ticks = 0;

   /* update main timer / sons to match highest frequency */
   if (freq > timer_main.freq) {
      unsigned int new_counter = PIT_FREQ / freq;

      for (i = 0; i < MAX_TIMERS; i++) {
         if (timer_func[i].func) {
            ADJUST(timer_func[i], freq);
         }
      }

      outportb(0x43, 0x34);
      outportb(0x40, (unsigned char)new_counter);
      outportb(0x40, (unsigned char)(new_counter>>8));
      timer_main.clock_ticks = 0;
      timer_main.counter = new_counter;
      timer_main.freq = freq;
   } else {
      /* t == &timer_func[i] */
      ADJUST(timer_func[i], timer_main.freq);
   }

   i = t - timer_func;

   ENABLE();

   return i;
}