/*	clock.c	6.1	83/07/29	*/

#include "../h/param.h"
#include "../h/time.h"
#include "../h/kernel.h"

#include "../is68kdev/ctcreg.h"
#include "../machine/clock.h"
#include "../machine/board.h"

/*
 * Machine-dependent clock routines.
 *
 * Startrtclock restarts the real-time clock, which provides
 * hardclock interrupts to kern_clock.c.
 *
 * Inittodr initializes the time of day hardware which provides
 * date functions.  Its primary function is to use some file
 * system information in case the hardware clock lost state.
 *
 * Resettodr restores the time of day hardware after a time change.
 */
short fastclock = 1;			/* for clusters */

/*
 * Start the real-time clock.
 */
#ifdef	M68020
startrtclock()
{
	ctx_mask |= CTX_CLOCK;		/* turn on 60 Hz clock */
	*CTX = ctx | ctx_mask;
	prcpuspeed();
	memenable();
	trapcheck();
}
#else	M68020
static int use_ltc = 0;
int ltc_tick()
{
	use_ltc = 1;
}

#ifdef	QBUS
static int n_ctc_tick = 1;
extern int ctc_div;
int ctc_tick()
{
	if (n_ctc_tick) {
		n_ctc_tick++;
		timeout(ctc_tick, (caddr_t)0, 1);
	}
}
#endif	QBUS

startrtclock()
{
	register struct ctcdevice *p;
	register int n;

	/*
	 * Determine if the line time clock (LTC) is enabled by setting a 
	 * timeout and waiting with interrupts enabled.
	 */
	timeout(ltc_tick, (caddr_t)0, 1);
	spl0();
	DELAY(100000);			/* wait at least 1/60th sec */
	spl7();
	if (use_ltc) {			/* exit if using the line time clock */
		printf("LTC system clock\n");
		return;
	}

	/*
	 *	VME BUS or E28-E30 on QBUS
	 *  8 Mhz:  8000000 / 4 / 165 / 202 = 60.006
	 * 10 Mhz: 10000000 / 4 / 166 / 251 = 60.001
	 * 11 Mhz: 11059200 / 4 / 192 / 240 = 60 (exactly!)
	 * 12 Mhz: 11592000 / 4 / 230 / 210 = 60 (exactly!)
	 *	E29-E30 on QBUS
	 *  8 Mhz:  8000000 / 8 / 165 / 101 = 60.006
	 * 10 Mhz: 10000000 / 8 /  83 / 251 = 60.001
	 * 11 Mhz: 11059200 / 8 /  96 / 240 = 60 (exactly!)
	 * 12 Mhz: 11592000 / 8 / 115 / 210 = 60 (exactly!)
	 */
	p = (struct ctcdevice *)CTC0_ADR;
	p->ctc_ctrl = 80;		/* KLUDGE base vector (channel 0) */
	p = (struct ctcdevice *)CTC2_ADR;
	p->ctc_ctrl = CTC_CTRL | CTC_TCF | CTC_CNT;

	switch (*BSR & BSR_CPUSPEED) {
#ifdef BSR_CPUSPEED8
	  case BSR_CPUSPEED8:	p->ctc_ctrl = 165; break;
#endif BSR_CPUSPEED8
#ifdef BSR_CPUSPEED10
	  case BSR_CPUSPEED10:	p->ctc_ctrl = 166; break;
#endif BSR_CPUSPEED10
#ifdef BSR_CPUSPEED11
	  case BSR_CPUSPEED11:	p->ctc_ctrl = 192; break;
#endif BSR_CPUSPEED11
#ifdef BSR_CPUSPEED12
	  case BSR_CPUSPEED12:	p->ctc_ctrl = 230; break;
#endif BSR_CPUSPEED12
	}

	p = (struct ctcdevice *)CTC3_ADR;
	p->ctc_ctrl = CTC_CTRL | CTC_TCF | CTC_CNT | CTC_IENBL;

	n = 0;
	switch (*BSR & BSR_CPUSPEED) {
#ifdef BSR_CPUSPEED8
	  case BSR_CPUSPEED8:	n = 202; break;
#endif BSR_CPUSPEED8
#ifdef BSR_CPUSPEED10
	  case BSR_CPUSPEED10:	n = 251; break;
#endif BSR_CPUSPEED10
#ifdef BSR_CPUSPEED11
	  case BSR_CPUSPEED11:	n = 240; break;
#endif BSR_CPUSPEED11
#ifdef BSR_CPUSPEED12
	  case BSR_CPUSPEED12:	n = 210; break;
#endif BSR_CPUSPEED12
	}
	if (fastclock) {
		int i = n;
		n /= fastclock;
		if (n * fastclock != i) {
			n = i;
			fastclock = 1;
		}
	}
	p->ctc_ctrl = n;

#ifdef	QBUS
	/*
	 * On the Qbus the jumper between E28-E30 could have been moved to
	 * E29-E30 causing a divide by 8 instead of divide by four into ctc.
	 * Determine if it is divide by 8, and reprogram CTC for 60Hz if so.
	 */
	timeout(ctc_tick, (caddr_t)0, 1);
	n_ctc_tick = 1;
	spl0();
	DELAY(200000);
	n = n_ctc_tick;
	n_ctc_tick = 0;
	spl7();
/*printf("%d ticks\n",n); /**/
	/* 
	 * "15" determined empirically, dependent on delay above,
	 * and must work for 10 and 12 MHz
	 */
	if ( n < 15 ) {
		printf("CTC system clock / 8\n");
		ctc_div = 8;

		p = (struct ctcdevice *)CTC0_ADR;
		p->ctc_ctrl = 80;	/* KLUDGE base vector (channel 0) */
		p = (struct ctcdevice *)CTC2_ADR;
		p->ctc_ctrl = CTC_CTRL | CTC_TCF | CTC_CNT;

		switch (*BSR & BSR_CPUSPEED) {
#ifdef BSR_CPUSPEED8
	  	case BSR_CPUSPEED8:	p->ctc_ctrl = 165; break;
#endif BSR_CPUSPEED8
#ifdef BSR_CPUSPEED10
	  	case BSR_CPUSPEED10:	p->ctc_ctrl = 83; break;
#endif BSR_CPUSPEED10
#ifdef BSR_CPUSPEED11
	  	case BSR_CPUSPEED11:	p->ctc_ctrl = 96; break;
#endif BSR_CPUSPEED11
#ifdef BSR_CPUSPEED12
	  	case BSR_CPUSPEED12:	p->ctc_ctrl = 115; break;
#endif BSR_CPUSPEED12
		}

		p = (struct ctcdevice *)CTC3_ADR;
		p->ctc_ctrl = CTC_CTRL | CTC_TCF | CTC_CNT | CTC_IENBL;

		switch (*BSR & BSR_CPUSPEED) {
#ifdef BSR_CPUSPEED8
	  	case BSR_CPUSPEED8:	p->ctc_ctrl = 101; break;
#endif BSR_CPUSPEED8
#ifdef BSR_CPUSPEED10
	  	case BSR_CPUSPEED10:	p->ctc_ctrl = 251; break;
#endif BSR_CPUSPEED10
#ifdef BSR_CPUSPEED11
	  	case BSR_CPUSPEED11:	p->ctc_ctrl = 240; break;
#endif BSR_CPUSPEED11
#ifdef BSR_CPUSPEED12
	  	case BSR_CPUSPEED12:	p->ctc_ctrl = 210; break;
#endif BSR_CPUSPEED12
		}
	} else
#endif	QBUS
		printf("CTC system clock\n");
}
#endif	M68020

unsigned short *battery_clock = 0;
#ifndef	QBUS
/* NOTE: These macros MUST be used at spl >= controller interrupt level. */
#define BCLOCK_WAIT()		do DELAY(200) while(*battery_clock & 0x8000);
#define BCLOCK_READ(i, v)	*battery_clock = 0xC000 | ((i & 0xF)<<8); \
				BCLOCK_WAIT(); v = *battery_clock & 0xFF; \
				v = ((v >> 4) * 10)  + (v & 0xF);
#define BCLOCK_WRITE(i, v)	*battery_clock = 0xD000 | ((i&0xF)<<8) | \
				((((v/10) << 4) + (v%10)) & 0xFF); BCLOCK_WAIT()
#define BCLOCK_GET()		*battery_clock = 0xE000; BCLOCK_WAIT()
#define BCLOCK_SET()		*battery_clock = 0xF000; BCLOCK_WAIT()
#define	BCLOCK_RESET()		*battery_clock = 0;

static	int	dmsize[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
#endif	QBUS

/*
 * Initialize the time of day register, based on the time base which is, e.g.
 * from a filesystem. Called after configure.
 */
inittodr(base)
	time_t base;
{
	short year, month, day, hour, mins, secs;
	register struct tm *tm;
	struct tm *gmtime();
	register int i;

	if (base < 5*SECYR) {
		printf("***  WARNING:  preposterous time in file system  ***");
		printf(" -- CHECK AND RESET THE DATE!\n");
		time.tv_sec = 6*SECYR + 186*SECDAY + SECDAY/2;
	} else {
		/*
		 * Believe the time in the file system for lack of
		 * anything better.
		 */
#ifndef	QBUS
		if (battery_clock) {
			register int s = spl7();

			BCLOCK_GET();
			BCLOCK_READ( 7, month);
			BCLOCK_READ( 6, day);
			BCLOCK_READ( 4, hour);
			BCLOCK_READ( 3, mins);
			BCLOCK_READ( 2, secs);
			BCLOCK_RESET();
			splx(s);
			tm = gmtime(&base);
			year = tm->tm_year - 70;
			if (tm->tm_mon >= 6 && month <= 6)
				year += 1;	/* merry XMAS, happy new year */
			base = 0;
			for (i = 0; i < year; i++)
				base += dysize(70 + i);
			/* Leap year */
			if (dysize(70 + year) == 366 && month >= 3)
				base++;
			while (--month)
				base += dmsize[month-1];
			base += day-1;
			base = 24*base + hour;
			base = 60*base + mins;
			base = 60*base + secs;
		}
#endif	QBUS
		time.tv_sec = base;
	}
}

/*
 * Reset the TODR based on the time value; used when the TODR has a preposterous
 * value and also when the time is reset by the stime system call.
 */
resettodr()
{
#ifndef	QBUS
	register struct tm *tm;
	struct tm *gmtime();
	register int s;
	
	if (battery_clock == 0)
		return;
	tm = gmtime(&time.tv_sec);
	tm->tm_mon += 1;
	tm->tm_year -= 70;

	s = spl7();
	BCLOCK_WRITE( 2, tm->tm_sec);
	BCLOCK_WRITE( 3, tm->tm_min);
	BCLOCK_WRITE( 4, tm->tm_hour);
	BCLOCK_WRITE( 6, tm->tm_mday);
	BCLOCK_WRITE( 7, tm->tm_mon);
	BCLOCK_WRITE( 8, tm->tm_year);
	BCLOCK_WRITE( 9, 1);		/* set 24 hour mode on 1/2" tape */
	BCLOCK_SET();
	BCLOCK_RESET();
	splx(s);
#endif	QBUS
}

#ifndef	QBUS
struct tm *
gmtime(tim)
unsigned long *tim;
{
	register int d0, d1;
	long hms, day;
	register int *tp;
	static struct tm xtime;

	/* break initial number into days */
	hms = *tim % 86400;
	day = *tim / 86400;
	if (hms<0) {
		hms += 86400;
		day -= 1;
	}
	tp = (int *)&xtime;

	/* generate hours:minutes:seconds */
	*tp++ = hms%60;
	d1 = hms/60;
	*tp++ = d1%60;
	d1 /= 60;
	*tp++ = d1;

	/*
	 * day is the day number. generate day of the week.
	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
	 */

	xtime.tm_wday = (day+7340036)%7;

	/* year number */
	if (day>=0) for(d1=70; day >= dysize(d1); d1++)
		day -= dysize(d1);
	else for (d1=70; day<0; d1--)
		day += dysize(d1-1);
	xtime.tm_year = d1;
	xtime.tm_yday = d0 = day;

	/* generate month */
	if (dysize(d1)==366)
		dmsize[1] = 29;
	for(d1=0; d0 >= dmsize[d1]; d1++)
		d0 -= dmsize[d1];
	dmsize[1] = 28;
	*tp++ = d0+1;
	*tp++ = d1;
	xtime.tm_isdst = 0;
	return(&xtime);
}

dysize(y)
{
	if((y%4) == 0)
		return(366);
	return(365);
}
#endif	QBUS
