staging: dgnc: add dgnc digi driver
authorLidza Louina <lidza.louina@gmail.com>
Thu, 1 Aug 2013 21:00:20 +0000 (17:00 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Aug 2013 21:42:45 +0000 (05:42 +0800)
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>.

This driver isn't hooked up to the build system because it doesn't
build, it merely adds the driver written by Digi to the kernel tree so
that it can be cleaned up and fixed up properly over time.

Cc: Mark Hounschell <markh@compro.net>
Signed-off-by: Lidza Louina <lidza.louina@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
24 files changed:
drivers/staging/dgnc/Kconfig [new file with mode: 0644]
drivers/staging/dgnc/Makefile [new file with mode: 0644]
drivers/staging/dgnc/Makefile.inc [new file with mode: 0644]
drivers/staging/dgnc/dgnc_cls.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_cls.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_driver.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_driver.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_kcompat.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_mgmt.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_mgmt.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_neo.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_neo.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_pci.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_proc.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_proc.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_sysfs.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_sysfs.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_trace.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_trace.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_tty.c [new file with mode: 0644]
drivers/staging/dgnc/dgnc_tty.h [new file with mode: 0644]
drivers/staging/dgnc/dgnc_types.h [new file with mode: 0644]
drivers/staging/dgnc/digi.h [new file with mode: 0644]
drivers/staging/dgnc/dpacompat.h [new file with mode: 0644]

diff --git a/drivers/staging/dgnc/Kconfig b/drivers/staging/dgnc/Kconfig
new file mode 100644 (file)
index 0000000..23daaa5
--- /dev/null
@@ -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 (file)
index 0000000..c4c96dc
--- /dev/null
@@ -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 (file)
index 0000000..6ca38c7
--- /dev/null
@@ -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 (file)
index 0000000..83ded18
--- /dev/null
@@ -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 (file)
index 0000000..dca5ea3
--- /dev/null
@@ -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 (file)
index 0000000..7c88de7
--- /dev/null
@@ -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 (file)
index 0000000..43177f4
--- /dev/null
@@ -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 (file)
index 0000000..3f69e1d
--- /dev/null
@@ -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 (file)
index 0000000..b8e4792
--- /dev/null
@@ -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 (file)
index 0000000..a0d1338
--- /dev/null
@@ -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 (file)
index 0000000..503db8f
--- /dev/null
@@ -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 (file)
index 0000000..ffb4209
--- /dev/null
@@ -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 (file)
index 0000000..5550707
--- /dev/null
@@ -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 (file)
index 0000000..8fbaf3b
--- /dev/null
@@ -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 (file)
index 0000000..19670e2
--- /dev/null
@@ -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 (file)
index 0000000..af49e44
--- /dev/null
@@ -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 (file)
index 0000000..fe99110
--- /dev/null
@@ -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 (file)
index 0000000..ea710e5
--- /dev/null
@@ -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 (file)
index 0000000..1e8870b
--- /dev/null
@@ -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 (file)
index 0000000..461e881
--- /dev/null
@@ -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 (file)
index 0000000..deb388d
--- /dev/null
@@ -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 (file)
index 0000000..4fa3585
--- /dev/null
@@ -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 (file)
index 0000000..ab90382
--- /dev/null
@@ -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 (file)
index 0000000..44379eb
--- /dev/null
@@ -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 */