[PATCH] staging: dgnc - adds dgnc digi driver

lidza.louina at gmail.com lidza.louina at gmail.com
Thu Aug 1 18:03:13 UTC 2013


This patch adds the DGNC driver. This is a TTY Serial Port
Driver for the Digi International Neo and Classic PCI based product
line by Digi International <http://www.digi.com>.

Signed-off-by: Lidza Louina <lidza.louina at gmail.com>
---
 drivers/staging/dgnc/Kconfig        |    6 +
 drivers/staging/dgnc/Makefile       |    7 +
 drivers/staging/dgnc/Makefile.inc   |  133 ++
 drivers/staging/dgnc/dgnc_cls.c     | 1412 ++++++++++++++
 drivers/staging/dgnc/dgnc_cls.h     |   90 +
 drivers/staging/dgnc/dgnc_driver.c  | 1028 ++++++++++
 drivers/staging/dgnc/dgnc_driver.h  |  566 ++++++
 drivers/staging/dgnc/dgnc_kcompat.h |   91 +
 drivers/staging/dgnc/dgnc_mgmt.c    |  313 +++
 drivers/staging/dgnc/dgnc_mgmt.h    |   37 +
 drivers/staging/dgnc/dgnc_neo.c     | 1977 +++++++++++++++++++
 drivers/staging/dgnc/dgnc_neo.h     |  157 ++
 drivers/staging/dgnc/dgnc_pci.h     |   77 +
 drivers/staging/dgnc/dgnc_proc.c    | 1551 +++++++++++++++
 drivers/staging/dgnc/dgnc_proc.h    |  147 ++
 drivers/staging/dgnc/dgnc_sysfs.c   |  761 ++++++++
 drivers/staging/dgnc/dgnc_sysfs.h   |   49 +
 drivers/staging/dgnc/dgnc_trace.c   |  187 ++
 drivers/staging/dgnc/dgnc_trace.h   |   45 +
 drivers/staging/dgnc/dgnc_tty.c     | 3648 +++++++++++++++++++++++++++++++++++
 drivers/staging/dgnc/dgnc_tty.h     |   42 +
 drivers/staging/dgnc/dgnc_types.h   |   36 +
 drivers/staging/dgnc/digi.h         |  419 ++++
 drivers/staging/dgnc/dpacompat.h    |  115 ++
 24 files changed, 12894 insertions(+)
 create mode 100644 drivers/staging/dgnc/Kconfig
 create mode 100644 drivers/staging/dgnc/Makefile
 create mode 100644 drivers/staging/dgnc/Makefile.inc
 create mode 100644 drivers/staging/dgnc/dgnc_cls.c
 create mode 100644 drivers/staging/dgnc/dgnc_cls.h
 create mode 100644 drivers/staging/dgnc/dgnc_driver.c
 create mode 100644 drivers/staging/dgnc/dgnc_driver.h
 create mode 100644 drivers/staging/dgnc/dgnc_kcompat.h
 create mode 100644 drivers/staging/dgnc/dgnc_mgmt.c
 create mode 100644 drivers/staging/dgnc/dgnc_mgmt.h
 create mode 100644 drivers/staging/dgnc/dgnc_neo.c
 create mode 100644 drivers/staging/dgnc/dgnc_neo.h
 create mode 100644 drivers/staging/dgnc/dgnc_pci.h
 create mode 100644 drivers/staging/dgnc/dgnc_proc.c
 create mode 100644 drivers/staging/dgnc/dgnc_proc.h
 create mode 100644 drivers/staging/dgnc/dgnc_sysfs.c
 create mode 100644 drivers/staging/dgnc/dgnc_sysfs.h
 create mode 100644 drivers/staging/dgnc/dgnc_trace.c
 create mode 100644 drivers/staging/dgnc/dgnc_trace.h
 create mode 100644 drivers/staging/dgnc/dgnc_tty.c
 create mode 100644 drivers/staging/dgnc/dgnc_tty.h
 create mode 100644 drivers/staging/dgnc/dgnc_types.h
 create mode 100644 drivers/staging/dgnc/digi.h
 create mode 100644 drivers/staging/dgnc/dpacompat.h

diff --git a/drivers/staging/dgnc/Kconfig b/drivers/staging/dgnc/Kconfig
new file mode 100644
index 0000000..23daaa5
--- /dev/null
+++ b/drivers/staging/dgnc/Kconfig
@@ -0,0 +1,6 @@
+config DGNC
+       tristate "Digi Neo and Classic PCI Products"
+       default n
+       depends on TTY
+       ---help---
+       Driver for the Digi International Neo and Classic PCI based product line.
diff --git a/drivers/staging/dgnc/Makefile b/drivers/staging/dgnc/Makefile
new file mode 100644
index 0000000..c4c96dc
--- /dev/null
+++ b/drivers/staging/dgnc/Makefile
@@ -0,0 +1,7 @@
+EXTRA_CFLAGS += -DDG_NAME=\"dgnc-1.3-16\" -DDG_PART=\"40002369_F\"
+
+obj-$(CONFIG_DGNC) += dgnc.o
+
+dgnc-objs :=   dgnc_cls.o dgnc_driver.o\
+               dgnc_mgmt.o dgnc_neo.o\
+               dgnc_proc.o dgnc_trace.o dgnc_tty.o dgnc_sysfs.o
diff --git a/drivers/staging/dgnc/Makefile.inc b/drivers/staging/dgnc/Makefile.inc
new file mode 100644
index 0000000..6ca38c7
--- /dev/null
+++ b/drivers/staging/dgnc/Makefile.inc
@@ -0,0 +1,133 @@
+#
+# From Makefile.inc
+#
+
+#
+# Common definitions go here.
+#
+
+#
+# TRUE_VERSION is the version string used in the driver build,
+# it is intended to be in the form:
+#
+#   2.0-0
+#
+# A string noting a particular special modification could be
+# used as well.  This string will be reported when the driver
+# is loaded, and will be exposed by its /proc/dgnc/info
+# interface.
+#
+TRUE_VERSION="1.3-16"
+
+#
+# DGNC_PART_NUM is the part number string for the driver package.
+# It should be in the form:
+#
+#   40002369_A
+#
+DGNC_PART_NUM=40002369_F
+
+#
+# DGNC_REL_NOTE is the part number string for the driver release
+# notes.  It should be in the form:
+#
+#   93000517_A
+#
+DGNC_REL_NOTE=93000517_F
+
+#
+# DGNC_PKG_VER is the "version" number string included in the
+# various documentation and packaging files.  It should be
+# in the form:
+#
+#   1.0
+#
+DGNC_PKG_VER=1.3
+
+#
+# DGNC_PKG_REV is the "revision" of this version.  Together,
+# a linux module revision is built with:
+#
+#   ${DGNC_PKG_VER}-${DGNC_PKG_REV}
+#
+DGNC_PKG_REV=16
+
+#
+# DRP_PKG_DATE is the "date" string included in (for now) the
+# release notes.  It should be in the form:
+#
+#   11/04/2003
+#
+DGNC_PKG_DATE=10/17/2008
+
+INIT_DIR= $(shell \
+	if [ -d /etc/rc.d/init.d ]; \
+	then echo "$(RPM_BUILD_ROOT)/etc/rc.d/init.d"; \
+	else echo "$(RPM_BUILD_ROOT)/etc/init.d"; fi)
+
+#
+#	Miscelaneous path macro's
+#
+
+PACKAGE=	dgnc
+DEVDIR=		/dev/dg/$(PACKAGE)
+SRCDIR=		/usr/src/dg/$(PACKAGE)
+BINDIR=		/usr/bin
+DRVLIBDIR=	/etc/$(PACKAGE)
+MANDIR=		/usr/man
+USRLIBDIR=	/usr/lib
+DGNCDIR=	/etc/dgnc
+
+
+INIT_DIR= $(shell \
+	if [ -d /etc/rc.d/init.d ]; \
+	then echo "/etc/rc.d/init.d"; \
+	else echo "/etc/init.d"; fi)
+
+
+#
+# From Makefile
+#
+ifndef MYPWD
+MYPWD = $(shell pwd)
+endif
+
+ifeq ($(KERNDIR),)
+  KERNVERS       := $(shell uname -r)
+  KERNDIR         :=/lib/modules/${KERNVERS}/
+endif
+
+# Grab version and other important stuff
+
+RPMNAME := $(PACKAGE)-$(TRUE_VERSION)
+
+PARTNUM := $(DGNC_PART_NUM)
+
+RELNOTES := $(DGNC_REL_NOTE)
+
+MODDIR = $(shell echo $(BUILDROOT)/lib/modules/3.4.36-lcrs/misc)
+LSMOD = /sbin/lsmod
+RMMOD = /sbin/rmmod
+INSMOD = /sbin/insmod
+NEW_TTY_LOCKING = No
+NEW_TTY_BUFFERING = No
+REGISTER_TTYS_WITH_SYSFS = No
+
+# Send in some extra things...
+EXTRA_CFLAGS += -I${MYPWD} -I${MYPWD}/include -I${MYPWD}/../../commoninc\
+	-I${MYPWD}/../../dpa -DLINUX -DDG_NAME=\"$(RPMNAME)\"\
+	-DDG_PART=\"$(PARTNUM)\" -DDGNC_TRACER
+
+ifeq ($(NEW_TTY_LOCKING),Yes)
+	EXTRA_CFLAGS += -DNEW_TTY_LOCKING
+endif
+
+ifeq ($(NEW_TTY_BUFFERING),Yes)
+	EXTRA_CFLAGS += -DNEW_TTY_BUFFERING
+endif
+
+ifeq ($(REGISTER_TTYS_WITH_SYSFS),Yes)
+	EXTRA_CFLAGS += -DREGISTER_TTYS_WITH_SYSFS
+endif
+
+# Conform to correct kbuild conventions...
diff --git a/drivers/staging/dgnc/dgnc_cls.c b/drivers/staging/dgnc/dgnc_cls.c
new file mode 100644
index 0000000..83ded18
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_cls.c
@@ -0,0 +1,1412 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
+ *	Thank you.
+ *
+ *
+ * $Id: dgnc_cls.c,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
+#include <linux/delay.h>	/* For udelay */
+#include <asm/io.h>		/* For read[bwl]/write[bwl] */
+#include <linux/serial.h>	/* For struct async_serial */
+#include <linux/serial_reg.h>	/* For the various UART offsets */
+#include <linux/pci.h>
+
+#include "dgnc_driver.h"	/* Driver main header file */
+#include "dgnc_cls.h"
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+
+static inline void cls_parse_isr(struct board_t *brd, uint port);
+static inline void cls_clear_break(struct channel_t *ch, int force);
+static inline void cls_set_cts_flow_control(struct channel_t *ch);
+static inline void cls_set_rts_flow_control(struct channel_t *ch);
+static inline void cls_set_ixon_flow_control(struct channel_t *ch);
+static inline void cls_set_ixoff_flow_control(struct channel_t *ch);
+static inline void cls_set_no_output_flow_control(struct channel_t *ch);
+static inline void cls_set_no_input_flow_control(struct channel_t *ch);
+static void cls_parse_modem(struct channel_t *ch, uchar signals);
+static void cls_tasklet(unsigned long data);
+static void cls_vpd(struct board_t *brd);
+static void cls_uart_init(struct channel_t *ch);
+static void cls_uart_off(struct channel_t *ch);
+static int cls_drain(struct tty_struct *tty, uint seconds);
+static void cls_param(struct tty_struct *tty);
+static void cls_assert_modem_signals(struct channel_t *ch);
+static void cls_flush_uart_write(struct channel_t *ch);
+static void cls_flush_uart_read(struct channel_t *ch);
+static void cls_disable_receiver(struct channel_t *ch);
+static void cls_enable_receiver(struct channel_t *ch);
+static void cls_send_break(struct channel_t *ch, int msecs);
+static void cls_send_start_character(struct channel_t *ch);
+static void cls_send_stop_character(struct channel_t *ch);
+static void cls_copy_data_from_uart_to_queue(struct channel_t *ch);
+static void cls_copy_data_from_queue_to_uart(struct channel_t *ch);
+static uint cls_get_uart_bytes_left(struct channel_t *ch);
+static void cls_send_immediate_char(struct channel_t *ch, unsigned char);
+static irqreturn_t cls_intr(int irq, void *voidbrd);
+
+struct board_ops dgnc_cls_ops = {
+	.tasklet =			cls_tasklet,
+	.intr =				cls_intr,
+	.uart_init =			cls_uart_init,
+	.uart_off =			cls_uart_off,
+	.drain =			cls_drain,
+	.param =			cls_param,
+	.vpd =				cls_vpd,
+	.assert_modem_signals =		cls_assert_modem_signals,
+	.flush_uart_write =		cls_flush_uart_write,
+	.flush_uart_read =		cls_flush_uart_read,
+	.disable_receiver =		cls_disable_receiver,
+	.enable_receiver =		cls_enable_receiver,
+	.send_break =			cls_send_break,
+	.send_start_character =		cls_send_start_character,
+	.send_stop_character =		cls_send_stop_character,
+	.copy_data_from_queue_to_uart = cls_copy_data_from_queue_to_uart,
+	.get_uart_bytes_left =		cls_get_uart_bytes_left,
+	.send_immediate_char =		cls_send_immediate_char
+};
+
+
+static inline void cls_set_cts_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Setting CTSFLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn on CTS flow control, turn off IXON flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_CTSDSR);
+	isr_fcr &= ~(UART_EXAR654_EFR_IXON);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Enable interrupts for CTS flow, turn off interrupts for received XOFF chars */
+	ier |= (UART_EXAR654_IER_CTSDSR);
+	ier &= ~(UART_EXAR654_IER_XOFF);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+	ch->ch_t_tlevel = 16;
+
+}
+
+
+static inline void cls_set_ixon_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Setting IXON FLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn on IXON flow control, turn off CTS flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXON);
+	isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Now set our current start/stop chars while in enhanced mode */
+	writeb(ch->ch_startc, &ch->ch_cls_uart->mcr);
+	writeb(0, &ch->ch_cls_uart->lsr);
+	writeb(ch->ch_stopc, &ch->ch_cls_uart->msr);
+	writeb(0, &ch->ch_cls_uart->spr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Disable interrupts for CTS flow, turn on interrupts for received XOFF chars */
+	ier &= ~(UART_EXAR654_IER_CTSDSR);
+	ier |= (UART_EXAR654_IER_XOFF);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+}
+
+
+static inline void cls_set_no_output_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Unsetting Output FLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn off IXON flow control, turn off CTS flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB);
+	isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR | UART_EXAR654_EFR_IXON);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Disable interrupts for CTS flow, turn off interrupts for received XOFF chars */
+	ier &= ~(UART_EXAR654_IER_CTSDSR);
+	ier &= ~(UART_EXAR654_IER_XOFF);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+	ch->ch_r_watermark = 0;
+        ch->ch_t_tlevel = 16;
+        ch->ch_r_tlevel = 16;
+
+}
+
+
+static inline void cls_set_rts_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Setting RTSFLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn on RTS flow control, turn off IXOFF flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_RTSDTR);
+	isr_fcr &= ~(UART_EXAR654_EFR_IXOFF);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Enable interrupts for RTS flow */
+	ier |= (UART_EXAR654_IER_RTSDTR);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+
+	ch->ch_r_watermark = 4;
+	ch->ch_r_tlevel = 8;
+
+}
+
+
+static inline void cls_set_ixoff_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Setting IXOFF FLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn on IXOFF flow control, turn off RTS flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXOFF);
+	isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Now set our current start/stop chars while in enhanced mode */
+	writeb(ch->ch_startc, &ch->ch_cls_uart->mcr);
+	writeb(0, &ch->ch_cls_uart->lsr);
+	writeb(ch->ch_stopc, &ch->ch_cls_uart->msr);
+	writeb(0, &ch->ch_cls_uart->spr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Disable interrupts for RTS flow */
+	ier &= ~(UART_EXAR654_IER_RTSDTR);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+}
+
+
+static inline void cls_set_no_input_flow_control(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar ier = readb(&ch->ch_cls_uart->ier);
+	uchar isr_fcr = 0;
+
+	DPR_PARAM(("Unsetting Input FLOW\n"));
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn off IXOFF flow control, turn off RTS flow control */
+	isr_fcr |= (UART_EXAR654_EFR_ECB);
+	isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR | UART_EXAR654_EFR_IXOFF);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+	/* Disable interrupts for RTS flow */
+	ier &= ~(UART_EXAR654_IER_RTSDTR);
+	writeb(ier, &ch->ch_cls_uart->ier);
+
+	/* Set the usual FIFO values */
+	writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+		UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+		&ch->ch_cls_uart->isr_fcr);
+
+        ch->ch_t_tlevel = 16;
+        ch->ch_r_tlevel = 16;
+
+}
+
+
+/*
+ * cls_clear_break.
+ * Determines whether its time to shut off break condition.
+ *
+ * No locks are assumed to be held when calling this function.
+ * channel lock is held and released in this function.
+ */
+static inline void cls_clear_break(struct channel_t *ch, int force)
+{
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* Bail if we aren't currently sending a break. */
+	if (!ch->ch_stop_sending_break) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/* Turn break off, and unset some variables */
+	if (ch->ch_flags & CH_BREAK_SENDING) {
+		if ((jiffies >= ch->ch_stop_sending_break) || force) {
+			uchar temp = readb(&ch->ch_cls_uart->lcr);
+        	        writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+			ch->ch_flags &= ~(CH_BREAK_SENDING);
+			ch->ch_stop_sending_break = 0;
+			DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+		}
+	}
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/* Parse the ISR register for the specific port */
+static inline void cls_parse_isr(struct board_t *brd, uint port)
+{
+	struct channel_t *ch;
+	uchar isr = 0;
+	ulong lock_flags;
+
+	/*
+	 * No need to verify board pointer, it was already
+	 * verified in the interrupt routine.
+	 */
+
+	if (port > brd->nasync)
+		return;
+
+	ch = brd->channels[port];
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	/* Here we try to figure out what caused the interrupt to happen */
+	while (1) {
+
+		isr = readb(&ch->ch_cls_uart->isr_fcr);
+
+		/* Bail if no pending interrupt on port */
+		if (isr & UART_IIR_NO_INT)  {
+			break;
+		}
+
+		DPR_INTR(("%s:%d port: %x isr: %x\n", __FILE__, __LINE__, port, isr));
+
+		/* Receive Interrupt pending */
+		if (isr & (UART_IIR_RDI | UART_IIR_RDI_TIMEOUT)) {
+			/* Read data from uart -> queue */
+			brd->intr_rx++;
+			ch->ch_intr_rx++;
+			cls_copy_data_from_uart_to_queue(ch);
+			dgnc_check_queue_flow_control(ch);
+		}
+
+		/* Transmit Hold register empty pending */
+		if (isr & UART_IIR_THRI) {
+			/* Transfer data (if any) from Write Queue -> UART. */
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+			ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+			brd->intr_tx++;
+			ch->ch_intr_tx++;
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			cls_copy_data_from_queue_to_uart(ch);
+		}
+
+		/* Received Xoff signal/Special character */
+		if (isr & UART_IIR_XOFF) {
+			/* Empty */
+		}
+
+		/* CTS/RTS change of state */
+		if (isr & UART_IIR_CTSRTS) {
+			brd->intr_modem++;
+			ch->ch_intr_modem++;
+			/*
+			 * Don't need to do anything, the cls_parse_modem
+			 * below will grab the updated modem signals.
+			 */
+		}
+
+		/* Parse any modem signal changes */
+		DPR_INTR(("MOD_STAT: sending to parse_modem_sigs\n"));
+		cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
+	}
+}
+
+
+/*
+ * cls_param()
+ * Send any/all changes to the line to the UART.
+ */
+static void cls_param(struct tty_struct *tty)
+{
+	uchar lcr = 0;
+	uchar uart_lcr = 0;
+	uchar ier = 0;
+	uchar uart_ier = 0;
+        uint baud = 9600;
+	int quot = 0;
+        struct board_t *bd;
+	struct channel_t *ch;
+        struct un_t   *un;
+
+	if (!tty || tty->magic != TTY_MAGIC) {
+		return;
+	}
+
+	un = (struct un_t *) tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC) {
+		return;
+	}
+
+	ch = un->un_ch;   
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+		return;
+	}
+
+	DPR_PARAM(("param start: tdev: %x cflags: %x oflags: %x iflags: %x\n",
+		ch->ch_tun.un_dev, ch->ch_c_cflag, ch->ch_c_oflag, ch->ch_c_iflag));
+
+	/*
+	 * If baud rate is zero, flush queues, and set mval to drop DTR.
+	 */
+	if ((ch->ch_c_cflag & (CBAUD)) == 0) {
+		ch->ch_r_head = ch->ch_r_tail = 0;
+		ch->ch_e_head = ch->ch_e_tail = 0;
+		ch->ch_w_head = ch->ch_w_tail = 0;
+
+		cls_flush_uart_write(ch);
+                cls_flush_uart_read(ch);
+
+		/* The baudrate is B0 so all modem lines are to be dropped. */
+		ch->ch_flags |= (CH_BAUD0);
+		ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR);
+		cls_assert_modem_signals(ch);
+		ch->ch_old_baud = 0;
+		return;
+	} else if (ch->ch_custom_speed) {
+
+		baud = ch->ch_custom_speed;
+		/* Handle transition from B0 */
+		if (ch->ch_flags & CH_BAUD0) {
+			ch->ch_flags &= ~(CH_BAUD0);
+
+			/*
+			 * Bring back up RTS and DTR...
+			 * Also handle RTS or DTR toggle if set.
+			 */
+			if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_RTS);
+			if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_DTR);
+		}
+
+	} else {
+		int iindex = 0;
+		int jindex = 0;
+
+		ulong bauds[4][16] = {
+			{ /* slowbaud */
+				0,      50,     75,     110,
+				134,    150,    200,    300,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* slowbaud & CBAUDEX */
+				0,      57600,  115200, 230400,
+				460800, 150,    200,    921600,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* fastbaud */
+				0,      57600,   76800, 115200,
+				131657, 153600, 230400, 460800,
+				921600, 1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* fastbaud & CBAUDEX */
+				0,      57600,  115200, 230400,
+				460800, 150,    200,    921600,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 }
+		};
+
+		/* Only use the TXPrint baud rate if the terminal unit is NOT open */
+		if (!(ch->ch_tun.un_flags & UN_ISOPEN) && (un->un_type == DGNC_PRINT))
+			baud = C_BAUD(ch->ch_pun.un_tty) & 0xff;
+		else
+			baud = C_BAUD(ch->ch_tun.un_tty) & 0xff;
+
+		if (ch->ch_c_cflag & CBAUDEX)
+			iindex = 1;
+
+		if (ch->ch_digi.digi_flags & DIGI_FAST)
+			iindex += 2;
+
+		jindex = baud;
+
+		if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && (jindex < 16)) {
+			baud = bauds[iindex][jindex];  
+		} else {
+			DPR_IOCTL(("baud indices were out of range (%d)(%d)",
+				iindex, jindex));
+			baud = 0;
+		}
+
+		if (baud == 0)
+			baud = 9600;
+
+		/* Handle transition from B0 */
+		if (ch->ch_flags & CH_BAUD0) {
+			ch->ch_flags &= ~(CH_BAUD0);
+
+			/*
+			 * Bring back up RTS and DTR...
+			 * Also handle RTS or DTR toggle if set.
+			 */
+			if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_RTS);
+			if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_DTR);
+		}
+	}
+
+	if (ch->ch_c_cflag & PARENB) {
+		lcr |= UART_LCR_PARITY;
+	}
+
+	if (!(ch->ch_c_cflag & PARODD)) {
+		lcr |= UART_LCR_EPAR;
+	}
+
+	/*
+	 * Not all platforms support mark/space parity,
+	 * so this will hide behind an ifdef.
+	 */
+#ifdef CMSPAR
+	if (ch->ch_c_cflag & CMSPAR)
+		lcr |= UART_LCR_SPAR;    
+#endif
+
+	if (ch->ch_c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+
+	switch (ch->ch_c_cflag & CSIZE) {
+	case CS5:
+		lcr |= UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr |= UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr |= UART_LCR_WLEN7;
+		break;
+	case CS8:
+	default:
+		lcr |= UART_LCR_WLEN8;
+		break;
+	}
+
+	ier = uart_ier = readb(&ch->ch_cls_uart->ier);
+	uart_lcr = readb(&ch->ch_cls_uart->lcr);
+
+	if (baud == 0)
+		baud = 9600;
+
+	quot = ch->ch_bd->bd_dividend / baud;
+
+	if (quot != 0 && ch->ch_old_baud != baud) {
+		ch->ch_old_baud = baud;
+		writeb(UART_LCR_DLAB, &ch->ch_cls_uart->lcr);
+		writeb((quot & 0xff), &ch->ch_cls_uart->txrx);
+		writeb((quot >> 8), &ch->ch_cls_uart->ier);
+		writeb(lcr, &ch->ch_cls_uart->lcr);
+        }
+
+	if (uart_lcr != lcr)
+		writeb(lcr, &ch->ch_cls_uart->lcr);
+
+	if (ch->ch_c_cflag & CREAD) {
+		ier |= (UART_IER_RDI | UART_IER_RLSI);
+	}       
+	else {
+		ier &= ~(UART_IER_RDI | UART_IER_RLSI);
+	}
+
+	/*
+	 * Have the UART interrupt on modem signal changes ONLY when
+	 * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set.
+	 */
+	if ((ch->ch_digi.digi_flags & CTSPACE) || (ch->ch_digi.digi_flags & RTSPACE) ||
+		(ch->ch_c_cflag & CRTSCTS) || !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+		!(ch->ch_c_cflag & CLOCAL))
+	{
+		ier |= UART_IER_MSI;
+	}
+	else {
+		ier &= ~UART_IER_MSI;
+	}
+
+	ier |= UART_IER_THRI;
+
+	if (ier != uart_ier)
+		writeb(ier, &ch->ch_cls_uart->ier);
+
+	if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+		cls_set_cts_flow_control(ch);
+	}
+	else if (ch->ch_c_iflag & IXON) {
+		/* If start/stop is set to disable, then we should disable flow control */
+		if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+			cls_set_no_output_flow_control(ch);
+		else
+			cls_set_ixon_flow_control(ch);
+	}
+	else {
+		cls_set_no_output_flow_control(ch);
+	}
+
+	if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+		cls_set_rts_flow_control(ch);
+	}
+	else if (ch->ch_c_iflag & IXOFF) {
+		/* If start/stop is set to disable, then we should disable flow control */
+		if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+			cls_set_no_input_flow_control(ch);
+		else
+			cls_set_ixoff_flow_control(ch);
+	} 
+	else {
+		cls_set_no_input_flow_control(ch);
+	}
+
+	cls_assert_modem_signals(ch);
+
+	/* Get current status of the modem signals now */
+	cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
+}
+
+
+/*
+ * Our board poller function.
+ */
+static void cls_tasklet(unsigned long data)
+{
+        struct board_t *bd = (struct board_t *) data;
+	struct channel_t *ch;
+	ulong  lock_flags;
+	int i;
+	int state = 0;
+	int ports = 0;
+
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+		APR(("poll_tasklet() - NULL or bad bd.\n"));
+		return;
+	}
+
+	/* Cache a couple board values */
+	DGNC_LOCK(bd->bd_lock, lock_flags);
+	state = bd->state;
+	ports = bd->nasync;
+	DGNC_UNLOCK(bd->bd_lock, lock_flags);
+
+	/*
+	 * Do NOT allow the interrupt routine to read the intr registers
+	 * Until we release this lock.
+	 */
+	DGNC_LOCK(bd->bd_intr_lock, lock_flags);
+
+	/*
+	 * If board is ready, parse deeper to see if there is anything to do.
+	 */
+	if ((state == BOARD_READY) && (ports > 0)) {
+
+		/* Loop on each port */
+		for (i = 0; i < ports; i++) {
+			ch = bd->channels[i];
+			if (!ch)
+				continue;
+
+			/*
+			 * NOTE: Remember you CANNOT hold any channel
+			 * locks when calling input.
+			 * During input processing, its possible we
+			 * will call ld, which might do callbacks back
+			 * into us.
+			 */
+			dgnc_input(ch);
+
+			/*
+			 * Channel lock is grabbed and then released
+			 * inside this routine.
+			 */
+			cls_copy_data_from_queue_to_uart(ch);
+			dgnc_wakeup_writes(ch);
+
+			/*
+			 * Check carrier function.
+			 */
+			dgnc_carrier(ch);
+
+			/*
+			 * The timing check of turning off the break is done
+			 * inside clear_break()
+			 */
+			if (ch->ch_stop_sending_break)
+				cls_clear_break(ch, 0);
+		}
+	}
+
+	DGNC_UNLOCK(bd->bd_intr_lock, lock_flags);
+
+}
+
+
+/*
+ * cls_intr()
+ *
+ * Classic specific interrupt handler.
+ */
+static irqreturn_t cls_intr(int irq, void *voidbrd)
+{
+	struct board_t *brd = (struct board_t *) voidbrd;
+	uint i = 0;
+	uchar poll_reg;
+	unsigned long lock_flags;
+
+	if (!brd) {
+		APR(("Received interrupt (%d) with null board associated\n", irq));
+		return IRQ_NONE;
+	}
+
+	/*
+	 * Check to make sure its for us.
+	 */
+	if (brd->magic != DGNC_BOARD_MAGIC) {
+		APR(("Received interrupt (%d) with a board pointer that wasn't ours!\n", irq));
+		return IRQ_NONE;
+	}
+
+	DGNC_LOCK(brd->bd_intr_lock, lock_flags);
+
+	brd->intr_count++;
+
+	/*
+	 * Check the board's global interrupt offset to see if we 
+	 * we actually do have an interrupt pending for us.
+	 */
+	poll_reg = readb(brd->re_map_membase + UART_CLASSIC_POLL_ADDR_OFFSET);
+
+	/* If 0, no interrupts pending */
+	if (!poll_reg) {
+		DPR_INTR(("Kernel interrupted to me, but no pending interrupts...\n"));
+		DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+		return IRQ_NONE;
+	}
+
+	DPR_INTR(("%s:%d poll_reg: %x\n", __FILE__, __LINE__, poll_reg));
+
+	/* Parse each port to find out what caused the interrupt */
+	for (i = 0; i < brd->nasync; i++) {
+		cls_parse_isr(brd, i);
+	}
+
+	/*
+	 * Schedule tasklet to more in-depth servicing at a better time.
+	 */
+	tasklet_schedule(&brd->helper_tasklet);
+
+	DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+
+	DPR_INTR(("dgnc_intr finish.\n"));
+	return IRQ_HANDLED;
+}
+
+
+static void cls_disable_receiver(struct channel_t *ch)
+{
+	uchar tmp = readb(&ch->ch_cls_uart->ier);
+	tmp &= ~(UART_IER_RDI);
+	writeb(tmp, &ch->ch_cls_uart->ier);
+}
+
+
+static void cls_enable_receiver(struct channel_t *ch)
+{
+	uchar tmp = readb(&ch->ch_cls_uart->ier);
+	tmp |= (UART_IER_RDI);
+	writeb(tmp, &ch->ch_cls_uart->ier);
+}
+
+
+static void cls_copy_data_from_uart_to_queue(struct channel_t *ch)
+{
+        int qleft = 0;
+        uchar linestatus = 0;
+	uchar error_mask = 0;
+	ushort head;
+	ushort tail;
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* cache head and tail of queue */
+	head = ch->ch_r_head;
+	tail = ch->ch_r_tail;
+
+	/* Store how much space we have left in the queue */
+	if ((qleft = tail - head - 1) < 0)
+		qleft += RQUEUEMASK + 1;
+
+	/*
+	 * Create a mask to determine whether we should
+	 * insert the character (if any) into our queue.
+	 */
+	if (ch->ch_c_iflag & IGNBRK)
+		error_mask |= UART_LSR_BI;
+
+	while (1) {
+		linestatus = readb(&ch->ch_cls_uart->lsr);
+
+		if (!(linestatus & (UART_LSR_DR)))
+			break;
+
+		/*
+		 * Discard character if we are ignoring the error mask.
+		*/
+		if (linestatus & error_mask)  {
+			uchar discard;
+			linestatus = 0;
+			discard = readb(&ch->ch_cls_uart->txrx);
+			continue;
+		}
+
+		/*
+		 * If our queue is full, we have no choice but to drop some data.
+		 * The assumption is that HWFLOW or SWFLOW should have stopped
+		 * things way way before we got to this point.
+		 *
+		 * I decided that I wanted to ditch the oldest data first,
+		 * I hope thats okay with everyone? Yes? Good.
+		 */
+		while (qleft < 1) {
+			DPR_READ(("Queue full, dropping DATA:%x LSR:%x\n",
+				ch->ch_rqueue[tail], ch->ch_equeue[tail]));
+
+			ch->ch_r_tail = tail = (tail + 1) & RQUEUEMASK;
+			ch->ch_err_overrun++;
+			qleft++;
+		}
+
+		ch->ch_equeue[head] = linestatus & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE);
+		ch->ch_rqueue[head] = readb(&ch->ch_cls_uart->txrx);
+		dgnc_sniff_nowait_nolock(ch, "UART READ", ch->ch_rqueue + head, 1);
+
+		qleft--;
+
+		DPR_READ(("DATA/LSR pair: %x %x\n", ch->ch_rqueue[head], ch->ch_equeue[head]));
+
+		if (ch->ch_equeue[head] & UART_LSR_PE)
+			ch->ch_err_parity++;
+		if (ch->ch_equeue[head] & UART_LSR_BI)
+			ch->ch_err_break++;
+		if (ch->ch_equeue[head] & UART_LSR_FE)
+			ch->ch_err_frame++;
+
+		/* Add to, and flip head if needed */
+		head = (head + 1) & RQUEUEMASK;
+		ch->ch_rxcount++;
+	}
+
+	/*
+	 * Write new final heads to channel structure.
+	 */
+	ch->ch_r_head = head & RQUEUEMASK;
+	ch->ch_e_head = head & EQUEUEMASK;
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * This function basically goes to sleep for secs, or until
+ * it gets signalled that the port has fully drained.
+ */
+static int cls_drain(struct tty_struct *tty, uint seconds)
+{
+	ulong lock_flags;
+	struct channel_t *ch;
+        struct un_t *un;
+	int rc = 0;
+
+	if (!tty || tty->magic != TTY_MAGIC) {
+		return (-ENXIO);
+	}
+
+	un = (struct un_t *) tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC) {
+		return (-ENXIO);
+	}
+
+	ch = un->un_ch;   
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return (-ENXIO);
+	}
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	un->un_flags |= UN_EMPTY;
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	/*
+	 * NOTE: Do something with time passed in.
+	 */
+	rc = wait_event_interruptible(un->un_flags_wait, ((un->un_flags & UN_EMPTY) == 0));
+
+	/* If ret is non-zero, user ctrl-c'ed us */
+	if (rc)
+		DPR_IOCTL(("%d Drain - User ctrl c'ed\n", __LINE__));
+
+        return (rc);
+}
+        
+
+/* Channel lock MUST be held before calling this function! */
+static void cls_flush_uart_write(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT), &ch->ch_cls_uart->isr_fcr);
+	udelay(10);
+
+	ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+}
+
+
+/* Channel lock MUST be held before calling this function! */
+static void cls_flush_uart_read(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	/*
+	 * For complete POSIX compatibility, we should be purging the
+	 * read FIFO in the UART here.
+	 * 
+	 * However, doing the statement below also incorrectly flushes
+	 * write data as well as just basically trashing the FIFO.
+	 *
+	 * I believe this is a BUG in this UART.
+	 * So for now, we will leave the code #ifdef'ed out...
+	 */
+#if 0
+	writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR), &ch->ch_cls_uart->isr_fcr);
+#endif
+	udelay(10);
+}
+
+
+static void cls_copy_data_from_queue_to_uart(struct channel_t *ch)
+{
+	ushort head;
+	ushort tail;
+	int n;
+	int qlen;
+	uint len_written = 0;
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* No data to write to the UART */
+	if (ch->ch_w_tail == ch->ch_w_head) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/* If port is "stopped", don't send any data to the UART */
+	if ((ch->ch_flags & CH_FORCED_STOP) || (ch->ch_flags & CH_BREAK_SENDING)) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	n = 32;
+
+        /* cache head and tail of queue */
+        head = ch->ch_w_head & WQUEUEMASK;
+        tail = ch->ch_w_tail & WQUEUEMASK;
+        qlen = (head - tail) & WQUEUEMASK;
+
+	/* Find minimum of the FIFO space, versus queue length */
+	n = min(n, qlen);
+
+	while (n > 0) {
+
+		/*
+		 * If RTS Toggle mode is on, turn on RTS now if not already set,
+		 * and make sure we get an event when the data transfer has completed.
+		 */
+		if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+			if (!(ch->ch_mostat & UART_MCR_RTS)) {
+				ch->ch_mostat |= (UART_MCR_RTS);
+				cls_assert_modem_signals(ch);
+			}
+			ch->ch_tun.un_flags |= (UN_EMPTY);
+		}
+
+		/*
+		 * If DTR Toggle mode is on, turn on DTR now if not already set,
+		 * and make sure we get an event when the data transfer has completed.
+		 */
+		if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+			if (!(ch->ch_mostat & UART_MCR_DTR)) {
+				ch->ch_mostat |= (UART_MCR_DTR);
+				cls_assert_modem_signals(ch);
+			}
+			ch->ch_tun.un_flags |= (UN_EMPTY);
+		}
+		writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_cls_uart->txrx);
+		dgnc_sniff_nowait_nolock(ch, "UART WRITE", ch->ch_wqueue + ch->ch_w_tail, 1);
+		DPR_WRITE(("Tx data: %x\n", ch->ch_wqueue[ch->ch_w_tail]));
+		ch->ch_w_tail++;
+		ch->ch_w_tail &= WQUEUEMASK;
+		ch->ch_txcount++;
+		len_written++;
+		n--;
+	}
+
+	if (len_written > 0)
+		ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	return;
+}
+
+
+static void cls_parse_modem(struct channel_t *ch, uchar signals)
+{
+	volatile uchar msignals = signals;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DPR_MSIGS(("cls_parse_modem: port: %d signals: %d\n", ch->ch_portnum, msignals));
+
+	/*
+	 * Do altpin switching. Altpin switches DCD and DSR.
+	 * This prolly breaks DSRPACE, so we should be more clever here.
+	 */
+	if (ch->ch_digi.digi_flags & DIGI_ALTPIN) {
+		uchar mswap = signals;
+		if (mswap & UART_MSR_DDCD) {
+			msignals &= ~UART_MSR_DDCD;
+			msignals |= UART_MSR_DDSR;
+		}
+		if (mswap & UART_MSR_DDSR) {
+			msignals &= ~UART_MSR_DDSR;
+			msignals |= UART_MSR_DDCD;
+		}
+		if (mswap & UART_MSR_DCD) {
+			msignals &= ~UART_MSR_DCD;
+			msignals |= UART_MSR_DSR;
+		}
+		if (mswap & UART_MSR_DSR) {
+			msignals &= ~UART_MSR_DSR;
+			msignals |= UART_MSR_DCD;
+		}
+	}
+                        
+	/* Scrub off lower bits. They signify delta's, which I don't care about */
+	signals &= 0xf0;
+
+	if (msignals & UART_MSR_DCD)
+		ch->ch_mistat |= UART_MSR_DCD;
+	else
+		ch->ch_mistat &= ~UART_MSR_DCD;
+
+	if (msignals & UART_MSR_DSR)
+		ch->ch_mistat |= UART_MSR_DSR;
+	else
+		ch->ch_mistat &= ~UART_MSR_DSR;
+
+	if (msignals & UART_MSR_RI)
+		ch->ch_mistat |= UART_MSR_RI;
+	else
+		ch->ch_mistat &= ~UART_MSR_RI;
+
+	if (msignals & UART_MSR_CTS)
+		ch->ch_mistat |= UART_MSR_CTS;
+	else
+		ch->ch_mistat &= ~UART_MSR_CTS;
+
+
+	DPR_MSIGS(("Port: %d DTR: %d RTS: %d CTS: %d DSR: %d " "RI: %d CD: %d\n",
+		ch->ch_portnum,
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_DTR),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_RTS),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_CTS), 
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DSR), 
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_RI),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DCD)));
+}
+
+
+/* Make the UART raise any of the output signals we want up */
+static void cls_assert_modem_signals(struct channel_t *ch)
+{
+	uchar out;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	out = ch->ch_mostat;
+
+	if (ch->ch_flags & CH_LOOPBACK)
+		out |= UART_MCR_LOOP;
+
+        writeb(out, &ch->ch_cls_uart->mcr);
+
+	/* Give time for the UART to actually drop the signals */
+	udelay(10);
+}
+
+
+static void cls_send_start_character(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	if (ch->ch_startc != _POSIX_VDISABLE) {
+		ch->ch_xon_sends++;
+		writeb(ch->ch_startc, &ch->ch_cls_uart->txrx);
+        }
+}
+
+
+static void cls_send_stop_character(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	if (ch->ch_stopc != _POSIX_VDISABLE) {
+		ch->ch_xoff_sends++;
+		writeb(ch->ch_stopc, &ch->ch_cls_uart->txrx);
+        }
+}
+
+
+/* Inits UART */
+static void cls_uart_init(struct channel_t *ch)
+{
+	uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+	uchar isr_fcr = 0;
+
+	writeb(0, &ch->ch_cls_uart->ier);
+
+	/*
+	 * The Enhanced Register Set may only be accessed when
+	 * the Line Control Register is set to 0xBFh.
+	 */
+	writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+	isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+	
+	/* Turn on Enhanced/Extended controls */
+	isr_fcr |= (UART_EXAR654_EFR_ECB);
+
+	writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+	/* Write old LCR value back out, which turns enhanced access off */
+	writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+        /* Clear out UART and FIFO */
+	readb(&ch->ch_cls_uart->txrx);
+
+	writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), &ch->ch_cls_uart->isr_fcr);
+	udelay(10);
+
+	ch->ch_flags |= (CH_FIFO_ENABLED | CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+
+	readb(&ch->ch_cls_uart->lsr);
+	readb(&ch->ch_cls_uart->msr);
+}
+
+
+/*
+ * Turns off UART.
+ */
+static void cls_uart_off(struct channel_t *ch)
+{
+	writeb(0, &ch->ch_cls_uart->ier);
+}
+
+
+/*
+ * cls_get_uarts_bytes_left.
+ * Returns 0 is nothing left in the FIFO, returns 1 otherwise.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static uint cls_get_uart_bytes_left(struct channel_t *ch)
+{
+	uchar left = 0;
+	uchar lsr = 0;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return 0;
+
+	lsr = readb(&ch->ch_cls_uart->lsr);
+
+	/* Determine whether the Transmitter is empty or not */
+	if (!(lsr & UART_LSR_TEMT)) {
+		if (ch->ch_flags & CH_TX_FIFO_EMPTY) {
+			tasklet_schedule(&ch->ch_bd->helper_tasklet);
+		}
+		left = 1;
+	}
+	else {
+		ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		left = 0;
+	}
+
+	return left;
+}
+
+
+/*
+ * cls_send_break.
+ * Starts sending a break thru the UART.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void cls_send_break(struct channel_t *ch, int msecs)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	/*
+	 * If we receive a time of 0, this means turn off the break.
+	 */
+	if (msecs == 0) {
+		/* Turn break off, and unset some variables */
+		if (ch->ch_flags & CH_BREAK_SENDING) {
+			uchar temp = readb(&ch->ch_cls_uart->lcr);
+			writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+			ch->ch_flags &= ~(CH_BREAK_SENDING);
+			ch->ch_stop_sending_break = 0;
+			DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+		}
+		return;
+        }
+
+	/*
+	 * Set the time we should stop sending the break.
+	 * If we are already sending a break, toss away the existing
+	 * time to stop, and use this new value instead.
+	 */
+	ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs);
+
+	/* Tell the UART to start sending the break */
+	if (!(ch->ch_flags & CH_BREAK_SENDING)) {
+		uchar temp = readb(&ch->ch_cls_uart->lcr);
+		writeb((temp | UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+		ch->ch_flags |= (CH_BREAK_SENDING);
+		DPR_IOCTL(("Port %d. Starting UART_LCR_SBC! start: %lx should end: %lx\n",
+			ch->ch_portnum, jiffies, ch->ch_stop_sending_break));
+	}
+}
+
+
+/*
+ * cls_send_immediate_char.
+ * Sends a specific character as soon as possible to the UART,
+ * jumping over any bytes that might be in the write queue.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void cls_send_immediate_char(struct channel_t *ch, unsigned char c)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	writeb(c, &ch->ch_cls_uart->txrx);
+}
+
+static void cls_vpd(struct board_t *brd) 
+{
+        ulong           vpdbase;        /* Start of io base of the card */
+        uchar           *re_map_vpdbase;/* Remapped memory of the card */
+	int i = 0;
+
+
+	vpdbase = pci_resource_start(brd->pdev, 3);
+
+	/* No VPD */
+	if (!vpdbase)
+		return;
+
+	re_map_vpdbase = ioremap(vpdbase, 0x400);
+
+	if (!re_map_vpdbase)
+		return;
+
+        /* Store the VPD into our buffer */
+        for (i = 0; i < 0x40; i++) {
+		brd->vpd[i] = readb(re_map_vpdbase + i);
+		printk("%x ", brd->vpd[i]);
+        }
+	printk("\n");
+
+	if (re_map_vpdbase)
+		iounmap(re_map_vpdbase);
+}
+
diff --git a/drivers/staging/dgnc/dgnc_cls.h b/drivers/staging/dgnc/dgnc_cls.h
new file mode 100644
index 0000000..dca5ea3
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_cls.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ */
+
+#ifndef __DGNC_CLS_H
+#define __DGNC_CLS_H
+
+#include "dgnc_types.h"
+
+
+/************************************************************************ 
+ * Per channel/port Classic UART structure				*
+ ************************************************************************
+ *		Base Structure Entries Usage Meanings to Host		*
+ *									*
+ *	W = read write		R = read only				* 
+ *			U = Unused.					*
+ ************************************************************************/
+
+struct cls_uart_struct {
+	volatile uchar txrx;		/* WR  RHR/THR - Holding Reg */
+	volatile uchar ier;		/* WR  IER - Interrupt Enable Reg */
+	volatile uchar isr_fcr;		/* WR  ISR/FCR - Interrupt Status Reg/Fifo Control Reg */
+	volatile uchar lcr;		/* WR  LCR - Line Control Reg */
+	volatile uchar mcr;		/* WR  MCR - Modem Control Reg */
+	volatile uchar lsr;		/* WR  LSR - Line Status Reg */
+	volatile uchar msr;		/* WR  MSR - Modem Status Reg */
+	volatile uchar spr;		/* WR  SPR - Scratch Pad Reg */
+};
+
+/* Where to read the interrupt register (8bits) */
+#define	UART_CLASSIC_POLL_ADDR_OFFSET	0x40
+
+#define UART_EXAR654_ENHANCED_REGISTER_SET 0xBF
+
+#define UART_16654_FCR_TXTRIGGER_8	0x0
+#define UART_16654_FCR_TXTRIGGER_16	0x10
+#define UART_16654_FCR_TXTRIGGER_32	0x20
+#define UART_16654_FCR_TXTRIGGER_56	0x30
+
+#define UART_16654_FCR_RXTRIGGER_8	0x0
+#define UART_16654_FCR_RXTRIGGER_16	0x40
+#define UART_16654_FCR_RXTRIGGER_56	0x80
+#define UART_16654_FCR_RXTRIGGER_60     0xC0
+
+#define UART_IIR_XOFF			0x10	/* Received Xoff signal/Special character */
+#define UART_IIR_CTSRTS			0x20	/* Received CTS/RTS change of state */
+#define UART_IIR_RDI_TIMEOUT		0x0C    /* Receiver data TIMEOUT */
+
+/*
+ * These are the EXTENDED definitions for the Exar 654's Interrupt
+ * Enable Register.
+ */
+#define UART_EXAR654_EFR_ECB      0x10    /* Enhanced control bit */
+#define UART_EXAR654_EFR_IXON     0x2     /* Receiver compares Xon1/Xoff1 */
+#define UART_EXAR654_EFR_IXOFF    0x8     /* Transmit Xon1/Xoff1 */
+#define UART_EXAR654_EFR_RTSDTR   0x40    /* Auto RTS/DTR Flow Control Enable */
+#define UART_EXAR654_EFR_CTSDSR   0x80    /* Auto CTS/DSR Flow COntrol Enable */
+
+#define UART_EXAR654_XOFF_DETECT  0x1     /* Indicates whether chip saw an incoming XOFF char  */
+#define UART_EXAR654_XON_DETECT   0x2     /* Indicates whether chip saw an incoming XON char */
+
+#define UART_EXAR654_IER_XOFF     0x20    /* Xoff Interrupt Enable */
+#define UART_EXAR654_IER_RTSDTR   0x40    /* Output Interrupt Enable */
+#define UART_EXAR654_IER_CTSDSR   0x80    /* Input Interrupt Enable */
+
+/* 
+ * Our Global Variables
+ */
+extern struct board_ops dgnc_cls_ops;
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_driver.c b/drivers/staging/dgnc/dgnc_driver.c
new file mode 100644
index 0000000..7c88de7
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_driver.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
+ *	Thank you.
+ *
+ * $Id: dgnc_driver.c,v 1.3 2011/06/23 12:47:35 markh Exp $
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+#include <linux/sched.h>
+#endif
+
+#include "dgnc_driver.h"
+#include "dgnc_pci.h"
+#include "dgnc_proc.h"
+#include "dpacompat.h"
+#include "dgnc_mgmt.h"
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+#include "dgnc_cls.h"
+#include "dgnc_neo.h"
+#include "dgnc_sysfs.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Digi International, http://www.digi.com");
+MODULE_DESCRIPTION("Driver for the Digi International Neo and Classic PCI based product line");
+MODULE_SUPPORTED_DEVICE("dgnc");
+
+/*
+ * insmod command line overrideable parameters
+ *
+ * NOTE: we use a set of macros to create the variables, which allows
+ * us to specify the variable type, name, initial value, and description.
+ */
+PARM_INT(debug,		0x00,		0644,	"Driver debugging level");
+PARM_INT(rawreadok,	1,		0644,	"Bypass flip buffers on input");
+PARM_INT(trcbuf_size,	0x100000,	0644, 	"Debugging trace buffer size.");
+
+/**************************************************************************
+ *
+ * protos for this file
+ *
+ */
+static int		dgnc_start(void);
+static int		dgnc_finalize_board_init(struct board_t *brd);
+static void		dgnc_init_globals(void);
+static int		dgnc_found_board(struct pci_dev *pdev, int id);
+static void		dgnc_cleanup_board(struct board_t *brd);
+static void		dgnc_poll_handler(ulong dummy);
+static int		dgnc_init_pci(void);
+static int		dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
+static void		dgnc_remove_one(struct pci_dev *dev);
+static int		dgnc_probe1(struct pci_dev *pdev, int card_type);
+static void		dgnc_do_remap(struct board_t *brd);
+static void		dgnc_mbuf(struct board_t *brd, const char *fmt, ...);
+
+
+/* Driver load/unload functions */
+int		dgnc_init_module(void);
+void		dgnc_cleanup_module(void);
+
+module_init(dgnc_init_module);
+module_exit(dgnc_cleanup_module);
+
+
+/*
+ * File operations permitted on Control/Management major.
+ */
+static struct file_operations dgnc_BoardFops =
+{
+	.owner		=	THIS_MODULE,
+#ifdef HAVE_UNLOCKED_IOCTL
+        .unlocked_ioctl =  	dgnc_mgmt_ioctl,
+#else
+        .ioctl          =       dgnc_mgmt_ioctl,
+#endif
+	.open		=	dgnc_mgmt_open,
+	.release	=	dgnc_mgmt_close
+};
+
+
+/*
+ * Globals
+ */
+uint			dgnc_NumBoards;
+struct board_t		*dgnc_Board[MAXBOARDS];
+DEFINE_SPINLOCK(dgnc_global_lock);
+int			dgnc_driver_state = DRIVER_INITIALIZED;
+ulong			dgnc_poll_counter;
+uint			dgnc_Major;
+int			dgnc_poll_tick = 20;	/* Poll interval - 20 ms */
+
+/*
+ * Static vars.
+ */
+static uint		dgnc_Major_Control_Registered = FALSE;
+static uint		dgnc_driver_start = FALSE;
+
+static struct class *dgnc_class;
+
+/*
+ * Poller stuff
+ */
+static 			DEFINE_SPINLOCK(dgnc_poll_lock);	/* Poll scheduling lock */
+static ulong		dgnc_poll_time;				/* Time of next poll */
+static uint		dgnc_poll_stop;				/* Used to tell poller to stop */
+static struct timer_list dgnc_poll_timer;
+
+
+static struct pci_device_id dgnc_pci_tbl[] = {
+	{	DIGI_VID, PCI_DEVICE_CLASSIC_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	0 },
+	{	DIGI_VID, PCI_DEVICE_CLASSIC_4_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	1 },
+	{	DIGI_VID, PCI_DEVICE_CLASSIC_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	2 },
+	{	DIGI_VID, PCI_DEVICE_CLASSIC_8_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	3 },
+	{	DIGI_VID, PCI_DEVICE_NEO_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,		4 },
+	{	DIGI_VID, PCI_DEVICE_NEO_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,		5 },
+	{	DIGI_VID, PCI_DEVICE_NEO_2DB9_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	6 },
+	{	DIGI_VID, PCI_DEVICE_NEO_2DB9PRI_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	7 },
+	{	DIGI_VID, PCI_DEVICE_NEO_2RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	8 },
+	{	DIGI_VID, PCI_DEVICE_NEO_2RJ45PRI_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	9 },
+	{	DIGI_VID, PCI_DEVICE_NEO_1_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	10 },
+	{	DIGI_VID, PCI_DEVICE_NEO_1_422_485_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	11 },
+	{	DIGI_VID, PCI_DEVICE_NEO_2_422_485_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	12 },
+	{	DIGI_VID, PCI_DEVICE_NEO_EXPRESS_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	 13 },
+	{	DIGI_VID, PCI_DEVICE_NEO_EXPRESS_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	14 },
+	{	DIGI_VID, PCI_DEVICE_NEO_EXPRESS_4RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	15 },
+	{	DIGI_VID, PCI_DEVICE_NEO_EXPRESS_8RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0,	16 },
+	{0,}						/* 0 terminated list. */
+};
+MODULE_DEVICE_TABLE(pci, dgnc_pci_tbl);
+
+struct board_id {
+	uchar *name;
+	uint maxports;
+	unsigned int is_pci_express;
+};
+
+static struct board_id dgnc_Ids[] =
+{
+	{	PCI_DEVICE_CLASSIC_4_PCI_NAME,		4,	0	},
+	{	PCI_DEVICE_CLASSIC_4_422_PCI_NAME,	4,	0	},
+	{	PCI_DEVICE_CLASSIC_8_PCI_NAME,		8,	0	},
+	{	PCI_DEVICE_CLASSIC_8_422_PCI_NAME,	8,	0	},
+	{	PCI_DEVICE_NEO_4_PCI_NAME,		4,	0	},
+	{	PCI_DEVICE_NEO_8_PCI_NAME,		8,	0	},
+	{	PCI_DEVICE_NEO_2DB9_PCI_NAME,		2,	0	},
+	{	PCI_DEVICE_NEO_2DB9PRI_PCI_NAME,	2,	0	},
+	{	PCI_DEVICE_NEO_2RJ45_PCI_NAME,		2,	0	},
+	{	PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME,	2,	0	},
+	{	PCI_DEVICE_NEO_1_422_PCI_NAME,		1,	0	},
+	{	PCI_DEVICE_NEO_1_422_485_PCI_NAME,	1,	0	},
+	{	PCI_DEVICE_NEO_2_422_485_PCI_NAME,	2,	0	},
+	{	PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME,	8,	1	},
+	{	PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME,	4,	1	},
+	{	PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME,	4,	1	},
+	{	PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME,	8,	1	},
+	{	NULL,					0,	0	}
+};
+
+static struct pci_driver dgnc_driver = {
+	.name		= "dgnc",
+	.probe		= dgnc_init_one,
+	.id_table       = dgnc_pci_tbl,
+	.remove		= dgnc_remove_one,
+};
+
+
+char *dgnc_state_text[] = {
+	"Board Failed",
+	"Board Found",
+	"Board READY",
+};
+
+char *dgnc_driver_state_text[] = {
+	"Driver Initialized",
+	"Driver Ready."
+};
+
+
+
+/************************************************************************
+ *
+ * Driver load/unload functions
+ *
+ ************************************************************************/
+
+
+/*
+ * init_module()
+ *
+ * Module load.  This is where it all starts.
+ */
+int dgnc_init_module(void)
+{
+	int rc = 0;
+
+	APR(("%s, Digi International Part Number %s\n", DG_NAME, DG_PART));
+
+	/*
+	 * Initialize global stuff
+	 */
+	rc = dgnc_start();
+
+	if (rc < 0) {
+		return(rc);
+	}
+
+	/*
+	 * Find and configure all the cards
+	 */
+	rc = dgnc_init_pci();
+
+	/*
+	 * If something went wrong in the scan, bail out of driver.
+	 */
+	if (rc < 0) {
+		/* Only unregister the pci driver if it was actually registered. */
+		if (dgnc_NumBoards)
+			pci_unregister_driver(&dgnc_driver);
+		else
+			printk("WARNING: dgnc driver load failed.  No Digi Neo or Classic boards found.\n");
+
+		dgnc_cleanup_module();
+	}
+	else {
+		dgnc_create_driver_sysfiles(&dgnc_driver);
+	}
+
+	DPR_INIT(("Finished init_module. Returning %d\n", rc));
+	return (rc);
+}
+
+
+/*
+ * Start of driver.
+ */
+static int dgnc_start(void)
+{
+	int rc = 0;
+	unsigned long flags;
+
+	if (dgnc_driver_start == FALSE) {
+
+		dgnc_driver_start = TRUE;
+
+		/* make sure that the globals are init'd before we do anything else */
+		dgnc_init_globals();
+
+		dgnc_NumBoards = 0;
+
+		APR(("For the tools package or updated drivers please visit http://www.digi.com\n"));
+
+		/*
+		 * Register our base character device into the kernel.
+		 * This allows the download daemon to connect to the downld device
+		 * before any of the boards are init'ed.
+		 */
+		if (!dgnc_Major_Control_Registered) {
+			/*
+			 * Register management/dpa devices
+			 */
+			rc = register_chrdev(0, "dgnc", &dgnc_BoardFops);
+			if (rc <= 0) {
+				APR(("Can't register dgnc driver device (%d)\n", rc));
+				rc = -ENXIO;
+				return(rc);
+			}
+			dgnc_Major = rc;
+
+			dgnc_class = class_create(THIS_MODULE, "dgnc_mgmt");
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+			device_create_drvdata(dgnc_class, NULL,
+				MKDEV(dgnc_Major, 0),
+				NULL, "dgnc_mgmt");
+#else
+                        device_create(dgnc_class, NULL,
+                                MKDEV(dgnc_Major, 0),
+                                NULL, "dgnc_mgmt");
+#endif
+
+			dgnc_Major_Control_Registered = TRUE;
+		}
+
+		/*
+		 * Register our basic stuff in /proc/dgnc
+		 */
+		dgnc_proc_register_basic_prescan();
+
+		/*
+		 * Init any global tty stuff.
+		 */
+		rc = dgnc_tty_preinit();
+
+		if (rc < 0) {
+			APR(("tty preinit - not enough memory (%d)\n", rc));
+			return(rc);
+		}
+
+		/* Start the poller */
+		DGNC_LOCK(dgnc_poll_lock, flags);
+		init_timer(&dgnc_poll_timer);
+		dgnc_poll_timer.function = dgnc_poll_handler;
+		dgnc_poll_timer.data = 0;
+		dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick);
+		dgnc_poll_timer.expires = dgnc_poll_time;
+		DGNC_UNLOCK(dgnc_poll_lock, flags);
+
+		add_timer(&dgnc_poll_timer);
+
+		dgnc_driver_state = DRIVER_READY;
+	}
+
+	return(rc);
+}
+
+/*
+ * Register pci driver, and return how many boards we have.
+ */
+static int dgnc_init_pci(void)
+{
+	return pci_register_driver(&dgnc_driver);
+}
+
+
+/* returns count (>= 0), or negative on error */
+static int dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{       
+	int rc;
+
+	/* wake up and enable device */
+	rc = pci_enable_device(pdev);
+
+	if (rc < 0) {
+		rc = -EIO;
+	} else {
+		rc = dgnc_probe1(pdev, ent->driver_data);
+		if (rc == 0) {
+			dgnc_NumBoards++;
+	                DPR_INIT(("Incrementing numboards to %d\n", dgnc_NumBoards));
+		}
+	}
+	return rc;
+}
+
+static int dgnc_probe1(struct pci_dev *pdev, int card_type)
+{
+	return dgnc_found_board(pdev, card_type);
+}
+
+
+static void dgnc_remove_one(struct pci_dev *dev)
+{
+	/* Do Nothing */
+}
+
+/*
+ * dgnc_cleanup_module()
+ *
+ * Module unload.  This is where it all ends.
+ */
+void dgnc_cleanup_module(void)
+{
+	int i;
+	ulong lock_flags;
+
+	DGNC_LOCK(dgnc_poll_lock, lock_flags);
+	dgnc_poll_stop = 1;
+	DGNC_UNLOCK(dgnc_poll_lock, lock_flags);
+
+	/* Turn off poller right away. */
+	del_timer_sync(&dgnc_poll_timer);
+
+	dgnc_proc_unregister_all();
+
+	dgnc_remove_driver_sysfiles(&dgnc_driver);
+
+	if (dgnc_Major_Control_Registered) {
+		device_destroy(dgnc_class, MKDEV(dgnc_Major, 0));
+		class_destroy(dgnc_class);
+		unregister_chrdev(dgnc_Major, "dgnc");
+	}
+
+	for (i = 0; i < dgnc_NumBoards; ++i) {
+		dgnc_remove_ports_sysfiles(dgnc_Board[i]);
+		dgnc_tty_uninit(dgnc_Board[i]);
+		dgnc_cleanup_board(dgnc_Board[i]);
+	}
+
+	dgnc_tty_post_uninit();
+
+#if defined(DGNC_TRACER)
+	/* last thing, make sure we release the tracebuffer */
+	dgnc_tracer_free();
+#endif
+	if (dgnc_NumBoards)
+		pci_unregister_driver(&dgnc_driver);
+}
+
+
+/*
+ * dgnc_cleanup_board()
+ *
+ * Free all the memory associated with a board
+ */
+static void dgnc_cleanup_board(struct board_t *brd)
+{
+	int i = 0;
+
+        if(!brd || brd->magic != DGNC_BOARD_MAGIC)
+                return;
+
+	switch (brd->device) {
+	case PCI_DEVICE_CLASSIC_4_DID:
+	case PCI_DEVICE_CLASSIC_8_DID:
+	case PCI_DEVICE_CLASSIC_4_422_DID:
+	case PCI_DEVICE_CLASSIC_8_422_DID:
+
+		/* Tell card not to interrupt anymore. */
+		outb(0, brd->iobase + 0x4c);
+		break;
+
+	default:
+		break;
+	}
+
+	if (brd->irq)
+		free_irq(brd->irq, brd);
+
+	tasklet_kill(&brd->helper_tasklet);
+
+	if (brd->re_map_membase) {
+		iounmap(brd->re_map_membase);
+		brd->re_map_membase = NULL;
+	}
+
+	if (brd->msgbuf_head) {
+		unsigned long flags;
+
+		DGNC_LOCK(dgnc_global_lock, flags);
+		brd->msgbuf = NULL;
+		printk(brd->msgbuf_head);
+		kfree(brd->msgbuf_head);
+		brd->msgbuf_head = NULL;
+		DGNC_UNLOCK(dgnc_global_lock, flags);
+        }
+
+	/* Free all allocated channels structs */
+	for (i = 0; i < MAXPORTS ; i++) {
+		if (brd->channels[i]) {
+			if (brd->channels[i]->ch_rqueue)
+				kfree(brd->channels[i]->ch_rqueue);
+			if (brd->channels[i]->ch_equeue)
+				kfree(brd->channels[i]->ch_equeue);
+			if (brd->channels[i]->ch_wqueue)
+				kfree(brd->channels[i]->ch_wqueue);
+
+			kfree(brd->channels[i]);
+			brd->channels[i] = NULL;
+		}
+	}
+
+	if (brd->flipbuf)
+		kfree(brd->flipbuf);
+
+	dgnc_Board[brd->boardnum] = NULL;
+
+        kfree(brd);
+}
+
+
+/*
+ * dgnc_found_board()
+ *
+ * A board has been found, init it.
+ */
+static int dgnc_found_board(struct pci_dev *pdev, int id)
+{
+        struct board_t *brd;
+	unsigned int pci_irq;
+	int i = 0;
+	int rc = 0;
+	unsigned long flags;
+
+        /* get the board structure and prep it */
+        brd = dgnc_Board[dgnc_NumBoards] =
+        (struct board_t *) dgnc_driver_kzmalloc(sizeof(struct board_t), GFP_KERNEL);
+	if (!brd) {
+		APR(("memory allocation for board structure failed\n"));
+		return(-ENOMEM);
+	}
+
+        /* make a temporary message buffer for the boot messages */
+        brd->msgbuf = brd->msgbuf_head =
+                (char *) dgnc_driver_kzmalloc(sizeof(char) * 8192, GFP_KERNEL);
+        if (!brd->msgbuf) {
+		kfree(brd);
+		APR(("memory allocation for board msgbuf failed\n"));
+		return(-ENOMEM);
+        }
+        
+	/* store the info for the board we've found */
+	brd->magic = DGNC_BOARD_MAGIC;
+	brd->boardnum = dgnc_NumBoards;
+	brd->vendor = dgnc_pci_tbl[id].vendor;
+	brd->device = dgnc_pci_tbl[id].device;
+	brd->pdev = pdev;
+	brd->pci_bus = pdev->bus->number;
+	brd->pci_slot = PCI_SLOT(pdev->devfn);
+	brd->name = dgnc_Ids[id].name;
+	brd->maxports = dgnc_Ids[id].maxports;
+	if (dgnc_Ids[i].is_pci_express)
+		brd->bd_flags |= BD_IS_PCI_EXPRESS;
+	brd->dpastatus = BD_NOFEP;
+	init_waitqueue_head(&brd->state_wait);
+
+	DGNC_SPINLOCK_INIT(brd->bd_lock);
+	DGNC_SPINLOCK_INIT(brd->bd_intr_lock);
+
+	brd->state		= BOARD_FOUND;
+
+	for (i = 0; i < MAXPORTS; i++) {
+		brd->channels[i] = NULL;
+	}
+
+	/* store which card & revision we have */
+	pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor);
+	pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice);
+	pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev);
+
+	pci_irq = pdev->irq;
+	brd->irq = pci_irq;
+
+
+	switch(brd->device) {
+
+	case PCI_DEVICE_CLASSIC_4_DID:
+	case PCI_DEVICE_CLASSIC_8_DID:
+	case PCI_DEVICE_CLASSIC_4_422_DID:
+	case PCI_DEVICE_CLASSIC_8_422_DID:
+
+		brd->dpatype = T_CLASSIC | T_PCIBUS;
+
+                DPR_INIT(("dgnc_found_board - Classic.\n"));
+
+		/*
+		 * For PCI ClassicBoards
+		 * PCI Local Address (i.e. "resource" number) space
+		 * 0        PLX Memory Mapped Config
+		 * 1        PLX I/O Mapped Config
+		 * 2        I/O Mapped UARTs and Status   
+		 * 3        Memory Mapped VPD
+		 * 4        Memory Mapped UARTs and Status
+		 */
+
+
+		/* get the PCI Base Address Registers */
+		brd->membase = pci_resource_start(pdev, 4);
+
+		if (!brd->membase) {
+			APR(("card has no PCI IO resources, failing board.\n"));
+			return -ENODEV;
+		}
+
+		brd->membase_end = pci_resource_end(pdev, 4);
+
+		if (brd->membase & 1)
+			brd->membase &= ~3;
+		else
+			brd->membase &= ~15;
+
+		brd->iobase     = pci_resource_start(pdev, 1);
+		brd->iobase_end = pci_resource_end(pdev, 1);
+ 		brd->iobase     = ((unsigned int) (brd->iobase)) & 0xFFFE;
+
+		/* Assign the board_ops struct */
+		brd->bd_ops = &dgnc_cls_ops;
+
+		brd->bd_uart_offset = 0x8;
+		brd->bd_dividend = 921600;
+
+		dgnc_do_remap(brd);
+
+		/* Get and store the board VPD, if it exists */
+		brd->bd_ops->vpd(brd);
+
+		/*
+		 * Enable Local Interrupt 1		  (0x1),
+		 * Local Interrupt 1 Polarity Active high (0x2),
+		 * Enable PCI interrupt			  (0x40)
+		 */
+		outb(0x43, brd->iobase + 0x4c);
+
+		break;
+
+
+	case PCI_DEVICE_NEO_4_DID:
+	case PCI_DEVICE_NEO_8_DID:
+	case PCI_DEVICE_NEO_2DB9_DID:
+	case PCI_DEVICE_NEO_2DB9PRI_DID:
+	case PCI_DEVICE_NEO_2RJ45_DID:
+	case PCI_DEVICE_NEO_2RJ45PRI_DID:
+	case PCI_DEVICE_NEO_1_422_DID:
+	case PCI_DEVICE_NEO_1_422_485_DID:
+	case PCI_DEVICE_NEO_2_422_485_DID:
+	case PCI_DEVICE_NEO_EXPRESS_8_DID:
+	case PCI_DEVICE_NEO_EXPRESS_4_DID:
+	case PCI_DEVICE_NEO_EXPRESS_4RJ45_DID:
+	case PCI_DEVICE_NEO_EXPRESS_8RJ45_DID:
+
+		/*
+		 * This chip is set up 100% when we get to it.
+		 * No need to enable global interrupts or anything. 
+		 */
+		if (brd->bd_flags & BD_IS_PCI_EXPRESS)
+			brd->dpatype = T_NEO_EXPRESS | T_PCIBUS;
+		else
+			brd->dpatype = T_NEO | T_PCIBUS;
+
+                DPR_INIT(("dgnc_found_board - NEO.\n"));
+
+		/* get the PCI Base Address Registers */
+		brd->membase     = pci_resource_start(pdev, 0);
+		brd->membase_end = pci_resource_end(pdev, 0);
+
+		if (brd->membase & 1)
+			brd->membase &= ~3;
+		else
+			brd->membase &= ~15;
+
+		/* Assign the board_ops struct */
+		brd->bd_ops = &dgnc_neo_ops;
+
+		brd->bd_uart_offset = 0x200;
+		brd->bd_dividend = 921600;
+
+		dgnc_do_remap(brd);
+
+		if (brd->re_map_membase) {
+
+			/* After remap is complete, we need to read and store the dvid */
+			brd->dvid = readb(brd->re_map_membase + 0x8D);
+
+			/* Get and store the board VPD, if it exists */
+			brd->bd_ops->vpd(brd);
+		}
+		break;
+
+	default:
+		APR(("Did not find any compatible Neo or Classic PCI boards in system.\n"));
+		return (-ENXIO);
+
+	}
+
+	/*
+	 * Do tty device initialization.
+	 */
+
+	rc = dgnc_tty_register(brd);
+	if (rc < 0) {
+		dgnc_tty_uninit(brd);
+		APR(("Can't register tty devices (%d)\n", rc));
+		brd->state = BOARD_FAILED;
+		brd->dpastatus = BD_NOFEP;
+		goto failed;
+	}
+
+	rc = dgnc_finalize_board_init(brd);
+	if (rc < 0) {
+		APR(("Can't finalize board init (%d)\n", rc));
+		brd->state = BOARD_FAILED;
+		brd->dpastatus = BD_NOFEP;
+
+		goto failed;
+	}
+
+	rc = dgnc_tty_init(brd);
+	if (rc < 0) {
+		dgnc_tty_uninit(brd);
+		APR(("Can't init tty devices (%d)\n", rc));
+		brd->state = BOARD_FAILED;
+		brd->dpastatus = BD_NOFEP;
+
+		goto failed;
+	}
+
+	brd->state = BOARD_READY;
+	brd->dpastatus = BD_RUNNING;
+
+	dgnc_create_ports_sysfiles(brd);
+
+	/* init our poll helper tasklet */
+	tasklet_init(&brd->helper_tasklet, brd->bd_ops->tasklet, (unsigned long) brd);
+
+	 /* Log the information about the board */
+	dgnc_mbuf(brd, DRVSTR": board %d: %s (rev %d), irq %d\n",
+		dgnc_NumBoards, brd->name, brd->rev, brd->irq);
+
+	DPR_INIT(("dgnc_scan(%d) - printing out the msgbuf\n", i));
+	DGNC_LOCK(dgnc_global_lock, flags);
+	brd->msgbuf = NULL;
+	printk(brd->msgbuf_head);
+	kfree(brd->msgbuf_head);
+	brd->msgbuf_head = NULL;
+	DGNC_UNLOCK(dgnc_global_lock, flags);
+
+	/*
+	 * allocate flip buffer for board.
+	 *
+	 * Okay to malloc with GFP_KERNEL, we are not at interrupt
+	 * context, and there are no locks held.
+	 */
+	brd->flipbuf = dgnc_driver_kzmalloc(MYFLIPLEN, GFP_KERNEL);
+
+	dgnc_proc_register_basic_postscan(dgnc_NumBoards);
+
+	wake_up_interruptible(&brd->state_wait);
+
+        return(0);
+
+failed:
+
+	return (-ENXIO);
+
+}
+
+
+static int dgnc_finalize_board_init(struct board_t *brd) {
+	int rc = 0;
+
+        DPR_INIT(("dgnc_finalize_board_init() - start\n"));
+
+	if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+                return(-ENODEV);
+
+        DPR_INIT(("dgnc_finalize_board_init() - start #2\n"));
+
+	if (brd->irq) {
+		rc = request_irq(brd->irq, brd->bd_ops->intr, IRQF_SHARED, "DGNC", brd);
+
+		if (rc) {
+			printk("Failed to hook IRQ %d\n",brd->irq);
+			brd->state = BOARD_FAILED;
+			brd->dpastatus = BD_NOFEP;
+			rc = -ENODEV;
+		} else {
+			DPR_INIT(("Requested and received usage of IRQ %d\n", brd->irq));
+		}
+	}
+	return(rc);
+}
+
+/*
+ * Remap PCI memory.
+ */
+static void dgnc_do_remap(struct board_t *brd)
+{
+
+	if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	brd->re_map_membase = ioremap(brd->membase, 0x1000);
+
+	DPR_INIT(("remapped mem: 0x%p\n", brd->re_map_membase));
+}
+
+
+/*****************************************************************************
+*
+* Function:
+*                                       
+*    dgnc_poll_handler
+*
+* Author:
+*
+*    Scott H Kilau
+*       
+* Parameters:
+*
+*    dummy -- ignored                    
+*
+* Return Values:
+*
+*    none
+*
+* Description:   
+*                                       
+*    As each timer expires, it determines (a) whether the "transmit"
+*    waiter needs to be woken up, and (b) whether the poller needs to
+*    be rescheduled.
+*
+******************************************************************************/
+
+static void dgnc_poll_handler(ulong dummy)
+{
+        struct board_t *brd;
+        unsigned long lock_flags;
+	int i;
+	unsigned long new_time;
+
+	dgnc_poll_counter++;
+
+	/*
+	 * Do not start the board state machine until
+	 * driver tells us its up and running, and has
+	 * everything it needs.
+	 */
+	if (dgnc_driver_state != DRIVER_READY) {
+		goto schedule_poller;
+	}
+
+	/* Go thru each board, kicking off a tasklet for each if needed */
+	for (i = 0; i < dgnc_NumBoards; i++) {
+		brd = dgnc_Board[i];
+
+		DGNC_LOCK(brd->bd_lock, lock_flags);
+
+		/* If board is in a failed state, don't bother scheduling a tasklet */
+		if (brd->state == BOARD_FAILED) {
+			DGNC_UNLOCK(brd->bd_lock, lock_flags);
+			continue;
+		}
+
+		/* Schedule a poll helper task */
+		tasklet_schedule(&brd->helper_tasklet);
+
+		DGNC_UNLOCK(brd->bd_lock, lock_flags);
+	}
+
+schedule_poller:
+
+	/*
+	 * Schedule ourself back at the nominal wakeup interval.
+	 */
+	DGNC_LOCK(dgnc_poll_lock, lock_flags);
+	dgnc_poll_time += dgnc_jiffies_from_ms(dgnc_poll_tick);
+
+	new_time = dgnc_poll_time - jiffies;
+
+	if ((ulong) new_time >= 2 * dgnc_poll_tick) {
+		dgnc_poll_time = jiffies +  dgnc_jiffies_from_ms(dgnc_poll_tick);
+	}
+
+	init_timer(&dgnc_poll_timer);
+	dgnc_poll_timer.function = dgnc_poll_handler;
+	dgnc_poll_timer.data = 0;
+	dgnc_poll_timer.expires = dgnc_poll_time;
+	DGNC_UNLOCK(dgnc_poll_lock, lock_flags);
+
+	if (!dgnc_poll_stop)
+		add_timer(&dgnc_poll_timer);
+}
+
+/*
+ * dgnc_init_globals()
+ *
+ * This is where we initialize the globals from the static insmod
+ * configuration variables.  These are declared near the head of
+ * this file.
+ */
+static void dgnc_init_globals(void)
+{
+	int i = 0;
+
+	dgnc_rawreadok		= rawreadok;
+        dgnc_trcbuf_size	= trcbuf_size;
+	dgnc_debug		= debug;
+
+	for (i = 0; i < MAXBOARDS; i++) {
+		dgnc_Board[i] = NULL;
+	}
+
+	init_timer(&dgnc_poll_timer);
+}
+
+
+/************************************************************************
+ *
+ * Utility functions
+ *
+ ************************************************************************/
+
+
+/*
+ * dgnc_driver_kzmalloc()
+ *
+ * Malloc and clear memory,
+ */
+void *dgnc_driver_kzmalloc(size_t size, int priority)
+{
+ 	void *p = kmalloc(size, priority);
+	if(p)
+		memset(p, 0, size);
+	return(p);
+}
+
+
+/*
+ * dgnc_mbuf()
+ *
+ * Used to print to the message buffer during board init.
+ */
+static void dgnc_mbuf(struct board_t *brd, const char *fmt, ...) {
+	va_list		ap;
+	char		buf[1024];
+	int		i;
+	unsigned long	flags;
+
+	DGNC_LOCK(dgnc_global_lock, flags);
+
+	/* Format buf using fmt and arguments contained in ap. */
+	va_start(ap, fmt);
+	i = vsprintf(buf, fmt,  ap);
+	va_end(ap);
+
+	DPR((buf));
+
+	if (!brd || !brd->msgbuf) {
+		printk(buf);
+		DGNC_UNLOCK(dgnc_global_lock, flags);
+		return;
+	}
+
+	memcpy(brd->msgbuf, buf, strlen(buf));
+	brd->msgbuf += strlen(buf);
+	*brd->msgbuf = (char) NULL;
+
+	DGNC_UNLOCK(dgnc_global_lock, flags);
+}
+
+
+/*
+ * dgnc_ms_sleep()
+ *
+ * Put the driver to sleep for x ms's
+ *
+ * Returns 0 if timed out, !0 (showing signal) if interrupted by a signal.
+ */
+int dgnc_ms_sleep(ulong ms)
+{
+	current->state = TASK_INTERRUPTIBLE;
+	schedule_timeout((ms * HZ) / 1000);
+	return (signal_pending(current));
+}
+
+
+
+/*
+ *      dgnc_ioctl_name() : Returns a text version of each ioctl value.
+ */
+char *dgnc_ioctl_name(int cmd)
+{
+	switch(cmd) {
+
+	case TCGETA:		return("TCGETA");
+	case TCGETS:		return("TCGETS");
+	case TCSETA:		return("TCSETA");
+	case TCSETS:		return("TCSETS");
+	case TCSETAW:		return("TCSETAW");
+	case TCSETSW:		return("TCSETSW");
+	case TCSETAF:		return("TCSETAF");
+	case TCSETSF:		return("TCSETSF");
+	case TCSBRK:		return("TCSBRK");
+	case TCXONC:		return("TCXONC");
+	case TCFLSH:		return("TCFLSH");
+	case TIOCGSID:		return("TIOCGSID");
+
+	case TIOCGETD:		return("TIOCGETD");
+	case TIOCSETD:		return("TIOCSETD");
+	case TIOCGWINSZ:	return("TIOCGWINSZ");
+	case TIOCSWINSZ:	return("TIOCSWINSZ");
+
+	case TIOCMGET:		return("TIOCMGET");
+	case TIOCMSET:		return("TIOCMSET");
+	case TIOCMBIS:		return("TIOCMBIS");
+	case TIOCMBIC:		return("TIOCMBIC");
+
+	/* from digi.h */
+	case DIGI_SETA:		return("DIGI_SETA");
+	case DIGI_SETAW:	return("DIGI_SETAW");
+	case DIGI_SETAF:	return("DIGI_SETAF");
+	case DIGI_SETFLOW:	return("DIGI_SETFLOW");
+	case DIGI_SETAFLOW:	return("DIGI_SETAFLOW");
+	case DIGI_GETFLOW:	return("DIGI_GETFLOW");
+	case DIGI_GETAFLOW:	return("DIGI_GETAFLOW");
+	case DIGI_GETA:		return("DIGI_GETA");
+	case DIGI_GEDELAY:	return("DIGI_GEDELAY");
+	case DIGI_SEDELAY:	return("DIGI_SEDELAY");
+	case DIGI_GETCUSTOMBAUD: return("DIGI_GETCUSTOMBAUD");
+	case DIGI_SETCUSTOMBAUD: return("DIGI_SETCUSTOMBAUD");
+	case TIOCMODG:		return("TIOCMODG");
+	case TIOCMODS:		return("TIOCMODS");
+	case TIOCSDTR:		return("TIOCSDTR");
+	case TIOCCDTR:		return("TIOCCDTR");
+
+	default:		return("unknown");
+	}
+}
diff --git a/drivers/staging/dgnc/dgnc_driver.h b/drivers/staging/dgnc/dgnc_driver.h
new file mode 100644
index 0000000..43177f4
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_driver.h
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *      Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *************************************************************************
+ *
+ * Driver includes
+ *
+ *************************************************************************/
+
+#ifndef __DGNC_DRIVER_H
+#define __DGNC_DRIVER_H
+
+#include <linux/version.h>	/* To get the current Linux version */
+#include <linux/types.h>	/* To pick up the varions Linux types */
+#include <linux/tty.h>          /* To pick up the various tty structs/defines */
+#include <linux/interrupt.h>	/* For irqreturn_t type */
+
+#include "dgnc_types.h"		/* Additional types needed by the Digi header files */
+#include "digi.h"		/* Digi specific ioctl header */  
+#include "dgnc_kcompat.h"	/* Kernel 2.4/2.6 compat includes */
+#include "dgnc_sysfs.h"		/* Support for SYSFS */
+
+/*************************************************************************
+ *
+ * Driver defines
+ *
+ *************************************************************************/
+
+/*
+ * Driver identification, error and debugging statments
+ *
+ * In theory, you can change all occurances of "digi" in the next
+ * three lines, and the driver printk's will all automagically change.
+ *
+ * APR((fmt, args, ...));	Always prints message
+ * DPR((fmt, args, ...));	Only prints if DGNC_TRACER is defined at
+ *				  compile time and dgnc_debug!=0
+ */
+#define	PROCSTR		"dgnc"			/* /proc entries	 */
+#define	DEVSTR		"/dev/dg/dgnc"		/* /dev entries		 */
+#define	DRVSTR		"dgnc"			/* Driver name string 
+						 * displayed by APR	 */
+#define	APR(args)	do { PRINTF_TO_KMEM(args); printk(DRVSTR": "); printk args; \
+			   } while (0)
+#define	RAPR(args)	do { PRINTF_TO_KMEM(args); printk args; } while (0)
+
+#define TRC_TO_CONSOLE 1
+
+/*
+ * Debugging levels can be set using debug insmod variable
+ * They can also be compiled out completely.
+ */
+
+#define	DBG_INIT		(dgnc_debug & 0x01)
+#define	DBG_BASIC		(dgnc_debug & 0x02)
+#define	DBG_CORE		(dgnc_debug & 0x04)
+
+#define	DBG_OPEN		(dgnc_debug & 0x08)
+#define	DBG_CLOSE		(dgnc_debug & 0x10)
+#define	DBG_READ		(dgnc_debug & 0x20)
+#define	DBG_WRITE		(dgnc_debug & 0x40)
+
+#define	DBG_IOCTL		(dgnc_debug & 0x80)
+
+#define	DBG_PROC		(dgnc_debug & 0x100)
+#define	DBG_PARAM		(dgnc_debug & 0x200)
+#define	DBG_PSCAN		(dgnc_debug & 0x400)
+#define	DBG_EVENT		(dgnc_debug & 0x800)
+
+#define	DBG_DRAIN		(dgnc_debug & 0x1000)
+#define	DBG_MSIGS		(dgnc_debug & 0x2000)
+
+#define	DBG_MGMT		(dgnc_debug & 0x4000)
+#define	DBG_INTR		(dgnc_debug & 0x8000)
+
+#define	DBG_CARR		(dgnc_debug & 0x10000)
+
+
+#if defined(DGNC_TRACER)
+
+# if defined(TRC_TO_KMEM)
+/* Choose one: */
+#  define TRC_ON_OVERFLOW_WRAP_AROUND
+#  undef  TRC_ON_OVERFLOW_SHIFT_BUFFER
+# endif //TRC_TO_KMEM
+
+# define TRC_MAXMSG		1024
+# define TRC_OVERFLOW		"(OVERFLOW)"
+# define TRC_DTRC		"/usr/bin/dtrc"
+
+#if defined TRC_TO_CONSOLE
+#define PRINTF_TO_CONSOLE(args) { printk(DRVSTR": "); printk args; }
+#else //!defined TRACE_TO_CONSOLE
+#define PRINTF_TO_CONSOLE(args)
+#endif
+
+#if defined TRC_TO_KMEM
+#define PRINTF_TO_KMEM(args) dgnc_tracef args 
+#else //!defined TRC_TO_KMEM
+#define PRINTF_TO_KMEM(args)
+#endif
+
+#define	TRC(args)	{ PRINTF_TO_KMEM(args); PRINTF_TO_CONSOLE(args) }
+
+# define DPR_INIT(ARGS)		if (DBG_INIT) TRC(ARGS)
+# define DPR_BASIC(ARGS)	if (DBG_BASIC) TRC(ARGS)
+# define DPR_CORE(ARGS)		if (DBG_CORE) TRC(ARGS)
+# define DPR_OPEN(ARGS)		if (DBG_OPEN)  TRC(ARGS)
+# define DPR_CLOSE(ARGS)	if (DBG_CLOSE)  TRC(ARGS)
+# define DPR_READ(ARGS)		if (DBG_READ)  TRC(ARGS)
+# define DPR_WRITE(ARGS)	if (DBG_WRITE) TRC(ARGS)
+# define DPR_IOCTL(ARGS)	if (DBG_IOCTL) TRC(ARGS)
+# define DPR_PROC(ARGS)		if (DBG_PROC)  TRC(ARGS)
+# define DPR_PARAM(ARGS)	if (DBG_PARAM)  TRC(ARGS)
+# define DPR_PSCAN(ARGS)	if (DBG_PSCAN)  TRC(ARGS)
+# define DPR_EVENT(ARGS)	if (DBG_EVENT)  TRC(ARGS)
+# define DPR_DRAIN(ARGS)	if (DBG_DRAIN)  TRC(ARGS)
+# define DPR_CARR(ARGS)		if (DBG_CARR)  TRC(ARGS)
+# define DPR_MGMT(ARGS)		if (DBG_MGMT)  TRC(ARGS)
+# define DPR_INTR(ARGS)		if (DBG_INTR)  TRC(ARGS)
+# define DPR_MSIGS(ARGS)	if (DBG_MSIGS)  TRC(ARGS)
+
+# define DPR(ARGS)		if (dgnc_debug) TRC(ARGS)
+# define P(X)			dgnc_tracef(#X "=%p\n", X)
+# define X(X)			dgnc_tracef(#X "=%x\n", X)
+
+#else//!defined DGNC_TRACER
+
+#define PRINTF_TO_KMEM(args)
+# define TRC(ARGS)
+# define DPR_INIT(ARGS)
+# define DPR_BASIC(ARGS)
+# define DPR_CORE(ARGS)
+# define DPR_OPEN(ARGS)
+# define DPR_CLOSE(ARGS)
+# define DPR_READ(ARGS)
+# define DPR_WRITE(ARGS)
+# define DPR_IOCTL(ARGS)
+# define DPR_PROC(ARGS)
+# define DPR_PARAM(ARGS)
+# define DPR_PSCAN(ARGS)
+# define DPR_EVENT(ARGS)
+# define DPR_DRAIN(ARGS)
+# define DPR_CARR(ARGS)
+# define DPR_MGMT(ARGS)
+# define DPR_INTR(ARGS)
+# define DPR_MSIGS(ARGS)
+
+# define DPR(args)
+
+#endif//DGNC_TRACER
+
+/* Number of boards we support at once. */
+#define	MAXBOARDS	20
+#define	MAXPORTS	8
+#define MAXTTYNAMELEN	200
+
+/* Our 3 magic numbers for our board, channel and unit structs */
+#define DGNC_BOARD_MAGIC	0x5c6df104
+#define DGNC_CHANNEL_MAGIC	0x6c6df104
+#define DGNC_UNIT_MAGIC		0x7c6df104
+
+/* Serial port types */
+#define DGNC_SERIAL		0
+#define DGNC_PRINT		1
+
+#define	SERIAL_TYPE_NORMAL	1
+
+#define PORT_NUM(dev)	((dev) & 0x7f)
+#define IS_PRINT(dev)	(((dev) & 0xff) >= 0x80)
+
+/* MAX number of stop characters we will send when our read queue is getting full */
+#define MAX_STOPS_SENT 5
+
+/* 4 extra for alignment play space */
+#define WRITEBUFLEN		((4096) + 4)
+#define MYFLIPLEN		N_TTY_BUF_SIZE
+
+#define dgnc_jiffies_from_ms(a) (((a) * HZ) / 1000)
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.  This is the same structure that is defined
+ * as the default in tty_io.c with the same settings overriden as in serial.c
+ *
+ * In short, this should match the internal serial ports' defaults.
+ */
+#define	DEFAULT_IFLAGS	(ICRNL | IXON)
+#define	DEFAULT_OFLAGS	(OPOST | ONLCR)
+#define	DEFAULT_CFLAGS	(B9600 | CS8 | CREAD | HUPCL | CLOCAL)
+#define	DEFAULT_LFLAGS	(ISIG | ICANON | ECHO | ECHOE | ECHOK | \
+			ECHOCTL | ECHOKE | IEXTEN)
+
+#ifndef _POSIX_VDISABLE
+#define   _POSIX_VDISABLE '\0'
+#endif
+
+#define SNIFF_MAX	65536		/* Sniff buffer size (2^n) */
+#define SNIFF_MASK	(SNIFF_MAX - 1)	/* Sniff wrap mask */
+
+/*
+ * Lock function/defines.
+ * Makes spotting lock/unlock locations easier.
+ */
+# define DGNC_SPINLOCK_INIT(x)		spin_lock_init(&(x))
+# define DGNC_LOCK(x,y)			spin_lock_irqsave(&(x), y)
+# define DGNC_UNLOCK(x,y)		spin_unlock_irqrestore(&(x), y)
+
+/*
+ * All the possible states the driver can be while being loaded.
+ */
+enum {
+	DRIVER_INITIALIZED = 0,
+	DRIVER_READY
+};
+
+/*
+ * All the possible states the board can be while booting up.
+ */
+enum {
+	BOARD_FAILED = 0,
+	BOARD_FOUND,
+	BOARD_READY
+};
+
+
+/*************************************************************************
+ *
+ * Structures and closely related defines.
+ *
+ *************************************************************************/
+
+struct board_t;
+struct channel_t;
+
+/************************************************************************
+ * Per board operations structure                                       *
+ ************************************************************************/
+struct board_ops {
+	void (*tasklet) (unsigned long data);
+	irqreturn_t (*intr) (int irq, void *voidbrd);
+	void (*uart_init) (struct channel_t *ch);
+	void (*uart_off) (struct channel_t *ch);
+	int  (*drain) (struct tty_struct *tty, uint seconds);
+	void (*param) (struct tty_struct *tty);
+	void (*vpd) (struct board_t *brd);
+	void (*assert_modem_signals) (struct channel_t *ch);
+	void (*flush_uart_write) (struct channel_t *ch);
+	void (*flush_uart_read) (struct channel_t *ch);
+	void (*disable_receiver) (struct channel_t *ch);
+	void (*enable_receiver) (struct channel_t *ch);
+	void (*send_break) (struct channel_t *ch, int);
+	void (*send_start_character) (struct channel_t *ch);
+	void (*send_stop_character) (struct channel_t *ch);
+	void (*copy_data_from_queue_to_uart) (struct channel_t *ch);
+	uint (*get_uart_bytes_left) (struct channel_t *ch);
+	void (*send_immediate_char) (struct channel_t *ch, unsigned char);
+};
+
+/************************************************************************ 
+ * Device flag definitions for bd_flags.
+ ************************************************************************/
+#define BD_IS_PCI_EXPRESS     0x0001          /* Is a PCI Express board */
+
+
+/*
+ *	Per-board information
+ */
+struct board_t
+{
+	int		magic;		/* Board Magic number.  */
+	int		boardnum;	/* Board number: 0-32 */
+
+	int		type;		/* Type of board */
+	char		*name;		/* Product Name */
+	struct pci_dev	*pdev;		/* Pointer to the pci_dev struct */
+	unsigned long	bd_flags;	/* Board flags */
+	u16		vendor;		/* PCI vendor ID */
+	u16		device;		/* PCI device ID */
+	u16		subvendor;	/* PCI subsystem vendor ID */
+	u16		subdevice;	/* PCI subsystem device ID */
+	uchar		rev;		/* PCI revision ID */
+	uint		pci_bus;	/* PCI bus value */
+	uint		pci_slot;	/* PCI slot value */
+	uint		maxports;	/* MAX ports this board can handle */
+	uchar		dvid;		/* Board specific device id */
+	uchar		vpd[128];	/* VPD of board, if found */
+	uchar		serial_num[20];	/* Serial number of board, if found in VPD */
+
+	spinlock_t	bd_lock;	/* Used to protect board */
+
+	spinlock_t	bd_intr_lock;	/* Used to protect the poller tasklet and
+					 * the interrupt routine from each other.
+					 */
+
+	uint		state;		/* State of card. */
+	wait_queue_head_t state_wait;	/* Place to sleep on for state change */
+
+	struct		tasklet_struct helper_tasklet; /* Poll helper tasklet */
+
+	uint		nasync;		/* Number of ports on card */
+
+	uint		irq;		/* Interrupt request number */
+	ulong		intr_count;	/* Count of interrupts */
+	ulong		intr_modem;	/* Count of interrupts */
+	ulong		intr_tx;	/* Count of interrupts */
+	ulong		intr_rx;	/* Count of interrupts */
+
+	ulong		membase;	/* Start of base memory of the card */
+	ulong		membase_end;	/* End of base memory of the card */
+
+	uchar		*re_map_membase;/* Remapped memory of the card */
+
+	ulong		iobase;		/* Start of io base of the card */
+	ulong		iobase_end;	/* End of io base of the card */
+
+	uint		bd_uart_offset;	/* Space between each UART */
+
+	struct channel_t *channels[MAXPORTS]; /* array of pointers to our channels. */
+
+	struct tty_driver	SerialDriver;
+	char		SerialName[200];
+	struct tty_driver	PrintDriver;
+	char		PrintName[200];
+
+	uint		dgnc_Major_Serial_Registered;
+	uint		dgnc_Major_TransparentPrint_Registered;
+
+	uint		dgnc_Serial_Major;
+	uint		dgnc_TransparentPrint_Major;
+
+	uint		TtyRefCnt;
+
+	char		*flipbuf;	/* Our flip buffer, alloced if board is found */
+
+	u16		dpatype;	/* The board "type", as defined by DPA */
+	u16		dpastatus;	/* The board "status", as defined by DPA */
+
+	/*
+	 *	Mgmt data.
+	 */
+        char		*msgbuf_head;
+        char		*msgbuf;
+
+	uint		bd_dividend;	/* Board/UARTs specific dividend */
+
+	struct board_ops *bd_ops;
+
+	/* /proc/<board> entries */
+	struct proc_dir_entry *proc_entry_pointer;
+	struct dgnc_proc_entry *dgnc_board_table;
+
+};
+
+
+/************************************************************************ 
+ * Unit flag definitions for un_flags.
+ ************************************************************************/
+#define UN_ISOPEN	0x0001		/* Device is open		*/
+#define UN_CLOSING	0x0002		/* Line is being closed		*/
+#define UN_IMM		0x0004		/* Service immediately		*/
+#define UN_BUSY		0x0008		/* Some work this channel	*/
+#define UN_BREAKI	0x0010		/* Input break received		*/
+#define UN_PWAIT	0x0020		/* Printer waiting for terminal	*/
+#define UN_TIME		0x0040		/* Waiting on time		*/
+#define UN_EMPTY	0x0080		/* Waiting output queue empty	*/
+#define UN_LOW		0x0100		/* Waiting output low water mark*/
+#define UN_EXCL_OPEN	0x0200		/* Open for exclusive use	*/
+#define UN_WOPEN	0x0400		/* Device waiting for open	*/
+#define UN_WIOCTL	0x0800		/* Device waiting for open	*/
+#define UN_HANGUP	0x8000		/* Carrier lost			*/
+
+struct device;
+
+/************************************************************************
+ * Structure for terminal or printer unit. 
+ ************************************************************************/
+struct un_t {
+	int	magic;		/* Unit Magic Number.			*/
+	struct	channel_t *un_ch;
+	ulong	un_time;
+	uint	un_type;
+	uint	un_open_count;	/* Counter of opens to port		*/
+	struct tty_struct *un_tty;/* Pointer to unit tty structure	*/
+	uint	un_flags;	/* Unit flags				*/
+	wait_queue_head_t un_flags_wait; /* Place to sleep to wait on unit */
+	uint	un_dev;		/* Minor device number			*/
+	struct device *un_sysfs;
+};
+
+
+/************************************************************************ 
+ * Device flag definitions for ch_flags.
+ ************************************************************************/
+#define CH_PRON		0x0001		/* Printer on string		*/
+#define CH_STOP		0x0002		/* Output is stopped		*/
+#define CH_STOPI	0x0004		/* Input is stopped		*/
+#define CH_CD		0x0008		/* Carrier is present		*/
+#define CH_FCAR		0x0010		/* Carrier forced on		*/
+#define CH_HANGUP       0x0020		/* Hangup received		*/
+
+#define CH_RECEIVER_OFF	0x0040		/* Receiver is off		*/
+#define CH_OPENING	0x0080		/* Port in fragile open state	*/
+#define CH_CLOSING	0x0100		/* Port in fragile close state	*/
+#define CH_FIFO_ENABLED 0x0200		/* Port has FIFOs enabled	*/
+#define CH_TX_FIFO_EMPTY 0x0400		/* TX Fifo is completely empty	*/
+#define CH_TX_FIFO_LWM  0x0800		/* TX Fifo is below Low Water	*/
+#define CH_BREAK_SENDING 0x1000		/* Break is being sent		*/
+#define CH_LOOPBACK 0x2000		/* Channel is in lookback mode	*/
+#define CH_FLIPBUF_IN_USE 0x4000	/* Channel's flipbuf is in use	*/
+#define CH_BAUD0	0x08000		/* Used for checking B0 transitions */
+#define CH_FORCED_STOP  0x20000		/* Output is forcibly stopped	*/ 
+#define CH_FORCED_STOPI 0x40000		/* Input is forcibly stopped	*/
+
+/*
+ * Definitions for ch_sniff_flags
+ */
+#define SNIFF_OPEN	0x1
+#define SNIFF_WAIT_DATA	0x2
+#define SNIFF_WAIT_SPACE 0x4
+
+
+/* Our Read/Error/Write queue sizes */
+#define RQUEUEMASK	0x1FFF		/* 8 K - 1 */
+#define EQUEUEMASK	0x1FFF		/* 8 K - 1 */
+#define WQUEUEMASK	0x0FFF		/* 4 K - 1 */
+#define RQUEUESIZE	(RQUEUEMASK + 1)
+#define EQUEUESIZE	RQUEUESIZE
+#define WQUEUESIZE	(WQUEUEMASK + 1)
+
+
+/************************************************************************ 
+ * Channel information structure.
+ ************************************************************************/
+struct channel_t {
+	int magic;			/* Channel Magic Number		*/
+	struct board_t	*ch_bd;		/* Board structure pointer      */
+	struct digi_t	ch_digi;	/* Transparent Print structure  */
+	struct un_t	ch_tun;		/* Terminal unit info           */
+	struct un_t	ch_pun;		/* Printer unit info            */
+
+	spinlock_t	ch_lock;	/* provide for serialization */
+	wait_queue_head_t ch_flags_wait;
+
+	uint		ch_portnum;	/* Port number, 0 offset.	*/
+	uint		ch_open_count;	/* open count			*/
+	uint		ch_flags;	/* Channel flags                */
+
+	ulong		ch_close_delay;	/* How long we should drop RTS/DTR for */
+
+	ulong		ch_cpstime;	/* Time for CPS calculations    */
+
+	tcflag_t	ch_c_iflag;	/* channel iflags               */
+	tcflag_t	ch_c_cflag;	/* channel cflags               */
+	tcflag_t	ch_c_oflag;	/* channel oflags               */
+	tcflag_t	ch_c_lflag;	/* channel lflags               */
+	uchar		ch_stopc;	/* Stop character               */
+	uchar		ch_startc;	/* Start character              */
+
+	uint		ch_old_baud;	/* Cache of the current baud */
+	uint		ch_custom_speed;/* Custom baud, if set */
+
+	uint		ch_wopen;	/* Waiting for open process cnt */
+
+	uchar		ch_mostat;	/* FEP output modem status      */
+	uchar		ch_mistat;	/* FEP input modem status       */
+
+	struct neo_uart_struct *ch_neo_uart;	/* Pointer to the "mapped" UART struct */
+	struct cls_uart_struct *ch_cls_uart;	/* Pointer to the "mapped" UART struct */
+
+	uchar		ch_cached_lsr;	/* Cached value of the LSR register */
+
+	uchar		*ch_rqueue;	/* Our read queue buffer - malloc'ed */
+	ushort		ch_r_head;	/* Head location of the read queue */
+	ushort		ch_r_tail;	/* Tail location of the read queue */
+
+	uchar		*ch_equeue;	/* Our error queue buffer - malloc'ed */
+	ushort		ch_e_head;	/* Head location of the error queue */
+	ushort		ch_e_tail;	/* Tail location of the error queue */
+
+	uchar		*ch_wqueue;	/* Our write queue buffer - malloc'ed */
+	ushort		ch_w_head;	/* Head location of the write queue */
+	ushort		ch_w_tail;	/* Tail location of the write queue */
+
+	ulong		ch_rxcount;	/* total of data received so far */
+	ulong		ch_txcount;	/* total of data transmitted so far */
+
+	uchar		ch_r_tlevel;	/* Receive Trigger level */
+	uchar		ch_t_tlevel;	/* Transmit Trigger level */
+
+	uchar		ch_r_watermark;	/* Receive Watermark */
+
+	ulong		ch_stop_sending_break;	/* Time we should STOP sending a break */
+
+	uint		ch_stops_sent;	/* How many times I have sent a stop character
+					 * to try to stop the other guy sending.
+					 */
+	ulong		ch_err_parity;	/* Count of parity errors on channel */
+	ulong		ch_err_frame;	/* Count of framing errors on channel */
+	ulong		ch_err_break;	/* Count of breaks on channel */
+	ulong		ch_err_overrun; /* Count of overruns on channel */
+
+	ulong		ch_xon_sends;	/* Count of xons transmitted */
+	ulong		ch_xoff_sends;	/* Count of xoffs transmitted */
+
+	ulong		ch_intr_modem;	/* Count of interrupts */
+	ulong		ch_intr_tx;	/* Count of interrupts */
+	ulong		ch_intr_rx;	/* Count of interrupts */
+
+
+	/* /proc/<board>/<channel> entries */
+	struct proc_dir_entry *proc_entry_pointer;
+	struct dgnc_proc_entry *dgnc_channel_table;
+
+	uint ch_sniff_in;
+	uint ch_sniff_out;
+	char *ch_sniff_buf;		/* Sniff buffer for proc */
+	ulong ch_sniff_flags;		/* Channel flags                */
+	wait_queue_head_t ch_sniff_wait;
+};
+
+
+/*************************************************************************
+ *
+ * Prototypes for non-static functions used in more than one module
+ *
+ *************************************************************************/
+
+extern int		dgnc_ms_sleep(ulong ms);
+extern void		*dgnc_driver_kzmalloc(size_t size, int priority);
+extern char		*dgnc_ioctl_name(int cmd);
+
+/*
+ * Our Global Variables.
+ */
+extern int		dgnc_driver_state;	/* The state of the driver	*/
+extern uint		dgnc_Major;		/* Our driver/mgmt major	*/
+extern int		dgnc_debug;		/* Debug variable		*/
+extern int		dgnc_rawreadok;		/* Set if user wants rawreads	*/
+extern int		dgnc_poll_tick;		/* Poll interval - 20 ms	*/
+extern int		dgnc_trcbuf_size;	/* Size of the ringbuffer	*/
+extern spinlock_t	dgnc_global_lock;	/* Driver global spinlock	*/
+extern uint		dgnc_NumBoards;		/* Total number of boards	*/
+extern struct board_t	*dgnc_Board[MAXBOARDS];	/* Array of board structs	*/
+extern ulong		dgnc_poll_counter;	/* Times the poller has run	*/
+extern char		*dgnc_state_text[];	/* Array of state text		*/
+extern char		*dgnc_driver_state_text[];/* Array of driver state text */
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_kcompat.h b/drivers/staging/dgnc/dgnc_kcompat.h
new file mode 100644
index 0000000..3f69e1d
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_kcompat.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2004 Digi International (www.digi.com)
+ *      Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *************************************************************************
+ *
+ * This file is intended to contain all the kernel "differences" between the
+ * various kernels that we support.
+ *
+ *************************************************************************/
+
+#ifndef __DGNC_KCOMPAT_H
+#define __DGNC_KCOMPAT_H
+
+# ifndef KERNEL_VERSION
+#  define KERNEL_VERSION(a,b,c)  (((a) << 16) + ((b) << 8) + (c))
+# endif
+
+
+#if !defined(TTY_FLIPBUF_SIZE)        
+# define TTY_FLIPBUF_SIZE 512        
+#endif
+
+
+/* Sparse stuff */
+# ifndef __user
+#  define __user
+#  define __kernel
+#  define __safe
+#  define __force
+#  define __chk_user_ptr(x) (void)0
+# endif
+
+
+#  define PARM_STR(VAR, INIT, PERM, DESC) \
+		static char *VAR = INIT; \
+		char *dgnc_##VAR; \
+		module_param(VAR, charp, PERM); \
+		MODULE_PARM_DESC(VAR, DESC);
+
+#  define PARM_INT(VAR, INIT, PERM, DESC) \
+		static int VAR = INIT; \
+		int dgnc_##VAR; \
+		module_param(VAR, int, PERM); \
+		MODULE_PARM_DESC(VAR, DESC);
+
+#  define PARM_ULONG(VAR, INIT, PERM, DESC) \
+		static ulong VAR = INIT; \
+		ulong dgnc_##VAR; \
+		module_param(VAR, long, PERM); \
+		MODULE_PARM_DESC(VAR, DESC);
+
+
+
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+
+
+
+/* NOTHING YET */
+
+
+
+# else
+
+
+
+# error "this driver does not support anything below the 2.6.27 kernel series."
+
+
+
+# endif
+
+#endif /* ! __DGNC_KCOMPAT_H */
diff --git a/drivers/staging/dgnc/dgnc_mgmt.c b/drivers/staging/dgnc/dgnc_mgmt.c
new file mode 100644
index 0000000..b8e4792
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_mgmt.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE! 
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com. 
+ *	Thank you. 
+ *
+ */
+
+/************************************************************************
+ * 
+ * This file implements the mgmt functionality for the
+ * Neo and ClassicBoard based product lines.
+ * 
+ ************************************************************************
+ * $Id: dgnc_mgmt.c,v 1.2 2010/12/14 20:08:29 markh Exp $
+ */
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/ctype.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
+#include <linux/serial_reg.h>
+#include <linux/termios.h>
+#include <asm/uaccess.h>	/* For copy_from_user/copy_to_user */
+
+#include "dgnc_driver.h"
+#include "dgnc_pci.h"
+#include "dgnc_proc.h"
+#include "dgnc_kcompat.h"	/* Kernel 2.4/2.6 compat includes */
+#include "dgnc_mgmt.h"
+#include "dpacompat.h"
+
+
+/* Our "in use" variables, to enforce 1 open only */
+static int dgnc_mgmt_in_use[MAXMGMTDEVICES];
+
+
+/*
+ * dgnc_mgmt_open()  
+ *
+ * Open the mgmt/downld/dpa device
+ */  
+int dgnc_mgmt_open(struct inode *inode, struct file *file)
+{
+	unsigned long lock_flags;
+	unsigned int minor = iminor(inode);
+
+	DPR_MGMT(("dgnc_mgmt_open start.\n"));
+
+	DGNC_LOCK(dgnc_global_lock, lock_flags); 
+
+	/* mgmt device */
+	if (minor < MAXMGMTDEVICES) {
+		/* Only allow 1 open at a time on mgmt device */
+		if (dgnc_mgmt_in_use[minor]) {
+			DGNC_UNLOCK(dgnc_global_lock, lock_flags); 
+			return (-EBUSY);
+		}
+		dgnc_mgmt_in_use[minor]++;
+	}
+	else {
+		DGNC_UNLOCK(dgnc_global_lock, lock_flags); 
+		return (-ENXIO);
+	}
+
+	DGNC_UNLOCK(dgnc_global_lock, lock_flags); 
+
+	DPR_MGMT(("dgnc_mgmt_open finish.\n"));
+
+	return 0;
+}
+
+
+/*
+ * dgnc_mgmt_close()
+ *
+ * Open the mgmt/dpa device
+ */  
+int dgnc_mgmt_close(struct inode *inode, struct file *file)
+{
+	unsigned long lock_flags;
+	unsigned int minor = iminor(inode);
+
+	DPR_MGMT(("dgnc_mgmt_close start.\n"));
+
+	DGNC_LOCK(dgnc_global_lock, lock_flags); 
+
+	/* mgmt device */
+	if (minor < MAXMGMTDEVICES) {
+		if (dgnc_mgmt_in_use[minor]) {
+			dgnc_mgmt_in_use[minor] = 0;
+		}
+	}
+	DGNC_UNLOCK(dgnc_global_lock, lock_flags); 
+
+	DPR_MGMT(("dgnc_mgmt_close finish.\n"));
+
+	return 0;
+}
+
+
+/*
+ * dgnc_mgmt_ioctl()
+ *
+ * ioctl the mgmt/dpa device
+ */  
+#ifdef HAVE_UNLOCKED_IOCTL
+long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+#else        
+int dgnc_mgmt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+#endif
+	unsigned long lock_flags;
+	void __user *uarg = (void __user *) arg;
+
+	DPR_MGMT(("dgnc_mgmt_ioctl start.\n"));
+
+	switch (cmd) {
+
+	case DIGI_GETDD:
+	{
+		/*
+		 * This returns the total number of boards
+		 * in the system, as well as driver version
+		 * and has space for a reserved entry
+		 */
+		struct digi_dinfo ddi;
+
+		DGNC_LOCK(dgnc_global_lock, lock_flags); 
+
+		ddi.dinfo_nboards = dgnc_NumBoards;
+		sprintf(ddi.dinfo_version, "%s", DG_PART);
+
+		DGNC_UNLOCK(dgnc_global_lock, lock_flags); 
+
+		DPR_MGMT(("DIGI_GETDD returning numboards: %d version: %s\n",
+			ddi.dinfo_nboards, ddi.dinfo_version));
+
+		if (copy_to_user(uarg, &ddi, sizeof (ddi)))
+			return(-EFAULT);
+
+		break;
+	}
+
+	case DIGI_GETBD:
+	{
+		int brd;
+
+		struct digi_info di;
+
+		if (copy_from_user(&brd, uarg, sizeof(int))) {
+			return(-EFAULT);
+		}
+
+		DPR_MGMT(("DIGI_GETBD asking about board: %d\n", brd));
+
+		if ((brd < 0) || (brd > dgnc_NumBoards) || (dgnc_NumBoards == 0))
+			return (-ENODEV);
+
+		memset(&di, 0, sizeof(di));
+
+		di.info_bdnum = brd;
+
+		DGNC_LOCK(dgnc_Board[brd]->bd_lock, lock_flags); 
+
+		di.info_bdtype = dgnc_Board[brd]->dpatype;
+		di.info_bdstate = dgnc_Board[brd]->dpastatus;
+		di.info_ioport = 0;
+		di.info_physaddr = (ulong) dgnc_Board[brd]->membase;
+		di.info_physsize = (ulong) dgnc_Board[brd]->membase - dgnc_Board[brd]->membase_end;
+		if (dgnc_Board[brd]->state != BOARD_FAILED)
+			di.info_nports = dgnc_Board[brd]->nasync;
+		else
+			di.info_nports = 0;
+
+		DGNC_UNLOCK(dgnc_Board[brd]->bd_lock, lock_flags); 
+
+		DPR_MGMT(("DIGI_GETBD returning type: %x state: %x ports: %x size: %x\n",
+			di.info_bdtype, di.info_bdstate, di.info_nports, di.info_physsize));
+
+		if (copy_to_user(uarg, &di, sizeof (di)))
+			return (-EFAULT);
+
+		break;
+	}
+
+	case DIGI_GET_NI_INFO:
+	{
+		struct channel_t *ch;
+		struct ni_info ni;
+		uchar mstat = 0;
+		uint board = 0;
+		uint channel = 0;
+
+		if (copy_from_user(&ni, uarg, sizeof(struct ni_info))) {
+			return(-EFAULT);
+		}
+
+		DPR_MGMT(("DIGI_GETBD asking about board: %d channel: %d\n",
+			ni.board, ni.channel));
+
+		board = ni.board;
+		channel = ni.channel;
+
+		/* Verify boundaries on board */
+		if ((board < 0) || (board > dgnc_NumBoards) || (dgnc_NumBoards == 0))
+			return (-ENODEV);
+
+		/* Verify boundaries on channel */
+		if ((channel < 0) || (channel > dgnc_Board[board]->nasync))
+			return (-ENODEV);
+
+		ch = dgnc_Board[board]->channels[channel];
+
+		if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+			return (-ENODEV);
+
+		memset(&ni, 0, sizeof(ni));
+		ni.board = board;
+		ni.channel = channel;
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		mstat = (ch->ch_mostat | ch->ch_mistat);
+
+		if (mstat & UART_MCR_DTR) {
+			ni.mstat |= TIOCM_DTR;
+			ni.dtr = TIOCM_DTR;
+		}
+		if (mstat & UART_MCR_RTS) {
+			ni.mstat |= TIOCM_RTS;
+			ni.rts = TIOCM_RTS;
+		}
+		if (mstat & UART_MSR_CTS) {
+			ni.mstat |= TIOCM_CTS;
+			ni.cts = TIOCM_CTS;
+		}
+		if (mstat & UART_MSR_RI) {
+			ni.mstat |= TIOCM_RI;
+			ni.ri = TIOCM_RI;
+		}
+		if (mstat & UART_MSR_DCD) {
+			ni.mstat |= TIOCM_CD;
+			ni.dcd = TIOCM_CD;
+		}
+		if (mstat & UART_MSR_DSR)
+			ni.mstat |= TIOCM_DSR;
+
+		ni.iflag = ch->ch_c_iflag;
+		ni.oflag = ch->ch_c_oflag;
+		ni.cflag = ch->ch_c_cflag;
+		ni.lflag = ch->ch_c_lflag;
+
+		if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS)
+			ni.hflow = 1;
+		else
+			ni.hflow = 0;
+
+		if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI))
+			ni.recv_stopped = 1;
+		else
+			ni.recv_stopped = 0;
+
+		if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP))
+			ni.xmit_stopped = 1;
+		else
+			ni.xmit_stopped = 0;
+
+		ni.curtx = ch->ch_txcount;
+		ni.currx = ch->ch_rxcount;
+
+		ni.baud = ch->ch_old_baud;
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		if (copy_to_user(uarg, &ni, sizeof(ni)))
+			return (-EFAULT);
+
+		break;
+	}
+
+
+	}
+
+	DPR_MGMT(("dgnc_mgmt_ioctl finish.\n"));
+
+	return 0;
+}
diff --git a/drivers/staging/dgnc/dgnc_mgmt.h b/drivers/staging/dgnc/dgnc_mgmt.h
new file mode 100644
index 0000000..a0d1338
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_mgmt.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_MGMT_H
+#define __DGNC_MGMT_H
+
+#define MAXMGMTDEVICES 8
+
+int dgnc_mgmt_open(struct inode *inode, struct file *file);
+int dgnc_mgmt_close(struct inode *inode, struct file *file);
+
+#ifdef HAVE_UNLOCKED_IOCTL
+long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+#else
+int dgnc_mgmt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
+#endif
+
+#endif
+
diff --git a/drivers/staging/dgnc/dgnc_neo.c b/drivers/staging/dgnc/dgnc_neo.c
new file mode 100644
index 0000000..503db8f
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_neo.c
@@ -0,0 +1,1977 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
+ *	Thank you.
+ *
+ *
+ * $Id: dgnc_neo.c,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>    /* For tasklet and interrupt structs/defines */
+#include <linux/delay.h>	/* For udelay */
+#include <asm/io.h>		/* For read[bwl]/write[bwl] */
+#include <linux/serial.h>	/* For struct async_serial */
+#include <linux/serial_reg.h>	/* For the various UART offsets */
+
+#include "dgnc_driver.h"	/* Driver main header file */
+#include "dgnc_neo.h"		/* Our header file */
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+
+static inline void neo_parse_lsr(struct board_t *brd, uint port);
+static inline void neo_parse_isr(struct board_t *brd, uint port);
+static void neo_copy_data_from_uart_to_queue(struct channel_t *ch);
+static inline void neo_clear_break(struct channel_t *ch, int force);
+static inline void neo_set_cts_flow_control(struct channel_t *ch);
+static inline void neo_set_rts_flow_control(struct channel_t *ch);
+static inline void neo_set_ixon_flow_control(struct channel_t *ch);
+static inline void neo_set_ixoff_flow_control(struct channel_t *ch);
+static inline void neo_set_no_output_flow_control(struct channel_t *ch);
+static inline void neo_set_no_input_flow_control(struct channel_t *ch);
+static inline void neo_set_new_start_stop_chars(struct channel_t *ch);
+static void neo_parse_modem(struct channel_t *ch, uchar signals);
+static void neo_tasklet(unsigned long data);
+static void neo_vpd(struct board_t *brd);
+static void neo_uart_init(struct channel_t *ch);
+static void neo_uart_off(struct channel_t *ch);
+static int neo_drain(struct tty_struct *tty, uint seconds);
+static void neo_param(struct tty_struct *tty);
+static void neo_assert_modem_signals(struct channel_t *ch);
+static void neo_flush_uart_write(struct channel_t *ch);
+static void neo_flush_uart_read(struct channel_t *ch);
+static void neo_disable_receiver(struct channel_t *ch);
+static void neo_enable_receiver(struct channel_t *ch);
+static void neo_send_break(struct channel_t *ch, int msecs);
+static void neo_send_start_character(struct channel_t *ch);
+static void neo_send_stop_character(struct channel_t *ch);
+static void neo_copy_data_from_queue_to_uart(struct channel_t *ch);
+static uint neo_get_uart_bytes_left(struct channel_t *ch);
+static void neo_send_immediate_char(struct channel_t *ch, unsigned char c);
+static irqreturn_t neo_intr(int irq, void *voidbrd);
+
+
+struct board_ops dgnc_neo_ops = {
+	.tasklet =			neo_tasklet,
+	.intr =				neo_intr,
+	.uart_init =			neo_uart_init,
+	.uart_off =			neo_uart_off,
+	.drain =			neo_drain,
+	.param =			neo_param,
+	.vpd =				neo_vpd,
+	.assert_modem_signals =		neo_assert_modem_signals,
+	.flush_uart_write =		neo_flush_uart_write,
+	.flush_uart_read =		neo_flush_uart_read,
+	.disable_receiver =		neo_disable_receiver,
+	.enable_receiver =		neo_enable_receiver,
+	.send_break =			neo_send_break,
+	.send_start_character =		neo_send_start_character,
+	.send_stop_character =		neo_send_stop_character,
+	.copy_data_from_queue_to_uart =	neo_copy_data_from_queue_to_uart,
+	.get_uart_bytes_left =		neo_get_uart_bytes_left,
+	.send_immediate_char =		neo_send_immediate_char
+};
+        
+static uint dgnc_offset_table[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+
+
+/*
+ * This function allows calls to ensure that all outstanding
+ * PCI writes have been completed, by doing a PCI read against
+ * a non-destructive, read-only location on the Neo card.
+ *
+ * In this case, we are reading the DVID (Read-only Device Identification)
+ * value of the Neo card.
+ */
+static inline void neo_pci_posting_flush(struct board_t *bd)
+{
+	readb(bd->re_map_membase + 0x8D);
+}
+
+static inline void neo_set_cts_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+
+	DPR_PARAM(("Setting CTSFLOW\n"));
+
+	/* Turn on auto CTS flow control */
+#if 1
+	ier |= (UART_17158_IER_CTSDSR);
+#else
+	ier &= ~(UART_17158_IER_CTSDSR);
+#endif
+
+	efr |= (UART_17158_EFR_ECB | UART_17158_EFR_CTSDSR);
+
+	/* Turn off auto Xon flow control */   
+	efr &= ~(UART_17158_EFR_IXON);
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	/* Turn on table D, with 8 char hi/low watermarks */
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr);
+
+	/* Feed the UART our trigger levels */
+	writeb(8, &ch->ch_neo_uart->tfifo);
+	ch->ch_t_tlevel = 8;
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_rts_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+	DPR_PARAM(("Setting RTSFLOW\n"));
+
+	/* Turn on auto RTS flow control */
+#if 1
+	ier |= (UART_17158_IER_RTSDTR);
+#else
+	ier &= ~(UART_17158_IER_RTSDTR);
+#endif
+	efr |= (UART_17158_EFR_ECB | UART_17158_EFR_RTSDTR);
+
+	/* Turn off auto Xoff flow control */
+	ier &= ~(UART_17158_IER_XOFF);
+	efr &= ~(UART_17158_EFR_IXOFF);
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr);
+	ch->ch_r_watermark = 4;
+
+	writeb(32, &ch->ch_neo_uart->rfifo);
+	ch->ch_r_tlevel = 32;
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	/*
+	 * From the Neo UART spec sheet:
+	 * The auto RTS/DTR function must be started by asserting
+	 * RTS/DTR# output pin (MCR bit-0 or 1 to logic 1 after
+	 * it is enabled.
+	 */
+	ch->ch_mostat |= (UART_MCR_RTS);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_ixon_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+	DPR_PARAM(("Setting IXON FLOW\n"));
+
+	/* Turn off auto CTS flow control */
+	ier &= ~(UART_17158_IER_CTSDSR);
+	efr &= ~(UART_17158_EFR_CTSDSR);
+
+	/* Turn on auto Xon flow control */
+	efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXON);
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+	ch->ch_r_watermark = 4;
+
+	writeb(32, &ch->ch_neo_uart->rfifo);
+	ch->ch_r_tlevel = 32;
+
+	/* Tell UART what start/stop chars it should be looking for */
+	writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+	writeb(0, &ch->ch_neo_uart->xonchar2);
+
+	writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+	writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_ixoff_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+	DPR_PARAM(("Setting IXOFF FLOW\n"));
+
+	/* Turn off auto RTS flow control */
+	ier &= ~(UART_17158_IER_RTSDTR);
+	efr &= ~(UART_17158_EFR_RTSDTR);
+
+	/* Turn on auto Xoff flow control */
+	ier |= (UART_17158_IER_XOFF);
+	efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXOFF);
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	/* Turn on table D, with 8 char hi/low watermarks */
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+	writeb(8, &ch->ch_neo_uart->tfifo);
+        ch->ch_t_tlevel = 8;
+
+	/* Tell UART what start/stop chars it should be looking for */
+	writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+	writeb(0, &ch->ch_neo_uart->xonchar2);
+
+	writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+	writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_no_input_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+	DPR_PARAM(("Unsetting Input FLOW\n"));
+
+	/* Turn off auto RTS flow control */
+	ier &= ~(UART_17158_IER_RTSDTR);
+	efr &= ~(UART_17158_EFR_RTSDTR);
+
+	/* Turn off auto Xoff flow control */
+	ier &= ~(UART_17158_IER_XOFF);
+	if (ch->ch_c_iflag & IXON)
+		efr &= ~(UART_17158_EFR_IXOFF);
+	else
+		efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXOFF);
+
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	/* Turn on table D, with 8 char hi/low watermarks */
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+	ch->ch_r_watermark = 0;
+
+	writeb(16, &ch->ch_neo_uart->tfifo);
+        ch->ch_t_tlevel = 16;
+
+	writeb(16, &ch->ch_neo_uart->rfifo);
+        ch->ch_r_tlevel = 16;
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_no_output_flow_control(struct channel_t *ch)
+{
+	uchar ier = readb(&ch->ch_neo_uart->ier);
+	uchar efr = readb(&ch->ch_neo_uart->efr);
+
+	DPR_PARAM(("Unsetting Output FLOW\n"));
+
+	/* Turn off auto CTS flow control */
+	ier &= ~(UART_17158_IER_CTSDSR);
+	efr &= ~(UART_17158_EFR_CTSDSR);
+
+	/* Turn off auto Xon flow control */
+	if (ch->ch_c_iflag & IXOFF)
+		efr &= ~(UART_17158_EFR_IXON);
+	else
+		efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXON);
+
+	/* Why? Becuz Exar's spec says we have to zero it out before setting it */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Turn on UART enhanced bits */
+	writeb(efr, &ch->ch_neo_uart->efr);
+
+	/* Turn on table D, with 8 char hi/low watermarks */
+	writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+	ch->ch_r_watermark = 0;
+
+	writeb(16, &ch->ch_neo_uart->tfifo);
+        ch->ch_t_tlevel = 16;
+
+	writeb(16, &ch->ch_neo_uart->rfifo);
+        ch->ch_r_tlevel = 16;
+
+	writeb(ier, &ch->ch_neo_uart->ier);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/* change UARTs start/stop chars */
+static inline void neo_set_new_start_stop_chars(struct channel_t *ch)
+{
+
+	/* if hardware flow control is set, then skip this whole thing */
+	if (ch->ch_digi.digi_flags & (CTSPACE | RTSPACE) || ch->ch_c_cflag & CRTSCTS)
+		return;
+
+	DPR_PARAM(("In new start stop chars\n"));
+
+	/* Tell UART what start/stop chars it should be looking for */
+	writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+	writeb(0, &ch->ch_neo_uart->xonchar2);
+
+	writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+	writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * No locks are assumed to be held when calling this function.
+ */
+static inline void neo_clear_break(struct channel_t *ch, int force)
+{
+	ulong lock_flags;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* Bail if we aren't currently sending a break. */
+	if (!ch->ch_stop_sending_break) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/* Turn break off, and unset some variables */
+	if (ch->ch_flags & CH_BREAK_SENDING) {
+		if ((jiffies >= ch->ch_stop_sending_break) || force) {
+			uchar temp = readb(&ch->ch_neo_uart->lcr);
+        	        writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+			neo_pci_posting_flush(ch->ch_bd);
+			ch->ch_flags &= ~(CH_BREAK_SENDING);
+			ch->ch_stop_sending_break = 0;
+			DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+                }
+        }
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * Parse the ISR register.
+ */
+static inline void neo_parse_isr(struct board_t *brd, uint port)
+{
+	struct channel_t *ch;
+	uchar isr;
+	uchar cause;
+	ulong lock_flags;
+
+	if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	if (port > brd->maxports)
+		return;
+
+	ch = brd->channels[port];
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	/* Here we try to figure out what caused the interrupt to happen */
+	while (1) {
+
+		isr = readb(&ch->ch_neo_uart->isr_fcr);
+
+		/* Bail if no pending interrupt */
+		if (isr & UART_IIR_NO_INT)  {
+			break;
+		}
+
+		/*
+		 * Yank off the upper 2 bits, which just show that the FIFO's are enabled.
+		 */
+		isr &= ~(UART_17158_IIR_FIFO_ENABLED);
+
+		DPR_INTR(("%s:%d isr: %x\n", __FILE__, __LINE__, isr));
+
+		if (isr & (UART_17158_IIR_RDI_TIMEOUT | UART_IIR_RDI)) {
+			/* Read data from uart -> queue */
+			brd->intr_rx++;
+			ch->ch_intr_rx++;
+			neo_copy_data_from_uart_to_queue(ch);
+
+			/* Call our tty layer to enforce queue flow control if needed. */
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+			dgnc_check_queue_flow_control(ch);
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		}
+
+		if (isr & UART_IIR_THRI) {
+			brd->intr_tx++;
+			ch->ch_intr_tx++;
+			/* Transfer data (if any) from Write Queue -> UART. */
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+			ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			neo_copy_data_from_queue_to_uart(ch);
+		}
+
+		if (isr & UART_17158_IIR_XONXOFF) {
+			cause = readb(&ch->ch_neo_uart->xoffchar1);
+
+			DPR_INTR(("Port %d. Got ISR_XONXOFF: cause:%x\n", port, cause));
+
+			/*
+			 * Since the UART detected either an XON or
+			 * XOFF match, we need to figure out which
+			 * one it was, so we can suspend or resume data flow.
+			 */
+			if (cause == UART_17158_XON_DETECT) {
+				/* Is output stopped right now, if so, resume it */
+				if (brd->channels[port]->ch_flags & CH_STOP) {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_flags &= ~(CH_STOP);
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				}
+				DPR_INTR(("Port %d. XON detected in incoming data\n", port));
+			} 
+			else if (cause == UART_17158_XOFF_DETECT) {
+				if (!(brd->channels[port]->ch_flags & CH_STOP)) {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_flags |= CH_STOP;
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+					DPR_INTR(("Setting CH_STOP\n"));
+				}
+				DPR_INTR(("Port: %d. XOFF detected in incoming data\n", port));
+			}
+		}
+
+		if (isr & UART_17158_IIR_HWFLOW_STATE_CHANGE) {
+			/*
+			 * If we get here, this means the hardware is doing auto flow control.
+			 * Check to see whether RTS/DTR or CTS/DSR caused this interrupt.
+			 */
+			brd->intr_modem++;
+			ch->ch_intr_modem++;
+			cause = readb(&ch->ch_neo_uart->mcr);
+			/* Which pin is doing auto flow? RTS or DTR? */
+			if ((cause & 0x4) == 0) {
+				if (cause & UART_MCR_RTS) {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_mostat |= UART_MCR_RTS;
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				}
+				else {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_mostat &= ~(UART_MCR_RTS);
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				}
+			} else {
+				if (cause & UART_MCR_DTR) {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_mostat |= UART_MCR_DTR;
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				}
+				else {
+					DGNC_LOCK(ch->ch_lock, lock_flags);
+					ch->ch_mostat &= ~(UART_MCR_DTR);
+					DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				}
+			}
+		}
+
+		/* Parse any modem signal changes */
+		DPR_INTR(("MOD_STAT: sending to parse_modem_sigs\n"));
+		neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr));
+	}
+}
+
+
+static inline void neo_parse_lsr(struct board_t *brd, uint port)
+{
+	struct channel_t *ch;
+	int linestatus;
+	ulong lock_flags;
+
+	if (!brd)
+		return;
+
+	if (brd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	if (port > brd->maxports)
+		return;
+
+	ch = brd->channels[port];
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	linestatus = readb(&ch->ch_neo_uart->lsr);
+
+	DPR_INTR(("%s:%d port: %d linestatus: %x\n", __FILE__, __LINE__, port, linestatus));
+
+	ch->ch_cached_lsr |= linestatus;
+
+	if (ch->ch_cached_lsr & UART_LSR_DR) {
+		brd->intr_rx++;
+		ch->ch_intr_rx++;
+		/* Read data from uart -> queue */
+		neo_copy_data_from_uart_to_queue(ch);
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		dgnc_check_queue_flow_control(ch);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+	}
+
+	/*
+	 * This is a special flag. It indicates that at least 1
+	 * RX error (parity, framing, or break) has happened.
+	 * Mark this in our struct, which will tell me that I have
+	 *to do the special RX+LSR read for this FIFO load.
+	 */
+	if (linestatus & UART_17158_RX_FIFO_DATA_ERROR) {
+		DPR_INTR(("%s:%d Port: %d Got an RX error, need to parse LSR\n",
+			__FILE__, __LINE__, port));
+	}
+
+	/*
+	 * The next 3 tests should *NOT* happen, as the above test
+	 * should encapsulate all 3... At least, thats what Exar says.
+	 */
+
+	if (linestatus & UART_LSR_PE) {
+		ch->ch_err_parity++;
+		DPR_INTR(("%s:%d Port: %d. PAR ERR!\n", __FILE__, __LINE__, port));
+	}
+
+	if (linestatus & UART_LSR_FE) {
+		ch->ch_err_frame++;
+		DPR_INTR(("%s:%d Port: %d. FRM ERR!\n", __FILE__, __LINE__, port));
+	}
+
+	if (linestatus & UART_LSR_BI) {
+		ch->ch_err_break++;
+		DPR_INTR(("%s:%d Port: %d. BRK INTR!\n", __FILE__, __LINE__, port));
+	}
+
+	if (linestatus & UART_LSR_OE) {
+		/*
+		 * Rx Oruns. Exar says that an orun will NOT corrupt
+		 * the FIFO. It will just replace the holding register
+		 * with this new data byte. So basically just ignore this.
+		 * Probably we should eventually have an orun stat in our driver...
+		 */
+		ch->ch_err_overrun++;
+		DPR_INTR(("%s:%d Port: %d. Rx Overrun!\n", __FILE__, __LINE__, port));
+	}
+
+	if (linestatus & UART_LSR_THRE) {
+		brd->intr_tx++;
+		ch->ch_intr_tx++;
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/* Transfer data (if any) from Write Queue -> UART. */
+		neo_copy_data_from_queue_to_uart(ch);
+	}
+	else if (linestatus & UART_17158_TX_AND_FIFO_CLR) {
+		brd->intr_tx++;
+		ch->ch_intr_tx++;
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/* Transfer data (if any) from Write Queue -> UART. */
+		neo_copy_data_from_queue_to_uart(ch);
+	}
+}
+
+
+/*
+ * neo_param()
+ * Send any/all changes to the line to the UART.
+ */
+static void neo_param(struct tty_struct *tty)
+{
+	uchar lcr = 0;
+	uchar uart_lcr = 0;
+	uchar ier = 0;
+	uchar uart_ier = 0;
+        uint baud = 9600;
+	int quot = 0;
+        struct board_t *bd;
+	struct channel_t *ch;
+        struct un_t   *un;
+
+	if (!tty || tty->magic != TTY_MAGIC) {
+		return;
+	}
+
+	un = (struct un_t *) tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC) {
+		return;
+	}
+
+	ch = un->un_ch;   
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+		return;
+	}
+
+	DPR_PARAM(("param start: tdev: %x cflags: %x oflags: %x iflags: %x\n",
+		ch->ch_tun.un_dev, ch->ch_c_cflag, ch->ch_c_oflag, ch->ch_c_iflag));
+         
+	/*
+	 * If baud rate is zero, flush queues, and set mval to drop DTR.
+	 */
+	if ((ch->ch_c_cflag & (CBAUD)) == 0) {
+		ch->ch_r_head = ch->ch_r_tail = 0;
+		ch->ch_e_head = ch->ch_e_tail = 0;
+		ch->ch_w_head = ch->ch_w_tail = 0;
+
+		neo_flush_uart_write(ch);
+		neo_flush_uart_read(ch);
+
+		/* The baudrate is B0 so all modem lines are to be dropped. */
+		ch->ch_flags |= (CH_BAUD0);
+		ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR);
+		neo_assert_modem_signals(ch);
+		ch->ch_old_baud = 0;
+		return;
+
+	} else if (ch->ch_custom_speed) {
+
+		baud = ch->ch_custom_speed;
+		/* Handle transition from B0 */
+		if (ch->ch_flags & CH_BAUD0) {
+			ch->ch_flags &= ~(CH_BAUD0);
+
+			/*
+			 * Bring back up RTS and DTR...
+			 * Also handle RTS or DTR toggle if set.
+			 */
+			if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_RTS);
+			if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_DTR);
+		}
+	} else {
+		int iindex = 0;
+		int jindex = 0;
+
+		ulong bauds[4][16] = {
+			{ /* slowbaud */
+				0,      50,     75,     110,
+				134,    150,    200,    300,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* slowbaud & CBAUDEX */
+				0,      57600,  115200, 230400,
+				460800, 150,    200,    921600,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* fastbaud */
+				0,      57600,   76800, 115200,
+				131657, 153600, 230400, 460800,
+				921600, 1200,   1800,   2400,
+				4800,   9600,   19200,  38400 },
+			{ /* fastbaud & CBAUDEX */
+				0,      57600,  115200, 230400,
+				460800, 150,    200,    921600,
+				600,    1200,   1800,   2400,
+				4800,   9600,   19200,  38400 }
+		};
+
+		/* Only use the TXPrint baud rate if the terminal unit is NOT open */
+		if (!(ch->ch_tun.un_flags & UN_ISOPEN) && (un->un_type == DGNC_PRINT))
+			baud = C_BAUD(ch->ch_pun.un_tty) & 0xff;
+		else
+			baud = C_BAUD(ch->ch_tun.un_tty) & 0xff;
+
+		if (ch->ch_c_cflag & CBAUDEX)
+			iindex = 1;
+
+		if (ch->ch_digi.digi_flags & DIGI_FAST)
+			iindex += 2;
+
+		jindex = baud;
+
+		if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && (jindex < 16)) {
+			baud = bauds[iindex][jindex];  
+		} else {
+			DPR_IOCTL(("baud indices were out of range (%d)(%d)",
+				iindex, jindex));
+			baud = 0;
+		}
+
+		if (baud == 0)
+			baud = 9600;
+
+		/* Handle transition from B0 */
+		if (ch->ch_flags & CH_BAUD0) {
+			ch->ch_flags &= ~(CH_BAUD0);
+
+			/*
+			 * Bring back up RTS and DTR...
+			 * Also handle RTS or DTR toggle if set.
+			 */
+			if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_RTS);
+			if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+				ch->ch_mostat |= (UART_MCR_DTR);
+		}
+	}
+
+	if (ch->ch_c_cflag & PARENB) {
+		lcr |= UART_LCR_PARITY;
+	}
+
+	if (!(ch->ch_c_cflag & PARODD)) {
+		lcr |= UART_LCR_EPAR;
+	}
+
+	/* 
+	 * Not all platforms support mark/space parity,
+	 * so this will hide behind an ifdef.
+	 */
+#ifdef CMSPAR
+	if (ch->ch_c_cflag & CMSPAR) 
+		lcr |= UART_LCR_SPAR;
+#endif
+
+	if (ch->ch_c_cflag & CSTOPB)
+		lcr |= UART_LCR_STOP;
+
+	switch (ch->ch_c_cflag & CSIZE) {
+	case CS5:
+		lcr |= UART_LCR_WLEN5;
+		break;
+	case CS6:
+		lcr |= UART_LCR_WLEN6;
+		break;
+	case CS7:
+		lcr |= UART_LCR_WLEN7;
+		break;
+	case CS8:
+	default:
+		lcr |= UART_LCR_WLEN8;
+		break;
+	}
+
+	ier = uart_ier = readb(&ch->ch_neo_uart->ier);
+	uart_lcr = readb(&ch->ch_neo_uart->lcr);
+
+	if (baud == 0)
+		baud = 9600;
+
+	quot = ch->ch_bd->bd_dividend / baud;
+
+	if (quot != 0 && ch->ch_old_baud != baud) {
+		ch->ch_old_baud = baud;
+		writeb(UART_LCR_DLAB, &ch->ch_neo_uart->lcr);
+		writeb((quot & 0xff), &ch->ch_neo_uart->txrx);
+		writeb((quot >> 8), &ch->ch_neo_uart->ier);
+		writeb(lcr, &ch->ch_neo_uart->lcr);
+        }
+
+	if (uart_lcr != lcr)
+		writeb(lcr, &ch->ch_neo_uart->lcr);
+
+	if (ch->ch_c_cflag & CREAD) {
+		ier |= (UART_IER_RDI | UART_IER_RLSI);
+	}
+	else {
+		ier &= ~(UART_IER_RDI | UART_IER_RLSI);
+	}
+
+	/*
+	 * Have the UART interrupt on modem signal changes ONLY when
+	 * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set.
+	 */
+	if ((ch->ch_digi.digi_flags & CTSPACE) || (ch->ch_digi.digi_flags & RTSPACE) ||
+		(ch->ch_c_cflag & CRTSCTS) || !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+		!(ch->ch_c_cflag & CLOCAL))
+	{
+		ier |= UART_IER_MSI;
+	}
+	else {
+		ier &= ~UART_IER_MSI;
+	}
+
+	ier |= UART_IER_THRI;
+
+	if (ier != uart_ier)
+		writeb(ier, &ch->ch_neo_uart->ier);
+
+	/* Set new start/stop chars */
+	neo_set_new_start_stop_chars(ch);
+
+	if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+		neo_set_cts_flow_control(ch);
+	}
+	else if (ch->ch_c_iflag & IXON) {
+		/* If start/stop is set to disable, then we should disable flow control */
+		if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+			neo_set_no_output_flow_control(ch);
+		else
+			neo_set_ixon_flow_control(ch);
+	}
+	else {
+		neo_set_no_output_flow_control(ch);
+	}
+
+	if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+		neo_set_rts_flow_control(ch);
+	}
+	else if (ch->ch_c_iflag & IXOFF) {
+		/* If start/stop is set to disable, then we should disable flow control */
+		if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+			neo_set_no_input_flow_control(ch);
+		else
+			neo_set_ixoff_flow_control(ch);
+	} 
+	else {
+		neo_set_no_input_flow_control(ch);
+	}
+
+	/*
+	 * Adjust the RX FIFO Trigger level if baud is less than 9600.
+	 * Not exactly elegant, but this is needed because of the Exar chip's
+	 * delay on firing off the RX FIFO interrupt on slower baud rates.
+	 */
+	if (baud < 9600) {
+		writeb(1, &ch->ch_neo_uart->rfifo);
+		ch->ch_r_tlevel = 1;
+	}
+
+	neo_assert_modem_signals(ch);
+
+	/* Get current status of the modem signals now */
+	neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr));
+}
+
+
+/*
+ * Our board poller function.
+ */
+static void neo_tasklet(unsigned long data)
+{
+        struct board_t *bd = (struct board_t *) data;
+	struct channel_t *ch;
+	ulong  lock_flags;
+	int i;
+	int state = 0;
+	int ports = 0;
+
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+		APR(("poll_tasklet() - NULL or bad bd.\n"));
+		return;
+	}
+
+	/* Cache a couple board values */
+	DGNC_LOCK(bd->bd_lock, lock_flags);
+	state = bd->state;
+	ports = bd->nasync;
+	DGNC_UNLOCK(bd->bd_lock, lock_flags);
+
+	/*
+	 * Do NOT allow the interrupt routine to read the intr registers
+	 * Until we release this lock.
+	 */
+	DGNC_LOCK(bd->bd_intr_lock, lock_flags);
+
+	/*
+	 * If board is ready, parse deeper to see if there is anything to do.
+	 */
+	if ((state == BOARD_READY) && (ports > 0)) {
+		/* Loop on each port */
+		for (i = 0; i < ports; i++) {
+			ch = bd->channels[i];
+
+			/* Just being careful... */
+			if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+				continue;
+
+			/*
+			 * NOTE: Remember you CANNOT hold any channel
+			 * locks when calling the input routine.
+			 *
+			 * During input processing, its possible we
+			 * will call the Linux ld, which might in turn,
+			 * do a callback right back into us, resulting
+			 * in us trying to grab the channel lock twice!
+			 */
+			dgnc_input(ch);
+
+			/*
+			 * Channel lock is grabbed and then released
+			 * inside both of these routines, but neither
+			 * call anything else that could call back into us.
+			 */
+			neo_copy_data_from_queue_to_uart(ch);
+			dgnc_wakeup_writes(ch);
+
+			/*
+			 * Call carrier carrier function, in case something
+			 * has changed.
+			 */
+			dgnc_carrier(ch);
+
+			/*
+			 * Check to see if we need to turn off a sending break.
+			 * The timing check is done inside clear_break()
+			 */
+			if (ch->ch_stop_sending_break)
+				neo_clear_break(ch, 0);
+		}
+	}
+
+	/* Allow interrupt routine to access the interrupt register again */
+	DGNC_UNLOCK(bd->bd_intr_lock, lock_flags);
+
+}
+
+
+/*
+ * dgnc_neo_intr()
+ *
+ * Neo specific interrupt handler.
+ */
+static irqreturn_t neo_intr(int irq, void *voidbrd)
+{
+	struct board_t *brd = (struct board_t *) voidbrd;
+	struct channel_t *ch;
+	int port = 0;
+	int type = 0;
+	int current_port;
+	u32 tmp;
+	u32 uart_poll;
+	unsigned long lock_flags;
+	unsigned long lock_flags2;
+
+	if (!brd) {
+		APR(("Received interrupt (%d) with null board associated\n", irq));
+		return IRQ_NONE;
+	}
+
+	/*
+	 * Check to make sure its for us.
+	 */
+	if (brd->magic != DGNC_BOARD_MAGIC) {
+		APR(("Received interrupt (%d) with a board pointer that wasn't ours!\n", irq));
+		return IRQ_NONE;
+	}
+
+	brd->intr_count++;
+
+	/* Lock out the slow poller from running on this board. */
+	DGNC_LOCK(brd->bd_intr_lock, lock_flags);
+
+	/*
+	 * Read in "extended" IRQ information from the 32bit Neo register.
+	 * Bits 0-7: What port triggered the interrupt.
+	 * Bits 8-31: Each 3bits indicate what type of interrupt occurred.
+	 */
+	uart_poll = readl(brd->re_map_membase + UART_17158_POLL_ADDR_OFFSET);
+
+	DPR_INTR(("%s:%d uart_poll: %x\n", __FILE__, __LINE__, uart_poll));
+
+	/*
+	 * If 0, no interrupts pending.
+	 * This can happen if the IRQ is shared among a couple Neo/Classic boards.
+	 */
+	if (!uart_poll) {
+		DPR_INTR(("Kernel interrupted to me, but no pending interrupts...\n"));
+		DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+		return IRQ_NONE;
+	}
+
+	/* At this point, we have at least SOMETHING to service, dig further... */
+
+	current_port = 0;
+
+	/* Loop on each port */
+	while ((uart_poll & 0xff) != 0) {
+
+		tmp = uart_poll;
+
+		/* Check current port to see if it has interrupt pending */
+		if ((tmp & dgnc_offset_table[current_port]) != 0) {
+			port = current_port;
+			type = tmp >> (8 + (port * 3));
+			type &= 0x7;
+		} else {
+			current_port++;
+			continue;
+		}
+
+		DPR_INTR(("%s:%d port: %x type: %x\n", __FILE__, __LINE__, port, type));
+
+		/* Remove this port + type from uart_poll */
+		uart_poll &= ~(dgnc_offset_table[port]);
+
+		if (!type) {
+			/* If no type, just ignore it, and move onto next port */
+			DPR_INTR(("Interrupt with no type! port: %d\n", port));
+			continue;
+		}
+
+		/* Switch on type of interrupt we have */
+		switch (type) {
+
+		case UART_17158_RXRDY_TIMEOUT:
+			/*
+			 * RXRDY Time-out is cleared by reading data in the
+                	 * RX FIFO until it falls below the trigger level.
+			 */
+
+			/* Verify the port is in range. */
+			if (port > brd->nasync)
+				continue;
+
+			ch = brd->channels[port];
+			neo_copy_data_from_uart_to_queue(ch);
+
+			/* Call our tty layer to enforce queue flow control if needed. */
+			DGNC_LOCK(ch->ch_lock, lock_flags2);
+			dgnc_check_queue_flow_control(ch);
+			DGNC_UNLOCK(ch->ch_lock, lock_flags2);
+
+			continue;
+
+		case UART_17158_RX_LINE_STATUS:
+			/*
+			 * RXRDY and RX LINE Status (logic OR of LSR[4:1])
+			 */
+			neo_parse_lsr(brd, port);
+			continue;
+
+		case UART_17158_TXRDY:
+			/*
+			 * TXRDY interrupt clears after reading ISR register for the UART channel.
+			 */
+
+			/*
+			 * Yes, this is odd...
+			 * Why would I check EVERY possibility of type of
+			 * interrupt, when we know its TXRDY???
+			 * Becuz for some reason, even tho we got triggered for TXRDY,
+			 * it seems to be occassionally wrong. Instead of TX, which
+			 * it should be, I was getting things like RXDY too. Weird.
+			 */
+			neo_parse_isr(brd, port);
+			continue;
+
+		case UART_17158_MSR:
+			/*
+			 * MSR or flow control was seen.
+			 */
+			neo_parse_isr(brd, port);
+			continue;
+                   
+		default:
+			/*
+			 * The UART triggered us with a bogus interrupt type.
+			 * It appears the Exar chip, when REALLY bogged down, will throw
+			 * these once and awhile.
+			 * Its harmless, just ignore it and move on.
+			 */
+			DPR_INTR(("%s:%d Unknown Interrupt type: %x\n", __FILE__, __LINE__, type));
+			continue;
+		}
+	}
+
+	/*
+	 * Schedule tasklet to more in-depth servicing at a better time.
+	 */
+	tasklet_schedule(&brd->helper_tasklet);
+
+	DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+
+	DPR_INTR(("dgnc_intr finish.\n"));
+	return IRQ_HANDLED;
+}
+
+
+/*
+ * Neo specific way of turning off the receiver.
+ * Used as a way to enforce queue flow control when in
+ * hardware flow control mode.
+ */
+static void neo_disable_receiver(struct channel_t *ch)
+{
+	uchar tmp = readb(&ch->ch_neo_uart->ier);
+	tmp &= ~(UART_IER_RDI);
+	writeb(tmp, &ch->ch_neo_uart->ier);
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * Neo specific way of turning on the receiver.
+ * Used as a way to un-enforce queue flow control when in
+ * hardware flow control mode.
+ */
+static void neo_enable_receiver(struct channel_t *ch)
+{
+	uchar tmp = readb(&ch->ch_neo_uart->ier);
+	tmp |= (UART_IER_RDI);
+	writeb(tmp, &ch->ch_neo_uart->ier);
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static void neo_copy_data_from_uart_to_queue(struct channel_t *ch)
+{
+        int qleft = 0;
+	uchar linestatus = 0;
+	uchar error_mask = 0;
+        int n = 0;
+        int total = 0;  
+	ushort head;
+	ushort tail;
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* cache head and tail of queue */
+	head = ch->ch_r_head & RQUEUEMASK;
+	tail = ch->ch_r_tail & RQUEUEMASK;
+
+	/* Get our cached LSR */
+	linestatus = ch->ch_cached_lsr;
+	ch->ch_cached_lsr = 0;
+
+	/* Store how much space we have left in the queue */
+	if ((qleft = tail - head - 1) < 0)
+		qleft += RQUEUEMASK + 1;
+
+	/*
+	 * If the UART is not in FIFO mode, force the FIFO copy to
+	 * NOT be run, by setting total to 0.
+	 *
+	 * On the other hand, if the UART IS in FIFO mode, then ask
+	 * the UART to give us an approximation of data it has RX'ed.
+	 */
+	if (!(ch->ch_flags & CH_FIFO_ENABLED))
+		total = 0;
+	else {
+		total = readb(&ch->ch_neo_uart->rfifo);
+
+		/*
+		 * EXAR chip bug - RX FIFO COUNT - Fudge factor.
+		 *
+		 * This resolves a problem/bug with the Exar chip that sometimes
+		 * returns a bogus value in the rfifo register.
+		 * The count can be any where from 0-3 bytes "off".
+		 * Bizarre, but true.
+		 */
+		if ((ch->ch_bd->dvid & 0xf0) >= UART_XR17E158_DVID) {
+			total -= 1;
+		}
+		else {
+			total -= 3;
+		}
+	}
+
+
+	/*
+	 * Finally, bound the copy to make sure we don't overflow
+	 * our own queue...
+	 * The byte by byte copy loop below this loop this will
+	 * deal with the queue overflow possibility.
+	 */
+	total = min(total, qleft);
+
+	while (total > 0) { 
+
+		/*
+		 * Grab the linestatus register, we need to check
+		 * to see if there are any errors in the FIFO.
+		 */
+		linestatus = readb(&ch->ch_neo_uart->lsr);
+
+		/*
+		 * Break out if there is a FIFO error somewhere.
+		 * This will allow us to go byte by byte down below,
+		 * finding the exact location of the error.
+		 */
+		if (linestatus & UART_17158_RX_FIFO_DATA_ERROR)
+			break;
+
+		/* Make sure we don't go over the end of our queue */
+		n = min(((uint) total), (RQUEUESIZE - (uint) head));
+
+		/*
+		 * Cut down n even further if needed, this is to fix
+		 * a problem with memcpy_fromio() with the Neo on the
+		 * IBM pSeries platform.
+		 * 15 bytes max appears to be the magic number.
+		 */
+		n = min((uint) n, (uint) 12);
+
+		/*
+		 * Since we are grabbing the linestatus register, which
+		 * will reset some bits after our read, we need to ensure
+		 * we don't miss our TX FIFO emptys.
+		 */
+		if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR)) {
+			ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		}
+
+		linestatus = 0;
+
+		/* Copy data from uart to the queue */
+		memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, n);
+		dgnc_sniff_nowait_nolock(ch, "UART READ", ch->ch_rqueue + head, n);
+
+		/*
+		 * Since RX_FIFO_DATA_ERROR was 0, we are guarenteed
+		 * that all the data currently in the FIFO is free of
+		 * breaks and parity/frame/orun errors.
+		 */
+		memset(ch->ch_equeue + head, 0, n);
+
+		/* Add to and flip head if needed */
+		head = (head + n) & RQUEUEMASK;
+		total -= n;
+		qleft -= n;
+		ch->ch_rxcount += n;
+	}
+
+	/*
+	 * Create a mask to determine whether we should
+	 * insert the character (if any) into our queue.
+	 */
+	if (ch->ch_c_iflag & IGNBRK)
+		error_mask |= UART_LSR_BI;
+
+	/*
+	 * Now cleanup any leftover bytes still in the UART.
+	 * Also deal with any possible queue overflow here as well.
+	 */
+	while (1) {
+
+		/*
+		 * Its possible we have a linestatus from the loop above
+		 * this, so we "OR" on any extra bits.
+		 */
+		linestatus |= readb(&ch->ch_neo_uart->lsr);
+
+		/*
+		 * If the chip tells us there is no more data pending to
+		 * be read, we can then leave.
+		 * But before we do, cache the linestatus, just in case.
+		 */
+		if (!(linestatus & UART_LSR_DR)) {
+			ch->ch_cached_lsr = linestatus;
+			break;
+		}
+
+		/* No need to store this bit */
+		linestatus &= ~UART_LSR_DR;
+
+		/*
+		 * Since we are grabbing the linestatus register, which
+		 * will reset some bits after our read, we need to ensure
+		 * we don't miss our TX FIFO emptys.
+		 */
+		if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR)) {
+			linestatus &= ~(UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR);
+			ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		}
+
+		/*
+		 * Discard character if we are ignoring the error mask.
+		 */
+		if (linestatus & error_mask)  {
+			uchar discard;
+			linestatus = 0;
+			memcpy_fromio(&discard, &ch->ch_neo_uart->txrxburst, 1);
+			continue;
+		}
+
+		/*
+		 * If our queue is full, we have no choice but to drop some data.
+		 * The assumption is that HWFLOW or SWFLOW should have stopped
+		 * things way way before we got to this point.
+		 *
+		 * I decided that I wanted to ditch the oldest data first,
+		 * I hope thats okay with everyone? Yes? Good.
+		 */
+		while (qleft < 1) {
+			DPR_READ(("Queue full, dropping DATA:%x LSR:%x\n",
+				ch->ch_rqueue[tail], ch->ch_equeue[tail]));
+
+			ch->ch_r_tail = tail = (tail + 1) & RQUEUEMASK;
+			ch->ch_err_overrun++;
+			qleft++;
+		}
+
+		memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, 1);
+		ch->ch_equeue[head] = (uchar) linestatus;
+		dgnc_sniff_nowait_nolock(ch, "UART READ", ch->ch_rqueue + head, 1);
+
+		DPR_READ(("DATA/LSR pair: %x %x\n", ch->ch_rqueue[head], ch->ch_equeue[head]));
+
+		/* Ditch any remaining linestatus value. */
+		linestatus = 0;
+
+		/* Add to and flip head if needed */
+		head = (head + 1) & RQUEUEMASK;
+
+		qleft--;
+		ch->ch_rxcount++;
+	}
+
+	/*
+	 * Write new final heads to channel structure.
+	 */
+	ch->ch_r_head = head & RQUEUEMASK;
+	ch->ch_e_head = head & EQUEUEMASK;
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * This function basically goes to sleep for secs, or until
+ * it gets signalled that the port has fully drained.
+ */
+static int neo_drain(struct tty_struct *tty, uint seconds)
+{
+	ulong lock_flags;
+	struct channel_t *ch;
+        struct un_t *un;
+	int rc = 0;
+
+	if (!tty || tty->magic != TTY_MAGIC) {
+		return (-ENXIO);
+	}
+
+	un = (struct un_t *) tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC) {
+		return (-ENXIO);
+	}
+
+	ch = un->un_ch;   
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return (-ENXIO);
+	}
+
+	DPR_IOCTL(("%d Drain wait started.\n", __LINE__));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	un->un_flags |= UN_EMPTY;
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	/*
+	 * Go to sleep waiting for the tty layer to wake me back up when
+	 * the empty flag goes away.
+	 *
+	 * NOTE: TODO: Do something with time passed in.
+	 */
+	rc = wait_event_interruptible(un->un_flags_wait, ((un->un_flags & UN_EMPTY) == 0));
+
+	/* If ret is non-zero, user ctrl-c'ed us */
+	if (rc) {
+		DPR_IOCTL(("%d Drain - User ctrl c'ed\n", __LINE__));
+	}
+	else {
+		DPR_IOCTL(("%d Drain wait finished.\n", __LINE__));
+	}
+
+        return (rc);
+}
+        
+
+/*
+ * Flush the WRITE FIFO on the Neo.
+ *
+ * NOTE: Channel lock MUST be held before calling this function!
+ */
+static void neo_flush_uart_write(struct channel_t *ch)
+{
+	uchar tmp = 0;
+	int i = 0;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr);
+	neo_pci_posting_flush(ch->ch_bd);
+
+	for (i = 0; i < 10; i++) {
+
+		/* Check to see if the UART feels it completely flushed the FIFO. */
+		tmp = readb(&ch->ch_neo_uart->isr_fcr);
+		if (tmp & 4) {
+			DPR_IOCTL(("Still flushing TX UART... i: %d\n", i));
+			udelay(10);
+		}
+		else
+			break;
+	}
+
+	ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+}
+
+
+/*
+ * Flush the READ FIFO on the Neo.
+ *
+ * NOTE: Channel lock MUST be held before calling this function!
+ */
+static void neo_flush_uart_read(struct channel_t *ch)
+{
+	uchar tmp = 0;
+	int i = 0;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return;
+	}
+
+	writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR), &ch->ch_neo_uart->isr_fcr);
+	neo_pci_posting_flush(ch->ch_bd);
+
+	for (i = 0; i < 10; i++) {
+
+		/* Check to see if the UART feels it completely flushed the FIFO. */
+		tmp = readb(&ch->ch_neo_uart->isr_fcr);
+		if (tmp & 2) {
+			DPR_IOCTL(("Still flushing RX UART... i: %d\n", i));
+			udelay(10);
+		}
+		else
+			break;
+	}
+}
+
+
+static void neo_copy_data_from_queue_to_uart(struct channel_t *ch)
+{
+	ushort head;
+	ushort tail;
+	int n;
+	int s;
+	int qlen;
+	uint len_written = 0;
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* No data to write to the UART */
+	if (ch->ch_w_tail == ch->ch_w_head) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/* If port is "stopped", don't send any data to the UART */
+	if ((ch->ch_flags & CH_FORCED_STOP) || (ch->ch_flags & CH_BREAK_SENDING)) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/*
+	 * If FIFOs are disabled. Send data directly to txrx register
+	 */
+	if (!(ch->ch_flags & CH_FIFO_ENABLED)) {
+		uchar lsrbits = readb(&ch->ch_neo_uart->lsr);
+
+		/* Cache the LSR bits for later parsing */
+		ch->ch_cached_lsr |= lsrbits;
+		if (ch->ch_cached_lsr & UART_LSR_THRE) {
+			ch->ch_cached_lsr &= ~(UART_LSR_THRE);
+
+			/*
+			 * If RTS Toggle mode is on, turn on RTS now if not already set,
+			 * and make sure we get an event when the data transfer has completed.
+			 */
+			if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+				if (!(ch->ch_mostat & UART_MCR_RTS)) {
+					ch->ch_mostat |= (UART_MCR_RTS);
+					neo_assert_modem_signals(ch);
+				}
+				ch->ch_tun.un_flags |= (UN_EMPTY);
+			}
+			/*
+			 * If DTR Toggle mode is on, turn on DTR now if not already set,
+			 * and make sure we get an event when the data transfer has completed.
+			 */
+			if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+				if (!(ch->ch_mostat & UART_MCR_DTR)) {
+					ch->ch_mostat |= (UART_MCR_DTR);
+					neo_assert_modem_signals(ch);
+				}
+				ch->ch_tun.un_flags |= (UN_EMPTY);
+			}
+
+			writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_neo_uart->txrx);
+			DPR_WRITE(("Tx data: %x\n", ch->ch_wqueue[ch->ch_w_head]));
+			ch->ch_w_tail++;
+			ch->ch_w_tail &= WQUEUEMASK;
+			ch->ch_txcount++;
+		}
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/*
+	 * We have to do it this way, because of the EXAR TXFIFO count bug.
+	 */
+	if ((ch->ch_bd->dvid & 0xf0) < UART_XR17E158_DVID) {
+		if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) {
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			return;
+		}
+
+		len_written = 0;
+
+		n = readb(&ch->ch_neo_uart->tfifo);
+
+		if ((unsigned int) n > ch->ch_t_tlevel) {
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			return;
+		}
+
+		n = UART_17158_TX_FIFOSIZE - ch->ch_t_tlevel;
+	}
+	else {
+		n = UART_17158_TX_FIFOSIZE - readb(&ch->ch_neo_uart->tfifo);
+	}
+
+	/* cache head and tail of queue */
+	head = ch->ch_w_head & WQUEUEMASK;
+	tail = ch->ch_w_tail & WQUEUEMASK;
+	qlen = (head - tail) & WQUEUEMASK;
+
+	/* Find minimum of the FIFO space, versus queue length */
+	n = min(n, qlen);
+
+	while (n > 0) {
+
+		s = ((head >= tail) ? head : WQUEUESIZE) - tail;
+		s = min(s, n);
+
+		if (s <= 0)
+			break;
+
+		/*
+		 * If RTS Toggle mode is on, turn on RTS now if not already set,
+		 * and make sure we get an event when the data transfer has completed.
+		 */
+		if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+			if (!(ch->ch_mostat & UART_MCR_RTS)) {
+				ch->ch_mostat |= (UART_MCR_RTS);
+				neo_assert_modem_signals(ch);
+			}
+			ch->ch_tun.un_flags |= (UN_EMPTY);
+		}
+
+		/*
+		 * If DTR Toggle mode is on, turn on DTR now if not already set,
+		 * and make sure we get an event when the data transfer has completed.
+		 */
+		if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+			if (!(ch->ch_mostat & UART_MCR_DTR)) {
+				ch->ch_mostat |= (UART_MCR_DTR);
+				neo_assert_modem_signals(ch);
+			}
+			ch->ch_tun.un_flags |= (UN_EMPTY);
+		}
+
+		memcpy_toio(&ch->ch_neo_uart->txrxburst, ch->ch_wqueue + tail, s);
+		dgnc_sniff_nowait_nolock(ch, "UART WRITE", ch->ch_wqueue + tail, s);
+
+		/* Add and flip queue if needed */
+		tail = (tail + s) & WQUEUEMASK;
+		n -= s;
+		ch->ch_txcount += s;
+		len_written += s;
+	}
+
+	/* Update the final tail */
+	ch->ch_w_tail = tail & WQUEUEMASK;
+
+	if (len_written > 0) {
+		neo_pci_posting_flush(ch->ch_bd);
+		ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+static void neo_parse_modem(struct channel_t *ch, uchar signals)
+{
+	volatile uchar msignals = signals;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DPR_MSIGS(("neo_parse_modem: port: %d msignals: %x\n", ch->ch_portnum, msignals));
+
+	/*
+	 * Do altpin switching. Altpin switches DCD and DSR.
+	 * This prolly breaks DSRPACE, so we should be more clever here.
+	 */
+	if (ch->ch_digi.digi_flags & DIGI_ALTPIN) {
+		uchar mswap = msignals;
+
+		if (mswap & UART_MSR_DDCD) {
+			msignals &= ~UART_MSR_DDCD;
+			msignals |= UART_MSR_DDSR;
+		}
+		if (mswap & UART_MSR_DDSR) {
+			msignals &= ~UART_MSR_DDSR;
+			msignals |= UART_MSR_DDCD;
+		}
+		if (mswap & UART_MSR_DCD) {
+			msignals &= ~UART_MSR_DCD;
+			msignals |= UART_MSR_DSR;
+		}
+		if (mswap & UART_MSR_DSR) {
+			msignals &= ~UART_MSR_DSR;
+			msignals |= UART_MSR_DCD;
+		}
+	}
+
+	/* Scrub off lower bits. They signify delta's, which I don't care about */
+	msignals &= 0xf0;
+
+	if (msignals & UART_MSR_DCD)
+		ch->ch_mistat |= UART_MSR_DCD;
+	else
+		ch->ch_mistat &= ~UART_MSR_DCD;
+
+	if (msignals & UART_MSR_DSR)
+		ch->ch_mistat |= UART_MSR_DSR;
+	else
+		ch->ch_mistat &= ~UART_MSR_DSR;
+
+	if (msignals & UART_MSR_RI)
+		ch->ch_mistat |= UART_MSR_RI;
+	else
+		ch->ch_mistat &= ~UART_MSR_RI;
+
+	if (msignals & UART_MSR_CTS)
+		ch->ch_mistat |= UART_MSR_CTS;
+	else
+		ch->ch_mistat &= ~UART_MSR_CTS;
+
+	DPR_MSIGS(("Port: %d DTR: %d RTS: %d CTS: %d DSR: %d " "RI: %d CD: %d\n",
+		ch->ch_portnum,
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_DTR),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_RTS),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_CTS), 
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DSR), 
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_RI),
+		!!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DCD)));
+}
+
+
+/* Make the UART raise any of the output signals we want up */
+static void neo_assert_modem_signals(struct channel_t *ch)
+{
+	uchar out;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	out = ch->ch_mostat;
+
+	if (ch->ch_flags & CH_LOOPBACK)
+		out |= UART_MCR_LOOP;
+
+	writeb(out, &ch->ch_neo_uart->mcr);
+	neo_pci_posting_flush(ch->ch_bd);
+
+	/* Give time for the UART to actually raise/drop the signals */
+	udelay(10);
+}
+
+
+static void neo_send_start_character(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	if (ch->ch_startc != _POSIX_VDISABLE) {
+		ch->ch_xon_sends++;
+		writeb(ch->ch_startc, &ch->ch_neo_uart->txrx);
+		neo_pci_posting_flush(ch->ch_bd);
+		udelay(10);
+	}
+}
+
+
+static void neo_send_stop_character(struct channel_t *ch)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	if (ch->ch_stopc != _POSIX_VDISABLE) {
+		ch->ch_xoff_sends++;
+		writeb(ch->ch_stopc, &ch->ch_neo_uart->txrx);
+		neo_pci_posting_flush(ch->ch_bd);
+		udelay(10);
+	}
+}
+
+
+/*
+ * neo_uart_init
+ */
+static void neo_uart_init(struct channel_t *ch)
+{
+
+	writeb(0, &ch->ch_neo_uart->ier);
+	writeb(0, &ch->ch_neo_uart->efr);
+	writeb(UART_EFR_ECB, &ch->ch_neo_uart->efr);
+        
+
+        /* Clear out UART and FIFO */
+	readb(&ch->ch_neo_uart->txrx);
+	writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr);
+	readb(&ch->ch_neo_uart->lsr);
+	readb(&ch->ch_neo_uart->msr);
+
+	ch->ch_flags |= CH_FIFO_ENABLED;
+
+	/* Assert any signals we want up */
+	writeb(ch->ch_mostat, &ch->ch_neo_uart->mcr);
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * Make the UART completely turn off.
+ */
+static void neo_uart_off(struct channel_t *ch)
+{
+	/* Turn off UART enhanced bits */
+	writeb(0, &ch->ch_neo_uart->efr);
+
+	/* Stop all interrupts from occurring. */
+	writeb(0, &ch->ch_neo_uart->ier);
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static uint neo_get_uart_bytes_left(struct channel_t *ch)
+{
+	uchar left = 0;
+	uchar lsr = readb(&ch->ch_neo_uart->lsr);
+
+	/* We must cache the LSR as some of the bits get reset once read... */
+	ch->ch_cached_lsr |= lsr;
+ 
+	/* Determine whether the Transmitter is empty or not */
+	if (!(lsr & UART_LSR_TEMT)) {
+		if (ch->ch_flags & CH_TX_FIFO_EMPTY) {
+                	tasklet_schedule(&ch->ch_bd->helper_tasklet);
+		}
+		left = 1;
+	} else {
+		ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+		left = 0;
+	}
+
+	return left;
+}
+
+
+/* Channel lock MUST be held by the calling function! */
+static void neo_send_break(struct channel_t *ch, int msecs)
+{
+	/*
+	 * If we receive a time of 0, this means turn off the break.
+	 */
+	if (msecs == 0) {
+		if (ch->ch_flags & CH_BREAK_SENDING) {
+			uchar temp = readb(&ch->ch_neo_uart->lcr);
+			writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+			neo_pci_posting_flush(ch->ch_bd);
+			ch->ch_flags &= ~(CH_BREAK_SENDING);
+			ch->ch_stop_sending_break = 0;
+			DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+		}
+		return;
+	}
+
+	/*
+	 * Set the time we should stop sending the break.
+	 * If we are already sending a break, toss away the existing
+	 * time to stop, and use this new value instead.
+	 */
+	ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs);
+
+	/* Tell the UART to start sending the break */
+	if (!(ch->ch_flags & CH_BREAK_SENDING)) {
+		uchar temp = readb(&ch->ch_neo_uart->lcr);
+                writeb((temp | UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+		neo_pci_posting_flush(ch->ch_bd);
+		ch->ch_flags |= (CH_BREAK_SENDING);
+		DPR_IOCTL(("Port %d. Starting UART_LCR_SBC! start: %lx should end: %lx\n",
+			ch->ch_portnum, jiffies, ch->ch_stop_sending_break));
+        }
+}
+
+
+/*
+ * neo_send_immediate_char.
+ *
+ * Sends a specific character as soon as possible to the UART,   
+ * jumping over any bytes that might be in the write queue.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void neo_send_immediate_char(struct channel_t *ch, unsigned char c)
+{
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	writeb(c, &ch->ch_neo_uart->txrx);
+	neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static unsigned int neo_read_eeprom(unsigned char *base, unsigned int address)
+{
+	unsigned int enable;
+	unsigned int bits;
+	unsigned int databit;
+	unsigned int val;
+
+	/* enable chip select */
+	writeb(NEO_EECS, base + NEO_EEREG);
+	/* READ */
+	enable = (address | 0x180);
+
+	for (bits = 9; bits--; ) {
+		databit = (enable & (1 << bits)) ? NEO_EEDI : 0;
+		/* Set read address */
+		writeb(databit | NEO_EECS, base + NEO_EEREG);
+		writeb(databit | NEO_EECS | NEO_EECK, base + NEO_EEREG);
+	}
+
+	val = 0;
+
+	for (bits = 17; bits--; ) {
+		/* clock to EEPROM */
+		writeb(NEO_EECS, base + NEO_EEREG);
+		writeb(NEO_EECS | NEO_EECK, base + NEO_EEREG);
+		val <<= 1;
+		/* read EEPROM */
+		if (readb(base + NEO_EEREG) & NEO_EEDO)
+			val |= 1;
+	}
+
+	/* clock falling edge */
+	writeb(NEO_EECS, base + NEO_EEREG);
+
+	/* drop chip select */
+	writeb(0x00, base + NEO_EEREG);
+
+	return val;
+}
+
+
+static void neo_vpd(struct board_t *brd)
+{
+	unsigned int i = 0;
+	unsigned int a;
+
+	if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	if (!brd->re_map_membase)
+		return;
+
+	/* Store the VPD into our buffer */
+	for (i = 0; i < NEO_VPD_IMAGESIZE; i++) {
+		a = neo_read_eeprom(brd->re_map_membase, i);
+		brd->vpd[i*2] = a & 0xff;
+		brd->vpd[(i*2)+1] = (a >> 8) & 0xff;
+	}
+
+	if  (((brd->vpd[0x08] != 0x82)	   /* long resource name tag */
+		&&  (brd->vpd[0x10] != 0x82))   /* long resource name tag (PCI-66 files)*/
+		||  (brd->vpd[0x7F] != 0x78))   /* small resource end tag */
+	{
+		memset(brd->vpd, '\0', NEO_VPD_IMAGESIZE);
+	}
+	else {
+		/* Search for the serial number */
+		for (i = 0; i < NEO_VPD_IMAGESIZE * 2; i++) {
+			if (brd->vpd[i] == 'S' && brd->vpd[i + 1] == 'N') {
+				strncpy(brd->serial_num, &(brd->vpd[i + 3]), 9);
+			}
+		}
+	}
+}
diff --git a/drivers/staging/dgnc/dgnc_neo.h b/drivers/staging/dgnc/dgnc_neo.h
new file mode 100644
index 0000000..ffb4209
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_neo.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ */
+
+#ifndef __DGNC_NEO_H
+#define __DGNC_NEO_H
+
+#include "dgnc_types.h"
+#include "dgnc_driver.h"
+
+/************************************************************************ 
+ * Per channel/port NEO UART structure					*
+ ************************************************************************
+ *		Base Structure Entries Usage Meanings to Host		*
+ *									*
+ *	W = read write		R = read only				* 
+ *			U = Unused.					*
+ ************************************************************************/
+
+struct neo_uart_struct {
+	volatile uchar txrx;		/* WR  RHR/THR - Holding Reg */
+	volatile uchar ier;		/* WR  IER - Interrupt Enable Reg */
+	volatile uchar isr_fcr;		/* WR  ISR/FCR - Interrupt Status Reg/Fifo Control Reg */
+	volatile uchar lcr;		/* WR  LCR - Line Control Reg */
+	volatile uchar mcr;		/* WR  MCR - Modem Control Reg */
+	volatile uchar lsr;		/* WR  LSR - Line Status Reg */
+	volatile uchar msr;		/* WR  MSR - Modem Status Reg */
+	volatile uchar spr;		/* WR  SPR - Scratch Pad Reg */
+	volatile uchar fctr;		/* WR  FCTR - Feature Control Reg */
+	volatile uchar efr;		/* WR  EFR - Enhanced Function Reg */
+	volatile uchar tfifo;		/* WR  TXCNT/TXTRG - Transmit FIFO Reg */	
+	volatile uchar rfifo;		/* WR  RXCNT/RXTRG - Recieve  FIFO Reg */
+	volatile uchar xoffchar1;	/* WR  XOFF 1 - XOff Character 1 Reg */
+	volatile uchar xoffchar2;	/* WR  XOFF 2 - XOff Character 2 Reg */
+	volatile uchar xonchar1;	/* WR  XON 1 - Xon Character 1 Reg */
+	volatile uchar xonchar2;	/* WR  XON 2 - XOn Character 2 Reg */
+
+	volatile uchar reserved1[0x2ff - 0x200]; /* U   Reserved by Exar */
+	volatile uchar txrxburst[64];	/* RW  64 bytes of RX/TX FIFO Data */
+	volatile uchar reserved2[0x37f - 0x340]; /* U   Reserved by Exar */
+	volatile uchar rxburst_with_errors[64];	/* R  64 bytes of RX FIFO Data + LSR */
+};
+
+/* Where to read the extended interrupt register (32bits instead of 8bits) */
+#define	UART_17158_POLL_ADDR_OFFSET	0x80
+
+/* These are the current dvid's of the Neo boards */
+#define UART_XR17C158_DVID 0x20
+#define UART_XR17D158_DVID 0x20
+#define UART_XR17E158_DVID 0x40
+
+#define NEO_EECK  0x10		/* Clock */
+#define NEO_EECS  0x20		/* Chip Select */
+#define NEO_EEDI  0x40		/* Data In  is an Output Pin */
+#define NEO_EEDO  0x80		/* Data Out is an Input Pin */
+#define NEO_EEREG 0x8E		/* offset to EEPROM control reg */
+
+
+#define NEO_VPD_IMAGESIZE 0x40	/* size of image to read from EEPROM in words */           
+#define NEO_VPD_IMAGEBYTES (NEO_VPD_IMAGESIZE * 2)
+
+/*
+ * These are the redefinitions for the FCTR on the XR17C158, since
+ * Exar made them different than their earlier design. (XR16C854)
+ */
+
+/* These are only applicable when table D is selected */
+#define UART_17158_FCTR_RTS_NODELAY	0x00
+#define UART_17158_FCTR_RTS_4DELAY	0x01
+#define UART_17158_FCTR_RTS_6DELAY	0x02
+#define UART_17158_FCTR_RTS_8DELAY	0x03
+#define UART_17158_FCTR_RTS_12DELAY	0x12
+#define UART_17158_FCTR_RTS_16DELAY	0x05
+#define UART_17158_FCTR_RTS_20DELAY	0x13
+#define UART_17158_FCTR_RTS_24DELAY	0x06
+#define UART_17158_FCTR_RTS_28DELAY	0x14
+#define UART_17158_FCTR_RTS_32DELAY	0x07
+#define UART_17158_FCTR_RTS_36DELAY	0x16
+#define UART_17158_FCTR_RTS_40DELAY	0x08
+#define UART_17158_FCTR_RTS_44DELAY	0x09
+#define UART_17158_FCTR_RTS_48DELAY	0x10
+#define UART_17158_FCTR_RTS_52DELAY	0x11
+
+#define UART_17158_FCTR_RTS_IRDA	0x10
+#define UART_17158_FCTR_RS485		0x20
+#define UART_17158_FCTR_TRGA		0x00
+#define UART_17158_FCTR_TRGB		0x40
+#define UART_17158_FCTR_TRGC		0x80
+#define UART_17158_FCTR_TRGD		0xC0
+
+/* 17158 trigger table selects.. */
+#define UART_17158_FCTR_BIT6		0x40
+#define UART_17158_FCTR_BIT7		0x80
+
+/* 17158 TX/RX memmapped buffer offsets */
+#define UART_17158_RX_FIFOSIZE		64  
+#define UART_17158_TX_FIFOSIZE		64  
+
+/* 17158 Extended IIR's */
+#define UART_17158_IIR_RDI_TIMEOUT	0x0C	/* Receiver data TIMEOUT */
+#define UART_17158_IIR_XONXOFF		0x10	/* Received an XON/XOFF char */
+#define UART_17158_IIR_HWFLOW_STATE_CHANGE 0x20	/* CTS/DSR or RTS/DTR state change */
+#define UART_17158_IIR_FIFO_ENABLED	0xC0	/* 16550 FIFOs are Enabled */
+
+/*
+ * These are the extended interrupts that get sent
+ * back to us from the UART's 32bit interrupt register
+ */
+#define UART_17158_RX_LINE_STATUS	0x1	/* RX Ready */
+#define UART_17158_RXRDY_TIMEOUT	0x2	/* RX Ready Timeout */
+#define UART_17158_TXRDY		0x3	/* TX Ready */
+#define UART_17158_MSR			0x4	/* Modem State Change */
+#define UART_17158_TX_AND_FIFO_CLR	0x40	/* Transmitter Holding Reg Empty */
+#define UART_17158_RX_FIFO_DATA_ERROR	0x80	/* UART detected an RX FIFO Data error */
+
+/*
+ * These are the EXTENDED definitions for the 17C158's Interrupt
+ * Enable Register.
+ */
+#define UART_17158_EFR_ECB	0x10	/* Enhanced control bit */
+#define UART_17158_EFR_IXON	0x2	/* Receiver compares Xon1/Xoff1 */
+#define UART_17158_EFR_IXOFF	0x8	/* Transmit Xon1/Xoff1 */
+#define UART_17158_EFR_RTSDTR	0x40	/* Auto RTS/DTR Flow Control Enable */
+#define UART_17158_EFR_CTSDSR	0x80	/* Auto CTS/DSR Flow COntrol Enable */
+
+#define UART_17158_XOFF_DETECT	0x1	/* Indicates whether chip saw an incoming XOFF char  */
+#define UART_17158_XON_DETECT	0x2	/* Indicates whether chip saw an incoming XON char */
+
+#define UART_17158_IER_RSVD1	0x10	/* Reserved by Exar */
+#define UART_17158_IER_XOFF	0x20	/* Xoff Interrupt Enable */
+#define UART_17158_IER_RTSDTR	0x40	/* Output Interrupt Enable */
+#define UART_17158_IER_CTSDSR	0x80	/* Input Interrupt Enable */
+
+/*
+ * Our Global Variables
+ */
+extern struct board_ops dgnc_neo_ops;
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_pci.h b/drivers/staging/dgnc/dgnc_pci.h
new file mode 100644
index 0000000..5550707
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_pci.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+/* $Id: dgnc_pci.h,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $ */
+
+#ifndef __DGNC_PCI_H
+#define __DGNC_PCI_H
+
+#define PCIMAX 32			/* maximum number of PCI boards */
+
+#define DIGI_VID				0x114F
+
+#define PCI_DEVICE_CLASSIC_4_DID		0x0028
+#define PCI_DEVICE_CLASSIC_8_DID		0x0029
+#define PCI_DEVICE_CLASSIC_4_422_DID		0x00D0
+#define PCI_DEVICE_CLASSIC_8_422_DID		0x00D1
+#define PCI_DEVICE_NEO_4_DID			0x00B0
+#define PCI_DEVICE_NEO_8_DID			0x00B1
+#define PCI_DEVICE_NEO_2DB9_DID			0x00C8
+#define PCI_DEVICE_NEO_2DB9PRI_DID		0x00C9
+#define PCI_DEVICE_NEO_2RJ45_DID		0x00CA
+#define PCI_DEVICE_NEO_2RJ45PRI_DID		0x00CB
+#define PCI_DEVICE_NEO_1_422_DID		0x00CC
+#define PCI_DEVICE_NEO_1_422_485_DID		0x00CD
+#define PCI_DEVICE_NEO_2_422_485_DID		0x00CE
+#define PCI_DEVICE_NEO_EXPRESS_8_DID		0x00F0
+#define PCI_DEVICE_NEO_EXPRESS_4_DID		0x00F1
+#define PCI_DEVICE_NEO_EXPRESS_4RJ45_DID	0x00F2
+#define PCI_DEVICE_NEO_EXPRESS_8RJ45_DID	0x00F3
+#define PCI_DEVICE_NEO_EXPRESS_4_IBM_DID	0x00F4
+
+#define PCI_DEVICE_CLASSIC_4_PCI_NAME		"ClassicBoard 4 PCI"
+#define PCI_DEVICE_CLASSIC_8_PCI_NAME		"ClassicBoard 8 PCI"
+#define PCI_DEVICE_CLASSIC_4_422_PCI_NAME	"ClassicBoard 4 422 PCI"
+#define PCI_DEVICE_CLASSIC_8_422_PCI_NAME	"ClassicBoard 8 422 PCI"
+#define PCI_DEVICE_NEO_4_PCI_NAME		"Neo 4 PCI"
+#define PCI_DEVICE_NEO_8_PCI_NAME		"Neo 8 PCI"
+#define PCI_DEVICE_NEO_2DB9_PCI_NAME		"Neo 2 - DB9 Universal PCI"
+#define PCI_DEVICE_NEO_2DB9PRI_PCI_NAME		"Neo 2 - DB9 Universal PCI - Powered Ring Indicator"
+#define PCI_DEVICE_NEO_2RJ45_PCI_NAME		"Neo 2 - RJ45 Universal PCI"
+#define PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME	"Neo 2 - RJ45 Universal PCI - Powered Ring Indicator"
+#define PCI_DEVICE_NEO_1_422_PCI_NAME		"Neo 1 422 PCI"
+#define PCI_DEVICE_NEO_1_422_485_PCI_NAME	"Neo 1 422/485 PCI"
+#define PCI_DEVICE_NEO_2_422_485_PCI_NAME	"Neo 2 422/485 PCI"
+
+#define PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME	"Neo 8 PCI Express"
+#define PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME	"Neo 4 PCI Express"
+#define PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME	"Neo 4 PCI Express RJ45"
+#define PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME	"Neo 8 PCI Express RJ45"
+#define PCI_DEVICE_NEO_EXPRESS_4_IBM_PCI_NAME	"Neo 4 PCI Express IBM"
+
+
+/* Size of Memory and I/O for PCI (4 K) */
+#define PCI_RAM_SIZE				0x1000
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE				0x1000
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_proc.c b/drivers/staging/dgnc/dgnc_proc.c
new file mode 100644
index 0000000..8fbaf3b
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_proc.c
@@ -0,0 +1,1551 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com.
+ *	Thank you.
+ *
+ *
+ * $Id: dgnc_proc.c,v 1.3 2011/06/22 12:16:35 markh Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/proc_fs.h>
+#include <linux/serial_reg.h>
+#include <linux/sched.h>		/* For in_egroup_p() */
+#include <linux/string.h>
+#include <asm/uaccess.h>		/* For copy_from_user/copy_to_user */
+
+#include "dgnc_driver.h"
+#include "dgnc_proc.h"
+#include "dgnc_mgmt.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+#define init_MUTEX(sem)         sema_init(sem, 1)
+#define DECLARE_MUTEX(name)     \
+        struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+#endif
+
+
+/* The /proc/dgnc directory */
+static struct proc_dir_entry *ProcDGNC;
+
+
+/* File operation declarations */
+static int	dgnc_gen_proc_open(struct inode *, struct file *);
+static int	dgnc_gen_proc_close(struct inode *, struct file *);
+static ssize_t	dgnc_gen_proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
+static ssize_t	dgnc_gen_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
+
+static int dgnc_proc_chk_perm(struct inode *, int);
+
+static const struct file_operations dgnc_proc_file_ops =
+{
+	.owner =	THIS_MODULE,
+	.read =		dgnc_gen_proc_read,	/* read		*/
+	.write =	dgnc_gen_proc_write,	/* write	*/
+	.open =		dgnc_gen_proc_open,	/* open		*/
+	.release =	dgnc_gen_proc_close,	/* release	*/
+};
+
+
+static struct inode_operations dgnc_proc_inode_ops =
+{
+	.permission =	dgnc_proc_chk_perm
+};
+
+
+static void dgnc_register_proc_table(struct dgnc_proc_entry *, struct proc_dir_entry *);
+static void dgnc_unregister_proc_table(struct dgnc_proc_entry *, struct proc_dir_entry *);
+static void dgnc_remove_proc_entry(struct proc_dir_entry *pde);
+
+
+/* Stuff in /proc/ */
+static int dgnc_read_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_write_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				const char __user *buffer, ssize_t *lenp, loff_t *ppos);
+
+static int dgnc_read_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+
+static struct dgnc_proc_entry dgnc_table[] = {
+	{DGNC_INFO,	"info", 0600, NULL, NULL, NULL, &dgnc_read_info, &dgnc_write_info,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_table[0].excl_sem, 1), 0, NULL },
+	{DGNC_MKNOD, "mknod", 0600, NULL, NULL, NULL, &dgnc_read_mknod, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_table[1].excl_sem, 1), 0, NULL },
+	{0}
+};
+
+
+/* Stuff in /proc/<board>/ */
+static int dgnc_read_board_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_board_vpd(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_board_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_board_ttystats(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_board_ttyintr(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_board_ttyflags(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+
+static struct dgnc_proc_entry dgnc_board_table[] = {
+	{DGNC_BOARD_INFO, "info", 0600, NULL, NULL, NULL, &dgnc_read_board_info, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[0].excl_sem, 1), 0, NULL },
+	{DGNC_BOARD_VPD, "vpd", 0600, NULL, NULL, NULL, &dgnc_read_board_vpd, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[1].excl_sem, 1), 0, NULL },
+	{DGNC_BOARD_TTYSTATS, "stats", 0600, NULL, NULL, NULL, &dgnc_read_board_ttystats, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[2].excl_sem, 1), 0, NULL },
+	{DGNC_BOARD_TTYINTR, "intr", 0600, NULL, NULL, NULL, &dgnc_read_board_ttyintr, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[3].excl_sem, 1), 0, NULL },
+	{DGNC_BOARD_TTYFLAGS, "flags", 0600, NULL, NULL, NULL, &dgnc_read_board_ttyflags, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[4].excl_sem, 1), 0, NULL },
+	{DGNC_BOARD_MKNOD, "mknod", 0600, NULL, NULL, NULL, &dgnc_read_board_mknod, NULL,
+			NULL, __SEMAPHORE_INITIALIZER(dgnc_board_table[5].excl_sem, 1), 0, NULL },
+	{0}
+};
+
+
+/* Stuff in /proc/<board>/<channel> */
+static int dgnc_read_channel_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_open_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				void *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_close_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				void *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user  *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_channel_custom_ttyname(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+static int dgnc_read_channel_custom_prname(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos);
+
+static struct dgnc_proc_entry dgnc_channel_table[] = {
+	{DGNC_PORT_INFO, "info", 0600, NULL, NULL, NULL, &dgnc_read_channel_info, NULL,
+		NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[0].excl_sem, 1), 0, NULL },
+	{DGNC_PORT_SNIFF, "sniff", 0600, NULL, &dgnc_open_channel_sniff, &dgnc_close_channel_sniff, &dgnc_read_channel_sniff, NULL,
+		NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[1].excl_sem, 1), 0, NULL},
+        {DGNC_PORT_CUSTOM_TTYNAME, "ttyname", 0600, NULL, NULL, NULL, &dgnc_read_channel_custom_ttyname, NULL,
+		NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[2].excl_sem, 1), 0, NULL },
+        {DGNC_PORT_CUSTOM_PRNAME, "prname", 0600, NULL, NULL, NULL, &dgnc_read_channel_custom_prname, NULL,
+		NULL, __SEMAPHORE_INITIALIZER(dgnc_channel_table[3].excl_sem, 1), 0, NULL },
+	{0}
+};
+
+
+/*
+ * dgnc_test_perm does NOT grant the superuser all rights automatically, because
+ * some entries are readonly even to root.
+ */
+static inline int dgnc_test_perm(int mode, int op)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
+	if (!current->euid)
+#else
+	if (!current_euid())
+#endif
+		mode >>= 6;
+	else if (in_egroup_p(0))
+		mode >>= 3;
+	if ((mode & op & 0007) == op)
+		return 0;
+	if (capable(CAP_SYS_ADMIN))
+		return 0;
+	return -EACCES;
+}
+
+
+/*
+ * /proc/sys support
+ */
+static inline int dgnc_proc_match(int len, const char *name, struct proc_dir_entry *de)
+{
+	if (!de || !de->low_ino)
+		return 0;
+	if (de->namelen != len)  
+		return 0;
+	return !memcmp(name, de->name, len);
+}
+
+
+/*
+ *  Scan the entries in table and add them all to /proc at the position
+ *  referred to by "root"
+ */
+static void dgnc_register_proc_table(struct dgnc_proc_entry *table, struct proc_dir_entry *root)
+{
+	struct proc_dir_entry *de;
+	int len;
+	mode_t mode;
+
+	for (; table->magic; table++) {
+		/* Can't do anything without a proc name. */
+		if (!table->name) {
+			DPR_PROC(("dgnc_register_proc_table, no name...\n"));
+			continue;
+		}
+
+		/* Maybe we can't do anything with it... */
+		if (!table->read_handler && !table->write_handler && !table->child) {
+			DPR_PROC((KERN_WARNING "DGNC PROC: Can't register %s\n", table->name));
+			continue;
+		}
+
+		len = strlen(table->name);
+		mode = table->mode;
+		de = NULL;
+
+		if (!table->child) {
+			mode |= S_IFREG;
+		} else {
+			mode |= S_IFDIR;
+			for (de = root->subdir; de; de = de->next) {
+				if (dgnc_proc_match(len, table->name, de))
+					break;
+			}
+
+			/* If the subdir exists already, de is non-NULL */
+		}
+
+		if (!de) {
+			de = create_proc_entry(table->name, mode, root);
+			if (!de)
+				continue;
+			de->data = (void *) table;
+			if (!table->child) {
+				de->proc_iops = &dgnc_proc_inode_ops;
+				de->proc_fops = &dgnc_proc_file_ops;		
+			}
+		}
+
+		table->de = de;
+
+		if (de->mode & S_IFDIR)
+			dgnc_register_proc_table(table->child, de);
+
+	}
+}
+
+
+
+/*
+ * Unregister a /proc sysctl table and any subdirectories.
+ */
+static void dgnc_unregister_proc_table(struct dgnc_proc_entry *table, struct proc_dir_entry *root)
+{
+	struct proc_dir_entry *de;
+
+	for (; table->magic; table++) {
+		if (!(de = table->de))
+			continue;
+
+		if (de->mode & S_IFDIR) {
+			if (!table->child) {
+				DPR_PROC((KERN_ALERT "Help - malformed sysctl tree on free\n"));
+				continue;
+			}
+
+			/* recurse down into subdirectory... */
+			DPR_PROC(("Recursing down a directory...\n"));
+			dgnc_unregister_proc_table(table->child, de);
+
+			/* Don't unregister directories which still have entries.. */
+			if (de->subdir)
+				continue;
+		}   
+
+		/* Don't unregister proc entries that are still being used.. */
+		if ((atomic_read(&de->count)) != 1) {
+			DPR_PROC(("proc entry in use... Not removing...\n"));
+			continue;
+		}
+
+		dgnc_remove_proc_entry(de);
+		table->de = NULL;
+	}
+}
+
+
+
+static int dgnc_gen_proc_open(struct inode *inode, struct file *file)
+{
+	struct proc_dir_entry *de;
+	struct dgnc_proc_entry *entry;
+	int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,
+		void *buffer, ssize_t *lenp, loff_t *ppos);
+	int ret = 0, error = 0;
+
+	de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode);
+	if (!de || !de->data) {
+		ret = -ENXIO;
+		goto done;
+	}
+
+	entry = (struct dgnc_proc_entry *) de->data;
+	if (!entry) {
+		ret = -ENXIO;
+		goto done;
+	}
+
+	down(&entry->excl_sem);
+
+	if (entry->excl_cnt) {
+		ret = -EBUSY;
+	} else {
+		entry->excl_cnt++;
+
+		handler = entry->open_handler;
+		if (handler) {
+			error = (*handler) (entry, OUTBOUND, file, NULL, NULL, NULL);
+			if (error) {
+				entry->excl_cnt--;
+				ret = error;
+			}
+		}
+	}
+
+	up(&entry->excl_sem);
+
+done:
+
+	return ret;
+}
+
+
+static int dgnc_gen_proc_close(struct inode *inode, struct file *file)
+{
+	struct proc_dir_entry *de;
+	int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,
+		void *buffer, ssize_t *lenp, loff_t *ppos);
+	struct dgnc_proc_entry *entry;
+	int error = 0;
+
+	de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode);
+	if (!de || !de->data)
+		goto done;
+
+	entry = (struct dgnc_proc_entry *) de->data;
+	if (!entry)
+		goto done;
+
+	down(&entry->excl_sem);
+
+	if (entry->excl_cnt)
+		entry->excl_cnt = 0;
+
+
+	handler = entry->close_handler;
+	if (handler) {
+		error = (*handler) (entry, OUTBOUND, file, NULL, NULL, NULL);
+	}
+
+	up(&entry->excl_sem);
+
+done:
+	return 0;
+}
+
+
+static ssize_t dgnc_gen_proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct proc_dir_entry *de;
+	struct dgnc_proc_entry *entry;
+	int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,
+		char __user *buffer, ssize_t *lenp, loff_t *ppos2);
+	ssize_t res;
+	ssize_t error;
+
+	de = (struct proc_dir_entry*) PDE(file->f_dentry->d_inode);
+	if (!de || !de->data)
+		return -ENXIO; 
+
+	entry = (struct dgnc_proc_entry *) de->data;
+	if (!entry)
+		return -ENXIO;
+
+	/* Test for read permission */
+	if (dgnc_test_perm(entry->mode, 4))
+		return -EPERM;
+
+	res = count;
+
+	handler = entry->read_handler;
+	if (!handler)
+		return -ENXIO;
+
+	error = (*handler) (entry, OUTBOUND, file, buf, &res, ppos);
+	if (error)
+		return error;
+
+	return res;
+}
+
+
+static ssize_t	dgnc_gen_proc_write(struct file *file, const char __user *buf,
+				    size_t count, loff_t *ppos)
+{
+	struct proc_dir_entry *de;
+	struct dgnc_proc_entry *entry;
+	int (*handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,
+		const char __user *buffer, ssize_t *lenp, loff_t *ppos2);
+	ssize_t res;   
+	ssize_t error;
+
+	de = (struct proc_dir_entry *) PDE(file->f_dentry->d_inode);
+	if (!de || !de->data)
+		return -ENXIO;
+
+	entry = (struct dgnc_proc_entry *) de->data;
+	if (!entry)
+		return -ENXIO;
+
+	/* Test for write permission */
+	if (dgnc_test_perm(entry->mode, 2))
+		return -EPERM;
+
+	res = count;
+
+	handler = entry->write_handler;
+	if (!handler)
+		return -ENXIO;
+
+	error = (*handler) (entry, INBOUND, file, buf, &res, ppos);
+	if (error)
+		return error; 
+
+	return res;
+}
+
+
+static int dgnc_proc_chk_perm(struct inode *inode, int op)
+{               
+	return dgnc_test_perm(inode->i_mode, op);
+}
+
+
+/*               
+ *  Return what is (hopefully) useful information about the
+ *  driver. 
+ */
+static int dgnc_read_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+
+	DPR_PROC(("dgnc_proc_info\n"));
+
+	if (done) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	p += sprintf(p, "Driver:\t\t%s\n", DG_NAME);
+	p += sprintf(p, "\n");
+	p += sprintf(p, "Debug:\t\t0x%x\n", dgnc_debug);
+	p += sprintf(p, "Sysfs Support:\t0x1\n");
+	p += sprintf(p, "Rawreadok:\t0x%x\n", dgnc_rawreadok);
+	p += sprintf(p, "Max Boards:\t%d\n", MAXBOARDS);
+	p += sprintf(p, "Total Boards:\t%d\n", dgnc_NumBoards);
+	p += sprintf(p, "Poll rate:\t%dms\n", dgnc_poll_tick);
+	p += sprintf(p, "Poll counter:\t%ld\n", dgnc_poll_counter);
+	p += sprintf(p, "State:\t\t%s\n", dgnc_driver_state_text[dgnc_driver_state]);
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  When writing to the "info" entry point, I actually allow one
+ *  to modify certain variables.  This may be a sleazy overload
+ *  of this /proc entry, but I don't want:
+ *
+ *     a. to clutter /proc more than I have to
+ *     b. to overload the "config" entry, which would be somewhat
+ *        more natural
+ *     c. necessarily advertise the fact this ability exists
+ *
+ *  The continued support of this feature has not yet been
+ *  guaranteed.
+ *
+ *  Writing operates on a "state machine" principle.
+ *
+ *  State 0: waiting for a symbol to start.  Waiting for anything
+ *           which isn't " ' = or whitespace.
+ *  State 1: reading a symbol.  If the character is a space, move
+ *           to state 2.  If =, move to state 3.  If " or ', move
+ *           to state 0.
+ *  State 2: Waiting for =... suck whitespace.  If anything other
+ *           than whitespace, drop to state 0.
+ *  State 3: Got =.  Suck whitespace waiting for value to start.
+ *           If " or ', go to state 4 (and remember which quote it
+ *           was).  Otherwise, go to state 5.
+ *  State 4: Reading value, within quotes.  Everything is added to
+ *           value up until the matching quote.  When you hit the
+ *           matching quote, try to set the variable, then state 0.
+ *  State 5: Reading value, outside quotes.  Everything not " ' =
+ *           or whitespace goes in value.  Hitting one of the
+ *           terminators tosses us back to state 0 after trying to
+ *           set the variable.
+ */
+typedef enum {
+	INFO_NONE, INFO_INT, INFO_CHAR, INFO_SHORT,
+	INFO_LONG, INFO_PTR, INFO_STRING, INFO_END
+} info_proc_var_val;
+
+static struct {
+	char              *name;
+	info_proc_var_val  type;
+	int                rw;       /* 0=readonly */
+	void              *val_ptr;
+} dgnc_info_vars[] = {
+	{ "rawreadok",   INFO_INT,    1, (void *) &dgnc_rawreadok },
+        { "pollrate",    INFO_INT,    1, (void *) &dgnc_poll_tick },
+	{ NULL, INFO_NONE, 0, NULL },
+	{ "debug",   INFO_LONG,   1, (void *) &dgnc_debug },
+	{ NULL, INFO_END, 0, NULL }
+};
+
+static void dgnc_set_info_var(char *name, char *val)
+{
+	int i;
+	unsigned long newval;
+	unsigned char charval;
+	unsigned short shortval;
+	unsigned int intval;
+
+	for (i = 0; dgnc_info_vars[i].type != INFO_END; i++) {
+		if (dgnc_info_vars[i].name)
+			if (!strcmp(name, dgnc_info_vars[i].name))
+				break;
+	}
+
+	if (dgnc_info_vars[i].type == INFO_END)
+		return;
+	if (dgnc_info_vars[i].rw == 0)
+		return;
+	if (dgnc_info_vars[i].val_ptr == NULL)
+		return;
+
+	newval = simple_strtoul(val, NULL, 0 ); 
+
+	switch (dgnc_info_vars[i].type) {
+	case INFO_CHAR:
+		charval = newval & 0xff;
+		APR(("Modifying %s (%lx) <= 0x%02x  (%d)\n",
+		           name, (long)(dgnc_info_vars[i].val_ptr ),
+		           charval, charval));
+		*(uchar *)(dgnc_info_vars[i].val_ptr) = charval;
+		break;
+	case INFO_SHORT:
+		shortval = newval & 0xffff;
+		APR(("Modifying %s (%lx) <= 0x%04x  (%d)\n",
+		           name, (long)(dgnc_info_vars[i].val_ptr),
+		           shortval, shortval));
+		*(ushort *)(dgnc_info_vars[i].val_ptr) = shortval;
+		break;
+	case INFO_INT:
+		intval = newval & 0xffffffff;
+		APR(("Modifying %s (%lx) <= 0x%08x  (%d)\n",
+		           name, (long)(dgnc_info_vars[i].val_ptr),
+		           intval, intval));
+		*(uint *)(dgnc_info_vars[i].val_ptr) = intval;
+		break;
+	case INFO_LONG:
+		APR(("Modifying %s (%lx) <= 0x%lx  (%ld)\n",
+		           name, (long)(dgnc_info_vars[i].val_ptr),
+		           newval, newval));
+		*(ulong *)(dgnc_info_vars[i].val_ptr) = newval;
+		break;
+	case INFO_PTR:
+	case INFO_STRING:
+	case INFO_END:
+	case INFO_NONE:
+	default:
+		break;
+	}
+}
+
+static int dgnc_write_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				const char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	static int state = 0;
+	#define MAXSYM 255
+	static int sympos, valpos;
+	static char sym[MAXSYM + 1];
+	static char val[MAXSYM + 1];
+	static int quotchar = 0;
+
+	int i;
+
+	long len;
+	#define INBUFLEN 256
+	char inbuf[INBUFLEN];
+
+	if (*ppos == 0) {
+		state = 0;
+		sympos = 0; sym[0] = 0;
+		valpos = 0; val[0] = 0;
+		quotchar = 0;
+	}
+
+	if ((!*lenp) || (dir != INBOUND)) {
+		*lenp = 0;
+		return 0;
+	}
+
+	len = *lenp;
+
+	if (len > INBUFLEN - 1)
+		len = INBUFLEN - 1;
+
+	if (copy_from_user(inbuf, buffer, len))
+		return -EFAULT;
+
+	inbuf[len] = 0;
+
+	for (i = 0; i < len; i++) {
+		unsigned char c = inbuf[i];
+
+		switch (state) {
+		case 0:
+			quotchar = sympos = valpos = sym[0] = val[0] = 0;
+			if (!isspace(c) && (c != '\"') &&
+			    (c != '\'') && (c != '=')) {
+				sym[sympos++] = c;
+				state = 1;
+				break;
+			}
+			break;
+		case 1:
+			if (isspace(c)) {
+				sym[sympos] = 0;
+				state = 2;
+				break;
+			}
+			if (c == '=') {
+				sym[sympos] = 0;
+				state = 3;
+				break;
+			}
+			if ((c == '\"' ) || ( c == '\'' )) {
+				state = 0;
+				break;
+			}
+			if (sympos < MAXSYM) sym[sympos++] = c;
+			break;
+		case 2:
+			if (isspace(c)) break;
+			if (c == '=') {
+				state = 3;
+				break;
+			}
+			if ((c != '\"') && (c != '\'')) {
+				quotchar = sympos = valpos = sym[0] = val[0] = 0;
+				sym[sympos++] = c;
+				state = 1;
+				break;
+			}
+			state = 0;
+			break;
+		case 3:
+			if (isspace(c)) break;
+			if (c == '=') {
+				state = 0;
+				break;
+			}
+			if ((c == '\"') || (c == '\'')) {
+				state = 4;
+				quotchar = c;
+				break;
+			}
+			val[valpos++] = c;
+			state = 5;
+			break;
+		case 4:
+			if (c == quotchar) {
+				val[valpos] = 0;
+				dgnc_set_info_var(sym, val);
+				state = 0;
+				break;
+			}
+			if (valpos < MAXSYM) val[valpos++] = c;
+			break;
+		case 5:
+			if (isspace(c) || (c == '\"') ||
+			    (c == '\'') || (c == '=')) {
+				val[valpos] = 0;
+				dgnc_set_info_var(sym, val);
+				state = 0;
+				break;
+			}
+			if (valpos < MAXSYM) val[valpos++] = c;
+			break;
+		default:
+			break;
+		}
+	}
+
+	*lenp = len;
+	*ppos += len;
+		
+	return len;
+}
+
+
+/*
+ *  Return mknod information for the driver's devices.
+ */                                             
+static int dgnc_read_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int i = 0;
+
+	DPR_PROC(("dgnc_proc_info\n"));
+
+	if (done) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	DPR_PROC(("dgnc_proc_mknod\n"));
+
+	p += sprintf(p, "#\tCreate the management devices.\n");
+
+	for (i = 0; i < MAXMGMTDEVICES; i++) {
+		char tmp[100];
+		sprintf(tmp, "/dev/dg/dgnc/mgmt%d", i);
+		p += sprintf(p, "%s\t%d\t%d\t%d\n",
+			tmp, dgnc_Major, i, 1);
+	}
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*               
+ *  Return what is (hopefully) useful information about the specific board.
+ */
+static int dgnc_read_board_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	char *name;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	name = brd->name;
+
+	p += sprintf(p, "Board Name = %s\n", name);
+	if (brd->serial_num[0] == '\0')
+		p += sprintf(p, "Serial number = <UNKNOWN>\n");
+	else
+		p += sprintf(p, "Serial number = %s\n", brd->serial_num);
+
+	p += sprintf(p, "Board Type = %d\n", brd->type);
+	p += sprintf(p, "Number of Ports = %d\n", brd->nasync);
+
+	/*
+	 * report some things about the PCI bus that are important
+	 * to some applications
+	 */
+        p += sprintf(p, "Vendor ID = 0x%x\n", brd->vendor);
+        p += sprintf(p, "Device ID = 0x%x\n", brd->device);
+        p += sprintf(p, "Subvendor ID = 0x%x\n", brd->subvendor);
+        p += sprintf(p, "Subdevice ID = 0x%x\n", brd->subdevice);
+        p += sprintf(p, "Bus = %d\n", brd->pci_bus);
+        p += sprintf(p, "Slot = %d\n", brd->pci_slot);
+
+	/*
+	 * report the physical addresses assigned to us when we got
+	 * registered
+	 */	
+        p += sprintf(p, "Memory Base Address = 0x%lx\n", brd->membase);
+        p += sprintf(p, "Remapped Memory Base Address = 0x%p\n", brd->re_map_membase);
+
+        p += sprintf(p, "Current state of board = %s\n", dgnc_state_text[brd->state]);
+        p += sprintf(p, "Interrupt #: %d. Times interrupted: %ld\n",
+		brd->irq, brd->intr_count);
+
+        p += sprintf(p, "TX interrupts: %ld  RX interrupts: %ld\n",
+		brd->intr_tx, brd->intr_rx);
+        p += sprintf(p, "Modem interrupts: %ld\n", brd->intr_modem);
+
+        p += sprintf(p, "Majors allocated to board = TTY: %d PR: %d\n",
+		brd->SerialDriver.major, brd->PrintDriver.major);
+
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+
+static int dgnc_read_board_vpd(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	int i = 0, j = 0;
+	char *p = buf;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	p += sprintf(p, "\n      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F          ASCII\n");
+
+	for (i = 0; i < 0x40 * 2; i++) {
+		j = i;
+		if (!(i % 16)) {
+			if (j > 0) {
+				p += sprintf(p, "    ");
+				for (j = i - 16; j < i; j++) {
+					if (0x20 <= brd->vpd[j] && brd->vpd[j] <= 0x7e)
+						p += sprintf(p, "%c", brd->vpd[j]);
+					else
+						p += sprintf(p, ".");
+				}
+				p += sprintf(p, "\n");
+			}
+			p += sprintf(p, "%04X ", i);
+		}
+		p += sprintf(p, "%02X ", brd->vpd[i]);
+	}
+	if (!(i % 16)) {
+		p += sprintf(p, "    ");
+		for (j = i - 16; j < i; j++) {
+			if (0x20 <= brd->vpd[j] && brd->vpd[j] <= 0x7e)
+				p += sprintf(p, "%c", brd->vpd[j]);
+			else
+				p += sprintf(p, ".");
+		}
+		p += sprintf(p, "\n");
+	}
+
+	p += sprintf(p, "\n");
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return what is (hopefully) useful stats about the specific board's ttys
+ */
+static int dgnc_read_board_ttystats(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int i = 0;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	/* Prepare the Header Labels */
+	p += sprintf(p, "%2s %10s %23s %10s %9s\n",
+		"Ch", "Chars Rx", "  Rx Par--Brk--Frm--Ovr", 
+		"Chars Tx", "XON XOFF");
+
+        for (i = 0; i < brd->nasync; i++) {
+
+		struct channel_t *ch = brd->channels[i];
+
+		p += sprintf(p, "%2d ", i);
+		p += sprintf(p, "%10ld ", ch->ch_rxcount);
+		p += sprintf(p, "    %4ld %4ld %4ld %4ld ", ch->ch_err_parity,
+			ch->ch_err_break, ch->ch_err_frame, ch->ch_err_overrun);
+		p += sprintf(p, "%10ld ", ch->ch_txcount);
+		p += sprintf(p, "%4ld %4ld ", ch->ch_xon_sends, ch->ch_xoff_sends);
+
+		p += sprintf(p, "\n");
+	}
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return what is (hopefully) useful stats about the specific board's tty intrs
+ */
+static int dgnc_read_board_ttyintr(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int i = 0;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	/* Prepare the Header Labels */
+	p += sprintf(p, "%2s     %14s  %14s  %14s\n",
+		"Ch", "TX interrupts", "RX interrupts", "Modem interrupts");
+
+        for (i = 0; i < brd->nasync; i++) {
+
+		struct channel_t *ch = brd->channels[i];
+
+		p += sprintf(p, "%2d ", i);
+
+	        p += sprintf(p, "    %14ld  %14ld    %14ld",
+			ch->ch_intr_tx, ch->ch_intr_rx, ch->ch_intr_modem);
+
+		p += sprintf(p, "\n");
+	}
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return what is (hopefully) useful flags about the specific board's ttys
+ */
+static int dgnc_read_board_ttyflags(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int i = 0;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	/* Prepare the Header Labels */
+	p += sprintf(p, "%2s %5s %5s %5s %5s %5s %10s  Line Status Flags\n",
+		"Ch", "CFlag", "IFlag", "OFlag", "LFlag", "DFlag", "Baud");
+
+        for (i = 0; i < brd->nasync; i++) {
+
+		struct channel_t *ch = brd->channels[i];
+
+		p += sprintf(p, "%2d ", i);
+		p += sprintf(p, "%5x ", ch->ch_c_cflag);
+		p += sprintf(p, "%5x ", ch->ch_c_iflag);
+		p += sprintf(p, "%5x ", ch->ch_c_oflag);
+		p += sprintf(p, "%5x ", ch->ch_c_lflag);
+		p += sprintf(p, "%5x ", ch->ch_digi.digi_flags);
+		p += sprintf(p, "%10d ", ch->ch_old_baud);
+
+		if (!ch->ch_open_count) {
+			p += sprintf(p, " -- -- -- -- -- -- --") ;
+		} else {
+			p += sprintf(p, " op %s %s %s %s %s %s",
+				(ch->ch_mostat & UART_MCR_RTS) ? "rs" : "--",
+				(ch->ch_mistat & UART_MSR_CTS) ? "cs" : "--",
+				(ch->ch_mostat & UART_MCR_DTR) ? "tr" : "--",
+				(ch->ch_mistat & UART_MSR_DSR) ? "mr" : "--",
+				(ch->ch_mistat & UART_MSR_DCD) ? "cd" : "--",
+				(ch->ch_mistat & UART_MSR_RI)  ? "ri" : "--");
+		}
+
+		p += sprintf(p, "\n");
+	}
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return mknod information for the board's devices.
+ */                                             
+static int dgnc_read_board_mknod(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct board_t	*brd;
+	static int done = 0;
+	static char buf[4096];
+	char str[MAXTTYNAMELEN];
+	char *p = buf;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	brd = (struct board_t *) table->data;
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	/*
+	 * For each board, output the device information in
+	 * a handy table format...
+	 */
+	p += sprintf(p,	"# Create the TTY and PR devices\n");
+
+	/* TTY devices */
+	sprintf(str, "ttyn%d%%p", brd->boardnum + 1);
+	p += sprintf(p, "%s\t\t\t%d\t%d\t%d\n", str,
+		brd->dgnc_Serial_Major, 0, brd->maxports);
+
+	/* PR devices */
+	sprintf(str, "prn%d%%p", brd->boardnum + 1);
+	p += sprintf(p, "%s\t\t\t%d\t%d\t%d\n", str,
+		brd->dgnc_TransparentPrint_Major, 128, brd->maxports);
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return what is (hopefully) useful information about the specific channel.
+ */
+static int dgnc_read_channel_info(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+
+	DPR_PROC(("dgnc_proc_info\n"));
+
+	ch = (struct channel_t *) table->data;
+
+	if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	p += sprintf(p, "Port number:\t\t%d\n", ch->ch_portnum);
+	p += sprintf(p, "\n");
+
+	/* Prepare the Header Labels */
+	p += sprintf(p, "%10s %23s %10s %9s\n",
+		"Chars Rx", "  Rx Par--Brk--Frm--Ovr",
+		"Chars Tx", "XON XOFF");
+	p += sprintf(p, "%10ld ", ch->ch_rxcount);
+	p += sprintf(p, "    %4ld %4ld %4ld %4ld ", ch->ch_err_parity,
+		ch->ch_err_break, ch->ch_err_frame, ch->ch_err_overrun);
+	p += sprintf(p, "%10ld ", ch->ch_txcount);  
+	p += sprintf(p, "%4ld %4ld ", ch->ch_xon_sends, ch->ch_xoff_sends);
+	p += sprintf(p, "\n\n");
+
+	/* Prepare the Header Labels */
+	p += sprintf(p, "%5s %5s %5s %5s %5s %10s  Line Status Flags\n",
+		"CFlag", "IFlag", "OFlag", "LFlag", "DFlag", "Baud");
+
+	p += sprintf(p, "%5x ", ch->ch_c_cflag);
+	p += sprintf(p, "%5x ", ch->ch_c_iflag);
+	p += sprintf(p, "%5x ", ch->ch_c_oflag);
+	p += sprintf(p, "%5x ", ch->ch_c_lflag);
+	p += sprintf(p, "%5x ", ch->ch_digi.digi_flags);
+	p += sprintf(p, "%10d ", ch->ch_old_baud);
+	if (!ch->ch_open_count) {
+		p += sprintf(p, " -- -- -- -- -- -- --") ;
+	} else {
+		p += sprintf(p, " op %s %s %s %s %s %s",
+			(ch->ch_mostat & UART_MCR_RTS) ? "rs" : "--",
+			(ch->ch_mistat & UART_MSR_CTS) ? "cs" : "--",
+			(ch->ch_mostat & UART_MCR_DTR) ? "tr" : "--",
+			(ch->ch_mistat & UART_MSR_DSR) ? "mr" : "--",
+			(ch->ch_mistat & UART_MSR_DCD) ? "cd" : "--",
+			(ch->ch_mistat & UART_MSR_RI)  ? "ri" : "--");
+	}
+	p += sprintf(p, "\n\n");
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf; 
+	done = 1;
+	return 0;
+}
+
+
+/*
+ *  Return mknod information for the board's devices.
+ */
+static int dgnc_read_channel_custom_ttyname(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	struct board_t  *brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int     cn;
+	int     bn;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	ch = (struct channel_t *) table->data;
+
+	if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	brd = ch->ch_bd;        
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	bn = brd->boardnum;
+	cn = ch->ch_portnum;
+
+	p += sprintf(p, "ttyn%d%c\n", bn + 1, 'a' + cn);
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf;
+	done = 1;
+	return 0;
+}
+
+
+
+
+/*
+ *  Return mknod information for the board's devices.
+ */
+static int dgnc_read_channel_custom_prname(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	struct board_t  *brd;
+	static int done = 0;
+	static char buf[4096];
+	char *p = buf;
+	int     cn;
+	int     bn;
+
+	DPR_PROC(("dgnc_proc_brd_info\n"));
+
+	ch = (struct channel_t *) table->data;
+
+	if (done || !ch || (ch->magic != DGNC_CHANNEL_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	brd = ch->ch_bd;        
+
+	if (done || !brd || (brd->magic != DGNC_BOARD_MAGIC)) {
+		done = 0;
+		*lenp = 0;
+		return 0;
+	}
+
+	bn = brd->boardnum;
+	cn = ch->ch_portnum;
+
+	p += sprintf(p, "prn%d%c\n", bn + 1, 'a' + cn);
+
+	if (copy_to_user(buffer, buf, (p - (char *) buf)))
+		return -EFAULT;
+
+	*lenp = p - (char *) buf;
+	*ppos += p - (char *) buf;
+	done = 1;
+	return 0;
+}
+
+
+static int dgnc_open_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				void *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	ulong  lock_flags;
+
+	ch = (struct channel_t *) table->data;
+
+	if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC))
+		return 0;
+
+	ch->ch_sniff_buf = dgnc_driver_kzmalloc(SNIFF_MAX, GFP_KERNEL);
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	ch->ch_sniff_flags |= SNIFF_OPEN;
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	return 0;
+}
+
+static int dgnc_close_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				void *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	ulong  lock_flags;
+
+	ch = (struct channel_t *) table->data;
+
+	if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC))
+		return 0;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	ch->ch_sniff_flags &= ~(SNIFF_OPEN);
+	kfree(ch->ch_sniff_buf);
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	return 0;
+}
+
+
+/*
+ *    Copy data from the monitoring buffer to the user, freeing space
+ *    in the monitoring buffer for more messages
+ *
+ */
+static int dgnc_read_channel_sniff(struct dgnc_proc_entry *table, int dir, struct file *filp,
+				char __user *buffer, ssize_t *lenp, loff_t *ppos)
+{
+	struct channel_t *ch;
+	int n;
+	int r;
+	int offset = 0;
+	int res = 0;
+	ssize_t rtn = 0;
+	ulong  lock_flags;
+
+	ch = (struct channel_t *) table->data;
+
+	if (!ch || (ch->magic != DGNC_CHANNEL_MAGIC)) {
+		rtn = -ENXIO;
+		goto done;
+	}
+
+	/*
+	 *  Wait for some data to appear in the buffer.
+	 */
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	for (;;) {
+		n = (ch->ch_sniff_in - ch->ch_sniff_out) & SNIFF_MASK;
+
+		if (n != 0)
+			break;
+
+		ch->ch_sniff_flags |= SNIFF_WAIT_DATA;
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/*
+		 * Go to sleep waiting until the condition becomes true.
+		 */
+		rtn = wait_event_interruptible(ch->ch_sniff_wait,
+			((ch->ch_sniff_flags & SNIFF_WAIT_DATA) == 0));
+
+		if (rtn)
+			goto done;
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+	}
+
+	/*
+	 *  Read whatever is there.
+	 */
+
+	if (n > *lenp)
+		n = *lenp;
+
+	res = n;
+
+	r = SNIFF_MAX - ch->ch_sniff_out;
+
+	if (r <= n) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rtn = copy_to_user(buffer, ch->ch_sniff_buf + ch->ch_sniff_out, r);
+		if (rtn) {
+			rtn = -EFAULT;
+			goto done;
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		ch->ch_sniff_out = 0;
+		n -= r;
+		offset = r;
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+	rtn = copy_to_user(buffer + offset, ch->ch_sniff_buf + ch->ch_sniff_out, n);
+	if (rtn) {
+		rtn = -EFAULT;
+		goto done;
+	}
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_sniff_out += n;
+	*ppos += res;
+	rtn = res;
+//	rtn = 0;
+
+	/*
+	 *  Wakeup any thread waiting for buffer space.
+	 */
+
+	if (ch->ch_sniff_flags & SNIFF_WAIT_SPACE) {
+		ch->ch_sniff_flags &= ~SNIFF_WAIT_SPACE;
+		wake_up_interruptible(&ch->ch_sniff_wait);
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+done:
+	return rtn;
+}
+
+
+/*
+ * Register the basic /proc/dgnc files that appear whenever
+ * the driver is loaded.
+ */
+void dgnc_proc_register_basic_prescan(void)
+{
+	/*
+	 *      Register /proc/dgnc
+	 */
+	ProcDGNC = proc_create("dgnc", (0700 | S_IFDIR), NULL, &dgnc_proc_file_ops);
+	dgnc_register_proc_table(dgnc_table, ProcDGNC);
+}
+
+
+/*
+ * Register the basic /proc/dgnc files that appear whenever
+ * the driver is loaded.
+ */
+void dgnc_proc_register_basic_postscan(int board_num)
+{
+	int i, j;
+	char board[10];
+	sprintf(board, "%d", board_num);
+
+	/* Set proc board entry pointer */
+	dgnc_Board[board_num]->proc_entry_pointer = create_proc_entry(board, (0700 | S_IFDIR), ProcDGNC);
+
+	/* Create a new copy of the board_table... */
+	dgnc_Board[board_num]->dgnc_board_table = dgnc_driver_kzmalloc(sizeof(dgnc_board_table), 
+		GFP_KERNEL);
+
+	/* Now copy the default table into that memory */
+	memcpy(dgnc_Board[board_num]->dgnc_board_table, dgnc_board_table, sizeof(dgnc_board_table));
+
+	/* Initialize semaphores in each table slot */
+	for (i = 0; i < 999; i++) {
+		if (!dgnc_Board[board_num]->dgnc_board_table[i].magic) {
+			break;
+		}
+
+		init_MUTEX(&(dgnc_Board[board_num]->dgnc_board_table[i].excl_sem));
+		dgnc_Board[board_num]->dgnc_board_table[i].data = dgnc_Board[board_num];
+
+	}
+
+	/* Register board table into proc */
+	dgnc_register_proc_table(dgnc_Board[board_num]->dgnc_board_table, 
+		dgnc_Board[board_num]->proc_entry_pointer);
+
+	/*
+	 * Add new entries for each port.
+	 */
+	for (i = 0; i < dgnc_Board[board_num]->nasync; i++) {
+
+		char channel[10];
+		sprintf(channel, "%d", i);
+
+		/* Set proc channel entry pointer */
+		dgnc_Board[board_num]->channels[i]->proc_entry_pointer =
+			create_proc_entry(channel, (0700 | S_IFDIR), 
+			dgnc_Board[board_num]->proc_entry_pointer);
+
+		/* Create a new copy of the channel_table... */
+		dgnc_Board[board_num]->channels[i]->dgnc_channel_table =
+			dgnc_driver_kzmalloc(sizeof(dgnc_channel_table), GFP_KERNEL);
+
+		/* Now copy the default table into that memory */
+		memcpy(dgnc_Board[board_num]->channels[i]->dgnc_channel_table,
+			dgnc_channel_table, sizeof(dgnc_channel_table));
+
+		/* Initialize semaphores in each table slot */
+		for (j = 0; j < 999; j++) {
+			if (!dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].magic) {
+				break;
+			}
+
+			init_MUTEX(&(dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].excl_sem));
+			dgnc_Board[board_num]->channels[i]->dgnc_channel_table[j].data = 
+				dgnc_Board[board_num]->channels[i];
+		}
+
+		/* Register channel table into proc */
+		dgnc_register_proc_table(dgnc_Board[board_num]->channels[i]->dgnc_channel_table, 
+			dgnc_Board[board_num]->channels[i]->proc_entry_pointer);
+	}
+}
+
+
+static void dgnc_remove_proc_entry(struct proc_dir_entry *pde)
+{ 
+	if (!pde) {
+		DPR_PROC(("dgnc_remove_proc_entry... NULL entry... not removing...\n"));
+		return;
+	}
+
+	remove_proc_entry(pde->name, pde->parent);
+}
+
+
+void dgnc_proc_unregister_all(void)
+{
+	int i = 0, j = 0;
+
+	/* Walk each board, blowing away their proc entries... */
+	for (i = 0; i < dgnc_NumBoards; i++) {
+
+		/* Walk each channel, blowing away their proc entries... */
+		for (j = 0; j < dgnc_Board[i]->nasync; j++) {
+
+			dgnc_unregister_proc_table(dgnc_Board[i]->channels[j]->dgnc_channel_table, 
+				dgnc_Board[i]->channels[j]->proc_entry_pointer);
+			dgnc_remove_proc_entry(dgnc_Board[i]->channels[j]->proc_entry_pointer);
+			kfree(dgnc_Board[i]->channels[j]->dgnc_channel_table);
+		}
+
+		dgnc_unregister_proc_table(dgnc_Board[i]->dgnc_board_table, 
+			dgnc_Board[i]->proc_entry_pointer);
+		dgnc_remove_proc_entry(dgnc_Board[i]->proc_entry_pointer);
+		kfree(dgnc_Board[i]->dgnc_board_table);
+	}
+
+	/* Blow away the top proc entry */
+	dgnc_unregister_proc_table(dgnc_table, ProcDGNC);
+	dgnc_remove_proc_entry(ProcDGNC);
+}
diff --git a/drivers/staging/dgnc/dgnc_proc.h b/drivers/staging/dgnc/dgnc_proc.h
new file mode 100644
index 0000000..19670e2
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_proc.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *      Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *      NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *
+ *     $Id: dgnc_proc.h,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $
+ *
+ *  Description:
+ *
+ *     Describes the private structures used to manipulate the "special"
+ *     proc constructs (not read-only) used by the Digi Neo software.
+ *     The concept is borrowed heavily from the "sysctl" interface of
+ *     the kernel.  I decided not to use the structures and functions
+ *     provided by the kernel for two reasons:
+ *
+ *       1. Due to the planned use of "/proc" in the Neo driver, many
+ *          of the functions of the "sysctl" interface would go unused.
+ *          A simpler interface will be easier to maintain.
+ *
+ *       2. I'd rather divorce our "added package" from the kernel internals.
+ *          If the "sysctl" structures should change, I will be insulated
+ *          from those changes.  These "/proc" entries won't be under the
+ *          "sys" tree anyway, so there is no need to maintain a strict
+ *          dependence relationship.
+ *
+ *  Author:
+ *
+ *     Scott H Kilau
+ *
+ */
+
+#ifndef _DGNC_RW_PROC_H
+#define _DGNC_RW_PROC_H
+
+/*
+ *  The list of DGNC entries with r/w capabilities. 
+ *  These magic numbers are used for identification purposes.
+ */
+enum {
+	DGNC_INFO = 1,			/* Get info about the running module	*/
+	DGNC_MKNOD = 2,			/* Get info about driver devices	*/
+	DGNC_BOARD_INFO = 3,		/* Get info about the specific board	*/
+	DGNC_BOARD_VPD = 4,		/* Get info about the board's VPD	*/
+	DGNC_BOARD_TTYSTATS = 5,	/* Get info about the board's tty stats	*/
+	DGNC_BOARD_TTYINTR = 6,		/* Get info about the board's tty intrs	*/
+	DGNC_BOARD_TTYFLAGS = 7,	/* Get info about the board's tty flags	*/
+	DGNC_BOARD_MKNOD = 8,		/* Get info about board devices		*/
+	DGNC_PORT_INFO = 9,		/* Get info about the specific port	*/
+	DGNC_PORT_SNIFF = 10,		/* Sniff data in/out of specific port	*/
+	DGNC_PORT_CUSTOM_TTYNAME = 11,	/* Get info about UDEV tty name		*/
+	DGNC_PORT_CUSTOM_PRNAME = 12,	/* Get info about UDEV pr name		*/
+};
+
+/*
+ *  Directions for proc handlers
+ */
+enum {
+        INBOUND = 1,		/* Data being written to kernel */
+        OUTBOUND = 2,		/* Data being read from the kernel */
+};
+
+/*
+ *  Each entry in a DGNC proc directory is described with a
+ *  "dgnc_proc_entry" structure.  A collection of these
+ *  entries (in an array) represents the members associated
+ *  with a particular "/proc" directory, and is referred to
+ *  as a table.  All "tables" are terminated by an entry with
+ *  zeros for every member.
+ *
+ *  The structure members are as follows:
+ *
+ *    int magic              -- ID number associated with this particular
+ *                              entry.  Should be unique across all of
+ *                              DGNC.
+ *
+ *    const char *name       -- ASCII name associated with the /proc entry.
+ *
+ *    mode_t mode            -- File access permisssions for the /proc entry.
+ *
+ *    dgnc_proc_entry *child -- When set, this entry refers to a directory,
+ *                              and points to the table which describes the
+ *                              entries in the subdirectory
+ *
+ *    dgnc_proc_handler *open_handler -- When set, points to the fxn which
+ *                                       does any "extra" open stuff.
+ *
+ *    dgnc_proc_handler *close_handler -- When set, points to the fxn which
+ *                                        does any "extra" close stuff.
+ *
+ *    dgnc_proc_handler *read_handler -- When set, points to the fxn which
+ *                                       handle outbound data flow
+ *
+ *    dgnc_proc_handler *write_handler -- When set, points to the fxn which
+ *                                        handles inbound data flow
+ *
+ *    struct proc_dir_entry *de -- Pointer to the directory entry for this
+ *                                 object once registered.  Used to grab
+ *                                 the handle of the object for
+ *                                 unregistration
+ *
+ *    void *data;		   When set, points to the parent structure
+ *
+ */
+
+struct dgnc_proc_entry {
+	int		magic;		/* Integer identifier	*/
+	const char	*name;		/* ASCII identifier	*/
+	mode_t		mode;		/* File access permissions */
+	struct dgnc_proc_entry *child;	/* Child pointer	*/
+
+	int (*open_handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,   
+		void *buffer, ssize_t *lenp, loff_t *ppos); 
+	int (*close_handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,   
+		void *buffer, ssize_t *lenp, loff_t *ppos); 
+	int (*read_handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,   
+		char __user *buffer, ssize_t *lenp, loff_t *ppos); 
+	int (*write_handler) (struct dgnc_proc_entry *table, int dir, struct file *filp,   
+		const char __user *buffer, ssize_t *lenp, loff_t *ppos); 
+
+	struct proc_dir_entry *de;	/* proc entry pointer	*/
+	struct semaphore excl_sem;	/* Protects exclusive access var	*/
+	int		excl_cnt;	/* Counts number of curr accesses	*/
+	void		*data;		/* Allows storing a pointer to parent	*/
+};
+
+void dgnc_proc_register_basic_prescan(void);
+void dgnc_proc_register_basic_postscan(int board_num);
+void dgnc_proc_unregister_all(void);
+
+
+#endif /* _DGNC_RW_PROC_H */
diff --git a/drivers/staging/dgnc/dgnc_sysfs.c b/drivers/staging/dgnc/dgnc_sysfs.c
new file mode 100644
index 0000000..af49e44
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_sysfs.c
@@ -0,0 +1,761 @@
+/*
+ * Copyright 2004 Digi International (www.digi.com)
+ *      Scott H Kilau <Scott_Kilau at digi dot com>
+ * 
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ * 
+ *      NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE!
+ *
+ *      This is shared code between Digi's CVS archive and the
+ *      Linux Kernel sources.
+ *      Changing the source just for reformatting needlessly breaks
+ *      our CVS diff history.
+ *
+ *      Send any bug fixes/changes to:  Eng.Linux at digi dot com.
+ *      Thank you.
+ *
+ *
+ * 
+ * $Id: dgnc_sysfs.c,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $   
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/serial_reg.h>
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/kdev_t.h>
+  
+#include "dgnc_driver.h"
+#include "dgnc_proc.h"
+#include "dgnc_mgmt.h"
+
+
+static ssize_t dgnc_driver_version_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%s\n", DG_PART);
+}
+static DRIVER_ATTR(version, S_IRUSR, dgnc_driver_version_show, NULL);
+
+
+static ssize_t dgnc_driver_boards_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", dgnc_NumBoards);
+}
+static DRIVER_ATTR(boards, S_IRUSR, dgnc_driver_boards_show, NULL);
+
+
+static ssize_t dgnc_driver_maxboards_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", MAXBOARDS);
+}
+static DRIVER_ATTR(maxboards, S_IRUSR, dgnc_driver_maxboards_show, NULL);
+
+
+static ssize_t dgnc_driver_pollcounter_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%ld\n", dgnc_poll_counter);
+}
+static DRIVER_ATTR(pollcounter, S_IRUSR, dgnc_driver_pollcounter_show, NULL);
+
+
+static ssize_t dgnc_driver_state_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%s\n", dgnc_driver_state_text[dgnc_driver_state]);
+}
+static DRIVER_ATTR(state, S_IRUSR, dgnc_driver_state_show, NULL);
+
+
+static ssize_t dgnc_driver_debug_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "0x%x\n", dgnc_debug);
+}
+
+static ssize_t dgnc_driver_debug_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+	sscanf(buf, "0x%x\n", &dgnc_debug);
+	return count;
+}
+static DRIVER_ATTR(debug, (S_IRUSR | S_IWUSR), dgnc_driver_debug_show, dgnc_driver_debug_store);
+
+
+static ssize_t dgnc_driver_rawreadok_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "0x%x\n", dgnc_rawreadok);
+}
+
+static ssize_t dgnc_driver_rawreadok_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+	sscanf(buf, "0x%x\n", &dgnc_rawreadok);
+	return count;
+}
+static DRIVER_ATTR(rawreadok, (S_IRUSR | S_IWUSR), dgnc_driver_rawreadok_show, dgnc_driver_rawreadok_store);
+
+
+static ssize_t dgnc_driver_pollrate_show(struct device_driver *ddp, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%dms\n", dgnc_poll_tick);
+}
+
+static ssize_t dgnc_driver_pollrate_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+	sscanf(buf, "%d\n", &dgnc_poll_tick);
+	return count;
+}
+static DRIVER_ATTR(pollrate, (S_IRUSR | S_IWUSR), dgnc_driver_pollrate_show, dgnc_driver_pollrate_store);
+
+
+void dgnc_create_driver_sysfiles(struct pci_driver *dgnc_driver)
+{
+	int rc = 0;
+	struct device_driver *driverfs = &dgnc_driver->driver;
+
+	rc |= driver_create_file(driverfs, &driver_attr_version);
+	rc |= driver_create_file(driverfs, &driver_attr_boards);
+	rc |= driver_create_file(driverfs, &driver_attr_maxboards);
+	rc |= driver_create_file(driverfs, &driver_attr_debug);
+	rc |= driver_create_file(driverfs, &driver_attr_rawreadok); 
+	rc |= driver_create_file(driverfs, &driver_attr_pollrate);
+	rc |= driver_create_file(driverfs, &driver_attr_pollcounter);
+	rc |= driver_create_file(driverfs, &driver_attr_state);
+	if (rc) {
+		printk(KERN_ERR "DGNC: sysfs driver_create_file failed!\n");
+	}
+}
+
+
+void dgnc_remove_driver_sysfiles(struct pci_driver *dgnc_driver)
+{
+	struct device_driver *driverfs = &dgnc_driver->driver;
+	driver_remove_file(driverfs, &driver_attr_version);
+	driver_remove_file(driverfs, &driver_attr_boards);
+	driver_remove_file(driverfs, &driver_attr_maxboards);
+	driver_remove_file(driverfs, &driver_attr_debug);
+	driver_remove_file(driverfs, &driver_attr_rawreadok);
+	driver_remove_file(driverfs, &driver_attr_pollrate);
+	driver_remove_file(driverfs, &driver_attr_pollcounter);
+	driver_remove_file(driverfs, &driver_attr_state);
+}
+
+
+#define DGNC_VERIFY_BOARD(p, bd)			\
+	if (!p)						\
+		return (0);				\
+							\
+	bd = dev_get_drvdata(p);			\
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)	\
+		return (0);				\
+	if (bd->state != BOARD_READY)			\
+		return (0);				\
+
+
+
+static ssize_t dgnc_vpd_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	count += sprintf(buf + count, "\n      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
+	for (i = 0; i < 0x40 * 2; i++) {
+		if (!(i % 16))
+			count += sprintf(buf + count, "\n%04X ", i * 2);
+		count += sprintf(buf + count, "%02X ", bd->vpd[i]);
+	}
+	count += sprintf(buf + count, "\n");
+
+	return count;
+}
+static DEVICE_ATTR(vpd, S_IRUSR, dgnc_vpd_show, NULL);
+
+static ssize_t dgnc_serial_number_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	if (bd->serial_num[0] == '\0')
+		count += sprintf(buf + count, "<UNKNOWN>\n");
+	else
+		count += sprintf(buf + count, "%s\n", bd->serial_num);
+
+	return count;
+}
+static DEVICE_ATTR(serial_number, S_IRUSR, dgnc_serial_number_show, NULL);
+
+
+static ssize_t dgnc_ports_state_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count,
+			"%d %s\n", bd->channels[i]->ch_portnum,
+			bd->channels[i]->ch_open_count ? "Open" : "Closed");
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_state, S_IRUSR, dgnc_ports_state_show, NULL);
+
+
+static ssize_t dgnc_ports_baud_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count +=  snprintf(buf + count, PAGE_SIZE - count,
+			"%d %d\n", bd->channels[i]->ch_portnum, bd->channels[i]->ch_old_baud);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_baud, S_IRUSR, dgnc_ports_baud_show, NULL);
+
+
+static ssize_t dgnc_ports_msignals_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		if (bd->channels[i]->ch_open_count) {
+			count += snprintf(buf + count, PAGE_SIZE - count,
+				"%d %s %s %s %s %s %s\n", bd->channels[i]->ch_portnum,
+				(bd->channels[i]->ch_mostat & UART_MCR_RTS) ? "RTS" : "",
+				(bd->channels[i]->ch_mistat & UART_MSR_CTS) ? "CTS" : "",
+				(bd->channels[i]->ch_mostat & UART_MCR_DTR) ? "DTR" : "",
+				(bd->channels[i]->ch_mistat & UART_MSR_DSR) ? "DSR" : "",
+				(bd->channels[i]->ch_mistat & UART_MSR_DCD) ? "DCD" : "",
+				(bd->channels[i]->ch_mistat & UART_MSR_RI)  ? "RI"  : "");
+		} else {
+			count += snprintf(buf + count, PAGE_SIZE - count,
+				"%d\n", bd->channels[i]->ch_portnum);
+		}
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_msignals, S_IRUSR, dgnc_ports_msignals_show, NULL);
+
+
+static ssize_t dgnc_ports_iflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_iflag);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_iflag, S_IRUSR, dgnc_ports_iflag_show, NULL);
+
+
+static ssize_t dgnc_ports_cflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_cflag);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_cflag, S_IRUSR, dgnc_ports_cflag_show, NULL);
+
+
+static ssize_t dgnc_ports_oflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_oflag);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_oflag, S_IRUSR, dgnc_ports_oflag_show, NULL);
+
+
+static ssize_t dgnc_ports_lflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_lflag);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_lflag, S_IRUSR, dgnc_ports_lflag_show, NULL);
+
+
+static ssize_t dgnc_ports_digi_flag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_digi.digi_flags);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_digi_flag, S_IRUSR, dgnc_ports_digi_flag_show, NULL);
+
+
+static ssize_t dgnc_ports_rxcount_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_rxcount);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_rxcount, S_IRUSR, dgnc_ports_rxcount_show, NULL);
+
+
+static ssize_t dgnc_ports_txcount_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	int count = 0;
+	int i = 0;
+
+	DGNC_VERIFY_BOARD(p, bd);
+
+	for (i = 0; i < bd->nasync; i++) {
+		count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+			bd->channels[i]->ch_portnum, bd->channels[i]->ch_txcount);
+	}
+	return count;
+}
+static DEVICE_ATTR(ports_txcount, S_IRUSR, dgnc_ports_txcount_show, NULL);
+
+
+/* this function creates the sys files that will export each signal status
+ * to sysfs each value will be put in a separate filename
+ */
+void dgnc_create_ports_sysfiles(struct board_t *bd)
+{
+	int rc = 0;
+
+	dev_set_drvdata(&bd->pdev->dev, bd);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_state);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_vpd);
+	rc |= device_create_file(&(bd->pdev->dev), &dev_attr_serial_number);
+	if (rc) {
+		printk(KERN_ERR "DGNC: sysfs device_create_file failed!\n");
+	}
+}
+
+
+/* removes all the sys files created for that port */
+void dgnc_remove_ports_sysfiles(struct board_t *bd)
+{
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_state);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_vpd);
+	device_remove_file(&(bd->pdev->dev), &dev_attr_serial_number);
+}
+
+
+static ssize_t dgnc_tty_state_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%s", un->un_open_count ? "Open" : "Closed");
+}
+static DEVICE_ATTR(state, S_IRUSR, dgnc_tty_state_show, NULL);
+
+
+static ssize_t dgnc_tty_baud_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_old_baud);
+}
+static DEVICE_ATTR(baud, S_IRUSR, dgnc_tty_baud_show, NULL);
+
+
+static ssize_t dgnc_tty_msignals_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	if (ch->ch_open_count) {
+		return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n",
+			(ch->ch_mostat & UART_MCR_RTS) ? "RTS" : "",
+			(ch->ch_mistat & UART_MSR_CTS) ? "CTS" : "",
+			(ch->ch_mostat & UART_MCR_DTR) ? "DTR" : "",
+			(ch->ch_mistat & UART_MSR_DSR) ? "DSR" : "",
+			(ch->ch_mistat & UART_MSR_DCD) ? "DCD" : "",
+			(ch->ch_mistat & UART_MSR_RI)  ? "RI"  : "");
+	}
+	return 0;
+}
+static DEVICE_ATTR(msignals, S_IRUSR, dgnc_tty_msignals_show, NULL);
+
+
+static ssize_t dgnc_tty_iflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_iflag);
+}
+static DEVICE_ATTR(iflag, S_IRUSR, dgnc_tty_iflag_show, NULL);
+
+
+static ssize_t dgnc_tty_cflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_cflag);
+}
+static DEVICE_ATTR(cflag, S_IRUSR, dgnc_tty_cflag_show, NULL);
+
+
+static ssize_t dgnc_tty_oflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_oflag);
+}
+static DEVICE_ATTR(oflag, S_IRUSR, dgnc_tty_oflag_show, NULL);
+
+
+static ssize_t dgnc_tty_lflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_lflag);
+}
+static DEVICE_ATTR(lflag, S_IRUSR, dgnc_tty_lflag_show, NULL);
+
+
+static ssize_t dgnc_tty_digi_flag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags);
+}
+static DEVICE_ATTR(digi_flag, S_IRUSR, dgnc_tty_digi_flag_show, NULL);
+
+
+static ssize_t dgnc_tty_rxcount_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_rxcount);
+}
+static DEVICE_ATTR(rxcount, S_IRUSR, dgnc_tty_rxcount_show, NULL);
+
+
+static ssize_t dgnc_tty_txcount_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_txcount);
+}
+static DEVICE_ATTR(txcount, S_IRUSR, dgnc_tty_txcount_show, NULL);
+
+
+static ssize_t dgnc_tty_name_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+
+	if (!d)
+		return (0);
+	un = (struct un_t *) dev_get_drvdata(d);
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (0);
+	if (bd->state != BOARD_READY)
+		return (0);
+
+	return snprintf(buf, PAGE_SIZE, "%sn%d%c\n",
+		(un->un_type == DGNC_PRINT) ? "pr" : "tty",
+		bd->boardnum + 1, 'a' + ch->ch_portnum);
+}
+static DEVICE_ATTR(custom_name, S_IRUSR, dgnc_tty_name_show, NULL);
+
+
+static struct attribute *dgnc_sysfs_tty_entries[] = {
+	&dev_attr_state.attr,
+	&dev_attr_baud.attr,
+	&dev_attr_msignals.attr,
+	&dev_attr_iflag.attr,
+	&dev_attr_cflag.attr,
+	&dev_attr_oflag.attr,          
+	&dev_attr_lflag.attr,
+	&dev_attr_digi_flag.attr,
+	&dev_attr_rxcount.attr,
+	&dev_attr_txcount.attr,
+	&dev_attr_custom_name.attr,
+	NULL
+};
+
+
+static struct attribute_group dgnc_tty_attribute_group = {
+        .name = NULL,
+        .attrs = dgnc_sysfs_tty_entries,
+};
+
+
+void dgnc_create_tty_sysfs(struct un_t *un, struct device *c)
+{
+	int ret;
+
+	ret = sysfs_create_group(&c->kobj, &dgnc_tty_attribute_group);
+	if (ret) {
+		printk(KERN_ERR "dgnc: failed to create sysfs tty device attributes.\n");
+		sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group);
+		return;
+	}
+
+	dev_set_drvdata(c, un);
+
+}
+
+ 
+void dgnc_remove_tty_sysfs(struct device *c)
+{
+	sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group);
+}
+
diff --git a/drivers/staging/dgnc/dgnc_sysfs.h b/drivers/staging/dgnc/dgnc_sysfs.h
new file mode 100644
index 0000000..fe99110
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_sysfs.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_SYSFS_H
+#define __DGNC_SYSFS_H
+
+#include "dgnc_driver.h"
+
+#include <linux/device.h>
+
+struct board_t;
+struct channel_t;
+struct un_t;
+struct pci_driver;
+struct class_device;
+
+extern void dgnc_create_ports_sysfiles(struct board_t *bd); 
+extern void dgnc_remove_ports_sysfiles(struct board_t *bd);
+
+extern void dgnc_create_driver_sysfiles(struct pci_driver *);
+extern void dgnc_remove_driver_sysfiles(struct pci_driver *);
+
+extern int dgnc_tty_class_init(void);
+extern int dgnc_tty_class_destroy(void);
+
+extern void dgnc_create_tty_sysfs(struct un_t *un, struct device *c);
+extern void dgnc_remove_tty_sysfs(struct device *c);
+
+
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_trace.c b/drivers/staging/dgnc/dgnc_trace.c
new file mode 100644
index 0000000..ea710e5
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_trace.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE! 
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com. 
+ *	Thank you. 
+ *
+ */
+
+/* $Id: dgnc_trace.c,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
+#include <linux/vmalloc.h>
+
+#include "dgnc_driver.h"
+
+#define TRC_TO_CONSOLE 1
+
+/* file level globals */
+static char *dgnc_trcbuf;		/* the ringbuffer */
+
+#if defined(TRC_TO_KMEM)
+static int dgnc_trcbufi = 0;		/* index of the tilde at the end of */
+#endif
+
+#if defined(TRC_TO_KMEM)
+static DEFINE_SPINLOCK(dgnc_tracef_lock);
+#endif
+
+
+#if 0
+
+#if !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE)
+
+void dgnc_tracef(const char *fmt, ...)
+{
+	return;
+}
+
+#else /* !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE) */
+
+void dgnc_tracef(const char *fmt, ...)
+{
+	va_list	         ap;
+	char  	         buf[TRC_MAXMSG+1];
+	size_t		 lenbuf;
+	int		 i;
+	static int	 failed = FALSE;
+# if defined(TRC_TO_KMEM)
+	unsigned long	 flags;
+#endif
+
+	if(failed)
+		return;
+# if defined(TRC_TO_KMEM)
+	DGNC_LOCK(dgnc_tracef_lock, flags);
+#endif
+
+	/* Format buf using fmt and arguments contained in ap. */
+	va_start(ap, fmt);
+	i = vsprintf(buf, fmt,  ap);
+	va_end(ap);
+	lenbuf = strlen(buf);
+
+# if defined(TRC_TO_KMEM)
+	{
+		static int	 initd=0;
+
+		/*
+		 * Now, in addition to (or instead of) printing this stuff out
+		 * (which is a buffered operation), also tuck it away into a
+		 * corner of memory which can be examined post-crash in kdb.
+		 */
+		if (!initd) {
+			dgnc_trcbuf = (char *) vmalloc(dgnc_trcbuf_size);
+			if(!dgnc_trcbuf) {
+				failed = TRUE;
+				printk("dgnc: tracing init failed!\n");
+				return;
+			}
+
+			memset(dgnc_trcbuf, '\0',  dgnc_trcbuf_size);
+			dgnc_trcbufi = 0;
+			initd++;
+
+			printk("dgnc: tracing enabled - " TRC_DTRC 
+				" 0x%lx 0x%x\n",
+				(unsigned long)dgnc_trcbuf, 
+				dgnc_trcbuf_size);
+		}
+
+#  if defined(TRC_ON_OVERFLOW_WRAP_AROUND)
+		/*
+		 * This is the less CPU-intensive way to do things.  We simply
+		 * wrap around before we fall off the end of the buffer.  A 
+		 * tilde (~) demarcates the current end of the trace.
+		 *
+		 * This method should be used if you are concerned about race
+		 * conditions as it is less likely to affect the timing of
+		 * things.
+		 */
+
+		if (dgnc_trcbufi + lenbuf >= dgnc_trcbuf_size) {
+			/* We are wrapping, so wipe out the last tilde. */
+			dgnc_trcbuf[dgnc_trcbufi] = '\0';
+			/* put the new string at the beginning of the buffer */
+			dgnc_trcbufi = 0;
+		}
+
+		strcpy(&dgnc_trcbuf[dgnc_trcbufi], buf);	
+		dgnc_trcbufi += lenbuf;
+		dgnc_trcbuf[dgnc_trcbufi] = '~';
+
+#  elif defined(TRC_ON_OVERFLOW_SHIFT_BUFFER)
+		/*
+		 * This is the more CPU-intensive way to do things.  If we
+		 * venture into the last 1/8 of the buffer, we shift the 
+		 * last 7/8 of the buffer forward, wiping out the first 1/8.
+		 * Advantage: No wrap-around, only truncation from the
+		 * beginning.
+		 *
+		 * This method should not be used if you are concerned about
+		 * timing changes affecting the behaviour of the driver (ie,
+		 * race conditions).
+		 */
+		strcpy(&dgnc_trcbuf[dgnc_trcbufi], buf);
+		dgnc_trcbufi += lenbuf;
+		dgnc_trcbuf[dgnc_trcbufi] = '~';
+		dgnc_trcbuf[dgnc_trcbufi+1] = '\0';
+
+		/* If we're near the end of the trace buffer... */
+		if (dgnc_trcbufi > (dgnc_trcbuf_size/8)*7) {
+			/* Wipe out the first eighth to make some more room. */
+			strcpy(dgnc_trcbuf, &dgnc_trcbuf[dgnc_trcbuf_size/8]);
+			dgnc_trcbufi = strlen(dgnc_trcbuf)-1;
+			/* Plop overflow message at the top of the buffer. */
+			bcopy(TRC_OVERFLOW, dgnc_trcbuf, strlen(TRC_OVERFLOW));
+		}
+#  else
+#   error "TRC_ON_OVERFLOW_WRAP_AROUND or TRC_ON_OVERFLOW_SHIFT_BUFFER?"
+#  endif
+	}
+	DGNC_UNLOCK(dgnc_tracef_lock, flags);
+
+# endif /* defined(TRC_TO_KMEM) */
+}
+
+#endif /* !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE) */
+
+#endif
+
+
+/*
+ * dgnc_tracer_free()
+ *
+ *
+ */
+void dgnc_tracer_free(void)
+{
+	if(dgnc_trcbuf)
+		vfree(dgnc_trcbuf);
+}
diff --git a/drivers/staging/dgnc/dgnc_trace.h b/drivers/staging/dgnc/dgnc_trace.h
new file mode 100644
index 0000000..1e8870b
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_trace.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *****************************************************************************
+ * Header file for dgnc_trace.c
+ *
+ * $Id: dgnc_trace.h,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $
+ */
+
+#ifndef __DGNC_TRACE_H
+#define __DGNC_TRACE_H
+
+#include "dgnc_driver.h"
+
+#if 0
+
+# if !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE)
+   void dgnc_tracef(const char *fmt, ...);
+# else
+   void dgnc_tracef(const char *fmt, ...);
+# endif
+
+#endif
+
+void dgnc_tracer_free(void);
+
+#endif
+
diff --git a/drivers/staging/dgnc/dgnc_tty.c b/drivers/staging/dgnc/dgnc_tty.c
new file mode 100644
index 0000000..461e881
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_tty.c
@@ -0,0 +1,3648 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *
+ *	NOTE TO LINUX KERNEL HACKERS:  DO NOT REFORMAT THIS CODE! 
+ *
+ *	This is shared code between Digi's CVS archive and the
+ *	Linux Kernel sources.
+ *	Changing the source just for reformatting needlessly breaks
+ *	our CVS diff history.
+ *
+ *	Send any bug fixes/changes to:  Eng.Linux at digi dot com. 
+ *	Thank you. 
+ */
+
+/************************************************************************
+ * 
+ * This file implements the tty driver functionality for the
+ * Neo and ClassicBoard PCI based product lines.
+ * 
+ ************************************************************************
+ *
+ * $Id: dgnc_tty.c,v 1.5 2013/04/30 19:18:30 markh Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>	/* For jiffies, task states */
+#include <linux/interrupt.h>	/* For tasklet and interrupt structs/defines */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/delay.h>        /* For udelay */
+#include <asm/uaccess.h>	/* For copy_from_user/copy_to_user */
+#include <linux/pci.h>
+
+#include "dgnc_driver.h"
+#include "dgnc_tty.h"
+#include "dgnc_types.h"
+#include "dgnc_trace.h"
+#include "dgnc_neo.h"
+#include "dgnc_cls.h"
+#include "dpacompat.h"
+#include "dgnc_sysfs.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+#define init_MUTEX(sem)         sema_init(sem, 1)
+#define DECLARE_MUTEX(name)     \
+        struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+#endif
+
+/*
+ * internal variables
+ */
+static struct board_t	*dgnc_BoardsByMajor[256];
+static uchar		*dgnc_TmpWriteBuf = NULL;
+static DECLARE_MUTEX(dgnc_TmpWriteSem);
+
+/*
+ * Default transparent print information.
+ */
+static struct digi_t dgnc_digi_init = {
+	.digi_flags =	DIGI_COOK,	/* Flags			*/
+	.digi_maxcps =	100,		/* Max CPS			*/
+	.digi_maxchar =	50,		/* Max chars in print queue	*/
+	.digi_bufsize =	100,		/* Printer buffer size		*/
+	.digi_onlen =	4,		/* size of printer on string	*/
+	.digi_offlen =	4,		/* size of printer off string	*/
+	.digi_onstr =	"\033[5i",	/* ANSI printer on string ]	*/
+	.digi_offstr =	"\033[4i",	/* ANSI printer off string ]	*/
+	.digi_term =	"ansi"		/* default terminal type	*/
+};
+
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.
+ *
+ * This defines a raw port at 9600 baud, 8 data bits, no parity,
+ * 1 stop bit.
+ */
+static struct ktermios DgncDefaultTermios =
+{
+	.c_iflag =	(DEFAULT_IFLAGS),	/* iflags */
+	.c_oflag =	(DEFAULT_OFLAGS),	/* oflags */
+	.c_cflag =	(DEFAULT_CFLAGS),	/* cflags */
+	.c_lflag =	(DEFAULT_LFLAGS),	/* lflags */
+	.c_cc =		INIT_C_CC,
+	.c_line =	0,
+};
+
+
+/* Our function prototypes */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file);
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file);
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch);
+static int dgnc_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo);
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info);
+static int dgnc_tty_write_room(struct tty_struct* tty);
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c);
+static int dgnc_tty_chars_in_buffer(struct tty_struct* tty);
+static void dgnc_tty_start(struct tty_struct *tty);
+static void dgnc_tty_stop(struct tty_struct *tty);
+static void dgnc_tty_throttle(struct tty_struct *tty);
+static void dgnc_tty_unthrottle(struct tty_struct *tty);
+static void dgnc_tty_flush_chars(struct tty_struct *tty);
+static void dgnc_tty_flush_buffer(struct tty_struct *tty);
+static void dgnc_tty_hangup(struct tty_struct *tty);
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value);
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+static int dgnc_tty_tiocmget(struct tty_struct *tty);
+static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear);
+#else
+static int dgnc_tty_tiocmget(struct tty_struct *tty, struct file *file);
+static int dgnc_tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear);
+#endif
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec);
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout);
+static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios);
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch);
+
+
+static const struct tty_operations dgnc_tty_ops = {
+	.open = dgnc_tty_open,
+	.close = dgnc_tty_close,
+	.write = dgnc_tty_write,
+	.write_room = dgnc_tty_write_room,
+	.flush_buffer = dgnc_tty_flush_buffer,
+	.chars_in_buffer = dgnc_tty_chars_in_buffer,
+	.flush_chars = dgnc_tty_flush_chars,
+	.ioctl = dgnc_tty_ioctl,
+	.set_termios = dgnc_tty_set_termios,
+	.stop = dgnc_tty_stop,
+	.start = dgnc_tty_start,
+	.throttle = dgnc_tty_throttle,
+	.unthrottle = dgnc_tty_unthrottle,
+	.hangup = dgnc_tty_hangup,
+	.put_char = dgnc_tty_put_char,
+	.tiocmget = dgnc_tty_tiocmget,
+	.tiocmset = dgnc_tty_tiocmset,
+	.break_ctl = dgnc_tty_send_break,
+	.wait_until_sent = dgnc_tty_wait_until_sent,
+	.send_xchar = dgnc_tty_send_xchar
+};
+
+/************************************************************************
+ *                      
+ * TTY Initialization/Cleanup Functions
+ *      
+ ************************************************************************/
+         
+/*
+ * dgnc_tty_preinit()
+ *
+ * Initialize any global tty related data before we download any boards.
+ */
+int dgnc_tty_preinit(void)
+{
+	/*
+	 * Allocate a buffer for doing the copy from user space to
+	 * kernel space in dgnc_write().  We only use one buffer and
+	 * control access to it with a semaphore.  If we are paging, we
+	 * are already in trouble so one buffer won't hurt much anyway.
+	 *
+	 * We are okay to sleep in the malloc, as this routine
+	 * is only called during module load, (not in interrupt context),
+	 * and with no locks held.
+	 */
+	dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL);
+
+	if (!dgnc_TmpWriteBuf) {
+		DPR_INIT(("unable to allocate tmp write buf"));
+		return (-ENOMEM);
+	}
+
+	return(0);
+}
+
+
+/*
+ * dgnc_tty_register()
+ *
+ * Init the tty subsystem for this board.
+ */
+int dgnc_tty_register(struct board_t *brd)
+{
+	int rc = 0;
+
+	DPR_INIT(("tty_register start\n"));
+
+        memset(&brd->SerialDriver, 0, sizeof(struct tty_driver));
+	memset(&brd->PrintDriver, 0, sizeof(struct tty_driver));
+
+	brd->SerialDriver.magic = TTY_DRIVER_MAGIC;
+	
+	snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum);
+
+	brd->SerialDriver.name = brd->SerialName;
+	brd->SerialDriver.name_base = 0;
+	brd->SerialDriver.major = 0;
+	brd->SerialDriver.minor_start = 0;
+	brd->SerialDriver.num = brd->maxports;
+	brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL; 
+	brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL;   
+	brd->SerialDriver.init_termios = DgncDefaultTermios;
+	brd->SerialDriver.driver_name = DRVSTR;
+	brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+	/* 
+	 * The kernel wants space to store pointers to
+	 * tty_struct's and termios's. 
+	 */
+	brd->SerialDriver.ttys = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct tty_struct *), GFP_KERNEL);
+	if (!brd->SerialDriver.ttys)
+		return(-ENOMEM);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+        brd->SerialDriver.refcount = brd->TtyRefCnt;
+#else
+        kref_init(&brd->SerialDriver.kref);
+#endif
+
+	brd->SerialDriver.termios = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct ktermios *), GFP_KERNEL);
+	if (!brd->SerialDriver.termios)
+		return(-ENOMEM);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
+	brd->SerialDriver.termios_locked = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct ktermios *), GFP_KERNEL);
+	if (!brd->SerialDriver.termios_locked)
+		return(-ENOMEM);
+#endif
+	/*
+	 * Entry points for driver.  Called by the kernel from
+	 * tty_io.c and n_tty.c.
+	 */
+	tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops);
+
+	if (!brd->dgnc_Major_Serial_Registered) {
+		/* Register tty devices */
+		rc = tty_register_driver(&brd->SerialDriver);
+		if (rc < 0) {
+			APR(("Can't register tty device (%d)\n", rc));
+			return(rc);
+		}
+		brd->dgnc_Major_Serial_Registered = TRUE;
+	}
+
+	/*
+	 * If we're doing transparent print, we have to do all of the above
+	 * again, seperately so we don't get the LD confused about what major
+	 * we are when we get into the dgnc_tty_open() routine.
+	 */
+	brd->PrintDriver.magic = TTY_DRIVER_MAGIC;
+	snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum);
+
+	brd->PrintDriver.name = brd->PrintName;
+	brd->PrintDriver.name_base = 0;
+	brd->PrintDriver.major = brd->SerialDriver.major;
+	brd->PrintDriver.minor_start = 0x80;
+	brd->PrintDriver.num = brd->maxports;
+	brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL;   
+	brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL;
+	brd->PrintDriver.init_termios = DgncDefaultTermios;
+	brd->PrintDriver.driver_name = DRVSTR;
+	brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+	/*
+	 * The kernel wants space to store pointers to
+	 * tty_struct's and termios's.  Must be seperate from
+	 * the Serial Driver so we don't get confused
+	 */
+	brd->PrintDriver.ttys = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct tty_struct *), GFP_KERNEL);
+	if (!brd->PrintDriver.ttys)
+		return(-ENOMEM);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+        brd->PrintDriver.refcount = brd->TtyRefCnt;
+#else
+        kref_init(&brd->PrintDriver.kref);
+#endif
+
+	brd->PrintDriver.termios = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct ktermios *), GFP_KERNEL);
+	if (!brd->PrintDriver.termios)
+		return(-ENOMEM);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
+	brd->PrintDriver.termios_locked = dgnc_driver_kzmalloc(brd->maxports * sizeof(struct ktermios *), GFP_KERNEL);
+	if (!brd->PrintDriver.termios_locked)
+		return(-ENOMEM);
+#endif
+
+	/*
+	 * Entry points for driver.  Called by the kernel from
+	 * tty_io.c and n_tty.c.
+	 */
+	tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops);
+
+	if (!brd->dgnc_Major_TransparentPrint_Registered) {
+		/* Register Transparent Print devices */
+ 		rc = tty_register_driver(&brd->PrintDriver);
+		if (rc < 0) {
+			APR(("Can't register Transparent Print device (%d)\n", rc));
+			return(rc);
+		}
+		brd->dgnc_Major_TransparentPrint_Registered = TRUE;
+	}
+
+	dgnc_BoardsByMajor[brd->SerialDriver.major] = brd;
+	brd->dgnc_Serial_Major = brd->SerialDriver.major;
+	brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major;
+
+	DPR_INIT(("DGNC REGISTER TTY: MAJOR: %d\n", brd->SerialDriver.major));
+
+	return (rc);
+}
+
+
+/*
+ * dgnc_tty_init()
+ *
+ * Init the tty subsystem.  Called once per board after board has been
+ * downloaded and init'ed.
+ */
+int dgnc_tty_init(struct board_t *brd)
+{
+	int i;
+	uchar *vaddr;
+	struct channel_t *ch;
+
+	if (!brd)
+		return (-ENXIO);
+
+	DPR_INIT(("dgnc_tty_init start\n"));
+
+	/*
+	 * Initialize board structure elements.
+	 */
+
+	vaddr = brd->re_map_membase;
+
+	brd->nasync = brd->maxports;
+
+	/*
+	 * Allocate channel memory that might not have been allocated
+	 * when the driver was first loaded.
+	 */
+	for (i = 0; i < brd->nasync; i++) {
+		if (!brd->channels[i]) {
+
+			/*
+			 * Okay to malloc with GFP_KERNEL, we are not at
+			 * interrupt context, and there are no locks held.
+			 */
+			brd->channels[i] = dgnc_driver_kzmalloc(sizeof(struct channel_t), GFP_KERNEL);
+			if (!brd->channels[i]) {
+				DPR_CORE(("%s:%d Unable to allocate memory for channel struct\n",
+				    __FILE__, __LINE__));
+			}
+		}
+	}
+
+	ch = brd->channels[0];
+	vaddr = brd->re_map_membase;
+
+	/* Set up channel variables */
+	for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
+
+		if (!brd->channels[i])
+			continue;
+
+		DGNC_SPINLOCK_INIT(ch->ch_lock);
+
+		/* Store all our magic numbers */
+		ch->magic = DGNC_CHANNEL_MAGIC;
+		ch->ch_tun.magic = DGNC_UNIT_MAGIC;
+		ch->ch_tun.un_ch = ch;
+		ch->ch_tun.un_type = DGNC_SERIAL;
+		ch->ch_tun.un_dev = i;
+
+		ch->ch_pun.magic = DGNC_UNIT_MAGIC;
+		ch->ch_pun.un_ch = ch;
+		ch->ch_pun.un_type = DGNC_PRINT;
+		ch->ch_pun.un_dev = i + 128;
+
+		if (brd->bd_uart_offset == 0x200)
+			ch->ch_neo_uart = (struct neo_uart_struct *) ((ulong) vaddr + (brd->bd_uart_offset * i));
+		else
+			ch->ch_cls_uart = (struct cls_uart_struct *) ((ulong) vaddr + (brd->bd_uart_offset * i));
+
+		ch->ch_bd = brd;
+		ch->ch_portnum = i;
+		ch->ch_digi = dgnc_digi_init;
+
+		/* .25 second delay */
+		ch->ch_close_delay = 250;
+
+		init_waitqueue_head(&ch->ch_flags_wait);
+		init_waitqueue_head(&ch->ch_tun.un_flags_wait);
+		init_waitqueue_head(&ch->ch_pun.un_flags_wait);
+		init_waitqueue_head(&ch->ch_sniff_wait);
+
+		{
+			struct device *classp;
+			classp = tty_register_device(&brd->SerialDriver, i,
+				&(ch->ch_bd->pdev->dev));
+			ch->ch_tun.un_sysfs = classp;
+			dgnc_create_tty_sysfs(&ch->ch_tun, classp);
+
+			classp = tty_register_device(&brd->PrintDriver, i,
+				&(ch->ch_bd->pdev->dev));
+			ch->ch_pun.un_sysfs = classp;
+			dgnc_create_tty_sysfs(&ch->ch_pun, classp);
+		}
+
+	}
+
+	DPR_INIT(("dgnc_tty_init finish\n"));
+
+	return (0);
+}
+
+
+/*
+ * dgnc_tty_post_uninit()
+ *
+ * UnInitialize any global tty related data.
+ */
+void dgnc_tty_post_uninit(void)
+{
+	if (dgnc_TmpWriteBuf) {
+		kfree(dgnc_TmpWriteBuf);
+		dgnc_TmpWriteBuf = NULL;
+	}
+}
+
+
+/*
+ * dgnc_tty_uninit()
+ *
+ * Uninitialize the TTY portion of this driver.  Free all memory and
+ * resources. 
+ */
+void dgnc_tty_uninit(struct board_t *brd)
+{
+	int i = 0;
+
+	if (brd->dgnc_Major_Serial_Registered) {
+		dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL;
+		brd->dgnc_Serial_Major = 0;
+		for (i = 0; i < brd->nasync; i++) {
+			dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs);
+			tty_unregister_device(&brd->SerialDriver, i);
+		}
+		tty_unregister_driver(&brd->SerialDriver);
+		brd->dgnc_Major_Serial_Registered = FALSE;
+	}
+
+	if (brd->dgnc_Major_TransparentPrint_Registered) {
+		dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL;
+		brd->dgnc_TransparentPrint_Major = 0;
+		for (i = 0; i < brd->nasync; i++) {
+			dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs);
+			tty_unregister_device(&brd->PrintDriver, i);
+		}
+		tty_unregister_driver(&brd->PrintDriver);
+		brd->dgnc_Major_TransparentPrint_Registered = FALSE;
+	}
+
+	if (brd->SerialDriver.ttys) {
+		kfree(brd->SerialDriver.ttys);
+		brd->SerialDriver.ttys = NULL;
+	}
+	if (brd->PrintDriver.ttys) {
+		kfree(brd->PrintDriver.ttys);
+		brd->PrintDriver.ttys = NULL;
+        }
+}
+
+
+#define TMPBUFLEN (1024)
+
+/*
+ * dgnc_sniff - Dump data out to the "sniff" buffer if the
+ * proc sniff file is opened...
+ */
+void dgnc_sniff_nowait_nolock(struct channel_t *ch, uchar *text, uchar *buf, int len)
+{
+	struct timeval tv;
+	int n;
+	int r;
+	int nbuf;
+	int i;
+	int tmpbuflen;
+	char tmpbuf[TMPBUFLEN];
+	char *p = tmpbuf;
+	int too_much_data;
+
+	/* Leave if sniff not open */
+	if (!(ch->ch_sniff_flags & SNIFF_OPEN))
+		return;
+
+	do_gettimeofday(&tv);
+
+	/* Create our header for data dump */
+	p += sprintf(p, "<%ld %ld><%s><", tv.tv_sec, tv.tv_usec, text);
+	tmpbuflen = p - tmpbuf;
+
+	do {
+		too_much_data = 0;
+
+		for (i = 0; i < len && tmpbuflen < (TMPBUFLEN - 4); i++) {
+			p += sprintf(p, "%02x ", *buf);
+			buf++;
+			tmpbuflen = p - tmpbuf;
+		}
+
+		if (tmpbuflen < (TMPBUFLEN - 4)) {
+			if (i > 0)
+				p += sprintf(p - 1, "%s\n", ">");
+			else
+				p += sprintf(p, "%s\n", ">");
+		} else {
+			too_much_data = 1;
+			len -= i;
+		}
+
+		nbuf = strlen(tmpbuf);
+		p = tmpbuf;
+
+		/*
+		 *  Loop while data remains.
+		 */
+		while (nbuf > 0 && ch->ch_sniff_buf != 0) {
+			/*
+			 *  Determine the amount of available space left in the
+			 *  buffer.  If there's none, wait until some appears.
+			 */
+			n = (ch->ch_sniff_out - ch->ch_sniff_in - 1) & SNIFF_MASK;
+
+			/*
+			 * If there is no space left to write to in our sniff buffer,
+			 * we have no choice but to drop the data.
+			 * We *cannot* sleep here waiting for space, because this
+			 * function was probably called by the interrupt/timer routines!
+			 */
+			if (n == 0) {
+				return;
+			}
+	
+			/*
+			 * Copy as much data as will fit.
+			 */
+
+			if (n > nbuf)
+				n = nbuf;
+
+			r = SNIFF_MAX - ch->ch_sniff_in;
+
+			if (r <= n) {
+				memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, r);
+
+				n -= r;
+				ch->ch_sniff_in = 0;
+				p += r;
+				nbuf -= r;
+			}
+
+			memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, n);
+
+			ch->ch_sniff_in += n;
+			p += n;
+			nbuf -= n;
+
+			/*
+			 *  Wakeup any thread waiting for data
+			 */
+			if (ch->ch_sniff_flags & SNIFF_WAIT_DATA) {
+				ch->ch_sniff_flags &= ~SNIFF_WAIT_DATA;
+				wake_up_interruptible(&ch->ch_sniff_wait);
+			}
+		}
+
+		/*
+		 * If the user sent us too much data to push into our tmpbuf,
+		 * we need to keep looping around on all the data.
+		 */
+		if (too_much_data) {
+			p = tmpbuf;
+			tmpbuflen = 0;
+		}
+
+	} while (too_much_data);
+}
+
+
+/*=======================================================================
+ *
+ *	dgnc_wmove - Write data to transmit queue.
+ *
+ *		ch	- Pointer to channel structure.
+ *		buf	- Poiter to characters to be moved.
+ *		n	- Number of characters to move.
+ *
+ *=======================================================================*/
+static void dgnc_wmove(struct channel_t *ch, char *buf, uint n)
+{
+	int	remain;
+	uint	head;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+ 
+	head = ch->ch_w_head & WQUEUEMASK;
+
+	/*
+	 * If the write wraps over the top of the circular buffer,
+	 * move the portion up to the wrap point, and reset the
+	 * pointers to the bottom.
+	 */
+	remain = WQUEUESIZE - head;
+
+	if (n >= remain) {
+		n -= remain;  
+		memcpy(ch->ch_wqueue + head, buf, remain);
+		head = 0;
+		buf += remain;
+	}
+
+	if (n > 0) {
+		/*
+		 * Move rest of data.
+		 */
+		remain = n;
+		memcpy(ch->ch_wqueue + head, buf, remain);
+		head += remain;
+	}
+
+	head &= WQUEUEMASK;
+	ch->ch_w_head = head;
+}
+
+
+
+
+/*=======================================================================
+ *
+ *      dgnc_input - Process received data.
+ * 
+ *              ch      - Pointer to channel structure.
+ * 
+ *=======================================================================*/
+void dgnc_input(struct channel_t *ch)
+{
+	struct board_t *bd;
+	struct tty_struct *tp;
+	struct tty_ldisc *ld;
+	uint	rmask;
+	ushort	head;
+	ushort	tail;
+	int	data_len;
+	ulong	lock_flags;
+	int flip_len;
+	int len = 0;
+	int n = 0;
+	char *buf = NULL;
+	int s = 0;
+	int i = 0;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	tp = ch->ch_tun.un_tty;
+
+	bd = ch->ch_bd;
+	if(!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* 
+	 *      Figure the number of characters in the buffer.   
+	 *      Exit immediately if none.
+	 */
+	rmask = RQUEUEMASK;
+	head = ch->ch_r_head & rmask;
+	tail = ch->ch_r_tail & rmask;
+	data_len = (head - tail) & rmask;
+
+	if (data_len == 0) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	DPR_READ(("dgnc_input start\n"));
+
+	/*
+	 * If the device is not open, or CREAD is off,
+         * flush input data and return immediately.
+	 */
+	if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) || 
+	    !(tp->termios->c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) {
+
+		DPR_READ(("input. dropping %d bytes on port %d...\n", data_len, ch->ch_portnum));
+		DPR_READ(("input. tp: %p tp->magic: %x MAGIC:%x ch flags: %x\n",
+			tp, tp ? tp->magic : 0, TTY_MAGIC, ch->ch_tun.un_flags));
+
+		ch->ch_r_head = tail;
+
+		/* Force queue flow control to be released, if needed */
+		dgnc_check_queue_flow_control(ch);
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	/*
+	 * If we are throttled, simply don't read any data.
+	 */
+	if (ch->ch_flags & CH_FORCED_STOPI) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		DPR_READ(("Port %d throttled, not reading any data. head: %x tail: %x\n",
+			ch->ch_portnum, head, tail));
+		return;
+	}
+
+	DPR_READ(("dgnc_input start 2\n"));
+
+	/* Decide how much data we can send into the tty layer */
+	if (dgnc_rawreadok && tp->real_raw)
+		flip_len = MYFLIPLEN;
+	else
+		flip_len = TTY_FLIPBUF_SIZE;
+
+	/* Chop down the length, if needed */
+	len = min(data_len, flip_len);
+	len = min(len, (N_TTY_BUF_SIZE - 1) - tp->read_cnt);
+
+	ld = tty_ldisc_ref(tp);
+
+#ifdef TTY_DONT_FLIP
+	/*
+	 * If the DONT_FLIP flag is on, don't flush our buffer, and act
+	 * like the ld doesn't have any space to put the data right now.
+	 */
+	if (test_bit(TTY_DONT_FLIP, &tp->flags))
+		len = 0;
+#endif
+
+	/*
+	 * If we were unable to get a reference to the ld,
+	 * don't flush our buffer, and act like the ld doesn't
+	 * have any space to put the data right now.
+	 */
+	if (!ld) {
+		len = 0;
+	} else {
+		/*
+		 * If ld doesn't have a pointer to a receive_buf function,
+		 * flush the data, then act like the ld doesn't have any
+		 * space to put the data right now.
+		 */
+		if (!ld->ops->receive_buf) {
+			ch->ch_r_head = ch->ch_r_tail;
+			len = 0;
+		} 
+	}
+
+	if (len <= 0) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		if (ld)
+			tty_ldisc_deref(ld);
+		return;
+	}
+
+	/*
+	 * The tty layer in the kernel has changed in 2.6.16+.
+	 *
+	 * The flip buffers in the tty structure are no longer exposed,
+	 * and probably will be going away eventually.
+	 * 
+	 * If we are completely raw, we don't need to go through a lot
+	 * of the tty layers that exist.
+	 * In this case, we take the shortest and fastest route we
+	 * can to relay the data to the user.
+	 *
+	 * On the other hand, if we are not raw, we need to go through
+	 * the new 2.6.16+ tty layer, which has its API more well defined.
+	 */
+	if (dgnc_rawreadok && tp->real_raw) {
+
+		if (ch->ch_flags & CH_FLIPBUF_IN_USE) {
+			DPR_READ(("DGNC - FLIPBUF in use. delaying input\n"));
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			if (ld)
+				tty_ldisc_deref(ld);
+			return;
+		}
+
+		ch->ch_flags |= CH_FLIPBUF_IN_USE;
+		buf = ch->ch_bd->flipbuf;
+
+		n = len;
+
+		/*
+		 * n now contains the most amount of data we can copy,
+		 * bounded either by the flip buffer size or the amount
+		 * of data the card actually has pending...
+		 */
+		while (n) {
+			s = ((head >= tail) ? head : RQUEUESIZE) - tail;
+			s = min(s, n);
+
+			if (s <= 0)
+				break;
+
+			memcpy(buf, ch->ch_rqueue + tail, s);
+			dgnc_sniff_nowait_nolock(ch, "USER READ", ch->ch_rqueue + tail, s);
+
+			tail += s;
+			buf += s;
+
+			n -= s;
+			/* Flip queue if needed */
+			tail &= rmask;
+		}
+
+		ch->ch_r_tail = tail & rmask;
+		ch->ch_e_tail = tail & rmask;
+
+		dgnc_check_queue_flow_control(ch);
+
+		/* !!! WE *MUST* LET GO OF ALL LOCKS BEFORE CALLING RECEIVE BUF !!! */
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_READ(("dgnc_input. %d real_raw len:%d calling receive_buf for buffer for board %d\n", 
+			 __LINE__, len, ch->ch_bd->boardnum));
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+		tp->ldisc->ops->receive_buf(tp, ch->ch_bd->flipbuf, NULL, len);
+#else
+		tp->ldisc.ops->receive_buf(tp, ch->ch_bd->flipbuf, NULL, len);
+#endif
+
+		/* Allow use of channel flip buffer again */
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		ch->ch_flags &= ~CH_FLIPBUF_IN_USE;
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	}
+	else {
+		len = tty_buffer_request_room(tp, len);
+		n = len;
+
+		/*
+		 * n now contains the most amount of data we can copy,
+		 * bounded either by how much the Linux tty layer can handle,
+		 * or the amount of data the card actually has pending...
+		 */
+		while (n) {
+			s = ((head >= tail) ? head : RQUEUESIZE) - tail;
+			s = min(s, n);
+
+			if (s <= 0)
+				break;
+
+			/*
+			 * If conditions are such that ld needs to see all 
+			 * UART errors, we will have to walk each character
+			 * and error byte and send them to the buffer one at
+			 * a time.
+			 */
+			if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
+				for (i = 0; i < s; i++) {
+					if (*(ch->ch_equeue + tail + i) & UART_LSR_BI)
+						tty_insert_flip_char(tp, *(ch->ch_rqueue + tail + i), TTY_BREAK);
+					else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE)
+						tty_insert_flip_char(tp, *(ch->ch_rqueue + tail + i), TTY_PARITY);
+					else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE)
+						tty_insert_flip_char(tp, *(ch->ch_rqueue + tail + i), TTY_FRAME);
+					else
+						tty_insert_flip_char(tp, *(ch->ch_rqueue + tail + i), TTY_NORMAL);
+				}
+			}
+			else {
+				tty_insert_flip_string(tp, ch->ch_rqueue + tail, s);
+			}
+
+			dgnc_sniff_nowait_nolock(ch, "USER READ", ch->ch_rqueue + tail, s);
+
+			tail += s;
+			n -= s;
+			/* Flip queue if needed */
+			tail &= rmask;
+		}
+
+		ch->ch_r_tail = tail & rmask;
+		ch->ch_e_tail = tail & rmask;
+		dgnc_check_queue_flow_control(ch);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/* Tell the tty layer its okay to "eat" the data now */
+		tty_flip_buffer_push(tp);
+	}
+
+	if (ld)
+		tty_ldisc_deref(ld);
+
+	DPR_READ(("dgnc_input - finish\n"));
+}
+
+
+/************************************************************************   
+ * Determines when CARRIER changes state and takes appropriate
+ * action. 
+ ************************************************************************/
+void dgnc_carrier(struct channel_t *ch)
+{
+	struct board_t *bd;
+
+        int virt_carrier = 0;
+        int phys_carrier = 0;
+ 
+	DPR_CARR(("dgnc_carrier called...\n"));
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	bd = ch->ch_bd;
+
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	if (ch->ch_mistat & UART_MSR_DCD) {
+		DPR_CARR(("mistat: %x  D_CD: %x\n", ch->ch_mistat, ch->ch_mistat & UART_MSR_DCD));
+		phys_carrier = 1;
+	}
+
+	if (ch->ch_digi.digi_flags & DIGI_FORCEDCD) {
+		virt_carrier = 1;
+	}  
+
+	if (ch->ch_c_cflag & CLOCAL) {
+		virt_carrier = 1;
+	}  
+
+
+	DPR_CARR(("DCD: physical: %d virt: %d\n", phys_carrier, virt_carrier));
+
+	/*
+	 * Test for a VIRTUAL carrier transition to HIGH.
+	 */
+	if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {
+
+		/*
+		 * When carrier rises, wake any threads waiting
+		 * for carrier in the open routine.
+		 */
+
+		DPR_CARR(("carrier: virt DCD rose\n"));
+
+		if (waitqueue_active(&(ch->ch_flags_wait)))
+			wake_up_interruptible(&ch->ch_flags_wait);
+	}
+
+	/*
+	 * Test for a PHYSICAL carrier transition to HIGH.
+	 */
+	if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {
+
+		/*
+		 * When carrier rises, wake any threads waiting
+		 * for carrier in the open routine.
+		 */
+
+		DPR_CARR(("carrier: physical DCD rose\n"));
+
+		if (waitqueue_active(&(ch->ch_flags_wait)))
+			wake_up_interruptible(&ch->ch_flags_wait);
+	}
+
+	/*
+	 *  Test for a PHYSICAL transition to low, so long as we aren't
+	 *  currently ignoring physical transitions (which is what "virtual
+	 *  carrier" indicates).
+	 *
+	 *  The transition of the virtual carrier to low really doesn't
+	 *  matter... it really only means "ignore carrier state", not
+	 *  "make pretend that carrier is there".
+	 */
+	if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) &&
+	    (phys_carrier == 0)) 
+	{
+
+		/*
+		 *   When carrier drops:
+		 *
+		 *   Drop carrier on all open units.
+		 *
+		 *   Flush queues, waking up any task waiting in the
+		 *   line discipline.
+		 *
+		 *   Send a hangup to the control terminal.
+		 *
+		 *   Enable all select calls.
+		 */
+		if (waitqueue_active(&(ch->ch_flags_wait)))
+			wake_up_interruptible(&ch->ch_flags_wait);
+
+		if (ch->ch_tun.un_open_count > 0) {
+			DPR_CARR(("Sending tty hangup\n"));
+			tty_hangup(ch->ch_tun.un_tty);
+		}
+
+		if (ch->ch_pun.un_open_count > 0) { 
+			DPR_CARR(("Sending pr hangup\n"));
+			tty_hangup(ch->ch_pun.un_tty);
+		}
+	}
+
+	/*
+	 *  Make sure that our cached values reflect the current reality.
+	 */
+	if (virt_carrier == 1)
+		ch->ch_flags |= CH_FCAR;
+	else      
+		ch->ch_flags &= ~CH_FCAR;
+
+	if (phys_carrier == 1)
+		ch->ch_flags |= CH_CD;
+	else
+		ch->ch_flags &= ~CH_CD;
+}
+
+/*
+ *  Assign the custom baud rate to the channel structure
+ */
+static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate)
+{
+	int testdiv;
+	int testrate_high;
+	int testrate_low;  
+	int deltahigh;
+	int deltalow;
+
+	if (newrate < 0)
+		newrate = 0;
+
+	/*
+	 *  Since the divisor is stored in a 16-bit integer, we make sure
+	 *  we don't allow any rates smaller than a 16-bit integer would allow.
+	 *  And of course, rates above the dividend won't fly.
+	 */
+	if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1))
+		newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1);
+
+	if (newrate && newrate > ch->ch_bd->bd_dividend)
+                newrate = ch->ch_bd->bd_dividend;
+
+	while (newrate > 0) {
+		testdiv = ch->ch_bd->bd_dividend / newrate;
+
+		/*
+		 *  If we try to figure out what rate the board would use
+		 *  with the test divisor, it will be either equal or higher
+		 *  than the requested baud rate.  If we then determine the
+		 *  rate with a divisor one higher, we will get the next lower 
+		 *  supported rate below the requested.
+		 */
+		testrate_high = ch->ch_bd->bd_dividend / testdiv;
+		testrate_low  = ch->ch_bd->bd_dividend / (testdiv + 1);
+
+		/*
+		 *  If the rate for the requested divisor is correct, just
+		 *  use it and be done.
+		 */
+		if (testrate_high == newrate )
+			break;
+
+		/*
+		 *  Otherwise, pick the rate that is closer (i.e. whichever rate
+		 *  has a smaller delta).
+		 */
+		deltahigh = testrate_high - newrate;
+		deltalow = newrate - testrate_low;
+
+		if (deltahigh < deltalow) {
+			newrate = testrate_high;
+                } else {
+			newrate = testrate_low;
+		}
+
+		break;
+	}
+                 
+	ch->ch_custom_speed = newrate;
+
+	return;
+}
+
+
+void dgnc_check_queue_flow_control(struct channel_t *ch)
+{
+	int qleft = 0;
+
+	/* Store how much space we have left in the queue */
+	if ((qleft = ch->ch_r_tail - ch->ch_r_head - 1) < 0)
+		qleft += RQUEUEMASK + 1;
+
+	/*
+	 * Check to see if we should enforce flow control on our queue because
+	 * the ld (or user) isn't reading data out of our queue fast enuf.
+	 *
+	 * NOTE: This is done based on what the current flow control of the
+	 * port is set for.
+	 *
+	 * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt.
+	 *	This will cause the UART's FIFO to back up, and force
+	 *	the RTS signal to be dropped.
+	 * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to
+	 *	the other side, in hopes it will stop sending data to us.
+	 * 3) NONE - Nothing we can do.  We will simply drop any extra data
+	 *	that gets sent into us when the queue fills up.
+	 */
+	if (qleft < 256) {
+		/* HWFLOW */
+		if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+			if(!(ch->ch_flags & CH_RECEIVER_OFF)) {
+				ch->ch_bd->bd_ops->disable_receiver(ch);
+				ch->ch_flags |= (CH_RECEIVER_OFF);
+				DPR_READ(("Internal queue hit hilevel mark (%d)! Turning off interrupts.\n",
+					qleft));
+			}
+		}
+		/* SWFLOW */
+		else if (ch->ch_c_iflag & IXOFF) {
+			if (ch->ch_stops_sent <= MAX_STOPS_SENT) {
+				ch->ch_bd->bd_ops->send_stop_character(ch);
+				ch->ch_stops_sent++;
+				DPR_READ(("Sending stop char!  Times sent: %x\n", ch->ch_stops_sent));
+			}
+		}
+		/* No FLOW */
+		else {
+			/* Empty... Can't do anything about the impending overflow... */
+		}
+	}
+
+	/*
+	 * Check to see if we should unenforce flow control because
+	 * ld (or user) finally read enuf data out of our queue.
+	 *
+	 * NOTE: This is done based on what the current flow control of the
+	 * port is set for.
+	 *
+	 * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt.
+	 *	This will cause the UART's FIFO to raise RTS back up,
+	 *	which will allow the other side to start sending data again.
+	 * 2) SWFLOW (IXOFF) - Send a start character to
+	 *	the other side, so it will start sending data to us again.
+	 * 3) NONE - Do nothing. Since we didn't do anything to turn off the
+	 *	other side, we don't need to do anything now.
+	 */
+	if (qleft > (RQUEUESIZE / 2)) {
+		/* HWFLOW */
+		if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+			if (ch->ch_flags & CH_RECEIVER_OFF) {
+				ch->ch_bd->bd_ops->enable_receiver(ch);
+				ch->ch_flags &= ~(CH_RECEIVER_OFF);
+				DPR_READ(("Internal queue hit lowlevel mark (%d)! Turning on interrupts.\n",
+					qleft));
+			}
+		}
+		/* SWFLOW */
+		else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) {
+			ch->ch_stops_sent = 0;
+			ch->ch_bd->bd_ops->send_start_character(ch);
+			DPR_READ(("Sending start char!\n"));
+		}
+		/* No FLOW */
+		else {
+			/* Nothing needed. */
+		}
+	}
+}
+
+
+void dgnc_wakeup_writes(struct channel_t *ch)
+{
+	int qlen = 0;
+	ulong lock_flags;
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/*
+	 * If channel now has space, wake up anyone waiting on the condition.
+	 */
+	if ((qlen = ch->ch_w_head - ch->ch_w_tail) < 0)
+                qlen += WQUEUESIZE;
+
+	if (qlen >= (WQUEUESIZE - 256)) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return;
+	}
+
+	if (ch->ch_tun.un_flags & UN_ISOPEN) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+                if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                        ch->ch_tun.un_tty->ldisc->ops->write_wakeup)
+                {
+                        DGNC_UNLOCK(ch->ch_lock, lock_flags);
+                        (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty);
+                        DGNC_LOCK(ch->ch_lock, lock_flags);
+                }
+#else
+		if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+			ch->ch_tun.un_tty->ldisc.ops->write_wakeup)
+		{
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			(ch->ch_tun.un_tty->ldisc.ops->write_wakeup)(ch->ch_tun.un_tty);
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+		}
+#endif
+
+		wake_up_interruptible(&ch->ch_tun.un_tty->write_wait);
+
+		/*
+		 * If unit is set to wait until empty, check to make sure
+		 * the queue AND FIFO are both empty.
+		 */
+		if (ch->ch_tun.un_flags & UN_EMPTY) {
+			if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
+				ch->ch_tun.un_flags &= ~(UN_EMPTY);
+
+				/*
+				 * If RTS Toggle mode is on, whenever
+				 * the queue and UART is empty, keep RTS low.
+				 */
+				if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+					ch->ch_mostat &= ~(UART_MCR_RTS);
+					ch->ch_bd->bd_ops->assert_modem_signals(ch);
+				}
+
+				/*
+				 * If DTR Toggle mode is on, whenever
+				 * the queue and UART is empty, keep DTR low.
+				 */
+				if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+					ch->ch_mostat &= ~(UART_MCR_DTR);
+					ch->ch_bd->bd_ops->assert_modem_signals(ch);
+				}
+			}
+		}
+
+		wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+	}
+
+	if (ch->ch_pun.un_flags & UN_ISOPEN) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+                if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                        ch->ch_pun.un_tty->ldisc->ops->write_wakeup)
+                {
+                        DGNC_UNLOCK(ch->ch_lock, lock_flags);
+                        (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty);
+                        DGNC_LOCK(ch->ch_lock, lock_flags);
+                }
+#else
+		if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+			ch->ch_pun.un_tty->ldisc.ops->write_wakeup)
+		{
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			(ch->ch_pun.un_tty->ldisc.ops->write_wakeup)(ch->ch_pun.un_tty);
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+		}
+#endif
+
+		wake_up_interruptible(&ch->ch_pun.un_tty->write_wait);
+
+		/*
+		 * If unit is set to wait until empty, check to make sure
+		 * the queue AND FIFO are both empty.
+		 */
+		if (ch->ch_pun.un_flags & UN_EMPTY) {
+			if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
+				ch->ch_pun.un_flags &= ~(UN_EMPTY);
+			}
+		}
+
+		wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+
+/************************************************************************
+ *      
+ * TTY Entry points and helper functions
+ *              
+ ************************************************************************/
+
+/*
+ * dgnc_tty_open()
+ *
+ */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file)
+{
+	struct board_t	*brd;
+	struct channel_t *ch;
+	struct un_t	*un;
+	uint		major = 0;
+	uint		minor = 0;
+	int		rc = 0;
+	ulong		lock_flags;
+
+	rc = 0;
+
+	major = MAJOR(tty_devnum(tty));
+	minor = MINOR(tty_devnum(tty));
+
+	if (major > 255) {
+		return -ENXIO;
+	}
+
+	/* Get board pointer from our array of majors we have allocated */
+	brd = dgnc_BoardsByMajor[major];
+	if (!brd) {
+		return -ENXIO;
+	}
+
+	/*
+	 * If board is not yet up to a state of READY, go to
+	 * sleep waiting for it to happen or they cancel the open.
+	 */
+	rc = wait_event_interruptible(brd->state_wait,
+		(brd->state & BOARD_READY));
+
+	if (rc) {
+		return rc;
+	}
+
+	DGNC_LOCK(brd->bd_lock, lock_flags);
+
+	/* If opened device is greater than our number of ports, bail. */
+	if (PORT_NUM(minor) > brd->nasync) {
+		DGNC_UNLOCK(brd->bd_lock, lock_flags);
+		return -ENXIO;
+	}
+
+	ch = brd->channels[PORT_NUM(minor)];
+	if (!ch) {
+		DGNC_UNLOCK(brd->bd_lock, lock_flags);
+		return -ENXIO;
+	}
+
+	/* Drop board lock */
+	DGNC_UNLOCK(brd->bd_lock, lock_flags);
+
+	/* Grab channel lock */
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* Figure out our type */
+	if (!IS_PRINT(minor)) {
+		un = &brd->channels[PORT_NUM(minor)]->ch_tun;
+		un->un_type = DGNC_SERIAL;
+	}
+	else if (IS_PRINT(minor)) {
+		un = &brd->channels[PORT_NUM(minor)]->ch_pun;
+		un->un_type = DGNC_PRINT;
+	}
+	else {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		DPR_OPEN(("%d Unknown TYPE!\n", __LINE__));
+		return -ENXIO;
+	}
+
+	/*
+	 * If the port is still in a previous open, and in a state
+	 * where we simply cannot safely keep going, wait until the
+	 * state clears.
+	 */
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0));
+
+	/* If ret is non-zero, user ctrl-c'ed us */
+	if (rc) {
+		DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
+		return -EINTR;
+	}
+
+	/*
+	 * If either unit is in the middle of the fragile part of close,
+	 * we just cannot touch the channel safely.
+	 * Go to sleep, knowing that when the channel can be
+	 * touched safely, the close routine will signal the
+	 * ch_flags_wait to wake us back up.
+	 */
+	rc = wait_event_interruptible(ch->ch_flags_wait,
+		(((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0));
+
+	/* If ret is non-zero, user ctrl-c'ed us */
+	if (rc) {
+		DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
+		return -EINTR;
+	}
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+
+	/* Store our unit into driver_data, so we always have it available. */
+	tty->driver_data = un;
+
+	DPR_OPEN(("Open called. MAJOR: %d MINOR:%d PORT_NUM: %x unit: %p NAME: %s\n",
+		MAJOR(tty_devnum(tty)), MINOR(tty_devnum(tty)), PORT_NUM(minor), un, brd->name));
+
+	DPR_OPEN(("%d: tflag=%x  pflag=%x\n", __LINE__, ch->ch_tun.un_flags, ch->ch_pun.un_flags));
+
+	/*
+	 * Initialize tty's
+	 */
+	if (!(un->un_flags & UN_ISOPEN)) {
+		/* Store important variables. */
+		un->un_tty     = tty;
+
+		/* Maybe do something here to the TTY struct as well? */
+	}
+
+
+	/*
+	 * Allocate channel buffers for read/write/error.
+	 * Set flag, so we don't get trounced on.
+	 */
+	ch->ch_flags |= (CH_OPENING);
+
+	/* Drop locks, as malloc with GFP_KERNEL can sleep */
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	if (!ch->ch_rqueue)
+		ch->ch_rqueue = dgnc_driver_kzmalloc(RQUEUESIZE, GFP_KERNEL);
+	if (!ch->ch_equeue)
+		ch->ch_equeue = dgnc_driver_kzmalloc(EQUEUESIZE, GFP_KERNEL);
+	if (!ch->ch_wqueue)
+		ch->ch_wqueue = dgnc_driver_kzmalloc(WQUEUESIZE, GFP_KERNEL);
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags &= ~(CH_OPENING);
+	wake_up_interruptible(&ch->ch_flags_wait);
+
+	/*
+	 * Initialize if neither terminal or printer is open.
+	 */
+	if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) {
+
+		DPR_OPEN(("dgnc_open: initializing channel in open...\n"));
+
+		/*
+		 * Flush input queues.
+		 */
+		ch->ch_r_head = ch->ch_r_tail = 0;
+		ch->ch_e_head = ch->ch_e_tail = 0;
+		ch->ch_w_head = ch->ch_w_tail = 0;
+
+		brd->bd_ops->flush_uart_write(ch);
+		brd->bd_ops->flush_uart_read(ch);
+
+		ch->ch_flags = 0;
+		ch->ch_cached_lsr = 0;
+		ch->ch_stop_sending_break = 0;
+		ch->ch_stops_sent = 0;
+
+		ch->ch_c_cflag   = tty->termios->c_cflag;
+		ch->ch_c_iflag   = tty->termios->c_iflag;
+		ch->ch_c_oflag   = tty->termios->c_oflag;
+		ch->ch_c_lflag   = tty->termios->c_lflag;
+		ch->ch_startc = tty->termios->c_cc[VSTART];
+		ch->ch_stopc  = tty->termios->c_cc[VSTOP];
+
+		/*
+		 * Bring up RTS and DTR...
+		 * Also handle RTS or DTR toggle if set.
+		 */
+		if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+			ch->ch_mostat |= (UART_MCR_RTS);
+		if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+			ch->ch_mostat |= (UART_MCR_DTR);
+
+		/* Tell UART to init itself */
+		brd->bd_ops->uart_init(ch);
+	}
+
+	/*
+	 * Run param in case we changed anything
+	 */
+	brd->bd_ops->param(tty);
+
+	dgnc_carrier(ch);
+
+	/*                              
+	 * follow protocol for opening port
+	 */
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	rc = dgnc_block_til_ready(tty, file, ch);
+
+	if (rc) {
+		DPR_OPEN(("dgnc_tty_open returning after dgnc_block_til_ready "
+			"with %d\n", rc));
+	}
+
+	/* No going back now, increment our unit and channel counters */
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	ch->ch_open_count++;
+	un->un_open_count++;
+	un->un_flags |= (UN_ISOPEN);
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_OPEN(("dgnc_tty_open finished\n"));
+	return (rc);
+}
+
+
+/*   
+ * dgnc_block_til_ready()
+ *
+ * Wait for DCD, if needed.
+ */
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch)
+{ 
+	int retval = 0;
+	struct un_t *un = NULL;
+	ulong   lock_flags;
+	uint	old_flags = 0;
+	int	sleep_on_un_flags = 0;
+
+	if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+		return (-ENXIO);
+	}
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC) {
+		return (-ENXIO);
+	}
+
+	DPR_OPEN(("dgnc_block_til_ready - before block.\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_wopen++;
+
+	/* Loop forever */
+	while (1) {
+
+		sleep_on_un_flags = 0;
+
+		/*
+		 * If board has failed somehow during our sleep, bail with error.
+		 */
+		if (ch->ch_bd->state == BOARD_FAILED) {
+			retval = -ENXIO;
+			break;
+		}
+
+		/* If tty was hung up, break out of loop and set error. */
+		if (tty_hung_up_p(file)) {
+			retval = -EAGAIN;
+			break;
+		}
+
+		/*
+		 * If either unit is in the middle of the fragile part of close,
+		 * we just cannot touch the channel safely.
+		 * Go back to sleep, knowing that when the channel can be
+		 * touched safely, the close routine will signal the 
+		 * ch_wait_flags to wake us back up.
+		 */
+		if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) {
+
+			/*
+			 * Our conditions to leave cleanly and happily:
+			 * 1) NONBLOCKING on the tty is set.
+			 * 2) CLOCAL is set.
+			 * 3) DCD (fake or real) is active.
+			 */
+
+			if (file->f_flags & O_NONBLOCK) {
+				break;
+			}
+
+			if (tty->flags & (1 << TTY_IO_ERROR)) {
+				retval = -EIO;
+				break;
+			}
+
+			if (ch->ch_flags & CH_CD) {
+				DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
+				break;
+			}
+
+			if (ch->ch_flags & CH_FCAR) {
+				DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
+				break;
+			}
+		}
+		else {
+			sleep_on_un_flags = 1;
+		}
+
+		/*
+		 * If there is a signal pending, the user probably
+		 * interrupted (ctrl-c) us.
+		 * Leave loop with error set.
+		 */
+		if (signal_pending(current)) {
+			DPR_OPEN(("%d: signal pending...\n", __LINE__));
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+		DPR_OPEN(("dgnc_block_til_ready - blocking.\n"));
+
+		/*
+		 * Store the flags before we let go of channel lock
+		 */
+		if (sleep_on_un_flags)
+			old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags;
+		else
+			old_flags = ch->ch_flags;
+
+		/*
+		 * Let go of channel lock before calling schedule.
+		 * Our poller will get any FEP events and wake us up when DCD
+		 * eventually goes active.
+		 */
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_OPEN(("Going to sleep on %s flags...\n",
+			(sleep_on_un_flags ? "un" : "ch")));
+
+		/*
+		 * Wait for something in the flags to change from the current value.
+		 */
+		if (sleep_on_un_flags) {
+			retval = wait_event_interruptible(un->un_flags_wait,
+				(old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags)));
+		}
+		else {
+			retval = wait_event_interruptible(ch->ch_flags_wait,
+				(old_flags != ch->ch_flags));
+		}
+
+		DPR_OPEN(("After sleep... retval: %x\n", retval));
+
+		/*
+		 * We got woken up for some reason.
+		 * Before looping around, grab our channel lock.
+		 */
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+	}
+
+	ch->ch_wopen--;
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_OPEN(("dgnc_block_til_ready - after blocking.\n"));
+
+	if (retval) {
+		DPR_OPEN(("dgnc_block_til_ready - done. error. retval: %x\n", retval));
+		return(retval);
+	}
+
+	DPR_OPEN(("dgnc_block_til_ready - done no error. jiffies: %lu\n", jiffies));
+
+	return(0);
+}
+
+
+/*
+ * dgnc_tty_hangup()
+ *
+ * Hangup the port.  Like a close, but don't wait for output to drain.
+ */     
+static void dgnc_tty_hangup(struct tty_struct *tty)
+{
+	struct un_t	*un;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+
+	DPR_CLOSE(("dgnc_hangup called. ch->ch_open_count: %d un->un_open_count: %d\n",
+		un->un_ch->ch_open_count, un->un_open_count));
+
+	/* flush the transmit queues */
+	dgnc_tty_flush_buffer(tty);
+
+	DPR_CLOSE(("dgnc_hangup finished. ch->ch_open_count: %d un->un_open_count: %d\n",
+		un->un_ch->ch_open_count, un->un_open_count));
+}
+
+
+/*
+ * dgnc_tty_close()
+ *
+ */
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file)
+{
+	struct ktermios *ts;
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong lock_flags;
+	int rc = 0;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	ts = tty->termios;
+
+	DPR_CLOSE(("Close called\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/*
+	 * Determine if this is the last close or not - and if we agree about
+	 * which type of close it is with the Line Discipline
+	 */
+	if ((tty->count == 1) && (un->un_open_count != 1)) {
+		/*
+		 * Uh, oh.  tty->count is 1, which means that the tty
+		 * structure will be freed.  un_open_count should always
+		 * be one in these conditions.  If it's greater than
+		 * one, we've got real problems, since it means the
+		 * serial port won't be shutdown.
+		 */
+		APR(("tty->count is 1, un open count is %d\n", un->un_open_count));
+		un->un_open_count = 1;
+	}  
+
+	if (--un->un_open_count < 0) {
+		APR(("bad serial port open count of %d\n", un->un_open_count));
+		un->un_open_count = 0;
+	}
+
+	ch->ch_open_count--;
+
+	if (ch->ch_open_count && un->un_open_count) {
+		DPR_CLOSE(("dgnc_tty_close: not last close ch: %d un:%d\n",
+			ch->ch_open_count, un->un_open_count));
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+                return;
+        }
+
+	/* OK, its the last close on the unit */
+	DPR_CLOSE(("dgnc_tty_close - last close on unit procedures\n"));
+
+	un->un_flags |= UN_CLOSING;
+
+	tty->closing = 1;
+
+
+	/*
+	 * Only officially close channel if count is 0 and
+         * DIGI_PRINTER bit is not set.
+	 */
+	if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) {
+
+		ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI);
+
+		/*
+		 * turn off print device when closing print device.
+		 */
+		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON) ) {
+			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+				(int) ch->ch_digi.digi_offlen);
+			ch->ch_flags &= ~CH_PRON;
+		}
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		/* wait for output to drain */
+		/* This will also return if we take an interrupt */
+
+		DPR_CLOSE(("Calling wait_for_drain\n"));
+		rc = bd->bd_ops->drain(tty, 0);
+
+		DPR_CLOSE(("After calling wait_for_drain\n"));
+
+		if (rc) {
+			DPR_BASIC(("dgnc_tty_close - bad return: %d ", rc));
+		}
+
+		dgnc_tty_flush_buffer(tty);
+		tty_ldisc_flush(tty);
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		tty->closing = 0;
+
+		/*
+		 * If we have HUPCL set, lower DTR and RTS
+		 */
+		if (ch->ch_c_cflag & HUPCL) {
+			DPR_CLOSE(("Close. HUPCL set, dropping DTR/RTS\n"));
+
+			/* Drop RTS/DTR */
+			ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS);
+			bd->bd_ops->assert_modem_signals(ch);
+
+			/*
+			 * Go to sleep to ensure RTS/DTR 
+			 * have been dropped for modems to see it.
+			 */
+			if (ch->ch_close_delay) {
+				DPR_CLOSE(("Close. Sleeping for RTS/DTR drop\n"));
+
+				DGNC_UNLOCK(ch->ch_lock, lock_flags);
+				dgnc_ms_sleep(ch->ch_close_delay);
+				DGNC_LOCK(ch->ch_lock, lock_flags);
+
+				DPR_CLOSE(("Close. After sleeping for RTS/DTR drop\n"));
+			}
+		}
+
+		ch->ch_old_baud = 0;
+
+		/* Turn off UART interrupts for this port */
+		ch->ch_bd->bd_ops->uart_off(ch);
+	}
+	else {
+		/*
+		 * turn off print device when closing print device.
+		 */
+		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON) ) {
+			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+				(int) ch->ch_digi.digi_offlen);
+			ch->ch_flags &= ~CH_PRON;
+		}
+	}
+
+	un->un_tty = NULL;
+	un->un_flags &= ~(UN_ISOPEN | UN_CLOSING);
+
+	DPR_CLOSE(("Close. Doing wakeups\n"));
+	wake_up_interruptible(&ch->ch_flags_wait);
+	wake_up_interruptible(&un->un_flags_wait);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+        DPR_BASIC(("dgnc_tty_close - complete\n"));
+}
+
+
+/*
+ * dgnc_tty_chars_in_buffer()
+ *
+ * Return number of characters that have not been transmitted yet.
+ *
+ * This routine is used by the line discipline to determine if there
+ * is data waiting to be transmitted/drained/flushed or not.
+ */
+static int dgnc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct channel_t *ch = NULL;
+	struct un_t *un = NULL;
+	ushort thead;
+	ushort ttail;
+	uint tmask;
+	uint chars = 0;
+	ulong   lock_flags = 0;
+
+	if (tty == NULL)
+		return(0);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	tmask = WQUEUEMASK;
+	thead = ch->ch_w_head & tmask;
+	ttail = ch->ch_w_tail & tmask;
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	if (ttail == thead) {
+		chars = 0;
+	} else {
+		if (thead >= ttail)
+			chars = thead - ttail;
+		else
+			chars = thead - ttail + WQUEUESIZE;
+	}
+
+ 	DPR_WRITE(("dgnc_tty_chars_in_buffer. Port: %x - %d (head: %d tail: %d)\n", 
+		ch->ch_portnum, chars, thead, ttail));
+
+        return(chars);
+}
+
+
+/*       
+ * dgnc_maxcps_room
+ *
+ * Reduces bytes_available to the max number of characters
+ * that can be sent currently given the maxcps value, and
+ * returns the new bytes_available.  This only affects printer
+ * output.
+ */                     
+static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available)
+{
+	struct channel_t *ch = NULL;
+	struct un_t *un = NULL;
+
+	if (!tty)
+		return (bytes_available);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (bytes_available);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (bytes_available);
+
+	/*
+	 * If its not the Transparent print device, return
+	 * the full data amount.
+	 */
+	if (un->un_type != DGNC_PRINT)
+		return (bytes_available);
+
+	if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0 ) {
+		int cps_limit = 0;
+		unsigned long current_time = jiffies;
+		unsigned long buffer_time = current_time +
+			(HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps;
+
+		if (ch->ch_cpstime < current_time) {
+			/* buffer is empty */
+			ch->ch_cpstime = current_time;            /* reset ch_cpstime */
+			cps_limit = ch->ch_digi.digi_bufsize;
+		}
+		else if (ch->ch_cpstime < buffer_time) {
+			/* still room in the buffer */
+			cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ;
+		}
+		else {
+			/* no room in the buffer */
+			cps_limit = 0; 
+		}
+
+		bytes_available = min(cps_limit, bytes_available);
+	}
+
+	return (bytes_available);
+}
+
+
+/*
+ * dgnc_tty_write_room()
+ *
+ * Return space available in Tx buffer
+ */        
+static int dgnc_tty_write_room(struct tty_struct *tty)
+{
+	struct channel_t *ch = NULL;
+	struct un_t *un = NULL;
+	ushort head;
+	ushort tail;
+	ushort tmask;
+	int ret = 0;
+	ulong   lock_flags = 0;
+
+	if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+		return(0);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (0);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (0);
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	tmask = WQUEUEMASK;
+	head = (ch->ch_w_head) & tmask;
+	tail = (ch->ch_w_tail) & tmask;
+
+	if ((ret = tail - head - 1) < 0)
+		ret += WQUEUESIZE;
+
+	/* Limit printer to maxcps */
+	ret = dgnc_maxcps_room(tty, ret);
+
+	/*
+	 * If we are printer device, leave space for 
+	 * possibly both the on and off strings.
+	 */
+	if (un->un_type == DGNC_PRINT) {
+		if (!(ch->ch_flags & CH_PRON))
+			ret -= ch->ch_digi.digi_onlen;
+		ret -= ch->ch_digi.digi_offlen;
+	}
+	else {
+		if (ch->ch_flags & CH_PRON)
+			ret -= ch->ch_digi.digi_offlen;
+	}
+
+	if (ret < 0)
+		ret = 0;
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ 
+	DPR_WRITE(("dgnc_tty_write_room - %d tail: %d head: %d\n", ret, tail, head));
+
+        return(ret);
+}
+
+
+/*
+ * dgnc_tty_put_char()
+ *
+ * Put a character into ch->ch_buf
+ *                              
+ *      - used by the line discipline for OPOST processing
+ */
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c)
+{
+	/*
+	 * Simply call tty_write.
+	 */
+	DPR_WRITE(("dgnc_tty_put_char called\n"));
+	dgnc_tty_write(tty, &c, 1);
+	return 1;
+}
+
+
+/*
+ * dgnc_tty_write()
+ *
+ * Take data from the user or kernel and send it out to the FEP.
+ * In here exists all the Transparent Print magic as well.
+ */
+static int dgnc_tty_write(struct tty_struct *tty,
+		const unsigned char *buf, int count)
+{
+	struct channel_t *ch = NULL;
+	struct un_t *un = NULL;
+	int bufcount = 0, n = 0;
+	int orig_count = 0;
+	ulong lock_flags;
+	ushort head;
+	ushort tail;
+	ushort tmask;
+	uint remain;
+	int from_user = 0;
+
+	if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+		return(0);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return(0);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return(0);
+
+	if (!count)
+		return(0);
+
+	DPR_WRITE(("dgnc_tty_write: Port: %x tty=%p user=%d len=%d\n",
+		ch->ch_portnum, tty, from_user, count));
+
+	/*
+	 * Store original amount of characters passed in.
+	 * This helps to figure out if we should ask the FEP
+	 * to send us an event when it has more space available.
+	 */
+	orig_count = count;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* Get our space available for the channel from the board */
+	tmask = WQUEUEMASK;
+	head = (ch->ch_w_head) & tmask;
+	tail = (ch->ch_w_tail) & tmask;
+
+	if ((bufcount = tail - head - 1) < 0)
+		bufcount += WQUEUESIZE;
+
+	DPR_WRITE(("%d: bufcount: %x count: %x tail: %x head: %x tmask: %x\n",
+		__LINE__, bufcount, count, tail, head, tmask));
+
+	/*
+	 * Limit printer output to maxcps overall, with bursts allowed
+	 * up to bufsize characters.
+	 */
+	bufcount = dgnc_maxcps_room(tty, bufcount);
+
+	/*
+	 * Take minimum of what the user wants to send, and the
+	 * space available in the FEP buffer.
+	 */
+	count = min(count, bufcount);
+
+	/*
+	 * Bail if no space left.
+	 */
+	if (count <= 0) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(0);
+	}
+
+	/*
+	 * Output the printer ON string, if we are in terminal mode, but
+	 * need to be in printer mode.
+	 */
+	if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) {
+		dgnc_wmove(ch, ch->ch_digi.digi_onstr,
+		    (int) ch->ch_digi.digi_onlen);
+		head = (ch->ch_w_head) & tmask;
+		ch->ch_flags |= CH_PRON;
+	}
+
+	/*
+	 * On the other hand, output the printer OFF string, if we are
+	 * currently in printer mode, but need to output to the terminal.
+	 */
+	if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+		dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+			(int) ch->ch_digi.digi_offlen);
+		head = (ch->ch_w_head) & tmask;
+		ch->ch_flags &= ~CH_PRON;
+	}
+
+	/*
+	 * If there is nothing left to copy, or I can't handle any more data, leave.
+	 */     
+	if (count <= 0) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(0);
+	}
+
+	if (from_user) {
+
+		count = min(count, WRITEBUFLEN);
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/*
+		 * If data is coming from user space, copy it into a temporary
+		 * buffer so we don't get swapped out while doing the copy to
+		 * the board.
+		 */
+		/* we're allowed to block if it's from_user */
+		if (down_interruptible(&dgnc_TmpWriteSem)) {
+			return (-EINTR);
+		}
+
+		/*
+		 * copy_from_user() returns the number
+		 * of bytes that could *NOT* be copied.
+		 */
+		count -= copy_from_user(dgnc_TmpWriteBuf, (const uchar __user *) buf, count);
+
+		if (!count) {
+			up(&dgnc_TmpWriteSem);
+			return(-EFAULT);
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		buf = dgnc_TmpWriteBuf;
+
+	}
+
+	n = count;
+
+	/*
+	 * If the write wraps over the top of the circular buffer,
+	 * move the portion up to the wrap point, and reset the
+	 * pointers to the bottom.
+	 */
+	remain = WQUEUESIZE - head;
+
+	if (n >= remain) {
+		n -= remain;
+		memcpy(ch->ch_wqueue + head, buf, remain);
+		dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
+		head = 0;
+		buf += remain;
+	}
+
+	if (n > 0) {
+		/*
+		 * Move rest of data.
+		 */
+		remain = n;
+		memcpy(ch->ch_wqueue + head, buf, remain);
+		dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
+		head += remain;
+	}
+
+	if (count) {
+		head &= tmask;
+		ch->ch_w_head = head;
+	}
+
+#if 0
+	/*
+	 * If this is the print device, and the
+	 * printer is still on, we need to turn it
+	 * off before going idle.
+	 */
+	if (count == orig_count) {
+		if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+			head &= tmask;
+			ch->ch_w_head = head;
+			dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+				(int) ch->ch_digi.digi_offlen);
+			head = (ch->ch_w_head) & tmask;
+			ch->ch_flags &= ~CH_PRON;
+		}
+	}
+#endif
+
+	/* Update printer buffer empty time. */
+	if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0)
+	    && (ch->ch_digi.digi_bufsize > 0)) {
+                ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps;
+	}
+
+	if (from_user) {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		up(&dgnc_TmpWriteSem);
+	} else {
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+	}
+
+	DPR_WRITE(("Write finished - Write %d bytes of %d.\n", count, orig_count));
+
+	if (count) {
+		/*
+		 * Channel lock is grabbed and then released
+		 * inside this routine.
+		 */
+		ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch);
+	}
+
+	return (count);
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+static int dgnc_tty_tiocmget(struct tty_struct *tty)
+#else
+static int dgnc_tty_tiocmget(struct tty_struct *tty, struct file *file)
+#endif
+{
+	struct channel_t *ch;
+	struct un_t *un;
+	int result = -EIO;
+	uchar mstat = 0;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return result;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return result;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return result;
+
+	DPR_IOCTL(("dgnc_tty_tiocmget start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	mstat = (ch->ch_mostat | ch->ch_mistat);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	result = 0;
+
+	if (mstat & UART_MCR_DTR)
+		result |= TIOCM_DTR;
+	if (mstat & UART_MCR_RTS)
+		result |= TIOCM_RTS;
+	if (mstat & UART_MSR_CTS)
+		result |= TIOCM_CTS;
+	if (mstat & UART_MSR_DSR)
+		result |= TIOCM_DSR;
+	if (mstat & UART_MSR_RI)
+		result |= TIOCM_RI;
+	if (mstat & UART_MSR_DCD)
+		result |= TIOCM_CD;
+
+	DPR_IOCTL(("dgnc_tty_tiocmget finish\n"));
+
+	return result;
+}
+
+
+/*
+ * dgnc_tty_tiocmset()
+ *
+ * Set modem signals, called by ld.
+ */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+static int dgnc_tty_tiocmset(struct tty_struct *tty,
+                unsigned int set, unsigned int clear)
+#else
+static int dgnc_tty_tiocmset(struct tty_struct *tty, struct file *file,
+		unsigned int set, unsigned int clear)
+#endif
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	int ret = -EIO;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return ret;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return ret;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return ret;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return ret;
+
+	DPR_IOCTL(("dgnc_tty_tiocmset start\n"));
+
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	if (set & TIOCM_RTS) {
+		ch->ch_mostat |= UART_MCR_RTS;
+        }         
+
+	if (set & TIOCM_DTR) {
+		ch->ch_mostat |= UART_MCR_DTR;
+        }         
+
+	if (clear & TIOCM_RTS) {
+		ch->ch_mostat &= ~(UART_MCR_RTS);
+        }
+
+	if (clear & TIOCM_DTR) {
+		ch->ch_mostat &= ~(UART_MCR_DTR);
+        }
+
+	ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_tiocmset finish\n"));
+
+	return (0);
+}
+
+
+/*
+ * dgnc_tty_send_break()
+ *
+ * Send a Break, called by ld.
+ */
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	int ret = -EIO;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return ret;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return ret;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return ret;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return ret;
+
+	switch (msec) {
+	case -1:
+		msec = 0xFFFF;
+		break;
+	case 0:
+		msec = 0;
+		break;
+	default:
+		break;
+	}
+
+	DPR_IOCTL(("dgnc_tty_send_break start 1.  %lx\n", jiffies));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_bd->bd_ops->send_break(ch, msec);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_send_break finish\n"));
+
+	return (0);
+
+}
+
+
+/*
+ * dgnc_tty_wait_until_sent()
+ *
+ * wait until data has been transmitted, called by ld.
+ */
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	int rc;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	rc = bd->bd_ops->drain(tty, 0);
+	if (rc) {
+		DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+		return;
+	}
+	return;
+}       
+
+
+/*
+ * dgnc_send_xchar()
+ *
+ * send a high priority character, called by ld.
+ */
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char c)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DPR_IOCTL(("dgnc_tty_send_xchar start\n"));
+	printk("dgnc_tty_send_xchar start\n");
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	bd->bd_ops->send_immediate_char(ch, c);
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_send_xchar finish\n"));
+	printk("dgnc_tty_send_xchar finish\n");
+	return;
+}       
+
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static inline int dgnc_get_mstat(struct channel_t *ch)
+{
+	unsigned char mstat;
+	int result = -EIO;
+	ulong   lock_flags;
+
+	DPR_IOCTL(("dgnc_getmstat start\n"));
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return(-ENXIO);
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	mstat = (ch->ch_mostat | ch->ch_mistat);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	result = 0;
+
+	if (mstat & UART_MCR_DTR)
+		result |= TIOCM_DTR;
+	if (mstat & UART_MCR_RTS)
+		result |= TIOCM_RTS;
+	if (mstat & UART_MSR_CTS)
+		result |= TIOCM_CTS;
+	if (mstat & UART_MSR_DSR)
+		result |= TIOCM_DSR;
+	if (mstat & UART_MSR_RI)
+		result |= TIOCM_RI;
+	if (mstat & UART_MSR_DCD)
+		result |= TIOCM_CD;
+
+	DPR_IOCTL(("dgnc_getmstat finish\n"));
+
+	return(result);
+}
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int  __user *value)
+{
+	int result;
+	int rc;
+
+	DPR_IOCTL(("dgnc_get_modem_info start\n"));
+
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return(-ENXIO);
+
+	result = dgnc_get_mstat(ch);
+
+	if (result < 0)
+		return (-ENXIO);
+
+	rc = put_user(result, value);
+
+	DPR_IOCTL(("dgnc_get_modem_info finish\n"));
+	return(rc);
+}
+
+
+/*
+ * dgnc_set_modem_info()
+ *
+ * Set modem signals, called by ld.
+ */
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	int ret = -ENXIO;
+	unsigned int arg = 0;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return ret;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return ret;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return ret;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return ret;
+
+	ret = 0;
+
+	DPR_IOCTL(("dgnc_set_modem_info() start\n"));
+
+	ret = get_user(arg, value);
+	if (ret)
+		return(ret);
+
+	switch (command) {
+	case TIOCMBIS:
+		if (arg & TIOCM_RTS) {
+			ch->ch_mostat |= UART_MCR_RTS;
+        	}
+
+		if (arg & TIOCM_DTR) {
+			ch->ch_mostat |= UART_MCR_DTR;
+        	}
+
+		break;
+
+	case TIOCMBIC:
+		if (arg & TIOCM_RTS) {
+			ch->ch_mostat &= ~(UART_MCR_RTS);
+        	}
+
+		if (arg & TIOCM_DTR) {
+			ch->ch_mostat &= ~(UART_MCR_DTR);
+        	}
+
+		break;
+
+        case TIOCMSET:
+
+		if (arg & TIOCM_RTS) {
+			ch->ch_mostat |= UART_MCR_RTS;
+        	}
+		else {
+			ch->ch_mostat &= ~(UART_MCR_RTS);
+		}
+
+		if (arg & TIOCM_DTR) {
+			ch->ch_mostat |= UART_MCR_DTR;
+        	}
+		else {
+			ch->ch_mostat &= ~(UART_MCR_DTR);
+		}
+
+		break;
+
+	default:
+		return(-EINVAL);
+	}
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_set_modem_info finish\n"));
+
+	return (0);
+}
+
+
+/*
+ * dgnc_tty_digigeta() 
+ *
+ * Ioctl to get the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo)
+{
+	struct channel_t *ch;
+	struct un_t *un;
+	struct digi_t tmp;
+	ulong   lock_flags;
+
+	if (!retinfo)
+		return (-EFAULT);
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return (-EFAULT);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (-EFAULT);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (-EFAULT);
+
+	memset(&tmp, 0, sizeof(tmp));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+	memcpy(&tmp, &ch->ch_digi, sizeof(tmp));
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+		return (-EFAULT);
+
+	return (0);
+}
+
+
+/*
+ * dgnc_tty_digiseta() 
+ *
+ * Ioctl to set the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	struct digi_t new_digi;
+	ulong lock_flags;
+
+	DPR_IOCTL(("DIGI_SETA start\n"));
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return (-EFAULT);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (-EFAULT);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (-EFAULT);
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (-EFAULT);
+
+        if (copy_from_user(&new_digi, new_info, sizeof(struct digi_t))) {
+		DPR_IOCTL(("DIGI_SETA failed copy_from_user\n"));
+                return(-EFAULT);
+	}
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/*
+	 * Handle transistions to and from RTS Toggle.
+	 */
+	if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE))
+		ch->ch_mostat &= ~(UART_MCR_RTS);
+	if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE))
+		ch->ch_mostat |= (UART_MCR_RTS);
+
+	/*
+	 * Handle transistions to and from DTR Toggle.
+	 */
+	if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE))
+		ch->ch_mostat &= ~(UART_MCR_DTR);
+	if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE))
+		ch->ch_mostat |= (UART_MCR_DTR);
+
+	memcpy(&ch->ch_digi, &new_digi, sizeof(struct digi_t));
+
+	if (ch->ch_digi.digi_maxcps < 1) 
+		ch->ch_digi.digi_maxcps = 1;
+
+	if (ch->ch_digi.digi_maxcps > 10000) 
+		ch->ch_digi.digi_maxcps = 10000;
+
+	if (ch->ch_digi.digi_bufsize < 10)
+		ch->ch_digi.digi_bufsize = 10;
+
+	if (ch->ch_digi.digi_maxchar < 1)
+		ch->ch_digi.digi_maxchar = 1;
+
+	if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+		ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+	if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+		ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+	if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+		ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+	ch->ch_bd->bd_ops->param(tty);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("DIGI_SETA finish\n"));
+
+	return(0);
+}
+
+
+/*
+ * dgnc_set_termios()
+ */
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	unsigned long lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_c_cflag   = tty->termios->c_cflag;
+	ch->ch_c_iflag   = tty->termios->c_iflag;
+	ch->ch_c_oflag   = tty->termios->c_oflag;
+	ch->ch_c_lflag   = tty->termios->c_lflag;
+	ch->ch_startc = tty->termios->c_cc[VSTART];
+	ch->ch_stopc  = tty->termios->c_cc[VSTOP];
+
+	ch->ch_bd->bd_ops->param(tty);
+	dgnc_carrier(ch);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+static void dgnc_tty_throttle(struct tty_struct *tty)
+{
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong   lock_flags = 0;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	DPR_IOCTL(("dgnc_tty_throttle start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags |= (CH_FORCED_STOPI);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_throttle finish\n"));
+}
+
+
+static void dgnc_tty_unthrottle(struct tty_struct *tty)
+{
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong   lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	DPR_IOCTL(("dgnc_tty_unthrottle start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags &= ~(CH_FORCED_STOPI);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_unthrottle finish\n"));
+}
+
+
+static void dgnc_tty_start(struct tty_struct *tty)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DPR_IOCTL(("dgcn_tty_start start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags &= ~(CH_FORCED_STOP);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_start finish\n"));
+}
+
+
+static void dgnc_tty_stop(struct tty_struct *tty)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DPR_IOCTL(("dgnc_tty_stop start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags |= (CH_FORCED_STOP);
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_stop finish\n"));
+}
+
+
+/* 
+ * dgnc_tty_flush_chars()
+ *
+ * Flush the cook buffer
+ *
+ * Note to self, and any other poor souls who venture here:
+ *
+ * flush in this case DOES NOT mean dispose of the data.
+ * instead, it means "stop buffering and send it if you
+ * haven't already."  Just guess how I figured that out...   SRW 2-Jun-98
+ *
+ * It is also always called in interrupt context - JAR 8-Sept-99
+ */
+static void dgnc_tty_flush_chars(struct tty_struct *tty)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return;
+
+	DPR_IOCTL(("dgnc_tty_flush_chars start\n"));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	/* Do something maybe here */
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_flush_chars finish\n"));
+}
+
+
+
+/*
+ * dgnc_tty_flush_buffer()
+ *              
+ * Flush Tx buffer (make in == out)
+ */
+static void dgnc_tty_flush_buffer(struct tty_struct *tty)
+{
+	struct channel_t *ch;
+	struct un_t *un;
+	ulong lock_flags;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return;
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return;
+        
+        ch = un->un_ch;
+        if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+                return;
+
+	DPR_IOCTL(("dgnc_tty_flush_buffer on port: %d start\n", ch->ch_portnum));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	ch->ch_flags &= ~CH_STOP;
+
+	/* Flush our write queue */
+	ch->ch_w_head = ch->ch_w_tail;
+
+	/* Flush UARTs transmit FIFO */
+	ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+	if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+		ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+		wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+	}
+	if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+		ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+		wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_flush_buffer finish\n"));
+}
+
+
+
+/*****************************************************************************
+ *
+ * The IOCTL function and all of its helpers
+ *
+ *****************************************************************************/
+                        
+/*
+ * dgnc_tty_ioctl()
+ *
+ * The usual assortment of ioctl's
+ */
+static int dgnc_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd,
+		unsigned long arg)
+{
+	struct board_t *bd;
+	struct channel_t *ch;
+	struct un_t *un;
+	int rc;
+	ulong lock_flags;
+	void __user *uarg = (void __user *) arg;
+
+	if (!tty || tty->magic != TTY_MAGIC)
+		return (-ENODEV);
+
+	un = tty->driver_data;
+	if (!un || un->magic != DGNC_UNIT_MAGIC)
+		return (-ENODEV);
+
+	ch = un->un_ch;
+	if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+		return (-ENODEV);
+
+	bd = ch->ch_bd;
+	if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+		return (-ENODEV);
+
+	DPR_IOCTL(("dgnc_tty_ioctl start on port %d - cmd %s (%x), arg %lx\n", 
+		ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+	DGNC_LOCK(ch->ch_lock, lock_flags);
+
+	if (un->un_open_count <= 0) {
+		DPR_BASIC(("dgnc_tty_ioctl - unit not open.\n"));
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(-EIO);
+	}
+
+	switch (cmd) {
+
+	/* Here are all the standard ioctl's that we MUST implement */
+
+	case TCSBRK:
+		/*
+		 * TCSBRK is SVID version: non-zero arg --> no break  
+		 * this behaviour is exploited by tcdrain().
+		 *
+		 * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+		 * between 0.25 and 0.5 seconds so we'll ask for something
+		 * in the middle: 0.375 seconds.
+		 */
+		rc = tty_check_change(tty);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		if (rc) {
+			return(rc);
+		}
+
+		rc = ch->ch_bd->bd_ops->drain(tty, 0);
+
+		if (rc) {
+			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+			return(-EINTR);
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		if(((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) {
+			ch->ch_bd->bd_ops->send_break(ch, 250);
+		}
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n", 
+			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+		return(0);
+
+
+	case TCSBRKP:
+ 		/* support for POSIX tcsendbreak()
+		 * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+		 * between 0.25 and 0.5 seconds so we'll ask for something
+		 * in the middle: 0.375 seconds.
+		 */
+		rc = tty_check_change(tty);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		if (rc) {
+			return(rc);
+		}
+
+		rc = ch->ch_bd->bd_ops->drain(tty, 0);
+		if (rc) {
+			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+			return(-EINTR);
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		ch->ch_bd->bd_ops->send_break(ch, 250);
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n", 
+			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+		return(0);
+
+	case TIOCSBRK:
+		rc = tty_check_change(tty);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		if (rc) {
+			return(rc);
+		}
+
+		rc = ch->ch_bd->bd_ops->drain(tty, 0);
+		if (rc) {
+			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+			return(-EINTR);
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		ch->ch_bd->bd_ops->send_break(ch, 250);
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n", 
+			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+		return(0);
+
+	case TIOCCBRK:
+		/* Do Nothing */
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return 0;
+
+	case TIOCGSOFTCAR:
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
+		return(rc);
+
+	case TIOCSSOFTCAR:
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = get_user(arg, (unsigned long __user *) arg);
+		if (rc)
+			return(rc);
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+		ch->ch_bd->bd_ops->param(tty);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		return(0);
+                        
+	case TIOCMGET:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+                return(dgnc_get_modem_info(ch, uarg));
+
+	case TIOCMBIS:
+	case TIOCMBIC:
+	case TIOCMSET:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(dgnc_set_modem_info(tty, cmd, uarg));
+
+		/*
+		 * Here are any additional ioctl's that we want to implement
+		 */
+                        
+	case TCFLSH:  
+		/*
+		 * The linux tty driver doesn't have a flush
+		 * input routine for the driver, assuming all backed
+		 * up data is in the line disc. buffers.  However,
+		 * we all know that's not the case.  Here, we
+		 * act on the ioctl, but then lie and say we didn't
+		 * so the line discipline will process the flush
+		 * also.
+		 */   
+		rc = tty_check_change(tty);
+		if (rc) {
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			return(rc);
+		}
+
+		if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) {
+			ch->ch_r_head = ch->ch_r_tail;
+			ch->ch_bd->bd_ops->flush_uart_read(ch);
+			/* Force queue flow control to be released, if needed */
+			dgnc_check_queue_flow_control(ch);
+		}
+
+		if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) {
+			if (!(un->un_type == DGNC_PRINT)) {
+				ch->ch_w_head = ch->ch_w_tail;
+				ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+				if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+					ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+					wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+				}
+
+				if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+					ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+					wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+				}
+
+			}
+		}
+
+		/* pretend we didn't recognize this IOCTL */  
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(-ENOIOCTLCMD);
+
+#ifdef TIOCGETP
+	case TIOCGETP:
+#endif
+	case TCGETS:
+	case TCGETA:
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+		if (tty->ldisc->ops->ioctl) {
+#else
+		if (tty->ldisc.ops->ioctl) {
+#endif
+			int retval = (-ENXIO);
+
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+			if (tty->termios) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+				retval = ((tty->ldisc->ops->ioctl) (tty, file, cmd, arg));
+#else
+				retval = ((tty->ldisc.ops->ioctl) (tty, file, cmd, arg));
+#endif
+			}
+
+			DPR_IOCTL(("dgnc_tty_ioctl (LINE:%d) finish on port %d - cmd %s (%x), arg %lx\n", 
+				__LINE__, ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+			return(retval);
+		}
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		DPR_IOCTL(("dgnc_tty_ioctl (LINE:%d) finish on port %d - cmd %s (%x), arg %lx\n", 
+			__LINE__, ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+		return(-ENOIOCTLCMD);
+
+	case TCSETSF:
+	case TCSETSW:
+		/*
+		 * The linux tty driver doesn't have a flush
+		 * input routine for the driver, assuming all backed
+		 * up data is in the line disc. buffers.  However,
+		 * we all know that's not the case.  Here, we
+		 * act on the ioctl, but then lie and say we didn't
+		 * so the line discipline will process the flush
+		 * also.
+		 */
+		if (cmd == TCSETSF) {
+			/* flush rx */
+			ch->ch_flags &= ~CH_STOP;
+			ch->ch_r_head = ch->ch_r_tail;
+			ch->ch_bd->bd_ops->flush_uart_read(ch);
+			/* Force queue flow control to be released, if needed */
+			dgnc_check_queue_flow_control(ch);
+		}
+
+		/* now wait for all the output to drain */
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = ch->ch_bd->bd_ops->drain(tty, 0);
+		if (rc) {
+			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d\n", rc));
+			return(-EINTR);
+		}
+
+		DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n", 
+			ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+		/* pretend we didn't recognize this */
+		return(-ENOIOCTLCMD);
+
+	case TCSETAW:
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = ch->ch_bd->bd_ops->drain(tty, 0);
+		if (rc) {
+			DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+			return(-EINTR);
+		}
+
+		/* pretend we didn't recognize this */
+		return(-ENOIOCTLCMD);  
+
+	case TCXONC:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		/* Make the ld do it */
+		return(-ENOIOCTLCMD);
+
+	case DIGI_GETA:
+		/* get information for ditty */
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(dgnc_tty_digigeta(tty, uarg));
+
+	case DIGI_SETAW:
+	case DIGI_SETAF:
+
+		/* set information for ditty */
+		if (cmd == (DIGI_SETAW)) {
+
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			rc = ch->ch_bd->bd_ops->drain(tty, 0);
+			if (rc) {
+				DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+				return(-EINTR);
+			}
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+		}
+		else {
+			tty_ldisc_flush(tty);
+		}
+		/* fall thru */
+
+	case DIGI_SETA:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(dgnc_tty_digiseta(tty, uarg));
+                
+	case DIGI_LOOPBACK:
+		{
+			uint loopback = 0;
+			/* Let go of locks when accessing user space, could sleep */
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			rc = get_user(loopback, (unsigned int __user *) arg);
+			if (rc)
+				return(rc);
+			DGNC_LOCK(ch->ch_lock, lock_flags);
+
+			/* Enable/disable internal loopback for this port */   
+			if (loopback)
+				ch->ch_flags |= CH_LOOPBACK;
+			else
+				ch->ch_flags &= ~(CH_LOOPBACK);
+
+			ch->ch_bd->bd_ops->param(tty);
+			DGNC_UNLOCK(ch->ch_lock, lock_flags);
+			return(0);
+                }
+
+	case DIGI_GETCUSTOMBAUD:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
+		return(rc);
+
+	case DIGI_SETCUSTOMBAUD:
+	{
+		uint new_rate;
+		/* Let go of locks when accessing user space, could sleep */
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = get_user(new_rate, (unsigned int __user *) arg);
+		if (rc)
+			return(rc);
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		dgnc_set_custom_speed(ch, new_rate);
+		ch->ch_bd->bd_ops->param(tty);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(0);
+        }
+
+	/*
+	 * This ioctl allows insertion of a character into the front
+	 * of any pending data to be transmitted.
+	 *
+	 * This ioctl is to satify the "Send Character Immediate"
+	 * call that the RealPort protocol spec requires.
+	 */
+	case DIGI_REALPORT_SENDIMMEDIATE:
+	{
+		unsigned char c;
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = get_user(c, (unsigned char __user *) arg);
+		if (rc)
+			return(rc);
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+		ch->ch_bd->bd_ops->send_immediate_char(ch, c);
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		return(0);
+	}
+
+	/*
+	 * This ioctl returns all the current counts for the port.
+	 *
+	 * This ioctl is to satify the "Line Error Counters"
+	 * call that the RealPort protocol spec requires.
+	 */
+	case DIGI_REALPORT_GETCOUNTERS:
+	{
+		struct digi_getcounter buf;
+
+		buf.norun = ch->ch_err_overrun;
+		buf.noflow = 0;  	/* The driver doesn't keep this stat */
+		buf.nframe = ch->ch_err_frame;
+		buf.nparity = ch->ch_err_parity;
+		buf.nbreak = ch->ch_err_break;
+		buf.rbytes = ch->ch_rxcount;
+		buf.tbytes = ch->ch_txcount;
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		if (copy_to_user(uarg, &buf, sizeof(struct digi_getcounter))) {
+			return (-EFAULT);
+		}
+		return(0);
+	}
+
+	/*
+	 * This ioctl returns all current events.
+	 *
+	 * This ioctl is to satify the "Event Reporting"
+	 * call that the RealPort protocol spec requires.
+         */
+	case DIGI_REALPORT_GETEVENTS:
+	{
+		unsigned int events = 0;
+
+		/* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */
+		if (ch->ch_flags & CH_BREAK_SENDING)
+			events |= EV_TXB;
+		if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP)) {
+			events |= (EV_OPU | EV_OPS);
+		}
+		if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI)) {
+			events |= (EV_IPU | EV_IPS);
+		}
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+		rc = put_user(events, (unsigned int __user *) arg);
+		return(rc);
+	}
+
+	/*
+	 * This ioctl returns TOUT and TIN counters based
+	 * upon the values passed in by the RealPort Server.
+	 * It also passes back whether the UART Transmitter is
+	 * empty as well.
+         */
+	case DIGI_REALPORT_GETBUFFERS:
+	{
+		struct digi_getbuffer buf;
+		int tdist;
+		int count;
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		/*
+		 * Get data from user first.
+		 */
+		if (copy_from_user(&buf, uarg, sizeof(struct digi_getbuffer))) {
+			return (-EFAULT);
+		}
+
+		DGNC_LOCK(ch->ch_lock, lock_flags);
+
+		/*
+		 * Figure out how much data is in our RX and TX queues.
+		 */
+		buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK;
+		buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK;
+
+		/*
+		 * Is the UART empty? Add that value to whats in our TX queue.
+		 */
+		count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch);
+
+		/*
+		 * Figure out how much data the RealPort Server believes should
+		 * be in our TX queue.
+		 */
+		tdist = (buf.tIn - buf.tOut) & 0xffff;
+
+		/*
+		 * If we have more data than the RealPort Server believes we
+		 * should have, reduce our count to its amount.
+		 *
+		 * This count difference CAN happen because the Linux LD can
+		 * insert more characters into our queue for OPOST processing
+		 * that the RealPort Server doesn't know about.
+		 */
+		if (buf.txbuf > tdist) {
+			buf.txbuf = tdist;
+		}
+
+		/*
+		 * Report whether our queue and UART TX are completely empty.
+		 */
+		if (count) {
+			buf.txdone = 0;
+		} else {
+			buf.txdone = 1;
+		}
+
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		if (copy_to_user(uarg, &buf, sizeof(struct digi_getbuffer))) {
+			return (-EFAULT);
+		}
+		return(0);
+	}
+	default:
+		DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+		DPR_IOCTL(("dgnc_tty_ioctl - in default\n"));
+		DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n", 
+			dgnc_ioctl_name(cmd), cmd, arg));
+
+		return(-ENOIOCTLCMD);
+	}
+
+	DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+	DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n", 
+		dgnc_ioctl_name(cmd), cmd, arg));
+                        
+	return(0);
+}
diff --git a/drivers/staging/dgnc/dgnc_tty.h b/drivers/staging/dgnc/dgnc_tty.h
new file mode 100644
index 0000000..deb388d
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_tty.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_TTY_H
+#define __DGNC_TTY_H
+
+#include "dgnc_driver.h"
+
+int	dgnc_tty_register(struct board_t *brd);
+
+int	dgnc_tty_preinit(void);
+int     dgnc_tty_init(struct board_t *);
+
+void	dgnc_tty_post_uninit(void);
+void	dgnc_tty_uninit(struct board_t *);
+
+void	dgnc_input(struct channel_t *ch);
+void	dgnc_carrier(struct channel_t *ch);
+void	dgnc_wakeup_writes(struct channel_t *ch);
+void	dgnc_check_queue_flow_control(struct channel_t *ch);
+
+void	dgnc_sniff_nowait_nolock(struct channel_t *ch, uchar *text, uchar *buf, int nbuf);
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_types.h b/drivers/staging/dgnc/dgnc_types.h
new file mode 100644
index 0000000..4fa3585
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_types.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_TYPES_H
+#define __DGNC_TYPES_H
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+/* Required for our shared headers! */
+typedef unsigned char uchar;
+
+#endif
diff --git a/drivers/staging/dgnc/digi.h b/drivers/staging/dgnc/digi.h
new file mode 100644
index 0000000..ab90382
--- /dev/null
+++ b/drivers/staging/dgnc/digi.h
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *	Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ * $Id: digi.h,v 1.1.1.1 2009/05/20 12:19:19 markh Exp $
+ *
+ *	NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DIGI_H
+#define __DIGI_H
+
+/************************************************************************
+ ***	Definitions for Digi ditty(1) command.
+ ************************************************************************/
+
+
+/*
+ * Copyright (c) 1988-96 Digi International Inc., All Rights Reserved.
+ */
+
+/************************************************************************
+ * This module provides application access to special Digi
+ * serial line enhancements which are not standard UNIX(tm) features.
+ ************************************************************************/
+
+#if !defined(TIOCMODG)
+
+#define	TIOCMODG	('d'<<8) | 250		/* get modem ctrl state	*/
+#define	TIOCMODS	('d'<<8) | 251		/* set modem ctrl state	*/
+
+#ifndef TIOCM_LE 
+#define		TIOCM_LE	0x01		/* line enable		*/
+#define		TIOCM_DTR	0x02		/* data terminal ready	*/
+#define		TIOCM_RTS	0x04		/* request to send	*/
+#define		TIOCM_ST	0x08		/* secondary transmit	*/
+#define		TIOCM_SR	0x10		/* secondary receive	*/
+#define		TIOCM_CTS	0x20		/* clear to send	*/
+#define		TIOCM_CAR	0x40		/* carrier detect	*/
+#define		TIOCM_RNG	0x80		/* ring	indicator	*/
+#define		TIOCM_DSR	0x100		/* data set ready	*/
+#define		TIOCM_RI	TIOCM_RNG	/* ring (alternate)	*/
+#define		TIOCM_CD	TIOCM_CAR	/* carrier detect (alt)	*/
+#endif
+
+#endif
+
+#if !defined(TIOCMSET)
+#define	TIOCMSET	('d'<<8) | 252		/* set modem ctrl state	*/
+#define	TIOCMGET	('d'<<8) | 253		/* set modem ctrl state	*/
+#endif
+
+#if !defined(TIOCMBIC)
+#define	TIOCMBIC	('d'<<8) | 254		/* set modem ctrl state */
+#define	TIOCMBIS	('d'<<8) | 255		/* set modem ctrl state */
+#endif
+
+
+#if !defined(TIOCSDTR)
+#define	TIOCSDTR	('e'<<8) | 0		/* set DTR		*/
+#define	TIOCCDTR	('e'<<8) | 1		/* clear DTR		*/
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA	('e'<<8) | 94		/* Read params		*/
+
+#define DIGI_SETA	('e'<<8) | 95		/* Set params		*/
+#define DIGI_SETAW	('e'<<8) | 96		/* Drain & set params	*/
+#define DIGI_SETAF	('e'<<8) | 97		/* Drain, flush & set params */
+
+#define DIGI_KME	('e'<<8) | 98		/* Read/Write Host	*/
+						/* Adapter Memory	*/
+
+#define	DIGI_GETFLOW	('e'<<8) | 99		/* Get startc/stopc flow */
+						/* control characters 	 */
+#define	DIGI_SETFLOW	('e'<<8) | 100		/* Set startc/stopc flow */
+						/* control characters	 */
+#define	DIGI_GETAFLOW	('e'<<8) | 101		/* Get Aux. startc/stopc */
+						/* flow control chars 	 */
+#define	DIGI_SETAFLOW	('e'<<8) | 102		/* Set Aux. startc/stopc */
+						/* flow control chars	 */
+
+#define DIGI_GEDELAY	('d'<<8) | 246		/* Get edelay */
+#define DIGI_SEDELAY	('d'<<8) | 247		/* Set edelay */
+
+struct	digiflow_t {
+	unsigned char	startc;				/* flow cntl start char	*/
+	unsigned char	stopc;				/* flow cntl stop char	*/
+};
+
+
+#ifdef	FLOW_2200
+#define	F2200_GETA	('e'<<8) | 104		/* Get 2x36 flow cntl flags */
+#define	F2200_SETAW	('e'<<8) | 105		/* Set 2x36 flow cntl flags */
+#define		F2200_MASK	0x03		/* 2200 flow cntl bit mask  */
+#define		FCNTL_2200	0x01		/* 2x36 terminal flow cntl  */
+#define		PCNTL_2200	0x02		/* 2x36 printer flow cntl   */
+#define	F2200_XON	0xf8
+#define	P2200_XON	0xf9
+#define	F2200_XOFF	0xfa
+#define	P2200_XOFF	0xfb
+
+#define	FXOFF_MASK	0x03			/* 2200 flow status mask    */
+#define	RCVD_FXOFF	0x01			/* 2x36 Terminal XOFF rcvd  */
+#define	RCVD_PXOFF	0x02			/* 2x36 Printer XOFF rcvd   */
+#endif
+
+/************************************************************************
+ * Values for digi_flags 
+ ************************************************************************/
+#define DIGI_IXON	0x0001		/* Handle IXON in the FEP	*/
+#define DIGI_FAST	0x0002		/* Fast baud rates		*/
+#define RTSPACE		0x0004		/* RTS input flow control	*/
+#define CTSPACE		0x0008		/* CTS output flow control	*/
+#define DSRPACE		0x0010		/* DSR output flow control	*/
+#define DCDPACE		0x0020		/* DCD output flow control	*/
+#define DTRPACE		0x0040		/* DTR input flow control	*/
+#define DIGI_COOK	0x0080		/* Cooked processing done in FEP */
+#define DIGI_FORCEDCD	0x0100		/* Force carrier		*/
+#define	DIGI_ALTPIN	0x0200		/* Alternate RJ-45 pin config	*/
+#define	DIGI_AIXON	0x0400		/* Aux flow control in fep	*/
+#define	DIGI_PRINTER	0x0800		/* Hold port open for flow cntrl*/
+#define DIGI_PP_INPUT	0x1000		/* Change parallel port to input*/
+#define DIGI_DTR_TOGGLE	0x2000		/* Support DTR Toggle           */
+#define DIGI_422	0x4000		/* for 422/232 selectable panel */
+#define DIGI_RTS_TOGGLE	0x8000		/* Support RTS Toggle		*/
+
+/************************************************************************
+ * These options are not supported on the comxi.
+ ************************************************************************/
+#define	DIGI_COMXI	(DIGI_FAST|DIGI_COOK|DSRPACE|DCDPACE|DTRPACE)
+
+#define DIGI_PLEN	28		/* String length		*/
+#define	DIGI_TSIZ	10		/* Terminal string len		*/
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_t {
+	unsigned short	digi_flags;		/* Flags (see above)	*/
+	unsigned short	digi_maxcps;		/* Max printer CPS	*/
+	unsigned short	digi_maxchar;		/* Max chars in print queue */
+	unsigned short	digi_bufsize;		/* Buffer size		*/
+	unsigned char	digi_onlen;		/* Length of ON string	*/
+	unsigned char	digi_offlen;		/* Length of OFF string	*/
+	char		digi_onstr[DIGI_PLEN];	/* Printer on string	*/
+	char		digi_offstr[DIGI_PLEN];	/* Printer off string	*/
+	char		digi_term[DIGI_TSIZ];	/* terminal string	*/
+};
+
+/************************************************************************
+ * KME definitions and structures.
+ ************************************************************************/
+#define	RW_IDLE		0	/* Operation complete			*/
+#define	RW_READ		1	/* Read Concentrator Memory		*/
+#define	RW_WRITE	2	/* Write Concentrator Memory		*/
+
+struct rw_t {
+	unsigned char	rw_req;		/* Request type			*/
+	unsigned char	rw_board;	/* Host Adapter board number	*/
+	unsigned char	rw_conc;	/* Concentrator number		*/
+	unsigned char	rw_reserved;	/* Reserved for expansion	*/
+	unsigned int	rw_addr;	/* Address in concentrator	*/
+	unsigned short	rw_size;	/* Read/write request length	*/
+	unsigned char	rw_data[128];	/* Data to read/write		*/
+};
+
+/***********************************************************************
+ * Shrink Buffer and Board Information definitions and structures.
+
+ ************************************************************************/
+			/* Board type return codes */
+#define	PCXI_TYPE 1     /* Board type at the designated port is a PC/Xi */
+#define PCXM_TYPE 2     /* Board type at the designated port is a PC/Xm */
+#define	PCXE_TYPE 3     /* Board type at the designated port is a PC/Xe */
+#define	MCXI_TYPE 4     /* Board type at the designated port is a MC/Xi */
+#define COMXI_TYPE 5     /* Board type at the designated port is a COM/Xi */
+
+			 /* Non-Zero Result codes. */
+#define RESULT_NOBDFND 1 /* A Digi product at that port is not config installed */ 
+#define RESULT_NODESCT 2 /* A memory descriptor was not obtainable */ 
+#define RESULT_NOOSSIG 3 /* FEP/OS signature was not detected on the board */
+#define RESULT_TOOSML  4 /* Too small an area to shrink.  */
+#define RESULT_NOCHAN  5 /* Channel structure for the board was not found */
+
+struct shrink_buf_struct {
+	unsigned int	shrink_buf_vaddr;	/* Virtual address of board */
+	unsigned int	shrink_buf_phys;	/* Physical address of board */
+	unsigned int	shrink_buf_bseg;	/* Amount of board memory */
+	unsigned int	shrink_buf_hseg;	/* '186 Begining of Dual-Port */
+
+	unsigned int	shrink_buf_lseg;	/* '186 Begining of freed memory */ 
+	unsigned int	shrink_buf_mseg;	/* Linear address from start of
+						   dual-port were freed memory
+						   begins, host viewpoint. */
+
+	unsigned int	shrink_buf_bdparam;	/* Parameter for xxmemon and
+						   xxmemoff */
+
+	unsigned int	shrink_buf_reserva;	/* Reserved */
+	unsigned int	shrink_buf_reservb;	/* Reserved */
+	unsigned int	shrink_buf_reservc;	/* Reserved */
+	unsigned int	shrink_buf_reservd;	/* Reserved */
+
+	unsigned char	shrink_buf_result;	/* Reason for call failing
+						   Zero is Good return */
+	unsigned char	shrink_buf_init;	/* Non-Zero if it caused an     
+						   xxinit call. */
+
+	unsigned char	shrink_buf_anports;	/* Number of async ports  */
+	unsigned char	shrink_buf_snports; 	/* Number of sync  ports */
+	unsigned char	shrink_buf_type;	/* Board type 1 = PC/Xi,
+							      2 = PC/Xm,
+							      3 = PC/Xe  
+							      4 = MC/Xi  
+							      5 = COMX/i */
+	unsigned char	shrink_buf_card;	/* Card number */
+	
+};
+
+/************************************************************************
+ * Structure to get driver status information
+ ************************************************************************/
+struct digi_dinfo {
+	unsigned int	dinfo_nboards;		/* # boards configured	*/
+	char		dinfo_reserved[12];	/* for future expansion */
+	char		dinfo_version[16];	/* driver version       */
+};
+
+#define	DIGI_GETDD	('d'<<8) | 248		/* get driver info      */
+ 
+/************************************************************************
+ * Structure used with ioctl commands for per-board information
+ *
+ * physsize and memsize differ when board has "windowed" memory
+ ************************************************************************/
+struct digi_info {
+	unsigned int	info_bdnum;		/* Board number (0 based)  */
+	unsigned int	info_ioport;		/* io port address         */
+	unsigned int	info_physaddr;		/* memory address          */
+	unsigned int	info_physsize;		/* Size of host mem window */
+	unsigned int	info_memsize;		/* Amount of dual-port mem */
+						/* on board                */
+	unsigned short	info_bdtype;		/* Board type              */
+	unsigned short	info_nports;		/* number of ports         */
+	char		info_bdstate;		/* board state             */
+	char		info_reserved[7];	/* for future expansion    */
+};
+
+#define	DIGI_GETBD	('d'<<8) | 249		/* get board info          */
+ 
+struct digi_stat {
+	unsigned int	info_chan;		/* Channel number (0 based)  */
+	unsigned int	info_brd;		/* Board number (0 based)  */
+	unsigned int	info_cflag;		/* cflag for channel       */
+	unsigned int	info_iflag;		/* iflag for channel       */
+	unsigned int	info_oflag;		/* oflag for channel       */
+	unsigned int	info_mstat;		/* mstat for channel       */
+	unsigned int	info_tx_data;		/* tx_data for channel       */
+	unsigned int	info_rx_data;		/* rx_data for channel       */
+	unsigned int	info_hflow;		/* hflow for channel       */
+	unsigned int	info_reserved[8];	/* for future expansion    */
+};
+
+#define	DIGI_GETSTAT	('d'<<8) | 244		/* get board info          */
+/************************************************************************
+ *
+ * Structure used with ioctl commands for per-channel information
+ *
+ ************************************************************************/
+struct digi_ch {
+	unsigned int	info_bdnum;		/* Board number (0 based)  */
+	unsigned int	info_channel;		/* Channel index number    */
+	unsigned int	info_ch_cflag;		/* Channel cflag   	   */
+	unsigned int	info_ch_iflag;		/* Channel iflag   	   */
+	unsigned int	info_ch_oflag;		/* Channel oflag   	   */
+	unsigned int	info_chsize;		/* Channel structure size  */
+	unsigned int	info_sleep_stat;	/* sleep status		   */
+	dev_t		info_dev;		/* device number	   */
+	unsigned char	info_initstate;		/* Channel init state	   */
+	unsigned char	info_running;		/* Channel running state   */
+	int		reserved[8];		/* reserved for future use */
+};
+
+/*
+* This structure is used with the DIGI_FEPCMD ioctl to 
+* tell the driver which port to send the command for.
+*/
+struct digi_cmd {
+	int	cmd;
+	int	word;
+	int	ncmds;
+	int	chan; /* channel index (zero based) */
+	int	bdid; /* board index (zero based) */
+};
+
+
+struct digi_getbuffer /* Struct for holding buffer use counts */
+{
+	unsigned long tIn;
+	unsigned long tOut;
+	unsigned long rxbuf;
+	unsigned long txbuf;
+	unsigned long txdone;
+};
+
+struct digi_getcounter
+{
+	unsigned long norun;		/* number of UART overrun errors */
+	unsigned long noflow;		/* number of buffer overflow errors */
+	unsigned long nframe;		/* number of framing errors */
+	unsigned long nparity;		/* number of parity errors */
+	unsigned long nbreak;		/* number of breaks received */
+	unsigned long rbytes;		/* number of received bytes */
+	unsigned long tbytes;		/* number of bytes transmitted fully */
+};
+
+/*
+*  info_sleep_stat defines
+*/
+#define INFO_RUNWAIT	0x0001
+#define INFO_WOPEN	0x0002
+#define INFO_TTIOW	0x0004
+#define INFO_CH_RWAIT	0x0008
+#define INFO_CH_WEMPTY	0x0010
+#define INFO_CH_WLOW	0x0020
+#define INFO_XXBUF_BUSY 0x0040
+
+#define	DIGI_GETCH	('d'<<8) | 245		/* get board info          */
+
+/* Board type definitions */
+
+#define	SUBTYPE		0007
+#define	T_PCXI		0000
+#define T_PCXM		0001
+#define T_PCXE		0002
+#define T_PCXR		0003
+#define T_SP		0004
+#define T_SP_PLUS	0005
+#	define T_HERC	0000
+#	define T_HOU	0001
+#	define T_LON	0002
+#	define T_CHA	0003
+#define FAMILY		0070
+#define T_COMXI		0000
+#define T_PCXX		0010
+#define T_CX		0020
+#define T_EPC		0030
+#define	T_PCLITE	0040
+#define	T_SPXX		0050
+#define	T_AVXX		0060
+#define T_DXB		0070
+#define T_A2K_4_8	0070
+#define BUSTYPE		0700
+#define T_ISABUS	0000
+#define T_MCBUS		0100
+#define	T_EISABUS	0200
+#define	T_PCIBUS	0400
+
+/* Board State Definitions */
+
+#define	BD_RUNNING	0x0
+#define	BD_REASON	0x7f
+#define	BD_NOTFOUND	0x1
+#define	BD_NOIOPORT	0x2
+#define	BD_NOMEM	0x3
+#define	BD_NOBIOS	0x4
+#define	BD_NOFEP	0x5
+#define	BD_FAILED	0x6
+#define BD_ALLOCATED	0x7
+#define BD_TRIBOOT	0x8
+#define	BD_BADKME	0x80
+
+#define DIGI_SPOLL            ('d'<<8) | 254  /* change poller rate   */
+
+#define DIGI_SETCUSTOMBAUD	_IOW('e', 106, int)	/* Set integer baud rate */
+#define DIGI_GETCUSTOMBAUD	_IOR('e', 107, int)	/* Get integer baud rate */
+
+#define DIGI_REALPORT_GETBUFFERS ('e'<<8 ) | 108
+#define DIGI_REALPORT_SENDIMMEDIATE ('e'<<8 ) | 109
+#define DIGI_REALPORT_GETCOUNTERS ('e'<<8 ) | 110
+#define DIGI_REALPORT_GETEVENTS ('e'<<8 ) | 111
+
+#define EV_OPU		0x0001		//!<Output paused by client
+#define EV_OPS		0x0002		//!<Output paused by reqular sw flowctrl  
+#define EV_OPX		0x0004		//!<Output paused by extra sw flowctrl
+#define EV_OPH		0x0008		//!<Output paused by hw flowctrl
+#define EV_OPT		0x0800		//!<Output paused for RTS Toggle predelay
+
+#define EV_IPU		0x0010		//!<Input paused unconditionally by user
+#define EV_IPS		0x0020		//!<Input paused by high/low water marks
+//#define EV_IPH	0x0040		//!<Input paused w/ hardware  
+#define EV_IPA		0x0400		//!<Input paused by pattern alarm module
+                                        
+#define EV_TXB		0x0040		//!<Transmit break pending
+#define EV_TXI		0x0080		//!<Transmit immediate pending
+#define EV_TXF		0x0100		//!<Transmit flowctrl char pending
+#define EV_RXB		0x0200		//!<Break received
+
+#define EV_OPALL	0x080f		//!<Output pause flags
+#define EV_IPALL	0x0430		//!<Input pause flags
+
+#endif /* DIGI_H */
diff --git a/drivers/staging/dgnc/dpacompat.h b/drivers/staging/dgnc/dpacompat.h
new file mode 100644
index 0000000..44379eb
--- /dev/null
+++ b/drivers/staging/dgnc/dpacompat.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ *      Scott H Kilau <Scott_Kilau at digi dot com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; 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.
+ *
+ *      NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+
+/* 
+ * This structure holds data needed for the intelligent <--> nonintelligent 
+ * DPA translation
+ */
+ struct ni_info {
+	int board;
+	int channel;
+	int dtr;
+	int rts;
+	int cts;
+	int dsr;
+	int ri;
+	int dcd;
+	int curtx;
+	int currx;
+	unsigned short iflag;
+	unsigned short oflag;
+	unsigned short cflag;
+	unsigned short lflag;
+
+	unsigned int mstat;
+	unsigned char hflow;
+
+	unsigned char xmit_stopped;
+	unsigned char recv_stopped;
+
+	unsigned int baud;
+};
+
+#define RW_READ		1
+#define RW_WRITE        2
+#define DIGI_KME        ('e'<<8) | 98           /* Read/Write Host */
+
+#define SUBTYPE         0007
+#define T_PCXI          0000
+#define T_PCXEM         0001
+#define T_PCXE          0002
+#define T_PCXR          0003
+#define T_SP            0004
+#define T_SP_PLUS       0005
+
+#define T_HERC   0000
+#define T_HOU    0001
+#define T_LON    0002
+#define T_CHA    0003
+
+#define T_NEO	 0000
+#define T_NEO_EXPRESS  0001
+#define T_CLASSIC 0002
+
+#define FAMILY          0070
+#define T_COMXI         0000
+#define	T_NI		0000
+#define T_PCXX          0010
+#define T_CX            0020
+#define T_EPC           0030
+#define T_PCLITE        0040
+#define T_SPXX          0050
+#define T_AVXX          0060
+#define T_DXB           0070
+#define T_A2K_4_8       0070
+
+#define BUSTYPE         0700
+#define T_ISABUS        0000
+#define T_MCBUS         0100
+#define T_EISABUS       0200
+#define T_PCIBUS        0400
+
+/* Board State Definitions */
+
+#define BD_RUNNING      0x0
+#define BD_REASON       0x7f
+#define BD_NOTFOUND     0x1
+#define BD_NOIOPORT     0x2
+#define BD_NOMEM        0x3
+#define BD_NOBIOS       0x4
+#define BD_NOFEP        0x5
+#define BD_FAILED       0x6
+#define BD_ALLOCATED    0x7
+#define BD_TRIBOOT      0x8
+#define BD_BADKME       0x80
+
+#define DIGI_AIXON      0x0400          /* Aux flow control in fep */
+
+/* Ioctls needed for dpa operation */
+
+#define DIGI_GETDD      ('d'<<8) | 248          /* get driver info      */
+#define DIGI_GETBD      ('d'<<8) | 249          /* get board info       */
+#define DIGI_GET_NI_INFO ('d'<<8) | 250		/* nonintelligent state snfo */
+
+/* Other special ioctls */
+#define DIGI_TIMERIRQ ('d'<<8) | 251		/* Enable/disable RS_TIMER use */
+#define DIGI_LOOPBACK ('d'<<8) | 252		/* Enable/disable UART internal loopback */
-- 
1.8.1.2



More information about the devel mailing list