patch-pre2.0.5 linux/drivers/char/baycom.c

Next file: linux/drivers/char/console.c
Previous file: linux/drivers/char/apm_bios.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file pre2.0.4/linux/drivers/char/baycom.c linux/drivers/char/baycom.c
@@ -0,0 +1,2168 @@
+/*****************************************************************************/
+
+/*
+ *	baycom.c  -- baycom ser12 and par96 radio modem driver.
+ *
+ *	Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  Please note that the GPL allows you to use the driver, NOT the radio.
+ *  In order to use the radio, you need a license from the communications
+ *  authority of your country.
+ *
+ *
+ *  Supported modems
+ *
+ *  ser12: This is a very simple 1200 baud AFSK modem. The modem consists only
+ *         of a modulator/demodulator chip, usually a TI TCM3105. The computer
+ *         is responsible for regenerating the receiver bit clock, as well as
+ *         for handling the HDLC protocol. The modem connects to a serial port,
+ *         hence the name. Since the serial port is not used as an async serial
+ *         port, the kernel driver for serial ports cannot be used, and this
+ *         driver only supports standard serial hardware (8250, 16450, 16550)
+ *  
+ *  par96: This is a modem for 9600 baud FSK compatible to the G3RUH standard.
+ *         The modem does all the filtering and regenerates the receiver clock.
+ *         Data is transferred from and to the PC via a shift register.
+ *         The shift register is filled with 16 bits and an interrupt is
+ *         signalled. The PC then empties the shift register in a burst. This
+ *         modem connects to the parallel port, hence the name. The modem
+ *         leaves the implementation of the HDLC protocol and the scrambler
+ *         polynomial to the PC.
+ *  
+ *  par97: This is a redesign of the par96 modem by Henning Rech, DF9IC. The
+ *         modem is protocol compatible to par96, but uses only three low
+ *         power ICs and can therefore be fed from the parallel port and
+ *         does not require an additional power supply.
+ *
+ *
+ *  Command line options (insmod command line)
+ * 
+ *  major    major number the driver should use; default 60 
+ *  modem    modem type of the first channel (minor 0); 1=ser12,
+ *           2=par96/par97, any other value invalid
+ *  iobase   base address of the port; common values are for ser12 0x3f8,
+ *           0x2f8, 0x3e8, 0x2e8 and for par96/par97 0x378, 0x278, 0x3bc
+ *  irq      interrupt line of the port; common values are for ser12 3,4
+ *           and for par96/par97 7
+ *  options  0=use hardware DCD, 1=use software DCD
+ * 
+ *
+ *  History:
+ *   0.1  03.05.96  Renamed from ser12 0.5 and added support for par96
+ *                  Various resource allocation cleanups
+ *   0.2  12.05.96  Changed major to allocated 51. Integrated into kernel
+ *                  source tree
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/version.h>
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <asm/segment.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+
+#include <linux/baycom.h>
+
+/* --------------------------------------------------------------------- */
+
+#define BAYCOM_TYPE_NORMAL 0		/* not used */
+#define TTY_DRIVER_TYPE_BAYCOM 6
+
+/*
+ * ser12 options:
+ * BAYCOM_OPTIONS_SOFTDCD: if undefined, you must use the transmitters
+ * hardware carrier detect circuitry, the driver will report DCD as soon as
+ * there are transitions on the input line. Advantage: lower interrupt load
+ * on the system. Disadvantage: slower, since hardware carrier detect
+ * circuitry is usually slow.
+ */
+
+#define BUFLEN_RX 16384
+#define BUFLEN_TX 16384
+
+#define NR_PORTS 4
+
+#define KISS_VERBOSE
+#undef HDLC_LOOPBACK
+
+#define BAYCOM_MAGIC 0x3105bac0
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * user settable parameters (from the command line)
+ */
+#ifndef MODULE
+static
+#endif /* MODULE */
+int major = BAYCOM_MAJOR;
+
+/* --------------------------------------------------------------------- */
+
+static struct tty_struct *baycom_table[NR_PORTS];
+static struct termios *baycom_termios[NR_PORTS];
+static struct termios *baycom_termios_locked[NR_PORTS];
+
+static int baycom_refcount;
+
+static struct tty_driver baycom_driver;
+
+static struct {
+	int modem, iobase, irq, options;
+} baycom_ports[NR_PORTS] = { { BAYCOM_MODEM_INVALID, 0, 0, 0, }, };
+
+/* --------------------------------------------------------------------- */
+
+#define RBR(iobase) (iobase+0)
+#define THR(iobase) (iobase+0)
+#define IER(iobase) (iobase+1)
+#define IIR(iobase) (iobase+2)
+#define FCR(iobase) (iobase+2)
+#define LCR(iobase) (iobase+3)
+#define MCR(iobase) (iobase+4)
+#define LSR(iobase) (iobase+5)
+#define MSR(iobase) (iobase+6)
+#define SCR(iobase) (iobase+7)
+#define DLL(iobase) (iobase+0)
+#define DLM(iobase) (iobase+1)
+
+#define SER12_EXTENT 8
+
+#define LPT_DATA(iobase)    (iobase+0)
+#define LPT_STATUS(iobase)  (iobase+1)
+#define LPT_CONTROL(iobase) (iobase+2)
+#define LPT_IRQ_ENABLE      0x10
+#define PAR96_BURSTBITS 16
+#define PAR96_BURST     4
+#define PAR96_PTT       2
+#define PAR96_TXBIT     1
+#define PAR96_ACK       0x40
+#define PAR96_RXBIT     0x20
+#define PAR96_DCD       0x10
+#define PAR97_POWER     0xf8
+
+#define PAR96_EXTENT 3
+
+/* ---------------------------------------------------------------------- */
+
+struct access_params {
+	int tx_delay;
+	int tx_tail;
+	int slottime;
+	int ppersist;
+	int fulldup;
+};
+
+struct hdlc_state_rx {
+	int rx_state;	/* 0 = sync hunt, != 0 receiving */
+	unsigned char lastbit;
+	unsigned int bitstream;
+	unsigned int assembly;
+	
+	int len;
+	unsigned char *bp;
+	unsigned char buffer[BAYCOM_MAXFLEN+2];	   /* make room for CRC */
+};
+
+struct hdlc_state_tx {
+	/*
+	 * 0 = send flags
+	 * 1 = send txtail (flags)
+	 * 2 = send packet
+	 */
+	int tx_state;	
+	int numflags;
+	unsigned int bitstream;
+	unsigned int current_byte;
+	unsigned char ptt;
+
+	int len;
+	unsigned char *bp;
+	unsigned char buffer[BAYCOM_MAXFLEN+2];		/* make room for CRC */
+};
+
+struct modem_state_ser12 {
+	unsigned char last_sample;
+	unsigned char interm_sample;
+	unsigned int bit_pll;
+	unsigned int dcd_shreg;
+	int dcd_sum0, dcd_sum1, dcd_sum2;
+	unsigned int dcd_time;
+	unsigned char last_rxbit;
+	unsigned char tx_bit;
+};
+
+struct modem_state_par96 {
+	int dcd_count;
+	unsigned int dcd_shreg;
+	unsigned long descram;
+	unsigned long scram;
+	unsigned int tx_bits;
+};
+
+struct modem_state {
+	unsigned char dcd;
+	short arb_divider;
+	struct modem_state_ser12 ser12;
+	struct modem_state_par96 par96;
+};
+
+struct packet_buffer {
+	unsigned int rd;
+	unsigned int wr;
+	
+	unsigned int buflen;
+	unsigned char *buffer;
+};
+
+struct packet_hdr {
+	unsigned int next;
+	unsigned int len;
+	/* packet following */
+};
+
+#ifdef BAYCOM_DEBUG
+struct bit_buffer {
+	unsigned int rd;
+	unsigned int wr;
+	unsigned int shreg;
+	unsigned char buffer[64];
+};
+
+struct debug_vals {
+	unsigned long last_jiffies;
+	unsigned cur_intcnt;
+	unsigned last_intcnt;
+	int cur_pllcorr;
+	int last_pllcorr;
+};
+#endif /* BAYCOM_DEBUG */
+
+struct kiss_decode {
+	unsigned char dec_state; /* 0 = hunt FEND */
+	unsigned char escaped;
+	unsigned char pkt_buf[BAYCOM_MAXFLEN+1];
+	unsigned int wr;
+};
+
+/* ---------------------------------------------------------------------- */
+
+struct baycom_state {
+	int magic;
+
+	unsigned char modem_type;
+
+	unsigned int iobase;
+	unsigned int irq;
+	unsigned int options;
+
+	int opened;
+	struct tty_struct *tty;
+
+	struct packet_buffer rx_buf;
+	struct packet_buffer tx_buf;
+
+	struct access_params ch_params;
+
+	struct hdlc_state_rx hdlc_rx;
+	struct hdlc_state_tx hdlc_tx;
+
+	int calibrate;
+
+	struct modem_state modem;
+
+#ifdef BAYCOM_DEBUG
+	struct bit_buffer bitbuf_channel;
+	struct bit_buffer bitbuf_hdlc;
+	
+	struct debug_vals debug_vals;
+#endif /* BAYCOM_DEBUG */
+
+	struct kiss_decode kiss_decode;
+
+	struct baycom_statistics stat;
+};
+
+/* --------------------------------------------------------------------- */
+
+struct baycom_state baycom_state[NR_PORTS];
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * the CRC routines are stolen from WAMPES
+ * by Dieter Deyke
+ */
+
+static const unsigned short crc_ccitt_table[] = {
+	0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+	0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+	0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+	0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+	0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+	0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+	0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+	0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+	0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+	0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+	0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+	0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+	0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+	0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+	0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+	0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+	0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+	0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+	0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+	0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+	0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+	0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+	0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+	0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+	0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+	0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+	0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+	0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+	0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+	0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+	0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+	0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+/*---------------------------------------------------------------------------*/
+
+static inline void append_crc_ccitt(unsigned char *buffer, int len)
+{
+ 	unsigned int crc = 0xffff;
+
+	for (;len>0;len--)
+		crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buffer++) & 0xff];
+	crc ^= 0xffff;
+	*buffer++ = crc;
+	*buffer++ = crc >> 8;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static inline int check_crc_ccitt(const unsigned char *buf,int cnt)
+{
+	unsigned int crc = 0xffff;
+
+	for (; cnt > 0; cnt--)
+		crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff];
+	return (crc & 0xffff) == 0xf0b8;
+}
+
+/*---------------------------------------------------------------------------*/
+
+#if 0
+static int calc_crc_ccitt(const unsigned char *buf,int cnt)
+{
+	unsigned int crc = 0xffff;
+
+	for (; cnt > 0; cnt--)
+		crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff];
+	crc ^= 0xffff;
+	return (crc & 0xffff);
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static int store_packet(struct packet_buffer *buf, unsigned char *data,
+	char from_user, unsigned int len)
+{
+	unsigned int free;
+	struct packet_hdr *hdr;
+	unsigned int needed = sizeof(struct packet_hdr)+len;
+	
+	free = buf->rd-buf->wr;
+	if(buf->rd <= buf->wr) {
+		free = buf->buflen - buf->wr;
+		if((free < needed) && (buf->rd >= needed)) {
+			hdr = (struct packet_hdr *)(buf->buffer+buf->wr);
+			hdr->next = 0;
+			hdr->len = 0;
+			buf->wr = 0;
+			free = buf->rd;
+		}
+	}
+	if(free < needed) return 0;		/* buffer overrun */
+	hdr = (struct packet_hdr *)(buf->buffer+buf->wr);
+	if (from_user) 
+		memcpy_fromfs(hdr+1,data,len);
+	else
+		memcpy(hdr+1,data,len);
+	hdr->len = len;
+	hdr->next = buf->wr+needed;
+	if (hdr->next + sizeof(struct packet_hdr) >= buf->buflen)
+		hdr->next = 0;
+	buf->wr = hdr->next;
+	return 1;
+}
+	
+/* ---------------------------------------------------------------------- */
+
+static void get_packet(struct packet_buffer *buf, unsigned char **data,
+	unsigned int *len)
+{
+	struct packet_hdr *hdr;
+	
+	*data = NULL;
+	*len = 0;
+	if (buf->rd == buf->wr)
+		return;
+	hdr = (struct packet_hdr *)(buf->buffer+buf->rd);
+	while (!(hdr->len)) {
+		buf->rd = hdr->next;
+		if (buf->rd == buf->wr)
+			return;
+		hdr = (struct packet_hdr *)(buf->buffer+buf->rd);
+	}
+	*data = (unsigned char *)(hdr+1);
+	*len = hdr->len;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void ack_packet(struct packet_buffer *buf)
+{
+	struct packet_hdr *hdr;
+	
+	if (buf->rd == buf->wr)
+		return;
+	hdr = (struct packet_hdr *)(buf->buffer+buf->rd);
+	buf->rd = hdr->next;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int store_kiss_packet(struct packet_buffer *buf, unsigned char *data,
+	unsigned int len)
+{
+	unsigned char *bp = data;
+	int ln = len;
+	/*
+	 * variables of buf
+	 */
+	unsigned int rd;
+	unsigned int wr;
+	unsigned int buflen;
+	unsigned char *buffer;
+
+	if (!len || !data || !buf)
+		return 0;
+	buflen = buf->buflen;
+	rd = buf->rd;
+	wr = buf->wr;
+	buffer = buf->buffer;
+	
+#define ADD_CHAR(c) {\
+		buffer[wr++] = c;\
+		if (wr >= buflen) wr = 0;\
+		if (wr == rd) return 0;\
+	}
+#define ADD_KISSCHAR(c) {\
+		if (((c) & 0xff) == KISS_FEND) {\
+			ADD_CHAR(KISS_FESC);\
+			ADD_CHAR(KISS_TFEND);\
+		} else if (((c) & 0xff) == KISS_FESC) {\
+			ADD_CHAR(KISS_FESC);\
+			ADD_CHAR(KISS_TFESC);\
+		} else {\
+			ADD_CHAR(c);\
+		}\
+	}
+
+	ADD_CHAR(KISS_FEND);
+	ADD_KISSCHAR(KISS_CMD_DATA);
+	for(; ln > 0; ln--,bp++) {
+		ADD_KISSCHAR(*bp);
+	}
+	ADD_CHAR(KISS_FEND);
+	buf->wr = wr;
+#undef ADD_CHAR
+#undef ADD_KISSCHAR
+	return 1;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef BAYCOM_DEBUG
+static inline void add_bitbuffer(struct bit_buffer * buf, unsigned int bit)
+{
+	if (!buf) return;
+	buf->shreg <<= 1;
+	if (bit)
+		buf->shreg |= 1;
+	if (buf->shreg & 0x100) {
+		buf->buffer[buf->wr] = buf->shreg;
+		buf->wr = (buf->wr+1) % sizeof(buf->buffer);
+		buf->shreg = 1;
+	}
+}
+#endif /* BAYCOM_DEBUG */
+
+/* ---------------------------------------------------------------------- */
+
+static inline unsigned int tenms_to_flags(struct baycom_state *bc, 
+					  unsigned int tenms)
+{
+	switch (bc->modem_type) {
+	case BAYCOM_MODEM_SER12:
+		return tenms * 12 / 8;
+	case BAYCOM_MODEM_PAR96:
+		return tenms * 12;
+	default:
+		return 0;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * The HDLC routines could be more efficient; they could take more than
+ * one bit per call
+ */
+
+static void hdlc_rx_bit(struct baycom_state *bc, unsigned int bit)
+{
+	if (!bc) return;
+
+	bc->hdlc_rx.bitstream <<= 1;
+	if (bit)
+		bc->hdlc_rx.bitstream |= 1;
+#ifdef BAYCOM_DEBUG
+	add_bitbuffer(&bc->bitbuf_hdlc, bc->hdlc_rx.bitstream & 1);
+#endif /* BAYCOM_DEBUG */
+	if(bc->hdlc_rx.rx_state) {
+		if ((bc->hdlc_rx.bitstream & 0x3f) != 0x3e) {
+			/* not a stuffed bit */
+			if (bc->hdlc_rx.bitstream & 1)
+				bc->hdlc_rx.assembly |= 0x100;
+			if (bc->hdlc_rx.assembly & 1) {
+				/* store byte */
+				if (bc->hdlc_rx.len >= sizeof(bc->hdlc_rx.buffer)) {
+					bc->hdlc_rx.rx_state = 0;
+				} else {
+					*bc->hdlc_rx.bp++ = bc->hdlc_rx.assembly>>1;
+					bc->hdlc_rx.len++;
+					bc->hdlc_rx.assembly = 0x80;
+				}
+			} else {
+				bc->hdlc_rx.assembly >>= 1;
+			}
+		}
+		if ((bc->hdlc_rx.bitstream & 0x7f) == 0x7e) {
+			if (bc->hdlc_rx.len >= 4) {
+				if (check_crc_ccitt(bc->hdlc_rx.buffer,bc->hdlc_rx.len)) {
+					bc->stat.rx_packets++;
+					if (!store_kiss_packet(&bc->rx_buf,bc->hdlc_rx.buffer,bc->hdlc_rx.len-2))
+						bc->stat.rx_bufferoverrun++;
+				}
+			}
+			bc->hdlc_rx.len = 0;
+			bc->hdlc_rx.bp = bc->hdlc_rx.buffer;
+			bc->hdlc_rx.assembly = 0x80;
+		}
+		if ((bc->hdlc_rx.bitstream & 0x7f) == 0x7f)
+			bc->hdlc_rx.rx_state = 0;
+	} else {
+		if ((bc->hdlc_rx.bitstream & 0x7f) == 0x7e) {
+			bc->hdlc_rx.len = 0;
+			bc->hdlc_rx.bp = bc->hdlc_rx.buffer;
+			bc->hdlc_rx.assembly = 0x80;
+			bc->hdlc_rx.rx_state = 1;
+		}
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+static unsigned char hdlc_tx_bit(struct baycom_state *bc)
+{
+	unsigned char bit;
+
+	if (!bc || !bc->hdlc_tx.ptt)
+		return 0;
+	for(;;) {
+		switch (bc->hdlc_tx.tx_state) {
+		default:
+			bc->hdlc_tx.ptt = 0;
+			bc->hdlc_tx.tx_state = 0;
+			return 0;
+		case 0:
+		case 1:
+			if (bc->hdlc_tx.current_byte > 1) {
+				/*
+				 * return bit 
+				 */
+				bit = bc->hdlc_tx.current_byte & 1;
+				bc->hdlc_tx.current_byte >>= 1;
+				return bit;
+			}
+			/*
+			 * get new bit 
+			 */
+			if (bc->hdlc_tx.numflags) {
+				bc->hdlc_tx.numflags--;
+				bc->hdlc_tx.current_byte = 0x17e;
+			} else {
+				if (bc->hdlc_tx.tx_state == 1) {
+					bc->hdlc_tx.ptt = 0;
+					return 0;
+				}
+				get_packet(&bc->tx_buf, &bc->hdlc_tx.bp,
+					   &bc->hdlc_tx.len);
+				if (!bc->hdlc_tx.bp || !bc->hdlc_tx.len) {
+					bc->hdlc_tx.tx_state = 1;
+					bc->hdlc_tx.current_byte = 0;
+					bc->hdlc_tx.numflags = tenms_to_flags
+						(bc, bc->ch_params.tx_tail);
+				} else if (bc->hdlc_tx.len >= BAYCOM_MAXFLEN) {
+					bc->hdlc_tx.tx_state = 0;
+					bc->hdlc_tx.current_byte = 0;
+					bc->hdlc_tx.numflags = 1;
+					ack_packet(&bc->tx_buf);
+				} else {
+					memcpy(bc->hdlc_tx.buffer,
+					       bc->hdlc_tx.bp, 
+					       bc->hdlc_tx.len);
+					ack_packet(&bc->tx_buf);
+					bc->hdlc_tx.bp = bc->hdlc_tx.buffer;
+					append_crc_ccitt(bc->hdlc_tx.buffer,
+							 bc->hdlc_tx.len);
+					/* the appended CRC */
+					bc->hdlc_tx.len += 2; 
+					bc->hdlc_tx.tx_state = 2;
+					bc->hdlc_tx.current_byte = 0;
+					bc->hdlc_tx.bitstream = 0;
+					bc->stat.tx_packets++;
+				}
+			}
+			break;
+		case 2:
+			if ((bc->hdlc_tx.bitstream & 0x1f) == 0x1f) {
+				/*
+				 * bit stuffing
+				 */
+				bc->hdlc_tx.bitstream <<= 1;
+				return 0;
+			}
+			if (bc->hdlc_tx.current_byte > 1) {
+				/*
+				 * return bit 
+				 */
+				bc->hdlc_tx.bitstream <<= 1;
+				bit = bc->hdlc_tx.current_byte & 1;
+				bc->hdlc_tx.bitstream |= bit;
+				bc->hdlc_tx.current_byte >>= 1;
+				return bit;
+			}
+			if (!bc->hdlc_tx.len) {
+				bc->hdlc_tx.tx_state = 0;
+				bc->hdlc_tx.current_byte = 0;
+				bc->hdlc_tx.numflags = 1;
+			} else {
+				bc->hdlc_tx.len--;
+				bc->hdlc_tx.current_byte = 0x100 | 
+					(*bc->hdlc_tx.bp++);
+			}
+			break;
+		}
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+static unsigned short random_seed;
+
+static inline unsigned short random_num(void)
+{
+	random_seed = 28629 * random_seed + 157;
+	return random_seed;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline void tx_arbitrate(struct baycom_state *bc)
+{
+	unsigned char *bp;
+	unsigned int len;
+	
+	if (!bc || bc->hdlc_tx.ptt || bc->modem.dcd)
+		return;
+	get_packet(&bc->tx_buf,&bp,&len);
+	if (!bp || !len)
+		return;
+	
+	if (!bc->ch_params.fulldup) {
+		if ((random_num() % 256) > bc->ch_params.ppersist)
+			return;
+	}
+	bc->hdlc_tx.ptt = 1;
+	bc->hdlc_tx.tx_state = 0;
+	bc->hdlc_tx.numflags = tenms_to_flags(bc, bc->ch_params.tx_delay);
+	bc->stat.ptt_keyed++;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef BAYCOM_DEBUG
+static void inline baycom_int_freq(struct baycom_state *bc)
+{
+	unsigned long cur_jiffies = jiffies;
+	/* 
+	 * measure the interrupt frequency
+	 */
+	bc->debug_vals.cur_intcnt++;
+	if ((cur_jiffies - bc->debug_vals.last_jiffies) >= HZ) {
+		bc->debug_vals.last_jiffies = cur_jiffies;
+		bc->debug_vals.last_intcnt = bc->debug_vals.cur_intcnt;
+		bc->debug_vals.cur_intcnt = 0;
+		bc->debug_vals.last_pllcorr = bc->debug_vals.cur_pllcorr;
+		bc->debug_vals.cur_pllcorr = 0;
+	}
+}
+#endif /* BAYCOM_DEBUG */
+
+/* --------------------------------------------------------------------- */
+
+static inline void rx_chars_to_flip(struct baycom_state *bc) 
+{
+	int flip_free;
+	unsigned int cnt;
+	unsigned int new_rd;
+	unsigned long flags;
+
+	if ((!bc) || (!bc->tty) || (bc->tty->flip.count >= TTY_FLIPBUF_SIZE) ||
+	    (bc->rx_buf.rd == bc->rx_buf.wr) || 
+	    (!bc->tty->flip.char_buf_ptr) ||
+	    (!bc->tty->flip.flag_buf_ptr))
+		return;
+	for(;;) {
+		flip_free = TTY_FLIPBUF_SIZE - bc->tty->flip.count;
+		if (bc->rx_buf.rd <= bc->rx_buf.wr)
+			cnt = bc->rx_buf.wr - bc->rx_buf.rd;
+		else
+			cnt = bc->rx_buf.buflen - bc->rx_buf.rd;
+		if ((flip_free <= 0) || (!cnt)) {
+			tty_schedule_flip(bc->tty);
+			return;
+		}
+		if (cnt > flip_free)
+			cnt = flip_free;
+		save_flags(flags); cli();
+		memcpy(bc->tty->flip.char_buf_ptr, bc->rx_buf.buffer+bc->rx_buf.rd, cnt);
+		memset(bc->tty->flip.flag_buf_ptr, TTY_NORMAL, cnt);
+		bc->tty->flip.count += cnt;
+		bc->tty->flip.char_buf_ptr += cnt;
+		bc->tty->flip.flag_buf_ptr += cnt;
+		restore_flags(flags);
+		new_rd = bc->rx_buf.rd+cnt;
+		if (new_rd >= bc->rx_buf.buflen)
+			new_rd -= bc->rx_buf.buflen;
+		bc->rx_buf.rd = new_rd;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== SER12 specific routines =========================
+ */
+
+static void inline ser12_set_divisor(struct baycom_state *bc, 
+				     unsigned char divisor)
+{
+	outb(0x81, LCR(bc->iobase));	/* DLAB = 1 */
+	outb(divisor, DLL(bc->iobase));
+	outb(0, DLM(bc->iobase));
+	outb(0x01, LCR(bc->iobase));	/* word length = 6 */
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * must call the TX arbitrator every 10ms
+ */
+#define SER12_ARB_DIVIDER(bc) ((bc->options & BAYCOM_OPTIONS_SOFTDCD) ? \
+			       36 : 24)
+#define SER12_DCD_INTERVAL(bc) ((bc->options & BAYCOM_OPTIONS_SOFTDCD) ? \
+				240 : 12)
+
+static void baycom_ser12_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct baycom_state *bc = (struct baycom_state *)dev_id;
+	unsigned char cur_s;
+	
+	if (!bc || bc->magic != BAYCOM_MAGIC)
+		return;
+	/*
+	 * make sure the next interrupt is generated;
+	 * 0 must be used to power the modem; the modem draws its
+	 * power from the TxD line
+	 */	
+	outb(0x00, THR(bc->iobase));
+	rx_chars_to_flip(bc);
+#ifdef BAYCOM_DEBUG
+	baycom_int_freq(bc);
+#endif /* BAYCOM_DEBUG */
+	/*
+	 * check if transmitter active
+	 */
+	if (bc->hdlc_tx.ptt || bc->calibrate > 0) {
+		ser12_set_divisor(bc, 12); /* one interrupt per channel bit */
+		/*
+		 * first output the last bit (!) then call HDLC transmitter,
+		 * since this may take quite long
+		 */
+		outb(0x0e | (bc->modem.ser12.tx_bit ? 1 : 0), MCR(bc->iobase));
+		if (bc->calibrate > 0) {
+			bc->modem.ser12.tx_bit = !bc->modem.ser12.tx_bit;
+			bc->calibrate--;
+			return;
+		}
+#ifdef HDLC_LOOPBACK
+		hdlc_rx_bit(bc, bc->modem.ser12.tx_bit == 
+			    bc->modem.ser12.last_rxbit);
+		bc->modem.ser12.last_rxbit = bc->modem.ser12.tx_bit;
+#endif /* HDLC_LOOPBACK */
+		if (!hdlc_tx_bit(bc))
+			bc->modem.ser12.tx_bit = !bc->modem.ser12.tx_bit;
+		return;
+	}
+	/*
+	 * do demodulator
+	 */
+	outb(0x0d, MCR(bc->iobase));			/* transmitter off */
+	cur_s = inb(MSR(bc->iobase)) & 0x10;	/* the CTS line */
+#ifdef BAYCOM_DEBUG
+	add_bitbuffer(&bc->bitbuf_channel, cur_s);
+#endif /* BAYCOM_DEBUG */
+	bc->modem.ser12.dcd_shreg <<= 1;
+	if(cur_s != bc->modem.ser12.last_sample) {
+		bc->modem.ser12.dcd_shreg |= 1;
+
+		if (bc->options & BAYCOM_OPTIONS_SOFTDCD) {
+			unsigned int dcdspos, dcdsneg;
+
+			dcdspos = dcdsneg = 0;
+			dcdspos += ((bc->modem.ser12.dcd_shreg >> 1) & 1);
+			if (!(bc->modem.ser12.dcd_shreg & 0x7ffffffe))
+				dcdspos += 2;
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 2) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 3) & 1);
+			dcdsneg += ((bc->modem.ser12.dcd_shreg >> 4) & 1);
+
+			bc->modem.ser12.dcd_sum0 += 16*dcdspos - dcdsneg;
+		} else
+			bc->modem.ser12.dcd_sum0--;
+	}
+	bc->modem.ser12.last_sample = cur_s;
+	if(!bc->modem.ser12.dcd_time) {
+		bc->modem.dcd = (bc->modem.ser12.dcd_sum0 + 
+				 bc->modem.ser12.dcd_sum1 +
+				 bc->modem.ser12.dcd_sum2) < 0;
+		bc->modem.ser12.dcd_sum2 = bc->modem.ser12.dcd_sum1;
+		bc->modem.ser12.dcd_sum1 = bc->modem.ser12.dcd_sum0;
+		/* offset to ensure DCD off on silent input */
+		bc->modem.ser12.dcd_sum0 = 2;
+		bc->modem.ser12.dcd_time = SER12_DCD_INTERVAL(bc);
+	}
+	bc->modem.ser12.dcd_time--;
+	if (bc->options & BAYCOM_OPTIONS_SOFTDCD) {
+		/*
+		 * PLL code for the improved software DCD algorithm
+		 */
+		if (bc->modem.ser12.interm_sample) {
+			/*
+			 * intermediate sample; set timing correction to normal
+			 */
+			ser12_set_divisor(bc, 4);
+		} else {
+			/*
+			 * do PLL correction and call HDLC receiver
+			 */
+			switch (bc->modem.ser12.dcd_shreg & 7) {
+			case 1: /* transition too late */
+				ser12_set_divisor(bc, 5);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr++;
+#endif /* BAYCOM_DEBUG */
+				break;
+			case 4:	/* transition too early */
+				ser12_set_divisor(bc, 3);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr--;
+#endif /* BAYCOM_DEBUG */
+				break;
+			default:
+				ser12_set_divisor(bc, 4);
+				break;
+			}
+			hdlc_rx_bit(bc, bc->modem.ser12.last_sample == 
+			    bc->modem.ser12.last_rxbit);
+			bc->modem.ser12.last_rxbit = 
+				bc->modem.ser12.last_sample;
+		}
+		if (++bc->modem.ser12.interm_sample >= 3)
+			bc->modem.ser12.interm_sample = 0;		
+	} else {
+		/*
+		 * PLL algorithm for the hardware squelch DCD algorithm
+		 */
+		if (bc->modem.ser12.interm_sample) {
+			/*
+			 * intermediate sample; set timing correction to normal
+			 */
+			ser12_set_divisor(bc, 6);
+		} else {
+			/*
+			 * do PLL correction and call HDLC receiver
+			 */
+			switch (bc->modem.ser12.dcd_shreg & 3) {
+			case 1: /* transition too late */
+				ser12_set_divisor(bc, 7);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr++;
+#endif /* BAYCOM_DEBUG */
+				break;
+			case 2:	/* transition too early */
+				ser12_set_divisor(bc, 5);
+#ifdef BAYCOM_DEBUG
+				bc->debug_vals.cur_pllcorr--;
+#endif /* BAYCOM_DEBUG */
+				break;
+			default:
+				ser12_set_divisor(bc, 6);
+				break;
+			}
+			hdlc_rx_bit(bc, bc->modem.ser12.last_sample == 
+			    bc->modem.ser12.last_rxbit);
+			bc->modem.ser12.last_rxbit = 
+				bc->modem.ser12.last_sample;
+		}
+		bc->modem.ser12.interm_sample = !bc->modem.ser12.interm_sample;
+	}
+	if (--bc->modem.arb_divider <= 0) {
+		tx_arbitrate(bc);
+		bc->modem.arb_divider = bc->ch_params.slottime * 
+			SER12_ARB_DIVIDER(bc);
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+enum uart { c_uart_unknown, c_uart_8250,
+	c_uart_16450, c_uart_16550, c_uart_16550A};
+static const char *uart_str[] =
+	{ "unknown", "8250", "16450", "16550", "16550A" };
+
+static enum uart ser12_check_uart(unsigned int iobase)
+{
+	unsigned char b1,b2,b3;
+	enum uart u;
+	enum uart uart_tab[] =
+		{ c_uart_16450, c_uart_unknown, c_uart_16550, c_uart_16550A };
+
+	b1 = inb(MCR(iobase));
+	outb(b1 | 0x10, MCR(iobase));	/* loopback mode */
+	b2 = inb(MSR(iobase));
+	outb(0x1a, MCR(iobase));
+	b3 = inb(MSR(iobase)) & 0xf0;
+	outb(b1, MCR(iobase));			/* restore old values */
+	outb(b2, MSR(iobase));
+	if (b3 != 0x90) 
+		return c_uart_unknown;
+	inb(RBR(iobase));
+	inb(RBR(iobase));
+	outb(0x01, FCR(iobase));		/* enable FIFOs */
+	u = uart_tab[(inb(IIR(iobase)) >> 6) & 3];
+	if (u == c_uart_16450) {
+		outb(0x5a, SCR(iobase));
+		b1 = inb(SCR(iobase));
+		outb(0xa5, SCR(iobase));
+		b2 = inb(SCR(iobase));
+		if ((b1 != 0x5a) || (b2 != 0xa5)) 
+			u = c_uart_8250;
+	}
+	return u;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_allocate_resources(unsigned int iobase, unsigned int irq,
+				    unsigned int options)
+{
+	enum uart u;
+
+	if (!iobase || iobase > 0xfff || irq < 2 || irq > 15)
+		return -ENXIO;
+	if (check_region(iobase, SER12_EXTENT))
+		return -EACCES;
+	if ((u = ser12_check_uart(iobase)) == c_uart_unknown)
+		return -EIO;
+	request_region(iobase, SER12_EXTENT, "baycom_ser12");
+	outb(0, FCR(iobase));		/* disable FIFOs */
+	outb(0x0d, MCR(iobase));
+	printk(KERN_INFO "baycom: ser12 at iobase 0x%x irq %u options 0x%x "
+	       "uart %s\n", iobase, irq, options, uart_str[u]);
+	return 0;
+}
+	
+/* --------------------------------------------------------------------- */
+
+static void ser12_deallocate_resources(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_SER12)
+		return;
+	/*
+	 * disable interrupts
+	 */
+	outb(0, IER(bc->iobase));
+	outb(1, MCR(bc->iobase));
+	/* 
+	 * this should prevent kernel: Trying to free IRQx
+	 * messages
+	 */
+	if (bc->opened > 0)
+		free_irq(bc->irq, bc);
+	release_region(bc->iobase, SER12_EXTENT);
+	bc->modem_type = BAYCOM_MODEM_INVALID;
+	printk(KERN_INFO "baycom: release ser12 at iobase 0x%x irq %u\n",
+	       bc->iobase, bc->irq);
+	bc->iobase = bc->irq = bc->options = 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ser12_on_open(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_SER12)
+		return -ENXIO;
+	/*
+	 * set the SIO to 6 Bits/character and 19200 or 28800 baud, so that
+	 * we get exactly (hopefully) 2 or 3 interrupts per radio symbol,
+	 * depending on the usage of the software DCD routine
+	 */
+	ser12_set_divisor(bc, (bc->options & BAYCOM_OPTIONS_SOFTDCD) ? 4 : 6);
+	outb(0x0d, MCR(bc->iobase));
+	outb(0, IER(bc->iobase));
+	if (request_irq(bc->irq, baycom_ser12_interrupt, 0, 
+			"baycom_ser12", bc))
+		return -EBUSY;
+	/*
+	 * enable transmitter empty interrupt
+	 */
+	outb(2, IER(bc->iobase));  
+	/* 
+	 * the value here serves to power the modem
+	 */     
+	outb(0x00, THR(bc->iobase));
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void ser12_on_close(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_SER12)
+		return;
+	/*
+	 * disable interrupts
+	 */
+	outb(0, IER(bc->iobase));
+	outb(1, MCR(bc->iobase));
+	free_irq(bc->irq, bc);	
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== PAR96 specific routines =========================
+ */
+
+#define PAR96_DESCRAM_TAP1 0x20000
+#define PAR96_DESCRAM_TAP2 0x01000
+#define PAR96_DESCRAM_TAP3 0x00001
+
+#define PAR96_DESCRAM_TAPSH1 17
+#define PAR96_DESCRAM_TAPSH2 12
+#define PAR96_DESCRAM_TAPSH3 0
+
+#define PAR96_SCRAM_TAP1 0x20000 /* X^17 */
+#define PAR96_SCRAM_TAPN 0x00021 /* X^0+X^5 */
+
+/* --------------------------------------------------------------------- */
+
+static void baycom_par96_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	register struct baycom_state *bc = (struct baycom_state *)dev_id;
+	int i;
+	unsigned int data, mask, mask2;
+	
+	if (!bc || bc->magic != BAYCOM_MAGIC)
+		return;
+
+	rx_chars_to_flip(bc);
+#ifdef BAYCOM_DEBUG
+	baycom_int_freq(bc);
+#endif /* BAYCOM_DEBUG */
+	/*
+	 * check if transmitter active
+	 */
+	if (bc->hdlc_tx.ptt || bc->calibrate > 0) {
+		/*
+		 * first output the last 16 bits (!) then call HDLC
+		 * transmitter, since this may take quite long
+		 * do the differential encoder and the scrambler on the fly
+		 */
+		data = bc->modem.par96.tx_bits;
+		for(i = 0; i < PAR96_BURSTBITS; i++, data <<= 1) {
+			unsigned char val = PAR97_POWER;
+			bc->modem.par96.scram = ((bc->modem.par96.scram << 1) |
+						 (bc->modem.par96.scram & 1));
+			if (!(data & 0x8000))
+				bc->modem.par96.scram ^= 1;
+			if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 1))
+				bc->modem.par96.scram ^= 
+					(PAR96_SCRAM_TAPN << 1);
+			if (bc->modem.par96.scram & (PAR96_SCRAM_TAP1 << 2))
+				val |= PAR96_TXBIT;
+			outb(val, LPT_DATA(bc->iobase));
+			outb(val | PAR96_BURST, LPT_DATA(bc->iobase));
+		}
+		if (bc->calibrate > 0) {
+			bc->modem.par96.tx_bits = 0;
+			bc->calibrate--;
+			return;
+		}
+#ifdef HDLC_LOOPBACK
+		for(mask = 0x8000, i = 0; i < PAR96_BURSTBITS; i++, mask >>= 1)
+			hdlc_rx_bit(bc, bc->modem.par96.tx_bits & mask);
+#endif /* HDLC_LOOPBACK */
+		bc->modem.par96.tx_bits = 0;
+		for(i = 0; i < PAR96_BURSTBITS; i++) {
+			bc->modem.par96.tx_bits <<= 1;
+			if (hdlc_tx_bit(bc))
+				bc->modem.par96.tx_bits |= 1;
+		}
+		return;
+	}
+	/*
+	 * do receiver; differential decode and descramble on the fly
+	 */
+	for(data = i = 0; i < PAR96_BURSTBITS; i++) {
+		unsigned int descx;
+		bc->modem.par96.descram = (bc->modem.par96.descram << 1);
+		if (inb(LPT_STATUS(bc->iobase)) & PAR96_RXBIT)
+			bc->modem.par96.descram |= 1;
+		descx = bc->modem.par96.descram ^ 
+			(bc->modem.par96.descram >> 1);
+		/* now the diff decoded data is inverted in descram */
+		outb(PAR97_POWER | PAR96_PTT, LPT_DATA(bc->iobase));
+		descx ^= ((descx >> PAR96_DESCRAM_TAPSH1) ^
+			  (descx >> PAR96_DESCRAM_TAPSH2));
+		data <<= 1;
+		data |= !(descx & 1);
+		outb(PAR97_POWER | PAR96_PTT | PAR96_BURST, 
+		     LPT_DATA(bc->iobase));
+	}
+	for(mask = 0x8000, i = 0; i < PAR96_BURSTBITS; i++, mask >>= 1)
+		hdlc_rx_bit(bc, data & mask);
+	/*
+	 * do DCD algorithm
+	 */
+	if (bc->options & BAYCOM_OPTIONS_SOFTDCD) {
+		bc->modem.par96.dcd_shreg = (bc->modem.par96.dcd_shreg << 16)
+			| data;
+		/* search for flags and set the dcd counter appropriately */
+		for(mask = 0x7f8000, mask2 = 0x3f0000, i = 0; 
+		    i < PAR96_BURSTBITS; i++, mask >>= 1, mask2 >>= 1)
+			if ((bc->modem.par96.dcd_shreg & mask) == mask2)
+				bc->modem.par96.dcd_count = BAYCOM_MAXFLEN+4;
+		/* check for abort/noise sequences */
+		for(mask = 0x3f8000, mask2 = 0x3f8000, i = 0; 
+		    i < PAR96_BURSTBITS; i++, mask >>= 1, mask2 >>= 1)
+			if ((bc->modem.par96.dcd_shreg & mask) == mask2)
+				if (bc->modem.par96.dcd_count >= 0)
+					bc->modem.par96.dcd_count -= 
+						BAYCOM_MAXFLEN-10;
+		/* decrement and set the dcd variable */
+		if (bc->modem.par96.dcd_count >= 0)
+			bc->modem.par96.dcd_count -= 2;
+		bc->modem.dcd = bc->modem.par96.dcd_count > 0;
+	} else {
+		bc->modem.dcd = !!(inb(LPT_STATUS(bc->iobase)) & PAR96_DCD);
+	}
+	if (--bc->modem.arb_divider <= 0) {
+		tx_arbitrate(bc);
+		bc->modem.arb_divider = bc->ch_params.slottime * 6;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static int par96_check_lpt(unsigned int iobase)
+{
+	unsigned char b1,b2;
+	int i;
+
+	b1 = inb(LPT_DATA(iobase));
+	b2 = inb(LPT_CONTROL(iobase));
+	outb(0xaa, LPT_DATA(iobase));
+	i = inb(LPT_DATA(iobase)) == 0xaa;
+	outb(0x55, LPT_DATA(iobase));
+	i &= inb(LPT_DATA(iobase)) == 0x55;
+	outb(0x0a, LPT_CONTROL(iobase));
+	i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x0a;
+	outb(0x05, LPT_CONTROL(iobase));
+	i &= (inb(LPT_CONTROL(iobase)) & 0xf) == 0x05;
+	outb(b1, LPT_DATA(iobase));
+	outb(b2, LPT_CONTROL(iobase));
+	return !i;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int par96_allocate_resources(unsigned int iobase, unsigned int irq,
+				    unsigned int options)
+{
+	if (!iobase || iobase > 0xfff || irq < 2 || irq > 15)
+		return -ENXIO;
+	if (check_region(iobase, PAR96_EXTENT))
+		return -EACCES;
+	if (par96_check_lpt(iobase))
+		return -EIO;
+	request_region(iobase, PAR96_EXTENT, "baycom_par96");
+	outb(0, LPT_CONTROL(iobase));                 /* disable interrupt */
+	outb(PAR96_PTT | PAR97_POWER, LPT_DATA(iobase)); /* switch off PTT */
+	printk(KERN_INFO "baycom: par96 at iobase 0x%x irq %u options 0x%x\n", 
+	       iobase, irq, options);
+	return 0;
+}
+	
+/* --------------------------------------------------------------------- */
+
+static void par96_deallocate_resources(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96)
+		return;
+	outb(0, LPT_CONTROL(bc->iobase));      /* disable interrupt */
+	outb(PAR96_PTT, LPT_DATA(bc->iobase)); /* switch off PTT */
+	/* 
+	 * this should prevent kernel: Trying to free IRQx
+	 * messages
+	 */
+	if (bc->opened > 0)
+		free_irq(bc->irq, bc);
+	release_region(bc->iobase, PAR96_EXTENT);
+	bc->modem_type = BAYCOM_MODEM_INVALID;
+	printk(KERN_INFO "baycom: release par96 at iobase 0x%x irq %u\n",
+	       bc->iobase, bc->irq);
+	bc->iobase = bc->irq = bc->options = 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int par96_on_open(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96)
+		return -ENXIO;
+	outb(0, LPT_CONTROL(bc->iobase));      /* disable interrupt */
+	 /* switch off PTT */
+	outb(PAR96_PTT | PAR97_POWER, LPT_DATA(bc->iobase));
+	if (request_irq(bc->irq, baycom_par96_interrupt, 0, 
+			"baycom_par96", bc))
+		return -EBUSY;
+	outb(LPT_IRQ_ENABLE, LPT_CONTROL(bc->iobase));  /* enable interrupt */
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void par96_on_close(struct baycom_state *bc) 
+{
+	if (!bc || bc->modem_type != BAYCOM_MODEM_PAR96)
+		return;
+	outb(0, LPT_CONTROL(bc->iobase));  /* disable interrupt */
+	/* switch off PTT */
+	outb(PAR96_PTT | PAR97_POWER, LPT_DATA(bc->iobase));
+	free_irq(bc->irq, bc);	
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * ===================== TTY interface routines ==========================
+ */
+
+static inline int baycom_paranoia_check(struct baycom_state *bc, 
+					const char *routine)
+{
+	if (!bc || bc->magic != BAYCOM_MAGIC) {
+		printk(KERN_ERR "baycom: bad magic number for baycom struct "
+		       "in routine %s\n", routine);
+		return 1;
+	}
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * Here the tty driver code starts
+ */
+
+static void baycom_put_fend(struct baycom_state *bc)
+{
+	if (bc->kiss_decode.wr <= 0 ||
+	    (bc->kiss_decode.pkt_buf[0] & 0xf0) != 0)
+		return;
+
+	switch (bc->kiss_decode.pkt_buf[0] & 0xf) {
+	case KISS_CMD_DATA:
+		if (bc->kiss_decode.wr <= 8) 
+			break;
+		if (!store_packet(&bc->tx_buf, bc->kiss_decode.pkt_buf+1, 0, 
+				  bc->kiss_decode.wr-1))
+			bc->stat.tx_bufferoverrun++;
+		break;
+
+	case KISS_CMD_TXDELAY:
+		if (bc->kiss_decode.wr < 2) 
+			break;
+		bc->ch_params.tx_delay = bc->kiss_decode.pkt_buf[1];
+#ifdef KISS_VERBOSE
+		printk(KERN_INFO "baycom: TX delay = %ums\n", 
+		       bc->ch_params.tx_delay * 10);
+#endif /* KISS_VERBOSE */
+		break;
+
+	case KISS_CMD_PPERSIST:
+		if (bc->kiss_decode.wr < 2) 
+			break;
+		bc->ch_params.ppersist = bc->kiss_decode.pkt_buf[1];
+#ifdef KISS_VERBOSE
+		printk("KERN_INFO baycom: p-persistence = %u\n", 
+		       bc->ch_params.ppersist);
+#endif /* KISS_VERBOSE */
+		break;
+
+	case KISS_CMD_SLOTTIME:
+		if (bc->kiss_decode.wr < 2) 
+			break;
+		bc->ch_params.slottime = bc->kiss_decode.pkt_buf[1];
+#ifdef KISS_VERBOSE
+		printk("baycom: slottime = %ums\n", 
+		       bc->ch_params.slottime * 10);
+#endif /* KISS_VERBOSE */
+		break;
+
+	case KISS_CMD_TXTAIL:
+		if (bc->kiss_decode.wr < 2) 
+			break;
+		bc->ch_params.tx_tail = bc->kiss_decode.pkt_buf[1];
+#ifdef KISS_VERBOSE
+		printk(KERN_INFO "baycom: TX tail = %ums\n",
+		       bc->ch_params.tx_tail * 10);
+#endif /* KISS_VERBOSE */
+		break;
+
+	case KISS_CMD_FULLDUP:
+		if (bc->kiss_decode.wr < 2) 
+			break;
+		bc->ch_params.fulldup = bc->kiss_decode.pkt_buf[1];
+#ifdef KISS_VERBOSE
+		printk(KERN_INFO "baycom: %s duplex\n", 
+		       bc->ch_params.fulldup ? "full" : "half");
+#endif /* KISS_VERBOSE */
+		break;
+
+	default:
+#ifdef KISS_VERBOSE
+		printk(KERN_INFO "baycom: unhandled KISS packet code %u\n",
+		       bc->kiss_decode.pkt_buf[0] & 0xf);
+#endif /* KISS_VERBOSE */
+		break;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void baycom_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct baycom_state *bc;
+		
+	if (!tty)
+		return;
+	if (baycom_paranoia_check(bc = tty->driver_data, "put_char"))
+		return;
+		
+	if (ch == KISS_FEND) {
+		baycom_put_fend(bc);
+		bc->kiss_decode.wr = 0;
+		bc->kiss_decode.escaped = 0;
+		bc->kiss_decode.dec_state = 1;
+		return;
+	}
+	if (!bc->kiss_decode.dec_state)
+		return;
+	if (bc->kiss_decode.wr >= sizeof(bc->kiss_decode.pkt_buf)) {
+		bc->kiss_decode.wr = 0;
+		bc->kiss_decode.dec_state = 0;
+		return;
+	}
+	if (bc->kiss_decode.escaped) {
+		if (ch == KISS_TFEND)
+			bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = 
+				KISS_FEND;
+		else if (ch == KISS_TFESC)
+			bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = 
+				KISS_FESC;
+		else {
+			bc->kiss_decode.wr = 0;
+			bc->kiss_decode.dec_state = 0;
+		}
+		bc->kiss_decode.escaped = 0;
+		return;
+	}
+	bc->kiss_decode.pkt_buf[bc->kiss_decode.wr++] = ch;
+}
+	
+/* --------------------------------------------------------------------- */
+
+static int baycom_write(struct tty_struct * tty, int from_user,
+	const unsigned char *buf, int count)
+{
+	int c;
+	const unsigned char *bp;
+	struct baycom_state *bc;
+		
+	if (!tty || !buf || count <= 0)
+		return count;
+	
+	if (baycom_paranoia_check(bc = tty->driver_data, "write"))
+		return count; 
+		
+	if (from_user) {
+		for(c = count, bp = buf; c > 0; c--,bp++)
+			baycom_put_char(tty, get_user(bp));
+	} else {
+		for(c = count, bp = buf; c > 0; c--,bp++)
+			baycom_put_char(tty, *bp);
+	}
+	return count;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_write_room(struct tty_struct *tty)
+{
+	int free;
+	struct baycom_state *bc;
+		
+	if (!tty)
+		return 0;
+	if (baycom_paranoia_check(bc = tty->driver_data, "write_room"))
+		return 0;
+		
+	free = bc->tx_buf.rd - bc->tx_buf.wr;
+	if (free <= 0) {
+		free = bc->tx_buf.buflen - bc->tx_buf.wr;
+		if (free < bc->tx_buf.rd)
+			free = bc->tx_buf.rd;	/* we may fold */
+	}
+
+	return free / 2; /* a rather pessimistic estimate */
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_chars_in_buffer(struct tty_struct *tty)
+{
+	int cnt;
+	struct baycom_state *bc;
+		
+	if (!tty)
+		return 0;
+	if (baycom_paranoia_check(bc = tty->driver_data, "chars_in_buffer"))
+		return 0;
+		
+	cnt = bc->rx_buf.wr - bc->rx_buf.rd;
+	if (cnt < 0)
+		cnt += bc->rx_buf.buflen;
+		
+	return cnt;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void baycom_flush_buffer(struct tty_struct *tty)
+{
+	struct baycom_state *bc;
+		
+	if (!tty)
+		return;
+	if (baycom_paranoia_check(bc = tty->driver_data, "flush_buffer"))
+		return;
+
+	wake_up_interruptible(&tty->write_wait);
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+		tty->ldisc.write_wakeup)
+			(tty->ldisc.write_wakeup)(tty);
+}
+
+/* --------------------------------------------------------------------- */
+
+static inline void baycom_dealloc_hw(struct baycom_state *bc) 
+{
+	if (!bc || bc->magic != BAYCOM_MAGIC || 
+	    bc->modem_type == BAYCOM_MODEM_INVALID)
+		return;
+	switch(bc->modem_type) {
+	case BAYCOM_MODEM_SER12:
+		ser12_deallocate_resources(bc);
+		break;
+	case BAYCOM_MODEM_PAR96:
+		par96_deallocate_resources(bc);
+		break;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_set_hardware(struct baycom_state *bc,
+			       unsigned int modem_type, unsigned int iobase, 
+			       unsigned int irq, unsigned int options)
+{
+	int i;
+
+	if (!bc)
+		return -EINVAL;
+
+	if (modem_type == BAYCOM_MODEM_SER12) {
+		i = ser12_allocate_resources(iobase, irq, options);
+		if (i < 0)
+			return i;
+	} else if (modem_type == BAYCOM_MODEM_PAR96) {
+		i = par96_allocate_resources(iobase, irq, options);
+		if (i < 0)
+			return i;
+	} else if (modem_type == BAYCOM_MODEM_INVALID) {
+		iobase = irq = options = 0;
+	} else {
+		return -ENXIO;
+	}
+	baycom_dealloc_hw(bc);
+	bc->modem_type = modem_type;
+	bc->iobase = iobase;
+	bc->irq = irq;
+	bc->options = options;
+	i = 0;
+	if (bc->opened > 0) {
+		switch(bc->modem_type) {
+		case BAYCOM_MODEM_SER12:
+			i = ser12_on_open(bc);
+			break;
+		case BAYCOM_MODEM_PAR96:
+			i = par96_on_open(bc);
+			break;
+		}
+	}
+	return i;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int baycom_ioctl(struct tty_struct *tty, struct file * file,
+	unsigned int cmd, unsigned long arg)
+{
+	int i;
+	struct baycom_state *bc;
+	struct baycom_params par;
+		
+	if (!tty)
+		return -EINVAL;
+	if (baycom_paranoia_check(bc = tty->driver_data, "ioctl"))
+		return -EINVAL;
+		
+	switch (cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+	case TIOCMGET:
+		i = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int));
+		if (i)
+			return i;
+		i = (bc->modem.dcd ? TIOCM_CAR : 0) |
+			(bc->hdlc_tx.ptt ? TIOCM_RTS : 0);
+		put_user(i, (int *) arg);
+		return 0;
+		
+	case BAYCOMCTL_GETDCD:
+		i = verify_area(VERIFY_WRITE, (void *) arg,
+				sizeof(unsigned char));
+		if (!i)
+			put_user(bc->modem.dcd, (unsigned char *) arg);
+		return i;
+		
+	case BAYCOMCTL_GETPTT:
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(unsigned char));
+		if (!i)
+			put_user(bc->hdlc_tx.ptt, (unsigned char *) arg);
+		return i;
+		
+	case BAYCOMCTL_PARAM_TXDELAY:
+		if (arg > 255)
+			return -EINVAL;
+		bc->ch_params.tx_delay = arg;
+		return 0;
+		
+	case BAYCOMCTL_PARAM_PPERSIST:
+		if (arg > 255)
+			return -EINVAL;
+		bc->ch_params.ppersist = arg;
+		return 0;
+		
+	case BAYCOMCTL_PARAM_SLOTTIME:
+		if (arg > 255)
+			return -EINVAL;
+		bc->ch_params.slottime = arg;
+		return 0;
+		
+	case BAYCOMCTL_PARAM_TXTAIL:
+		if (arg > 255)
+			return -EINVAL;
+		bc->ch_params.tx_tail = arg;
+		return 0;
+		
+	case BAYCOMCTL_PARAM_FULLDUP:
+		bc->ch_params.fulldup = arg ? 1 : 0;
+		return 0;
+		
+	case BAYCOMCTL_CALIBRATE:
+		bc->calibrate = arg * ((bc->modem_type == BAYCOM_MODEM_PAR96) ?
+				       600 : 1200);
+		return 0;
+
+	case BAYCOMCTL_GETPARAMS:
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(par));
+		if (i)
+			return i;
+		par.modem_type = bc->modem_type;
+		par.iobase = bc->iobase;
+		par.irq = bc->irq;
+		par.options = bc->options;
+		par.tx_delay = bc->ch_params.tx_delay;
+		par.tx_tail = bc->ch_params.tx_tail;
+		par.slottime = bc->ch_params.slottime;
+		par.ppersist = bc->ch_params.ppersist;
+		par.fulldup = bc->ch_params.fulldup;
+		memcpy_tofs((void *)arg, &par, sizeof(par));
+		return 0;
+
+	case BAYCOMCTL_SETPARAMS:
+		if (!suser())
+			return -EPERM;
+		i = verify_area(VERIFY_READ, (void *) arg, 
+				sizeof(par));
+		if (i)
+			return i;
+		memcpy_fromfs(&par, (void *)arg, sizeof(par));
+		printk(KERN_INFO "baycom: changing hardware type: modem %u "
+		       "iobase 0x%x irq %u options 0x%x\n", par.modem_type,
+		       par.iobase, par.irq, par.options);
+		i = baycom_set_hardware(bc, par.modem_type, par.iobase,
+					par.irq, par.options); 
+		if (i)
+			return i;
+		bc->ch_params.tx_delay = par.tx_delay;
+		bc->ch_params.tx_tail = par.tx_tail;
+		bc->ch_params.slottime = par.slottime;
+		bc->ch_params.ppersist = par.ppersist;
+		bc->ch_params.fulldup = par.fulldup;
+		return 0;
+
+	case BAYCOMCTL_GETSTAT:
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(struct baycom_statistics));
+		if (i)
+			return i;
+		memcpy_tofs((void *)arg, &bc->stat, 
+			    sizeof(struct baycom_statistics));
+		return 0;
+		
+
+#ifdef BAYCOM_DEBUG
+	case BAYCOMCTL_GETSAMPLES:
+		if (bc->bitbuf_channel.rd == bc->bitbuf_channel.wr) 
+			return -EAGAIN;
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(unsigned char));
+		if (!i) {
+			put_user(bc->bitbuf_channel.buffer
+				 [bc->bitbuf_channel.rd],
+				 (unsigned char *) arg);
+			bc->bitbuf_channel.rd = (bc->bitbuf_channel.rd+1) %
+				sizeof(bc->bitbuf_channel.buffer);
+		}
+		return i;
+		
+	case BAYCOMCTL_GETBITS:
+		if (bc->bitbuf_hdlc.rd == bc->bitbuf_hdlc.wr) 
+			return -EAGAIN;
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(unsigned char));
+		if (!i) {
+			put_user(bc->bitbuf_hdlc.buffer[bc->bitbuf_hdlc.rd],
+				 (unsigned char *) arg);
+			bc->bitbuf_hdlc.rd = (bc->bitbuf_hdlc.rd+1) %
+				sizeof(bc->bitbuf_hdlc.buffer);
+		}
+		return i;
+		
+	case BAYCOMCTL_DEBUG1:
+		i = verify_area(VERIFY_WRITE, (void *) arg,
+				sizeof(unsigned long));
+		if (!i)
+			put_user((bc->rx_buf.wr-bc->rx_buf.rd) % 
+				 bc->rx_buf.buflen, (unsigned long *)arg);
+		return i;
+		
+	case BAYCOMCTL_DEBUG2:
+		i = verify_area(VERIFY_WRITE, (void *) arg,
+				sizeof(unsigned long));
+		if (!i)
+			put_user(bc->debug_vals.last_intcnt, 
+				 (unsigned long *)arg);
+		return i;
+		
+	case BAYCOMCTL_DEBUG3:
+		i = verify_area(VERIFY_WRITE, (void *) arg, 
+				sizeof(unsigned long));
+		if (!i)
+			put_user((long)bc->debug_vals.last_pllcorr,
+				 (long *)arg);
+		return i;		
+#endif /* BAYCOM_DEBUG */
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+int baycom_open(struct tty_struct *tty, struct file * filp)
+{
+	int line;
+	struct baycom_state *bc;
+	int i;
+
+	if(!tty)
+		return -ENODEV;
+
+	line = MINOR(tty->device) - tty->driver.minor_start;
+	if (line < 0 || line >= NR_PORTS)
+		return -ENODEV;
+	bc = baycom_state+line;
+
+	if (bc->opened > 0) {
+		bc->opened++;
+		MOD_INC_USE_COUNT;
+		return 0;
+	}
+	/*
+	 * initialise some variables
+	 */
+	bc->calibrate = 0;
+
+	/*
+	 * allocate the buffer space
+	 */
+	if (bc->rx_buf.buffer)
+		kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen);
+	if (bc->tx_buf.buffer)
+		kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen);
+	bc->rx_buf.buflen = BUFLEN_RX;
+	bc->tx_buf.buflen = BUFLEN_TX;
+	bc->rx_buf.rd = bc->rx_buf.wr = 0;
+	bc->tx_buf.rd = bc->tx_buf.wr = 0;
+	bc->rx_buf.buffer = kmalloc(bc->rx_buf.buflen, GFP_KERNEL);
+	bc->tx_buf.buffer = kmalloc(bc->tx_buf.buflen, GFP_KERNEL);
+	if (!bc->rx_buf.buffer || !bc->tx_buf.buffer) {
+		if (bc->rx_buf.buffer)
+			kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen);
+		if (bc->tx_buf.buffer)
+			kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen);
+		bc->rx_buf.buffer = bc->tx_buf.buffer = NULL;
+		bc->rx_buf.buflen = bc->tx_buf.buflen = 0;
+		return -ENOMEM;
+	}
+	/*
+	 * check if the modem type has been set
+	 */
+	switch(bc->modem_type) {
+	case BAYCOM_MODEM_SER12:
+		i = ser12_on_open(bc);
+		break;
+	case BAYCOM_MODEM_PAR96:
+		i = par96_on_open(bc);
+		break;
+	case BAYCOM_MODEM_INVALID:
+		/*
+		 * may open even if no hardware specified, in order to
+		 * subsequently allow the BAYCOMCTL_SETPARAMS ioctl
+		 */
+		i = 0;
+		break;
+	default:
+		return -ENODEV;
+	}
+	if (i) 
+		return i;
+
+	bc->opened++;
+	MOD_INC_USE_COUNT;
+
+	tty->driver_data = bc;
+	bc->tty = tty;
+
+	return 0;   
+}
+
+
+/* --------------------------------------------------------------------- */
+	
+static void baycom_close(struct tty_struct *tty, struct file * filp)
+{
+	struct baycom_state *bc;
+		
+	if(!tty) return;
+	if (baycom_paranoia_check(bc = tty->driver_data, "close"))
+		return;
+
+	MOD_DEC_USE_COUNT;
+	bc->opened--;
+	if (bc->opened <= 0) {
+		switch(bc->modem_type) {
+		case BAYCOM_MODEM_SER12:
+			ser12_on_close(bc);
+			break;
+		case BAYCOM_MODEM_PAR96:
+			par96_on_close(bc);
+			break;
+		}
+		tty->driver_data = NULL;
+		bc->tty = NULL;
+		bc->opened = 0;
+		/*
+		 * free the buffers 
+		 */
+		bc->rx_buf.rd = bc->rx_buf.wr = 0;
+		bc->tx_buf.rd = bc->tx_buf.wr = 0;
+		if (bc->rx_buf.buffer)
+			kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen);
+		if (bc->tx_buf.buffer)
+			kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen);
+		bc->rx_buf.buffer = bc->tx_buf.buffer = NULL;
+		bc->rx_buf.buflen = bc->tx_buf.buflen = 0;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+/*
+ * And now the modules code and kernel interface.
+ */
+
+static void init_channel(struct baycom_state *bc)
+{
+	struct access_params dflt_ch_params = { 20, 2, 10, 40, 0 };
+
+	if (!bc)
+		return;
+
+	bc->hdlc_rx.rx_state = 0;
+
+	bc->hdlc_tx.tx_state = bc->hdlc_tx.numflags = 0;
+	bc->hdlc_tx.bitstream = 0;
+	bc->hdlc_tx.current_byte = bc->hdlc_tx.ptt = 0;
+
+	memset(&bc->modem, 0, sizeof(bc->modem));
+
+#ifdef BAYCOM_DEBUG
+	bc->bitbuf_channel.rd = bc->bitbuf_channel.wr = 0;
+	bc->bitbuf_channel.shreg = 1;
+
+	bc->bitbuf_hdlc.rd = bc->bitbuf_hdlc.wr = 0;
+	bc->bitbuf_hdlc.shreg = 1;
+#endif /* BAYCOM_DEBUG */
+
+	bc->kiss_decode.dec_state = bc->kiss_decode.escaped = 
+	bc->kiss_decode.wr = 0;
+
+	bc->ch_params = dflt_ch_params;
+}
+
+static void init_datastructs(void)
+{
+	int i;
+
+	for(i = 0; i < NR_PORTS; i++) {
+		struct baycom_state *bc = baycom_state+i;
+
+		bc->magic = BAYCOM_MAGIC;
+		bc->modem_type = BAYCOM_MODEM_INVALID;
+		bc->iobase = bc->irq = bc->options = bc->opened = 0;
+		bc->tty = NULL;
+
+		bc->rx_buf.rd = bc->rx_buf.wr = 0;
+		bc->rx_buf.buflen = 0;
+		bc->rx_buf.buffer = NULL;
+
+		bc->tx_buf.rd = bc->tx_buf.wr = 0;
+		bc->tx_buf.buflen = 0;
+		bc->tx_buf.buffer = NULL;
+
+		memset(&bc->stat, 0, sizeof(bc->stat));
+
+		init_channel(bc);
+	}
+}
+
+int baycom_init(void) {
+	int i, j;
+
+	/*
+	 * initialize the data structures
+	 */
+	init_datastructs();
+	/*
+	 * register the driver as tty driver
+	 */
+	memset(&baycom_driver, 0, sizeof(struct tty_driver));
+	baycom_driver.magic = TTY_DRIVER_MAGIC;
+	baycom_driver.name = "baycom";
+	baycom_driver.major = major;
+	baycom_driver.minor_start = 0;
+	baycom_driver.num = NR_PORTS;
+	baycom_driver.type = TTY_DRIVER_TYPE_BAYCOM;
+	baycom_driver.subtype = BAYCOM_TYPE_NORMAL;
+	baycom_driver.init_termios.c_iflag = 0;
+	baycom_driver.init_termios.c_oflag = 0;
+	baycom_driver.init_termios.c_cflag = CS8 | B1200 | CREAD | CLOCAL;
+	baycom_driver.init_termios.c_lflag = 0;
+	baycom_driver.flags = TTY_DRIVER_REAL_RAW;
+	baycom_driver.refcount = &baycom_refcount;
+	baycom_driver.table = baycom_table;
+	baycom_driver.termios = baycom_termios;
+	baycom_driver.termios_locked = baycom_termios_locked;
+	/*
+	 * the functions
+	 */
+	baycom_driver.open = baycom_open;
+	baycom_driver.close = baycom_close;
+	baycom_driver.write = baycom_write;
+	baycom_driver.put_char = baycom_put_char;
+	baycom_driver.flush_chars = NULL;
+	baycom_driver.write_room = baycom_write_room;
+	baycom_driver.chars_in_buffer = baycom_chars_in_buffer;
+	baycom_driver.flush_buffer = baycom_flush_buffer;
+	baycom_driver.ioctl = baycom_ioctl;
+	/*
+	 * cannot throttle the transmitter on this layer
+	 */
+	baycom_driver.throttle = NULL;
+	baycom_driver.unthrottle = NULL;
+	/*
+	 * no special actions on termio changes
+	 */
+	baycom_driver.set_termios = NULL;
+	/*
+	 * no XON/XOFF and no hangup on the radio port
+	 */
+	baycom_driver.stop = NULL;
+	baycom_driver.start = NULL;
+	baycom_driver.hangup = NULL;
+	baycom_driver.set_ldisc = NULL;
+
+	if (tty_register_driver(&baycom_driver)) {
+		printk(KERN_WARNING "baycom: tty_register_driver failed\n");
+		return -EIO;
+	}
+
+	for (i = 0; i < NR_PORTS && 
+	     baycom_ports[i].modem != BAYCOM_MODEM_INVALID; i++) {
+		j = baycom_set_hardware(baycom_state+i, 
+					baycom_ports[i].modem,
+					baycom_ports[i].iobase, 
+					baycom_ports[i].irq, 
+					baycom_ports[i].options);
+		if (j < 0) {
+			const char *s;
+			switch (-j) {
+			case ENXIO:
+				s = "invalid iobase and/or irq";
+				break;
+			case EACCES:
+				s = "io region already used";
+				break;
+			case EIO:
+				s = "no uart/lpt port at iobase";
+				break;
+			case EBUSY:
+				s = "interface already in use";
+				break;
+			case EINVAL:
+				s = "internal error";
+				break;
+			default:
+				s = "unknown error";
+				break;
+			}
+			printk(KERN_WARNING "baycom: modem %u iobase 0x%x "
+			       "irq %u: (%i) %s\n", baycom_ports[i].modem, 
+			       baycom_ports[i].iobase, baycom_ports[i].irq, 
+			       j, s);
+		}
+	}
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+
+int modem = BAYCOM_MODEM_INVALID;
+int iobase = 0x3f8;
+int irq = 4;
+int options = BAYCOM_OPTIONS_SOFTDCD;
+
+int init_module(void)
+{
+	int i;
+
+	printk(KERN_INFO "baycom: init_module called\n");
+
+	baycom_ports[0].modem = modem;
+	baycom_ports[0].iobase = iobase;
+	baycom_ports[0].irq = irq;
+	baycom_ports[0].options = options;
+	baycom_ports[1].modem = BAYCOM_MODEM_INVALID;
+
+	i = baycom_init();
+	if (i)
+		return i;
+
+	printk(KERN_INFO "baycom: version 0.2; "
+	       "(C) 1996 by Thomas Sailer HB9JNX, sailer@ife.ee.ethz.ch\n");
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+void cleanup_module(void)
+{
+	int i;
+
+#if 0
+	if (MOD_IN_USE)
+		printk(KERN_INFO "baycom: device busy, remove delayed\n");
+#endif
+	printk(KERN_INFO "baycom: cleanup_module called\n");
+
+	if (tty_unregister_driver(&baycom_driver))
+		printk(KERN_WARNING "baycom: failed to unregister tty "
+		       "driver\n");
+	for(i = 0; i < NR_PORTS; i++) {
+		struct baycom_state *bc = baycom_state+i;
+
+		if (bc->magic != BAYCOM_MAGIC)
+			printk(KERN_ERR "baycom: invalid magic in "
+			       "cleanup_module\n");
+		else {
+			baycom_dealloc_hw(bc);
+			/*
+			 * free the buffers 
+			 */
+			bc->rx_buf.rd = bc->rx_buf.wr = 0;
+			bc->tx_buf.rd = bc->tx_buf.wr = 0;
+			if (bc->rx_buf.buffer)
+				kfree_s(bc->rx_buf.buffer, bc->rx_buf.buflen);
+			if (bc->tx_buf.buffer)
+				kfree_s(bc->tx_buf.buffer, bc->tx_buf.buflen);
+			bc->rx_buf.buffer = bc->tx_buf.buffer = NULL;
+			bc->rx_buf.buflen = bc->tx_buf.buflen = 0;
+		}
+	}
+}
+
+#else /* MODULE */
+/* --------------------------------------------------------------------- */
+/*
+ * format: baycom=modem,io,irq,options[,modem,io,irq,options]
+ * modem=1: ser12, modem=2: par96
+ * options=0: hardware DCD, options=1: software DCD
+ */
+
+void baycom_setup(char *str, int *ints)
+{
+	int i;
+
+	for (i = 0; i < NR_PORTS; i++) 
+		if (ints[0] >= 4*i+4) {
+			baycom_ports[i].modem = ints[4*i+1];
+			baycom_ports[i].iobase = ints[4*i+2];
+			baycom_ports[i].irq = ints[4*i+3];
+			baycom_ports[i].options = ints[4*i+4];
+		} else
+			baycom_ports[i].modem = BAYCOM_MODEM_INVALID;
+
+}
+
+#endif /* MODULE */
+/* --------------------------------------------------------------------- */
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 8
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -8
+ * c-argdecl-indent: 8
+ * c-label-offset: -8
+ * c-continued-statement-offset: 8
+ * c-continued-brace-offset: 0
+ * End:
+ */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this