0,0 → 1,267 |
/* 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 = 0; //Enable the TSC counter mode |
unsigned char use_cmos = 0; //Enable the RTC correction |
|
//Max single delta_clk_per_msec increment |
#define MAX_DELTA_INK 10 |
|
//Max delta_clk_per_msec before declare lost |
//the CMOS sync |
#define MAX_DELTA_TOT 100 |
|
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 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_DELTA_INK) |
clk_per_msec += delta_clk_per_msec; |
else |
clk_per_msec -= MAX_DELTA_INK; |
} else { |
|
if (delta_clk_per_msec < MAX_DELTA_INK) |
clk_per_msec += delta_clk_per_msec; |
else |
clk_per_msec += MAX_DELTA_INK; |
} |
|
if (delta_clk_per_msec > MAX_DELTA_TOT || delta_clk_per_msec < -MAX_DELTA_TOT) { |
message("Error: Delta_clk_per_msec |%ld| > %ld\n",\ |
(long)delta_clk_per_msec,(long)MAX_DELTA_TOT); |
ll_abort(10); |
} |
|
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 read_timespec(struct timespec *tspec) |
{ |
|
signed long long actual_tsc; |
signed long long dt,dn; |
|
rdtscll(actual_tsc); |
|
tspec->tsc = 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 adjustment 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 restore_CMOS() |
{ |
if (use_cmos) { |
cli(); |
|
irq_mask(8); |
|
CMOS_WRITE(0x0A,save_CMOS_regA); |
CMOS_WRITE(0x0B,save_CMOS_regB); |
|
sti(); |
} |
} |