Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/isdn/act2000/Kconfig b/drivers/isdn/act2000/Kconfig
new file mode 100644
index 0000000..78e6ad8
--- /dev/null
+++ b/drivers/isdn/act2000/Kconfig
@@ -0,0 +1,13 @@
+#
+# Config.in for IBM Active 2000 ISDN driver
+#
+config ISDN_DRV_ACT2000
+	tristate "IBM Active 2000 support"
+	depends on ISDN_I4L && ISA
+	help
+	  Say Y here if you have an IBM Active 2000 ISDN card. In order to use
+	  this card, additional firmware is necessary, which has to be loaded
+	  into the card using a utility which is part of the latest
+	  isdn4k-utils package. Please read the file
+	  <file:Documentation/isdn/README.act2000> for more information.
+
diff --git a/drivers/isdn/act2000/Makefile b/drivers/isdn/act2000/Makefile
new file mode 100644
index 0000000..05e582f
--- /dev/null
+++ b/drivers/isdn/act2000/Makefile
@@ -0,0 +1,9 @@
+# Makefile for the act2000 ISDN device driver
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_ISDN_DRV_ACT2000)	+= act2000.o
+
+# Multipart objects.
+
+act2000-y			:= module.o capi.o act2000_isa.o
diff --git a/drivers/isdn/act2000/act2000.h b/drivers/isdn/act2000/act2000.h
new file mode 100644
index 0000000..b091d1a
--- /dev/null
+++ b/drivers/isdn/act2000/act2000.h
@@ -0,0 +1,202 @@
+/* $Id: act2000.h,v 1.8.6.3 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef act2000_h
+#define act2000_h
+
+#include <linux/compiler.h>
+
+#define ACT2000_IOCTL_SETPORT    1
+#define ACT2000_IOCTL_GETPORT    2
+#define ACT2000_IOCTL_SETIRQ     3
+#define ACT2000_IOCTL_GETIRQ     4
+#define ACT2000_IOCTL_SETBUS     5
+#define ACT2000_IOCTL_GETBUS     6
+#define ACT2000_IOCTL_SETPROTO   7
+#define ACT2000_IOCTL_GETPROTO   8
+#define ACT2000_IOCTL_SETMSN     9
+#define ACT2000_IOCTL_GETMSN    10
+#define ACT2000_IOCTL_LOADBOOT  11
+#define ACT2000_IOCTL_ADDCARD   12
+
+#define ACT2000_IOCTL_TEST      98
+#define ACT2000_IOCTL_DEBUGVAR  99
+
+#define ACT2000_BUS_ISA          1
+#define ACT2000_BUS_MCA          2
+#define ACT2000_BUS_PCMCIA       3
+
+/* Struct for adding new cards */
+typedef struct act2000_cdef {
+	int bus;
+        int port;
+        int irq;
+        char id[10];
+} act2000_cdef;
+
+/* Struct for downloading firmware */
+typedef struct act2000_ddef {
+        int length;             /* Length of code */
+        char __user *buffer;    /* Ptr. to code   */
+} act2000_ddef;
+
+typedef struct act2000_fwid {
+        char isdn[4];
+        char revlen[2];
+        char revision[504];
+} act2000_fwid;
+
+#if defined(__KERNEL__) || defined(__DEBUGVAR__)
+
+#ifdef __KERNEL__
+/* Kernel includes */
+
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/isdnif.h>
+
+#endif                           /* __KERNEL__ */
+
+#define ACT2000_PORTLEN        8
+
+#define ACT2000_FLAGS_RUNNING  1 /* Cards driver activated */
+#define ACT2000_FLAGS_PVALID   2 /* Cards port is valid    */
+#define ACT2000_FLAGS_IVALID   4 /* Cards irq is valid     */
+#define ACT2000_FLAGS_LOADED   8 /* Firmware loaded        */
+
+#define ACT2000_BCH            2 /* # of channels per card */
+
+/* D-Channel states */
+#define ACT2000_STATE_NULL     0
+#define ACT2000_STATE_ICALL    1
+#define ACT2000_STATE_OCALL    2
+#define ACT2000_STATE_IWAIT    3
+#define ACT2000_STATE_OWAIT    4
+#define ACT2000_STATE_IBWAIT   5
+#define ACT2000_STATE_OBWAIT   6
+#define ACT2000_STATE_BWAIT    7
+#define ACT2000_STATE_BHWAIT   8
+#define ACT2000_STATE_BHWAIT2  9
+#define ACT2000_STATE_DHWAIT  10
+#define ACT2000_STATE_DHWAIT2 11
+#define ACT2000_STATE_BSETUP  12
+#define ACT2000_STATE_ACTIVE  13
+
+#define ACT2000_MAX_QUEUED  8000 /* 2 * maxbuff */
+
+#define ACT2000_LOCK_TX 0
+#define ACT2000_LOCK_RX 1
+
+typedef struct act2000_chan {
+	unsigned short callref;          /* Call Reference              */
+	unsigned short fsm_state;        /* Current D-Channel state     */
+	unsigned short eazmask;          /* EAZ-Mask for this Channel   */
+	short queued;                    /* User-Data Bytes in TX queue */
+	unsigned short plci;
+	unsigned short ncci;
+	unsigned char  l2prot;           /* Layer 2 protocol            */
+	unsigned char  l3prot;           /* Layer 3 protocol            */
+} act2000_chan;
+
+typedef struct msn_entry {
+	char eaz;
+        char msn[16];
+        struct msn_entry * next;
+} msn_entry;
+
+typedef struct irq_data_isa {
+	__u8           *rcvptr;
+	__u16           rcvidx;
+	__u16           rcvlen;
+	struct sk_buff *rcvskb;
+	__u8            rcvignore;
+	__u8            rcvhdr[8];
+} irq_data_isa;
+
+typedef union irq_data {
+	irq_data_isa isa;
+} irq_data;
+
+/*
+ * Per card driver data
+ */
+typedef struct act2000_card {
+	unsigned short port;		/* Base-port-address                */
+	unsigned short irq;		/* Interrupt                        */
+	u_char ptype;			/* Protocol type (1TR6 or Euro)     */
+	u_char bus;			/* Cardtype (ISA, MCA, PCMCIA)      */
+	struct act2000_card *next;	/* Pointer to next device struct    */
+	spinlock_t lock;		/* protect critical operations      */
+	int myid;			/* Driver-Nr. assigned by linklevel */
+	unsigned long flags;		/* Statusflags                      */
+	unsigned long ilock;		/* Semaphores for IRQ-Routines      */
+	struct sk_buff_head rcvq;	/* Receive-Message queue            */
+	struct sk_buff_head sndq;	/* Send-Message queue               */
+	struct sk_buff_head ackq;	/* Data-Ack-Message queue           */
+	u_char *ack_msg;		/* Ptr to User Data in User skb     */
+	__u16 need_b3ack;		/* Flag: Need ACK for current skb   */
+	struct sk_buff *sbuf;		/* skb which is currently sent      */
+	struct timer_list ptimer;	/* Poll timer                       */
+	struct work_struct snd_tq;	/* Task struct for xmit bh          */
+	struct work_struct rcv_tq;	/* Task struct for rcv bh           */
+	struct work_struct poll_tq;	/* Task struct for polled rcv bh    */
+	msn_entry *msn_list;
+	unsigned short msgnum;		/* Message number for sending       */
+	spinlock_t mnlock;		/* lock for msgnum                  */
+	act2000_chan bch[ACT2000_BCH];	/* B-Channel status/control         */
+	char   status_buf[256];		/* Buffer for status messages       */
+	char   *status_buf_read;
+	char   *status_buf_write;
+	char   *status_buf_end;
+	irq_data idat;			/* Data used for IRQ handler        */
+	isdn_if interface;		/* Interface to upper layer         */
+	char regname[35];		/* Name used for request_region     */
+} act2000_card;
+
+extern __inline__ void act2000_schedule_tx(act2000_card *card)
+{
+        schedule_work(&card->snd_tq);
+}
+
+extern __inline__ void act2000_schedule_rx(act2000_card *card)
+{
+        schedule_work(&card->rcv_tq);
+}
+
+extern __inline__ void act2000_schedule_poll(act2000_card *card)
+{
+        schedule_work(&card->poll_tq);
+}
+
+extern char *act2000_find_eaz(act2000_card *, char);
+
+#endif                          /* defined(__KERNEL__) || defined(__DEBUGVAR__) */
+#endif                          /* act2000_h */
diff --git a/drivers/isdn/act2000/act2000_isa.c b/drivers/isdn/act2000/act2000_isa.c
new file mode 100644
index 0000000..bc98d77
--- /dev/null
+++ b/drivers/isdn/act2000/act2000_isa.c
@@ -0,0 +1,449 @@
+/* $Id: act2000_isa.c,v 1.11.6.3 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version).
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "act2000_isa.h"
+#include "capi.h"
+
+static act2000_card *irq2card_map[16];
+
+/*
+ * Reset Controller, then try to read the Card's signature.
+ + Return:
+ *   1 = Signature found.
+ *   0 = Signature not found.
+ */
+static int
+act2000_isa_reset(unsigned short portbase)
+{
+        unsigned char reg;
+        int i;
+        int found;
+        int serial = 0;
+
+        found = 0;
+        if ((reg = inb(portbase + ISA_COR)) != 0xff) {
+                outb(reg | ISA_COR_RESET, portbase + ISA_COR);
+                mdelay(10);
+                outb(reg, portbase + ISA_COR);
+                mdelay(10);
+
+                for (i = 0; i < 16; i++) {
+                        if (inb(portbase + ISA_ISR) & ISA_ISR_SERIAL)
+                                serial |= 0x10000;
+                        serial >>= 1;
+                }
+                if (serial == ISA_SER_ID)
+                        found++;
+        }
+        return found;
+}
+
+int
+act2000_isa_detect(unsigned short portbase)
+{
+        int ret = 0;
+
+	if (request_region(portbase, ACT2000_PORTLEN, "act2000isa")) {
+                ret = act2000_isa_reset(portbase);
+		release_region(portbase, ISA_REGION);
+	}
+        return ret;
+}
+
+static irqreturn_t
+act2000_isa_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+        act2000_card *card = irq2card_map[irq];
+        u_char istatus;
+
+        if (!card) {
+                printk(KERN_WARNING
+                       "act2000: Spurious interrupt!\n");
+                return IRQ_NONE;
+        }
+        istatus = (inb(ISA_PORT_ISR) & 0x07);
+        if (istatus & ISA_ISR_OUT) {
+                /* RX fifo has data */
+		istatus &= ISA_ISR_OUT_MASK;
+		outb(0, ISA_PORT_SIS);
+		act2000_isa_receive(card);
+		outb(ISA_SIS_INT, ISA_PORT_SIS);
+        }
+        if (istatus & ISA_ISR_ERR) {
+                /* Error Interrupt */
+		istatus &= ISA_ISR_ERR_MASK;
+                printk(KERN_WARNING "act2000: errIRQ\n");
+        }
+	if (istatus)
+		printk(KERN_DEBUG "act2000: ?IRQ %d %02x\n", irq, istatus);
+	return IRQ_HANDLED;
+}
+
+static void
+act2000_isa_select_irq(act2000_card * card)
+{
+	unsigned char reg;
+
+	reg = (inb(ISA_PORT_COR) & ~ISA_COR_IRQOFF) | ISA_COR_PERR;
+	switch (card->irq) {
+		case 3:
+			reg = ISA_COR_IRQ03;
+			break;
+		case 5:
+			reg = ISA_COR_IRQ05;
+			break;
+		case 7:
+			reg = ISA_COR_IRQ07;
+			break;
+		case 10:
+			reg = ISA_COR_IRQ10;
+			break;
+		case 11:
+			reg = ISA_COR_IRQ11;
+			break;
+		case 12:
+			reg = ISA_COR_IRQ12;
+			break;
+		case 15:
+			reg = ISA_COR_IRQ15;
+			break;
+	}
+	outb(reg, ISA_PORT_COR);
+}
+
+static void
+act2000_isa_enable_irq(act2000_card * card)
+{
+	act2000_isa_select_irq(card);
+	/* Enable READ irq */
+	outb(ISA_SIS_INT, ISA_PORT_SIS);
+}
+
+/*
+ * Install interrupt handler, enable irq on card.
+ * If irq is -1, choose next free irq, else irq is given explicitely.
+ */
+int
+act2000_isa_config_irq(act2000_card * card, short irq)
+{
+        if (card->flags & ACT2000_FLAGS_IVALID) {
+                free_irq(card->irq, NULL);
+                irq2card_map[card->irq] = NULL;
+        }
+        card->flags &= ~ACT2000_FLAGS_IVALID;
+        outb(ISA_COR_IRQOFF, ISA_PORT_COR);
+        if (!irq)
+                return 0;
+
+	if (!request_irq(irq, &act2000_isa_interrupt, 0, card->regname, NULL)) {
+		card->irq = irq;
+		irq2card_map[card->irq] = card;
+		card->flags |= ACT2000_FLAGS_IVALID;
+                printk(KERN_WARNING
+                       "act2000: Could not request irq %d\n",irq);
+                return -EBUSY;
+        } else {
+		act2000_isa_select_irq(card);
+                /* Disable READ and WRITE irq */
+                outb(0, ISA_PORT_SIS);
+                outb(0, ISA_PORT_SOS);
+        }
+        return 0;
+}
+
+int
+act2000_isa_config_port(act2000_card * card, unsigned short portbase)
+{
+        if (card->flags & ACT2000_FLAGS_PVALID) {
+                release_region(card->port, ISA_REGION);
+                card->flags &= ~ACT2000_FLAGS_PVALID;
+        }
+	if (request_region(portbase, ACT2000_PORTLEN, card->regname) == NULL)
+		return -EBUSY;
+	else {
+                card->port = portbase;
+                card->flags |= ACT2000_FLAGS_PVALID;
+                return 0;
+        }
+}
+
+/*
+ * Release ressources, used by an adaptor.
+ */
+void
+act2000_isa_release(act2000_card * card)
+{
+        unsigned long flags;
+
+        spin_lock_irqsave(&card->lock, flags);
+        if (card->flags & ACT2000_FLAGS_IVALID) {
+                free_irq(card->irq, NULL);
+                irq2card_map[card->irq] = NULL;
+        }
+        card->flags &= ~ACT2000_FLAGS_IVALID;
+        if (card->flags & ACT2000_FLAGS_PVALID)
+                release_region(card->port, ISA_REGION);
+        card->flags &= ~ACT2000_FLAGS_PVALID;
+        spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static int
+act2000_isa_writeb(act2000_card * card, u_char data)
+{
+        u_char timeout = 40;
+
+        while (timeout) {
+                if (inb(ISA_PORT_SOS) & ISA_SOS_READY) {
+                        outb(data, ISA_PORT_SDO);
+                        return 0;
+                } else {
+                        timeout--;
+                        udelay(10);
+                }
+        }
+        return 1;
+}
+
+static int
+act2000_isa_readb(act2000_card * card, u_char * data)
+{
+        u_char timeout = 40;
+
+        while (timeout) {
+                if (inb(ISA_PORT_SIS) & ISA_SIS_READY) {
+                        *data = inb(ISA_PORT_SDI);
+                        return 0;
+                } else {
+                        timeout--;
+                        udelay(10);
+                }
+        }
+        return 1;
+}
+
+void
+act2000_isa_receive(act2000_card *card)
+{
+	u_char c;
+
+        if (test_and_set_bit(ACT2000_LOCK_RX, (void *) &card->ilock) != 0)
+		return;
+	while (!act2000_isa_readb(card, &c)) {
+		if (card->idat.isa.rcvidx < 8) {
+                        card->idat.isa.rcvhdr[card->idat.isa.rcvidx++] = c;
+			if (card->idat.isa.rcvidx == 8) {
+				int valid = actcapi_chkhdr(card, (actcapi_msghdr *)&card->idat.isa.rcvhdr);
+
+				if (valid) {
+					card->idat.isa.rcvlen = ((actcapi_msghdr *)&card->idat.isa.rcvhdr)->len;
+					card->idat.isa.rcvskb = dev_alloc_skb(card->idat.isa.rcvlen);
+					if (card->idat.isa.rcvskb == NULL) {
+						card->idat.isa.rcvignore = 1;
+						printk(KERN_WARNING
+						       "act2000_isa_receive: no memory\n");
+						test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock);
+						return;
+					}
+					memcpy(skb_put(card->idat.isa.rcvskb, 8), card->idat.isa.rcvhdr, 8);
+					card->idat.isa.rcvptr = skb_put(card->idat.isa.rcvskb, card->idat.isa.rcvlen - 8);
+				} else {
+					card->idat.isa.rcvidx = 0;
+					printk(KERN_WARNING
+					       "act2000_isa_receive: Invalid CAPI msg\n");
+					{
+						int i; __u8 *p; __u8 *c; __u8 tmp[30];
+						for (i = 0, p = (__u8 *)&card->idat.isa.rcvhdr, c = tmp; i < 8; i++)
+							c += sprintf(c, "%02x ", *(p++));
+						printk(KERN_WARNING "act2000_isa_receive: %s\n", tmp);
+					}
+				}
+			}
+		} else {
+			if (!card->idat.isa.rcvignore)
+				*card->idat.isa.rcvptr++ = c;
+			if (++card->idat.isa.rcvidx >= card->idat.isa.rcvlen) {
+				if (!card->idat.isa.rcvignore) {
+					skb_queue_tail(&card->rcvq, card->idat.isa.rcvskb);
+					act2000_schedule_rx(card);
+				}
+				card->idat.isa.rcvidx = 0;
+				card->idat.isa.rcvlen = 8;
+				card->idat.isa.rcvignore = 0;
+				card->idat.isa.rcvskb = NULL;
+				card->idat.isa.rcvptr = card->idat.isa.rcvhdr;
+			}
+		}
+	}
+	if (!(card->flags & ACT2000_FLAGS_IVALID)) {
+		/* In polling mode, schedule myself */
+		if ((card->idat.isa.rcvidx) &&
+		    (card->idat.isa.rcvignore ||
+		     (card->idat.isa.rcvidx < card->idat.isa.rcvlen)))
+			act2000_schedule_poll(card);
+	}
+	test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock);
+}
+
+void
+act2000_isa_send(act2000_card * card)
+{
+	unsigned long flags;
+	struct sk_buff *skb;
+	actcapi_msg *msg;
+	int l;
+
+        if (test_and_set_bit(ACT2000_LOCK_TX, (void *) &card->ilock) != 0)
+		return;
+	while (1) {
+		spin_lock_irqsave(&card->lock, flags);
+		if (!(card->sbuf)) {
+			if ((card->sbuf = skb_dequeue(&card->sndq))) {
+				card->ack_msg = card->sbuf->data;
+				msg = (actcapi_msg *)card->sbuf->data;
+				if ((msg->hdr.cmd.cmd == 0x86) &&
+				    (msg->hdr.cmd.subcmd == 0)   ) {
+					/* Save flags in message */
+					card->need_b3ack = msg->msg.data_b3_req.flags;
+					msg->msg.data_b3_req.flags = 0;
+				}
+			}
+		}
+		spin_unlock_irqrestore(&card->lock, flags);
+		if (!(card->sbuf)) {
+			/* No more data to send */
+			test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock);
+			return;
+		}
+		skb = card->sbuf;
+		l = 0;
+		while (skb->len) {
+			if (act2000_isa_writeb(card, *(skb->data))) {
+				/* Fifo is full, but more data to send */
+				test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock);
+				/* Schedule myself */
+				act2000_schedule_tx(card);
+				return;
+			}
+			skb_pull(skb, 1);
+			l++;
+		}
+		msg = (actcapi_msg *)card->ack_msg;
+		if ((msg->hdr.cmd.cmd == 0x86) &&
+		    (msg->hdr.cmd.subcmd == 0)   ) {
+			/*
+			 * If it's user data, reset data-ptr
+			 * and put skb into ackq.
+			 */
+			skb->data = card->ack_msg;
+			/* Restore flags in message */
+			msg->msg.data_b3_req.flags = card->need_b3ack;
+			skb_queue_tail(&card->ackq, skb);
+		} else
+			dev_kfree_skb(skb);
+		card->sbuf = NULL;
+	}
+}
+
+/*
+ * Get firmware ID, check for 'ISDN' signature.
+ */
+static int
+act2000_isa_getid(act2000_card * card)
+{
+
+        act2000_fwid fid;
+        u_char *p = (u_char *) & fid;
+        int count = 0;
+
+        while (1) {
+                if (count > 510)
+                        return -EPROTO;
+                if (act2000_isa_readb(card, p++))
+                        break;
+                count++;
+        }
+        if (count <= 20) {
+                printk(KERN_WARNING "act2000: No Firmware-ID!\n");
+                return -ETIME;
+        }
+        *p = '\0';
+        fid.revlen[0] = '\0';
+        if (strcmp(fid.isdn, "ISDN")) {
+                printk(KERN_WARNING "act2000: Wrong Firmware-ID!\n");
+                return -EPROTO;
+        }
+	if ((p = strchr(fid.revision, '\n')))
+		*p = '\0';
+        printk(KERN_INFO "act2000: Firmware-ID: %s\n", fid.revision);
+	if (card->flags & ACT2000_FLAGS_IVALID) {
+		printk(KERN_DEBUG "Enabling Interrupts ...\n");
+		act2000_isa_enable_irq(card);
+	}
+        return 0;
+}
+
+/*
+ * Download microcode into card, check Firmware signature.
+ */
+int
+act2000_isa_download(act2000_card * card, act2000_ddef __user * cb)
+{
+        unsigned int length;
+        int l;
+        int c;
+        long timeout;
+        u_char *b;
+        u_char __user *p;
+        u_char *buf;
+        act2000_ddef cblock;
+
+        if (!act2000_isa_reset(card->port))
+                return -ENXIO;
+        msleep_interruptible(500);
+        if (copy_from_user(&cblock, cb, sizeof(cblock)))
+        	return -EFAULT;
+        length = cblock.length;
+        p = cblock.buffer;
+        if (!access_ok(VERIFY_READ, p, length))
+                return -EFAULT;
+        buf = (u_char *) kmalloc(1024, GFP_KERNEL);
+        if (!buf)
+                return -ENOMEM;
+        timeout = 0;
+        while (length) {
+                l = (length > 1024) ? 1024 : length;
+                c = 0;
+                b = buf;
+                if (copy_from_user(buf, p, l)) {
+                        kfree(buf);
+                        return -EFAULT;
+                }
+                while (c < l) {
+                        if (act2000_isa_writeb(card, *b++)) {
+                                printk(KERN_WARNING
+                                       "act2000: loader timed out"
+                                       " len=%d c=%d\n", length, c);
+                                kfree(buf);
+                                return -ETIME;
+                        }
+                        c++;
+                }
+                length -= l;
+                p += l;
+        }
+        kfree(buf);
+        msleep_interruptible(500);
+        return (act2000_isa_getid(card));
+}
diff --git a/drivers/isdn/act2000/act2000_isa.h b/drivers/isdn/act2000/act2000_isa.h
new file mode 100644
index 0000000..ad86c5ed
--- /dev/null
+++ b/drivers/isdn/act2000/act2000_isa.h
@@ -0,0 +1,136 @@
+/* $Id: act2000_isa.h,v 1.4.6.1 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version).
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef act2000_isa_h
+#define act2000_isa_h
+
+#define ISA_POLL_LOOP 40        /* Try to read-write before give up */
+
+typedef enum {
+        INT_NO_CHANGE = 0,      /* Do not change the Mask */
+        INT_ON = 1,             /* Set to Enable */
+        INT_OFF = 2,            /* Set to Disable */
+} ISA_INT_T;
+
+/**************************************************************************/
+/*      Configuration Register COR (RW)                                   */
+/**************************************************************************/
+/*    7    |   6    |    5   |   4    |    3   |    2   |    1   |    0   */
+/* Soft Res|  IRQM  |        IRQ Select        |   N/A  |  WAIT  |Proc err */
+/**************************************************************************/
+#define        ISA_COR             0	/* Offset for ISA config register */
+#define        ISA_COR_PERR     0x01	/* Processor Error Enabled        */
+#define        ISA_COR_WS       0x02	/* Insert Wait State if 1         */
+#define        ISA_COR_IRQOFF   0x38	/* No Interrupt                   */
+#define        ISA_COR_IRQ07    0x30	/* IRQ 7 Enable                   */
+#define        ISA_COR_IRQ05    0x28	/* IRQ 5 Enable                   */
+#define        ISA_COR_IRQ03    0x20	/* IRQ 3 Enable                   */
+#define        ISA_COR_IRQ10    0x18	/* IRQ 10 Enable                  */
+#define        ISA_COR_IRQ11    0x10	/* IRQ 11 Enable                  */
+#define        ISA_COR_IRQ12    0x08	/* IRQ 12 Enable                  */
+#define        ISA_COR_IRQ15    0x00	/* IRQ 15 Enable                  */
+#define        ISA_COR_IRQPULSE 0x40	/* 0 = Level 1 = Pulse Interrupt  */
+#define        ISA_COR_RESET    0x80	/* Soft Reset for Transputer      */
+
+/**************************************************************************/
+/*      Interrupt Source Register ISR (RO)                                */
+/**************************************************************************/
+/*    7    |   6    |    5   |   4    |    3   |    2   |    1   |    0   */
+/*   N/A   |  N/A   |   N/A  |Err sig |Ser ID  |IN Intr |Out Intr| Error  */
+/**************************************************************************/
+#define        ISA_ISR             1	/* Offset for Interrupt Register  */
+#define        ISA_ISR_ERR      0x01	/* Error Interrupt                */
+#define        ISA_ISR_OUT      0x02	/* Output Interrupt               */
+#define        ISA_ISR_INP      0x04	/* Input Interrupt                */
+#define        ISA_ISR_SERIAL   0x08	/* Read out Serial ID after Reset */
+#define        ISA_ISR_ERRSIG   0x10	/* Error Signal Input             */
+#define        ISA_ISR_ERR_MASK 0xfe    /* Mask Error Interrupt           */
+#define        ISA_ISR_OUT_MASK 0xfd    /* Mask Output Interrupt          */
+#define        ISA_ISR_INP_MASK 0xfb    /* Mask Input Interrupt           */
+
+/* Signature delivered after Reset at ISA_ISR_SERIAL (LSB first)          */
+#define        ISA_SER_ID     0x0201	/* ID for ISA Card                */
+
+/**************************************************************************/
+/*      EEPROM Register EPR (RW)                                          */
+/**************************************************************************/
+/*    7    |   6    |    5   |   4    |    3   |    2   |    1   |    0   */
+/*   N/A   |  N/A   |   N/A  |ROM Hold| ROM CS |ROM CLK | ROM IN |ROM Out */
+/**************************************************************************/
+#define        ISA_EPR             2	/* Offset for this Register       */
+#define        ISA_EPR_OUT      0x01	/* Rome Register Out (RO)         */
+#define        ISA_EPR_IN       0x02	/* Rom Register In (WR)           */
+#define        ISA_EPR_CLK      0x04	/* Rom Clock (WR)                 */
+#define        ISA_EPR_CS       0x08	/* Rom Cip Select (WR)            */
+#define        ISA_EPR_HOLD     0x10	/* Rom Hold Signal (WR)           */
+
+/**************************************************************************/
+/*      EEPROM enable Register EER (unused)                               */
+/**************************************************************************/
+#define        ISA_EER             3	/* Offset for this Register       */
+
+/**************************************************************************/
+/*      SLC Data Input SDI (RO)                                           */
+/**************************************************************************/
+#define        ISA_SDI             4	/* Offset for this Register       */
+
+/**************************************************************************/
+/*      SLC Data Output SDO (WO)                                          */
+/**************************************************************************/
+#define        ISA_SDO             5	/* Offset for this Register       */
+
+/**************************************************************************/
+/*      IMS C011 Mode 2 Input Status Register for INMOS CPU SIS (RW)      */
+/**************************************************************************/
+/*    7    |   6    |    5   |   4    |    3   |    2   |    1   |    0   */
+/*   N/A   |  N/A   |   N/A  |  N/A   |   N/A  |   N/A  |Int Ena |Data Pre */
+/**************************************************************************/
+#define        ISA_SIS             6	/* Offset for this Register       */
+#define        ISA_SIS_READY    0x01	/* If 1 : data is available       */
+#define        ISA_SIS_INT      0x02	/* Enable Interrupt for READ      */
+
+/**************************************************************************/
+/*      IMS C011 Mode 2 Output Status Register from INMOS CPU SOS (RW)    */
+/**************************************************************************/
+/*    7    |   6    |    5   |   4    |    3   |    2   |    1   |    0   */
+/*   N/A   |  N/A   |   N/A  |  N/A   |   N/A  |   N/A  |Int Ena |Out Rdy */
+/**************************************************************************/
+#define        ISA_SOS             7	/* Offset for this Register       */
+#define        ISA_SOS_READY    0x01	/* If 1 : we can write Data       */
+#define        ISA_SOS_INT      0x02	/* Enable Interrupt for WRITE     */
+
+#define        ISA_REGION          8	/* Number of Registers            */
+
+
+/* Macros for accessing ports */
+#define ISA_PORT_COR (card->port+ISA_COR)
+#define ISA_PORT_ISR (card->port+ISA_ISR)
+#define ISA_PORT_EPR (card->port+ISA_EPR)
+#define ISA_PORT_EER (card->port+ISA_EER)
+#define ISA_PORT_SDI (card->port+ISA_SDI)
+#define ISA_PORT_SDO (card->port+ISA_SDO)
+#define ISA_PORT_SIS (card->port+ISA_SIS)
+#define ISA_PORT_SOS (card->port+ISA_SOS)
+
+/* Prototypes */
+
+extern int act2000_isa_detect(unsigned short portbase);
+extern int act2000_isa_config_irq(act2000_card * card, short irq);
+extern int act2000_isa_config_port(act2000_card * card, unsigned short portbase);
+extern int act2000_isa_download(act2000_card * card, act2000_ddef __user * cb);
+extern void act2000_isa_release(act2000_card * card);
+extern void act2000_isa_receive(act2000_card *card);
+extern void act2000_isa_send(act2000_card *card);
+
+#endif                          /* act2000_isa_h */
diff --git a/drivers/isdn/act2000/capi.c b/drivers/isdn/act2000/capi.c
new file mode 100644
index 0000000..40395f5
--- /dev/null
+++ b/drivers/isdn/act2000/capi.c
@@ -0,0 +1,1177 @@
+/* $Id: capi.c,v 1.9.6.2 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ * CAPI encoder/decoder
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "capi.h"
+
+static actcapi_msgdsc valid_msg[] = {
+	{{ 0x86, 0x02}, "DATA_B3_IND"},       /* DATA_B3_IND/CONF must be first because of speed!!! */
+	{{ 0x86, 0x01}, "DATA_B3_CONF"},
+	{{ 0x02, 0x01}, "CONNECT_CONF"},
+	{{ 0x02, 0x02}, "CONNECT_IND"},
+	{{ 0x09, 0x01}, "CONNECT_INFO_CONF"},
+	{{ 0x03, 0x02}, "CONNECT_ACTIVE_IND"},
+	{{ 0x04, 0x01}, "DISCONNECT_CONF"},
+	{{ 0x04, 0x02}, "DISCONNECT_IND"},
+	{{ 0x05, 0x01}, "LISTEN_CONF"},
+	{{ 0x06, 0x01}, "GET_PARAMS_CONF"},
+	{{ 0x07, 0x01}, "INFO_CONF"},
+	{{ 0x07, 0x02}, "INFO_IND"},
+	{{ 0x08, 0x01}, "DATA_CONF"},
+	{{ 0x08, 0x02}, "DATA_IND"},
+	{{ 0x40, 0x01}, "SELECT_B2_PROTOCOL_CONF"},
+	{{ 0x80, 0x01}, "SELECT_B3_PROTOCOL_CONF"},
+	{{ 0x81, 0x01}, "LISTEN_B3_CONF"},
+	{{ 0x82, 0x01}, "CONNECT_B3_CONF"},
+	{{ 0x82, 0x02}, "CONNECT_B3_IND"},
+	{{ 0x83, 0x02}, "CONNECT_B3_ACTIVE_IND"},
+	{{ 0x84, 0x01}, "DISCONNECT_B3_CONF"},
+	{{ 0x84, 0x02}, "DISCONNECT_B3_IND"},
+	{{ 0x85, 0x01}, "GET_B3_PARAMS_CONF"},
+	{{ 0x01, 0x01}, "RESET_B3_CONF"},
+	{{ 0x01, 0x02}, "RESET_B3_IND"},
+	/* {{ 0x87, 0x02, "HANDSET_IND"}, not implemented */
+	{{ 0xff, 0x01}, "MANUFACTURER_CONF"},
+	{{ 0xff, 0x02}, "MANUFACTURER_IND"},
+#ifdef DEBUG_MSG
+	/* Requests */
+	{{ 0x01, 0x00}, "RESET_B3_REQ"},
+	{{ 0x02, 0x00}, "CONNECT_REQ"},
+	{{ 0x04, 0x00}, "DISCONNECT_REQ"},
+	{{ 0x05, 0x00}, "LISTEN_REQ"},
+	{{ 0x06, 0x00}, "GET_PARAMS_REQ"},
+	{{ 0x07, 0x00}, "INFO_REQ"},
+	{{ 0x08, 0x00}, "DATA_REQ"},
+	{{ 0x09, 0x00}, "CONNECT_INFO_REQ"},
+	{{ 0x40, 0x00}, "SELECT_B2_PROTOCOL_REQ"},
+	{{ 0x80, 0x00}, "SELECT_B3_PROTOCOL_REQ"},
+	{{ 0x81, 0x00}, "LISTEN_B3_REQ"},
+	{{ 0x82, 0x00}, "CONNECT_B3_REQ"},
+	{{ 0x84, 0x00}, "DISCONNECT_B3_REQ"},
+	{{ 0x85, 0x00}, "GET_B3_PARAMS_REQ"},
+	{{ 0x86, 0x00}, "DATA_B3_REQ"},
+	{{ 0xff, 0x00}, "MANUFACTURER_REQ"},
+	/* Responses */
+	{{ 0x01, 0x03}, "RESET_B3_RESP"},	
+	{{ 0x02, 0x03}, "CONNECT_RESP"},	
+	{{ 0x03, 0x03}, "CONNECT_ACTIVE_RESP"},	
+	{{ 0x04, 0x03}, "DISCONNECT_RESP"},	
+	{{ 0x07, 0x03}, "INFO_RESP"},	
+	{{ 0x08, 0x03}, "DATA_RESP"},	
+	{{ 0x82, 0x03}, "CONNECT_B3_RESP"},	
+	{{ 0x83, 0x03}, "CONNECT_B3_ACTIVE_RESP"},	
+	{{ 0x84, 0x03}, "DISCONNECT_B3_RESP"},
+	{{ 0x86, 0x03}, "DATA_B3_RESP"},
+	{{ 0xff, 0x03}, "MANUFACTURER_RESP"},
+#endif
+	{{ 0x00, 0x00}, NULL},
+};
+#define num_valid_msg (sizeof(valid_msg)/sizeof(actcapi_msgdsc))
+#define num_valid_imsg 27 /* MANUFACTURER_IND */
+
+/*
+ * Check for a valid incoming CAPI message.
+ * Return:
+ *   0 = Invalid message
+ *   1 = Valid message, no B-Channel-data
+ *   2 = Valid message, B-Channel-data
+ */
+int
+actcapi_chkhdr(act2000_card * card, actcapi_msghdr *hdr)
+{
+	int i;
+
+	if (hdr->applicationID != 1)
+		return 0;
+	if (hdr->len < 9)
+		return 0;
+	for (i = 0; i < num_valid_imsg; i++)
+		if ((hdr->cmd.cmd == valid_msg[i].cmd.cmd) &&
+		    (hdr->cmd.subcmd == valid_msg[i].cmd.subcmd)) {
+			return (i?1:2);
+		}
+	return 0;
+}
+
+#define ACTCAPI_MKHDR(l, c, s) { \
+	skb = alloc_skb(l + 8, GFP_ATOMIC); \
+	if (skb) { \
+	        m = (actcapi_msg *)skb_put(skb, l + 8); \
+		m->hdr.len = l + 8; \
+		m->hdr.applicationID = 1; \
+	        m->hdr.cmd.cmd = c; \
+	        m->hdr.cmd.subcmd = s; \
+	        m->hdr.msgnum = actcapi_nextsmsg(card); \
+	} else m = NULL;\
+}
+
+#define ACTCAPI_CHKSKB if (!skb) { \
+	printk(KERN_WARNING "actcapi: alloc_skb failed\n"); \
+	return; \
+}
+
+#define ACTCAPI_QUEUE_TX { \
+	actcapi_debug_msg(skb, 1); \
+	skb_queue_tail(&card->sndq, skb); \
+	act2000_schedule_tx(card); \
+}
+
+int
+actcapi_listen_req(act2000_card *card)
+{
+	__u16 eazmask = 0;
+	int i;
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	for (i = 0; i < ACT2000_BCH; i++)
+		eazmask |= card->bch[i].eazmask;
+	ACTCAPI_MKHDR(9, 0x05, 0x00);
+        if (!skb) {
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+                return -ENOMEM;
+        }
+	m->msg.listen_req.controller = 0;
+	m->msg.listen_req.infomask = 0x3f; /* All information */
+	m->msg.listen_req.eazmask = eazmask;
+	m->msg.listen_req.simask = (eazmask)?0x86:0; /* All SI's  */
+	ACTCAPI_QUEUE_TX;
+        return 0;
+}
+
+int
+actcapi_connect_req(act2000_card *card, act2000_chan *chan, char *phone,
+		    char eaz, int si1, int si2)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR((11 + strlen(phone)), 0x02, 0x00);
+	if (!skb) {
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+		chan->fsm_state = ACT2000_STATE_NULL;
+		return -ENOMEM;
+	}
+	m->msg.connect_req.controller = 0;
+	m->msg.connect_req.bchan = 0x83;
+	m->msg.connect_req.infomask = 0x3f;
+	m->msg.connect_req.si1 = si1;
+	m->msg.connect_req.si2 = si2;
+	m->msg.connect_req.eaz = eaz?eaz:'0';
+	m->msg.connect_req.addr.len = strlen(phone) + 1;
+	m->msg.connect_req.addr.tnp = 0x81;
+	memcpy(m->msg.connect_req.addr.num, phone, strlen(phone));
+	chan->callref = m->hdr.msgnum;
+	ACTCAPI_QUEUE_TX;
+	return 0;
+}
+
+static void
+actcapi_connect_b3_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(17, 0x82, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.connect_b3_req.plci = chan->plci;
+	memset(&m->msg.connect_b3_req.ncpi, 0,
+	       sizeof(m->msg.connect_b3_req.ncpi));
+	m->msg.connect_b3_req.ncpi.len = 13;
+	m->msg.connect_b3_req.ncpi.modulo = 8;
+	ACTCAPI_QUEUE_TX;
+}
+
+/*
+ * Set net type (1TR6) or (EDSS1)
+ */
+int
+actcapi_manufacturer_req_net(act2000_card *card)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(5, 0xff, 0x00);
+        if (!skb) {
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+                return -ENOMEM;
+        }
+	m->msg.manufacturer_req_net.manuf_msg = 0x11;
+	m->msg.manufacturer_req_net.controller = 1;
+	m->msg.manufacturer_req_net.nettype = (card->ptype == ISDN_PTYPE_EURO)?1:0;
+	ACTCAPI_QUEUE_TX;
+	printk(KERN_INFO "act2000 %s: D-channel protocol now %s\n",
+	       card->interface.id, (card->ptype == ISDN_PTYPE_EURO)?"euro":"1tr6");
+	card->interface.features &=
+		~(ISDN_FEATURE_P_UNKNOWN | ISDN_FEATURE_P_EURO | ISDN_FEATURE_P_1TR6);
+	card->interface.features |=
+		((card->ptype == ISDN_PTYPE_EURO)?ISDN_FEATURE_P_EURO:ISDN_FEATURE_P_1TR6);
+        return 0;
+}
+
+/*
+ * Switch V.42 on or off
+ */
+int
+actcapi_manufacturer_req_v42(act2000_card *card, ulong arg)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(8, 0xff, 0x00);
+        if (!skb) {
+
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+                return -ENOMEM;
+        }
+	m->msg.manufacturer_req_v42.manuf_msg = 0x10;
+	m->msg.manufacturer_req_v42.controller = 0;
+	m->msg.manufacturer_req_v42.v42control = (arg?1:0);
+	ACTCAPI_QUEUE_TX;
+        return 0;
+}
+
+/*
+ * Set error-handler
+ */
+int
+actcapi_manufacturer_req_errh(act2000_card *card)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(4, 0xff, 0x00);
+        if (!skb) {
+
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+                return -ENOMEM;
+        }
+	m->msg.manufacturer_req_err.manuf_msg = 0x03;
+	m->msg.manufacturer_req_err.controller = 0;
+	ACTCAPI_QUEUE_TX;
+        return 0;
+}
+
+/*
+ * Set MSN-Mapping.
+ */
+int
+actcapi_manufacturer_req_msn(act2000_card *card)
+{
+	msn_entry *p = card->msn_list;
+	actcapi_msg *m;
+	struct sk_buff *skb;
+	int len;
+
+	while (p) {
+		int i;
+
+		len = strlen(p->msn);
+		for (i = 0; i < 2; i++) {
+			ACTCAPI_MKHDR(6 + len, 0xff, 0x00);
+			if (!skb) {
+				printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+				return -ENOMEM;
+			}
+			m->msg.manufacturer_req_msn.manuf_msg = 0x13 + i;
+			m->msg.manufacturer_req_msn.controller = 0;
+			m->msg.manufacturer_req_msn.msnmap.eaz = p->eaz;
+			m->msg.manufacturer_req_msn.msnmap.len = len;
+			memcpy(m->msg.manufacturer_req_msn.msnmap.msn, p->msn, len);
+			ACTCAPI_QUEUE_TX;
+		}
+		p = p->next;
+	}
+        return 0;
+}
+
+void
+actcapi_select_b2_protocol_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(10, 0x40, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.select_b2_protocol_req.plci = chan->plci;
+	memset(&m->msg.select_b2_protocol_req.dlpd, 0,
+	       sizeof(m->msg.select_b2_protocol_req.dlpd));
+	m->msg.select_b2_protocol_req.dlpd.len = 6;
+	switch (chan->l2prot) {
+		case ISDN_PROTO_L2_TRANS:
+			m->msg.select_b2_protocol_req.protocol = 0x03;
+			m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+			break;
+		case ISDN_PROTO_L2_HDLC:
+			m->msg.select_b2_protocol_req.protocol = 0x02;
+			m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+			break;
+		case ISDN_PROTO_L2_X75I:
+		case ISDN_PROTO_L2_X75UI:
+		case ISDN_PROTO_L2_X75BUI:
+			m->msg.select_b2_protocol_req.protocol = 0x01;
+			m->msg.select_b2_protocol_req.dlpd.dlen = 4000;
+			m->msg.select_b2_protocol_req.dlpd.laa = 3;
+			m->msg.select_b2_protocol_req.dlpd.lab = 1;
+			m->msg.select_b2_protocol_req.dlpd.win = 7;
+			m->msg.select_b2_protocol_req.dlpd.modulo = 8;
+			break;
+	}
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_select_b3_protocol_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(17, 0x80, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.select_b3_protocol_req.plci = chan->plci;
+	memset(&m->msg.select_b3_protocol_req.ncpd, 0,
+	       sizeof(m->msg.select_b3_protocol_req.ncpd));
+	switch (chan->l3prot) {
+		case ISDN_PROTO_L3_TRANS:
+			m->msg.select_b3_protocol_req.protocol = 0x04;
+			m->msg.select_b3_protocol_req.ncpd.len = 13;
+			m->msg.select_b3_protocol_req.ncpd.modulo = 8;
+			break;
+	}
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_listen_b3_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x81, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.listen_b3_req.plci = chan->plci;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(3, 0x04, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.disconnect_req.plci = chan->plci;
+	m->msg.disconnect_req.cause = 0;
+	ACTCAPI_QUEUE_TX;
+}
+
+void
+actcapi_disconnect_b3_req(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(17, 0x84, 0x00);
+	ACTCAPI_CHKSKB;
+	m->msg.disconnect_b3_req.ncci = chan->ncci;
+	memset(&m->msg.disconnect_b3_req.ncpi, 0,
+	       sizeof(m->msg.disconnect_b3_req.ncpi));
+	m->msg.disconnect_b3_req.ncpi.len = 13;
+	m->msg.disconnect_b3_req.ncpi.modulo = 8;
+	chan->fsm_state = ACT2000_STATE_BHWAIT;
+	ACTCAPI_QUEUE_TX;
+}
+
+void
+actcapi_connect_resp(act2000_card *card, act2000_chan *chan, __u8 cause)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(3, 0x02, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.connect_resp.plci = chan->plci;
+	m->msg.connect_resp.rejectcause = cause;
+	if (cause) {
+		chan->fsm_state = ACT2000_STATE_NULL;
+		chan->plci = 0x8000;
+	} else
+		chan->fsm_state = ACT2000_STATE_IWAIT;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_active_resp(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x03, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.connect_resp.plci = chan->plci;
+	if (chan->fsm_state == ACT2000_STATE_IWAIT)
+		chan->fsm_state = ACT2000_STATE_IBWAIT;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_b3_resp(act2000_card *card, act2000_chan *chan, __u8 rejectcause)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR((rejectcause?3:17), 0x82, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.connect_b3_resp.ncci = chan->ncci;
+	m->msg.connect_b3_resp.rejectcause = rejectcause;
+	if (!rejectcause) {
+		memset(&m->msg.connect_b3_resp.ncpi, 0,
+		       sizeof(m->msg.connect_b3_resp.ncpi));
+		m->msg.connect_b3_resp.ncpi.len = 13;
+		m->msg.connect_b3_resp.ncpi.modulo = 8;
+		chan->fsm_state = ACT2000_STATE_BWAIT;
+	}
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_connect_b3_active_resp(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x83, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.connect_b3_active_resp.ncci = chan->ncci;
+	chan->fsm_state = ACT2000_STATE_ACTIVE;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_info_resp(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x07, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.info_resp.plci = chan->plci;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_b3_resp(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x84, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.disconnect_b3_resp.ncci = chan->ncci;
+	chan->ncci = 0x8000;
+	chan->queued = 0;
+	ACTCAPI_QUEUE_TX;
+}
+
+static void
+actcapi_disconnect_resp(act2000_card *card, act2000_chan *chan)
+{
+	actcapi_msg *m;
+	struct sk_buff *skb;
+
+	ACTCAPI_MKHDR(2, 0x04, 0x03);
+	ACTCAPI_CHKSKB;
+	m->msg.disconnect_resp.plci = chan->plci;
+	chan->plci = 0x8000;
+	ACTCAPI_QUEUE_TX;
+}
+
+static int
+new_plci(act2000_card *card, __u16 plci)
+{
+	int i;
+	for (i = 0; i < ACT2000_BCH; i++)
+		if (card->bch[i].plci == 0x8000) {
+			card->bch[i].plci = plci;
+			return i;
+		}
+	return -1;
+}
+
+static int
+find_plci(act2000_card *card, __u16 plci)
+{
+	int i;
+	for (i = 0; i < ACT2000_BCH; i++)
+		if (card->bch[i].plci == plci)
+			return i;
+	return -1;
+}
+
+static int
+find_ncci(act2000_card *card, __u16 ncci)
+{
+	int i;
+	for (i = 0; i < ACT2000_BCH; i++)
+		if (card->bch[i].ncci == ncci)
+			return i;
+	return -1;
+}
+
+static int
+find_dialing(act2000_card *card, __u16 callref)
+{
+	int i;
+	for (i = 0; i < ACT2000_BCH; i++)
+		if ((card->bch[i].callref == callref) &&
+		    (card->bch[i].fsm_state == ACT2000_STATE_OCALL))
+			return i;
+	return -1;
+}
+
+static int
+actcapi_data_b3_ind(act2000_card *card, struct sk_buff *skb) {
+	__u16 plci;
+	__u16 ncci;
+	__u16 controller;
+	__u8  blocknr;
+	int chan;
+	actcapi_msg *msg = (actcapi_msg *)skb->data;
+
+	EVAL_NCCI(msg->msg.data_b3_ind.fakencci, plci, controller, ncci);
+	chan = find_ncci(card, ncci);
+	if (chan < 0)
+		return 0;
+	if (card->bch[chan].fsm_state != ACT2000_STATE_ACTIVE)
+		return 0;
+	if (card->bch[chan].plci != plci)
+		return 0;
+	blocknr = msg->msg.data_b3_ind.blocknr;
+	skb_pull(skb, 19);
+	card->interface.rcvcallb_skb(card->myid, chan, skb);
+        if (!(skb = alloc_skb(11, GFP_ATOMIC))) {
+                printk(KERN_WARNING "actcapi: alloc_skb failed\n");
+                return 1;
+        }
+	msg = (actcapi_msg *)skb_put(skb, 11);
+	msg->hdr.len = 11;
+	msg->hdr.applicationID = 1;
+	msg->hdr.cmd.cmd = 0x86;
+	msg->hdr.cmd.subcmd = 0x03;
+	msg->hdr.msgnum = actcapi_nextsmsg(card);
+	msg->msg.data_b3_resp.ncci = ncci;
+	msg->msg.data_b3_resp.blocknr = blocknr;
+	ACTCAPI_QUEUE_TX;
+	return 1;
+}
+
+/*
+ * Walk over ackq, unlink DATA_B3_REQ from it, if
+ * ncci and blocknr are matching.
+ * Decrement queued-bytes counter.
+ */
+static int
+handle_ack(act2000_card *card, act2000_chan *chan, __u8 blocknr) {
+	unsigned long flags;
+	struct sk_buff *skb;
+	struct sk_buff *tmp;
+	struct actcapi_msg *m;
+	int ret = 0;
+
+	spin_lock_irqsave(&card->lock, flags);
+	skb = skb_peek(&card->ackq);
+	spin_unlock_irqrestore(&card->lock, flags);
+        if (!skb) {
+		printk(KERN_WARNING "act2000: handle_ack nothing found!\n");
+		return 0;
+	}
+        tmp = skb;
+        while (1) {
+                m = (actcapi_msg *)tmp->data;
+                if ((((m->msg.data_b3_req.fakencci >> 8) & 0xff) == chan->ncci) &&
+		    (m->msg.data_b3_req.blocknr == blocknr)) {
+			/* found corresponding DATA_B3_REQ */
+                        skb_unlink(tmp);
+			chan->queued -= m->msg.data_b3_req.datalen;
+			if (m->msg.data_b3_req.flags)
+				ret = m->msg.data_b3_req.datalen;
+			dev_kfree_skb(tmp);
+			if (chan->queued < 0)
+				chan->queued = 0;
+                        return ret;
+                }
+                spin_lock_irqsave(&card->lock, flags);
+                tmp = skb_peek((struct sk_buff_head *)tmp);
+                spin_unlock_irqrestore(&card->lock, flags);
+                if ((tmp == skb) || (tmp == NULL)) {
+			/* reached end of queue */
+			printk(KERN_WARNING "act2000: handle_ack nothing found!\n");
+                        return 0;
+		}
+        }
+}
+
+void
+actcapi_dispatch(act2000_card *card)
+{
+	struct sk_buff *skb;
+	actcapi_msg *msg;
+	__u16 ccmd;
+	int chan;
+	int len;
+	act2000_chan *ctmp;
+	isdn_ctrl cmd;
+	char tmp[170];
+
+	while ((skb = skb_dequeue(&card->rcvq))) {
+		actcapi_debug_msg(skb, 0);
+		msg = (actcapi_msg *)skb->data;
+		ccmd = ((msg->hdr.cmd.cmd << 8) | msg->hdr.cmd.subcmd);
+		switch (ccmd) {
+			case 0x8602:
+				/* DATA_B3_IND */
+				if (actcapi_data_b3_ind(card, skb))
+					return;
+				break;
+			case 0x8601:
+				/* DATA_B3_CONF */
+				chan = find_ncci(card, msg->msg.data_b3_conf.ncci);
+				if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_ACTIVE)) {
+					if (msg->msg.data_b3_conf.info != 0)
+						printk(KERN_WARNING "act2000: DATA_B3_CONF: %04x\n",
+						       msg->msg.data_b3_conf.info);
+					len = handle_ack(card, &card->bch[chan],
+							 msg->msg.data_b3_conf.blocknr);
+					if (len) {
+						cmd.driver = card->myid;
+						cmd.command = ISDN_STAT_BSENT;
+						cmd.arg = chan;
+						cmd.parm.length = len;
+						card->interface.statcallb(&cmd);
+					}
+				}
+				break;
+			case 0x0201:
+				/* CONNECT_CONF */
+				chan = find_dialing(card, msg->hdr.msgnum);
+				if (chan >= 0) {
+					if (msg->msg.connect_conf.info) {
+						card->bch[chan].fsm_state = ACT2000_STATE_NULL;
+						cmd.driver = card->myid;
+						cmd.command = ISDN_STAT_DHUP;
+						cmd.arg = chan;
+						card->interface.statcallb(&cmd);
+					} else {
+						card->bch[chan].fsm_state = ACT2000_STATE_OWAIT;
+						card->bch[chan].plci = msg->msg.connect_conf.plci;
+					}
+				}
+				break;
+			case 0x0202:
+				/* CONNECT_IND */
+				chan = new_plci(card, msg->msg.connect_ind.plci);
+				if (chan < 0) {
+					ctmp = (act2000_chan *)tmp;
+					ctmp->plci = msg->msg.connect_ind.plci;
+					actcapi_connect_resp(card, ctmp, 0x11); /* All Card-Cannels busy */
+				} else {
+					card->bch[chan].fsm_state = ACT2000_STATE_ICALL;
+					cmd.driver = card->myid;
+					cmd.command = ISDN_STAT_ICALL;
+					cmd.arg = chan;
+					cmd.parm.setup.si1 = msg->msg.connect_ind.si1;
+					cmd.parm.setup.si2 = msg->msg.connect_ind.si2;
+					if (card->ptype == ISDN_PTYPE_EURO)
+						strcpy(cmd.parm.setup.eazmsn,
+						       act2000_find_eaz(card, msg->msg.connect_ind.eaz));
+					else {
+						cmd.parm.setup.eazmsn[0] = msg->msg.connect_ind.eaz;
+						cmd.parm.setup.eazmsn[1] = 0;
+					}
+					memset(cmd.parm.setup.phone, 0, sizeof(cmd.parm.setup.phone));
+					memcpy(cmd.parm.setup.phone, msg->msg.connect_ind.addr.num,
+					       msg->msg.connect_ind.addr.len - 1);
+					cmd.parm.setup.plan = msg->msg.connect_ind.addr.tnp;
+					cmd.parm.setup.screen = 0;
+					if (card->interface.statcallb(&cmd) == 2)
+						actcapi_connect_resp(card, &card->bch[chan], 0x15); /* Reject Call */
+				}
+				break;
+			case 0x0302:
+				/* CONNECT_ACTIVE_IND */
+				chan = find_plci(card, msg->msg.connect_active_ind.plci);
+				if (chan >= 0)
+					switch (card->bch[chan].fsm_state) {
+						case ACT2000_STATE_IWAIT:
+							actcapi_connect_active_resp(card, &card->bch[chan]);
+							break;
+						case ACT2000_STATE_OWAIT:
+							actcapi_connect_active_resp(card, &card->bch[chan]);
+							actcapi_select_b2_protocol_req(card, &card->bch[chan]);
+							break;
+					}
+				break;
+			case 0x8202:
+				/* CONNECT_B3_IND */
+				chan = find_plci(card, msg->msg.connect_b3_ind.plci);
+				if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_IBWAIT)) {
+					card->bch[chan].ncci = msg->msg.connect_b3_ind.ncci;
+					actcapi_connect_b3_resp(card, &card->bch[chan], 0);
+				} else {
+					ctmp = (act2000_chan *)tmp;
+					ctmp->ncci = msg->msg.connect_b3_ind.ncci;
+					actcapi_connect_b3_resp(card, ctmp, 0x11); /* All Card-Cannels busy */
+				}
+				break;
+			case 0x8302:
+				/* CONNECT_B3_ACTIVE_IND */
+				chan = find_ncci(card, msg->msg.connect_b3_active_ind.ncci);
+				if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BWAIT)) {
+					actcapi_connect_b3_active_resp(card, &card->bch[chan]);
+					cmd.driver = card->myid;
+					cmd.command = ISDN_STAT_BCONN;
+					cmd.arg = chan;
+					card->interface.statcallb(&cmd);
+				}
+				break;
+			case 0x8402:
+				/* DISCONNECT_B3_IND */
+				chan = find_ncci(card, msg->msg.disconnect_b3_ind.ncci);
+				if (chan >= 0) {
+					ctmp = &card->bch[chan];
+					actcapi_disconnect_b3_resp(card, ctmp);
+					switch (ctmp->fsm_state) {
+						case ACT2000_STATE_ACTIVE:
+							ctmp->fsm_state = ACT2000_STATE_DHWAIT2;
+							cmd.driver = card->myid;
+							cmd.command = ISDN_STAT_BHUP;
+							cmd.arg = chan;
+							card->interface.statcallb(&cmd);
+							break;
+						case ACT2000_STATE_BHWAIT2:
+							actcapi_disconnect_req(card, ctmp);
+							ctmp->fsm_state = ACT2000_STATE_DHWAIT;
+							cmd.driver = card->myid;
+							cmd.command = ISDN_STAT_BHUP;
+							cmd.arg = chan;
+							card->interface.statcallb(&cmd);
+							break;
+					}
+				}
+				break;
+			case 0x0402:
+				/* DISCONNECT_IND */
+				chan = find_plci(card, msg->msg.disconnect_ind.plci);
+				if (chan >= 0) {
+					ctmp = &card->bch[chan];
+					actcapi_disconnect_resp(card, ctmp);
+					ctmp->fsm_state = ACT2000_STATE_NULL;
+					cmd.driver = card->myid;
+					cmd.command = ISDN_STAT_DHUP;
+					cmd.arg = chan;
+					card->interface.statcallb(&cmd);
+				} else {
+					ctmp = (act2000_chan *)tmp;
+					ctmp->plci = msg->msg.disconnect_ind.plci;
+					actcapi_disconnect_resp(card, ctmp);
+				}
+				break;
+			case 0x4001:
+				/* SELECT_B2_PROTOCOL_CONF */
+				chan = find_plci(card, msg->msg.select_b2_protocol_conf.plci);
+				if (chan >= 0)
+					switch (card->bch[chan].fsm_state) {
+						case ACT2000_STATE_ICALL:
+						case ACT2000_STATE_OWAIT:
+							ctmp = &card->bch[chan];
+							if (msg->msg.select_b2_protocol_conf.info == 0)
+								actcapi_select_b3_protocol_req(card, ctmp);
+							else {
+								ctmp->fsm_state = ACT2000_STATE_NULL;
+								cmd.driver = card->myid;
+								cmd.command = ISDN_STAT_DHUP;
+								cmd.arg = chan;
+								card->interface.statcallb(&cmd);
+							}
+							break;
+					}
+				break;
+			case 0x8001:
+				/* SELECT_B3_PROTOCOL_CONF */
+				chan = find_plci(card, msg->msg.select_b3_protocol_conf.plci);
+				if (chan >= 0)
+					switch (card->bch[chan].fsm_state) {
+						case ACT2000_STATE_ICALL:
+						case ACT2000_STATE_OWAIT:
+							ctmp = &card->bch[chan];
+							if (msg->msg.select_b3_protocol_conf.info == 0)
+								actcapi_listen_b3_req(card, ctmp);
+							else {
+								ctmp->fsm_state = ACT2000_STATE_NULL;
+								cmd.driver = card->myid;
+								cmd.command = ISDN_STAT_DHUP;
+								cmd.arg = chan;
+								card->interface.statcallb(&cmd);
+							}
+					}
+				break;
+			case 0x8101:
+				/* LISTEN_B3_CONF */
+				chan = find_plci(card, msg->msg.listen_b3_conf.plci);
+				if (chan >= 0)
+					switch (card->bch[chan].fsm_state) {
+						case ACT2000_STATE_ICALL:
+							ctmp = &card->bch[chan];
+							if (msg->msg.listen_b3_conf.info == 0)
+								actcapi_connect_resp(card, ctmp, 0);
+							else {
+								ctmp->fsm_state = ACT2000_STATE_NULL;
+								cmd.driver = card->myid;
+								cmd.command = ISDN_STAT_DHUP;
+								cmd.arg = chan;
+								card->interface.statcallb(&cmd);
+							}
+							break;
+						case ACT2000_STATE_OWAIT:
+							ctmp = &card->bch[chan];
+							if (msg->msg.listen_b3_conf.info == 0) {
+								actcapi_connect_b3_req(card, ctmp);
+								ctmp->fsm_state = ACT2000_STATE_OBWAIT;
+								cmd.driver = card->myid;
+								cmd.command = ISDN_STAT_DCONN;
+								cmd.arg = chan;
+								card->interface.statcallb(&cmd);
+							} else {
+								ctmp->fsm_state = ACT2000_STATE_NULL;
+								cmd.driver = card->myid;
+								cmd.command = ISDN_STAT_DHUP;
+								cmd.arg = chan;
+								card->interface.statcallb(&cmd);
+							}
+							break;
+					}
+				break;
+			case 0x8201:
+				/* CONNECT_B3_CONF */
+				chan = find_plci(card, msg->msg.connect_b3_conf.plci);
+				if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_OBWAIT)) {
+					ctmp = &card->bch[chan];
+					if (msg->msg.connect_b3_conf.info) {
+						ctmp->fsm_state = ACT2000_STATE_NULL;
+						cmd.driver = card->myid;
+						cmd.command = ISDN_STAT_DHUP;
+						cmd.arg = chan;
+						card->interface.statcallb(&cmd);
+					} else {
+						ctmp->ncci = msg->msg.connect_b3_conf.ncci;
+						ctmp->fsm_state = ACT2000_STATE_BWAIT;
+					}
+				}
+				break;
+			case 0x8401:
+				/* DISCONNECT_B3_CONF */
+				chan = find_ncci(card, msg->msg.disconnect_b3_conf.ncci);
+				if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BHWAIT))
+					card->bch[chan].fsm_state = ACT2000_STATE_BHWAIT2;
+				break;
+			case 0x0702:
+				/* INFO_IND */
+				chan = find_plci(card, msg->msg.info_ind.plci);
+				if (chan >= 0)
+					/* TODO: Eval Charging info / cause */
+					actcapi_info_resp(card, &card->bch[chan]);
+				break;
+			case 0x0401:
+				/* LISTEN_CONF */
+			case 0x0501:
+				/* LISTEN_CONF */
+			case 0xff01:
+				/* MANUFACTURER_CONF */
+				break;
+			case 0xff02:
+				/* MANUFACTURER_IND */
+				if (msg->msg.manuf_msg == 3) {
+					memset(tmp, 0, sizeof(tmp));
+					strncpy(tmp,
+						&msg->msg.manufacturer_ind_err.errstring,
+						msg->hdr.len - 16);
+					if (msg->msg.manufacturer_ind_err.errcode)
+						printk(KERN_WARNING "act2000: %s\n", tmp);
+					else {
+						printk(KERN_DEBUG "act2000: %s\n", tmp);
+						if ((!strncmp(tmp, "INFO: Trace buffer con", 22)) ||
+						    (!strncmp(tmp, "INFO: Compile Date/Tim", 22))) {
+							card->flags |= ACT2000_FLAGS_RUNNING;
+							cmd.command = ISDN_STAT_RUN;
+							cmd.driver = card->myid;
+							cmd.arg = 0;
+							actcapi_manufacturer_req_net(card);
+							actcapi_manufacturer_req_msn(card);
+							actcapi_listen_req(card);
+							card->interface.statcallb(&cmd);
+						}
+					}
+				}
+				break;
+			default:
+				printk(KERN_WARNING "act2000: UNHANDLED Message %04x\n", ccmd);
+				break;
+		}
+		dev_kfree_skb(skb);
+	}
+}
+
+#ifdef DEBUG_MSG
+static void
+actcapi_debug_caddr(actcapi_addr *addr)
+{
+	char tmp[30];
+
+	printk(KERN_DEBUG " Alen  = %d\n", addr->len);
+	if (addr->len > 0)
+		printk(KERN_DEBUG " Atnp  = 0x%02x\n", addr->tnp);
+	if (addr->len > 1) {
+		memset(tmp, 0, 30);
+		memcpy(tmp, addr->num, addr->len - 1);
+		printk(KERN_DEBUG " Anum  = '%s'\n", tmp);
+	}
+}
+
+static void
+actcapi_debug_ncpi(actcapi_ncpi *ncpi)
+{
+	printk(KERN_DEBUG " ncpi.len = %d\n", ncpi->len);
+	if (ncpi->len >= 2)
+		printk(KERN_DEBUG " ncpi.lic = 0x%04x\n", ncpi->lic);
+	if (ncpi->len >= 4)
+		printk(KERN_DEBUG " ncpi.hic = 0x%04x\n", ncpi->hic);
+	if (ncpi->len >= 6)
+		printk(KERN_DEBUG " ncpi.ltc = 0x%04x\n", ncpi->ltc);
+	if (ncpi->len >= 8)
+		printk(KERN_DEBUG " ncpi.htc = 0x%04x\n", ncpi->htc);
+	if (ncpi->len >= 10)
+		printk(KERN_DEBUG " ncpi.loc = 0x%04x\n", ncpi->loc);
+	if (ncpi->len >= 12)
+		printk(KERN_DEBUG " ncpi.hoc = 0x%04x\n", ncpi->hoc);
+	if (ncpi->len >= 13)
+		printk(KERN_DEBUG " ncpi.mod = %d\n", ncpi->modulo);
+}
+
+static void
+actcapi_debug_dlpd(actcapi_dlpd *dlpd)
+{
+	printk(KERN_DEBUG " dlpd.len = %d\n", dlpd->len);
+	if (dlpd->len >= 2)
+		printk(KERN_DEBUG " dlpd.dlen   = 0x%04x\n", dlpd->dlen);
+	if (dlpd->len >= 3)
+		printk(KERN_DEBUG " dlpd.laa    = 0x%02x\n", dlpd->laa);
+	if (dlpd->len >= 4)
+		printk(KERN_DEBUG " dlpd.lab    = 0x%02x\n", dlpd->lab);
+	if (dlpd->len >= 5)
+		printk(KERN_DEBUG " dlpd.modulo = %d\n", dlpd->modulo);
+	if (dlpd->len >= 6)
+		printk(KERN_DEBUG " dlpd.win    = %d\n", dlpd->win);
+}
+
+#ifdef DEBUG_DUMP_SKB
+static void dump_skb(struct sk_buff *skb) {
+	char tmp[80];
+	char *p = skb->data;
+	char *t = tmp;
+	int i;
+
+	for (i = 0; i < skb->len; i++) {
+		t += sprintf(t, "%02x ", *p++ & 0xff);
+		if ((i & 0x0f) == 8) {
+			printk(KERN_DEBUG "dump: %s\n", tmp);
+			t = tmp;
+		}
+	}
+	if (i & 0x07)
+		printk(KERN_DEBUG "dump: %s\n", tmp);
+}
+#endif
+
+void
+actcapi_debug_msg(struct sk_buff *skb, int direction)
+{
+	actcapi_msg *msg = (actcapi_msg *)skb->data;
+	char *descr;
+	int i;
+	char tmp[170];
+	
+#ifndef DEBUG_DATA_MSG
+	if (msg->hdr.cmd.cmd == 0x86)
+		return;
+#endif
+	descr = "INVALID";
+#ifdef DEBUG_DUMP_SKB
+	dump_skb(skb);
+#endif
+	for (i = 0; i < num_valid_msg; i++)
+		if ((msg->hdr.cmd.cmd == valid_msg[i].cmd.cmd) &&
+		    (msg->hdr.cmd.subcmd == valid_msg[i].cmd.subcmd)) {
+			descr = valid_msg[i].description;
+			break;
+		}
+	printk(KERN_DEBUG "%s %s msg\n", direction?"Outgoing":"Incoming", descr);
+	printk(KERN_DEBUG " ApplID = %d\n", msg->hdr.applicationID);
+	printk(KERN_DEBUG " Len    = %d\n", msg->hdr.len);
+	printk(KERN_DEBUG " MsgNum = 0x%04x\n", msg->hdr.msgnum);
+	printk(KERN_DEBUG " Cmd    = 0x%02x\n", msg->hdr.cmd.cmd);
+	printk(KERN_DEBUG " SubCmd = 0x%02x\n", msg->hdr.cmd.subcmd);
+	switch (i) {
+		case 0:
+			/* DATA B3 IND */
+			printk(KERN_DEBUG " BLOCK = 0x%02x\n",
+			       msg->msg.data_b3_ind.blocknr);
+			break;
+		case 2:
+			/* CONNECT CONF */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.connect_conf.plci);
+			printk(KERN_DEBUG " Info = 0x%04x\n",
+			       msg->msg.connect_conf.info);
+			break;
+		case 3:
+			/* CONNECT IND */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.connect_ind.plci);
+			printk(KERN_DEBUG " Contr = %d\n",
+			       msg->msg.connect_ind.controller);
+			printk(KERN_DEBUG " SI1   = %d\n",
+			       msg->msg.connect_ind.si1);
+			printk(KERN_DEBUG " SI2   = %d\n",
+			       msg->msg.connect_ind.si2);
+			printk(KERN_DEBUG " EAZ   = '%c'\n",
+			       msg->msg.connect_ind.eaz);
+			actcapi_debug_caddr(&msg->msg.connect_ind.addr);
+			break;
+		case 5:
+			/* CONNECT ACTIVE IND */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.connect_active_ind.plci);
+			actcapi_debug_caddr(&msg->msg.connect_active_ind.addr);
+			break;
+		case 8:
+			/* LISTEN CONF */
+			printk(KERN_DEBUG " Contr = %d\n",
+			       msg->msg.listen_conf.controller);
+			printk(KERN_DEBUG " Info = 0x%04x\n",
+			       msg->msg.listen_conf.info);
+			break;
+		case 11:
+			/* INFO IND */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.info_ind.plci);
+			printk(KERN_DEBUG " Imsk = 0x%04x\n",
+			       msg->msg.info_ind.nr.mask);
+			if (msg->hdr.len > 12) {
+				int l = msg->hdr.len - 12;
+				int j;
+				char *p = tmp;
+				for (j = 0; j < l ; j++)
+					p += sprintf(p, "%02x ", msg->msg.info_ind.el.display[j]);
+				printk(KERN_DEBUG " D = '%s'\n", tmp);
+			}
+			break;
+		case 14:
+			/* SELECT B2 PROTOCOL CONF */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.select_b2_protocol_conf.plci);
+			printk(KERN_DEBUG " Info = 0x%04x\n",
+			       msg->msg.select_b2_protocol_conf.info);
+			break;
+		case 15:
+			/* SELECT B3 PROTOCOL CONF */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.select_b3_protocol_conf.plci);
+			printk(KERN_DEBUG " Info = 0x%04x\n",
+			       msg->msg.select_b3_protocol_conf.info);
+			break;
+		case 16:
+			/* LISTEN B3 CONF */
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.listen_b3_conf.plci);
+			printk(KERN_DEBUG " Info = 0x%04x\n",
+			       msg->msg.listen_b3_conf.info);
+			break;
+		case 18:
+			/* CONNECT B3 IND */
+			printk(KERN_DEBUG " NCCI = 0x%04x\n",
+			       msg->msg.connect_b3_ind.ncci);
+			printk(KERN_DEBUG " PLCI = 0x%04x\n",
+			       msg->msg.connect_b3_ind.plci);
+			actcapi_debug_ncpi(&msg->msg.connect_b3_ind.ncpi);
+			break;
+		case 19:
+			/* CONNECT B3 ACTIVE IND */
+			printk(KERN_DEBUG " NCCI = 0x%04x\n",
+			       msg->msg.connect_b3_active_ind.ncci);
+			actcapi_debug_ncpi(&msg->msg.connect_b3_active_ind.ncpi);
+			break;
+		case 26:
+			/* MANUFACTURER IND */
+			printk(KERN_DEBUG " Mmsg = 0x%02x\n",
+			       msg->msg.manufacturer_ind_err.manuf_msg);
+			switch (msg->msg.manufacturer_ind_err.manuf_msg) {
+				case 3:
+					printk(KERN_DEBUG " Contr = %d\n",
+					       msg->msg.manufacturer_ind_err.controller);
+					printk(KERN_DEBUG " Code = 0x%08x\n",
+					       msg->msg.manufacturer_ind_err.errcode);
+					memset(tmp, 0, sizeof(tmp));
+					strncpy(tmp, &msg->msg.manufacturer_ind_err.errstring,
+						msg->hdr.len - 16);
+					printk(KERN_DEBUG " Emsg = '%s'\n", tmp);
+					break;
+			}
+			break;
+		case 30:
+			/* LISTEN REQ */
+			printk(KERN_DEBUG " Imsk = 0x%08x\n",
+			       msg->msg.listen_req.infomask);
+			printk(KERN_DEBUG " Emsk = 0x%04x\n",
+			       msg->msg.listen_req.eazmask);
+			printk(KERN_DEBUG " Smsk = 0x%04x\n",
+			       msg->msg.listen_req.simask);
+			break;
+		case 35:
+			/* SELECT_B2_PROTOCOL_REQ */
+			printk(KERN_DEBUG " PLCI  = 0x%04x\n",
+			       msg->msg.select_b2_protocol_req.plci);
+			printk(KERN_DEBUG " prot  = 0x%02x\n",
+			       msg->msg.select_b2_protocol_req.protocol);
+			if (msg->hdr.len >= 11)
+				printk(KERN_DEBUG "No dlpd\n");
+			else
+				actcapi_debug_dlpd(&msg->msg.select_b2_protocol_req.dlpd);
+			break;
+		case 44:
+			/* CONNECT RESP */
+			printk(KERN_DEBUG " PLCI  = 0x%04x\n",
+			       msg->msg.connect_resp.plci);
+			printk(KERN_DEBUG " CAUSE = 0x%02x\n",
+			       msg->msg.connect_resp.rejectcause);
+			break;
+		case 45:
+			/* CONNECT ACTIVE RESP */
+			printk(KERN_DEBUG " PLCI  = 0x%04x\n",
+			       msg->msg.connect_active_resp.plci);
+			break;
+	}
+}
+#endif
diff --git a/drivers/isdn/act2000/capi.h b/drivers/isdn/act2000/capi.h
new file mode 100644
index 0000000..04d2bcd
--- /dev/null
+++ b/drivers/isdn/act2000/capi.h
@@ -0,0 +1,366 @@
+/* $Id: capi.h,v 1.6.6.2 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#ifndef CAPI_H
+#define CAPI_H
+
+/* Command-part of a CAPI message */
+typedef struct actcapi_msgcmd {
+	__u8 cmd;
+	__u8 subcmd;
+} actcapi_msgcmd;
+
+/* CAPI message header */
+typedef struct actcapi_msghdr {
+	__u16 len;
+	__u16 applicationID;
+	actcapi_msgcmd cmd;
+	__u16 msgnum;
+} actcapi_msghdr;
+
+/* CAPI message description (for debugging) */
+typedef struct actcapi_msgdsc {
+	actcapi_msgcmd cmd;
+	char *description;
+} actcapi_msgdsc;
+
+/* CAPI Address */
+typedef struct actcapi_addr {
+	__u8 len;                            /* Length of element            */
+	__u8 tnp;                            /* Type/Numbering Plan          */
+	__u8 num[20];                        /* Caller ID                    */
+} actcapi_addr;
+
+/* CAPI INFO element mask */
+typedef  union actcapi_infonr {              /* info number                  */
+	__u16 mask;                          /* info-mask field              */
+	struct bmask {                       /* bit definitions              */
+		unsigned  codes : 3;         /* code set                     */
+		unsigned  rsvd  : 5;         /* reserved                     */
+		unsigned  svind : 1;         /* single, variable length ind. */
+		unsigned  wtype : 7;         /* W-element type               */
+	} bmask;
+} actcapi_infonr;
+
+/* CAPI INFO element */
+typedef union  actcapi_infoel {              /* info element                 */
+	__u8 len;                            /* length of info element       */
+	__u8 display[40];                    /* display contents             */
+	__u8 uuinfo[40];                     /* User-user info field         */
+	struct cause {                       /* Cause information            */
+		unsigned ext2  : 1;          /* extension                    */
+		unsigned cod   : 2;          /* coding standard              */
+		unsigned spare : 1;          /* spare                        */
+		unsigned loc   : 4;          /* location                     */
+		unsigned ext1  : 1;          /* extension                    */
+		unsigned cval  : 7;          /* Cause value                  */
+	} cause;                     
+	struct charge {                      /* Charging information         */
+		__u8 toc;                    /* type of charging info        */
+		__u8 unit[10];               /* charging units               */
+	} charge;
+	__u8 date[20];                       /* date fields                  */
+	__u8 stat;                           /* state of remote party        */
+} actcapi_infoel;
+
+/* Message for EAZ<->MSN Mapping */
+typedef struct actcapi_msn {
+	__u8 eaz;
+	__u8 len;                            /* Length of MSN                */
+	__u8 msn[15] __attribute__ ((packed));
+} actcapi_msn;
+
+typedef struct actcapi_dlpd {
+	__u8 len;                            /* Length of structure          */
+	__u16 dlen __attribute__ ((packed)); /* Data Length                  */
+	__u8 laa __attribute__ ((packed));   /* Link Address A               */
+	__u8 lab;                            /* Link Address B               */
+	__u8 modulo;                         /* Modulo Mode                  */
+	__u8 win;                            /* Window size                  */
+	__u8 xid[100];                       /* XID Information              */
+} actcapi_dlpd;
+
+typedef struct actcapi_ncpd {
+	__u8   len;                          /* Length of structure          */
+	__u16  lic __attribute__ ((packed));
+	__u16  hic __attribute__ ((packed));
+	__u16  ltc __attribute__ ((packed));
+	__u16  htc __attribute__ ((packed));
+	__u16  loc __attribute__ ((packed));
+	__u16  hoc __attribute__ ((packed));
+	__u8   modulo __attribute__ ((packed));
+} actcapi_ncpd;
+#define actcapi_ncpi actcapi_ncpd
+
+/*
+ * Layout of NCCI field in a B3 DATA CAPI message is different from
+ * standard at act2000:
+ *
+ * Bit 0-4  = PLCI
+ * Bit 5-7  = Controller
+ * Bit 8-15 = NCCI
+ */
+#define MAKE_NCCI(plci,contr,ncci) \
+        ((plci & 0x1f) | ((contr & 0x7) << 5) | ((ncci & 0xff) << 8))
+
+#define EVAL_NCCI(fakencci,plci,contr,ncci) { \
+	plci  = fakencci & 0x1f; \
+	contr = (fakencci >> 5) & 0x7; \
+	ncci  = (fakencci >> 8) & 0xff; \
+}
+
+/*
+ * Layout of PLCI field in a B3 DATA CAPI message is different from
+ * standard at act2000:
+ *
+ * Bit 0-4  = PLCI
+ * Bit 5-7  = Controller
+ * Bit 8-15 = reserved (must be 0)
+ */
+#define MAKE_PLCI(plci,contr) \
+        ((plci & 0x1f) | ((contr & 0x7) << 5))
+
+#define EVAL_PLCI(fakeplci,plci,contr) { \
+	plci  = fakeplci & 0x1f; \
+	contr = (fakeplci >> 5) & 0x7; \
+}
+
+typedef struct actcapi_msg {
+	actcapi_msghdr hdr;
+	union {
+		__u16 manuf_msg;
+		struct manufacturer_req_net {
+			__u16 manuf_msg;
+			__u16 controller;
+			__u8  nettype;
+		} manufacturer_req_net;
+		struct manufacturer_req_v42 {
+			__u16 manuf_msg;
+			__u16 controller;
+			__u32 v42control;
+		} manufacturer_req_v42;
+		struct manufacturer_conf_v42 {
+			__u16 manuf_msg;
+			__u16 controller;
+		} manufacturer_conf_v42;
+		struct manufacturer_req_err {
+			__u16 manuf_msg;
+			__u16 controller;
+		} manufacturer_req_err;
+		struct manufacturer_ind_err {
+			__u16 manuf_msg;
+			__u16 controller;
+			__u32 errcode;
+			__u8  errstring; /* actually up to 160 */
+		} manufacturer_ind_err;
+		struct manufacturer_req_msn {
+			__u16 manuf_msg;
+			__u16 controller;
+			actcapi_msn msnmap;
+		} manufacturer_req_msn;
+		/* TODO: TraceInit-req/conf/ind/resp and
+		 *       TraceDump-req/conf/ind/resp
+		 */
+		struct connect_req {
+			__u8  controller;
+			__u8  bchan;
+			__u32 infomask __attribute__ ((packed));
+			__u8  si1;
+			__u8  si2;
+			__u8  eaz;
+			actcapi_addr addr;
+		} connect_req;
+		struct connect_conf {
+			__u16 plci;
+			__u16 info;
+		} connect_conf;
+		struct connect_ind {
+			__u16 plci;
+			__u8  controller;
+			__u8  si1;
+			__u8  si2;
+			__u8  eaz;
+			actcapi_addr addr;
+		} connect_ind;
+		struct connect_resp {
+			__u16 plci;
+			__u8  rejectcause;
+		} connect_resp;
+		struct connect_active_ind {
+			__u16 plci;
+			actcapi_addr addr;
+		} connect_active_ind;
+		struct connect_active_resp {
+			__u16 plci;
+		} connect_active_resp;
+		struct connect_b3_req {
+			__u16 plci;
+			actcapi_ncpi ncpi;
+		} connect_b3_req;
+		struct connect_b3_conf {
+			__u16 plci;
+			__u16 ncci;
+			__u16 info;
+		} connect_b3_conf;
+		struct connect_b3_ind {
+			__u16 ncci;
+			__u16 plci;
+			actcapi_ncpi ncpi;
+		} connect_b3_ind;
+		struct connect_b3_resp {
+			__u16 ncci;
+			__u8  rejectcause;
+			actcapi_ncpi ncpi __attribute__ ((packed));
+		} connect_b3_resp;
+		struct disconnect_req {
+			__u16 plci;
+			__u8  cause;
+		} disconnect_req;
+		struct disconnect_conf {
+			__u16 plci;
+			__u16 info;
+		} disconnect_conf;
+		struct disconnect_ind {
+			__u16 plci;
+			__u16 info;
+		} disconnect_ind;
+		struct disconnect_resp {
+			__u16 plci;
+		} disconnect_resp;
+		struct connect_b3_active_ind {
+			__u16 ncci;
+			actcapi_ncpi ncpi;
+		} connect_b3_active_ind;
+		struct connect_b3_active_resp {
+			__u16 ncci;
+		} connect_b3_active_resp;
+		struct disconnect_b3_req {
+			__u16 ncci;
+			actcapi_ncpi ncpi;
+		} disconnect_b3_req;
+		struct disconnect_b3_conf {
+			__u16 ncci;
+			__u16 info;
+		} disconnect_b3_conf;
+		struct disconnect_b3_ind {
+			__u16 ncci;
+			__u16 info;
+			actcapi_ncpi ncpi;
+		} disconnect_b3_ind;
+		struct disconnect_b3_resp {
+			__u16 ncci;
+		} disconnect_b3_resp;
+		struct info_ind {
+			__u16 plci;
+			actcapi_infonr nr;
+			actcapi_infoel el;
+		} info_ind;
+		struct info_resp {
+			__u16 plci;
+		} info_resp;
+		struct listen_b3_req {
+			__u16 plci;
+		} listen_b3_req;
+		struct listen_b3_conf {
+			__u16 plci;
+			__u16 info;
+		} listen_b3_conf;
+		struct select_b2_protocol_req {
+			__u16 plci;
+			__u8  protocol;
+			actcapi_dlpd dlpd __attribute__ ((packed));
+		} select_b2_protocol_req;
+		struct select_b2_protocol_conf {
+			__u16 plci;
+			__u16 info;
+		} select_b2_protocol_conf;
+		struct select_b3_protocol_req {
+			__u16 plci;
+			__u8  protocol;
+			actcapi_ncpd ncpd __attribute__ ((packed));
+		} select_b3_protocol_req;
+		struct select_b3_protocol_conf {
+			__u16 plci;
+			__u16 info;
+		} select_b3_protocol_conf;
+		struct listen_req {
+			__u8  controller;
+			__u32 infomask __attribute__ ((packed));  
+			__u16 eazmask __attribute__ ((packed));
+			__u16 simask __attribute__ ((packed));
+		} listen_req;
+		struct listen_conf {
+			__u8  controller;
+			__u16 info __attribute__ ((packed));
+		} listen_conf;
+		struct data_b3_req {
+			__u16 fakencci;
+			__u16 datalen;
+			__u32 unused;
+			__u8  blocknr;
+			__u16 flags __attribute__ ((packed));
+		} data_b3_req;
+		struct data_b3_ind {
+			__u16 fakencci;
+			__u16 datalen;
+			__u32 unused;
+			__u8  blocknr;
+			__u16 flags __attribute__ ((packed));
+		} data_b3_ind;
+		struct data_b3_resp {
+			__u16 ncci;
+			__u8  blocknr;
+		} data_b3_resp;
+		struct data_b3_conf {
+			__u16 ncci;
+			__u8  blocknr;
+			__u16 info __attribute__ ((packed));
+		} data_b3_conf;
+	} msg;
+} actcapi_msg;
+
+extern __inline__ unsigned short
+actcapi_nextsmsg(act2000_card *card)
+{
+	unsigned long flags;
+	unsigned short n;
+
+	spin_lock_irqsave(&card->mnlock, flags);
+	n = card->msgnum;
+	card->msgnum++;
+	card->msgnum &= 0x7fff;
+	spin_unlock_irqrestore(&card->mnlock, flags);
+	return n;
+}
+#define DEBUG_MSG
+#undef DEBUG_DATA_MSG
+#undef DEBUG_DUMP_SKB
+
+extern int actcapi_chkhdr(act2000_card *, actcapi_msghdr *);
+extern int actcapi_listen_req(act2000_card *);
+extern int actcapi_manufacturer_req_net(act2000_card *);
+extern int actcapi_manufacturer_req_v42(act2000_card *, ulong);
+extern int actcapi_manufacturer_req_errh(act2000_card *);
+extern int actcapi_manufacturer_req_msn(act2000_card *);
+extern int actcapi_connect_req(act2000_card *, act2000_chan *, char *, char, int, int);
+extern void actcapi_select_b2_protocol_req(act2000_card *, act2000_chan *);
+extern void actcapi_disconnect_b3_req(act2000_card *, act2000_chan *);
+extern void actcapi_connect_resp(act2000_card *, act2000_chan *, __u8);
+extern void actcapi_dispatch(act2000_card *);
+#ifdef DEBUG_MSG
+extern void actcapi_debug_msg(struct sk_buff *skb, int);
+#else
+#define actcapi_debug_msg(skb, len)
+#endif
+#endif
diff --git a/drivers/isdn/act2000/module.c b/drivers/isdn/act2000/module.c
new file mode 100644
index 0000000..d89dcde
--- /dev/null
+++ b/drivers/isdn/act2000/module.c
@@ -0,0 +1,808 @@
+/* $Id: module.c,v 1.14.6.4 2001/09/23 22:24:32 kai Exp $
+ *
+ * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000.
+ *
+ * Author       Fritz Elfert
+ * Copyright    by Fritz Elfert      <fritz@isdn4linux.de>
+ * 
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * Thanks to Friedemann Baitinger and IBM Germany
+ *
+ */
+
+#include "act2000.h"
+#include "act2000_isa.h"
+#include "capi.h"
+#include <linux/module.h>
+#include <linux/init.h>
+
+static unsigned short act2000_isa_ports[] =
+{
+        0x0200, 0x0240, 0x0280, 0x02c0, 0x0300, 0x0340, 0x0380,
+        0xcfe0, 0xcfa0, 0xcf60, 0xcf20, 0xcee0, 0xcea0, 0xce60,
+};
+#define ISA_NRPORTS (sizeof(act2000_isa_ports)/sizeof(unsigned short))
+
+static act2000_card *cards = (act2000_card *) NULL;
+
+/* Parameters to be set by insmod */
+static int   act_bus  =  0;
+static int   act_port = -1;  /* -1 = Autoprobe  */
+static int   act_irq  = -1;
+static char *act_id   = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+MODULE_DESCRIPTION(       "ISDN4Linux: Driver for IBM Active 2000 ISDN card");
+MODULE_AUTHOR(            "Fritz Elfert");
+MODULE_LICENSE(           "GPL");
+MODULE_PARM_DESC(act_bus, "BusType of first card, 1=ISA, 2=MCA, 3=PCMCIA, currently only ISA");
+MODULE_PARM_DESC(membase, "Base port address of first card");
+MODULE_PARM_DESC(act_irq, "IRQ of first card");
+MODULE_PARM_DESC(act_id,  "ID-String of first card");
+module_param(act_bus,  int, 0);
+module_param(act_port, int, 0);
+module_param(act_irq, int, 0);
+module_param(act_id, charp, 0);
+
+static int act2000_addcard(int, int, int, char *);
+
+static act2000_chan *
+find_channel(act2000_card *card, int channel)
+{
+	if ((channel >= 0) && (channel < ACT2000_BCH))
+        	return &(card->bch[channel]);
+	printk(KERN_WARNING "act2000: Invalid channel %d\n", channel);
+	return NULL;
+}
+
+/*
+ * Free MSN list
+ */
+static void
+act2000_clear_msn(act2000_card *card)
+{
+	struct msn_entry *p = card->msn_list;
+	struct msn_entry *q;
+	unsigned long flags;
+
+	spin_lock_irqsave(&card->lock, flags);
+	card->msn_list = NULL;
+	spin_unlock_irqrestore(&card->lock, flags);
+	while (p) {
+		q  = p->next;
+		kfree(p);
+		p = q;
+	}
+}
+
+/*
+ * Find an MSN entry in the list.
+ * If ia5 != 0, return IA5-encoded EAZ, else
+ * return a bitmask with corresponding bit set.
+ */
+static __u16
+act2000_find_msn(act2000_card *card, char *msn, int ia5)
+{
+        struct msn_entry *p = card->msn_list;
+	__u8 eaz = '0';
+
+	while (p) {
+		if (!strcmp(p->msn, msn)) {
+			eaz = p->eaz;
+			break;
+		}
+		p = p->next;
+	}
+	if (!ia5)
+		return (1 << (eaz - '0'));
+	else
+		return eaz;
+}
+
+/*
+ * Find an EAZ entry in the list.
+ * return a string with corresponding msn.
+ */
+char *
+act2000_find_eaz(act2000_card *card, char eaz)
+{
+        struct msn_entry *p = card->msn_list;
+
+	while (p) {
+		if (p->eaz == eaz)
+			return(p->msn);
+		p = p->next;
+	}
+	return("\0");
+}
+
+/*
+ * Add or delete an MSN to the MSN list
+ *
+ * First character of msneaz is EAZ, rest is MSN.
+ * If length of eazmsn is 1, delete that entry.
+ */
+static int
+act2000_set_msn(act2000_card *card, char *eazmsn)
+{
+        struct msn_entry *p = card->msn_list;
+        struct msn_entry *q = NULL;
+	unsigned long flags;
+	int i;
+	
+	if (!strlen(eazmsn))
+		return 0;
+	if (strlen(eazmsn) > 16)
+		return -EINVAL;
+	for (i = 0; i < strlen(eazmsn); i++)
+		if (!isdigit(eazmsn[i]))
+			return -EINVAL;
+        if (strlen(eazmsn) == 1) {
+		/* Delete a single MSN */
+		while (p) {
+			if (p->eaz == eazmsn[0]) {
+				spin_lock_irqsave(&card->lock, flags);
+				if (q)
+					q->next = p->next;
+				else
+					card->msn_list = p->next;
+				spin_unlock_irqrestore(&card->lock, flags);
+				kfree(p);
+				printk(KERN_DEBUG
+				       "Mapping for EAZ %c deleted\n",
+				       eazmsn[0]);
+				return 0;
+			}
+			q = p;
+			p = p->next;
+		}
+		return 0;
+        }
+	/* Add a single MSN */
+	while (p) {
+		/* Found in list, replace MSN */
+		if (p->eaz == eazmsn[0]) {
+			spin_lock_irqsave(&card->lock, flags);
+			strcpy(p->msn, &eazmsn[1]);
+			spin_unlock_irqrestore(&card->lock, flags);
+			printk(KERN_DEBUG
+			       "Mapping for EAZ %c changed to %s\n",
+			       eazmsn[0],
+			       &eazmsn[1]);
+			return 0;
+		}
+		p = p->next;
+	}
+	/* Not found in list, add new entry */
+	p = kmalloc(sizeof(msn_entry), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+	p->eaz = eazmsn[0];
+	strcpy(p->msn, &eazmsn[1]);
+	p->next = card->msn_list;
+	spin_lock_irqsave(&card->lock, flags);
+	card->msn_list = p;
+	spin_unlock_irqrestore(&card->lock, flags);
+	printk(KERN_DEBUG
+	       "Mapping %c -> %s added\n",
+	       eazmsn[0],
+	       &eazmsn[1]);
+	return 0;
+}
+
+static void
+act2000_transmit(struct act2000_card *card)
+{
+	switch (card->bus) {
+		case ACT2000_BUS_ISA:
+			act2000_isa_send(card);
+			break;
+		case ACT2000_BUS_PCMCIA:
+		case ACT2000_BUS_MCA:
+		default:
+			printk(KERN_WARNING
+			       "act2000_transmit: Illegal bustype %d\n", card->bus);
+	}
+}
+
+static void
+act2000_receive(struct act2000_card *card)
+{
+	switch (card->bus) {
+		case ACT2000_BUS_ISA:
+			act2000_isa_receive(card);
+			break;
+		case ACT2000_BUS_PCMCIA:
+		case ACT2000_BUS_MCA:
+		default:
+			printk(KERN_WARNING
+			       "act2000_receive: Illegal bustype %d\n", card->bus);
+	}
+}
+
+static void
+act2000_poll(unsigned long data)
+{
+	act2000_card * card = (act2000_card *)data;
+	unsigned long flags;
+
+	act2000_receive(card);
+	spin_lock_irqsave(&card->lock, flags);
+	mod_timer(&card->ptimer, jiffies+3);
+	spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static int
+act2000_command(act2000_card * card, isdn_ctrl * c)
+{
+        ulong a;
+        act2000_chan *chan;
+	act2000_cdef cdef;
+	isdn_ctrl cmd;
+	char tmp[17];
+	int ret;
+	unsigned long flags;
+	void __user *arg;
+ 
+        switch (c->command) {
+		case ISDN_CMD_IOCTL:
+			memcpy(&a, c->parm.num, sizeof(ulong));
+			arg = (void __user *)a;
+			switch (c->arg) {
+				case ACT2000_IOCTL_LOADBOOT:
+					switch (card->bus) {
+						case ACT2000_BUS_ISA:
+							ret = act2000_isa_download(card,
+									   arg);
+							if (!ret) {
+								card->flags |= ACT2000_FLAGS_LOADED;
+								if (!(card->flags & ACT2000_FLAGS_IVALID)) {
+									card->ptimer.expires = jiffies + 3;
+									card->ptimer.function = act2000_poll;
+									card->ptimer.data = (unsigned long)card;
+									add_timer(&card->ptimer);
+								}
+								actcapi_manufacturer_req_errh(card);
+							}
+							break;
+						default:
+							printk(KERN_WARNING
+							       "act2000: Illegal BUS type %d\n",
+							       card->bus);
+							ret = -EIO;
+					}
+					return ret;
+				case ACT2000_IOCTL_SETPROTO:
+					card->ptype = a?ISDN_PTYPE_EURO:ISDN_PTYPE_1TR6;
+					if (!(card->flags & ACT2000_FLAGS_RUNNING))
+						return 0;
+					actcapi_manufacturer_req_net(card);
+					return 0;
+				case ACT2000_IOCTL_SETMSN:
+					if (copy_from_user(tmp, arg,
+							   sizeof(tmp)))
+						return -EFAULT;
+					if ((ret = act2000_set_msn(card, tmp)))
+						return ret;
+					if (card->flags & ACT2000_FLAGS_RUNNING)
+						return(actcapi_manufacturer_req_msn(card));
+					return 0;
+				case ACT2000_IOCTL_ADDCARD:
+					if (copy_from_user(&cdef, arg,
+							   sizeof(cdef)))
+						return -EFAULT;
+					if (act2000_addcard(cdef.bus, cdef.port, cdef.irq, cdef.id))
+						return -EIO;
+					return 0;
+				case ACT2000_IOCTL_TEST:
+					if (!(card->flags & ACT2000_FLAGS_RUNNING))
+						return -ENODEV;
+					return 0;
+				default:
+					return -EINVAL;
+			}
+			break;
+		case ISDN_CMD_DIAL:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			spin_lock_irqsave(&card->lock, flags);
+			if (chan->fsm_state != ACT2000_STATE_NULL) {
+				spin_unlock_irqrestore(&card->lock, flags);
+				printk(KERN_WARNING "Dial on channel with state %d\n",
+					chan->fsm_state);
+				return -EBUSY;
+			}
+			if (card->ptype == ISDN_PTYPE_EURO)
+				tmp[0] = act2000_find_msn(card, c->parm.setup.eazmsn, 1);
+			else
+				tmp[0] = c->parm.setup.eazmsn[0];
+			chan->fsm_state = ACT2000_STATE_OCALL;
+			chan->callref = 0xffff;
+			spin_unlock_irqrestore(&card->lock, flags);
+			ret = actcapi_connect_req(card, chan, c->parm.setup.phone,
+						  tmp[0], c->parm.setup.si1,
+						  c->parm.setup.si2);
+			if (ret) {
+				cmd.driver = card->myid;
+				cmd.command = ISDN_STAT_DHUP;
+				cmd.arg &= 0x0f;
+				card->interface.statcallb(&cmd);
+			}
+			return ret;
+		case ISDN_CMD_ACCEPTD:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			if (chan->fsm_state == ACT2000_STATE_ICALL)
+				actcapi_select_b2_protocol_req(card, chan);
+			return 0;
+		case ISDN_CMD_ACCEPTB:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			return 0;
+		case ISDN_CMD_HANGUP:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			switch (chan->fsm_state) {
+				case ACT2000_STATE_ICALL:
+				case ACT2000_STATE_BSETUP:
+					actcapi_connect_resp(card, chan, 0x15);
+					break;
+				case ACT2000_STATE_ACTIVE:
+					actcapi_disconnect_b3_req(card, chan);
+					break;
+			}
+			return 0;
+		case ISDN_CMD_SETEAZ:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			if (strlen(c->parm.num)) {
+				if (card->ptype == ISDN_PTYPE_EURO) {
+					chan->eazmask = act2000_find_msn(card, c->parm.num, 0);
+				}
+				if (card->ptype == ISDN_PTYPE_1TR6) {
+					int i;
+					chan->eazmask = 0;
+					for (i = 0; i < strlen(c->parm.num); i++)
+						if (isdigit(c->parm.num[i]))
+							chan->eazmask |= (1 << (c->parm.num[i] - '0'));
+				}
+			} else
+				chan->eazmask = 0x3ff;
+			actcapi_listen_req(card);
+			return 0;
+		case ISDN_CMD_CLREAZ:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			chan->eazmask = 0;
+			actcapi_listen_req(card);
+			return 0;
+		case ISDN_CMD_SETL2:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			chan->l2prot = (c->arg >> 8);
+			return 0;
+		case ISDN_CMD_SETL3:
+			if (!card->flags & ACT2000_FLAGS_RUNNING)
+				return -ENODEV;
+			if ((c->arg >> 8) != ISDN_PROTO_L3_TRANS) {
+				printk(KERN_WARNING "L3 protocol unknown\n");
+				return -1;
+			}
+			if (!(chan = find_channel(card, c->arg & 0x0f)))
+				break;
+			chan->l3prot = (c->arg >> 8);
+			return 0;
+        }
+	
+        return -EINVAL;
+}
+
+static int
+act2000_sendbuf(act2000_card *card, int channel, int ack, struct sk_buff *skb)
+{
+        struct sk_buff *xmit_skb;
+        int len;
+        act2000_chan *chan;
+	actcapi_msg *msg;
+
+        if (!(chan = find_channel(card, channel)))
+		return -1;
+        if (chan->fsm_state != ACT2000_STATE_ACTIVE)
+                return -1;
+        len = skb->len;
+        if ((chan->queued + len) >= ACT2000_MAX_QUEUED)
+                return 0;
+	if (!len)
+		return 0;
+	if (skb_headroom(skb) < 19) {
+		printk(KERN_WARNING "act2000_sendbuf: Headroom only %d\n",
+		       skb_headroom(skb));
+		xmit_skb = alloc_skb(len + 19, GFP_ATOMIC);
+		if (!xmit_skb) {
+			printk(KERN_WARNING "act2000_sendbuf: Out of memory\n");
+			return 0;
+		}
+		skb_reserve(xmit_skb, 19);
+		memcpy(skb_put(xmit_skb, len), skb->data, len);
+	} else {
+		xmit_skb = skb_clone(skb, GFP_ATOMIC);
+		if (!xmit_skb) {
+			printk(KERN_WARNING "act2000_sendbuf: Out of memory\n");
+			return 0;
+		}
+	}
+	dev_kfree_skb(skb);
+	msg = (actcapi_msg *)skb_push(xmit_skb, 19);
+	msg->hdr.len = 19 + len;
+	msg->hdr.applicationID = 1;
+	msg->hdr.cmd.cmd = 0x86;
+	msg->hdr.cmd.subcmd = 0x00;
+	msg->hdr.msgnum = actcapi_nextsmsg(card);
+	msg->msg.data_b3_req.datalen = len;
+	msg->msg.data_b3_req.blocknr = (msg->hdr.msgnum & 0xff);
+	msg->msg.data_b3_req.fakencci = MAKE_NCCI(chan->plci, 0, chan->ncci);
+	msg->msg.data_b3_req.flags = ack; /* Will be set to 0 on actual sending */
+	actcapi_debug_msg(xmit_skb, 1);
+        chan->queued += len;
+	skb_queue_tail(&card->sndq, xmit_skb);
+	act2000_schedule_tx(card);
+        return len;
+}
+
+
+/* Read the Status-replies from the Interface */
+static int
+act2000_readstatus(u_char __user * buf, int len, act2000_card * card)
+{
+        int count;
+        u_char __user *p;
+
+        for (p = buf, count = 0; count < len; p++, count++) {
+                if (card->status_buf_read == card->status_buf_write)
+                        return count;
+		put_user(*card->status_buf_read++, p);
+                if (card->status_buf_read > card->status_buf_end)
+                        card->status_buf_read = card->status_buf;
+        }
+        return count;
+}
+
+/*
+ * Find card with given driverId
+ */
+static inline act2000_card *
+act2000_findcard(int driverid)
+{
+        act2000_card *p = cards;
+
+        while (p) {
+                if (p->myid == driverid)
+                        return p;
+                p = p->next;
+        }
+        return (act2000_card *) 0;
+}
+
+/*
+ * Wrapper functions for interface to linklevel
+ */
+static int
+if_command(isdn_ctrl * c)
+{
+        act2000_card *card = act2000_findcard(c->driver);
+
+        if (card)
+                return (act2000_command(card, c));
+        printk(KERN_ERR
+             "act2000: if_command %d called with invalid driverId %d!\n",
+               c->command, c->driver);
+        return -ENODEV;
+}
+
+static int
+if_writecmd(const u_char __user *buf, int len, int id, int channel)
+{
+        act2000_card *card = act2000_findcard(id);
+
+        if (card) {
+                if (!card->flags & ACT2000_FLAGS_RUNNING)
+                        return -ENODEV;
+                return (len);
+        }
+        printk(KERN_ERR
+               "act2000: if_writecmd called with invalid driverId!\n");
+        return -ENODEV;
+}
+
+static int
+if_readstatus(u_char __user * buf, int len, int id, int channel)
+{
+        act2000_card *card = act2000_findcard(id);
+	
+        if (card) {
+                if (!card->flags & ACT2000_FLAGS_RUNNING)
+                        return -ENODEV;
+                return (act2000_readstatus(buf, len, card));
+        }
+        printk(KERN_ERR
+               "act2000: if_readstatus called with invalid driverId!\n");
+        return -ENODEV;
+}
+
+static int
+if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)
+{
+        act2000_card *card = act2000_findcard(id);
+	
+        if (card) {
+                if (!card->flags & ACT2000_FLAGS_RUNNING)
+                        return -ENODEV;
+		return (act2000_sendbuf(card, channel, ack, skb));
+        }
+        printk(KERN_ERR
+               "act2000: if_sendbuf called with invalid driverId!\n");
+        return -ENODEV;
+}
+
+
+/*
+ * Allocate a new card-struct, initialize it
+ * link it into cards-list.
+ */
+static void
+act2000_alloccard(int bus, int port, int irq, char *id)
+{
+	int i;
+        act2000_card *card;
+        if (!(card = (act2000_card *) kmalloc(sizeof(act2000_card), GFP_KERNEL))) {
+                printk(KERN_WARNING
+		       "act2000: (%s) Could not allocate card-struct.\n", id);
+                return;
+        }
+        memset((char *) card, 0, sizeof(act2000_card));
+        spin_lock_init(&card->lock);
+        spin_lock_init(&card->mnlock);
+	skb_queue_head_init(&card->sndq);
+	skb_queue_head_init(&card->rcvq);
+	skb_queue_head_init(&card->ackq);
+	INIT_WORK(&card->snd_tq, (void *) (void *) act2000_transmit, card);
+	INIT_WORK(&card->rcv_tq, (void *) (void *) actcapi_dispatch, card);
+	INIT_WORK(&card->poll_tq, (void *) (void *) act2000_receive, card);
+	init_timer(&card->ptimer);
+	card->interface.owner = THIS_MODULE;
+        card->interface.channels = ACT2000_BCH;
+        card->interface.maxbufsize = 4000;
+        card->interface.command = if_command;
+        card->interface.writebuf_skb = if_sendbuf;
+        card->interface.writecmd = if_writecmd;
+        card->interface.readstat = if_readstatus;
+        card->interface.features =
+		ISDN_FEATURE_L2_X75I |
+		ISDN_FEATURE_L2_HDLC |
+		ISDN_FEATURE_L3_TRANS |
+		ISDN_FEATURE_P_UNKNOWN;
+        card->interface.hl_hdrlen = 20;
+        card->ptype = ISDN_PTYPE_EURO;
+        strlcpy(card->interface.id, id, sizeof(card->interface.id));
+        for (i=0; i<ACT2000_BCH; i++) {
+                card->bch[i].plci = 0x8000;
+                card->bch[i].ncci = 0x8000;
+                card->bch[i].l2prot = ISDN_PROTO_L2_X75I;
+                card->bch[i].l3prot = ISDN_PROTO_L3_TRANS;
+        }
+        card->myid = -1;
+        card->bus = bus;
+        card->port = port;
+        card->irq = irq;
+        card->next = cards;
+        cards = card;
+}
+
+/*
+ * register card at linklevel
+ */
+static int
+act2000_registercard(act2000_card * card)
+{
+        switch (card->bus) {
+		case ACT2000_BUS_ISA:
+			break;
+		case ACT2000_BUS_MCA:
+		case ACT2000_BUS_PCMCIA:
+		default:
+			printk(KERN_WARNING
+			       "act2000: Illegal BUS type %d\n",
+			       card->bus);
+			return -1;
+        }
+        if (!register_isdn(&card->interface)) {
+                printk(KERN_WARNING
+                       "act2000: Unable to register %s\n",
+                       card->interface.id);
+                return -1;
+        }
+        card->myid = card->interface.channels;
+        sprintf(card->regname, "act2000-isdn (%s)", card->interface.id);
+        return 0;
+}
+
+static void
+unregister_card(act2000_card * card)
+{
+        isdn_ctrl cmd;
+
+        cmd.command = ISDN_STAT_UNLOAD;
+        cmd.driver = card->myid;
+        card->interface.statcallb(&cmd);
+        switch (card->bus) {
+		case ACT2000_BUS_ISA:
+			act2000_isa_release(card);
+			break;
+		case ACT2000_BUS_MCA:
+		case ACT2000_BUS_PCMCIA:
+		default:
+			printk(KERN_WARNING
+			       "act2000: Invalid BUS type %d\n",
+			       card->bus);
+			break;
+        }
+}
+
+static int
+act2000_addcard(int bus, int port, int irq, char *id)
+{
+	act2000_card *p;
+	act2000_card *q = NULL;
+	int initialized;
+	int added = 0;
+	int failed = 0;
+	int i;
+
+	if (!bus)
+		bus = ACT2000_BUS_ISA;
+	if (port != -1) {
+		/* Port defined, do fixed setup */
+		act2000_alloccard(bus, port, irq, id);
+	} else {
+		/* No port defined, perform autoprobing.
+		 * This may result in more than one card detected.
+		 */
+		switch (bus) {
+			case ACT2000_BUS_ISA:
+				for (i = 0; i < ISA_NRPORTS; i++)
+					if (act2000_isa_detect(act2000_isa_ports[i])) {
+						printk(KERN_INFO
+						       "act2000: Detected ISA card at port 0x%x\n",
+						       act2000_isa_ports[i]);
+						act2000_alloccard(bus, act2000_isa_ports[i], irq, id);
+					}
+				break;
+			case ACT2000_BUS_MCA:
+			case ACT2000_BUS_PCMCIA:
+			default:
+				printk(KERN_WARNING
+				       "act2000: addcard: Invalid BUS type %d\n",
+				       bus);
+		}
+	}
+	if (!cards)
+		return 1;
+        p = cards;
+        while (p) {
+		initialized = 0;
+		if (!p->interface.statcallb) {
+			/* Not yet registered.
+			 * Try to register and activate it.
+			 */
+			added++;
+			switch (p->bus) {
+				case ACT2000_BUS_ISA:
+					if (act2000_isa_detect(p->port)) {
+						if (act2000_registercard(p))
+							break;
+						if (act2000_isa_config_port(p, p->port)) {
+							printk(KERN_WARNING
+							       "act2000: Could not request port 0x%04x\n",
+							       p->port);
+							unregister_card(p);
+							p->interface.statcallb = NULL;
+							break;
+						}
+						if (act2000_isa_config_irq(p, p->irq)) {
+							printk(KERN_INFO
+							       "act2000: No IRQ available, fallback to polling\n");
+							/* Fall back to polled operation */
+							p->irq = 0;
+						}
+						printk(KERN_INFO
+						       "act2000: ISA"
+						       "-type card at port "
+						       "0x%04x ",
+						       p->port);
+						if (p->irq)
+							printk("irq %d\n", p->irq);
+						else
+							printk("polled\n");
+						initialized = 1;
+					}
+					break;
+				case ACT2000_BUS_MCA:
+				case ACT2000_BUS_PCMCIA:
+				default:
+					printk(KERN_WARNING
+					       "act2000: addcard: Invalid BUS type %d\n",
+					       p->bus);
+			}
+		} else
+			/* Card already initialized */
+			initialized = 1;
+                if (initialized) {
+			/* Init OK, next card ... */
+                        q = p;
+                        p = p->next;
+                } else {
+                        /* Init failed, remove card from list, free memory */
+                        printk(KERN_WARNING
+                               "act2000: Initialization of %s failed\n",
+                               p->interface.id);
+                        if (q) {
+                                q->next = p->next;
+                                kfree(p);
+                                p = q->next;
+                        } else {
+                                cards = p->next;
+                                kfree(p);
+                                p = cards;
+                        }
+			failed++;
+                }
+	}
+        return (added - failed);
+}
+
+#define DRIVERNAME "IBM Active 2000 ISDN driver"
+
+static int __init act2000_init(void)
+{
+        printk(KERN_INFO "%s\n", DRIVERNAME);
+        if (!cards)
+		act2000_addcard(act_bus, act_port, act_irq, act_id);
+        if (!cards)
+                printk(KERN_INFO "act2000: No cards defined yet\n");
+        return 0;
+}
+
+static void __exit act2000_exit(void)
+{
+        act2000_card *card = cards;
+        act2000_card *last;
+        while (card) {
+                unregister_card(card);
+		del_timer(&card->ptimer);
+                card = card->next;
+        }
+        card = cards;
+        while (card) {
+                last = card;
+                card = card->next;
+		act2000_clear_msn(last);
+                kfree(last);
+        }
+        printk(KERN_INFO "%s unloaded\n", DRIVERNAME);
+}
+
+module_init(act2000_init);
+module_exit(act2000_exit);