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

/*
 * Synertek Asyncronous Communication Interface Adapter SY6551
 *
 *	Copyright 1983 Corvus Systems, San Jose Ca.
 */

/*
 * Modified:
 * 1) Removed software parity generation and replaced with hardware
 *	setting in acparam function
 * 2) case CS7 in acparam now causes character length to be specified as
 *	7 bits ( + one bit for parity for the total of 8 bits)	
 * 3) changed PMASK to include the "parity enable" bit
 * 4) call to ioctl (from stty) will now set parity
 */

/*
 * There are two acias. #0 is connected to an RS-232 (data terminal) adapter
 * which can be ported out of the box to provide a terminal.  #1 is connected
 * to a telephone jack for use by the Mouse Systems TTL mouse.
 */
#include "../h/param.h"			/* system definitions and constants */
#include "../h/conf.h"			/* system configuration */
#include "../h/errno.h"			/* system error numbers */
#include "../h/time.h"			/* system time definitions */
#include "../h/kernel.h"		/* kernel global variables */
#include "../h/ioctl.h"			/* general ioctl interface */
#include "../h/tty.h"			/* tty structures and flags */
#include "../s32/reentrant.h"		/* reentrant macro */
#include "../s32/vectors.h"		/* CPU interrupt vector definitions */
#include "../white/config.h"		/* I/O base address */
#include "../white/acia.h"		/* UART (acia) specific definitions */
#include "../white/via.h"		/* VIA defs (for modem operation) */

#define ACBASE		((struct acia_device *)(IOBASE+0x30F20))

#define ACCONSOLE	0		/* the terminal */
#define line(x)		((x)&0x7F)	/* XXX */
#define islocal(x)	(((x)&0x80)==0)	/* XXX */


	/* baud rate conversion table, from unix code to hardware code */
char ac_baud[] = {
	-1, X_B50, X_B75, X_B110, X_B134, X_B150, -1,
	X_B300, X_B600, X_B1200, X_B1800, X_B2400, X_B4800,
	X_B9600, X_B19200, -1
};

/* int overr; */

int	ac_cnt = 2;
struct tty ac_tty[2];
int	ac_addr[2] = { (int)ACBASE+0, (int)ACBASE+32 };
int	acstart(),
	acint();
char	partab[];
char	ac_noscan;


#define ACTIME (hz*10)

acinit() {
	IRQ5vector = acint;
#ifdef NOTYET
	actimer();
#endif NOTYET
}


/*
 * 840915 sbs
 *	if dev == 1, this is being called by mouse_ioctl;
 *	set speed to 1200 and don't call the l_open routine
 *	(mouse_ioctl initializes the line discipline)
 */
acopen(dev, flag)
	register dev_t dev;
{
	register struct tty *tp;
	register d, s;

	d = line(dev);
#ifdef HOWFAR
	printf("acopen: %d\n", d);
#endif
	if (d >= ac_cnt)
		return(ENXIO);
	tp = &ac_tty[d];
	tp->t_addr = 0;
	tp->t_oproc = acstart;
	s = spl6();	/* high enough to lock out clock as well as recv (sbs)*/
	if ((tp->t_state&TS_ISOPEN) == 0) {
		tp->t_state = TS_ISOPEN|TS_CARR_ON;
		ac_noscan++;	/* disable modem scanning for CLOCAL */
		ttychars(tp);
		tp->t_flags = EVENP|ODDP|ECHO|XTABS|CRMOD|
				CRTBS|CRTERA|CRTKIL|CTLECH;
		tp->t_ispeed = tp->t_ospeed = (d==1)?B1200:B9600;
		--ac_noscan;	/* reenable modem scanning */
		acline(d, 1);
		acparam(d);
		acscan(d);
	}
	splx(s);
	if (d != 1) {
		tp->t_line = NTTYDISC;	/* new tty line discipline */
		(*linesw[tp->t_line].l_open)(dev, tp);
	}
#ifdef HOWFAR
	printf("acopen: returning\n");
#endif
	return(0);
}


acclose(dev, flag)
	dev_t	dev;
{
	register struct tty *tp;
	register struct acia_device *addr;
	register d;

	d = line(dev);
	tp = &ac_tty[d];
	if (d != 1) {
		(*linesw[tp->t_line].l_close)(tp);
		ttyclose(tp);
	}
	addr = (struct acia_device *)ac_addr[d];

	/* Clear DTR line only if HUPCL is specified */
	if (tp->t_state & TS_HUPCLS)
		addr->ac_cmd = C_RIND|C_XIN2;
	else 
		addr->ac_cmd = (addr->ac_cmd & C_DTR) | C_RIND|C_XIN2;
}


acread(dev, uio)
	dev_t	dev;
	struct uio *uio;
{
	register struct tty *tp;

	tp = &ac_tty[line(dev)];
	return((*linesw[tp->t_line].l_read)(tp, uio));
}


acwrite(dev, uio)
	dev_t	dev;
	struct uio *uio;
{
	register struct tty *tp;

	tp = &ac_tty[line(dev)];
	return((*linesw[tp->t_line].l_write)(tp, uio));
}


acline(d, f) {
	struct acia_device *addr;

	addr = (struct acia_device *)ac_addr[d];
	addr->ac_cmd = f? C_DTR|C_XIN2: C_RIND|C_XIN2;
}


/*
 * Interrupt catcher
 *	The 6551 clears a pending interrupt whenever you read the
 *	status register.  This strange and wonderful property causes
 *	no end of wailing, gnashing of teeth, pulling out of hair,
 *	and horrible code kludges...
 */
reentrant(acint) {
	register struct acia_device *ac;
	register char st;

	ac = (struct acia_device *)ac_addr[0];
	st = ac->ac_stat;
	if (st&S_IRQ)
		acintr(0, st);
	ac = (struct acia_device *)ac_addr[1];
	st = ac->ac_stat;
	if (st&S_IRQ)
		acintr(1, st);
}


acintr(d, st)
	register d;
	register char st;
{
	register struct tty *tp;
	register struct acia_device *addr;
	register c;
	int	s;

	tp = &ac_tty[d];
	addr = (struct acia_device *)ac_addr[d];
	if (st&S_RRDY) {	/* character input: call linesw input routine */
		c = addr->ac_data;
		if (tp->t_state&TS_ISOPEN)
			(*linesw[tp->t_line].l_rint)(c, tp);
	}
	if (st&S_XEMP) {	/* output completed: call start */
		addr->ac_cmd = addr->ac_cmd&~C_XIN | C_XIN2;
		tp->t_state &= ~TS_BUSY;
		(*linesw[tp->t_line].l_start)(tp);
		if (tp->t_state&TS_ASLEEP && tp->t_outq.c_cc <= TTLOWAT(tp)) {
			tp->t_state &= ~TS_ASLEEP;
			wakeup((caddr_t)&tp->t_outq);
		}
	}
}


acioctl(dev, cmd, arg, mode)
	dev_t	dev;
	caddr_t	arg;
{
	register struct tty *tp;
	int	i, error;

	ac_noscan++;	/* disable modem scanning for CLOCAL */
	tp = &ac_tty[line(dev)];
	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, arg, mode);
	if (error >= 0)
		return(error);
	error = ttioctl(tp, cmd, arg, mode);
	if (error < 0)
		error = ENOTTY;
	acparam(line(dev));
	--ac_noscan;	/* reenable modem scanning */
	return(error);
}


acparam(d) {
	register struct tty *tp;
	register struct acia_device *addr;
	register baud, speed, parity;

	tp = &ac_tty[d];
	addr = (struct acia_device *)ac_addr[d];
	speed = tp->t_ospeed;
	if ((baud = ac_baud[speed]) < 0)
		baud = addr->ac_ctrl & X_BAUD;
	baud |= X_LEN8;
	parity = 0;
	baud |= X_STOP1;
	addr->ac_ctrl = baud | X_RCLK;
	addr->ac_cmd = (addr->ac_cmd & ~C_PMASK) | parity;
}


acstart(tp)
	register struct tty *tp;
{
	register struct acia_device *addr;
	register c;

	addr = (struct acia_device *)ac_addr[tp-ac_tty];
	if (tp->t_state&(TS_TIMEOUT|TS_BUSY|TS_TTSTOP) || tp->t_outq.c_cc==0)
		return;
	if (/* addr->ac_stat&S_XEMP && */(c = getc(&tp->t_outq)) >= 0) {
		addr->ac_data = c;
		addr->ac_cmd = addr->ac_cmd&~C_XIN | C_XIN1;
		tp->t_state |= TS_BUSY;
	}
}


mdint() {
	register short s,i;

	VIAADDR->v_rax ^= A_XOR;
	s = spl1();
	for(i = 0; i < ac_cnt; i++)
		acscan(i);
	splx(s);
}


actimer() {
	register short s,i;

	s = spl1();
	for(i = 0; i < ac_cnt; i++)
		acscan(i);
	splx(s);
	timeout(actimer, 0, ACTIME);
}


acscan(dev)
	dev_t dev;
{
#ifdef NOTYET
	register struct tty *tp;
	register bits;

	if (ac_noscan)	/* skip scanning if flagged */
		return;
	tp = &ac_tty[dev];
	if (dev == 0) {
		bits = A_DCD0|A_DSR0;
	} else {
		bits = A_DCD1|A_DSR1;
	}
	if ((VIAADDR->v_rax & bits) == 0) {
		bits = spl5();
		if ((tp->t_state&CARR_ON) == 0) {
			tp->t_state |= CARR_ON;
			if (tp->t_state & WOPEN) {
				wakeup((caddr_t)&tp->t_rawq);
			}
		}
		splx(bits);
	} else {
		if (tp->t_state&CARR_ON && !(tp->t_cflag & CLOCAL)) {
			tp->t_state &= ~CARR_ON;
			if (tp->t_state&ISOPEN) {
				ttyflush(tp, FREAD|FWRITE);
				signal(tp->t_pgrp, SIGHUP);
			}
		}
	}
#endif NOTYET
}


/*
 * Print a character on console.
 * Attempts to save and restore device
 * status.
 */
acputchar(c)
	register c;
{
	register struct acia_device *addr;
	register int timo;
	int s, cmd;

	addr = (struct acia_device *)ac_addr[ACCONSOLE];
	cmd = addr->ac_cmd;
	addr->ac_cmd |= C_DTR;	/* turn transmitter on */
	s = spl7();
	/*
	 * Try waiting for the console tty to come ready,
	 * otherwise give up after a reasonable time.
	 */
again:
	timo = 10000;
	while ((addr->ac_stat&S_XEMP) == 0 && timo--)
		;
	if (c == 0)
		goto out;
	addr->ac_data = c;
	if (c == '\n')
		c = '\r';
	else
		c = 0;
	goto again;
out:
	addr->ac_cmd = cmd;		/* set it back */
	splx(s);
}
