static char rcsid[] = "$Header: sc.c,v 820.1 86/12/04 19:56:26 root Exp $";
static char sccsid[]="%W% %Y% %Q% %G%";

/************************************************************************
*									*
*				Copyright 1984				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*
 * Signetics 2661 Enhanced Programmable Communications Interface.
 */

#include "sc.h"
#if NSC > 0
#include "log.h"
#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/conf.h"
#include "../h/file.h"
#include "../h/kernel.h"
#include "../h/pdma.h"
#include "../h/uio.h"
#include "../s32/vectors.h"
#include "../s32/cpu.h"
#include "../s32dev/console.h"
#include "../s32dev/mbvar.h"
#include "../s32dev/screg.h"

int screset(), scprobe(), scattach();
struct mb_device *scinfo[NSC];
u_short scstd[] = { 0xff, 0 };		/* token */
struct mb_driver scdriver =
			{ screset, scprobe, 0, scattach, scstd, "sc", scinfo };

char sc_baud[] = { 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 10, 12, 14, 15, 15 };

char sc_db[NLINE * NSC] = { B9600, B9600 };
struct tty sc_tty[NLINE * NSC];
struct pdma sc_pdma[NLINE * NSC];
struct pdma *sc_pdmap[NSC];		/* find sc_pdma by unit number */
int sc_softCAR[NSC];

#define ADDR(tp) ((struct scdevice *)((struct pdma *)tp->t_addr)->p_addr)

/*ARGSUSED*/
screset(reg)
	caddr_t reg;
{
	CADDR->sccr &= ~(SCCR_RXEN|SCCR_TXEN);	/* kill console Rx/Tx intr */
	CADDR->sccr |= SCCR_CLR;		/* kill console error intr */
	HADDR->sccr &= ~(SCCR_RXEN|SCCR_TXEN);	/* kill host Rx/Tx intr */
	HADDR->sccr |= SCCR_CLR;		/* kill host error intr */
}

/*ARGSUSED*/
scprobe(reg, intr, unit)
	caddr_t reg;
	int (*intr)();
{
	register i;
	register struct scdevice *addr = CADDR;
	int scintr();

	if (intr != scintr)
		return 0;
	for (i = 0; i < NSC; i++)
		if (scinfo[i] && scinfo[i]->md_alive &&
		    scinfo[i]->md_addr == reg)
			return 0;
	addr->sccr = SCCR_NORMAL|SCCR_TXEN|SCCR_RTS;
	for (i = 100000; --i >= 0 && (addr->scsr & SCSR_TXRDY) == 0;)
		;
	addr->sccr = SCCR_NORMAL;
	if (i < 0)
		return 0;
	return sizeof (struct scdevice);
}

/*ARGUSED*/
scattach(md)
	struct mb_device *md;
{
	register i;
	register struct pdma *dp = sc_pdma + md->md_unit * NLINE;
	register struct tty *tp = sc_tty + md->md_unit * NLINE;
	int scxint();
	int scscan();
	static char scan;

	sc_db[CONSOLE] = scgetbaudrate(CADDR);
#ifndef	M68020
	V_STATUS &= ~V_ENBKDET;		/* turn off break detect */
#endif	M68020
	sc_pdmap[md->md_unit] = dp;
	sc_softCAR[md->md_unit] = md->md_flags;
	for (i = 0; i < NLINE; i++, tp++, dp++) {
		/*
		 * Set up pdma data structures so stray interrupts
		 * won't crash the system.  The assembly code
		 * should call scxint() immediately which just
		 * turns off transmit interrupt.
		 * In any case, we turn the port off; but leave console
		 * receiver on, for break debug.
		 */
		tp->t_addr = (caddr_t)dp;
		dp->p_addr = (struct device *)SCADDR(i + md->md_unit * NLINE);
		dp->p_mem = dp->p_end = 0;
		dp->p_fcn = scxint;
		dp->p_arg = (int)tp;
		ADDR(tp)->sccr = SCCR_NORMAL |
			(i == CONSOLE ? SCCR_RXEN|SCCR_RTS : 0);
	}
	if (!scan) {
		scan = 1;
		timeout(scscan, (caddr_t)0, hz);
	}
}

/*ARGSUSED*/
scopen(dev, flag)
	dev_t dev;
{
	int line = minor(dev);
	int unit = line / NLINE;
	register struct tty *tp = &sc_tty[line];
	int error;
	int scstart();

	if (unit >= NSC || scinfo[unit] == 0 || !scinfo[unit]->md_alive)
		return ENXIO;
	if (tp->t_state & TS_XCLUDE && u.u_uid != 0)
		return EBUSY;
	if ((tp->t_state & (TS_ISOPEN|TS_WOPEN)) == 0) {
		tp->t_oproc = scstart;
		tp->t_flags = ECHO|XTABS|CRMOD|CRTBS|CRTERA|CRTKIL|CTLECH;
		ttychars(tp);
		tp->t_ispeed = tp->t_ospeed = sc_db[line];
		tp->t_line = NTTYDISC;
		ADDR(tp)->sccr = SCCR_DTR|SCCR_RTS|SCCR_RXEN;
		scparam(tp);
	}
	(void) spl5();
	while ((tp->t_state & TS_CARR_ON) == 0) {
		tp->t_state |= TS_WOPEN;
		sleep((caddr_t)&tp->t_rawq, TTIPRI);
	}
	error = (*linesw[tp->t_line].l_open)(dev, tp);
	(void) spl0();
	return error;
}

scclose(dev)
	dev_t dev;
{
	register struct tty *tp = &sc_tty[minor(dev)];
	struct scdevice *addr = ADDR(tp);
	int error;

	(*linesw[tp->t_line].l_close)(tp);
	addr->sccr &= ~SCCR_BRK;
	error = ttyclose(tp);			/* drain output first */
	addr->sccr &= ~(SCCR_DTR|SCCR_RTS|SCCR_RXEN);
	return error;
}

scread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register struct tty *tp = &sc_tty[minor(dev)];

	return (*linesw[tp->t_line].l_read)(tp, uio);
}

scwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register struct tty *tp = &sc_tty[minor(dev)];

	return (*linesw[tp->t_line].l_write)(tp, uio);
}

/*
 * Pseudo-dma code.
 */
asm(".p_addr = 0");
asm(".p_mem = 4");
asm(".p_end = 8");
asm(".p_arg = 12");
asm(".p_fcn = 16");
asm(".p_size = 20");		/* sizeof (struct pdma) */
asm(".nline = 2");
asm(".scdr = 1");
asm(".scsr = 3");
asm(".txrdy = 0");		/* bit number of TXRDY */
asm(".rxrdy = 1");
asm("	.text ");
asm("	.globl	scintr");
asm("scintr:");
	asm("	movl	sp@(4),d0");
	asm("	moveml	#0x3030,sp@-");		/* d2, d3, a2, a3 */
	asm("	clrl	d2");
	asm("	movw	#.nline-1,d3");
	asm("	asll	#2,d0");
	asm("	movl	#sc_pdmap,a2");
	asm("	movl	a2@(0,d0:l),a2");
	asm(".loop:");
	asm("	movl	a2@(.p_addr),a3");
	asm("	btst	#.rxrdy,a3@(.scsr)");
	asm("	beqs	.scintr0");
	asm("	movb	#1,d2");
	asm("	movl	a2@(.p_arg),sp@-");	/* call scrint */
	asm("	jsr	scrint");
	asm("	addql	#4,sp");
	asm(".scintr0:");
	asm("	btst	#.txrdy,a3@(.scsr)");
	asm("	beqs	.scintr2");
	asm("	movb	#1,d2");
	asm("	movl	a2@(.p_mem),a0");	/* check pdma */
	asm("	cmpl	a2@(.p_end),a0");
	asm("	bcss	.scintr1");
	asm("	movl	a2@(.p_arg),sp@-");	/* call scxint */
	asm("	movl	a2@(.p_fcn),a0");
	asm("	jsr	a0@");
	asm("	addql	#4,sp");
	asm("	bras	.scintr2");
	asm(".scintr1:");
	asm("	movb	a0@+,a3@(.scdr)");
	asm("	movl	a0,a2@(.p_mem)");
	asm(".scintr2:");
	asm("	addl	#.p_size,a2");
	asm("	dbra	d3,.loop");
	asm("	movl	d2,d0");
	asm("	moveml	sp@+,#0x0c0c");		/* a3, a2, d3, d2 */
	asm("	rts");

scrint(tp)
	register struct tty *tp;
{
	register struct scdevice *addr = ADDR(tp);
	register c;
	extern char brkdebug;

	while (addr->scsr & SCSR_RXRDY) {
		c = addr->scdr;
		if (addr->scsr & SCSR_FE) {
			addr->sccr |= SCCR_CLR;
			if (brkdebug && tp == sc_tty + CONSOLE && c == 0) {
				breakkey();
				continue;
			}
			c = tp->t_flags & RAW ? 0 : tp->t_intrc;
		}
		if (constate == CON_UNASSIGNED && concandidate(tp->t_dev))
			conassign(tp);
		(*linesw[tp->t_line].l_rint)(c, tp);
	}
}

scxint(tp)
	register struct tty *tp;
{
	register struct pdma *dp = (struct pdma *)tp->t_addr;

	((struct scdevice *)dp->p_addr)->sccr &= ~SCCR_TXEN;
	if ((tp->t_state & TS_BUSY) == 0) {
		printf("sc%d: spurious interrupt.\n", minor(tp->t_dev));
		return;
	}
	tp->t_state &= ~TS_BUSY;
	if (tp->t_state & TS_FLUSH)
		tp->t_state &= ~TS_FLUSH;
	else {
		ndflush(&tp->t_outq, dp->p_mem - tp->t_outq.c_cf);
		dp->p_end = dp->p_mem = 0;
	}
	(*linesw[tp->t_line].l_start)(tp);
}

scstart(tp)
	register struct tty *tp;
{
	register struct pdma *dp = (struct pdma *)tp->t_addr;
	register c, n;
	int ttrstrt();

	if (tp->t_state & (TS_TIMEOUT|TS_BUSY|TS_TTSTOP))
		return;
	if (tp->t_outq.c_cc <= TTLOWAT(tp)) {
		if (tp->t_state & TS_ASLEEP) {
			tp->t_state &= ~TS_ASLEEP;
			wakeup((caddr_t)&tp->t_outq);
		}
		if (tp->t_wsel) {
			selwakeup(tp->t_wsel, tp->t_state & TS_WCOLL);
			tp->t_wsel = 0;
			tp->t_state &= ~TS_WCOLL;
		}
	}
	if (tp->t_outq.c_cc == 0)
		return;
	if (tp->t_flags & (RAW|LITOUT))
		n = ndqb(&tp->t_outq, 0);
	else {
		n = ndqb(&tp->t_outq, 0x80);
		if (n == 0) {
			c = getc(&tp->t_outq);
			timeout(ttrstrt, (caddr_t)tp, (c & 0x7f) + 6);
			tp->t_state |= TS_TIMEOUT;
			return;
		}
	}
#if NLOG > 0
	if (tp == sc_tty + CONSOLE)
		log_data(tp->t_outq.c_cf, n, 2);
#endif
	c = getc(&tp->t_outq);
	n--;
	dp->p_mem = tp->t_outq.c_cf;
	dp->p_end = dp->p_mem + n;
	((struct scdevice *)dp->p_addr)->sccr |= SCCR_TXEN;
	((struct scdevice *)dp->p_addr)->scdr = c;
	tp->t_state |= TS_BUSY;
}

/*ARGSUSED*/
scstop(tp, flag)
	register struct tty *tp;
{
	register struct pdma *dp = (struct pdma *)tp->t_addr;
	int s = spl5();

	if (tp->t_state & TS_BUSY) {
		dp->p_end = dp->p_mem;
		if ((tp->t_state & TS_TTSTOP) == 0)
			tp->t_state |= TS_FLUSH;
	}
	splx(s);
}

scioctl(dev, cmd, data, flag)
	caddr_t data;
	dev_t dev;
{
	int line = minor(dev);
	register struct tty *tp = sc_tty + line;
	register struct scdevice *addr = ADDR(tp);
	register i;
	int error;

	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag);
	if (error >= 0)
		return error;
	error = ttioctl(tp, cmd, data, flag);
	if (error > 0)
		return error;
	if (error == 0) {
		switch (cmd) {
		case TIOCSETN:
		case TIOCSETP:
		case TIOCSET:
		case TIOCBIS:
		case TIOCBIC:
		case TIOCLSET:
		case TIOCLBIS:
		case TIOCLBIC:
			if (tp->t_ospeed == BDEFAULT)
				tp->t_ispeed = tp->t_ospeed = sc_db[line];
			scparam(tp);
			break;
		}
		return 0;
	}
	switch (cmd) {
	case TIOCSBRK:
		addr->sccr |= SCCR_BRK;
		break;
	case TIOCCBRK:
		addr->sccr &= ~SCCR_BRK;
		break;
	case TIOCSDTR:
		addr->sccr |= SCCR_DTR;
		break;
	case TIOCCDTR:
		addr->sccr &= ~SCCR_DTR;
		break;
	case TIOCMODG:
		i = TIOCM_LE;
		if (addr->sccr & SCCR_DTR)
			i |= TIOCM_DTR;
		if (addr->sccr & SCCR_RTS)
			i |= TIOCM_RTS;
		if (addr->scsr & SCSR_DSR)
			i |= TIOCM_CTS|TIOCM_DSR;
		if (addr->scsr & SCSR_DCD)
			i |= TIOCM_CAR;
		*(int *)data = i;
		break;
	case TIOCMODS:
		spl7();
		i = *(int *)data;
		addr->sccr = addr->sccr & (SCCR_RXEN|SCCR_TXEN) |
			(i & TIOCM_DTR ? SCCR_DTR : 0) |
			(i & TIOCM_RTS ? SCCR_RTS : 0);
		spl0();
		break;
	case TIOCMGET:		/* not supported */
	case TIOCMSET:
	case TIOCMBIS:
	case TIOCMBIC:
	default:
		return ENOTTY;
	}
	return 0;
}

scparam(tp)
	register struct tty *tp;
{
	register struct scdevice *addr = ADDR(tp);
	register i;
	int s = spl7();			/* for cnputc, otherwise spl5 */

	i = addr->sccr;			/* point to mr1 */
	i = SCMR1_NORMAL;
	switch (tp->t_ospeed) {
	case B110:
		i |= SCMR1_STOP2;
		break;
	case B134:
		i |= SCMR1_STOP1_5;
		break;
	default:
		i |= SCMR1_STOP1;
		break;
	}
	if (tp->t_flags&(RAW|LITOUT) || (tp->t_flags&ANYP) == 0)
		i |= SCMR1_LEN8;
	else {
		/* both EVENP|ODDP is not supported */
		i |= SCMR1_LEN7|SCMR1_PEN;
		if (tp->t_flags & EVENP)
			i |= SCMR1_PEVEN;
	}
	addr->scmr = i;						/* mr1 */
	addr->scmr = SCMR2_NORMAL | sc_baud[tp->t_ospeed];	/* mr2 */
	splx(s);
}

scscan()
{
	register bit, *c;
	register struct tty *tp;

	for (tp = sc_tty, c = sc_softCAR; c < sc_softCAR + NSC; c++)
		for (bit = 1; bit != 1 << NLINE; bit <<= 1, tp++) {
			if (tp->t_addr == 0)	/* not attached */
				continue;
			if (*c & bit || ADDR(tp)->scsr & (SCSR_DSR|SCSR_DCD)) {
				if ((tp->t_state & TS_CARR_ON) == 0) {
					wakeup((caddr_t)&tp->t_rawq);
					tp->t_state |= TS_CARR_ON;
				}
			} else
				if (tp->t_state & TS_CARR_ON &&
				    (tp->t_flags & NOHANG) == 0) {
					if (tp->t_state&TS_ISOPEN) {
						gsignal(tp->t_pgrp, SIGHUP);
						gsignal(tp->t_pgrp, SIGCONT);
					}
					tp->t_state &= ~TS_CARR_ON;
				}
		}
	timeout(scscan, (caddr_t)0, 2 * hz);
}

scgetbaudrate(addr)
	register struct scdevice *addr;
{
	register u_char baud;
	register int i;

	baud = addr->sccr;			/* reset to mr1 */
	baud = addr->scmr;			/* skip mr1 */
	baud = addr->scmr & SCMR2_BAUD;		/* get mr2 */
	for (i = 0; i < sizeof sc_baud / sizeof *sc_baud; i++)
		if (sc_baud[i] == baud)
			return i;
	return B9600;
}

cnputc(c)
	char c;
{
	register struct scdevice *addr = CADDR;
	register i;
	u_char sccr;
	int s;

	if (c == '\n')
		cnputc ('\r');
#if NLOG > 0
	log_data(&c, 1, 1);
#endif NLOG
	s = spl7();
	sccr = addr->sccr;
	addr->sccr = SCCR_NORMAL|SCCR_RTS|SCCR_DTR|SCCR_TXEN|SCCR_RXEN;
	for (i = 1000000; --i >= 0 && (addr->scsr & SCSR_TXRDY) == 0;)
		;
	addr->scdr = c;
	for (i = 1000000; --i >= 0 && (addr->scsr & SCSR_TXRDY) == 0;)
		;
	addr->sccr = sccr;
	splx(s);
}

getchar()
{
	register struct scdevice *addr = CADDR;
	register c;
	u_char sccr;
	int s;

	s = spl7();
	sccr = addr->sccr;
	addr->sccr = SCCR_NORMAL|SCCR_RTS|SCCR_DTR|SCCR_TXEN|SCCR_RXEN;
	while ((addr->scsr & SCSR_RXRDY) == 0)
		;
	c = addr->scdr & 0x7f;
	addr->sccr = sccr;
	splx(s);
	if (c == '\r')
		cnputc('\n');
	else
		cnputc(c);
	return c;
}

/*
 * Check for break debug.
 */
scbrk()
{
	if (CADDR->scsr & SCSR_FE && CADDR->scdr == 0) {
		CADDR->sccr |= SCCR_CLR;
		return 1;
	}
	return 0;
}
#endif
