/*
 * Copyright 1991-1998 by Open Software Foundation, Inc. 
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * OSF DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL OSF BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */
/*
 * MkLinux
 */
/* CMU_HIST */
/*
 * Revision 2.3  91/08/28  11:09:53  jsb
 * 	Fixed scc_scan to actually check the tp->state each time,
 * 	we are not notified when the MI code brutally zeroes it
 * 	on close and so we cannot update our software CARrier.
 * 	[91/08/27  16:18:05  af]
 * 
 * Revision 2.2  91/08/24  11:52:54  af
 * 	Created, from the Zilog specs:
 * 	"Z8530 SCC Serial Communications Controller, Product Specification"
 * 	in the "1983/84 Components Data Book" pp 409-429, September 1983
 * 	Zilog, Campbell, CA 95008
 * 	[91/06/28            af]
 * 
 */
/* CMU_ENDHIST */
/* 
 * Mach Operating System
 * Copyright (c) 1991,1990,1989 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 */
/*
 *	File: scc_8530_hdw.c
 * 	Author: Alessandro Forin, Carnegie Mellon University
 *	Date:	6/91
 *
 *	Hardware-level operations for the SCC Serial Line Driver
 */

#include <scc.h>
#if	NSCC > 0
#include <bm.h>
#include <platforms.h>

#include <mach_kdb.h>

#include <kern/spl.h>
#include <mach/std_types.h>
#include <sys/types.h>
#include <device/io_req.h>
#include <device/tty.h>

#include <chips/busses.h>
#include <chips/serial_defs.h>
#include <chips/screen_defs.h>

/* Alignment and padding */
#if	defined(DECSTATION)
/*
 * 3min's padding
 */
typedef struct {
	char			pad0;
	volatile unsigned char	datum;
	char			pad1[2];
} scc_padded1_register_t;

#define	scc_register_t	scc_padded1_register_t
#endif

#define SCC_KYBD  0
#define SCC_MOUSE 1
#define SCC_COMM1 2
#define SCC_COMM2 3

extern int kdebug_state();

#if	defined(__alpha)
typedef struct {
	volatile unsigned int	datum;
	unsigned int		pad1;
} scc_padded1_register_t;

#define	scc_register_t	scc_padded1_register_t

#define	scc_set_datum(d,v)	(d) = (volatile unsigned int) (v) << 8, wbflush()
#define	scc_get_datum(d,v)	(v) = ((d) >> 8) & 0xff

#endif

#include <chips/scc_8530.h>	/* needs the above defs */

/*
 * On the 3min keyboard and mouse come in on channels A
 * of the two units.  The MI code expects them at 'lines'
 * 0 and 1, respectively.  So we map here back and forth.
 * Note also the MI code believes unit 0 has four lines.
 */

#define	SCC_KBDUNIT	1
#define	SCC_PTRUNIT	0

mi_to_scc(unitp, linep)
	int	*unitp, *linep;
{
	/* only play games on MI 'unit' 0 */
	if (*unitp) {
		/* e.g. by mapping the first four lines specially */
		*unitp++;
		return;
	}

	/* always get unit=0 (console) and line = 0|1 */
	if (*linep == SCREEN_LINE_KEYBOARD) {
		*unitp = SCC_KBDUNIT;
		*linep = SCC_CHANNEL_A;
	} else if (*linep == SCREEN_LINE_POINTER) {
		*unitp = SCC_PTRUNIT;
		*linep = SCC_CHANNEL_A;
	} else {
		*unitp = (*linep & 1);
		*linep = SCC_CHANNEL_B;
	}
/* line 0 is channel B, line 1 is channel A */
}

#define	NSCC_LINE	2	/* 2 ttys per chip */

/* only care for mapping to ttyno */
scc_to_mi(sccunit, sccline)
{
	if (sccunit > 1)
		return (sccunit * NSCC_LINE + sccline);
	/* only for console (first pair of SCCs): */
	if (sccline == SCC_CHANNEL_A)
		return ((!sccunit) & 1);
	return 2+sccunit;
}


/*
 * Driver status
 */
struct scc_softc {
	scc_regmap_t	*regs;

	/* software copy of some write regs, for reg |= */
	struct softreg {
		unsigned char	wr1;
		unsigned char	wr4;
		unsigned char	wr5;
		unsigned char	wr14;
	} softr[2];	/* per channel */

	unsigned short	breaks;
	unsigned short	fake;	/* missing rs232 bits, channel A */
	char		polling_mode;
	char		softCAR, osoftCAR;
	char		probed_once;

	boolean_t	full_modem;
	boolean_t	isa_console;

} scc_softc_data[NSCC];

typedef struct scc_softc *scc_softc_t;

scc_softc_t	scc_softc[NSCC];

scc_softCAR(unit, line, on)
{
	mi_to_scc(&unit, &line);
	if (on)
		scc_softc[unit]->softCAR |= 1<<line;
	else
		scc_softc[unit]->softCAR &= ~(1 << line);
}


/*
 * BRG formula is:
 *				ClockFrequency
 *	BRGconstant = 	---------------------------  -  2
 *			2 * BaudRate * ClockDivider
 */
/* Speed selections with Pclk=7.3728Mhz, clock x16 */
static
short	scc_speeds[] =
	/* 0   50    75    110  134.5  150  200   300  600 1200 1800 2400 */
	{  0, 4606, 3070, 2093, 1711, 1534, 1150, 766, 382, 190, 126, 94,

	/* 4800 9600 19.2k 38.4k */
	  46,   22,  10,    4};

/*
 * Definition of the driver for the auto-configuration program.
 */

int	scc_probe(), scc_intr();
void	scc_attach( register struct bus_device *ui );

caddr_t	scc_std[NSCC] = { 0 };
struct	bus_device *scc_info[NSCC];
struct	bus_driver scc_driver = 
        { scc_probe, 0, scc_attach, 0, scc_std, "scc", scc_info,};

/*
 * Adapt/Probe/Attach functions
 */
boolean_t		scc_uses_modem_control = FALSE;/* patch this with adb */

set_scc_address( sccunit, regs, has_modem, isa_console)
	caddr_t		regs;
	boolean_t	has_modem;
	boolean_t	isa_console;
{
	extern int	scc_probe(), scc_param(), scc_start(),
			scc_putc(), scc_getc(),
			scc_pollc(), scc_mctl(), scc_softCAR();

	scc_std[sccunit] = regs;
	scc_softc_data[sccunit].full_modem = has_modem & scc_uses_modem_control;
	scc_softc_data[sccunit].isa_console = isa_console;

	/* Do this here */
	console_probe	= (int (*)(vm_offset_t, struct bus_device *))scc_probe;
	console_param		= scc_param;
	console_start		= scc_start;
	console_putc		= scc_putc;
	console_getc		= scc_getc;
	console_pollc		= scc_pollc;
	console_mctl		= scc_mctl;
	console_softCAR		= scc_softCAR;

}

scc_probe( xxx, ui)
	vm_offset_t xxx;
	struct bus_device *ui;
{
	int             sccunit = ui->unit;
	scc_softc_t     scc;
	register int	val;
	register scc_regmap_t	*regs;

	regs = (scc_regmap_t *)scc_std[sccunit];
	if (regs == 0)
		return 0;

	/*
	 * See if we are here
	 */
	if (check_memory(regs, 0)) {
		/* no rides today */
		return 0;
	}

	scc = &scc_softc_data[sccunit];

	if (scc->probed_once++){
		return 1;
	}
	/*
	 * Chip once-only initialization
	 *
	 * NOTE: The wiring we assume is the one on the 3min:
	 *
	 *	out	A-TxD	-->	TxD	keybd or mouse
	 *	in	A-RxD	-->	RxD	keybd or mouse
	 *	out	A-DTR~	-->	DTR	comm
	 *	out	A-RTS~	-->	RTS	comm
	 *	in	A-CTS~	-->	SI	comm
	 *	in	A-DCD~	-->	RI	comm
	 *	in	A-SYNCH~-->	DSR	comm
	 *	out	B-TxD	-->	TxD	comm
	 *	in	B-RxD	-->	RxD	comm
	 *	in	B-RxC	-->	TRxCB	comm
	 *	in	B-TxC	-->	RTxCB	comm
	 *	out	B-RTS~	-->	SS	comm
	 *	in	B-CTS~	-->	CTS	comm
	 *	in	B-DCD~	-->	CD	comm
	 */

	scc_softc[sccunit] = scc;
	scc->regs = regs;

	scc->fake = 1<<SCC_CHANNEL_A;

	{ 
		register int i;
		/* We need this in scc_start only, hence the funny
		   value: we need it non-zero and we want to avoid
		   too much overhead in getting to (scc,regs,line) */
		for (i = 0; i < NSCC_LINE; i++) {
			register struct tty	*tp;

			tp = console_tty[scc_to_mi(sccunit,i)];
			tp->t_addr = (char*)(0x80000000L + (sccunit<<1) + (i&1));
			/* do min buffering */
			tp->t_state |= TS_MIN;
		}
	}

#define IF_NOT_KDEBUG	if ( (sccunit == 0) && (kdebug_state() == 0) )

	/* make sure reg pointer is in known state */
	scc_init_reg(regs, SCC_CHANNEL_A);
	IF_NOT_KDEBUG scc_init_reg(regs, SCC_CHANNEL_B);

#ifdef notdef
	/* reset chip, fully */
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_WR9, SCC_WR9_HW_RESET);
	delay(50000);/*enough ? */
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_WR9, 0);
#endif
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_WR9, SCC_WR9_RESET_CHA_A)
	DELAY(20);
	IF_NOT_KDEBUG {
		scc_write_reg(regs, SCC_CHANNEL_B, SCC_WR9, SCC_WR9_RESET_CHA_B)
		DELAY(20);
	}

	/* program the interrupt vector */
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_WR2, 0xf0);
	IF_NOT_KDEBUG scc_write_reg(regs, SCC_CHANNEL_B, SCC_WR2, 0xf0);
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_WR9, SCC_WR9_VIS);

	/* most of the init is in scc_param() */

	/* timing base defaults */
	scc->softr[SCC_CHANNEL_A].wr4 = (SCC_WR4_CLK_x16 | SCC_WR4_1_STOP);
	IF_NOT_KDEBUG scc->softr[SCC_CHANNEL_B].wr4 = (SCC_WR4_CLK_x16 | SCC_WR4_1_STOP);

	/* enable DTR, RTS and SS */
	IF_NOT_KDEBUG scc->softr[SCC_CHANNEL_B].wr5 = SCC_WR5_RTS;
	scc->softr[SCC_CHANNEL_A].wr5 = SCC_WR5_RTS | SCC_WR5_DTR;

	/* baud rates */
	val = SCC_WR14_BAUDR_ENABLE|SCC_WR14_BAUDR_SRC;
	IF_NOT_KDEBUG scc->softr[SCC_CHANNEL_B].wr14 = val;
	scc->softr[SCC_CHANNEL_A].wr14 = val;

	/* interrupt conditions */
	val =	SCC_WR1_RXI_ALL_CHAR | SCC_WR1_PARITY_IE |
		SCC_WR1_EXT_IE | SCC_WR1_TX_IE;		
	scc->softr[SCC_CHANNEL_A].wr1 = val;
	IF_NOT_KDEBUG scc->softr[SCC_CHANNEL_B].wr1 = val;

	/*
	 * After probing, any line that should be active
	 * (keybd,mouse,rcline) is activated via scc_param().
	 */

	scc_set_modem_control(scc, scc->full_modem);

#if defined(KMIN) || defined (__alpha)
	/*
	 * Crock: MI code knows of unit 0 as console, we need
	 * unit 1 as well since the keyboard is there
	 * This is acceptable on maxine, which has to call its
	 * only one chip unit 1 so that rconsole is happy.
	 */
	if (sccunit == 0) {
		struct bus_device d;
		d = *ui;
		d.unit = 1;
		scc_probe( xxx, &d);
	}
#endif
	return 1;
}

boolean_t scc_timer_started = FALSE;

void
scc_attach( register struct bus_device *ui )
{
	int sccunit = ui->unit;
	extern scc_scan();
	extern int tty_inq_size;
	int i;

	/* We only have 4 ttys, but always at 9600
	 * Give em a lot of room (plus dma..)
	 */
	tty_inq_size = 4096;
	if (!scc_timer_started) {
		/* do all of them, before we call scc_scan() */
		/* harmless if done already */
		for (i = 0; i < NSCC*NSCC_LINE; i++) {
			if ( kdebug_state() && (i == SCC_COMM1) )
				continue;
			ttychars(console_tty[i]);
		}
		scc_timer_started = TRUE;
		scc_scan();
	}

#if	NBM > 0
	if (SCREEN_ISA_CONSOLE() && scc_softc[sccunit]->isa_console) {
		printf("\n sl0: ");
		if (sccunit && rcline == 3) printf("( rconsole )");

		if (sccunit == SCC_KBDUNIT) {
			printf("\n sl1: "); lk201_attach(0, sccunit >> 1);
		} else if (sccunit == SCC_PTRUNIT) {
			printf("\n sl1: "); mouse_attach(0, sccunit >> 1);
		}
	} else
#endif	/*NBM > 0*/
	{
		printf("%s", (sccunit == 1) ?
			"\n sl0: ( alternate console )\n sl1:" :
			"\n sl0:\n sl1:");
	}
}

/*
 * Would you like to make a phone call ?
 */
scc_set_modem_control(scc, on)
	scc_softc_t      scc;
	boolean_t	on;
{
	if (on)
		/* your problem if the hardware then is broke */
		scc->fake = 0;
	else
		scc->fake = 3;
	scc->full_modem = on;
	/* user should do an scc_param() ifchanged */
}

/*
 * Polled I/O (debugger)
 */
scc_pollc(unit, on)
	boolean_t		on;
{
	scc_softc_t		scc;
	int			line = SCREEN_LINE_KEYBOARD,
				sccunit = unit;

	mi_to_scc(&sccunit, &line);

	scc = scc_softc[sccunit];
	if (on) {
		scc->polling_mode++;
#if	NBM > 0
		screen_on_off(unit, TRUE);
#endif	/* NBM > 0 */
	} else
		scc->polling_mode--;
}

/*
 * Interrupt routine
 */
int scc_intr_count;

scc_intr(unit,spllevel)
	spl_t	spllevel;
{
	scc_softc_t		scc = scc_softc[unit];
	register scc_regmap_t	*regs = scc->regs;
	register int		rr1, rr2;
	register int		c;

scc_intr_count++;

#if	mips
	splx(spllevel);		/* lower priority */
#endif

next_intr:

	scc_read_reg(regs, SCC_CHANNEL_B, SCC_RR2, rr2);

	rr2 = SCC_RR2_STATUS(rr2);

	/* are we done yet ? */
	if (rr2 == 6) {	/* strange, distinguished value */
		register int rr3;
		scc_read_reg(regs, SCC_CHANNEL_A, SCC_RR3, rr3);
		if (rr3 == 0)
			return;
	}

	if ((rr2 == SCC_RR2_A_XMIT_DONE) || (rr2 == SCC_RR2_B_XMIT_DONE)) {

		register chan = (rr2 == SCC_RR2_A_XMIT_DONE) ?
					SCC_CHANNEL_A : SCC_CHANNEL_B;

		scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
		c = cons_simple_tint(scc_to_mi(unit,chan), FALSE);

		if (c == -1) {
			/* no more data for this line */

			scc_read_reg(regs, chan, SCC_RR15, c);
			c &= ~SCC_WR15_TX_UNDERRUN_IE;
			scc_write_reg(regs, chan, SCC_WR15, c);

			c = scc->softr[chan].wr1 & ~SCC_WR1_TX_IE;
			scc_write_reg(regs, chan, SCC_WR1, c);
			scc->softr[chan].wr1 = c;

			c = cons_simple_tint(scc_to_mi(unit,chan), TRUE);
			if (c != -1)
				/* funny race, scc_start has been called already */
				scc_write_data(regs, chan, c);
		} else {
			scc_write_data(regs, chan, c);
			/* and leave it enabled */
		}
	}

	else if (rr2 == SCC_RR2_A_RECV_DONE) {
		int	err = 0;

		scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
		if (scc->polling_mode)
			goto next_intr;

		scc_read_data(regs, SCC_CHANNEL_A, c);
		rr1 = scc_to_mi(unit,SCC_CHANNEL_A);
		cons_simple_rint (rr1, rr1, c, 0);
	}

	else if (rr2 == SCC_RR2_B_RECV_DONE) {
		int	err = 0;

		scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
		if (scc->polling_mode)
			goto next_intr;

		scc_read_data(regs, SCC_CHANNEL_B, c);
		rr1 = scc_to_mi(unit,SCC_CHANNEL_B);
		cons_simple_rint (rr1, rr1, c, 0);
	}

	else if ((rr2 == SCC_RR2_A_EXT_STATUS) || (rr2 == SCC_RR2_B_EXT_STATUS)) {
		int chan = (rr2 == SCC_RR2_A_EXT_STATUS) ?
			SCC_CHANNEL_A : SCC_CHANNEL_B;
		scc_write_reg(regs, chan, SCC_RR0, SCC_RESET_EXT_IP);
		scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
		scc_modem_intr(scc, chan);
	}

	else if ((rr2 == SCC_RR2_A_RECV_SPECIAL) || (rr2 == SCC_RR2_B_RECV_SPECIAL)) {
		register int chan = (rr2 == SCC_RR2_A_RECV_SPECIAL) ?
			SCC_CHANNEL_A : SCC_CHANNEL_B;

		scc_read_reg(regs, chan, SCC_RR1, rr1);
		if (rr1 & (SCC_RR1_PARITY_ERR | SCC_RR1_RX_OVERRUN | SCC_RR1_FRAME_ERR)) {
			int err;
			/* map to CONS_ERR_xxx MI error codes */
			err =	((rr1 & SCC_RR1_PARITY_ERR)<<8) |
				((rr1 & SCC_RR1_RX_OVERRUN)<<9) |
				((rr1 & SCC_RR1_FRAME_ERR)<<7);
			scc_write_reg(regs, chan, SCC_RR0, SCC_RESET_ERROR);
			rr1 = scc_to_mi(unit,chan);
			cons_simple_rint(rr1, rr1, 0, err);
		}
		scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
	}

	goto next_intr;

}

boolean_t
scc_start(tp)
	struct tty *tp;
{
	register scc_regmap_t	*regs;
	register int		chan, temp;
	register struct softreg	*sr;

	temp = (natural_t)tp->t_addr;
	chan = (temp & 1);	/* channel */
	temp = (temp >> 1)&0xff;/* sccunit */
	regs = scc_softc[temp]->regs;
	sr   = &scc_softc[temp]->softr[chan];

	scc_read_reg(regs, chan, SCC_RR15, temp);
	temp |= SCC_WR15_TX_UNDERRUN_IE;
	scc_write_reg(regs, chan, SCC_WR15, temp);

	temp = sr->wr1 | SCC_WR1_TX_IE;
	scc_write_reg(regs, chan, SCC_WR1, temp);
	sr->wr1 = temp;

	/* but we need a first char out or no cookie */
	scc_read_reg(regs, chan, SCC_RR0, temp);
	if (temp & SCC_RR0_TX_EMPTY)
	{
		register char	c;

		c = getc(&tp->t_outq);
		scc_write_data(regs, chan, c);
	}
}

/*
 * Get a char from a specific SCC line
 * [this is only used for console&screen purposes]
 */
scc_getc( unit, line, wait, raw )
	boolean_t	wait;
	boolean_t	raw;
{
	scc_softc_t     scc;
	register scc_regmap_t *regs;
	unsigned char   c;
	int             value, mi_line, rcvalue, from_line;

	mi_line = line;
	mi_to_scc(&unit, &line);

	scc = scc_softc[unit];
	regs = scc->regs;

	/*
	 * wait till something available
	 *
	 * NOTE: we know! that rcline==3
	 */
	if (rcline) rcline = 3;
again:
	rcvalue = 0;
	while (1) {
		scc_read_reg_zero(regs, line, value);
		if (rcline && (mi_line == SCREEN_LINE_KEYBOARD)) {
			scc_read_reg_zero(regs, SCC_CHANNEL_B, rcvalue);
			value |= rcvalue;
		}
		if (((value & SCC_RR0_RX_AVAIL) == 0) && wait)
			delay(10);
		else
			break;
	}

	/*
	 * if nothing found return -1 
	 */
	from_line = (rcvalue & SCC_RR0_RX_AVAIL) ? SCC_CHANNEL_B : line;

	if (value & SCC_RR0_RX_AVAIL) {
		scc_read_reg(regs, from_line, SCC_RR1, value);
		scc_read_data(regs, from_line, c);
	} else {
/*		splx(s);*/
		return -1;
	}

	/*
	 * bad chars not ok 
	 */
	if (value&(SCC_RR1_PARITY_ERR | SCC_RR1_RX_OVERRUN | SCC_RR1_FRAME_ERR)) {
/* scc_state(unit,from_line); */
		scc_write_reg(regs, from_line, SCC_RR0, SCC_RESET_ERROR);
		if (wait) {
			scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
			goto again;
		}
	}
	scc_write_reg(regs, SCC_CHANNEL_A, SCC_RR0, SCC_RESET_HIGHEST_IUS);
/*	splx(s);*/


#if	NBM > 0
	if ((mi_line == SCREEN_LINE_KEYBOARD) && (from_line == SCC_CHANNEL_A) &&
	    !raw && SCREEN_ISA_CONSOLE() && scc->isa_console)
		return lk201_rint(SCREEN_CONS_UNIT(), c, wait, scc->polling_mode);
	else
#endif	/* NBM > 0 */
		return c;
}

/*
 * Put a char on a specific SCC line
 */
scc_putc( unit, line, c )
{
	scc_softc_t      scc;
	register scc_regmap_t *regs;
	spl_t             s = spltty();
	register int	value;

	mi_to_scc(&unit, &line);

	scc = scc_softc[unit];
	regs = scc->regs;

	do {
		scc_read_reg(regs, line, SCC_RR0, value);
		if (value & SCC_RR0_TX_EMPTY)
			break;
		delay(100);
	} while (1);

	scc_write_data(regs, line, c);
/* wait for it to swallow the char ? */

	splx(s);
}

scc_param(tp, line)
	struct tty	*tp;
{
	scc_regmap_t	*regs;
	int		value, sccline, unit;
	struct softreg	*sr;
	scc_softc_t	scc;
 
	line = tp->t_dev;
	/* MI code wants us to handle 4 lines on unit 0 */
	unit = (line < 4) ? 0 : (line / NSCC_LINE);
	sccline = line;
	mi_to_scc(&unit, &sccline);

	if ((scc = scc_softc[unit]) == 0) return;	/* sanity */
	regs = scc->regs;

	sr = &scc->softr[sccline];

	/*
	 * Do not let user fool around with kbd&mouse
	 */
#if	NBM > 0
	if (screen_captures(line)) {
		tp->t_ispeed = tp->t_ospeed = B4800;
		tp->t_flags |= TF_LITOUT;
	}
#endif	/* NBM > 0 */

	if (tp->t_ispeed == 0) {
		(void) scc_mctl(tp, TM_HUP, DMSET);	/* hang up line */
		return;
	}

	/* reset line */
	value = (sccline == SCC_CHANNEL_A) ? SCC_WR9_RESET_CHA_A : SCC_WR9_RESET_CHA_B;
	scc_write_reg(regs, sccline, SCC_WR9, value);
	delay(25);

	/* stop bits, normally 1 */
	value = sr->wr4 & 0xf0;
	value |= (tp->t_ispeed == B110) ? SCC_WR4_2_STOP : SCC_WR4_1_STOP;
	/* .. and parity */
#if 0
	if ((tp->t_flags & (TF_ODDP | TF_EVENP)) == TF_EVENP)
		value |= SCC_WR4_EVEN_PARITY;
	value |= SCC_WR4_PARITY_ENABLE;
#else
	if ((tp->t_flags & (TF_ODDP | TF_EVENP)) == TF_ODDP)
		value |= SCC_WR4_PARITY_ENABLE;
#endif

	/* set it now, remember it must be first after reset */
	sr->wr4 = value;
	scc_write_reg(regs, sccline, SCC_WR4, value);

	/* vector again */
	scc_write_reg(regs, sccline, SCC_WR2, 0xf0);

	/* we only do 8 bits per char */
	value = SCC_WR3_RX_8_BITS;
	scc_write_reg(regs, sccline, SCC_WR3, value);

	/* clear break, keep rts dtr */
	value = sr->wr5 & (SCC_WR5_DTR|SCC_WR5_RTS);
	value |= SCC_WR5_TX_8_BITS;
	sr->wr5 = value;
	scc_write_reg(regs, sccline, SCC_WR5, value);

	scc_write_reg(regs, sccline, SCC_WR6, 0);
	scc_write_reg(regs, sccline, SCC_WR7, 0);

	scc_write_reg(regs, sccline, SCC_WR9, SCC_WR9_VIS);

	scc_write_reg(regs, sccline, SCC_WR10, 0);

	/* clock config */
	value = SCC_WR11_RCLK_BAUDR | SCC_WR11_XTLK_BAUDR |
		SCC_WR11_TRc_OUT | SCC_WR11_TRcOUT_BAUDR;
	scc_write_reg(regs, sccline, SCC_WR11, value);

	value = scc_speeds[tp->t_ispeed];
	scc_set_timing_base(regs,sccline,value);

	value = sr->wr14;
	scc_write_reg(regs, sccline, SCC_WR14, value);

	value = (scc->full_modem) ?
		SCC_WR15_BREAK_IE | SCC_WR15_CTS_IE | SCC_WR15_DCD_IE :
		SCC_WR15_BREAK_IE;
	scc_write_reg(regs, sccline, SCC_WR15, value);

	/* and now the enables */
	value = SCC_WR3_RX_8_BITS | SCC_WR3_RX_ENABLE;
	scc_write_reg(regs, sccline, SCC_WR3, value);

	value = sr->wr5 | SCC_WR5_TX_ENABLE;
	sr->wr5 = value;
	scc_write_reg(regs, sccline, SCC_WR5, value);

	/* master inter enable */
	scc_write_reg(regs,sccline,SCC_WR9,SCC_WR9_MASTER_IE|SCC_WR9_VIS);

	scc_write_reg(regs, sccline, SCC_WR1, sr->wr1);

}
 
/*
 * Modem control functions
 */
scc_mctl(tp, bits, how)
	struct tty	*tp;
	int bits, how;
{
	return 0;
#if 0
	register scc_regmap_t *regs;
	int unit, sccline;
	register int tcr, msr, brk, n_tcr, n_brk;
	int b;
	spl_t	s;
	scc_softc_t      sc;

	unit = tp->t_dev;
	sccline = unit & 3;
	unit >>= 2;
	mi_to_scc(&unit, &sccline);

	/* no modem support on channel A */
/* XXX break on 0&1 */
	if (sccline == SCC_CHANNEL_A)
		return TM_LE | TM_DTR | TM_CTS | TM_CAR | TM_DSR;

	scc = scc_softc[unit];
	regs = scc->regs;
	s = spltty();

....some other day..

	tcr = ((regs->scc_tcr | (scc->fake>>4)) & 0xf00) >> (8 + b*2);
	brk = (scc->breaks >> (8 + (unit&3))) & 1;	/* THE break bit */

	n_tcr = (bits & (TM_RTS | TM_DTR)) >> 1;
	n_brk = (bits & TM_BRK) >> 9;

	/* break transitions, must 'send' a char out */
	bits = (brk ^ n_brk) & 1;

	switch (how) {
	case DMSET:
		tcr = n_tcr;
		brk = n_brk;
		break;

	case DMBIS:
		tcr |= n_tcr;
		brk |= n_brk;
		break;

	case DMBIC:
		tcr &= ~n_tcr;
		brk = 0;
		break;

	case DMGET:
		msr = ((regs->scc_msr | scc->fake) & 0xf0f) >> (b*8);
		splx(s);
		return  (tcr<<1)|/* DTR, RTS */
			((msr&1)<<5)|/* CTS */
			((msr&2)<<7)|/* DSR */
			((msr&0xc)<<4)|/* CD, RNG */
			(brk << 9)|/* BRK */
			TM_LE;
	}
	n_tcr =	(regs->scc_tcr & ~(3 << (8 + b*2))) |
		(tcr << (8 + b*2));

	regs->scc_tcr = n_tcr;
	scc->fake = (scc->fake & 0xf0f) | (n_tcr<<4&0xf000);

	scc->breaks = (scc->breaks & ~(1 << (8 + (unit&3)))) |
		    (brk << (8 + (unit&3)));
	if(bits) scc_putc( unit>>2, unit&3, 0);/* force break, now */
	splx(s);
	return 0;/* useless to compute it */
#endif
}

scc_modem_intr(scc, chan)
{
	/* nothing yet */
}

static check_car(
	register struct tty *tp,
        boolean_t car)
		
{
	if (car) {
#if notyet
		/* cancel modem timeout if need to */
		if (car & (SCC_MSR_CD2 | SCC_MSR_CD3))
			(void)untimeout((timeout_fcn_t)scc_hup, (void *)tp);
#endif

		/* I think this belongs in the MI code */
		if (tp->t_state & TS_WOPEN)
			tp->t_state |= TS_ISOPEN;
		/* carrier present */
		if ((tp->t_state & TS_CARR_ON) == 0)
			(void)ttymodem(tp, 1);
	} else if ((tp->t_state&TS_CARR_ON) && ttymodem(tp, 0) == 0)
		scc_mctl(tp, TM_DTR, DMBIC);
}

/*
 * Periodically look at the CD signals:
 * they do generate interrupts but we
 * must fake them on channel A.  We might
 * also fake them on channel B.
 */
scc_scan()
{
	register i;
	spl_t s = spltty();

	for (i = 0; i < NSCC; i++) {
		register scc_softc_t	scc;
		register int		car;
		register struct tty	**tpp;
		register int first;

		scc = scc_softc[i];
		if (scc == 0)
			continue;
		car = scc->softCAR | scc->fake;

		tpp = &console_tty[i * NSCC_LINE];

#define IF_KDEBUG	((i == 1) && kdebug_state())
		for (first = 0; car; first++, tpp++, car >>= 1) {
			if ((first == 0) && IF_KDEBUG)
				continue;
			if (car & 1)
				check_car(*tpp, 1);
		}

	}
	splx(s);
	timeout((timeout_fcn_t)scc_scan, (void *)0, 5*hz);
}


#if 0 /* debug */
scc_rreg(unit,chan,n)
{
	int val;
	scc_read_reg(scc_softc[unit]->regs, chan, n, val);
	return val;
}

scc_wreg(unit,chan,n,val)
{
	scc_write_reg(scc_softc[unit]->regs, chan, n, val);
}

scc_state(unit,soft)
{
	printf("{%d intr, A: R0 %x R1 %x R3 %x baudr %x R15 %x}\n",
		scc_intr_count,
		scc_rreg(unit, 1, SCC_RR0),
		scc_rreg(unit, 1, SCC_RR1),
		scc_rreg(unit, 1, SCC_RR3),
		(scc_rreg(unit, 1, SCC_RR13) << 8) | scc_rreg(unit, 1, SCC_RR12),
		scc_rreg(unit, 1, SCC_RR15));
	printf("{B: R0 %x R1 %x R2 %x baudr %x R15 %x}\n",
		scc_rreg(unit, 1, SCC_RR0),
		scc_rreg(unit, 1, SCC_RR1),
		scc_rreg(unit, 1, SCC_RR2),
		(scc_rreg(unit, 1, SCC_RR13) << 8) | scc_rreg(unit, 1, SCC_RR12),
		scc_rreg(unit, 1, SCC_RR15));
	if (soft) {
		struct softreg *sr;
		sr = scc_softc[unit]->softr;
		printf("{B: W1 %x W4 %x W5 %x W14 %x}",
			sr->wr1, sr->wr4, sr->wr5, sr->wr14);
		sr++;
		printf("{A: W1 %x W4 %x W5 %x W14 %x}\n",
			sr->wr1, sr->wr4, sr->wr5, sr->wr14);
	}
}

#endif

#endif	/* NSCC > 0 */
