/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * toy clock
 *
 * Arguments:
 *	ctrl toy csr_base
 */

# include "proc.h"

RCSID("$Id: dev_toy.c 478 2002-01-29 15:30:38Z hbb $")

# define D(X)

typedef struct TOY TOY;

/*
 * Keep it simple: in start we keep the hosts number of seconds, when the
 * toy was last set (or when it was started). clock keeps the value it was
 * set to. If we read the clock, we look at the current host number of seconds,
 * compute how many have elapsed and add this to the 'clock'.
 */
struct TOY {
	u_int csr_base;

	u_short	csr;
	u_xquad_t code;
	int	state;
	u_xquad_t value;

	u_xquad_t clock;	/* the toy, as set by the user */
	u_xquad_t start;	/* the clock at start time */
	int	cnt;

	int	local;		/* option */
	int	offs_year;	/* offset to host clock */
	char	*settoy;	/* an initial settoy */
};

enum {
	TOY_WAITI	= 0,
	TOY_INIT	= 1,
	TOY_READ	= 2,
	TOY_WRITE	= 3,
};

void	toy_ctrl_create(iodev_t *, int, char **);
void	toy_ctrl_complete(iodev_t *);
u_short	toy_fetch(iodev_t *, u_int);
void	toy_store(iodev_t *, u_int, mmode_t, u_short);
void	toy_info(iodev_t *);
void	toy_command(iodev_t *, int, char **);

static int	toyc_info(iodev_t *, int argc, char **);


iocmd_t toy_cmds[] = {
	{ "?",		"[command ...]",	dev_help },
	{ "help",	"[command ...]",	dev_help },
	{ "info",	"",			toyc_info },
	{ NULL,		NULL,			NULL }
};

ioops_t	toy_ops = {
	toy_ctrl_create,	/* ctrl_create */
	0,			/* dev_create */
	toy_ctrl_complete,	/* ctrl_complete */
	0,			/* reset */
	toy_fetch,		/* fetch */
	toy_store,		/* store */
	0,			/* vector */
	0,			/* dma */
	0,			/* onsig */
	toy_info,		/* info */
	0,			/* flush */
	toy_cmds,		/* cmds */
};

static u_xquad_t conv2bcd(u_xquad_t, int);
static u_xquad_t toyset(u_xquad_t, int);

opt_t toy_opts[] = {
	{ "localtime",		OptFlag,	offsetof(TOY, local)},
	{ "offs_year",		OptInt,		offsetof(TOY, offs_year)},
	{ "settoy",		OptString,	offsetof(TOY, settoy)},
	{ NULL, 0, 0}
};

/*
 * create controller for parallel interface
 * - nothing to do
 */
void
toy_ctrl_create(iodev_t *dev, int argc, char **argv)
{
	TOY *toy;

	if(argc != 1)
		conf_panic("toy: need 1 arg in controller configuration");
	toy = dev->data = xalloc(sizeof(TOY));
	(void)memset(toy, 0, sizeof(TOY));

	toy->csr_base = parse_csr(argv[0], "toy");
	toy->code = 0;
	toy->state = TOY_WAITI;

	proc.iopage[IOP(toy->csr_base+0)] = dev;
}

void	
toy_ctrl_complete(iodev_t *dev)
{
	TOY *toy = (TOY *)dev->data;
	struct timeval tp;
	struct tm *tm;
	time_t cv;
	int n;

	gettimeofday(&tp, NULL);

	toy->clock = tp.tv_sec;
	toy->start = tp.tv_sec;

	if(toy->settoy != 0) {
		cv = toy->clock;
		tm = localtime(&cv);
		n = sscanf(toy->settoy, "%d:%d:%d %d/%d/%d",
			&tm->tm_hour, &tm->tm_min, &tm->tm_sec,
			&tm->tm_mon, &tm->tm_mday, &tm->tm_year);
		if(n != 6) {
			conf_warning("bad settoy string '%s'", toy->settoy);
		} else {
			tm->tm_mon++;
			if(tm->tm_year >= 1900)
				tm->tm_year -= 1900;
			toy->clock = mktime(tm);
		}
	}

	if(toy->offs_year != 0) {
		/*
		 * Do this heuristic. Convert to localtime, subtract and
		 * convert back.
		 */
		cv = toy->clock;
		tm = localtime(&cv);
		if(tm->tm_year < toy->offs_year) {
			conf_warning("year offset for toy too large");
		} else {
			tm->tm_year -= toy->offs_year;
			toy->clock = mktime(tm);
		}
	}
}

/*
 * fetch from csr/buf
 */
u_short	
toy_fetch(iodev_t *dev, u_int a)
{
	TOY *toy = dev->data;
	struct timeval tp;
	u_xquad_t diff;

	if(a == toy->csr_base) {
		D(printf("toy_fetch: state=%d\n", toy->state));
		switch(toy->state) {

		  case TOY_WAITI:	/* waiting for init sequence */
		  case TOY_WRITE:
			return toy->csr;

		  case TOY_INIT:	/* first read after init -> read clock */
			gettimeofday(&tp, NULL);
			diff = tp.tv_sec - toy->start;
			toy->value = conv2bcd(toy->clock + diff, toy->local);
			toy->state = TOY_READ;

		  case TOY_READ:	/* next read */
# if 0
			printf("reading bit %d\n", (int)(toy->value & 1));
# endif
			toy->csr = (toy->csr & 0xfeff) | ((toy->value & 1) << 8);
			toy->value >>= 1;
			return toy->csr;
		}
	}
	D(printf("toy_fetch(%o)\n", a);)
	Trap4(0100);
}

void	
toy_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	TOY *toy = dev->data;
	struct timeval tp;

	if(a == toy->csr_base) {
		D(printf("toy_store: %06u %d state=%d\n", v, mode, toy->state));
		if(toy->state == TOY_READ) {
			toy->state = TOY_WAITI;
			toy->code = 0;
		}

		SETWORD(toy->csr, mode, v);

		switch(toy->state) {

		  case TOY_WAITI:	/* waiting for recognition code */
			toy->code = (toy->code >> 1)
				  | ((u_xquad_t)(toy->csr & 0x100) << 55);
			if(toy->code == ((056243ULL << 48) | (035305ULL << 32)
				       | (056243ULL << 16) | (035305ULL << 0)))
				toy->state = TOY_INIT;
			break;

		  case TOY_INIT:	/* code recognized - first write */
			toy->cnt = 0;
			toy->state = TOY_WRITE;
			toy->value = 0;

		  case TOY_WRITE:
			toy->cnt++;
			toy->value = (toy->value >> 1)
				  | ((u_xquad_t)(toy->csr & 0x100) << 55);
			if(toy->cnt == 64) {
				gettimeofday(&tp, NULL);
				toy->start = tp.tv_sec;
				toy->clock = toyset(toy->value, toy->local);
				toy->state = TOY_WAITI;
				toy->code = 0;
			}
			break;

		  case TOY_READ:
			toy->state = TOY_WAITI;
			toy->code = 0;
			break;
		}

		return;
	}
	printf("toy_store(%o)\n", a);
	Trap4(0100);
}

/*
 * print info about device
 */
void	
toy_info(iodev_t *dev)
{
	TOY *toy = dev->data;
	time_t t;

	printf("Time Of Day clock\n");
	printf("CSR at %08o\n", toy->csr_base);
	printf(" csr\t= %06o\n", toy->csr);
	printf(" code\t= %02lo%010lo%010lo\n",
		(u_long)(toy->code >> 60) & 017,
		(u_long)(toy->code >> 30) & 07777777777UL,
		(u_long)(toy->code >>  0) & 07777777777UL);
	printf(" state\t= %s\n",
		 (toy->state == TOY_WAITI) ? "WAITI"
		:(toy->state == TOY_INIT) ? "INIT"
		:(toy->state == TOY_READ) ? "READ"
		:(toy->state == TOY_WRITE) ? "WRITE"
		: "UPS!");
	printf(" value\t= %02lo%010lo%010lo\n",
		(u_long)(toy->value >> 60) & 017,
		(u_long)(toy->value >> 30) & 07777777777UL,
		(u_long)(toy->value >>  0) & 07777777777UL);
	t = (time_t)toy->clock;
	printf(" clock\t= %02lo%010lo%010lo %s",
		(u_long)(toy->clock >> 60) & 017,
		(u_long)(toy->clock >> 30) & 07777777777UL,
		(u_long)(toy->clock >>  0) & 07777777777UL,
		ctime(&t));
	t = (time_t)toy->start;
	printf(" start\t= %02lo%010lo%010lo %s",
		(u_long)(toy->start >> 60) & 017,
		(u_long)(toy->start >> 30) & 07777777777UL,
		(u_long)(toy->start >>  0) & 07777777777UL,
		ctime(&t));
	printf(" cnt\t= %d\n", toy->cnt);
	printf(" local\t= %s\n", toy->local ? "yes" : "no");
}

static int
toyc_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	toy_info(dev);
	return 0;
}

static u_int
bcd(u_int v)
{
	return (((v / 10) % 10) << 4) | (v % 10);
}

static u_int
bcd2bin(u_int v)
{
	return ((v >> 4) & 0xf) * 10 + (v & 0xf);
}

/*
 * Convert the clock we are goin to return to the user to BCD.
 * Do UTC or local time depending on the option.
 */
static u_xquad_t
conv2bcd(u_xquad_t clk, int local)
{
	struct tm *tm;
	u_xquad_t ret;
	time_t c = clk;

	if(local)
		tm = localtime(&c);
	else
		tm = gmtime(&c);

	ret = (u_xquad_t)bcd(tm->tm_sec) << 8
	    | (u_xquad_t)bcd(tm->tm_min) << 16
	    | (u_xquad_t)bcd(tm->tm_hour) << 24
	    | (u_xquad_t)bcd(tm->tm_wday) << 32
	    | (u_xquad_t)bcd(tm->tm_mday) << 40
	    | (u_xquad_t)bcd(tm->tm_mon + 1) << 48
	    | (u_xquad_t)bcd(tm->tm_year % 100) << 56;
# if 0
	printf("%d/%d/%d %d:%d:%d -> %qx\n",
		tm->tm_mday, tm->tm_mon, tm->tm_year,
		tm->tm_hour, tm->tm_min, tm->tm_sec, ret);
# endif

	return ret;
}

/*
 * Set the toy to the user qualified value. This is either UTC or localtime.
 */
static u_xquad_t
toyset(u_xquad_t val, int local)
{
	struct tm tm, loctm, utctm;
	time_t u, locu, utcu;
	long diff;

# if 0
	printf("VAL: %qx\n", val);
# endif
	tm.tm_sec = bcd2bin(val >> 8);
	tm.tm_min = bcd2bin(val >> 16);
	tm.tm_hour = bcd2bin(val >> 24);
	tm.tm_mday = bcd2bin(val >> 40);
	tm.tm_mon = bcd2bin(val >> 48) - 1;
	tm.tm_year = bcd2bin(val >> 56);
	tm.tm_isdst = 0;
	if(tm.tm_year < 70)
		tm.tm_year += 100;

# if 0
	printf("%d/%d/%d %d:%d:%d\n",
		tm.tm_mday, tm.tm_mon, tm.tm_year,
		tm.tm_hour, tm.tm_min, tm.tm_sec);
# endif

	/*
	 * mktime() 'corrects' the time according to the current
	 * timezone. In order to correct this correction, we need
	 * the current time offset to UTC. There is no standard way
	 * to get this! So try the following heuristic (this is likely
	 * to give the wrong result during the daylight saving time switch,
	 * so don't change the toy clock at that time :-)
	 */
	/* interpret the value according to local time. Later on we will
	 * correct this value */
	tm.tm_isdst = -1;
	u = mktime(&tm);
# if 0
	{
		printf("----> mktime()=%lu: %s", u, ctime(&u));
	}
# endif

	if(local)
		return u;

	/* now interpret this time as localtime again. Well, loctm should
	 * probably be the same as tm after this. */
	loctm = *localtime(&u);

	/* and as UTC time */
	utctm = *gmtime(&u);

	/* the difference between loctm and utctm is what we need, so let's
	 * both of them convert back with the same rule. */
	utctm.tm_isdst = loctm.tm_isdst;

	locu = mktime(&loctm);
	utcu = mktime(&utctm);

	diff = (long)difftime(locu, utcu);

# if 0
	printf("diff=%ld\n", diff);
# endif

	return u + diff;
}
