/*      $NetBSD: rtc.c,v 1.20 2023/10/19 22:07:13 andvar Exp $        */
/*
 * Copyright (c) 1998 Darrin Jewell
 * Copyright (c) 1997 Rolf Grossmann
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Rolf Grossmann.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* These haven't been tested to see how they interact with NeXTstep's
 * interpretation of the rtc.
 */

/* Now using this in the kernel.  This should be turned into a device
 * Darrin B Jewell <jewell@mit.edu>  Tue Jan 27 20:59:25 1998
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rtc.c,v 1.20 2023/10/19 22:07:13 andvar Exp $");

#include <sys/param.h>
#include <sys/systm.h>          /* for panic */

#include <machine/bus.h>
#include <machine/cpu.h>

#include <dev/clock_subr.h>

#include <next68k/next68k/rtc.h>

#include <next68k/dev/clockreg.h>
#include <next68k/dev/intiovar.h>

/* #define RTC_DEBUG */

u_char new_clock;

/* will get memory mapped in rtc_init */
volatile u_int *scr2 = (u_int *)NEXT_P_SCR2;

static int gettime_old(todr_chip_handle_t, struct clock_ymdhms *);
static int settime_old(todr_chip_handle_t, struct clock_ymdhms *);
static int gettime_new(todr_chip_handle_t, struct timeval *);
static int settime_new(todr_chip_handle_t, struct timeval *);

/*
 * NB: This code should probably be converted to a _true_ device, then this
 * initialization could happen in attach.  The printf could get fixed then,
 * too.
 */
void
rtc_init(void)
{
	static struct todr_chip_handle tch;
	uint8_t val;

	scr2 = (u_int *)IIOV(NEXT_P_SCR2);
	val = rtc_read(RTC_STATUS);
	new_clock = (val & RTC_NEW_CLOCK) ? 1 : 0;

	printf("Looks like a %s clock chip.\n", new_clock ?
	    "MCS1850 (new style)" :
	    "MC68HC68T1 (old style)");

#ifdef RTC_DEBUG
	rtc_print();
#endif

	if (new_clock) {
		tch.todr_gettime = gettime_new;
		tch.todr_settime = settime_new;
		tch.todr_gettime_ymdhms = NULL;
		tch.todr_settime_ymdhms = NULL;
	} else {
		tch.todr_gettime_ymdhms = gettime_old;
		tch.todr_settime_ymdhms = settime_old;
		tch.todr_gettime = NULL;
		tch.todr_settime = NULL;
	}
	tch.todr_setwen = NULL;

	todr_attach(&tch);
}

void
rtc_print(void)
{

#define RTC_PRINT(x)	printf("\t%16s= 0x%02x\n",#x, rtc_read(x))

	if (new_clock) {
		RTC_PRINT(RTC_RAM);
		RTC_PRINT(RTC_CNTR0);
		RTC_PRINT(RTC_CNTR1);
		RTC_PRINT(RTC_CNTR2);
		RTC_PRINT(RTC_CNTR3);
		RTC_PRINT(RTC_ALARM0);
		RTC_PRINT(RTC_ALARM1);
		RTC_PRINT(RTC_ALARM2);
		RTC_PRINT(RTC_ALARM3);
		RTC_PRINT(RTC_STATUS);
		RTC_PRINT(RTC_CONTROL);
	} else {
		RTC_PRINT(RTC_RAM);
		RTC_PRINT(RTC_SEC);
		RTC_PRINT(RTC_MIN);
		RTC_PRINT(RTC_HRS);
		RTC_PRINT(RTC_DAY);
		RTC_PRINT(RTC_DATE);
		RTC_PRINT(RTC_MON);
		RTC_PRINT(RTC_YR);
		RTC_PRINT(RTC_ALARM_SEC);
		RTC_PRINT(RTC_ALARM_MIN);
		RTC_PRINT(RTC_ALARM_HR);
		RTC_PRINT(RTC_STATUS);
		RTC_PRINT(RTC_CONTROL);
		RTC_PRINT(RTC_INTRCTL);
	}
}


uint8_t
rtc_read(uint8_t reg)
{
	int i;
	u_int tmp;
	uint8_t val;

	*scr2 = (*scr2 & ~(SCR2_RTDATA | SCR2_RTCLK)) | SCR2_RTCE;
	DELAY(1);

	val = reg;
	for (i = 0; i < 8; i++) {
		tmp = *scr2 & ~(SCR2_RTDATA | SCR2_RTCLK);
		if ((val & 0x80) != 0)
			tmp |= SCR2_RTDATA;

		*scr2 = tmp;
		DELAY(1);
		*scr2 = tmp | SCR2_RTCLK;
		DELAY(1);
		*scr2 = tmp;
		DELAY(1);

		val <<= 1;
	}

	val = 0;			/* should be anyway */
	for (i = 0; i < 8; i++) {
		val <<= 1;

		tmp = *scr2 & ~(SCR2_RTDATA | SCR2_RTCLK);

		*scr2 = tmp | SCR2_RTCLK;
		DELAY(1);
		*scr2 = tmp;
		DELAY(1);

		if ((*scr2 & SCR2_RTDATA) != 0)
			val |= 1;
	}

	*scr2 &= ~(SCR2_RTDATA | SCR2_RTCLK | SCR2_RTCE);
	DELAY(1);

	return val;
}

void
rtc_write(uint8_t reg, uint8_t v)
{
	int i;
	u_int tmp;
	uint8_t val;

	*scr2 = (*scr2 & ~(SCR2_RTDATA | SCR2_RTCLK)) | SCR2_RTCE;
	DELAY(1);

	val = reg | RTC_WRITE;

	for (i = 0; i < 8; i++) {
		tmp = *scr2 & ~(SCR2_RTDATA | SCR2_RTCLK);
		if ((val & 0x80) != 0)
			tmp |= SCR2_RTDATA;

		*scr2 = tmp;
		DELAY(1);
		*scr2 = tmp | SCR2_RTCLK;
		DELAY(1);
		*scr2 = tmp;
		DELAY(1);

		val <<= 1;
	}

	DELAY(1);

	for (i = 0; i < 8; i++) {
		tmp = *scr2 & ~(SCR2_RTDATA | SCR2_RTCLK);
		if ((v & 0x80) != 0)
			tmp |= SCR2_RTDATA;

		*scr2 = tmp;
		DELAY(1);
		*scr2 = tmp | SCR2_RTCLK;
		DELAY(1);
		*scr2 = tmp;
		DELAY(1);

		v <<= 1;
	}

	*scr2 &= ~(SCR2_RTDATA | SCR2_RTCLK | SCR2_RTCE);
	DELAY(1);
}

void
poweroff(void)
{
	int reg, t;

	if(new_clock) {
		reg = RTC_CNTR3;
	} else {
		reg = RTC_CNTR0;
	}

	t = rtc_read(reg);	/* seconds */
	/* wait for clock to tick */
	while(t == rtc_read(reg))
		continue;

	DELAY(850000);	/* hardware bug workaround ? */

	if (new_clock) {
		reg = RTC_CONTROL;
	} else {
		reg = RTC_INTRCTL;
	}

	rtc_write(reg, rtc_read(reg)|(RTC_PDOWN));

	printf("....................."); /* @@@ work around some sort of bug. */

	panic("Failed to poweroff!");
}


int
gettime_old(todr_chip_handle_t tch, struct clock_ymdhms *dt)
{
	uint8_t h, y;

	y = bcdtobin(rtc_read(RTC_YR));
	if (y >= 69) {
		dt->dt_year = 1900 + y;
	} else {
		dt->dt_year = 2000 + y;
	}

	dt->dt_mon  = bcdtobin(rtc_read(RTC_MON)  & 0x1f);
	dt->dt_day  = bcdtobin(rtc_read(RTC_DATE) & 0x3f);
	dt->dt_wday = bcdtobin(rtc_read(RTC_DAY)  & 0x07);

	h = rtc_read(RTC_HRS);
	if ((h & 0x80) != 0) {
		/* time is am/pm format */
		dt->dt_hour = bcdtobin(h & 0x1f);
		if ((h & 0x20) != 0) {
			/* pm */
			if (dt->dt_hour < 12)
				dt->dt_hour += 12;
		} else {
			/* am */
			if (dt->dt_hour == 12)
				dt->dt_hour = 0;
		}
#ifdef notdef
	} else {
		/* time is 24 hour format */
		struct clock_ymdhms val;
		val.dt_hour = bcdtobin(h & 0x3f);
#endif
	}

	dt->dt_min = bcdtobin(rtc_read(RTC_MIN) & 0x7f);
	dt->dt_sec = bcdtobin(rtc_read(RTC_SEC) & 0x7f);

	return 0;
}

int
settime_old(todr_chip_handle_t tcr, struct clock_ymdhms *dt)
{
	uint8_t h;

	/* Stop the clock */
	rtc_write(RTC_CONTROL, rtc_read(RTC_CONTROL) & ~RTC_START);

#ifdef RTC_DEBUG
	printf("Regs before:\n");
	rtc_print();
#endif

	rtc_write(RTC_SEC, bintobcd(dt->dt_sec));
	rtc_write(RTC_MIN, bintobcd(dt->dt_min));
	h = rtc_read(RTC_HRS);
	if ((h & 0x80) != 0) {
		/* time is am/pm format */
		if (dt->dt_hour == 0) {
			rtc_write(RTC_HRS, bintobcd(12) | 0x80);
		} else if (dt->dt_hour < 12) {
			/* am */
			rtc_write(RTC_HRS, bintobcd(dt->dt_hour) | 0x80);
		} else if (dt->dt_hour == 12) {
			rtc_write(RTC_HRS, bintobcd(12) | 0x80 | 0x20);
		} else {
			/* pm */
			rtc_write(RTC_HRS,
			    bintobcd(dt->dt_hour - 12) | 0x80 | 0x20);
		}
	} else {
		/* time is 24 hour format */
		rtc_write(RTC_HRS, bintobcd(dt->dt_hour));
	}
	rtc_write(RTC_DAY, bintobcd(dt->dt_wday));
	rtc_write(RTC_DATE, bintobcd(dt->dt_day));
	rtc_write(RTC_MON, bintobcd(dt->dt_mon));
	rtc_write(RTC_YR, bintobcd(dt->dt_year % 100));

#ifdef RTC_DEBUG
	printf("Regs after:\n");
	rtc_print();
#endif

	/* restart the clock */
	rtc_write(RTC_CONTROL, rtc_read(RTC_CONTROL) | RTC_START);
	return 0;
}

int
gettime_new(todr_chip_handle_t tch, struct timeval *tvp)
{
	tvp->tv_sec =
	    rtc_read(RTC_CNTR0) << 24 |
	    rtc_read(RTC_CNTR1) << 16 |
	    rtc_read(RTC_CNTR2) <<  8 |
	    rtc_read(RTC_CNTR3);
	return 0;
}

int
settime_new(todr_chip_handle_t tch, struct timeval *tvp)
{

	/* Stop the clock */
	rtc_write(RTC_CONTROL, rtc_read(RTC_CONTROL) & ~RTC_START);

#ifdef RTC_DEBUG
	printf("Setting RTC to 0x%08llx.  Regs before:\n", tvp->tv_sec);
	rtc_print();
#endif

	rtc_write(RTC_CNTR0, (tvp->tv_sec >> 24) & 0xff);
	rtc_write(RTC_CNTR1, (tvp->tv_sec >> 16) & 0xff);
	rtc_write(RTC_CNTR2, (tvp->tv_sec >>  8) & 0xff);
	rtc_write(RTC_CNTR3, (tvp->tv_sec) & 0xff);

#ifdef RTC_DEBUG
	printf("Regs after:\n");
	rtc_print();
#endif

	/* restart the clock */
	rtc_write(RTC_CONTROL, rtc_read(RTC_CONTROL) | RTC_START);

	return 0;
}
