Subversion Repositories shark

Rev

Rev 130 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/* Project:     OSLib
 * Description: The OS Construction Kit
 * Date:                1.6.2000
 * Idea by:             Luca Abeni & Gerardo Lamastra
 *
 * OSLib is an SO project aimed at developing a common, easy-to-use
 * low-level infrastructure for developing OS kernels and Embedded
 * Applications; it partially derives from the HARTIK project but it
 * currently is independently developed.
 *
 * OSLib is distributed under GPL License, and some of its code has
 * been derived from the Linux kernel source; also some important
 * ideas come from studying the DJGPP go32 extender.
 *
 * We acknowledge the Linux Community, Free Software Foundation,
 * D.J. Delorie and all the other developers who believe in the
 * freedom of software and ideas.
 *
 * For legalese, check out the included GPL license.
 */


/*      Advanced Timer Managment
 *      Author: Giacomo Guidi <giacomo@gandalf.sssup.it>
 */


#include <ll/i386/stdlib.h>
#include <ll/i386/error.h>
#include <ll/i386/advtimer.h>
#include <ll/sys/ll/ll-data.h>
#include <ll/sys/ll/ll-func.h>
#include <ll/i386/pic.h>
#include <ll/sys/ll/event.h>
#include <ll/sys/ll/time.h>

unsigned char use_tsc = 1; //Enable the TSC counter mode
unsigned char use_cmos = 0; //Enable the RTC correction

//Max single delta_clk_per_msec increment = clk_per_msec / MAX_DIV_INK;
#define MAX_DIV_INK 30000

signed long long init_tsc;
signed long long init_nsec; //Warp around 292 years !!
signed long long clk_per_msec;

signed long last_delta_clk_per_msec;
signed long total_delta_clk_per_msec;

unsigned char save_CMOS_regA;
unsigned char save_CMOS_regB;

void HandlerIRQ8(void *p)
{

   unsigned char set;
   
   static unsigned long init_step = 0;
   
   signed long long actual_tsc;

   signed long max_dcms = clk_per_msec / MAX_DIV_INK;
   
   signed long long dt,dn;
   signed long delta_clk_per_msec;
   
   cli();
   
   CMOS_READ(0x0C,set);
       
   rdtscll(actual_tsc);
   
   //Delta TSC
   dt = actual_tsc - init_tsc;

   init_tsc = actual_tsc;
 
   UNSIGNED_TSC2NSEC(dt,&dn);
 
   //Offset
   init_nsec += dn;
   
   if (init_step < 5) {
           init_step++;
           return;
   }
   
   dn = dn % 1000000000 - 500000000;
   
   //Delta clk/msec
   delta_clk_per_msec = dn * clk_per_msec / (500000000 - dn);
   
   //clk_per_msec adjustment
   if (delta_clk_per_msec < 0) {
               
        if (delta_clk_per_msec > -max_dcms)
                clk_per_msec += delta_clk_per_msec;
        else
                clk_per_msec -= max_dcms;
   } else {
       
        if (delta_clk_per_msec < max_dcms)
                clk_per_msec += delta_clk_per_msec;
        else
                clk_per_msec += max_dcms;
   }
       
   last_delta_clk_per_msec = delta_clk_per_msec;
   total_delta_clk_per_msec += delta_clk_per_msec;
   
   sti();
   
}

#define HZ 100

#ifdef CONFIG_MELAN
#  define CLOCK_TICK_RATE 1189200 /* AMD Elan has different frequency! */
#else
#  define CLOCK_TICK_RATE 1193180 /* Underlying HZ */
#endif

#define LATCH  ((CLOCK_TICK_RATE + HZ/2) / HZ)

#define CALIBRATE_LATCH (5 * LATCH)
#define CALIBRATE_TIME  (5 * 1000020/HZ)

//TSC Calibration (idea from the linux kernel code)
void ll_calibrate_tsc(void)
{

        signed long long start;
        signed long long end;
        signed long long dtsc;
       
        signed long start_8253, end_8253, delta_8253;

        cli();
       
        /* Set the Gate high, disable speaker */
        outp(0x61, (inp(0x61) & ~0x02) | 0x01);

        outp(0x43,0xB0);                        /* binary, mode 0, LSB/MSB, Ch 2 */
        outp(0x42,CALIBRATE_LATCH & 0xff);      /* LSB of count */
        outp(0x42,CALIBRATE_LATCH >> 8);        /* MSB of count */

        rdtscll(start);
        outp(0x43,0x00);
        start_8253 = inp(0x42);
        start_8253 |= inp(0x42) << 8;          

        do {
                               
            outp(0x43,0x00);
            end_8253 = inp(0x42);
            end_8253 |= inp(0x42) << 8;

        } while (end_8253 > 10);
       
        rdtscll(end);
        outp(0x43,0x00);
        end_8253 = inp(0x42);
        end_8253 |= inp(0x42) << 8;

        //Delta TSC
        dtsc = end - start;

        //Delta PIT
        delta_8253 = start_8253 - end_8253 + 1;

        if (delta_8253 > 0xFFFF) {
                message("Error calculating Delta PIT\n");
                ll_abort(10);
        }

        message("Delta TSC               = %10ld\n",(long)dtsc);

        message("Delta PIT               = %10ld\n",(long)delta_8253);

        clk_per_msec = dtsc * CALIBRATE_LATCH * 1000 / delta_8253 / CALIBRATE_TIME;
       
        message("Calibrated Clk_per_msec = %10ld\n",(long)clk_per_msec);

        sti();
       
}

//Low level time read function
void ll_read_timespec(struct timespec *tspec)
{

    signed long long actual_tsc;
    signed long long dt,dn;

    if (clk_per_msec <= 0) {
            NULL_TIMESPEC(tspec);
            return;
    }
   
    rdtscll(actual_tsc);

    dt = actual_tsc - init_tsc;
   
    UNSIGNED_TSC2NSEC(dt,&dn);
     
    tspec->tv_sec = (init_nsec + dn) / 1000000000;
    tspec->tv_nsec = (init_nsec + dn) % 1000000000;
   
}

void ll_init_advtimer()
{

    if (use_tsc) {
 
        ll_calibrate_tsc();
       
        last_delta_clk_per_msec = 0;
        total_delta_clk_per_msec = 0;
       
        rdtscll(init_tsc); // Read start TSC
        init_nsec = 0;

        if (use_cmos) {

            message("CMOS adjustement enabled\n");
       
            cli();         
           
            irq_bind(8, HandlerIRQ8, INT_FORCE);

            CMOS_READ(0x0A,save_CMOS_regA);
            CMOS_READ(0x0B,save_CMOS_regB);
           
            CMOS_WRITE(0x0A,0x2F); // Set 2 Hz Periodic Interrupt
            CMOS_WRITE(0x0B,0x42); // Enable Interrupt

            irq_unmask(8);

            sti();
           
        }

    } else {

        use_cmos = 0;

   }

}

void ll_restore_CMOS()
{
        if (use_cmos) {
                cli();
               
                irq_mask(8);
               
                CMOS_WRITE(0x0A,save_CMOS_regA);
                CMOS_WRITE(0x0B,save_CMOS_regB);
               
                sti();         
        }
}