patch-2.1.44 linux/arch/mips/sgi/kernel/indy_timer.c

Next file: linux/arch/mips/sgi/kernel/reset.c
Previous file: linux/arch/mips/sgi/kernel/indy_mc.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.43/linux/arch/mips/sgi/kernel/indy_timer.c linux/arch/mips/sgi/kernel/indy_timer.c
@@ -0,0 +1,299 @@
+/*
+ * indy_timer.c: Setting up the clock on the INDY 8254 controller.
+ *
+ * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
+ *
+ * $Id: indy_timer.c,v 1.2 1997/06/30 15:53:04 ralf Exp $
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/kernel_stat.h>
+
+#include <asm/bootinfo.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/ptrace.h>
+#include <asm/system.h>
+#include <asm/sgi.h>
+#include <asm/sgialib.h>
+#include <asm/sgihpc.h>
+#include <asm/sgint23.h>
+
+/* The layout of registers for the INDY Dallas 1286 clock chipset. */
+struct indy_clock {
+        volatile unsigned int hsec;
+        volatile unsigned int sec;
+        volatile unsigned int min;
+        volatile unsigned int malarm;
+        volatile unsigned int hr;
+        volatile unsigned int halarm;
+        volatile unsigned int day;
+        volatile unsigned int dalarm;
+        volatile unsigned int date;
+        volatile unsigned int month;
+        volatile unsigned int year;
+        volatile unsigned int cmd;
+        volatile unsigned int whsec;
+        volatile unsigned int wsec;
+        volatile unsigned int _unused0[50];
+};
+
+#define INDY_CLOCK_REGS ((struct indy_clock *)(KSEG1ADDR(0x1fbe0000)))
+
+/* Because of a bug in the i8254 timer we need to use the onchip r4k
+ * counter as our system wide timer interrupt running at 100HZ.
+ */
+static unsigned long r4k_offset; /* Amount to increment compare reg each time */
+static unsigned long r4k_cur;    /* What counter should be at next timer irq */
+
+static inline void ack_r4ktimer(unsigned long newval)
+{
+	write_32bit_cp0_register(CP0_COMPARE, newval);
+}
+
+static int set_rtc_mmss(unsigned long nowtime)
+{
+	struct indy_clock *clock = INDY_CLOCK_REGS;
+	int retval = 0;
+	int real_seconds, real_minutes, clock_minutes;
+
+#define FROB_FROM_CLOCK(x) (((x) & 0xf) | ((((x) & 0xf0) >> 4) * 10));
+#define FROB_TO_CLOCK(x)  ((((((x) & 0xff) / 10)<<4) | (((x) & 0xff) % 10)) & 0xff)
+
+	clock->cmd &= ~(0x80);
+	clock_minutes = clock->min;
+	clock->cmd |= (0x80);
+
+	clock_minutes = FROB_FROM_CLOCK(clock_minutes);
+	real_seconds = nowtime % 60;
+	real_minutes = nowtime / 60;
+
+	if(((abs(real_minutes - clock_minutes) + 15)/30) & 1)
+		real_minutes += 30; /* correct for half hour time zone */
+
+	real_minutes %= 60;
+	if(abs(real_minutes - clock_minutes) < 30) {
+		/* Force clock oscillator to be on. */
+		clock->month &= ~(0x80);
+
+		/* Write real_seconds and real_minutes into the Dallas. */
+		clock->cmd &= ~(0x80);
+		clock->sec = real_seconds;
+		clock->min = real_minutes;
+		clock->cmd |= (0x80);
+	} else
+		return -1;
+
+#undef FROB_FROM_CLOCK
+#undef FROB_TO_CLOCK
+
+	return retval;
+}
+
+static long last_rtc_update = 0;
+
+void indy_timer_interrupt(struct pt_regs *regs)
+{
+	/* Ack timer and compute new compare. */
+	r4k_cur = (read_32bit_cp0_register(CP0_COUNT) + r4k_offset);
+	ack_r4ktimer(r4k_cur);
+	kstat.interrupts[7]++;
+	do_timer(regs);
+
+	/* We update the Dallas time of day approx. every 11 minutes,
+	 * because of how the numbers work out we need to make
+	 * absolutely sure we do this update within 500ms before the
+	 * next second starts, thus the following code.
+	 */
+	if (time_state != TIME_BAD && xtime.tv_sec > last_rtc_update + 660 &&
+	    xtime.tv_usec > 500000 - (tick >> 1) &&
+	    xtime.tv_usec < 500000 + (tick >> 1))
+	  if (set_rtc_mmss(xtime.tv_sec) == 0)
+	    last_rtc_update = xtime.tv_sec;
+	  else
+	    last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
+}
+
+static inline unsigned long dosample(volatile unsigned char *tcwp,
+				     volatile unsigned char *tc2p)
+{
+	unsigned long ct0, ct1;
+	unsigned char msb, lsb;
+
+	/* Start the counter. */
+	*tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL | SGINT_TCWORD_MRGEN);
+	*tc2p = (SGINT_TCSAMP_COUNTER & 0xff);
+	*tc2p = (SGINT_TCSAMP_COUNTER >> 8);
+
+	/* Get initial counter invariant */
+	ct0 = read_32bit_cp0_register(CP0_COUNT);
+
+	/* Latch and spin until top byte of counter2 is zero */
+	*tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CLAT);
+	ct1 = read_32bit_cp0_register(CP0_COUNT);
+	lsb = *tc2p;
+	msb = *tc2p;
+	while(msb) {
+		*tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CLAT);
+		ct1 = read_32bit_cp0_register(CP0_COUNT);
+		lsb = *tc2p;
+		msb = *tc2p;
+	}
+
+	/* Stop the counter. */
+	*tcwp = (SGINT_TCWORD_CNT2 | SGINT_TCWORD_CALL | SGINT_TCWORD_MSWST);
+
+	/* Return the difference, this is how far the r4k counter increments
+	 * for every one HZ.
+	 */
+	return ct1 - ct0;
+}
+
+/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
+ * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
+ * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
+ *
+ * [For the Julian calendar (which was used in Russia before 1917,
+ * Britain & colonies before 1752, anywhere else before 1582,
+ * and is still in use by some communities) leave out the
+ * -year/100+year/400 terms, and add 10.]
+ *
+ * This algorithm was first published by Gauss (I think).
+ *
+ * WARNING: this function will overflow on 2106-02-07 06:28:16 on
+ * machines were long is 32-bit! (However, as time_t is signed, we
+ * will already get problems at other places on 2038-01-19 03:14:08)
+ */
+static inline unsigned long mktime(unsigned int year, unsigned int mon,
+	unsigned int day, unsigned int hour,
+	unsigned int min, unsigned int sec)
+{
+	if (0 >= (int) (mon -= 2)) {	/* 1..12 -> 11,12,1..10 */
+		mon += 12;	/* Puts Feb last since it has leap day */
+		year -= 1;
+	}
+	return (((
+	    (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) +
+	      year*365 - 719499
+	    )*24 + hour /* now have hours */
+	   )*60 + min /* now have minutes */
+	  )*60 + sec; /* finally seconds */
+}
+
+unsigned long get_indy_time(void)
+{
+	struct indy_clock *clock = INDY_CLOCK_REGS;
+	unsigned int year, mon, day, hour, min, sec;
+
+	/* Freeze it. */
+	clock->cmd &= ~(0x80);
+
+	/* Read regs. */
+	sec = clock->sec;
+	min = clock->min;
+	hour = (clock->hr & 0x3f);
+	day = (clock->date & 0x3f);
+	mon = (clock->month & 0x1f);
+	year = clock->year;
+
+	/* Unfreeze clock. */
+	clock->cmd |= 0x80;
+
+	/* Frob the bits. */
+#define FROB1(x)  (((x) & 0xf) + ((((x) & 0xf0) >> 4) * 10));
+#define FROB2(x)  (((x) & 0xf) + (((((x) & 0xf0) >> 4) & 0x3) * 10));
+
+	/* XXX Should really check that secs register is the same
+	 * XXX as when we first read it and if not go back and
+	 * XXX read the regs above again.
+	 */
+	sec = FROB1(sec); min = FROB1(min); day = FROB1(day);
+	mon = FROB1(mon); year = FROB1(year);
+	hour = FROB2(hour);
+
+#undef FROB1
+#undef FROB2
+
+	/* Wheee... */
+	if(year < 45)
+		year += 30;
+	if ((year += 1940) < 1970)
+		year += 100;
+
+	return mktime(year, mon, day, hour, min, sec);
+}
+
+#define ALLINTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5)
+
+void indy_timer_init(void)
+{
+	struct sgi_ioc_timers *p;
+	volatile unsigned char *tcwp, *tc2p;
+
+	/* Figure out the r4k offset, the algorithm is very simple
+	 * and works in _all_ cases as long as the 8254 counter
+	 * register itself works ok (as an interrupt driving timer
+	 * it does not because of bug, this is why we are using
+	 * the onchip r4k counter/compare register to serve this
+	 * purpose, but for r4k_offset calculation it will work
+	 * ok for us).  There are other very complicated ways
+	 * of performing this calculation but this one works just
+	 * fine so I am not going to futz around. ;-)
+	 */
+	p = ioc_timers;
+	tcwp = &p->tcword;
+	tc2p = &p->tcnt2;
+
+	printk("calculating r4koff... ");
+	r4k_offset = dosample(tcwp, tc2p);  /* First sample. */
+	dosample(tcwp, tc2p);               /* Eat one... */
+	r4k_offset += dosample(tcwp, tc2p); /* Second sample. */
+	r4k_offset = (r4k_offset >> 1);     /* Get average. */
+	r4k_offset = HZ * r4k_offset;       /* Multiply by HZ */
+
+	printk("%08lx(%d)\n", r4k_offset, (int) r4k_offset);
+
+	r4k_cur = (read_32bit_cp0_register(CP0_COUNT) + r4k_offset);
+	write_32bit_cp0_register(CP0_COMPARE, r4k_cur);
+	set_cp0_status(ST0_IM, ALLINTS);
+	sti();
+
+	/* Read time from the dallas chipset. */
+	xtime.tv_sec = get_indy_time();
+	xtime.tv_usec = 0;
+}
+
+void indy_8254timer_irq(void)
+{
+	kstat.interrupts[4]++;
+	printk("indy_8254timer_irq: Whoops, should not have gotten this IRQ\n");
+	prom_getchar();
+	prom_imode();
+}
+
+void do_gettimeofday(struct timeval *tv)
+{
+	unsigned long flags;
+
+	save_and_cli(flags);
+	*tv = xtime;
+	restore_flags(flags);
+}
+
+void do_settimeofday(struct timeval *tv)
+{
+	cli();
+	xtime = *tv;
+	time_state = TIME_BAD;
+	time_maxerror = MAXPHASE;
+	time_esterror = MAXPHASE;
+	sti();
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov