/*
 * refclock_leitch.c - clock driver for Leitch 5300 Clock System Driver
 *	Ralph Siemsen <ralphs@netwinder.org> Nov 16, 2001
 *	based heavily on refclock_nmea.c
 *	Could be improved by using all three leitch timestamps.
 *
 * Leitch only provides two digits of year information, so this driver
 * currently assumes that the year is between 2000 and 2099.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_LEITCH)

#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"

#include <stdio.h>
#include <ctype.h>

/*
 * This driver supports the Leitch 5300 Clock System Driver
 * It is a total rewrite of the existing driver.  This one is
 * based heavily on refclock_nmea.c.
 *
 * COMMANDS:
 *      DATE:   D <CR>
 *      TIME:   T <CR>
 *      STATUS: S <CR>
 *      LOOP:   L <CR>
 *
 * FORMAT:
 *      DATE: YYMMDD<CR>
 *      TIME: <CR>/HHMMSS <CR>/HHMMSS <CR>/HHMMSS <CR>/
 *              second bondaried on the stop bit of the <CR>
 *              second boundaries at '/' above.
 *      STATUS: G (good), D (diag fail), T (time not provided) or
 *              P (last phone update failed)
 */

/*
 * Definitions
 */
# define DEVICE	"/dev/leitch%d"	/* name of radio device */
#define	SPEED232	B300	/* uart speed (300 bps) */
#define	PRECISION	(-9)	/* precision assumed (about 2 ms) */
#define	PPS_PRECISION	(-20)	/* precision assumed (about 1 us) */
#define	REFID		"ATOM"	/* reference id */
#define	DESCRIPTION	"Leitch 5300 Clock System Driver" /* who we are */

/*
 * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
 * leap.
 */
static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * Unit control structure
 */
struct leitchunit {
	short	state;		/* serial state machine counter */
	l_fp	tstamp;		/* timestamp of last poll */
	u_short	year, yearday;		/* date from last leitch poll */
	u_short	hour, minute, second;	/* time form last leitch poll */
};

/*
 * Possible states for leitchunit->state
 */
#define LEITCH_STATE_IDLE 0
#define LEITCH_STATE_DATE 1
#define LEITCH_STATE_TIME0 2
#define LEITCH_STATE_TIME1 3
#define LEITCH_STATE_TIME2 4
#define LEITCH_STATE_TIME3 5

/*
 * Function prototypes
 */
static	int	leitch_start	P((int, struct peer *));
static	void	leitch_shutdown	P((int, struct peer *));
static	void	leitch_receive	P((struct recvbuf *));
static	int	leitch_get_date	P((char *, struct peer *));
static	int	leitch_get_time	P((char *, struct peer *));
static	void	leitch_process	P((struct peer *, char *, int));
static	void	leitch_poll	P((int, struct peer *));
static	void	leitch_send	P((int, const char *, struct peer *));

/*
 * Transfer vector
 */
struct	refclock refclock_leitch = {
	leitch_start,		/* start up driver */
	leitch_shutdown,	/* shut down driver */
	leitch_poll,		/* transmit poll message */
	noentry,		/* fudge control */
	noentry,		/* initialize driver */
	noentry,		/* buginfo */
	NOFLAGS			/* not used */
};

/*
 * leitch_start - open the Leitch devices and initialize data for processing
 */
static int
leitch_start(
	int unit,
	struct peer *peer
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;
	int fd;
	char device[20];

	/*
	 * Open serial port. Use CLK line discipline, if available.
	 */
	(void)sprintf(device, DEVICE, unit);

	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
	    return (0);

	/*
	 * Allocate and initialize unit structure
	 */
	if (!(up = (struct leitchunit *)
	      emalloc(sizeof(struct leitchunit)))) {
		(void) close(fd);
		return (0);
	}
	memset((char *)up, 0, sizeof(struct leitchunit));
	pp = peer->procptr;
	pp->io.clock_recv = leitch_receive;
	pp->io.srcclock = (caddr_t)peer;
	pp->io.datalen = 0;
	pp->io.fd = fd;
	if (!io_addclock(&pp->io)) {
		(void) close(fd);
		free(up);
		return (0);
	}
	pp->unitptr = (caddr_t)up;

	/*
	 * Initialize miscellaneous variables
	 */
	peer->precision = PRECISION;
	pp->clockdesc = DESCRIPTION;
	memcpy((char *)&pp->refid, REFID, 4);
	up->state = LEITCH_STATE_IDLE;

	return (1);
}

/*
 * leitch_shutdown - shut down the Leitch clock
 */
static void
leitch_shutdown(
	int unit,
	struct peer *peer
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;
	io_closeclock(&pp->io);
	free(up);
}

/*
 * leitch_receive - receive data from the serial interface
 */
static void
leitch_receive(
	struct recvbuf *rbufp
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;
	struct peer *peer;

	/* Use these variables to hold data until we decide its worth keeping */
	char	rd_lastcode[BMAX];
	l_fp	rd_tmp;
	u_short	rd_lencode;

	/*
	 * Initialize pointers and read the timecode and timestamp
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;
	rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp);

#ifdef DEBUG
	if (debug)
	    printf("leitch: leitchread %d %s\n", rd_lencode,
		   rd_lastcode);
#endif

	/*
	 * We check the timecode format and decode its contents.
	 */
	switch(up->state) {
	
	case LEITCH_STATE_IDLE:
		/* Unexpected, discard */
		printf("RFS: unexpected call to leitch_receive\n");
		return;

	case LEITCH_STATE_DATE:
		/* Validate the date and store it into up->yearday */
		if (!leitch_get_date(rd_lastcode, peer)) {
			refclock_report(peer, CEVNT_BADREPLY);
			up->state = LEITCH_STATE_IDLE;
			return;
		}
		
		/* Advance to next state, reading the time */
		leitch_send(pp->io.fd,"T\r", peer);
		up->state = LEITCH_STATE_TIME0;
		break;
	
	case LEITCH_STATE_TIME0:
		/* Expect an empty line (only CR before the time) */
		if (rd_lencode != 0) {
			refclock_report(peer, CEVNT_BADREPLY);
			up->state = LEITCH_STATE_IDLE;
			printf("RFS: didn't get CR before timestamp\n");
		}
		up->state = LEITCH_STATE_TIME1;
		break;

	case LEITCH_STATE_TIME1:
	case LEITCH_STATE_TIME2:
	case LEITCH_STATE_TIME3:
		/* Validate time and store into up->TIME1 */
		if (!leitch_get_time(rd_lastcode, peer)) {
			refclock_report(peer, CEVNT_BADREPLY);
			return;
		}
		up->tstamp = rd_tmp;
		
		/* Advance to next state */
		if (up->state++ == LEITCH_STATE_TIME3) {

			/* Transfer time to pp struct and report to caller */
			leitch_process(peer, rd_lastcode, rd_lencode);
		
			/* Return state machine to idle */
			up->state = LEITCH_STATE_IDLE;
		}
		break;

	default:
		syslog(LOG_ERR, "leitch_receive: invalid state %d ", up->state);
	}
}

/*
 * Parse the date from the Leitch clock.
 * Returns 0 on error and -1 if successful.
 * Date is written to the *yearday parameter
 * NOTE: year value isn't really used.
 */
static int
leitch_get_date(
	char *rd_lastcode,
	struct peer *peer
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;
	int year, month, day;

	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;

	if (sscanf(rd_lastcode, "%02d%02d%02d", &year, &month, &day) != 3) {
		refclock_report(peer, CEVNT_BADREPLY);
		return 0;
	}

	if (month < 1 || month > 12 || day < 1) {
		refclock_report(peer, CEVNT_BADTIME);
		return 0;
	}

	/*
	 * Leitch only provides two digits of year.
	 * Simple-mindedly we'll assume it is after 2000.
	 */
	if (year > 99) {
		refclock_report(peer, CEVNT_BADTIME);
		return 0;
	}
	year = year + 2000;

	/*
	 * Determine day-of-year.
	 * Only works up until 2099.
	 */
	if (year % 4) {
		int i;
		if (day > day1tab[month - 1]) {
			refclock_report(peer, CEVNT_BADTIME);
			return 0;
		}
		for (i = 0; i < month - 1; i++)
		    day += day1tab[i];
	} else {
		int i;
		if (day > day2tab[month - 1]) {
			refclock_report(peer, CEVNT_BADTIME);
			return 0;
		}
		for (i = 0; i < month - 1; i++)
		    day += day2tab[i];
	}

	/* Store the results into leitchunit struct */
	up->year = year;
	up->yearday = day;
	return 1;
}

/*
 * Parse time from the leitch clock.
 * Returns 0 on error and 1 on success.
 */
static int
leitch_get_time(
	char *rd_lastcode,
	struct peer *peer
	)
{	
	register struct leitchunit *up;
	struct refclockproc *pp;
	int hour, minute, second;

	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;

	if (sscanf(rd_lastcode, "%02d%02d%02d", &hour, &minute, &second) != 3) {
		refclock_report(peer, CEVNT_BADREPLY);
		return 0;
	}

	if (hour > 23 || minute > 59 || second > 59) {
		refclock_report(peer, CEVNT_BADTIME);
		return 0;
	}

	up->hour = hour;
	up->minute = minute;
	up->second = second;
	return 1;
}

/*
 * Called when a complete time/date sequence has been read from leitch.
 * At this time, the last reported date/time and the timestamp are all
 * contained in fields of the leitchunit structure.
 */
static void
leitch_process(
	struct peer *peer,
	char *rd_lastcode,
	int rd_lencode
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;

#ifdef	DEBUG
	if (debug)
		printf("leitch_process: called for %d/%03d %02d:%02d:%02d\n",
			up->year, up->yearday, up->hour, up->minute, up->second);
#endif

	/* Copy last serial stream to pp, for logging purposes */
	pp->lencode = rd_lencode;
	strcpy(pp->a_lastcode,rd_lastcode);

	/* Set reception time stamp */
	pp->lastrec = up->tstamp;

	/* Copy time out of leitchunit, assume 0 milliseconds */
	pp->msec = 0;
	pp->second = up->second;
	pp->minute = up->minute;
	pp->hour = up->hour;
	pp->day = up->yearday;
	pp->year = up->year;

	/* Leap second notification is enabled by fudge time2 */
	pp->leap = LEAP_NOWARNING;
	if (pp->fudgetime2 > 0)
		pp->leap = LEAP_ADDSECOND;
	if (pp->fudgetime2 < 0)
		pp->leap = LEAP_DELSECOND;

	/*
	 * Process the new sample in the median filter and determine the
	 * reference clock offset and dispersion. We use lastrec as both
	 * the reference time and receive time, in order to avoid being
	 * cute, like setting the reference time later than the receive
	 * time, which may cause a paranoid protocol module to chuck out
	 * the data.
	 */
	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}

	refclock_receive(peer);

        /* If we get here - what we got from the clock is OK, so say so */
	refclock_report(peer, CEVNT_NOMINAL);

	record_clock_stats(&peer->srcadr, pp->a_lastcode);
}

/*
 * leitch_poll - called by the transmit procedure
 */
static void
leitch_poll(
	int unit,
	struct peer *peer
	)
{
	register struct leitchunit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct leitchunit *)pp->unitptr;

	if (up->state != LEITCH_STATE_IDLE) {
		refclock_report(peer, CEVNT_TIMEOUT);
		up->state = LEITCH_STATE_IDLE;
	} else {
		leitch_send(pp->io.fd,"D\r", peer);
		up->state = LEITCH_STATE_DATE;
	}
}

/*
 *	leitch_send(fd,cmd, peer)  send command to Leitch.
 */
static void
leitch_send(
	int fd,
	const char *cmd,
	struct peer *peer
	)
{
	if (write(fd, cmd, strlen(cmd)) == -1) {
		refclock_report(peer, CEVNT_FAULT);
	}
}

#else
int refclock_leitch_bs;
#endif /* REFCLOCK */

