summaryrefslogtreecommitdiffstats
path: root/sys/dev/sx
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2004-04-11 19:32:20 +0000
committerimp <imp@FreeBSD.org>2004-04-11 19:32:20 +0000
commita0e860ef19779054d5ad9eb746f068206b1e9a39 (patch)
tree92a64c36715360db424986d3b585c53a6025c391 /sys/dev/sx
parent3f9b8e99a0f8988f51e816f2970682f0854def1d (diff)
downloadFreeBSD-src-a0e860ef19779054d5ad9eb746f068206b1e9a39.zip
FreeBSD-src-a0e860ef19779054d5ad9eb746f068206b1e9a39.tar.gz
Frank Mayhar's <frank@exit.com> sx driver for older Specialix
I/O8+ and I/O4+ intelligent serial controllers. si is for completely different hardware, also made by Specialix.
Diffstat (limited to 'sys/dev/sx')
-rw-r--r--sys/dev/sx/cd1865.h313
-rw-r--r--sys/dev/sx/sx.c2039
-rw-r--r--sys/dev/sx/sx.h210
-rw-r--r--sys/dev/sx/sx_pci.c166
-rw-r--r--sys/dev/sx/sx_util.c260
-rw-r--r--sys/dev/sx/sx_util.h159
-rw-r--r--sys/dev/sx/sxvar.h83
7 files changed, 3230 insertions, 0 deletions
diff --git a/sys/dev/sx/cd1865.h b/sys/dev/sx/cd1865.h
new file mode 100644
index 0000000..5056dea
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 0000000..e88d745
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 <sys/param.h>
+#include <sys/systm.h>
+#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
+#include <sys/ioctl_compat.h>
+#endif
+#include <sys/tty.h>
+#include <sys/proc.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/dkstat.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+#include <sys/bus.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <machine/resource.h>
+
+#include <machine/clock.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/stdarg.h>
+
+#include <dev/sx/cd1865.h>
+#include <dev/sx/sxvar.h>
+#include <dev/sx/sx.h>
+#include <dev/sx/sx_util.h>
+
+#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 0000000..5d142a7
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 <sys/callout.h>
+
+/*
+ * 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 0000000..8f7d31a
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/tty.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <machine/clock.h>
+#include <sys/rman.h>
+
+#include <dev/sx/cd1865.h>
+#include <dev/sx/sxvar.h>
+#include <dev/sx/sx.h>
+#include <dev/sx/sx_util.h>
+
+#include <pci/pcivar.h>
+
+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 0000000..d940925
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/tty.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <machine/clock.h>
+#include <sys/rman.h>
+
+#include <dev/sx/cd1865.h>
+#include <dev/sx/sxvar.h>
+#include <dev/sx/sx.h>
+#include <dev/sx/sx_util.h>
+
+/*
+ * 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 0000000..582e73b
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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 0000000..7eea733
--- /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 <frank@exit.com>
+ *
+ * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
+ * lots of information from the Linux "specialix" driver by Roger Wolff
+ * <R.E.Wolff@BitWizard.nl> 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
OpenPOWER on IntegriCloud