static char rcsid[] = "$Header: cal.c,v 800.0 85/08/06 14:06:35 root Exp $";
static char sccsid[] = "%W% %Y% %Q% %G%";

/*
 * Calendar/Clock driver
 * National Semiconductor MM58174
 * For details see the applications notes.
 * On reads from clock returns a date character string in the form mmddhhmm.
 * On writes to clock expects a date character string in the form mmddhhmmyy.
 */

#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/uio.h"
#include "../white/config.h"


/* Calendar register addresses (also used as indices into array cal_reg),
 * OT = over ten, U = units, XT = times ten
 */

#define SEC_OT 	0x01			/* Read only */
#define SEC_U  	0x02			/* Read only */
#define SEC_XT 	0x03			/* Read only */
#define MIN_U  	0x04
#define MIN_XT 	0x05
#define HOUR_U 	0x06
#define HOUR_XT	0x07
#define DAY_U  	0x08
#define DAY_XT 	0x09
#define DOW   	0x0A 			/* Day of week */
#define MNTH_U 	0x0B
#define MNTH_XT	0x0C
#define YR  	0x0D			/* Leap-year counter--write only */
#define ST_STP  0x0E			/* Start and stop clock */
#define STATUS  0x0F			/* Interrupt status (not used) */

#define MAP 	0x40			/* Select alternate memory map */
#define N_CS 	0x10			/* De-select chip */

#define START	0x01			/* Start and stop clock bits */
#define STOP	0x00

#define RETRIES  50		/* Retries on cal reg reads before give up */
#define BAD_READ 0xf		/* Cal reg value returned on invalid read */

#define	WRITE_LN 10	/* Number of bytes required to write device */
#define	READ_LN	8	/* Number of bytes read from device before EOF */


static char *addr = (char *)0x30F81+IOBASE; /* For addressing registers */
static char *data = (char *)0x30C01+IOBASE; /* For read/write data to regs */

#define SETUP(ADDR) *addr = (ADDR)|MAP|N_CS; *addr = (ADDR)|MAP;
#define READ(VAL,ADDR) {SETUP((ADDR)); (VAL) = *data&0x0f; *addr = MAP|N_CS;}
#define WRITE(VAL, ADDR)  { SETUP((ADDR)); *data = (VAL); *addr = MAP|N_CS; }

#define ATOI(C) (0xf & ((C) - '0'))
#define ITOA(C) ((C) + '0')

int cal_access;			/* Restricts to one open at a time */
short cal_debug;		/* debugging flag */

unsigned char cal_reg[16];
char	wr_date[16];

calopen()
{
	if (cal_access++)
		return EBUSY;
	return(0);
}

calclose()
{
/* deselect chip */
	*addr = MAP|N_CS;
	cal_access = 0;
}


calread(dev, uio)
	dev_t	dev;
	struct uio *uio;
{
	int i, address;
	unsigned char cal_t1[16], cal_t2[16], *cal_reg, diff_t;
	char	rd_date[16];
	char *dp;
	int rcount;
	int tries;

	/*
	 * Force the read to be atomic
	 */
	if (uio->uio_offset || uio->uio_resid < READ_LN) {
		if (cal_debug)
			printf("calread: offset %d resid %d -- REJECT\n",
					uio->uio_offset, uio->uio_resid);
		return EINVAL;
	}

	/* get date from chip (twice for rollover) */
	for (address = MIN_U; address <= MNTH_XT; address++) {
		for (tries = 0; tries < RETRIES; tries++) {
			READ(cal_t1[address], address);
		 	if (cal_t1[address] != BAD_READ)
				break;
		}
	}

	for (address = MIN_U; address <= MNTH_XT; address++) {
		for (tries = 0; tries < RETRIES; tries++) {
			READ(cal_t2[address], address);
		 	if (cal_t2[address] != BAD_READ)
				break;
		}
	}

	/* If rollover during first read, use second read */
	cal_reg = cal_t1;
	for (address = MNTH_XT; address >= MIN_U; address--) {
		diff_t = cal_t2[address] - cal_t1[address];
		if (diff_t) {
			if (diff_t < 0) {
				cal_reg = cal_t2;
			}
			break;
		}
	}

	/* Transform read values into a UNIX date (mmddhhmm) */
	for (address = MNTH_XT, dp = rd_date; address >= MIN_U; address--) {
		if (address == DOW) continue;
		*dp++ = ITOA(cal_reg[address]);
	}
	*dp = '\0';
	if (cal_debug)
		printf("Copying out the date %s\n", rd_date);
	return uiomove(rd_date, READ_LN, UIO_READ, uio);
}


calwrite(dev, uio)
	dev_t	dev;
	struct uio *uio;
{
	register address, d;
 	char	*dp;
	int	error;

	if (!suser())
		return EPERM;
	/*
	 * Range-check the user's write request; if ok, read it in
	 * expecting a string of the form mmddhhmm[yy]
	 * Writes must be atomic, sorry
	 */
	if (uio->uio_offset || uio->uio_resid < WRITE_LN) {
		if (cal_debug)
			printf("cal: uio_offset %d uio_resid %d REJECT\n",
				uio->uio_offset, uio->uio_resid);
		return EINVAL;
	}
	if (error = uiomove(wr_date, WRITE_LN, UIO_WRITE, uio))
		return error;

	/* Convert input date string into calendar register form */
	for (address = MNTH_XT, dp = wr_date; address >= MIN_U; address--) {
		if (address == DOW) {
			cal_reg[DOW] = 0;
			continue;
		}
		cal_reg[address] = ATOI(*dp++);
	}
#ifdef notdef		/* corvus -- "cannot work" */
	cal_reg[YR] = 0x08 >> ((10*ATOI(*dp++) + ATOI(*dp++)) % 4);
#else notdef
	d = 10 * ATOI(*dp++);
	d += ATOI(*dp++);
	d &= 3;
	cal_reg[YR] = 8 >> d;
#endif notdef

	/* Check for valid input */
	d = cal_reg[MNTH_U] + cal_reg[MNTH_XT]*10;
	if (d > 12 || d < 1)
		return(EINVAL);
	d = cal_reg[DAY_U] + cal_reg[DAY_XT]*10;
	if (d > 31 || d < 1)
		return(EINVAL);
	d = cal_reg[HOUR_U] + cal_reg[HOUR_XT]*10;
	if (d > 24 || d < 0)
		return(EINVAL);
	d = cal_reg[MIN_U] + cal_reg[MIN_XT]*10;
	if (d > 59 || d < 0)
		return(EINVAL);

	/*
	 * Everything's ok.  Stop the clock, load the new date, and restart.
	 */
	WRITE(STOP, ST_STP);
	for (address = MIN_U; address <= YR; address++)
		WRITE(cal_reg[address], address);
	WRITE(START, ST_STP);
	return(0);
}


/*
 * untested
 */
calioctl(dev, cmd, addr, flag)
	dev_t dev;
	caddr_t addr;
{
	char c;

	switch(cmd) {
	case CALIOCRESTART:
		WRITE(0, STATUS);
		READ(c, STATUS);
		READ(c, STATUS);
		READ(c, STATUS);
		WRITE(0, 0);
		WRITE(0, ST_STP);
		WRITE(1, ST_STP);
		break;
	default:
		return ENOTTY;	/* what else ? */
	}
	return 0;
}
