/*
 * PC/HW routine collection v1.5 for DOS/DJGPP
 *
 *  Copyright (C) 2002 - Borca Daniel
 *  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;
}