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); +} + +/*****************************************************************************/