/*
 * rtl_time.c
 *
 * architecture-dependent clock support
 *
 * Written by Michael Barabanov
 * Copyright VJY Associates 1998-1999
 * Released under the terms of the GPL
 *
 */

#include <rtl_conf.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/smp.h>
#include <asm/io.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/timex.h>
#include <linux/mc146818rtc.h>

#include <rtl.h>
#include <rtl_core.h>
#include <rtl_debug.h>
#include <rtl_sync.h>
#include <rtl_time.h>

/* #define CONFIG_RTL_FAST_8254 */

hrtime_t _gethrtime(struct rtl_clock *c)
{
	return gethrtime();
}


struct rtl_clock _i8254_clock;
#ifdef __SMP__
struct rtl_clock _apic_clock[NR_CPUS];
#endif


extern void (*kd_mksound)(unsigned int hz, unsigned int ticks);

static hrtime_t (*rtl_do_get_time)(void);
static hrtime_t hrtime_resolution;

static long LATCH_NS;
static long MAX_LATCH_ONESHOT;

/* getting global time from 8254 */

#define READ_CNT0(var) \
{ var = inb(0x40); var |= (inb(0x40) << 8); }

#define READ_CNT2(var) \
{ var = inb(0x42); var |= (inb(0x42) << 8); }


#define LATCH_CNT0() \
outb(0xd2, 0x43);

#define LATCH_CNT0_AND_2() \
outb(0xda, 0x43);

#define LATCH_CNT2() \
outb(0xd8, 0x43);

#define WRITE_COUNTER_ZERO16(x)  { \
	outb_p(x&0xff,0x40); outb_p((x>>8)&0xff,0x40);\
      	clock_counter =x; \
} 

#define WRITE_COUNTER_ZERO8(x)  { \
	outb_p(x&0xff,0x40); \
	clock_counter =x; \
}

#ifdef CONFIG_RTL_FAST_8254
#define WRITE_COUNTER_ZERO_ONESHOT(x) WRITE_COUNTER_ZERO8(x)
#else 
#define WRITE_COUNTER_ZERO_ONESHOT(x) WRITE_COUNTER_ZERO16(x)
#endif

static volatile int last_c2;
unsigned long scaler_8254_to_hrtime;  /* =8380965  ns*100 */
unsigned long scaler_hrtime_to_8254;
#define LATCH2 0x8000

#ifdef __SMP__
static spinlock_t lock8254;
/* static spinlock_t rtl_time_lock; */
#define RTL_SPIN_LOCK rtl_time_lock
#endif

hrtime_t gethrtime(void)
{
	return rtl_do_get_time();
}

hrtime_t gethrtimeres(void)
{
	return hrtime_resolution;
}

hrtime_t base_time;
hrtime_t last_8254_time;
long offset_time;
hrtime_t global_8254_gettime (void)
{
	register unsigned int c2;
	int flags;
	long t;

	rtl_spin_lock_irqsave (&lock8254, flags);
	LATCH_CNT2();
	READ_CNT2(c2);
	offset_time += ((c2 < last_c2) ? (last_c2 - c2) / 2 : (last_c2 - c2 + LATCH2) / 2);
	last_c2 = c2;
	if (offset_time >= CLOCK_TICK_RATE) {
		offset_time -= CLOCK_TICK_RATE;
		base_time += HRTICKS_PER_SEC;
	}

#if HRTICKS_PER_SEC != CLOCK_TICK_RATE
	__asm__("shl $10, %%eax\n\t"
		"mul %%ebx\n\t"
	:"=d" (t) : "b" (scaler_8254_to_hrtime), "a" (offset_time));
#else
	t = offset_time;
#endif
	last_8254_time = base_time + t;
	rtl_spin_unlock_irqrestore (&lock8254, flags);
	return last_8254_time;
}


/* getting global time from Pentium TSC */
static unsigned long scaler_pentium_to_hrtime;
long rtl_delay_mult;
int can_change_latch2;

int I8253_channel2_free(void)
{
	return can_change_latch2;
}


hrtime_t pent_gettime(void)
{

	hrtime_t t;

	/* time = counter * scaler_pentium_to_hrtime / 2^32 * 2^5; */
	/* Why 2^5? Because the slowest Pentiums run at 60 MHz */

	__asm__("rdtsc\n\t"
		"mov %%edx, %%ecx\n\t"
		"mul %%ebx\n\t"  	/* multiply the low 32 bits of the counter by the scaler_pentium */
		"mov %%ecx, %%eax\n\t"
		"mov %%edx, %%ecx\n\t"	/* save the high 32 bits of the product */
		"mul %%ebx\n\t" 	/* now the high 32 bits of the counter */
		"add %%ecx, %%eax\n\t"
		"adc $0, %%edx\n\t"
		"shld $5, %%eax, %%edx\n\t"
		"shl $5, %%eax\n\t"
		:"=A" (t) : "b" (scaler_pentium_to_hrtime) : "cx");
	return t;
}

/* TODO  I commented these out here and where used, Don't think we need it
   except on 486 ! Didn't want to debug */
//static unsigned long (*save_do_gettimeoffset)(void);
/* we can't allow Linux to read the clocks; it must use this function */
//static unsigned long do_rt_gettimeoffset(void)
//{
//	return 0; /* TODO: take code from 2.0 version */
//}


static void rtl_kd_nosound(unsigned long ignored)
{
	int flags;
	rtl_no_interrupts(flags);
	outb(inb_p(0x61) & 0xfd, 0x61);
	rtl_restore_interrupts(flags);
}

static void rtl_kd_mksound(unsigned int hz, unsigned int ticks)
{
	static struct timer_list sound_timer = { NULL, NULL, 0, 0,
						 rtl_kd_nosound };

	unsigned int count = 0;

	if (hz > 20 && hz < 32767)
		count = 1193180 / hz;
	
	cli();
	del_timer(&sound_timer);
	if (count) {
		int flags;
		rtl_spin_lock_irqsave (&lock8254, flags);
		outb_p(inb_p(0x61)|3, 0x61);
		if (can_change_latch2) {
			outb_p(0xB6, 0x43);
			outb_p(count & 0xff, 0x42);
			outb((count >> 8) & 0xff, 0x42);
		}
		rtl_spin_unlock_irqrestore (&lock8254, flags);

		if (ticks) {
			sound_timer.expires = jiffies+ticks;
			add_timer(&sound_timer);
		}
	} else
		rtl_kd_nosound(0);
	sti();
	return;
}


static void (*save_kd_mksound)(unsigned int hz, unsigned int ticks);

static void uninit_hrtime (void){
//	do_gettimeoffset = save_do_gettimeoffset;
	rtl_kd_mksound(0, 0);
	kd_mksound = save_kd_mksound;
}

#define wait_value(x) do {; } while ((inb(0x61) & 0x20) != (x))
#define wait_cycle() do { wait_value(0); wait_value(0x20); } while (0)

#define CLATCH (1024 * 32)
#define NLOOPS 50

#ifdef __SMP__
static unsigned long scaler_hrtime_to_apic;
/* this is defined in arch/i386/kernel/smp.c */
#define APIC_DIVISOR 16
static long apic_ticks_per_sec;
#endif

/* scaler_pentium ==  2^32 / (2^5 * (cpu clocks per ns)) */
static void do_calibration(void)
{
#ifdef __SMP__
	long temp = 0;
	long a1 = 0;
	long a2 = 0;
	long save_apic = 0;
	long result_apic;
#endif
	long long t1;
	long long t2;
	long pps;
	int j;
	long result;

	rtl_irqstate_t flags;
	rtl_no_interrupts(flags);

#ifdef __SMP__
	if (smp_found_config) {
		save_apic = apic_read(APIC_TMICT);
		apic_write(APIC_TMICT, 1000000000/APIC_DIVISOR);
	}
#endif

	outb((inb(0x61) & ~0x02) | 0x01, 0x61);

	outb_p(0xb6, 0x43);     /* binary, mode 3, LSB/MSB, ch 2 */
	outb(CLATCH & 0xff, 0x42);	/* LSB of count */
	outb(CLATCH >> 8, 0x42);	/* MSB of count */

	wait_cycle();
	rdtscll(t1);
#ifdef __SMP__
	if (smp_found_config) {
		a1 = apic_read(APIC_TMCCT);
	}
#endif

	for (j = 0; j < NLOOPS; j++) {
		wait_cycle();
	}
	rdtscll(t2);
#ifdef __SMP__
	if (smp_found_config) {
		a2 = apic_read(APIC_TMCCT);
	}
	result_apic = a1 - a2;
#endif
	result = t2 - t1;

#ifdef __SMP__
	if (smp_found_config) {
		temp = apic_read(APIC_TMICT);
		apic_write(APIC_TMICT, save_apic);
	}
#endif

	rtl_restore_interrupts(flags);

	pps = muldiv (result, CLOCK_TICK_RATE, CLATCH * NLOOPS);
	scaler_pentium_to_hrtime = muldiv (1 << 27, HRTICKS_PER_SEC, pps);
#ifdef __SMP__
	if (smp_found_config) {
		temp = muldiv (result_apic, CLOCK_TICK_RATE, CLATCH * NLOOPS);
#if HRTICKS_PER_SEC == NSECS_PER_SEC
		scaler_hrtime_to_apic = muldiv (temp, 1 << 31, HRTICKS_PER_SEC / 2);
#else
		scaler_hrtime_to_apic = muldiv (temp, 1 << (31 - 10), HRTICKS_PER_SEC / 2);
#endif
/* 		printk("sca=%ld, temp=%ld resapic=%ld\n", scaler_hrtime_to_apic, temp, result_apic); */
 		apic_ticks_per_sec = temp;
 /* 		printk("pps apic=%ld %ld\n", temp * APIC_DIVISOR, scaler_hrtime_to_apic); */

	}
#endif
/* 	printk("pps=%ld\n", pps); */
}

static void init_hrtime (void)
{
	int flags;

	kd_mksound(0, 0); /* clear the possibly pending sound timer */
	save_kd_mksound = kd_mksound;
	kd_mksound = rtl_kd_mksound;

#if HRTICKS_PER_SEC != CLOCK_TICK_RATE
	scaler_8254_to_hrtime = muldiv (HRTICKS_PER_SEC, 1 << 22, CLOCK_TICK_RATE);
	scaler_hrtime_to_8254 = muldiv (CLOCK_TICK_RATE, 1 << 31, HRTICKS_PER_SEC / 2);
	LATCH_NS = muldiv (LATCH, HRTICKS_PER_SEC, CLOCK_TICK_RATE);
#else
	LATCH_NS = LATCH;
#endif
	MAX_LATCH_ONESHOT = LATCH_NS * 3 / 4;
#ifdef CONFIG_RTL_FAST_8254
	MAX_LATCH_ONESHOT = muldiv (250, HRTICKS_PER_SEC, CLOCK_TICK_RATE);
#endif

	rtl_no_interrupts(flags);
	if (boot_cpu_data.x86_capability & X86_FEATURE_TSC) {
		can_change_latch2 = 1;

		do_calibration();

		rtl_do_get_time = pent_gettime;
		hrtime_resolution = 32;
		
/* printk("scaler_pentium_to_hrtime = %d\n", (int) scaler_pentium_to_hrtime); */
	} else {
		can_change_latch2 = 0;
		/* program channel 2 of the 8254 chip for periodic counting */
		outb_p(0xb6, 0x43);     /* binary, mode 3, LSB/MSB, ch 2 */
		outb_p(LATCH2 & 0xff, 0x42);
		outb_p((LATCH2 >> 8) & 0xff, 0x42);
		outb_p((inb_p(0x61) & 0xfd) | 1, 0x61); /* shut up the speaker and enable counting */

		LATCH_CNT2();
		READ_CNT2(last_c2);
		offset_time = 0;
		base_time = 0;
		rtl_do_get_time = global_8254_gettime;
		hrtime_resolution = HRTICKS_PER_SEC / CLOCK_TICK_RATE;
	}


	/* TODO What's going on here ?  I don't think we need this VY*/
//	save_do_gettimeoffset = do_gettimeoffset;
//	do_gettimeoffset = do_rt_gettimeoffset;

	/*
	current_time.tv_sec = xtime.tv_sec;
	current_time.tv_nsec = 0;

	rtl_time_offset = 0;
	rtl_time_offset = rtl_timespec_to_rtime(&current_time) - rtl_get_time();

	*/
	rtl_restore_interrupts(flags);
}

void rtl_clock_clear(clockid_t h)
{
	h->uninit(h);
}




static hrtime_t periodic_gethrtime (struct rtl_clock *c) { return c->value; }
static hrtime_t oneshot_gethrtime (struct rtl_clock *c) { return gethrtime(); }

/* the 8254 clock */

static unsigned int clock_counter; /* current latch value */


static inline long RTIME_to_8254_ticks(long t)
{
#if HRTICKS_PER_SEC != CLOCK_TICK_RATE
	int dummy;
	__asm__("mull %2"
		:"=a" (dummy), "=d" (t)
		:"g" (scaler_hrtime_to_8254), "0" (t)
		);
#endif
		
	return (t);
}

void _8254_checklinuxirq (void)
{
/*	if (test_and_set_bit(0, &checklinuxirq[clock])) {
		return;
	}
	*/

	/* TODO must periodically call gethrtime if it's based on i8254 */

	/* or maybe not (for speed reasons)
	 * anyway, here's a way to do it:
	 * set a bit on every gethrtime();
	 * clear it in periodic irq
	 * periodically check: if it's not set, gethrtime()
	 *
	 * alternatively, use the fact that in periodic
	 * mode we can add periods to 8254 ticks count
	 */
	hrtime_t t;
	if (rtl_do_get_time == &pent_gettime) {
		t = _i8254_clock.gethrtime(&_i8254_clock);
	} else {
		t = last_8254_time;
	}
	if  (t > _i8254_clock.arch.linux_time) {
		_i8254_clock.arch.linux_time += LATCH_NS;
		rtl_global_pend_irq (0);
		if  (t > _i8254_clock.arch.linux_time) {
			ifdebug(TIME) {
				rtl_printf("RTL: lost jiffies:");
				rtl_printf("\n");
			}
			_i8254_clock.arch.linux_time = t;
			_i8254_clock.arch.linux_time += MAX_LATCH_ONESHOT;
		}
	}


	if (!_i8254_clock.arch.istimerset) {
		_i8254_clock.settimer (&_i8254_clock, MAX_LATCH_ONESHOT);
	}

/* 	clear_bit (0, &checklinuxirq [clock]); */
}

static unsigned int _8254_irq(unsigned int irq, struct pt_regs *regs)
{
	int flags;
	rtl_spin_lock_irqsave (&lock8254, flags);
	if (_i8254_clock.mode == RTL_CLOCK_MODE_PERIODIC) {
		if (test_and_set_bit(0, &_i8254_clock.arch.count_irqs)) {
			_i8254_clock.value += _i8254_clock.resolution;
		}
	} else {
		_i8254_clock.arch.istimerset = 0;
	}
	rtl_hard_enable_irq(0);
	rtl_spin_unlock_irqrestore (&lock8254, flags);
	_i8254_clock.handler(regs);
	if (rtl_rt_system_is_idle()) {
		_8254_checklinuxirq();
	}
	return 0;
}


static int _8254_setperiodic (clockid_t c, hrtime_t interval)
{
	int flags;
	long t;

	rtl_spin_lock_irqsave (&lock8254, flags);
	t = RTIME_to_8254_ticks (interval);
	if(t < 10) {
		t = LATCH;
		printk("RTL settimer set too low!\n");
	}
	WRITE_COUNTER_ZERO16 (t);

	_i8254_clock.value = gethrtime();
	_i8254_clock.resolution = muldiv (t, HRTICKS_PER_SEC, CLOCK_TICK_RATE);
	_i8254_clock.arch.istimerset = 1;
	rtl_spin_unlock_irqrestore(&lock8254, flags);
	return 0;
}
	
static int _8254_setoneshot (clockid_t c, hrtime_t interval)
{
	rtl_irqstate_t flags;
	long t;
	rtl_spin_lock_irqsave (&lock8254, flags);
	if (interval > MAX_LATCH_ONESHOT) {
		interval = MAX_LATCH_ONESHOT;
	}

	t = RTIME_to_8254_ticks (interval);
	if (t < 1) {
		t = 1;
	}
	WRITE_COUNTER_ZERO_ONESHOT(t);
	_i8254_clock.arch.istimerset = 1;

	rtl_spin_unlock_irqrestore(&lock8254, flags);
	return 0;
}



int _8254_settimermode (struct rtl_clock *c, int mode)
{
	if (mode == _i8254_clock.mode) {
		return 0;
	}
	if (mode == RTL_CLOCK_MODE_PERIODIC) {
		outb_p(0x30, 0x43);/* 8254, channel 0, mode 0, lsb+msb */
		outb_p(0x34, 0x43); /* binary, mode 2, LSB/MSB, ch 0 */
		_i8254_clock.mode = mode;
		_i8254_clock.gethrtime = periodic_gethrtime;
		_i8254_clock.settimer = _8254_setperiodic;
		_i8254_clock.arch.count_irqs = 0;
	} else if (mode == RTL_CLOCK_MODE_ONESHOT) {
#ifdef CONFIG_RTL_FAST_8254
		outb_p(0x10, 0x43);    /* 8254, channel 0, mode 0, lsb */
#else
		outb_p(0x30, 0x43);    /* 8254, channel 0, mode 0, lsb+msb */
#endif
		_i8254_clock.mode = mode;
		_i8254_clock.gethrtime = oneshot_gethrtime;
		_i8254_clock.settimer = _8254_setoneshot;
		_i8254_clock.resolution = HRTICKS_PER_SEC / CLOCK_TICK_RATE;
	} else {
		return -EINVAL;
	}
	return 0;
}


static int _8254_init (clockid_t clock)
{
	int flags;
	rtl_no_interrupts (flags);

	_i8254_clock.arch.linux_time = gethrtime() + LATCH_NS;
	rtl_request_global_irq(0, _8254_irq);
	_8254_settimermode (clock, RTL_CLOCK_MODE_ONESHOT);
	_i8254_clock.settimer (clock, HRTIME_INFINITY);
	rtl_restore_interrupts (flags);
	return 0;
}

static void _8254_uninit (clockid_t clock)
{
	int flags;
	if (clock -> mode == RTL_CLOCK_MODE_UNINITIALIZED) {
		return;
	}
	clock->handler = RTL_CLOCK_DEFAULTS.handler;
	rtl_spin_lock_irqsave (&lock8254, flags);
	outb_p(0x34,0x43);		/* binary, mode 2, LSB/MSB, ch 0 */
	WRITE_COUNTER_ZERO16(LATCH);
	rtl_free_global_irq(0);
	clock -> mode = RTL_CLOCK_MODE_UNINITIALIZED;
	rtl_spin_unlock_irqrestore (&lock8254, flags);
}


/* sort of a constructor */
int rtl_create_clock_8254(void)
{
	_i8254_clock = RTL_CLOCK_DEFAULTS;
	_i8254_clock.init = _8254_init;
	_i8254_clock.uninit = _8254_uninit;
	_i8254_clock.settimermode = _8254_settimermode;
	return 0;
}


#ifdef __SMP__
/* APIC clocks */

#define LOCAL_TIMER_VECTOR	0x41


static long RTIME_to_apic_ticks(long t)
{
	int dummy;
#if HRTICKS_PER_SEC != NSECS_PER_SEC
	t <<= 10;
#endif
	__asm__("mull %2"
		:"=a" (dummy), "=d" (t)
		:"g" (scaler_hrtime_to_apic), "0" (t)
		);
		
	return (t);
}


static inline int rtl_apic_write_initial_count (long count)
{
	unsigned int tmp_value;
	tmp_value = apic_read(APIC_TMICT);
	apic_write (APIC_TMICT, count);
	return 0;
}

static int apic_setoneshot (clockid_t apic, hrtime_t interval)
{
	long t;
	if (apic != &_apic_clock[rtl_getcpuid()]) {
		rtl_printf("apic_setoneshot crosses CPUs!\n");
		return -1;
	}
	if (interval > MAX_LATCH_ONESHOT) {
		interval = MAX_LATCH_ONESHOT;
	}

	t = RTIME_to_apic_ticks (interval);
	if (t < 1) {
		t = 1;
	}
	rtl_apic_write_initial_count (t);
	apic->arch.istimerset = 1;
	return 0;
}

static int apic_setperiodic (clockid_t apic, hrtime_t interval)
{
	long t;
	t = RTIME_to_apic_ticks (interval);
	rtl_apic_write_initial_count (t);
	apic->value = gethrtime();
	apic->resolution = muldiv (t, NSECS_PER_SEC, apic_ticks_per_sec);
	apic->arch.istimerset = 1;
	return 0;
}

int apic_settimermode (struct rtl_clock *apic, int mode)
{
	unsigned long lvtt1_value;
	unsigned int tmp_value;

	if (apic != &_apic_clock[rtl_getcpuid()]) {
		rtl_printf("apic_settimermode crosses CPUs!\n");
		return -EINVAL;
	}

	if (mode == apic->mode) {
		return 0;
	}

	if (mode == RTL_CLOCK_MODE_PERIODIC) {
		apic -> mode = mode;
		apic -> gethrtime = periodic_gethrtime;
		apic -> settimer = apic_setperiodic;
		tmp_value = apic_read(APIC_LVTT);
		lvtt1_value = APIC_LVT_TIMER_PERIODIC | LOCAL_TIMER_VECTOR;
		apic_write(APIC_LVTT , lvtt1_value);
	} else if (mode == RTL_CLOCK_MODE_ONESHOT) {
		apic -> mode = mode;
		apic -> gethrtime = oneshot_gethrtime;
		apic -> settimer = apic_setoneshot;
		apic -> resolution = hrtime_resolution;

		tmp_value = apic_read(APIC_LVTT);
		lvtt1_value = LOCAL_TIMER_VECTOR;
		apic_write(APIC_LVTT , lvtt1_value);

	} else {
		return -EINVAL;
	}
	return 0;
}


static void apic_checklinuxirq (void)
{
	DECLARE_CPUID(cpu_id);
	clockid_t apic = &_apic_clock[cpu_id];
	hrtime_t t = apic->gethrtime(apic);
	if  (t > apic->arch.linux_time) {
		rtl_local_pend_vec (LOCAL_TIMER_VECTOR,cpu_id);
		apic->arch.linux_time += LATCH_NS;
		if  (t > apic->arch.linux_time) {
			ifdebug(TIME) {
				rtl_printf("RTL: lost SMP jiffies:");
				rtl_printf("\n");
			}
			apic->arch.linux_time = t;
			apic->arch.linux_time += MAX_LATCH_ONESHOT;
		}
	}
	if (!apic -> arch.istimerset) {
		apic->settimer (apic, MAX_LATCH_ONESHOT);
	}
}

static unsigned int apic_timer_irq(struct pt_regs *r)
{
	DECLARE_CPUID(cpu_id);
	clockid_t apic = &_apic_clock[cpu_id];
	if (apic->mode == RTL_CLOCK_MODE_PERIODIC) {
		apic->value += apic->resolution;
	} else {
		apic->arch.istimerset = 0;
	}
	apic -> handler (r);
	if (rtl_rt_system_is_idle()) {
		apic_checklinuxirq();
	}
	return 0;
}


int volatile apic_init_flag;

static unsigned int apic_init_irq(struct pt_regs *r)
{
	DECLARE_CPUID(cpu_id);
	CLOCK_APIC->settimermode (CLOCK_APIC, RTL_CLOCK_MODE_ONESHOT);
	CLOCK_APIC->settimer (CLOCK_APIC, HRTIME_INFINITY);
	rtl_free_local_irq (LOCAL_TIMER_VECTOR, cpu_id);
	clear_bit (0, &apic_init_flag);
	return 0;
}

/* apics are initialized to the oneshot mode by default */
static int apic_clock_init (clockid_t clk)
{
	int flags;
	DECLARE_CPUID(cpu_id);

	rtl_no_interrupts (flags);
	clk->arch.linux_time = gethrtime() + LATCH_NS;

	if (clk->arch.apic_cpu == cpu_id) {
		clk->settimermode (clk, RTL_CLOCK_MODE_ONESHOT);
		clk->settimer (clk, HRTIME_INFINITY);
	} else {
		set_bit (0, &apic_init_flag);
		rtl_request_local_irq (LOCAL_TIMER_VECTOR, apic_init_irq, clk->arch.apic_cpu);
		while (test_bit(0, &apic_init_flag));
	}
	rtl_request_local_irq (LOCAL_TIMER_VECTOR, apic_timer_irq, clk->arch.apic_cpu);
	rtl_restore_interrupts (flags);
	return 0;
}


static unsigned int apic_uninit_irq(struct pt_regs *r)
{
	DECLARE_CPUID(cpu_id);
	CLOCK_APIC->settimermode (CLOCK_APIC, RTL_CLOCK_MODE_PERIODIC);
	CLOCK_APIC->settimer (CLOCK_APIC, LATCH_NS);
/* 	rtl_free_local_irq (LOCAL_TIMER_VECTOR, cpu_id); */
	free_ipi (cpu_id);
	clear_bit (0, &apic_init_flag);
	return 0;
}

static void apic_clock_uninit (clockid_t clock)
{
	int flags;
	DECLARE_CPUID(cpu_id);

	if (clock -> mode == RTL_CLOCK_MODE_UNINITIALIZED) {
		return;
	}

	if (clock->arch.apic_cpu == cpu_id) {
		rtl_no_interrupts (flags);
		clock->handler = RTL_CLOCK_DEFAULTS.handler;
		clock->settimermode (clock, RTL_CLOCK_MODE_PERIODIC);
		clock->settimer (clock, LATCH_NS);
		clock -> mode = RTL_CLOCK_MODE_UNINITIALIZED;

		rtl_free_local_irq (LOCAL_TIMER_VECTOR, cpu_id);
		rtl_restore_interrupts (flags);
	} else {
#if 1
		rtl_no_interrupts (flags);
		clock->handler = RTL_CLOCK_DEFAULTS.handler;

		set_bit (0, &apic_init_flag);
		rtl_free_local_irq (LOCAL_TIMER_VECTOR, clock->arch.apic_cpu);
		request_ipi (apic_uninit_irq, clock->arch.apic_cpu);
		rtl_reschedule(clock->arch.apic_cpu);
/* 		rtl_request_local_irq (LOCAL_TIMER_VECTOR, apic_uninit_irq, clock - CLOCK_APIC); */
		rtl_restore_interrupts (flags);
		while (test_bit(0, &apic_init_flag)); /* wait for apic to uninit */
		clock -> mode = RTL_CLOCK_MODE_UNINITIALIZED;
#endif
	}

}

int rtl_create_clock_apic(int cpu)
{
	_apic_clock[cpu] = RTL_CLOCK_DEFAULTS;
	_apic_clock[cpu].init = apic_clock_init;
	_apic_clock[cpu].uninit = apic_clock_uninit;
	_apic_clock[cpu].settimermode = apic_settimermode;
	_apic_clock[cpu].arch.apic_cpu = cpu;
	return 0;
}

#endif



/* returns a pointer to the clock structure of the best controlling hw clock 
 * for this CPU */
clockid_t rtl_getbestclock (unsigned int cpu)
{
#ifdef __SMP__
	if (smp_found_config) {
		return &_apic_clock[cpu];
	} else {
		return &_i8254_clock;
	}
#else
	return &_i8254_clock;
#endif
}


spinlock_t rtl_tqueue_lock;
int init_module (void)
{
	spin_lock_init (&lock8254);
	spin_lock_init (&rtl_tqueue_lock);
	rtl_debug = 0;

	rtl_delay_mult = muldiv (loops_per_sec, 1 << 31, NSECS_PER_SEC / 2);

	init_hrtime();
	rtl_create_clock_8254();
#ifdef __SMP__
	{
	int i;	
		for (i = 0; i < rtl_num_cpus(); i++) {
			int cpu = cpu_logical_map (i);
			rtl_create_clock_apic(cpu);
		}
	}
#endif
	rtl_init_standard_clocks();
/* 	rtl_setdebug(RTLDBG_TIME); */
	return 0;
}

void cleanup_module(void)
{
	rtl_cleanup_standard_clocks();
#ifdef __SMP__
	if (smp_found_config) {
		int i;
		for (i = 0; i < rtl_num_cpus(); i++) {
			int cpu = cpu_logical_map(i);
			apic_clock_uninit (&_apic_clock[cpu]);
		}
	}
#endif
	_8254_uninit(&_i8254_clock);
	uninit_hrtime();
}


/* the following should really belong to rtl_core */

/* compatibility irq handler table */
#include <asm/rt_irq.h>
RTL_V1_HANDLER rtl_v1_irq[NR_IRQS];

extern unsigned int rtl_compat_irq_handler(unsigned int irq, struct pt_regs *regs)
{
	rtl_v1_irq[irq]();
	rtl_hard_enable_irq(irq);
	return 0;
}


/* debugging */
unsigned int rtl_debug;

#ifdef CONFIG_RTL_DEBUG
void rtl_setdebug (unsigned int state)
{
	rtl_debug = state;
}

unsigned int rtl_getdebug (void)
{
	return rtl_debug;
}
#endif

/* SMP rescheduling */
#ifdef __SMP__
#define RTL_RESCHEDULE_VECTOR 0x51
int request_ipi (unsigned int (*f)(struct pt_regs *r), int cpu)
{
	return rtl_request_local_irq (RTL_RESCHEDULE_VECTOR, f, cpu);
}

int free_ipi (int cpu)
{
	return rtl_free_local_irq (RTL_RESCHEDULE_VECTOR, cpu);
}
#endif
