/* DClockChip.c *************************************************************
 *
 *	DClockChip ----	Real time clock support routines for DClock.
 *
 *	Author --------	Olaf 'Olsen' Barthel, MXM
 *			Brabeckstrasse 35
 *			D-3000 Hannover 71
 *
 *			Federal Republic of Germany
 *
 *	This  program  truly is in the PUBLIC DOMAIN.  Written on a cold
 *	and  damp  September  evening,  hoping the next morning would be
 *	better.
 *
 *	Compiled using Aztec C 5.0a, CygnusEd Professional 2 & ARexx.
 *
 ***************************************************************************/

	/* Various definitions needed by the datestamp
	 * conversion routine.
	 */

#define MINS_PER_HOUR	60
#define SECS_PER_MIN	60
#define SECS_PER_HOUR	(SECS_PER_MIN * MINS_PER_HOUR)
#define SECS_PER_DAY	(SECS_PER_HOUR * 24)
#define TICS_PER_SEC	50

#define FEB		1
#define DAYS_PER_YEAR	365
#define YEARS_PER_LEAP	4
#define START_YEAR	1978
#define FIRST_LEAP_YEAR	1980
#define LEAP_ADJUST	(FIRST_LEAP_YEAR - START_YEAR)
#define LEAP_FEB_DAYS	29
#define NORM_FEB_DAYS	28

	/* Alias names for clock chip registers. */

#define Clock_Second1	(clock . Second1	& 0xF)
#define Clock_Second10	(clock . Second10	& 0xF)
#define Clock_Minute1	(clock . Minute1	& 0xF)
#define Clock_Minute10	(clock . Minute10	& 0xF)
#define Clock_Hour1	(clock . Hour1		& 0xF)
#define Clock_Hour10	(clock . Hour10		& 0xF)
#define Clock_Day1	(clock . Day1		& 0xF)
#define Clock_Day10	(clock . Day10		& 0xF)
#define Clock_Month1	(clock . Month1		& 0xF)
#define Clock_Month10	(clock . Month10	& 0xF)
#define Clock_Year1	(clock . Year1		& 0xF)
#define Clock_Year10	(clock . Year10		& 0xF)

	/* The builtin clock chip (OKI MSM624SRS) consists of
	 * sixteen registers of which each contains four bits
	 * of relevant information. The following structure
	 * tries to come as close to the register map as
	 * possible, even though bitfields would have been
	 * the better approach. Unfortunately Aztec 'C' 5.0a
	 * forces bitfields to be long word aligned rather than
	 * word aligned which causes a whole lot of confusion.
	 */

struct ClockChip
{
	UWORD		pad0;
	volatile UWORD	Second1;
	UWORD		pad1;
	volatile UWORD	Second10;
	UWORD		pad2;
	volatile UWORD	Minute1;
	UWORD		pad3;
	volatile UWORD	Minute10;
	UWORD		pad4;
	volatile UWORD	Hour1;
	UWORD		pad5;
	volatile UWORD	Hour10;
	UWORD		pad6;
	volatile UWORD	Day1;
	UWORD		pad7;
	volatile UWORD	Day10;
	UWORD		pad8;
	volatile UWORD	Month1;
	UWORD		pad9;
	volatile UWORD	Month10;
	UWORD		pad10;
	volatile UWORD	Year1;
	UWORD		pad11;
	volatile UWORD	Year10;
	ULONG		pad12;
	UWORD		pad13;
	volatile UWORD	Adjust;
};

	/* Easy way to access the builtin clock chip. */

#define clock (*((struct ClockChip *)0xdc0000))

	/* DateToTimeVal(struct DateTag *Date,struct timeval *TimeVal):
	 *
	 *	Converts the contents of a DateTag into a timeval
	 *	as used by timer.device.
	 */

VOID
DateToTimeVal(struct DateTag *Date,struct timeval *TimeVal)
{
	LONG	DaysElapsed,YearsElapsed,LeapYears;
	SHORT	i;

		/* Number of days in each month. */

	static UBYTE Months[12] =
	{
		31,28,31,30,
		31,30,31,31,
		30,31,30,31
	};

		/* Is this a leap year? */

	if(Date -> Year % YEARS_PER_LEAP)
		Months[FEB] = NORM_FEB_DAYS;
	else
		Months[FEB] = LEAP_FEB_DAYS;

		/* Calculate elapsed time. */

	YearsElapsed	= Date -> Year - START_YEAR;
	LeapYears	= (YearsElapsed + LEAP_ADJUST - 1) / YEARS_PER_LEAP;
	DaysElapsed	= (YearsElapsed * DAYS_PER_YEAR) + LeapYears;

		/* Add the days already passed in this year. */

	for(i = 0; i < Date -> Month - 1 ; i++)
		DaysElapsed += Months[i];

		/* Add the days in the current month. */

	DaysElapsed += Date -> Day - 1;

		/* Calculate number of seconds. */

	TimeVal -> tv_secs	= DaysElapsed * SECS_PER_DAY + (Date -> Hour * MINS_PER_HOUR + Date -> Minute) * SECS_PER_MIN + Date -> Second;
	TimeVal -> tv_micro	= 0;
}

	/* ReadClock():
	 *
	 *	Read the system clock and set the system time
	 *	accordingly.
	 */

BYTE
ReadClock()
{
	struct MsgPort		*TimePort;
	struct timerequest	*TimeRequest;
	struct DateTag		 Date;
	UBYTE			 LastTick;
	BYTE			 Success = FALSE;

		/* Check the second timer. */

	LastTick = Clock_Second1;

		/* Wait two seconds. */

	Delay(TICS_PER_SEC * 2);

		/* If the value has changed, we have a
		 * clock chip installed.
		 */

	if(LastTick != Clock_Second1)
	{
		if(TimePort = CreatePort(NULL,0))
		{
			if(TimeRequest = (struct timerequest *)CreateExtIO(TimePort,sizeof(struct timerequest)))
			{
				if(!OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,0))
				{
					TimeRequest -> tr_node . io_Command = TR_SETSYSTIME;

					Forbid();

						/* Stop the clock. */

					clock . Adjust |= 1;

						/* Wait for it to calm down. */

					while(clock . Adjust & 2);

						/* Convert the clock entries into a DateTag. */

					Date . Year	= (Clock_Year10 < 7 ? 10 + Clock_Year10 : Clock_Year10) * 10 + Clock_Year1 + 1900;
					Date . Month	= Clock_Month10 * 10 + Clock_Month1;
					Date . Day	= Clock_Day10 * 10 + Clock_Day1;

					Date . Hour	= Clock_Hour10 * 10 + Clock_Hour1;
					Date . Minute	= Clock_Minute10 * 10 + Clock_Minute1;
					Date . Second	= Clock_Second10 * 10 + Clock_Second1;

						/* Restart the clock. */

					clock . Adjust &= ~1;

					DateToTimeVal(&Date,&TimeRequest -> tr_time);

						/* Set the system time. */

					DoIO(TimeRequest);

					Permit();

					Success = TRUE;

					CloseDevice(TimeRequest);
				}

				DeleteExtIO(TimeRequest);
			}

			DeletePort(TimePort);
		}
	}

	return(Success);
}
