freebsd-skq/sys/i386/isa/stallion.c
2001-03-26 12:41:29 +00:00

3089 lines
78 KiB
C

/*****************************************************************************/
/*
* stallion.c -- stallion multiport serial driver.
*
* Copyright (c) 1995-1996 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.
*
* $FreeBSD$
*/
/*****************************************************************************/
#define TTYDEFCHARS 1
#include "opt_compat.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/bus.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/ic/scd1400.h>
#include <machine/comstats.h>
#include "pci.h"
#ifdef COMPILING_LINT
#warning "The stallion pci device is broken and not compiled with LINT"
#undef NPCI
#define NPCI 0
#endif
#if NPCI > 0
#ifndef COMPAT_OLDPCI
#error "The stallion pci driver requires the old pci compatibility shims"
#endif
#include <pci/pcivar.h>
#include <pci/pcireg.h>
#endif
/*****************************************************************************/
/*
* Define the version level of the kernel - so we can compile in the
* appropriate bits of code. By default this will compile for a 2.1
* level kernel.
*/
#define VFREEBSD 220
#if VFREEBSD >= 220
#define STATIC static
#else
#define STATIC
#endif
/*****************************************************************************/
/*
* 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 64
/*
* Define the important minor number break down bits. These have been
* chosen to be "compatible" 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_MEMDEV 0x07000000
#define STL_DEFSPEED TTYDEF_SPEED
#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 2048
#define STL_TXBUFSIZE 2048
#define STL_TXBUFLOW (STL_TXBUFSIZE / 4)
#define STL_RXBUFHIGH (3 * STL_RXBUFSIZE / 4)
/*****************************************************************************/
/*
* Define our local driver identity first. Set up stuff to deal with
* all the local structures required by a serial tty driver.
*/
static const char stl_drvname[] = "stl";
static const char stl_longdrvname[] = "Stallion Multiport Serial Driver";
static const char stl_drvversion[] = "1.0.0";
static int stl_brdprobed[STL_MAXBRDS];
static int stl_nrbrds = 0;
static int stl_doingtimeout = 0;
static const char __file__[] = /*__FILE__*/ "stallion.c";
/*
* Define global stats structures. Not used often, and can be
* re-used for each stats call.
*/
static combrd_t stl_brdstats;
static comstats_t stl_comstats;
/*****************************************************************************/
/*
* 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;
int hotchar;
unsigned int state;
unsigned int hwid;
unsigned int sigs;
unsigned int rxignoremsk;
unsigned int rxmarkmsk;
unsigned long clk;
struct termios initintios;
struct termios initouttios;
struct termios lockintios;
struct termios lockouttios;
struct timeval timestamp;
comstats_t stats;
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 hwid;
unsigned int ackmask;
stlport_t *ports[STL_PORTSPERPANEL];
} stlpanel_t;
typedef struct {
int brdnr;
int brdtype;
int unitid;
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;
unsigned int hwid;
unsigned long clk;
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_RTSFLOWMODE 0x20
#define ASY_CTSFLOWMODE 0x40
#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 accessible 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 EIO_CLK 25000000
#define EIO_CLK8M 20000000
#define ECH_CLK EIO_CLK
/*
* 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 PCI vendor and device id for ECH8/32-PCI.
*/
#define STL_PCIDEVID 0xd001100b
/*
* 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) ((minor(m) & 0x00700000) >> 20)
#define MKDEV2PORT(m) ((minor(m) & 0x1f) | ((minor(m) & 0x00010000) >> 11))
/*
* 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.
*/
static int stlprobe(struct isa_device *idp);
static int stlattach(struct isa_device *idp);
STATIC d_open_t stlopen;
STATIC d_close_t stlclose;
STATIC d_ioctl_t stlioctl;
/*
* Internal function prototypes.
*/
static stlport_t *stl_dev2port(dev_t dev);
static int stl_findfreeunit(void);
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_stop(struct tty *tp, int);
static void stl_ttyoptim(stlport_t *portp, struct termios *tiosp);
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 ointhand2_t stlintr;
static __inline void stl_txisr(stlpanel_t *panelp, int ioaddr);
static __inline void stl_rxisr(stlpanel_t *panelp, int ioaddr);
static __inline 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 int 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);
static int stl_memioctl(dev_t dev, unsigned long cmd, caddr_t data,
int flag, struct proc *p);
static int stl_getbrdstats(caddr_t data);
static int stl_getportstats(stlport_t *portp, caddr_t data);
static int stl_clrportstats(stlport_t *portp, caddr_t data);
static stlport_t *stl_getport(int brdnr, int panelnr, int portnr);
#if NPCI > 0
static const char *stlpciprobe(pcici_t tag, pcidi_t type);
static void stlpciattach(pcici_t tag, int unit);
static void stlpciintr(void * arg);
#endif
/*****************************************************************************/
/*
* Declare the driver isa structure.
*/
struct isa_driver stldriver = {
INTR_TYPE_TTY,
stlprobe,
stlattach,
"stl"
};
COMPAT_ISA_DRIVER(stl, stldriver);
/*****************************************************************************/
#if NPCI > 0
/*
* Declare the driver pci structure.
*/
static unsigned long stl_count;
static struct pci_device stlpcidriver = {
"stl",
stlpciprobe,
stlpciattach,
&stl_count,
NULL,
};
COMPAT_PCI_DRIVER (stlpci, stlpcidriver);
#endif
/*****************************************************************************/
#if VFREEBSD >= 220
/*
* FreeBSD-2.2+ kernel linkage.
*/
#define CDEV_MAJOR 72
static struct cdevsw stl_cdevsw = {
/* open */ stlopen,
/* close */ stlclose,
/* read */ ttyread,
/* write */ ttywrite,
/* ioctl */ stlioctl,
/* poll */ ttypoll,
/* mmap */ nommap,
/* strategy */ nostrategy,
/* name */ "stl",
/* maj */ CDEV_MAJOR,
/* dump */ nodump,
/* psize */ nopsize,
/* flags */ D_TTY | D_KQFILTER,
/* kqfilter */ ttykqfilter,
};
static void stl_drvinit(void *unused)
{
cdevsw_add(&stl_cdevsw);
}
SYSINIT(sidev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,stl_drvinit,NULL)
#endif
/*****************************************************************************/
/*
* 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... (Note, PCI boards not checked here,
* they are done in the stlpciprobe() routine).
*/
static 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);
}
/*****************************************************************************/
/*
* Find an available internal board number (unit number). The problem
* is that the same unit numbers can be assigned to different boards
* detected during the ISA and PCI initialization phases.
*/
static int stl_findfreeunit()
{
int i;
for (i = 0; (i < STL_MAXBRDS); i++)
if (stl_brds[i] == (stlbrd_t *) NULL)
break;
return((i >= STL_MAXBRDS) ? -1 : i);
}
/*****************************************************************************/
/*
* Allocate resources for and initialize the specified board.
*/
static int stlattach(struct isa_device *idp)
{
stlbrd_t *brdp;
#if DEBUG
printf("stlattach(idp=%p): unit=%d iobase=%x\n", (void *) idp,
idp->id_unit, idp->id_iobase);
#endif
idp->id_ointr = stlintr;
brdp = (stlbrd_t *) malloc(sizeof(stlbrd_t), M_TTYS, M_NOWAIT | M_ZERO);
if (brdp == (stlbrd_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlbrd_t));
return(0);
}
if ((brdp->brdnr = stl_findfreeunit()) < 0) {
printf("STALLION: too many boards found, max=%d\n",
STL_MAXBRDS);
return(0);
}
if (brdp->brdnr >= stl_nrbrds)
stl_nrbrds = brdp->brdnr + 1;
brdp->unitid = 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);
}
/*****************************************************************************/
#if NPCI > 0
/*
* Probe specifically for the PCI boards. We need to be a little
* carefull here, since it looks sort like a Nat Semi IDE chip...
*/
static const char *stlpciprobe(pcici_t tag, pcidi_t type)
{
unsigned long class;
#if DEBUG
printf("stlpciprobe(tag=%x,type=%x)\n", (int) &tag, (int) type);
#endif
switch (type) {
case STL_PCIDEVID:
break;
default:
return((char *) NULL);
}
class = pci_conf_read(tag, PCI_CLASS_REG);
if ((class & PCI_CLASS_MASK) == PCI_CLASS_MASS_STORAGE)
return((char *) NULL);
return("Stallion EasyConnection 8/32-PCI");
}
/*****************************************************************************/
/*
* Allocate resources for and initialize the specified PCI board.
*/
void stlpciattach(pcici_t tag, int unit)
{
stlbrd_t *brdp;
#if DEBUG
printf("stlpciattach(tag=%x,unit=%x)\n", (int) &tag, unit);
#endif
brdp = (stlbrd_t *) malloc(sizeof(stlbrd_t), M_TTYS, M_NOWAIT | M_ZERO);
if (brdp == (stlbrd_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlbrd_t));
return;
}
if ((unit < 0) || (unit > STL_MAXBRDS)) {
printf("STALLION: bad PCI board unit number=%d\n", unit);
return;
}
/*
* Allocate us a new driver unique unit number.
*/
if ((brdp->brdnr = stl_findfreeunit()) < 0) {
printf("STALLION: too many boards found, max=%d\n",
STL_MAXBRDS);
return;
}
if (brdp->brdnr >= stl_nrbrds)
stl_nrbrds = brdp->brdnr + 1;
brdp->unitid = 0;
brdp->brdtype = BRD_ECHPCI;
brdp->ioaddr1 = ((unsigned int) pci_conf_read(tag, 0x14)) & 0xfffc;
brdp->ioaddr2 = ((unsigned int) pci_conf_read(tag, 0x10)) & 0xfffc;
brdp->irq = ((int) pci_conf_read(tag, 0x3c)) & 0xff;
brdp->irqtype = 0;
if (pci_map_int(tag, stlpciintr, (void *) NULL, &tty_imask) == 0) {
printf("STALLION: failed to map interrupt irq=%d for unit=%d\n",
brdp->irq, brdp->brdnr);
return;
}
#if 0
printf("%s(%d): ECH-PCI iobase=%x iopage=%x irq=%d\n", __file__, __LINE__, brdp->ioaddr2, brdp->ioaddr1, brdp->irq);
#endif
stl_brdinit(brdp);
}
#endif
/*****************************************************************************/
STATIC 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.
*/
if (minor(dev) & STL_MEMDEV)
return(0);
portp = stl_dev2port(dev);
if (portp == (stlport_t *) NULL)
return(ENXIO);
tp = &portp->tty;
dev->si_tty = tp;
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_stop = stl_stop;
tp->t_param = stl_param;
tp->t_dev = dev;
tp->t_termios = callout ? portp->initouttios :
portp->initintios;
stl_rawopen(portp);
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 (portp->callout != 0) {
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) &&
suser(p)) {
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(TSA_CARR_ON(tp), (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);
stl_ttyoptim(portp, &tp->t_termios);
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) && (portp->waitopens == 0))
stl_rawclose(portp);
return(error);
}
/*****************************************************************************/
STATIC 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=%s,flag=%x,mode=%x,p=%p)\n", devtoname(dev),
flag, mode, (void *) p);
#endif
if (minor(dev) & STL_MEMDEV)
return(0);
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_ttyoptim(portp, &tp->t_termios);
stl_rawclose(portp);
ttyclose(tp);
splx(x);
return(0);
}
/*****************************************************************************/
#if VFREEBSD >= 220
STATIC void stl_stop(struct tty *tp, int rw)
{
#if DEBUG
printf("stl_stop(tp=%x,rw=%x)\n", (int) tp, rw);
#endif
stl_flush((stlport_t *) tp, rw);
}
#else
STATIC 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);
}
#endif
/*****************************************************************************/
STATIC int stlioctl(dev_t dev, unsigned long 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=%s,cmd=%lx,data=%p,flag=%x,p=%p)\n",
devtoname(dev), cmd, (void *) data, flag, (void *) p);
#endif
if (minor(dev) & STL_MEMDEV)
return(stl_memioctl(dev, cmd, data, flag, p));
portp = stl_dev2port(dev);
if (portp == (stlport_t *) NULL)
return(ENODEV);
tp = &portp->tty;
error = 0;
/*
* First up handle ioctls on the control devices.
*/
if (minor(dev) & STL_CTRLDEV) {
if ((minor(dev) & STL_CTRLDEV) == STL_CTRLINIT)
localtios = (minor(dev) & STL_CALLOUTDEV) ?
&portp->initouttios : &portp->initintios;
else if ((minor(dev) & STL_CTRLDEV) == STL_CTRLLOCK)
localtios = (minor(dev) & STL_CALLOUTDEV) ?
&portp->lockouttios : &portp->lockintios;
else
return(ENODEV);
switch (cmd) {
case TIOCSETA:
if ((error = suser(p)) == 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 compatibility issues if we have too...
*/
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
if (1) {
struct termios tios;
unsigned long oldcmd;
tios = tp->t_termios;
oldcmd = cmd;
if ((error = ttsetcompat(tp, &cmd, data, &tios)))
return(error);
if (cmd != oldcmd)
data = (caddr_t) &tios;
}
#endif
/*
* 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 = (minor(dev) & STL_CALLOUTDEV) ?
&portp->lockouttios : &portp->lockintios;
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;
}
/*
* 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 != ENOIOCTL)
return(error);
x = spltty();
error = ttioctl(tp, cmd, data, flag);
stl_ttyoptim(portp, &tp->t_termios);
if (error != ENOIOCTL) {
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:
*((int *) data) = (stl_getsignals(portp) | TIOCM_LE);
break;
case TIOCMSDTRWAIT:
if ((error = suser(p)) == 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;
brdp = stl_brds[MKDEV2BRD(dev)];
if (brdp == (stlbrd_t *) NULL)
return((stlport_t *) NULL);
return(brdp->ports[MKDEV2PORT(dev)]);
}
/*****************************************************************************/
/*
* 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=%p): brdnr=%d panelnr=%d portnr=%d\n",
(void *) portp, portp->brdnr, portp->panelnr, portp->portnr);
#endif
stl_param(&portp->tty, &portp->tty.t_termios);
portp->sigs = 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=%p): brdnr=%d panelnr=%d portnr=%d\n",
(void *) 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(TSA_CARR_ON(tp));
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);
}
#if VFREEBSD == 205
/*
* 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);
}
#endif
if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
splx(x);
return;
}
/*
* 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.
*/
if (tp->t_outq.c_cc != 0) {
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);
}
/*
* If we sent something, make sure we are called again.
*/
tp->t_state |= TS_BUSY;
}
#if VFREEBSD != 205
/*
* Do any writer wakeups.
*/
ttwwakeup(tp);
#endif
splx(x);
}
/*****************************************************************************/
static void stl_flush(stlport_t *portp, int flag)
{
char *head, *tail;
int len, x;
#if DEBUG
printf("stl_flush(portp=%x,flag=%x)\n", (int) portp, flag);
#endif
if (portp == (stlport_t *) NULL)
return;
x = spltty();
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);
}
splx(x);
}
/*****************************************************************************/
/*
* 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);
portp->stats.txtotal += len;
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) &&
(portp->state & ASY_RTSFLOWMODE)) {
portp->state |= ASY_RTSFLOW;
stl_setreg(portp, MCOR1,
(stl_getreg(portp, MCOR1) & 0xf0));
stl_setreg(portp, MSVR2, 0);
portp->stats.rxrtsoff++;
}
}
/*
* 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->stats.rxlost += len;
portp->stats.rxtotal += len;
} else {
len = MIN(len, buflen);
portp->stats.rxtotal += len;
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->stats.rxbreaks++;
if (status & ST_FRAMING)
portp->stats.rxframing++;
if (status & ST_PARITY)
portp->stats.rxparity++;
if (status & ST_OVERRUN)
portp->stats.rxoverrun++;
if (status & ST_SCHARMASK) {
if ((status & ST_SCHARMASK) == ST_SCHAR1)
portp->stats.txxon++;
if ((status & ST_SCHARMASK) == ST_SCHAR2)
portp->stats.txxoff++;
goto stl_rxalldone;
}
if ((portp->rxignoremsk & status) == 0) {
if ((tp->t_state & TS_CAN_BYPASS_L_RINT) &&
((status & ST_FRAMING) ||
((status & ST_PARITY) && (tp->t_iflag & INPCK))))
ch = 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();
stl_rxalldone:
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;
portp->stats.modem++;
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.
*/
static 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 NPCI > 0
static void stlpciintr(void *arg)
{
stlintr(0);
}
#endif
/*****************************************************************************/
/*
* 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();
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;
portp->sigs = 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, lostlen;
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 (len > 0) {
if (((tp->t_rawq.c_cc + len) >= TTYHOG) &&
((portp->state & ASY_RTSFLOWMODE) ||
(tp->t_iflag & IXOFF)) &&
((tp->t_state & TS_TBLOCK) == 0)) {
ch = TTYHOG - tp->t_rawq.c_cc - 1;
len = (ch > 0) ? ch : 0;
stlen = MIN(stlen, len);
ttyblock(tp);
}
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->stats.rxlost += lostlen;
ttwakeup(tp);
portp->rx.tail = tail;
}
} else {
while (portp->rx.tail != head) {
ch = (unsigned char) *(portp->rx.tail);
status = *(portp->rx.tail + STL_RXBUFSIZE);
if (status) {
*(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;
int x;
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;
}
if (tiosp->c_iflag & ISTRIP)
cor5 |= COR5_ISTRIP;
/*
* 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 = ((portp->clk / 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_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 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
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
srer = stl_getreg(portp, SRER);
stl_setreg(portp, SRER, 0);
ccr += stl_updatereg(portp, COR1, cor1);
ccr += stl_updatereg(portp, COR2, cor2);
ccr += stl_updatereg(portp, COR3, cor3);
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);
portp->state &= ~(ASY_RTSFLOWMODE | ASY_CTSFLOWMODE);
portp->state |= ((tiosp->c_cflag & CRTS_IFLOW) ? ASY_RTSFLOWMODE : 0);
portp->state |= ((tiosp->c_cflag & CCTS_OFLOW) ? ASY_CTSFLOWMODE : 0);
stl_ttyoptim(portp, tiosp);
splx(x);
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, x;
#if DEBUG
printf("stl_flowcontrol(portp=%x,hw=%d,sw=%d)\n", (int) portp, hw, sw);
#endif
hwflow = -1;
if (portp->state & ASY_RTSFLOWMODE) {
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) {
x = spltty();
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);
portp->stats.rxrtsoff++;
} else if (hwflow > 0) {
portp->state &= ~ASY_RTSFLOW;
stl_setreg(portp, MSVR2, MSVR2_RTS);
stl_setreg(portp, MCOR1,
(stl_getreg(portp, MCOR1) | FIFO_RTSTHRESHOLD));
portp->stats.rxrtson++;
}
BRDDISABLE(portp->brdnr);
splx(x);
}
}
/*****************************************************************************/
/*
* Set the state of the DTR and RTS signals.
*/
static void stl_setsignals(stlport_t *portp, int dtr, int rts)
{
unsigned char msvr1, msvr2;
int x;
#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;
x = spltty();
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);
splx(x);
}
/*****************************************************************************/
/*
* Get the state of the signals.
*/
static int stl_getsignals(stlport_t *portp)
{
unsigned char msvr1, msvr2;
int sigs, x;
#if DEBUG
printf("stl_getsignals(portp=%x)\n", (int) portp);
#endif
x = spltty();
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);
splx(x);
sigs = 0;
sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
return(sigs);
}
/*****************************************************************************/
/*
* Enable or disable the Transmitter and/or Receiver.
*/
static void stl_enablerxtx(stlport_t *portp, int rx, int tx)
{
unsigned char ccr;
int x;
#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;
x = spltty();
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);
splx(x);
}
/*****************************************************************************/
/*
* Start or stop the Transmitter and/or Receiver.
*/
static void stl_startrxtx(stlport_t *portp, int rx, int tx)
{
unsigned char sreron, sreroff;
int x;
#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;
x = spltty();
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;
splx(x);
}
/*****************************************************************************/
/*
* Disable all interrupts from this port.
*/
static void stl_disableintrs(stlport_t *portp)
{
int x;
#if DEBUG
printf("stl_disableintrs(portp=%x)\n", (int) portp);
#endif
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
stl_setreg(portp, SRER, 0);
BRDDISABLE(portp->brdnr);
splx(x);
}
/*****************************************************************************/
static void stl_sendbreak(stlport_t *portp, long len)
{
int x;
#if DEBUG
printf("stl_sendbreak(portp=%x,len=%d)\n", (int) portp, (int) len);
#endif
x = spltty();
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;
}
splx(x);
portp->stats.txbreaks++;
}
/*****************************************************************************/
/*
* Enable l_rint processing bypass mode if tty modes allow it.
*/
static void stl_ttyoptim(stlport_t *portp, struct termios *tiosp)
{
struct tty *tp;
tp = &portp->tty;
if (((tiosp->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR)) == 0) &&
(((tiosp->c_iflag & BRKINT) == 0) || (tiosp->c_iflag & IGNBRK)) &&
(((tiosp->c_iflag & PARMRK) == 0) ||
((tiosp->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))) &&
((tiosp->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) ==0) &&
(linesw[tp->t_line].l_rint == ttyinput))
tp->t_state |= TS_CAN_BYPASS_L_RINT;
else
tp->t_state &= ~TS_CAN_BYPASS_L_RINT;
portp->hotchar = linesw[tp->t_line].l_hotchar;
}
/*****************************************************************************/
/*
* 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 | M_ZERO);
if (portp == (stlport_t *) NULL) {
printf("STALLION: failed to allocate port memory "
"(size=%d)\n", sizeof(stlport_t));
break;
}
portp->portnr = i;
portp->brdnr = panelp->brdnr;
portp->panelnr = panelp->panelnr;
portp->clk = brdp->clk;
portp->ioaddr = ioaddr;
portp->uartaddr = (i & 0x4) << 5;
portp->pagenr = panelp->pagenr + (i >> 3);
portp->hwid = stl_getreg(portp, GFRCR);
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;
brdp->clk = EIO_CLK;
status = inb(brdp->iostatus);
switch (status & EIO_IDBITMASK) {
case EIO_8PORTM:
brdp->clk = EIO_CLK8M;
/* fall thru */
case EIO_8PORTRS:
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 | M_ZERO);
if (panelp == (stlpanel_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlpanel_t));
return(ENOMEM);
}
panelp->brdnr = brdp->brdnr;
panelp->panelnr = 0;
panelp->nrports = brdp->nrports;
panelp->iobase = brdp->ioaddr1;
panelp->hwid = status;
brdp->panels[0] = panelp;
brdp->nrpanels = 1;
brdp->hwid = status;
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);
brdp->hwid = status;
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);
brdp->hwid = status;
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;
}
brdp->clk = ECH_CLK;
/*
* 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 | M_ZERO);
if (panelp == (stlpanel_t *) NULL) {
printf("STALLION: failed to allocate memory"
"(size=%d)\n", sizeof(stlpanel_t));
break;
}
panelp->brdnr = brdp->brdnr;
panelp->panelnr = panelnr;
panelp->iobase = ioaddr;
panelp->pagenr = nxtid;
panelp->hwid = status;
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) unit=%d nrpanels=%d nrports=%d\n",
brdp->unitid, stl_brdnames[brdp->brdtype], stl_drvversion,
brdp->brdnr, brdp->nrpanels, brdp->nrports);
return(0);
}
/*****************************************************************************/
/*
* Return the board stats structure to user app.
*/
static int stl_getbrdstats(caddr_t data)
{
stlbrd_t *brdp;
stlpanel_t *panelp;
int i;
stl_brdstats = *((combrd_t *) data);
if (stl_brdstats.brd >= STL_MAXBRDS)
return(-ENODEV);
brdp = stl_brds[stl_brdstats.brd];
if (brdp == (stlbrd_t *) NULL)
return(-ENODEV);
bzero(&stl_brdstats, sizeof(combrd_t));
stl_brdstats.brd = brdp->brdnr;
stl_brdstats.type = brdp->brdtype;
stl_brdstats.hwid = brdp->hwid;
stl_brdstats.state = brdp->state;
stl_brdstats.ioaddr = brdp->ioaddr1;
stl_brdstats.ioaddr2 = brdp->ioaddr2;
stl_brdstats.irq = brdp->irq;
stl_brdstats.nrpanels = brdp->nrpanels;
stl_brdstats.nrports = brdp->nrports;
for (i = 0; (i < brdp->nrpanels); i++) {
panelp = brdp->panels[i];
stl_brdstats.panels[i].panel = i;
stl_brdstats.panels[i].hwid = panelp->hwid;
stl_brdstats.panels[i].nrports = panelp->nrports;
}
*((combrd_t *) data) = stl_brdstats;;
return(0);
}
/*****************************************************************************/
/*
* Resolve the referenced port number into a port struct pointer.
*/
static stlport_t *stl_getport(int brdnr, int panelnr, int portnr)
{
stlbrd_t *brdp;
stlpanel_t *panelp;
if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
return((stlport_t *) NULL);
brdp = stl_brds[brdnr];
if (brdp == (stlbrd_t *) NULL)
return((stlport_t *) NULL);
if ((panelnr < 0) || (panelnr >= brdp->nrpanels))
return((stlport_t *) NULL);
panelp = brdp->panels[panelnr];
if (panelp == (stlpanel_t *) NULL)
return((stlport_t *) NULL);
if ((portnr < 0) || (portnr >= panelp->nrports))
return((stlport_t *) NULL);
return(panelp->ports[portnr]);
}
/*****************************************************************************/
/*
* Return the port stats structure to user app. A NULL port struct
* pointer passed in means that we need to find out from the app
* what port to get stats for (used through board control device).
*/
static int stl_getportstats(stlport_t *portp, caddr_t data)
{
unsigned char *head, *tail;
if (portp == (stlport_t *) NULL) {
stl_comstats = *((comstats_t *) data);
portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
stl_comstats.port);
if (portp == (stlport_t *) NULL)
return(-ENODEV);
}
portp->stats.state = portp->state;
/*portp->stats.flags = portp->flags;*/
portp->stats.hwid = portp->hwid;
portp->stats.ttystate = portp->tty.t_state;
portp->stats.cflags = portp->tty.t_cflag;
portp->stats.iflags = portp->tty.t_iflag;
portp->stats.oflags = portp->tty.t_oflag;
portp->stats.lflags = portp->tty.t_lflag;
head = portp->tx.head;
tail = portp->tx.tail;
portp->stats.txbuffered = ((head >= tail) ? (head - tail) :
(STL_TXBUFSIZE - (tail - head)));
head = portp->rx.head;
tail = portp->rx.tail;
portp->stats.rxbuffered = (head >= tail) ? (head - tail) :
(STL_RXBUFSIZE - (tail - head));
portp->stats.signals = (unsigned long) stl_getsignals(portp);
*((comstats_t *) data) = portp->stats;
return(0);
}
/*****************************************************************************/
/*
* Clear the port stats structure. We also return it zeroed out...
*/
static int stl_clrportstats(stlport_t *portp, caddr_t data)
{
if (portp == (stlport_t *) NULL) {
stl_comstats = *((comstats_t *) data);
portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
stl_comstats.port);
if (portp == (stlport_t *) NULL)
return(-ENODEV);
}
bzero(&portp->stats, sizeof(comstats_t));
portp->stats.brd = portp->brdnr;
portp->stats.panel = portp->panelnr;
portp->stats.port = portp->portnr;
*((comstats_t *) data) = stl_comstats;
return(0);
}
/*****************************************************************************/
/*
* The "staliomem" device is used for stats collection in this driver.
*/
static int stl_memioctl(dev_t dev, unsigned long cmd, caddr_t data, int flag,
struct proc *p)
{
stlbrd_t *brdp;
int brdnr, rc;
#if DEBUG
printf("stl_memioctl(dev=%s,cmd=%lx,data=%p,flag=%x)\n",
devtoname(dev), cmd, (void *) data, flag);
#endif
brdnr = minor(dev) & 0x7;
brdp = stl_brds[brdnr];
if (brdp == (stlbrd_t *) NULL)
return(ENODEV);
if (brdp->state == 0)
return(ENODEV);
rc = 0;
switch (cmd) {
case COM_GETPORTSTATS:
rc = stl_getportstats((stlport_t *) NULL, data);
break;
case COM_CLRPORTSTATS:
rc = stl_clrportstats((stlport_t *) NULL, data);
break;
case COM_GETBRDSTATS:
rc = stl_getbrdstats(data);
break;
default:
rc = ENOTTY;
break;
}
return(rc);
}
/*****************************************************************************/