commit 0a9462e35325b60cb1b2b7e71377dfa1d39b2132
Author: Peter Wemm <peter@FreeBSD.org>
Date:   Sat May 4 06:03:59 1996 +0000

    Initial import of driver for the Stallion EasyIO and EasyConnection 8/32
    boards by Greg Ungerer (gerg@stallion.oz.au).  (v0.0.1 alpha)
    
    This is a multiple import of all revisions available to build up
    a history.
    
    This driver supports only some of the Stallion range, in particular, not
    the highly intelligent cards.  That comes in shortly.
    
    Submitted by: Greg Ungerer (gerg@stallion.oz.au)

diff --git a/sys/i386/isa/README.stl b/sys/i386/isa/README.stl
new file mode 100644
index 000000000000..585b91bad070
--- /dev/null
+++ b/sys/i386/isa/README.stl
@@ -0,0 +1,241 @@
+
+Stallion Multiport Serial Driver Readme
+---------------------------------------
+
+Version: 0.0.1 alpha
+Date:    21DEC95
+Author:  Greg Ungerer (gerg@stallion.oz.au)
+
+
+
+1. INTRODUCTION
+
+This is a FreeBSD driver for some of the Stallion Technologies range of
+multiport serial boards. This is the very first release of this driver, so
+it should be considered to be of very alpha quality.
+
+This driver has not been developed by Stallion Technologies. I developed it
+in my spare time in the hope that it would be useful. As such there is no
+warranty or support of any form.
+
+What this means is that this driver is not officially supported by Stallion
+Technologies, so don't ring their support if you can't get it working. They
+will probably not be able to help you. Instead email me if you have problems
+or bug reports and I will do what I can... (Sorry to sound so heavy handed,
+but I need to stress that this driver is not officially supported in any way.)
+
+This driver supports the EasyIO and EasyConnection 8/32 range of boards.
+All of these boards are not classical intelligent multiport boards, but are
+host based multiport boards that use high performance Cirrus Logic CL-CD1400
+RISC UART's (they have built in FIFO's, automatic flow control, and some
+other good stuff).
+
+The EasyIO range of cards comes in 3 forms, the EasyIO-4, EasyIO-8 and the
+EasyIO-8M. All of these are non-expandable, low cost, ISA, multiport boards
+with 4, 8 and 8 RS-232C ports respectively. The EasyIO-8M is not currently
+supported by this driver. Though it is pretty easy to support so I'll do
+that when I get a chance. Each EasyIO board requires 8 bytes of IO address
+space and 1 interrupt. On an EISA system it is possible to share 1 interrupt
+between multiple boards. The EasyIO-4 has 10 pin RJ connectors, and the
+EasyIO-8 comes with a dongle cable that can be either 10 pin RJ connectors or
+DB-25 connectors. The EasyIO-8M has 6 pin RJ connectors.
+
+The EasyConnection 8/32 family of boards is a relatively low cost modular
+range of multiport serial boards. The EasyConnection 8/32 boards can be
+configured to have from 8 to 32 serial ports by plugging in external serial
+port modules that contain from 8 to 16 ports each. There is a wide range of
+external modules available that offer: DB-25 connectors, RJ-45 connectors
+(both with RS-232 D and E compatible drivers), and also RS-422 ports. The
+EasyConnection 8/32 boards come in ISA and MCA bus versions. The board takes
+the form of a host adapter card, with an external connector cable that plugs
+into the external modules. The external modules just clip together to add
+ports (BTW they are not hot pluggable). Each EasyConnection 8/32 board
+requires 2 separate IO address ranges, one 2 bytes in size and a secondary
+region of 32 bytes. Each board also requires 1 interrupt, on EISA systems
+multiple boards can share 1 interrupt. The secondary IO range (the 32 byte
+range) can be shared between multiple boards on any bus type.
+
+So thats the hardware supported (sounds like a marketing spiel doesn't it!).
+I am working on drivers for other boards in the Stallion range, so look
+out for those some time soon...
+
+
+
+2. INSTALLATION
+
+This driver was developed on a FreeBSD 2.0.5 system. I have not tryed it
+on a 2.1 system yet, so I don't know if it will go in painlessly or not...
+
+You will need to build a new kernel to use this driver. So the first thing
+you need is to have the full kernel source. Most people will have this
+(I hope!). The following assumes that the kernel source is in /usr/src/sys.
+
+The driver can support up to 8 boards (any combination of EasyIO and
+EasyConnection 8/32 boards). So there is a theoretical maximum of 256 ports.
+(Off-course I have not tested a system with this many!)
+
+Instructions to install:
+
+1. Copy the driver source files into the kernel source tree.
+
+        cp stallion.c /usr/src/sys/i386/isa
+        cp scd1400.h /usr/src/sys/i386/ic
+
+2. Add a character device switch table entry for the driver into the cdevsw
+   table structure. This involves adding some code into the kernel conf.c
+   file:
+
+        cd /usr/src/sys/i386/i386
+        vi conf.c
+            - add the following lines (in 2.0.5 I put them at line 1019):
+
+/* Stallion Multiport Serial Driver */
+#include "stl.h"
+#if	NSTL > 0
+d_open_t        stlopen;
+d_close_t       stlclose;
+d_read_t        stlread;
+d_write_t       stlwrite;
+d_ioctl_t	stlioctl;
+d_stop_t        stlstop;
+d_ttycv_t	stldevtotty;
+#define stlreset	nxreset
+#define	stlmmap		nxmmap
+#define stlstrategy	nxstrategy
+#else
+#define	stlopen		nxopen
+#define stlclose	nxclose
+#define stlread		nxread
+#define stlwrite	nxwrite
+#define stlioctl	nxioctl
+#define stlstop		nxstop
+#define stlreset	nxreset
+#define stlmmap		nxmmap
+#define stlstrategy	nxstrategy
+#define	stldevtotty	nxdevtotty
+#endif
+
+
+            - and then inside the actual cdevsw structure definition, at the
+              last entry add (this is now line 1259 in the 2.0.5 conf.c):
+
+	{ stlopen,	stlclose,	stlread,	stlwrite,	/*67*/
+	  stlioctl,	stlstop,	stlreset,	stldevtotty,/*stallion*/
+	  ttselect,	stlmmap,	stlstrategy },
+
+            - the line above used major number 67, but this may be different
+              on your system. Take note of what major number you are using.
+              
+            - save the file and exit vi.
+
+3. Add the driver source files to the kernel files list:
+
+        cd /usr/src/sys/i386/conf
+        vi files.i386
+            - add the following definition line into the list (it is stored
+              alphabetically, so insert it appropriately):
+
+i386/isa/stallion.c		optional	stl	device-driver
+
+            - save the file and exit vi.
+
+4. Add board probe entries into the kernel configuration file:
+
+        cd /usr/src/sys/i386/conf
+        cp GENERIC MYKERNEL
+            - if you already have a kernel config that you use then you
+              could just use that (instead of MYKERNEL)
+        vi MYKERNEL
+            - enter a line for each board that you want to use, eg:
+
+device		stl0	at isa? port 0x2a0 tty irq 10 vector stlintr
+
+            - change the io address and irq in this line as required
+            - save the file and exit
+
+5. Build a new kernel using this configuration.
+
+        cd /usr/src/sys/i386/conf
+        config MYKERNEL
+        cd ../../compile/MYKERNEL
+        make depend
+        make all
+        make install
+
+
+And there you have it!  It is a little bit of effort to get it in there...
+
+So once you have a new kernel built, reboot to start it up. On startup the
+Stallion board probes will report on whether the boards were found or not.
+For each board found the driver will print out the type of board found,
+and how many panels and ports it has. 
+
+If a board is not found by the driver but is actually in the system then the
+most likely problem is that the IO address is wrong. The easiest thing to do
+is change the DIP switches on the board to the desired address and reboot.
+On EasyIO and EasyConnection 8/32 boards the IRQ is software programmable,
+so if there is a conflict you may need to change the IRQ used for a board in
+the MYKERNEL configuration file and rebuild the kernel.
+
+Note that the secondary IO address of the EasyConnection 8/32 boards is hard
+wired into the stallion.c driver code. It is currently set to IO address
+0x280. If you need to use a different address then you will need to edit this
+file and change the variable named stl_ioshared.
+
+
+
+3. USING THE DRIVER
+
+Once the driver is installed you will need to setup some device nodes to
+access the serial ports. Use the supplied "mkdevnods" script to automatically
+create all required device entries for your boards. To make device nodes for
+more than 1 board then just supply the number of boards you are using as a
+command line parameter to mkdevnods and it will create nodes for that number
+of boards. By default it will create device nodes for 1 board only.
+
+Note that if the driver is not installed at character major number 67 then
+you will need to edit the mkdevnods script and modify the STL_SERIALMAJOR
+variable to the major number you are using.
+
+Device nodes created for the normal serial port devices are named /dev/ttyEX
+where X is the port number. (The second boards ports will start from ttyE32,
+the third boards from ttyE64, etc). It will also create a set of modem call
+out devices named cueX where again X is the port number.
+
+For the most part the Stallion driver tries to emulate the standard PC system
+com ports and the standard sio serial driver. The idea is that you should
+be able to use Stallion board ports and com ports inter-changeably without
+modifying anything but the device name. Anything that doesn't work like that
+should be considered a bug in this driver!
+
+Since this driver tries to emulate the standard serial ports as much as
+possible then most system utilities should work as they do for the standard
+com ports. Most importantly "stty" works as expected and "comcontrol" can be
+used just like for the serial ports.
+
+This driver should work with anything that works on standard com serial ports.
+Having said that, I have used it on at least the following types of "things"
+under FreeBSD:
+    a) standard dumb terminals (using getty)
+    b) modems (using cu, etc)
+
+
+
+4. NOTES
+
+Be aware that this is the first release of this driver, so there is sure to
+be some bugs in it. Please email me any feedback on bugs, problems, or even
+good experiences with this driver!
+
+There is no real smart line discipline bypass code yet (like in the sio
+driver). I will add this for the next driver release.
+
+I will probably also add LKM support for the next driver release.
+
+
+
+5. ACKNOWLEDGEMENTS
+
+This driver is loosely based on the code of the FreeBSD sio serial driver.
+A big thanks to Stallion Technologies for the use of their equipment.
+
diff --git a/sys/i386/isa/ic/scd1400.h b/sys/i386/isa/ic/scd1400.h
new file mode 100644
index 000000000000..b9cdceeaed0f
--- /dev/null
+++ b/sys/i386/isa/ic/scd1400.h
@@ -0,0 +1,307 @@
+/*****************************************************************************/
+
+/*
+ *	cd1400.h  -- cd1400 UART hardware info.
+ *
+ * Copyright (c) 1995 Greg Ungerer (gerg@stallion.oz.au).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Greg Ungerer.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*****************************************************************************/
+
+#ifndef	_CD1400_H
+#define	_CD1400_H
+/*****************************************************************************/
+
+/*
+ *	Define the number of async ports per cd1400 uart chip.
+ */
+#define	CD1400_PORTS		4
+
+#define	CD1400_CLKHZ		25000000
+
+/*
+ *	Define the cd1400 uarts internal FIFO sizes.
+ */
+#define	CD1400_TXFIFOSIZE	12
+#define	CD1400_RXFIFOSIZE	12
+
+/*
+ *	Local RX FIFO thresh hold level. Also define the RTS thresh hold
+ *	based on the RX thresh hold.
+ */
+#define	FIFO_RXTHRESHOLD	6
+#define	FIFO_RTSTHRESHOLD	7
+
+/*****************************************************************************/
+
+/*
+ *	Define the cd1400 register addresses. These are all the valid
+ *	registers with the cd1400. Some are global, some virtual, some
+ *	per port.
+ */
+#define	GFRCR		0x40
+#define	CAR		0x68
+#define	GCR		0x4b
+#define	SVRR		0x67
+#define	RICR		0x44
+#define	TICR		0x45
+#define	MICR		0x46
+#define	RIR		0x6b
+#define	TIR		0x6a
+#define	MIR		0x69
+#define	PPR		0x7e
+
+#define	RIVR		0x43
+#define	TIVR		0x42
+#define	MIVR		0x41
+#define	TDR		0x63
+#define	RDSR		0x62
+#define	MISR		0x4c
+#define	EOSRR		0x60
+
+#define	LIVR		0x18
+#define	CCR		0x05
+#define	SRER		0x06
+#define	COR1		0x08
+#define	COR2		0x09
+#define	COR3		0x0a
+#define	COR4		0x1e
+#define	COR5		0x1f
+#define	CCSR		0x0b
+#define	RDCR		0x0e
+#define	SCHR1		0x1a
+#define	SCHR2		0x1b
+#define	SCHR3		0x1c
+#define	SCHR4		0x1d
+#define	SCRL		0x22
+#define	SCRH		0x23
+#define	LNC		0x24
+#define	MCOR1		0x15
+#define	MCOR2		0x16
+#define	RTPR		0x21
+#define	MSVR1		0x6c
+#define	MSVR2		0x6d
+#define	PSVR		0x6f
+#define	RBPR		0x78
+#define	RCOR		0x7c
+#define	TBPR		0x72
+#define	TCOR		0x76
+
+/*****************************************************************************/
+
+/*
+ *	Define the set of baud rate clock divisors.
+ */
+#define	CD1400_CLK0	8
+#define	CD1400_CLK1	32
+#define	CD1400_CLK2	128
+#define	CD1400_CLK3	512
+#define	CD1400_CLK4	2048
+
+#define	CD1400_NUMCLKS	5
+
+/*****************************************************************************/
+
+/*
+ *	Define the clock pre-scalar value to be a 5 ms clock. This should be
+ *	OK for now. It would probably be better to make it 10 ms, but we
+ *	can't fit that divisor into 8 bits!
+ */
+#define	PPR_SCALAR	244
+
+/*****************************************************************************/
+
+/*
+ *	Define values used to set character size options.
+ */
+#define	COR1_CHL5	0x00
+#define	COR1_CHL6	0x01
+#define	COR1_CHL7	0x02
+#define	COR1_CHL8	0x03
+
+/*
+ *	Define values used to set the number of stop bits.
+ */
+#define	COR1_STOP1	0x00
+#define	COR1_STOP15	0x04
+#define	COR1_STOP2	0x08
+
+/*
+ *	Define values used to set the parity scheme in use.
+ */
+#define	COR1_PARNONE	0x00
+#define	COR1_PARFORCE	0x20
+#define	COR1_PARENB	0x40
+#define	COR1_PARIGNORE	0x10
+
+#define	COR1_PARODD	0x80
+#define	COR1_PAREVEN	0x00
+
+#define	COR2_IXM	0x80
+#define	COR2_TXIBE	0x40
+#define	COR2_ETC	0x20
+#define	COR2_LLM	0x10
+#define	COR2_RLM	0x08
+#define	COR2_RTSAO	0x04
+#define	COR2_CTSAE	0x02
+
+#define	COR3_SCDRNG	0x80
+#define	COR3_SCD34	0x40
+#define	COR3_FCT	0x20
+#define	COR3_SCD12	0x10
+
+/*
+ *	Define values used by COR4.
+ */
+#define	COR4_BRKINT	0x08
+#define	COR4_IGNBRK	0x18
+
+/*****************************************************************************/
+
+/*
+ *	Define the modem control register values.
+ *	Note that the actual hardware is a little different to the conventional
+ *	pin names on the cd1400.
+ */
+#define	MSVR1_DTR	0x01
+#define	MSVR1_DSR	0x10
+#define	MSVR1_RI	0x20
+#define	MSVR1_CTS	0x40
+#define	MSVR1_DCD	0x80
+
+#define	MSVR2_RTS	0x02
+#define	MSVR2_DSR	0x10
+#define	MSVR2_RI	0x20
+#define	MSVR2_CTS	0x40
+#define	MSVR2_DCD	0x80
+
+#define	MCOR1_DCD	0x80
+#define	MCOR1_CTS	0x40
+#define	MCOR1_RI	0x20
+#define	MCOR1_DSR	0x10
+
+#define	MCOR2_DCD	0x80
+#define	MCOR2_CTS	0x40
+#define	MCOR2_RI	0x20
+#define	MCOR2_DSR	0x10
+
+/*****************************************************************************/
+
+/*
+ *	Define the bits used with the service (interrupt) enable register.
+ */
+#define	SRER_NNDT	0x01
+#define	SRER_TXEMPTY	0x02
+#define	SRER_TXDATA	0x04
+#define	SRER_RXDATA	0x10
+#define	SRER_MODEM	0x80
+
+/*****************************************************************************/
+
+/*
+ *	Define operational commands for the command register.
+ */
+#define	CCR_RESET	0x80
+#define	CCR_CORCHANGE	0x4e
+#define	CCR_SENDCH	0x20
+#define	CCR_CHANCTRL	0x10
+
+#define	CCR_TXENABLE	(CCR_CHANCTRL | 0x08)
+#define	CCR_TXDISABLE	(CCR_CHANCTRL | 0x04)
+#define	CCR_RXENABLE	(CCR_CHANCTRL | 0x02)
+#define	CCR_RXDISABLE	(CCR_CHANCTRL | 0x01)
+
+#define	CCR_SENDSCHR1	(CCR_SENDCH | 0x01)
+#define	CCR_SENDSCHR2	(CCR_SENDCH | 0x02)
+#define	CCR_SENDSCHR3	(CCR_SENDCH | 0x03)
+#define	CCR_SENDSCHR4	(CCR_SENDCH | 0x04)
+
+#define	CCR_RESETCHAN	(CCR_RESET | 0x00)
+#define	CCR_RESETFULL	(CCR_RESET | 0x01)
+#define	CCR_TXFLUSHFIFO	(CCR_RESET | 0x02)
+
+#define	CCR_MAXWAIT	10000
+
+/*****************************************************************************/
+
+/*
+ *	Define the valid acknowledgement types (for hw ack cycle).
+ */
+#define	ACK_TYPMASK	0x07
+#define	ACK_TYPTX	0x02
+#define	ACK_TYPMDM	0x01
+#define	ACK_TYPRXGOOD	0x03
+#define	ACK_TYPRXBAD	0x07
+
+#define	SVRR_RX		0x01
+#define	SVRR_TX		0x02
+#define	SVRR_MDM	0x04
+
+#define	ST_OVERRUN	0x01
+#define	ST_FRAMING	0x02
+#define	ST_PARITY	0x04
+#define	ST_BREAK	0x08
+#define	ST_SCHAR1	0x10
+#define	ST_SCHAR2	0x20
+#define	ST_SCHAR3	0x30
+#define	ST_SCHAR4	0x40
+#define	ST_RANGE	0x70
+#define	ST_TIMEOUT	0x80
+
+#define	MISR_DCD	0x80
+#define	MISR_CTS	0x40
+#define	MISR_RI		0x20
+#define	MISR_DSR	0x10
+
+/*****************************************************************************/
+
+/*
+ *	Defines for the CCSR status register.
+ */
+#define	CCSR_RXENABLED	0x80
+#define	CCSR_RXFLOWON	0x40
+#define	CCSR_RXFLOWOFF	0x20
+#define	CCSR_TXENABLED	0x08
+#define	CCSR_TXFLOWON	0x04
+#define	CCSR_TXFLOWOFF	0x02
+
+/*****************************************************************************/
+
+/*
+ *	Define the embedded commands.
+ */
+#define	ETC_CMD		0x00
+#define	ETC_STARTBREAK	0x81
+#define	ETC_DELAY	0x82
+#define	ETC_STOPBREAK	0x83
+
+/*****************************************************************************/
+#endif
diff --git a/sys/i386/isa/stallion.c b/sys/i386/isa/stallion.c
new file mode 100644
index 000000000000..041a1408021e
--- /dev/null
+++ b/sys/i386/isa/stallion.c
@@ -0,0 +1,2601 @@
+/*****************************************************************************/
+
+/*
+ * stallion.c  -- stallion multiport serial driver.
+ *
+ * Copyright (c) 1995 Greg Ungerer (gerg@stallion.oz.au).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Greg Ungerer.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*****************************************************************************/
+
+#define	TTYDEFCHARS	1
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/ioctl.h>
+#include <sys/tty.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/uio.h>
+#include <sys/syslog.h>
+
+#include <machine/cpu.h>
+#include <machine/clock.h>
+#include <i386/isa/isa_device.h>
+#include <i386/isa/ic/scd1400.h>
+
+/*****************************************************************************/
+
+/*
+ *	Define different board types. At the moment I have only declared
+ *	those boards that this driver supports. But I will use the standard
+ *	"assigned" board numbers. In the future this driver will support
+ *	some of the other Stallion boards. Currently supported boards are
+ *	abbreviated as EIO = EasyIO and ECH = EasyConnection 8/32.
+ */
+#define	BRD_EASYIO	20
+#define	BRD_ECH		21
+#define	BRD_ECHMC	22
+#define	BRD_ECHPCI	26
+
+/*
+ *	When using the BSD "config" stuff there is no easy way to specifiy
+ *	a secondary IO address region. So it is hard wired here. Also the
+ *	shared interrupt information is hard wired here...
+ */
+static unsigned int	stl_ioshared = 0x280;
+static unsigned int	stl_irqshared = 0;
+
+/*****************************************************************************/
+
+/*
+ *	Define important driver limitations.
+ */
+#define	STL_MAXBRDS		8
+#define	STL_MAXPANELS		4
+#define	STL_PORTSPERPANEL	16
+#define	STL_PORTSPERBRD		32
+
+/*
+ *	Define the important minor number break down bits. These have been
+ *	chosen to be "compatable" with the standard sio driver minor numbers.
+ *	Extra high bits are used to distinguish between boards.
+ */
+#define	STL_CALLOUTDEV		0x80
+#define	STL_CTRLLOCK		0x40
+#define	STL_CTRLINIT		0x20
+#define	STL_CTRLDEV		(STL_CTRLLOCK | STL_CTRLINIT)
+
+#define	STL_DEFSPEED	9600
+#define	STL_DEFCFLAG	(CS8 | CREAD | HUPCL)
+
+/*
+ *	I haven't really decided (or measured) what buffer sizes give
+ *	a good balance between performance and memory usage. These seem
+ *	to work pretty well...
+ */
+#define	STL_RXBUFSIZE		1024
+#define	STL_TXBUFSIZE		1024
+
+#define	STL_TXBUFLOW		256
+#define	STL_RXBUFHIGH		768
+
+/*****************************************************************************/
+
+/*
+ *	Define our local driver identity first. Set up stuff to deal with
+ *	all the local structures required by a serial tty driver.
+ */
+static char	*stl_drvname = "stl";
+static char	*stl_longdrvname = "Stallion Multiport Serial Driver";
+static char	*stl_drvversion = "0.0.1";
+static int	stl_brdprobed[STL_MAXBRDS];
+
+static int	stl_nrbrds = 0;
+static int	stl_doingtimeout = 0;
+
+static char	*__file__ = /*__FILE__*/ "stallion.c";
+
+/*
+ *	Define the set of RX character error types.
+ */
+#define	STL_NRRXERRS	6
+
+#define	STL_RXPARITY	0
+#define	STL_RXFRAMING	1
+#define	STL_RXOVERRUN	2
+#define	STL_RXBREAK	3
+#define	STL_RXLOST	4
+#define	STL_RXLDLOST	5
+
+/*****************************************************************************/
+
+/*
+ *	Define a set of structures to hold all the board/panel/port info
+ *	for our ports. These will be dynamically allocated as required.
+ */
+
+/*
+ *	Define a ring queue structure for each port. This will hold the
+ *	TX data waiting to be output. Characters are fed into this buffer
+ *	from the line discipline (or even direct from user space!) and
+ *	then fed into the UARTs during interrupts. Will use a clasic ring
+ *	queue here for this. The good thing about this type of ring queue
+ *	is that the head and tail pointers can be updated without interrupt
+ *	protection - since "write" code only needs to change the head, and
+ *	interrupt code only needs to change the tail.
+ */
+typedef struct {
+	char	*buf;
+	char	*endbuf;
+	char	*head;
+	char	*tail;
+} stlrq_t;
+
+/*
+ *	Port, panel and board structures to hold status info about each.
+ *	The board structure contains pointers to structures for each panel
+ *	connected to it, and in turn each panel structure contains pointers
+ *	for each port structure for each port on that panel. Note that
+ *	the port structure also contains the board and panel number that it
+ *	is associated with, this makes it (fairly) easy to get back to the
+ *	board/panel info for a port. Also note that the tty struct is at
+ *	the top of the structure, this is important, since the code uses
+ *	this fact to get the port struct pointer from the tty struct
+ *	pointer!
+ */
+typedef struct {
+	struct tty	tty;
+	int		portnr;
+	int		panelnr;
+	int		brdnr;
+	int		ioaddr;
+	int		uartaddr;
+	int		pagenr;
+	int		callout;
+	int		brklen;
+	int		dtrwait;
+	int		dotimestamp;
+	int		waitopens;
+	unsigned int	state;
+	unsigned int	sigs;
+	unsigned int	rxignoremsk;
+	unsigned int	rxmarkmsk;
+	unsigned int	rxerrs[STL_NRRXERRS];
+	struct termios	initintios;
+	struct termios	initouttios;
+	struct termios	lockintios;
+	struct termios	lockouttios;
+	struct timeval	timestamp;
+	stlrq_t		tx;
+	stlrq_t		rx;
+	stlrq_t		rxstatus;
+} stlport_t;
+
+typedef struct {
+	int		panelnr;
+	int		brdnr;
+	int		pagenr;
+	int		nrports;
+	int		iobase;
+	unsigned int	ackmask;
+	stlport_t	*ports[STL_PORTSPERPANEL];
+} stlpanel_t;
+
+typedef struct {
+	int		brdnr;
+	int		brdtype;
+	int		state;
+	int		nrpanels;
+	int		nrports;
+	int		irq;
+	int		irqtype;
+	unsigned int	ioaddr1;
+	unsigned int	ioaddr2;
+	unsigned int	iostatus;
+	unsigned int	ioctrl;
+	unsigned int	ioctrlval;
+	stlpanel_t	*panels[STL_MAXPANELS];
+	stlport_t	*ports[STL_PORTSPERBRD];
+} stlbrd_t;
+
+static stlbrd_t		*stl_brds[STL_MAXBRDS];
+
+/*
+ *	Per board state flags. Used with the state field of the board struct.
+ *	Not really much here yet!
+ */
+#define	BRD_FOUND	0x1
+
+/*
+ *	Define the port structure state flags. These set of flags are
+ *	modified at interrupt time - so setting and reseting them needs
+ *	to be atomic.
+ */
+#define	ASY_TXLOW	0x1
+#define	ASY_RXDATA	0x2
+#define	ASY_DCDCHANGE	0x4
+#define	ASY_DTRWAIT	0x8
+#define	ASY_RTSFLOW	0x10
+
+#define	ASY_ACTIVE	(ASY_TXLOW | ASY_RXDATA | ASY_DCDCHANGE)
+
+/*
+ *	Define an array of board names as printable strings. Handy for
+ *	referencing boards when printing trace and stuff.
+ */
+static char	*stl_brdnames[] = {
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	"EasyIO",
+	"EC8/32-AT",
+	"EC8/32-MC",
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	"EC8/32-PCI",
+};
+
+/*****************************************************************************/
+
+/*
+ *	Hardware ID bits for the EasyIO and ECH boards. These defines apply
+ *	to the directly accessable io ports of these boards (not the cd1400
+ *	uarts - they are in scd1400.h).
+ */
+#define	EIO_8PORTRS	0x04
+#define	EIO_4PORTRS	0x05
+#define	EIO_8PORTDI	0x00
+#define	EIO_8PORTM	0x06
+#define	EIO_IDBITMASK	0x07
+#define	EIO_INTRPEND	0x08
+#define	EIO_INTEDGE	0x00
+#define	EIO_INTLEVEL	0x08
+
+#define	ECH_ID		0xa0
+#define	ECH_IDBITMASK	0xe0
+#define	ECH_BRDENABLE	0x08
+#define	ECH_BRDDISABLE	0x00
+#define	ECH_INTENABLE	0x01
+#define	ECH_INTDISABLE	0x00
+#define	ECH_INTLEVEL	0x02
+#define	ECH_INTEDGE	0x00
+#define	ECH_INTRPEND	0x01
+#define	ECH_BRDRESET	0x01
+
+#define	ECHMC_INTENABLE	0x01
+#define	ECHMC_BRDRESET	0x02
+
+#define	ECH_PNLSTATUS	2
+#define	ECH_PNL16PORT	0x20
+#define	ECH_PNLIDMASK	0x07
+#define	ECH_PNLINTRPEND	0x80
+#define	ECH_ADDR2MASK	0x1e0
+
+/*
+ *	Define the offsets within the register bank for all io registers.
+ *	These io address offsets are common to both the EIO and ECH.
+ */
+#define	EREG_ADDR	0
+#define	EREG_DATA	4
+#define	EREG_RXACK	5
+#define	EREG_TXACK	6
+#define	EREG_MDACK	7
+
+#define	EREG_BANKSIZE	8
+
+/*
+ *	Define the vector mapping bits for the programmable interrupt board
+ *	hardware. These bits encode the interrupt for the board to use - it
+ *	is software selectable (except the EIO-8M).
+ */
+static unsigned char	stl_vecmap[] = {
+	0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07,
+	0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03
+};
+
+/*
+ *	Set up enable and disable macros for the ECH boards. They require
+ *	the secondary io address space to be activated and deactivated.
+ *	This way all ECH boards can share their secondary io region.
+ *	If this is an ECH-PCI board then also need to set the page pointer
+ *	to point to the correct page.
+ */
+#define	BRDENABLE(brdnr,pagenr)						\
+	if (stl_brds[(brdnr)]->brdtype == BRD_ECH)			\
+		outb(stl_brds[(brdnr)]->ioctrl,				\
+			(stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE));\
+	else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI)		\
+		outb(stl_brds[(brdnr)]->ioctrl, (pagenr));
+
+#define	BRDDISABLE(brdnr)						\
+	if (stl_brds[(brdnr)]->brdtype == BRD_ECH)			\
+		outb(stl_brds[(brdnr)]->ioctrl,				\
+			(stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE));
+
+/*
+ *	Define the cd1400 baud rate clocks. These are used when calculating
+ *	what clock and divisor to use for the required baud rate. Also
+ *	define the maximum baud rate allowed, and the default base baud.
+ */
+static int	stl_cd1400clkdivs[] = {
+	CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4
+};
+
+#define	STL_MAXBAUD	230400
+
+/*****************************************************************************/
+
+/*
+ *	Define macros to extract a brd and port number from a minor number.
+ *	This uses the extended minor number range in the upper 2 bytes of
+ *	the device number. This gives us plenty of minor numbers to play
+ *	with...
+ */
+#define	MKDEV2BRD(m)		(((m) & 0x00070000) >> 16)
+#define	MKDEV2PORT(m)		((m) & 0x3f)
+
+/*
+ *	Define some handy local macros...
+ */
+#ifndef	MIN
+#define	MIN(a,b)		(((a) <= (b)) ? (a) : (b))
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Declare all those functions in this driver!  First up is the set of
+ *	externally visible functions. Followed by the internal functions.
+ */
+int		stlprobe(struct isa_device *idp);
+int		stlattach(struct isa_device *idp);
+void		stlintr(int unit);
+int		stlopen(dev_t dev, int flag, int mode, struct proc *p);
+int		stlclose(dev_t dev, int flag, int mode, struct proc *p);
+int 		stlwrite(dev_t dev, struct uio *uiop, int flag);
+int 		stlread(dev_t dev, struct uio *uiop, int flag);
+int 		stlioctl(dev_t dev, int cmd, caddr_t data, int flag,
+			struct proc *p);
+int		stlstop(struct tty *tp, int rw);
+struct tty	*stldevtotty(dev_t dev);
+
+static stlport_t *stl_dev2port(dev_t dev);
+static int	stl_rawopen(stlport_t *portp);
+static int	stl_rawclose(stlport_t *portp);
+static int	stl_param(struct tty *tp, struct termios *tiosp);
+static void	stl_start(struct tty *tp);
+static void	stl_dotimeout(void);
+static void	stl_poll(void *arg);
+static void	stl_rxprocess(stlport_t *portp);
+static void	stl_dtrwakeup(void *arg);
+static int	stl_brdinit(stlbrd_t *brdp);
+static int	stl_initeio(stlbrd_t *brdp);
+static int	stl_initech(stlbrd_t *brdp);
+static int	stl_initports(stlbrd_t *brdp, stlpanel_t *panelp);
+static void	stl_txisr(stlpanel_t *panelp, int ioaddr);
+static void	stl_rxisr(stlpanel_t *panelp, int ioaddr);
+static void	stl_mdmisr(stlpanel_t *panelp, int ioaddr);
+static void	stl_setreg(stlport_t *portp, int regnr, int value);
+static int	stl_getreg(stlport_t *portp, int regnr);
+static int	stl_updatereg(stlport_t *portp, int regnr, int value);
+static void	stl_getsignals(stlport_t *portp);
+static void	stl_setsignals(stlport_t *portp, int dtr, int rts);
+static void	stl_flowcontrol(stlport_t *portp, int hw, int sw);
+static void	stl_ccrwait(stlport_t *portp);
+static void	stl_enablerxtx(stlport_t *portp, int rx, int tx);
+static void	stl_startrxtx(stlport_t *portp, int rx, int tx);
+static void	stl_disableintrs(stlport_t *portp);
+static void	stl_sendbreak(stlport_t *portp, long len);
+static void	stl_flush(stlport_t *portp, int flag);
+
+/*****************************************************************************/
+
+/*
+ *	Declare the driver isa structure.
+ */
+struct isa_driver	stldriver = {
+	stlprobe, stlattach, "stl"
+};
+
+/*****************************************************************************/
+
+/*
+ *	Probe for some type of EasyIO or EasyConnection 8/32 board at
+ *	the supplied address. All we do is check if we can find the
+ *	board ID for the board...
+ */
+
+int stlprobe(struct isa_device *idp)
+{
+	unsigned int	status;
+
+#if DEBUG
+	printf("stlprobe(idp=%x): unit=%d iobase=%x\n", (int) idp,
+		idp->id_unit, idp->id_iobase);
+#endif
+
+	if (idp->id_unit > STL_MAXBRDS)
+		return(0);
+
+	status = inb(idp->id_iobase + 1);
+	if ((status & ECH_IDBITMASK) == ECH_ID) {
+		stl_brdprobed[idp->id_unit] = BRD_ECH;
+		return(1);
+	}
+
+	status = inb(idp->id_iobase + 2);
+	switch (status & EIO_IDBITMASK) {
+	case EIO_8PORTRS:
+	case EIO_8PORTM:
+	case EIO_8PORTDI:
+	case EIO_4PORTRS:
+		stl_brdprobed[idp->id_unit] = BRD_EASYIO;
+		return(1);
+	default:
+		break;
+	}
+	
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Allocate resources for and initialize the specified board.
+ */
+
+int stlattach(struct isa_device *idp)
+{
+	stlbrd_t	*brdp;
+
+#if DEBUG
+	printf("stlattach(idp=%x): unit=%d iobase=%x\n", idp,
+		idp->id_unit, idp->id_iobase);
+#endif
+
+	brdp = (stlbrd_t *) malloc(sizeof(stlbrd_t), M_TTYS, M_NOWAIT);
+	if (brdp == (stlbrd_t *) NULL) {
+		printf("STALLION: failed to allocate memory (size=%d)\n",
+			sizeof(stlbrd_t));
+		return(0);
+	}
+	bzero(brdp, sizeof(stlbrd_t));
+
+	if (idp->id_unit >= stl_nrbrds)
+		stl_nrbrds = idp->id_unit + 1;
+
+	brdp->brdnr = idp->id_unit;
+	brdp->brdtype = stl_brdprobed[idp->id_unit];
+	brdp->ioaddr1 = idp->id_iobase;
+	brdp->ioaddr2 = stl_ioshared;
+	brdp->irq = ffs(idp->id_irq) - 1;
+	brdp->irqtype = stl_irqshared;
+	stl_brdinit(brdp);
+
+	return(1);
+}
+
+/*****************************************************************************/
+
+int stlopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+	struct tty	*tp;
+	stlport_t	*portp;
+	int		error, callout, x;
+
+#if DEBUG
+	printf("stlopen(dev=%x,flag=%x,mode=%x,p=%x)\n", (int) dev, flag,
+		mode, (int) p);
+#endif
+
+/*
+ *	Firstly check if the supplied device number is a valid device.
+ */
+	portp = stl_dev2port(dev);
+	if (portp == (stlport_t *) NULL)
+		return(ENXIO);
+
+	tp = &portp->tty;
+	callout = minor(dev) & STL_CALLOUTDEV;
+	error = 0;
+
+	x = spltty();
+
+stlopen_restart:
+/*
+ *	Wait here for the DTR drop timeout period to expire.
+ */
+	while (portp->state & ASY_DTRWAIT) {
+		error = tsleep(&portp->dtrwait, (TTIPRI | PCATCH),
+			"stldtr", 0);
+		if (error)
+			goto stlopen_end;
+	}
+	
+/*
+ *	We have a valid device, so now we check if it is already open.
+ *	If not then initialize the port hardware and set up the tty
+ *	struct as required.
+ */
+	if ((tp->t_state & TS_ISOPEN) == 0) {
+		tp->t_oproc = stl_start;
+		tp->t_param = stl_param;
+		tp->t_dev = dev;
+		tp->t_termios = callout ? portp->initouttios :
+			portp->initintios;
+		stl_rawopen(portp);
+		ttsetwater(tp);
+		if ((portp->sigs & TIOCM_CD) || callout)
+			(*linesw[tp->t_line].l_modem)(tp, 1);
+	} else {
+		if (callout) {
+			if (portp->callout == 0) {
+				error = EBUSY;
+				goto stlopen_end;
+			}
+		} else {
+			if (callout) {
+				if (flag & O_NONBLOCK) {
+					error = EBUSY;
+					goto stlopen_end;
+				}
+				error = tsleep(&portp->callout,
+					(TTIPRI | PCATCH), "stlcall", 0);
+				if (error)
+					goto stlopen_end;
+				goto stlopen_restart;
+			}
+		}
+		if ((tp->t_state & TS_XCLUDE) && (p->p_ucred->cr_uid != 0)) {
+			error = EBUSY;
+			goto stlopen_end;
+		}
+	}
+
+/*
+ *	If this port is not the callout device and we do not have carrier
+ *	then we need to sleep, waiting for it to be asserted.
+ */
+	if (((tp->t_state & TS_CARR_ON) == 0) && !callout &&
+			((tp->t_cflag & CLOCAL) == 0) &&
+			((flag & O_NONBLOCK) == 0)) {
+		portp->waitopens++;
+		error = tsleep(&tp->t_rawq, (TTIPRI | PCATCH), "stldcd", 0);
+		portp->waitopens--;
+		if (error)
+			goto stlopen_end;
+		goto stlopen_restart;
+	}
+
+/*
+ *	Open the line discipline.
+ */
+	error = (*linesw[tp->t_line].l_open)(dev, tp);
+	if ((tp->t_state & TS_ISOPEN) && callout)
+		portp->callout = 1;
+
+/*
+ *	If for any reason we get to here and the port is not actually
+ *	open then close of the physical hardware - no point leaving it
+ *	active when the open failed...
+ */
+stlopen_end:
+	splx(x);
+	if ((tp->t_state & TS_ISOPEN) == 0)
+		stl_rawclose(portp);
+
+	return(error);
+}
+
+/*****************************************************************************/
+
+int stlclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+	struct tty	*tp;
+	stlport_t	*portp;
+	int		x;
+
+#if DEBUG
+	printf("stlclose(dev=%x,flag=%x,mode=%x,p=%x)\n", dev, flag, mode, p);
+#endif
+
+	portp = stl_dev2port(dev);
+	if (portp == (stlport_t *) NULL)
+		return(ENXIO);
+	tp = &portp->tty;
+
+	x = spltty();
+	(*linesw[tp->t_line].l_close)(tp, flag);
+	stl_rawclose(portp);
+	ttyclose(tp);
+	splx(x);
+	return(0);
+}
+
+/*****************************************************************************/
+
+int stlread(dev_t dev, struct uio *uiop, int flag)
+{
+	stlport_t	*portp;
+
+#if DEBUG
+	printf("stlread(dev=%x,uiop=%x,flag=%x)\n", dev, uiop, flag);
+#endif
+
+	portp = stl_dev2port(dev);
+	if (portp == (stlport_t *) NULL)
+		return(ENODEV);
+	return((*linesw[portp->tty.t_line].l_read)(&portp->tty, uiop, flag));
+}
+
+/*****************************************************************************/
+
+int stlstop(struct tty *tp, int rw)
+{
+#if DEBUG
+	printf("stlstop(tp=%x,rw=%x)\n", (int) tp, rw);
+#endif
+
+	stl_flush((stlport_t *) tp, rw);
+	return(0);
+}
+
+/*****************************************************************************/
+
+struct tty *stldevtotty(dev_t dev)
+{
+#if DEBUG
+	printf("stldevtotty(dev=%x)\n", dev);
+#endif
+	return((struct tty *) stl_dev2port(dev));
+}
+
+/*****************************************************************************/
+
+int stlwrite(dev_t dev, struct uio *uiop, int flag)
+{
+	stlport_t	*portp;
+
+#if DEBUG
+	printf("stlwrite(dev=%x,uiop=%x,flag=%x)\n", dev, uiop, flag);
+#endif
+
+	portp = stl_dev2port(dev);
+	if (portp == (stlport_t *) NULL)
+		return(ENODEV);
+	return((*linesw[portp->tty.t_line].l_write)(&portp->tty, uiop, flag));
+}
+
+/*****************************************************************************/
+
+int stlioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
+{
+	struct termios	*newtios, *localtios;
+	struct tty	*tp;
+	stlport_t	*portp;
+	int		error, i, x;
+
+#if DEBUG
+	printf("stlioctl(dev=%x,cmd=%x,data=%x,flag=%x,p=%x)\n", dev, cmd,
+		data, flag, p);
+#endif
+
+	portp = stl_dev2port(dev);
+	dev = minor(dev);
+	if (portp == (stlport_t *) NULL)
+		return(ENODEV);
+	tp = &portp->tty;
+	error = 0;
+	
+/*
+ *	First up handle ioctls on the control devices.
+ */
+	if (dev & STL_CTRLDEV) {
+		if ((dev & STL_CTRLDEV) == STL_CTRLINIT)
+			localtios = (dev & STL_CALLOUTDEV) ?
+				&portp->initouttios : &portp->initintios;
+		else if ((dev & STL_CTRLDEV) == STL_CTRLLOCK)
+			localtios = (dev & STL_CALLOUTDEV) ?
+				&portp->lockouttios : &portp->lockintios;
+		else
+			return(ENODEV);
+
+		switch (cmd) {
+		case TIOCSETA:
+			if ((error = suser(p->p_ucred, &p->p_acflag)) == 0)
+				*localtios = *((struct termios *) data);
+			break;
+		case TIOCGETA:
+			*((struct termios *) data) = *localtios;
+			break;
+		case TIOCGETD:
+			*((int *) data) = TTYDISC;
+			break;
+		case TIOCGWINSZ:
+			bzero(data, sizeof(struct winsize));
+			break;
+		default:
+			error = ENOTTY;
+			break;
+		}
+		return(error);
+	}
+
+/*
+ *	Deal with 4.3 compatability issues if we have too...
+ */
+#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
+	if (1) {
+		struct termios	tios;
+		int		oldcmd;
+
+		tios = tp->t_termios;
+		oldcmd = cmd;
+		if ((error = ttsetcompat(tp, &cmd, data, &tios)))
+			return(error);
+		if (cmd != oldcmd)
+			data = (caddr_t) &tios;
+	}
+#endif
+
+#if 0
+/*
+ *	Carry out some pre-cmd processing work first...
+ *	Hmmm, not so sure we want this, disable for now...
+ */
+	if ((cmd == TIOCSETA) || (cmd == TIOCSETAW) || (cmd == TIOCSETAF)) {
+		newtios = (struct termios *) data;
+		localtios = (dev & STL_CALLOUTDEV) ? &portp->initouttios :
+			 &portp->initintios;
+
+		newtios->c_iflag = (tp->t_iflag & localtios->c_iflag) |
+			(newtios->c_iflag & ~localtios->c_iflag);
+		newtios->c_oflag = (tp->t_oflag & localtios->c_oflag) |
+			(newtios->c_oflag & ~localtios->c_oflag);
+		newtios->c_cflag = (tp->t_cflag & localtios->c_cflag) |
+			(newtios->c_cflag & ~localtios->c_cflag);
+		newtios->c_lflag = (tp->t_lflag & localtios->c_lflag) |
+			(newtios->c_lflag & ~localtios->c_lflag);
+		for (i = 0; (i < NCCS); i++) {
+			if (localtios->c_cc[i] != 0)
+				newtios->c_cc[i] = tp->t_cc[i];
+		}
+		if (localtios->c_ispeed != 0)
+			newtios->c_ispeed = tp->t_ispeed;
+		if (localtios->c_ospeed != 0)
+			newtios->c_ospeed = tp->t_ospeed;
+	}
+#endif
+
+/*
+ *	Call the line discipline and the common command processing to
+ *	process this command (if they can).
+ */
+	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
+	if (error >= 0)
+		return(error);
+
+	x = spltty();
+	error = ttioctl(tp, cmd, data, flag);
+	if (error >= 0) {
+		splx(x);
+		return(error);
+	}
+
+	error = 0;
+
+/*
+ *	Process local commands here. These are all commands that only we
+ *	can take care of (they all rely on actually doing something special
+ *	to the actual hardware).
+ */
+	switch (cmd) {
+	case TIOCSBRK:
+		stl_sendbreak(portp, -1);
+		break;
+	case TIOCCBRK:
+		stl_sendbreak(portp, -2);
+		break;
+	case TIOCSDTR:
+		stl_setsignals(portp, 1, -1);
+		break;
+	case TIOCCDTR:
+		stl_setsignals(portp, 0, -1);
+		break;
+	case TIOCMSET:
+		i = *((int *) data);
+		stl_setsignals(portp, ((i & TIOCM_DTR) ? 1 : 0),
+			((i & TIOCM_RTS) ? 1 : 0));
+		break;
+	case TIOCMBIS:
+		i = *((int *) data);
+		stl_setsignals(portp, ((i & TIOCM_DTR) ? 1 : -1),
+			((i & TIOCM_RTS) ? 1 : -1));
+		break;
+	case TIOCMBIC:
+		i = *((int *) data);
+		stl_setsignals(portp, ((i & TIOCM_DTR) ? 0 : -1),
+			((i & TIOCM_RTS) ? 0 : -1));
+		break;
+	case TIOCMGET:
+		stl_getsignals(portp);
+		*((int *) data) = (portp->sigs | TIOCM_LE);
+		break;
+	case TIOCMSDTRWAIT:
+		if ((error = suser(p->p_ucred, &p->p_acflag)) == 0)
+			portp->dtrwait = *((int *) data) * hz / 100;
+		break;
+	case TIOCMGDTRWAIT:
+		*((int *) data) = portp->dtrwait * 100 / hz;
+		break;
+	case TIOCTIMESTAMP:
+		portp->dotimestamp = 1;
+		*((struct timeval *) data) = portp->timestamp;
+		break;
+	default:
+		error = ENOTTY;
+		break;
+	}
+	splx(x);
+
+	return(error);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Convert the specified minor device number into a port struct
+ *	pointer. Return NULL if the device number is not a valid port.
+ */
+
+static stlport_t *stl_dev2port(dev_t dev)
+{
+	stlbrd_t	*brdp;
+	int		brdnr, portnr;
+
+	dev = minor(dev);
+	brdnr = MKDEV2BRD(dev);
+	if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+		return((stlport_t *) NULL);
+	brdp = stl_brds[brdnr];
+	if (brdp == (stlbrd_t *) NULL)
+		return((stlport_t *) NULL);
+	portnr = MKDEV2PORT(dev);
+	if ((portnr < 0) || (portnr >= brdp->nrports))
+		return((stlport_t *) NULL);
+	return(brdp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize the port hardware. This involves enabling the transmitter
+ *	and receiver, setting the port configuration, and setting the initial
+ *	signal state.
+ */
+
+static int stl_rawopen(stlport_t *portp)
+{
+#if DEBUG
+	printf("stl_rawopen(portp=%x): brdnr=%d panelnr=%d portnr=%d\n",
+		portp, portp->brdnr, portp->panelnr, portp->portnr);
+#endif
+	stl_param(&portp->tty, &portp->tty.t_termios);
+	stl_getsignals(portp);
+	stl_setsignals(portp, 1, 1);
+	stl_enablerxtx(portp, 1, 1);
+	stl_startrxtx(portp, 1, 0);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Shutdown the hardware of a port. Disable its transmitter and
+ *	receiver, and maybe drop signals if appropriate.
+ */
+
+static int stl_rawclose(stlport_t *portp)
+{
+	struct tty	*tp;
+
+#if DEBUG
+	printf("stl_rawclose(portp=%x): brdnr=%d panelnr=%d portnr=%d\n",
+		portp, portp->brdnr, portp->panelnr, portp->portnr);
+#endif
+
+	tp = &portp->tty;
+	stl_disableintrs(portp);
+	stl_enablerxtx(portp, 0, 0);
+	stl_flush(portp, (FWRITE | FREAD));
+	if (tp->t_cflag & HUPCL) {
+		stl_setsignals(portp, 0, 0);
+		if (portp->dtrwait != 0) {
+			portp->state |= ASY_DTRWAIT;
+			timeout(stl_dtrwakeup, portp, portp->dtrwait);
+		}
+	}
+	portp->callout = 0;
+	portp->brklen = 0;
+	portp->state &= ~(ASY_ACTIVE | ASY_RTSFLOW);
+	wakeup(&portp->callout);
+	wakeup(&tp->t_rawq);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Clear the DTR waiting flag, and wake up any sleepers waiting for
+ *	DTR wait period to finish.
+ */
+
+static void stl_dtrwakeup(void *arg)
+{
+	stlport_t	*portp;
+
+	portp = (stlport_t *) arg;
+	portp->state &= ~ASY_DTRWAIT;
+	wakeup(&portp->dtrwait);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start (or continue) the transfer of TX data on this port. If the
+ *	port is not currently busy then load up the interrupt ring queue
+ *	buffer and kick of the transmitter. If the port is running low on
+ *	TX data then refill the ring queue. This routine is also used to
+ *	activate input flow control!
+ */
+
+static void stl_start(struct tty *tp)
+{
+	stlport_t	*portp;
+	unsigned int	len, stlen;
+	char		*head, *tail;
+	int		count, x;
+
+	portp = (stlport_t *) tp;
+
+#if DEBUG
+	printf("stl_start(tp=%x): brdnr=%d portnr=%d\n", (int) tp, 
+		portp->brdnr, portp->portnr);
+#endif
+
+	x = spltty();
+
+/*
+ *	Check if the ports input has been blocked, and take appropriate action.
+ *	Not very often do we really need to do anything, so make it quick.
+ */
+	if (tp->t_state & TS_TBLOCK) {
+		if ((portp->state & ASY_RTSFLOW) == 0)
+			stl_flowcontrol(portp, 0, -1);
+	} else {
+		if (portp->state & ASY_RTSFLOW)
+			stl_flowcontrol(portp, 1, -1);
+	}
+
+/*
+ *	Check if the output cooked clist buffers are near empty, wake up
+ *	the line discipline to fill it up.
+ */
+	if (tp->t_outq.c_cc <= tp->t_lowat) {
+		if (tp->t_state & TS_ASLEEP) {
+			tp->t_state &= ~TS_ASLEEP;
+			wakeup(&tp->t_outq);
+		}
+		selwakeup(&tp->t_wsel);
+	}
+
+/*
+ *	Copy data from the clists into the interrupt ring queue. This will
+ *	require at most 2 copys... What we do is calculate how many chars
+ *	can fit into the ring queue, and how many can fit in 1 copy. If after
+ *	the first copy there is still more room then do the second copy. 
+ *	The beauty of this type of ring queue is that we do not need to
+ *	spl protect our-selves, since we only ever update the head pointer,
+ *	and the interrupt routine only ever updates the tail pointer.
+ */
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	if (head >= tail) {
+		len = STL_TXBUFSIZE - (head - tail) - 1;
+		stlen = portp->tx.endbuf - head;
+	} else {
+		len = tail - head - 1;
+		stlen = len;
+	}
+
+	if (len > 0) {
+		stlen = MIN(len, stlen);
+		count = q_to_b(&tp->t_outq, head, stlen);
+		len -= count;
+		head += count;
+		if (head >= portp->tx.endbuf) {
+			head = portp->tx.buf;
+			if (len > 0) {
+				stlen = q_to_b(&tp->t_outq, head, len);
+				head += stlen;
+				count += stlen;
+			}
+		}
+		portp->tx.head = head;
+		if (count > 0)
+			stl_startrxtx(portp, -1, 1);
+	}
+
+	splx(x);
+}
+
+/*****************************************************************************/
+
+static void stl_flush(stlport_t *portp, int flag)
+{
+	char	*head, *tail;
+	int	len;
+
+#if DEBUG
+	printf("stl_flush(portp=%x,flag=%x)\n", (int) portp, flag);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	disable_intr();
+
+	if (flag & FWRITE) {
+		BRDENABLE(portp->brdnr, portp->pagenr);
+		stl_setreg(portp, CAR, (portp->portnr & 0x03));
+		stl_ccrwait(portp);
+		stl_setreg(portp, CCR, CCR_TXFLUSHFIFO);
+		stl_ccrwait(portp);
+		portp->tx.tail = portp->tx.head;
+		BRDDISABLE(portp->brdnr);
+	}
+
+/*
+ *	The only thing to watch out for when flushing the read side is
+ *	the RX status buffer. The interrupt code relys on the status
+ *	bytes as being zeroed all the time (it does not bother setting
+ *	a good char status to 0, it expects that it already will be).
+ *	We also need to un-flow the RX channel if flow control was
+ *	active.
+ */
+	if (flag & FREAD) {
+		head = portp->rx.head;
+		tail = portp->rx.tail;
+		if (head != tail) {
+			if (head >= tail) {
+				len = head - tail;
+			} else {
+				len = portp->rx.endbuf - tail;
+				bzero(portp->rxstatus.buf,
+					(head - portp->rx.buf));
+			}
+			bzero((tail + STL_RXBUFSIZE), len);
+			portp->rx.tail = head;
+		}
+
+		if ((portp->state & ASY_RTSFLOW) &&
+				((portp->tty.t_state & TS_TBLOCK) == 0))
+			stl_flowcontrol(portp, 1, -1);
+	}
+
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	These functions get/set/update the registers of the cd1400 UARTs.
+ *	Access to the cd1400 registers is via an address/data io port pair.
+ *	(Maybe should make this inline...)
+ */
+
+static int stl_getreg(stlport_t *portp, int regnr)
+{
+	outb(portp->ioaddr, (regnr + portp->uartaddr));
+	return(inb(portp->ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+static void stl_setreg(stlport_t *portp, int regnr, int value)
+{
+	outb(portp->ioaddr, (regnr + portp->uartaddr));
+	outb((portp->ioaddr + EREG_DATA), value);
+}
+
+/*****************************************************************************/
+
+static int stl_updatereg(stlport_t *portp, int regnr, int value)
+{
+	outb(portp->ioaddr, (regnr + portp->uartaddr));
+	if (inb(portp->ioaddr + EREG_DATA) != value) {
+		outb((portp->ioaddr + EREG_DATA), value);
+		return(1);
+	}
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Wait for the command register to be ready. We will poll this, since
+ *	it won't usually take too long to be ready, and it is only really
+ *	used for non-critical actions.
+ */
+
+static void stl_ccrwait(stlport_t *portp)
+{
+	int	i;
+
+	for (i = 0; (i < CCR_MAXWAIT); i++) {
+		if (stl_getreg(portp, CCR) == 0) {
+			return;
+		}
+	}
+
+	printf("STALLION: cd1400 device not responding, brd=%d panel=%d"
+		"port=%d\n", portp->brdnr, portp->panelnr, portp->portnr);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Transmit interrupt handler. This has gotta be fast!  Handling TX
+ *	chars is pretty simple, stuff as many as possible from the TX buffer
+ *	into the cd1400 FIFO. Must also handle TX breaks here, since they
+ *	are embedded as commands in the data stream. Oh no, had to use a goto!
+ *	This could be optimized more, will do when I get time...
+ *	In practice it is possible that interrupts are enabled but that the
+ *	port has been hung up. Need to handle not having any TX buffer here,
+ *	this is done by using the side effect that head and tail will also
+ *	be NULL if the buffer has been freed.
+ */
+
+static inline void stl_txisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t	*portp;
+	int		len, stlen;
+	char		*head, *tail;
+	unsigned char	ioack, srer;
+
+#if DEBUG
+	printf("stl_txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+	ioack = inb(ioaddr + EREG_TXACK);
+	if (((ioack & panelp->ackmask) != 0) ||
+			((ioack & ACK_TYPMASK) != ACK_TYPTX)) {
+		printf("STALLION: bad TX interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+
+/*
+ *	Unfortunately we need to handle breaks in the data stream, since
+ *	this is the only way to generate them on the cd1400. Do it now if
+ *	a break is to be sent. Some special cases here: brklen is -1 then
+ *	start sending an un-timed break, if brklen is -2 then stop sending
+ *	an un-timed break, if brklen is -3 then we have just sent an
+ *	un-timed break and do not want any data to go out, if brklen is -4
+ *	then a break has just completed so clean up the port settings.
+ */
+	if (portp->brklen != 0) {
+		if (portp->brklen >= -1) {
+			outb(ioaddr, (TDR + portp->uartaddr));
+			outb((ioaddr + EREG_DATA), ETC_CMD);
+			outb((ioaddr + EREG_DATA), ETC_STARTBREAK);
+			if (portp->brklen > 0) {
+				outb((ioaddr + EREG_DATA), ETC_CMD);
+				outb((ioaddr + EREG_DATA), ETC_DELAY);
+				outb((ioaddr + EREG_DATA), portp->brklen);
+				outb((ioaddr + EREG_DATA), ETC_CMD);
+				outb((ioaddr + EREG_DATA), ETC_STOPBREAK);
+				portp->brklen = -4;
+			} else {
+				portp->brklen = -3;
+			}
+		} else if (portp->brklen == -2) {
+			outb(ioaddr, (TDR + portp->uartaddr));
+			outb((ioaddr + EREG_DATA), ETC_CMD);
+			outb((ioaddr + EREG_DATA), ETC_STOPBREAK);
+			portp->brklen = -4;
+		} else if (portp->brklen == -3) {
+			outb(ioaddr, (SRER + portp->uartaddr));
+			srer = inb(ioaddr + EREG_DATA);
+			srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
+			outb((ioaddr + EREG_DATA), srer);
+		} else {
+			outb(ioaddr, (COR2 + portp->uartaddr));
+			outb((ioaddr + EREG_DATA),
+				(inb(ioaddr + EREG_DATA) & ~COR2_ETC));
+			portp->brklen = 0;
+		}
+		goto stl_txalldone;
+	}
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+	if ((len == 0) || ((len < STL_TXBUFLOW) &&
+			((portp->state & ASY_TXLOW) == 0))) {
+		portp->state |= ASY_TXLOW;
+		stl_dotimeout();
+	}
+
+	if (len == 0) {
+		outb(ioaddr, (SRER + portp->uartaddr));
+		srer = inb(ioaddr + EREG_DATA);
+		if (srer & SRER_TXDATA) {
+			srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY;
+		} else {
+			srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
+			portp->tty.t_state &= ~TS_BUSY;
+		}
+		outb((ioaddr + EREG_DATA), srer);
+	} else {
+		len = MIN(len, CD1400_TXFIFOSIZE);
+		stlen = MIN(len, (portp->tx.endbuf - tail));
+		outb(ioaddr, (TDR + portp->uartaddr));
+		outsb((ioaddr + EREG_DATA), tail, stlen);
+		len -= stlen;
+		tail += stlen;
+		if (tail >= portp->tx.endbuf)
+			tail = portp->tx.buf;
+		if (len > 0) {
+			outsb((ioaddr + EREG_DATA), tail, len);
+			tail += len;
+		}
+		portp->tx.tail = tail;
+	}
+
+stl_txalldone:
+	outb(ioaddr, (EOSRR + portp->uartaddr));
+	outb((ioaddr + EREG_DATA), 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Receive character interrupt handler. Determine if we have good chars
+ *	or bad chars and then process appropriately. Good chars are easy
+ *	just shove the lot into the RX buffer and set all status bytes to 0.
+ *	If a bad RX char then process as required. This routine needs to be
+ *	fast!
+ */
+
+static inline void stl_rxisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t	*portp;
+	struct tty	*tp;
+	unsigned int	ioack, len, buflen, stlen;
+	unsigned char	status;
+	char		ch;
+	char		*head, *tail;
+	static char	unwanted[CD1400_RXFIFOSIZE];
+
+#if DEBUG
+	printf("stl_rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+	ioack = inb(ioaddr + EREG_RXACK);
+	if ((ioack & panelp->ackmask) != 0) {
+		printf("STALLION: bad RX interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+	tp = &portp->tty;
+
+/*
+ *	First up, caluclate how much room there is in the RX ring queue.
+ *	We also want to keep track of the longest possible copy length,
+ *	this has to allow for the wrapping of the ring queue.
+ */
+	head = portp->rx.head;
+	tail = portp->rx.tail;
+	if (head >= tail) {
+		buflen = STL_RXBUFSIZE - (head - tail) - 1;
+		stlen = portp->rx.endbuf - head;
+	} else {
+		buflen = tail - head - 1;
+		stlen = buflen;
+	}
+
+/*
+ *	Check if the input buffer is near full. If so then we should take
+ *	some flow control action... It is very easy to do hardware and
+ *	software flow control from here since we have the port selected on
+ *	the UART.
+ */
+	if (buflen <= (STL_RXBUFSIZE - STL_RXBUFHIGH)) {
+		if (((portp->state & ASY_RTSFLOW) == 0) &&
+				(tp->t_cflag & CRTS_IFLOW)) {
+			portp->state |= ASY_RTSFLOW;
+			stl_setreg(portp, MCOR1,
+				(stl_getreg(portp, MCOR1) & 0xf0));
+			stl_setreg(portp, MSVR2, 0);
+		}
+	}
+
+/*
+ *	OK we are set, process good data... If the RX ring queue is full
+ *	just chuck the chars - don't leave them in the UART.
+ */
+	if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) {
+		outb(ioaddr, (RDCR + portp->uartaddr));
+		len = inb(ioaddr + EREG_DATA);
+		if (buflen == 0) {
+			outb(ioaddr, (RDSR + portp->uartaddr));
+			insb((ioaddr + EREG_DATA), &unwanted[0], len);
+			portp->rxerrs[STL_RXLOST] += len;
+		} else {
+			len = MIN(len, buflen);
+			stlen = MIN(len, stlen);
+			if (len > 0) {
+				outb(ioaddr, (RDSR + portp->uartaddr));
+				insb((ioaddr + EREG_DATA), head, stlen);
+				head += stlen;
+				if (head >= portp->rx.endbuf) {
+					head = portp->rx.buf;
+					len -= stlen;
+					insb((ioaddr + EREG_DATA), head, len);
+					head += len;
+				}
+			}
+		}
+	} else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) {
+		outb(ioaddr, (RDSR + portp->uartaddr));
+		status = inb(ioaddr + EREG_DATA);
+		ch = inb(ioaddr + EREG_DATA);
+		if (status & ST_BREAK)
+			portp->rxerrs[STL_RXBREAK]++;
+		if (status & ST_FRAMING)
+			portp->rxerrs[STL_RXFRAMING]++;
+		if (status & ST_PARITY)
+			portp->rxerrs[STL_RXPARITY]++;
+		if (status & ST_OVERRUN)
+			portp->rxerrs[STL_RXOVERRUN]++;
+		if ((portp->rxignoremsk & status) == 0) {
+			if ((portp->rxmarkmsk & status) == 0)
+				status = 0;
+			*(head + STL_RXBUFSIZE) = status;
+			*head++ = ch;
+			if (head >= portp->rx.endbuf)
+				head = portp->rx.buf;
+		}
+	} else {
+		printf("STALLION: bad RX interrupt ack value=%x\n", ioack);
+		return;
+	}
+
+	portp->rx.head = head;
+	portp->state |= ASY_RXDATA;
+	stl_dotimeout();
+
+	outb(ioaddr, (EOSRR + portp->uartaddr));
+	outb((ioaddr + EREG_DATA), 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Modem interrupt handler. The is called when the modem signal line
+ *	(DCD) has changed state. Leave most of the work to the off-level
+ *	processing routine.
+ */
+
+static inline void stl_mdmisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t	*portp;
+	unsigned int	ioack;
+	unsigned char	misr;
+
+#if DEBUG
+	printf("stl_mdmisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+	ioack = inb(ioaddr + EREG_MDACK);
+	if (((ioack & panelp->ackmask) != 0) ||
+			((ioack & ACK_TYPMASK) != ACK_TYPMDM)) {
+		printf("STALLION: bad MODEM interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+
+	outb(ioaddr, (MISR + portp->uartaddr));
+	misr = inb(ioaddr + EREG_DATA);
+	if (misr & MISR_DCD) {
+		portp->state |= ASY_DCDCHANGE;
+		stl_dotimeout();
+	}
+
+	outb(ioaddr, (EOSRR + portp->uartaddr));
+	outb((ioaddr + EREG_DATA), 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt handler for EIO and ECH boards. This code ain't all that
+ *	pretty, but the idea is to make it as fast as possible. This code is
+ *	well suited to be assemblerized :-)  We don't use the general purpose
+ *	register access functions here, for speed we will go strait to the
+ *	io register.
+ */
+
+void stlintr(int unit)
+{
+	stlbrd_t	*brdp;
+	stlpanel_t	*panelp;
+	unsigned char	svrtype;
+	int		i, panelnr, iobase;
+	int		cnt;
+
+#if DEBUG
+	printf("stlintr(unit=%d)\n", unit);
+#endif
+
+	cnt = 0;
+	panelp = (stlpanel_t *) NULL;
+	for (i = 0; (i < stl_nrbrds); ) {
+		if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) {
+			i++;
+			continue;
+		}
+		if (brdp->state == 0) {
+			i++;
+			continue;
+		}
+/*
+ *		The following section of code handles the subtle differences
+ *		between board types. It is sort of similar, but different
+ *		enough to handle each separately.
+ */
+		if (brdp->brdtype == BRD_EASYIO) {
+			if ((inb(brdp->iostatus) & EIO_INTRPEND) == 0) {
+				i++;
+				continue;
+			}
+			panelp = brdp->panels[0];
+			iobase = panelp->iobase;
+			outb(iobase, SVRR);
+			svrtype = inb(iobase + EREG_DATA);
+			if (brdp->nrports > 4) {
+				outb(iobase, (SVRR + 0x80));
+				svrtype |= inb(iobase + EREG_DATA);
+			}
+		} else if (brdp->brdtype == BRD_ECH) {
+			if ((inb(brdp->iostatus) & ECH_INTRPEND) == 0) {
+				i++;
+				continue;
+			}
+			outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDENABLE));
+			for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+				panelp = brdp->panels[panelnr];
+				iobase = panelp->iobase;
+				if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+					break;
+				if (panelp->nrports > 8) {
+					iobase += 0x8;
+					if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+						break;
+				}
+			}	
+			if (panelnr >= brdp->nrpanels) {
+				i++;
+				continue;
+			}
+			outb(iobase, SVRR);
+			svrtype = inb(iobase + EREG_DATA);
+			outb(iobase, (SVRR + 0x80));
+			svrtype |= inb(iobase + EREG_DATA);
+		} else if (brdp->brdtype == BRD_ECHPCI) {
+			iobase = brdp->ioaddr2;
+			for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+				panelp = brdp->panels[panelnr];
+				outb(brdp->ioctrl, panelp->pagenr);
+				if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+					break;
+				if (panelp->nrports > 8) {
+					outb(brdp->ioctrl, (panelp->pagenr + 1));
+					if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+						break;
+				}
+			}	
+			if (panelnr >= brdp->nrpanels) {
+				i++;
+				continue;
+			}
+			outb(iobase, SVRR);
+			svrtype = inb(iobase + EREG_DATA);
+			outb(iobase, (SVRR + 0x80));
+			svrtype |= inb(iobase + EREG_DATA);
+		} else if (brdp->brdtype == BRD_ECHMC) {
+			if ((inb(brdp->iostatus) & ECH_INTRPEND) == 0) {
+				i++;
+				continue;
+			}
+			for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+				panelp = brdp->panels[panelnr];
+				iobase = panelp->iobase;
+				if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+					break;
+				if (panelp->nrports > 8) {
+					iobase += 0x8;
+					if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
+						break;
+				}
+			}	
+			if (panelnr >= brdp->nrpanels) {
+				i++;
+				continue;
+			}
+			outb(iobase, SVRR);
+			svrtype = inb(iobase + EREG_DATA);
+			outb(iobase, (SVRR + 0x80));
+			svrtype |= inb(iobase + EREG_DATA);
+		} else {
+			printf("STALLION: unknown board type=%x\n", brdp->brdtype);
+			i++;
+			continue;
+		}
+
+/*
+ *		We have determined what type of service is required for a
+ *		port. From here on in the service of a port is the same no
+ *		matter what the board type...
+ */
+		if (svrtype & SVRR_RX)
+			stl_rxisr(panelp, iobase);
+		if (svrtype & SVRR_TX)
+			stl_txisr(panelp, iobase);
+		if (svrtype & SVRR_MDM)
+			stl_mdmisr(panelp, iobase);
+
+		if (brdp->brdtype == BRD_ECH)
+			outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDDISABLE));
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	If we haven't scheduled a timeout then do it, some port needs high
+ *	level processing.
+ */
+
+static void stl_dotimeout()
+{
+#if DEBUG
+	printf("stl_dotimeout()\n");
+#endif
+
+	if (stl_doingtimeout == 0) {
+		timeout(stl_poll, 0, 1);
+		stl_doingtimeout++;
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Service "software" level processing. Too slow or painfull to be done
+ *	at real hardware interrupt time. This way we might also be able to
+ *	do some service on other waiting ports as well...
+ */
+
+static void stl_poll(void *arg)
+{
+	stlbrd_t	*brdp;
+	stlport_t	*portp;
+	struct tty	*tp;
+	int		brdnr, portnr, rearm, x;
+
+#if DEBUG
+	printf("stl_poll()\n");
+#endif
+
+	stl_doingtimeout = 0;
+	rearm = 0;
+
+	x = spltty();	/* Hmmm, do we need this??? */
+	for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) {
+		if ((brdp = stl_brds[brdnr]) == (stlbrd_t *) NULL)
+			continue;
+		for (portnr = 0; (portnr < brdp->nrports); portnr++) {
+			if ((portp = brdp->ports[portnr]) == (stlport_t *) NULL)
+				continue;
+			if ((portp->state & ASY_ACTIVE) == 0)
+				continue;
+			tp = &portp->tty;
+
+			if (portp->state & ASY_RXDATA)
+				stl_rxprocess(portp);
+			if (portp->state & ASY_DCDCHANGE) {
+				portp->state &= ~ASY_DCDCHANGE;
+				stl_getsignals(portp);
+				(*linesw[tp->t_line].l_modem)(tp,
+					(portp->sigs & TIOCM_CD));
+			}
+			if (portp->state & ASY_TXLOW) {
+				portp->state &= ~ASY_TXLOW;
+				(*linesw[tp->t_line].l_start)(tp);
+			}
+
+			if (portp->state & ASY_ACTIVE)
+				rearm++;
+		}
+	}
+	splx(x);
+
+	if (rearm)
+		stl_dotimeout();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Process the RX data that has been buffered up in the RX ring queue.
+ */
+
+static void stl_rxprocess(stlport_t *portp)
+{
+	struct tty	*tp;
+	unsigned int	len, stlen;
+	char		*head, *tail;
+	char		status;
+	int		ch;
+
+#if DEBUG
+	printf("stl_rxprocess(portp=%x): brdnr=%d portnr=%d\n", (int) portp, 
+		portp->brdnr, portp->portnr);
+#endif
+
+	tp = &portp->tty;
+	portp->state &= ~ASY_RXDATA;
+
+	if ((tp->t_state & TS_ISOPEN) == 0) {
+		stl_flush(portp, FREAD);
+		return;
+	}
+
+/*
+ *	Calculate the amount of data in the RX ring queue. Also calculate
+ *	the largest single copy size...
+ */
+	head = portp->rx.head;
+	tail = portp->rx.tail;
+	if (head >= tail) {
+		len = head - tail;
+		stlen = len;
+	} else {
+		len = STL_RXBUFSIZE - (tail - head);
+		stlen = portp->rx.endbuf - tail;
+	}
+
+	if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
+#if 1
+printf("%s(%d): cannot TS_CAN_BYPASS_L_RINT!\n", __file__, __LINE__);
+#else
+		if (len > 0) {
+			lostlen = b_to_q(tail, stlen, &tp->t_rawq);
+			tail += stlen;
+			len -= stlen;
+			if (tail >= portp->rx.endbuf) {
+				tail = portp->rx.buf;
+				lostlen += b_to_q(tail, len, &tp->t_rawq);
+				tail += len;
+			}
+			portp->rxerrs[STL_RXLDLOST] += lostlen;
+			ttwakeup(tp);
+			if (tp->t_state & TS_TTSTOP) {
+				tp->t_state &= ~TS_TTSTOP;
+				tp->t_lflag &= ~FLUSHO;
+				ttstart(tp);
+			}
+			portp->rx.tail = tail;
+		}
+#endif
+	} else {
+		while (portp->rx.tail != head) {
+			ch = *(portp->rx.tail);
+			if (status = *(portp->rx.tail + STL_RXBUFSIZE)) {
+				*(portp->rx.tail + STL_RXBUFSIZE) = 0;
+				if (status & ST_BREAK)
+					ch |= TTY_BI;
+				if (status & ST_FRAMING)
+					ch |= TTY_FE;
+				if (status & ST_PARITY)
+					ch |= TTY_PE;
+				if (status & ST_OVERRUN)
+					ch |= TTY_OE;
+			}
+			(*linesw[tp->t_line].l_rint)(ch, tp);
+			if (portp->rx.tail == head)
+				break;
+
+			if (++(portp->rx.tail) >= portp->rx.endbuf)
+				portp->rx.tail = portp->rx.buf;
+		}
+	}
+
+	if (head != portp->rx.tail)
+		portp->state |= ASY_RXDATA;
+
+/*
+ *	If we where flow controled then maybe the buffer is low enough that
+ *	we can re-activate it.
+ */
+	if ((portp->state & ASY_RTSFLOW) && ((tp->t_state & TS_TBLOCK) == 0))
+		stl_flowcontrol(portp, 1, -1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set up the cd1400 registers for a port based on the termios port
+ *	settings.
+ */
+
+static int stl_param(struct tty *tp, struct termios *tiosp)
+{
+	stlport_t	*portp;
+	unsigned int	clkdiv;
+	unsigned char	cor1, cor2, cor3;
+	unsigned char	cor4, cor5, ccr;
+	unsigned char	srer, sreron, sreroff;
+	unsigned char	mcor1, mcor2, rtpr;
+	unsigned char	clk, div;
+
+	portp = (stlport_t *) tp;
+
+#if DEBUG
+	printf("stl_param(tp=%x,tiosp=%x): brdnr=%d portnr=%d\n", (int) tp, 
+		(int) tiosp, portp->brdnr, portp->portnr);
+#endif
+
+	cor1 = 0;
+	cor2 = 0;
+	cor3 = 0;
+	cor4 = 0;
+	cor5 = 0;
+	ccr = 0;
+	rtpr = 0;
+	clk = 0;
+	div = 0;
+	mcor1 = 0;
+	mcor2 = 0;
+	sreron = 0;
+	sreroff = 0;
+
+/*
+ *	Set up the RX char ignore mask with those RX error types we
+ *	can ignore. We could have used some special modes of the cd1400
+ *	UART to help, but it is better this way because we can keep stats
+ *	on the number of each type of RX exception event.
+ */
+	portp->rxignoremsk = 0;
+	if (tiosp->c_iflag & IGNPAR)
+		portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN);
+	if (tiosp->c_iflag & IGNBRK)
+		portp->rxignoremsk |= ST_BREAK;
+
+	portp->rxmarkmsk = ST_OVERRUN;
+	if (tiosp->c_iflag & (INPCK | PARMRK))
+		portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING);
+	if (tiosp->c_iflag & BRKINT)
+		portp->rxmarkmsk |= ST_BREAK;
+
+/*
+ *	Go through the char size, parity and stop bits and set all the
+ *	option registers appropriately.
+ */
+	switch (tiosp->c_cflag & CSIZE) {
+	case CS5:
+		cor1 |= COR1_CHL5;
+		break;
+	case CS6:
+		cor1 |= COR1_CHL6;
+		break;
+	case CS7:
+		cor1 |= COR1_CHL7;
+		break;
+	default:
+		cor1 |= COR1_CHL8;
+		break;
+	}
+
+	if (tiosp->c_cflag & CSTOPB)
+		cor1 |= COR1_STOP2;
+	else
+		cor1 |= COR1_STOP1;
+
+	if (tiosp->c_cflag & PARENB) {
+		if (tiosp->c_cflag & PARODD)
+			cor1 |= (COR1_PARENB | COR1_PARODD);
+		else
+			cor1 |= (COR1_PARENB | COR1_PAREVEN);
+	} else {
+		cor1 |= COR1_PARNONE;
+	}
+
+/*
+ *	Set the RX FIFO threshold at 6 chars. This gives a bit of breathing
+ *	space for hardware flow control and the like. This should be set to
+ *	VMIN. Also here we will set the RX data timeout to 10ms - this should
+ *	really be based on VTIME...
+ */
+	cor3 |= FIFO_RXTHRESHOLD;
+	rtpr = 2;
+
+/*
+ *	Calculate the baud rate timers. For now we will just assume that
+ *	the input and output baud are the same. Could have used a baud
+ *	table here, but this way we can generate virtually any baud rate
+ *	we like!
+ */
+	if (tiosp->c_ispeed == 0)
+		tiosp->c_ispeed = tiosp->c_ospeed;
+	if ((tiosp->c_ospeed < 0) || (tiosp->c_ospeed > STL_MAXBAUD))
+		return(EINVAL);
+
+	if (tiosp->c_ospeed > 0) {
+		for (clk = 0; (clk < CD1400_NUMCLKS); clk++) {
+			clkdiv = ((CD1400_CLKHZ / stl_cd1400clkdivs[clk]) /
+				tiosp->c_ospeed);
+			if (clkdiv < 0x100)
+				break;
+		}
+		div = (unsigned char) clkdiv;
+	}
+
+/*
+ *	Check what form of modem signaling is required and set it up.
+ */
+	if ((tiosp->c_cflag & CLOCAL) == 0) {
+		mcor1 |= MCOR1_DCD;
+		mcor2 |= MCOR2_DCD;
+		sreron |= SRER_MODEM;
+	}
+
+/*
+ *	Setup cd1400 enhanced modes if we can. In particular we want to
+ *	handle as much of the flow control as possbile automatically. As
+ *	well as saving a few CPU cycles it will also greatly improve flow
+ *	control reliablilty.
+ */
+	if (tiosp->c_iflag & IXON) {
+		cor2 |= COR2_TXIBE;
+		cor3 |= (COR3_FCT | COR3_SCD12);
+		if (tiosp->c_iflag & IXANY)
+			cor2 |= COR2_IXM;
+	}
+
+	if (tiosp->c_cflag & CCTS_OFLOW)
+		cor2 |= COR2_CTSAE;
+	if (tiosp->c_cflag & CRTS_IFLOW)
+		mcor1 |= FIFO_RTSTHRESHOLD;
+
+/*
+ *	All register cd1400 register values calculated so go through and set
+ *	them all up.
+ */
+
+#if DEBUG
+	printf("SETPORT: portnr=%d panelnr=%d brdnr=%d\n", portp->portnr,
+		portp->panelnr, portp->brdnr);
+	printf("    cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n", cor1, cor2,
+		cor3, cor4, cor5);
+	printf("    mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n",
+		mcor1, mcor2, rtpr, sreron, sreroff);
+	printf("    tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div);
+	printf("    schr1=%x schr2=%x schr3=%x schr4=%x\n",
+		tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP], tiosp->c_cc[VSTART],
+		tiosp->c_cc[VSTOP]);
+#endif
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x3));
+	srer = stl_getreg(portp, SRER);
+	stl_setreg(portp, SRER, 0);
+	if (stl_updatereg(portp, COR1, cor1))
+		ccr = 1;
+	if (stl_updatereg(portp, COR2, cor2))
+		ccr = 1;
+	if (stl_updatereg(portp, COR3, cor3))
+		ccr = 1;
+	if (ccr) {
+		stl_ccrwait(portp);
+		stl_setreg(portp, CCR, CCR_CORCHANGE);
+	}
+	stl_setreg(portp, COR4, cor4);
+	stl_setreg(portp, COR5, cor5);
+	stl_setreg(portp, MCOR1, mcor1);
+	stl_setreg(portp, MCOR2, mcor2);
+	if (tiosp->c_ospeed == 0) {
+		stl_setreg(portp, MSVR1, 0);
+	} else {
+		stl_setreg(portp, MSVR1, MSVR1_DTR);
+		stl_setreg(portp, TCOR, clk);
+		stl_setreg(portp, TBPR, div);
+		stl_setreg(portp, RCOR, clk);
+		stl_setreg(portp, RBPR, div);
+	}
+	stl_setreg(portp, SCHR1, tiosp->c_cc[VSTART]);
+	stl_setreg(portp, SCHR2, tiosp->c_cc[VSTOP]);
+	stl_setreg(portp, SCHR3, tiosp->c_cc[VSTART]);
+	stl_setreg(portp, SCHR4, tiosp->c_cc[VSTOP]);
+	stl_setreg(portp, RTPR, rtpr);
+	mcor1 = stl_getreg(portp, MSVR1);
+	if (mcor1 & MSVR1_DCD)
+		portp->sigs |= TIOCM_CD;
+	else
+		portp->sigs &= ~TIOCM_CD;
+	stl_setreg(portp, SRER, ((srer & ~sreroff) | sreron));
+	BRDDISABLE(portp->brdnr);
+	enable_intr();
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Action the flow control as required. The hw and sw args inform the
+ *	routine what flow control methods it should try.
+ */
+
+static void stl_flowcontrol(stlport_t *portp, int hw, int sw)
+{
+	unsigned char	*head, *tail;
+	int		len, hwflow;
+
+#if DEBUG
+	printf("stl_flowcontrol(portp=%x,hw=%d,sw=%d)\n", (int) portp, hw, sw);
+#endif
+
+	hwflow = -1;
+
+	if (portp->tty.t_cflag & CRTS_IFLOW) {
+		if (hw == 0) {
+			if ((portp->state & ASY_RTSFLOW) == 0)
+				hwflow = 0;
+		} else if (hw > 0) {
+			if (portp->state & ASY_RTSFLOW) {
+				head = portp->rx.head;
+				tail = portp->rx.tail;
+				len = (head >= tail) ? (head - tail) :
+					(STL_RXBUFSIZE - (tail - head));
+				if (len < STL_RXBUFHIGH)
+					hwflow = 1;
+			}
+		}
+	}
+
+/*
+ *	We have worked out what to do, if anything. So now apply it to the
+ *	UART port.
+ */
+	if (hwflow >= 0) {
+		disable_intr();
+		BRDENABLE(portp->brdnr, portp->pagenr);
+		stl_setreg(portp, CAR, (portp->portnr & 0x03));
+		if (hwflow == 0) {
+			portp->state |= ASY_RTSFLOW;
+			stl_setreg(portp, MCOR1,
+				(stl_getreg(portp, MCOR1) & 0xf0));
+			stl_setreg(portp, MSVR2, 0);
+		} else if (hwflow > 0) {
+			portp->state &= ~ASY_RTSFLOW;
+			stl_setreg(portp, MSVR2, MSVR2_RTS);
+			stl_setreg(portp, MCOR1,
+				(stl_getreg(portp, MCOR1) | FIFO_RTSTHRESHOLD));
+		}
+		BRDDISABLE(portp->brdnr);
+		enable_intr();
+	}
+}
+
+
+/*****************************************************************************/
+
+/*
+ *	Set the state of the DTR and RTS signals.
+ */
+
+static void stl_setsignals(stlport_t *portp, int dtr, int rts)
+{
+	unsigned char	msvr1, msvr2;
+
+#if DEBUG
+	printf("stl_setsignals(portp=%x,dtr=%d,rts=%d)\n", (int) portp,
+		dtr, rts);
+#endif
+
+	msvr1 = 0;
+	msvr2 = 0;
+	if (dtr > 0)
+		msvr1 = MSVR1_DTR;
+	if (rts > 0)
+		msvr2 = MSVR2_RTS;
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x03));
+	if (rts >= 0)
+		stl_setreg(portp, MSVR2, msvr2);
+	if (dtr >= 0)
+		stl_setreg(portp, MSVR1, msvr1);
+	BRDDISABLE(portp->brdnr);
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Get the state of the signals.
+ */
+
+static void stl_getsignals(stlport_t *portp)
+{
+	unsigned char	msvr1, msvr2;
+
+#if DEBUG
+	printf("stl_getsignals(portp=%x)\n", (int) portp);
+#endif
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x3));
+	msvr1 = stl_getreg(portp, MSVR1);
+	msvr2 = stl_getreg(portp, MSVR2);
+	BRDDISABLE(portp->brdnr);
+	portp->sigs = 0;
+	portp->sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
+	portp->sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
+	portp->sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
+	portp->sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
+	portp->sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
+	portp->sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Enable or disable the Transmitter and/or Receiver.
+ */
+
+static void stl_enablerxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	ccr;
+
+#if DEBUG
+	printf("stl_enablerxtx(portp=%x,rx=%d,tx=%d)\n", (int) portp, rx, tx);
+#endif
+
+	ccr = 0;
+	if (tx == 0)
+		ccr |= CCR_TXDISABLE;
+	else if (tx > 0)
+		ccr |= CCR_TXENABLE;
+	if (rx == 0)
+		ccr |= CCR_RXDISABLE;
+	else if (rx > 0)
+		ccr |= CCR_RXENABLE;
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_ccrwait(portp);
+	stl_setreg(portp, CCR, ccr);
+	stl_ccrwait(portp);
+	BRDDISABLE(portp->brdnr);
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start or stop the Transmitter and/or Receiver.
+ */
+
+static void stl_startrxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	sreron, sreroff;
+
+#if DEBUG
+	printf("stl_startrxtx(portp=%x,rx=%d,tx=%d)\n", (int) portp, rx, tx);
+#endif
+
+	sreron = 0;
+	sreroff = 0;
+	if (tx == 0)
+		sreroff |= (SRER_TXDATA | SRER_TXEMPTY);
+	else if (tx == 1)
+		sreron |= SRER_TXDATA;
+	else if (tx >= 2)
+		sreron |= SRER_TXEMPTY;
+	if (rx == 0)
+		sreroff |= SRER_RXDATA;
+	else if (rx > 0)
+		sreron |= SRER_RXDATA;
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x3));
+	stl_setreg(portp, SRER,
+		((stl_getreg(portp, SRER) & ~sreroff) | sreron));
+	BRDDISABLE(portp->brdnr);
+	if (tx > 0)
+		portp->tty.t_state |= TS_BUSY;
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Disable all interrupts from this port.
+ */
+
+static void stl_disableintrs(stlport_t *portp)
+{
+#if DEBUG
+	printf("stl_disableintrs(portp=%x)\n", (int) portp);
+#endif
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x3));
+	stl_setreg(portp, SRER, 0);
+	BRDDISABLE(portp->brdnr);
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+static void stl_sendbreak(stlport_t *portp, long len)
+{
+#if DEBUG
+	printf("stl_sendbreak(portp=%x,len=%d)\n", (int) portp, (int) len);
+#endif
+
+	disable_intr();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_setreg(portp, CAR, (portp->portnr & 0x3));
+	stl_setreg(portp, COR2, (stl_getreg(portp, COR2) | COR2_ETC));
+	stl_setreg(portp, SRER,
+		((stl_getreg(portp, SRER) & ~SRER_TXDATA) | SRER_TXEMPTY));
+	BRDDISABLE(portp->brdnr);
+	if (len > 0) {
+		len = len / 5;
+		portp->brklen = (len > 255) ? 255 : len;
+	} else {
+		portp->brklen = len;
+	}
+	enable_intr();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try and find and initialize all the ports on a panel. We don't care
+ *	what sort of board these ports are on - since the port io registers
+ *	are almost identical when dealing with ports.
+ */
+
+static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+	stlport_t	*portp;
+	unsigned int	chipmask;
+	unsigned int	gfrcr;
+	int		nrchips, uartaddr, ioaddr;
+	int		i, j;
+
+#if DEBUG
+	printf("stl_initports(panelp=%x)\n", (int) panelp);
+#endif
+
+	BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ *	Check that each chip is present and started up OK.
+ */
+	chipmask = 0;
+	nrchips = panelp->nrports / CD1400_PORTS;
+	for (i = 0; (i < nrchips); i++) {
+		if (brdp->brdtype == BRD_ECHPCI) {
+			outb(brdp->ioctrl, (panelp->pagenr + (i >> 1)));
+			ioaddr = panelp->iobase;
+		} else {
+			ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1));
+		}
+		uartaddr = (i & 0x01) ? 0x080 : 0;
+		outb(ioaddr, (GFRCR + uartaddr));
+		outb((ioaddr + EREG_DATA), 0);
+		outb(ioaddr, (CCR + uartaddr));
+		outb((ioaddr + EREG_DATA), CCR_RESETFULL);
+		outb((ioaddr + EREG_DATA), CCR_RESETFULL);
+		outb(ioaddr, (GFRCR + uartaddr));
+		for (j = 0; (j < CCR_MAXWAIT); j++) {
+			gfrcr = inb(ioaddr + EREG_DATA);
+			if ((gfrcr > 0x40) && (gfrcr < 0x60))
+				break;
+		}
+		if (j >= CCR_MAXWAIT) {
+			printf("STALLION: cd1400 not responding, brd=%d "
+				"panel=%d chip=%d\n", panelp->brdnr,
+				panelp->panelnr, i);
+			continue;
+		}
+		chipmask |= (0x1 << i);
+		outb(ioaddr, (PPR + uartaddr));
+		outb((ioaddr + EREG_DATA), PPR_SCALAR);
+	}
+
+/*
+ *	All cd1400's are initialized (if found!). Now go through and setup
+ *	each ports data structures. Also init the LIVR register of cd1400
+ *	for each port.
+ */
+	ioaddr = panelp->iobase;
+	for (i = 0; (i < panelp->nrports); i++) {
+		if (brdp->brdtype == BRD_ECHPCI) {
+			outb(brdp->ioctrl, (panelp->pagenr + (i >> 3)));
+			ioaddr = panelp->iobase;
+		} else {
+			ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 3));
+		}
+		if ((chipmask & (0x1 << (i / 4))) == 0)
+			continue;
+		portp = (stlport_t *) malloc(sizeof(stlport_t), M_TTYS,
+			M_NOWAIT);
+		if (portp == (stlport_t *) NULL) {
+			printf("STALLION: failed to allocate port memory "
+				"(size=%d)\n", sizeof(stlport_t));
+			break;
+		}
+		bzero(portp, sizeof(stlport_t));
+
+		portp->portnr = i;
+		portp->brdnr = panelp->brdnr;
+		portp->panelnr = panelp->panelnr;
+		portp->ioaddr = ioaddr;
+		portp->uartaddr = (i & 0x4) << 5;
+		portp->pagenr = panelp->pagenr + (i >> 3);
+		stl_setreg(portp, CAR, (i & 0x3));
+		stl_setreg(portp, LIVR, (i << 3));
+		panelp->ports[i] = portp;
+
+		j = STL_TXBUFSIZE + (2 * STL_RXBUFSIZE);
+		portp->tx.buf = (char *) malloc(j, M_TTYS, M_NOWAIT);
+		if (portp->tx.buf == (char *) NULL) {
+			printf("STALLION: failed to allocate buffer memory "
+				"(size=%d)\n", j);
+			break;
+		}
+		portp->tx.endbuf = portp->tx.buf + STL_TXBUFSIZE;
+		portp->tx.head = portp->tx.buf;
+		portp->tx.tail = portp->tx.buf;
+		portp->rx.buf = portp->tx.buf + STL_TXBUFSIZE;
+		portp->rx.endbuf = portp->rx.buf + STL_RXBUFSIZE;
+		portp->rx.head = portp->rx.buf;
+		portp->rx.tail = portp->rx.buf;
+		portp->rxstatus.buf = portp->rx.buf + STL_RXBUFSIZE;
+		portp->rxstatus.endbuf = portp->rxstatus.buf + STL_RXBUFSIZE;
+		portp->rxstatus.head = portp->rxstatus.buf;
+		portp->rxstatus.tail = portp->rxstatus.buf;
+		bzero(portp->rxstatus.head, STL_RXBUFSIZE);
+
+		portp->initintios.c_ispeed = STL_DEFSPEED;
+		portp->initintios.c_ospeed = STL_DEFSPEED;
+		portp->initintios.c_cflag = STL_DEFCFLAG;
+		portp->initintios.c_iflag = 0;
+		portp->initintios.c_oflag = 0;
+		portp->initintios.c_lflag = 0;
+		bcopy(&ttydefchars[0], &portp->initintios.c_cc[0],
+			sizeof(portp->initintios.c_cc));
+		portp->initouttios = portp->initintios;
+		portp->dtrwait = 3 * hz;
+	}
+
+	BRDDISABLE(panelp->brdnr);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find and initialize an EasyIO board.
+ */
+
+static int stl_initeio(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	status;
+
+#if DEBUG
+	printf("stl_initeio(brdp=%x)\n", (int) brdp);
+#endif
+
+	brdp->ioctrl = brdp->ioaddr1 + 1;
+	brdp->iostatus = brdp->ioaddr1 + 2;
+
+	status = inb(brdp->iostatus);
+	switch (status & EIO_IDBITMASK) {
+	case EIO_8PORTRS:
+	case EIO_8PORTM:
+	case EIO_8PORTDI:
+		brdp->nrports = 8;
+		break;
+	case EIO_4PORTRS:
+		brdp->nrports = 4;
+		break;
+	default:
+		return(ENODEV);
+	}
+
+/*
+ *	Check that the supplied IRQ is good and then use it to setup the
+ *	programmable interrupt bits on EIO board. Also set the edge/level
+ *	triggered interrupt bit.
+ */
+	if ((brdp->irq < 0) || (brdp->irq > 15) ||
+			(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+		printf("STALLION: invalid irq=%d for brd=%d\n", brdp->irq,
+			brdp->brdnr);
+		return(EINVAL);
+	}
+	outb(brdp->ioctrl, (stl_vecmap[brdp->irq] |
+		((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)));
+
+	panelp = (stlpanel_t *) malloc(sizeof(stlpanel_t), M_TTYS, M_NOWAIT);
+	if (panelp == (stlpanel_t *) NULL) {
+		printf("STALLION: failed to allocate memory (size=%d)\n",
+			sizeof(stlpanel_t));
+		return(ENOMEM);
+	}
+	bzero(panelp, sizeof(stlpanel_t));
+
+	panelp->brdnr = brdp->brdnr;
+	panelp->panelnr = 0;
+	panelp->nrports = brdp->nrports;
+	panelp->iobase = brdp->ioaddr1;
+	brdp->panels[0] = panelp;
+	brdp->nrpanels = 1;
+	brdp->state |= BRD_FOUND;
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find an ECH board and initialize it. This code is capable of
+ *	dealing with all types of ECH board.
+ */
+
+static int stl_initech(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	status, nxtid;
+	int		panelnr, ioaddr, i;
+
+#if DEBUG
+	printf("stl_initech(brdp=%x)\n", (int) brdp);
+#endif
+
+/*
+ *	Set up the initial board register contents for boards. This varys a
+ *	bit between the different board types. So we need to handle each
+ *	separately. Also do a check that the supplied IRQ is good.
+ */
+	if (brdp->brdtype == BRD_ECH) {
+		brdp->ioctrl = brdp->ioaddr1 + 1;
+		brdp->iostatus = brdp->ioaddr1 + 1;
+		status = inb(brdp->iostatus);
+		if ((status & ECH_IDBITMASK) != ECH_ID)
+			return(ENODEV);
+
+		if ((brdp->irq < 0) || (brdp->irq > 15) ||
+				(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+			printf("STALLION: invalid irq=%d for brd=%d\n",
+				brdp->irq, brdp->brdnr);
+			return(EINVAL);
+		}
+		status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1);
+		status |= (stl_vecmap[brdp->irq] << 1);
+		outb(brdp->ioaddr1, (status | ECH_BRDRESET));
+		brdp->ioctrlval = ECH_INTENABLE |
+			((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE);
+		outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDENABLE));
+		outb(brdp->ioaddr1, status);
+	} else if (brdp->brdtype == BRD_ECHMC) {
+		brdp->ioctrl = brdp->ioaddr1 + 0x20;
+		brdp->iostatus = brdp->ioctrl;
+		status = inb(brdp->iostatus);
+		if ((status & ECH_IDBITMASK) != ECH_ID)
+			return(ENODEV);
+
+		if ((brdp->irq < 0) || (brdp->irq > 15) ||
+				(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+			printf("STALLION: invalid irq=%d for brd=%d\n",
+				brdp->irq, brdp->brdnr);
+			return(EINVAL);
+		}
+		outb(brdp->ioctrl, ECHMC_BRDRESET);
+		outb(brdp->ioctrl, ECHMC_INTENABLE);
+	} else if (brdp->brdtype == BRD_ECHPCI) {
+		brdp->ioctrl = brdp->ioaddr1 + 2;
+	}
+
+/*
+ *	Scan through the secondary io address space looking for panels.
+ *	As we find'em allocate and initialize panel structures for each.
+ */
+	ioaddr = brdp->ioaddr2;
+	panelnr = 0;
+	nxtid = 0;
+
+	for (i = 0; (i < STL_MAXPANELS); i++) {
+		if (brdp->brdtype == BRD_ECHPCI) {
+			outb(brdp->ioctrl, nxtid);
+			ioaddr = brdp->ioaddr2;
+		}
+		status = inb(ioaddr + ECH_PNLSTATUS);
+		if ((status & ECH_PNLIDMASK) != nxtid)
+			break;
+		panelp = (stlpanel_t *) malloc(sizeof(stlpanel_t), M_TTYS,
+			M_NOWAIT);
+		if (panelp == (stlpanel_t *) NULL) {
+			printf("STALLION: failed to allocate memory"
+				"(size=%d)\n", sizeof(stlpanel_t));
+			break;
+		}
+		bzero(panelp, sizeof(stlpanel_t));
+		panelp->brdnr = brdp->brdnr;
+		panelp->panelnr = panelnr;
+		panelp->iobase = ioaddr;
+		panelp->pagenr = nxtid;
+		if (status & ECH_PNL16PORT) {
+			if ((brdp->nrports + 16) > 32)
+				break;
+			panelp->nrports = 16;
+			panelp->ackmask = 0x80;
+			brdp->nrports += 16;
+			ioaddr += (EREG_BANKSIZE * 2);
+			nxtid += 2;
+		} else {
+			panelp->nrports = 8;
+			panelp->ackmask = 0xc0;
+			brdp->nrports += 8;
+			ioaddr += EREG_BANKSIZE;
+			nxtid++;
+		}
+		brdp->panels[panelnr++] = panelp;
+		brdp->nrpanels++;
+		if (ioaddr >= (brdp->ioaddr2 + 0x20))
+			break;
+	}
+
+	if (brdp->brdtype == BRD_ECH)
+		outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDDISABLE));
+
+	brdp->state |= BRD_FOUND;
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize and configure the specified board. This firstly probes
+ *	for the board, if it is found then the board is initialized and
+ *	then all its ports are initialized as well.
+ */
+
+static int stl_brdinit(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	int		i, j, k;
+
+#if DEBUG
+	printf("stl_brdinit(brdp=%x): unit=%d type=%d io1=%x io2=%x irq=%d\n",
+		(int) brdp, brdp->brdnr, brdp->brdtype, brdp->ioaddr1,
+		brdp->ioaddr2, brdp->irq);
+#endif
+
+	switch (brdp->brdtype) {
+	case BRD_EASYIO:
+		stl_initeio(brdp);
+		break;
+	case BRD_ECH:
+	case BRD_ECHMC:
+	case BRD_ECHPCI:
+		stl_initech(brdp);
+		break;
+	default:
+		printf("STALLION: unit=%d is unknown board type=%d\n",
+			brdp->brdnr, brdp->brdtype);
+		return(ENODEV);
+	}
+
+	stl_brds[brdp->brdnr] = brdp;
+	if ((brdp->state & BRD_FOUND) == 0) {
+#if 0
+		printf("STALLION: %s board not found, unit=%d io=%x irq=%d\n",
+			stl_brdnames[brdp->brdtype], brdp->brdnr,
+			brdp->ioaddr1, brdp->irq);
+#endif
+		return(ENODEV);
+	}
+
+	for (i = 0, k = 0; (i < STL_MAXPANELS); i++) {
+		panelp = brdp->panels[i];
+		if (panelp != (stlpanel_t *) NULL) {
+			stl_initports(brdp, panelp);
+			for (j = 0; (j < panelp->nrports); j++)
+				brdp->ports[k++] = panelp->ports[j];
+		}
+	}
+
+	printf("stl%d: %s (driver version %s) nrpanels=%d nrports=%d\n",
+		brdp->brdnr, stl_brdnames[brdp->brdtype], stl_drvversion,
+		brdp->nrpanels, brdp->nrports);
+	return(0);
+}
+
+/*****************************************************************************/