]> git.neil.brown.name Git - history.git/commitdiff
Import 2.1.37pre2 2.1.37pre2
authorLinus Torvalds <torvalds@linuxfoundation.org>
Fri, 23 Nov 2007 20:13:10 +0000 (15:13 -0500)
committerLinus Torvalds <torvalds@linuxfoundation.org>
Fri, 23 Nov 2007 20:13:10 +0000 (15:13 -0500)
26 files changed:
Documentation/00-INDEX
Documentation/Configure.help
Documentation/digiepca.txt [new file with mode: 0644]
MAINTAINERS
arch/i386/kernel/entry.S
arch/i386/kernel/head.S
arch/i386/kernel/i386_ksyms.c
arch/i386/kernel/irq.c
arch/i386/kernel/irq.h
arch/i386/kernel/smp.c
drivers/char/Config.in
drivers/char/Makefile
drivers/char/README.epca [new file with mode: 0644]
drivers/char/epca.c [new file with mode: 0644]
drivers/char/tty_io.c
include/asm-i386/locks.h
include/linux/digi1.h [new file with mode: 0644]
include/linux/digiFep1.h [new file with mode: 0644]
include/linux/digiPCI.h [new file with mode: 0644]
include/linux/epca.h [new file with mode: 0644]
include/linux/epcaconfig.h [new file with mode: 0644]
include/linux/tty.h
init/main.c
kernel/exit.c
kernel/printk.c
kernel/softirq.c

index 09d482dac082167c0a42bca9aafaabc3924dd312..63466ffd52a73d74aac1743fb90e61eca62f3ae1 100644 (file)
@@ -26,6 +26,8 @@ devices.txt
        - plain ASCII listing of all the nodes in /dev/ with major minor #'s
 digiboard.txt
        - info on the Digiboard PC/X{i,e,eve} multiport boards.
+digiepca.txt
+       - info on Digi Intl. {PC,PCI,EISA}Xx and Xem series cards.
 exception.txt
        - how linux v2.1 handles exceptions without verify_area etc.
 ez.txt
index 469fb2e020c358a2c3ea2676b40bab5c87bcf63f..062bf99434f70fc212882b6dc355d83f893f9fa2 100644 (file)
@@ -4036,6 +4036,16 @@ CONFIG_SERIAL_CONSOLE
   N here so that they can use the serial port for modem, mouse or some
   other device.
 
+Digi Intl. epca support
+CONFIG_DIGIEPCA
+  This is a driver for Digi Internationals Xx, Xeve, and Xem 
+  series of cards.  This driver supports the original PC (ISA) boards as
+  well as PCI, and EISA.  If you have a card like this, say Y here and read
+  the file Documentation/digiepca.txt.  NOTE: This driver is seperate from
+  the driver written and copyrighted by Troy De Jongh.  Because they both
+  attempt (In some cases) to access the same hardware only one of these
+  drivers (CONFIG_DIGIEPCA or CONFIG_DIGI) should be selected.
+
 Digiboard PC/Xx Support
 CONFIG_DIGI
   This is a driver for the Digiboard PC/Xe, PC/Xi, and PC/Xeve cards
@@ -5317,7 +5327,7 @@ CONFIG_MSDOS_PARTITION
 # LocalWords:  mgetty sendfax gert greenie muc lowlevel Lasermate LanManager io
 # LocalWords:  OOPSes trackball binghamton mobileip ncr IOMAPPED settags ns ser
 # LocalWords:  setsync NEGO MPARITY autotuning prefetch PIIX cdwrite utils rc
-# LocalWords:  PCWATCHDOG berkprod bitgate boldt ucsb jf kyoto jp euc Tetsuyasu
+# LocalWords:  PCWATCHDOG berkprod bitgate boldt ucsb jf kyoto jp euc Tetsuyasu 
 # LocalWords:  YAMADA tetsu cauchy nslab ntt nevod perm su doc kaf kheops wsc
 # LocalWords:  traduc Bourgin dbourgin helptext menuconfig kfill READMEs HOWTOs
 # LocalWords:  IDEDISK IDEFLOPPY EIDE firewalls QMAGIC ZMAGIC LocalWords opti
diff --git a/Documentation/digiepca.txt b/Documentation/digiepca.txt
new file mode 100644 (file)
index 0000000..9904afb
--- /dev/null
@@ -0,0 +1,96 @@
+The Digi Intl. epca driver. 
+----------------------------
+The Digi Intl. epca driver for Linux supports the following boards:
+
+Digi PC/Xem, PC/Xr, PC/Xe, PC/Xi, PC/Xeve 
+Digi EISA/Xem, PCI/Xem, PCI/Xr 
+
+Limitations:
+------------
+Currently the driver only autoprobes for supported PCI boards. 
+
+The Linux MAKEDEV command does not support generating the Digiboard
+Devices.  Users executing digiConfig to setup EISA and PC series cards
+will have their device nodes automaticly constructed (cud?? for ~CLOCAL,
+and ttyD?? for CLOCAL).  Users wishing to boot their board from the LILO
+prompt, or those users booting PCI cards may use buildDIGI to construct 
+the necessary nodes. 
+
+Notes:
+------
+This driver may be configured via LILO.  For users who have already configured
+their driver using digiConfig, configuring from lilo will override previous 
+settings.  Multiple boards may be configured by issuing multiple LILO command 
+lines.  For examples see the bottom of this document.
+
+Device names start at 0 and continue up.  Beware of this as previous Digi 
+drivers started device names with 1.
+
+PCI boards are auto-detected and configured by the driver.  PCI boards will
+be allocated device numbers (internally) begining with the lowest PCI slot
+first.  In other words a PCI card in slot 3 will always have higher device
+nodes than a PCI card in slot 1. 
+
+LILO config examples:
+---------------------
+Using LILO's APPEND command, a string of comma separated identifiers or 
+integers can be used to configure supported boards.  The six values in order 
+are:
+
+   Enable/Disable this card or Override,
+   Type of card: PC/Xe (AccelePort) (0), PC/Xeve (1), PC/Xem or PC/Xr (2), 
+                 EISA/Xem (3), PC/64Xe (4), PC/Xi (5), 
+   Enable/Disable alternate pin arrangement,
+   Number of ports on this card,
+   I/O Port where card is configured (in HEX if using string identifiers),
+   Base of memory window (in HEX if using string identifiers), 
+
+NOTE : PCI boards are auto-detected and configured.  Do not attempt to 
+configure PCI boards with the LILO append comand.  If you wish to override
+previous configuration data (As set by digiConfig), but you do not wish to
+configure any specific card (Example if there are PCI cards in the system) 
+the following override command will accomplish this:
+-> append="digi=2"
+
+Samples:
+   append="digiepca=E,PC/Xe,D,16,200,D0000"
+                  or
+   append="digi=1,0,0,16,512,851968"
+
+Supporting Tools:
+-----------------
+Supporting tools include digiDload, digiConfig, buildPCI, and ditty.  See
+/usr/src/linux/Documentation/README.epca.dir/user.doc for more details.  Note,
+this driver REQUIRES that digiDload be executed prior to it being used. 
+Failure to do this will result in an ENODEV error.
+
+The latest version of the tool package is available at:
+ftp://ftp.dgii.com/drivers/linux/released/async/
+
+Documentation:
+--------------
+Complete documentation for this product may be found in the tool package. 
+
+Sources of information and support:
+-----------------------------------
+Digi Intl. support site for this product:
+-> digilnux@dgii.com 
+
+Related information and information concerning other drivers supporting 
+Digi Intl. products:
+
+-> FTP: ftp://dgii.com
+-> Webpage: http://www.dgii.com
+-> Webpage: http://private.fuller.edu/clameter/digi.html
+-> Mailing List: digiboard@list.fuller.edu  Note write e-mail to subscribe
+   common ListServ commands will not work.
+
+Acknowledgments:
+----------------
+Much of this work (And even text) was derived from a similar document 
+supporting the original public domain DigiBoard driver Copyright (C)
+1994,1995 Troy De Jongh.  Many thanks to Christoph Lameter 
+(clameter@fuller.edu) and Mike McLagan (mike.mclagan@linux.org) who authored 
+and contributed to the original document. 
+
+
index 588898993187885f49a3c3526f5c02019fc1f8af..be976ea8a2c4668b18dbab9f5f50cd0b1af83a55 100644 (file)
@@ -342,6 +342,13 @@ M: clameter@fuller.edu
 L:     digiboard@list.fuller.edu
 S:     Maintained
 
+DIGI INTL. EPCA DRIVER:
+P:      Daniel Taylor
+M:      support@dgii.com
+M:      digilnux@dgii.com
+L:      digiboard@list.fuller.edu
+S:      Maintained
+
 RISCOM8 DRIVER:
 P:     Dmitry Gorodchanin
 M:     begemot@bgm.rosprint.net
index fc5d4822a67bc503adaa6b9bbc4282934d0b82aa..ac67da797b02e17fa4150c67aa1c5de7007d0873 100644 (file)
@@ -110,45 +110,24 @@ ENOSYS = 38
        addl $4,%esp; \
        iret
 
-#ifdef __SMP__
-/* Get the processor ID multiplied by 4 */
-#if 0
-#define GET_PROCESSOR_OFFSET(reg) \
-       movl SYMBOL_NAME(apic_reg), reg; \
-       movl 32(reg), reg; \
-       shrl $22, reg; \
-       andl $0x3C, reg;
-
-#define GET_CURRENT(reg) \
-       GET_PROCESSOR_OFFSET(reg) \
-       movl SYMBOL_NAME(current_set)(reg),reg
-#endif
-
 #define GET_CURRENT(reg) \
        movl %esp, reg; \
        andl $-8192, reg;
-#else
-
-#define GET_CURRENT(reg) \
-       movl SYMBOL_NAME(current_set),reg
-
-#endif
 
 ENTRY(lcall7)
        pushfl                  # We get a different stack layout with call gates,
        pushl %eax              # which has to be cleaned up later..
        SAVE_ALL
-       GET_CURRENT(%ebx)
        movl EIP(%esp),%eax     # due to call gates, this is eflags, not eip..
        movl CS(%esp),%edx      # this is eip..
        movl EFLAGS(%esp),%ecx  # and this is cs..
        movl %eax,EFLAGS(%esp)  #
        movl %edx,EIP(%esp)     # Now we move them to their "normal" places
        movl %ecx,CS(%esp)      #
-       movl %esp,%eax
-       GET_CURRENT(%edx)
-       pushl %eax
-       movl exec_domain(%edx),%edx     # Get the execution domain
+       movl %esp,%ebx
+       pushl %ebx
+       andl $-8192,%ebx        # GET_CURRENT
+       movl exec_domain(%ebx),%edx     # Get the execution domain
        movl 4(%edx),%edx       # Get the lcall7 handler for the domain
        call *%edx
        popl %eax
@@ -159,6 +138,7 @@ ENTRY(lcall7)
        ALIGN
        .globl  ret_from_smpfork
 ret_from_smpfork:
+       GET_CURRENT(%ebx)
        btrl    $0, SYMBOL_NAME(scheduler_lock)
        jmp     ret_from_sys_call
 #endif /* __SMP__ */
@@ -190,7 +170,6 @@ ret_from_sys_call:
 ret_with_reschedule:
        cmpl $0,SYMBOL_NAME(need_resched)
        jne reschedule
-       GET_CURRENT(%ebx)
        movl blocked(%ebx),%eax
        movl %eax,%esi                  # save blocked in %esi for signal handling
        notl %eax
@@ -235,6 +214,7 @@ ret_from_exception:
        jne handle_bottom_half
        ALIGN
 ret_from_intr:
+       GET_CURRENT(%ebx)
        movl EFLAGS(%esp),%eax          # mix EFLAGS and CS
        movb CS(%esp),%al
        testl $(VM_MASK | 3),%eax       # return to VM86 mode or non-supervisor?
index be0f864e03ea344ab3b36cd122fc28dffe1510f9..f815352b0a705d829a3a54c20a8b18afce72b2af 100644 (file)
@@ -40,19 +40,20 @@ startup_32:
 /*
  *     New page tables may be in 4Mbyte page mode and may
  *     be using the global pages. 
+ *
+ *     NOTE! We have to correct for the fact that we're
+ *     not yet offset 0xC0000000..
  */
-#define mmu_cr4_features $128+16
+#define cr4_bits mmu_cr4_features-0xC0000000
 #ifdef GAS_KNOWS_CR4
        movl %cr4,%eax          # Turn on 4Mb pages
-       orl mmu_cr4_features,%eax
+       orl cr4_bits,%eax
        movl %eax,%cr4
 #else
        .byte 0x0f,0x20,0xe0
-       orl mmu_cr4_features,%eax
+       orl cr4_bits,%eax
        .byte 0x0f,0x22,0xe0
 #endif
-       movl %eax,%cr3          /* flush TLB as per app note */
-       movl %cr0,%eax
 #endif
 /*
  * Setup paging (the tables are already set up, just switch them on)
index 3aec838cb931cbe6fd0003e3aeadec151272f51d..61f4f5e63708a33dc4909b25183e15330320a6aa 100644 (file)
@@ -36,8 +36,6 @@ EXPORT_SYMBOL(__intel_bh_counter);
 /* Networking helper routines. */
 EXPORT_SYMBOL(csum_partial_copy);
 
-EXPORT_SYMBOL(synchronize_irq);
-
 #ifdef __SMP__
 EXPORT_SYMBOL(apic_reg);       /* Needed internally for the I386 inlines */
 EXPORT_SYMBOL(cpu_data);
@@ -47,6 +45,7 @@ EXPORT_SYMBOL(smp_invalidate_needed);
 EXPORT_SYMBOL_NOVERS(__lock_kernel);
 
 /* Global SMP irq stuff */
+EXPORT_SYMBOL(synchronize_irq);
 EXPORT_SYMBOL(global_irq_holder);
 EXPORT_SYMBOL(__global_cli);
 EXPORT_SYMBOL(__global_sti);
index 6ca54c7313ecd3d178f4c697c2a44c5e47e4b225..f8bc39514174d8a886094baba7c43db433bfbbb9 100644 (file)
@@ -44,8 +44,8 @@ extern volatile unsigned long smp_local_timer_ticks[1+NR_CPUS];
 
 #define CR0_NE 32
 
-static unsigned char cache_21 = 0xff;
-static unsigned char cache_A1 = 0xff;
+/* This contains the irq mask for both irq controllers */
+static unsigned long cached_irq_mask = 0xffff;
 
 unsigned int local_irq_count[NR_CPUS];
 #ifdef __SMP__
@@ -58,32 +58,33 @@ int __intel_bh_counter;
 static unsigned int int_count[NR_CPUS][NR_IRQS] = {{0},};
 #endif
 
-static inline void mask_irq(unsigned int irq_nr)
+static inline void ack_irq(int irq_nr)
 {
-       unsigned char mask;
+       if (irq_nr & 8) {
+               outb(0x20,0xA0);
+       }
+       outb(0x20,0x20);
+}
 
-       mask = 1 << (irq_nr & 7);
-       if (irq_nr < 8) {
-               cache_21 |= mask;
-               outb(cache_21,0x21);
+static inline void set_irq_mask(int irq_nr)
+{
+       if (irq_nr & 8) {
+               outb(cached_irq_mask>>8,0xA1);
        } else {
-               cache_A1 |= mask;
-               outb(cache_A1,0xA1);
+               outb(cached_irq_mask,0x21);
        }
+}              
+
+static inline void mask_irq(unsigned int irq_nr)
+{
+       set_bit(irq_nr, &cached_irq_mask);
+       set_irq_mask(irq_nr);
 }
 
 static inline void unmask_irq(unsigned int irq_nr)
 {
-       unsigned char mask;
-
-       mask = ~(1 << (irq_nr & 7));
-       if (irq_nr < 8) {
-               cache_21 &= mask;
-               outb(cache_21,0x21);
-       } else {
-               cache_A1 &= mask;
-               outb(cache_A1,0xA1);
-       }
+       clear_bit(irq_nr, &cached_irq_mask);
+       set_irq_mask(irq_nr);
 }
 
 void disable_irq(unsigned int irq_nr)
@@ -134,7 +135,8 @@ void enable_irq(unsigned int irq_nr)
 #error make irq stub building NR_IRQS dependent and remove me.
 #endif
 
-BUILD_TIMER_IRQ(FIRST,0,0x01)
+BUILD_COMMON_IRQ()
+BUILD_IRQ(FIRST,0,0x01)
 BUILD_IRQ(FIRST,1,0x02)
 BUILD_IRQ(FIRST,2,0x04)
 BUILD_IRQ(FIRST,3,0x08)
@@ -158,10 +160,6 @@ BUILD_SMP_INTERRUPT(stop_cpu_interrupt)
 BUILD_SMP_TIMER_INTERRUPT(apic_timer_interrupt)
 #endif
 
-/*
- * Pointers to the low-level handlers: first the general ones, then the
- * fast ones, then the bad ones.
- */
 static void (*interrupt[17])(void) = {
        IRQ0_interrupt, IRQ1_interrupt, IRQ2_interrupt, IRQ3_interrupt,
        IRQ4_interrupt, IRQ5_interrupt, IRQ6_interrupt, IRQ7_interrupt,
@@ -169,17 +167,6 @@ static void (*interrupt[17])(void) = {
        IRQ12_interrupt, IRQ13_interrupt, IRQ14_interrupt, IRQ15_interrupt      
 };
 
-static void (*bad_interrupt[16])(void) = {
-       bad_IRQ0_interrupt, bad_IRQ1_interrupt,
-       bad_IRQ2_interrupt, bad_IRQ3_interrupt,
-       bad_IRQ4_interrupt, bad_IRQ5_interrupt,
-       bad_IRQ6_interrupt, bad_IRQ7_interrupt,
-       bad_IRQ8_interrupt, bad_IRQ9_interrupt,
-       bad_IRQ10_interrupt, bad_IRQ11_interrupt,
-       bad_IRQ12_interrupt, bad_IRQ13_interrupt,
-       bad_IRQ14_interrupt, bad_IRQ15_interrupt
-};
-
 /*
  * Initial irq handlers.
  */
@@ -520,25 +507,34 @@ void __global_restore_flags(unsigned long flags)
  * SMP cross-CPU interrupts have their own specific
  * handlers).
  */
-asmlinkage void do_IRQ(int irq, struct pt_regs * regs)
+asmlinkage void do_IRQ(struct pt_regs regs)
 {
+       int irq = regs.orig_eax & 0xff;
        struct irqaction * action;
-       int do_random, cpu = smp_processor_id();
+       int status, cpu = smp_processor_id();
 
        irq_enter(cpu, irq);
+       mask_irq(irq);
+       ack_irq(irq);
        kstat.interrupts[irq]++;
 
+       /* Return with this interrupt masked if no action */
+       status = 0;
        action = *(irq + irq_action);
-       do_random = 0;
-       while (action) {
-               do_random |= action->flags;
-               action->handler(irq, action->dev_id, regs);
-               action = action->next;
+       if (action) {
+               do {
+                       status |= action->flags;
+                       action->handler(irq, action->dev_id, &regs);
+                       action = action->next;
+               } while (action);
+               if (status & SA_SAMPLE_RANDOM)
+                       add_interrupt_randomness(irq);
+
+               __cli();
+               unmask_irq(irq);
        }
-       if (do_random & SA_SAMPLE_RANDOM)
-               add_interrupt_randomness(irq);
-       irq_exit(cpu, irq);
 
+       irq_exit(cpu, irq);
        /*
         * This should be conditional: we should really get
         * a return code from the irq handler to tell us
@@ -635,10 +631,6 @@ void free_irq(unsigned int irq, void *dev_id)
                save_flags(flags);
                cli();
                *p = action->next;
-               if (!irq[irq_action]) {
-                       mask_irq(irq);
-                       set_intr_gate(0x20+irq,bad_interrupt[irq]);
-               }
                restore_flags(flags);
                kfree(action);
                return;
@@ -648,7 +640,7 @@ void free_irq(unsigned int irq, void *dev_id)
 
 unsigned long probe_irq_on (void)
 {
-       unsigned int i, irqs = 0, irqmask;
+       unsigned int i, irqs = 0;
        unsigned long delay;
 
        /* first, enable any unassigned irqs */
@@ -664,19 +656,17 @@ unsigned long probe_irq_on (void)
                /* about 100ms delay */;
 
        /* now filter out any obviously spurious interrupts */
-       irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21;
-       return irqs & ~irqmask;
+       return irqs & ~cached_irq_mask;
 }
 
 int probe_irq_off (unsigned long irqs)
 {
-       unsigned int i, irqmask;
+       unsigned int i;
 
-       irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21;
 #ifdef DEBUG
-       printk("probe_irq_off: irqs=0x%04lx irqmask=0x%04x\n", irqs, irqmask);
+       printk("probe_irq_off: irqs=0x%04lx irqmask=0x%04x\n", irqs, cached_irq_mask);
 #endif
-       irqs &= irqmask;
+       irqs &= cached_irq_mask;
        if (!irqs)
                return 0;
        i = ffz(~irqs);
@@ -699,7 +689,7 @@ __initfunc(void init_IRQ(void))
        outb(LATCH >> 8 , 0x40);        /* MSB */
 
        for (i = 0; i < NR_IRQS ; i++)
-               set_intr_gate(0x20+i,bad_interrupt[i]);
+               set_intr_gate(0x20+i,interrupt[i]);
 
 #ifdef __SMP__ 
        /*
index 26fc989117468e7f10c88bdab6504bb77adaa9e2..628d87068f6d73985736d322bbec73033807c4b0 100644 (file)
@@ -23,10 +23,7 @@ static inline void irq_enter(int cpu, int irq)
        hardirq_enter(cpu);
        while (test_bit(0,&global_irq_lock)) {
                if ((unsigned char) cpu == global_irq_holder) {
-                       static int printed = 0;
-                       if (!printed)
-                               printk("BAD! Local interrupts enabled, global disabled\n");
-                       printed++;
+                       printk("BAD! Local interrupts enabled, global disabled\n");
                        break;
                }
                STUCK;
@@ -36,7 +33,6 @@ static inline void irq_enter(int cpu, int irq)
 
 static inline void irq_exit(int cpu, int irq)
 {
-       __cli();
        hardirq_exit(cpu);
        release_irqlock(cpu);
 }
@@ -89,54 +85,6 @@ static inline void irq_exit(int cpu, int irq)
        "pop %es\n\t" \
        "iret"
 
-/*
- * The "inb" instructions are not needed, but seem to change the timings
- * a bit - without them it seems that the harddisk driver won't work on
- * all hardware. Arghh.
- */
-#define ACK_FIRST(mask,nr) \
-       "inb $0x21,%al\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\torb $" #mask ","SYMBOL_NAME_STR(cache_21)"\n\t" \
-       "movb "SYMBOL_NAME_STR(cache_21)",%al\n\t" \
-       "outb %al,$0x21\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\tmovb $0x20,%al\n\t" \
-       "outb %al,$0x20\n\t"
-
-#define ACK_SECOND(mask,nr) \
-       "inb $0xA1,%al\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\torb $" #mask ","SYMBOL_NAME_STR(cache_A1)"\n\t" \
-       "movb "SYMBOL_NAME_STR(cache_A1)",%al\n\t" \
-       "outb %al,$0xA1\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\tmovb $0x20,%al\n\t" \
-       "outb %al,$0xA0\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\toutb %al,$0x20\n\t"
-
-#define UNBLK_FIRST(mask) \
-       "inb $0x21,%al\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\tandb $~(" #mask "),"SYMBOL_NAME_STR(cache_21)"\n\t" \
-       "movb "SYMBOL_NAME_STR(cache_21)",%al\n\t" \
-       "outb %al,$0x21\n\t"
-
-#define UNBLK_SECOND(mask) \
-       "inb $0xA1,%al\n\t" \
-       "jmp 1f\n" \
-       "1:\tjmp 1f\n" \
-       "1:\tandb $~(" #mask "),"SYMBOL_NAME_STR(cache_A1)"\n\t" \
-       "movb "SYMBOL_NAME_STR(cache_A1)",%al\n\t" \
-       "outb %al,$0xA1\n\t"
-
 #define IRQ_NAME2(nr) nr##_interrupt(void)
 #define IRQ_NAME(nr) IRQ_NAME2(IRQ##nr)
 #define BAD_IRQ_NAME(nr) IRQ_NAME2(bad_IRQ##nr)
@@ -176,45 +124,21 @@ SYMBOL_NAME_STR(x) ":\n\t" \
 
 #endif /* __SMP__ */
 
-#define BUILD_IRQ(chip,nr,mask) \
-asmlinkage void IRQ_NAME(nr); \
-asmlinkage void BAD_IRQ_NAME(nr); \
+#define BUILD_COMMON_IRQ() \
 __asm__( \
-"\n"__ALIGN_STR"\n" \
-SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
-       "pushl $-"#nr"-2\n\t" \
+       "\n" __ALIGN_STR"\n" \
+       "common_interrupt:\n\t" \
        SAVE_ALL \
-       ACK_##chip(mask,(nr&7)) \
-       "movl %esp,%eax\n\t" \
-       "pushl %eax\n\t" \
-       "pushl $" #nr "\n\t" \
-       "call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
-       "addl $8,%esp\n\t" \
-       UNBLK_##chip(mask) \
-       "jmp ret_from_intr\n" \
-"\n"__ALIGN_STR"\n" \
-SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" \
-       SAVE_MOST \
-       ACK_##chip(mask,(nr&7)) \
-       RESTORE_MOST);
+       "pushl $ret_from_intr\n\t" \
+       "jmp "SYMBOL_NAME_STR(do_IRQ));
 
-#define BUILD_TIMER_IRQ(chip,nr,mask) \
+#define BUILD_IRQ(chip,nr,mask) \
 asmlinkage void IRQ_NAME(nr); \
-asmlinkage void BAD_IRQ_NAME(nr); \
 __asm__( \
 "\n"__ALIGN_STR"\n" \
-SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:\n\t" \
 SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
-       "pushl $-"#nr"-2\n\t" \
-       SAVE_ALL \
-       ACK_##chip(mask,(nr&7)) \
-       "movl %esp,%eax\n\t" \
-       "pushl %eax\n\t" \
-       "pushl $" #nr "\n\t" \
-       "call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
-       "addl $8,%esp\n\t" \
-       UNBLK_##chip(mask) \
-       "jmp ret_from_intr\n");
+       "pushl $"#nr"-256\n\t" \
+       "jmp common_interrupt");
 
 /*
  * x86 profiling function, SMP safe. We might want to do this in
index 2b4f06905006a1f00befd9e92fdf1f88996b7b1b..476657e2ea8b8418c8207a9698f2cafd9c231f0f 100644 (file)
@@ -678,8 +678,6 @@ __initfunc(void initialize_secondary(void))
 {
        struct thread_struct * p = &current->tss;
 
-printk("current = %p, real_esp = %p, real_eip = %p\n", current, p->esp, p->eip);
-printk("start_secondary = %p, cpu=%d\n", start_secondary, smp_processor_id());
        /*
         * We don't actually need to load the full TSS,
         * just the stack pointer and the eip.
index c32475be7dd7fbf3ba77ec9c3a98ede3dfaf751a..4216b315408b4bbf11f27fa78fb25cc05f716f87 100644 (file)
@@ -19,7 +19,10 @@ if [ "$CONFIG_SERIAL_EXTENDED" = "y" ]; then
 fi
 bool 'Non-standard serial port support' CONFIG_SERIAL_NONSTANDARD
 if [ "$CONFIG_SERIAL_NONSTANDARD" = "y" ]; then
-   tristate 'Digiboard PC/Xx Support' CONFIG_DIGI
+   tristate 'Digiboard Intelligent Async Support' CONFIG_DIGIEPCA
+   if [ "$CONFIG_DIGIEPCA" = "n" ]; then
+      tristate 'Digiboard PC/Xx Support' CONFIG_DIGI
+   fi
    tristate 'Cyclades async mux support' CONFIG_CYCLADES
    bool 'Stallion multiport serial support' CONFIG_STALDRV
    if [ "$CONFIG_STALDRV" = "y" ]; then
index cf50335d48ad72547e9f29003e5d013d269c83a9..ff9bd02130a7d2024d7f2c41671ffc805e6d1752 100644 (file)
@@ -61,6 +61,14 @@ else
   endif
 endif
 
+ifeq ($(CONFIG_DIGIEPCA),y)
+L_OBJS += epca.o
+else
+  ifeq ($(CONFIG_DIGIEPCA),m)
+  M_OBJS += epca.o
+  endif
+endif
+
 ifeq ($(CONFIG_CYCLADES),y)
 L_OBJS += cyclades.o
 else
diff --git a/drivers/char/README.epca b/drivers/char/README.epca
new file mode 100644 (file)
index 0000000..3561608
--- /dev/null
@@ -0,0 +1,506 @@
+user.doc
+Digi International driver package for the PC/Xe, PC/Xi, PC/Xr, PC/Xem as well
+the EISA and PCI variants of these boards where applicable.
+Copyright (C) 1996 Digi International.  Written by Ronnie Sanford digilnux@dgii.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 of the License, or (At your
+   option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along 
+   with this program; if not write to the Free Software Foundation, Inc., 
+   675 Mass Ave, Cambridge, MA 02139, USA. 
+
+
+This document describes the software used with the Digi/Linux driver package.
+The four user programs listed below are described in this document:
+
+  1.  digiConfig   -> Application that configures the Digi driver.
+
+  2.  digiDload    -> Application which initializes the Digi hardware.
+
+  3.  buildPCI     -> Application which provides the user a method of
+                      building device nodes for PCI devices. 
+
+  4.  ditty        -> Application which provides the user a method of
+                      configuring terminal options on Digi hardware.
+
+
+
+--------------------------------------------------------------------------
+1.  Configuring driver/kernel for Digi products
+--------------------------------------------------------------------------
+
+   The Digi driver must be configured each time Digi hardware is added
+   or removed.  There are two supported methods of doing this.  The
+   first method configures the driver dynamically at boot time but requires
+   the user to utilize the lilo loader.  This method is the preffered method
+   as it does not require the rebuilding of the kernel.  In order to use lilo
+   to configure Digi boards at boot time an appropriate append command should
+   be added to /etc/lilo.conf below the appropriate label decleration.  
+   See footer 4.  The append commands format is a string of comma seperated
+   identifiers or integers used to configure supported boards.  These six
+   values in order are:
+
+     Enable/Disable this card or Override,
+     Type of card: PC/Xe (AccelePort) (0), PC/Xeve (1), PC/Xem or PC/Xr (2),
+                   EISA/Xem (3), PC/Xe (64K) (4), PC/Xi (5).
+     Enable/Disable alternate pin arrangement,
+     Number of ports on this card,
+     I/O Port where card is configured (in HEX if using string identifiers),
+     Base of memory window (in HEX if using string identifiers) 
+
+   A sample append command is given below which if used would configure and 
+   enable a PC/Xe with 8 ports, at i/o address 200, memory address 0xd0000 
+   with alt pin turned off.  The lilo.conf file should look like this:
+
+     image = /vmlinuz
+       root = /dev/hda2
+       label = vmlinuz
+       append="digiepca=E,PC/Xe,D,8,200,D0000"
+
+   likewise the below will perform the same function:
+
+     image = /vmlinuz
+       root = /dev/hda2
+       label = vmlinuz
+       append="digiepca=1,0,0,8,512,851968"
+
+   Note:
+
+     PCI boards are auto-detected and configured (Hence their codes are
+     not given here).  Do not attempt to configure PCI boards with the lilo 
+     append command.
+
+     If configuration data has been specified by using digiConfig (Described
+     below), and you wish to override this configuration using lilo without
+     specifying a specific card (Example if there are PCI cards in the system)
+     the following override command will accomplish this:
+
+     -> append="digiepca=2"
+   
+   If lilo is not enabled, the second method of configuring Digi hardware 
+   will have to be used.  digiConfig is an application that can be used 
+   to inform the system of any additions, deletions, or modifications
+   involving Digi hardware.  To use this method the operator executes  
+   digiConfig anytime an EISA or ISA card is added that he wishes to use. 
+   This routine is also used to remove cards from the system, and to modify 
+   parameters of those cards already present in the system.  Upon being 
+   executed digiConfig modifies files accessed by the Digi driver.  To make 
+   these changes permanent; the operating system must be recompiled.  After 
+   the operating system has been recompiled and booted, the changes made with
+   digiConfig will be introduced to the user.  This program MUST be executed
+   every time Digi EISA/ISA hardware configuration changes.  Note, it is not
+   necessary to execute digiConfig in order to configure the Digi PCI cards.
+   These cards are self-identifying and will be recognized by the driver.  
+   They cannot be displayed using digiConfig nor will digiConfig build the 
+   device nodes their device nodes. See footer 1.
+
+   To execute digiConfig; simply type: digiConfig
+
+   The application will query you for the type, memory address, port 
+   address, number of ports, alt pin disposition and status of each board
+   that exist on the system.  Note, currently this driver only supports 
+   PC/Xe, PC/Xeve, PC/Xi, PC/Xr, and PC/Xem as well as their EISA and PCI 
+   implementations if applicable.  All supported cards (Other than PCI) that
+   are present should be registered via digiConfig.  See footer 2.
+
+   After all cards have been configured select exit.  The system will then
+   inform you if any changes have been made, and ask you if it is okay to
+   make these changes permanent.  If the data entered is correct, select okay.
+   Selecting cancel will prevent the changes from becoming active.  digiConfig
+   can then be re-executed to configure the system again.
+
+--------------------------------------------------------------------------
+2.  Initializing Digi hardware with digiDload
+--------------------------------------------------------------------------
+
+   digiDload is the application executed after the Digi driver has been
+   loaded.  It is responsible for initializing the hardware and leaving
+   it in a state such that the Digi board may be operated by the user.
+   The application may be placed anywhere on the path, but its related
+   support files must be located in /etc/digi.  The related files are:
+
+         sxfep.bin
+         sxbios.bin
+         xxfep.bin
+         xxbios.bin
+
+   The format for this command is "digiDload [v]".  If given the "v"
+   option turns on verbosity.  If not given the application runs in quite
+   mode.  To execute the program simply type:
+
+        digiDload 
+
+   Upon completion digiDload will generate the below message:
+
+        "digiDload complete: Card initialized"
+
+   At this point the card is configured and ready for normal usage.  See
+   technotes.doc for information on how how ports are determined and 
+   assigned.
+
+--------------------------------------------------------------------------
+3.  Build PCI device nodes with buildPCI 
+--------------------------------------------------------------------------
+
+   buildPCI is an application useful for building the necessary device nodes
+   for Digi PCI cards.  It is reccomended that this tool be used because the
+   current digiConfig application does not provide this function for PCI cards
+   (Though it does build device nodes for non-PCI cards).  To use this program
+   execute the following:first install the driver, and execute digiDload (See above).  After digiDload
+   has sucessfully loaded, execute the following:
+
+        buildPCI <arg1> <arg2>
+
+   Where arg1 is the number of ports connected to Digi cards that are not PCI 
+   (As shown by the digiConfig utility), and arg2 is the number of ports 
+   connected to Digi cards that are PCI.
+
+   Note, buildPCI only has to be ran once to build the necessary device 
+   nodes.  Though this program may be executed at anytime, we reccomend 
+   delaying execution until the first time you install the package and after 
+   digiDload has been executed.
+
+--------------------------------------------------------------------------
+4.  Setting Terminal Options with ditty
+--------------------------------------------------------------------------
+
+ditty is a utility program that sets and displays the terminal options 
+for Digi intelligent serial products.  See man ditty for detailed information.
+
+
+Footnotes:
+
+1.  The 1.2.x kernel does not provide a method of mapping the high 
+    addresses (Normally higher than RAM) associated with PCI.  For this
+    reason, this driver disables PCI support while running under the 1.2.x
+    kernels.
+
+2.  PCI cards should not and cannot be registered with digiConfig.  After
+    the driver has been loaded buildPCI may be executed to construct the 
+    necessary device nodes.  This step is not necessary for system not 
+    having Digi PCI cards.
+
+3.  This is because we forsee a time when buildPCI may auto-detect the
+    available Digi PCI cards and this would only work if the program is 
+    executed after digiDload.
+
+4.  A complete example is given in install.doc.
+
+-------------CHANGES--------------------
+
+All changes should be recorded here.  All changes should be explained in 
+verbose detail.  
+-----------------------------------------------------------------------
+Programmer            : Ronnie Sanford
+Date                  : June 1, 1996
+Description (Verbose) : Initial release of driver package.
+Files affected        : all
+Release version       : 1.0.0f  (BETA)
+-----------------------------------------------------------------------
+-----------------------------------------------------------------------
+Programmer            : Ronnie Sanford
+Date                  : August 7, 1996
+Description (Verbose) : Made several modifications to provide PCI and EISA
+                        support:
+
+                        1.  We now allocate the termios structures based on
+                            the maximum number of channels that COULD be 
+                            available to the system.  We no longer use the
+                            number of channels declared in epcaconfig.h 
+                            (NBDEVS) as the total channel number.  This is 
+                            because this value does not represent channels
+                            available to potential PCI cards.  This new 
+                            larger value is also passed back to the os in
+                            the num field of tty_driver. 
+
+                        2.  Added code to copy the previous board structure
+                            (Now called static_boards) into a new local 
+                            copy of the boards structure.  This has been 
+                            done so that PCI cards may be added to this 
+                            board array and later referenced (And even 
+                            queried.). 
+
+                        3.  Added code to pc_init that checks for supported
+                            PCI cards.  If found this code initializes a new
+                            entry into the drivers local board structure 
+                            with the PCI cards address, and type, etc..  It 
+                            also bumps the card count (num_cards).
+
+                        4.  Modified code in post_fep_init so that when this
+                            routine is executed the number of ports supported
+                            by a particular PCI card will be determined and
+                            loaded into the board structure.  It would be 
+                            much better if this code was placed in pc_init
+                            (Because we could then report to the os the true
+                            number of ports available; not just the max), but
+                            since the card has to be booted to determine the
+                            number of ports it supports, we are forced to do it
+                            after DIGI_INIT has called post_fep_init.  In the 
+                            future we may attempt to read the num ports 
+                            attached directly (address 0x1ac).
+
+                        5.  Added board types to epca.h in support of various
+                            PCI boards (Some of which do not exist yet).  
+                            Added procedures for these boards throughout the
+                            code.  Note, windowing is not necessary for PCI
+                            boards.  
+
+                        6.  Added code supporting the EISA/XEM.  This included
+                            modifying epca.h with the new board type and 
+                            adding this type into the driver.  The EISA/XEM
+                            is basically identical to the PC/XEM, other than
+                            it's base address does not have to be (And cannot
+                            be configured directly).
+
+                        7.  Modified digiConfig to prompt for EISA/XEM cards.
+                             
+Files affected        : epca.c, epca.h, digi1.h, digiConfig
+Release version       : 1.0.0g  (BETA)
+-----------------------------------------------------------------------
+-----------------------------------------------------------------------
+Programmer            : Ronnie Sanford
+Date                  : August 21, 1996
+Description (Verbose) : Made the following modifications:
+
+                        1.  A problem affecting hard flow control was found 
+                            in the termios2digi_h routine.  Specifically,
+                            when the user activated hard flow control using
+                            the CRTSCTS specification, the values used to 
+                            program hard flow control on the board were 
+                            incorrect.  The solution was to change a line
+                            that read "res |= ((ch->m_dtr) | (ch->m_rts));"
+                            to "res |= ((ch->m_cts) | (ch->m_rts));"  This 
+                            line only applies if cflag & CRTSCTS.  Special
+                            thanks to Matt Robinson (matt@mania.com.au) who
+                            found and fixed this problem.
+
+                        2.  In previous betas the cud device was set to  CLOCAL
+                            on driver boot up.  Likewise the ttyD device was
+                            set to ~CLOCAL.  This has been fixed in this driver.
+                            Now ttyD is CLOCAL and cud is ~CLOCAL.  The fix
+                            for this can be found in pc_init.
+
+                        3.  In ditty.c many changes were made to eliminate bugs
+                            and warning messages.  Two ioctl calls were eliminated
+                            as well a problem involving using the returned baud
+                            index to determine the drivers baud rate.  Newer 
+                            Linux kernels support higher baud rates by using
+                            0x1000 bit.  When the returned value (ored with
+                            0x1000) was used to reference our fbaud table a 
+                            serious memory problem occured.  This has been fixed.
+
+                        4.  Added a request_region call to post_fep_init.  This
+                            should cause the i/o ports being used to be 
+                            registered with proc.
+                        5.  Modified digiConfig to set all cud and ttyD devices
+                            to read/write all permission.
+
+                        6.  Developed a new apps called buildPCI that provides 
+                            an easy way to build device nodes for PCI cards.
+         
+                        7.  Modified user.doc and technotes.doc document the
+                            use of buildPCI.
+
+Files affected        : epca.c, ditty.c, digiConfig, user.doc, technotes.doc 
+Release version       : 1.0.0 (Official release)
+-----------------------------------------------------------------------
+Programmer            : Ronnie Sanford
+Date                  : August 21, 1996
+Description (Verbose) : Made the following modifications:
+
+                        1.  Removed code from pc_close which closes the 
+                            drivers line discipline and restores its original
+                            line discipline.  This is currently unecessary,
+                            though future fast cook enhancements may require
+                            this.
+
+                        2.  Removed code in block_til_ready that set the 
+                            asyncflags to either ASYNC_CALLOUT_ACTIVE, or
+                            ASYNC_NORMAL_ACTIVE.  This code was redundant
+                            as it already existed in block_til_ready.
+
+                        3.  Added code in block_til_ready to cause a return
+                            prior to schedule being called if the device 
+                            was a CALLOUT device.  CALLOUT devices never
+                            block on CD. (This was a serious bug that 
+                            prevented the CALLOUT devices (ttyD) from 
+                            functioning properly in some instances.
+
+                            Make a change in the MODEMCHG_IND case of doevent
+                            such that it does not require ASYNC_CALLOUT_ACTIVE
+                            or ASYNC_NORMAL_ACTIVE to be set in order to 
+                            unblock an open (Using wait_interruptible).
+
+                            Thanks to Mike McLagan (mike.mclagan@linux.org)
+                            for diagnosing and fixing this problem. 
+
+                        4.  Made changes to the disposition of CLOCAL on 
+                            both SERIAL NORMAL and CALLOUT devices.  Both
+                            device types now have CLOCAL active at default.
+                            This may be changed with a stty command.
+
+                        5.  Made changes to digiConfig such that it checks
+                            major.h (If valid) for the correct major
+                            numbers to use.
+
+Files affected        : epca.c, digiConfig 
+Release version       : 1.0.1a 
+
+
+-----------------------------------------------------------------------
+Programmer            : Ronnie Sanford
+Date                  : September 17, 1996
+Description (Verbose) : Made the following modifications:
+       
+                        1. Modified pc_open such that it no longer checks 
+                           the cflag value returned by termios2digi_c for
+                           CLOCAL.  Digi hardware does not use this value
+                           and thus termios2digi_c rightly screens this 
+                           value out.  This driver checks for CLOCAL using
+                           the drivers cflag value as known by the Linux OS.
+                           (The value passed into termios2digi_c)
+
+                        2. Modified termios2digi_c to screen out the 
+                           CBAUDEX in CBAUD.  This error caused parity to
+                           automaticaly be enabled on at higher baud rates.
+               
+
+                        3. Added the "disable_bh()" call to the shutdown
+                           subroutine.  Hopefully this will allow the driver
+                           to correctly clean up after itself when used as a
+                           module. 
+
+                        4. Added support for the PC/XI and 64K PC/XE cards.
+                           This involved primarily modifying digiDload to
+                           initialize and boot the new cards; however 
+                           driver modifications were also required to 
+                           provide the proper windowing for the newly 
+                           supported cards. (Code was also added to 
+                           determine the memory segment of the XI card as
+                           that card may have more than 64K.  Currently
+                           digiDload assumes a 64K XI card.)
+
+                        5. Added subroutine called epca_setup that can be 
+                           called during LILO boot up.  This provides the 
+                           user an easy way to change cards; without 
+                           running digiConfig and without recompiling the
+                           kernel.  Added code in pc_init and pc_open to
+                           support the epca_setup routine.  pc_init checks
+                           the liloconfig flag (Which is set by epca_setup)
+                           to determine if the driver is using the LILO 
+                           arguments.  If not pc_init loads the board data 
+                           found in epcaconfig.h; if so it DOESN'T load
+                           epcaconfig data depending on epca_setup to handle 
+                           board configuration.  pc_open has been modified 
+                           such that it checks to insure that no errors
+                           occured during the LILO boot process.  If a 
+                           user attempts to boot the driver (via. LILO)
+                           with incorrect data, the open will fail. 
+
+                        6. Modified the windowing routines pcxe_rxwinon
+                           and pcxe_txwinon routines.  A bug existed such
+                           that those routines checked to see if the rxwin 
+                           and txwin flags were reset.  If so they assumed 
+                           the board was an XI or 64K XE.  Furthermore since
+                           these flags were never initialized in our driver
+                           sometimes they were 0 and therefore caused a 
+                           memory fault (Or at least a window overrun).  This
+                           code has been removed since the pcxe shares 
+                           nothing in common with the 64K XI and XE. 
+
+                        7. Added code in pc_init to set the memory_seg for
+                           the various boards.  This code was necessary to
+                           correct a bug in the PCXE, PCXEVE code where 
+                           receive and transmit pointers were being calculated
+                           from an uninitialized variable (memory_seg). 
+
+                        8. Modified digiConfig to allow 64K PC/XI and 64K
+                           PC/XE cards to be configured.
+                           
+                        9. Made changes to support the new 2.1.x development 
+                           kernel.  In particular this required changing all
+                           references to vremap to ioremap. 
+
+                       10. Modified digiConfig such that it now generates 
+                           node names corresponding to their internal 
+                           as opposed to the label on the port itself.  Nodes
+                           (ttyD?? and cud??) now start at 0.  Example:
+                           ttyD0 and cud0 represent port 1 on any supported
+                           Digi product.  A similar change has been made
+                           in buildPCI.c. 
+
+                       12. At the early portion of post_fep_init if a PCI
+                           card is detected a warning message could be given
+                           incorrectly if 64 ports were attached to a PCI 
+                           card.  The below line :
+
+                           epcaassert(bd->numports > 64,"PCI returned a invalid number of ports"); 
+    
+                           was changed to :
+
+                           epcaassert(bd->numports <= 64,"PCI returned a invalid number of ports"); 
+
+                           Remember that epcaassert checks for NOT true.
+                           Special thanks to Daniel Taylor for fixing this.
+
+                       13. Modified the epcaparam routine.  In version 100
+                           and 101a there was a line that looked like the 
+                           below:
+
+                             if (ch->omodem != mval)
+                           
+                           The problem with this line was that the first time
+                           through omodem was not initialized.  Secondly, since
+                           many TIOC commands did not alter mval (They use
+                           a different variable) changes made by these commands
+                           could be lost.  This line was changed to:
+
+                             mval ^= ch->modemfake & (mval ^ ch->modem);
+
+                             if (ch->omodem ^ mval)
+
+                       14. Modified digiConfig in such a way that it checks 
+                           the version number of the kernel and if it finds
+                           a 2.x.x kernel or higher it reads the necessary 
+                           major numbers for cud and ttyD devices from major.h.
+                           This was also done in prior versions but these
+                           versions required a #define which identified the 
+                           kernel as a version which did not have major numbers
+                           assigned to Digi systems.  This #define is no 
+                           longer required allowing the same source tree for
+                           multiple kernel releases.
+
+                       15. Used macros to replace kernel specific calls such
+                           as put_fs_long, get_fs_long, put_user, and get_user
+                           the kernel version is now detected and the macro
+                           is defined as to correspond with the kernel it
+                           is being compiled into.  Again this was done to
+                           allow one source tree for multiple kernel releases. 
+
+                       16. Added support for the new 2.1.x development kernels
+                           to digiInstall.
+
+Files affected        : epca.c, digiConfig 
+Release version       : 1.1.0
+-----------------------------------------------------------------------
+Programmer            : Daniel Taylor
+Date                  : April 25, 1997
+Description (Verbose) : Updated driver:
+                        1.  Fixed DCD bug. (&tq_scheduler)
+                        2.  Removed BH handler code, as it was only handling
+                            hangups, and not being called for that.
+                        3.  Namespace cleanup (DIGI_TIMER2 => DIGI_TIMER)
+                        4.  Updated to 2.1.36, removed #ifdefs for earlier
+                            kernel revisions.
+Files affected        : epca.c
+Release version       : 1.1.1  (BETA)
+-----------------------------------------------------------------------
diff --git a/drivers/char/epca.c b/drivers/char/epca.c
new file mode 100644 (file)
index 0000000..3a40a05
--- /dev/null
@@ -0,0 +1,4313 @@
+/*
+
+       Copyright (C) 1996  Digi International.
+       For technical support please email digiLinux@dgii.com or
+       call Digi tech support at (612) 912-3456
+
+       Much of this design and code came from epca.c which was 
+       copyright (C) 1994, 1995 Troy De Jongh, and subsquently 
+       modified by David Nugent, Christoph Lameter, Mike McLagan. 
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+--------------------------------------------------------------------------- */
+/* See README.epca for change history --DAT*/
+
+
+#ifdef MODVERSIONS
+#define MODULE
+#endif
+
+/* -----------------------------------------------------------------------
+   This way modules should work regardless if they defined MODULE or
+   MODVERSIONS.  (MODVERSIONS is for the newer kernels ...
+-------------------------------------------------------------------------- */
+
+#ifdef MODULE
+#include <linux/config.h>
+#endif /* MODULE */
+
+#include <linux/version.h>
+
+#define NEW_MODULES
+
+#ifdef NEW_MODULES
+#ifdef MODVERSIONS
+#include <linux/modversions.h>
+#endif /* MODVERSIONS */
+#endif /* NEW_MODULES */
+
+#ifdef MODULE
+#include <linux/module.h>
+#endif /* MODULE */
+
+
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/ctype.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/segment.h>
+
+
+#include <asm/bitops.h>
+
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty_flip.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/major.h>
+#include <linux/ioport.h>
+
+#ifdef MODULE
+#ifndef NEW_MODULES
+char kernel_version[]=UTS_RELEASE;
+#endif /* NEW_MODULE */ 
+#endif /* MODULE */
+
+
+#ifdef CONFIG_PCI
+#define ENABLE_PCI
+#endif /* CONFIG_PCI */
+
+
+
+#include <asm/uaccess.h>
+#define putUser(arg1, arg2) put_user(arg1, (unsigned long *)arg2)
+#define getUser(arg1, arg2) get_user(arg1, (unsigned int *)arg2)
+
+
+
+#ifdef ENABLE_PCI
+#include <linux/bios32.h>
+#include <linux/pci.h>
+#include <linux/digiPCI.h>
+#endif /* ENABLE_PCI */
+
+#include <linux/digi1.h>
+#include <linux/digiFep1.h>
+#include <linux/epca.h>
+#include <linux/epcaconfig.h>
+
+/* ---------------------- Begin defines ------------------------ */
+
+#define VERSION            "1.1.0"     
+
+/* This major needs to be submitted to Linux to join the majors list */
+
+#define DIGIINFOMAJOR       35  /* For Digi specific ioctl */ 
+
+
+#define MIN(a,b)       ((a) < (b) ? (a) : (b))
+#define MAXCARDS 7
+#define epcaassert(x, msg)  if (!(x)) epca_error(__LINE__, msg)
+
+/* ----------------- Begin global definitions ------------------- */
+
+static char mesg[100];
+static int pc_refcount, nbdevs = 0, num_cards = 0, liloconfig = 0;
+static int digi_poller_inhibited = 1 ;
+
+static int setup_error_code = 0;
+static int invalid_lilo_config = 0;
+
+/* -----------------------------------------------------------------------
+       MAXBOARDS is typically 12, but ISA and EISA cards are restricted to 
+       7 below.
+--------------------------------------------------------------------------*/
+static struct board_info boards[7];
+
+
+/* ------------- Begin structures used for driver registeration ---------- */
+
+struct tty_driver pc_driver;
+struct tty_driver pc_callout;
+struct tty_driver pc_info;
+
+/* The below structures are used to initialize the tty_driver structures. */
+
+/*     -------------------------------------------------------------------------
+       Note : MAX_ALLOC is currently limited to 0x100.  This restriction is 
+       placed on us by Linux not Digi.
+----------------------------------------------------------------------------*/
+static struct tty_struct *pc_table[MAX_ALLOC];
+static struct termios *pc_termios[MAX_ALLOC];
+static struct termios *pc_termios_locked[MAX_ALLOC];
+
+
+/* ------------------ Begin Digi specific structures -------------------- */
+
+/* ------------------------------------------------------------------------
+       digi_channels represents an array of structures that keep track of
+       each channel of the Digi product.  Information such as transmit and
+       receive pointers, termio data, and signal definitions (DTR, CTS, etc ...)
+       are stored here.  This structure is NOT used to overlay the cards 
+       physical channel structure.
+-------------------------------------------------------------------------- */
+  
+static struct channel digi_channels[MAX_ALLOC];
+
+/* ------------------------------------------------------------------------
+       card_ptr is an array used to hold the address of the
+       first channel structure of each card.  This array will hold
+       the addresses of various channels located in digi_channels.
+-------------------------------------------------------------------------- */
+static struct channel *card_ptr[MAXCARDS];
+
+/* ---------------------- Begin function prototypes --------------------- */
+
+/* ----------------------------------------------------------------------
+       Begin generic memory functions.  These functions will be alias
+       (point at) more specific functions dependant on the board being
+       configured.
+----------------------------------------------------------------------- */
+       
+
+#ifdef MODULE
+int                init_module(void);
+void               cleanup_module(void);
+#endif /* MODULE */
+
+static inline void memwinon(struct board_info *b, unsigned int win);
+static inline void memwinoff(struct board_info *b, unsigned int win);
+static inline void globalwinon(struct channel *ch);
+static inline void rxwinon(struct channel *ch);
+static inline void txwinon(struct channel *ch);
+static inline void memoff(struct channel *ch);
+static inline void assertgwinon(struct channel *ch);
+static inline void assertmemoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for cx_like products --- */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxem_globalwinon(struct channel *ch);
+static inline void pcxem_rxwinon(struct channel *ch);
+static inline void pcxem_txwinon(struct channel *ch);
+static inline void pcxem_memoff(struct channel *ch);
+
+/* ------ Begin more 'specific' memory functions for the pcxe ------- */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxe_globalwinon(struct channel *ch);
+static inline void pcxe_rxwinon(struct channel *ch);
+static inline void pcxe_txwinon(struct channel *ch);
+static inline void pcxe_memoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for the pc64xe and pcxi ---- */
+/* Note : pc64xe and pcxi share the same windowing routines */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxi_globalwinon(struct channel *ch);
+static inline void pcxi_rxwinon(struct channel *ch);
+static inline void pcxi_txwinon(struct channel *ch);
+static inline void pcxi_memoff(struct channel *ch);
+
+/* - Begin 'specific' do nothing memory functions needed for some cards - */
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win);
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win);
+static inline void dummy_globalwinon(struct channel *ch);
+static inline void dummy_rxwinon(struct channel *ch);
+static inline void dummy_txwinon(struct channel *ch);
+static inline void dummy_memoff(struct channel *ch);
+static inline void dummy_assertgwinon(struct channel *ch);
+static inline void dummy_assertmemoff(struct channel *ch);
+
+/* ------------------- Begin declare functions ----------------------- */
+
+static inline struct channel *verifyChannel(register struct tty_struct *);
+static inline void pc_sched_event(struct channel *, int);
+static void epca_error(int, char *);
+static void pc_close(struct tty_struct *, struct file *);
+static void shutdown(struct channel *);
+static void pc_hangup(struct tty_struct *);
+static void pc_put_char(struct tty_struct *, unsigned char);
+static int pc_write_room(struct tty_struct *);
+static int pc_chars_in_buffer(struct tty_struct *);
+static void pc_flush_buffer(struct tty_struct *);
+static void pc_flush_chars(struct tty_struct *);
+static int block_til_ready(struct tty_struct *, struct file *,
+                           struct channel *);
+static int pc_open(struct tty_struct *, struct file *);
+static void post_fep_init(unsigned int crd);
+static void epcapoll(unsigned long);
+static void doevent(int);
+static void fepcmd(struct channel *, int, int, int, int, int);
+static unsigned termios2digi_h(struct channel *ch, unsigned);
+static unsigned termios2digi_i(struct channel *ch, unsigned);
+static unsigned termios2digi_c(struct channel *ch, unsigned);
+static void epcaparam(struct tty_struct *, struct channel *);
+static void receive_data(struct channel *);
+static int pc_ioctl(struct tty_struct *, struct file *,
+                    unsigned int, unsigned long);
+static void pc_set_termios(struct tty_struct *, struct termios *);
+static void do_softint(void *);
+static void pc_stop(struct tty_struct *);
+static void pc_start(struct tty_struct *);
+static void pc_throttle(struct tty_struct * tty);
+static void pc_unthrottle(struct tty_struct *tty);
+static void digi_send_break(struct channel *ch, int msec);
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch);
+void epca_setup(char *, int *);
+void console_print(const char *);
+
+static int get_termio(struct tty_struct *, struct termio *);
+static int pc_write(struct tty_struct *, int, const unsigned char *, int);
+int pc_init(void);
+
+#ifdef ENABLE_PCI
+static int init_PCI(int);
+static int get_PCI_configuration(char, char, unsigned int *, unsigned int *,
+                                             unsigned int *, unsigned int *,
+                                             unsigned int *, unsigned int *);
+#endif /* ENABLE_PCI */
+
+
+/* ------------------------------------------------------------------
+       Table of functions for each board to handle memory.  Mantaining 
+       parallelism is a *very* good idea here.  The idea is for the 
+       runtime code to blindly call these functions, not knowing/caring    
+       about the underlying hardware.  This stuff should contain no
+       conditionals; if more functionality is needed a different entry
+       should be established.  These calls are the interface calls and 
+       are the only functions that should be accessed.  Anyone caught
+       making direct calls deserves what they get.
+-------------------------------------------------------------------- */
+
+static inline void memwinon(struct board_info *b, unsigned int win)
+{
+       (b->memwinon)(b, win);
+}
+
+static inline void memwinoff(struct board_info *b, unsigned int win)
+{
+       (b->memwinoff)(b, win);
+}
+
+static inline void globalwinon(struct channel *ch)
+{
+       (ch->board->globalwinon)(ch);
+}
+
+static inline void rxwinon(struct channel *ch)
+{
+       (ch->board->rxwinon)(ch);
+}
+
+static inline void txwinon(struct channel *ch)
+{
+       (ch->board->txwinon)(ch);
+}
+
+static inline void memoff(struct channel *ch)
+{
+       (ch->board->memoff)(ch);
+}
+static inline void assertgwinon(struct channel *ch)
+{
+       (ch->board->assertgwinon)(ch);
+}
+
+static inline void assertmemoff(struct channel *ch)
+{
+       (ch->board->assertmemoff)(ch);
+}
+
+/* ---------------------------------------------------------
+       PCXEM windowing is the same as that used in the PCXR 
+       and CX series cards.
+------------------------------------------------------------ */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win)
+{
+        outb_p(FEPWIN|win, (int)b->port + 1);
+}
+
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win)
+{
+       outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxem_globalwinon(struct channel *ch)
+{
+       outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_rxwinon(struct channel *ch)
+{
+       outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_txwinon(struct channel *ch)
+{
+       outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_memoff(struct channel *ch)
+{
+       outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ----------------- Begin pcxe memory window stuff ------------------ */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win)
+{
+               outb_p(FEPWIN | win, (int)b->port + 1);
+}
+
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win)
+{
+       outb_p(inb((int)b->port) & ~FEPMEM,
+                  (int)b->port + 1);
+       outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxe_globalwinon(struct channel *ch)
+{
+       outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_rxwinon(struct channel *ch)
+{
+               outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_txwinon(struct channel *ch)
+{
+               outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_memoff(struct channel *ch)
+{
+       outb_p(0, (int)ch->board->port);
+       outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ------------- Begin pc64xe and pcxi memory window stuff -------------- */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win)
+{
+               outb_p(inb((int)b->port) | FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win)
+{
+       outb_p(inb((int)b->port) & ~FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_globalwinon(struct channel *ch)
+{
+       outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_rxwinon(struct channel *ch)
+{
+               outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_txwinon(struct channel *ch)
+{
+               outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_memoff(struct channel *ch)
+{
+       outb_p(0, (int)ch->board->port);
+}
+
+static inline void pcxi_assertgwinon(struct channel *ch)
+{
+       epcaassert(inb((int)ch->board->port) & FEPMEM, "Global memory off");
+}
+
+static inline void pcxi_assertmemoff(struct channel *ch)
+{
+       epcaassert(!(inb((int)ch->board->port) & FEPMEM), "Memory on");
+}
+
+
+/* ----------------------------------------------------------------------
+       Not all of the cards need specific memory windowing routines.  Some
+       cards (Such as PCI) needs no windowing routines at all.  We provide
+       these do nothing routines so that the same code base can be used.
+       The driver will ALWAYS call a windowing routine if it thinks it needs
+       to; regardless of the card.  However, dependant on the card the routine
+       may or may not do anything.
+---------------------------------------------------------------------------*/
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_globalwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_rxwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_txwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_memoff(struct channel *ch)
+{
+}
+
+static inline void dummy_assertgwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_assertmemoff(struct channel *ch)
+{
+}
+
+/* ----------------- Begin verifyChannel function ----------------------- */
+static inline struct channel *verifyChannel(register struct tty_struct *tty)
+{ /* Begin verifyChannel */
+
+       /* --------------------------------------------------------------------
+               This routine basically provides a sanity check.  It insures that
+               the channel returned is within the proper range of addresses as
+               well as properly initialized.  If some bogus info gets passed in
+               through tty->driver_data this should catch it.
+       --------------------------------------------------------------------- */
+
+       if (tty) 
+       { /* Begin if tty */
+
+               register struct channel *ch = (struct channel *)tty->driver_data;
+
+               if ((ch >= &digi_channels[0]) && (ch < &digi_channels[nbdevs])) 
+               {
+                       if (ch->magic == EPCA_MAGIC)
+                               return ch;
+               }
+
+       } /* End if tty */
+
+       /* Else return a NULL for invalid */
+       return NULL;
+
+} /* End verifyChannel */
+
+/* ------------------ Begin pc_sched_event ------------------------- */
+
+static inline void pc_sched_event(struct channel *ch, int event)
+{ /* Begin pc_sched_event */
+
+
+       /* ----------------------------------------------------------------------
+               We call this to schedule interrupt processing on some event.  The 
+               kernel sees our request and calls the related routine in OUR driver.
+       -------------------------------------------------------------------------*/
+
+       ch->event |= 1 << event;
+       queue_task(&ch->tqueue, &tq_scheduler);
+
+
+} /* End pc_sched_event */
+
+/* ------------------ Begin epca_error ------------------------- */
+
+static void epca_error(int line, char *msg)
+{ /* Begin epca_error */
+
+       printk(KERN_ERR "epca_error (Digi): line = %d %s\n",line,msg);
+       return;
+
+} /* End epca_error */
+
+/* ------------------ Begin pc_close ------------------------- */
+static void pc_close(struct tty_struct * tty, struct file * filp)
+{ /* Begin pc_close */
+
+       struct channel *ch;
+       unsigned long flags;
+
+       if (tty->driver.subtype == SERIAL_TYPE_INFO) 
+       {
+               return;
+       }
+
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if ch != NULL */
+
+               save_flags(flags);
+               cli();
+
+               if (tty_hung_up_p(filp)) 
+               {
+                       restore_flags(flags);
+                       return;
+               }
+
+               /* Check to see if the channel is open more than once */
+               if (ch->count-- > 1) 
+               { /* Begin channel is open more than once */
+
+                       /* -------------------------------------------------------------
+                               Return without doing anything.  Someone might still be using
+                               the channel.
+                       ---------------------------------------------------------------- */
+
+                       restore_flags(flags);
+                       return;
+               } /* End channel is open more than once */
+
+               /* Port open only once go ahead with shutdown & reset */
+
+               if (ch->count < 0) 
+               {
+                       ch->count = 0;
+               }
+
+               /* ---------------------------------------------------------------
+                       Let the rest of the driver know the channel is being closed.
+                       This becomes important if an open is attempted before close 
+                       is finished.
+               ------------------------------------------------------------------ */
+
+               ch->asyncflags |= ASYNC_CLOSING;
+       
+               /* -------------------------------------------------------------
+                       Save the termios structure, since this port may have
+                       separate termios for callout and dialin.
+               --------------------------------------------------------------- */
+
+               if (ch->asyncflags & ASYNC_NORMAL_ACTIVE)
+                       ch->normal_termios = *tty->termios;
+
+               if (ch->asyncflags & ASYNC_CALLOUT_ACTIVE)
+                       ch->callout_termios = *tty->termios;
+
+               tty->closing = 1;
+
+               if (ch->asyncflags & ASYNC_INITIALIZED) 
+               {
+                       /* Setup an event to indicate when the transmit buffer empties */
+                       setup_empty_event(tty, ch);             
+                       tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+               }
+       
+               if (tty->driver.flush_buffer)
+                       tty->driver.flush_buffer(tty);
+
+               if (tty->ldisc.flush_buffer)
+                       tty->ldisc.flush_buffer(tty);
+
+               shutdown(ch);
+               tty->closing = 0;
+               ch->event = 0;
+               ch->tty = NULL;
+
+               if (ch->blocked_open) 
+               { /* Begin if blocked_open */
+
+                       if (ch->close_delay) 
+                       {
+                               current->state = TASK_INTERRUPTIBLE;
+                               current->timeout = jiffies + ch->close_delay;
+                               schedule();
+                       }
+
+                       wake_up_interruptible(&ch->open_wait);
+
+               } /* End if blocked_open */
+
+               ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED | 
+                                     ASYNC_CALLOUT_ACTIVE | ASYNC_CLOSING);
+               wake_up_interruptible(&ch->close_wait);
+
+#ifdef MODULE
+               MOD_DEC_USE_COUNT;
+#endif
+
+               restore_flags(flags);
+
+       } /* End if ch != NULL */
+
+} /* End pc_close */ 
+
+/* ------------------ Begin shutdown  ------------------------- */
+
+static void shutdown(struct channel *ch)
+{ /* Begin shutdown */
+
+       unsigned long flags;
+       struct tty_struct *tty;
+       volatile struct board_chan *bc;
+
+       if (!(ch->asyncflags & ASYNC_INITIALIZED)) 
+               return;
+
+       save_flags(flags);
+       cli();
+       globalwinon(ch);
+
+       bc = ch->brdchan;
+
+       /* ------------------------------------------------------------------
+               In order for an event to be generated on the receipt of data the
+               idata flag must be set. Since we are shutting down, this is not 
+               necessary clear this flag.
+       --------------------------------------------------------------------- */ 
+
+       if (bc)
+               bc->idata = 0;
+
+       tty = ch->tty;
+
+       /* ----------------------------------------------------------------
+          If we're a modem control device and HUPCL is on, drop RTS & DTR.
+       ------------------------------------------------------------------ */
+
+       if (tty->termios->c_cflag & HUPCL) 
+       {
+               ch->omodem &= ~(ch->m_rts | ch->m_dtr);
+               fepcmd(ch, SETMODEM, 0, ch->m_dtr | ch->m_rts, 10, 1);
+       }
+
+       memoff(ch);
+
+       /* ------------------------------------------------------------------
+               The channel has officialy been closed.  The next time it is opened
+               it will have to reinitialized.  Set a flag to indicate this.
+       ---------------------------------------------------------------------- */
+
+       /* Prevent future Digi programmed interrupts from coming active */
+
+       ch->asyncflags &= ~ASYNC_INITIALIZED;
+       restore_flags(flags);
+
+} /* End shutdown */
+
+/* ------------------ Begin pc_hangup  ------------------------- */
+
+static void pc_hangup(struct tty_struct *tty)
+{ /* Begin pc_hangup */
+
+       struct channel *ch;
+       
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if ch != NULL */
+
+               unsigned long flags;
+
+               save_flags(flags);
+               cli();
+               if (tty->driver.flush_buffer)
+                       tty->driver.flush_buffer(tty);
+
+               if (tty->ldisc.flush_buffer)
+                       tty->ldisc.flush_buffer(tty);
+
+               shutdown(ch);
+
+#ifdef MODULE
+               if (ch->count)
+                       MOD_DEC_USE_COUNT;
+#endif /* MODULE */
+               
+
+               ch->tty   = NULL;
+               ch->event = 0;
+               ch->count = 0;
+               restore_flags(flags);
+               ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED | ASYNC_CALLOUT_ACTIVE);
+               wake_up_interruptible(&ch->open_wait);
+
+       } /* End if ch != NULL */
+
+} /* End pc_hangup */
+
+/* ------------------ Begin pc_write  ------------------------- */
+
+static int pc_write(struct tty_struct * tty, int from_user,
+                    const unsigned char *buf, int bytesAvailable)
+{ /* Begin pc_write */
+
+       register unsigned int head, tail;
+       register int dataLen;
+       register int size;
+       register int amountCopied;
+
+
+       struct channel *ch;
+       unsigned long flags;
+       int remain;
+       volatile struct board_chan *bc;
+
+
+       /* ----------------------------------------------------------------
+               pc_write is primarily called directly by the kernel routine
+               tty_write (Though it can also be called by put_char) found in
+               tty_io.c.  pc_write is passed a line discipline buffer where 
+               the data to be written out is stored.  The line discipline 
+               implementation itself is done at the kernel level and is not 
+               brought into the driver.  
+       ------------------------------------------------------------------- */
+
+       /* Stop users from hurting themselves on control minor */
+
+       if (tty->driver.subtype == SERIAL_TYPE_INFO) 
+       {
+               return (0) ;
+       }
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) == NULL)
+               return 0;
+
+       /* Make a pointer to the channel data structure found on the board. */
+
+       bc   = ch->brdchan;
+       size = ch->txbufsize;
+
+       if (from_user) 
+       { /* Begin from_user */
+
+               save_flags(flags);
+               cli();
+
+               globalwinon(ch);
+
+               /* -----------------------------------------------------------------    
+                       Anding against size will wrap the pointer back to its begining 
+                       position if it is necessary.  This will only work if size is
+                       a power of 2 which should always be the case.  Size is determined 
+                       by the cards on board FEP/OS.
+               -------------------------------------------------------------------- */ 
+
+               /* head refers to the next empty location in which data may be stored */ 
+
+               head = bc->tin & (size - 1);
+
+               /* tail refers to the next data byte to be transmitted */ 
+
+               tail = bc->tout;
+
+               /* Consider changing this to a do statement to make sure */
+
+               if (tail != bc->tout)
+                       tail = bc->tout;
+
+               /* ------------------------------------------------------------------   
+                       Anding against size will wrap the pointer back to its begining 
+                       position if it is necessary.  This will only work if size is
+                       a power of 2 which should always be the case.  Size is determined 
+                       by the cards on board FEP/OS.
+               --------------------------------------------------------------------- */        
+
+               tail &= (size - 1);
+
+               /* -----------------------------------------------------------------
+                       Two situations can affect how space in the transmit buffer
+                       is calculated.  You can have a situation where the transmit
+                       in pointer (tin) head has wrapped around and actually has a 
+                       lower address than the transmit out pointer (tout) tail; or
+                       the transmit in pointer (tin) head will not be wrapped around
+                       yet, and have a higher address than the transmit out pointer
+                       (tout) tail.  Obviously space available in the transmit buffer
+                       is calculated differently for each case.
+
+                       Example 1:
+                       
+                       Consider a 10 byte buffer where head is a pointer to the next
+                       empty location in the buffer and tail is a pointer to the next 
+                       byte to transmit.  In this example head will not have wrapped 
+                       around and therefore head > tail.  
+
+                       0      1      2      3      4      5      6      7      8      9   
+                               tail                               head
+
+                       The above diagram shows that buffer locations 2,3,4,5 and 6 have
+                       data to be transmited, while head points at the next empty
+                       location.  To calculate how much space is available first we have
+                       to determine if the head pointer (tin) has wrapped.  To do this
+                       compare the head pointer to the tail pointer,  If head is equal
+                       or greater than tail; then it has not wrapped; and the space may
+                       be calculated by subtracting tail from head and then subtracting
+                       that value from the buffers size.  A one is subtracted from the
+                       new value to indicate how much space is available between the 
+                       head pointer and end of buffer; as well as the space between the
+                       begining of the buffer and the tail.  If the head is not greater
+                       or equal to the tail this indicates that the head has wrapped
+                       around to the begining of the buffer.  To calculate the space 
+                       available in this case simply subtract head from tail.  This new 
+                       value minus one represents the space available betwwen the head 
+                       and tail pointers.  In this example head (7) is greater than tail (2)
+                       and therefore has not wrapped around.  We find the space by first
+                       subtracting tail from head (7-2=5).  We then subtract this value
+                       from the buffer size of ten and subtract one (10-5-1=4).  The space
+                       remaining is 4 bytes. 
+
+                       Example 2:
+                       
+                       Consider a 10 byte buffer where head is a pointer to the next
+                       empty location in the buffer and tail is a pointer to the next 
+                       byte to transmit.  In this example head will wrapped around and 
+                       therefore head < tail.  
+
+                       0      1      2      3      4      5      6      7      8      9   
+                               head                               tail
+
+                       The above diagram shows that buffer locations 7,8,9,0 and 1 have
+                       data to be transmited, while head points at the next empty
+                       location.  To find the space available we compare head to tail.  If
+                       head is not equal to, or greater than tail this indicates that head
+                       has wrapped around. In this case head (2) is not equal to, or
+                       greater than tail (7) and therefore has already wrapped around.  To
+                       calculate the available space between the two pointers we subtract
+                       head from tail (7-2=5).  We then subtract one from this new value
+                       (5-1=4).  We have 5 bytes empty remaining in the buffer.  Unlike the
+                       previous example these five bytes are located between the head and
+                       tail pointers. 
+
+               ----------------------------------------------------------------------- */
+
+               dataLen = (head >= tail) ? (size - (head - tail) - 1) : (tail - head - 1);
+
+               /* ----------------------------------------------------------------------
+                       In this case bytesAvailable has been passed into pc_write and
+                       represents the amount of data that needs to be written.  dataLen
+                       represents the amount of space available on the card.  Whichever
+                       value is smaller will be the amount actually written. 
+                       bytesAvailable will then take on this newly calculated value.
+               ---------------------------------------------------------------------- */
+
+               bytesAvailable = MIN(dataLen, bytesAvailable);
+
+               /* First we read the data in from the file system into a temp buffer */
+
+               if (bytesAvailable) 
+               { /* Begin bytesAvailable */
+
+                       /* Can the user buffer be accessed at the moment ? */
+                       if (verify_area(VERIFY_READ, (char*)buf, bytesAvailable))
+                               bytesAvailable = 0; /* Can't do; try again later */
+                       else  /* Evidently it can, began transmission */
+                       { /* Begin if area verified */
+                               /* ---------------------------------------------------------------
+                                       The below function reads data from user memory.  This routine
+                                       can not be used in an interrupt routine. (Because it may 
+                                       generate a page fault)  It can only be called while we can the
+                                       user context is accessible. 
+
+                                       The prototype is :
+                                       inline void copy_from_user(void * to, const void * from,
+                                                                 unsigned long count);
+
+                                       You must include <asm/segment.h>
+                                       I also think (Check hackers guide) that optimization must
+                                       be turned ON.  (Which sounds strange to me...)
+       
+                                       Remember copy_from_user WILL generate a page fault if the
+                                       user memory being accessed has been swapped out.  This can
+                                       cause this routine to temporarily sleep while this page
+                                       fault is occuring.
+                               
+                               ----------------------------------------------------------------- */
+
+                               copy_from_user(ch->tmp_buf, buf, bytesAvailable);
+
+                       } /* End if area verified */
+
+               } /* End bytesAvailable */
+
+               /* ------------------------------------------------------------------ 
+                       Set buf to this address for the moment.  tmp_buf was allocated in
+                       post_fep_init.
+               --------------------------------------------------------------------- */
+               buf = ch->tmp_buf;
+               memoff(ch);
+               restore_flags(flags);
+
+       } /* End from_user */
+
+       /* All data is now local */
+
+       amountCopied = 0;
+       save_flags(flags);
+       cli();
+
+       globalwinon(ch);
+
+       head = bc->tin & (size - 1);
+       tail = bc->tout;
+
+       if (tail != bc->tout)
+               tail = bc->tout;
+       tail &= (size - 1);
+
+       /*      If head >= tail, head has not wrapped around. */ 
+       if (head >= tail) 
+       { /* Begin head has not wrapped */
+
+               /* ---------------------------------------------------------------
+                       remain (much like dataLen above) represents the total amount of
+                       space available on the card for data.  Here dataLen represents
+                       the space existing between the head pointer and the end of 
+                       buffer.  This is important because a memcpy cannot be told to
+                       automatically wrap around when it hits the buffer end.
+               ------------------------------------------------------------------ */ 
+
+               dataLen = size - head;
+               remain = size - (head - tail) - 1;
+
+       } /* End head has not wrapped */
+       else 
+       { /* Begin head has wrapped around */
+
+               remain = tail - head - 1;
+               dataLen = remain;
+
+       } /* End head has wrapped around */
+
+       /* -------------------------------------------------------------------
+                       Check the space on the card.  If we have more data than 
+                       space; reduce the amount of data to fit the space.
+       ---------------------------------------------------------------------- */
+
+       bytesAvailable = MIN(remain, bytesAvailable);
+
+       txwinon(ch);
+       while (bytesAvailable > 0) 
+       { /* Begin while there is data to copy onto card */
+
+               /* -----------------------------------------------------------------
+                       If head is not wrapped, the below will make sure the first 
+                       data copy fills to the end of card buffer.
+               ------------------------------------------------------------------- */
+
+               dataLen = MIN(bytesAvailable, dataLen);
+               memcpy(ch->txptr + head, buf, dataLen);
+               buf += dataLen;
+               head += dataLen;
+               amountCopied += dataLen;
+               bytesAvailable -= dataLen;
+
+               if (head >= size) 
+               {
+                       head = 0;
+                       dataLen = tail;
+               }
+
+       } /* End while there is data to copy onto card */
+
+       ch->statusflags |= TXBUSY;
+       globalwinon(ch);
+       bc->tin = head;
+
+       if ((ch->statusflags & LOWWAIT) == 0) 
+       {
+               ch->statusflags |= LOWWAIT;
+               bc->ilow = 1;
+       }
+       memoff(ch);
+       restore_flags(flags);
+
+       return(amountCopied);
+
+} /* End pc_write */
+
+/* ------------------ Begin pc_put_char  ------------------------- */
+
+static void pc_put_char(struct tty_struct *tty, unsigned char c)
+{ /* Begin pc_put_char */
+
+   
+       pc_write(tty, 0, &c, 1);
+       return;
+
+} /* End pc_put_char */
+
+/* ------------------ Begin pc_write_room  ------------------------- */
+
+static int pc_write_room(struct tty_struct *tty)
+{ /* Begin pc_write_room */
+
+       int remain;
+       struct channel *ch;
+       unsigned long flags;
+       unsigned int head, tail;
+       volatile struct board_chan *bc;
+
+       remain = 0;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       {
+               save_flags(flags);
+               cli();
+               globalwinon(ch);
+
+               bc   = ch->brdchan;
+               head = bc->tin & (ch->txbufsize - 1);
+               tail = bc->tout;
+
+               if (tail != bc->tout)
+                       tail = bc->tout;
+               /* Wrap tail if necessary */
+               tail &= (ch->txbufsize - 1);
+
+               if ((remain = tail - head - 1) < 0 )
+                       remain += ch->txbufsize;
+
+               if (remain && (ch->statusflags & LOWWAIT) == 0) 
+               {
+                       ch->statusflags |= LOWWAIT;
+                       bc->ilow = 1;
+               }
+               memoff(ch);
+               restore_flags(flags);
+       }
+
+       /* Return how much room is left on card */
+       return remain;
+
+} /* End pc_write_room */
+
+/* ------------------ Begin pc_chars_in_buffer  ---------------------- */
+
+static int pc_chars_in_buffer(struct tty_struct *tty)
+{ /* Begin pc_chars_in_buffer */
+
+       int chars;
+       unsigned int ctail, head, tail;
+       int remain;
+       unsigned long flags;
+       struct channel *ch;
+       volatile struct board_chan *bc;
+
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) == NULL)
+               return(0);
+
+       save_flags(flags);
+       cli();
+       globalwinon(ch);
+
+       bc = ch->brdchan;
+       tail = bc->tout;
+       head = bc->tin;
+       ctail = ch->mailbox->cout;
+
+       if (tail == head && ch->mailbox->cin == ctail && bc->tbusy == 0)
+               chars = 0;
+       else 
+       { /* Begin if some space on the card has been used */
+
+               head = bc->tin & (ch->txbufsize - 1);
+               tail &= (ch->txbufsize - 1);
+
+               /*  --------------------------------------------------------------
+                       The logic here is basically opposite of the above pc_write_room
+                       here we are finding the amount of bytes in the buffer filled.
+                       Not the amount of bytes empty.
+               ------------------------------------------------------------------- */
+
+               if ((remain = tail - head - 1) < 0 )
+                       remain += ch->txbufsize;
+
+               chars = (int)(ch->txbufsize - remain);
+
+               /* -------------------------------------------------------------  
+                       Make it possible to wakeup anything waiting for output
+                       in tty_ioctl.c, etc.
+
+                       If not already set.  Setup an event to indicate when the
+                       transmit buffer empties 
+               ----------------------------------------------------------------- */
+
+               if (!(ch->statusflags & EMPTYWAIT))
+                       setup_empty_event(tty,ch);
+
+       } /* End if some space on the card has been used */
+
+       memoff(ch);
+       restore_flags(flags);
+
+       /* Return number of characters residing on card. */
+       return(chars);
+
+} /* End pc_chars_in_buffer */
+
+/* ------------------ Begin pc_flush_buffer  ---------------------- */
+
+static void pc_flush_buffer(struct tty_struct *tty)
+{ /* Begin pc_flush_buffer */
+
+       unsigned int tail;
+       unsigned long flags;
+       struct channel *ch;
+       volatile struct board_chan *bc;
+
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) == NULL)
+               return;
+
+       save_flags(flags);
+       cli();
+
+       globalwinon(ch);
+
+       bc   = ch->brdchan;
+       tail = bc->tout;
+
+       /* Have FEP move tout pointer; effectively flushing transmit buffer */
+
+       fepcmd(ch, STOUT, (unsigned) tail, 0, 0, 0);
+
+       memoff(ch);
+       restore_flags(flags);
+
+       wake_up_interruptible(&tty->write_wait);
+       if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
+               (tty->ldisc.write_wakeup)(tty);
+
+} /* End pc_flush_buffer */
+
+/* ------------------ Begin pc_flush_chars  ---------------------- */
+
+static void pc_flush_chars(struct tty_struct *tty)
+{ /* Begin pc_flush_chars */
+
+       struct channel * ch;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       {
+               unsigned long flags;
+
+               save_flags(flags);
+               cli();
+
+               /* ----------------------------------------------------------------
+                       If not already set and the transmitter is busy setup an event
+                       to indicate when the transmit empties.
+               ------------------------------------------------------------------- */
+
+               if ((ch->statusflags & TXBUSY) && !(ch->statusflags & EMPTYWAIT))
+                       setup_empty_event(tty,ch);
+
+               restore_flags(flags);
+       }
+
+} /* End pc_flush_chars */
+
+/* ------------------ Begin block_til_ready  ---------------------- */
+
+static int block_til_ready(struct tty_struct *tty, 
+                           struct file *filp, struct channel *ch)
+{ /* Begin block_til_ready */
+
+       struct wait_queue wait = {current, NULL};
+       int     retval, do_clocal = 0;
+       unsigned long flags;
+
+
+       if (tty_hung_up_p(filp))
+       {
+               if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+                       retval = -EAGAIN;
+               else
+                       retval = -ERESTARTSYS;  
+               return(retval);
+       }
+
+       /* ----------------------------------------------------------------- 
+               If the device is in the middle of being closed, then block
+               until it's done, and then try again.
+       -------------------------------------------------------------------- */
+       if (ch->asyncflags & ASYNC_CLOSING) 
+       {
+               interruptible_sleep_on(&ch->close_wait);
+
+               if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+                       return -EAGAIN;
+               else
+                       return -ERESTARTSYS;
+       }
+
+       /* ----------------------------------------------------------------- 
+          If this is a callout device, then just make sure the normal
+          device isn't being used.
+       -------------------------------------------------------------------- */
+
+       if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) 
+       { /* A cud device has been opened */
+               if (ch->asyncflags & ASYNC_NORMAL_ACTIVE)
+                       return -EBUSY;
+
+               if ((ch->asyncflags & ASYNC_CALLOUT_ACTIVE) &&
+                   (ch->asyncflags & ASYNC_SESSION_LOCKOUT) &&
+                   (ch->session != current->session))
+                   return -EBUSY;
+
+               if ((ch->asyncflags & ASYNC_CALLOUT_ACTIVE) &&
+                   (ch->asyncflags & ASYNC_PGRP_LOCKOUT) &&
+                   (ch->pgrp != current->pgrp))
+                   return -EBUSY;
+               ch->asyncflags |= ASYNC_CALLOUT_ACTIVE;
+
+               return 0;
+       } /* End a cud device has been opened */
+
+       if (filp->f_flags & O_NONBLOCK) 
+       {
+               /* ----------------------------------------------------------------- 
+                If non-blocking mode is set, then make the check up front
+                and then exit.
+               -------------------------------------------------------------------- */
+
+               if (ch->asyncflags & ASYNC_CALLOUT_ACTIVE)
+                       return -EBUSY;
+
+               ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+               return 0;
+       }
+
+
+       if (ch->asyncflags & ASYNC_CALLOUT_ACTIVE) 
+       {
+               if (ch->normal_termios.c_cflag & CLOCAL)
+                       do_clocal = 1;
+       }
+       else 
+       {
+               if (tty->termios->c_cflag & CLOCAL)
+                       do_clocal = 1;
+       }
+       
+   /* Block waiting for the carrier detect and the line to become free */
+       
+       retval = 0;
+       add_wait_queue(&ch->open_wait, &wait);
+       save_flags(flags);
+       cli();
+
+
+       /* We dec count so that pc_close will know when to free things */
+       if (!tty_hung_up_p(filp))
+               ch->count--;
+
+       restore_flags(flags);
+
+       ch->blocked_open++;
+
+       while(1) 
+       { /* Begin forever while  */
+
+               current->state = TASK_INTERRUPTIBLE;
+
+               if (tty_hung_up_p(filp) ||
+                   !(ch->asyncflags & ASYNC_INITIALIZED)) 
+               {
+                       if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+                               retval = -EAGAIN;
+                       else
+                               retval = -ERESTARTSYS;  
+                       break;
+               }
+
+               if (!(ch->asyncflags & ASYNC_CLOSING) && 
+                   !(ch->asyncflags & ASYNC_CALLOUT_ACTIVE) &&
+                         (do_clocal || (ch->imodem & ch->dcd)))
+                       break;
+
+               if (current->signal & ~current->blocked) 
+               {
+                       retval = -ERESTARTSYS;
+                       break;
+               }
+
+               /* ---------------------------------------------------------------
+                       Allow someone else to be scheduled.  We will occasionaly go
+                       through this loop until one of the above conditions change.
+                       The below schedule call will allow other processes to enter and
+                       prevent this loop from hogging the cpu.
+               ------------------------------------------------------------------ */
+               schedule();
+
+       } /* End forever while  */
+
+       current->state = TASK_RUNNING;
+       remove_wait_queue(&ch->open_wait, &wait);
+       cli();
+       if (!tty_hung_up_p(filp))
+               ch->count++;
+       restore_flags(flags);
+
+       ch->blocked_open--;
+
+       if (retval)
+               return retval;
+
+       ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+       return 0;
+
+} /* End block_til_ready */    
+
+/* ------------------ Begin pc_open  ---------------------- */
+
+static int pc_open(struct tty_struct *tty, struct file * filp)
+{ /* Begin pc_open */
+
+       struct channel *ch;
+       unsigned long flags;
+       int line, retval, boardnum;
+       volatile struct board_chan *bc;
+       volatile unsigned int head;
+
+       /* Nothing "real" happens in open of control device */
+
+       if (tty->driver.subtype == SERIAL_TYPE_INFO) 
+       {
+               return (0) ;
+       }
+
+       line = MINOR(tty->device) - tty->driver.minor_start;
+       if (line < 0 || line >= nbdevs) 
+       {
+               printk(KERN_ERR "<Error> - pc_open : line out of range in pc_open\n");
+               tty->driver_data = NULL;
+               return(-ENODEV);
+       }
+
+#ifdef MODULE
+
+       MOD_INC_USE_COUNT;
+
+#endif
+
+       ch = &digi_channels[line];
+       boardnum = ch->boardnum;
+
+       /* Check status of board configured in system.  */
+
+       /* -----------------------------------------------------------------
+               I check to see if the epca_setup routine detected an user error.  
+               It might be better to put this in pc_init, but for the moment it
+               goes here.
+       ---------------------------------------------------------------------- */
+
+       if (invalid_lilo_config)
+       {
+               if (setup_error_code & INVALID_BOARD_TYPE)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid board type specified in LILO command\n");
+
+               if (setup_error_code & INVALID_NUM_PORTS)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid number of ports specified in LILO command\n");
+
+               if (setup_error_code & INVALID_MEM_BASE)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid board memory address specified in LILO command\n");
+
+               if (setup_error_code & INVALID_PORT_BASE)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid board port address specified in LILO command\n");
+
+               if (setup_error_code & INVALID_BOARD_STATUS)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid board status specified in LILO command\n");
+
+               if (setup_error_code & INVALID_ALTPIN)
+                       printk(KERN_ERR "<Error> - pc_open: Invalid board altpin specified in LILO command\n");
+
+               tty->driver_data = NULL;   /* Mark this device as 'down' */
+               return(-ENODEV);
+       }
+
+       if ((boardnum >= num_cards) || (boards[boardnum].status == DISABLED)) 
+       {
+               tty->driver_data = NULL;   /* Mark this device as 'down' */
+               return(-ENODEV);
+       }
+       
+       if (( bc = ch->brdchan) == 0) 
+       {
+               tty->driver_data = NULL;
+               return(-ENODEV);
+       }
+
+       /* ------------------------------------------------------------------
+               Every time a channel is opened, increment a counter.  This is 
+               necessary because we do not wish to flush and shutdown the channel
+               until the last app holding the channel open, closes it.         
+       --------------------------------------------------------------------- */
+
+       ch->count++;
+
+       /* ----------------------------------------------------------------
+               Set a kernel structures pointer to our local channel 
+               structure.  This way we can get to it when passed only
+               a tty struct.
+       ------------------------------------------------------------------ */
+
+       tty->driver_data = ch;
+       
+       /* ----------------------------------------------------------------
+               If this is the first time the channel has been opened, initialize
+               the tty->termios struct otherwise let pc_close handle it.
+       -------------------------------------------------------------------- */
+
+       /* Should this be here except for SPLIT termios ? */
+       if (ch->count == 1) 
+       {
+               if (tty->driver.subtype == SERIAL_TYPE_NORMAL)
+                       *tty->termios = ch->normal_termios;
+               else 
+                       *tty->termios = ch->callout_termios;
+       }
+
+       ch->session = current->session;
+       ch->pgrp = current->pgrp;
+
+       save_flags(flags);
+       cli();
+
+       globalwinon(ch);
+       ch->statusflags = 0;
+
+       /* Save boards current modem status */
+       ch->imodem = bc->mstat;
+
+       /* ----------------------------------------------------------------
+          Set receive head and tail ptrs to each other.  This indicates
+          no data available to read.
+       ----------------------------------------------------------------- */
+       head = bc->rin;
+       bc->rout = head;
+
+       /* Set the channels associated tty structure */
+       ch->tty = tty;
+
+       /* -----------------------------------------------------------------
+               The below routine generally sets up parity, baud, flow control 
+               issues, etc.... It effect both control flags and input flags.
+       -------------------------------------------------------------------- */
+       epcaparam(tty,ch);
+
+       ch->asyncflags |= ASYNC_INITIALIZED;
+       memoff(ch);
+
+       restore_flags(flags);
+
+       retval = block_til_ready(tty, filp, ch);
+       if (retval)
+       {
+               return retval;
+       }
+
+       /* -------------------------------------------------------------
+               Set this again in case a hangup set it to zero while this 
+               open() was waiting for the line...
+       --------------------------------------------------------------- */
+       ch->tty = tty;
+
+       save_flags(flags);
+       cli();
+       globalwinon(ch);
+
+       /* Enable Digi Data events */
+       bc->idata = 1;
+
+       memoff(ch);
+       restore_flags(flags);
+
+       return 0;
+
+} /* End pc_open */
+
+#ifdef MODULE
+/* -------------------- Begin init_module ---------------------- */
+int init_module()
+{ /* Begin init_module */
+
+       unsigned long   flags;
+
+       save_flags(flags);
+       cli();
+
+       pc_init();
+
+       restore_flags(flags);
+
+       return(0);
+} /* End init_module */
+
+#endif
+#ifdef MODULE
+/* -------------------- Begin cleanup_module  ---------------------- */
+void cleanup_module()
+{ /* Begin cleanup_module */
+
+       int               count, crd;
+       struct board_info *bd;
+       struct channel    *ch;
+       unsigned long     flags;
+
+
+       save_flags(flags);
+       cli();
+
+       timer_table[DIGI_TIMER].fn = 0;
+
+       if ((tty_unregister_driver(&pc_driver)) ||  
+           (tty_unregister_driver(&pc_callout)))
+       {
+               printk(KERN_WARNING "<Error> - DIGI : cleanup_module failed to un-register tty driver\n");
+               restore_flags(flags);
+               return;
+       }
+
+       for (crd = 0; crd < num_cards; crd++) 
+       { /* Begin for each card */
+
+               bd = &boards[crd];
+
+               if (!bd)
+               { /* Begin sanity check */
+                       printk(KERN_ERR "<Error> - Digi : cleanup_module failed\n");
+                       return;
+               } /* End sanity check */
+
+               ch = card_ptr[crd]; 
+
+               for (count = 0; count < bd->numports; count++, ch++) 
+               { /* Begin for each port */
+
+                       if (ch) 
+                       {
+                               if (ch->tty)
+                                       tty_hangup(ch->tty);
+                               kfree_s(ch->tmp_buf, ch->txbufsize);
+                       }
+
+               } /* End for each port */
+       } /* End for each card */
+
+
+       restore_flags(flags);
+
+} /* End cleanup_module */
+#endif /* MODULE */
+
+/* ------------------ Begin pc_init  ---------------------- */
+
+int pc_init(void)
+{ /* Begin pc_init */
+
+       /* ----------------------------------------------------------------
+               pc_init is called by the operating system during boot up prior to
+               any open calls being made.  In the older versions of Linux (Prior
+               to 2.0.0) an entry is made into tty_io.c.  A pointer to the last
+               memory location (from kernel space) used (kmem_start) is passed
+               to pc_init.  It is pc_inits responsibility to modify this value 
+               for any memory that the Digi driver might need and then return
+               this value to the operating system.  For example if the driver
+               wishes to allocate 1K of kernel memory, pc_init would return 
+               (kmem_start + 1024).  This memory (Between kmem_start and kmem_start
+               + 1024) would then be available for use exclusively by the driver.  
+               In this case our driver does not allocate any of this kernel 
+               memory.
+       ------------------------------------------------------------------*/
+
+       ulong flags, save_loops_per_sec; 
+       int crd;
+       struct board_info *bd;
+       unsigned char board_id = 0;
+       
+
+#ifdef ENABLE_PCI
+       int pci_boards_found, pci_count;
+
+       pci_count = 0;
+#endif /* ENABLE_PCI */
+
+       /* -----------------------------------------------------------------------
+               If epca_setup has not been ran by LILO set num_cards to defaults; copy
+               board structure defined by digiConfig into drivers board structure.
+               Note : If LILO has ran epca_setup then epca_setup will handle defining
+               num_cards as well as copying the data into the board structure.
+       -------------------------------------------------------------------------- */
+       if (!liloconfig)
+       { /* Begin driver has been configured via. epcaconfig */
+
+               nbdevs = NBDEVS;
+               num_cards = NUMCARDS;
+               memcpy((void *)&boards, (void *)&static_boards,
+                      (sizeof(struct board_info) * NUMCARDS));
+       } /* End driver has been configured via. epcaconfig */
+
+       /* -----------------------------------------------------------------
+               Note : If lilo was used to configure the driver and the 
+               ignore epcaconfig option was choosen (digiepca=2) then 
+               nbdevs and num_cards will equal 0 at this point.  This is
+               okay; PCI cards will still be picked up if detected.
+       --------------------------------------------------------------------- */
+
+       /*  -----------------------------------------------------------
+               Set up interrupt, we will worry about memory allocation in
+               post_fep_init. 
+       --------------------------------------------------------------- */
+
+
+       printk(KERN_INFO "DIGI epca driver version %s loaded.\n",VERSION);
+
+#ifdef ENABLE_PCI
+
+       /* ------------------------------------------------------------------
+               NOTE : This code assumes that the number of ports found in 
+                      the boards array is correct.  This could be wrong if
+                      the card in question is PCI (And therefore has no ports 
+                      entry in the boards structure.)  The rest of the 
+                      information will be valid for PCI because the begining
+                      of pc_init scans for PCI and determines i/o and base
+                      memory addresses.  I am not sure if it is possible to 
+                      read the number of ports supported by the card prior to
+                      it being booted (Since that is the state it is in when 
+                      pc_init is run).  Because it is not possible to query the
+                      number of supported ports until after the card has booted;
+                      we are required to calculate the card_ptrs as the card is         
+                      is initialized (Inside post_fep_init).  The negative thing
+                      about this approach is that digiDload's call to GET_INFO
+                      will have a bad port value.  (Since this is called prior
+                      to post_fep_init.)
+
+       --------------------------------------------------------------------- */
+  
+       pci_boards_found = 0;
+       if (pcibios_present())
+       {
+               if(num_cards < MAXBOARDS)
+                       pci_boards_found += init_PCI(num_cards);
+               num_cards += pci_boards_found;
+       }
+       else 
+       {
+               printk(KERN_ERR "<Error> - No PCI BIOS found\n");
+       }
+
+#endif /* ENABLE_PCI */
+
+       memset(&pc_driver, 0, sizeof(struct tty_driver));
+       memset(&pc_callout, 0, sizeof(struct tty_driver));
+       memset(&pc_info, 0, sizeof(struct tty_driver));
+
+       pc_driver.magic = TTY_DRIVER_MAGIC;
+       pc_driver.name = "ttyD"; 
+       pc_driver.major = DIGI_MAJOR; 
+       pc_driver.minor_start = 0;
+       pc_driver.num = MAX_ALLOC;
+       pc_driver.type = TTY_DRIVER_TYPE_SERIAL;
+       pc_driver.subtype = SERIAL_TYPE_NORMAL;
+       pc_driver.init_termios = tty_std_termios;
+       pc_driver.init_termios.c_iflag = 0;
+       pc_driver.init_termios.c_oflag = 0;
+
+       pc_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+       pc_driver.init_termios.c_lflag = 0;
+       pc_driver.flags = TTY_DRIVER_REAL_RAW;
+       pc_driver.refcount = &pc_refcount;
+       pc_driver.table = pc_table;
+       
+       /* pc_termios is an array of pointers pointing at termios structs */
+       /* The below should get the first pointer */
+       pc_driver.termios = pc_termios;
+       pc_driver.termios_locked = pc_termios_locked;
+
+       /* ------------------------------------------------------------------
+               Setup entry points for the driver.  These are primarily called by 
+               the kernel in tty_io.c and n_tty.c
+       --------------------------------------------------------------------- */
+
+       pc_driver.open = pc_open;
+       pc_driver.close = pc_close;
+       pc_driver.write = pc_write;
+       pc_driver.write_room = pc_write_room;
+       pc_driver.flush_buffer = pc_flush_buffer;
+       pc_driver.chars_in_buffer = pc_chars_in_buffer;
+       pc_driver.flush_chars = pc_flush_chars;
+       pc_driver.put_char = pc_put_char;
+       pc_driver.ioctl = pc_ioctl;
+       pc_driver.set_termios = pc_set_termios;
+       pc_driver.stop = pc_stop;
+       pc_driver.start = pc_start;
+       pc_driver.throttle = pc_throttle;
+       pc_driver.unthrottle = pc_unthrottle;
+       pc_driver.hangup = pc_hangup;
+       pc_callout = pc_driver;
+
+       pc_callout.name = "cud";
+       pc_callout.major = DIGICU_MAJOR;
+       pc_callout.minor_start = 0;
+       pc_callout.init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+       pc_callout.subtype = SERIAL_TYPE_CALLOUT;
+
+       pc_info = pc_driver;
+       pc_info.name = "digiCtl";
+       pc_info.major = DIGIINFOMAJOR;
+       pc_info.minor_start = 0;
+       pc_info.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+       pc_info.subtype = SERIAL_TYPE_INFO;
+
+
+       /* --------------------------------------------------------------------- 
+          loops_per_sec hasn't been set at this point :-(, so fake it out... 
+          I set it, so that I can use the __delay() function.
+       ------------------------------------------------------------------------ */
+       save_loops_per_sec = loops_per_sec;
+       loops_per_sec = 13L * 500000L;
+
+       save_flags(flags);
+       cli();
+
+       for (crd = 0; crd < num_cards; crd++) 
+       { /* Begin for each card */
+
+               /*  ------------------------------------------------------------------
+                       This is where the appropriate memory handlers for the hardware is
+                       set.  Everything at runtime blindly jumps through these vectors.
+               ---------------------------------------------------------------------- */
+
+               /* defined in epcaconfig.h */
+               bd = &boards[crd];
+
+               switch (bd->type)
+               { /* Begin switch on bd->type {board type} */
+                       case PCXEM:
+                       case EISAXEM:
+                               bd->memwinon     = pcxem_memwinon ;
+                               bd->memwinoff    = pcxem_memwinoff ;
+                               bd->globalwinon  = pcxem_globalwinon ;
+                               bd->txwinon      = pcxem_txwinon ;
+                               bd->rxwinon      = pcxem_rxwinon ;
+                               bd->memoff       = pcxem_memoff ;
+                               bd->assertgwinon = dummy_assertgwinon;
+                               bd->assertmemoff = dummy_assertmemoff;
+                               break;
+
+                       case PCIXEM:
+                       case PCIXRJ:
+                       case PCIXR:
+                               bd->memwinon     = dummy_memwinon;
+                               bd->memwinoff    = dummy_memwinoff;
+                               bd->globalwinon  = dummy_globalwinon;
+                               bd->txwinon      = dummy_txwinon;
+                               bd->rxwinon      = dummy_rxwinon;
+                               bd->memoff       = dummy_memoff;
+                               bd->assertgwinon = dummy_assertgwinon;
+                               bd->assertmemoff = dummy_assertmemoff;
+                               break;
+
+                       case PCXE:
+                       case PCXEVE:
+
+                               bd->memwinon     = pcxe_memwinon;
+                               bd->memwinoff    = pcxe_memwinoff;
+                               bd->globalwinon  = pcxe_globalwinon;
+                               bd->txwinon      = pcxe_txwinon;
+                               bd->rxwinon      = pcxe_rxwinon;
+                               bd->memoff       = pcxe_memoff;
+                               bd->assertgwinon = dummy_assertgwinon;
+                               bd->assertmemoff = dummy_assertmemoff;
+                               break;
+
+                       case PCXI:
+                       case PC64XE:
+
+                               bd->memwinon     = pcxi_memwinon;
+                               bd->memwinoff    = pcxi_memwinoff;
+                               bd->globalwinon  = pcxi_globalwinon;
+                               bd->txwinon      = pcxi_txwinon;
+                               bd->rxwinon      = pcxi_rxwinon;
+                               bd->memoff       = pcxi_memoff;
+                               bd->assertgwinon = pcxi_assertgwinon;
+                               bd->assertmemoff = pcxi_assertmemoff;
+                               break;
+
+                       default:
+                               break;
+
+               } /* End switch on bd->type */
+
+               /* ---------------------------------------------------------------
+                       Some cards need a memory segment to be defined for use in 
+                       transmit and receive windowing operations.  These boards
+                       are listed in the below switch.  In the case of the XI the
+                       amount of memory on the board is variable so the memory_seg
+                       is also variable.  This code determines what they segment 
+                       should be.
+               ----------------------------------------------------------------- */
+
+               switch (bd->type)
+               { /* Begin switch on bd->type {board type} */
+
+                       case PCXE:
+                       case PCXEVE:
+                       case PC64XE:
+                               bd->memory_seg = 0xf000;
+                       break;
+
+                       case PCXI:
+                               board_id = inb((int)bd->port);
+                               if ((board_id & 0x1) == 0x1) 
+                               { /* Begin its an XI card */ 
+
+                                       /* Is it a 64K board */
+                                       if ((board_id & 0x30) == 0) 
+                                               bd->memory_seg = 0xf000;
+
+                                       /* Is it a 128K board */
+                                       if ((board_id & 0x30) == 0x10) 
+                                               bd->memory_seg = 0xe000;
+
+                                       /* Is is a 256K board */        
+                                       if ((board_id & 0x30) == 0x20) 
+                                               bd->memory_seg = 0xc000;
+
+                                       /* Is it a 512K board */
+                                       if ((board_id & 0x30) == 0x30) 
+                                               bd->memory_seg = 0x8000;
+
+                               } /* End it is an XI card */
+                               else
+                               {
+                                       printk(KERN_ERR "<Error> - Board at 0x%x doesn't appear to be an XI\n",(int)bd->port);
+                               }
+                       break;
+
+               } /* End switch on bd->type */
+
+       } /* End for each card */
+
+       if (tty_register_driver(&pc_driver))
+               panic("Couldn't register Digi PC/ driver");
+
+       if (tty_register_driver(&pc_callout))
+               panic("Couldn't register Digi PC/ callout");
+
+       if (tty_register_driver(&pc_info))
+               panic("Couldn't register Digi PC/ info ");
+
+       loops_per_sec = save_loops_per_sec;  /* reset it to what it should be */
+
+       /* -------------------------------------------------------------------
+          Start up the poller to check for events on all enabled boards
+       ---------------------------------------------------------------------- */
+
+       timer_table[DIGI_TIMER].fn = (void *)epcapoll;
+       timer_table[DIGI_TIMER].expires = 0;
+
+       restore_flags(flags);
+
+       timer_active |= 1 << DIGI_TIMER;
+       return 0;
+
+} /* End pc_init */
+
+/* ------------------ Begin post_fep_init  ---------------------- */
+
+static void post_fep_init(unsigned int crd)
+{ /* Begin post_fep_init */
+
+       int i;
+       unchar *memaddr;
+       volatile struct global_data *gd;
+       struct board_info *bd;
+       volatile struct board_chan *bc;
+       struct channel *ch; 
+       int shrinkmem = 0, lowwater ; 
+       /*  -------------------------------------------------------------
+               This call is made by the user via. the ioctl call DIGI_INIT.
+               It is resposible for setting up all the card specific stuff.
+       ---------------------------------------------------------------- */
+       bd = &boards[crd];
+
+       /* -----------------------------------------------------------------
+               If this is a PCI board, get the port info.  Remember PCI cards
+               do not have entries into the epcaconfig.h file, so we can't get 
+               the number of ports from it.  Unfortunetly, this means that anyone
+               doing a DIGI_GETINFO before the board has booted will get an invalid
+               number of ports returned (It should return 0).  Calls to DIGI_GETINFO
+               after DIGI_INIT has been called will return the proper values. 
+       ------------------------------------------------------------------- */
+
+       if (bd->type >= PCIXEM) /* If the board in question is PCI */
+       { /* Begin get PCI number of ports */
+
+               /* --------------------------------------------------------------------
+                       Below we use XEMPORTS as a memory offset regardless of which PCI
+                       card it is.  This is because all of the supported PCI cards have
+                       the same memory offset for the channel data.  This will have to be
+                       changed if we ever develop a PCI/XE card.  NOTE : The FEP manual
+                       states that the port offset is 0xC22 as opposed to 0xC02.  This is
+                       only true for PC/XE, and PC/XI cards; not for the XEM, or CX series.
+                       On the PCI cards the number of ports is determined by reading a 
+                       ID PROM located in the box attached to the card.  The card can then
+                       determine the index the id to determine the number of ports available.
+                       (FYI - The id should be located at 0x1ac (And may use up to 4 bytes
+                       if the box in question is a XEM or CX)).  
+               ------------------------------------------------------------------------ */ 
+
+               bd->numports = (unsigned short)*(unsigned char *)bus_to_virt((unsigned long)
+                                                       (bd->re_map_membase + XEMPORTS));
+
+               
+               epcaassert(bd->numports <= 64,"PCI returned a invalid number of ports");
+               nbdevs += (bd->numports);
+
+       } /* End get PCI number of ports */
+
+       if (crd != 0)
+               card_ptr[crd] = card_ptr[crd-1] + boards[crd-1].numports;
+       else
+               card_ptr[crd] = &digi_channels[crd]; /* <- For card 0 only */
+
+       ch = card_ptr[crd];
+
+
+       epcaassert(ch <= &digi_channels[nbdevs - 1], "ch out of range");
+
+       if (bd->membase < (unsigned char *)0x100000)
+               memaddr = (unchar *) bd->membase;
+       else /* Else get special mapped memory above RAM */
+               memaddr = (unchar *)bd->re_map_membase;
+
+       /* 
+          The below command is necessary because newer kernels (2.1.x and
+          up) do not have a 1:1 virtual to physical mapping.  The below
+          call adjust for that.
+       */
+
+       memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+       /* -----------------------------------------------------------------
+               The below assignment will set bc to point at the BEGINING of
+               the cards channel structures.  For 1 card there will be between
+               8 and 64 of these structures.
+       -------------------------------------------------------------------- */
+
+       bc = (volatile struct board_chan *)((ulong)memaddr + CHANSTRUCT);
+
+       /* -------------------------------------------------------------------
+               The below assignment will set gd to point at the BEGINING of
+               global memory address 0xc00.  The first data in that global
+               memory actually starts at address 0xc1a.  The command in 
+               pointer begins at 0xd10.
+       ---------------------------------------------------------------------- */
+
+       gd = (volatile struct global_data *)((ulong)memaddr + GLOBAL);
+
+       /* --------------------------------------------------------------------
+               XEPORTS (address 0xc22) points at the number of channels the
+               card supports. (For 64XE, XI, XEM, and XR use 0xc02)
+       ----------------------------------------------------------------------- */
+
+       if (((bd->type == PCXEVE) | (bd->type == PCXE)) &&
+           (*(ushort *)((ulong)memaddr + XEPORTS) < 3))
+               shrinkmem = 1;
+       if (bd->type < PCIXEM)
+               request_region((int)bd->port, 4, board_desc[bd->type]);
+
+       memwinon(bd, 0);
+
+       /*  --------------------------------------------------------------------
+               Remember ch is the main drivers channels structure, while bc is 
+          the cards channel structure.
+       ------------------------------------------------------------------------ */
+
+       /* For every port on the card do ..... */
+
+       for (i = 0; i < bd->numports; i++, ch++, bc++) 
+       { /* Begin for each port */
+
+               ch->brdchan        = bc;
+               ch->mailbox        = gd; 
+               ch->tqueue.routine = do_softint;
+               ch->tqueue.data    = ch;
+               ch->board          = &boards[crd];
+
+               switch (bd->type)
+               { /* Begin switch bd->type */
+
+                       /* ----------------------------------------------------------------
+                               Since some of the boards use different bitmaps for their
+                               control signals we cannot hard code these values and retain
+                               portability.  We virtualize this data here.
+                       ------------------------------------------------------------------- */
+                       case EISAXEM:
+                       case PCXEM:
+                       case PCIXEM:
+                       case PCIXRJ:
+                       case PCIXR:
+                               ch->m_rts = 0x02 ;
+                               ch->m_dcd = 0x80 ; 
+                               ch->m_dsr = 0x20 ;
+                               ch->m_cts = 0x10 ;
+                               ch->m_ri  = 0x40 ;
+                               ch->m_dtr = 0x01 ;
+                               break;
+
+                       case PCXE:
+                       case PCXEVE:
+                       case PCXI:
+                       case PC64XE:
+                               ch->m_rts = 0x02 ;
+                               ch->m_dcd = 0x08 ; 
+                               ch->m_dsr = 0x10 ;
+                               ch->m_cts = 0x20 ;
+                               ch->m_ri  = 0x40 ;
+                               ch->m_dtr = 0x80 ;
+                               break;
+       
+               } /* End switch bd->type */
+
+               if (boards[crd].altpin) 
+               {
+                       ch->dsr = ch->m_dcd;
+                       ch->dcd = ch->m_dsr;
+                       ch->digiext.digi_flags |= DIGI_ALTPIN;
+               }
+               else 
+               { 
+                       ch->dcd = ch->m_dcd;
+                       ch->dsr = ch->m_dsr;
+               }
+       
+               ch->boardnum   = crd;
+               ch->channelnum = i;
+               ch->magic      = EPCA_MAGIC;
+               ch->tty        = 0;
+
+               if (shrinkmem) 
+               {
+                       fepcmd(ch, SETBUFFER, 32, 0, 0, 0);
+                       shrinkmem = 0;
+               }
+
+               switch (bd->type)
+               { /* Begin switch bd->type */
+
+                       case PCIXEM:
+                       case PCIXRJ:
+                       case PCIXR:
+                               /* Cover all the 2MEG cards */
+                               ch->txptr = memaddr + (((bc->tseg) << 4) & 0x1fffff);
+                               ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x1fffff);
+                               ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+                               ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+                               break;
+
+                       case PCXEM:
+                       case EISAXEM:
+                               /* Cover all the 32K windowed cards */
+                               /* Mask equal to window size - 1 */
+                               ch->txptr = memaddr + (((bc->tseg) << 4) & 0x7fff);
+                               ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x7fff);
+                               ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+                               ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+                               break;
+
+                       case PCXEVE:
+                       case PCXE:
+                               ch->txptr = memaddr + (((bc->tseg - bd->memory_seg) << 4) & 0x1fff);
+                               ch->txwin = FEPWIN | ((bc->tseg - bd->memory_seg) >> 9);
+                               ch->rxptr = memaddr + (((bc->rseg - bd->memory_seg) << 4) & 0x1fff);
+                               ch->rxwin = FEPWIN | ((bc->rseg - bd->memory_seg) >>9 );
+                               break;
+
+                       case PCXI:
+                       case PC64XE:
+                               ch->txptr = memaddr + ((bc->tseg - bd->memory_seg) << 4);
+                               ch->rxptr = memaddr + ((bc->rseg - bd->memory_seg) << 4);
+                               ch->txwin = ch->rxwin = 0;
+                               break;
+
+               } /* End switch bd->type */
+
+               ch->txbufhead = 0;
+               ch->txbufsize = bc->tmax + 1;
+       
+               ch->rxbufhead = 0;
+               ch->rxbufsize = bc->rmax + 1;
+       
+               lowwater = ch->txbufsize >= 2000 ? 1024 : (ch->txbufsize / 2);
+
+               /* Set transmitter low water mark */
+               fepcmd(ch, STXLWATER, lowwater, 0, 10, 0);
+
+               /* Set receiver low water mark */
+
+               fepcmd(ch, SRXLWATER, (ch->rxbufsize / 4), 0, 10, 0);
+
+               /* Set receiver high water mark */
+
+               fepcmd(ch, SRXHWATER, (3 * ch->rxbufsize / 4), 0, 10, 0);
+
+               bc->edelay = 100;
+               bc->idata = 1;
+       
+               ch->startc  = bc->startc;
+               ch->stopc   = bc->stopc;
+               ch->startca = bc->startca;
+               ch->stopca  = bc->stopca;
+       
+               ch->fepcflag = 0;
+               ch->fepiflag = 0;
+               ch->fepoflag = 0;
+               ch->fepstartc = 0;
+               ch->fepstopc = 0;
+               ch->fepstartca = 0;
+               ch->fepstopca = 0;
+       
+               ch->close_delay = 50;
+               ch->count = 0;
+               ch->blocked_open = 0;
+               ch->callout_termios = pc_callout.init_termios;
+               ch->normal_termios = pc_driver.init_termios;
+               ch->open_wait = 0;
+               ch->close_wait = 0;
+               ch->tmp_buf = kmalloc(ch->txbufsize,GFP_KERNEL);
+               if (!(ch->tmp_buf))
+               {
+                       printk(KERN_ERR "POST FEP INIT : kmalloc failed for port 0x%x\n",i);
+
+               }
+               memset((void *)ch->tmp_buf,0,ch->txbufsize);
+       } /* End for each port */
+
+       printk(KERN_INFO 
+               "Digi PC/Xx Driver V%s:  %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n", 
+               VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+       sprintf(mesg, 
+               "Digi PC/Xx Driver V%s:  %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n", 
+               VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+       console_print(mesg);
+
+       memwinoff(bd, 0);
+
+} /* End post_fep_init */
+
+/* --------------------- Begin epcapoll  ------------------------ */
+
+static void epcapoll(unsigned long ignored)
+{ /* Begin epcapoll */
+
+       unsigned long flags;
+       int crd;
+       volatile unsigned int head, tail;
+       struct channel *ch;
+       struct board_info *bd;
+
+       /* -------------------------------------------------------------------
+               This routine is called upon every timer interrupt.  Even though
+               the Digi series cards are capable of generating interupts this 
+               method of non-looping polling is more efficient.  This routine
+               checks for card generated events (Such as receive data, are transmit
+               buffer empty) and acts on those events.
+       ----------------------------------------------------------------------- */
+       
+       save_flags(flags);
+       cli();
+
+       for (crd = 0; crd < num_cards; crd++) 
+       { /* Begin for each card */
+
+               bd = &boards[crd];
+               ch = card_ptr[crd];
+
+               if ((bd->status == DISABLED) || digi_poller_inhibited)
+                       continue; /* Begin loop next interation */
+
+               /* -----------------------------------------------------------
+                       assertmemoff is not needed here; indeed it is an empty subroutine.
+                       It is being kept because future boards may need this as well as
+                       some legacy boards.
+               ---------------------------------------------------------------- */
+
+               assertmemoff(ch);
+
+               globalwinon(ch);
+
+               /* ---------------------------------------------------------------
+                       In this case head and tail actually refer to the event queue not
+                       the transmit or receive queue.
+               ------------------------------------------------------------------- */
+
+               head = ch->mailbox->ein;
+               tail = ch->mailbox->eout;
+               
+               /* If head isn't equal to tail we have an event */
+
+               if (head != tail)
+                       doevent(crd);
+
+               memoff(ch);
+
+       } /* End for each card */
+
+       timer_table[DIGI_TIMER].fn = (void *)epcapoll;
+       timer_table[DIGI_TIMER].expires = jiffies + (HZ / 25);
+       timer_active |= 1 << DIGI_TIMER;
+
+       restore_flags(flags);
+
+} /* End epcapoll */
+
+/* --------------------- Begin doevent  ------------------------ */
+
+static void doevent(int crd)
+{ /* Begin doevent */
+
+       volatile unchar *eventbuf;
+       struct channel *ch, *chan0;
+       static struct tty_struct *tty;
+       volatile struct board_info *bd;
+       volatile struct board_chan *bc;
+       register volatile unsigned int tail, head;
+       register int event, channel;
+       register int mstat, lstat;
+
+       /* -------------------------------------------------------------------
+               This subroutine is called by epcapoll when an event is detected 
+               in the event queue.  This routine responds to those events.
+       --------------------------------------------------------------------- */
+
+       bd = &boards[crd];
+
+       chan0 = card_ptr[crd];
+       epcaassert(chan0 <= &digi_channels[nbdevs - 1], "ch out of range");
+
+       assertgwinon(chan0);
+
+       while ((tail = chan0->mailbox->eout) != (head = chan0->mailbox->ein)) 
+       { /* Begin while something in event queue */
+
+               assertgwinon(chan0);
+
+               if (bd->membase < (unsigned char *)0x100000)
+                       eventbuf = (volatile unchar *)bus_to_virt((ulong)(bd->membase + tail + ISTART));
+               else
+               {
+                       eventbuf = (volatile unchar *)bus_to_virt((ulong)(bd->re_map_membase + tail + ISTART));
+               }
+
+               /* Get the channel the event occured on */
+               channel = eventbuf[0];
+
+               /* Get the actual event code that occured */
+               event = eventbuf[1];
+
+               /*  ----------------------------------------------------------------
+                       The two assignments below get the current modem status (mstat)
+                       and the previous modem status (lstat).  These are useful becuase
+                       an event could signal a change in modem signals itself.
+               ------------------------------------------------------------------- */
+
+               mstat = eventbuf[2];
+               lstat = eventbuf[3];
+
+               ch = chan0 + channel;
+
+               if ((unsigned)channel >= bd->numports || !ch) 
+               { 
+                       if (channel >= bd->numports)
+                               ch = chan0;
+                       bc = ch->brdchan;
+                       goto next;
+               }
+
+               if ((bc = ch->brdchan) == NULL)
+                       goto next;
+
+               if (event & DATA_IND) 
+               { /* Begin DATA_IND */
+
+                       receive_data(ch);
+                       assertgwinon(ch);
+
+               } /* End DATA_IND */
+               else
+               if (event & MODEMCHG_IND) 
+               { /* Begin MODEMCHG_IND */
+
+                       /* A modem signal change has been indicated */
+
+                       ch->imodem = mstat;
+
+                       if (ch->asyncflags & ASYNC_CHECK_CD) 
+                       {
+                               if (mstat & ch->dcd)  /* We are now receiving dcd */
+                                       wake_up_interruptible(&ch->open_wait);
+                               else
+                                       pc_sched_event(ch, EPCA_EVENT_HANGUP); /* No dcd; hangup */
+                       }
+
+               } /* End MODEMCHG_IND */
+
+               tty = ch->tty;
+               if (tty) 
+               { /* Begin if valid tty */
+
+                       if (event & BREAK_IND) 
+                       { /* Begin if BREAK_IND */
+
+                               /* A break has been indicated */
+
+                               tty->flip.count++;
+                               *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+
+                               *tty->flip.char_buf_ptr++ = 0;
+
+                               tty_schedule_flip(tty); 
+
+                       } /* End if BREAK_IND */
+                       else
+                       if (event & LOWTX_IND) 
+                       { /* Begin LOWTX_IND */
+
+                               if (ch->statusflags & LOWWAIT) 
+                               { /* Begin if LOWWAIT */
+
+                                       ch->statusflags &= ~LOWWAIT;
+                                       if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                                                 tty->ldisc.write_wakeup)
+                                               (tty->ldisc.write_wakeup)(tty);
+                                       wake_up_interruptible(&tty->write_wait);
+
+                               } /* End if LOWWAIT */
+
+                       } /* End LOWTX_IND */
+                       else
+                       if (event & EMPTYTX_IND) 
+                       { /* Begin EMPTYTX_IND */
+
+                               /* This event is generated by setup_empty_event */
+
+                               ch->statusflags &= ~TXBUSY;
+                               if (ch->statusflags & EMPTYWAIT) 
+                               { /* Begin if EMPTYWAIT */
+
+                                       ch->statusflags &= ~EMPTYWAIT;
+                                       if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+                                                 tty->ldisc.write_wakeup)
+                                               (tty->ldisc.write_wakeup)(tty);
+
+                                       wake_up_interruptible(&tty->write_wait);
+
+                               } /* End if EMPTYWAIT */
+
+                       } /* End EMPTYTX_IND */
+
+               } /* End if valid tty */
+
+
+       next:
+               globalwinon(ch);
+
+               if (!bc)
+                       printk(KERN_ERR "<Error> - bc == NULL in doevent!\n");
+               else 
+                       bc->idata = 1;
+
+               chan0->mailbox->eout = (tail + 4) & (IMAX - ISTART - 4);
+               globalwinon(chan0);
+
+       } /* End while something in event queue */
+
+} /* End doevent */
+
+/* --------------------- Begin fepcmd  ------------------------ */
+
+static void fepcmd(struct channel *ch, int cmd, int word_or_byte,
+                   int byte2, int ncmds, int bytecmd)
+{ /* Begin fepcmd */
+
+       unchar *memaddr;
+       unsigned int head, cmdTail, cmdStart, cmdMax;
+       long count;
+       int n;
+
+       /* This is the routine in which commands may be passed to the card. */
+
+       if (ch->board->status == DISABLED)
+       {
+               return;
+       }
+
+       assertgwinon(ch);
+
+       /* Remember head (As well as max) is just an offset not a base addr */
+       head = ch->mailbox->cin;
+
+       /* cmdStart is a base address */
+       cmdStart = ch->mailbox->cstart;
+
+       /* ------------------------------------------------------------------
+               We do the addition below because we do not want a max pointer 
+               relative to cmdStart.  We want a max pointer that points at the 
+               physical end of the command queue.
+       -------------------------------------------------------------------- */
+
+       cmdMax = (cmdStart + 4 + (ch->mailbox->cmax));
+
+       if (ch->board->membase < (unsigned char *)0x100000)
+               memaddr = ch->board->membase;
+       else
+               memaddr = ch->board->re_map_membase;
+
+       /* 
+          The below command is necessary because newer kernels (2.1.x and
+          up) do not have a 1:1 virtual to physical mapping.  The below
+          call adjust for that.
+       */
+
+       memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+       if (head >= (cmdMax - cmdStart) || (head & 03)) 
+       {
+               printk(KERN_ERR "line %d: Out of range, cmd = %x, head = %x\n", __LINE__, 
+              cmd, head);
+               printk(KERN_ERR "line %d: Out of range, cmdMax = %x, cmdStart = %x\n", __LINE__, 
+              cmdMax, cmdStart);
+               return;
+       }
+
+       if (bytecmd) 
+       {
+               *(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+
+               *(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+               /* Below word_or_byte is bits to set */
+               *(volatile unchar *)(memaddr + head + cmdStart + 2) = (unchar)word_or_byte;
+               /* Below byte2 is bits to reset */
+               *(volatile unchar *)(memaddr + head + cmdStart + 3) = (unchar)byte2;
+
+       } 
+       else 
+       {
+               *(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+               *(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+               *(volatile ushort*)(memaddr + head + cmdStart + 2) = (ushort)word_or_byte;
+       }
+
+       head = (head + 4) & (cmdMax - cmdStart - 4);
+       ch->mailbox->cin = head;
+
+       count = FEPTIMEOUT;
+
+       for (;;) 
+       { /* Begin forever loop */
+
+               count--;
+               if (count == 0) 
+               {
+                       printk(KERN_ERR "<Error> - Fep not responding in fepcmd()\n");
+                       return;
+               }
+
+               head = ch->mailbox->cin;
+               cmdTail = ch->mailbox->cout;
+
+               n = (head - cmdTail) & (cmdMax - cmdStart - 4);
+
+               /* ----------------------------------------------------------
+                       Basically this will break when the FEP acknowledges the 
+                       command by incrementing cmdTail (Making it equal to head).
+               ------------------------------------------------------------- */
+
+               if (n <= ncmds * (sizeof(short) * 4))
+                       break; /* Well nearly forever :-) */
+
+       } /* End forever loop */
+
+} /* End fepcmd */
+
+/* ---------------------------------------------------------------------
+       Digi products use fields in their channels structures that are very
+       similar to the c_cflag and c_iflag fields typically found in UNIX
+       termios structures.  The below three routines allow mappings 
+       between these hardware "flags" and their respective Linux flags.
+------------------------------------------------------------------------- */
+/* --------------------- Begin termios2digi_h -------------------- */
+
+static unsigned termios2digi_h(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_h */
+
+       unsigned res = 0;
+
+       if (cflag & CRTSCTS) 
+       {
+               ch->digiext.digi_flags |= (RTSPACE | CTSPACE);
+               res |= ((ch->m_cts) | (ch->m_rts));
+       }
+
+       if (ch->digiext.digi_flags & RTSPACE)
+               res |= ch->m_rts;
+
+       if (ch->digiext.digi_flags & DTRPACE)
+               res |= ch->m_dtr;
+
+       if (ch->digiext.digi_flags & CTSPACE)
+               res |= ch->m_cts;
+
+       if (ch->digiext.digi_flags & DSRPACE)
+               res |= ch->dsr;
+
+       if (ch->digiext.digi_flags & DCDPACE)
+               res |= ch->dcd;
+
+       if (res & (ch->m_rts))
+               ch->digiext.digi_flags |= RTSPACE;
+
+       if (res & (ch->m_cts))
+               ch->digiext.digi_flags |= CTSPACE;
+
+       return res;
+
+} /* End termios2digi_h */
+
+/* --------------------- Begin termios2digi_i -------------------- */
+static unsigned termios2digi_i(struct channel *ch, unsigned iflag)
+{ /* Begin termios2digi_i */
+
+       unsigned res = iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK | 
+                               INPCK | ISTRIP|IXON|IXANY|IXOFF);
+       
+       if (ch->digiext.digi_flags & DIGI_AIXON)
+               res |= IAIXON;
+       return res;
+
+} /* End termios2digi_i */
+
+/* --------------------- Begin termios2digi_c -------------------- */
+
+static unsigned termios2digi_c(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_c */
+
+       unsigned res = 0;
+
+#ifdef SPEED_HACK
+       /* CL: HACK to force 115200 at 38400 and 57600 at 19200 Baud */
+       if ((cflag & CBAUD)== B38400) cflag=cflag - B38400 + B115200;
+       if ((cflag & CBAUD)== B19200) cflag=cflag - B19200 + B57600;
+#endif /* SPEED_HACK */
+
+       if (cflag & CBAUDEX)
+       { /* Begin detected CBAUDEX */
+
+               ch->digiext.digi_flags |= DIGI_FAST;
+
+               /* -------------------------------------------------------------
+                  HUPCL bit is used by FEP to indicate fast baud
+                  table is to be used.
+               ----------------------------------------------------------------- */
+
+               res |= FEP_HUPCL;
+
+       } /* End detected CBAUDEX */
+       else ch->digiext.digi_flags &= ~DIGI_FAST; 
+
+       /* -------------------------------------------------------------------
+               CBAUD has bit position 0x1000 set these days to indicate Linux
+               baud rate remap.  Digi hardware can't handle the bit assignment.
+               (We use a different bit assignment for high speed.).  Clear this
+               bit out.
+       ---------------------------------------------------------------------- */
+       res |= cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB | CSTOPB | CSIZE);
+
+       /* -------------------------------------------------------------
+               This gets a little confusing.  The Digi cards have their own
+               representation of c_cflags controling baud rate.  For the most
+               part this is identical to the Linux implementation.  However;
+               Digi supports one rate (76800) that Linux doesn't.  This means 
+               that the c_cflag entry that would normally mean 76800 for Digi
+               actually means 115200 under Linux.  Without the below mapping,
+               a stty 115200 would only drive the board at 76800.  Since 
+               the rate 230400 is also found after 76800, the same problem afflicts    
+               us when we choose a rate of 230400.  Without the below modificiation
+               stty 230400 would actually give us 115200.
+
+               There are two additional differences.  The Linux value for CLOCAL
+               (0x800; 0004000) has no meaning to the Digi hardware.  Also in 
+               later releases of Linux; the CBAUD define has CBAUDEX (0x1000;
+               0010000) ored into it (CBAUD = 0x100f as opposed to 0xf). CBAUDEX
+               should be checked for a screened out prior to termios2digi_c 
+               returning.  Since CLOCAL isn't used by the board this can be
+               ignored as long as the returned value is used only by Digi hardware. 
+       ----------------------------------------------------------------- */
+
+       if (cflag & CBAUDEX)
+       {
+               /* -------------------------------------------------------------
+                       The below code is trying to guarantee that only baud rates
+                       115200 and 230400 are remapped.  We use exclusive or because
+                       the various baud rates share common bit positions and therefore
+                       can't be tested for easily.
+               ----------------------------------------------------------------- */
+
+                               
+               if ((!((cflag & 0x7) ^ (B115200 & ~CBAUDEX))) || 
+                   (!((cflag & 0x7) ^ (B230400 & ~CBAUDEX))))
+               {
+                       res += 1;
+               }
+       }
+
+       return res;
+
+} /* End termios2digi_c */
+
+/* --------------------- Begin epcaparam  ----------------------- */
+
+static void epcaparam(struct tty_struct *tty, struct channel *ch)
+{ /* Begin epcaparam */
+
+       unsigned int cmdHead;
+       struct termios *ts;
+       volatile struct board_chan *bc;
+       unsigned mval, hflow, cflag, iflag;
+
+       bc = ch->brdchan;
+       epcaassert(bc !=0, "bc out of range");
+
+       assertgwinon(ch);
+
+       ts = tty->termios;
+
+       if ((ts->c_cflag & CBAUD) == 0) 
+       { /* Begin CBAUD detected */
+
+               cmdHead = bc->rin;
+               bc->rout = cmdHead;
+               cmdHead = bc->tin;
+
+               /* Changing baud in mid-stream transmission can be wonderful */
+               /* ---------------------------------------------------------------
+                       Flush current transmit buffer by setting cmdTail pointer (tout)
+                       to cmdHead pointer (tin).  Hopefully the transmit buffer is empty.
+               ----------------------------------------------------------------- */
+
+               fepcmd(ch, STOUT, (unsigned) cmdHead, 0, 0, 0);
+               mval = 0;
+
+       } /* End CBAUD detected */
+       else 
+       { /* Begin CBAUD not detected */
+
+               /* -------------------------------------------------------------------
+                       c_cflags have changed but that change had nothing to do with BAUD.
+                       Propagate the change to the card.
+               ---------------------------------------------------------------------- */ 
+
+               cflag = termios2digi_c(ch, ts->c_cflag);
+
+               if (cflag != ch->fepcflag) 
+               {
+                       ch->fepcflag = cflag;
+                       /* Set baud rate, char size, stop bits, parity */
+                       fepcmd(ch, SETCTRLFLAGS, (unsigned) cflag, 0, 0, 0);
+               }
+
+
+               /* ----------------------------------------------------------------
+                       If the user has not forced CLOCAL and if the device is not a 
+                       CALLOUT device (Which is always CLOCAL) we set flags such that
+                       the driver will wait on carrier detect.
+               ------------------------------------------------------------------- */
+
+               if ((ts->c_cflag & CLOCAL) || (tty->driver.subtype == SERIAL_TYPE_CALLOUT))
+               { /* Begin it is a cud device or a ttyD device with CLOCAL on */
+                       ch->asyncflags &= ~ASYNC_CHECK_CD;
+               } /* End it is a cud device or a ttyD device with CLOCAL on */
+               else
+               { /* Begin it is a ttyD device */
+                       ch->asyncflags |= ASYNC_CHECK_CD;
+               } /* End it is a ttyD device */
+
+               mval = ch->m_dtr | ch->m_rts;
+
+       } /* End CBAUD not detected */
+
+       iflag = termios2digi_i(ch, ts->c_iflag);
+
+       /* Check input mode flags */
+
+       if (iflag != ch->fepiflag) 
+       {
+               ch->fepiflag = iflag;
+
+               /* ---------------------------------------------------------------
+                       Command sets channels iflag structure on the board. Such things 
+                       as input soft flow control, handeling of parity errors, and
+                       break handeling are all set here.
+               ------------------------------------------------------------------- */
+
+               /* break handeling, parity handeling, input stripping, flow control chars */
+               fepcmd(ch, SETIFLAGS, (unsigned int) ch->fepiflag, 0, 0, 0);
+       }
+
+       /* ---------------------------------------------------------------
+               Set the board mint value for this channel.  This will cause hardware
+               events to be generated each time the DCD signal (Described in mint) 
+               changes.        
+       ------------------------------------------------------------------- */
+       bc->mint = ch->dcd;
+
+       if ((ts->c_cflag & CLOCAL) || (ch->digiext.digi_flags & DIGI_FORCEDCD))
+               if (ch->digiext.digi_flags & DIGI_FORCEDCD)
+                       bc->mint = 0;
+
+       ch->imodem = bc->mstat;
+
+       hflow = termios2digi_h(ch, ts->c_cflag);
+
+       if (hflow != ch->hflow) 
+       {
+               ch->hflow = hflow;
+
+               /* --------------------------------------------------------------
+                       Hard flow control has been selected but the board is not
+                       using it.  Activate hard flow control now.
+               ----------------------------------------------------------------- */
+
+               fepcmd(ch, SETHFLOW, hflow, 0xff, 0, 1);
+       }
+       
+
+       mval ^= ch->modemfake & (mval ^ ch->modem);
+
+       if (ch->omodem ^ mval) 
+       {
+               ch->omodem = mval;
+
+               /* --------------------------------------------------------------
+                       The below command sets the DTR and RTS mstat structure.  If
+                       hard flow control is NOT active these changes will drive the
+                       output of the actual DTR and RTS lines.  If hard flow control 
+                       is active, the changes will be saved in the mstat structure and
+                       only asserted when hard flow control is turned off. 
+               ----------------------------------------------------------------- */
+
+               /* First reset DTR & RTS; then set them */
+               fepcmd(ch, SETMODEM, 0, ((ch->m_dtr)|(ch->m_rts)), 0, 1);
+               fepcmd(ch, SETMODEM, mval, 0, 0, 1);
+
+       }
+
+       if (ch->startc != ch->fepstartc || ch->stopc != ch->fepstopc) 
+       {
+               ch->fepstartc = ch->startc;
+               ch->fepstopc = ch->stopc;
+
+               /* ------------------------------------------------------------
+                       The XON / XOFF characters have changed; propogate these
+                       changes to the card.    
+               --------------------------------------------------------------- */
+
+               fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+       }
+
+       if (ch->startca != ch->fepstartca || ch->stopca != ch->fepstopca) 
+       {
+               ch->fepstartca = ch->startca;
+               ch->fepstopca = ch->stopca;
+
+               /* ---------------------------------------------------------------
+                       Similar to the above, this time the auxilarly XON / XOFF 
+                       characters have changed; propogate these changes to the card.
+               ------------------------------------------------------------------ */
+
+               fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+       }
+
+} /* End epcaparam */
+
+/* --------------------- Begin receive_data  ----------------------- */
+
+static void receive_data(struct channel *ch)
+{ /* Begin receive_data */
+
+       unchar *rptr;
+       struct termios *ts = 0;
+       struct tty_struct *tty;
+       volatile struct board_chan *bc;
+       register int dataToRead, wrapgap, bytesAvailable;
+       register unsigned int tail, head;
+       unsigned int wrapmask;
+       int rc;
+
+
+       /* ---------------------------------------------------------------
+               This routine is called by doint when a receive data event 
+               has taken place.
+       ------------------------------------------------------------------- */
+
+       globalwinon(ch);
+
+       if (ch->statusflags & RXSTOPPED)
+               return;
+
+       tty = ch->tty;
+       if (tty)
+               ts = tty->termios;
+
+       bc = ch->brdchan;
+
+       if (!bc) 
+       {
+               printk(KERN_ERR "<Error> - bc is NULL in receive_data!\n");
+               return;
+       }
+
+       wrapmask = ch->rxbufsize - 1;
+
+       /* --------------------------------------------------------------------- 
+               Get the head and tail pointers to the receiver queue.  Wrap the 
+               head pointer if it has reached the end of the buffer.
+       ------------------------------------------------------------------------ */
+
+       head = bc->rin;
+       head &= wrapmask;
+       tail = bc->rout & wrapmask;
+
+       bytesAvailable = (head - tail) & wrapmask;
+
+       if (bytesAvailable == 0)
+               return;
+
+       /* ------------------------------------------------------------------
+          If CREAD bit is off or device not open, set TX tail to head
+       --------------------------------------------------------------------- */
+
+       if (!tty || !ts || !(ts->c_cflag & CREAD)) 
+       {
+               bc->rout = head;
+               return;
+       }
+
+       if (tty->flip.count == TTY_FLIPBUF_SIZE) 
+               return;
+
+       if (bc->orun) 
+       {
+               bc->orun = 0;
+               printk(KERN_WARNING "overrun! DigiBoard device minor = %d\n",MINOR(tty->device));
+       }
+
+       rxwinon(ch);
+       rptr = tty->flip.char_buf_ptr;
+       rc = tty->flip.count;
+
+       while (bytesAvailable > 0) 
+       { /* Begin while there is data on the card */
+
+               wrapgap = (head >= tail) ? head - tail : ch->rxbufsize - tail;
+
+               /* ---------------------------------------------------------------
+                       Even if head has wrapped around only report the amount of
+                       data to be equal to the size - tail.  Remember memcpy can't
+                       automaticly wrap around the receive buffer.
+               ----------------------------------------------------------------- */
+
+               dataToRead = (wrapgap < bytesAvailable) ? wrapgap : bytesAvailable;
+
+               /* --------------------------------------------------------------
+                  Make sure we don't overflow the buffer
+               ----------------------------------------------------------------- */
+
+               if ((rc + dataToRead) > TTY_FLIPBUF_SIZE)
+                       dataToRead = TTY_FLIPBUF_SIZE - rc;
+
+               if (dataToRead == 0)
+                       break;
+
+               /* ---------------------------------------------------------------
+                       Move data read from our card into the line disciplines buffer
+                       for translation if necessary.
+               ------------------------------------------------------------------ */
+
+               if ((memcpy(rptr, ch->rxptr + tail, dataToRead)) != rptr)
+                       printk(KERN_ERR "<Error> - receive_data : memcpy failed\n");
+                       
+               rc   += dataToRead;
+               rptr += dataToRead;
+               tail = (tail + dataToRead) & wrapmask;
+               bytesAvailable -= dataToRead;
+
+       } /* End while there is data on the card */
+
+
+       tty->flip.count = rc;
+       tty->flip.char_buf_ptr = rptr;
+       globalwinon(ch);
+       bc->rout = tail;
+
+       /* Must be called with global data */
+       tty_schedule_flip(ch->tty); 
+       return;
+
+} /* End receive_data */
+
+/* --------------------- Begin pc_ioctl  ----------------------- */
+
+static int pc_ioctl(struct tty_struct *tty, struct file * file,
+                   unsigned int cmd, unsigned long arg)
+{ /* Begin pc_ioctl */
+
+       digiflow_t dflow;
+       int retval, error;
+       unsigned long flags;
+       unsigned int mflag, mstat;
+       unsigned char startc, stopc;
+       volatile struct board_chan *bc;
+       struct channel *ch = (struct channel *) tty->driver_data;
+       
+       /* The control device has it's own set of commands */
+       if (tty->driver.subtype == SERIAL_TYPE_INFO) 
+       { /* Begin if subtype is the control device */
+
+               switch (cmd) 
+               { /* Begin switch cmd */
+
+                       case DIGI_GETINFO:
+                       { /* Begin case DIGI_GETINFO */
+
+                               struct digi_info di ;
+                               int brd;
+
+                               getUser(brd, (unsigned int *)arg);
+
+                               if ((error = verify_area(VERIFY_WRITE, (char*)arg, sizeof(di))))
+                               {
+                                       printk(KERN_ERR "DIGI_GETINFO : verify area size 0x%x failed\n",sizeof(di));
+                                       return(error);
+                               }
+
+                               if ((brd < 0) || (brd >= num_cards) || (num_cards == 0))
+                                       return (-ENODEV);
+
+                               memset(&di, 0, sizeof(di));
+
+                               di.board = brd ; 
+                               di.status = boards[brd].status;
+                               di.type = boards[brd].type ;
+                               di.numports = boards[brd].numports ;
+                               di.port = boards[brd].port ;
+                               di.membase = boards[brd].membase ;
+
+                               copy_to_user((char *)arg, &di, sizeof (di));
+                               break;
+
+                       } /* End case DIGI_GETINFO */
+
+                       case DIGI_POLLER:
+                       { /* Begin case DIGI_POLLER */
+
+                               int brd = arg & 0xff000000 >> 16 ; 
+                               unsigned char state = arg & 0xff ; 
+
+                               if ((brd < 0) || (brd >= num_cards))
+                               {
+                                       printk(KERN_ERR "<Error> - DIGI POLLER : brd not valid!\n");
+                                       return (-ENODEV);
+                               }
+
+                               digi_poller_inhibited = state ;
+                               break ; 
+
+                       } /* End case DIGI_POLLER */
+
+                       case DIGI_INIT:
+                       { /* Begin case DIGI_INIT */
+
+                               /* ------------------------------------------------------------
+                                       This call is made by the apps to complete the initilization
+                                       of the board(s).  This routine is responsible for setting
+                                       the card to its initial state and setting the drivers control
+                                       fields to the sutianle settings for the card in question.
+                               ---------------------------------------------------------------- */
+                       
+                               int crd ; 
+                               for (crd = 0; crd < num_cards; crd++) 
+                                       post_fep_init (crd);
+
+                               break ; 
+
+                       } /* End case DIGI_INIT */
+
+
+                       default:
+                               return -ENOIOCTLCMD;
+
+               } /* End switch cmd */
+               return (0) ;
+
+       } /* End if subtype is the control device */
+
+       if (ch)
+               bc = ch->brdchan;
+       else 
+       {
+               printk(KERN_ERR "<Error> - ch is NULL in pc_ioctl!\n");
+               return(-EINVAL);
+       }
+
+       save_flags(flags);
+
+       /* -------------------------------------------------------------------
+               For POSIX compliance we need to add more ioctls.  See tty_ioctl.c
+               in /usr/src/linux/drivers/char for a good example.  In particular 
+               think about adding TCSETAF, TCSETAW, TCSETA, TCSETSF, TCSETSW, TCSETS.
+       ---------------------------------------------------------------------- */
+
+       switch (cmd) 
+       { /* Begin switch cmd */
+
+               case TCGETS:
+                       retval = verify_area(VERIFY_WRITE, (void *)arg,
+                              sizeof(struct termios));
+                       
+                       if (retval)
+                               return(retval);
+
+                       copy_to_user((struct termios *)arg, 
+                                    tty->termios, sizeof(struct termios));
+                       return(0);
+
+               case TCGETA:
+                       return get_termio(tty, (struct termio *)arg);
+
+               case TCSBRK:    /* SVID version: non-zero arg --> no break */
+
+                       retval = tty_check_change(tty);
+                       if (retval)
+                               return retval;
+
+                       /* Setup an event to indicate when the transmit buffer empties */
+
+                       setup_empty_event(tty,ch);              
+                       tty_wait_until_sent(tty, 0);
+                       if (!arg)
+                               digi_send_break(ch, HZ/4);    /* 1/4 second */
+                       return 0;
+
+               case TCSBRKP:   /* support for POSIX tcsendbreak() */
+
+                       retval = tty_check_change(tty);
+                       if (retval)
+                               return retval;
+
+                       /* Setup an event to indicate when the transmit buffer empties */
+
+                       setup_empty_event(tty,ch);              
+                       tty_wait_until_sent(tty, 0);
+                       digi_send_break(ch, arg ? arg*(HZ/10) : HZ/4);
+                       return 0;
+
+               case TIOCGSOFTCAR:
+
+                       error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
+                       if (error)
+                               return error;
+
+                       putUser(C_CLOCAL(tty) ? 1 : 0,
+                                   (unsigned long *) arg);
+                       return 0;
+
+               case TIOCSSOFTCAR:
+                       /*RONNIE PUT VERIFY_READ (See above) check here */
+               {
+                       unsigned int value;
+
+                       getUser(value, (unsigned int *)arg);
+                       tty->termios->c_cflag =
+                               ((tty->termios->c_cflag & ~CLOCAL) |
+                                (value ? CLOCAL : 0));
+                       return 0;
+               }
+
+               case TIOCMODG:
+               case TIOCMGET:
+
+                       mflag = 0;
+
+                       cli();
+                       globalwinon(ch);
+                       mstat = bc->mstat;
+                       memoff(ch);
+                       restore_flags(flags);
+
+                       if (mstat & ch->m_dtr)
+                               mflag |= TIOCM_DTR;
+
+                       if (mstat & ch->m_rts)
+                               mflag |= TIOCM_RTS;
+
+                       if (mstat & ch->m_cts)
+                               mflag |= TIOCM_CTS;
+
+                       if (mstat & ch->dsr)
+                               mflag |= TIOCM_DSR;
+
+                       if (mstat & ch->m_ri)
+                               mflag |= TIOCM_RI;
+
+                       if (mstat & ch->dcd)
+                               mflag |= TIOCM_CD;
+
+                       error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
+
+                       if (error)
+                               return error;
+
+                       putUser(mflag, (unsigned long *) arg);
+
+                       break;
+
+               case TIOCMBIS:
+               case TIOCMBIC:
+               case TIOCMODS:
+               case TIOCMSET:
+
+                       getUser(mstat, (unsigned int *)arg);
+
+                       mflag = 0;
+                       if (mstat & TIOCM_DTR)
+                               mflag |= ch->m_dtr;
+
+                       if (mstat & TIOCM_RTS)
+                               mflag |= ch->m_rts;
+
+                       switch (cmd) 
+                       { /* Begin switch cmd */
+
+                               case TIOCMODS:
+                               case TIOCMSET:
+                                       ch->modemfake = ch->m_dtr|ch->m_rts;
+                                       ch->modem = mflag;
+                                       break;
+
+                               case TIOCMBIS:
+                                       ch->modemfake |= mflag;
+                                       ch->modem |= mflag;
+                                       break;
+
+                               case TIOCMBIC:
+                                       ch->modemfake |= mflag;
+                                       ch->modem &= ~mflag;
+                                       break;
+
+                       } /* End switch cmd */
+
+                       cli();
+                       globalwinon(ch);
+
+                       /*  --------------------------------------------------------------
+                               The below routine generally sets up parity, baud, flow control 
+                               issues, etc.... It effect both control flags and input flags.
+                       ------------------------------------------------------------------ */
+
+                       epcaparam(tty,ch);
+                       memoff(ch);
+                       restore_flags(flags);
+                       break;
+
+               case TIOCSDTR:
+                       ch->omodem |= ch->m_dtr;
+                       cli();
+                       globalwinon(ch);
+                       fepcmd(ch, SETMODEM, ch->m_dtr, 0, 10, 1);
+                       memoff(ch);
+                       restore_flags(flags);
+                       break;
+
+               case TIOCCDTR:
+                       ch->omodem &= ~ch->m_dtr;
+                       cli();
+                       globalwinon(ch);
+                       fepcmd(ch, SETMODEM, 0, ch->m_dtr, 10, 1);
+                       memoff(ch);
+                       restore_flags(flags);
+                       break;
+
+               case DIGI_GETA:
+                       if ((error=
+                               verify_area(VERIFY_WRITE, (char*)arg, sizeof(digi_t))))
+                       {
+                               printk(KERN_ERR "<Error> - Digi GETA failed\n");
+                               return(error);
+                       }
+
+                       copy_to_user((char*)arg, &ch->digiext, sizeof(digi_t));
+                       break;
+
+               case DIGI_SETAW:
+               case DIGI_SETAF:
+                       if ((cmd) == (DIGI_SETAW)) 
+                       {
+                               /* Setup an event to indicate when the transmit buffer empties */
+
+                               setup_empty_event(tty,ch);              
+                               tty_wait_until_sent(tty, 0);
+                       }
+                       else 
+                       {
+                               if (tty->ldisc.flush_buffer)
+                                       tty->ldisc.flush_buffer(tty);
+                       }
+
+                       /* Fall Thru */
+
+               case DIGI_SETA:
+                       if ((error =
+                               verify_area(VERIFY_READ, (char*)arg,sizeof(digi_t))))
+                               return(error);
+
+                       copy_from_user(&ch->digiext, (char*)arg, sizeof(digi_t));
+                       
+                       if (ch->digiext.digi_flags & DIGI_ALTPIN) 
+                       {
+                               ch->dcd = ch->m_dsr;
+                               ch->dsr = ch->m_dcd;
+                       } 
+                       else 
+                       {
+                               ch->dcd = ch->m_dcd;
+                               ch->dsr = ch->m_dsr;
+                       }
+               
+                       cli();
+                       globalwinon(ch);
+
+                       /* -----------------------------------------------------------------
+                               The below routine generally sets up parity, baud, flow control 
+                               issues, etc.... It effect both control flags and input flags.
+                       ------------------------------------------------------------------- */
+
+                       epcaparam(tty,ch);
+                       memoff(ch);
+                       restore_flags(flags);
+                       break;
+
+               case DIGI_GETFLOW:
+               case DIGI_GETAFLOW:
+                       cli();  
+                       globalwinon(ch);
+                       if ((cmd) == (DIGI_GETFLOW)) 
+                       {
+                               dflow.startc = bc->startc;
+                               dflow.stopc = bc->stopc;
+                       }
+                       else 
+                       {
+                               dflow.startc = bc->startca;
+                               dflow.stopc = bc->stopca;
+                       }
+                       memoff(ch);
+                       restore_flags(flags);
+
+                       if ((error = verify_area(VERIFY_WRITE, (char*)arg,sizeof(dflow))))
+                               return(error);
+
+                       copy_to_user((char*)arg, &dflow, sizeof(dflow));
+                       break;
+
+               case DIGI_SETAFLOW:
+               case DIGI_SETFLOW:
+                       if ((cmd) == (DIGI_SETFLOW)) 
+                       {
+                               startc = ch->startc;
+                               stopc = ch->stopc;
+                       } 
+                       else 
+                       {
+                               startc = ch->startca;
+                               stopc = ch->stopca;
+                       }
+
+                       if ((error = verify_area(VERIFY_READ, (char*)arg,sizeof(dflow))))
+                               return(error);
+
+                       copy_from_user(&dflow, (char*)arg, sizeof(dflow));
+
+                       if (dflow.startc != startc || dflow.stopc != stopc) 
+                       { /* Begin  if setflow toggled */
+                               cli();
+                               globalwinon(ch);
+
+                               if ((cmd) == (DIGI_SETFLOW)) 
+                               {
+                                       ch->fepstartc = ch->startc = dflow.startc;
+                                       ch->fepstopc = ch->stopc = dflow.stopc;
+                                       fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+                               } 
+                               else 
+                               {
+                                       ch->fepstartca = ch->startca = dflow.startc;
+                                       ch->fepstopca  = ch->stopca = dflow.stopc;
+                                       fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+                               }
+
+                               if      (ch->statusflags & TXSTOPPED)
+                                       pc_start(tty);
+
+                               memoff(ch);
+                               restore_flags(flags);
+
+                       } /* End if setflow toggled */
+                       break;
+
+               default:
+                       return -ENOIOCTLCMD;
+
+       } /* End switch cmd */
+
+       return 0;
+
+} /* End pc_ioctl */
+
+/* --------------------- Begin pc_set_termios  ----------------------- */
+
+static void pc_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{ /* Begin pc_set_termios */
+
+       struct channel *ch;
+       unsigned long flags;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if channel valid */
+
+               save_flags(flags);
+               cli();
+               globalwinon(ch);
+               epcaparam(tty, ch);
+               memoff(ch);
+
+               if ((old_termios->c_cflag & CRTSCTS) &&
+                        ((tty->termios->c_cflag & CRTSCTS) == 0))
+                       tty->hw_stopped = 0;
+
+               if (!(old_termios->c_cflag & CLOCAL) &&
+                        (tty->termios->c_cflag & CLOCAL))
+                       wake_up_interruptible(&ch->open_wait);
+
+               restore_flags(flags);
+
+       } /* End if channel valid */
+
+} /* End pc_set_termios */
+
+/* --------------------- Begin do_softint  ----------------------- */
+
+static void do_softint(void *private_)
+{ /* Begin do_softint */
+
+       struct channel *ch = (struct channel *) private_;
+       
+
+       /* Called in response to a modem change event */
+
+       if (ch && ch->magic == EPCA_MAGIC) 
+       { /* Begin EPCA_MAGIC */
+
+               struct tty_struct *tty = ch->tty;
+
+               if (tty && tty->driver_data) 
+               { 
+                       if (clear_bit(EPCA_EVENT_HANGUP, &ch->event)) 
+                       { /* Begin if clear_bit */
+
+                               tty_hangup(tty);
+                               wake_up_interruptible(&ch->open_wait);
+                               ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE);
+
+                       } /* End if clear_bit */
+               }
+
+       } /* End EPCA_MAGIC */
+
+} /* End do_softint */
+
+/* ------------------------------------------------------------
+       pc_stop and pc_start provide software flow control to the 
+       routine and the pc_ioctl routine.
+---------------------------------------------------------------- */
+
+/* --------------------- Begin pc_stop  ----------------------- */
+
+static void pc_stop(struct tty_struct *tty)
+{ /* Begin pc_stop */
+
+       struct channel *ch;
+       unsigned long flags;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if valid channel */
+
+               save_flags(flags); 
+               cli();
+
+               if ((ch->statusflags & TXSTOPPED) == 0) 
+               { /* Begin if transmit stop requested */
+
+                       globalwinon(ch);
+
+                       /* STOP transmitting now !! */
+
+                       fepcmd(ch, PAUSETX, 0, 0, 0, 0);
+
+                       ch->statusflags |= TXSTOPPED;
+                       memoff(ch);
+
+               } /* End if transmit stop requested */
+
+               restore_flags(flags);
+
+       } /* End if valid channel */
+
+} /* End pc_stop */
+
+/* --------------------- Begin pc_start  ----------------------- */
+
+static void pc_start(struct tty_struct *tty)
+{ /* Begin pc_start */
+
+       struct channel *ch;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if channel valid */
+
+               unsigned long flags;
+
+               save_flags(flags);
+               cli();
+
+               /* Just in case output was resumed because of a change in Digi-flow */
+               if (ch->statusflags & TXSTOPPED) 
+               { /* Begin transmit resume requested */
+
+                       volatile struct board_chan *bc;
+
+                       globalwinon(ch);
+                       bc = ch->brdchan;
+                       if (ch->statusflags & LOWWAIT)
+                               bc->ilow = 1;
+
+                       /* Okay, you can start transmitting again... */
+
+                       fepcmd(ch, RESUMETX, 0, 0, 0, 0);
+
+                       ch->statusflags &= ~TXSTOPPED;
+                       memoff(ch);
+
+               } /* End transmit resume requested */
+
+               restore_flags(flags);
+
+       } /* End if channel valid */
+
+} /* End pc_start */
+
+/* ------------------------------------------------------------------
+       The below routines pc_throttle and pc_unthrottle are used 
+       to slow (And resume) the receipt of data into the kernels
+       receive buffers.  The exact occurence of this depends on the
+       size of the kernels receive buffer and what the 'watermarks'
+       are set to for that buffer.  See the n_ttys.c file for more
+       details. 
+______________________________________________________________________ */
+/* --------------------- Begin throttle  ----------------------- */
+
+static void pc_throttle(struct tty_struct * tty)
+{ /* Begin pc_throttle */
+
+       struct channel *ch;
+       unsigned long flags;
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if channel valid */
+
+
+               save_flags(flags);
+               cli();
+
+               if ((ch->statusflags & RXSTOPPED) == 0)
+               {
+                       globalwinon(ch);
+                       fepcmd(ch, PAUSERX, 0, 0, 0, 0);
+
+                       ch->statusflags |= RXSTOPPED;
+                       memoff(ch);
+               }
+               restore_flags(flags);
+
+       } /* End if channel valid */
+
+} /* End pc_throttle */
+
+/* --------------------- Begin unthrottle  ----------------------- */
+
+static void pc_unthrottle(struct tty_struct *tty)
+{ /* Begin pc_unthrottle */
+
+       struct channel *ch;
+       unsigned long flags;
+       volatile struct board_chan *bc;
+
+
+       /* ---------------------------------------------------------
+               verifyChannel returns the channel from the tty struct
+               if it is valid.  This serves as a sanity check.
+       ------------------------------------------------------------- */
+
+       if ((ch = verifyChannel(tty)) != NULL) 
+       { /* Begin if channel valid */
+
+
+               /* Just in case output was resumed because of a change in Digi-flow */
+               save_flags(flags);
+               cli();
+
+               if (ch->statusflags & RXSTOPPED) 
+               {
+
+                       globalwinon(ch);
+                       bc = ch->brdchan;
+                       fepcmd(ch, RESUMERX, 0, 0, 0, 0);
+
+                       ch->statusflags &= ~RXSTOPPED;
+                       memoff(ch);
+               }
+               restore_flags(flags);
+
+       } /* End if channel valid */
+
+} /* End pc_unthrottle */
+
+/* --------------------- Begin digi_send_break  ----------------------- */
+
+void digi_send_break(struct channel *ch, int msec)
+{ /* Begin digi_send_break */
+
+       unsigned long flags;
+
+       save_flags(flags);
+       cli();
+       globalwinon(ch);
+
+       /* -------------------------------------------------------------------- 
+          Maybe I should send an infinite break here, schedule() for
+          msec amount of time, and then stop the break.  This way,
+          the user can't screw up the FEP by causing digi_send_break()
+          to be called (i.e. via an ioctl()) more than once in msec amount 
+          of time.  Try this for now...
+       ------------------------------------------------------------------------ */
+
+       fepcmd(ch, SENDBREAK, msec, 0, 10, 0);
+       memoff(ch);
+
+       restore_flags(flags);
+
+} /* End digi_send_break */
+
+/* --------------------- Begin setup_empty_event  ----------------------- */
+
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch)
+{ /* Begin setup_empty_event */
+
+       volatile struct board_chan *bc = ch->brdchan;
+       unsigned long int flags;
+
+       save_flags(flags);
+       cli();
+       globalwinon(ch);
+       ch->statusflags |= EMPTYWAIT;
+       
+       /* ------------------------------------------------------------------
+               When set the iempty flag request a event to be generated when the 
+               transmit buffer is empty (If there is no BREAK in progress).
+       --------------------------------------------------------------------- */
+
+       bc->iempty = 1;
+       memoff(ch);
+       restore_flags(flags);
+
+} /* End setup_empty_event */
+
+/* --------------------- Begin get_termio ----------------------- */
+
+static int get_termio(struct tty_struct * tty, struct termio * termio)
+{ /* Begin get_termio */
+       int error;
+
+       error = verify_area(VERIFY_WRITE, termio, sizeof (struct termio));
+       if (error)
+               return error;
+
+       kernel_termios_to_user_termio(termio, tty->termios);
+
+       return 0;
+} /* End get_termio */
+/* ---------------------- Begin epca_setup  -------------------------- */
+void epca_setup(char *str, int *ints)
+{ /* Begin epca_setup */
+
+       struct board_info board;
+       int               index, loop, last;
+       char              *temp, *t2;
+       unsigned          len;
+
+       /* ----------------------------------------------------------------------
+               If this routine looks a little strange it is because it is only called
+               if a LILO append command is given to boot the kernel with parameters.  
+               In this way, we can provide the user a method of changing his board
+               configuration without rebuilding the kernel.
+       ----------------------------------------------------------------------- */
+       if (!liloconfig) 
+               liloconfig = 1; 
+
+       memset(&board, 0, sizeof(board));
+
+       /* Assume the data is int first, later we can change it */
+       /* I think that array position 0 of ints holds the number of args */
+       for (last = 0, index = 1; index <= ints[0]; index++)
+               switch(index)
+               { /* Begin parse switch */
+
+                       case 1:
+                               board.status = ints[index];
+                               
+                               /* ---------------------------------------------------------
+                                       We check for 2 (As opposed to 1; because 2 is a flag
+                                       instructing the driver to ignore epcaconfig.)  For this
+                                       reason we check for 2.
+                               ------------------------------------------------------------ */ 
+                               if (board.status == 2)
+                               { /* Begin ignore epcaconfig as well as lilo cmd line */
+                                       nbdevs = 0;
+                                       num_cards = 0;
+                                       return;
+                               } /* End ignore epcaconfig as well as lilo cmd line */
+       
+                               if (board.status > 2)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid board status 0x%x\n", board.status);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_BOARD_STATUS;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 2:
+                               board.type = ints[index];
+                               if (board.type >= PCIXEM) 
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid board type 0x%x\n", board.type);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_BOARD_TYPE;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 3:
+                               board.altpin = ints[index];
+                               if (board.altpin > 1)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid board altpin 0x%x\n", board.altpin);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_ALTPIN;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 4:
+                               board.numports = ints[index];
+                               if ((board.numports < 2) || (board.numports > 256))
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid board numports 0x%x\n", board.numports);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_NUM_PORTS;
+                                       return;
+                               }
+                               nbdevs += board.numports;
+                               last = index;
+                               break;
+
+                       case 5:
+                               board.port = (unsigned char *)ints[index];
+                               if (board.port <= 0)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid io port 0x%x\n", (unsigned int)board.port);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_PORT_BASE;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 6:
+                               board.membase = (unsigned char *)ints[index];
+                               if (board.membase <= 0)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid memory base 0x%x\n",(unsigned int)board.membase);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_MEM_BASE;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       default:
+                               printk(KERN_ERR "<Error> - epca_setup: Too many integer parms\n");
+                               return;
+
+               } /* End parse switch */
+
+       while (str && *str) 
+       { /* Begin while there is a string arg */
+
+               /* find the next comma or terminator */
+               temp = str;
+
+               /* While string is not null, and a comma hasn't been found */
+               while (*temp && (*temp != ','))
+                       temp++;
+
+               if (!*temp)
+                       temp = NULL;
+               else
+                       *temp++ = 0;
+
+               /* Set index to the number of args + 1 */
+               index = last + 1;
+
+               switch(index)
+               {
+                       case 1:
+                               len = strlen(str);
+                               if (strncmp("Disable", str, len) == 0) 
+                                       board.status = 0;
+                               else
+                               if (strncmp("Enable", str, len) == 0)
+                                       board.status = 1;
+                               else
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid status %s\n", str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_BOARD_STATUS;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 2:
+
+                               for(loop = 0; loop < EPCA_NUM_TYPES; loop++)
+                                       if (strcmp(board_desc[loop], str) == 0)
+                                               break;
+
+
+                               /* ---------------------------------------------------------------
+                                       If the index incremented above refers to a legitamate board 
+                                       type set it here. 
+                               ------------------------------------------------------------------*/
+
+                               if (index < EPCA_NUM_TYPES) 
+                                       board.type = loop;
+                               else
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid board type: %s\n", str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_BOARD_TYPE;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 3:
+                               len = strlen(str);
+                               if (strncmp("Disable", str, len) == 0) 
+                                       board.altpin = 0;
+                               else
+                               if (strncmp("Enable", str, len) == 0)
+                                       board.altpin = 1;
+                               else
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid altpin %s\n", str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_ALTPIN;
+                                       return;
+                               }
+                               last = index;
+                               break;
+
+                       case 4:
+                               t2 = str;
+                               while (isdigit(*t2))
+                                       t2++;
+
+                               if (*t2)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid port count %s\n", str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_NUM_PORTS;
+                                       return;
+                               }
+
+                               /* ------------------------------------------------------------
+                                       There is not a man page for simple_strtoul but the code can be 
+                                       found in vsprintf.c.  The first argument is the string to 
+                                       translate (To an unsigned long obviously),  the second argument
+                                       can be the address of any character variable or a NULL.  If a
+                                       variable is given, the end pointer of the string will be stored 
+                                       in that variable; if a NULL is given the the end pointer will 
+                                       not be returned.  The last argument is the base to use.  If 
+                                       a 0 is indicated, the routine will attempt to determine the 
+                                       proper base by looking at the values prefix (A '0' for octal,
+                                       a 'x' for hex, etc ...  If a value is given it will use that 
+                                       value as the base. 
+                               ---------------------------------------------------------------- */ 
+                               board.numports = simple_strtoul(str, NULL, 0);
+                               nbdevs += board.numports;
+                               last = index;
+                               break;
+
+                       case 5:
+                               t2 = str;
+                               while (isxdigit(*t2))
+                                       t2++;
+
+                               if (*t2)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid i/o address %s\n", str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_PORT_BASE;
+                                       return;
+                               }
+
+                               board.port = (unsigned char *)simple_strtoul(str, NULL, 16);
+                               last = index;
+                               break;
+
+                       case 6:
+                               t2 = str;
+                               while (isxdigit(*t2))
+                                       t2++;
+
+                               if (*t2)
+                               {
+                                       printk(KERN_ERR "<Error> - epca_setup: Invalid memory base %s\n",str);
+                                       invalid_lilo_config = 1;
+                                       setup_error_code |= INVALID_MEM_BASE;
+                                       return;
+                               }
+
+                               board.membase = (unsigned char *)simple_strtoul(str, NULL, 16);
+                               last = index;
+                               break;
+
+                       default:
+                               printk(KERN_ERR "PC/Xx: Too many string parms\n");
+                               return;
+               }
+               str = temp;
+
+       } /* End while there is a string arg */
+
+
+       if (last < 6)  
+       {
+               printk(KERN_ERR "PC/Xx: Insufficient parms specified\n");
+               return;
+       }
+       /* I should REALLY validate the stuff here */
+
+       /* Copies our local copy of board into boards */
+       memcpy((void *)&boards[num_cards],(void *)&board, sizeof(board));
+
+
+       /* Does this get called once per lilo arg are what ? */
+
+       printk(KERN_INFO "PC/Xx: Added board %i, %s %i ports at 0x%4.4X base 0x%6.6X\n", 
+               num_cards, board_desc[board.type], 
+               board.numports, (int)board.port, (unsigned int) board.membase);
+
+       num_cards++;
+
+} /* End epca_setup */
+
+
+
+#ifdef ENABLE_PCI
+/* --------------------- Begin get_PCI_configuration  ---------------------- */
+
+int get_PCI_configuration(char bus, char device_fn, 
+                          unsigned int *base_addr0, unsigned int *base_addr1,
+                          unsigned int *base_addr2, unsigned int *base_addr3,
+                          unsigned int *base_addr4, unsigned int *base_addr5)
+{ /* Begin get_PCI_configuration */
+
+       int     error;
+
+       error = pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_0,
+                                         base_addr0);
+
+       error |= pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_1,
+                                      base_addr1);
+
+       error |= pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_2,
+                                      base_addr2);
+
+       error |= pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_3,
+                                      base_addr3);
+
+       error |= pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_4,
+                                      base_addr4);
+
+       error |= pcibios_read_config_dword(bus, device_fn, PCI_BASE_ADDRESS_5,
+                                      base_addr5);
+
+       /* ------------------------------------------------------------------------
+                        NOTE - The code below mask out either the 2 or 4 bits dependant on the
+                               space being addressed. (base_addr value reflecting io space, have their
+                               first 2 bits mask out, while base_addr value reflecting mem space, have
+                               their first 4 bits mask out.)  These bits are flag bits and should always
+                               be 0 when used as an address.
+       ---------------------------------------------------------------------------- */
+
+       if ((*base_addr0) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr0) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr0) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if ((*base_addr1) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr1) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr1) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if ((*base_addr2) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr2) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr2) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if ((*base_addr3) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr3) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr3) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if ((*base_addr4) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr4) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr4) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if ((*base_addr5) & PCI_BASE_ADDRESS_SPACE_IO) /* Is this an io address ? */
+               (*base_addr5) &= PCI_BASE_ADDRESS_IO_MASK;
+       else
+               (*base_addr5) &= PCI_BASE_ADDRESS_MEM_MASK;
+
+       if (error) 
+       {
+               printk(KERN_ERR "<Error> - DIGI PCI error: board not initializing due to error\n");
+               return(0);
+       }
+       return(1);
+} /* End get_PCI_configuration */
+
+/* ------------------------ Begin init_PCI  --------------------------- */
+
+int init_PCI(int boards_found)
+{ /* Begin init_PCI */
+
+       unsigned char   bus, device_fn;
+       int i, pci_count = 0;
+       unsigned int base_addr0, base_addr1, base_addr2,
+                base_addr3, base_addr4, base_addr5;
+
+       base_addr0 = base_addr1 = base_addr2 = 0;
+       base_addr3 = base_addr4 = base_addr5 = 0;
+
+       for(i = 0; i < (MAXBOARDS - boards_found); i++) 
+       { /* Begin for each POSSIBLE remaining board */
+
+               if (!pcibios_find_device(PCI_VENDOR_DIGI, PCI_DEVICE_XR,
+                       i, &bus, &device_fn)) 
+               { /* Begin found XR */
+                       if (get_PCI_configuration(bus, device_fn, &base_addr0, &base_addr1,
+                                                   &base_addr2, &base_addr3,
+                                                   &base_addr4, &base_addr5))
+                       { /* Store a PCI/XR into the boards structure */
+
+
+                               boards[boards_found + pci_count].status   = ENABLED;
+                               boards[boards_found + pci_count].type     = PCIXR;
+
+                               boards[boards_found + pci_count].numports = 0x0;
+
+                               boards[boards_found + pci_count].port     = (unsigned char *)((char *)base_addr0 + PCI_IO_OFFSET);
+                               /* Most cards use base_addr0, but some use base_addr2 */
+                               boards[boards_found + pci_count].membase  = (unsigned char *)base_addr0;
+
+                               if (base_addr0 >= 0x100000)
+                               {
+                                       /* ------------------------------------------------------------
+                                               Standard physical addresses are valid to the kernel as long 
+                                               as they aren't above RAM. Higher addresses (Such as are 
+                                               typical of a PCI system) need to be mapped in with the 
+                                               ioremap command.  For boards using such high addresses the
+                                               driver will store both addresses.  One address (The physical)
+                                               will be held for the apps use (And mmap) and the other (The
+                                               ioremapped address) will be used in the kernel.
+                                       ---------------------------------------------------------------- */
+                                       boards[boards_found + pci_count].re_map_port     = ioremap((base_addr0 + PCI_IO_OFFSET),0x200000);
+                                       boards[boards_found + pci_count].re_map_membase  = ioremap(base_addr0, 0x200000);
+                               }
+
+                               pci_count++;
+
+                               /* --------------------------------------------------------------
+                                       I don't know what the below does, but the hardware guys say
+                                       its required on everything except PLX (In this case XRJ).
+                               ---------------------------------------------------------------- */
+                               pcibios_write_config_byte(bus, device_fn, 0x40, 0);  
+                               pcibios_write_config_byte(bus, device_fn, 0x46, 0);  
+
+                       } /* End store a PCI/XR into the board structure */
+                               
+               } /* End found XR */
+
+               if (!pcibios_find_device(PCI_VENDOR_DIGI, PCI_DEVICE_XEM,
+                       i, &bus, &device_fn)) 
+               { /* Begin found XEM */
+
+                       if (get_PCI_configuration(bus, device_fn, &base_addr0, &base_addr1,
+                                                                 &base_addr2, &base_addr3,
+                                                                 &base_addr4, &base_addr5))
+                       { /* Begin store a PCI/XEM into the boards structure */
+
+                               boards[boards_found + pci_count].status   = ENABLED;
+                               boards[boards_found + pci_count].type     = PCIXEM;
+
+                               boards[boards_found + pci_count].numports = 0x0;
+                               boards[boards_found + pci_count].port     = (char *)((char *)base_addr0 + PCI_IO_OFFSET);
+                               /* Most cards use base_addr0, but some use base_addr2 */
+                               boards[boards_found + pci_count].membase  = (unsigned char *)base_addr0;
+
+                               if (base_addr0 >= 0x100000)
+                               {
+                                       /* ------------------------------------------------------------
+                                               Standard physical addresses are valid to the kernel as long 
+                                               as they aren't above RAM. Higher addresses (Such as are 
+                                               typical of a PCI system) need to be mapped in with the 
+                                               ioremap command.  For boards using such high addresses the
+                                               driver will store both addresses.  One address (The physical)
+                                               will be held for the apps use (And mmap) and the other (The
+                                               vremapped address) will be used in the kernel.
+                                       ---------------------------------------------------------------- */
+                                       boards[boards_found + pci_count].re_map_port     = ioremap((base_addr0 + PCI_IO_OFFSET),0x200000);
+                                       boards[boards_found + pci_count].re_map_membase  = ioremap(base_addr0, 0x200000);
+                               }
+
+                               pci_count++;
+                               /* --------------------------------------------------------------
+                                       I don't know what the below does, but the hardware guys say
+                                       its required on everything except PLX (In this case XRJ).
+                               ---------------------------------------------------------------- */
+                               pcibios_write_config_byte(bus, device_fn, 0x40, 0);  
+                               pcibios_write_config_byte(bus, device_fn, 0x46, 0);  
+
+                       } /* End store a PCI/XEM into the boards structure */
+
+               } /* End found XEM */
+
+
+               if (!pcibios_find_device(PCI_VENDOR_DIGI, PCI_DEVICE_CX,
+                                        i, &bus, &device_fn)) 
+               { /* Begin found CX */
+
+                       if (get_PCI_configuration(bus, device_fn, &base_addr0, &base_addr1,
+                                                                 &base_addr2, &base_addr3,
+                                                                 &base_addr4, &base_addr5))
+                       { /* Begin store a PCI/CX into the boards structure */
+
+                               boards[boards_found + pci_count].status   = ENABLED;
+                               boards[boards_found + pci_count].type     = PCICX;
+
+                               boards[boards_found + pci_count].numports = 0x0;
+                               boards[boards_found + pci_count].port     = (char *)((char *)base_addr0 + PCI_IO_OFFSET);
+                               /* Most cards use base_addr0, but some use base_addr2 */
+                               boards[boards_found + pci_count].membase  = (unsigned char *)base_addr0;
+
+                               if (base_addr0 >= 0x100000)
+                               {
+                                       /* ------------------------------------------------------------
+                                               Standard physical addresses are valid to the kernel as long 
+                                               as they aren't above RAM. Higher addresses (Such as are 
+                                               typical of a PCI system) need to be mapped in with the 
+                                               ioremap command.  For boards using such high addresses the
+                                               driver will store both addresses.  One address (The physical)
+                                               will be held for the apps use (And mmap) and the other (The
+                                               vremapped address) will be used in the kernel.
+                                       ---------------------------------------------------------------- */
+                                       boards[boards_found + pci_count].re_map_port     = ioremap((base_addr0 + PCI_IO_OFFSET),0x200000);
+                                       boards[boards_found + pci_count].re_map_membase  = ioremap(base_addr0, 0x200000);
+                               }
+
+                               pci_count++;
+                               /* --------------------------------------------------------------
+                                       I don't know what the below does, but the hardware guys say
+                                       its required on everything except PLX (In this case XRJ).
+                               ---------------------------------------------------------------- */
+                               pcibios_write_config_byte(bus, device_fn, 0x40, 0);  
+                               pcibios_write_config_byte(bus, device_fn, 0x46, 0);  
+
+                       } /* End store a PCI/CX into the boards structure */
+
+               } /* End found CX */
+
+               if (!pcibios_find_device(PCI_VENDOR_DIGI, PCI_DEVICE_XRJ,
+                                        i, &bus, &device_fn)) 
+               { /* Begin found XRJ */
+
+                       if (get_PCI_configuration(bus, device_fn, &base_addr0, &base_addr1,
+                                                                 &base_addr2, &base_addr3,
+                                                                 &base_addr4, &base_addr5))
+                       { /* Begin store a PCI/XRJ into the boards structure */
+
+                               boards[boards_found + pci_count].status   = ENABLED;
+                               boards[boards_found + pci_count].type     = PCIXRJ;
+
+                               boards[boards_found + pci_count].numports = 0x0;
+                               boards[boards_found + pci_count].port     = (unsigned char *)(base_addr2 + PCI_IO_OFFSET);
+                               /* Most cards use base_addr0, but some use base_addr2 */
+                               boards[boards_found + pci_count].membase  = (unsigned char *)base_addr2;
+
+                               if (base_addr2 >= 0x100000)
+                               {
+                                       /* ------------------------------------------------------------
+                                               Standard physical addresses are valid to the kernel as long 
+                                               as they aren't above RAM. Higher addresses (Such as are 
+                                               typical of a PCI system) need to be mapped in with the 
+                                               ioremap command.  For boards using such high addresses the
+                                               driver will store both addresses.  One address (The physical)
+                                               will be held for the apps use (And mmap) and the other (The
+                                               vremapped address) will be used in the kernel.
+                                       ---------------------------------------------------------------- */
+                                       boards[boards_found + pci_count].re_map_port     = ioremap((base_addr2 + PCI_IO_OFFSET),0x200000);
+                                       boards[boards_found + pci_count].re_map_membase  = ioremap(base_addr2, 0x200000);
+                               }
+
+                               pci_count++;
+
+                       } /* End store a PCI/XRJ into the boards structure */
+
+               } /* End found XRJ */
+
+       } /* End for each POSSIBLE remaining board */
+
+       return(pci_count);
+
+} /* End init_PCI */
+
+#endif /* ENABLE_PCI */
+
+
index 969fb552ee7d45712a19b68c3375995bede0d0d1..adac2831a9b0c6dfb6a3cc78c4016e83ed2972c5 100644 (file)
@@ -1763,6 +1763,9 @@ __initfunc(int tty_init(void))
 #ifdef CONFIG_DIGI
        pcxe_init();
 #endif
+#ifdef CONFIG_DIGIEPCA
+       pc_init();
+#endif
 #ifdef CONFIG_RISCOM8
        riscom8_init();
 #endif
index a506f33994940b780b315a61ce167753fd29e026..357d309fa23c158a8802e07e21d83329454599e7 100644 (file)
@@ -12,7 +12,7 @@
 
 extern __inline__ void prim_spin_lock(struct spinlock *sp)
 {
-       int processor=hard_smp_processor_id();
+       int processor=smp_processor_id();
        
        /*
         *      Grab the lock bit
diff --git a/include/linux/digi1.h b/include/linux/digi1.h
new file mode 100644 (file)
index 0000000..184378d
--- /dev/null
@@ -0,0 +1,100 @@
+/*          Definitions for DigiBoard ditty(1) command.                 */
+
+#if !defined(TIOCMODG)
+#define        TIOCMODG        ('d'<<8) | 250          /* get modem ctrl state */
+#define        TIOCMODS        ('d'<<8) | 251          /* set modem ctrl state */
+#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_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_GETINFO    ('e'<<8) | 103          /* Fill in digi_info */
+#define        DIGI_POLLER     ('e'<<8) | 104          /* Turn on/off poller */
+#define        DIGI_INIT       ('e'<<8) | 105          /* Allow things to run. */
+
+struct digiflow_struct 
+{
+       unsigned char   startc;                         /* flow cntl start char */
+       unsigned char   stopc;                          /* flow cntl stop char  */
+};
+
+typedef struct digiflow_struct digiflow_t;
+
+
+/************************************************************************
+ * 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_FORCEDCD  0x0100          /* Force carrier                */
+#define        DIGI_ALTPIN     0x0200          /* Alternate RJ-45 pin config   */
+#define        DIGI_AIXON      0x0400          /* Aux flow control in fep      */
+
+
+/************************************************************************
+ * Values for digiDload
+ ************************************************************************/
+#define NORMAL  0
+#define PCI_CTL 1
+
+#define SIZE8  0
+#define SIZE16 1
+#define SIZE32 2
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct 
+{
+       unsigned short  digi_flags;             /* Flags (see above)    */
+};
+
+typedef struct digi_struct digi_t;
+
+struct digi_info 
+{
+       unsigned long board;        /* Which board is this ? */
+       unsigned char status;       /* Alive or dead */
+       unsigned char type;         /* see epca.h */
+       unsigned char subtype;      /* For future XEM, XR, etc ... */
+       unsigned short numports;    /* Number of ports configured */
+       unsigned char *port;        /* I/O Address */
+       unsigned char *membase;     /* DPR Address */
+       unsigned char *version;     /* For future ... */
+       unsigned short windowData;  /* For future ... */
+} ;
diff --git a/include/linux/digiFep1.h b/include/linux/digiFep1.h
new file mode 100644 (file)
index 0000000..c47d7fc
--- /dev/null
@@ -0,0 +1,136 @@
+
+#define CSTART       0x400L
+#define CMAX         0x800L
+#define ISTART       0x800L
+#define IMAX         0xC00L
+#define CIN          0xD10L
+#define GLOBAL       0xD10L
+#define EIN          0xD18L
+#define FEPSTAT      0xD20L
+#define CHANSTRUCT   0x1000L
+#define RXTXBUF      0x4000L
+
+
+struct global_data 
+{
+       volatile ushort cin;
+       volatile ushort cout;
+       volatile ushort cstart;
+       volatile ushort cmax;
+       volatile ushort ein;
+       volatile ushort eout;
+       volatile ushort istart;
+       volatile ushort imax;
+};
+
+
+struct board_chan 
+{
+       int filler1; 
+       int filler2;
+       volatile ushort tseg;
+       volatile ushort tin;
+       volatile ushort tout;
+       volatile ushort tmax;
+       
+       volatile ushort rseg;
+       volatile ushort rin;
+       volatile ushort rout;
+       volatile ushort rmax;
+       
+       volatile ushort tlow;
+       volatile ushort rlow;
+       volatile ushort rhigh;
+       volatile ushort incr;
+       
+       volatile ushort etime;
+       volatile ushort edelay;
+       volatile unchar *dev;
+       
+       volatile ushort iflag;
+       volatile ushort oflag;
+       volatile ushort cflag;
+       volatile ushort gmask;
+       
+       volatile ushort col;
+       volatile ushort delay;
+       volatile ushort imask;
+       volatile ushort tflush;
+
+       int filler3;
+       int filler4;
+       int filler5;
+       int filler6;
+       
+       volatile unchar num;
+       volatile unchar ract;
+       volatile unchar bstat;
+       volatile unchar tbusy;
+       volatile unchar iempty;
+       volatile unchar ilow;
+       volatile unchar idata;
+       volatile unchar eflag;
+       
+       volatile unchar tflag;
+       volatile unchar rflag;
+       volatile unchar xmask;
+       volatile unchar xval;
+       volatile unchar mstat;
+       volatile unchar mchange;
+       volatile unchar mint;
+       volatile unchar lstat;
+
+       volatile unchar mtran;
+       volatile unchar orun;
+       volatile unchar startca;
+       volatile unchar stopca;
+       volatile unchar startc;
+       volatile unchar stopc;
+       volatile unchar vnext;
+       volatile unchar hflow;
+
+       volatile unchar fillc;
+       volatile unchar ochar;
+       volatile unchar omask;
+
+       unchar filler7;
+       unchar filler8[28];
+}; 
+
+
+#define SRXLWATER      0xE0
+#define SRXHWATER      0xE1
+#define STOUT          0xE2
+#define PAUSETX        0xE3
+#define RESUMETX       0xE4
+#define SAUXONOFFC     0xE6
+#define SENDBREAK      0xE8
+#define SETMODEM       0xE9
+#define SETIFLAGS      0xEA
+#define SONOFFC        0xEB
+#define STXLWATER      0xEC
+#define PAUSERX        0xEE
+#define RESUMERX       0xEF
+#define SETBUFFER      0xF2
+#define SETCOOKED      0xF3
+#define SETHFLOW       0xF4
+#define SETCTRLFLAGS   0xF5
+#define SETVNEXT       0xF6
+
+
+
+#define BREAK_IND        0x01
+#define LOWTX_IND        0x02
+#define EMPTYTX_IND      0x04
+#define DATA_IND         0x08
+#define MODEMCHG_IND     0x20
+
+#define FEP_HUPCL  0002000
+#if 0
+#define RTS   0x02
+#define CD    0x08
+#define DSR   0x10
+#define CTS   0x20
+#define RI    0x40
+#define DTR   0x80
+#endif
diff --git a/include/linux/digiPCI.h b/include/linux/digiPCI.h
new file mode 100644 (file)
index 0000000..6ca7819
--- /dev/null
@@ -0,0 +1,42 @@
+/*************************************************************************
+ * Defines and structure definitions for PCI BIOS Interface 
+ *************************************************************************/
+#define        PCIMAX  32              /* maximum number of PCI boards */
+
+
+#define        PCI_VENDOR_DIGI         0x114F
+#define        PCI_DEVICE_EPC          0x0002
+#define        PCI_DEVICE_RIGHTSWITCH 0x0003  /* For testing */
+#define        PCI_DEVICE_XEM          0x0004
+#define        PCI_DEVICE_XR           0x0005
+#define        PCI_DEVICE_CX           0x0006
+#define        PCI_DEVICE_XRJ          0x0009   /* Jupiter boards with */
+#define        PCI_DEVICE_EPCJ         0x000a   /* PLX 9060 chip for PCI  */
+
+
+/*
+ * On the PCI boards, there is no IO space allocated 
+ * The I/O registers will be in the first 3 bytes of the   
+ * upper 2MB of the 4MB memory space.  The board memory 
+ * will be mapped into the low 2MB of the 4MB memory space 
+ */
+
+/* Potential location of PCI Bios from E0000 to FFFFF*/
+#define PCI_BIOS_SIZE          0x00020000      
+
+/* Size of Memory and I/O for PCI (4MB) */
+#define PCI_RAM_SIZE           0x00400000      
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE           0x00200000      
+
+/* Offset of I/0 in Memory (2MB) */
+#define PCI_IO_OFFSET          0x00200000      
+
+#define MEMOUTB(basemem, pnum, setmemval)  *(caddr_t)((basemem) + ( PCI_IO_OFFSET | pnum << 4 | pnum )) = (setmemval)
+#define MEMINB(basemem, pnum)  *(caddr_t)((basemem) + (PCI_IO_OFFSET | pnum << 4 | pnum ))   /* for PCI I/O */
+
+
+
+
+
diff --git a/include/linux/epca.h b/include/linux/epca.h
new file mode 100644 (file)
index 0000000..5049481
--- /dev/null
@@ -0,0 +1,170 @@
+#define XEMPORTS    0xC02
+#define XEPORTS     0xC22
+
+#define MAX_ALLOC   0x100
+
+#define MAXBOARDS   12
+#define FEPCODESEG  0x0200L
+#define FEPCODE     0x2000L
+#define BIOSCODE    0xf800L
+
+#define MISCGLOBAL  0x0C00L
+#define NPORT       0x0C22L
+#define MBOX        0x0C40L
+#define PORTBASE    0x0C90L
+
+/* Begin code defines used for epca_setup */
+
+#define INVALID_BOARD_TYPE   0x1
+#define INVALID_NUM_PORTS    0x2
+#define INVALID_MEM_BASE     0x4
+#define INVALID_PORT_BASE    0x8
+#define INVALID_BOARD_STATUS 0x10
+#define INVALID_ALTPIN       0x20
+
+/* End code defines used for epca_setup */
+
+
+#define FEPCLR      0x00
+#define FEPMEM      0x02
+#define FEPRST      0x04
+#define FEPINT      0x08
+#define        FEPMASK     0x0e
+#define        FEPWIN      0x80
+
+#define PCXE    0
+#define PCXEVE  1
+#define PCXEM   2   
+#define EISAXEM 3
+#define PC64XE  4
+#define PCXI    5
+#define PCIXEM  7
+#define PCICX   8
+#define PCIXR   9
+#define PCIXRJ  10
+#define EPCA_NUM_TYPES 6
+
+
+static char *board_desc[] = 
+{
+       "PC/Xe",
+       "PC/Xeve",
+       "PC/Xem",
+       "EISA/Xem",
+       "PC/64Xe",
+       "PC/Xi",
+       "unknown",
+       "PCI/Xem",
+       "PCI/CX",
+       "PCI/Xr",
+       "PCI/Xrj",
+};
+
+#define STARTC      021
+#define STOPC       023
+#define IAIXON      0x2000
+
+
+#define TXSTOPPED  0x1
+#define LOWWAIT    0x2
+#define EMPTYWAIT  0x4
+#define RXSTOPPED  0x8
+#define TXBUSY     0x10
+
+#define DISABLED   0
+#define ENABLED    1
+#define OFF        0
+#define ON         1
+
+#define FEPTIMEOUT 200000  
+#define SERIAL_TYPE_NORMAL  1
+#define SERIAL_TYPE_CALLOUT 2
+#define SERIAL_TYPE_INFO    3
+#define EPCA_EVENT_HANGUP   1
+#define EPCA_MAGIC          0x5c6df104L
+
+struct channel 
+{
+       long   magic;
+       unchar boardnum;
+       unchar channelnum;
+       unchar omodem;         /* FEP output modem status     */
+       unchar imodem;         /* FEP input modem status      */
+       unchar modemfake;      /* Modem values to be forced   */
+       unchar modem;          /* Force values                */
+       unchar hflow;
+       unchar dsr;
+       unchar dcd;
+       unchar m_rts ;          /* The bits used in whatever FEP */
+       unchar m_dcd ;          /* is indiginous to this board to */
+       unchar m_dsr ;          /* represent each of the physical */
+       unchar m_cts ;          /* handshake lines */
+       unchar m_ri ;
+       unchar m_dtr ;
+       unchar stopc;
+       unchar startc;
+       unchar stopca;
+       unchar startca;
+       unchar fepstopc;
+       unchar fepstartc;
+       unchar fepstopca;
+       unchar fepstartca;
+       unchar txwin;
+       unchar rxwin;
+       ushort fepiflag;
+       ushort fepcflag;
+       ushort fepoflag;
+       ushort txbufhead;
+       ushort txbufsize;
+       ushort rxbufhead;
+       ushort rxbufsize;
+       int    close_delay;
+       int    count;
+       int    blocked_open;
+       int    event;
+       int    asyncflags;
+       uint   dev;
+       long   session;
+       long   pgrp;
+       ulong  statusflags;
+       ulong  c_iflag;
+       ulong  c_cflag;
+       ulong  c_lflag;
+       ulong  c_oflag;
+       unchar *txptr;
+       unchar *rxptr;
+       unchar *tmp_buf;
+       struct board_info           *board;
+       volatile struct board_chan  *brdchan;
+       struct digi_struct          digiext;
+       struct tty_struct           *tty;
+       struct termios              normal_termios;
+       struct termios              callout_termios;
+       struct wait_queue           *open_wait;
+       struct wait_queue           *close_wait;
+       struct tq_struct            tqueue;
+       volatile struct global_data *mailbox;
+};
+
+struct board_info      
+{
+       unchar status;
+       unchar type;
+       unchar altpin;
+       ushort numports;
+       unchar *port;
+       unchar *membase;
+       unchar *re_map_port;
+       unchar *re_map_membase;
+       ulong  memory_seg;
+       void ( * memwinon )     (struct board_info *, unsigned int) ;
+       void ( * memwinoff )    (struct board_info *, unsigned int) ;
+       void ( * globalwinon )  (struct channel *) ;
+       void ( * txwinon )      (struct channel *) ;
+       void ( * rxwinon )      (struct channel *) ;
+       void ( * memoff )       (struct channel *) ;
+       void ( * assertgwinon ) (struct channel *) ;
+       void ( * assertmemoff ) (struct channel *) ;
+       unchar poller_inhibited ;
+};
+
diff --git a/include/linux/epcaconfig.h b/include/linux/epcaconfig.h
new file mode 100644 (file)
index 0000000..c840c67
--- /dev/null
@@ -0,0 +1,8 @@
+#define NUMCARDS 1
+#define NBDEVS 2
+
+struct board_info static_boards[NUMCARDS]={
+       { ENABLED, 0, OFF, 2, (unchar*) 0x320, (unchar*) 0xd0000 },
+};
+
+/* DO NOT HAND EDIT THIS FILE! */
index ca74d3ec9fe605ffdd72bc4cde64783196805a82..cab84d21299b7ecd39aee3bf8b2444e0f597ccc7 100644 (file)
@@ -288,6 +288,7 @@ extern int lp_init(void);
 extern int pty_init(void);
 extern int tty_init(void);
 extern int pcxe_init(void);
+extern int pc_init(void);
 extern int vcs_init(void);
 extern int cy_init(void);
 extern int stl_init(void);
@@ -330,11 +331,15 @@ extern int n_tty_ioctl(struct tty_struct * tty, struct file * file,
 /* serial.c */
 
 extern long serial_console_init(long kmem_start, long kmem_end);
-
 /* pcxx.c */
 
 extern int pcxe_open(struct tty_struct *tty, struct file *filp);
 
+/* epca.c */
+
+extern int pc_open(struct tty_struct *tty, struct file *filp);
+
 /* console.c */
 
 extern void update_screen(int new_console);
index 0d7d8570a465d0c6c49a9e022c6df25cd0e41a35..0f6cba37987339928e741a8adc41cae862f91af3 100644 (file)
@@ -175,6 +175,9 @@ extern void teles_setup(char *str, int *ints);
 #ifdef CONFIG_ISDN_DRV_HISAX
 extern void HiSax_setup(char *str, int *ints);
 #endif
+#ifdef CONFIG_DIGIEPCA
+extern void epca_setup(char *str, int *ints);
+#endif
 #ifdef CONFIG_ISDN_DRV_PCBIT
 extern void pcbit_setup(char *str, int *ints);
 #endif
@@ -481,6 +484,9 @@ struct {
 #ifdef CONFIG_DIGI
        { "digi=", pcxx_setup },
 #endif
+#ifdef CONFIG_DIGIEPCA
+       { "digiepca=", epca_setup },
+#endif
 #ifdef CONFIG_RISCOM8
        { "riscom8=", riscom8_setup },
 #endif
index 55a9dbf48316ad86e1582c268de61abae0b60254..1abe1ef2ea1b2d1bc82ce4a1910faf8a6aca8137 100644 (file)
@@ -520,7 +520,7 @@ static void exit_notify(void)
 NORET_TYPE void do_exit(long code)
 {
        if (in_interrupt()) {
-               local_irq_count[hard_smp_processor_id()] = 0;   /* Not really correct */
+               local_irq_count[smp_processor_id()] = 0;        /* Not really correct */
                printk("Aiee, killing interrupt handler\n");
        }
 fake_volatile:
index 64d264d633740f612ecdb5744c6e55f14f4ebf3d..0d5d619b0932f07a2767de6047f483f75c11105b 100644 (file)
@@ -28,7 +28,7 @@
 
 #include <asm/uaccess.h>
 
-#define LOG_BUF_LEN    (8192*4)
+#define LOG_BUF_LEN    8192
 
 static char buf[1024];
 
index 89745bbdbb86af70329938f55169246f9c8b4001..6b9b41aa588c26d026ce94f7c08d32ed707d35fe 100644 (file)
@@ -62,7 +62,7 @@ static inline void run_bottom_halves(void)
 
 asmlinkage void do_bottom_half(void)
 {
-       int cpu = hard_smp_processor_id();
+       int cpu = smp_processor_id();
 
        if (hardirq_trylock(cpu)) {
                if (softirq_trylock()) {