/*
	@(#)cpd.c	1.3	(2/20/85 09:57:33)

	Standalone driver for the Unistar 300 Control Panel

	Copyright 1983 by Callan Data Systems, Inc.
*/

#include <sys/config.h>
#include "standalone.h"

/*
 *	Table of Contents:
 */
static void	cpdinit ();	/* Initialize the control panel driver */
int		cpdgetch ();	/* Get a character from the CPD */
void		cpdputch ();	/* Write a character to the CPD */
int		cpdkeypos ();	/* Get control panel key position */
int		cpdcheck ();	/* Check current panel status */

#define	CPANELADDR	((struct i2651 *) CPDCMDP_VA)

/*
 * Values to store in the cpd board status word 'cpdstatus'
 */
#define NOTINIT		0		/* Board not initialized */
#define NOBOARD	        (-1)		/* Board is not there at all */
#define ISINIT		1		/* Board has been initialized */

/*
 * Structure defining the 2651 USART.
 * Intel and Motorola do not agree on how to address bytes.
 * This means that all byte addresses to the MULTIBUS are byte
 * reversed. To do this, take every byte address and invert its
 * least significant bit. Thus 0x44 becomes 0x45 and so on.
 */
struct i2651				/* A single 8251 USART's registers */
    {
    unsigned char	i_stat;		/* +1 status */
    unsigned char	i_rdata;	/* +0 receive data  */
    unsigned char	i_cmdr;		/* +3 command read only */
    unsigned char	i_modr;		/* +2 mode - read only */
    unsigned char	i_sync;		/* +5 sync register */
    unsigned char	i_tdata;	/* +4 transmit data */
    unsigned char	i_cmdw;		/* +7 command write only */
    unsigned char	i_modw;		/* +6 mode write only */
    }; 	

/* Command register bits */

#define TXENABLE	0x01	/* Enable transmitter */
#define RXENABLE	0x04	/* Enable receiver    */
#define RESET		0x10	/* Reset error        */

/* Status register bits */

#define TXRDY		0x01	/* Transmit ready */
#define RXRDY		0x02	/* Receiver ready */
#define OVRNERR		0x10	/* Overrun error  */
#define FRAMERR		0x20	/* Framing error  */

/* Mode register 1 settings */

#define CPMODE1		0x4E	/* Asynchronous, 16 x rate, 8 bits, */
				/* ..no parity, 1 stop bit */

/* Mode register 2 settings */

#define CPMODE2		0x30	/* 50 baud, internal clocks */

/*
	Definitions relating to communications between the control panel
	and the boot program.
*/

/*
	ASCII character names
*/
#define ETX		0x03	/* End of text */
#define ENQ		0x05	/* Enquiry */
#define	ACK	        0x06	/* Acknowledgement */
#define BELL		0x07	/* Ring bell */
#define DC2		0x12	/* Device control 2 */
#define DC4		0x14	/* Device control 4 */
/*
	Messages from the CPU to the control panel
*/
#define	CPUALIVE	ACK	/* Sent to indicate the CPU is still running */
#define	KEY_POSITION    ENQ 	/* Sent to request the current key position */
#define SHUTDOWN	ETX 	/* Sent to cause 8748 to power system down */
#define	SOUND_BELL	BELL	/* Sent to ring the piezoelectric bell */
#define WDOG_ON		DC2	/* Turn watchdog timer on and run it */
#define WDOG_OFF	DC4	/* Turn watchdog timer off */

static int	cpdstatus = NOTINIT; /* Initialization status flag */

static void	cpdinit ()		/* Initialize control panel */
/*
	This routine is called to initialize the 2651 USART.  If the USART is
	nonexistent or not initialized, the routine simply exits. Otherwise,
	the USART is initialized as follows:

	asynchronous, 16 x rate, 8 bits, no parity, 1 stop bit
	50 baud, internal clocks for receiver and transmitter
*/
{
    register struct i2651 *addr;	/* Address of the board */
    register int cmd;			/* Scratch for reading command reg */

    addr = (struct i2651 *) CPANELADDR;
    if (cpdstatus == NOTINIT)
    {
	if (iocheck ((unsigned char *) addr, -1) >= 0) /* Does usart exist? */
	{
	    if ((addr->i_stat & 0x22) == 0x20) /* check for existence of panel*/
	    {	cpdstatus = NOBOARD;	/* Control panel ain't there! */
		return;
	    }
	    cpdstatus = ISINIT;
	    cmd = addr->i_cmdr;		/* Read to set internal pointer */
					/* ..to mode register 1 */
	    addr->i_modw = CPMODE1; 
  	    addr->i_modw = CPMODE2;	/* The internal pointer now points */
					/* ..to mode2 */
	    addr->i_cmdw = TXENABLE | RXENABLE; /* Enable rcvr and xmitter */
	}
	else				/* Come here if addr fault */
	    cpdstatus = NOBOARD;	/* Control panel ain't there! */
    }
    return;
}

int		cpdgetch (		/* Get a character from the CPD */
			waitflg)	/* Nonzero to wait for the char */
/*
	This routine reads a character from the control panel.  If
	"waitflg" is nonzero, it waits until a character is available
	before returning.  If "waitflg" is zero and no character is
	available, or if there is no control panel, -1 is returned.
*/
    int			waitflg;	/* Nonzero to wait for the char */
    {
    register struct i2651 *	addr;	/* Pointer to 2651 control regs */

    cpdinit ();
    if (cpdstatus == NOBOARD)		/* If no board, never return a char */
	return -1;
    addr = (struct i2651 *) CPANELADDR;
    while ((addr->i_stat & RXRDY) == 0)	/* Wait for a char */
	if (!waitflg)
	    return -1;			/* If none and no wait, quit */
    return (addr->i_rdata & 0xFF);	/* Got one, return it */
    }

void		cpdputch (		/* Write a character to the CPD */
			ch,		/* Character to be written */
			waitflg)	/* Nonzero to wait for the char */
/*
	This routine writes a character to the control panel.  If
	"waitflg" is nonzero, it waits until the panel is ready and
	transmits the character.  If "waitflg" is zero and the panel is
	not ready, or if the panel does not exist, the character is
	discarded and the routine returns immediately.
*/
    int			ch;		/* Character to be written */
    int			waitflg;	/* Nonzero to wait for the char */
    {
    register struct i2651 *	addr;	/* Pointer to 2651 control regs */

    cpdinit ();
    if (cpdstatus == NOBOARD)		/* If no board, toss char */
	return;
    addr = (struct i2651 *) CPANELADDR;
    while ((addr->i_stat & TXRDY) == 0)	/* Wait for a char */
	if (!waitflg)
	    return;			/* If none and no wait, quit */
    addr->i_tdata = ch;			/* Send the char */
    }

int		cpdkeypos ()		/* Get control panel key position */
/*
	This routine turns off the watchdog timer, reads the control panel
	key position, and returns an indicator of the position, as follows:

	No control panel available:	-1
	Key is in 'on' position:	 0
	Key is in 'maint' position:	 1
	Key is in 'off' position:	Immediate powerdown, no return
*/
    {
    register int	curkey;		/* Current key position */

    cpdputch (WDOG_OFF, 1);		/* Turn off the watchdog timer */
    cpdputch (KEY_POSITION, 1);		/* Request current key position */
    while (1)
	{
	curkey = cpdgetch (1);		/* Read current key position */
	if (curkey < 0)
	    return -1;			/* No control panel, give up */
	else if (curkey == KEY_OFF)
	    {
	    cpdputch (SHUTDOWN, 1);	/* Off, turn system off */
	    while (1)			/* And wait for it to happen */
		;
	    }
	else 
	    return curkey;
	}
    }

int
cpdcheck()
{
    register int curkey;		/* Current key position */

    curkey = cpdgetch(0);		/* Read current key position */
    if (curkey == KEY_OFF)
    {
	cpdputch (SHUTDOWN, 1);		/* Off, turn system off */
	while (1)			/* And wait for it to happen */
	    ;
    }
    else
	return curkey;
}
