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


#include <pc.h>

#include "pc_hw.h"

#define TIMER_IRQ 0

#define MAX_TIMERS 8

#define PIT_FREQ 0x1234DD

#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];

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)

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);
 }
}

static int install_timer (void)
{
 if (timer_installed||pc_install_irq(TIMER_IRQ, timer)) {
    return -1;
 } else {
    LOCKDATA(timer_func);
    LOCKDATA(timer_main);
    LOCKFUNC(timer);

    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;
 }
}

static TIMER *find_slot (PFUNC func)
{
 int i;

 for (i=0;i<MAX_TIMERS;i++) {
     if (timer_func[i].func==func) {
        return &timer_func[i];
     }
 }
 for (i=0;i<MAX_TIMERS;i++) {
     if (!timer_func[i].func) {
        return &timer_func[i];
     }
 }

 return NULL;
}

int pc_install_int (PFUNC func, void *parm, unsigned int freq)
{
 int i;
 TIMER *t;

 if (!timer_installed) {
    if (install_timer()) {
       return -1;
    }
 }

 if ((t=find_slot(func))!=NULL) {
    unsigned int new_counter = PIT_FREQ / freq;

    DISABLE();

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

    if (new_counter < timer_main.counter) {
       for (i=0;i<MAX_TIMERS;i++) {
           if (timer_func[i].func) {
              timer_func[i].counter = new_counter * timer_func[i].freq / 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->counter = PIT_FREQ * freq / (timer_main.freq * timer_main.freq);
    }

    ENABLE();

    return 0;
 }
 
 return -1;
}
