/*	$NetBSD: oftodclock.c,v 1.4 1998/07/07 00:48:12 mark Exp $	*/

/*
 * Copyright 1997
 * Digital Equipment Corporation. All rights reserved.
 *
 * This software is furnished under license and may be used and
 * copied only in accordance with the following terms and conditions.
 * Subject to these conditions, you may download, copy, install,
 * use, modify and distribute this software in source and/or binary
 * form. No title or ownership is transferred hereby.
 *
 * 1) Any source code used, modified or distributed must reproduce
 *    and retain this copyright notice and list of conditions as
 *    they appear in the source file.
 *
 * 2) No right is granted to use any trade name, trademark, or logo of
 *    Digital Equipment Corporation. Neither the "Digital Equipment
 *    Corporation" name nor any trademark or logo of Digital Equipment
 *    Corporation may be used to endorse or promote products derived
 *    from this software without the prior written permission of
 *    Digital Equipment Corporation.
 *
 * 3) This software is provided "AS-IS" and any express or implied
 *    warranties, including but not limited to, any implied warranties
 *    of merchantability, fitness for a particular purpose, or
 *    non-infringement are disclaimed. In no event shall DIGITAL be
 *    liable for any damages whatsoever, and in particular, DIGITAL
 *    shall not be liable for special, indirect, consequential, or
 *    incidental damages or damages for lost profits, loss of
 *    revenue or loss of use, whether such damages arise in contract,
 *    negligence, tort, under statute, in equity, at law or otherwise,
 *    even if advised of the possibility of such damage.
 */

/* 
 *   Real time clock (RTC) functions using OFW /rtc device:
 *      inittodr()
 *      resettodr()
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/kernel.h>

#include <dev/ofw/openfirm.h>

/* The OFW RTC interface, straight from wmb:

selftest  ( -- error? )
	Designed to be called from the "test" user interface command, e.g.
		test /rtc
	which displays a message if the test fails.
	Basically all this does is to check the battery.

check-battery  ( -- error? )
	Called from an open instance; returns non-zero if the battery
	is dead.  Also displays a message to that effect, which it probably
	should not do.

set-time  ( s m h d m y -- )
get-time  ( -- s m h d m y )
	Called from an open instance.
	seconds: 0-59
	minutes: 0-59
	hours: 0-23
	day: 1-31
	month: 1-12
	year: e.g. 1997
*/

#define OFRTC_SEC 0
#define OFRTC_MIN 1
#define OFRTC_HR  2
#define OFRTC_DOM 3
#define OFRTC_MON 4
#define OFRTC_YR  5

static int month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

#define	SECMIN	((unsigned)60)			/* seconds per minute */
#define	SECHOUR	((unsigned)(60*SECMIN))		/* seconds per hour */
#define	SECDAY	((unsigned)(24*SECHOUR))	/* seconds per day */
#define	SECYR	((unsigned)(365*SECDAY))	/* seconds per common year */

static int
yeartoday(year)
	int year;
{

	return ((year % 4) ? 365 : 366);
}

static int timeset = 0;

static void
setthetime(thetime, warning)
	time_t thetime;
	int warning;
{

	timeset = 1;
	time.tv_sec = thetime;
	time.tv_usec = 0;

	if (warning)
		printf("WARNING: CHECK AND RESET THE DATE!\n");
}

static int ofrtc_phandle;
static int ofrtc_ihandle = 0;
static int ofrtcinited = 0;

static void
ofrtcinit(void)
{
	char buf[256];
	int  l;
	int  chosen;

	if (ofrtcinited) return;

	if ((ofrtc_phandle = OF_finddevice("/rtc")) == -1)
		panic("OFW RTC: no package");

	if ((l = OF_getprop(ofrtc_phandle, "device_type", buf, sizeof buf - 1)) < 0)
		panic("OFW RTC: no device type");

	if ((l >= sizeof buf) || strcmp(buf, "rtc"))
		panic("OFW RTC: bad device type");

	if ((chosen = OF_finddevice("/chosen")) == -1 ||
 	    OF_getprop(chosen, "clock", &ofrtc_ihandle, sizeof(int)) < 0) {
		ofrtc_ihandle = 0;
		return;
	}

	ofrtc_ihandle = of_decode_int((unsigned char *)&ofrtc_ihandle);

	ofrtcinited = 1;
}

static int
ofrtcstatus(void)
{
	char status[256];
	int  l;

	if ((ofrtc_ihandle == 0) || (l = OF_getprop(ofrtc_phandle, "status", 
	    status, sizeof status - 1)) < 0) {
		printf("OFW RTC: old firmware does not support RTC\n");
		return 0;
	}

	status[sizeof status - 1] = 0; /* always null terminate */

	if (strcmp(status, "okay")) { /* something is wrong */
		printf("RTC: %s\n", status);
		return 0;
	}

	return 1; /* all systems are go */
}

/*
 * Initialize the time of day register, based on the time base which is, e.g.
 * from a filesystem.
 */
void
inittodr(base)
        time_t base;
{
	time_t n;
	int i, days = 0;
	int date[6];
	int yr;

	/*
	 * We mostly ignore the suggested time and go for the RTC clock time
	 * stored in the CMOS RAM.  If the time can't be obtained from the
	 * CMOS, or if the time obtained from the CMOS is 5 or more years
	 * less than the suggested time, we used the suggested time.  (In
	 * the latter case, it's likely that the CMOS battery has died.)
	 */

	if (base < 25*SECYR) {	/* if before 1995, something's odd... */
		printf("WARNING: preposterous time in file system\n");
		/* read the system clock anyway */
		/* happy b-day sarina */
		base = 26*SECYR + 24*SECDAY + 18 * SECHOUR + 58 * SECMIN;
	}

	ofrtcinit();

	if (!ofrtcstatus()) {
		setthetime(base, 1);
		return;
	}

	if (OF_call_method("get-time", ofrtc_ihandle, 0, 6,
	    date, date + 1, date + 2, date + 3, date + 4, date + 5)) {
		printf("OFW RTC: get-time failed\n");
		setthetime(base, 1);
		return;
	}
	  
	n  = date[OFRTC_SEC];
	n += date[OFRTC_MIN]       * SECMIN;
	n += date[OFRTC_HR]        * SECHOUR;
	n += (date[OFRTC_DOM] - 1) * SECDAY;

	yr = date[OFRTC_YR];

	if (yeartoday(yr) == 366)
		month[1] = 29;
	for (i = date[OFRTC_MON] - 2; i >= 0; i--)
		days += month[i];
	month[1] = 28;
	for (i = 1970; i < yr; i++)
		days += yeartoday(i);
	n += days * 3600 * 24;

	n += rtc_offset * 60;

	if (base < n - 5*SECYR)
		printf("WARNING: file system time much less than clock time\n");

	else if (base > n + 5*SECYR) {
		printf("WARNING: clock time much less than file system time\n");
		printf("WARNING: using file system time\n");
		setthetime(base, 1);
		return;
	}

	setthetime(n, 0);
}

/*
 * Reset the clock.
 */
void
resettodr(void)
{
	time_t n;
	int diff, i, j;
	int sec, min, hr, dom, mon, yr;

	/* old version of the firmware? */
	if (ofrtc_ihandle == 0) return;

	/*
	 * We might have been called by boot() due to a crash early
	 * on.  Don't reset the clock chip in this case.
	 */
	if (!timeset)
		return;

	diff = rtc_offset * 60;
	n = (time.tv_sec - diff) % (3600 * 24);   /* hrs+mins+secs */
	sec = n % 60;
	n /= 60;
	min = n % 60;
	hr  = n / 60;

	n = (time.tv_sec - diff) / (3600 * 24);	/* days */

	for (j = 1970, i = yeartoday(j); n >= i; j++, i = yeartoday(j))
		n -= i;

	yr = j;

	if (i == 366)
		month[1] = 29;
	for (i = 0; n >= month[i]; i++)
		n -= month[i];
	month[1] = 28;
	mon = ++i;
	dom = ++n;

	if (OF_call_method("set-time", ofrtc_ihandle, 6, 0,
	     sec, min, hr, dom, mon, yr))
		printf("OFW RTC: set-time failed\n");
}
