/*
  0702941432 <- last update (yymmddhh'')


    !!!!! NEVER COMPILE WITH ACTIVE STACK OVERFLOW CHECK !!!!!

     _____________________________________________________________
    /                                                             \ 
    | Module:       expirq.cpp - CRITICAL INTERRUPT SERVICES
    \_____________________________________________________________/

    Description:    this stuff is DANGEROUS! On some systems it
                    may require special treatment on compilation.

                    e.g. BORLAND C++: do NOT compile with STACK
                                      OVERFLOW TEST active or the
                                      whole program will be
                                      corrupted at run-time!!! 


                         -> i.e. do NOT include expirq.cpp into
                            your project or it'll be compiled
                            with global settings (usually including
                            stack depth checking).
                            Just link the .obj to your project
                            and always compile this separate!

    Refers to:      expirq.h
    Used by:        everywhere

    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 940914 0.80    JTH created from file 'misc.cpp'
*/

#   include "expdef.h"

#ifdef  WIN16
#   include <dos.h>
#endif

#   include "expirq.h"

//|| FineTimCode
void FineTimer::delay(long msec)
{
    // 'cause implementation of FineTimer supports just upto very few msec
    // per period, we use this rather clumsy loop for larger periods:

    for(long i=0; i<msec; i++)
    {
        beginperiod(1);
        waitforperiodend();
        endperiod();
    }
}

#ifdef  AMIGA

void FineTimer::beginperiod(long msec)
{
    if(msec > 22)
    {
        SysErr(2901941804); msec=22;
    }

 // What now follows is 100.00% hardware dependent and
 // runs only on Commodore AMIGA systems (and even there isn't
 // very conform with system guidelines)
 // But what else may we do to measure milliseconds?

 volatile uchar *xBFD400=(uchar*)0xBFD400;
 volatile uchar *xBFD500=(uchar*)0xBFD500;
 volatile uchar *xBFDE00=(uchar*)0xBFDE00;

 // set the 16 Bit timer division value:
 uword TIMVAL = (uword)714 * (uword)msec;   // MAX. VALUE FOR msec: 22
 // ^714 kHz divided by...
 //  714 = 1000 (=0,0010 sec.)
 // 1428 =  500 (=0,0020 sec.)
 // 1785 =  400 (=0,0025 sec.)
 const uchar TIMHISTART = (TIMVAL>>8);
 const uchar TIMLOSTART = (TIMVAL-(TIMHISTART<<8));

    *xBFDE00 = (uchar)0;    // stop CIA B, Timer A
    *xBFD400 = TIMLOSTART;  // load countdown value
    *xBFD500 = TIMHISTART;  // into latch (FIRST Lo, THEN Hi!)

    // program & start timer:
    *xBFDE00 = (uchar)0x1F;
    // ^source=systemclock/10,
    //  runmode=OneShot, 3.PBon=TRUE
}

void FineTimer::waitforperiodend()
{
 volatile uchar *xBFD100=(uchar*)0xBFD100;

    // wait for timer underflow:
    while(*xBFD100 & (uchar)(1<<6));    // test CIA-B, parallel port B
}

intf FineTimer::remain()
{
 volatile uchar *xBFD100=(uchar*)0xBFD100;
 volatile uchar *xBFD400=(uchar*)0xBFD400;
 volatile uchar *xBFD500=(uchar*)0xBFD500;
 uchar lo,hi1,hi2;
 uword bustick;

    // if timer has already run low, reading the counter
    // would return the max value. So check this:

    ifn(*xBFD100 & (uchar)(1<<6))   // test CIA-B, parallel port B
        return convfi(0);   // ran low: return 0

    // read remaining timer standing:

    do
    {
        hi1 = *xBFD500;
        lo  = *xBFD400;
        hi2 = *xBFD500;
    }
    while(hi2 != hi1);  // as long as hi underflowed while reading lo

    bustick = (((uword)hi1 << 8) | lo);

    return convfi((int)(bustick)) / 714;    // return as intfloat in msec's
}

#endif  // AMIGA timer class code

#ifdef  WIN16

/*
static bool irq_installed = 0;  // interrupt server installed

static void interrupt (*irq_old)(...);  // old original timer IRQ handler
    // the following is needed to determine at which
    // 'up' counter tick a DOS interrupt must be called, e.g.
    // to keep system's real time clock running

static uword irq_cntmask;
static volatile ulong irq_ucnt = 0UL;   // 'static' means here:
static volatile ulong irq_dcnt = 0UL;   // symbol is local to this file

static void far irsub()
{
    irq_ucnt++;

    if(irq_dcnt)    irq_dcnt--;

    if(!(irq_ucnt & irq_cntmask))
    {
        // call standard DOS IRQ server
        // every standard DOS interrupt tick

        // (*irq_old)();
    }
}

static void interrupt IRQ_C_server(...)
{
    outportb(0x43, 0x36);   // reconfigure timer:
    outportb(0x40, 0x00);   // divide by 1024 so
    outportb(0x40, 0x04);   // every 1 msec 1 interrupt

    outportb(0x20,0x20);    // EOI
}
*/

static uword inst_cnt=0;    // instance counter

FineTimer::FineTimer()
{
 volatile ulong x;

    if(!inst_cnt++)
    {
        // initially setup value from which hardware timer
        // starts countdown loop:

        outportb(0x43, 0x34);   for(x=0;x<10;x++);

        // divide 1.19 MHz system clock by 32768,
        // i.e. timer will run down 27.536 milliseconds
        // in every period

        outportb(0x40, 0x00);   for(x=0;x<10;x++);  // low byte
        outportb(0x40, 0x00);   for(x=0;x<10;x++);  // high byte
    }
}

FineTimer::~FineTimer()
{
    --inst_cnt;

    outportb(0x43, 0x34);

    outportb(0x40, 0);
    outportb(0x40, 0);

    // if(irq_installed)    IRQ_restore();
}

/*
rcode FineTimer::IRQ_set()
{
    if(irq_installed)
    {
        perr("too many timers at 809941346");
        perr("... just one instance currently supported");

        return  FAILED;
    }

    irq_old = getvect(0x8); // 0x08 == timer vector

    setvect(0x8, IRQ_C_server);

    atexit(FineTimer::IRQ_restore);

    irq_installed = 1;

    return  OK;
}

void FineTimer::IRQ_restore()
{
    // re-set system timer values

    disable();

    outportb(0x43, 0x36);

    outportb(0x40, 0);
    outportb(0x40, 0);

    enable();

    // re-install system IRQ handler

    if(irq_installed)

        setvect(0x8, irq_old);

    irq_installed = 0;
}
*/

void FineTimer::beginperiod(long msec)
{
 uchar uc;

    if(msec > 20)
    {
        perr("timer period too large, using 20 msec");
        msec = 20;
    }
/*
    if(irq_installed)
    {
        perr("too many timers at 809941346");
        perr("... just one instance currently supported");

        return;
    }
*/
    outportb(0x43, 0x00);
    uc  = inportb(0x40);    // low byte
    uc  = inportb(0x40);    // high byte

    tstart  = uc;   // remember initial timer state

    tlen    = (uword)msec;

    // calc hardware timer dountdown target:
    // that's 27.54 - msec

    //ptarget   = (convfi(2754) / 100) - convfi(msec);

    // setup & start timer

    // disable

    // outportb(0x43, 0x34);    for(x=0;x<10;x++);

    // divide 1.19 MHz system clock by 32768,
    // i.e. timer will run down 27.536 milliseconds
    // before first interrupt is performed

    // outportb(0x40, 0x00);    for(x=0;x<10;x++);  // low byte
    // outportb(0x40, 0x7f);    for(x=0;x<10;x++);  // high byte

    // crt counter mask to decide whenever to call
    // original DOS handler

    // irq_cntmask  = (1 << shift) - 1;

    // finally, set up the counters

    // irq_ucnt = 0;
    // irq_dcnt = msec;

    // enable();

    // install timer interrupt for this class

    // IRQ_set();

    // now, timer counts down 27 msec.
}

void FineTimer::waitforperiodend()
{
    // wait 'til countdown reaches zero

    while(remain());
}

intf FineTimer::remain()
{
 uchar  uc;
 uword  diff;

    outportb(0x43, 0x00);
    uc  = inportb(0x40);
    uc  = inportb(0x40);    // high byte

    //  calc difference to tstart:

    // dmsg("uc %u tstart %u",(sysint)uc, (sysint)tstart);

    if(uc <= tstart)

        diff    = tstart - uc;

    else

        diff    = tstart + (255 - uc);

    //  'diff' represents now milliseconds * 4

    diff >>= 2; // make it msec

    // dmsg("-> diff = %u", diff);

    //  required distance reached?

    if(diff >= tlen)

        return  convfi(0);  // over it: return 0

    else

        return  convfi(tlen - diff);    // return rest  
}

/*
intf FineTimer::remain()
{
 uchar low,high;
 uword trem;    // ticks remaining
 intf  msrem;   // milliseconds remaining

    // disable();

    outportb(0x43, 0x00);
    low     = inportb(0x40);
    high    = inportb(0x40);

    trem    = ((uword)high << 8)|low;

    if(trem > 0x7f00)   return convfi(0);   // timer overflow'ed: elapsed

    // enable();

    //  convert ticks to intf and divide by 1024
    //  thus making it remaining milliseconds:

    msrem = (((long)trem) << INTF_SCALEB) >> 10;

    dmsg("remain: %u ticks %lu msec %lu target",
        trem, convif(msrem), convif(ptarget));  // ***

    if(msrem <= ptarget)

        return  convfi(0);  // period target reached: nothing remains

    return  msrem - ptarget;
}
*/

#endif  // WIN16 timer class code

void FineTimer::endperiod()
{
#ifdef  WIN16
//  if(irq_installed)
//      IRQ_restore();
#endif  
}
