/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION  1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:clock.c 12.0$ */
/* $ACIS:clock.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/ca/RCS/clock.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:clock.c 12.0$";
#endif

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


#include "param.h"
#include "time.h"
#include "kernel.h"
#include "clock.h"
#include "scr.h"
#include "debug.h"

#ifdef ATR
#include "../ca_atr/pcif.h"
#include "io.h"
#endif ATR 

#ifdef IBMRTPC
#define XCLOCK_SEC	0xf0008800
#define XCLOCK_SAL	0xf0008801
#define XCLOCK_MIN	0xf0008802
#define XCLOCK_MAL	0xf0008803
#define XCLOCK_HRS	0xf0008804
#define XCLOCK_HAL	0xf0008805
#define XCLOCK_DOW	0xf0008806
#define XCLOCK_DOM	0xf0008807
#define XCLOCK_MON	0xf0008808
#define XCLOCK_YR	0xf0008809
#define XCLOCK_A	0xf000880A
#define XCLOCK_B	0xf000880B
#define XCLOCK_C	0xf000880C
#define XCLOCK_D	0xf000880D
#endif IBMRTPC

#ifdef ATR
#define XCLOCK_SEC	0xc0000000		/* an invalid address */
#endif ATR

struct XCLOCK {
	unsigned char sec;
	unsigned char sal;
	unsigned char min;
	unsigned char mal;
	unsigned char hrs;
	unsigned char hal;
	unsigned char dow;
	unsigned char dom;
	unsigned char mon;
	unsigned char year;
	unsigned char a;
	unsigned char b;
	unsigned char c;
	unsigned char d;
} *xclock = (struct XCLOCK *)XCLOCK_SEC;
#ifdef ATR
struct XCLOCK newclock;
static int pcclock;
static int	cl_pc_cb;		/* pc pointer to clock data */
#endif ATR 


/* Bits in XCLOCK->a */

#define XCLOCK_UIP	0x80		  /* 1 = update in progress */
#define XCLOCK_DV2	0x40		  /* divisor selection */
#define XCLOCK_DV1	0x20		  /* divisor selection */
#define XCLOCK_DV0	0x10		  /* divisor selection */
#define XCLOCK_RS3	0x08		  /* rate selection */
#define XCLOCK_RS2	0x04		  /* rate selection */
#define XCLOCK_RS1	0x02		  /* rate selection */
#define XCLOCK_RS0	0x01		  /* rate selection */

/* Reset the clock XLOCK_A */
#define XCLOCK_RS	(XCLOCK_DV1+XCLOCK_RS2+XCLOCK_RS1)	/* 0x26 */

 /* Start the external clock for timer XCLOCK_B */
#define XCLOCK_SET	0x80		  /* 0 - Update normal, 1 - Halt and init */
#define XCLOCK_PIE	0x40		  /* 0/1 disable/enable periodic interrupt */
#define XCLOCK_AIE	0x20		  /* 0/1 disable/enable alarm interrupt */
#define XCLOCK_UIE	0x10		  /* 0/1 disable/enable update-ended interrupt */
#define XCLOCK_SQWE	0x08		  /* 0/1 disable/enable sqware-wave signal */
#define XCLOCK_DM	0x04		  /* data mode, 0 - BCD, 1 - Binary */
#define XCLOCK_24HR	0x02		  /* 0 - 12 hour mode, 1 - 24 hour mode */
#define XCLOCK_DSE	0x01		  /* 0/1 diable/enable daylight savings */

char tickdebug=0;

/*
 * 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 hardare clock lost state.
 *
 * Resettodr restores the time of day hardware after a time change.
 */

unsigned timer_base;

/*
 * Start the real-time clock.
 */
startrtclock()
{

	if (tick * hz != MICROSECSEC) {
		printf("startrtclock: hz = %d\ntick= %d\n", hz, tick);
		panic("MICROSECSEC % hz not 0");
	}

	if (phz)
		timer_base = CPU_CLOCK_HZ / phz;  /* # of timer counts */
	else
		timer_base = CPU_CLOCK_HZ / hz;	  /* # of timer counts */

#ifdef ATR

	/*
	 * The clock chip on the PS/2 is addressed via ports 0x70 and 0x71
	 * on the PS/2 system board. Since we can not address those ports
	 * directly we need the PS/2 code to move the data from the clock
	 * chip and place it into a shared memory location. We get the 
	 * PS/2 address of this location from the cbcb and add to it the
	 * Unix address of the pcif 512K full window. This gives us a
	 * physical ROMP address that points to PS/2 memory. When we
	 * want to read the clock chip we tell the PS/2 code to do it
	 * for us, then we look at shared memory to find out what the
	 * values are. See the PS/2 source file clock.c for more details.
	 */

	init_ps2_clock();
	delay(20);			/* allow time for PS/2 to set clock */
#endif ATR

#ifdef IBMRTPC
	xclock->a = XCLOCK_RS;
	xclock->b = XCLOCK_SQWE | XCLOCK_DM | XCLOCK_24HR;
#endif IBMRTPC

	mtsr(SCR_TIMER, timer_base);	  /* start with a short tick */
	mtsr(SCR_TIMER_RESET, timer_base); /* 1st tick is a guess */
	mtsr(SCR_TIMER_STATUS, TS_ENABLE | TS_ILEVEL);

	delay_adjust();			/* get proper delay values */
}

#if !defined(GPROF)
/*
 * adjust the tick count to account for the incorrect tick frequency
 * on the 6152
 */
#define HZ	100		/* number of clock tics/second */
adjtick()
{
	static int total=0;	/* adjusted by timer_base each tic */
	static int sum=0;	/* adjusted by real timer base each tic */
	int tick;

	total += CPU_CLOCK_HZ;
	tick = total/HZ - sum;		/* tick size this instant */
	mtsr(SCR_TIMER_RESET, tick); 	/* set next tick length */
	sum += tick;
	if (sum == CPU_CLOCK_HZ)
		sum = total = 0;	/* cycled thru */
}
#endif /* defined(ATR) && !defined(GPROF) */

#ifdef ATR
/*
 * pick up the clock pointer from the PS/2 memory
 */
init_ps2_clock()
{
	register int oldwindow = get_512_window();
	extern struct kbdata *kbdata;
	extern int kb_pc_cb;

	cl_pc_cb = get_pc_cb(CLENT);
	pcclock = (set_512_window(cl_pc_cb) + pcif_512_fw);
	xclock = (struct XCLOCK *)pcclock;
	/*
	 * this keyboard initialization should be done elsewhere! 
	 */
	set_pcvec_map(KBIRQ, 1);	/* allow keyboard interrupt ?? */
	(void) set_512_window(kb_pc_cb);
	kbdata->fwd_int = 1;		/* enable keyboard interrupts */
	set_512_window(oldwindow);
}
#endif ATR

int clock_busy;		/* number of times we found update in progress */

/*
 * Initialze the time of day structure.
 */
inittodr(base)
	time_t base;
{
	register unsigned i;
	int secpast69 = 0;

#ifdef ATR
	register int old_window = get_512_window();
	set_512_window(cl_pc_cb);
		
#endif ATR
	{

#ifdef IBMRTPC
		while (xclock->a & XCLOCK_UIP) {
			DELAY(10);
			++clock_busy;	/* waited for clock to settle */
		}
#endif IBMRTPC
		DEBUGF(tickdebug,
			printf("yr (%d) mon (%d) dom (%d) hrs (%d) min (%d) sec (%d)\n",
				xclock->year, xclock->mon, xclock->dom, xclock->hrs,
				xclock->min, xclock->sec););

		for (i = 70; i < xclock->year; i++)
			secpast69 += SECYR + (LEAPYEAR(i) ? SECDAY : 0);

		for (i = 1; i < xclock->mon; i++) {
			adjustmonth(i, &secpast69, xclock->year, 1);
		}
		secpast69 += SECDAY * (xclock->dom - 1);
		secpast69 += SECHR * xclock->hrs;
		secpast69 += SECMIN * xclock->min;
		secpast69 += xclock->sec;
#ifdef ATR
		secpast69 += SECMIN * tz.tz_minuteswest - SECHR * config.dst_flag ; /* sigh: PS/2 runs on local time */
#endif ATR
	}
	time.tv_sec = secpast69;
	time.tv_usec = 0;
	if ((secpast69 < base-1) || (secpast69 > base + (SECDAY * 3)))
		printf("PREPOSTEROUS DATE, (gained %d) MAY BE WRONG -- CHECK AND RESET!\n",secpast69-base);
#ifdef ATR
	set_512_window(old_window);
#endif ATR 
		
}

/*
 * pick up the time change by getting the date from
 * the clock chip.
 */
get_timer_delta (timer_delta)
	time_t *timer_delta;
{
	register time_t temp;

	DEBUGF (tickdebug,printf("timer: Lost timer tick\n"););
	temp=time.tv_sec;
#ifdef IBMRTPC
	/*
	 * if clock is doing update cycle then we will just ignore
	 * the lost tick for now (which won't hurt us too much and is better
	 * than waiting around for the clock to finish updating as this
	 * could be up to 2ms later).
	 * we don't clear the timer overflow bit so we should try again
	 * in 16ms or so and the clock update should be finished then.
	 */
	if ((xclock->a & XCLOCK_UIP)) {
		clock_busy++;
		return;
	}
#endif IBMRTPC
		inittodr(time.tv_sec);
	*timer_delta=time.tv_sec-temp;
	if (*timer_delta > 15 * SECMIN) { /* if more than 15 minutes complain */
		printf("timer: local timer clock too far behind: updated all at once.\n");
		*timer_delta=0;
	} else {
		time.tv_sec = temp;
		*timer_delta *= MICROSECSEC / tick;
	}
	/* clear the overflow flag */
	mtsr(SCR_TIMER_STATUS, (mfsr(SCR_TIMER_STATUS) & ~TS_OVERFLOW));
}


/*
 * Reset the TODR based on the time value.  Used when:
 *   - the time is reset by the stime system call
 *   - on Jan 2 just after midnight
 */
resettodr()
{
	int s69 = time.tv_sec;
	register int i;

#ifdef ATR
	register int old_window = get_512_window();
	set_512_window(cl_pc_cb);
	s69 -= SECMIN * tz.tz_minuteswest - SECHR * config.dst_flag ;	/* sigh: PS/2 runs on local time */
	xclock = &newclock;
#endif ATR
	xclock->sal = xclock->mal = xclock->hal = xclock->dow = 0;

#ifdef CLOCKDEBUG
	printf("after tz adjustment s69 (%d) - ", s69);
#endif CLOCKDEBUG
	for (i = 70; s69 > SECYR + (LEAPYEAR(i) ? SECDAY : 0); i++)
		s69 -= SECYR + (LEAPYEAR(i) ? SECDAY : 0);

#ifdef IBMRTPC
	/* Tell the MC146818 that we are going to reset it */
	*(unsigned char *)XCLOCK_B |= XCLOCK_SET;
#endif IBMRTPC

	/* Set the year on the time of day clock */
	xclock->year = i;

/*
 * find the month by going past it (which will set s69 < 0)
 * and then backing off that extra month.
 */
	for (i = 1; s69 > 0; i++) {
		adjustmonth(i, &s69, xclock->year, 0);
	}
	/* Set the Month on time of day clock */
	xclock->mon = --i;
	adjustmonth(i, &s69, xclock->year, 1);

	/* Set the Day of Month on time of day clock */
/*	printf("s69=%d s69/SECDAY=%d ",s69,s69/SECDAY);	/* debug */
	xclock->dom = (s69 / SECDAY) + 1;

	s69 %= SECDAY;
	/* Set the Hours on the time of day clock */
	xclock->hrs = s69 / (SECHR);

	s69 %= SECHR;
	/* Set the Minutes on the time of day clock */
	xclock->min = s69 / (SECMIN);

	s69 %= SECMIN;
	/* Set the SECONDS on the time of day clock */
	xclock->sec = s69;

#ifdef IBMRTPC
	/* Tell the MC146818 that we are done with reset */
	*(unsigned char *)XCLOCK_B &= ~XCLOCK_SET;
#endif IBMRTPC
#ifdef ATR
	pc_req(CB_TODRESET,xclock,CLENT);
#endif ATR

#ifdef CLOCKDEBUG
	printf("yr (%d) mon (%d) dom (%d) hrs (%d) min (%d) sec (%d)\n",
	    xclock->year, xclock->mon, xclock->dom, xclock->hrs,
	    xclock->min, xclock->sec);
	printf("TODR reset !\n");
#endif CLOCKDEBUG
#ifdef ATR
	set_512_window(old_window);
	xclock = (struct XCLOCK *)pcclock;	/* restore pc pointer */
#endif ATR 
}


/*
 * add/subtrace number of seconds in month 'i' to/from *s69.
 */
adjustmonth(i, s69, year, add)
	register int i;
	register int *s69;
	register int year;
	register int add;
{
	register int secs;

	switch (i) {
	case 1:				  /* Jan */
	case 3:				  /* Mar */
	case 5:				  /* May */
	case 7:				  /* Jul */
	case 8:				  /* Aug */
	case 10:			  /* Oct */
	case 12:			  /* Dec */
		secs = 31 * SECDAY;
		break;
	case 4:				  /* Apr */
	case 6:				  /* Jun */
	case 9:				  /* Sep */
	case 11:			  /* Nov */
		secs = 30 * SECDAY;
		break;
	case 2:				  /* Feb */
		secs = 28 * SECDAY + (LEAPYEAR(year) ? SECDAY : 0);
		break;
	}
	if (add)
		*s69 += secs;
	else
		*s69 -= secs;
}

#ifdef GPROF
/*
 * physical clock routine: called at the fastest clock rate
 * by the first-level interrupt handler when GPROF is specified:
 * 1.	call gatherstats every clock tick to update the kernel
 * 	profiling information.
 * 2. call hardclock whenever a normal clock tick is due 
 *	(every phz/hz calls), note that we only do this if
 *	the priority is appropriate. This allows us to profile 
 *	hardclock. This works because nothing normally interrupts
 *	on level 2.
 */
int phz = CPU_CLOCK_HZ/2;		/* physical clock rate */
#define CLOCK_PRIORITY	2		/* priority to run hardclock at */

physclock(dev, ics_cs, iar)
register int dev;
register int ics_cs;
register int iar;
{
	static int phys_clock = 0;

	gatherstats(iar, ics_cs);	/* do profiling */
	if ((phys_clock += hz) >= phz && 
			(ics_cs&ICSCS_PRIORITY) > CLOCK_PRIORITY) {
		phys_clock -= phz;
		(void) _spl2();		/* go to lower priority */
		hardclock(dev, ics_cs, iar);
	}
}
#endif GPROF

#ifdef ATR
bcd_unpack(bcddig)
char bcddig;
{
	register char c;

	return( ((((bcddig & 0xf0)>>4) * 10) + (bcddig & 0x0f)));
}
#endif ATR
