From: murray@riverstone.com.au (M. Steve Bennett)
Subject: L68K: PLIP and LP support for GVP IO-Extender
To: linux-m68k@phil.uni-sb.de (Linux Mailing List)
Date: Sat, 29 Mar 1997 21:03:15 +1000 (EST)
Sender: owner-linux-m68k@phil.uni-sb.de
Reply-To: "M. Steve Bennett" <msteveb@ozemail.com.au>

Hi all,

I've added PLIP and parallel printer support for the GVP IO-Extender.
This patch is against 2.1.29, but it should fit into 2.0.x without
many problems. This PLIP is compatible with Linux/i386 PLIP. Use a
standard parallel LapLink cable.

I had to make a few changes to the lp_m68k stuff to get printing to
work. The amiga builtin parallel port still works, but perhaps someone
with a MultiFace card can check that printing still works there.

Steve

diff -u arch/m68k/config.in
--- arch/m68k/config.in.orig
+++ arch/m68k/config.in	Sat Mar 29 15:31:47 1997
@@ -221,7 +221,9 @@
 fi
 if [ "$CONFIG_AMIGA" = "y" ]; then
   tristate 'Amiga builtin serial support' CONFIG_AMIGA_BUILTIN_SERIAL
-  tristate 'GVP IO-Extender serial support' CONFIG_GVPIOEXT
+  tristate 'GVP IO-Extender support' CONFIG_GVPIOEXT
+  dep_tristate 'GVP IO-Extender PLIP support' CONFIG_GVPIOEXT_PLIP $CONFIG_GVPIOEXT
+  dep_tristate 'GVP IO-Extender parallel printer support' CONFIG_GVPIOEXT_LP $CONFIG_GVPIOEXT
   tristate 'Multiface Card III serial support' CONFIG_MULTIFACE_III_TTY
 fi
 if [ "$CONFIG_ATARI_MFPSER" = "y" -o "$CONFIG_ATARI_SCC" = "y" -o \
diff -u drivers/char/Makefile
--- drivers/char/Makefile.orig
+++ drivers/char/Makefile	Sat Mar 29 19:24:17 1997
@@ -96,12 +96,27 @@
 endif
 
 ifeq ($(CONFIG_GVPIOEXT),y)
-L_OBJS += ser_ioext.o
-S = y
-  else
+  L_OBJS += ser_ioext.o
+  S = y
+  ifeq ($(CONFIG_GVPIOEXT_PLIP),y)
+    L_OBJS += plip_ioext.o
+  endif
+  ifeq ($(CONFIG_GVPIOEXT_LP),y)
+    L = y
+    L_OBJS += lp_ioext.o
+  endif
+else
   ifeq ($(CONFIG_GVPIOEXT),m)
-  M_OBJS += ser_ioext.o
-  SM = y
+    M_OBJS += ioext.o
+    SM = y
+    IOEXT_OBJS = ser_ioext.o
+    ifeq ($(CONFIG_GVPIOEXT_PLIP),m)
+      IOEXT_OBJS += plip_ioext.o
+    endif
+    ifeq ($(CONFIG_GVPIOEXT_LP),m)
+      IOEXT_OBJS += lp_ioext.o
+      LM = y
+    endif
   endif
 endif
 
@@ -393,3 +408,6 @@
 
 defkeymap.c: defkeymap.map
 	loadkeys --mktable defkeymap.map > defkeymap.c
+
+ioext.o: $(IOEXT_OBJS)
+	$(LD) -r -o $@ $^
diff -u drivers/char/plip_ioext.c
--- drivers/char/plip_ioext.c.orig
+++ drivers/char/plip_ioext.c	Sat Mar 29 16:51:09 1997
@@ -0,0 +1,1051 @@
+/*
+ * plip_ioext: A parallel port "network" driver for GVP IO-Extender.
+ *
+ * Authors:	See drivers/net/plip.c
+ *              IO-Extender version by Steve Bennett, <msteveb@ozemail.com.au>
+ *
+ * This driver is for use with a 5-bit cable (LapLink (R) cable).
+ */
+
+static const char *version = "NET3 PLIP version 2.2/m68k";
+
+#define __NO_VERSION__
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/malloc.h>
+#include <linux/termios.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+
+#include <asm/setup.h>
+#include <asm/irq.h>
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+#include <linux/zorro.h>
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/ptrace.h>
+#include <linux/if_ether.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+
+#include <linux/in.h>
+#include <linux/delay.h>
+/*#include <linux/lp_m68k.h>*/
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_plip.h>
+
+#include <linux/tqueue.h>
+#include <linux/ioport.h>
+#include <asm/bitops.h>
+#include <asm/byteorder.h>
+
+#include "ioext.h"
+
+#define DEBUG 0
+
+/* Map 'struct device *' to our control structure */
+#define PLIP_DEV(DEV) (&ioext_info[(DEV)->irq])
+
+/************************************************************************
+**
+** PLIP definitions
+**
+*************************************************************************
+*/
+
+/* Use 0 for production, 1 for verification, >2 for debug */
+#ifndef NET_DEBUG
+#define NET_DEBUG 2
+#endif
+static unsigned int net_debug = NET_DEBUG;
+
+/* In micro second */
+#define PLIP_DELAY_UNIT       1
+
+/* Connection time out = PLIP_TRIGGER_WAIT * PLIP_DELAY_UNIT usec */
+#define PLIP_TRIGGER_WAIT	 500
+
+/* Nibble time out = PLIP_NIBBLE_WAIT * PLIP_DELAY_UNIT usec */
+#define PLIP_NIBBLE_WAIT        3000
+
+#define PAR_DATA(dev)    ((dev)->base_addr+0)
+#define PAR_STATUS(dev)    ((dev)->base_addr+2)
+#define PAR_CONTROL(dev)  ((dev)->base_addr+4)
+
+static void enable_par_irq(struct device *dev, int on);
+static int plip_init(struct device *dev);
+
+/* Bottom halfs */
+static void plip_kick_bh(struct device *dev);
+static void plip_bh(struct device *dev);
+
+/* Functions for DEV methods */
+static int plip_rebuild_header(struct sk_buff *skb);
+static int plip_tx_packet(struct sk_buff *skb, struct device *dev);
+static int plip_open(struct device *dev);
+static int plip_close(struct device *dev);
+static struct enet_statistics *plip_get_stats(struct device *dev);
+static int plip_config(struct device *dev, struct ifmap *map);
+static int plip_ioctl(struct device *dev, struct ifreq *ifr, int cmd);
+
+enum plip_connection_state {
+  PLIP_CN_NONE=0,
+  PLIP_CN_RECEIVE,
+  PLIP_CN_SEND,
+  PLIP_CN_CLOSING,
+  PLIP_CN_ERROR
+};
+
+enum plip_packet_state {
+  PLIP_PK_DONE=0,
+  PLIP_PK_TRIGGER,
+  PLIP_PK_LENGTH_LSB,
+  PLIP_PK_LENGTH_MSB,
+  PLIP_PK_DATA,
+  PLIP_PK_CHECKSUM
+};
+
+enum plip_nibble_state {
+  PLIP_NB_BEGIN,
+  PLIP_NB_1,
+  PLIP_NB_2,
+};
+
+struct plip_local {
+  enum plip_packet_state state;
+  enum plip_nibble_state nibble;
+  union {
+    struct {
+#if defined(__LITTLE_ENDIAN)
+      unsigned char lsb;
+      unsigned char msb;
+#elif defined(__BIG_ENDIAN)
+      unsigned char msb;
+      unsigned char lsb;
+#else
+#error  "Please fix the endianness defines in <asm/byteorder.h>"
+#endif            
+    } b;
+    unsigned short h;
+  } length;
+  unsigned short byte;
+  unsigned char  checksum;
+  unsigned char  data;
+  struct sk_buff *skb;
+};
+
+struct net_local {
+  struct enet_statistics enet_stats;
+  struct tq_struct immediate;
+  struct tq_struct deferred;
+  struct plip_local snd_data;
+  struct plip_local rcv_data;
+  unsigned long  trigger;
+  unsigned long  nibble;
+  enum plip_connection_state connection;
+  unsigned short timeout_count;
+  char is_deferred;
+  int (*orig_rebuild_header)(struct sk_buff *skb);
+};
+
+struct device ioext_dev_plip[] = {
+  {
+    "plip0",
+    0, 0, 0, 0,    /* memory */
+    0, 0,    /* base, irq */
+    0, 0, 0, NULL, plip_init 
+  },
+  {
+    "plip1",
+    0, 0, 0, 0,    /* memory */
+    0, 0,    /* base, irq */
+    0, 0, 0, NULL, plip_init 
+  },
+  {
+    "plip2",
+    0, 0, 0, 0,    /* memory */
+    0, 0,    /* base, irq */
+    0, 0, 0, NULL, plip_init 
+  }
+};
+
+/*
+ * Check for and handle an interrupt for this PLIP device.
+ *
+ */
+void ioext_plip_interrupt(struct device *dev, int *spurious_count)
+{
+  struct net_local *nl;
+  struct plip_local *rcv;
+  unsigned char c0;
+  unsigned long flags;
+
+  nl = (struct net_local *)dev->priv;
+  rcv = &nl->rcv_data;
+
+  c0 = inb(PAR_STATUS(dev));
+
+  if (dev->interrupt) {
+    return;
+  }
+
+  if ((c0 & 0xf8) != 0xc0) {
+    /* Not for us */
+    ++*spurious_count;
+    return;
+  }
+
+  *spurious_count = 0;
+  dev->interrupt = 1;
+
+  save_flags(flags);
+  cli();
+
+  switch (nl->connection) {
+  case PLIP_CN_CLOSING:
+          dev->tbusy = 0;
+  case PLIP_CN_NONE:
+  case PLIP_CN_SEND:
+          dev->last_rx = jiffies;
+          rcv->state = PLIP_PK_TRIGGER;
+          nl->connection = PLIP_CN_RECEIVE;
+          nl->timeout_count = 0;
+          queue_task(&nl->immediate, &tq_immediate);
+          mark_bh(IMMEDIATE_BH);
+          restore_flags(flags);
+#if 0
+          printk("%s: receive irq in SEND/NONE/CLOSING (%d) ok\n", dev->name, nl->connection);
+#endif
+          break;
+
+  case PLIP_CN_RECEIVE:
+          restore_flags(flags);
+          printk("%s: receive interrupt when receiving packet\n", dev->name);
+          break;
+
+  case PLIP_CN_ERROR:
+          restore_flags(flags);
+          printk("%s: receive interrupt in error state\n", dev->name);
+          break;
+  }
+}
+
+
+/* Bottom half handler for the delayed request.
+   This routine is kicked by do_timer().
+   Request `plip_bh' to be invoked. */
+static void
+plip_kick_bh(struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+
+  if (nl->is_deferred) {
+    queue_task(&nl->immediate, &tq_immediate);
+    mark_bh(IMMEDIATE_BH);
+  }
+}
+
+/* Forward declarations of internal routines */
+static int plip_none(struct device *, struct net_local *,
+         struct plip_local *, struct plip_local *);
+static int plip_receive_packet(struct device *, struct net_local *,
+             struct plip_local *, struct plip_local *);
+static int plip_send_packet(struct device *, struct net_local *,
+          struct plip_local *, struct plip_local *);
+static int plip_connection_close(struct device *, struct net_local *,
+         struct plip_local *, struct plip_local *);
+static int plip_error(struct device *, struct net_local *,
+          struct plip_local *, struct plip_local *);
+static int plip_bh_timeout_error(struct device *dev, struct net_local *nl,
+         struct plip_local *snd,
+         struct plip_local *rcv,
+         int error);
+
+#define OK        0
+#define TIMEOUT   1
+#define ERROR     2
+
+typedef int (*plip_func)(struct device *dev, struct net_local *nl,
+       struct plip_local *snd, struct plip_local *rcv);
+
+static plip_func connection_state_table[] =
+{
+  plip_none,
+  plip_receive_packet,
+  plip_send_packet,
+  plip_connection_close,
+  plip_error
+};
+
+/*
+** enable_par_irq()
+** 
+** Enable or disable parallel irq for 'dev' according to 'on'.
+**
+** It is NOT possible to disable only the parallel irq.
+** So we disable the board interrupt instead. This means that
+** during reception of a PLIP packet, no serial interrupts can
+** happen. Sorry.
+*/
+static void enable_par_irq(struct device *dev, int on)
+{
+  if (on) {
+    PLIP_DEV(dev)->board->CNTR |= GVP_IRQ_ENA;
+  }
+  else {
+    PLIP_DEV(dev)->board->CNTR &= ~GVP_IRQ_ENA;
+  }
+}
+
+/* Bottom half handler of PLIP. */
+static void
+plip_bh(struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+  struct plip_local *snd = &nl->snd_data;
+  struct plip_local *rcv = &nl->rcv_data;
+  plip_func f;
+  int r;
+
+  nl->is_deferred = 0;
+  f = connection_state_table[nl->connection];
+  if ((r = (*f)(dev, nl, snd, rcv)) != OK
+      && (r = plip_bh_timeout_error(dev, nl, snd, rcv, r)) != OK) {
+    nl->is_deferred = 1;
+    queue_task(&nl->deferred, &tq_timer);
+  }
+}
+
+static int
+plip_bh_timeout_error(struct device *dev, struct net_local *nl,
+          struct plip_local *snd, struct plip_local *rcv,
+          int error)
+{
+  unsigned char c0;
+  unsigned long flags;
+
+  save_flags(flags);
+  cli();
+  if (nl->connection == PLIP_CN_SEND) {
+
+    if (error != ERROR) { /* Timeout */
+      nl->timeout_count++;
+      if ((snd->state == PLIP_PK_TRIGGER
+           && nl->timeout_count <= 10)
+          || nl->timeout_count <= 3) {
+        restore_flags(flags);
+        /* Try again later */
+        return TIMEOUT;
+      }
+      c0 = inb(PAR_STATUS(dev));
+      printk(KERN_INFO "%s: transmit timeout(%d,%02x)\n",
+             dev->name, snd->state, c0);
+    }
+    nl->enet_stats.tx_errors++;
+    nl->enet_stats.tx_aborted_errors++;
+  } else if (nl->connection == PLIP_CN_RECEIVE) {
+    if (rcv->state == PLIP_PK_TRIGGER) {
+      /* Transmission was interrupted. */
+      restore_flags(flags);
+      return OK;
+    }
+    if (error != ERROR) { /* Timeout */
+      if (++nl->timeout_count <= 3) {
+        restore_flags(flags);
+        /* Try again later */
+        return TIMEOUT;
+      }
+      c0 = inb(PAR_STATUS(dev));
+      printk(KERN_INFO "%s: receive timeout(%d,%02x)\n",
+             dev->name, rcv->state, c0);
+    }
+    nl->enet_stats.rx_dropped++;
+  }
+  rcv->state = PLIP_PK_DONE;
+  if (rcv->skb) {
+    kfree_skb(rcv->skb, FREE_READ);
+    rcv->skb = NULL;
+  }
+  snd->state = PLIP_PK_DONE;
+  if (snd->skb) {
+    dev_kfree_skb(snd->skb, FREE_WRITE);
+    snd->skb = NULL;
+  }
+  enable_par_irq(dev, 0);
+  dev->tbusy = 1;
+  nl->connection = PLIP_CN_ERROR;
+  outb(0x00, PAR_DATA(dev));
+  restore_flags(flags);
+
+  return TIMEOUT;
+}
+
+static int
+plip_none(struct device *dev, struct net_local *nl,
+    struct plip_local *snd, struct plip_local *rcv)
+{
+  return OK;
+}
+
+/* PLIP_RECEIVE --- receive a byte(two nibbles)
+   Returns OK on success, TIMEOUT on timeout */
+inline static int
+plip_receive(struct device *dev, unsigned short nibble_timeout, 
+       enum plip_nibble_state *ns_p, unsigned char *data_p)
+{
+  unsigned char c0, c1;
+  unsigned int cx;
+
+  switch (*ns_p) {
+  case PLIP_NB_BEGIN:
+    cx = nibble_timeout;
+    while (1) {
+      c0 = inb(PAR_STATUS(dev));
+      udelay(PLIP_DELAY_UNIT);
+      if ((c0 & 0x80) == 0) {
+        c1 = inb(PAR_STATUS(dev));
+        if (c0 == c1)
+          break;
+      }
+      if (--cx == 0)
+        return TIMEOUT;
+    }
+#if 0
+    printk("received first nybble: %02X -> %02X\n",
+      c0, (c0 >> 3) & 0x0F);
+#endif
+    *data_p = (c0 >> 3) & 0x0f;
+    outb(0x10, PAR_DATA(dev)); /* send ACK */
+    *ns_p = PLIP_NB_1;
+
+  case PLIP_NB_1:
+    cx = nibble_timeout;
+    while (1) {
+      c0 = inb(PAR_STATUS(dev));
+      udelay(PLIP_DELAY_UNIT);
+      if (c0 & 0x80) {
+        c1 = inb(PAR_STATUS(dev));
+        if (c0 == c1)
+          break;
+      }
+      if (--cx == 0)
+        return TIMEOUT;
+    }
+#if 0
+    printk("received second nybble: %02X -> %02X\n",
+      c0, (c0 << 1) & 0xF0);
+#endif
+    *data_p |= (c0 << 1) & 0xf0;
+    outb(0x00, PAR_DATA(dev)); /* send ACK */
+    *ns_p = PLIP_NB_BEGIN;
+  case PLIP_NB_2:
+    break;
+  }
+  return OK;
+}
+
+/* PLIP_RECEIVE_PACKET --- receive a packet */
+static int
+plip_receive_packet(struct device *dev, struct net_local *nl,
+        struct plip_local *snd, struct plip_local *rcv)
+{
+  unsigned short nibble_timeout = nl->nibble;
+  unsigned char *lbuf;
+  unsigned long flags;
+
+  switch (rcv->state) {
+  case PLIP_PK_TRIGGER:
+    enable_par_irq(dev, 0);
+    dev->interrupt = 0;
+    outb(0x01, PAR_DATA(dev)); /* send ACK */
+    if (net_debug > 2)
+      printk(KERN_DEBUG "%s: receive start\n", dev->name);
+    rcv->state = PLIP_PK_LENGTH_LSB;
+    rcv->nibble = PLIP_NB_BEGIN;
+
+  case PLIP_PK_LENGTH_LSB:
+    if (snd->state != PLIP_PK_DONE) {
+      if (plip_receive(dev, nl->trigger,
+           &rcv->nibble, &rcv->length.b.lsb)) {
+        /* collision, here dev->tbusy == 1 */
+        rcv->state = PLIP_PK_DONE;
+        nl->is_deferred = 1;
+        nl->connection = PLIP_CN_SEND;
+        queue_task(&nl->deferred, &tq_timer);
+        enable_par_irq(dev, 1);
+        return OK;
+      }
+    } else {
+      if (plip_receive(dev, nibble_timeout, 
+           &rcv->nibble, &rcv->length.b.lsb))
+        return TIMEOUT;
+    }
+    rcv->state = PLIP_PK_LENGTH_MSB;
+
+  case PLIP_PK_LENGTH_MSB:
+    if (plip_receive(dev, nibble_timeout, 
+         &rcv->nibble, &rcv->length.b.msb))
+      return TIMEOUT;
+    if (rcv->length.h > dev->mtu + dev->hard_header_len
+        || rcv->length.h < 8) {
+      printk(KERN_INFO "%s: bogus packet size %d.\n", dev->name, rcv->length.h);
+      return ERROR;
+    }
+    /* Malloc up new buffer. */
+    rcv->skb = dev_alloc_skb(rcv->length.h);
+    if (rcv->skb == NULL) {
+      printk(KERN_INFO "%s: Memory squeeze.\n", dev->name);
+      return ERROR;
+    }
+    skb_put(rcv->skb,rcv->length.h);
+    rcv->skb->dev = dev;
+    rcv->state = PLIP_PK_DATA;
+    rcv->byte = 0;
+    rcv->checksum = 0;
+
+  case PLIP_PK_DATA:
+    lbuf = rcv->skb->data;
+    do
+      if (plip_receive(dev, nibble_timeout, 
+           &rcv->nibble, &lbuf[rcv->byte]))
+        return TIMEOUT;
+    while (++rcv->byte < rcv->length.h);
+    do
+      rcv->checksum += lbuf[--rcv->byte];
+    while (rcv->byte);
+    rcv->state = PLIP_PK_CHECKSUM;
+
+  case PLIP_PK_CHECKSUM:
+    if (plip_receive(dev, nibble_timeout, 
+         &rcv->nibble, &rcv->data))
+      return TIMEOUT;
+    if (rcv->data != rcv->checksum) {
+      nl->enet_stats.rx_crc_errors++;
+      if (net_debug)
+        printk(KERN_INFO "%s: checksum error\n", dev->name);
+      return ERROR;
+    }
+    rcv->state = PLIP_PK_DONE;
+
+  case PLIP_PK_DONE:
+    /* Inform the upper layer for the arrival of a packet. */
+    rcv->skb->protocol=eth_type_trans(rcv->skb, dev);
+    netif_rx(rcv->skb);
+    nl->enet_stats.rx_packets++;
+    rcv->skb = NULL;
+    if (net_debug > 2)
+      printk(KERN_DEBUG "%s: receive end\n", dev->name);
+
+    /* Close the connection. */
+    outb (0x00, PAR_DATA(dev));
+
+    save_flags(flags);
+    cli();
+    if (snd->state != PLIP_PK_DONE) {
+      nl->connection = PLIP_CN_SEND;
+      restore_flags(flags);
+      queue_task(&nl->immediate, &tq_immediate);
+      mark_bh(IMMEDIATE_BH);
+      enable_par_irq(dev, 1);
+      return OK;
+    } else {
+      nl->connection = PLIP_CN_NONE;
+      restore_flags(flags);
+      enable_par_irq(dev, 1);
+      return OK;
+    }
+  }
+  return OK;
+}
+
+/* PLIP_SEND --- send a byte (two nibbles) 
+   Returns OK on success, TIMEOUT when timeout    */
+inline static int
+plip_send(struct device *dev, unsigned short nibble_timeout, 
+    enum plip_nibble_state *ns_p, unsigned char data)
+{
+  unsigned char c0;
+  unsigned int cx;
+
+  switch (*ns_p) {
+  case PLIP_NB_BEGIN:
+    outb((data & 0x0f), PAR_DATA(dev));
+    *ns_p = PLIP_NB_1;
+
+  case PLIP_NB_1:
+    outb(0x10 | (data & 0x0f), PAR_DATA(dev));
+    cx = nibble_timeout;
+    while (1) {
+      c0 = inb(PAR_STATUS(dev));
+      if ((c0 & 0x80) == 0) 
+        break;
+      if (--cx == 0)
+        return TIMEOUT;
+      udelay(PLIP_DELAY_UNIT);
+    }
+    outb(0x10 | (data >> 4), PAR_DATA(dev));
+    *ns_p = PLIP_NB_2;
+
+  case PLIP_NB_2:
+    outb((data >> 4), PAR_DATA(dev));
+    cx = nibble_timeout;
+    while (1) {
+      c0 = inb(PAR_STATUS(dev));
+      if (c0 & 0x80)
+        break;
+      if (--cx == 0)
+        return TIMEOUT;
+      udelay(PLIP_DELAY_UNIT);
+    }
+    *ns_p = PLIP_NB_BEGIN;
+    return OK;
+  }
+  return OK;
+}
+
+/* PLIP_SEND_PACKET --- send a packet */
+static int
+plip_send_packet(struct device *dev, struct net_local *nl,
+     struct plip_local *snd, struct plip_local *rcv)
+{
+  unsigned short nibble_timeout = nl->nibble;
+  unsigned char *lbuf;
+  unsigned char c0;
+  unsigned int cx;
+  unsigned long flags;
+
+  if (snd->skb == NULL || (lbuf = snd->skb->data) == NULL) {
+    printk(KERN_INFO "%s: send skb lost\n", dev->name);
+    snd->state = PLIP_PK_DONE;
+    snd->skb = NULL;
+    return ERROR;
+  }
+
+  if (snd->length.h == 0) {
+    return OK;
+  }
+
+  switch (snd->state) {
+  case PLIP_PK_TRIGGER:
+    if ((inb(PAR_STATUS(dev)) & 0xf8) != 0x80)
+      return TIMEOUT;
+
+    /* Trigger remote rx interrupt. */
+    outb(0x08, PAR_DATA(dev));
+    cx = nl->trigger;
+    while (1) {
+      udelay(PLIP_DELAY_UNIT);
+                        save_flags(flags);
+      cli();
+      if (nl->connection == PLIP_CN_RECEIVE) {
+        restore_flags(flags);
+        /* interrupted */
+        nl->enet_stats.collisions++;
+        if (net_debug > 1)
+          printk(KERN_INFO "%s: collision.\n", dev->name);
+        return OK;
+      }
+      c0 = inb(PAR_STATUS(dev));
+      if (c0 & 0x08) {
+        enable_par_irq(dev, 0);
+        if (net_debug > 2)
+          printk(KERN_DEBUG "%s: send start\n", dev->name);
+        snd->state = PLIP_PK_LENGTH_LSB;
+        snd->nibble = PLIP_NB_BEGIN;
+        nl->timeout_count = 0;
+        restore_flags(flags);
+        break;
+      }
+      restore_flags(flags);
+      if (--cx == 0) {
+        outb(0x00, PAR_DATA(dev));
+        return TIMEOUT;
+      }
+    }
+
+  case PLIP_PK_LENGTH_LSB:
+    if (plip_send(dev, nibble_timeout, 
+            &snd->nibble, snd->length.b.lsb))
+      return TIMEOUT;
+    snd->state = PLIP_PK_LENGTH_MSB;
+
+  case PLIP_PK_LENGTH_MSB:
+    if (plip_send(dev, nibble_timeout, 
+            &snd->nibble, snd->length.b.msb))
+      return TIMEOUT;
+    snd->state = PLIP_PK_DATA;
+    snd->byte = 0;
+    snd->checksum = 0;
+
+  case PLIP_PK_DATA:
+    do
+      if (plip_send(dev, nibble_timeout, 
+              &snd->nibble, lbuf[snd->byte]))
+        return TIMEOUT;
+    while (++snd->byte < snd->length.h);
+    do
+      snd->checksum += lbuf[--snd->byte];
+    while (snd->byte);
+    snd->state = PLIP_PK_CHECKSUM;
+
+  case PLIP_PK_CHECKSUM:
+    if (plip_send(dev, nibble_timeout, 
+            &snd->nibble, snd->checksum))
+      return TIMEOUT;
+
+    dev_kfree_skb(snd->skb, FREE_WRITE);
+    nl->enet_stats.tx_packets++;
+    snd->state = PLIP_PK_DONE;
+
+  case PLIP_PK_DONE:
+    /* Close the connection */
+    outb (0x00, PAR_DATA(dev));
+    snd->skb = NULL;
+    if (net_debug > 2)
+      printk(KERN_DEBUG "%s: send end\n", dev->name);
+    nl->connection = PLIP_CN_CLOSING;
+    nl->is_deferred = 1;
+    queue_task(&nl->deferred, &tq_timer);
+    enable_par_irq(dev, 1);
+    return OK;
+  }
+  return OK;
+}
+
+static int
+plip_connection_close(struct device *dev, struct net_local *nl,
+          struct plip_local *snd, struct plip_local *rcv)
+{
+  unsigned long flags;
+
+        save_flags(flags);
+  cli();
+  if (nl->connection == PLIP_CN_CLOSING) {
+    nl->connection = PLIP_CN_NONE;
+    dev->tbusy = 0;
+    mark_bh(NET_BH);
+  }
+  restore_flags(flags);
+  return OK;
+}
+
+/* PLIP_ERROR --- wait till other end settled */
+static int
+plip_error(struct device *dev, struct net_local *nl,
+     struct plip_local *snd, struct plip_local *rcv)
+{
+  unsigned char status;
+
+  status = inb(PAR_STATUS(dev));
+  if ((status & 0xf8) == 0x80) {
+    if (net_debug > 2)
+      printk(KERN_DEBUG "%s: reset interface.\n", dev->name);
+    nl->connection = PLIP_CN_NONE;
+    dev->tbusy = 0;
+    dev->interrupt = 0;
+    enable_par_irq(dev, 1);
+    mark_bh(NET_BH);
+  } else {
+    nl->is_deferred = 1;
+    queue_task(&nl->deferred, &tq_timer);
+  }
+
+  return OK;
+}
+
+
+/* We don't need to send arp, for plip is point-to-point. */
+static int
+plip_rebuild_header(struct sk_buff *skb)
+{
+  struct device *dev = skb->dev;
+  struct net_local *nl = (struct net_local *)dev->priv;
+  struct ethhdr *eth = (struct ethhdr *)skb->data;
+  int i;
+
+  if ((dev->flags & IFF_NOARP)==0)
+    return nl->orig_rebuild_header(skb);
+
+  if (eth->h_proto != __constant_htons(ETH_P_IP)
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+    && eth->h_proto != __constant_htons(ETH_P_IPV6)
+#endif
+  ) {
+    printk(KERN_ERR "plip_rebuild_header: Don't know how to resolve type %d addresses?\n", (int)eth->h_proto);
+    memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
+    return 0;
+  }
+
+  for (i=0; i < ETH_ALEN - sizeof(u32); i++)
+    eth->h_dest[i] = 0xfc;
+#if 0
+  *(u32 *)(eth->h_dest+i) = dst;
+#else
+  /* Do not want to include net/route.h here.
+   * In any case, it is TOP of silliness to emulate
+   * hardware addresses on PtP link. --ANK
+   */
+  *(u32 *)(eth->h_dest+i) = 0;
+#endif
+  return 0;
+}
+
+static int
+plip_tx_packet(struct sk_buff *skb, struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+  struct plip_local *snd = &nl->snd_data;
+  unsigned long flags;
+
+  if (dev->tbusy)
+    return 1;
+
+  /* If some higher layer thinks we've missed an tx-done interrupt
+     we are passed NULL. Caution: dev_tint() handles the cli()/sti()
+     itself. */
+  if (skb == NULL) {
+    printk(KERN_ERR "plip_tx_packet() skb=NULL!\n");
+    dev_tint(dev);
+    return 0;
+  }
+
+  if (set_bit(0, (void*)&dev->tbusy) != 0) {
+    printk(KERN_ERR "%s: Transmitter access conflict.\n", dev->name);
+    return 1;
+  }
+
+  if (skb->len > dev->mtu + dev->hard_header_len) {
+    printk(KERN_ERR "%s: packet too big, %d.\n", dev->name, (int)skb->len);
+    dev->tbusy = 0;
+    return 0;
+  }
+
+  if (net_debug > 2)
+    printk(KERN_DEBUG "%s: send request\n", dev->name);
+
+  save_flags(flags);
+  cli();
+  dev->trans_start = jiffies;
+  snd->skb = skb;
+  snd->length.h = skb->len;
+  snd->state = PLIP_PK_TRIGGER;
+  if (nl->connection == PLIP_CN_NONE) {
+    nl->connection = PLIP_CN_SEND;
+    nl->timeout_count = 0;
+  }
+  queue_task(&nl->immediate, &tq_immediate);
+  mark_bh(IMMEDIATE_BH);
+  restore_flags(flags);
+
+  return 0;
+}
+
+/* Open/initialize the board.  This is called (in the current kernel)
+   sometime after booting when the 'ifconfig' program is run.
+
+ */
+static int
+plip_open(struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+  int i;
+
+#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE)
+  /* Yes, there is a race condition here. Fix it later */
+  if (PLIP_DEV(dev)->par_use & IOEXT_PAR_LP) {
+    /* Can't open if lp is in use */
+#if DEBUG
+    printk("par is in use by lp\n");
+#endif
+    return(-EBUSY);
+  }
+#endif
+  PLIP_DEV(dev)->par_use |= IOEXT_PAR_PLIP;
+
+#if DEBUG
+  printk("plip_open(): sending 00 to data port\n");
+#endif
+
+  /* Clear the data port. */
+  outb (0x00, PAR_DATA(dev));
+
+#if DEBUG
+  printk("plip_open(): sent\n");
+#endif
+
+  /* Initialize the state machine. */
+  nl->rcv_data.state = nl->snd_data.state = PLIP_PK_DONE;
+  nl->rcv_data.skb = nl->snd_data.skb = NULL;
+  nl->connection = PLIP_CN_NONE;
+  nl->is_deferred = 0;
+
+  /* Fill in the MAC-level header. */
+  for (i=0; i < ETH_ALEN - sizeof(u32); i++)
+    dev->dev_addr[i] = 0xfc;
+  *(u32 *)(dev->dev_addr+i) = dev->pa_addr;
+
+  dev->interrupt = 0;
+  dev->start = 1;
+  dev->tbusy = 0;
+  MOD_INC_USE_COUNT;
+
+  /* Enable rx interrupt. */
+  enable_par_irq(dev, 1);
+
+  return 0;
+}
+
+/* The inverse routine to plip_open (). */
+static int
+plip_close(struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+  struct plip_local *snd = &nl->snd_data;
+  struct plip_local *rcv = &nl->rcv_data;
+  unsigned long flags;
+
+  dev->tbusy = 1;
+  dev->start = 0;
+        save_flags(flags);
+  cli();
+  nl->is_deferred = 0;
+  nl->connection = PLIP_CN_NONE;
+  restore_flags(flags);
+  outb(0x00, PAR_DATA(dev));
+
+  snd->state = PLIP_PK_DONE;
+  if (snd->skb) {
+    dev_kfree_skb(snd->skb, FREE_WRITE);
+    snd->skb = NULL;
+  }
+  rcv->state = PLIP_PK_DONE;
+  if (rcv->skb) {
+    kfree_skb(rcv->skb, FREE_READ);
+    rcv->skb = NULL;
+  }
+
+  PLIP_DEV(dev)->par_use &= ~IOEXT_PAR_PLIP;
+
+  MOD_DEC_USE_COUNT;
+  return 0;
+}
+
+static struct enet_statistics *
+plip_get_stats(struct device *dev)
+{
+  struct net_local *nl = (struct net_local *)dev->priv;
+  struct enet_statistics *r = &nl->enet_stats;
+
+  return r;
+}
+
+static int
+plip_config(struct device *dev, struct ifmap *map)
+{
+  if (dev->flags & IFF_UP)
+    return -EBUSY;
+
+  printk(KERN_INFO "%s: This interface is autodetected (ignored).\n", dev->name);
+
+  return 0;
+}
+
+static int
+plip_ioctl(struct device *dev, struct ifreq *rq, int cmd)
+{
+  struct net_local *nl = (struct net_local *) dev->priv;
+  struct plipconf *pc = (struct plipconf *) &rq->ifr_data;
+  
+  switch(pc->pcmd) {
+  case PLIP_GET_TIMEOUT:
+    pc->trigger = nl->trigger;
+    pc->nibble  = nl->nibble;
+    break;
+  case PLIP_SET_TIMEOUT:
+    nl->trigger = pc->trigger;
+    nl->nibble  = pc->nibble;
+    break;
+  default:
+    return -EOPNOTSUPP;
+  }
+  return 0;
+}
+
+/*
+ * Detect and initialize all IO-Extenders in this system.
+ *
+ * Both PLIP and serial devices are configured.
+ */
+int plip_init(struct device *dev)
+{
+  IOEXT_struct *board;
+  struct net_local *nl;
+
+  if (ioext_num == 0) {
+    printk(KERN_INFO "%s\n", version);
+  }
+
+  board = PLIP_DEV(dev)->board;
+  dev->base_addr = (unsigned long)&board->par.DATA;
+
+  /* Cheat and use irq to index into our table */
+  dev->irq = ioext_num;
+
+  printk(KERN_INFO "%s: IO-Extender parallel port at 0x%08lX\n", dev->name, dev->base_addr);
+
+  /* Fill in the generic fields of the device structure. */
+  ether_setup(dev);
+
+  /* Then, override parts of it */
+  dev->hard_start_xmit  = plip_tx_packet;
+  dev->open    = plip_open;
+  dev->stop    = plip_close;
+  dev->get_stats     = plip_get_stats;
+  dev->set_config    = plip_config;
+  dev->do_ioctl    = plip_ioctl;
+  dev->tx_queue_len  = 10;
+  dev->flags          = IFF_POINTOPOINT|IFF_NOARP;
+
+  /* Set the private structure */
+  dev->priv = kmalloc(sizeof (struct net_local), GFP_KERNEL);
+  if (dev->priv == NULL) {
+          printk(KERN_ERR "%s: out of memory\n", dev->name);
+          return -ENOMEM;
+  }
+  memset(dev->priv, 0, sizeof(struct net_local));
+  nl = (struct net_local *) dev->priv;
+
+  nl->orig_rebuild_header = dev->rebuild_header;
+  dev->rebuild_header   = plip_rebuild_header;
+
+  /* Initialize constants */
+  nl->trigger  = PLIP_TRIGGER_WAIT;
+  nl->nibble  = PLIP_NIBBLE_WAIT;
+
+  /* Initialize task queue structures */
+  nl->immediate.next = NULL;
+  nl->immediate.sync = 0;
+  nl->immediate.routine = (void *)(void *)plip_bh;
+  nl->immediate.data = dev;
+
+  nl->deferred.next = NULL;
+  nl->deferred.sync = 0;
+  nl->deferred.routine = (void *)(void *)plip_kick_bh;
+  nl->deferred.data = dev;
+
+  /* Don't enable interrupts yet */
+
+  return 0;
+}
diff -u drivers/char/ser_ioext.c
--- drivers/char/ser_ioext.c.orig
+++ drivers/char/ser_ioext.c	Sat Mar 29 17:57:53 1997
@@ -78,9 +78,10 @@
  *           reading IIR achieves nothing.
  *           Register a separate interrupt handler per board. For some
  *           strange reason, I couldn't get it to work with a single handler.
- * 97/01/14: Add module support.
+ * 97/01/14: Add module support. <msteveb@ozemail.com.au>
  *           Allow multiple IOExtenders to be used (this was broken).
  *           Perhaps add support for the serial port on the GForce-040.
+ * 97/03/29: Add printer and PLIP support <msteveb@ozemail.com.au>
  */
 
 #include <linux/module.h>
@@ -92,6 +93,8 @@
 #include <linux/termios.h>
 #include <linux/tty.h>
 #include <linux/serial.h>
+#include <linux/netdevice.h>
+#include <linux/lp_m68k.h>
 
 #include <asm/setup.h>
 #include <asm/irq.h>
@@ -99,12 +102,11 @@
 #include <asm/amigaints.h>
 #include <linux/zorro.h>
 
-#include "ser_ioext.h"
+#include "ioext.h"
 
 /* Set these to 0 when the driver is finished */
 #define IOEXT_DEBUG 0
 #define DEBUG_IRQ 0
-#define DEBUG_IRQ_2 0
 #define DEBUG 0
 #define DEBUG_FLOW 0
 
@@ -133,6 +135,7 @@
 			      */
 
 /***************************** Prototypes *****************************/
+static void ioext_ser_interrupt(volatile struct uart_16c550 *uart, int line, int *spurious_count);
 static void ser_init( struct async_struct *info );
 static void ser_deinit( struct async_struct *info, int leave_dtr );
 static void ser_enab_tx_int( struct async_struct *info, int enab_flag );
@@ -198,18 +201,8 @@
 	/* B115K2 */ 4     /* ASYNC_SPD_VHI                    */
 };
 
-#define IOEXT_MAX_LINES 2
-
-typedef struct {
-	int num_uarts;
-	int line[IOEXT_MAX_LINES];
-	IOEXT_struct *board;
-	int zorro_key;
-} IOExtioext_info;
-
-/* Number of detected boards.  */
-static int num_ioext = 0;
-static IOExtioext_info ioext_info[MAX_IOEXT];
+int ioext_num;
+IOExtInfoType ioext_info[MAX_IOEXT];
 
 /*
  * Functions
@@ -217,207 +210,247 @@
  * dev_id = ioext_info.
  *
  */
-static void ser_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+static void ioext_interrupt(int irq, void *dev_id, struct pt_regs *regs)
 {
   int i, b;
 
-  if (num_ioext == 0) {
+  if (ioext_num == 0) {
     /* This interrupt can't be for us */
     return;
   }
 
-  for (b = 0; b < num_ioext; b++) {
+  for (b = 0; b < ioext_num; b++) {
     IOEXT_struct *board = ioext_info[b].board;
+    IOExtInfoType *board_info = &ioext_info[b];
 
     /* Check if any irq is pending for the current board.  */
     /* Note: The IRQ_PEND-bit=0 if an irq is pending.      */
 
     if (!(board->CNTR & GVP_IRQ_PEND)) {
-    	/* DON'T print anything here */
-    	continue;
+      /* DON'T print anything here */
+      continue;
     }
 
-    for (i = 0; i < ioext_info[b].num_uarts; i++) {
-    	int line = ioext_info[b].line[i];
-    	struct async_struct *info = rs_table + line;
-
-    	unsigned char iir;
-    	unsigned char lsr;
-    	int ch;
-    	volatile struct uart_16c550 *uart = curruart(info);
-
-	iir = uart->IIR;
-
-  #if DEBUG_IRQ
-  	printk("ttyS%d: IER=%02X, IIR=%02X, LSR=%02X, MSR=%02X\n", line, uart->IER, iir, uart->LSR, uart->MSR);
-  #endif
-
-      while (!(iir & IRQ_PEND)) {
-        /* IRQ for this uart */
-  #if DEBUG_IRQ
-        printk("IRQ_PEND on ttyS%d...\n", line);
-  #endif
-
-        switch (iir & (IRQ_ID1 | IRQ_ID2 | IRQ_ID3)) {
-          case IRQ_RLS: /* Receiver Line Status */
-  #if DEBUG_FLOW
-          printk("RLS irq on ttyS%d\n", line);
-  #endif
-          case IRQ_CTI: /* Character Timeout */
-          case IRQ_RDA: /* Received Data Available */
-  #if DEBUG_IRQ
-          printk("irq (IIR=%02X) on ttyS%d\n", line, iir);
-  #endif
-          /*
-           * Copy chars to the tty-queue ...
-           * Be careful that we aren't passing one of the
-           * Receiver Line Status interrupt-conditions without noticing.
-           */
-          {
-            int got = 0;
-
-            lsr = uart->LSR;
-  #if DEBUG_IRQ
-            printk("uart->LSR & DR = %02X\n", lsr & DR);
-  #endif
-            while (lsr & DR) {
-              unsigned char err = 0;
-              ch = uart->RBR;
-  #if DEBUG_IRQ
-              printk("Read a char from the uart: %02X, lsr=%02X\n", ch, lsr);
-  #endif
-              if (lsr & BI) {
-                err = TTY_BREAK;
-              }
-              else if (lsr & PE) {
-                err = TTY_PARITY;
-              }
-              else if (lsr & OE) {
-                err = TTY_OVERRUN;
-              }
-              else if (lsr & FE) {
-                err = TTY_FRAME;
-              }
-  #if DEBUG_IRQ
-              printk("rs_receive_char(ch=%02X, err=%02X)\n", ch, err);
-  #endif
-              rs_receive_char(info, ch, err);
-              got++;
-              lsr = uart->LSR;
-            }
-  #if DEBUG_FLOW
-            printk("[%d<]", got);
-  #endif
-          }
-          break;
+    /* Service any uart irqs. */
+    for (i = 0; i < board_info->num_uarts; i++) {
+      ioext_ser_interrupt(board_info->uart[i], board_info->line[i], &board_info->spurious_count);
+    }
 
-        case IRQ_THRE: /* Transmitter holding register empty */
-          {
-            int fifo_space = 16;
-            int sent = 0;
-
-  #if DEBUG_IRQ
-            printk("THRE-irq for ttyS%d\n", line);
-  #endif
-
-            /* If the uart is ready to receive data and there are chars in */
-            /* the queue we transfer all we can to the uart's FIFO         */
-            if (info->xmit_cnt <= 0 || info->tty->stopped ||
-                info->tty->hw_stopped) {
-              /* Disable transmitter empty interrupt */
-              uart->IER &= ~(ETHREI);
-              /* Need to send a char to acknowledge the interrupt */
-              uart->THR = 0;
-  #if DEBUG_FLOW
-              if (info->tty->hw_stopped) {
-                printk("[-]");
-              }
-              if (info->tty->stopped) {
-                printk("[*]");
-              }
-  #endif
-              break;
-            }
-
-            /* Handle software flow control */
-            if (info->x_char) {
-  #if DEBUG_FLOW
-              printk("[^%c]", info->x_char + '@');
-  #endif
-              uart->THR = info->x_char;
-              info->x_char = 0;
-              fifo_space--;
-              sent++;
-            }
-
-            /* Fill the fifo */
-            while (fifo_space > 0) {
-              fifo_space--;
-  #if DEBUG_IRQ
-              printk("Sending %02x to the uart.\n", info->xmit_buf[info->xmit_tail]);
-  #endif
-              uart->THR = info->xmit_buf[info->xmit_tail++];
-              sent++;
-              info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
-              if (--info->xmit_cnt == 0) {
-                break;
-              }
-            }
-  #if DEBUG_FLOW
-              printk("[>%d]", sent);
-  #endif
-
-            if (info->xmit_cnt == 0) {
-  #if DEBUG_IRQ
-              printk("Sent last char - turning off THRE interrupts\n");
-  #endif
-              /* Don't need THR interrupts any more */
-              uart->IER &= ~(ETHREI);
-            }
-
-            if (info->xmit_cnt < WAKEUP_CHARS) {
-              rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
-            }
-          }
-          break;
+#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE)
+    if (board_info->par_use & IOEXT_PAR_PLIP) {
+      ioext_plip_interrupt(board_info->dev, &board_info->spurious_count);
+      /*printk("Called ioext_plip_interrupt\n");*/
+    }
+    else
+#endif
+#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE)
+    if (board_info->par_use & IOEXT_PAR_LP) {
+      ioext_lp_interrupt(board_info->lp_dev, &board_info->spurious_count);
+      /*printk("Called ioext_lp_interrupt\n");*/
+    }
+    else
+#endif
+    if (!board_info->par_use) {
+      /* Ack parallel port interrupt */
+      (void)board->par.STATUS;
+      /*printk("ioext_ser_interrupt() handled par interrupt\n");*/
+    }
+
+    if (board_info->spurious_count > 10000) {
+      board->CNTR &= ~GVP_IRQ_ENA;
+      printk("Too many spurious interrupts, disabling board irq (CTRL=%02X)\n", board->par.CTRL);
+      board_info->spurious_count = 0;
+    }
+  } /* for b */
+}
+
+/*
+** ioext_ser_interrupt()
+** 
+** Check for and handle interrupts for this uart.
+*/
+static void ioext_ser_interrupt(volatile struct uart_16c550 *uart, int line, int *spurious_count)
+{
+  struct async_struct *info = rs_table + line;
+
+  u_char iir;
+  u_char lsr;
+  int ch;
+
+  iir = uart->IIR;
+
+  ++*spurious_count;
+
+#if DEBUG_IRQ
+  printk("ttyS%d: IER=%02X, IIR=%02X, LSR=%02X, MSR=%02X\n", line, uart->IER, iir, uart->LSR, uart->MSR);
+#endif
+
+  while (!(iir & IRQ_PEND)) {
+    /* IRQ for this uart */
+#if DEBUG_IRQ
+    printk("IRQ_PEND on ttyS%d...\n", line);
+#endif
+
+    /* This really is our interrupt */
+    *spurious_count = 0;
+
+    switch (iir & (IRQ_ID1 | IRQ_ID2 | IRQ_ID3)) {
+      case IRQ_RLS: /* Receiver Line Status */
+#if DEBUG_FLOW
+      printk("RLS irq on ttyS%d\n", line);
+#endif
+      case IRQ_CTI: /* Character Timeout */
+      case IRQ_RDA: /* Received Data Available */
+#if DEBUG_IRQ
+      printk("irq (IIR=%02X) on ttyS%d\n", line, iir);
+#endif
+      /*
+       * Copy chars to the tty-queue ...
+       * Be careful that we aren't passing one of the
+       * Receiver Line Status interrupt-conditions without noticing.
+       */
+      {
+        int got = 0;
 
-        case IRQ_MS: /* Must be modem status register interrupt? */
-          {
-            unsigned char msr = uart->MSR;
-  #if DEBUG_IRQ
-            printk("MS-irq for ttyS%d: %02x\n", line, msr);
-  #endif
-
-            if (info->flags & ASYNC_INITIALIZED) {
-              if (msr & DCTS) {
-                rs_check_cts(info, (msr & CTS)); /* active high */
-  #if DEBUG_FLOW
-                printk("[%c-%d]", (msr & CTS) ? '^' : 'v', info->tty ? info->tty->hw_stopped : -1);
-  #endif
-              }
-              if (msr & DDCD) {
-  #if DEBUG
-               printk("rs_dcd_changed(%d)\n", !(msr & DCD));
-  #endif
-                rs_dcd_changed(info, !(msr & DCD)); /* active low */
-              }
-            }
+        lsr = uart->LSR;
+#if DEBUG_IRQ
+        printk("uart->LSR & DR = %02X\n", lsr & DR);
+#endif
+        while (lsr & DR) {
+          u_char err = 0;
+          ch = uart->RBR;
+#if DEBUG_IRQ
+          printk("Read a char from the uart: %02X, lsr=%02X\n", ch, lsr);
+#endif
+          if (lsr & BI) {
+            err = TTY_BREAK;
+          }
+          else if (lsr & PE) {
+            err = TTY_PARITY;
+          }
+          else if (lsr & OE) {
+            err = TTY_OVERRUN;
+          }
+          else if (lsr & FE) {
+            err = TTY_FRAME;
           }
+#if DEBUG_IRQ
+          printk("rs_receive_char(ch=%02X, err=%02X)\n", ch, err);
+#endif
+          rs_receive_char(info, ch, err);
+          got++;
+          lsr = uart->LSR;
+        }
+#if DEBUG_FLOW
+        printk("[%d<]", got);
+#endif
+      }
+      break;
+
+    case IRQ_THRE: /* Transmitter holding register empty */
+      {
+        int fifo_space = 16;
+        int sent = 0;
+
+#if DEBUG_IRQ
+        printk("THRE-irq for ttyS%d\n", line);
+#endif
+
+        /* If the uart is ready to receive data and there are chars in */
+        /* the queue we transfer all we can to the uart's FIFO         */
+        if (info->xmit_cnt <= 0 || info->tty->stopped ||
+            info->tty->hw_stopped) {
+          /* Disable transmitter empty interrupt */
+          uart->IER &= ~(ETHREI);
+          /* Need to send a char to acknowledge the interrupt */
+          uart->THR = 0;
+#if DEBUG_FLOW
+          if (info->tty->hw_stopped) {
+            printk("[-]");
+          }
+          if (info->tty->stopped) {
+            printk("[*]");
+          }
+#endif
           break;
+        }
 
-        default:
-  #if DEBUG_IRQ
-            printk("Unexpected irq for ttyS%d\n", line);
-  #endif
-          break;
-        } /* switch (iir) */
-        iir = uart->IIR;
-      } /* while IRQ_PEND */
-    } /* for i = 0, i < 2 */
-  } /* for b */
+        /* Handle software flow control */
+        if (info->x_char) {
+#if DEBUG_FLOW
+          printk("[^%c]", info->x_char + '@');
+#endif
+          uart->THR = info->x_char;
+          info->x_char = 0;
+          fifo_space--;
+          sent++;
+        }
+
+        /* Fill the fifo */
+        while (fifo_space > 0) {
+          fifo_space--;
+#if DEBUG_IRQ
+          printk("Sending %02x to the uart.\n", info->xmit_buf[info->xmit_tail]);
+#endif
+          uart->THR = info->xmit_buf[info->xmit_tail++];
+          sent++;
+          info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+          if (--info->xmit_cnt == 0) {
+            break;
+          }
+        }
+#if DEBUG_FLOW
+          printk("[>%d]", sent);
+#endif
+
+        if (info->xmit_cnt == 0) {
+#if DEBUG_IRQ
+          printk("Sent last char - turning off THRE interrupts\n");
+#endif
+          /* Don't need THR interrupts any more */
+          uart->IER &= ~(ETHREI);
+        }
+
+        if (info->xmit_cnt < WAKEUP_CHARS) {
+          rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+        }
+      }
+      break;
+
+    case IRQ_MS: /* Must be modem status register interrupt? */
+      {
+        u_char msr = uart->MSR;
+#if DEBUG_IRQ
+        printk("MS-irq for ttyS%d: %02x\n", line, msr);
+#endif
+
+        if (info->flags & ASYNC_INITIALIZED) {
+          if (msr & DCTS) {
+            rs_check_cts(info, (msr & CTS)); /* active high */
+#if DEBUG_FLOW
+            printk("[%c-%d]", (msr & CTS) ? '^' : 'v', info->tty ? info->tty->hw_stopped : -1);
+#endif
+          }
+          if (msr & DDCD) {
+#if DEBUG
+           printk("rs_dcd_changed(%d)\n", !(msr & DCD));
+#endif
+            rs_dcd_changed(info, !(msr & DCD)); /* active low */
+          }
+        }
+      }
+      break;
+
+    default:
+#if DEBUG_IRQ
+        printk("Unexpected irq for ttyS%d\n", line);
+#endif
+      break;
+    } /* switch (iir) */
+    iir = uart->IIR;
+  } /* while IRQ_PEND */
 }
 
-
 static void ser_init(struct async_struct *info)
 {
 #if DEBUG
@@ -778,195 +811,179 @@
 
 /*
  * Detect and initialize all IO-Extenders in this system.
+ *
+ * Both PLIP/parallel and serial devices are configured.
  */
 int ioext_init(void)
 {
-	int key = 0;
-	struct ConfigDev *cd;
-	caddr_t address;
-	enum GVP_ident epc;
-	int line1, line2;
-	struct serial_struct req;
-	unsigned char isr_installed = 0;
-	IOEXT_struct *board;            /* Used as a pointer to the    */
-				        /* board-address               */
-
-	if (!MACH_IS_AMIGA)
-		return -ENODEV;
-
-	memset(ioext_info, 0, sizeof(ioext_info));
-
-	/*
-	 * key++ is not needed anymore when a board is found, as zorro_find
-	 * returns board_nr + 1 and it adds 1 to the index before it starts
-	 * searching.
-	 */
-	while ((key = zorro_find(MANUF_GVP, PROD_GVP, 0, key))) {
+  int isr_installed = 0;
+  int key = 0;
+  static char support_string[50] = "io-extender serial";
 
-		cd = zorro_get_board(key);
+  if (!MACH_IS_AMIGA) {
+      return -ENODEV;
+  }
 
-	    /* As GVP uses the same product code for several kind of boards, */
-	    /* we have to check the extended product code, to see if this */
-	    /* really is an io-extender. */
-	    /* check extended product code */
-		address = cd->cd_BoardAddr;
-
-	    /*
-	     * This must be read as a short.
-	     */
-		epc = *(unsigned short *)ZTWO_VADDR(address + 0x8000) & GVP_PRODMASK;
+  memset(ioext_info, 0, sizeof(ioext_info));
 
-		if (epc != GVP_IOEXT && epc != GVP_GFORCE_040) {
-			continue;
-		}
+  while ((key = zorro_find(MANUF_GVP, PROD_GVP, 0, key))) {
+    IOEXT_struct *board;
+    int num_uarts;
+    int i;
+
+    struct ConfigDev *cd = zorro_get_board(key);
+
+    /* As GVP uses the same product code for several kind of boards, */
+    /* we have to check the extended product code, to see if this */
+    /* really is an io-extender. */
+    /* check extended product code */
+    caddr_t address = cd->cd_BoardAddr;
+    enum GVP_ident epc = *(enum GVP_ident *)ZTWO_VADDR(address + 0x8000) & GVP_PRODMASK;
+
+    if (epc != GVP_IOEXT && epc != GVP_GFORCE_040) {
+      continue;
+    }
 
-		/* Wait for the board to be ready and disable everything */
-		board = (IOEXT_struct *) ZTWO_VADDR(address);
+    /* Wait for the board to be ready and disable everything */
+    board = (IOEXT_struct *) ZTWO_VADDR(address);
 
-		ioext_info[num_ioext].board = board;
-		ioext_info[num_ioext].zorro_key = key;
+    ioext_info[ioext_num].board = board;
+    ioext_info[ioext_num].zorro_key = key;
 
-		while (!board->CNTR & GVP_IRQ_PEND) {
-			printk("GVP-irq pending \n");
-		}
+    while (!board->CNTR & GVP_IRQ_PEND) {
+      printk("GVP-irq pending \n");
+    }
       
-		/*
-		 * Disable board-interrupts for the moment.
-		 */
-		board->CNTR = 0;
-
-		/*
-		 * Setting the necessary tty-stuff.
-		 */
-		req.line              = -1; /* first free ttyS? device */
-		req.type              = SER_IOEXT;
-		req.port              = (int) &board->uart0;
-		if ((line1 = register_serial( &req )) < 0) {
-			printk( "Cannot register IO-Extender serial port: no free device\n" );
-			return -EBUSY;
-		}
+    /*
+     * Disable board-interrupts for the moment.
+     */
+    board->CNTR &= ~GVP_IRQ_ENA;
 
-		ioext_info[num_ioext].line[ioext_info[num_ioext].num_uarts] = line1;
-		ioext_info[num_ioext].num_uarts++;
+    /* IO Extender has a second serial port. GForce 040 does not */
+    num_uarts = (epc == GVP_IOEXT) ? 2 : 1;
 
-		rs_table[line1].nr_uarts = 0;
-		rs_table[line1].board_base = board;
-		rs_table[line1].sw = &ioext_ser_switch;
-
-		init_ioext_uart(&board->uart0);
-
-		if (epc == GVP_IOEXT) {
-	      /* IO Extender has a second serial port. GForce 040 does not */
-			req.line = -1; /* first free ttyS? device */
-			req.type = SER_IOEXT;
-			req.port = (int) &board->uart1;
-			if ((line2 = register_serial( &req )) < 0) {
-				printk( "Cannot register IO-Extender serial port: no free device\n" );
-				unregister_serial( line1 );
-				return -EBUSY;
-			}
-
-			ioext_info[num_ioext].line[ioext_info[num_ioext].num_uarts] = line2;
-			ioext_info[num_ioext].num_uarts++;
-
-			rs_table[line2].nr_uarts = 1;
-			rs_table[line2].board_base = board;
-			rs_table[line2].sw = &ioext_ser_switch;
+    /*
+     * Register the serial port devices.
+     */
+    for (i = 0; i < num_uarts; i++) {
+      struct uart_16c550 *uart = (i == 0) ? &board->uart0 : &board->uart1;
+      struct serial_struct req;
+      int line;
+
+      req.line = -1; /* first free ttyS? device */
+      req.type = SER_IOEXT;
+      req.port = (int)uart;
+      if ((line = register_serial(&req)) < 0) {
+          printk( "Cannot register IO-Extender serial port: no free device\n" );
+          break;
+      }
 
-			init_ioext_uart(&board->uart1);
-		}
+      /* Add this uart to our ioext_info[] table */
+      ioext_info[ioext_num].line[ioext_info[ioext_num].num_uarts] = line;
+      ioext_info[ioext_num].uart[ioext_info[ioext_num].num_uarts] = uart;
+      ioext_info[ioext_num].num_uarts++;
 
-		/* Install ISR if it hasn't been installed already */
-		if (!isr_installed) {
-			request_irq(IRQ_AMIGA_EXTER, ser_interrupt, 0,
-				    "io-extender serial", ioext_info);
-			isr_installed++;
-		}
+      rs_table[line].sw = &ioext_ser_switch;
 
-		num_ioext++;
+      /* We don't use these values */
+      rs_table[line].nr_uarts = 0;
+      rs_table[line].board_base = board;
 
-		/*
-		 * Set the board to INT6 and RTSx port-control.
-		 * (GVP uses this setup).
-		 */
-		board->CTRL = (IRQ_SEL|PORT0_CTRL|PORT1_CTRL);
-
-		if (epc == GVP_IOEXT) {
-			/* 
-			 * Initialise parallel port.
-			 * I don't think the GForce-040 has a parallel port
-			 */
-			board->par.CTRL = (INT2EN | SLIN | INIT);
-		}
+      init_ioext_uart(uart);
+    }
 
-		zorro_config_board(key, 0);
+    /* Now we handle the parallel port (possibly as a PLIP device) */
+    if (epc == GVP_IOEXT) {
+      /* Make sure that LP_PINTEN is ALWAYS set, to prevent continuous interrupts */
+      board->par.CTRL = LP_PINTEN|LP_PINITP|LP_PSELECP;
+
+#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE)
+      ioext_info[ioext_num].dev = &ioext_dev_plip[ioext_num];
+      register_netdev(&ioext_dev_plip[ioext_num]);
+      strcat(support_string, "/plip");
+#endif
+#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE)
+      ioext_info[ioext_num].lp_table = &ioext_lp_table[ioext_num];
+      ioext_info[ioext_num].lp_table->base = (void *)&ioext_info[ioext_num];
+      ioext_info[ioext_num].lp_dev = register_parallel(ioext_info[ioext_num].lp_table, -1);
+      if (ioext_info[ioext_num].lp_dev < 0) {
+          printk( "Cannot register IO-Extender parallel port: no free device\n" );
+      }
+      strcat(support_string, "/lp");
+#endif
+    }
 
-		/*
-		 * Re-enable all IRQ's for the current board (board interrupt).
-		 */
-		board->CNTR |= GVP_IRQ_ENA;
-	}
+    /* Install ISR if it hasn't been installed already */
+    if (!isr_installed) {
+      request_irq(IRQ_AMIGA_EXTER, ioext_interrupt, 0, support_string, ioext_info);
+      isr_installed++;
+    }
+    ioext_num++;
 
-	return 0;
+    /*
+     * Set the board to INT6 and RTSx port-control.
+     * (GVP uses this setup).
+     */
+    board->CTRL = (IRQ_SEL|PORT0_CTRL|PORT1_CTRL);
+
+    board->CNTR |= GVP_IRQ_ENA;
+
+    zorro_config_board(key, 0);
+  }
+
+  return(0);
 }
 
 #ifdef MODULE
 int init_module(void)
 {
-	return(ioext_init());
+  return(ioext_init());
 }
 
 void cleanup_module(void)
 {
-	int i = 0;
+  int i;
 
-#if DEBUG
-	printk("ioext: cleanup_module()\n");
-#endif
-	for (i = 0; i < num_ioext; i++) {
-		IOEXT_struct *board = ioext_info[i].board;
-		int j;
+  for (i = 0; i < ioext_num; i++) {
+    IOEXT_struct *board = ioext_info[i].board;
+    int j;
 
-#if DEBUG
-		printk("ioext: board %d @ %08lX\n", i, (unsigned long)board);
-#endif
+    /* Disable board-interrupts */
+    board->CNTR &= ~GVP_IRQ_ENA;
 
-		/* Disable board-interrupts */
-		board->CNTR = 0;
+    /* Disable "master" interrupt select on uarts */
+    board->uart0.MCR = 0;
+    board->uart1.MCR = 0;
 
-		/* Disable "master" interrupt select on uarts */
-		board->uart0.MCR = 0;
-		board->uart1.MCR = 0;
+    /* Disable all uart interrupts */
+    board->uart0.IER = 0;
+    board->uart1.IER = 0;
 
-		/* Disable all uart interrupts */
-		board->uart0.IER = 0;
-		board->uart1.IER = 0;
+    for (j = 0; j < ioext_info[i].num_uarts; j++) {
+      unregister_serial(ioext_info[i].line[j]);
+    }
 
-#if DEBUG
-		printk("ioext: disabled interrupts\n");
+#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE)
+    if (ioext_info[i].dev != 0) {
+      unregister_netdev(ioext_info[i].dev);
+      kfree_s(ioext_info[i].dev->priv, sizeof(struct net_local));
+      ioext_info[i].dev->priv = NULL;
+    }
 #endif
-
-		for (j = 0; j < ioext_info[i].num_uarts; j++) {
-#if DEBUG
-			printk("ioext: uregister_serial(%d)\n",
-			       ioext_info[i].line[j]);
+#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE)
+    if (ioext_info[i].lp_dev >= 0) {
+      unregister_parallel(ioext_info[i].lp_dev);
+    }
 #endif
-			unregister_serial(ioext_info[i].line[j]);
-		}
 
-#if DEBUG
-		printk("ioext: zorro_unconfig_board(%d, 0)\n",
-		       ioext_info[i].zorro_key);
-#endif
-		zorro_unconfig_board(ioext_info[i].zorro_key, 0);
-	}
+    zorro_unconfig_board(ioext_info[i].zorro_key, 0);
+  }
 
-	/* Indicate to the IRQ that nothing needs to be serviced */
-	num_ioext = 0;
+  if (ioext_num != 0) {
+    /* Indicate to the IRQ that nothing needs to be serviced */
+    ioext_num = 0;
 
-#if DEBUG
-	printk("ioext: free_irq()\n");
-#endif
-	free_irq(IRQ_AMIGA_EXTER, ioext_info);
+    free_irq(IRQ_AMIGA_EXTER, ioext_info);
+  }
 }
 #endif
diff -u drivers/char/16c552.h
--- drivers/char/16c552.h.orig
+++ drivers/char/16c552.h	Sat Mar 29 14:14:05 1997
@@ -139,8 +139,6 @@
 #define BAUDOUT       (1<<1)
 #define RXRDY         (1<<2)
 
-
-
 /* Parallel stuff */
 
 /*
@@ -157,25 +155,22 @@
   volatile u_char CTRL;
 };
 
-/*
- * STATUS (the status-register - read-only)
- * NOTE: Several of them are active low (!)
+/* 
+ * bit defines for 8255 parallel status port
  */
-#define PRINT         (1<<2) /* !PRINT         */
-#define ERR           (1<<3) /* !ERR           */
-#define SLCT          (1<<4) /* SLCT           */
-#define PAE           (1<<5) /* Paper empty    */
-#define ACK           (1<<6) /* !ACK           */
-#define BSY           (1<<7) /* !BSY           */
+#define LP_PBUSY	0x80  /* inverted input, active high */
+#define LP_PACK		0x40  /* unchanged input, active low */
+#define LP_POUTPA	0x20  /* unchanged input, active high */
+#define LP_PSELECD	0x10  /* unchanged input, active high */
+#define LP_PERRORP	0x08  /* unchanged input, active low */
 
-/*
- * CTRL
+/* 
+ * defines for 8255 parallel control port
  */
-#define STRB          (1<<0) /* Printer strobe */
-#define AFD           (1<<1)
-#define INIT          (1<<2)
-#define SLIN          (1<<3)
-#define INT2EN        (1<<4)
-#define DIR           (1<<5)
+#define LP_PINTEN	0x10  /* high to read data in or-ed with data out */
+#define LP_PSELECP	0x08  /* inverted output, active low */
+#define LP_PINITP	0x04  /* unchanged output, active low */
+#define LP_PAUTOLF	0x02  /* inverted output, active low */
+#define LP_PSTROBE	0x01  /* short high output on raising edge */
 
 #endif
diff -u drivers/char/lp_m68k.c
--- drivers/char/lp_m68k.c.orig
+++ drivers/char/lp_m68k.c	Sat Mar 29 20:34:29 1997
@@ -139,40 +139,35 @@
 
 static int lp_error;
 
-void lp_interrupt(int irq, void *dummy, struct pt_regs *fp)
+void lp_interrupt(int dev)
 {
-    unsigned long flags;
-    int dev;
-
-    for (dev = 0; dev < MAX_LP; dev++) {
-	if ((lp_table[dev] != NULL) && (lp_table[dev]->lp_my_interrupt(dev) != 0))
-	  if (lp_table[dev]->do_print)
-	  {
-		  if (lp_table[dev]->copy_size)
-		  {
-			  save_flags(flags);
-			  cli();
-			  if (lp_char_interrupt(lp_table[dev]->lp_buffer[lp_table[dev]->bytes_written], dev)) {
-				  --lp_table[dev]->copy_size;
-				  ++lp_table[dev]->bytes_written;
-				  restore_flags(flags);
-			  }
-			  else
-			  {
-				  lp_table[dev]->do_print = 0;
-				  restore_flags(flags);
-				  lp_error = 1;
-				  wake_up_interruptible(&lp_table[dev]->lp_wait_q);
-			  }
-		  }
-		  else
-		  {
-			  lp_table[dev]->do_print = 0;
-			  lp_error = 0;
-			  wake_up_interruptible(&lp_table[dev]->lp_wait_q);
-		  }
+    if (dev >= 0 && dev < MAX_LP && lp_table[dev]->do_print)
+    {
+	if (lp_table[dev]->copy_size)
+	{
+    		unsigned long flags;
+		save_flags(flags);
+		cli();
+		if (lp_char_interrupt(lp_table[dev]->lp_buffer[lp_table[dev]->bytes_written], dev)) {
+			--lp_table[dev]->copy_size;
+			++lp_table[dev]->bytes_written;
+			restore_flags(flags);
+		}
+		else
+		{
+			lp_table[dev]->do_print = 0;
+			restore_flags(flags);
+			lp_error = 1;
+			wake_up_interruptible(&lp_table[dev]->lp_wait_q);
+		}
+	}
+	else
+	{
+		lp_table[dev]->do_print = 0;
+		lp_error = 0;
+		wake_up_interruptible(&lp_table[dev]->lp_wait_q);
+	}
 
-	  }
     }
 }
 
@@ -366,6 +361,7 @@
 static int lp_open(struct inode *inode, struct file *file)
 {
 	int dev = MINOR(inode->i_rdev);
+	int ret;
 
 	if (dev >= MAX_LP)
 		return -ENODEV;
@@ -386,10 +382,14 @@
 
 	lp_table[dev]->flags |= LP_BUSY;
 
-	MOD_INC_USE_COUNT;
-	lp_table[dev]->lp_open();
-
-	return 0;
+	ret = lp_table[dev]->lp_open(dev);
+	if (ret != 0) {
+		lp_table[dev]->flags &= ~LP_BUSY;
+	}
+	else {
+		MOD_INC_USE_COUNT;
+	}
+	return ret;
 }
 
 static void lp_release(struct inode *inode, struct file *file)
@@ -397,7 +397,7 @@
 	int dev =MINOR(inode->i_rdev);
 
 	lp_table[dev]->flags &= ~LP_BUSY;
-	lp_table[dev]->lp_release();
+	lp_table[dev]->lp_release(dev);
 	MOD_DEC_USE_COUNT;
 }
 
diff -u drivers/char/lp_intern.c
--- drivers/char/lp_intern.c.orig
+++ drivers/char/lp_intern.c	Sat Mar 29 18:45:33 1997
@@ -33,7 +33,6 @@
 #endif
 #include <linux/lp_intern.h>
 
-static int my_inter = 0;
 static int minor = -1;
 
 static void lp_int_out(int, int);
@@ -126,24 +125,18 @@
     }
 }
 
-static int lp_int_my_interrupt(int dev)
-{
-  return my_inter;
-}
-
 static void lp_int_interrupt(int irq, void *data, struct pt_regs *fp)
 {
-  my_inter = 1;
-  lp_interrupt(irq, data, fp);
-  my_inter = 0;
+  lp_interrupt(minor);
 }
 
-static void lp_int_open(void)
+static int lp_int_open(int dev)
 {
   MOD_INC_USE_COUNT;
+  return 0;
 }
 
-static void lp_int_release(void)
+static void lp_int_release(int dev)
 {
   MOD_DEC_USE_COUNT;
 }
@@ -155,7 +148,7 @@
 	lp_int_busy,
 	lp_int_pout,
 	lp_int_online,
-	lp_int_my_interrupt,
+	0,
 	NULL,				/* ioctl */
 	lp_int_open,
 	lp_int_release,
diff -u drivers/char/lp_ioext.c
--- drivers/char/lp_ioext.c.orig
+++ drivers/char/lp_ioext.c	Sat Mar 29 20:32:36 1997
@@ -0,0 +1,150 @@
+/*
+ * lp_ioext: Parallel printer support for GVP IO-Extender.
+ *
+ * Author:	Steve Bennett, <msteveb@ozemail.com.au>
+ *
+ */
+#define __NO_VERSION__
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <asm/irq.h>
+#include <asm/setup.h>
+#include <linux/lp_m68k.h>
+
+#include "ioext.h"
+
+#define DEBUG 0
+
+/* Match up par device numbers with our control structure */
+#define PAR_DEV(DEV)  ((IOExtInfoType *)lp_table[DEV]->base)
+
+static void lp_out(int c, int dev);
+static int lp_busy(int dev);
+static int lp_pout(int dev);
+static int lp_online(int dev);
+static int lp_open(int dev);
+static void lp_release(int dev);
+
+/*
+ * Check for and handle an interrupt for this PARALLEL device.
+ *
+ */
+void ioext_lp_interrupt(int dev, int *spurious_count)
+{
+  /* Acknowledge the interrupt */
+  (void)PAR_DEV(dev)->board->par.STATUS;
+
+  if (!(lp_table[dev]->flags & LP_BUSY)) {
+    /*printk("OOPS: lp_interrupt called, but lp not in use\n");*/
+    return;
+  }
+
+ /* This is cleared in lp_out() */
+  ++*spurious_count;
+
+  if (!(PAR_DEV(dev)->board->par.STATUS & LP_PBUSY)) {
+#if DEBUG
+    printk("ioext_lp_interrupt(): busy\n");
+#endif
+    /* Can't have been an interrupt for us if we are still busy */
+    return;
+  }
+
+  lp_interrupt(dev);
+}
+
+static void lp_out(int c, int dev)
+{
+  int wait;
+  IOEXT_struct *board = PAR_DEV(dev)->board;
+
+#if DEBUG
+  printk("lp_out(c=%02X, dev=%d)\n", c, dev);
+#endif
+
+  PAR_DEV(dev)->spurious_count = 0;
+
+  board->par.DATA = c;
+
+  /* must wait before taking strobe high, and after taking strobe
+     low, according to the spec.  Some printers need it, others don't. */
+  wait = lp_table[dev]->wait;
+  while (wait--) {
+  }
+  /* control port takes strobe high */
+  board->par.CTRL |= LP_PSTROBE;
+
+  wait = lp_table[dev]->wait;
+  while (wait) {
+    wait--;
+  }
+
+  /* take strobe low */
+  board->par.CTRL &= ~LP_PSTROBE;
+}
+
+static int lp_busy(int dev)
+{
+  return(!(PAR_DEV(dev)->board->par.STATUS & LP_PBUSY));
+}
+
+static int lp_pout(int dev)
+{
+  return(PAR_DEV(dev)->board->par.STATUS & LP_POUTPA);
+}
+
+static int lp_online(int dev)
+{
+  return(PAR_DEV(dev)->board->par.STATUS & LP_PSELECD);
+}
+
+static int lp_open(int dev)
+{
+#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE)
+  /* Yes, there is a race condition here. Fix it later */
+  if (PAR_DEV(dev)->par_use & IOEXT_PAR_PLIP) {
+    /* Can't open if PLIP is in use */
+#if DEBUG
+    printk("par is in use by plip\n");
+#endif
+    return(-EBUSY);
+  }
+#endif
+  MOD_INC_USE_COUNT;
+  PAR_DEV(dev)->par_use |= IOEXT_PAR_LP;
+  return(0);
+}
+
+static void lp_release(int dev)
+{
+  PAR_DEV(dev)->par_use &= ~IOEXT_PAR_LP;
+  MOD_DEC_USE_COUNT;
+}
+
+/* Only support one printer port for now.
+*/
+struct lp_struct ioext_lp_table[] = {
+  {
+	"IO-extender parallel port",	/* name */
+	0,				/* irq */
+	lp_out,
+	lp_busy,
+	lp_pout,
+	lp_online,
+	0,
+	NULL,				/* ioctl */
+	lp_open,
+	lp_release,
+	LP_EXIST,
+	LP_INIT_CHAR,
+	LP_INIT_TIME,
+	LP_INIT_WAIT,
+	NULL,
+	NULL,
+	LP_IOEXT
+  },
+};
diff -u drivers/char/ioext.h
--- drivers/char/ioext.h.orig
+++ drivers/char/ioext.h	Sat Mar 29 20:44:41 1997
@@ -0,0 +1,45 @@
+/*
+ * Shared data structure for GVP IO-Extender support.
+ */
+#ifndef IOEXT_H
+#define IOEXT_H
+
+#include <linux/lp_m68k.h>
+#include <linux/netdevice.h>
+
+#include "ser_ioext.h"
+
+#define IOEXT_MAX_LINES 2
+
+#define IOEXT_PAR_PLIP  0x0001
+#define IOEXT_PAR_LP    0x0002
+
+typedef struct {
+  int num_uarts;
+  int line[IOEXT_MAX_LINES];
+  volatile struct uart_16c550 *uart[IOEXT_MAX_LINES];
+  IOEXT_struct *board;
+  int zorro_key;
+  int spurious_count;
+  u_char par_use;		/* IOEXT_PAR_xxx */
+#if defined(CONFIG_GVPIOEXT_PLIP) || defined(CONFIG_GVPIOEXT_PLIP_MODULE)
+  struct device *dev;
+#endif
+#if defined(CONFIG_GVPIOEXT_LP) || defined(CONFIG_GVPIOEXT_LP_MODULE)
+  struct lp_struct *lp_table;
+  int lp_dev;
+  int lp_interrupt;
+#endif
+} IOExtInfoType;
+
+/* Number of detected boards.  */
+extern int ioext_num;
+extern IOExtInfoType ioext_info[MAX_IOEXT];
+
+void ioext_plip_interrupt(struct device *dev, int *spurious_count);
+void ioext_lp_interrupt(int dev, int *spurious_count);
+
+extern struct device ioext_dev_plip[3];
+extern struct lp_struct ioext_lp_table[1];
+
+#endif
diff -u include/linux/lp_m68k.h
--- include/linux/lp_m68k.h.orig
+++ include/linux/lp_m68k.h	Sat Mar 29 18:51:39 1997
@@ -91,7 +91,8 @@
 LP_UNKNOWN = 0,
 LP_AMIGA = 1,
 LP_ATARI = 2,
-LP_MFC = 3
+LP_MFC = 3,
+LP_IOEXT = 4
 };
 
 /*
@@ -105,10 +106,10 @@
 	int (*lp_is_busy)(int);
 	int (*lp_has_pout)(int);
 	int (*lp_is_online)(int);
-	int (*lp_my_interrupt)(int);
+	int (*lp_dummy)(int);
 	int (*lp_ioctl)(int, unsigned int, unsigned long);
-	void (*lp_open)(void);		/* for module use counter */
-	void (*lp_release)(void);	/* for module use counter */
+	int (*lp_open)(int);		/* for module use counter */
+	void (*lp_release)(int);	/* for module use counter */
 	int flags;		/*for BUSY... */
 	unsigned int chars;	/*busy timeout */
 	unsigned int time;	/*wait time */
@@ -124,7 +125,7 @@
 extern struct lp_struct *lp_table[MAX_LP];
 extern unsigned int lp_irq;
 
-void lp_interrupt(int, void *, struct pt_regs *);
+void lp_interrupt(int dev);
 int lp_init(void);
 int register_parallel(struct lp_struct *, int);
 void unregister_parallel(int);
-- 
M. Steve Bennett                  Stallion Technologies
Phone:  +61 2 9887 2442           33 Woodstock Rd
Email:  msteveb@ozemail.com.au    Toowong QLD 4066
Web: http://www.ozemail.com.au/~msteveb/
