From 7134a2219c4e7cc172a64047a03bb67b2009cad5 Mon Sep 17 00:00:00 2001 From: Warner Losh Date: Sun, 11 Apr 2004 19:32:20 +0000 Subject: [PATCH] Frank Mayhar's sx driver for older Specialix I/O8+ and I/O4+ intelligent serial controllers. si is for completely different hardware, also made by Specialix. --- sys/dev/sx/cd1865.h | 313 +++++++ sys/dev/sx/sx.c | 2039 ++++++++++++++++++++++++++++++++++++++++++ sys/dev/sx/sx.h | 210 +++++ sys/dev/sx/sx_pci.c | 166 ++++ sys/dev/sx/sx_util.c | 260 ++++++ sys/dev/sx/sx_util.h | 159 ++++ sys/dev/sx/sxvar.h | 83 ++ 7 files changed, 3230 insertions(+) create mode 100644 sys/dev/sx/cd1865.h create mode 100644 sys/dev/sx/sx.c create mode 100644 sys/dev/sx/sx.h create mode 100644 sys/dev/sx/sx_pci.c create mode 100644 sys/dev/sx/sx_util.c create mode 100644 sys/dev/sx/sx_util.h create mode 100644 sys/dev/sx/sxvar.h diff --git a/sys/dev/sx/cd1865.h b/sys/dev/sx/cd1865.h new file mode 100644 index 000000000000..5056deaeecab --- /dev/null +++ b/sys/dev/sx/cd1865.h @@ -0,0 +1,313 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + + +/* CD1865 chip register definitions. */ + +/* + * Service Match Register interrupt acknowledgement values. + * + * These values are "obligatory" if you use the register based + * interrupt acknowledgements; the wrong values can cause a lockup. + * See section 8.11.1 of the Intel CD1865 "Intelligent Eight-Channel + * Communications Controller" datasheet. + */ +#define CD1865_ACK_MINT 0x75 /* goes to MSMR */ +#define CD1865_ACK_TINT 0x76 /* goes to TSMR */ +#define CD1865_ACK_RINT 0x77 /* goes to RSMR */ + + +#define CD1865_NUMCHAN 8 /* Total number of channels. */ +#define CD1865_CHARTICK 16 /* Ticks per character. */ +#define CD1865_TFIFOSZ 8 /* TX FIFO size. */ +#define CD1865_RFIFOSZ 8 /* RX FIFO size. */ + +/* + * Global registers. + * These registers are not associated with any particular channel; + * some define the general behavior of the card and others are only + * active during service requests. + */ +#define CD1865_GIVR 0x40 /* Global Interrupt Vector Register. */ + /* The CD1865 datasheet calls this the */ + /* "Global Vector Register" _and_ the */ + /* "Global Service Vector Register," GSVR. */ +#define CD1865_GSVR CD1865_GIVR +#define CD1865_GICR 0x41 /* Global Interrupting Channel Register. */ + /* The CD1865 datasheet calls this the */ + /* "Global Channel Register 1," GSCR1. */ +#define CD1865_GSCR1 CD1865_GICR +#define CD1865_GSCR2 0x42 /* Global Channel Register 2. */ +#define CD1865_GSCR3 0x43 /* Global Channel Register 3. */ +#define CD1865_MSMR 0x61 /* Priority Interrupt Level Register 1. */ +#define CD1865_TSMR 0x62 /* Priority Interrupt Level Register 2. */ +#define CD1865_RSMR 0x63 /* Priority Interrupt Level Register 3. */ +#define CD1865_CAR 0x64 /* Channel Access Register. */ +#define CD1865_SRSR 0x65 /* Service Request Status Register. */ +#define CD1865_SRCR 0x66 /* Service Request Configuration Register. */ +#define CD1865_GFRCR 0x6b /* Global Firmware Revision Code Register. */ +#define CD1865_PPRH 0x70 /* Prescaler Period Register High. */ +#define CD1865_PPRL 0x71 /* Prescaler Period Register Low. */ +#define CD1865_RDR 0x78 /* Receiver Data Register. */ +#define CD1865_RCSR 0x7a /* Receiver Character Status Register. */ +#define CD1865_TDR 0x7b /* Transmit Data Register. */ +#define CD1865_EOIR 0x7f /* End of Interrupt Register. */ +#define CD1865_MRAR 0x75 /* Modem Request Acknowlege Register. */ +#define CD1865_TRAR 0x76 /* Transmit Request Acknowlege Register. */ +#define CD1865_RRAR 0x77 /* Receive Request Acknowlege Register. */ + +/* + * Channel Registers + * These registers control or provide status for individual channels. + * Use the CD1865_CAR register to set up access to the channel before + * using these registers. + */ +#define CD1865_CCR 0x01 /* Channel Command Register. */ +#define CD1865_IER 0x02 /* Interrupt Enable Register. */ + /* The CD1865 datasheet calls this the */ + /* "Service Request Enable Register," SRER. */ +#define CD1865_SRER CD1865_IER +#define CD1865_COR1 0x03 /* Channel Option Register 1. */ +#define CD1865_COR2 0x04 /* Channel Option Register 2. */ +#define CD1865_COR3 0x05 /* Channel Option Register 3. */ +#define CD1865_CCSR 0x06 /* Channel Control Status Register. */ +#define CD1865_RDCR 0x07 /* Receive Data Count Register. */ +#define CD1865_SCHR1 0x09 /* Special Character Register 1. */ +#define CD1865_SCHR2 0x0a /* Special Character Register 2. */ +#define CD1865_SCHR3 0x0b /* Special Character Register 3. */ +#define CD1865_SCHR4 0x0c /* Special Character Register 4. */ +#define CD1865_MCOR1 0x10 /* Modem Change Option 1 Register. */ +#define CD1865_MCOR2 0x11 /* Modem Change Option 2 Register. */ +#define CD1865_MCR 0x12 /* Modem Change Register. */ +#define CD1865_RTPR 0x18 /* Receive Timeout Period Register. */ +#define CD1865_MSVR 0x28 /* Modem Signal Value Register. */ +#define CD1865_MSVRTS 0x29 /* Modem Signal Value Register. */ +#define CD1865_MSVDTR 0x2a /* Modem Signal Value Register. */ +#define CD1865_RBPRH 0x31 /* Receive Baud Rate Period Register High. */ +#define CD1865_RBPRL 0x32 /* Receive Baud Rate Period Register Low. */ +#define CD1865_TBPRH 0x39 /* Transmit Baud Rate Period Register High. */ +#define CD1865_TBPRL 0x3a /* Transmit Baud Rate Period Register Low. */ + + +/* + * Global Interrupt Vector Register, read/write (0x40). + */ +#define CD1865_GIVR_ITMASK 0x07 /* Interrupt type mask. */ +#define CD1865_GIVR_IT_MODEM 0x01 /* Modem Signal Change Interrupt. */ +#define CD1865_GIVR_IT_TX 0x02 /* Transmit Data Interrupt. */ +#define CD1865_GIVR_IT_RCV 0x03 /* Receive Good Data Interrupt. */ +#define CD1865_GIVR_IT_REXC 0x07 /* Receive Exception Interrupt. */ + + +/* + * Global Interrupt Channel Register read/write (0x41) + */ +#define CD1865_GICR_CHAN_MASK 0x1c /* Channel Number Mask. */ +#define CD1865_GICR_CHAN_SHIFT 2 /* Channel Number shift. */ + + +/* + * Channel Access Register, read/write (0x64). + */ +#define CD1865_CAR_CHAN_MASK 0x07 /* Channel Number Mask. */ +#define CD1865_CAR_A7 0x08 /* A7 Address Extension (unused). */ + + +/* + * Receive Character Status Register, readonly (0x7a). + */ +#define CD1865_RCSR_TOUT 0x80 /* Rx Timeout. */ +#define CD1865_RCSR_SCDET 0x70 /* Special Character Detected Mask. */ +#define CD1865_RCSR_NO_SC 0x00 /* No Special Characters Detected. */ +#define CD1865_RCSR_SC_1 0x10 /* Special Char 1 (or 1 & 3) Detected. */ +#define CD1865_RCSR_SC_2 0x20 /* Special Char 2 (or 2 & 4) Detected. */ +#define CD1865_RCSR_SC_3 0x30 /* Special Char 3 Detected. */ +#define CD1865_RCSR_SC_4 0x40 /* Special Char 4 Detected. */ +#define CD1865_RCSR_BREAK 0x08 /* Break detected. */ +#define CD1865_RCSR_PE 0x04 /* Parity Error. */ +#define CD1865_RCSR_FE 0x02 /* Frame Error. */ +#define CD1865_RCSR_OE 0x01 /* Overrun Error. */ + + +/* + * Channel Command Register, read/write (0x01) + * Commands in groups can be OR-ed together. + */ +#define CD1865_CCR_HARDRESET 0x81 /* Reset the CD1865 (like a powercycle). */ + +#define CD1865_CCR_SOFTRESET 0x80 /* Soft Channel Reset (one channel). */ + +#define CD1865_CCR_CORCHG1 0x42 /* Channel Option Register 1 Changed. */ +#define CD1865_CCR_CORCHG2 0x44 /* Channel Option Register 2 Changed. */ +#define CD1865_CCR_CORCHG3 0x48 /* Channel Option Register 3 Changed. */ + +#define CD1865_CCR_SSCH1 0x21 /* Send Special Character 1. */ + +#define CD1865_CCR_SSCH2 0x22 /* Send Special Character 2. */ + +#define CD1865_CCR_SSCH3 0x23 /* Send Special Character 3. */ + +#define CD1865_CCR_SSCH4 0x24 /* Send Special Character 4. */ + +#define CD1865_CCR_TXEN 0x18 /* Enable Transmitter. */ +#define CD1865_CCR_RXEN 0x12 /* Enable Receiver. */ + +#define CD1865_CCR_TXDIS 0x14 /* Disable Transmitter. */ +#define CD1865_CCR_RXDIS 0x11 /* Disable Receiver. */ + + +/* + * Interrupt Enable Register, read/write (0x02). + * (aka Service Request Enable Register) + */ +#define CD1865_IER_DSR 0x80 /* Enable DSR change interrupt. */ +#define CD1865_IER_CD 0x40 /* Enable CD change interrupt. */ +#define CD1865_IER_CTS 0x20 /* Enable CTS change interrupt. */ +#define CD1865_IER_RXD 0x10 /* Enable Receive Data interrupt. */ +#define CD1865_IER_RXSC 0x08 /* Enable Receive Special Character int. */ +#define CD1865_IER_TXRDY 0x04 /* Enable Transmit ready interrupt. */ +#define CD1865_IER_TXEMPTY 0x02 /* Enable Transmit empty interrupt. */ +#define CD1865_IER_NNDT 0x01 /* Enable "No New Data Timeout" int. */ + + +/* + * Channel Option Register 1, read/write (0x03). + */ +#define CD1865_COR1_ODDP 0x80 /* Odd Parity. */ +#define CD1865_COR1_PARMODE 0x60 /* Parity enable mask. */ +#define CD1865_COR1_NOPAR 0x00 /* No Parity. */ +#define CD1865_COR1_FORCEPAR 0x20 /* Force Parity. */ +#define CD1865_COR1_NORMPAR 0x40 /* Normal Parity. */ +#define CD1865_COR1_IGNORE 0x10 /* Ignore Parity on RX. */ +#define CD1865_COR1_STOPBITS 0x0c /* Number of Stop Bits. */ +#define CD1865_COR1_1SB 0x00 /* 1 Stop Bit. */ +#define CD1865_COR1_15SB 0x04 /* 1.5 Stop Bits. */ +#define CD1865_COR1_2SB 0x08 /* 2 Stop Bits. */ +#define CD1865_COR1_CHARLEN 0x03 /* Character Length. */ +#define CD1865_COR1_5BITS 0x00 /* 5 bits. */ +#define CD1865_COR1_6BITS 0x01 /* 6 bits. */ +#define CD1865_COR1_7BITS 0x02 /* 7 bits. */ +#define CD1865_COR1_8BITS 0x03 /* 8 bits. */ + + +/* + * Channel Option Register 2, read/write (0x04). + */ +#define CD1865_COR2_IXM 0x80 /* Implied XON mode. */ +#define CD1865_COR2_TXIBE 0x40 /* Enable In-Band (XON/XOFF) Flow Control*/ +#define CD1865_COR2_ETC 0x20 /* Embedded Tx Commands Enable. */ +#define CD1865_COR2_LLM 0x10 /* Local Loopback Mode. */ +#define CD1865_COR2_RLM 0x08 /* Remote Loopback Mode. */ +#define CD1865_COR2_RTSAO 0x04 /* RTS Automatic Output Enable. */ +#define CD1865_COR2_CTSAE 0x02 /* CTS Automatic Enable. */ +#define CD1865_COR2_DSRAE 0x01 /* DSR Automatic Enable. */ + + +/* + * Channel Option Register 3, read/write (0x05). + */ +#define CD1865_COR3_XONCH 0x80 /* XON is a pair of characters (1 & 3). */ +#define CD1865_COR3_XOFFCH 0x40 /* XOFF is a pair of characters (2 & 4). */ +#define CD1865_COR3_FCT 0x20 /* Flow-Control Transparency Mode. */ +#define CD1865_COR3_SCDE 0x10 /* Special Character Detection Enable. */ +#define CD1865_COR3_RXTH 0x0f /* RX FIFO Threshold value (1-8). */ + + +/* + * Channel Control Status Register, readonly (0x06) + */ +#define CD1865_CCSR_RXEN 0x80 /* Receiver Enabled. */ +#define CD1865_CCSR_RXFLOFF 0x40 /* Receive Flow Off (XOFF was sent). */ +#define CD1865_CCSR_RXFLON 0x20 /* Receive Flow On (XON was sent). */ +#define CD1865_CCSR_TXEN 0x08 /* Transmitter Enabled. */ +#define CD1865_CCSR_TXFLOFF 0x04 /* Transmit Flow Off (got XOFF). */ +#define CD1865_CCSR_TXFLON 0x02 /* Transmit Flow On (got XON). */ + + +/* + * Modem Change Option Register 1, read/write (0x10). + */ +#define CD1865_MCOR1_DSRZD 0x80 /* Detect 0->1 transition of DSR. */ +#define CD1865_MCOR1_CDZD 0x40 /* Detect 0->1 transition of CD. */ +#define CD1865_MCOR1_CTSZD 0x20 /* Detect 0->1 transition of CTS. */ +#define CD1865_MCOR1_DTRTH 0x0f /* Auto DTR flow control Threshold (1-8).*/ +#define CD1865_MCOR1_NODTRFC 0x0 /* Automatic DTR flow control disabled. */ + + +/* + * Modem Change Option Register 2, read/write (0x11). + */ +#define CD1865_MCOR2_DSROD 0x80 /* Detect 1->0 transition of DSR. */ +#define CD1865_MCOR2_CDOD 0x40 /* Detect 1->0 transition of CD. */ +#define CD1865_MCOR2_CTSOD 0x20 /* Detect 1->0 transition of CTS. */ + +/* + * Modem Change Register, read/write (0x12). + */ +#define CD1865_MCR_DSRCHG 0x80 /* DSR Changed. */ +#define CD1865_MCR_CDCHG 0x40 /* CD Changed. */ +#define CD1865_MCR_CTSCHG 0x20 /* CTS Changed. */ + + +/* + * Modem Signal Value Register, read/write (0x28) + * + * Note: + * These are inverted with respect to the actual signals! If the + * signal is present, the bit is zero, else the bit is one. + */ +#define CD1865_MSVR_DSR 0x80 /* Current state of DSR input. */ +#define CD1865_MSVR_CD 0x40 /* Current state of CD input. */ +#define CD1865_MSVR_CTS 0x20 /* Current state of CTS input. */ +#define CD1865_MSVR_DTR 0x02 /* Current state of DTR output. */ +#define CD1865_MSVR_RTS 0x01 /* Current state of RTS output. */ +#define CD1865_MSVR_OFF 0xe3 /* All signals off. */ +#define CD1865_MSVR_ON 0x00 /* All signals on. */ + +/* + * Escape characters. These are sent in-band when embedded commands are + * enabled with CD1865_COR2_ETC. + */ +#define CD1865_C_ESC 0x00 /* Escape character. */ +#define CD1865_C_SBRK 0x81 /* Start sending BREAK. */ +#define CD1865_C_DELAY 0x82 /* Delay output. */ +#define CD1865_C_EBRK 0x83 /* Stop sending BREAK. */ + +#define CD1865_SRSR_RREQint 0x10 /* Receive request interrupt. */ +#define CD1865_SRSR_TREQint 0x04 /* Transmit request interrupt. */ +#define CD1865_SRSR_MREQint 0x01 /* Modem signal change request interrupt.*/ +#define CD1865_SRSR_REQint 0x15 /* All of the above. */ + +#define CD1865_SRCR_PKGTYPE 0x80 +#define CD1865_SRCR_REGACKEN 0x40 +#define CD1865_SRCR_DAISYEN 0x20 +#define CD1865_SRCR_GLOBPRI 0x10 +#define CD1865_SRCR_UNFAIR 0x08 +#define CD1865_SRCR_AUTOPRI 0x02 +#define CD1865_SRCR_PRISEL 0x01 diff --git a/sys/dev/sx/sx.c b/sys/dev/sx/sx.c new file mode 100644 index 000000000000..e88d7459b51a --- /dev/null +++ b/sys/dev/sx/sx.c @@ -0,0 +1,2039 @@ +/* + * Device tsfsdriver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the foljxowing disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + + +/* Main tty driver routines for the Specialix I/O8+ device driver. */ + +#include "opt_compat.h" +#include "opt_debug_sx.h" + +#include +#include +#if defined(COMPAT_43) || defined(COMPAT_SUNOS) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#define SX_BROKEN_CTS + +enum sx_mctl { GET, SET, BIS, BIC }; + +static int sx_modem(struct sx_softc *, struct sx_port *, enum sx_mctl, int); +static void sx_write_enable(struct sx_port *, int); +static void sx_start(struct tty *); +static void sx_stop(struct tty *, int); +static void sx_disc_optim(struct tty *tp, struct termios *t,struct sx_port *pp); +static void sxhardclose(struct sx_port *pp); +static void sxdtrwakeup(void *chan); +static void sx_shutdown_chan(struct sx_port *); + +#ifdef SX_DEBUG +static char *sx_mctl2str(enum sx_mctl cmd); +#endif + +static int sxparam(struct tty *, struct termios *); + +static void sx_modem_state(struct sx_softc *sc, struct sx_port *pp, int card); + +static d_open_t sxopen; +static d_close_t sxclose; +static d_write_t sxwrite; +static d_ioctl_t sxioctl; + +#define CDEV_MAJOR 185 +static struct cdevsw sx_cdevsw = { + /* open */ sxopen, + /* close */ sxclose, + /* read */ ttyread, + /* write */ sxwrite, + /* ioctl */ sxioctl, + /* poll */ ttypoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "sx", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_TTY | D_KQFILTER, + /* bmaj */ -1, + /* kqfilter */ ttykqfilter, +}; + +static int sx_debug = 0; /* DBG_ALL|DBG_PRINTF|DBG_MODEM|DBG_IOCTL|DBG_PARAM;e */ +SYSCTL_INT(_machdep, OID_AUTO, sx_debug, CTLFLAG_RW, &sx_debug, 0, ""); + +static struct tty *sx__tty; + +static int sx_numunits; + +devclass_t sx_devclass; + +/* + * See sx.h for these values. + */ +static struct speedtab bdrates[] = { + { B75, CLK75, }, + { B110, CLK110, }, + { B150, CLK150, }, + { B300, CLK300, }, + { B600, CLK600, }, + { B1200, CLK1200, }, + { B2400, CLK2400, }, + { B4800, CLK4800, }, + { B9600, CLK9600, }, + { B19200, CLK19200, }, + { B38400, CLK38400, }, + { B57600, CLK57600, }, + { B115200, CLK115200, }, + { -1, -1 }, +}; + + +/* + * Approximate (rounded) character per second rates. Translated at card + * initialization time to characters per clock tick. + */ +static int done_chartimes = 0; +static struct speedtab chartimes[] = { + { B75, 8, }, + { B110, 11, }, + { B150, 15, }, + { B300, 30, }, + { B600, 60, }, + { B1200, 120, }, + { B2400, 240, }, + { B4800, 480, }, + { B9600, 960, }, + { B19200, 1920, }, + { B38400, 3840, }, + { B57600, 5760, }, + { B115200, 11520, }, + { -1, -1 }, +}; +static volatile int in_interrupt = 0; /* Inside interrupt handler? */ + +static int sx_flags; /* The flags we were configured with. */ +SYSCTL_INT(_machdep, OID_AUTO, sx_flags, CTLFLAG_RW, &sx_flags, 0, ""); + +#ifdef POLL +static int sx_pollrate; /* in addition to irq */ +static int sx_realpoll = 0; /* poll HW on timer */ + +SYSCTL_INT(_machdep, OID_AUTO, sx_pollrate, CTLFLAG_RW, &sx_pollrate, 0, ""); +SYSCTL_INT(_machdep, OID_AUTO, sx_realpoll, CTLFLAG_RW, &sx_realpoll, 0, ""); + +static int init_finished = 0; +static void sx_poll(void *); +#endif + +/* + * sxattach() + * Initialize and attach the card, initialize the driver. + * + * Description: + * This is the standard attach routine. It initializes the I/O8+ + * card, identifies the chip on that card, then allocates and + * initializes the various data structures used by the driver + * itself. + */ +int +sxattach( + device_t dev) +{ + int unit; + struct sx_softc *sc; + struct tty *tp; + struct speedtab *spt; + int chip, x, y; + char rev; + int error; + + sc = device_get_softc(dev); + unit = device_get_unit(dev); + sx_flags = device_get_flags(dev); + + if (sx_numunits < unit + 1) + sx_numunits = unit + 1; + + DPRINT((0, DBG_AUTOBOOT, "sx%d: sxattach\n", unit)); + + /* Reset the CD1865. */ + if ((error = sx_init_cd1865(sc, unit)) != 0) { + return(error); + } + + /* + * ID the chip: + * + * Chip revcode pkgtype + * GFRCR SRCR bit 7 + * CD180 rev B 0x81 0 + * CD180 rev C 0x82 0 + * CD1864 rev A 0x82 1 + * CD1865 rev A 0x83 1 -- Do not use!!! Does not work. + * CD1865 rev B 0x84 1 + * -- Thanks to Gwen Wang, Cirrus Logic (via Roger Wollf). + */ + switch (sx_cd1865_in(sc, CD1865_GFRCR)) { + case 0x82: + chip = 1864; + rev = 'A'; + break; + case 0x83: + chip = 1865; + rev = 'A'; + break; + case 0x84: + chip = 1865; + rev = 'B'; + break; + case 0x85: + chip = 1865; + rev = 'C'; + break; + default: + chip = -1; + rev = '\0'; + break; + } + + if (bootverbose && chip != -1) + printf("sx%d: Specialix I/O8+ CD%d processor rev %c\n", + unit, chip, rev); + DPRINT((0, DBG_AUTOBOOT, "sx%d: GFRCR 0x%02x\n", + unit, sx_cd1865_in(sc, CD1865_GFRCR))); +#ifdef POLL + if (sx_pollrate == 0) { + sx_pollrate = POLLHZ; /* in addition to irq */ +#ifdef REALPOLL + sx_realpoll = 1; /* scan always */ +#endif + } +#endif + sc->sc_ports = (struct sx_port *)malloc( + sizeof(struct sx_port) * SX_NUMCHANS, + M_DEVBUF, + M_NOWAIT); + if (sc->sc_ports == NULL) { + printf("sx%d: No memory for sx_port structs!\n", unit); + return(EINVAL); + } + bzero(sc->sc_ports, sizeof(struct sx_port) * SX_NUMCHANS); + /* + * Allocate tty structures for the channels. + */ + tp = (struct tty *)malloc(sizeof(struct tty) * SX_NUMCHANS, + M_DEVBUF, + M_NOWAIT); + if (tp == NULL) { + free(sc->sc_ports, M_DEVBUF); + printf("sx%d: No memory for tty structs!\n", unit); + return(EINVAL); + } + bzero(tp, sizeof(struct tty) * SX_NUMCHANS); + sx__tty = tp; + /* + * Initialize the channels. + */ + for (x = 0; x < SX_NUMCHANS; x++) { + sc->sc_ports[x].sp_chan = x; + sc->sc_ports[x].sp_tty = tp++; + sc->sc_ports[x].sp_state = 0; /* internal flag */ + sc->sc_ports[x].sp_dtr_wait = 3 * hz; + sc->sc_ports[x].sp_iin.c_iflag = TTYDEF_IFLAG; + sc->sc_ports[x].sp_iin.c_oflag = TTYDEF_OFLAG; + sc->sc_ports[x].sp_iin.c_cflag = TTYDEF_CFLAG; + sc->sc_ports[x].sp_iin.c_lflag = TTYDEF_LFLAG; + termioschars(&sc->sc_ports[x].sp_iin); + sc->sc_ports[x].sp_iin.c_ispeed = TTYDEF_SPEED;; + sc->sc_ports[x].sp_iin.c_ospeed = TTYDEF_SPEED;; + sc->sc_ports[x].sp_iout = sc->sc_ports[x].sp_iin; + } + if (done_chartimes == 0) { + for (spt = chartimes ; spt->sp_speed != -1; spt++) { + if ((spt->sp_code /= hz) == 0) + spt->sp_code = 1; + } + done_chartimes = 1; + } + /* + * Set up the known devices. + */ + y = unit * (1 << SX_CARDSHIFT); + for (x = 0; x < SX_NUMCHANS; x++) { + register int num; + + /* DTR/RTS -> RTS devices. */ + num = x + y; + make_dev(&sx_cdevsw, x, 0, 0, 0600, "ttyG%02d", x+y); + make_dev(&sx_cdevsw, x + 0x00080, 0, 0, 0600, "cuaG%02d", num); + make_dev(&sx_cdevsw, x + 0x10000, 0, 0, 0600, "ttyiG%02d", num); + make_dev(&sx_cdevsw, x + 0x10080, 0, 0, 0600, "cuaiG%02d", num); + make_dev(&sx_cdevsw, x + 0x20000, 0, 0, 0600, "ttylG%02d", num); + make_dev(&sx_cdevsw, x + 0x20080, 0, 0, 0600, "cualG%02d", num); + /* DTR/RTS -> DTR devices. */ + num += SX_NUMCHANS; + make_dev(&sx_cdevsw, x + 0x00008, 0, 0, 0600, "ttyG%02d", num); + make_dev(&sx_cdevsw, x + 0x00088, 0, 0, 0600, "cuaG%02d", num); + make_dev(&sx_cdevsw, x + 0x10008, 0, 0, 0600, "ttyiG%02d", num); + make_dev(&sx_cdevsw, x + 0x10088, 0, 0, 0600, "cuaiG%02d", num); + make_dev(&sx_cdevsw, x + 0x20008, 0, 0, 0600, "ttylG%02d", num); + make_dev(&sx_cdevsw, x + 0x20088, 0, 0, 0600, "cualG%02d", num); + } + return (0); +} + +/* + * sxopen() + * Open a port on behalf of a user. + * + * Description: + * This is the standard open routine. + */ +static int +sxopen( + dev_t dev, + int flag, + int mode, + struct proc *p) +{ + int oldspl, error; + int card, chan; + struct sx_softc *sc; + struct tty *tp; + struct sx_port *pp; + int mynor = minor(dev); + + card = SX_MINOR2CARD(mynor); + if ((sc = devclass_get_softc(sx_devclass, card)) == NULL) + return (ENXIO); + chan = SX_MINOR2CHAN(mynor); + if (chan >= SX_NUMCHANS) { + DPRINT((0, DBG_OPEN|DBG_FAIL, "sx%d: nchans %d\n", + card, SX_NUMCHANS)); + return(ENXIO); + } +#ifdef POLL + /* + * We've now got a device, so start the poller. + */ + if (init_finished == 0) { + timeout(sx_poll, (caddr_t)0L, sx_pollrate); + init_finished = 1; + } +#endif + /* initial/lock device */ + if (DEV_IS_STATE(mynor)) { + return(0); + } + pp = &(sc->sc_ports[chan]); + tp = pp->sp_tty; /* the "real" tty */ + dev->si_tty = tp; + DPRINT((pp, DBG_ENTRY|DBG_OPEN, "sxopen(%s,%x,%x,%x)\n", + devtoname(dev), flag, mode, p)); + + oldspl = spltty(); /* Keep others out */ + error = 0; + /* + * The minor also indicates whether the DTR pin on this port is wired + * as DTR or as RTS. Default is zero, wired as RTS. + */ + if (DEV_DTRPIN(mynor)) + pp->sp_state |= SX_SS_DTRPIN; + else + pp->sp_state &= ~SX_SS_DTRPIN; + pp->sp_state &= SX_SS_XMIT; /* Turn off "transmitting" flag. */ +open_top: + /* + * If DTR is off and we actually do have a DTR pin, sleep waiting for + * it to assert. + */ + while (pp->sp_state & SX_SS_DTR_OFF && SX_DTRPIN(pp)) { + error = tsleep(&pp->sp_dtr_wait, TTIPRI|PCATCH, "sxdtr", 0); + if (error != 0) + goto out; + } + + if (tp->t_state & TS_ISOPEN) { + /* + * The device is open, so everything has been initialized. + * Handle conflicts. + */ + if (DEV_IS_CALLOUT(mynor)) { + if (!pp->sp_active_out) { + error = EBUSY; + goto out; + } + } + else { + if (pp->sp_active_out) { + if (flag & O_NONBLOCK) { + error = EBUSY; + goto out; + } + error = tsleep(&pp->sp_active_out, + TTIPRI|PCATCH, + "sxbi", 0); + if (error != 0) + goto out; + goto open_top; + } + } + if (tp->t_state & TS_XCLUDE && suser(p)) { + DPRINT((pp, DBG_OPEN|DBG_FAIL, + "already open and EXCLUSIVE set\n")); + error = EBUSY; + goto out; + } + } else { + /* + * The device isn't open, so there are no conflicts. + * Initialize it. Avoid sleep... :-) + */ + DPRINT((pp, DBG_OPEN, "first open\n")); + tp->t_oproc = sx_start; + tp->t_stop = sx_stop; + tp->t_param = sxparam; + tp->t_dev = dev; + tp->t_termios = mynor & SX_CALLOUT_MASK + ? pp->sp_iout : pp->sp_iin; + + (void)sx_modem(sc, pp, SET, TIOCM_DTR|TIOCM_RTS); + + ++pp->sp_wopeners; /* in case of sleep in sxparam */ + + error = sxparam(tp, &tp->t_termios); + + --pp->sp_wopeners; + if (error != 0) + goto out; + /* XXX: we should goto_top if sxparam slept */ + + /* set initial DCD state */ + if (DEV_IS_CALLOUT(mynor) || + (sx_modem(sc, pp, GET, 0) & TIOCM_CD)) { + (*linesw[tp->t_line].l_modem)(tp, 1); + } + } + /* whoops! we beat the close! */ + if (pp->sp_state & SX_SS_CLOSING) { + /* try and stop it from proceeding to bash the hardware */ + pp->sp_state &= ~SX_SS_CLOSING; + } + /* + * Wait for DCD if necessary + */ + if (!(tp->t_state & TS_CARR_ON) && !DEV_IS_CALLOUT(mynor) && + !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { + ++pp->sp_wopeners; + DPRINT((pp, DBG_OPEN, "sleeping for carrier\n")); + error = tsleep(TSA_CARR_ON(tp), TTIPRI|PCATCH, "sxdcd", 0); + --pp->sp_wopeners; + if (error != 0) + goto out; + goto open_top; + } + + error = (*linesw[tp->t_line].l_open)(dev, tp); + sx_disc_optim(tp, &tp->t_termios, pp); + if (tp->t_state & TS_ISOPEN && DEV_IS_CALLOUT(mynor)) + pp->sp_active_out = TRUE; + + pp->sp_state |= SX_SS_OPEN; /* made it! */ + +out: + splx(oldspl); + + DPRINT((pp, DBG_OPEN, "leaving sxopen\n")); + + if (!(tp->t_state & TS_ISOPEN) && pp->sp_wopeners == 0) + sxhardclose(pp); + + return(error); +} + +/* + * sxclose() + * Close a port for a user. + * + * Description: + * This is the standard close routine. + */ +static int +sxclose( + dev_t dev, + int flag, + int mode, + struct proc *p) +{ + struct sx_port *pp; + struct tty *tp; + int oldspl; + int error = 0; + int mynor = minor(dev); + + if (DEV_IS_SPECIAL(mynor)) + return(0); + + oldspl = spltty(); + + pp = MINOR2PP(mynor); + tp = pp->sp_tty; + + DPRINT((pp, DBG_ENTRY|DBG_CLOSE, "sxclose(%s,%x,%x,%x) sp_state:%x\n", + devtoname(dev), flag, mode, p, pp->sp_state)); + + /* did we sleep and lose a race? */ + if (pp->sp_state & SX_SS_CLOSING) { + /* error = ESOMETING? */ + goto out; + } + + /* begin race detection.. */ + pp->sp_state |= SX_SS_CLOSING; + + sx_write_enable(pp, 0); /* block writes for ttywait() */ + + /* THIS MAY SLEEP IN TTYWAIT!!! */ + (*linesw[tp->t_line].l_close)(tp, flag); + + sx_write_enable(pp, 1); + + /* did we sleep and somebody started another open? */ + if (!(pp->sp_state & SX_SS_CLOSING)) { + /* error = ESOMETING? */ + goto out; + } + /* ok. we are now still on the right track.. nuke the hardware */ + + sx_stop(tp, FREAD | FWRITE); + + sxhardclose(pp); + ttyclose(tp); + pp->sp_state &= ~SX_SS_OPEN; + +out: + DPRINT((pp, DBG_CLOSE|DBG_EXIT, "sxclose out\n")); + splx(oldspl); + return(error); +} + +/* + * sxhardclose() + * Do hard-close processing. + * + * Description: + * Called on last close. Handle DTR and RTS, do cleanup. If we have + * pending output in the FIFO, wait for it to clear before we shut down + * the hardware. + */ +static void +sxhardclose( + struct sx_port *pp) +{ + struct sx_softc *sc; + struct tty *tp; + int oldspl, dcd; + + oldspl = spltty(); + + DPRINT((pp, DBG_CLOSE, "sxhardclose sp_state:%x\n", pp->sp_state)); + tp = pp->sp_tty; + sc = PP2SC(pp); + dcd = sx_modem(sc, pp, GET, 0) & TIOCM_CD; + if (tp->t_cflag & HUPCL || + (!pp->sp_active_out && !dcd && !(pp->sp_iin.c_cflag && CLOCAL)) || + !(tp->t_state & TS_ISOPEN)) { + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan); + if (sx_cd1865_in(sc, CD1865_IER|SX_EI) & CD1865_IER_TXRDY) { + sx_cd1865_bic(sc, CD1865_IER, CD1865_IER_TXRDY); + sx_cd1865_bis(sc, CD1865_IER, CD1865_IER_TXEMPTY); + enable_intr(); + splx(oldspl); + ttysleep(tp, (caddr_t)pp, + TTOPRI|PCATCH, "sxclose", tp->t_timeout); + oldspl = spltty(); + } + else { + enable_intr(); + } + (void)sx_modem(sc, pp, BIC, TIOCM_DTR|TIOCM_RTS); + /* + * If we should hold DTR off for a bit and we actually have a + * DTR pin to hold down, schedule sxdtrwakeup(). + */ + if (pp->sp_dtr_wait != 0 && SX_DTRPIN(pp)) { + timeout(sxdtrwakeup, pp, pp->sp_dtr_wait); + pp->sp_state |= SX_SS_DTR_OFF; + } + + } + (void)sx_shutdown_chan(pp); /* Turn off the hardware. */ + pp->sp_active_out = FALSE; + wakeup((caddr_t)&pp->sp_active_out); + wakeup(TSA_CARR_ON(tp)); + + splx(oldspl); +} + + +/* + * called at splsoftclock()... + */ +static void +sxdtrwakeup(void *chan) +{ + struct sx_port *pp; + int oldspl; + + oldspl = spltty(); + pp = (struct sx_port *)chan; + pp->sp_state &= ~SX_SS_DTR_OFF; + wakeup(&pp->sp_dtr_wait); + splx(oldspl); +} + +/* + * sxwrite() + * Handle a write to a port on the I/O8+. + * + * Description: + * This just hands processing off to the line discipline. + */ +static int +sxwrite( + dev_t dev, + struct uio *uio, + int flag) +{ + struct sx_softc *sc; + struct sx_port *pp; + struct tty *tp; + int error = 0; + int mynor = minor(dev); + int oldspl; + + pp = MINOR2PP(mynor); + sc = PP2SC(pp); + tp = pp->sp_tty; + DPRINT((pp, DBG_WRITE, "sxwrite %s %x %x\n", devtoname(dev), uio, flag)); + + oldspl = spltty(); + /* + * If writes are currently blocked, wait on the "real" tty + */ + while (pp->sp_state & SX_SS_BLOCKWRITE) { + pp->sp_state |= SX_SS_WAITWRITE; + DPRINT((pp, DBG_WRITE, "sxwrite sleep on SX_SS_BLOCKWRITE\n")); + if ((error = ttysleep(tp, + (caddr_t)pp, + TTOPRI|PCATCH, + "sxwrite", + tp->t_timeout))) { + if (error == EWOULDBLOCK) + error = EIO; + goto out; + } + } + error = (*linesw[tp->t_line].l_write)(tp, uio, flag); +out: splx(oldspl); + DPRINT((pp, DBG_WRITE, "sxwrite out\n")); + return (error); +} + +/* + * sxioctl() + * Handle ioctl() processing. + * + * Description: + * This is the standard serial ioctl() routine. It was cribbed almost + * entirely from the si(4) driver. Thanks, Peter. + */ +static int +sxioctl( + dev_t dev, + u_long cmd, + caddr_t data, + int flag, + struct proc *p) +{ + struct sx_softc *sc; + struct sx_port *pp; + struct tty *tp; + int error; + int mynor = minor(dev); + int oldspl; + int blocked = 0; +#if defined(COMPAT_43) + u_long oldcmd; + + struct termios term; +#endif + + pp = MINOR2PP(mynor); + tp = pp->sp_tty; + + DPRINT((pp, DBG_ENTRY|DBG_IOCTL, "sxioctl %s %lx %x %x\n", + devtoname(dev), cmd, data, flag)); + if (DEV_IS_STATE(mynor)) { + struct termios *ct; + + switch (mynor & SX_STATE_MASK) { + case SX_INIT_STATE_MASK: + ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_iout : + &pp->sp_iin; + break; + case SX_LOCK_STATE_MASK: + ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_lout : + &pp->sp_lin; + break; + default: + return(ENODEV); + } + switch (cmd) { + case TIOCSETA: + error = suser(p); + if (error != 0) + return(error); + *ct = *(struct termios *)data; + return(0); + case TIOCGETA: + *(struct termios *)data = *ct; + return(0); + case TIOCGETD: + *(int *)data = TTYDISC; + return(0); + case TIOCGWINSZ: + bzero(data, sizeof(struct winsize)); + return(0); + default: + return(ENOTTY); + } + } + /* + * Do the old-style ioctl compat routines... + */ +#if defined(COMPAT_43) + term = tp->t_termios; + oldcmd = cmd; + error = ttsetcompat(tp, &cmd, data, &term); + if (error != 0) + return(error); + if (cmd != oldcmd) + data = (caddr_t)&term; +#endif + /* + * Do the initial / lock state business + */ + if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { + int cc; + struct termios *dt = (struct termios *)data; + struct termios *lt = mynor & SX_CALLOUT_MASK + ? &pp->sp_lout : &pp->sp_lin; + + dt->c_iflag = (tp->t_iflag & lt->c_iflag) | + (dt->c_iflag & ~lt->c_iflag); + dt->c_oflag = (tp->t_oflag & lt->c_oflag) | + (dt->c_oflag & ~lt->c_oflag); + dt->c_cflag = (tp->t_cflag & lt->c_cflag) | + (dt->c_cflag & ~lt->c_cflag); + dt->c_lflag = (tp->t_lflag & lt->c_lflag) | + (dt->c_lflag & ~lt->c_lflag); + for (cc = 0; cc < NCCS; ++cc) + if (lt->c_cc[cc] != 0) + dt->c_cc[cc] = tp->t_cc[cc]; + if (lt->c_ispeed != 0) + dt->c_ispeed = tp->t_ispeed; + if (lt->c_ospeed != 0) + dt->c_ospeed = tp->t_ospeed; + } + + /* + * Block user-level writes to give the ttywait() + * a chance to completely drain for commands + * that require the port to be in a quiescent state. + */ + switch (cmd) { + case TIOCSETAW: + case TIOCSETAF: + case TIOCDRAIN: +#ifdef COMPAT_43 + case TIOCSETP: +#endif + blocked++; /* block writes for ttywait() and sxparam() */ + sx_write_enable(pp, 0); + } + + error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); + if (error != ENOIOCTL) + goto out; + + oldspl = spltty(); + + error = ttioctl(tp, cmd, data, flag); + sx_disc_optim(tp, &tp->t_termios, pp); + if (error != ENOIOCTL) { + splx(oldspl); + goto out; + } + sc = PP2SC(pp); /* Need this to do I/O to the card. */ + error = 0; + switch (cmd) { + case TIOCSBRK: /* Send BREAK. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK S\n", + devtoname(dev))); + /* + * If there's already a break state change pending or + * we're already sending a break, just ignore this. + * Otherwise, just set our flag and start the + * transmitter. + */ + if (!SX_DOBRK(pp) && !SX_BREAK(pp)) { + pp->sp_state |= SX_SS_DOBRK; + sx_start(tp); + } + break; + case TIOCCBRK: /* Stop sending BREAK. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK E\n", + devtoname(dev))); + /* + * If a break is going, set our flag so we turn it off + * when we can, then kick the transmitter. If a break + * isn't going and the flag is set, turn it off. + */ + if (SX_BREAK(pp)) { + pp->sp_state |= SX_SS_DOBRK; + sx_start(tp); + } + else { + if (SX_DOBRK(pp)) + pp->sp_state &= SX_SS_DOBRK; + } + break; + case TIOCSDTR: /* Assert DTR. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s +DTR\n", + devtoname(dev))); + if (SX_DTRPIN(pp)) /* Using DTR? */ + (void)sx_modem(sc, pp, SET, TIOCM_DTR); + break; + case TIOCCDTR: /* Clear DTR. */ + DPRINT((pp, DBG_IOCTL, "sxioctl(%s) -DTR\n", + devtoname(dev))); + if (SX_DTRPIN(pp)) /* Using DTR? */ + (void)sx_modem(sc, pp, SET, 0); + break; + case TIOCMSET: /* Force all modem signals. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s =%x\n", + devtoname(dev), *(int *)data)); + (void)sx_modem(sc, pp, SET, *(int *)data); + break; + case TIOCMBIS: /* Set (some) modem signals. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s +%x\n", + devtoname(dev), *(int *)data)); + (void)sx_modem(sc, pp, BIS, *(int *)data); + break; + case TIOCMBIC: /* Clear (some) modem signals. */ + DPRINT((pp, DBG_IOCTL, "sxioctl %s -%x\n", + devtoname(dev), *(int *)data)); + (void)sx_modem(sc, pp, BIC, *(int *)data); + break; + case TIOCMGET: /* Get state of modem signals. */ + *(int *)data = sx_modem(sc, pp, GET, 0); + DPRINT((pp, DBG_IOCTL, "sxioctl(%s) got signals 0x%x\n", + devtoname(dev), *(int *)data)); + break; + case TIOCMSDTRWAIT: /* Set "wait on close" delay. */ + /* must be root since the wait applies to following logins */ + error = suser(p); + if (error == 0) + pp->sp_dtr_wait = *(int *)data * hz / 100; + break; + case TIOCMGDTRWAIT: /* Get "wait on close" delay. */ + *(int *)data = pp->sp_dtr_wait * 100 / hz; + break; + default: + error = ENOTTY; + } + splx(oldspl); + +out: DPRINT((pp, DBG_IOCTL|DBG_EXIT, "sxioctl out %d\n", error)); + if (blocked) + sx_write_enable(pp, 1); + return(error); +} + +/* + * sxparam() + * Configure line parameters. + * + * Description: + * Configure the bitrate, wordsize, flow control and various other serial + * port parameters for this line. + * + * Environment: + * Called at spltty(); this may sleep, does not flush nor wait for drain, + * nor block writes. Caller must arrange this if it's important.. + */ +static int +sxparam( + struct tty *tp, + struct termios *t) +{ + struct sx_softc *sc; + struct sx_port *pp = TP2PP(tp); + int oldspl, cflag, iflag, oflag, lflag; + int error = 0; + int ispd = 0; + int ospd = 0; + unsigned char val, cor1, cor2, cor3, ier; + + sc = PP2SC(pp); + DPRINT((pp, DBG_ENTRY|DBG_PARAM, "sxparam %x/%x\n", tp, t)); + cflag = t->c_cflag; + iflag = t->c_iflag; + oflag = t->c_oflag; + lflag = t->c_lflag; + DPRINT((pp, DBG_PARAM, "OF 0x%x CF 0x%x IF 0x%x LF 0x%x\n", + oflag, cflag, iflag, lflag)); + + /* If the port isn't hung up... */ + if (t->c_ospeed != 0) { + /* Convert bit rate to hardware divisor values. */ + ospd = ttspeedtab(t->c_ospeed, bdrates); + ispd = t->c_ispeed ? ttspeedtab(t->c_ispeed, bdrates) : ospd; + /* We only allow standard bit rates. */ + if (ospd < 0 || ispd < 0) + return(EINVAL); + } + oldspl = spltty(); /* Block other activity. */ + cor1 = 0; + cor2 = 0; + cor3 = 0; + ier = CD1865_IER_RXD | CD1865_IER_CD; +#ifdef notyet + /* We don't yet handle this stuff. */ + val = 0; + if (iflag & IGNBRK) /* Breaks */ + val |= BR_IGN; + if (iflag & BRKINT) /* Interrupt on break? */ + val |= BR_INT; + if (iflag & PARMRK) /* Parity mark? */ + val |= BR_PARMRK; +#endif /* notyet */ + /* + * If the device isn't hung up, set the serial port bitrates. + */ + if (t->c_ospeed != 0) { + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + sx_cd1865_out(sc, CD1865_RBPRH|SX_EI, (ispd >> 8) & 0xff); + sx_cd1865_out(sc, CD1865_RBPRL|SX_EI, ispd & 0xff); + sx_cd1865_out(sc, CD1865_TBPRH|SX_EI, (ospd >> 8) & 0xff); + sx_cd1865_out(sc, CD1865_TBPRL|SX_EI, ospd & 0xff); + enable_intr(); + } + if (cflag & CSTOPB) /* Two stop bits? */ + cor1 |= CD1865_COR1_2SB; /* Yep. */ + /* + * Parity settings. + */ + val = 0; + if (cflag & PARENB) { /* Parity enabled? */ + val = CD1865_COR1_NORMPAR; /* Turn on normal parity handling. */ + if (cflag & PARODD) /* Odd Parity? */ + val |= CD1865_COR1_ODDP; /* Turn it on. */ + } + else + val = CD1865_COR1_NOPAR; /* Turn off parity detection. */ + cor1 |= val; + if (iflag & IGNPAR) /* Ignore chars with parity errors? */ + cor1 |= CD1865_COR1_IGNORE; + /* + * Set word length. + */ + if ((cflag & CS8) == CS8) + val = CD1865_COR1_8BITS; + else if ((cflag & CS7) == CS7) + val = CD1865_COR1_7BITS; + else if ((cflag & CS6) == CS6) + val = CD1865_COR1_6BITS; + else + val = CD1865_COR1_5BITS; + cor1 |= val; + /* + * Enable hardware RTS/CTS flow control. We can handle output flow + * control at any time, since we have a dedicated CTS pin. + * Unfortunately, though, the RTS pin is really the DTR pin. This + * means that we can't ever use the automatic input flow control of + * the CD1865 and that we can only use the pin for input flow + * control when it's wired as RTS. + */ + if (cflag & CCTS_OFLOW) { /* Output flow control... */ + pp->sp_state |= SX_SS_OFLOW; +#ifdef SX_BROKEN_CTS + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) { + enable_intr(); + pp->sp_state |= SX_SS_OSTOP; + sx_write_enable(pp, 0); /* Block writes. */ + } + else { + enable_intr(); + } + ier |= CD1865_IER_CTS; +#else /* SX_BROKEN_CTS */ + cor2 |= CD1865_COR2_CTSAE; /* Set CTS automatic enable. */ +#endif /* SX_BROKEN_CTS */ + } + else { + pp->sp_state &= ~SX_SS_OFLOW; + } + if (cflag & CRTS_IFLOW && !SX_DTRPIN(pp)) /* Input flow control. */ + pp->sp_state |= SX_SS_IFLOW; + else + pp->sp_state &= ~SX_SS_IFLOW; + if (iflag & IXANY) + cor2 |= CD1865_COR2_IXM; /* Any character is XON. */ + if (iflag & IXOFF) { + cor2 |= CD1865_COR2_TXIBE; /* Enable inband flow control.*/ + cor3 |= CD1865_COR3_FCT | CD1865_COR3_SCDE; /* Hide from host */ + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Sel chan.*/ + sx_cd1865_out(sc, CD1865_SCHR1|SX_EI, t->c_cc[VSTART]); + sx_cd1865_out(sc, CD1865_SCHR2|SX_EI, t->c_cc[VSTOP]); + sx_cd1865_out(sc, CD1865_SCHR3|SX_EI, t->c_cc[VSTART]); + sx_cd1865_out(sc, CD1865_SCHR4|SX_EI, t->c_cc[VSTOP]); + enable_intr(); + } + /* + * All set, now program the hardware. + */ + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */ + sx_cd1865_out(sc, CD1865_COR1|SX_EI, cor1); + sx_cd1865_out(sc, CD1865_COR2|SX_EI, cor2); + sx_cd1865_out(sc, CD1865_COR3|SX_EI, cor3); + sx_cd1865_wait_CCR(sc, SX_EI); + sx_cd1865_out(sc, CD1865_CCR|SX_EI, + CD1865_CCR_CORCHG1|CD1865_CCR_CORCHG2|CD1865_CCR_CORCHG3); + sx_cd1865_wait_CCR(sc, SX_EI); + enable_intr(); + if (SX_DTRPIN(pp)) + val = TIOCM_DTR; + else + val = TIOCM_RTS; + if (t->c_ospeed == 0) /* Clear DTR/RTS if we're hung up. */ + (void)sx_modem(sc, pp, BIC, val); + else /* If we were hung up, we may have to */ + (void)sx_modem(sc, pp, BIS, val); /* re-enable the signal. */ + /* + * Last, enable the receiver and transmitter and turn on the + * interrupts we need (receive, carrier-detect and possibly CTS + * (iff we're built with SX_BROKEN_CTS and CCTS_OFLOW is on). + */ + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */ + sx_cd1865_wait_CCR(sc, SX_EI); + sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_RXEN|CD1865_CCR_TXEN); + sx_cd1865_wait_CCR(sc, SX_EI); + sx_cd1865_out(sc, CD1865_IER|SX_EI, ier); + enable_intr(); + DPRINT((pp, DBG_PARAM, "sxparam out\n")); + splx(oldspl); + return(error); +} + +/* + * sx_write_enable() + * Enable/disable writes to a card channel. + * + * Description: + * Set or clear the SX_SS_BLOCKWRITE flag in sp_state to block or allow + * writes to a serial port on the card. When we enable writes, we + * wake up anyone sleeping on SX_SS_WAITWRITE for this channel. + * + * Parameters: + * flag 0 - disable writes. + * 1 - enable writes. + */ +static void +sx_write_enable( + struct sx_port *pp, + int flag) +{ + int oldspl; + + oldspl = spltty(); /* Keep interrupts out. */ + if (flag) { /* Enable writes to the channel? */ + pp->sp_state &= ~SX_SS_BLOCKWRITE; /* Clear our flag. */ + if (pp->sp_state & SX_SS_WAITWRITE) { /* Sleepers? */ + pp->sp_state &= ~SX_SS_WAITWRITE; /* Clear their flag */ + wakeup((caddr_t)pp); /* & wake them up. */ + } + } + else /* Disabling writes. */ + pp->sp_state |= SX_SS_BLOCKWRITE; /* Set our flag. */ + splx(oldspl); +} + +/* + * sx_shutdown_chan() + * Shut down a channel on the I/O8+. + * + * Description: + * This does all hardware shutdown processing for a channel on the I/O8+. + * It is called from sxhardclose(). We reset the channel and turn off + * interrupts. + */ +static void +sx_shutdown_chan( + struct sx_port *pp) +{ + int s; + struct sx_softc *sc; + + DPRINT((pp, DBG_ENTRY, "sx_shutdown_chan %x %x\n", pp, pp->sp_state)); + sc = PP2SC(pp); + s = spltty(); + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan); /* Select channel. */ + sx_cd1865_wait_CCR(sc, 0); /* Wait for any commands to complete. */ + sx_cd1865_out(sc, CD1865_CCR, CD1865_CCR_SOFTRESET); /* Reset chan. */ + sx_cd1865_wait_CCR(sc, 0); + sx_cd1865_out(sc, CD1865_IER, 0); /* Disable all interrupts. */ + enable_intr(); + splx(s); +} + +/* + * sx_modem() + * Set/Get state of modem control lines. + * + * Description: + * Get and set the state of the modem control lines that we have available + * on the I/O8+. The only lines we are guaranteed to have are CD and CTS. + * We have DTR if the "DTR/RTS pin is DTR" flag is set, otherwise we have + * RTS through the DTR pin. + */ +static int +sx_modem( + struct sx_softc *sc, + struct sx_port *pp, + enum sx_mctl cmd, + int bits) +{ + int s, x; + + DPRINT((pp, DBG_ENTRY|DBG_MODEM, "sx_modem %x/%s/%x\n", + pp, sx_mctl2str(cmd), bits)); + s = spltty(); /* Block interrupts. */ + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select our port. */ + x = sx_cd1865_in(sc, CD1865_MSVR|SX_EI); /* Get the current signals. */ +#ifdef SX_DEBUG + DPRINT((pp, DBG_MODEM, "sx_modem MSVR 0x%x, CCSR %x GIVR %x SRSR %x\n", + x, sx_cd1865_in(sc, CD1865_CCSR|SX_EI), + sx_cd1865_in(sc, CD1865_GIVR|SX_EI), + sx_cd1865_in(sc, CD1865_SRSR|SX_EI))); +#endif + enable_intr(); /* Allow other interrupts. */ + switch (cmd) { + case GET: + bits = TIOCM_LE; + if ((x & CD1865_MSVR_CD) == 0) + bits |= TIOCM_CD; + if ((x & CD1865_MSVR_CTS) == 0) + bits |= TIOCM_CTS; + if ((x & CD1865_MSVR_DTR) == 0) { + if (SX_DTRPIN(pp)) /* Odd pin is DTR? */ + bits |= TIOCM_DTR; /* Report DTR. */ + else /* Odd pin is RTS. */ + bits |= TIOCM_RTS; /* Report RTS. */ + } + splx(s); + return(bits); + case SET: + x = CD1865_MSVR_OFF; + if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || + (bits & TIOCM_DTR && SX_DTRPIN(pp))) + x &= ~CD1865_MSVR_DTR; + break; + case BIS: + if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || + (bits & TIOCM_DTR && SX_DTRPIN(pp))) + x &= ~CD1865_MSVR_DTR; + break; + case BIC: + if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) || + (bits & TIOCM_DTR && SX_DTRPIN(pp))) + x |= CD1865_MSVR_DTR; + break; + } + DPRINT((pp, DBG_MODEM, "sx_modem MSVR=0x%x\n", x)); + disable_intr(); + /* + * Set the new modem signals. + */ + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + sx_cd1865_out(sc, CD1865_MSVR|SX_EI, x); + enable_intr(); + splx(s); + return 0; +} + +#ifdef POLL + +/* + * sx_poll() + * Poller to catch missed interrupts. + * + * Description: + * Only used if we're complied with POLL. This routine is called every + * sx_pollrate ticks to check for missed interrupts. We check each card + * in the system; if we missed an interrupt, we complain about each one + * and later call sx_intr() to handle them. + */ +static void +sx_poll( + void *dummy) +{ + struct sx_softc *sc; + struct sx_port *pp; + int card, lost, oldspl, chan; + + DPRINT((0, DBG_POLL, "sx_poll\n")); + oldspl = spltty(); + if (in_interrupt) + goto out; + lost = 0; + for (card = 0; card < sx_numunits; card++) { + sc = devclass_get_softc(sx_devclass, card); + if (sc == NULL) + continue; + if (sx_cd1865_in(sc, CD1865_SRSR|SX_EI) & CD1865_SRSR_REQint) { + printf("sx%d: lost interrupt\n", card); + lost++; + } + /* + * Gripe about no input flow control. + */ + for (chan = 0; chan < SX_NUMCHANS; pp++, chan++) { + pp = &(sc->sc_ports[chan]); + if (pp->sp_delta_overflows > 0) { + printf("sx%d: %d tty level buffer overflows\n", + card, pp->sp_delta_overflows); + pp->sp_delta_overflows = 0; + } + } + } + if (lost || sx_realpoll) + sx_intr(NULL); /* call intr with fake vector */ +out: splx(oldspl); + timeout(sx_poll, (caddr_t)0L, sx_pollrate); +} + +#endif /* POLL */ + + +/* + * sx_transmit() + * Handle transmit request interrupt. + * + * Description: + * This routine handles the transmit request interrupt from the CD1865 + * chip on the I/O8+ card. The CD1865 interrupts us for a transmit + * request under two circumstances: When the last character in the + * transmit FIFO is sent and the channel is ready for more characters + * ("transmit ready"), or when the last bit of the last character in the + * FIFO is actually transmitted ("transmit empty"). In the former case, + * we just pass processing off to sx_start() (via the line discipline) + * to queue more characters. In the latter case, we were waiting for + * the line to flush in sxhardclose() so we need to wake the sleeper. + */ +static void +sx_transmit( + struct sx_softc *sc, + struct sx_port *pp, + int card) +{ + struct tty *tp; + unsigned char flags; + + tp = pp->sp_tty; + /* + * Let others know what we're doing. + */ + pp->sp_state |= SX_SS_IXMIT; + /* + * Get the service request enable register to see what we're waiting + * for. + */ + flags = sx_cd1865_in(sc, CD1865_SRER|SX_EI); + + DPRINT((pp, DBG_TRANSMIT, "sx_xmit %x SRER %x\n", tp, flags)); + /* + * "Transmit ready." The transmit FIFO is empty (but there are still + * two characters being transmitted), so we need to tell the line + * discipline to send more. + */ + if (flags & CD1865_IER_TXRDY) { + (*linesw[tp->t_line].l_start)(tp); + pp->sp_state &= ~SX_SS_IXMIT; + DPRINT((pp, DBG_TRANSMIT, "sx_xmit TXRDY out\n")); + return; + } + /* + * "Transmit empty." The transmitter is completely empty; turn off the + * service request and wake up the guy in sxhardclose() who is waiting + * for this. + */ + if (flags & CD1865_IER_TXEMPTY) { + flags &= ~CD1865_IER_TXEMPTY; + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + sx_cd1865_out(sc, CD1865_SRER|SX_EI, flags); + wakeup((caddr_t)pp); + } + pp->sp_state &= ~SX_SS_IXMIT; + DPRINT((pp, DBG_TRANSMIT, "sx_xmit out\n")); +} + +/* + * sx_modem_state() + * Handle modem state-change request interrupt. + * + * Description: + * Handles changed modem signals CD and CTS. We pass the CD change + * off to the line discipline. We can't handle DSR since there isn't a + * pin for it. + */ +static void +sx_modem_state( + struct sx_softc *sc, + struct sx_port *pp, + int card) +{ + struct tty *tp; + unsigned char mcr; + + /* + * Let others know what we're doing. + */ + pp->sp_state |= SX_SS_IMODEM; + tp = pp->sp_tty; + /* Grab the Modem Change Register. */ + mcr = sx_cd1865_in(sc, CD1865_MCR|SX_EI); + DPRINT((pp, DBG_MODEM_STATE, + "sx_mdmst %x st %x sp %x mcr %x\n", + tp, tp->t_state, pp->sp_state, mcr)); + if (mcr & CD1865_MCR_CDCHG) { /* CD changed? */ + if ((sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_CD) == 0) { + DPRINT((pp, DBG_INTR, "modem carr on t_line %d\n", + tp->t_line)); + (void)(*linesw[tp->t_line].l_modem)(tp, 1); + } + else { /* CD went down. */ + DPRINT((pp, DBG_INTR, "modem carr off\n")); + if ((*linesw[tp->t_line].l_modem)(tp, 0)) + (void)sx_modem(sc, pp, SET, 0); + } + } +#ifdef SX_BROKEN_CTS + if (mcr & CD1865_MCR_CTSCHG) { /* CTS changed? */ + if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) { + pp->sp_state |= SX_SS_OSTOP; + sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); + sx_write_enable(pp, 0); /* Block writes. */ + } + else { + pp->sp_state &= ~SX_SS_OSTOP; + sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); + sx_write_enable(pp, 1); /* Unblock writes. */ + } + } +#endif /* SX_BROKEN_CTS */ + /* Clear state-change indicator bits. */ + sx_cd1865_out(sc, CD1865_MCR|SX_EI, 0); + pp->sp_state &= ~SX_SS_IMODEM; +} + +/* + * sx_receive() + * Handle receive request interrupt. + * + * Description: + * Handle a receive request interrupt from the CD1865. This is just a + * standard "we have characters to process" request, we don't have to + * worry about exceptions like BREAK and such. Exceptions are handled + * by sx_receive_exception(). + */ +static void +sx_receive( + struct sx_softc *sc, + struct sx_port *pp, + int card) +{ + struct tty *tp; + unsigned char count; + int i, x; + static unsigned char sx_rxbuf[SX_BUFFERSIZE]; /* input staging area */ + + tp = pp->sp_tty; + DPRINT((pp, DBG_RECEIVE, + "sx_rcv %x st %x sp %x\n", + tp, tp->t_state, pp->sp_state)); + /* + * Let others know what we're doing. + */ + pp->sp_state |= SX_SS_IRCV; + /* + * How many characters are waiting for us? + */ + count = sx_cd1865_in(sc, CD1865_RDCR|SX_EI); + if (count == 0) /* None? Bail. */ + return; + DPRINT((pp, DBG_RECEIVE, "sx_receive count %d\n", count)); + /* + * Pull the characters off the card into our local buffer, then + * process that. + */ + for (i = 0; i < count; i++) + sx_rxbuf[i] = sx_cd1865_in(sc, CD1865_RDR|SX_EI); + /* + * If we're not open and connected, bail. + */ + if (!(tp->t_state & TS_CONNECTED && tp->t_state & TS_ISOPEN)) { + pp->sp_state &= ~SX_SS_IRCV; + DPRINT((pp, DBG_RECEIVE, "sx_rcv not open\n")); + return; + } + /* + * If the tty input buffers are blocked and we have an RTS pin, + * drop RTS and bail. + */ + if (tp->t_state & TS_TBLOCK) { + if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) { + (void)sx_modem(sc, pp, BIC, TIOCM_RTS); + pp->sp_state |= SX_SS_ISTOP; + } + pp->sp_state &= ~SX_SS_IRCV; + return; + } + if (tp->t_state & TS_CAN_BYPASS_L_RINT) { + DPRINT((pp, DBG_RECEIVE, "sx_rcv BYPASS\n")); + /* + * Avoid the grotesquely inefficient lineswitch routine + * (ttyinput) in "raw" mode. It usually takes about 450 + * instructions (that's without canonical processing or + * echo!). slinput is reasonably fast (usually 40 + * instructions plus call overhead). + */ + if (tp->t_rawq.c_cc + count >= SX_I_HIGH_WATER && + (tp->t_cflag & CRTS_IFLOW || tp->t_iflag & IXOFF) && + !(tp->t_state & TS_TBLOCK)) { + ttyblock(tp); + DPRINT((pp, DBG_RECEIVE, "sx_rcv block\n")); + } + tk_nin += count; + tk_rawcc += count; + tp->t_rawcc += count; + + pp->sp_delta_overflows += + b_to_q((char *)sx_rxbuf, count, &tp->t_rawq); + ttwakeup(tp); + /* + * If we were stopped and need to start again because of this + * receive, kick the output routine to get things going again. + */ + if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY || + tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { + tp->t_state &= ~TS_TTSTOP; + tp->t_lflag &= ~FLUSHO; + sx_start(tp); + } + } + else { + DPRINT((pp, DBG_RECEIVE, "sx_rcv l_rint\n")); + /* + * It'd be nice to not have to go through the function call + * overhead for each char here. It'd be nice to block input + * it, saving a loop here and the call/return overhead. + */ + for (x = 0; x < count; x++) { + i = sx_rxbuf[x]; + if ((*linesw[tp->t_line].l_rint)(i, tp) == -1) + pp->sp_delta_overflows++; + /* + * doesn't seem to be much point doing this here. + * this driver has no softtty processing! ?? + */ + if (pp->sp_hotchar && i == pp->sp_hotchar) + setsofttty(); + } + } + pp->sp_state &= ~SX_SS_IRCV; + DPRINT((pp, DBG_RECEIVE, "sx_rcv out\n")); +} + + + +/* + * sx_receive_exception() + * Handle receive exception request interrupt processing. + * + * Description: + * Handle a receive exception request interrupt from the CD1865. + * Possible exceptions include BREAK, overrun, receiver timeout + * and parity and frame errors. We don't handle receiver timeout, + * we just complain. The rest are passed to ttyinput(). + */ +static void +sx_receive_exception( + struct sx_softc *sc, + struct sx_port *pp, + int card) +{ + struct tty *tp; + unsigned char st; + int ch, isopen; + + tp = pp->sp_tty; + /* + * Let others know what we're doing. + */ + pp->sp_state |= SX_SS_IRCVEXC; + /* + * Check to see whether we should receive characters. + */ + if (tp->t_state & TS_CONNECTED && + tp->t_state & TS_ISOPEN) + isopen = 1; + else + isopen = 0; + + st = sx_cd1865_in(sc, CD1865_RCSR|SX_EI); /* Get the character status.*/ + ch = (int)sx_cd1865_in(sc, CD1865_RDR|SX_EI); /* Get the character. */ + DPRINT((pp, DBG_RECEIVE_EXC, + "sx_rexc %x st %x sp %x st 0x%x ch 0x%x ('%c')\n", + tp, tp->t_state, pp->sp_state, st, ch, ch)); + /* If there's no status or the tty isn't open, bail. */ + if (!st || !isopen) { + pp->sp_state &= ~SX_SS_IRCVEXC; + DPRINT((pp, DBG_RECEIVE_EXC, "sx_rexc not open\n")); + return; + } + if (st & CD1865_RCSR_TOUT) /* Receiver timeout; just complain. */ + printf("sx%d: port %d: Receiver timeout.\n", card, pp->sp_chan); + else if (st & CD1865_RCSR_BREAK) + ch |= TTY_BI; + else if (st & CD1865_RCSR_PE) + ch |= TTY_PE; + else if (st & CD1865_RCSR_FE) + ch |= TTY_FE; + else if (st & CD1865_RCSR_OE) + ch |= TTY_OE; + (*linesw[tp->t_line].l_rint)(ch, tp); + pp->sp_state &= ~SX_SS_IRCVEXC; +} + +/* + * sx_intr() + * Field interrupts from the I/O8+. + * + * Description: + * The interrupt handler polls ALL ports on ALL adapters each time + * it is called. + */ +void +sx_intr( + void *arg) +{ + struct sx_softc *sc; + struct sx_port *pp = NULL; + int card; + unsigned char ack; + + sc = arg; + + DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr\n")); + if (in_interrupt) + return; + in_interrupt = 1; + + /* + * When we get an int we poll all the channels and do ALL pending + * work, not just the first one we find. This allows all cards to + * share the same vector. + * + * On the other hand, if we're sharing the vector with something + * that's not an I/O8+, we may be making extra work for ourselves. + */ + for (card = 0; card < sx_numunits; card++) { + unsigned char st; + + sc = devclass_get_softc(sx_devclass, card); + if (sc == NULL) + continue; + /* + * Check the Service Request Status Register to see who + * interrupted us and why. May be a receive, transmit or + * modem-signal-change interrupt. Reading the appropriate + * Request Acknowledge Register acknowledges the request and + * gives us the contents of the Global Service Vector Register, + * which in a daisy-chained configuration (not ours) uniquely + * identifies the particular CD1865 and gives us the request + * type. We mask off the ID part and use the rest. + * + * From the CD1865 specs, it appears that only one request can + * happen at a time, but in testing it's pretty obvious that + * the specs lie. Or perhaps we're just slow enough that the + * requests pile up. Regardless, if we try to process more + * than one at a time without clearing the previous request + * (writing zero to EOIR) first, we hang the card. Thus the + * "else if" logic here. + */ + while ((st = (sx_cd1865_in(sc, CD1865_SRSR|SX_EI)) & + CD1865_SRSR_REQint)) { + /* + * Transmit request interrupt. + */ + if (st & CD1865_SRSR_TREQint) { + ack = sx_cd1865_in(sc, CD1865_TRAR|SX_EI) & + CD1865_GIVR_ITMASK; + pp = sx_int_port(sc, card); + if (pp == NULL) /* Bad channel. */ + goto skip; + pp->sp_state |= SX_SS_INTR; /* In interrupt. */ + if (ack == CD1865_GIVR_IT_TX) + sx_transmit(sc, pp, card); + else + printf("sx%d: Bad transmit ack 0x%02x.\n", + card, ack); + } + /* + * Modem signal change request interrupt. + */ + else if (st & CD1865_SRSR_MREQint) { + ack = sx_cd1865_in(sc, CD1865_MRAR|SX_EI) & + CD1865_GIVR_ITMASK; + pp = sx_int_port(sc, card); + if (pp == NULL) /* Bad channel. */ + goto skip; + pp->sp_state |= SX_SS_INTR; /* In interrupt. */ + if (ack == CD1865_GIVR_IT_MODEM) + sx_modem_state(sc, pp, card); + else + printf("sx%d: Bad modem ack 0x%02x.\n", + card, ack); + } + /* + * Receive request interrupt. + */ + else if (st & CD1865_SRSR_RREQint) { + ack = sx_cd1865_in(sc, CD1865_RRAR|SX_EI) & + CD1865_GIVR_ITMASK; + pp = sx_int_port(sc, card); + if (pp == NULL) /* Bad channel. */ + goto skip; + pp->sp_state |= SX_SS_INTR; /* In interrupt. */ + if (ack == CD1865_GIVR_IT_RCV) + sx_receive(sc, pp, card); + else if (ack == CD1865_GIVR_IT_REXC) + sx_receive_exception(sc, pp, card); + else + printf("sx%d: Bad receive ack 0x%02x.\n", + card, ack); + } + /* + * None of the above; this is a "can't happen," but + * you never know... + */ + else { + printf("sx%d: Bad service request 0x%02x.\n", + card, st); + } + pp->sp_state &= ~SX_SS_INTR; +skip: sx_cd1865_out(sc, CD1865_EOIR|SX_EI, 0); /* EOI. */ + } /* while (st & CD1865_SRSR_REQint) */ + } /* for (card = 0; card < sx_numunits; card++) */ + in_interrupt = 0; + DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr out\n")); +} + +/* + * sx_start() + * Handle transmit and state-change stuff. + * + * Description: + * This is part of the line discipline processing; at various points in + * the line discipline he calls ttstart() which calls the oproc routine, + * which is this function. We're called by the line discipline to start + * data transmission and to change signal states (for RTS flow control). + * We're also called by this driver to perform line-breaks and to actually + * do the data transmission. + + * We can only fill the FIFO from interrupt since the card only makes it + * available to us during a service request such as TXRDY; this only + * happens at interrupt. + * + * All paths through this code call ttwwakeup(). + */ +static void +sx_start( + struct tty *tp) +{ + struct sx_softc *sc; + struct sx_port *pp; + struct clist *qp; + int s; + int count = CD1865_TFIFOSZ; + + s = spltty(); + pp = TP2PP(tp); + qp = &tp->t_outq; + DPRINT((pp, DBG_ENTRY|DBG_START, + "sx_start %x st %x sp %x cc %d\n", + tp, tp->t_state, pp->sp_state, qp->c_cc)); + + /* + * If we're stopped, just wake up sleepers and get out. + */ + if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) { + ttwwakeup(tp); + splx(s); + DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out\n", tp->t_state)); + return; + } + sc = TP2SC(tp); + /* + * If we're not transmitting, we may have been called to crank up the + * transmitter and start things rolling or we may have been called to + * get a bit of tty state. If the latter, handle it. Either way, if + * we have data to transmit, turn on the transmit-ready interrupt, + * set the XMIT flag and we're done. As soon as we allow interrupts + * the card will interrupt for the first chunk of data. Note that + * we don't mark the tty as busy until we are actually sending data + * and then only if we have more than will fill the FIFO. If there's + * no data to transmit, just handle the tty state. + */ + if (!SX_XMITTING(pp)) { + /* + * If we were flow-controlled and input is no longer blocked, + * raise RTS if we can. + */ + if (SX_ISTOP(pp) && !(tp->t_state & TS_TBLOCK)) { + if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) + (void)sx_modem(sc, pp, BIS, TIOCM_RTS); + pp->sp_state &= ~SX_SS_ISTOP; + } + /* + * If input is blocked, drop RTS if we can and set our flag. + */ + if (tp->t_state & TS_TBLOCK) { + if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) + (void)sx_modem(sc, pp, BIC, TIOCM_RTS); + pp->sp_state |= SX_SS_ISTOP; + } + if ((qp->c_cc > 0 && !SX_OSTOP(pp)) || SX_DOBRK(pp)) { + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); + enable_intr(); + pp->sp_state |= SX_SS_XMIT; + } + ttwwakeup(tp); + splx(s); + DPRINT((pp, DBG_EXIT|DBG_START, + "sx_start out B st %x sp %x cc %d\n", + tp->t_state, pp->sp_state, qp->c_cc)); + return; + } + /* + * If we weren't called from an interrupt or it wasn't a transmit + * interrupt, we've done all we need to do. Everything else is done + * in the transmit interrupt. + */ + if (!SX_INTR(pp) || !SX_IXMIT(pp)) { + ttwwakeup(tp); + splx(s); + DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out X\n")); + return; + } + /* + * We're transmitting. If the clist is empty and we don't have a break + * to send, turn off transmit-ready interrupts, and clear the XMIT + * flag. Mark the tty as no longer busy, in case we haven't done + * that yet. A future call to sxwrite() with more characters will + * start up the process once more. + */ + if (qp->c_cc == 0 && !SX_DOBRK(pp)) { + disable_intr(); +/* sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);*/ + sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); + enable_intr(); + pp->sp_state &= ~SX_SS_XMIT; + tp->t_state &= ~TS_BUSY; + ttwwakeup(tp); + splx(s); + DPRINT((pp, DBG_EXIT|DBG_START, + "sx_start out E st %x sp %x\n", + tp->t_state, pp->sp_state)); + return; + } + disable_intr(); + /* + * If we have a BREAK state-change pending, handle it. If we aren't + * sending a break, start one. If we are, turn it off. + */ + if (SX_DOBRK(pp)) { + count -= 2; /* Account for escape chars in FIFO. */ + if (SX_BREAK(pp)) { /* Doing break, stop it. */ + sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC); + sx_cd1865_out(sc, CD1865_TDR, CD1865_C_EBRK); + sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 0); + pp->sp_state &= ~SX_SS_BREAK; + } + else { /* Start doing break. */ + sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 1); + sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC); + sx_cd1865_out(sc, CD1865_TDR, CD1865_C_SBRK); + pp->sp_state |= SX_SS_BREAK; + } + pp->sp_state &= ~SX_SS_DOBRK; + } + /* + * We've still got data in the clist, fill the channel's FIFO. The + * CD1865 only gives us access to the FIFO during a transmit ready + * request [interrupt] for this channel. + */ + while (qp->c_cc > 0 && count-- >= 0) { + register unsigned char ch, *cp; + int nch; + + ch = (char)getc(qp); + /* + * If we're doing a break we're in ETC mode, so we need to + * double any NULs in the stream. + */ + if (SX_BREAK(pp)) { /* Doing break, in ETC mode. */ + if (ch == '\0') { /* NUL? Double it. */ + sx_cd1865_out(sc, CD1865_TDR, ch); + count--; + } + /* + * Peek the next character; if it's a NUL, we need + * to escape it, but we can't if we're out of FIFO. + * We'll do it on the next pass and leave the FIFO + * incompletely filled. + */ + if (qp->c_cc > 0) { + cp = qp->c_cf; + cp = nextc(qp, cp, &nch); + if (nch == '\0' && count < 1) + count = -1; + } + } + sx_cd1865_out(sc, CD1865_TDR, ch); + } + enable_intr(); + /* + * If we still have data to transmit, mark the tty busy for the + * line discipline. + */ + if (qp->c_cc > 0) + tp->t_state |= TS_BUSY; + else + tp->t_state &= ~TS_BUSY; + /* Wake up sleepers if necessary. */ + ttwwakeup(tp); + splx(s); + DPRINT((pp, DBG_EXIT|DBG_START, + "sx_start out R %d/%d\n", + count, qp->c_cc)); +} + +/* + * Stop output on a line. called at spltty(); + */ +void +sx_stop( + struct tty *tp, + int rw) +{ + struct sx_softc *sc; + struct sx_port *pp; + int s; + + sc = TP2SC(tp); + pp = TP2PP(tp); + DPRINT((TP2PP(tp), DBG_ENTRY|DBG_STOP, "sx_stop(%x,%x)\n", tp, rw)); + + s = spltty(); + /* XXX: must check (rw & FWRITE | FREAD) etc flushing... */ + if (rw & FWRITE) { + disable_intr(); + sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); + sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY); + sx_cd1865_wait_CCR(sc, SX_EI); /* Wait for CCR to go idle. */ + sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_TXDIS); + sx_cd1865_wait_CCR(sc, SX_EI); + enable_intr(); + /* what level are we meant to be flushing anyway? */ + if (tp->t_state & TS_BUSY) { + if ((tp->t_state & TS_TTSTOP) == 0) + tp->t_state |= TS_FLUSH; + tp->t_state &= ~TS_BUSY; + ttwwakeup(tp); + } + } + /* + * Nothing to do for FREAD. + */ + splx(s); +} + +static void +sx_disc_optim( + struct tty *tp, + struct termios *t, + struct sx_port *pp) +{ + /* + * If we're in "raw" mode, we can bypass ttyinput(). + */ + if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && + (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && + (!(t->c_iflag & PARMRK) || + (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && + !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && + linesw[tp->t_line].l_rint == ttyinput) + tp->t_state |= TS_CAN_BYPASS_L_RINT; + else + tp->t_state &= ~TS_CAN_BYPASS_L_RINT; + pp->sp_hotchar = linesw[tp->t_line].l_hotchar; + DPRINT((pp, DBG_OPTIM, "sx_disc_optim: bypass %s, hotchar %x\n", + (tp->t_state & TS_CAN_BYPASS_L_RINT) ? "yes" : "no", + pp->sp_hotchar)); +} + + +#ifdef SX_DEBUG + +void +sx_dprintf( + struct sx_port *pp, + int flags, + const char *fmt, ...) +{ + static char *logbuf = NULL; + static char *linebuf = NULL; + static char *logptr; + char *lbuf; + int n, m; + va_list ap; + + if (logbuf == NULL) { + logbuf = (char *)malloc(1024*1024, M_DEVBUF, M_WAITOK); + linebuf = (char *)malloc(256, M_DEVBUF, M_WAITOK); + logptr = logbuf; + } + lbuf = linebuf; + n = 0; + if ((pp == NULL && (sx_debug&flags)) || + (pp != NULL && ((pp->sp_debug&flags) || (sx_debug&flags)))) { + if (pp != NULL && + pp->sp_tty != NULL && + pp->sp_tty->t_dev != NULL) { + n = snprintf(linebuf, 256, "%cx%d(%d): ", 's', + (int)SX_MINOR2CARD(minor(pp->sp_tty->t_dev)), + (int)SX_MINOR2CHAN(minor(pp->sp_tty->t_dev))); + if (n > 256) + n = 256; + lbuf += n; + } + m = n; + va_start(ap, fmt); + n = vsnprintf(lbuf, 256 - m, fmt, ap); + va_end(ap); + if (n > 256 - m) + n = 256 - m; + n += m; + if (logptr + n + 1 > logbuf + (1024 * 1024)) { + bzero(logptr, logbuf + (1024 * 1024) - logptr); + logptr = logbuf; + } + bcopy(linebuf, logptr, n); + logptr += n; + *logptr = '\0'; + if (sx_debug & DBG_PRINTF) + printf("%s", linebuf); + } +} + +static char * +sx_mctl2str(enum sx_mctl cmd) +{ + switch (cmd) { + case GET: + return("GET"); + case SET: + return("SET"); + case BIS: + return("BIS"); + case BIC: + return("BIC"); + } + return("BAD"); +} + +#endif /* DEBUG */ diff --git a/sys/dev/sx/sx.h b/sys/dev/sx/sx.h new file mode 100644 index 000000000000..5d142a7c4346 --- /dev/null +++ b/sys/dev/sx/sx.h @@ -0,0 +1,210 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + + +/* + * Per-channel soft information structure, stored in the driver. It's called + * "sx_port" just because the si driver calls it "si_port." + * + * This information is mostly visible via ioctl(). + */ +struct sx_port { + int sp_chan; /* Channel number, for convenience. */ + struct tty *sp_tty; + int sp_state; + int sp_active_out; /* callout is open */ + int sp_dtr_wait; /* DTR holddown in hz */ + int sp_delta_overflows; + u_int sp_wopeners; /* Processes waiting for DCD. */ + u_char sp_hotchar; /* ldisc specific ASAP char */ + struct termios sp_iin; /* Initial state. */ + struct termios sp_iout; + struct termios sp_lin; /* Lock state. */ + struct termios sp_lout; +#ifdef SX_DEBUG + int sp_debug; /* debug mask */ +#endif +}; + +/* + * Various important values. + */ +#define SX_NUMCHANS 8 /* Eight channels on an I/O8+. */ +#define SX_PCI_IO_SPACE 8 /* How much address space to use. */ +#define SX_CCR_TIMEOUT 10000 /* Channel Command Register timeout, 10ms. */ +#define SX_GSVR_TIMEOUT 1000 /* GSVR reset timeout, 1ms. */ +#define SX_CD1865_ID 0x10 /* ID of the I/O8+ CD1865 chip. */ +#define SX_EI 0x80 /* "Enable interrupt" flag for I/O8+ commands.*/ + +#define SX_DATA_REG 0 /* Data register. */ +#define SX_ADDR_REG 1 /* Address register. */ + +/* + * The I/O8+ has a 25MHz oscillator on board, but the CD1865 runs at half + * that. + */ +#define SX_CD1865_CLOCK 12500000 /* CD1865 clock on I/O8+. */ +#define SX_CD1865_TICK 4000 /* Timer tick rate, via prescaler. */ +#define SX_CD1865_PRESCALE (SX_CD1865_CLOCK/SX_CD1865_TICK) /* Prescale value.*/ + +#include + +/* + * Device numbering for the sx device. + * + * The minor number is broken up into four fields as follows: + * Field Bits Mask + * --------------------- ---- ---- + * Channel (port) number 0-2 0x07 + * "DTR pin is DTR" flag 3 0x08 + * Unused (zero) 4 0x10 + * Card number 5-6 0x60 + * Callout device flag 7 0x80 + * + * The next 8 bits in the word is the major number, followed by the + * "initial state device" flag and then the "lock state device" flag. + */ +#define SX_CHAN_MASK 0x07 +#define SX_DTRPIN_MASK 0x08 +#define SX_CARD_MASK 0x60 +#define SX_TTY_MASK 0x7f +#define SX_CALLOUT_MASK 0x80 +#define SX_INIT_STATE_MASK 0x10000 +#define SX_LOCK_STATE_MASK 0x20000 +#define SX_STATE_MASK 0x30000 +#define SX_SPECIAL_MASK 0x30000 + +#define SX_CARDSHIFT 5 +#define SX_MINOR2CHAN(m) (m & SX_CHAN_MASK) +#define SX_MINOR2CARD(m) ((m & SX_CARD_MASK) >> SX_CARDSHIFT) +#define SX_MINOR2TTY(m) (m & SX_TTY_MASK) + +#define DEV_IS_CALLOUT(m) (m & SX_CALLOUT_MASK) +#define DEV_IS_STATE(m) (m & SX_STATE_MASK) +#define DEV_IS_SPECIAL(m) (m & SX_SPECIAL_MASK) +#define DEV_DTRPIN(m) (m & SX_DTRPIN_MASK) + +#define MINOR2SC(m) ((struct sx_softc *)devclass_get_softc(sx_devclass,\ + SX_MINOR2CARD(m))) +#define MINOR2PP(m) (MINOR2SC((m))->sc_ports + SX_MINOR2CHAN((m))) +#define MINOR2TP(m) (MINOR2PP((m))->sp_tty) +#define TP2PP(tp) (MINOR2PP(SX_MINOR2TTY(minor((tp)->t_dev)))) +#define TP2SC(tp) (MINOR2SC(minor((tp)->t_dev))) +#define PP2SC(pp) (MINOR2SC(minor((pp)->sp_tty->t_dev))) + +/* Buffer parameters */ +#define SX_BUFFERSIZE CD1865_RFIFOSZ /* Just the size of the receive FIFO. */ +#define SX_I_HIGH_WATER (TTYHOG - 2 * SX_BUFFERSIZE) + +/* + * Precomputed bitrate clock divisors. Formula is + * + * Clock rate (Hz) 12500000 + * divisor = --------------- or ------------ + * 16 * bit rate 16 * bitrate + * + * All values are rounded to the nearest integer. + */ +#define CLK75 0x28b1 /* 10416.666667 */ +#define CLK110 0x1bbe /* 7102.272727 */ +#define CLK150 0x1458 /* 5208.333333 */ +#define CLK300 0x0a2c /* 2604.166667 */ +#define CLK600 0x0516 /* 1302.083333 */ +#define CLK1200 0x028b /* 651.0416667 */ +#define CLK2000 0x0187 /* 390.625 */ +#define CLK2400 0x0146 /* 325.5208333 */ +#define CLK4800 0x00a3 /* 162.7604167 */ +#define CLK7200 0x006d /* 108.5069444 */ +#define CLK9600 0x0051 /* 81.38020833 */ +#define CLK19200 0x0029 /* 40.69010417 */ +#define CLK38400 0x0014 /* 20.34505208 */ +#define CLK57600 0x000e /* 13.56336806 */ +#define CLK115200 0x0007 /* 6.781684028 */ + + +/* sp_state */ +#define SX_SS_CLOSED 0x00000 /* Port is closed. */ +#define SX_SS_OPEN 0x00001 /* Port is open and active. */ +#define SX_SS_XMIT 0x00002 /* We're transmitting data. */ +#define SX_SS_INTR 0x00004 /* We're processing an interrupt. */ +#define SX_SS_CLOSING 0x00008 /* in the middle of an sxclose() */ +#define SX_SS_WAITWRITE 0x00010 +#define SX_SS_BLOCKWRITE 0x00020 +#define SX_SS_DTR_OFF 0x00040 /* DTR held off */ +#define SX_SS_IFLOW 0x00080 /* Input (RTS) flow control on. */ +#define SX_SS_OFLOW 0x00100 /* Output (CTS) flow control on. */ +#define SX_SS_IRCV 0x00200 /* In a receive interrupt. */ +#define SX_SS_IMODEM 0x00400 /* In a modem-signal interrupt. */ +#define SX_SS_IRCVEXC 0x00800 /* In a receive-exception interrupt. */ +#define SX_SS_IXMIT 0x01000 /* In a transmit interrupt. */ +#define SX_SS_OSTOP 0x02000 /* Stopped by output flow control. */ +#define SX_SS_ISTOP 0x04000 /* Stopped by input flow control. */ +#define SX_SS_DTRPIN 0x08000 /* DTR/RTS pin is DTR. */ +#define SX_SS_DOBRK 0x10000 /* Change break status. */ +#define SX_SS_BREAK 0x20000 /* Doing break. */ + +#define SX_DTRPIN(pp) ((pp)->sp_state & SX_SS_DTRPIN) /* DTR/RTS pin is DTR.*/ +#define SX_XMITTING(pp) ((pp)->sp_state & SX_SS_XMIT) /* We're transmitting. */ +#define SX_INTR(pp) ((pp)->sp_state & SX_SS_INTR) /* In an interrupt. */ +#define SX_IXMIT(pp) ((pp)->sp_state & SX_SS_IXMIT) /* Transmit interrupt. */ +#define SX_IFLOW(pp) ((pp)->sp_state & SX_SS_IFLOW) /* Input flow control. */ +#define SX_OFLOW(pp) ((pp)->sp_state & SX_SS_OFLOW) /* Output flow control.*/ +#define SX_IRCV(pp) ((pp)->sp_state & SX_SS_IRCV) /* Receive interrupt. */ +#define SX_IMODEM(pp) ((pp)->sp_state & SX_SS_IMODEM) /* Modem state change.*/ +#define SX_IRCVEXC(pp) ((pp)->sp_state & SX_SS_IRCVEXC) /* Rcv exception. */ +#define SX_OSTOP(pp) ((pp)->sp_state & SX_SS_OSTOP) /* Output stopped. */ +#define SX_ISTOP(pp) ((pp)->sp_state & SX_SS_ISTOP) /* Input stopped. */ +#define SX_DOBRK(pp) ((pp)->sp_state & SX_SS_DOBRK) /* Change break status.*/ +#define SX_BREAK(pp) ((pp)->sp_state & SX_SS_BREAK) /* Doing break. */ + +#define DBG_ENTRY 0x00000001 +#define DBG_DRAIN 0x00000002 +#define DBG_OPEN 0x00000004 +#define DBG_CLOSE 0x00000008 +/* 0x00000010*/ +#define DBG_WRITE 0x00000020 +#define DBG_PARAM 0x00000040 +#define DBG_INTR 0x00000080 +#define DBG_IOCTL 0x00000100 +/* 0x00000200 */ +/* 0x00000400*/ +#define DBG_OPTIM 0x00000800 +#define DBG_START 0x00001000 +#define DBG_EXIT 0x00002000 +#define DBG_FAIL 0x00004000 +#define DBG_STOP 0x00008000 +#define DBG_AUTOBOOT 0x00010000 +#define DBG_MODEM 0x00020000 +#define DBG_MODEM_STATE 0x00040000 +#define DBG_RECEIVE 0x00080000 +#define DBG_POLL 0x00100000 +#define DBG_TRANSMIT 0x00200000 +#define DBG_RECEIVE_EXC 0x00400000 +#define DBG_PRINTF 0x80000000 +#define DBG_ALL 0xffffffff diff --git a/sys/dev/sx/sx_pci.c b/sys/dev/sx/sx_pci.c new file mode 100644 index 000000000000..8f7d31aa9869 --- /dev/null +++ b/sys/dev/sx/sx_pci.c @@ -0,0 +1,166 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +static int +sx_pci_probe( + device_t dev) +{ + const char *desc = NULL; + + switch (pci_get_devid(dev)) { + case 0x200011cb: + if (pci_get_subdevice(dev) == (ushort)0xb008) { + desc = "Specialix I/O8+ Multiport Serial Card"; + } + break; + } + if (desc) { + device_set_desc(dev, desc); + return 0; + } + return ENXIO; +} + +static int +sx_pci_attach(device_t dev) +{ + struct sx_softc *sc; + void *ih; + int error; + + error = 0; + ih = NULL; + sc = device_get_softc(dev); + + sc->sc_io_rid = 0x18; + sc->sc_io_res = bus_alloc_resource(dev, + SYS_RES_IOPORT, + &sc->sc_io_rid, + 0, ~0, 1, + RF_ACTIVE); + if (!sc->sc_io_res) { + device_printf(dev, "can't map I/O\n"); + goto fail; + } + sc->sc_st = rman_get_bustag(sc->sc_io_res); + sc->sc_sh = rman_get_bushandle(sc->sc_io_res); + + /* + * Now that we have the bus handle, we can make certain that this + * is an I/O8+. + */ + if (sx_probe_io8(dev)) { + device_printf(dev, "Oops! Device is not an I/O8+ board!\n"); + goto fail; + } + + /*sc->sc_paddr = (caddr_t)rman_get_start(sc->sc_io_res);*/ + /*sc->sc_maddr = rman_get_virtual(sc->sc_io_res);*/ + + sc->sc_irq_rid = 0; + sc->sc_irq_res = bus_alloc_resource(dev, + SYS_RES_IRQ, + &sc->sc_irq_rid, + 0, ~0, 1, + RF_ACTIVE | RF_SHAREABLE); + if (!sc->sc_irq_res) { + device_printf(dev, "Can't map interrupt\n"); + goto fail; + } + sc->sc_irq = rman_get_start(sc->sc_irq_res); + error = bus_setup_intr(dev, + sc->sc_irq_res, + INTR_TYPE_TTY, + sx_intr, + sc, &ih); + if (error) { + device_printf(dev, "Can't activate interrupt\n"); + goto fail; + } + + error = sxattach(dev); + if (error) + goto fail; + return (0); /* success */ + +fail: + if (error == 0) + error = ENXIO; + if (sc->sc_irq_res) { + if (ih) + bus_teardown_intr(dev, sc->sc_irq_res, ih); + bus_release_resource(dev, + SYS_RES_IRQ, + sc->sc_irq_rid, + sc->sc_irq_res); + sc->sc_irq_res = 0; + } + if (sc->sc_io_res) { + bus_release_resource(dev, + SYS_RES_IOPORT, + sc->sc_io_rid, + sc->sc_io_res); + sc->sc_io_res = 0; + } + return(error); +} + +static device_method_t sx_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, sx_pci_probe), + DEVMETHOD(device_attach, sx_pci_attach), +/* DEVMETHOD(device_detach, sx_pci_detach),*/ + + { 0, 0 } +}; + +static driver_t sx_pci_driver = { + "sx", + sx_pci_methods, + sizeof(struct sx_softc), +}; + +DRIVER_MODULE(sx, pci, sx_pci_driver, sx_devclass, 0, 0); diff --git a/sys/dev/sx/sx_util.c b/sys/dev/sx/sx_util.c new file mode 100644 index 000000000000..d9409254d322 --- /dev/null +++ b/sys/dev/sx/sx_util.c @@ -0,0 +1,260 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + +#include "opt_debug_sx.h" + +/* Utility and support routines for the Specialix I/O8+ driver. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * sx_probe_io8() + * Probe the board to verify that it is a Specialix I/O8+. + * + * Description: + * This is called by sx_pci_attach() (and possibly in the future by + * sx_isa_attach()) to verify that the card we're attaching to is + * indeed a Specialix I/O8+. To do this, we check for the Prescaler + * Period Register of the CD1865 chip and for the Specialix signature + * on the DSR input line of each channel. These lines, along with the + * RTS output lines, are wired down in hardware. + */ +int +sx_probe_io8( + device_t dev) +{ + struct sx_softc *sc; + unsigned char val1, val2; + int i; + + sc = device_get_softc(dev); + /* + * Try to write the Prescaler Period Register, then read it back, + * twice. If this fails, it's not an I/O8+. + */ + sx_cd1865_out(sc, CD1865_PPRL, 0x5a); + DELAY(1); + val1 = sx_cd1865_in(sc, CD1865_PPRL); + + sx_cd1865_out(sc, CD1865_PPRL, 0xa5); + DELAY(1); + val2 = sx_cd1865_in(sc, CD1865_PPRL); + + if ((val1 != 0x5a) || (val2 != 0xa5)) + return(1); + + /* + * Check the lines that Specialix uses as board identification. + * These are the DSR input and the RTS output, which are wired + * down. + */ + val1 = 0; + for (i = 0; i < 8; i++) { + sx_cd1865_out(sc, CD1865_CAR, i); /* Select channel. */ + if (sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_DSR) /* Set? */ + val1 |= 1 << i; /* OR it in. */ + } +#ifdef notdef + val2 = 0; + for (i = 0; i < 8; i++) { + sx_cd1865_out(sc, CD1865_CAR, i); /* Select channel. */ + if (sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_RTS) /* Set? */ + val2 |= 1 << i; /* OR it in. */ + } + /* + * They managed to switch the bit order between the docs and + * the IO8+ card. The new PCI card now conforms to old docs. + * They changed the PCI docs to reflect the situation on the + * old card. + */ + val2 = (bp->flags & SX_BOARD_IS_PCI) ? 0x4d : 0xb2; +#endif /* notdef */ + if (val1 != 0x4d) { + if (bootverbose) + device_printf(dev, + "Specialix I/O8+ ID 0x4d not found (0x%02x).\n", + val1); + return(1); + } + return(0); /* Probed successfully. */ +} + +/* + * sx_init_CD1865() + * Hard-reset and initialize the I/O8+ CD1865 processor. + * + * Description: + * This routine does a hard reset of the CD1865 chip and waits for it + * to complete. (The reset should complete after 500us; we wait 1ms + * and fail if we time out.) We then initialize the CD1865 processor. + */ +int +sx_init_cd1865( + struct sx_softc *sc, + int unit) +{ + int s; + unsigned int to; + + s = spltty(); + disable_intr(); + sx_cd1865_out(sc, CD1865_GSVR, 0x00); /* Clear the GSVR. */ + sx_cd1865_wait_CCR(sc, 0); /* Wait for the CCR to clear. */ + sx_cd1865_out(sc, CD1865_CCR, CD1865_CCR_HARDRESET); /* Reset CD1865. */ + enable_intr(); + to = SX_GSVR_TIMEOUT/5; + while (to-- > 0) { + if (sx_cd1865_in(sc, CD1865_GSVR) == 0xff) + break; + DELAY(5); + } + if (to == 0) { + splx(s); + printf("sx%d: Timeout waiting for reset.\n", unit); + return(EIO); + } + /* + * The high five bits of the Global Interrupt Vector Register is + * used to identify daisy-chained CD1865 chips. The I/O8+ isn't + * daisy chained, but we have to initialize the field anyway. + */ + sx_cd1865_out(sc, CD1865_GIVR, SX_CD1865_ID); + /* Clear the Global Interrupting Channel register. */ + sx_cd1865_out(sc, CD1865_GICR, 0); + /* + * Set the Service Match Registers to the appropriate values. See + * the cd1865.h include file for more information. + */ + sx_cd1865_out(sc, CD1865_MSMR, CD1865_ACK_MINT); /* Modem. */ + sx_cd1865_out(sc, CD1865_TSMR, CD1865_ACK_TINT); /* Transmit. */ + sx_cd1865_out(sc, CD1865_RSMR, CD1865_ACK_RINT); /* Receive. */ + /* + * Set RegAckEn in the Service Request Configuration Register; + * we'll be acknowledging service requests in software, not + * hardware. + */ + sx_cd1865_bis(sc, CD1865_SRCR, CD1865_SRCR_REGACKEN); + /* + * Set the CD1865 timer tick rate. The value here is the processor + * clock rate (in MHz) divided by the rate in ticks per second. See + * commentary in sx.h. + */ + sx_cd1865_out(sc, CD1865_PPRH, SX_CD1865_PRESCALE >> 8); + sx_cd1865_out(sc, CD1865_PPRL, SX_CD1865_PRESCALE & 0xff); + + splx(s); + return(0); +} + +#ifdef notyet +/* + * Set the IRQ using the RTS lines that run to the PAL on the board.... + * + * This is a placeholder for ISA support, if that's ever implemented. This + * should _only_ be called from sx_isa_attach(). + */ +int +sx_set_irq( + struct sx_softc *sc, + int unit, + int irq) +{ + register int virq; + register int i, j; + + switch (irq) { + /* In the same order as in the docs... */ + case 15: + virq = 0; + break; + case 12: + virq = 1; + break; + case 11: + virq = 2; + break; + case 9: + virq = 3; + break; + default: + printf("sx%d: Illegal irq %d.\n", unit, irq); + return(0); + } + for (i = 0; i < 2; i++) { + sx_cd1865_out(sc, CD1865_CAR, i); /* Select channel. */ + j = ((virq >> i) & 0x1) ? MSVR_RTS : 0; + sx_cd1865_out(sc, CD1865_MSVRTS, j); + } + return(1); +} + +#endif /* notyet */ + +/* + * sx_int_port() + * Determine the port that interrupted us. + * + * Description: + * This routine checks the Global Interrupting Channel Register (GICR) + * to find the port that caused an interrupt. It returns a pointer to + * the sx_port structure of the interrupting port, or NULL if there was + * none. + * + * XXX - check type/validity of interrupt? + */ +struct sx_port * +sx_int_port( + struct sx_softc *sc, + int unit) +{ + unsigned char chan; + struct sx_port *pp; + + chan = (sx_cd1865_in(sc, CD1865_GSCR2|SX_EI) & CD1865_GICR_CHAN_MASK) + >> CD1865_GICR_CHAN_SHIFT; + DPRINT((NULL, DBG_INTR, "Intr chan %d\n", chan)); + if (chan < CD1865_NUMCHAN) { + pp = sc->sc_ports + (int)chan; + return(pp); + } + printf("sx%d: False interrupt on port %d.\n", unit, chan); + return(NULL); +} diff --git a/sys/dev/sx/sx_util.h b/sys/dev/sx/sx_util.h new file mode 100644 index 000000000000..582e73be1bb1 --- /dev/null +++ b/sys/dev/sx/sx_util.h @@ -0,0 +1,159 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + + +/* Utility functions and macros for the Specialix I/O8+ driver. */ + +/* + * sx_cd1865_out() + * Write a CD1865 register on the card. + */ +static __inline void +sx_cd1865_out( + struct sx_softc *sc, + unsigned int reg, + unsigned char val) +{ + bus_space_write_1(sc->sc_st, sc->sc_sh, SX_ADDR_REG, reg); + bus_space_write_1(sc->sc_st, sc->sc_sh, SX_DATA_REG, val); +} + +/* + * sx_cd1865_in() + * Read a register from the card. + */ +static __inline unsigned char +sx_cd1865_in( + struct sx_softc *sc, + unsigned int reg) +{ + bus_space_write_1(sc->sc_st, sc->sc_sh, SX_ADDR_REG, reg); + return(bus_space_read_1(sc->sc_st, sc->sc_sh, SX_DATA_REG)); +} + +/* + * sx_cd1865_bis() + * Set bits in a CD1865 register. + */ +static __inline void +sx_cd1865_bis( + struct sx_softc *sc, + unsigned int reg, + unsigned char bits) +{ + register unsigned char rval; + + rval = sx_cd1865_in(sc, reg); + rval |= bits; + sx_cd1865_out(sc, reg, rval); +} + +/* + * sx_cd1865_bic() + * Clear bits in a CD1865 register. + */ +static __inline void +sx_cd1865_bic( + struct sx_softc *sc, + unsigned int reg, + unsigned char bits) +{ + register unsigned char rval; + + rval = sx_cd1865_in(sc, reg); + rval &= ~bits; + sx_cd1865_out(sc, reg, rval); +} + +/* + * sx_cd1865_wait_CCR() + * Spin waiting for the board Channel Command Register to clear. + * + * Description: + * The CD1865 processor clears the Channel Command Register to + * indicate that it has completed the last command. This routine + * waits for the CCR to become zero by watching the register, + * delaying ten microseconds between each check. We time out after + * ten milliseconds (or SX_CCR_TIMEOUT microseconds). + */ +static __inline void +sx_cd1865_wait_CCR( + struct sx_softc *sc, + unsigned int ei_flag) +{ + unsigned int to = SX_CCR_TIMEOUT/10; + + while (to-- > 0) { + if (sx_cd1865_in(sc, CD1865_CCR|ei_flag) == 0) + return; + DELAY(10); + } + printf("sx: Timeout waiting for CCR to clear.\n"); +} + +/* + * sx_cd1865_etcmode() + * Set or clear embedded transmit command mode on a CD1865 port. + * + * Description: + * We can use commands embedded in the transmit data stream to do + * things like start and stop breaks or insert time delays. We normally + * run with embedded commands disabled; this routine selects the channel + * we're dealing with and enables or disables embedded commands depending + * on the flag passed to it. The caller must remember this state and + * escape any NULs it sends while embedded commands are enabled. + * Should be called at spltty(). Disables interrupts for the duration + * of the routine. + */ +static __inline void +sx_cd1865_etcmode( + struct sx_softc *sc, + unsigned int ei_flag, + int chan, + int mode) +{ + sx_cd1865_out(sc, CD1865_CAR|ei_flag, chan); /* Select channel. */ + if (mode) { /* Enable embedded commands? */ + sx_cd1865_bis(sc, CD1865_COR2|ei_flag, CD1865_COR2_ETC); + } + else { + sx_cd1865_bic(sc, CD1865_COR2|ei_flag, CD1865_COR2_ETC); + } + /* + * Wait for the CCR to clear, ding the card, let it know stuff + * changed, then wait for CCR to clear again. + */ + sx_cd1865_wait_CCR(sc, ei_flag); + sx_cd1865_out(sc, CD1865_CCR|ei_flag, CD1865_CCR_CORCHG2); + sx_cd1865_wait_CCR(sc, ei_flag); +} + +int sx_probe_io8(device_t dev); +int sx_init_cd1865(struct sx_softc *sc, int unit); +struct sx_port *sx_int_port(struct sx_softc *sc, int unit); diff --git a/sys/dev/sx/sxvar.h b/sys/dev/sx/sxvar.h new file mode 100644 index 000000000000..7eea73337abe --- /dev/null +++ b/sys/dev/sx/sxvar.h @@ -0,0 +1,83 @@ +/* + * Device driver for Specialix I/O8+ multiport serial card. + * + * Copyright 2003 Frank Mayhar + * + * Derived from the "si" driver by Peter Wemm , using + * lots of information from the Linux "specialix" driver by Roger Wolff + * and from the Intel CD1865 "Intelligent Eight- + * Channel Communications Controller" datasheet. Roger was also nice + * enough to answer numerous questions about stuff specific to the I/O8+ + * not covered by the CD1865 datasheet. + * + * 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 + * notices, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notices, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE. + * + * $FreeBSD$ + */ + + +int sxattach(device_t dev); +void sx_intr(void *); + +extern devclass_t sx_devclass; + +struct sx_softc { + struct sx_port *sc_ports; /* port structures for this card */ + int sc_irq; /* copy of attach irq */ + struct resource *sc_io_res; + struct resource *sc_irq_res; + bus_space_tag_t sc_st; + bus_space_handle_t sc_sh; + int sc_io_rid; + int sc_irq_rid; + int sc_unit; +}; + +#ifdef SX_DEBUG +/* + * debugging stuff + */ +void sx_dprintf(struct sx_port *pp, int flags, const char *fmt, ...); + +#define DPRINT(x) sx_dprintf x + +#define DBG_ENTRY 0x00000001 +#define DBG_DRAIN 0x00000002 +#define DBG_OPEN 0x00000004 +#define DBG_CLOSE 0x00000008 +#define DBG_READ 0x00000010 +#define DBG_WRITE 0x00000020 +#define DBG_PARAM 0x00000040 +#define DBG_INTR 0x00000080 +#define DBG_IOCTL 0x00000100 +/* 0x00000200 */ +#define DBG_SELECT 0x00000400 +#define DBG_OPTIM 0x00000800 +#define DBG_START 0x00001000 +#define DBG_EXIT 0x00002000 +#define DBG_FAIL 0x00004000 +#define DBG_STOP 0x00008000 +#define DBG_AUTOBOOT 0x00010000 +#define DBG_MODEM 0x00020000 +#define DBG_DOWNLOAD 0x00040000 +/* 0x00080000*/ +#define DBG_POLL 0x00100000 +#define DBG_ALL 0xffffffff + +#else + +#define DPRINT(x) /* void */ + +#endif