Subject: dahdi-extra: out-of-tree DAHDI drivers
Origin: http://gitorious.org/dahdi-extra/dahdi-linux-extra
Forwarded: No
Last-Update: 2012-03-06
 
This patch includes a number of out-of-tree DAHDI drivers from the
dahdi-extra repository. They are all out-of-tree and are highly likely
not to be included in DAHDI-linux in the forseeable future.
 
Git-Commit: f1fc13f5aad78e7d110f34d61b5a60dc83c119ff
Dahdi: branches/2.6@10442
---
diff --git a/drivers/dahdi/Kbuild b/drivers/dahdi/Kbuild
index 8bbfea0..9fed863 100644
--- a/drivers/dahdi/Kbuild
+++ b/drivers/dahdi/Kbuild
@@ -31,6 +31,14 @@ obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ECHOCAN_STEVE2)	+= dahdi_echocan_sec2.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ECHOCAN_KB1)	+= dahdi_echocan_kb1.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ECHOCAN_MG2)	+= dahdi_echocan_mg2.o
 
+ifdef CONFIG_PCI
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_AP400)		+= ap400/
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_OPVXA1200)		+= opvxa1200/
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_OPVXD115)		+= opvxd115/
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCOPENPCI)		+= wcopenpci.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_ZAPHFC)		+= zaphfc/
+endif
+
 obj-m += $(DAHDI_MODULES_EXTRA)
 
 # If you want to build OSLEC, include the code in the standard location:
diff --git a/drivers/dahdi/Kconfig b/drivers/dahdi/Kconfig
index 6952c6a..4275775 100644
--- a/drivers/dahdi/Kconfig
+++ b/drivers/dahdi/Kconfig
@@ -292,3 +292,89 @@ config DAHDI_WCTE11XP
 	  If unsure, say Y.
 
 source "drivers/dahdi/xpp/Kconfig"
+
+config DAHDI_AP4XX
+	tristate "Aligera AP4XX PCI Card Driver"
+	depends on DAHDI && PCI
+	default DAHDI
+	---help---
+	  This driver provides support for the Aligera AP400 quad-span
+	  E1/T1 DAHDI cards:
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ap4xx.
+
+	  If unsure, say Y.
+
+config DAHDI_OPVXA1200
+	tristate "OpenVox 8/12 ports analog card Support"
+	depends on DAHDI && PCI
+	default DAHDI
+	---help---
+	  This driver provides support for the following OpenVox
+	  Wildcard products:
+
+	  * A1200P (PCI)
+	  * A1200E (PCI-E)
+	  * A800P (PCI)
+	  * A800E (PCI-E)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called opvxa1200.
+
+	  If unsure, say Y.
+
+config DAHDI_OPVXD115
+	tristate "OpenVox Single-T1/E1/J1 Support"
+	depends on DAHDI && PCI
+	default DAHDI
+	---help---
+	  This driver provides support for the following OpenVox
+	  Wildcard products:
+
+	  * D115P/DE115P/D130P/DE130P (PCI)
+	  * D115E/DE115E/D130E/DE130E (PCI-E)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called opvxd115.
+
+	  If unsure, say Y.
+
+
+config DAHDI_WCOPENPCI
+	tristate "Voicetronix OpenPCI Interface DAHDI driver"
+	depends on DAHDI && PCI
+	default DAHDI
+	---help---
+	  This driver provides support for the Voicetronix OpenPCI Interface.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wcopenpci.
+
+	  If unsure, say Y.
+
+config DAHDI_ZAPHFC
+	tristate "HFC-S DAHDI Driver"
+	depends on DAHDI && PCI
+	default DAHDI
+	---help---
+	  This driver provides DAHDI support for various HFC-S single-port
+          ISDN (BRI) cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called zaphfc.
+
+	  If unsure, say Y.
+
+config ECHO
+	tristate "Line Echo Canceller support"
+	default DAHDI
+	--help--
+	  This driver provides line echo cancelling support for mISDN and
+	  DAHDI drivers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called echo.
+
+	  If unsure, say Y.
+
diff --git a/drivers/dahdi/ap400/Kbuild b/drivers/dahdi/ap400/Kbuild
new file mode 100644
index 0000000..d124261
--- /dev/null
+++ b/drivers/dahdi/ap400/Kbuild
@@ -0,0 +1,26 @@
+obj-m += ap400.o
+
+EXTRA_CFLAGS := -I$(src)/.. 
+
+ap400-objs := ap400_drv.o
+
+# APEC_SUPPORT
+ECHO_FIRMWARE := $(wildcard $(src)/OCT61*.ima)
+ifneq ($(strip $(ECHO_FIRMWARE)),)
+	EXTRA_CFLAGS+=-DAPEC_SUPPORT $(shell $(src)/../oct612x/octasic-helper cflags $(src)/../oct612x) -Wno-undef
+	ap400-objs += apec.o $(shell $(src)/../oct612x/octasic-helper objects ../oct612x) firmware_oct6104e-64d.o firmware_oct6104e-128d.o
+endif
+
+$(obj)/apec.o: $(src)/apec.h $(src)/../oct612x/include/oct6100api/oct6100_api.h
+
+$(obj)/firmware_oct6104e-64d.o: $(src)/OCT6104E-64D.ima $(obj)/ap400_drv.o $(src)/../firmware/make_firmware_object
+	@echo Making firmware object file for $(notdir $<)
+	@cd $(src) && ../firmware/make_firmware_object $(notdir $<) $@ $(obj)/ap400_drv.o
+
+$(obj)/firmware_oct6104e-128d.o: $(src)/OCT6104E-128D.ima $(obj)/ap400_drv.o $(src)/../firmware/make_firmware_object
+	@echo Making firmware object file for $(notdir $<)
+	@cd $(src) && ../firmware/make_firmware_object $(notdir $<) $@ $(obj)/ap400_drv.o
+
+$(src)/../firmware/make_firmware_object:
+	make -C $(src)/../firmware make_firmware_object
+
diff --git a/drivers/dahdi/ap400/ap400.h b/drivers/dahdi/ap400/ap400.h
new file mode 100644
index 0000000..ba233d1
--- /dev/null
+++ b/drivers/dahdi/ap400/ap400.h
@@ -0,0 +1,107 @@
+/*
+ * AP4XX  T1/E1 PCI Driver
+ *
+ * Written by Ronaldo Valiati <aligera@aligera.com.br>
+ *
+ * Based on previous works, designs, and archetectures conceived and
+ * written by Jim Dixon <jim@lambdatel.com> and Mark Spencer <markster@digium.com>.
+ *
+ * Copyright (C) 2001 Jim Dixon / Zapata Telephony.
+ * Copyright (C) 2001-2005, Digium, Inc.
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/ioctl.h>
+
+
+#define AP4_GET_ALARMS  _IOW (DAHDI_CODE, 60, int)
+#define AP4_GET_SLIPS	_IOW (DAHDI_CODE, 61, int)
+
+#define AP4XX_CARD_ID		0x41434532		// "ACE2"
+#define APE4XX_CARD_ID		0x41504534		// "APE4"
+
+#define AP_CAS_BASE		0x0080
+#define AP_DATA_BASE		0x0100
+
+#define AP_CARD_TYPE_REG	0x0001
+#define AP_T1E1_CONFIG_REG	0x0003
+#define AP_E1_CONFIG_REG	0x0004
+#define AP_E1_STATUS_REG	0x0005
+#define AP_LEDS_REG		0x0006
+#define AP_CLKSRC_REG		0x0007
+#define AP_HWCONFIG_REG		0x0008
+#define AP_INT_CONTROL_REG	0x0009
+#define AP_CNT_IRQ_REG		0x000B
+#define AP_CNT_CV_REG		0x000C
+#define AP_CNT_CRC_REG		0x000D
+#define AP_CLEAR_IRQ_REG	0x000E
+#define AP_CNT_SLIP_REG		0x000F
+
+#define AP_HWID_MASK		0x00F0
+
+#define AP_CLKSRC_MASK		0x07
+
+#define AP_LIU1_LINECODE	0x0080
+#define AP_LIU2_LINECODE	0x0100
+#define AP_LIU_RESET_BIT	0x0200
+
+#define AP_E1_AIS_STATUS	0x01
+#define AP_E1_BFAE_STATUS	0x02
+#define AP_E1_MFAE_STATUS	0x04
+#define AP_E1_SYNC_STATUS	0x08
+#define AP_E1_CAS_STATUS	0x10
+#define AP_E1_LOS_STATUS	0x20
+#define AP_E1_RAI_STATUS	0x40
+
+#define AP_E1_RAI_CONFIG	0x01
+#define AP_E1_LOOP_CONFIG	0x10
+#define AP_E1_CASEN_CONFIG	0x20
+#define AP_E1_PCM30_CONFIG	0x40
+#define AP_E1_CRCEN_CONFIG	0x80
+
+#define AP_INT_CTL_ENABLE	0x01
+#define AP_INT_CTL_ACTIVE	0x02
+
+#define AP_HWID_1E1_RJ		0x01
+#define AP_HWID_2E1_RJ		0x00
+#define AP_HWID_4E1_RJ		0x02
+#define AP_HWID_T1		0x04
+
+#define AP4_T1_NE1_SEL		0x04
+#define AP4_T1_ESF_NSF		0x02
+#define AP4_T1_CAS_ENABLE	0x01
+
+#define AP4_T1_FRAME_SYNC	0x01
+
+
+typedef enum {
+	AP_PULS_E1_75 = 0,
+	AP_PULS_E1_120,
+	AP_PULS_DSX1_0FT,
+	AP_PULS_DSX1_133FT,
+	AP_PULS_DSX1_266FT,
+	AP_PULS_DSX1_399FT,
+	AP_PULS_DSX1_533FT,
+	AP_PULS_J1_110,
+	AP_PULS_DS1_0DB,
+	AP_PULS_DS1_M075DB,
+	AP_PULS_DS1_M150DB,
+	AP_PULS_DS1_M225DB
+} liu_mode;
+
diff --git a/drivers/dahdi/ap400/ap400_drv.c b/drivers/dahdi/ap400/ap400_drv.c
new file mode 100644
index 0000000..24c3807
--- /dev/null
+++ b/drivers/dahdi/ap400/ap400_drv.c
@@ -0,0 +1,2337 @@
+/*
+ * AP4XX PCI Card Driver
+ *
+ * Written by Ronaldo Valiati <aligera@aligera.com.br>
+ *
+ * Based on previous works, designs, and architectures conceived and
+ * written by Jim Dixon <jim@lambdatel.com> and Mark Spencer <markster@digium.com>.
+ *
+ * Copyright (C) 2001 Jim Dixon / Zapata Telephony.
+ * Copyright (C) 2001-2005, Digium, Inc.
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <dahdi/kernel.h>
+#include <linux/moduleparam.h>
+
+#include "ap400.h"
+
+//#define AP400_DEBUG
+#ifdef AP400_DEBUG
+#define PDEBUG(fmt, args...) { \
+	printk(KERN_DEBUG "AP400 (%d): ",__LINE__); \
+	printk(fmt "\n", ## args); \
+}
+#else
+#define PDEBUG(fmt, args...)
+#endif
+
+/*
+ * Tasklets provide better system interactive response at the cost of the
+ * possibility of losing a frame of data at very infrequent intervals.  If
+ * you are more concerned with the performance of your machine, enable the
+ * tasklets.  If you are strict about absolutely no drops, then do not enable
+ * tasklets.
+ */
+
+/* #define ENABLE_TASKLETS */
+
+
+/* Work queues are a way to better distribute load on SMP systems */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+/*
+ * Work queues can significantly improve performance and scalability
+ * on multi-processor machines, but requires bypassing some kernel
+ * API's, so it's not guaranteed to be compatible with all kernels.
+ */
+/* #define ENABLE_WORKQUEUES */
+#endif
+
+/* Enable HDLC support by hardware */
+#ifdef AP400_HDLC
+#include "ap400_hdlc/ap400_hdlc.c"
+#endif
+
+//#define APEC_SUPPORT
+#ifdef APEC_SUPPORT
+#include "apec.h"
+#endif
+
+/* Workarounds */
+#ifndef IRQF_SHARED
+#define IRQF_SHARED		SA_SHIRQ
+#endif
+#ifndef IRQF_DISABLED
+#define IRQF_DISABLED		SA_INTERRUPT
+#endif
+#ifndef __iomem
+#define __iomem
+#endif
+
+/* Enable prefetching may help performance */
+#define ENABLE_PREFETCH
+
+/* Define to get more attention-grabbing but slightly more I/O using
+   alarm status */
+#define FANCY_ALARM
+
+#define DEBUG_MAIN 		(1 << 0)
+#define DEBUG_DTMF 		(1 << 1)
+#define DEBUG_REGS 		(1 << 2)
+#define DEBUG_TSI  		(1 << 3)
+#define DEBUG_ECHOCAN 		(1 << 4)
+#define DEBUG_RBS 		(1 << 5)
+#define DEBUG_FRAMER		(1 << 6)
+
+static int clock_source = -1;
+static int tdm_loop = 0;
+static int apec_enable = 1;
+module_param(tdm_loop, int, 0600);
+module_param(apec_enable, int, 0600);
+
+#ifdef ENABLE_WORKQUEUES
+#include <linux/cpumask.h>
+
+/* XXX UGLY!!!! XXX  We have to access the direct structures of the workqueue which
+  are only defined within workqueue.c because they don't give us a routine to allow us
+  to nail a work to a particular thread of the CPU.  Nailing to threads gives us substantially
+  higher scalability in multi-CPU environments though! */
+
+/*
+ * The per-CPU workqueue (if single thread, we always use cpu 0's).
+ *
+ * The sequence counters are for flush_scheduled_work().  It wants to wait
+ * until until all currently-scheduled works are completed, but it doesn't
+ * want to be livelocked by new, incoming ones.  So it waits until
+ * remove_sequence is >= the insert_sequence which pertained when
+ * flush_scheduled_work() was called.
+ */
+
+struct cpu_workqueue_struct {
+
+	spinlock_t lock;
+
+	long remove_sequence;	/* Least-recently added (next to run) */
+	long insert_sequence;	/* Next to add */
+
+	struct list_head worklist;
+	wait_queue_head_t more_work;
+	wait_queue_head_t work_done;
+
+	struct workqueue_struct *wq;
+	task_t *thread;
+
+	int run_depth;		/* Detect run_workqueue() recursion depth */
+} ____cacheline_aligned;
+
+/*
+ * The externally visible workqueue abstraction is an array of
+ * per-CPU workqueues:
+ */
+struct workqueue_struct {
+	struct cpu_workqueue_struct cpu_wq[NR_CPUS];
+	const char *name;
+	struct list_head list; 	/* Empty if single thread */
+};
+
+/* Preempt must be disabled. */
+static void __ap4_queue_work(struct cpu_workqueue_struct *cwq,
+			 struct work_struct *work)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&cwq->lock, flags);
+	work->wq_data = cwq;
+	list_add_tail(&work->entry, &cwq->worklist);
+	cwq->insert_sequence++;
+	wake_up(&cwq->more_work);
+	spin_unlock_irqrestore(&cwq->lock, flags);
+}
+
+/*
+ * Queue work on a workqueue. Return non-zero if it was successfully
+ * added.
+ *
+ * We queue the work to the CPU it was submitted, but there is no
+ * guarantee that it will be processed by that CPU.
+ */
+static inline int ap4_queue_work(struct workqueue_struct *wq, struct work_struct *work, int cpu)
+{
+	int ret = 0;
+
+	if (!test_and_set_bit(0, &work->pending)) {
+		BUG_ON(!list_empty(&work->entry));
+		__ap4_queue_work(wq->cpu_wq + cpu, work);
+		ret = 1;
+	}
+	return ret;
+}
+
+#endif
+
+static int debug=0;
+static int timingcable;
+static int highestorder;
+static int t1e1override = -1;
+static int j1mode = 0;
+static int loopback = 0;
+static int alarmdebounce = 0;
+
+/* Enabling bursting can more efficiently utilize PCI bus bandwidth, but
+   can also cause PCI bus starvation, especially in combination with other
+   aggressive cards.  Please note that burst mode has no effect on CPU
+   utilization / max number of calls / etc. */
+static int noburst = 1;
+static int debugslips = 0;
+static int polling = 0;
+
+#ifdef FANCY_ALARM
+static int altab[] = {
+0, 0, 0, 1, 2, 3, 4, 6, 8, 9, 11, 13, 16, 18, 20, 22, 24, 25, 27, 28, 29, 30, 31, 31, 32, 31, 31, 30, 29, 28, 27, 25, 23, 22, 20, 18, 16, 13, 11, 9, 8, 6, 4, 3, 2, 1, 0, 0,
+};
+#endif
+
+#define FLAG_STARTED (1 << 0)
+#define FLAG_NMF (1 << 1)
+#define FLAG_SENDINGYELLOW (1 << 2)
+
+#define	TYPE_T1	1		/* is a T1 card */
+#define	TYPE_E1	2		/* is an E1 card */
+#define TYPE_J1 3		/* is a running J1 */
+
+struct devtype {
+	char *desc;
+	unsigned int flags;
+};
+
+static struct devtype ap401  = { "Aligera AP401", 0 };
+static struct devtype ap402  = { "Aligera AP402", 0 };
+static struct devtype ap404  = { "Aligera AP404", 0 };
+static struct devtype ape401  = { "Aligera APE401", 0 };
+static struct devtype ape402  = { "Aligera APE402", 0 };
+static struct devtype ape404  = { "Aligera APE404", 0 };
+
+struct ap4;
+
+struct ap4_span {
+	struct ap4 *owner;
+	unsigned int *writechunk;					/* Double-word aligned write memory */
+	unsigned int *readchunk;					/* Double-word aligned read memory */
+	int spantype;		/* card type, T1 or E1 or J1 */
+	int sync;
+	int psync;
+	int alarmtimer;
+	int redalarms;
+	int notclear;
+	int alarmcount;
+	int spanflags;
+	int syncpos;
+	int e1check;			/* E1 check */
+	int reload_cas;
+	unsigned char casbuf[15];
+	unsigned int slipcount;
+	struct dahdi_span span;
+	unsigned char txsigs[16];	/* Transmit sigs */
+	int loopupcnt;
+	int loopdowncnt;
+	unsigned char ec_chunk1[31][DAHDI_CHUNKSIZE]; /* first EC chunk buffer */
+	unsigned char ec_chunk2[31][DAHDI_CHUNKSIZE]; /* second EC chunk buffer */
+	int irqmisses;
+#ifdef ENABLE_WORKQUEUES
+	struct work_struct swork;
+#endif
+	struct dahdi_chan *chans[32];		/* Individual channels */
+};
+
+struct ap4_regs {
+	volatile u32 card_id;		// 00h R0
+	volatile u16 fpga_ver;		// 04h R1
+	volatile u16 span_num;		// 06h R1
+	u32 __unused;			// 08h R2
+	volatile u32 liu_config;	// 0Ch R3
+	volatile u32 e1_config;		// 10h R4
+	volatile u32 e1_status;		// 14h R5
+	volatile u32 leds;		// 18h R6
+	volatile u32 clock_source;	// 1Ch R7
+	u32 __unused3[8];		// 20h - 3Ch R8 - R15
+	volatile u32 echo_ctrl;		// 40h R16
+	volatile u32 echo_data;		// 44h R17
+	volatile u32 t1_status;		// 48h R18
+	volatile u32 t1_config;		// 4Ch R19
+};
+
+struct ap4 {
+	/* This structure exists one per card */
+	struct pci_dev *dev;		/* Pointer to PCI device */
+	struct dahdi_device *ddev;
+	struct ap4_regs *hw_regs;
+	unsigned int intcount;
+	int flag_1st_irq;
+	int num;			/* Which card we are */
+	int fpgaver;		/* version of FPGA */
+	int hwid;			/* hardware ID */
+	int globalconfig;	/* Whether global setup has been done */
+	int syncsrc;			/* active sync source */
+	struct ap4_span *tspans[4];	/* Individual spans */
+	int numspans;			/* Number of spans on the card */
+	int blinktimer[4];
+#ifdef FANCY_ALARM
+	int alarmpos[4];
+#endif
+	int irq;			/* IRQ used by device */
+	int order;			/* Order */
+	int flags;			/* Device flags */
+	int ledreg;				/* LED Register */
+	int e1recover;			/* E1 recovery timer */
+	unsigned long memaddr;		/* Base address of card */
+	unsigned long memlen;
+	volatile unsigned int *membase;	/* Base address of card */
+	int spansstarted;		/* number of spans started */
+	/* spinlock_t lock; */		/* lock context */
+	spinlock_t reglock;		/* lock register access */
+	volatile unsigned int *writechunk;	/* Double-word aligned write memory */
+	volatile unsigned int *readchunk;	/* Double-word aligned read memory */
+#ifdef ENABLE_WORKQUEUES
+	atomic_t worklist;
+	struct workqueue_struct *workq;
+#else
+#ifdef ENABLE_TASKLETS
+	int taskletrun;
+	int taskletsched;
+	int taskletpending;
+	int taskletexec;
+	int txerrors;
+	struct tasklet_struct ap4_tlet;
+#endif
+#endif
+	unsigned int passno;	/* number of interrupt passes */
+	struct devtype *dt;
+	char *variety;
+	int last0;		/* for detecting double-missed IRQ */
+	int checktiming;	/* Set >0 to cause the timing source to be checked */
+#ifdef AP400_HDLC
+	struct card_s *hdlc_card;
+#endif
+#ifdef APEC_SUPPORT
+	int apec_enable;
+	struct apec_s *apec;
+#endif
+};
+
+
+static void __set_clear(struct ap4 *wc, int span);
+static int ap4_startup(struct file *file, struct dahdi_span *span);
+static int ap4_shutdown(struct dahdi_span *span);
+static int ap4_rbsbits(struct dahdi_chan *chan, int bits);
+static int ap4_maint(struct dahdi_span *span, int cmd);
+static int ap4_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data);
+static void __ap4_set_timing_source(struct ap4 *wc, int unit);
+static void __ap4_check_alarms(struct ap4 *wc, int span);
+static void __ap4_check_sigbits(struct ap4 *wc, int span);
+
+
+#define AP_ACTIVATE	(1 << 12)
+
+#define AP_OFF	(0)
+#define AP_ON	(1)
+
+#define MAX_AP4_CARDS 64
+
+#ifdef ENABLE_TASKLETS
+static void ap4_tasklet(unsigned long data);
+#endif
+
+static struct ap4 *cards[MAX_AP4_CARDS];
+
+//#define ap_debugk(fmt,args...) printk("ap400 -> %s: "fmt, __PRETTY_FUNCTION__, ##args)
+#define ap_debugk(fmt,args...)
+
+//#define TIMER_DEBUG	1
+
+#ifdef TIMER_DEBUG
+struct timer_list ap4xx_opt_timer;
+unsigned int delay = 1000;
+module_param(delay, uint, S_IRUGO);
+#endif
+
+#define PCI_DEVICE_ID_AP4XX		0x1004
+
+static inline void __ap4_set_led(struct ap4 *wc, int span, int color)
+{
+	wc->ledreg &= ~(AP_ON << span);
+	wc->ledreg |= (color << span);
+	*(wc->membase+AP_LEDS_REG) &= ~0x0000000F;
+	*(wc->membase+AP_LEDS_REG) |= ((wc->ledreg)&0x0F);
+}
+
+static inline void ap4_activate(struct ap4 *wc)
+{
+	wc->ledreg |= AP_ACTIVATE;
+}
+
+static void __set_clear(struct ap4 *wc, int span)
+{
+	int i,j;
+	int oldnotclear;
+	unsigned short val=0;
+	struct ap4_span *ts = wc->tspans[span];
+
+	oldnotclear = ts->notclear;
+	if (ts->spantype == TYPE_T1) {
+		for (i=0;i<24;i++) {
+			j = (i/8);
+			if (ts->span.chans[i]->flags & DAHDI_FLAG_CLEAR) {
+				val |= 1 << (7 - (i % 8));
+				ts->notclear &= ~(1 << i);
+			} else
+				ts->notclear |= (1 << i);
+			if ((i % 8)==7) {
+				val = 0;
+			}
+		}
+	} else {
+		for (i=0;i<31;i++) {
+			if (ts->span.chans[i]->flags & DAHDI_FLAG_CLEAR)
+				ts->notclear &= ~(1 << i);
+			else
+				ts->notclear |= (1 << i);
+		}
+	}
+}
+
+#ifdef APEC_SUPPORT
+
+#define APEC_CTRL_RESET		0x80000000
+#define APEC_CTRL_DDR_NCKE	0x40000000
+#define APEC_CTRL_EC_DISABLE	0x20000000
+#define APEC_CTRL_DAS		0x00080000
+#define APEC_CTRL_RD		0x00040000
+#define APEC_CTRL_REQ		0x00020000
+#define APEC_CTRL_READY		0x00010000
+
+#define APEC_ACCESS_TIMEOUT	1000
+
+static inline u16 oct_raw_read (struct ap4_regs *regs, unsigned short addr)
+{
+	unsigned short data;
+	// Poll ready bit
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+	// Write control bits and address
+	regs->echo_ctrl = APEC_CTRL_RD | APEC_CTRL_REQ | (addr & 0xFFFF);
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+	data = regs->echo_data & 0xFFFF;
+	//PDEBUG("Raw Read 0x%04hX @ 0x%08X", data, addr);
+	return data;
+}
+
+static inline void oct_raw_write (struct ap4_regs *regs, unsigned short addr,
+							unsigned short data)
+{
+	// Poll ready bit
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+	// Write data, then control bits and address
+	regs->echo_data = data & 0xFFFF;
+	regs->echo_ctrl = APEC_CTRL_REQ | (addr & 0xFFFF);
+	// Poll ready bit
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+	//PDEBUG("Raw Write 0x%04hX @ 0x%08X", data, addr);
+	//oct_raw_read(regs, addr);
+}
+
+static inline int oct_ext_wait (struct ap4_regs *regs)
+{
+	int i = APEC_ACCESS_TIMEOUT;
+	while ((oct_raw_read(regs, 0x0) & 0x100) && (i-- > 0));
+	if (i == -1) {
+		printk(KERN_WARNING "Wait access_req timeout\n");
+		return -1;
+	}
+	return 0;
+}
+
+static inline u16 oct_ind_read (struct ap4_regs *regs, unsigned int addr)
+{
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return 0;
+	// Write extended indirect registers
+	oct_raw_write(regs, 0x8, (addr >> 20) & 0x1FFF);
+	oct_raw_write(regs, 0xA, (addr >> 4) & 0xFFFF);
+	oct_raw_write(regs, 0x0, ((addr & 0xE) << 8) | 0x101);
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return 0;
+	// Return data
+	return oct_raw_read(regs, 0x4);
+}
+
+static inline void oct_ind_write (struct ap4_regs *regs, unsigned int addr,
+							unsigned short data)
+{
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return;
+	oct_raw_write(regs, 0x8, (addr >> 20) & 0x1FFF);
+	oct_raw_write(regs, 0xA, (addr >> 4) & 0xFFFF);
+	oct_raw_write(regs, 0x4, data);
+	oct_raw_write(regs, 0x0, ((addr & 0xE) << 8) | 0x3101);
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return;
+}
+
+static inline u16 oct_dir_read (struct ap4_regs *regs, unsigned int addr)
+{
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return 0;
+	// Write extended direct registers
+	oct_raw_write(regs, 0x8, (addr >> 20) & 0x1FFF);
+	oct_raw_write(regs, 0xA, (addr >> 4) & 0xFFFF);
+	oct_raw_write(regs, 0x0, 0x1);
+	regs->echo_ctrl = APEC_CTRL_DAS | APEC_CTRL_RD | APEC_CTRL_REQ | (addr & 0xFFFF);
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+	// Return data
+	return regs->echo_data;
+}
+
+static inline void oct_dir_write (struct ap4_regs *regs, unsigned int addr,
+							unsigned short data)
+{
+	// Poll access_req bit
+	if (oct_ext_wait(regs))
+		return;
+	// Write extended direct registers
+	oct_raw_write(regs, 0x8, (addr >> 20) & 0x1FFF);
+	oct_raw_write(regs, 0xA, (addr >> 4) & 0xFFFF);
+	oct_raw_write(regs, 0x0, 0x3001);
+	regs->echo_data = data & 0xFFFF;
+	regs->echo_ctrl = APEC_CTRL_DAS | APEC_CTRL_REQ | (addr & 0xFFFF);
+	while ((regs->echo_ctrl & APEC_CTRL_READY) == 0);
+}
+
+
+unsigned int oct_read (void *card, unsigned int addr)
+{
+	struct ap4 *wc = card;
+	int flags;
+	unsigned short data;
+	spin_lock_irqsave(&wc->reglock, flags);
+	data = oct_ind_read(wc->hw_regs, addr);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	PDEBUG("Read 0x%04hX @ 0x%08X", data, addr);
+	return data;
+}
+
+void oct_write (void *card, unsigned int addr, unsigned int data)
+{
+	struct ap4 *wc = card;
+	int flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	oct_ind_write(wc->hw_regs, addr, data);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	PDEBUG("Write 0x%04hX @ 0x%08X", data, addr);
+}
+
+static int ap4_apec_init(struct ap4 *wc)
+{
+	int laws[4];
+	int i;
+	unsigned int apec_capacity;
+	struct firmware embedded_firmware;
+	const struct firmware *firmware = &embedded_firmware;
+#if !defined(HOTPLUG_FIRMWARE)
+	extern void _binary_OCT6104E_64D_ima_size;
+	extern u8 _binary_OCT6104E_64D_ima_start[];
+	extern void _binary_OCT6104E_128D_ima_size;
+	extern u8 _binary_OCT6104E_128D_ima_start[];
+#else
+	static const char oct64_firmware[] = "OCT6104E-64D.ima";
+	static const char oct128_firmware[] = "OCT6104E-128D.ima";
+#endif
+
+	// Enable DDR and Reset Octasic
+	wc->hw_regs->echo_ctrl |= APEC_CTRL_RESET;
+	wc->hw_regs->echo_ctrl |= APEC_CTRL_DDR_NCKE;
+	udelay(500);
+	wc->hw_regs->echo_ctrl &= APEC_CTRL_RESET;
+	wc->hw_regs->echo_ctrl &= APEC_CTRL_DDR_NCKE;
+	wc->hw_regs->echo_ctrl &= APEC_CTRL_EC_DISABLE;
+
+	/* Setup alaw vs ulaw rules */
+	for (i = 0; i < wc->numspans; i++) {
+		if (wc->tspans[i]->span.channels > 24)
+			laws[i] = 1;	// E1: alaw
+		else
+			laws[i] = 0;	// T1: ulaw
+	}
+
+	switch ((apec_capacity = apec_capacity_get(wc))) {
+	case 64:
+#if defined(HOTPLUG_FIRMWARE)
+		if ((request_firmware(&firmware, oct64_firmware, &wc->dev->dev) != 0) ||
+		    !firmware) {
+			printk("%s: firmware %s not available from userspace\n",
+					wc->variety, oct64_firmware);
+			return -1;
+		}
+#else
+		embedded_firmware.data = _binary_OCT6104E_64D_ima_start;
+		/* Yes... this is weird. objcopy gives us a symbol containing
+		   the size of the firmware, not a pointer to a variable containing
+		   the size. The only way we can get the value of the symbol
+		   is to take its address, so we define it as a pointer and
+		   then cast that value to the proper type.
+		*/
+		embedded_firmware.size = (size_t) &_binary_OCT6104E_64D_ima_size;
+#endif
+		break;
+	case 128:
+#if defined(HOTPLUG_FIRMWARE)
+		if ((request_firmware(&firmware, oct128_firmware, &wc->dev->dev) != 0) ||
+		    !firmware) {
+			printk("%s: firmware %s not available from userspace\n",
+					wc->variety, oct128_firmware);
+			return -1;
+		}
+#else
+		embedded_firmware.data = _binary_OCT6104E_128D_ima_start;
+		/* Yes... this is weird. objcopy gives us a symbol containing
+		   the size of the firmware, not a pointer to a variable containing
+		   the size. The only way we can get the value of the symbol
+		   is to take its address, so we define it as a pointer and
+		   then cast that value to the proper type.
+		*/
+		embedded_firmware.size = (size_t) &_binary_OCT6104E_128D_ima_size;
+#endif
+		break;
+	default:
+		printk(KERN_INFO "Unsupported channel capacity found on"
+				"echo cancellation module (%d).\n", apec_capacity);
+		return -1;
+	}
+
+	if (!(wc->apec = apec_init(wc, laws, wc->numspans, firmware))) {
+		printk(KERN_WARNING "APEC: Failed to initialize\n");
+		if (firmware != &embedded_firmware)
+			release_firmware(firmware);
+		return -1;
+	}
+
+	if (firmware != &embedded_firmware)
+		release_firmware(firmware);
+
+	printk(KERN_INFO "APEC: Present and operational servicing %d span(s)\n", wc->numspans);
+	return 0;
+}
+
+void ap4_apec_release(struct ap4 *wc)
+{
+	// Disabel DDR and reset Octasic
+	wc->hw_regs->echo_ctrl |= APEC_CTRL_RESET;
+	wc->hw_regs->echo_ctrl |= APEC_CTRL_DDR_NCKE;
+	wc->hw_regs->echo_ctrl |= APEC_CTRL_EC_DISABLE;
+	if (wc->apec)
+		apec_release(wc->apec);
+}
+
+
+static int ap4_echocan(struct dahdi_chan *chan, int eclen)
+{
+	struct ap4 *wc = chan->pvt;
+	int channel;
+
+	if (!wc->apec)
+		return -ENODEV;
+	if (debug)
+		printk(KERN_DEBUG "AP400: ap4_echocan @ Span %d Channel %d Length: %d\n",
+				chan->span->offset, chan->chanpos, eclen);
+	channel = (chan->chanpos << 2) | chan->span->offset;
+	apec_setec(wc->apec, channel, eclen);
+	return 0;
+}
+
+#endif // APEC_SUPPORT
+
+
+static int ap4_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
+{
+	struct ap4 *wc = chan->pvt;
+	int span = 0;
+	int alarms = 0;
+	unsigned char c, e1_cfg;
+
+	switch(cmd) {
+		case AP4_GET_ALARMS:
+			if (copy_from_user(&span, (int *)data, sizeof(int)))
+				return -EFAULT;
+			// span starts in zero
+			span--;
+		if (wc->tspans[span]->spantype == TYPE_E1) {
+		        /* le status e configuracao do E1 */
+		        c = ((*(wc->membase+AP_E1_STATUS_REG))>>(8*span));
+		        e1_cfg = ((*(wc->membase+AP_E1_CONFIG_REG))>>(8*span));
+			if( c & AP_E1_LOS_STATUS) {
+				alarms = 0x01;
+			} else if( c & AP_E1_AIS_STATUS) {
+				alarms = 0x02;
+			} else if(!(c & AP_E1_BFAE_STATUS)) {
+				alarms = 0x04;
+				if (c & AP_E1_RAI_STATUS)
+					alarms |= 0x08;
+				// Erro de MFA: 00 - MFA desabilitado, 01 - erro de MFA, 10 - MFA OK
+				if ( (c & AP_E1_MFAE_STATUS) && (e1_cfg & AP_E1_CRCEN_CONFIG) )
+					alarms |= 0x10;
+				else if ( (!(c & AP_E1_MFAE_STATUS)) && (e1_cfg & AP_E1_CRCEN_CONFIG) )
+					alarms |= 0x20;
+				// Erro de CAS: 00 - desabilitado, 01 - erro de CAS, 10 - CAS OK
+				if ( (!(c & AP_E1_CAS_STATUS)) && (e1_cfg & AP_E1_PCM30_CONFIG))
+					alarms |= 0x40;
+				else if ( (c & AP_E1_CAS_STATUS) && (e1_cfg & AP_E1_PCM30_CONFIG))
+					alarms |= 0x80;
+			}
+		} else {
+			/* le status e configuracao do E1 */
+		        c = ((*(wc->membase+AP_E1_STATUS_REG))>>(8*span));
+		        if( c & AP_E1_LOS_STATUS)
+				alarms = 0x01;
+			else {
+			        c = wc->hw_regs->t1_status >> (8*span);
+			        if (!(c & AP4_T1_FRAME_SYNC))
+			        	alarms = 0x04;
+		        }
+		}
+			if(debug) printk("AP4_GET_ALARMS: span = %d, alarms = 0x%02x\n", span+1, alarms);
+			if (copy_to_user((int *)data, &alarms, sizeof(int)))
+				return -EFAULT;
+			break;
+
+		case AP4_GET_SLIPS:
+			if (copy_from_user(&span, (int *)data, sizeof(int)))
+				return -EFAULT;
+			// span starts in zero
+			span--;
+			if((span < wc->numspans) && (span >=0))
+				alarms = wc->tspans[span]->slipcount;
+			if(debug) printk("AP4_GET_SLIPS: span = %d, slips = 0x%02x\n", span+1, alarms);
+			if (copy_to_user((int *)data, &alarms, sizeof(int)))
+				return -EFAULT;
+			break;
+
+		default:
+			PDEBUG("%s: Unknown IOCTL CODE!", wc->variety);
+			return -ENOTTY;
+	}
+	return 0;
+}
+
+static inline struct ap4_span* ap4_span_from_span(struct dahdi_span *span) {
+	return container_of(span, struct ap4_span, span);
+}
+
+static int ap4_maint(struct dahdi_span *span, int cmd)
+{
+	struct ap4_span *ts = ap4_span_from_span(span);
+	struct ap4 *wc = ts->owner;
+
+
+	if (ts->spantype == TYPE_E1) {
+		switch(cmd) {
+		case DAHDI_MAINT_NONE:
+			printk("XXX Turn off local and remote loops E1 XXX\n");
+			*(wc->membase+AP_E1_CONFIG_REG) &= ~(AP_E1_LOOP_CONFIG<<((span->spanno-1)*8));
+			break;
+		case DAHDI_MAINT_LOCALLOOP:
+			printk("XXX Turn on local loopback E1 XXX\n");
+			break;
+		case DAHDI_MAINT_REMOTELOOP:
+			printk("XXX Turn on remote loopback E1 XXX\n");
+			break;
+		case DAHDI_MAINT_LOOPUP:
+			printk("XXX Turn on local loopback on E1 #%d instead of send loopup code XXX\n", span->spanno);
+			*(wc->membase+AP_E1_CONFIG_REG) |= (AP_E1_LOOP_CONFIG<<((span->spanno-1)*8));
+			break;
+		case DAHDI_MAINT_LOOPDOWN:
+			printk("XXX Turn on local loopback on E1 #%d instead of send loopdown code XXX\n", span->spanno);
+			*(wc->membase+AP_E1_CONFIG_REG) |= (AP_E1_LOOP_CONFIG<<((span->spanno-1)*8));
+			break;
+		default:
+			printk("%s: Unknown E1 maint command: %d\n", wc->variety, cmd);
+			break;
+		}
+	} else {
+		switch(cmd) {
+	    case DAHDI_MAINT_NONE:
+			printk("XXX Turn off local and remote loops T1 XXX\n");
+			break;
+	    case DAHDI_MAINT_LOCALLOOP:
+			printk("XXX Turn on local loop and no remote loop XXX\n");
+			break;
+	    case DAHDI_MAINT_REMOTELOOP:
+			printk("XXX Turn on remote loopup XXX\n");
+			break;
+	    case DAHDI_MAINT_LOOPUP:
+			break;
+	    case DAHDI_MAINT_LOOPDOWN:
+			break;
+	    default:
+			printk("%s: Unknown T1 maint command: %d\n", wc->variety, cmd);
+			break;
+	   }
+    }
+	return 0;
+}
+
+static int ap4_rbsbits(struct dahdi_chan *chan, int bits)
+{
+	u_char m,c;
+	int k,n,b;
+	struct ap4 *wc = chan->pvt;
+	struct ap4_span *ts = wc->tspans[chan->span->offset];
+	unsigned long flags;
+	volatile unsigned int *writecas = (wc->membase+AP_CAS_BASE);
+	unsigned int allspansbits;
+
+	//ap_debugk("chan->channo = %d, int bits = 0x%08x\n", chan->channo, bits);
+	if(debug & DEBUG_RBS) printk("Setting bits to %d on channel %s\n", bits, chan->name);
+	spin_lock_irqsave(&wc->reglock, flags);
+	k = chan->span->offset;
+	if (ts->spantype == TYPE_E1) { /* do it E1 way */
+		if (chan->chanpos == 16) {
+			spin_unlock_irqrestore(&wc->reglock, flags);
+			return 0;
+		}
+		n = chan->chanpos - 1;
+		if (chan->chanpos > 15) n--;
+		b = (n % 15);
+		c = ts->txsigs[b];
+		m = (n / 15) << 2; /* nibble selector */
+		c &= (0xf << m); /* keep the other nibble */
+		c |= (bits & 0xf) << (4 - m); /* put our new nibble here */
+		ts->txsigs[b] = c;
+		/* monta a word de 32 bits com informacao de todos os spans */
+		allspansbits =  wc->tspans[0]->txsigs[b];
+		if (wc->numspans > 1) {
+			allspansbits |=	(wc->tspans[1]->txsigs[b] << 8);
+		}
+		if (wc->numspans == 4) {
+			allspansbits |=	(wc->tspans[2]->txsigs[b] << 16) |
+							(wc->tspans[3]->txsigs[b] << 24);
+		}
+		/* output them to the chip */
+		writecas[b] = allspansbits;
+		ap_debugk("escrito 0x%08x para ser transmitido pelo CAS (b = %d)\n", allspansbits, b);
+#if 0
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_D4) {
+		n = chan->chanpos - 1;
+		b = (n/4);
+		c = ts->txsigs[b];
+		m = ((3 - (n % 4)) << 1); /* nibble selector */
+		c &= ~(0x3 << m); /* keep the other nibble */
+		c |= ((bits >> 2) & 0x3) << m; /* put our new nibble here */
+		ts->txsigs[b] = c;
+		  /* output them to the chip */
+		//__ap4_out( ... );
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_ESF) {
+#endif
+	} else {
+		n = chan->chanpos - 1;
+		b = (n/2);
+		c = ts->txsigs[b];
+		m = ((n % 2) << 2); /* nibble selector */
+		c &= (0xf << m); /* keep the other nibble */
+		c |= (bits & 0xf) << (4 - m); /* put our new nibble here */
+		ts->txsigs[b] = c;
+		  /* output them to the chip */
+		/* monta a word de 32 bits com informacao de todos os spans */
+		allspansbits =  wc->tspans[0]->txsigs[b];
+		if (wc->numspans > 1) {
+			allspansbits |=	(wc->tspans[1]->txsigs[b] << 8);
+		}
+		if (wc->numspans == 4) {
+			allspansbits |=	(wc->tspans[2]->txsigs[b] << 16) |
+							(wc->tspans[3]->txsigs[b] << 24);
+		}
+		/* output them to the chip */
+		writecas[b] = allspansbits;
+		ap_debugk("escrito 0x%08x para ser transmitido pelo CAS (b = %d)\n", allspansbits, b);
+	}
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	if (debug & DEBUG_RBS)
+		printk("Finished setting RBS bits\n");
+	return 0;
+}
+
+static int ap4_shutdown(struct dahdi_span *span)
+{
+	int tspan;
+	int wasrunning;
+	unsigned long flags;
+	struct ap4_span *ts = ap4_span_from_span(span);
+	struct ap4 *wc = ts->owner;
+
+	tspan = span->offset + 1;
+	if (tspan < 0) {
+		printk("%s: '%d' isn't us?\n", wc->variety, span->spanno);
+		return -1;
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	wasrunning = span->flags & DAHDI_FLAG_RUNNING;
+
+	span->flags &= ~DAHDI_FLAG_RUNNING;
+	if (wasrunning)
+		wc->spansstarted--;
+	__ap4_set_led(wc, span->offset, AP_OFF);
+	if (((wc->numspans == 4) &&
+	    (!(wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING)) &&
+	    (!(wc->tspans[1]->span.flags & DAHDI_FLAG_RUNNING)) &&
+	    (!(wc->tspans[2]->span.flags & DAHDI_FLAG_RUNNING)) &&
+	    (!(wc->tspans[3]->span.flags & DAHDI_FLAG_RUNNING)))
+	    			||
+	    ((wc->numspans == 2) &&
+	    (!(wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING)) &&
+	    (!(wc->tspans[1]->span.flags & DAHDI_FLAG_RUNNING)))
+	    			||
+	    ((wc->numspans == 1) &&
+	    (!(wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING)))) {
+		/* No longer in use, disable interrupts */
+		printk("%s: Disabling interrupts since there are no active spans\n",
+				wc->variety);
+	} else wc->checktiming = 1;
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	if (debug & DEBUG_MAIN)
+		printk("Span %d (%s) shutdown\n", span->spanno, span->name);
+	return 0;
+}
+
+static int ap4_spanconfig(struct file *file, struct dahdi_span *span,
+		struct dahdi_lineconfig *lc)
+{
+	int i;
+	struct ap4_span *ts = ap4_span_from_span(span);
+	struct ap4 *wc = ts->owner;
+	unsigned int val;
+
+	printk("About to enter spanconfig!\n");
+	if (debug & DEBUG_MAIN)
+		printk("%s: Configuring span %d\n", wc->variety, span->spanno);
+	/* XXX We assume lineconfig is okay and shouldn't XXX */
+	span->lineconfig = lc->lineconfig;
+	span->txlevel = lc->lbo;
+	span->rxlevel = 0;
+	if (lc->sync < 0)
+		lc->sync = 0;
+	if (lc->sync > 4)
+		lc->sync = 0;
+
+	/* remove this span number from the current sync sources, if there */
+	for(i = 0; i < wc->numspans; i++) {
+		if (wc->tspans[i]->sync == span->spanno) {
+			wc->tspans[i]->sync = 0;
+			wc->tspans[i]->psync = 0;
+		}
+	}
+	wc->tspans[span->offset]->syncpos = lc->sync;
+	/* if a sync src, put it in proper place */
+	if (lc->sync) {
+		wc->tspans[lc->sync - 1]->sync = span->spanno;
+		wc->tspans[lc->sync - 1]->psync = span->offset + 1;
+	}
+	wc->checktiming = 1;
+	/* If we're already running, then go ahead and apply the changes */
+	if (span->flags & DAHDI_FLAG_RUNNING)
+		return ap4_startup(file, span);
+
+	// Limpa contadores de slips, crc e bpv
+	val = (*(wc->membase + AP_CNT_SLIP_REG));
+	val = (*(wc->membase + AP_CNT_CRC_REG));
+	val = (*(wc->membase + AP_CNT_CV_REG));
+
+	ap_debugk("habilitando interrupcao!\n");
+	// Nao considera as primeiras interrupcoes na soma das IRQs perdidas
+	wc->flag_1st_irq = 16;
+	// Enable interrupt
+	*(wc->membase + AP_INT_CONTROL_REG) |= AP_INT_CTL_ENABLE;
+	// Limpa interrupcao da FPGA para forcar borda de subida na proxima
+	val = *(wc->membase + AP_CLEAR_IRQ_REG);
+
+	printk("Done with spanconfig!\n");
+	return 0;
+}
+
+static int ap4_chanconfig(struct file *file, struct dahdi_chan *chan,
+		int sigtype)
+{
+	int alreadyrunning;
+	unsigned long flags;
+	struct ap4 *wc = chan->pvt;
+
+	alreadyrunning = wc->tspans[chan->span->offset]->span.flags & DAHDI_FLAG_RUNNING;
+	if (debug & DEBUG_MAIN) {
+		if (alreadyrunning)
+			printk("%s: Reconfigured channel %d (%s) sigtype %d\n",
+					wc->variety, chan->channo, chan->name, sigtype);
+		else
+			printk("%s: Configured channel %d (%s) sigtype %d\n",
+					wc->variety, chan->channo, chan->name, sigtype);
+	}
+	spin_lock_irqsave(&wc->reglock, flags);
+	if (alreadyrunning)
+		__set_clear(wc, chan->span->offset);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	return 0;
+}
+
+static int ap4_open(struct dahdi_chan *chan)
+{
+	try_module_get(THIS_MODULE);
+	return 0;
+}
+
+static int ap4_close(struct dahdi_chan *chan)
+{
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static const struct dahdi_span_ops ap4_span_ops = {
+	.owner = THIS_MODULE,
+	.spanconfig = ap4_spanconfig,
+	.chanconfig = ap4_chanconfig,
+	.startup = ap4_startup,
+	.shutdown = ap4_shutdown,
+	.rbsbits = ap4_rbsbits,
+	.maint = ap4_maint,
+	.open = ap4_open,
+	.close  = ap4_close,
+#ifdef APEC_SUPPORT
+	.echocan = ap4_echocan,
+#endif
+	.ioctl = ap4_ioctl
+};
+
+static void init_spans(struct ap4 *wc)
+{
+	int x,y;
+	struct ap4_span *ts;
+
+	for (x=0;x<wc->numspans;x++) {
+		ts = wc->tspans[x];
+		sprintf(ts->span.name, "AP4%d%d/%d/%d", 0, wc->numspans, wc->num, x + 1);
+		snprintf(ts->span.desc, sizeof(ts->span.desc) - 1, "AP4%d%d Card %d Span %d", 0, wc->numspans, wc->num+1, x+1);
+		ts->span.ops = &ap4_span_ops;
+		if (ts->spantype == TYPE_E1) {
+			ts->span.channels = 31;
+			ts->span.spantype = "E1";
+			ts->span.linecompat = DAHDI_CONFIG_HDB3 | DAHDI_CONFIG_CCS | DAHDI_CONFIG_CRC4;
+			ts->span.deflaw = DAHDI_LAW_ALAW;
+		} else {
+			ts->span.channels = 24;
+			ts->span.spantype = "T1";
+			ts->span.linecompat = DAHDI_CONFIG_AMI | DAHDI_CONFIG_B8ZS | DAHDI_CONFIG_D4 | DAHDI_CONFIG_ESF;
+			ts->span.deflaw = DAHDI_LAW_MULAW;
+		}
+		ts->span.chans = ts->chans;
+		ts->span.flags = DAHDI_FLAG_RBS;
+		ts->owner = wc;
+		ts->span.offset = x;
+		ts->writechunk = (void *)(wc->writechunk + x * 32 * 2);
+		ts->readchunk = (void *)(wc->readchunk + x * 32 * 2);
+		for (y=0;y<wc->tspans[x]->span.channels;y++) {
+			struct dahdi_chan *mychans = ts->chans[y];
+			sprintf(mychans->name, "AP4%d%d/%d/%d/%d", 0, wc->numspans, wc->num, x + 1, y + 1);
+			mychans->sigcap = DAHDI_SIG_EM | DAHDI_SIG_CLEAR | DAHDI_SIG_FXSLS | DAHDI_SIG_FXSGS | DAHDI_SIG_FXSKS |
+									 DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_FXOKS | DAHDI_SIG_CAS | DAHDI_SIG_EM_E1 | DAHDI_SIG_DACS_RBS;
+			mychans->pvt = wc;
+			mychans->chanpos = y + 1;
+		}
+	}
+	printk("%s: Spans initialized\n", wc->variety);
+}
+
+
+
+static void __ap4_set_timing_source(struct ap4 *wc, int unit)
+{
+	unsigned int timing;
+	int x;
+
+	if (unit != wc->syncsrc) {
+		if ((unit > -1) && (unit < 4)) {
+			/* define fonte de clock para interface escolhida */
+			timing = *(wc->membase+AP_CLKSRC_REG);
+			timing &= ~AP_CLKSRC_MASK;
+			timing |= unit+1;
+			*(wc->membase+AP_CLKSRC_REG) = timing;
+		} else {
+			/* define clock para interno */
+			timing = *(wc->membase+AP_CLKSRC_REG);
+			timing &= ~AP_CLKSRC_MASK;
+			*(wc->membase+AP_CLKSRC_REG) = timing;
+		}
+		wc->syncsrc = unit;
+		if ((unit < 0) || (unit > 3))
+			unit = 0;
+		else
+			unit++;
+		for (x=0;x<wc->numspans;x++)
+			wc->tspans[x]->span.syncsrc = unit;
+	} else {
+		if (debug & DEBUG_MAIN)
+			printk("%s: Timing source already set to %d\n",
+					wc->variety, unit);
+	}
+	printk("%s: Timing source set to %d (clksrc_reg = 0x%08x)\n",
+			wc->variety, unit, *(wc->membase+AP_CLKSRC_REG));
+}
+
+static void __ap4_set_timing_source_auto(struct ap4 *wc)
+{
+	int x;
+
+	wc->checktiming = 0;
+	for (x=0;x<wc->numspans;x++) {
+		if (wc->tspans[x]->sync) {
+			if ((wc->tspans[wc->tspans[x]->psync - 1]->span.flags & DAHDI_FLAG_RUNNING) &&
+				!(wc->tspans[wc->tspans[x]->psync - 1]->span.alarms & (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE) )) {
+					/* Valid timing source */
+					__ap4_set_timing_source(wc, wc->tspans[x]->psync - 1);
+					return;
+			}
+		}
+	}
+	__ap4_set_timing_source(wc, 4);
+}
+
+static void __ap4_configure_t1(struct ap4 *wc, int unit, int lineconfig, int txlevel)
+{
+	char *framing, *line;
+	unsigned int config = 0;
+	unsigned int param = 0;
+	unsigned int linecode = 0;
+
+	wc->tspans[unit]->spantype = TYPE_T1;
+	wc->tspans[unit]->span.channels = 24;
+	wc->tspans[unit]->span.deflaw = DAHDI_LAW_MULAW;
+
+	/* Configure line code */
+	if (unit < 2)
+		linecode = AP_LIU1_LINECODE;
+	else
+		linecode = AP_LIU2_LINECODE;
+	if (lineconfig & DAHDI_CONFIG_AMI) {
+		*(wc->membase+AP_LEDS_REG) |= linecode;
+		line = "AMI";
+	} else {
+		*(wc->membase+AP_LEDS_REG) &= ~linecode;
+		line = "B8ZS";
+	}
+
+	/* loopback test*/
+	//wc->hw_regs->e1_config |= (AP_E1_LOOP_CONFIG  << (8 * unit));
+	//printk("E1 config = 0x%08x\n", wc->hw_regs->e1_config);
+
+	/* Configure T1 */
+	config = wc->hw_regs->liu_config;
+	config &= ~(0x000000ff << (8 * unit));
+	config |= (AP_PULS_DSX1_0FT << (8 * unit));
+	wc->hw_regs->liu_config = config;
+
+	param = AP4_T1_NE1_SEL | AP4_T1_CAS_ENABLE;
+	if (lineconfig & DAHDI_CONFIG_D4) {
+		framing = "D4";
+	} else {
+		framing = "ESF";
+		param |= AP4_T1_ESF_NSF;
+	}
+	config = wc->hw_regs->t1_config;
+	config &= ~(0x000000ff << (8 * unit));
+	config |= (param << (8 * unit));
+	wc->hw_regs->t1_config = config;
+
+	printk("T1 Status: 0x%08x\tT1 Config: 0x%08x\tPARAM: 0x%08x\n",
+			wc->hw_regs->t1_status, wc->hw_regs->t1_config, param);
+
+	if (!polling) {
+		__ap4_check_alarms(wc, unit);
+		__ap4_check_sigbits(wc, unit);
+	}
+	printk("%s: Span %d configured for %s/%s\n", wc->variety, unit + 1, framing, line);
+}
+
+static void __ap4_configure_e1(struct ap4 *wc, int unit, int lineconfig)
+{
+	char *crc4 = "";
+	char *framing, *line;
+	unsigned int e1s_cfg, config = 0;
+	unsigned int linecode = 0;
+
+	wc->tspans[unit]->spantype = TYPE_E1;
+	wc->tspans[unit]->span.channels = 31;
+	wc->tspans[unit]->span.deflaw = DAHDI_LAW_ALAW;
+
+	if (loopback) {
+	}
+
+	if (lineconfig & DAHDI_CONFIG_CRC4) {
+		crc4 = "/CRC4";
+		config |= AP_E1_CRCEN_CONFIG;
+	}
+
+	if(unit < 2)
+		linecode = AP_LIU1_LINECODE;
+	else
+		linecode = AP_LIU2_LINECODE;
+	/* Configure line interface */
+	if (lineconfig & DAHDI_CONFIG_AMI) {
+		*(wc->membase+AP_LEDS_REG) |= linecode;
+		line = "AMI";
+	} else {
+		*(wc->membase+AP_LEDS_REG) &= ~linecode;
+		line = "HDB3";
+	}
+
+	if (lineconfig & DAHDI_CONFIG_CCS) {
+		framing = "CCS";
+	} else {
+		framing = "CAS";
+		config |= (AP_E1_CASEN_CONFIG | AP_E1_PCM30_CONFIG);
+	}
+
+	e1s_cfg = *(wc->membase+AP_E1_CONFIG_REG);
+	e1s_cfg &= ~(0x000000ff<<(8*unit));
+	e1s_cfg |= (config<<(8*unit));
+	*(wc->membase+AP_E1_CONFIG_REG) = e1s_cfg;
+
+	/* Disable T1 framer */
+	config = wc->hw_regs->t1_config;
+	config &= ~(0x000000ff << (8 * unit));
+	wc->hw_regs->t1_config = config;
+
+	/* Configure LIU Signalling */
+	e1s_cfg = *(wc->membase+AP_T1E1_CONFIG_REG);
+	e1s_cfg &= ~(0x000000ff<<(8*unit));
+	e1s_cfg |= (AP_PULS_E1_120<<(8*unit));
+	*(wc->membase+AP_T1E1_CONFIG_REG) = e1s_cfg;
+
+	if (!polling) {
+		__ap4_check_alarms(wc, unit);
+		__ap4_check_sigbits(wc, unit);
+	}
+	printk("%s: Span %d configured for %s/%s%s\n",
+ 			wc->variety, unit + 1, framing, line, crc4);
+}
+
+static int ap4_startup(struct file *file, struct dahdi_span *span)
+{
+	int i;
+	int tspan;
+	unsigned long flags;
+	int alreadyrunning;
+	struct ap4_span *ts = ap4_span_from_span(span);
+	struct ap4 *wc = ts->owner;
+
+	printk("About to enter startup!\n");
+	tspan = span->offset + 1;
+	if (tspan < 0) {
+		printk("%s: Span '%d' isn't us?\n", wc->variety, span->spanno);
+		return -1;
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	alreadyrunning = span->flags & DAHDI_FLAG_RUNNING;
+
+	/* initialize the start value for the entire chunk of last ec buffer */
+	for(i = 0; i < span->channels; i++)
+	{
+		memset(ts->ec_chunk1[i],
+			DAHDI_LIN2X(0, span->chans[i]),DAHDI_CHUNKSIZE);
+		memset(ts->ec_chunk2[i],
+			DAHDI_LIN2X(0, span->chans[i]),DAHDI_CHUNKSIZE);
+	}
+
+	/* Force re-evaluation fo timing source */
+//	if (timingcable)
+		wc->syncsrc = -1;
+
+	if ((span->lineconfig & DAHDI_CONFIG_D4) || (span->lineconfig & DAHDI_CONFIG_ESF)) {
+		/* is a T1 card */
+		__ap4_configure_t1(wc, span->offset, span->lineconfig, span->txlevel);
+	} else { /* is a E1 card */
+		__ap4_configure_e1(wc, span->offset, span->lineconfig);
+	}
+
+	/* Note clear channel status */
+	wc->tspans[span->offset]->notclear = 0;
+	__set_clear(wc, span->offset);
+
+	if (!alreadyrunning) {
+		span->flags |= DAHDI_FLAG_RUNNING;
+		wc->spansstarted++;
+		/* enable interrupts */
+
+		if (!polling) {
+			__ap4_check_alarms(wc, span->offset);
+			__ap4_check_sigbits(wc, span->offset);
+		}
+	}
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	if (wc->tspans[0]->sync == span->spanno) printk("SPAN %d: Primary Sync Source\n",span->spanno);
+	if (wc->numspans > 1) {
+		if (wc->tspans[1]->sync == span->spanno) printk("SPAN %d: Secondary Sync Source\n",span->spanno);
+	}
+	if (wc->numspans == 4) {
+		if (wc->tspans[2]->sync == span->spanno) printk("SPAN %d: Tertiary Sync Source\n",span->spanno);
+		if (wc->tspans[3]->sync == span->spanno) printk("SPAN %d: Quaternary Sync Source\n",span->spanno);
+	}
+
+#ifdef APEC_SUPPORT
+	if (!apec_enable || !wc->apec_enable)
+		wc->hw_regs->echo_ctrl = 0xe0000000;
+	else if (!alreadyrunning && !wc->apec)
+			if (ap4_apec_init(wc))
+				ap4_apec_release(wc);
+#else
+	wc->hw_regs->echo_ctrl = 0xe0000000;
+#endif
+
+	printk("Completed startup!\n");
+	return 0;
+}
+
+
+static void ap4_receiveprep(struct ap4 *wc)
+{
+	volatile unsigned int *readchunk;
+	unsigned int buffer[32];
+	unsigned char *byte = (unsigned char *) buffer;
+	int i, j, k;
+
+	readchunk = (wc->membase + (AP_DATA_BASE));
+	for (i = 0; i < DAHDI_CHUNKSIZE; i++) {
+		/* Prefetch Card data */
+		for (j = 0; j < 32; ++j) {
+			buffer[j] = readchunk[j];
+		}
+		for (j = 0; j < wc->numspans; j++) {
+			/* Set first timeslot for first channel */
+			if (wc->tspans[j]->spantype == TYPE_E1) {
+				for (k = 0; k < 31; ++k) {
+					/* Skip first timeslot from E1 */
+					wc->tspans[j]->span.chans[k]->readchunk[i] =
+							byte[4*(k+1)+j];
+				}
+			}
+			else {
+				for (k = 0; k < 24; ++k) {
+					wc->tspans[j]->span.chans[k]->readchunk[i] =
+							byte[4*k+j];
+				}
+			}
+		}
+		readchunk += 32;
+	}
+
+	for (i = 0; i < wc->numspans; i++) {
+		if (wc->tspans[i]->span.flags & DAHDI_FLAG_RUNNING) {
+			for (j = 0; j < wc->tspans[i]->span.channels; j++) {
+				/* Echo cancel double buffered data */
+				dahdi_ec_chunk(wc->tspans[i]->span.chans[j],
+				    wc->tspans[i]->span.chans[j]->readchunk,
+					wc->tspans[i]->ec_chunk2[j]);
+				memcpy(wc->tspans[i]->ec_chunk2[j],wc->tspans[i]->ec_chunk1[j],
+					DAHDI_CHUNKSIZE);
+				memcpy(wc->tspans[i]->ec_chunk1[j],
+					wc->tspans[i]->span.chans[j]->writechunk,
+						DAHDI_CHUNKSIZE);
+			}
+			dahdi_receive(&wc->tspans[i]->span);
+		}
+	}
+}
+
+#if (DAHDI_CHUNKSIZE != 8)
+#error Sorry, AP400 driver does not support chunksize != 8
+#endif
+
+#ifdef ENABLE_WORKQUEUES
+static void workq_handlespan(void *data)
+{
+	struct ap4_span *ts = data;
+	struct ap4 *wc = ts->owner;
+
+//	__receive_span(ts);
+//	__transmit_span(ts);
+	atomic_dec(&wc->worklist);
+	atomic_read(&wc->worklist);
+
+}
+#endif
+
+static void ap4_transmitprep(struct ap4 *wc)
+{
+	volatile unsigned int *writechunk;
+	int x,y,z;
+	unsigned int tmp;
+
+	for (y=0;y<wc->numspans;y++) {
+		if (wc->tspans[y]->span.flags & DAHDI_FLAG_RUNNING)
+			dahdi_transmit(&wc->tspans[y]->span);
+	}
+
+	writechunk = (wc->membase+(AP_DATA_BASE));
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+		// Once per chunk
+		for (z=0;z<32;z++) {
+			// All channels
+			tmp = 0;
+			for (y = 0; y < wc->numspans; ++y) {
+				if (wc->tspans[y]->spantype == TYPE_T1 && z < 24)
+					tmp |= (wc->tspans[y]->span.chans[z]->writechunk[x]
+					                           << (8*y));
+				else /* Span Type is E1 */
+					if (z > 0) /* Skip first timeslot */
+						tmp |= (wc->tspans[y]->span.chans[z-1]->writechunk[x]
+									<< (8*y));
+			}
+			writechunk[z] = tmp;
+		}
+		// Advance pointer by 4 TDM frame lengths
+		writechunk += 32;
+	}
+
+}
+
+static void ap4_tdm_loop(struct ap4 *wc)
+{
+	volatile unsigned int *buf_ptr;
+	int x,z;
+	unsigned int tmp;
+
+	buf_ptr = (wc->membase+AP_DATA_BASE);
+
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+		// Once per chunk
+		for (z=0;z<32;z++) {
+			tmp = buf_ptr[z];
+			buf_ptr[z] = tmp;
+		}
+		buf_ptr += 32;
+	}
+}
+
+static void __ap4_check_sigbits(struct ap4 *wc, int span)
+{
+	int a,i,rxs;
+	struct ap4_span *ts = wc->tspans[span];
+	volatile unsigned int *readcas = (wc->membase+AP_CAS_BASE);
+
+//	if (debug & DEBUG_RBS)
+//		printk("Checking sigbits on span %d\n", span + 1);
+
+	if (!(ts->span.flags & DAHDI_FLAG_RUNNING))
+		return;
+	// se span estiver com alarme RED ou BLUE...
+	if( (ts->span.alarms & DAHDI_ALARM_RED) || (ts->span.alarms & DAHDI_ALARM_BLUE) ) {
+		ts->reload_cas = 4;
+	} else if(ts->reload_cas > 0) {
+		// da mais um tempo para framer recuperar e enviar bits de CAS validos
+		ts->reload_cas--;
+	}
+
+	if (ts->spantype == TYPE_E1) {
+		for (i = 0; i < 15; i++) {
+
+			// Se estamos em alarme ou recuperando de um entao mascara os bits para "1101" (bloqueado)
+			if(ts->reload_cas) {
+				a = 0xdd;
+			} else {
+				a = (int) ts->casbuf[i];
+			}
+			ts->casbuf[i] = (unsigned char) (readcas[i] >> (8*span))&0xff;
+
+			/* Get high channel in low bits */
+			rxs = (a & 0xf);
+			if (!(ts->span.chans[i+16]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i+16]->rxsig != rxs) {
+					ap_debugk("CAS no canal %d mudou de 0x%02x para 0x%02x\n", i+16, ts->span.chans[i+16]->rxsig, rxs);
+					dahdi_rbsbits(ts->span.chans[i+16], rxs);
+				}
+			}
+			rxs = (a >> 4) & 0xf;
+			if (!(ts->span.chans[i]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i]->rxsig != rxs) {
+					ap_debugk("CAS no canal %d mudou de 0x%02x para 0x%02x\n", i, ts->span.chans[i]->rxsig, rxs);
+					dahdi_rbsbits(ts->span.chans[i], rxs);
+				}
+			}
+		}
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_D4) {
+		for (i = 0; i < 12; i++) {
+			a = (unsigned char) (readcas[i] >> (8*span)) & 0xcc;
+			rxs = a & 0xc;
+			//rxs = (a & 0xc) >> 2;
+			if (!(ts->span.chans[2*i]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[2*i]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[2*i], rxs);
+			}
+			rxs = (a >> 4) & 0xc;
+			//rxs = ((a >> 4) & 0xc) >> 2;
+			if (!(ts->span.chans[2*i+1]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[2*i+1]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[2*i+1], rxs);
+			}
+		}
+	} else { // ESF
+		for (i = 0; i < 12; i++) {
+			a = (unsigned char) (readcas[i] >> (8*span)) & 0xff;
+			rxs = (a & 0xf);
+			if (!(ts->span.chans[2*i+1]->sig & DAHDI_SIG_CLEAR)) {
+				/* XXX Not really reset on every trans! XXX */
+				if (ts->span.chans[2*i+1]->rxsig != rxs) {
+					dahdi_rbsbits(ts->span.chans[2*i+1], rxs);
+				}
+			}
+			rxs = (a >> 4) & 0xf;
+			if (!(ts->span.chans[2*i]->sig & DAHDI_SIG_CLEAR)) {
+				/* XXX Not really reset on every trans! XXX */
+				if (ts->span.chans[2*i]->rxsig != rxs) {
+					dahdi_rbsbits(ts->span.chans[2*i], rxs);
+				}
+			}
+		}
+	}
+}
+
+static void __ap4_check_alarms(struct ap4 *wc, int span)
+{
+	unsigned char c;
+	int alarms;
+	int x,j;
+	struct ap4_span *ts = wc->tspans[span];
+	unsigned int e1_cfg;
+
+	if (!(ts->span.flags & DAHDI_FLAG_RUNNING))
+		return;
+
+	/* Assume no alarms */
+	alarms = DAHDI_ALARM_NONE;
+
+	/* And consider only carrier alarms */
+	ts->span.alarms &= (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE | DAHDI_ALARM_NOTOPEN);
+
+	if (ts->span.lineconfig & DAHDI_CONFIG_NOTOPEN) {
+		for (x=0,j=0;x < ts->span.channels;x++)
+			if ((ts->span.chans[x]->flags & DAHDI_FLAG_OPEN)
+#ifdef CONFIG_DAHDI_NET
+					||
+			    (ts->span.chans[x]->flags & DAHDI_FLAG_NETDEV)
+#endif
+			    )
+				j++;
+		if (!j)
+			alarms |= DAHDI_ALARM_NOTOPEN;
+	}
+
+/* le status e configuracao do E1 */
+	if (wc->tspans[span]->spantype == TYPE_E1) {
+		c = ((*(wc->membase+AP_E1_STATUS_REG))>>(8*span));
+		e1_cfg = ((*(wc->membase+AP_E1_CONFIG_REG))>>(8*span));
+
+		if ((c & AP_E1_LOS_STATUS)||(c & AP_E1_BFAE_STATUS)||(c & AP_E1_AIS_STATUS)) {
+			if (ts->alarmcount >= alarmdebounce)
+				alarms |= DAHDI_ALARM_RED;
+			else
+				ts->alarmcount++;
+		} else
+			ts->alarmcount = 0;
+
+		if ( c & AP_E1_MFAE_STATUS )
+			alarms |= DAHDI_ALARM_BLUE;
+
+		if ( (!(c & AP_E1_CAS_STATUS)) && (e1_cfg & AP_E1_PCM30_CONFIG))
+			alarms |= DAHDI_ALARM_BLUE;
+	} else {
+		c = ((*(wc->membase+AP_E1_STATUS_REG))>>(8*span));
+		if (c & AP_E1_LOS_STATUS) {
+			if (ts->alarmcount >= alarmdebounce)
+				alarms |= DAHDI_ALARM_RED;
+			else
+				ts->alarmcount++;
+		} else
+			ts->alarmcount = 0;
+		c = wc->hw_regs->t1_status >> (8 * span);
+		if (!(c & AP4_T1_FRAME_SYNC))
+			alarms |= DAHDI_ALARM_RED;
+	}
+
+	if (((!ts->span.alarms) && alarms) ||
+	    (ts->span.alarms && (!alarms)))
+		wc->checktiming = 1;
+
+	/* Keep track of recovering */
+	if ((!alarms) && ts->span.alarms)
+		ts->alarmtimer = DAHDI_ALARMSETTLE_TIME;
+	if (ts->alarmtimer)
+		alarms |= DAHDI_ALARM_RECOVER;
+
+
+	// If receiving alarms, go into Yellow alarm state
+	if (alarms && !(ts->spanflags & FLAG_SENDINGYELLOW)) {
+		printk("Setting yellow alarm on span %d\n", span + 1);
+		e1_cfg = *(wc->membase+AP_E1_CONFIG_REG);
+		e1_cfg |= (AP_E1_RAI_CONFIG<<(8*span));
+		*(wc->membase+AP_E1_CONFIG_REG) = e1_cfg;
+		ts->spanflags |= FLAG_SENDINGYELLOW;
+	} else if ((!alarms) && (ts->spanflags & FLAG_SENDINGYELLOW)) {
+		printk("Clearing yellow alarm on span %d\n", span + 1);
+		e1_cfg = *(wc->membase+AP_E1_CONFIG_REG);
+		e1_cfg &= ~(AP_E1_RAI_CONFIG<<(8*span));
+		*(wc->membase+AP_E1_CONFIG_REG) = e1_cfg;
+		ts->spanflags &= ~FLAG_SENDINGYELLOW;
+	}
+
+	// Re-check the timing source when we enter/leave alarm, not withstanding yellow alarm
+	if (c & AP_E1_RAI_STATUS)
+		alarms |= DAHDI_ALARM_YELLOW;
+
+	if (ts->span.mainttimer || ts->span.maintstat)
+		alarms |= DAHDI_ALARM_LOOPBACK;
+
+	ts->span.alarms = alarms;
+	dahdi_alarm_notify(&ts->span);
+}
+
+static void __ap4_do_counters(struct ap4 *wc)
+{
+	int span;
+
+	for (span=0;span<wc->numspans;span++) {
+		struct ap4_span *ts = wc->tspans[span];
+		int docheck=0;
+		if (ts->loopupcnt || ts->loopdowncnt)
+			docheck++;
+		if (ts->alarmtimer) {
+			if (!--ts->alarmtimer) {
+				docheck++;
+				ts->span.alarms &= ~(DAHDI_ALARM_RECOVER);
+			}
+		}
+		if (docheck) {
+			if (!polling)
+				__ap4_check_alarms(wc, span);
+			dahdi_alarm_notify(&ts->span);
+		}
+	}
+}
+
+static inline void __handle_leds(struct ap4 *wc)
+{
+	int x, span_status;
+	#define MAX_BLINKTIMER	0x14
+
+	for (x=0;x<wc->numspans;x++) {
+		struct ap4_span *ts = wc->tspans[x];
+		/* le status do E1 (para avaliar LOS) */
+		span_status = ((*(wc->membase+AP_E1_STATUS_REG))>>(8*x));
+		if (ts->span.flags & DAHDI_FLAG_RUNNING) {
+			if(span_status&AP_E1_LOS_STATUS) {
+				if (wc->blinktimer[x] >= (altab[wc->alarmpos[x]] /*>> 1*/)) {
+					__ap4_set_led(wc, x, AP_ON);
+				}
+				if (wc->blinktimer[x] >= (MAX_BLINKTIMER-1)) {
+					__ap4_set_led(wc, x, AP_OFF);
+				}
+				wc->blinktimer[x] += 1;
+			} else if (ts->span.alarms & (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE)) {
+				if (wc->blinktimer[x] >= (altab[wc->alarmpos[x]] /*>> 1*/)) {
+					__ap4_set_led(wc, x, AP_ON);
+				}
+				if (wc->blinktimer[x] >= (MAX_BLINKTIMER-2)) {
+					__ap4_set_led(wc, x, AP_OFF);
+				}
+				wc->blinktimer[x] += 3;
+			} /*else if (ts->span.alarms & DAHDI_ALARM_YELLOW) {
+				// Yellow Alarm
+				__ap4_set_led(wc, x, AP_ON);
+			} else if (ts->span.mainttimer || ts->span.maintstat) {
+
+				if (wc->blinktimer == (altab[wc->alarmpos] >> 1)) {
+					__ap4_set_led(wc, x, AP_GREEN);
+				}
+				if (wc->blinktimer == 0xf) {
+					__ap4_set_led(wc, x, AP_OFF);
+				}
+
+			} */else {
+				/* No Alarm */
+				__ap4_set_led(wc, x, AP_ON);
+			}
+		}	else
+				__ap4_set_led(wc, x, AP_OFF);
+
+		if (wc->blinktimer[x] > MAX_BLINKTIMER) {
+			wc->blinktimer[x] = 0;
+			wc->alarmpos[x]++;
+			if (wc->alarmpos[x] >= (sizeof(altab) / sizeof(altab[0])))
+				wc->alarmpos[x] = 0;
+		}
+
+	}
+}
+
+
+DAHDI_IRQ_HANDLER(ap4_interrupt)
+{
+	struct ap4 *wc = dev_id;
+	unsigned long flags;
+	int x;
+	static unsigned int val, cfg;
+	unsigned int cnt_irq_misses;
+	static unsigned int cnt_tmp;
+	int ret = 0;
+
+	/* retorna se interrupcao nao foi habilitada ou nao esta ativa */
+	cfg = *(wc->membase + AP_INT_CONTROL_REG);
+	if((cfg & AP_INT_CTL_ENABLE) == 0 || (cfg & AP_INT_CTL_ACTIVE) == 0) {
+		ret = 0;
+		goto out;
+	}
+	/* se chegamos aqui eh porque a interrupcao esta habilitada
+	 * e esta ativa, ou seja, foi gerada pelo nosso cartao.
+	 * Agora damos o ack da interrupcao */
+	val = *(wc->membase + AP_CLEAR_IRQ_REG);
+
+	/* conta interrupcoes perdidas */
+	if (wc->flag_1st_irq > 0) {
+		// nao considera as primeiras passagens pela rotina
+		cnt_irq_misses = (*(wc->membase+AP_CNT_IRQ_REG));
+		// so considera int. para o cartao
+		if(cnt_irq_misses) {
+			wc->flag_1st_irq--;
+			*(wc->membase+AP_CNT_IRQ_REG)=0;
+		}
+		// zera erro de CRC
+		cnt_tmp = (*(wc->membase + AP_CNT_CRC_REG));
+	} else {
+		// neste registro da FPGA temos o numero de interrupcoes que aconteceram
+		// desde o ultimo reset do contador de interrupcoes. O normal eh ler 1.
+		cnt_irq_misses = (*(wc->membase+AP_CNT_IRQ_REG));
+		// Se for zero significa que a interrupcao nao foi gerada pelo nosso cartao
+		if(cnt_irq_misses == 0) {
+			if(debug) printk("Interrupcao gerada mas nao pela FPGA?!\n");
+			ret = 0;
+			goto out;
+		}
+		// reseta o contador
+		*(wc->membase+AP_CNT_IRQ_REG)=0;
+		for(x=0;x<(wc->numspans);x++)
+			wc->ddev->irqmisses += (cnt_irq_misses-1);
+	}
+
+	if (!wc->spansstarted) {
+		/* Not prepped yet! */
+		ret = 0;
+		goto out;
+	}
+
+	wc->intcount++;
+
+#ifdef ENABLE_WORKQUEUES
+	int cpus = num_online_cpus();
+	atomic_set(&wc->worklist, wc->numspans);
+	if (wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING)
+		ap4_queue_work(wc->workq, &wc->tspans[0]->swork, 0);
+	else
+		atomic_dec(&wc->worklist);
+	if (wc->numspans > 1) {
+		if (wc->tspans[1]->span.flags & DAHDI_FLAG_RUNNING)
+			ap4_queue_work(wc->workq, &wc->tspans[1]->swork, 1 % cpus);
+		else
+			atomic_dec(&wc->worklist);
+	}
+	if (wc->numspans == 4) {
+		if (wc->tspans[2]->span.flags & DAHDI_FLAG_RUNNING)
+			ap4_queue_work(wc->workq, &wc->tspans[2]->swork, 2 % cpus);
+		else
+			atomic_dec(&wc->worklist);
+		if (wc->tspans[3]->span.flags & DAHDI_FLAG_RUNNING)
+			ap4_queue_work(wc->workq, &wc->tspans[3]->swork, 3 % cpus);
+		else
+			atomic_dec(&wc->worklist);
+	}
+#else
+	if (tdm_loop == 1)
+		ap4_tdm_loop(wc);
+	else {
+		ap4_receiveprep(wc);
+		ap4_transmitprep(wc);
+	}
+#endif
+
+	// Estatisticas a cada 128ms
+	if(!(wc->intcount&0x7f)){
+		clock_source = wc->hw_regs->clock_source;
+		cnt_tmp = (*(wc->membase + AP_CNT_CV_REG));
+		for(x=0;x<(wc->numspans);x++)
+			wc->tspans[x]->span.count.bpv += (cnt_tmp>>(8*x))&0xff;
+		cnt_tmp = (*(wc->membase + AP_CNT_CRC_REG));
+		for(x=0;x<(wc->numspans);x++)
+			wc->tspans[x]->span.count.crc4 += (cnt_tmp>>(8*x))&0xff;
+		cnt_tmp = (*(wc->membase + AP_CNT_SLIP_REG));
+		for(x=0;x<(wc->numspans);x++) {
+			if (((cnt_tmp>>(8*x))&0xff) && (!(wc->tspans[x]->span.alarms & DAHDI_ALARM_RED)) ){
+				wc->tspans[x]->slipcount++;
+				if(debug) printk("Slip detected on span %d: slipcount = %d\n", x+1, wc->tspans[x]->slipcount);
+			}
+		}
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	__handle_leds(wc);
+
+	__ap4_do_counters(wc);
+
+	//x = wc->intcount & 15;
+	x = wc->intcount & 7;
+	switch(x) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		__ap4_check_alarms(wc, x);
+		break;
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		__ap4_check_sigbits(wc, x - 4);
+		break;
+	}
+
+	if (wc->checktiming > 0)
+		__ap4_set_timing_source_auto(wc);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	/* IRQ was treated */
+	ret = 1;
+out:
+#ifdef AP400_HDLC
+	/* Call AP400_HDLC_CARD IRQ handler before leave */
+	ret |= ap400_intr_handler(irq, wc->hdlc_card);
+#endif
+
+	return IRQ_RETVAL(ret);
+}
+
+
+static int __devinit ap4_launch(struct ap4 *wc)
+{
+	int x;
+	unsigned long flags;
+
+	if (wc->tspans[0]->span.flags & DAHDI_FLAG_REGISTERED)
+		return 0;
+	printk("%s: Launching card: %d\n", wc->variety, wc->order);
+
+	/* Setup serial parameters and system interface */
+	for (x=0;x<4;x++) {
+		//ap4_serial_setup(wc, x);
+		wc->globalconfig = 1;
+	}
+
+	for (x=0; x<wc->numspans; x++)
+		list_add_tail(&wc->tspans[x]->span.device_node,
+				&wc->ddev->spans);
+	if (dahdi_register_device(wc->ddev, &wc->dev->dev)) {
+		printk(KERN_ERR "Unable to register span %s\n", wc->tspans[0]->span.name);
+		return -1; /* FIXME: proper error handling */
+	}
+	wc->checktiming = 1;
+	spin_lock_irqsave(&wc->reglock, flags);
+//	__ap4_set_timing_source(wc,4);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+#ifdef ENABLE_TASKLETS
+	tasklet_init(&wc->ap4_tlet, ap4_tasklet, (unsigned long)wc);
+#endif
+	return 0;
+}
+
+
+static int ap4xx_liu_reset(struct ap4 *wc)
+{
+ 	unsigned int jiffies_hold = jiffies;
+	*(wc->membase+AP_LEDS_REG) |= AP_LIU_RESET_BIT;
+	while(jiffies<=(jiffies_hold+2));
+	*(wc->membase+AP_LEDS_REG) &= ~AP_LIU_RESET_BIT;
+	return 0;
+}
+
+
+static int ap4xx_bus_test(struct ap4 *wc)
+{
+	int tst_result = 0;
+	unsigned int val;
+
+	*(wc->membase+AP_E1_CONFIG_REG) = 0xAAAAAAAA;
+	*wc->membase = 0; // flush
+	val = *(wc->membase+AP_E1_CONFIG_REG);
+	if(val != 0xAAAAAAAA) {
+		printk("Escrito 0xAAAAAAAA, lido 0x%08X!\n", val);
+		tst_result++;
+	}
+	*(wc->membase+AP_E1_CONFIG_REG) = 0x55555555;
+	*wc->membase = 0; // flush
+	val = *(wc->membase+AP_E1_CONFIG_REG);
+	if(val != 0x55555555) {
+		printk("Escrito 0x55555555, lido 0x%08X!\n", val);
+		tst_result++;
+	}
+	*(wc->membase+AP_E1_CONFIG_REG) = 0xFFFFFFFF;
+	*wc->membase = 0; // flush
+	val = *(wc->membase+AP_E1_CONFIG_REG);
+	if(val != 0xFFFFFFFF) {
+		printk("Escrito 0xFFFFFFFF, lido 0x%08X!\n", val);
+		tst_result++;
+	}
+	*(wc->membase+AP_E1_CONFIG_REG) = 0x00000000;
+	*wc->membase = 0xFFFFFFFF; // flush
+	val = *(wc->membase+AP_E1_CONFIG_REG);
+	if(val != 0x00000000) {
+		printk("Escrito 0x00000000, lido 0x%08X!\n", val);
+		tst_result++;
+	}
+	return tst_result;
+}
+
+#ifdef TIMER_DEBUG
+void ap4xx_opt_timeout(unsigned long arg)
+{
+	struct pci_dev *dev = (struct pci_dev *)arg;
+	struct ap4 *wc = pci_get_drvdata(dev);
+
+//	ap_debugk("wc->tspans[0]->span.chans[1].readchunk[1] = 0x%02x\n", wc->tspans[0]->span.chans[0].readchunk[1]);
+//	ap_debugk("e1s_cfg = 0x%08x\n", *(wc->membase+AP_E1_CONFIG_REG));
+//	ap_debugk("e1_status = 0x%08x\n", *(wc->membase + AP_E1_STATUS_REG));
+//	ap_debugk("clk_cfg = 0x%08x\n", *(wc->membase+0x07));
+//	ap_debugk("e1_data = 0x%08x\n", *(wc->membase + (AP_DATA_BASE + 1)));
+//	ap_debugk("cas_data = 0x%08x\n", *(wc->membase + AP_CAS_BASE));
+
+	// dispara timer novamente
+	init_timer(&ap4xx_opt_timer);
+	ap4xx_opt_timer.function = ap4xx_opt_timeout;
+	ap4xx_opt_timer.data = arg;
+	ap4xx_opt_timer.expires = jiffies + (delay/4);
+	add_timer(&ap4xx_opt_timer);
+
+}
+#endif
+
+static inline int ap4_card_detect (struct ap4 *wc) {
+	int i;
+	if ((wc->hw_regs->card_id != AP4XX_CARD_ID) &&
+			(wc->hw_regs->card_id != APE4XX_CARD_ID)) {
+		printk("AP400: Unknown card ID(0x%08X)! Aborting...\n", wc->hw_regs->card_id);
+		return -EPERM;
+	}
+	// Test bus integrity
+	for (i=0; i < 1000; i++) {
+		if (ap4xx_bus_test(wc)) {
+			printk("AP400: Bus integrity test failed! Aborting...\n");
+			return -EIO;
+		}
+	}
+	printk("AP400: Bus integrity OK!\n");
+
+	wc->fpgaver = wc->hw_regs->fpga_ver;
+	wc->numspans = wc->hw_regs->span_num;
+	wc->hwid = ((*(wc->membase+AP_HWCONFIG_REG))&AP_HWID_MASK)>>4;
+
+	if ((wc->hwid == AP_HWID_1E1_RJ && wc->numspans != 1) ||
+			(wc->hwid == AP_HWID_2E1_RJ && wc->numspans != 2) ||
+			(wc->hwid == AP_HWID_4E1_RJ && wc->numspans != 4)) {
+		printk("AP400: Incompatible Hardware ID(0x%02x)! Aborting...\n", wc->hwid);
+		return -EIO;
+	}
+
+	if (wc->hw_regs->card_id == AP4XX_CARD_ID)
+		switch (wc->numspans) {
+		case 1:
+			wc->dt = (struct devtype *) &ap401;
+			break;
+		case 2:
+			wc->dt = (struct devtype *) &ap402;
+			break;
+		case 4:
+			wc->dt = (struct devtype *) &ap404;
+			break;
+		default:
+			printk("AP400: Unsupported spans number(%d)! Aborting...\n",
+					wc->numspans);
+			return -EPERM;
+		}
+	else
+		switch (wc->numspans) {
+		case 1:
+			wc->dt = (struct devtype *) &ape401;
+			break;
+		case 2:
+			wc->dt = (struct devtype *) &ape402;
+			break;
+		case 4:
+			wc->dt = (struct devtype *) &ape404;
+			break;
+		default:
+			printk("APE400: Unsupported spans number(%d)! Aborting...\n",
+					wc->numspans);
+			return -EPERM;
+	}
+
+	wc->variety = wc->dt->desc;
+	printk("Found a %s (firmware version %d.%d) at base address %08lx, remapped to %p\n",
+			wc->variety, wc->fpgaver >> 8, wc->fpgaver & 0xFF,
+			wc->memaddr, wc->membase);
+
+	return 0;
+}
+
+static void __devexit ap4_remove_one(struct pci_dev *pdev);
+
+static int __devinit ap4_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	int res;
+	struct ap4 *wc;
+	int x,f;
+	int basesize;
+	static int initd_ifaces=0;
+	// Initialize pointer struct
+	if(!initd_ifaces){
+		memset((void *)cards,0,(sizeof(struct ap4 *))*MAX_AP4_CARDS);
+		initd_ifaces=1;
+	}
+
+	if ((res = pci_enable_device(pdev)) != 0) {
+		goto out;
+	}
+	// Allocate card struct
+	wc = kmalloc(sizeof(struct ap4), GFP_KERNEL);
+	if (wc == NULL) {
+		res = -ENOMEM;
+		goto out;
+	}
+
+	memset(wc, 0x0, sizeof(struct ap4));
+	spin_lock_init(&wc->reglock);
+
+	basesize = DAHDI_MAX_CHUNKSIZE * 32 * 2 * 4;
+
+	// Request PCI regions
+	if ((res = pci_request_regions(pdev, "ap400")) != 0) {
+		printk("AP400: Unable to request regions!\n");
+		goto out;
+	}
+
+	// Remap PCI address
+	wc->memaddr = pci_resource_start(pdev, 2);
+	wc->memlen = pci_resource_len(pdev, 2);
+	wc->membase = ioremap_nocache(wc->memaddr, wc->memlen);
+	if(wc->membase == NULL) {
+		printk("AP400: ioremap failed!\n");
+		res = -EIO;
+		goto out;
+	}
+	wc->hw_regs = (struct ap4_regs *) wc->membase;
+
+	// Detect Card model
+	if ((res = ap4_card_detect(wc)) != 0)
+		goto out;
+
+	ap4xx_liu_reset(wc);
+
+	// This rids of the Double missed interrupt message after loading
+	wc->last0 = 1;
+
+	wc->dev = pdev;
+
+	// 32 channels, Double-buffer, Read/Write, 4 spans
+	wc->writechunk = kmalloc(basesize * 2, GFP_KERNEL);
+	if (!wc->writechunk) {
+		printk("%s: Unable to allocate memory!\n", wc->variety);
+		res = -ENOMEM;
+		goto out;
+	}
+
+	// Read is after the whole write piece (in words)
+	wc->readchunk = wc->writechunk + basesize / 4;
+
+
+	// Initialize Write/Buffers to all blank data
+	memset((void *) wc->writechunk, 0x00, basesize);
+	memset((void *) wc->readchunk, 0xff, basesize);
+
+	/* Keep track of which device we are */
+	pci_set_drvdata(pdev, wc);
+
+	/* inicializa contador de interrupcao */
+	wc->intcount = 0;
+
+	for(x = 0; x < MAX_AP4_CARDS; x++) {
+		if (!cards[x]) break;
+	}
+
+	if (x >= MAX_AP4_CARDS) {
+		printk("No cards[] slot available!!\n");
+		res = -ENOMEM;
+		goto out;
+	}
+
+	wc->num = x;
+	cards[x] = wc;
+
+	/* Allocate pieces we need here, consider 31 channels for E1*/
+	for (x=0;x<4;x++) {
+		wc->tspans[x] = kmalloc(sizeof(struct ap4_span), GFP_KERNEL);
+		if (wc->tspans[x]) {
+			memset(wc->tspans[x], 0, sizeof(struct ap4_span));
+			wc->tspans[x]->spantype = TYPE_E1;
+		} else {
+			res = -ENOMEM;
+			goto out;
+		}
+		for (f = 0; f < 31; f++) {
+			if (!(wc->tspans[x]->chans[f] = kmalloc(sizeof(*wc->tspans[x]->chans[f]), GFP_KERNEL))) {
+				res = -ENOMEM;
+				goto out;
+			}
+			memset(wc->tspans[x]->chans[f], 0, sizeof(*wc->tspans[x]->chans[f]));
+		}
+#ifdef ENABLE_WORKQUEUES
+		INIT_WORK(&wc->tspans[x]->swork, workq_handlespan, wc->tspans[x]);
+#endif
+		wc->tspans[x]->spanflags |= wc->dt->flags;
+	}
+
+	if (request_irq(pdev->irq, ap4_interrupt, IRQF_DISABLED | IRQF_SHARED, "ap400", wc))
+	{
+		printk("%s: Unable to request IRQ %d\n", wc->variety, pdev->irq);
+		res = -EIO;
+		goto out;
+	}
+
+	wc->ddev = dahdi_create_device();
+	wc->ddev->location = kasprintf(GFP_KERNEL, "PCI Bus %02d Slot %02d",
+			wc->dev->bus->number, PCI_SLOT(wc->dev->devfn) + 1);
+	if (!wc->ddev->location)
+		return -ENOMEM; /* FIXME: proper error handling */
+	wc->ddev->manufacturer = "Aligera";
+	wc->ddev->devicetype = wc->variety;
+	wc->ddev->irqmisses = 0;
+	init_spans(wc);
+
+	/* Launch cards as appropriate */
+	x = 0;
+	for(;;) {
+		/* Find a card to activate */
+		f = 0;
+		for (x=0;cards[x];x++) {
+			if (cards[x]->order <= highestorder) {
+				ap4_launch(cards[x]);
+				if (cards[x]->order == highestorder)
+					f = 1;
+			}
+		}
+		/* If we found at least one, increment the highest order and search again, otherwise stop */
+		if (f)
+			highestorder++;
+		else
+			break;
+	}
+
+#ifdef APEC_SUPPORT
+	if (wc->fpgaver >= 0x0400)
+		wc->apec_enable = 1;
+#endif
+
+#ifdef TIMER_DEBUG
+	// dispara timer de debug
+	init_timer(&ap4xx_opt_timer);
+	ap4xx_opt_timer.function = ap4xx_opt_timeout;
+	ap4xx_opt_timer.data = (unsigned long) pdev;
+	ap4xx_opt_timer.expires = jiffies + 100;
+	add_timer(&ap4xx_opt_timer);
+#endif
+
+	/* Initialize HDLC_CARD */
+#ifdef AP400_HDLC
+	u8 __iomem *base_addr[3];
+	unsigned int bar_size[3];
+	int i;
+	base_addr[2] = (void *) wc->membase;
+	bar_size[2] = wc->memlen;
+	for (i = 0; i < 2; i++) {
+		bar_size[i] = (u32) pci_resource_len(pdev, i);
+		base_addr[i] = ioremap_nocache(pci_resource_start(pdev, i),
+								bar_size[i]);
+		if (base_addr[i] == NULL) {
+			printk(KERN_ERR "Memory map failed\n");
+			res = -ENODEV;
+			goto out;
+		}
+	}
+	ap400_card_init(&wc->hdlc_card, base_addr, bar_size);
+	ap400_intr_enable(wc->hdlc_card);
+#endif
+
+	res = 0;
+out:
+	if (res != 0) {
+		ap4_remove_one(pdev);
+	}
+	return res;
+}
+
+static void __devexit ap4_remove_one(struct pci_dev *pdev)
+{
+	struct ap4 *wc = pci_get_drvdata(pdev);
+	int x;
+
+	if (wc) {
+		ap_debugk("desabilita interrupcao!\n");
+		// desabilita interrupcao
+		*(wc->membase + AP_INT_CONTROL_REG) &= ~AP_INT_CTL_ENABLE;
+
+#ifdef APEC_SUPPORT
+		// Stop echo cancellation module
+		ap4_apec_release(wc);
+#endif
+		/* Unregister spans */
+		dahdi_unregister_device(wc->ddev);
+		kfree(wc->ddev->location);
+		dahdi_free_device(wc->ddev);
+#ifdef ENABLE_WORKQUEUES
+		if (wc->workq) {
+			flush_workqueue(wc->workq);
+			destroy_workqueue(wc->workq);
+		}
+#endif
+
+#ifdef TIMER_DEBUG
+		del_timer(&ap4xx_opt_timer);
+#endif
+
+		wc->hw_regs = NULL;
+		if(wc->membase)
+			iounmap((void *)wc->membase);
+
+		/* Immediately free resources */
+		kfree((void *) wc->writechunk);
+
+#ifdef AP400_HDLC
+		/* Remove HDLC Card */
+		ap400_card_remove(wc->hdlc_card);
+		if (wc->hdlc_card->cfg_base_addr)
+			iounmap(wc->hdlc_card->cfg_base_addr);
+		if (wc->hdlc_card->buf_base_addr)
+			iounmap(wc->hdlc_card->buf_base_addr);
+		kfree(wc->hdlc_card);
+#endif
+		free_irq(pdev->irq, wc);
+
+		cards[wc->num] = NULL;
+		for (x=0;x<wc->numspans;x++) {
+			if (wc->tspans[x])
+				kfree(wc->tspans[x]);
+		}
+		kfree(wc);
+	}
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	pci_set_drvdata(pdev, NULL);
+	printk(KERN_INFO "AP400 driver removed\n");
+}
+
+
+static struct pci_device_id ap4_pci_tbl[] __devinitdata =
+{
+	{ PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_AP4XX), },
+	{ 0, }
+};
+
+
+static struct pci_driver ap4_driver = {
+	.name = 	"Unified ap4xx driver",
+	.probe = 	ap4_init_one,
+#ifdef LINUX26
+	.remove =	__devexit_p(ap4_remove_one),
+#else
+	.remove =	ap4_remove_one,
+#endif
+	.id_table = ap4_pci_tbl,
+};
+
+static int __init ap4_init(void)
+{
+	int res;
+	printk("Unified AP4XX PCI Card Driver\n");
+	res = dahdi_pci_module(&ap4_driver);
+	if (res) {
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit ap4_cleanup(void)
+{
+	printk("Unified AP4XX PCI Card Driver Cleanup\n");
+	pci_unregister_driver(&ap4_driver);
+}
+
+
+MODULE_AUTHOR("Aligera (aligera@aligera.com.br)");
+MODULE_DESCRIPTION("Unified AP4XX PCI Card Driver");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+module_param(debug, int, 0600);
+module_param(loopback, int, 0600);
+module_param(noburst, int, 0600);
+module_param(debugslips, int, 0600);
+module_param(polling, int, 0600);
+module_param(timingcable, int, 0600);
+module_param(t1e1override, int, 0600);
+module_param(alarmdebounce, int, 0600);
+module_param(j1mode, int, 0600);
+
+MODULE_DEVICE_TABLE(pci, ap4_pci_tbl);
+
+module_init(ap4_init);
+module_exit(ap4_cleanup);
diff --git a/drivers/dahdi/ap400/apec.c b/drivers/dahdi/ap400/apec.c
new file mode 100644
index 0000000..b43655e
--- /dev/null
+++ b/drivers/dahdi/ap400/apec.c
@@ -0,0 +1,390 @@
+/*
+ * AP400 Echo Cancelation Hardware support
+ *
+ * Written by Wagner Gegler <aligera@aligera.com.br>
+ *
+ * Based on previous work written by Mark Spencer <markster@digium.com>
+ *
+ * Copyright (C) 2005-2006 Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/version.h>
+#include <linux/delay.h>
+
+#include "apec.h"
+#include "oct6100api/oct6100_api.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
+#include <linux/config.h>
+#else
+#include <linux/autoconf.h>
+#endif
+
+/* API for Octasic access */
+UINT32 Oct6100UserGetTime(tPOCT6100_GET_TIME f_pTime)
+{
+	/* Why couldn't they just take a timeval like everyone else? */
+	struct timeval tv;
+	unsigned long long total_usecs;
+	unsigned int mask = ~0;
+
+	do_gettimeofday(&tv);
+	total_usecs = (((unsigned long long)(tv.tv_sec)) * 1000000) +
+				  (((unsigned long long)(tv.tv_usec)));
+	f_pTime->aulWallTimeUs[0] = (total_usecs & mask);
+	f_pTime->aulWallTimeUs[1] = (total_usecs >> 32);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserMemSet(PVOID f_pAddress, UINT32 f_ulPattern, UINT32 f_ulLength)
+{
+	memset(f_pAddress, f_ulPattern, f_ulLength);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserMemCopy(PVOID f_pDestination, const void *f_pSource, UINT32 f_ulLength)
+{
+	memcpy(f_pDestination, f_pSource, f_ulLength);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserCreateSerializeObject(tPOCT6100_CREATE_SERIALIZE_OBJECT f_pCreate)
+{
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDestroySerializeObject(tPOCT6100_DESTROY_SERIALIZE_OBJECT f_pDestroy)
+{
+#ifdef OCTASIC_DEBUG
+	printk("I should never be called! (destroy serialize object)\n");
+#endif
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserSeizeSerializeObject(tPOCT6100_SEIZE_SERIALIZE_OBJECT f_pSeize)
+{
+	/* Not needed */
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserReleaseSerializeObject(tPOCT6100_RELEASE_SERIALIZE_OBJECT f_pRelease)
+{
+	/* Not needed */
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteApi(tPOCT6100_WRITE_PARAMS f_pWriteParams)
+{
+	oct_write(f_pWriteParams->pProcessContext, f_pWriteParams->ulWriteAddress, f_pWriteParams->usWriteData);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteSmearApi(tPOCT6100_WRITE_SMEAR_PARAMS f_pSmearParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pSmearParams->ulWriteLength;x++) {
+		oct_write(f_pSmearParams->pProcessContext, f_pSmearParams->ulWriteAddress + (x << 1), f_pSmearParams->usWriteData);
+	}
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteBurstApi(tPOCT6100_WRITE_BURST_PARAMS f_pBurstParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pBurstParams->ulWriteLength;x++) {
+		oct_write(f_pBurstParams->pProcessContext, f_pBurstParams->ulWriteAddress + (x << 1), f_pBurstParams->pusWriteData[x]);
+	}
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverReadApi(tPOCT6100_READ_PARAMS f_pReadParams)
+{
+	*(f_pReadParams->pusReadData) = oct_read(f_pReadParams->pProcessContext, f_pReadParams->ulReadAddress);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverReadBurstApi(tPOCT6100_READ_BURST_PARAMS f_pBurstParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pBurstParams->ulReadLength;x++) {
+		f_pBurstParams->pusReadData[x] = oct_read(f_pBurstParams->pProcessContext, f_pBurstParams->ulReadAddress + (x << 1));
+	}
+	return cOCT6100_ERR_OK;
+}
+
+#if 0
+#define cOCT6100_ECHO_OP_MODE_DIGITAL cOCT6100_ECHO_OP_MODE_HT_FREEZE
+#else
+#define cOCT6100_ECHO_OP_MODE_DIGITAL cOCT6100_ECHO_OP_MODE_POWER_DOWN
+#endif
+
+struct apec_s {
+	tPOCT6100_INSTANCE_API pApiInstance;
+	UINT32 aulEchoChanHndl[128];
+	int chanflags[128];
+	int ecmode[128];
+	int numchans;
+};
+
+#define FLAG_DTMF	 (1 << 0)
+#define FLAG_MUTE	 (1 << 1)
+#define FLAG_ECHO	 (1 << 2)
+
+static void apec_setecmode(struct apec_s *apec, int channel, int mode)
+{
+	tOCT6100_CHANNEL_MODIFY *modify;
+	UINT32 ulResult;
+
+	if (apec->ecmode[channel] == mode)
+		return;
+	modify = kmalloc(sizeof(tOCT6100_CHANNEL_MODIFY), GFP_ATOMIC);
+	if (!modify) {
+		printk("APEC: Unable to allocate memory for setec!\n");
+		return;
+	}
+	Oct6100ChannelModifyDef(modify);
+	modify->ulEchoOperationMode = mode;
+	modify->ulChannelHndl = apec->aulEchoChanHndl[channel];
+	ulResult = Oct6100ChannelModify(apec->pApiInstance, modify);
+	if (ulResult != GENERIC_OK) {
+		printk("Failed to apply echo can changes on channel %d!\n", channel);
+	} else {
+#ifdef OCTASIC_DEBUG
+		printk("Echo can on channel %d set to %d\n", channel, mode);
+#endif
+		apec->ecmode[channel] = mode;
+	}
+	kfree(modify);
+}
+
+void apec_setec(struct apec_s *apec, int channel, int eclen)
+{
+	if (eclen) {
+		apec->chanflags[channel] |= FLAG_ECHO;
+		apec_setecmode(apec, channel, cOCT6100_ECHO_OP_MODE_HT_RESET);
+		apec_setecmode(apec, channel, cOCT6100_ECHO_OP_MODE_NORMAL);
+	} else {
+		apec->chanflags[channel] &= ~FLAG_ECHO;
+		if (apec->chanflags[channel] & (FLAG_DTMF | FLAG_MUTE)) {
+			apec_setecmode(apec, channel, cOCT6100_ECHO_OP_MODE_HT_RESET);
+			apec_setecmode(apec, channel, cOCT6100_ECHO_OP_MODE_HT_FREEZE);
+		} else
+			apec_setecmode(apec, channel, cOCT6100_ECHO_OP_MODE_DIGITAL);
+	}
+	printk("APEC: Setting EC on channel %d to %d\n", channel, eclen);
+}
+
+int apec_checkirq(struct apec_s *apec)
+{
+	tOCT6100_INTERRUPT_FLAGS InterruptFlags;
+
+	Oct6100InterruptServiceRoutineDef(&InterruptFlags);
+	Oct6100InterruptServiceRoutine(apec->pApiInstance, &InterruptFlags);
+
+	return InterruptFlags.fToneEventsPending ? 1 : 0;
+}
+
+unsigned int apec_capacity_get(void *wc)
+{
+	UINT32 ulResult;
+
+	tOCT6100_API_GET_CAPACITY_PINS CapacityPins;
+
+	Oct6100ApiGetCapacityPinsDef(&CapacityPins);
+	CapacityPins.pProcessContext = wc;
+	CapacityPins.ulMemoryType = cOCT6100_MEM_TYPE_DDR;
+	CapacityPins.fEnableMemClkOut = TRUE;
+	CapacityPins.ulMemClkFreq = cOCT6100_MCLK_FREQ_133_MHZ;
+
+	ulResult = Oct6100ApiGetCapacityPins(&CapacityPins);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk("Failed to get chip capacity, code %08x!\n", ulResult);
+		return 0;
+	}
+	return CapacityPins.ulCapacityValue;
+}
+
+struct apec_s *apec_init(void *wc, int *isalaw, int numspans, const struct firmware *firmware)
+{
+	tOCT6100_CHIP_OPEN *ChipOpen;
+	tOCT6100_GET_INSTANCE_SIZE InstanceSize;
+	tOCT6100_CHANNEL_OPEN *ChannelOpen;
+	UINT32 ulResult;
+	struct apec_s *apec;
+	int x, law;
+#ifdef CONFIG_4KSTACKS
+	unsigned long flags;
+#endif
+
+	if (!(apec = kmalloc(sizeof(struct apec_s), GFP_KERNEL)))
+		return NULL;
+
+	memset(apec, 0, sizeof(struct apec_s));
+
+	if (!(ChipOpen = kmalloc(sizeof(tOCT6100_CHIP_OPEN), GFP_KERNEL))) {
+		kfree(apec);
+		return NULL;
+	}
+
+	memset(ChipOpen, 0, sizeof(tOCT6100_CHIP_OPEN));
+
+	if (!(ChannelOpen = kmalloc(sizeof(tOCT6100_CHANNEL_OPEN), GFP_KERNEL))) {
+		kfree(apec);
+		kfree(ChipOpen);
+		return NULL;
+	}
+
+	memset(ChannelOpen, 0, sizeof(tOCT6100_CHANNEL_OPEN));
+
+	for (x=0;x<128;x++)
+		apec->ecmode[x] = -1;
+
+	apec->numchans = numspans * 32;
+	printk("APEC: echo cancellation for %d channels\n", apec->numchans);
+
+	Oct6100ChipOpenDef(ChipOpen);
+
+	/* Setup Chip Open Parameters */
+	ChipOpen->ulUpclkFreq = cOCT6100_UPCLK_FREQ_33_33_MHZ;
+	Oct6100GetInstanceSizeDef(&InstanceSize);
+
+	ChipOpen->pProcessContext = wc;
+
+	ChipOpen->pbyImageFile = firmware->data;
+	ChipOpen->ulImageSize = firmware->size;
+
+	ChipOpen->fEnableMemClkOut = TRUE;
+	ChipOpen->ulMemClkFreq = cOCT6100_MCLK_FREQ_133_MHZ;
+	ChipOpen->ulMaxChannels = apec->numchans;
+	ChipOpen->ulMemoryType = cOCT6100_MEM_TYPE_DDR;
+	ChipOpen->ulMemoryChipSize = cOCT6100_MEMORY_CHIP_SIZE_32MB;
+	ChipOpen->ulNumMemoryChips = 1;
+	ChipOpen->ulMaxTdmStreams = 4;
+	ChipOpen->aulTdmStreamFreqs[0] = cOCT6100_TDM_STREAM_FREQ_8MHZ;
+	ChipOpen->ulTdmSampling = cOCT6100_TDM_SAMPLE_AT_FALLING_EDGE;
+#if 0
+	ChipOpen->fEnableAcousticEcho = TRUE;
+#endif
+
+	ulResult = Oct6100GetInstanceSize(ChipOpen, &InstanceSize);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk("Failed to get instance size, code %08x!\n", ulResult);
+		kfree(apec);
+		return NULL;
+	}
+
+
+	apec->pApiInstance = vmalloc(InstanceSize.ulApiInstanceSize);
+	if (!apec->pApiInstance) {
+		printk("Out of memory (can't allocate %d bytes)!\n", InstanceSize.ulApiInstanceSize);
+		kfree(apec);
+		kfree(ChipOpen);
+		kfree(ChannelOpen);
+		return NULL;
+	}
+
+	/* I don't know what to curse more in this comment, the problems caused by
+	 * the 4K kernel stack limit change or the octasic API for being so darn
+	 * stack unfriendly.  Stupid, stupid, stupid.  So we disable IRQs so we
+	 * don't run the risk of overflowing the stack while we initialize the
+	 * octasic. */
+#ifdef CONFIG_4KSTACKS
+	local_irq_save(flags);
+#endif
+	ulResult = Oct6100ChipOpen(apec->pApiInstance, ChipOpen);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk("Failed to open chip, code %08x!\n", ulResult);
+#ifdef CONFIG_4KSTACKS
+		local_irq_restore(flags);
+#endif
+		kfree(apec);
+		kfree(ChipOpen);
+		kfree(ChannelOpen);
+		return NULL;
+	}
+	for (x=0; x < 128; x++) {
+		/* execute this loop always on 4 span cards but
+		*  on 2 span cards only execute for the channels related to our spans */
+		if ((x & 0x3) < numspans) {
+			/* span timeslots are interleaved 12341234...
+		 	*  therefore, the lower 2 bits tell us which span this
+			*  timeslot/channel
+		 	*/
+			if (isalaw[x & 0x03])
+				law = cOCT6100_PCM_A_LAW;
+			else
+				law = cOCT6100_PCM_U_LAW;
+			Oct6100ChannelOpenDef(ChannelOpen);
+			ChannelOpen->pulChannelHndl = &apec->aulEchoChanHndl[x];
+			ChannelOpen->ulUserChanId = x;
+			ChannelOpen->TdmConfig.ulRinPcmLaw = law;
+			ChannelOpen->TdmConfig.ulRinStream = 0;
+			ChannelOpen->TdmConfig.ulRinTimeslot = x;
+			ChannelOpen->TdmConfig.ulSinPcmLaw = law;
+			ChannelOpen->TdmConfig.ulSinStream = 1;
+			ChannelOpen->TdmConfig.ulSinTimeslot = x;
+			ChannelOpen->TdmConfig.ulSoutPcmLaw = law;
+			ChannelOpen->TdmConfig.ulSoutStream = 2;
+			ChannelOpen->TdmConfig.ulSoutTimeslot = x;
+			ChannelOpen->TdmConfig.ulRoutPcmLaw = law;
+			ChannelOpen->TdmConfig.ulRoutStream = 3;
+			ChannelOpen->TdmConfig.ulRoutTimeslot = x;
+			ChannelOpen->VqeConfig.fEnableNlp = TRUE;
+			ChannelOpen->VqeConfig.fRinDcOffsetRemoval = TRUE;
+			ChannelOpen->VqeConfig.fSinDcOffsetRemoval = TRUE;
+
+			ChannelOpen->fEnableToneDisabler = TRUE;
+			ChannelOpen->ulEchoOperationMode = cOCT6100_ECHO_OP_MODE_DIGITAL;
+
+			ulResult = Oct6100ChannelOpen(apec->pApiInstance, ChannelOpen);
+			if (ulResult != GENERIC_OK) {
+				printk("Failed to open channel %d!\n", x);
+			}
+		}
+	}
+
+#ifdef CONFIG_4KSTACKS
+	local_irq_restore(flags);
+#endif
+	kfree(ChipOpen);
+	kfree(ChannelOpen);
+	return apec;
+}
+
+void apec_release(struct apec_s *apec)
+{
+	UINT32 ulResult;
+	tOCT6100_CHIP_CLOSE ChipClose;
+
+	Oct6100ChipCloseDef(&ChipClose);
+	ulResult = Oct6100ChipClose(apec->pApiInstance, &ChipClose);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk("Failed to close chip, code %08x!\n", ulResult);
+	}
+	vfree(apec->pApiInstance);
+	kfree(apec);
+	printk(KERN_INFO "APEC: Releasing...\n");
+}
diff --git a/drivers/dahdi/ap400/apec.h b/drivers/dahdi/ap400/apec.h
new file mode 100644
index 0000000..483b182
--- /dev/null
+++ b/drivers/dahdi/ap400/apec.h
@@ -0,0 +1,48 @@
+/*
+ * AP400 Echo Cancelation Hardware support
+ *
+ * Written by Wagner Gegler <aligera@aligera.com.br>
+ * 
+ * Based on previous work written by Mark Spencer <markster@digium.com>
+ * 
+ * Copyright (C) 2005-2006 Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef _APEC_H_
+#define _APEC_H_
+
+#include <linux/firmware.h>
+
+struct apec_s;
+
+/* From AP400 */
+unsigned int oct_read(void *card, unsigned int addr);
+void oct_write(void *card, unsigned int addr, unsigned int data);
+
+/* From APEC */
+struct apec_s *apec_init(void *wc, int *isalaw, int numspans, const struct firmware *firmware);
+unsigned int apec_capacity_get(void *wc);
+void apec_setec(struct apec_s *instance, int channel, int eclen);
+int apec_checkirq(struct apec_s *apec);
+void apec_release(struct apec_s *instance);
+
+#endif /*_APEC_H_*/
diff --git a/drivers/dahdi/opvxa1200/Kbuild b/drivers/dahdi/opvxa1200/Kbuild
new file mode 100644
index 0000000..8f90819
--- /dev/null
+++ b/drivers/dahdi/opvxa1200/Kbuild
@@ -0,0 +1,19 @@
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_OPVXA1200) += opvxa1200.o
+
+EXTRA_CFLAGS += -I$(src)/.. -Wno-undef
+
+opvxa1200-objs := base.o
+
+DAHDI_KERNEL_H_NAME:=kernel.h
+DAHDI_KERNEL_H_PATH:=$(DAHDI_INCLUDE)/dahdi/$(DAHDI_KERNEL_H_NAME)
+ifneq ($(DAHDI_KERNEL_H_PATH),)
+        DAHDI_SPAN_MODULE:=$(shell if grep -C 5 "struct dahdi_span {" $(DAHDI_KERNEL_H_PATH) | grep -q "struct module \*owner"; then echo "yes"; else echo "no"; fi)
+        DAHDI_SPAN_OPS:=$(shell if grep -q "struct dahdi_span_ops {" $(DAHDI_KERNEL_H_PATH); then echo "yes"; else echo "no"; fi)
+        ifeq ($(DAHDI_SPAN_MODULE),yes)
+                EXTRA_CFLAGS+=-DDAHDI_SPAN_MODULE
+        else
+                ifeq ($(DAHDI_SPAN_OPS),yes)
+                        EXTRA_CFLAGS+=-DDAHDI_SPAN_OPS
+                endif
+        endif
+endif
diff --git a/drivers/dahdi/opvxa1200/Makefile b/drivers/dahdi/opvxa1200/Makefile
new file mode 100644
index 0000000..baaab35
--- /dev/null
+++ b/drivers/dahdi/opvxa1200/Makefile
@@ -0,0 +1,8 @@
+ifdef KBUILD_EXTMOD
+# We only get here on kernels 2.6.0-2.6.9 .
+# For newer kernels, Kbuild will be included directly by the kernel
+# build system.
+include $(src)/Kbuild
+
+else
+endif
diff --git a/drivers/dahdi/opvxa1200/base.c b/drivers/dahdi/opvxa1200/base.c
new file mode 100644
index 0000000..25ce3c4
--- /dev/null
+++ b/drivers/dahdi/opvxa1200/base.c
@@ -0,0 +1,3065 @@
+/*
+ * OpenVox A1200P FXS/FXO Interface Driver for DAHDI Telephony interface
+ *
+ * Written by MiaoLin<miaolin@openvox.cn>
+ *
+ * Copyright (C) 2005-2010 OpenVox Communication Co. Ltd,
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ *
+ */
+
+/* Rev histroy
+ *
+ * Rev 0.10 initial version	
+ * Rev 0.11 
+ * 	fixed the led light on/off bug.
+ * 	modify some wctdm print to opvxa1200
+ * 	support firmware version 1.2, faster i/o operation, and better LED control.
+ * 
+ * Rev 0.12 patched to support new pci id 0x8519
+ * Rev 0.13 patched to remove the warning during compile under kernel 2.6.22 
+ * Rev 0.14 patched to remove the bug for ZAP_IRQ_SHARED , 3/9/2007 
+ * Rev 0.15 patched to support new pci ID 0X9532 by james.zhu, 23/10/2007
+ * Rev 0.16 support new pci id 0x9559 by Miao Lin 21/3/2008
+ * Rev 0.17 
+ *	patched a few bugs, 
+ *	add hwgain support.
+ *	fixed A800P version check
+ * Rev 1.4.9.2 
+ *		Only generate 8 channels for A800P
+ * 		Version number synced to zaptel distribution.
+ * Rev 1.4.9.2.a
+ *		Fixed freeregion.
+ * 		
+ * Rev 1.4.9.2.b
+ *    Add cid before first ring support.
+ *    New Paremeters:
+ *          	cidbeforering : set to 1 will cause the card enable cidbeforering function. default 0
+ * 		cidbuflen : length of cid buffer, in msec, default 3000 msec.
+ *              cidtimeout : time out of a ring, default 6000msec
+ *   	User must set cidstart=polarity in zapata.conf to use with this feature
+ * 		cidsignalling = signalling format send before 1st ring. most likely dtmf.
+ * 
+ * Rev 1.4.9.2.c
+ * 	add driver parameter cidtimeout.
+ * 
+ * Rev 1.4.9.2.d 
+ *  	add debug stuff to test fxs power alarm
+ *  
+ * Rev 1.4.11
+ *  	Support enhanced full scale tx/rx for FXO required by europe standard (Register 30, acim) (module parm fxofullscale)
+ *  
+ * Rev 1.4.12 2008/10/17
+ *      Fixed bug cause FXS module report fake power alarm.
+ *      Power alarm debug stuff removed.
+ * 
+ * Rev 2.0 DAHDI 2008/10/17
+ *
+ * Rev 2.0.1 add new pci id 0x9599
+ * Re 2.0.2 12/01/2009  
+       add fixedtimepolarity: set time(ms) when send polarity after 1st ring happen. 
+ *				Sometimes the dtmf cid is sent just after first ring off, and the system do not have 
+ *				enough time to start detect 1st dtmf.
+ *				0 means send polarity at the end of 1st ring.
+ *				x means send ploarity after x ms of 1st ring begin.
+ * 
+ * Rev 2.0.3 12/01/2009 
+ *        Add touch_softlockup_watchdog() in wctdm_hardware_init, to avoid cpu softlockup system message for FXS.
+ *
+ *
+ * Rev 1.4.12.4  17/04/2009 James.zhu
+ *       Changed wctdm_voicedaa_check_hook() to detect FXO battery and solved the problem with dial(dahdi/go/XXXXXXXXXX)
+ *       add alarm detection for FXO
+ *
+ * Rev 1.4.12.5 01/10/2009 james.zhu
+ *       Add jiffies for 5 second in wctdm_hardware_init
+ *
+ *
+ */ 
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <asm/io.h>
+#include <linux/sched.h>
+#include "proslic.h"
+   
+/* MiaoLin debug start */
+#include <linux/string.h>
+#include <asm/uaccess.h> 	/* get_fs(), set_fs(), KERNEL_DS */
+#include <linux/file.h> 	/* fput() */
+/* MiaoLin debug end */
+  
+
+/*
+ *  Define for audio vs. register based ring detection
+ *  
+ */
+/* #define AUDIO_RINGCHECK  */
+
+/*
+  Experimental max loop current limit for the proslic
+  Loop current limit is from 20 mA to 41 mA in steps of 3
+  (according to datasheet)
+  So set the value below to:
+  0x00 : 20mA (default)
+  0x01 : 23mA
+  0x02 : 26mA
+  0x03 : 29mA
+  0x04 : 32mA
+  0x05 : 35mA
+  0x06 : 37mA
+  0x07 : 41mA
+*/
+static int loopcurrent = 20;
+
+static int reversepolarity = 0;
+
+static alpha  indirect_regs[] =
+{
+{0,255,"DTMF_ROW_0_PEAK",0x55C2},
+{1,255,"DTMF_ROW_1_PEAK",0x51E6},
+{2,255,"DTMF_ROW2_PEAK",0x4B85},
+{3,255,"DTMF_ROW3_PEAK",0x4937},
+{4,255,"DTMF_COL1_PEAK",0x3333},
+{5,255,"DTMF_FWD_TWIST",0x0202},
+{6,255,"DTMF_RVS_TWIST",0x0202},
+{7,255,"DTMF_ROW_RATIO_TRES",0x0198},
+{8,255,"DTMF_COL_RATIO_TRES",0x0198},
+{9,255,"DTMF_ROW_2ND_ARM",0x0611},
+{10,255,"DTMF_COL_2ND_ARM",0x0202},
+{11,255,"DTMF_PWR_MIN_TRES",0x00E5},
+{12,255,"DTMF_OT_LIM_TRES",0x0A1C},
+{13,0,"OSC1_COEF",0x7B30},
+{14,1,"OSC1X",0x0063},
+{15,2,"OSC1Y",0x0000},
+{16,3,"OSC2_COEF",0x7870},
+{17,4,"OSC2X",0x007D},
+{18,5,"OSC2Y",0x0000},
+{19,6,"RING_V_OFF",0x0000},
+{20,7,"RING_OSC",0x7EF0},
+{21,8,"RING_X",0x0160},
+{22,9,"RING_Y",0x0000},
+{23,255,"PULSE_ENVEL",0x2000},
+{24,255,"PULSE_X",0x2000},
+{25,255,"PULSE_Y",0x0000},
+//{26,13,"RECV_DIGITAL_GAIN",0x4000},	// playback volume set lower
+{26,13,"RECV_DIGITAL_GAIN",0x2000},	// playback volume set lower
+{27,14,"XMIT_DIGITAL_GAIN",0x4000},
+//{27,14,"XMIT_DIGITAL_GAIN",0x2000},
+{28,15,"LOOP_CLOSE_TRES",0x1000},
+{29,16,"RING_TRIP_TRES",0x3600},
+{30,17,"COMMON_MIN_TRES",0x1000},
+{31,18,"COMMON_MAX_TRES",0x0200},
+{32,19,"PWR_ALARM_Q1Q2",0x07C0},
+{33,20,"PWR_ALARM_Q3Q4",0x2600},
+{34,21,"PWR_ALARM_Q5Q6",0x1B80},
+{35,22,"LOOP_CLOSURE_FILTER",0x8000},
+{36,23,"RING_TRIP_FILTER",0x0320},
+{37,24,"TERM_LP_POLE_Q1Q2",0x008C},
+{38,25,"TERM_LP_POLE_Q3Q4",0x0100},
+{39,26,"TERM_LP_POLE_Q5Q6",0x0010},
+{40,27,"CM_BIAS_RINGING",0x0C00},
+{41,64,"DCDC_MIN_V",0x0C00},
+{42,255,"DCDC_XTRA",0x1000},
+{43,66,"LOOP_CLOSE_TRES_LOW",0x1000},
+};
+
+
+#include <dahdi/kernel.h>
+#include <dahdi/wctdm_user.h>
+
+#include "fxo_modes.h"
+
+#define NUM_FXO_REGS 60
+
+#define WC_MAX_IFACES 128
+
+#define WC_OFFSET	4	/* Offset between transmit and receive, in bytes. */
+#define WC_SYNCFLAG	0xca1ef1ac
+
+#define WC_CNTL    	0x00
+#define WC_OPER		0x01
+#define WC_AUXC    	0x02
+#define WC_AUXD    	0x03
+#define WC_MASK0   	0x04
+#define WC_MASK1   	0x05
+#define WC_INTSTAT 	0x06
+#define WC_AUXR		0x07
+
+#define WC_DMAWS	0x08
+#define WC_DMAWI	0x0c
+#define WC_DMAWE	0x10
+#define WC_DMARS	0x18
+#define WC_DMARI	0x1c
+#define WC_DMARE	0x20
+
+#define WC_AUXFUNC	0x2b
+#define WC_SERCTL	0x2d
+#define WC_FSCDELAY	0x2f
+
+#define WC_REGBASE	0xc0
+
+#define WC_VER		0x0
+#define WC_CS		0x1
+#define WC_SPICTRL	0x2
+#define WC_SPIDATA	0x3
+
+#define BIT_SPI_BYHW 	(1 << 0)
+#define BIT_SPI_BUSY    (1 << 1)	// 0=can read/write spi, 1=spi working.
+#define BIT_SPI_START	(1 << 2)
+
+
+#define BIT_LED_CLK     (1 << 0)	// MiaoLin add to control the led. 
+#define BIT_LED_DATA    (1 << 1)	// MiaoLin add to control the led.
+
+#define BIT_CS		(1 << 2)
+#define BIT_SCLK	(1 << 3)
+#define BIT_SDI		(1 << 4)
+#define BIT_SDO		(1 << 5)
+
+#define FLAG_EMPTY	0
+#define FLAG_WRITE	1
+#define FLAG_READ	2
+#define DEFAULT_RING_DEBOUNCE		64		/* Ringer Debounce (64 ms) */
+#define POLARITY_DEBOUNCE 	64  	/* Polarity debounce (64 ms) */
+#define OHT_TIMER		6000	/* How long after RING to retain OHT */
+
+#define FLAG_3215	(1 << 0)
+#define FLAG_A800	(1 << 7)
+
+#define MAX_NUM_CARDS 12
+#define NUM_CARDS 12
+#define NUM_FLAG  4	/* number of flag channels. */
+
+
+enum cid_hook_state {
+	CID_STATE_IDLE = 0,
+	CID_STATE_RING_ON,
+	CID_STATE_RING_OFF,
+	CID_STATE_WAIT_RING_FINISH
+};
+
+/* if you want to record the last 8 sec voice before the driver unload, uncomment it and rebuild. */
+/* #define TEST_LOG_INCOME_VOICE */
+#define voc_buffer_size (8000*8)
+
+
+#define MAX_ALARMS 10
+
+#define MOD_TYPE_FXS	0
+#define MOD_TYPE_FXO	1
+
+#define MINPEGTIME	10 * 8		/* 30 ms peak to peak gets us no more than 100 Hz */
+#define PEGTIME		50 * 8		/* 50ms peak to peak gets us rings of 10 Hz or more */
+#define PEGCOUNT	5		/* 5 cycles of pegging means RING */
+
+#define NUM_CAL_REGS 12
+
+struct calregs {
+	unsigned char vals[NUM_CAL_REGS];
+};
+
+enum proslic_power_warn {
+	PROSLIC_POWER_UNKNOWN = 0,
+	PROSLIC_POWER_ON,
+	PROSLIC_POWER_WARNED,
+};
+
+enum battery_state {
+	BATTERY_UNKNOWN = 0,
+	BATTERY_PRESENT,
+	BATTERY_LOST,
+};
+struct wctdm {
+	struct pci_dev *dev;
+	char *variety;
+	struct dahdi_span span;
+	struct dahdi_device *ddev;
+	unsigned char ios;
+	int usecount;
+	unsigned int intcount;
+	int dead;
+	int pos;
+	int flags[MAX_NUM_CARDS];
+	int freeregion;
+	int alt;
+	int curcard;
+	int cardflag;		/* Bit-map of present cards */
+	enum proslic_power_warn proslic_power;
+	spinlock_t lock;
+
+	union {
+		struct fxo {
+#ifdef AUDIO_RINGCHECK
+			unsigned int pegtimer;
+			int pegcount;
+			int peg;
+			int ring;
+#else			
+			int wasringing;
+			int lastrdtx;
+#endif			
+			int ringdebounce;
+			int offhook;
+		    unsigned int battdebounce;
+			unsigned int battalarm;
+			enum battery_state battery;
+		        int lastpol;
+		        int polarity;
+		        int polaritydebounce;
+		} fxo;
+		struct fxs {
+			int oldrxhook;
+			int debouncehook;
+			int lastrxhook;
+			int debounce;
+			int ohttimer;
+			int idletxhookstate;		/* IDLE changing hook state */
+			int lasttxhook;
+			int palarms;
+			struct calregs calregs;
+		} fxs;
+	} mod[MAX_NUM_CARDS];
+
+	/* Receive hook state and debouncing */
+	int modtype[MAX_NUM_CARDS];
+	unsigned char reg0shadow[MAX_NUM_CARDS];
+	unsigned char reg1shadow[MAX_NUM_CARDS];
+
+	unsigned long ioaddr;
+	unsigned long mem_region;	/* 32 bit Region allocated to tiger320 */
+	unsigned long mem_len;		/* Length of 32 bit region */
+	volatile unsigned long mem32;	/* Virtual representation of 32 bit memory area */
+	
+	dma_addr_t 	readdma;
+	dma_addr_t	writedma;
+	volatile unsigned char *writechunk;					/* Double-word aligned write memory */
+	volatile unsigned char *readchunk;					/* Double-word aligned read memory */
+	/*struct dahdi_chan chans[MAX_NUM_CARDS];*/
+	struct dahdi_chan _chans[NUM_CARDS];
+	struct dahdi_chan *chans[NUM_CARDS];
+
+
+#ifdef TEST_LOG_INCOME_VOICE	
+	char * voc_buf[MAX_NUM_CARDS + NUM_FLAG];
+	int voc_ptr[MAX_NUM_CARDS + NUM_FLAG];
+#endif
+	int lastchan;
+	unsigned short ledstate;
+	unsigned char fwversion;
+	int max_cards;
+	char *card_name;
+	
+	char *cid_history_buf[MAX_NUM_CARDS];
+	int	 cid_history_ptr[MAX_NUM_CARDS];
+	int  cid_history_clone_cnt[MAX_NUM_CARDS];
+	enum cid_hook_state cid_state[MAX_NUM_CARDS];
+   int 	cid_ring_on_time[MAX_NUM_CARDS];
+};
+
+static char* A1200P_Name = "A1200P";
+static char* A800P_Name  = "A800P";
+
+struct wctdm_desc {
+	char *name;
+	int flags;
+};
+
+static struct wctdm_desc wctdme = { "OpenVox A1200P/A800P", 0 };
+static int acim2tiss[16] = { 0x0, 0x1, 0x4, 0x5, 0x7, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2, 0x0, 0x3 };
+
+static struct wctdm *ifaces[WC_MAX_IFACES];
+
+static void wctdm_release(struct wctdm *wc);
+
+static unsigned int battdebounce;
+static unsigned int battalarm;
+static unsigned int battthresh;
+static int ringdebounce = DEFAULT_RING_DEBOUNCE;
+/* times 4, because must be a multiple of 4ms: */
+static int dialdebounce = 8 * 8;
+static int fwringdetect = 0;
+static int debug = 0;
+static int robust = 0;
+static int timingonly = 0;
+static int lowpower = 0;
+static int boostringer = 0;
+static int fastringer = 0;
+static int _opermode = 0;
+static char *opermode = "FCC";
+static int fxshonormode = 0;
+static int alawoverride = 0;
+static int fastpickup = 0;
+static int fxotxgain = 0;
+static int fxorxgain = 0;
+static int fxstxgain = 0;
+static int fxsrxgain = 0;
+/* special h/w control command */
+static int spibyhw = 1;
+static int usememio = 1;
+static int cidbeforering = 0;
+static int cidbuflen = 3000;	/* in msec, default 3000 */
+static int cidtimeout = 6*1000;	/* in msec, default 6000 */
+static int fxofullscale = 0;	/* fxo full scale tx/rx, register 30, acim */
+static int fixedtimepolarity=0;	/* time delay in ms when send polarity after rise edge of 1st ring.*/
+
+static int wctdm_init_proslic(struct wctdm *wc, int card, int fast , int manual, int sane);
+
+static void wctdm_set_led(struct wctdm* wc, int card, int onoff)
+{
+	int i;
+	unsigned char c;
+	
+	wc->ledstate &= ~(0x01<<card);
+	wc->ledstate |= (onoff<<card);
+	c = (inb(wc->ioaddr + WC_AUXD)&~BIT_LED_CLK)|BIT_LED_DATA;
+	outb( c,  wc->ioaddr + WC_AUXD);
+	for(i=MAX_NUM_CARDS-1; i>=0; i--)
+	{
+		if(wc->ledstate & (0x0001<<i))
+			if(wc->fwversion == 0x11)
+				c &= ~BIT_LED_DATA;
+			else
+				c |= BIT_LED_DATA;
+		else
+			if(wc->fwversion == 0x11)
+				c |= BIT_LED_DATA;
+			else
+				c &= ~BIT_LED_DATA;
+			
+		outb( c,  wc->ioaddr + WC_AUXD);
+		outb( c|BIT_LED_CLK,  wc->ioaddr + WC_AUXD);
+		outb( (c&~BIT_LED_CLK)|BIT_LED_DATA,  wc->ioaddr + WC_AUXD);
+	}	
+}
+ 
+
+static inline void wctdm_transmitprep(struct wctdm *wc, unsigned char ints)
+{
+	int x, y, chan_offset, pos;
+	volatile unsigned char *txbuf;
+	
+	if (ints & /*0x01*/ 0x04) 
+		/* Write is at interrupt address.  Start writing from normal offset */
+		txbuf = wc->writechunk;
+	else 
+		txbuf = wc->writechunk + DAHDI_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG);
+		
+	/* Calculate Transmission */
+	dahdi_transmit(&wc->span);
+	
+	if(wc->lastchan == -1)	// not in sync.
+		return;
+	
+	chan_offset = (wc->lastchan*4 + 4 ) % (MAX_NUM_CARDS+NUM_FLAG);
+
+	for (y=0;y<DAHDI_CHUNKSIZE;y++) {
+#ifdef __BIG_ENDIAN
+	// operation pending...
+#else
+		for (x=0;x<(MAX_NUM_CARDS+NUM_FLAG);x++) {
+			pos = y * (MAX_NUM_CARDS+NUM_FLAG) + ((x + chan_offset + MAX_NUM_CARDS+NUM_FLAG - WC_OFFSET)&0x0f);
+			if(x<wc->max_cards/*MAX_NUM_CARDS*/)
+				txbuf[pos] = wc->chans[x]->writechunk[y]; 
+			else
+				txbuf[pos] = 0; 
+		}
+#endif
+	}
+}
+
+
+#ifdef AUDIO_RINGCHECK
+static inline void ring_check(struct wctdm *wc, int card)
+{
+	int x;
+	short sample;
+	if (wc->modtype[card] != MOD_TYPE_FXO)
+		return;
+	wc->mod[card].fxo.pegtimer += DAHDI_CHUNKSIZE;
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+		/* Look for pegging to indicate ringing */
+		sample = DAHDI_XLAW(wc->chans[card].readchunk[x], (&(wc->chans[card])));
+		if ((sample > 10000) && (wc->mod[card].fxo.peg != 1)) {
+			if (debug > 1) printk(KERN_DEBUG "High peg!\n");
+			if ((wc->mod[card].fxo.pegtimer < PEGTIME) && (wc->mod[card].fxo.pegtimer > MINPEGTIME))
+				wc->mod[card].fxo.pegcount++;
+			wc->mod[card].fxo.pegtimer = 0;
+			wc->mod[card].fxo.peg = 1;
+		} else if ((sample < -10000) && (wc->mod[card].fxo.peg != -1)) {
+			if (debug > 1) printk(KERN_DEBUG "Low peg!\n");
+			if ((wc->mod[card].fxo.pegtimer < (PEGTIME >> 2)) && (wc->mod[card].fxo.pegtimer > (MINPEGTIME >> 2)))
+				wc->mod[card].fxo.pegcount++;
+			wc->mod[card].fxo.pegtimer = 0;
+			wc->mod[card].fxo.peg = -1;
+		}
+	}
+	if (wc->mod[card].fxo.pegtimer > PEGTIME) {
+		/* Reset pegcount if our timer expires */
+		wc->mod[card].fxo.pegcount = 0;
+	}
+	/* Decrement debouncer if appropriate */
+	if (wc->mod[card].fxo.ringdebounce)
+		wc->mod[card].fxo.ringdebounce--;
+	if (!wc->mod[card].fxo.offhook && !wc->mod[card].fxo.ringdebounce) {
+		if (!wc->mod[card].fxo.ring && (wc->mod[card].fxo.pegcount > PEGCOUNT)) {
+			/* It's ringing */
+			if (debug)
+				printk(KERN_DEBUG "RING on %d/%d!\n", wc->span.spanno, card + 1);
+			if (!wc->mod[card].fxo.offhook)
+				dahdi_hooksig(&wc->chans[card], DAHDI_RXSIG_RING);
+			wc->mod[card].fxo.ring = 1;
+		}
+		if (wc->mod[card].fxo.ring && !wc->mod[card].fxo.pegcount) {
+			/* No more ring */
+			if (debug)
+				printk(KERN_DEBUG "NO RING on %d/%d!\n", wc->span.spanno, card + 1);
+			dahdi_hooksig(&wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+			wc->mod[card].fxo.ring = 0;
+		}
+	}
+}
+#endif
+
+
+static inline void wctdm_receiveprep(struct wctdm *wc, unsigned char ints)
+{
+	volatile unsigned char *rxbuf;
+	int x, y, chan_offset;
+
+
+	if (ints & 0x08/*0x04*/)
+		/* Read is at interrupt address.  Valid data is available at normal offset */
+		rxbuf = wc->readchunk;
+	else
+		rxbuf = wc->readchunk + DAHDI_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG);
+
+	for(x=0; x<4; x++)
+		if(  *(int*)(rxbuf+x*4) == WC_SYNCFLAG)
+			break;
+	if(x==4)
+	{
+		printk("buffer sync misseed!\n");
+		wc->lastchan = -1;
+		return;
+	}
+	else if(wc->lastchan != x)
+	{
+		printk("buffer re-sync occur from %d to %d\n", wc->lastchan, x);
+		wc->lastchan = x;
+	}
+	chan_offset = (wc->lastchan*4 + 4 ) % (MAX_NUM_CARDS+NUM_FLAG);
+
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+#ifdef __BIG_ENDIAN
+	// operation pending...
+#else
+		for (y=0;y<wc->max_cards/*MAX_NUM_CARDS*/;y++) { 
+			if (wc->cardflag & (1 << y))
+				wc->chans[y]->readchunk[x] = rxbuf[(MAX_NUM_CARDS+NUM_FLAG) * x + ((y + chan_offset ) & 0x0f)];
+#ifdef TEST_LOG_INCOME_VOICE
+			wc->voc_buf[y][wc->voc_ptr[y]] = rxbuf[(MAX_NUM_CARDS+NUM_FLAG) * x + ((y + chan_offset) & 0x0f)];
+			wc->voc_ptr[y]++;
+			if(wc->voc_ptr[y] >= voc_buffer_size)
+				wc->voc_ptr[y] = 0;
+#endif		
+		}
+#endif
+	}
+	
+	if(cidbeforering)
+	{
+		for(x=0; x<wc->max_cards; x++)
+		{
+			if (wc->modtype[wc->chans[x]->chanpos - 1] == MOD_TYPE_FXO)
+				if(wc->mod[wc->chans[x]->chanpos - 1].fxo.offhook == 0)
+				{
+					/*unsigned int *p_readchunk, *p_cid_history;
+					
+					p_readchunk = (unsigned int*)wc->chans[x].readchunk;
+					p_cid_history = (unsigned int*)(wc->cid_history_buf[x] + wc->cid_history_ptr[x]);*/
+					
+					if(wc->cid_state[x] == CID_STATE_IDLE)	/* we need copy data to the cid voice buffer */
+					{
+						memcpy(wc->cid_history_buf[x] + wc->cid_history_ptr[x], wc->chans[x]->readchunk, DAHDI_CHUNKSIZE);
+						wc->cid_history_ptr[x] = (wc->cid_history_ptr[x] + DAHDI_CHUNKSIZE)%(cidbuflen * DAHDI_MAX_CHUNKSIZE);
+					}
+					else if (wc->cid_state[x] == CID_STATE_RING_ON)
+						wc->cid_history_clone_cnt[x] = cidbuflen;
+					else if (wc->cid_state[x] == CID_STATE_RING_OFF)
+					{ 
+						if(wc->cid_history_clone_cnt[x])
+						{	
+							memcpy(wc->chans[x]->readchunk, wc->cid_history_buf[x] + wc->cid_history_ptr[x], DAHDI_MAX_CHUNKSIZE);
+							wc->cid_history_clone_cnt[x]--;
+							wc->cid_history_ptr[x] = (wc->cid_history_ptr[x] + DAHDI_MAX_CHUNKSIZE)%(cidbuflen * DAHDI_MAX_CHUNKSIZE);
+						}
+						else
+						{
+							wc->cid_state[x] = CID_STATE_WAIT_RING_FINISH;
+							wc->cid_history_clone_cnt[x] = cidtimeout; /* wait 6 sec, if no ring, return to idle */
+						}
+					}
+					else if(wc->cid_state[x] == CID_STATE_WAIT_RING_FINISH)
+					{
+						if(wc->cid_history_clone_cnt[x] > 0)
+							wc->cid_history_clone_cnt[x]--;
+						else
+						{
+							wc->cid_state[x] = CID_STATE_IDLE;
+							wc->cid_history_ptr[x] = 0;
+							wc->cid_history_clone_cnt[x] = 0;
+						}
+					}
+				}
+		}		
+	}
+	
+#ifdef AUDIO_RINGCHECK
+	for (x=0;x<wc->max_cards;x++)
+		ring_check(wc, x);
+#endif		
+	/* XXX We're wasting 8 taps.  We should get closer :( */
+	for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) {
+		if (wc->cardflag & (1 << x))
+			dahdi_ec_chunk(wc->chans[x], wc->chans[x]->readchunk, wc->chans[x]->writechunk);
+	}
+	dahdi_receive(&wc->span);
+}
+
+static void wctdm_stop_dma(struct wctdm *wc);
+static void wctdm_reset_tdm(struct wctdm *wc);
+static void wctdm_restart_dma(struct wctdm *wc);
+
+
+static unsigned char __wctdm_getcreg(struct wctdm *wc, unsigned char reg);
+static void __wctdm_setcreg(struct wctdm *wc, unsigned char reg, unsigned char val);
+
+
+static inline void __write_8bits(struct wctdm *wc, unsigned char bits)
+{
+	if(spibyhw == 0)
+	{
+		int x;
+		/* Drop chip select */
+		wc->ios |= BIT_SCLK;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+		wc->ios &= ~BIT_CS;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+		for (x=0;x<8;x++) {
+			/* Send out each bit, MSB first, drop SCLK as we do so */
+			if (bits & 0x80)
+				wc->ios |= BIT_SDI;
+			else
+				wc->ios &= ~BIT_SDI;
+			wc->ios &= ~BIT_SCLK;
+			outb(wc->ios, wc->ioaddr + WC_AUXD);
+			/* Now raise SCLK high again and repeat */
+			wc->ios |= BIT_SCLK;
+			outb(wc->ios, wc->ioaddr + WC_AUXD);
+			bits <<= 1;
+		}
+		/* Finally raise CS back high again */
+		wc->ios |= BIT_CS;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+	}
+	else
+	{
+		__wctdm_setcreg(wc, WC_SPIDATA, bits);
+		__wctdm_setcreg(wc, WC_SPICTRL, BIT_SPI_BYHW | BIT_SPI_START);
+		while ((__wctdm_getcreg(wc, WC_SPICTRL) & BIT_SPI_BUSY) != 0);
+		__wctdm_setcreg(wc, WC_SPICTRL, BIT_SPI_BYHW);
+	}
+}
+
+
+static inline void __reset_spi(struct wctdm *wc)
+{
+	__wctdm_setcreg(wc, WC_SPICTRL, 0);
+	
+	/* Drop chip select and clock once and raise and clock once */
+	wc->ios |= BIT_SCLK;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	wc->ios &= ~BIT_CS;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	wc->ios |= BIT_SDI;
+	wc->ios &= ~BIT_SCLK;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	/* Now raise SCLK high again and repeat */
+	wc->ios |= BIT_SCLK;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	/* Finally raise CS back high again */
+	wc->ios |= BIT_CS;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	/* Clock again */
+	wc->ios &= ~BIT_SCLK;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	/* Now raise SCLK high again and repeat */
+	wc->ios |= BIT_SCLK;
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+	
+	__wctdm_setcreg(wc, WC_SPICTRL, spibyhw);
+
+}
+
+static inline unsigned char __read_8bits(struct wctdm *wc)
+{
+	unsigned char res=0, c;
+	int x;
+	if(spibyhw == 0)
+	{
+		wc->ios &= ~BIT_CS;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+		/* Drop chip select */
+		wc->ios &= ~BIT_CS;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+		for (x=0;x<8;x++) {
+			res <<= 1;
+			/* Get SCLK */
+			wc->ios &= ~BIT_SCLK;
+			outb(wc->ios, wc->ioaddr + WC_AUXD);
+			/* Read back the value */
+			c = inb(wc->ioaddr + WC_AUXR);
+			if (c & BIT_SDO)
+				res |= 1;
+			/* Now raise SCLK high again */
+			wc->ios |= BIT_SCLK;
+			outb(wc->ios, wc->ioaddr + WC_AUXD);
+		}
+		/* Finally raise CS back high again */
+		wc->ios |= BIT_CS;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+		wc->ios &= ~BIT_SCLK;
+		outb(wc->ios, wc->ioaddr + WC_AUXD);
+	}
+	else
+	{
+		__wctdm_setcreg(wc, WC_SPICTRL, BIT_SPI_BYHW | BIT_SPI_START);
+		while ((__wctdm_getcreg(wc, WC_SPICTRL) & BIT_SPI_BUSY) != 0);
+		res = __wctdm_getcreg(wc, WC_SPIDATA);
+		__wctdm_setcreg(wc, WC_SPICTRL, BIT_SPI_BYHW);
+	}
+	
+	/* And return our result */
+	return res;
+}
+
+static void __wctdm_setcreg_mem(struct wctdm *wc, unsigned char reg, unsigned char val)
+{
+	unsigned int *p = (unsigned int*)(wc->mem32 + WC_REGBASE + ((reg & 0xf) << 2));
+	*p = val;
+}
+
+static unsigned char __wctdm_getcreg_mem(struct wctdm *wc, unsigned char reg)
+{
+	unsigned int *p = (unsigned int*)(wc->mem32 + WC_REGBASE + ((reg & 0xf) << 2));
+	return (*p)&0x00ff;
+}
+
+
+static void __wctdm_setcreg(struct wctdm *wc, unsigned char reg, unsigned char val)
+{
+	if(usememio)
+		__wctdm_setcreg_mem(wc, reg, val);
+	else
+		outb(val, wc->ioaddr + WC_REGBASE + ((reg & 0xf) << 2));
+}
+
+static unsigned char __wctdm_getcreg(struct wctdm *wc, unsigned char reg)
+{
+	if(usememio)
+		return __wctdm_getcreg_mem(wc, reg);
+	else
+		return inb(wc->ioaddr + WC_REGBASE + ((reg & 0xf) << 2));
+}
+
+static inline void __wctdm_setcard(struct wctdm *wc, int card)
+{
+	if (wc->curcard != card) {
+		__wctdm_setcreg(wc, WC_CS, card);
+		wc->curcard = card;
+		//printk("Select card %d\n", card);
+	}
+}
+
+static void __wctdm_setreg(struct wctdm *wc, int card, unsigned char reg, unsigned char value)
+{
+	__wctdm_setcard(wc, card);
+	if (wc->modtype[card] == MOD_TYPE_FXO) {
+		__write_8bits(wc, 0x20);
+		__write_8bits(wc, reg & 0x7f);
+	} else {
+		__write_8bits(wc, reg & 0x7f);
+	}
+	__write_8bits(wc, value);
+}
+
+static void wctdm_setreg(struct wctdm *wc, int card, unsigned char reg, unsigned char value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->lock, flags);
+	__wctdm_setreg(wc, card, reg, value);
+	spin_unlock_irqrestore(&wc->lock, flags);
+}
+
+static unsigned char __wctdm_getreg(struct wctdm *wc, int card, unsigned char reg)
+{
+	__wctdm_setcard(wc, card);
+	if (wc->modtype[card] == MOD_TYPE_FXO) {
+		__write_8bits(wc, 0x60);
+		__write_8bits(wc, reg & 0x7f);
+	} else {
+		__write_8bits(wc, reg | 0x80);
+	}
+	return __read_8bits(wc);
+}
+
+static inline void reset_spi(struct wctdm *wc, int card)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->lock, flags);
+	__wctdm_setcard(wc, card);
+	__reset_spi(wc);
+	__reset_spi(wc);
+	spin_unlock_irqrestore(&wc->lock, flags);
+}
+
+static unsigned char wctdm_getreg(struct wctdm *wc, int card, unsigned char reg)
+{
+	unsigned long flags;
+	unsigned char res;
+	spin_lock_irqsave(&wc->lock, flags);
+	res = __wctdm_getreg(wc, card, reg);
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return res;
+}
+
+static int __wait_access(struct wctdm *wc, int card)
+{
+    unsigned char data = 0;
+    long origjiffies;
+    int count = 0;
+
+    #define MAX 6000 /* attempts */
+
+
+    origjiffies = jiffies;
+    /* Wait for indirect access */
+    while (count++ < MAX)
+	 {
+		data = __wctdm_getreg(wc, card, I_STATUS);
+
+		if (!data)
+			return 0;
+
+	 }
+
+    if(count > (MAX-1)) printk(KERN_NOTICE " ##### Loop error (%02x) #####\n", data);
+
+	return 0;
+}
+
+static unsigned char translate_3215(unsigned char address)
+{
+	int x;
+	for (x=0;x<sizeof(indirect_regs)/sizeof(indirect_regs[0]);x++) {
+		if (indirect_regs[x].address == address) {
+			address = indirect_regs[x].altaddr;
+			break;
+		}
+	}
+	return address;
+}
+
+static int wctdm_proslic_setreg_indirect(struct wctdm *wc, int card, unsigned char address, unsigned short data)
+{
+	unsigned long flags;
+	int res = -1;
+	/* Translate 3215 addresses */
+	if (wc->flags[card] & FLAG_3215) {
+		address = translate_3215(address);
+		if (address == 255)
+			return 0;
+	}
+	spin_lock_irqsave(&wc->lock, flags);
+	if(!__wait_access(wc, card)) {
+		__wctdm_setreg(wc, card, IDA_LO,(unsigned char)(data & 0xFF));
+		__wctdm_setreg(wc, card, IDA_HI,(unsigned char)((data & 0xFF00)>>8));
+		__wctdm_setreg(wc, card, IAA,address);
+		res = 0;
+	};
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return res;
+}
+
+static int wctdm_proslic_getreg_indirect(struct wctdm *wc, int card, unsigned char address)
+{ 
+	unsigned long flags;
+	int res = -1;
+	char *p=NULL;
+	/* Translate 3215 addresses */
+	if (wc->flags[card] & FLAG_3215) {
+		address = translate_3215(address);
+		if (address == 255)
+			return 0;
+	}
+	spin_lock_irqsave(&wc->lock, flags);
+	if (!__wait_access(wc, card)) {
+		__wctdm_setreg(wc, card, IAA, address);
+		if (!__wait_access(wc, card)) {
+			unsigned char data1, data2;
+			data1 = __wctdm_getreg(wc, card, IDA_LO);
+			data2 = __wctdm_getreg(wc, card, IDA_HI);
+			res = data1 | (data2 << 8);
+		} else
+			p = "Failed to wait inside\n";
+	} else
+		p = "failed to wait\n";
+	spin_unlock_irqrestore(&wc->lock, flags);
+	if (p)
+		printk(KERN_NOTICE "%s", p);
+	return res;
+}
+
+static int wctdm_proslic_init_indirect_regs(struct wctdm *wc, int card)
+{
+	unsigned char i;
+
+	for (i=0; i<sizeof(indirect_regs) / sizeof(indirect_regs[0]); i++)
+	{
+		if(wctdm_proslic_setreg_indirect(wc, card, indirect_regs[i].address,indirect_regs[i].initial))
+			return -1;
+	}
+
+	return 0;
+}
+
+static int wctdm_proslic_verify_indirect_regs(struct wctdm *wc, int card)
+{ 
+	int passed = 1;
+	unsigned short i, initial;
+	int j;
+
+	for (i=0; i<sizeof(indirect_regs) / sizeof(indirect_regs[0]); i++) 
+	{
+		if((j = wctdm_proslic_getreg_indirect(wc, card, (unsigned char) indirect_regs[i].address)) < 0) {
+			printk(KERN_NOTICE "Failed to read indirect register %d\n", i);
+			return -1;
+		}
+		initial= indirect_regs[i].initial;
+
+		if ( j != initial && (!(wc->flags[card] & FLAG_3215) || (indirect_regs[i].altaddr != 255)))
+		{
+			 printk(KERN_NOTICE "!!!!!!! %s  iREG %X = %X  should be %X\n",
+				indirect_regs[i].name,indirect_regs[i].address,j,initial );
+			 passed = 0;
+		}	
+	}
+
+    if (passed) {
+		if (debug)
+			printk(KERN_DEBUG "Init Indirect Registers completed successfully.\n");
+    } else {
+		printk(KERN_NOTICE " !!!!! Init Indirect Registers UNSUCCESSFULLY.\n");
+		return -1;
+    }
+    return 0;
+}
+
+static inline void wctdm_proslic_recheck_sanity(struct wctdm *wc, int card)
+{
+	int res;
+	/* Check loopback */
+	res = wc->reg1shadow[card];
+	
+	if (!res && (res != wc->mod[card].fxs.lasttxhook))     // read real state from register   By wx
+		res=wctdm_getreg(wc, card, 64);
+	
+	if (!res && (res != wc->mod[card].fxs.lasttxhook)) {
+		res = wctdm_getreg(wc, card, 8);
+		if (res) {
+			printk(KERN_NOTICE "Ouch, part reset, quickly restoring reality (%d)\n", card);
+			wctdm_init_proslic(wc, card, 1, 0, 1);
+		} else {
+			if (wc->mod[card].fxs.palarms++ < MAX_ALARMS) {
+				printk(KERN_NOTICE "Power alarm on module %d, resetting!\n", card + 1);
+				if (wc->mod[card].fxs.lasttxhook == 4)
+					wc->mod[card].fxs.lasttxhook = 1;
+				wctdm_setreg(wc, card, 64, wc->mod[card].fxs.lasttxhook);
+			} else {
+				if (wc->mod[card].fxs.palarms == MAX_ALARMS)
+					printk(KERN_NOTICE "Too many power alarms on card %d, NOT resetting!\n", card + 1);
+			}
+		}
+	}
+}
+static inline void wctdm_voicedaa_check_hook(struct wctdm *wc, int card)
+{
+#define MS_PER_CHECK_HOOK 16
+
+#ifndef AUDIO_RINGCHECK
+	unsigned char res;
+#endif	
+	signed char b;
+	int errors = 0;
+	struct fxo *fxo = &wc->mod[card].fxo;
+
+	/* Try to track issues that plague slot one FXO's */
+	b = wc->reg0shadow[card];
+	if ((b & 0x2) || !(b & 0x8)) {
+		/* Not good -- don't look at anything else */
+		if (debug)
+			printk(KERN_DEBUG "Error (%02x) on card %d!\n", b, card + 1); 
+		errors++;
+	}
+	b &= 0x9b;
+	if (fxo->offhook) {
+		if (b != 0x9)
+			wctdm_setreg(wc, card, 5, 0x9);
+	} else {
+		if (b != 0x8)
+			wctdm_setreg(wc, card, 5, 0x8);
+	}
+	if (errors)
+		return;
+	if (!fxo->offhook) {
+ if(fixedtimepolarity) {
+			if ( wc->cid_state[card] == CID_STATE_RING_ON && wc->cid_ring_on_time[card]>0)
+			{
+ 	if(wc->cid_ring_on_time[card]>=fixedtimepolarity )
+			{
+			dahdi_qevent_lock(wc->chans[card], DAHDI_EVENT_POLARITY);
+			wc->cid_ring_on_time[card] = -1;	/* the polarity already sent */	
+			}
+			else
+		wc->cid_ring_on_time[card] += 16;
+    }
+}
+		if (fwringdetect) {
+			res = wc->reg0shadow[card] & 0x60;
+			if (fxo->ringdebounce) {
+				--fxo->ringdebounce;
+				if (res && (res != fxo->lastrdtx) &&
+				    (fxo->battery == BATTERY_PRESENT)) {
+					if (!fxo->wasringing) {
+						fxo->wasringing = 1;
+						if (debug)
+          printk(KERN_DEBUG "RING on %d/%d!\n", wc->span.spanno, card + 1);
+	if(cidbeforering)
+						{
+							if(wc->cid_state[card] == CID_STATE_IDLE)
+							{
+								wc->cid_state[card] = CID_STATE_RING_ON;
+								wc->cid_ring_on_time[card] = 16;	/* check every 16ms */
+							}
+							else
+								dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_RING);
+						}
+						else 							
+        dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_RING);
+					}
+					fxo->lastrdtx = res;
+					fxo->ringdebounce = 10;
+				} else if (!res) {
+					if ((fxo->ringdebounce == 0) && fxo->wasringing) {
+				fxo->wasringing = 0;
+				if (debug)
+				printk(KERN_DEBUG "NO RING on %d/%d!\n", wc->span.spanno, card + 1);
+	if(cidbeforering)
+						{
+							if(wc->cid_state[card] == CID_STATE_RING_ON)
+							{
+								if(fixedtimepolarity==0)
+									dahdi_qevent_lock(wc->chans[card], DAHDI_EVENT_POLARITY);
+								wc->cid_state[card] = CID_STATE_RING_OFF;
+							}
+							else 
+							{
+								if(wc->cid_state[card] == CID_STATE_WAIT_RING_FINISH)
+									wc->cid_history_clone_cnt[card] = cidtimeout;
+								dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+							}
+						}
+						else
+
+						dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+				}
+				}
+			} else if (res && (fxo->battery == BATTERY_PRESENT)) {
+				fxo->lastrdtx = res;
+				fxo->ringdebounce = 10;
+			}
+		} else {
+			res = wc->reg0shadow[card];
+			if ((res & 0x60) && (fxo->battery == BATTERY_PRESENT)) {
+				fxo->ringdebounce += (DAHDI_CHUNKSIZE * 16);
+				if (fxo->ringdebounce >= DAHDI_CHUNKSIZE * ringdebounce) {
+					if (!fxo->wasringing) {
+						fxo->wasringing = 1;
+ if(cidbeforering)
+						{
+							if(wc->cid_state[card] == CID_STATE_IDLE)
+							{	
+								wc->cid_state[card] = CID_STATE_RING_ON;
+								wc->cid_ring_on_time[card] = 16;		/* check every 16ms */
+							}
+							else
+								dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_RING);
+						}
+						else      
+						dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_RING);
+						if (debug)
+							printk(KERN_DEBUG "RING on %d/%d!\n", wc->span.spanno, card + 1);
+					}
+					fxo->ringdebounce = DAHDI_CHUNKSIZE * ringdebounce;
+				}
+			} else {
+				fxo->ringdebounce -= DAHDI_CHUNKSIZE * 4;
+				if (fxo->ringdebounce <= 0) {
+					if (fxo->wasringing) {
+						fxo->wasringing = 0;
+	if(cidbeforering)
+						{
+							if(wc->cid_state[card] == CID_STATE_RING_ON)
+							{
+								if(fixedtimepolarity==0)
+									dahdi_qevent_lock(wc->chans[card], DAHDI_EVENT_POLARITY);
+								wc->cid_state[card] = CID_STATE_RING_OFF;
+							}
+							else 
+							{
+								if(wc->cid_state[card] == CID_STATE_WAIT_RING_FINISH)
+									wc->cid_history_clone_cnt[card] = cidtimeout;
+								dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+							}
+						}
+						else
+						dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+						if (debug)
+							printk(KERN_DEBUG "NO RING on %d/%d!\n", wc->span.spanno, card + 1);
+					}
+					fxo->ringdebounce = 0;
+				}
+			}
+		}
+	}
+
+	b = wc->reg1shadow[card];
+	if (abs(b) < battthresh) {
+		/* possible existing states:
+		   battery lost, no debounce timer
+		   battery lost, debounce timer (going to battery present)
+		   battery present or unknown, no debounce timer
+		   battery present or unknown, debounce timer (going to battery lost)
+		*/
+
+		if (fxo->battery == BATTERY_LOST) {
+			if (fxo->battdebounce) {
+				/* we were going to BATTERY_PRESENT, but battery was lost again,
+				   so clear the debounce timer */
+				fxo->battdebounce = 0;
+			}
+		} else {
+			if (fxo->battdebounce) {
+				/* going to BATTERY_LOST, see if we are there yet */
+				if (--fxo->battdebounce == 0) {
+					fxo->battery = BATTERY_LOST;
+					if (debug)
+						printk(KERN_DEBUG "NO BATTERY on %d/%d!\n", wc->span.spanno, card + 1);
+#ifdef	JAPAN
+					if (!wc->ohdebounce && wc->offhook) {
+						dahdi_hooksig(&wc->chans[card], DAHDI_RXSIG_ONHOOK);
+						if (debug)
+							printk(KERN_DEBUG "Signalled On Hook\n");
+#ifdef	ZERO_BATT_RING
+						wc->onhook++;
+#endif
+					}
+#else
+					dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_ONHOOK);
+					/* set the alarm timer, taking into account that part of its time
+					   period has already passed while debouncing occurred */
+					fxo->battalarm = (battalarm - battdebounce) / MS_PER_CHECK_HOOK;
+#endif
+				}
+			} else {
+				/* start the debounce timer to verify that battery has been lost */
+				fxo->battdebounce = battdebounce / MS_PER_CHECK_HOOK;
+			}
+		}
+	} else {
+		/* possible existing states:
+		   battery lost or unknown, no debounce timer
+		   battery lost or unknown, debounce timer (going to battery present)
+		   battery present, no debounce timer
+		   battery present, debounce timer (going to battery lost)
+		*/
+
+		if (fxo->battery == BATTERY_PRESENT) {
+			if (fxo->battdebounce) {
+				/* we were going to BATTERY_LOST, but battery appeared again,
+				   so clear the debounce timer */
+				fxo->battdebounce = 0;
+			}
+		} else {
+			if (fxo->battdebounce) {
+				/* going to BATTERY_PRESENT, see if we are there yet */
+				if (--fxo->battdebounce == 0) {
+					fxo->battery = BATTERY_PRESENT;
+					if (debug)
+						printk(KERN_DEBUG "BATTERY on %d/%d (%s)!\n", wc->span.spanno, card + 1, 
+						       (b < 0) ? "-" : "+");			    
+#ifdef	ZERO_BATT_RING
+					if (wc->onhook) {
+						wc->onhook = 0;
+						dahdi_hooksig(&wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+						if (debug)
+							printk(KERN_DEBUG "Signalled Off Hook\n");
+					}
+#else
+					dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+#endif
+					/* set the alarm timer, taking into account that part of its time
+					   period has already passed while debouncing occurred */
+					fxo->battalarm = (battalarm - battdebounce) / MS_PER_CHECK_HOOK;
+				}
+			} else {
+				/* start the debounce timer to verify that battery has appeared */
+				fxo->battdebounce = battdebounce / MS_PER_CHECK_HOOK;
+			}
+		}
+	}
+
+	if (fxo->lastpol >= 0) {
+		if (b < 0) {
+			fxo->lastpol = -1;
+			fxo->polaritydebounce = POLARITY_DEBOUNCE / MS_PER_CHECK_HOOK;
+		}
+	} 
+	if (fxo->lastpol <= 0) {
+		if (b > 0) {
+			fxo->lastpol = 1;
+			fxo->polaritydebounce = POLARITY_DEBOUNCE / MS_PER_CHECK_HOOK;
+		}
+	}
+
+	if (fxo->battalarm) {
+		if (--fxo->battalarm == 0) {
+			/* the alarm timer has expired, so update the battery alarm state
+			   for this channel */
+			dahdi_alarm_channel(wc->chans[card], fxo->battery == BATTERY_LOST ? DAHDI_ALARM_RED : DAHDI_ALARM_NONE);
+		}
+	}
+
+	if (fxo->polaritydebounce) {
+		if (--fxo->polaritydebounce == 0) {
+		    if (fxo->lastpol != fxo->polarity) {
+				if (debug)
+					printk(KERN_DEBUG "%lu Polarity reversed (%d -> %d)\n", jiffies, 
+				       fxo->polarity, 
+				       fxo->lastpol);
+				if (fxo->polarity)
+					dahdi_qevent_lock(wc->chans[card], DAHDI_EVENT_POLARITY);
+				fxo->polarity = fxo->lastpol;
+		    }
+		}
+	}
+#undef MS_PER_CHECK_HOOK
+}
+
+static inline void wctdm_proslic_check_hook(struct wctdm *wc, int card)
+{
+	char res;
+	int hook;
+
+	/* For some reason we have to debounce the
+	   hook detector.  */
+
+	res = wc->reg0shadow[card];
+	hook = (res & 1);
+	if (hook != wc->mod[card].fxs.lastrxhook) {
+		/* Reset the debounce (must be multiple of 4ms) */
+		wc->mod[card].fxs.debounce = dialdebounce * 4;
+
+#if 0
+		printk(KERN_DEBUG "Resetting debounce card %d hook %d, %d\n", card, hook, wc->mod[card].fxs.debounce);
+#endif
+	} else {
+		if (wc->mod[card].fxs.debounce > 0) {
+			wc->mod[card].fxs.debounce-= 16 * DAHDI_CHUNKSIZE;
+#if 0
+			printk(KERN_DEBUG "Sustaining hook %d, %d\n", hook, wc->mod[card].fxs.debounce);
+#endif
+			if (!wc->mod[card].fxs.debounce) {
+#if 0
+				printk(KERN_DEBUG "Counted down debounce, newhook: %d...\n", hook);
+#endif
+				wc->mod[card].fxs.debouncehook = hook;
+			}
+			if (!wc->mod[card].fxs.oldrxhook && wc->mod[card].fxs.debouncehook) {
+				/* Off hook */
+#if 1
+				if (debug)
+#endif				
+					printk(KERN_DEBUG "opvxa1200: Card %d Going off hook\n", card);
+				dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_OFFHOOK);
+				if (robust)
+					wctdm_init_proslic(wc, card, 1, 0, 1);
+				wc->mod[card].fxs.oldrxhook = 1;
+			
+			} else if (wc->mod[card].fxs.oldrxhook && !wc->mod[card].fxs.debouncehook) {
+				/* On hook */
+#if 1
+				if (debug)
+#endif				
+					printk(KERN_DEBUG "opvxa1200: Card %d Going on hook\n", card);
+				dahdi_hooksig(wc->chans[card], DAHDI_RXSIG_ONHOOK);
+				wc->mod[card].fxs.oldrxhook = 0;
+			}
+		}
+	}
+	wc->mod[card].fxs.lastrxhook = hook;
+}
+
+DAHDI_IRQ_HANDLER(wctdm_interrupt)
+{
+	struct wctdm *wc = dev_id;
+	unsigned char ints;
+	int x, y, z;
+	int mode;
+
+	ints = inb(wc->ioaddr + WC_INTSTAT);
+
+	if (!ints)
+		return IRQ_NONE;
+
+	outb(ints, wc->ioaddr + WC_INTSTAT);
+	
+	if (ints & 0x10) {
+		/* Stop DMA, wait for watchdog */
+		printk(KERN_INFO "TDM PCI Master abort\n");
+		wctdm_stop_dma(wc);
+		return IRQ_RETVAL(1);
+	}
+	
+	if (ints & 0x20) {
+		printk(KERN_INFO "PCI Target abort\n");
+		return IRQ_RETVAL(1);
+	}
+
+	for (x=0;x<wc->max_cards/*4*3*/;x++) {
+		if (wc->cardflag & (1 << x) &&
+		    (wc->modtype[x] == MOD_TYPE_FXS)) {
+			if (wc->mod[x].fxs.lasttxhook == 0x4) {
+				/* RINGing, prepare for OHT */
+				wc->mod[x].fxs.ohttimer = OHT_TIMER << 3;
+				if (reversepolarity)
+					wc->mod[x].fxs.idletxhookstate = 0x6;	/* OHT mode when idle */
+				else
+					wc->mod[x].fxs.idletxhookstate = 0x2; 
+			} else {
+				if (wc->mod[x].fxs.ohttimer) {
+					wc->mod[x].fxs.ohttimer-= DAHDI_CHUNKSIZE;
+					if (!wc->mod[x].fxs.ohttimer) {
+						if (reversepolarity)
+							wc->mod[x].fxs.idletxhookstate = 0x5;	/* Switch to active */
+						else
+							wc->mod[x].fxs.idletxhookstate = 0x1;
+						if ((wc->mod[x].fxs.lasttxhook == 0x2) || (wc->mod[x].fxs.lasttxhook == 0x6)) {
+							/* Apply the change if appropriate */
+							if (reversepolarity) 
+								wc->mod[x].fxs.lasttxhook = 0x5;
+							else
+								wc->mod[x].fxs.lasttxhook = 0x1;
+							wctdm_setreg(wc, x, 64, wc->mod[x].fxs.lasttxhook);
+						}
+					}
+				}
+			}
+		}
+	}
+
+	if (ints & 0x0f) {
+		wc->intcount++;
+		z = wc->intcount & 0x3;
+		mode = wc->intcount & 0xc;
+		for(y=0; y<wc->max_cards/4/*3*/; y++)
+		{
+			x = z + y*4;
+			if (wc->cardflag & (1 << x ) ) 
+			{
+				switch(mode) 
+				{
+				case 0:
+					/* Rest */
+					break;
+				case 4:
+					/* Read first shadow reg */
+					if (wc->modtype[x] == MOD_TYPE_FXS)
+						wc->reg0shadow[x] = wctdm_getreg(wc, x, 68);
+					else if (wc->modtype[x] == MOD_TYPE_FXO)
+						wc->reg0shadow[x] = wctdm_getreg(wc, x, 5);
+					break;
+				case 8:
+					/* Read second shadow reg */
+					if (wc->modtype[x] == MOD_TYPE_FXS)
+						wc->reg1shadow[x] = wctdm_getreg(wc, x, 64);
+					else if (wc->modtype[x] == MOD_TYPE_FXO)
+						wc->reg1shadow[x] = wctdm_getreg(wc, x, 29);
+					break;
+				case 12:
+					/* Perform processing */
+					if (wc->modtype[x] == MOD_TYPE_FXS) {
+						wctdm_proslic_check_hook(wc, x);
+						if (!(wc->intcount & 0xf0))
+							wctdm_proslic_recheck_sanity(wc, x);
+					} else if (wc->modtype[x] == MOD_TYPE_FXO) {
+						wctdm_voicedaa_check_hook(wc, x);
+					}
+					break;
+				}
+			}
+		}
+		if (!(wc->intcount % 10000)) {
+			/* Accept an alarm once per 10 seconds */
+			for (x=0;x<wc->max_cards/*4*3*/;x++) 
+				if (wc->modtype[x] == MOD_TYPE_FXS) {
+					if (wc->mod[x].fxs.palarms)
+						wc->mod[x].fxs.palarms--;
+				}
+		}
+		wctdm_receiveprep(wc, ints);
+		wctdm_transmitprep(wc, ints);
+	}
+
+	return IRQ_RETVAL(1);
+
+}
+
+static int wctdm_voicedaa_insane(struct wctdm *wc, int card)
+{
+	int blah;
+	blah = wctdm_getreg(wc, card, 2);
+	if (blah != 0x3)
+		return -2;
+	blah = wctdm_getreg(wc, card, 11);
+	if (debug)
+		printk(KERN_DEBUG "VoiceDAA System: %02x\n", blah & 0xf);
+	return 0;
+}
+
+static int wctdm_proslic_insane(struct wctdm *wc, int card)
+{
+	int blah,insane_report;
+	insane_report=0;
+
+	blah = wctdm_getreg(wc, card, 0);
+	if (debug) 
+		printk(KERN_DEBUG "ProSLIC on module %d, product %d, version %d\n", card, (blah & 0x30) >> 4, (blah & 0xf));
+
+#if 0
+	if ((blah & 0x30) >> 4) {
+		printk(KERN_DEBUG "ProSLIC on module %d is not a 3210.\n", card);
+		return -1;
+	}
+#endif
+	if (((blah & 0xf) == 0) || ((blah & 0xf) == 0xf)) {
+		/* SLIC not loaded */
+		return -1;
+	}
+	if ((blah & 0xf) < 2) {
+		printk(KERN_NOTICE "ProSLIC 3210 version %d is too old\n", blah & 0xf);
+		return -1;
+	}
+	if (wctdm_getreg(wc, card, 1) & 0x80)
+	/* ProSLIC 3215, not a 3210 */
+		wc->flags[card] |= FLAG_3215;
+	
+	blah = wctdm_getreg(wc, card, 8);
+	if (blah != 0x2) {
+		printk(KERN_NOTICE  "ProSLIC on module %d insane (1) %d should be 2\n", card, blah);
+		return -1;
+	} else if ( insane_report)
+		printk(KERN_NOTICE  "ProSLIC on module %d Reg 8 Reads %d Expected is 0x2\n",card,blah);
+
+	blah = wctdm_getreg(wc, card, 64);
+	if (blah != 0x0) {
+		printk(KERN_NOTICE  "ProSLIC on module %d insane (2)\n", card);
+		return -1;
+	} else if ( insane_report)
+		printk(KERN_NOTICE  "ProSLIC on module %d Reg 64 Reads %d Expected is 0x0\n",card,blah);
+
+	blah = wctdm_getreg(wc, card, 11);
+	if (blah != 0x33) {
+		printk(KERN_NOTICE  "ProSLIC on module %d insane (3)\n", card);
+		return -1;
+	} else if ( insane_report)
+		printk(KERN_NOTICE  "ProSLIC on module %d Reg 11 Reads %d Expected is 0x33\n",card,blah);
+
+	/* Just be sure it's setup right. */
+	wctdm_setreg(wc, card, 30, 0);
+
+	if (debug) 
+		printk(KERN_DEBUG "ProSLIC on module %d seems sane.\n", card);
+	return 0;
+}
+
+static int wctdm_proslic_powerleak_test(struct wctdm *wc, int card)
+{
+	unsigned long origjiffies;
+	unsigned char vbat;
+
+	/* Turn off linefeed */
+	wctdm_setreg(wc, card, 64, 0);
+
+	/* Power down */
+	wctdm_setreg(wc, card, 14, 0x10);
+
+	/* Wait for one second */
+	origjiffies = jiffies;
+
+	while((vbat = wctdm_getreg(wc, card, 82)) > 0x6) {
+		if ((jiffies - origjiffies) >= (HZ/2))
+			break;
+	}
+
+	if (vbat < 0x06) {
+		printk(KERN_NOTICE "Excessive leakage detected on module %d: %d volts (%02x) after %d ms\n", card,
+		       376 * vbat / 1000, vbat, (int)((jiffies - origjiffies) * 1000 / HZ));
+		return -1;
+	} else if (debug) {
+		printk(KERN_NOTICE "Post-leakage voltage: %d volts\n", 376 * vbat / 1000);
+	}
+	return 0;
+}
+
+static int wctdm_powerup_proslic(struct wctdm *wc, int card, int fast)
+{
+	unsigned char vbat;
+	unsigned long origjiffies;
+	int lim;
+
+	/* Set period of DC-DC converter to 1/64 khz */
+	wctdm_setreg(wc, card, 92, 0xff /* was 0xff */);
+
+	/* Wait for VBat to powerup */
+	origjiffies = jiffies;
+
+	/* Disable powerdown */
+	wctdm_setreg(wc, card, 14, 0);
+
+	/* If fast, don't bother checking anymore */
+	if (fast)
+		return 0;
+
+	while((vbat = wctdm_getreg(wc, card, 82)) < 0xc0) {
+		/* Wait no more than 500ms */
+		if ((jiffies - origjiffies) > HZ/2) {
+			break;
+		}
+	}
+
+	if (vbat < 0xc0) {
+		if (wc->proslic_power == PROSLIC_POWER_UNKNOWN)
+				 printk(KERN_NOTICE "ProSLIC on module %d failed to powerup within %d ms (%d mV only)\n\n -- DID YOU REMEMBER TO PLUG IN THE HD POWER CABLE TO THE A1200P??\n",
+					card, (int)(((jiffies - origjiffies) * 1000 / HZ)),
+					vbat * 375);
+		wc->proslic_power = PROSLIC_POWER_WARNED;
+		return -1;
+	} else if (debug) {
+		printk(KERN_DEBUG "ProSLIC on module %d powered up to -%d volts (%02x) in %d ms\n",
+		       card, vbat * 376 / 1000, vbat, (int)(((jiffies - origjiffies) * 1000 / HZ)));
+	}
+	wc->proslic_power = PROSLIC_POWER_ON;
+
+        /* Proslic max allowed loop current, reg 71 LOOP_I_LIMIT */
+        /* If out of range, just set it to the default value     */
+        lim = (loopcurrent - 20) / 3;
+        if ( loopcurrent > 41 ) {
+                lim = 0;
+                if (debug)
+                        printk(KERN_DEBUG "Loop current out of range! Setting to default 20mA!\n");
+        }
+        else if (debug)
+                        printk(KERN_DEBUG "Loop current set to %dmA!\n",(lim*3)+20);
+        wctdm_setreg(wc,card,LOOP_I_LIMIT,lim);
+
+	/* Engage DC-DC converter */
+	wctdm_setreg(wc, card, 93, 0x19 /* was 0x19 */);
+#if 0
+	origjiffies = jiffies;
+	while(0x80 & wctdm_getreg(wc, card, 93)) {
+		if ((jiffies - origjiffies) > 2 * HZ) {
+			printk(KERN_DEBUG "Timeout waiting for DC-DC calibration on module %d\n", card);
+			return -1;
+		}
+	}
+
+#if 0
+	/* Wait a full two seconds */
+	while((jiffies - origjiffies) < 2 * HZ);
+
+	/* Just check to be sure */
+	vbat = wctdm_getreg(wc, card, 82);
+	printk(KERN_DEBUG "ProSLIC on module %d powered up to -%d volts (%02x) in %d ms\n",
+		       card, vbat * 376 / 1000, vbat, (int)(((jiffies - origjiffies) * 1000 / HZ)));
+#endif
+#endif
+	return 0;
+
+}
+
+static int wctdm_proslic_manual_calibrate(struct wctdm *wc, int card){
+	unsigned long origjiffies;
+	unsigned char i;
+
+	wctdm_setreg(wc, card, 21, 0);//(0)  Disable all interupts in DR21
+	wctdm_setreg(wc, card, 22, 0);//(0)Disable all interupts in DR21
+	wctdm_setreg(wc, card, 23, 0);//(0)Disable all interupts in DR21
+	wctdm_setreg(wc, card, 64, 0);//(0)
+
+	wctdm_setreg(wc, card, 97, 0x18); //(0x18)Calibrations without the ADC and DAC offset and without common mode calibration.
+	wctdm_setreg(wc, card, 96, 0x47); //(0x47)	Calibrate common mode and differential DAC mode DAC + ILIM
+
+	origjiffies=jiffies;
+	while( wctdm_getreg(wc,card,96)!=0 ){
+		if((jiffies-origjiffies)>80)
+			return -1;
+	}
+//Initialized DR 98 and 99 to get consistant results.
+// 98 and 99 are the results registers and the search should have same intial conditions.
+
+/*******************************The following is the manual gain mismatch calibration****************************/
+/*******************************This is also available as a function *******************************************/
+	// Delay 10ms
+	origjiffies=jiffies; 
+	while((jiffies-origjiffies)<1);
+	wctdm_proslic_setreg_indirect(wc, card, 88,0);
+	wctdm_proslic_setreg_indirect(wc,card,89,0);
+	wctdm_proslic_setreg_indirect(wc,card,90,0);
+	wctdm_proslic_setreg_indirect(wc,card,91,0);
+	wctdm_proslic_setreg_indirect(wc,card,92,0);
+	wctdm_proslic_setreg_indirect(wc,card,93,0);
+
+	wctdm_setreg(wc, card, 98,0x10); // This is necessary if the calibration occurs other than at reset time
+	wctdm_setreg(wc, card, 99,0x10);
+
+	for ( i=0x1f; i>0; i--)
+	{
+		wctdm_setreg(wc, card, 98,i);
+		origjiffies=jiffies; 
+		while((jiffies-origjiffies)<4);
+		if((wctdm_getreg(wc,card,88)) == 0)
+			break;
+	} // for
+
+	for ( i=0x1f; i>0; i--)
+	{
+		wctdm_setreg(wc, card, 99,i);
+		origjiffies=jiffies; 
+		while((jiffies-origjiffies)<4);
+		if((wctdm_getreg(wc,card,89)) == 0)
+			break;
+	}//for
+
+/*******************************The preceding is the manual gain mismatch calibration****************************/
+/**********************************The following is the longitudinal Balance Cal***********************************/
+	wctdm_setreg(wc,card,64,1);
+	while((jiffies-origjiffies)<10); // Sleep 100?
+
+	wctdm_setreg(wc, card, 64, 0);
+	wctdm_setreg(wc, card, 23, 0x4);  // enable interrupt for the balance Cal
+	wctdm_setreg(wc, card, 97, 0x1); // this is a singular calibration bit for longitudinal calibration
+	wctdm_setreg(wc, card, 96,0x40);
+
+	wctdm_getreg(wc,card,96); /* Read Reg 96 just cause */
+
+	wctdm_setreg(wc, card, 21, 0xFF);
+	wctdm_setreg(wc, card, 22, 0xFF);
+	wctdm_setreg(wc, card, 23, 0xFF);
+
+	/**The preceding is the longitudinal Balance Cal***/
+	return(0);
+
+}
+#if 1
+static int wctdm_proslic_calibrate(struct wctdm *wc, int card)
+{
+	unsigned long origjiffies;
+	int x;
+	/* Perform all calibrations */
+	wctdm_setreg(wc, card, 97, 0x1f);
+	
+	/* Begin, no speedup */
+	wctdm_setreg(wc, card, 96, 0x5f);
+
+	/* Wait for it to finish */
+	origjiffies = jiffies;
+	while(wctdm_getreg(wc, card, 96)) {
+		if ((jiffies - origjiffies) > 2 * HZ) {
+			printk(KERN_NOTICE "Timeout waiting for calibration of module %d\n", card);
+			return -1;
+		}
+	}
+	
+	if (debug) {
+		/* Print calibration parameters */
+		printk(KERN_DEBUG "Calibration Vector Regs 98 - 107: \n");
+		for (x=98;x<108;x++) {
+			printk(KERN_DEBUG "%d: %02x\n", x, wctdm_getreg(wc, card, x));
+		}
+	}
+	return 0;
+}
+#endif
+
+static void wait_just_a_bit(int foo)
+{
+	long newjiffies;
+	newjiffies = jiffies + foo;
+	while(jiffies < newjiffies);
+}
+
+/*********************************************************************
+ * Set the hwgain on the analog modules
+ *
+ * card = the card position for this module (0-23)
+ * gain = gain in dB x10 (e.g. -3.5dB  would be gain=-35)
+ * tx = (0 for rx; 1 for tx)
+ *
+ *******************************************************************/
+static int wctdm_set_hwgain(struct wctdm *wc, int card, __s32 gain, __u32 tx)
+{
+	if (!(wc->modtype[card] == MOD_TYPE_FXO)) {
+		printk(KERN_NOTICE "Cannot adjust gain.  Unsupported module type!\n");
+		return -1;
+	}
+	if (tx) {
+		if (debug)
+			printk(KERN_DEBUG "setting FXO tx gain for card=%d to %d\n", card, gain);
+		if (gain >=  -150 && gain <= 0) {
+			wctdm_setreg(wc, card, 38, 16 + (gain/-10));
+			wctdm_setreg(wc, card, 40, 16 + (-gain%10));
+		} else if (gain <= 120 && gain > 0) {
+			wctdm_setreg(wc, card, 38, gain/10);
+			wctdm_setreg(wc, card, 40, (gain%10));
+		} else {
+			printk(KERN_INFO "FXO tx gain is out of range (%d)\n", gain);
+			return -1;
+		}
+	} else { /* rx */
+		if (debug)
+			printk(KERN_DEBUG "setting FXO rx gain for card=%d to %d\n", card, gain);
+		if (gain >=  -150 && gain <= 0) {
+			wctdm_setreg(wc, card, 39, 16+ (gain/-10));
+			wctdm_setreg(wc, card, 41, 16 + (-gain%10));
+		} else if (gain <= 120 && gain > 0) {
+			wctdm_setreg(wc, card, 39, gain/10);
+			wctdm_setreg(wc, card, 41, (gain%10));
+		} else {
+			printk(KERN_INFO "FXO rx gain is out of range (%d)\n", gain);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int wctdm_init_voicedaa(struct wctdm *wc, int card, int fast, int manual, int sane)
+{
+	unsigned char reg16=0, reg26=0, reg30=0, reg31=0;
+	long newjiffies;
+	wc->modtype[card] = MOD_TYPE_FXO;
+	/* Sanity check the ProSLIC */
+	reset_spi(wc, card);
+	if (!sane && wctdm_voicedaa_insane(wc, card))
+		return -2;
+
+	/* Software reset */
+	wctdm_setreg(wc, card, 1, 0x80);
+
+	/* Wait just a bit */
+	wait_just_a_bit(HZ/10);
+
+	/* Enable PCM, ulaw */
+	if (alawoverride)
+		wctdm_setreg(wc, card, 33, 0x20);
+	else
+		wctdm_setreg(wc, card, 33, 0x28);
+
+	/* Set On-hook speed, Ringer impedence, and ringer threshold */
+	reg16 |= (fxo_modes[_opermode].ohs << 6);
+	reg16 |= (fxo_modes[_opermode].rz << 1);
+	reg16 |= (fxo_modes[_opermode].rt);
+	wctdm_setreg(wc, card, 16, reg16);
+
+	if(fwringdetect) {
+		/* Enable ring detector full-wave rectifier mode */
+		wctdm_setreg(wc, card, 18, 2);
+		wctdm_setreg(wc, card, 24, 0);
+	} else { 
+		/* Set to the device defaults */
+		wctdm_setreg(wc, card, 18, 0);
+		wctdm_setreg(wc, card, 24, 0x19);
+	}
+	
+	/* Set DC Termination:
+	   Tip/Ring voltage adjust, minimum operational current, current limitation */
+	reg26 |= (fxo_modes[_opermode].dcv << 6);
+	reg26 |= (fxo_modes[_opermode].mini << 4);
+	reg26 |= (fxo_modes[_opermode].ilim << 1);
+	wctdm_setreg(wc, card, 26, reg26);
+
+	/* Set AC Impedence */ 
+	reg30 = (fxofullscale==1) ? (fxo_modes[_opermode].acim|0x10) :  (fxo_modes[_opermode].acim);
+	wctdm_setreg(wc, card, 30, reg30);
+
+	/* Misc. DAA parameters */
+	if (fastpickup)
+		reg31 = 0xb3;
+	else
+		reg31 = 0xa3;
+
+	reg31 |= (fxo_modes[_opermode].ohs2 << 3);
+	wctdm_setreg(wc, card, 31, reg31);
+
+	/* Set Transmit/Receive timeslot */
+	//printk("set card %d to %d\n", card, (3-(card%4)) * 8 + (card/4) * 64);
+	wctdm_setreg(wc, card, 34, (3-(card%4)) * 8 + (card/4) * 64);
+	wctdm_setreg(wc, card, 35, 0x00);
+	wctdm_setreg(wc, card, 36, (3-(card%4)) * 8 + (card/4) * 64);
+	wctdm_setreg(wc, card, 37, 0x00);
+
+	/* Enable ISO-Cap */
+	wctdm_setreg(wc, card, 6, 0x00);
+
+	if (fastpickup)
+		wctdm_setreg(wc, card, 17, wctdm_getreg(wc, card, 17) | 0x20);
+
+	/* Wait 1000ms for ISO-cap to come up */
+	newjiffies = jiffies;
+	newjiffies += 2 * HZ;
+	while((jiffies < newjiffies) && !(wctdm_getreg(wc, card, 11) & 0xf0))
+		wait_just_a_bit(HZ/10);
+
+	if (!(wctdm_getreg(wc, card, 11) & 0xf0)) {
+		printk(KERN_NOTICE "VoiceDAA did not bring up ISO link properly!\n");
+		return -1;
+	}
+	if (debug)
+		printk(KERN_DEBUG "ISO-Cap is now up, line side: %02x rev %02x\n", 
+		       wctdm_getreg(wc, card, 11) >> 4,
+		       (wctdm_getreg(wc, card, 13) >> 2) & 0xf);
+	/* Enable on-hook line monitor */
+	wctdm_setreg(wc, card, 5, 0x08);
+
+	/* Take values for fxotxgain and fxorxgain and apply them to module */
+	wctdm_set_hwgain(wc, card, fxotxgain, 1);
+	wctdm_set_hwgain(wc, card, fxorxgain, 0);
+
+	/* NZ -- crank the tx gain up by 7 dB */
+	if (!strcmp(fxo_modes[_opermode].name, "NEWZEALAND")) {
+		printk(KERN_INFO "Adjusting gain\n");
+		wctdm_set_hwgain(wc, card, 7, 1);
+	}
+
+	if(debug)
+		printk(KERN_DEBUG "DEBUG fxotxgain:%i.%i fxorxgain:%i.%i\n", (wctdm_getreg(wc, card, 38)/16)?-(wctdm_getreg(wc, card, 38) - 16) : wctdm_getreg(wc, card, 38), (wctdm_getreg(wc, card, 40)/16)? -(wctdm_getreg(wc, card, 40) - 16):wctdm_getreg(wc, card, 40), (wctdm_getreg(wc, card, 39)/16)? -(wctdm_getreg(wc, card, 39) - 16) : wctdm_getreg(wc, card, 39),(wctdm_getreg(wc, card, 41)/16)?-(wctdm_getreg(wc, card, 41) - 16):wctdm_getreg(wc, card, 41));
+
+    return 0;
+		
+}
+
+static int wctdm_init_proslic(struct wctdm *wc, int card, int fast, int manual, int sane)
+{
+
+	unsigned short tmp[5];
+	unsigned char r19, r9;
+	int x;
+	int fxsmode=0;
+
+	/* Sanity check the ProSLIC */
+	if (!sane && wctdm_proslic_insane(wc, card))
+		return -2;
+
+	/* By default, don't send on hook */
+	if (reversepolarity)
+		wc->mod[card].fxs.idletxhookstate = 5;
+	else
+		wc->mod[card].fxs.idletxhookstate = 1;
+		
+	if (sane) {
+		/* Make sure we turn off the DC->DC converter to prevent anything from blowing up */
+		wctdm_setreg(wc, card, 14, 0x10);
+	}
+
+	if (wctdm_proslic_init_indirect_regs(wc, card)) {
+		printk(KERN_INFO "Indirect Registers failed to initialize on module %d.\n", card);
+		return -1;
+	}
+
+	/* Clear scratch pad area */
+	wctdm_proslic_setreg_indirect(wc, card, 97,0);
+
+	/* Clear digital loopback */
+	wctdm_setreg(wc, card, 8, 0);
+
+	/* Revision C optimization */
+	wctdm_setreg(wc, card, 108, 0xeb);
+
+	/* Disable automatic VBat switching for safety to prevent
+	   Q7 from accidently turning on and burning out. */
+	wctdm_setreg(wc, card, 67, 0x07);  /* Note, if pulse dialing has problems at high REN loads
+					      change this to 0x17 */
+
+	/* Turn off Q7 */
+	wctdm_setreg(wc, card, 66, 1);
+
+	/* Flush ProSLIC digital filters by setting to clear, while
+	   saving old values */
+	for (x=0;x<5;x++) {
+		tmp[x] = wctdm_proslic_getreg_indirect(wc, card, x + 35);
+		wctdm_proslic_setreg_indirect(wc, card, x + 35, 0x8000);
+	}
+
+	/* Power up the DC-DC converter */
+	if (wctdm_powerup_proslic(wc, card, fast)) {
+		printk(KERN_NOTICE "Unable to do INITIAL ProSLIC powerup on module %d\n", card);
+		return -1;
+	}
+
+	if (!fast) {
+
+		/* Check for power leaks */
+		if (wctdm_proslic_powerleak_test(wc, card)) {
+			printk(KERN_NOTICE "ProSLIC module %d failed leakage test.  Check for short circuit\n", card);
+		}
+		/* Power up again */
+		if (wctdm_powerup_proslic(wc, card, fast)) {
+			printk(KERN_NOTICE "Unable to do FINAL ProSLIC powerup on module %d\n", card);
+			return -1;
+		}
+#ifndef NO_CALIBRATION
+		/* Perform calibration */
+		if(manual) {
+			if (wctdm_proslic_manual_calibrate(wc, card)) {
+				//printk(KERN_NOTICE "Proslic failed on Manual Calibration\n");
+				if (wctdm_proslic_manual_calibrate(wc, card)) {
+					printk(KERN_NOTICE "Proslic Failed on Second Attempt to Calibrate Manually. (Try -DNO_CALIBRATION in Makefile)\n");
+					return -1;
+				}
+				printk(KERN_NOTICE "Proslic Passed Manual Calibration on Second Attempt\n");
+			}
+		}
+		else {
+			if(wctdm_proslic_calibrate(wc, card))  {
+				//printk(KERN_NOTICE "ProSlic died on Auto Calibration.\n");
+				if (wctdm_proslic_calibrate(wc, card)) {
+					printk(KERN_NOTICE "Proslic Failed on Second Attempt to Auto Calibrate\n");
+					return -1;
+				}
+				printk(KERN_NOTICE "Proslic Passed Auto Calibration on Second Attempt\n");
+			}
+		}
+		/* Perform DC-DC calibration */
+		wctdm_setreg(wc, card, 93, 0x99);
+		r19 = wctdm_getreg(wc, card, 107);
+		if ((r19 < 0x2) || (r19 > 0xd)) {
+			printk(KERN_NOTICE "DC-DC cal has a surprising direct 107 of 0x%02x!\n", r19);
+			wctdm_setreg(wc, card, 107, 0x8);
+		}
+
+		/* Save calibration vectors */
+		for (x=0;x<NUM_CAL_REGS;x++)
+			wc->mod[card].fxs.calregs.vals[x] = wctdm_getreg(wc, card, 96 + x);
+#endif
+
+	} else {
+		/* Restore calibration registers */
+		for (x=0;x<NUM_CAL_REGS;x++)
+			wctdm_setreg(wc, card, 96 + x, wc->mod[card].fxs.calregs.vals[x]);
+	}
+	/* Calibration complete, restore original values */
+	for (x=0;x<5;x++) {
+		wctdm_proslic_setreg_indirect(wc, card, x + 35, tmp[x]);
+	}
+
+	if (wctdm_proslic_verify_indirect_regs(wc, card)) {
+		printk(KERN_INFO "Indirect Registers failed verification.\n");
+		return -1;
+	}
+
+
+#if 0
+    /* Disable Auto Power Alarm Detect and other "features" */
+    wctdm_setreg(wc, card, 67, 0x0e);
+    blah = wctdm_getreg(wc, card, 67);
+#endif
+
+#if 0
+    if (wctdm_proslic_setreg_indirect(wc, card, 97, 0x0)) { // Stanley: for the bad recording fix
+		 printk(KERN_INFO "ProSlic IndirectReg Died.\n");
+		 return -1;
+	}
+#endif
+
+    if (alawoverride)
+    	wctdm_setreg(wc, card, 1, 0x20);
+    else
+    	wctdm_setreg(wc, card, 1, 0x28);
+  // U-Law 8-bit interface
+    wctdm_setreg(wc, card, 2, (3-(card%4)) * 8 + (card/4) * 64);    // Tx Start count low byte  0
+    wctdm_setreg(wc, card, 3, 0);    // Tx Start count high byte 0
+    wctdm_setreg(wc, card, 4, (3-(card%4)) * 8 + (card/4) * 64);    // Rx Start count low byte  0
+    wctdm_setreg(wc, card, 5, 0);    // Rx Start count high byte 0
+    wctdm_setreg(wc, card, 18, 0xff);     // clear all interrupt
+    wctdm_setreg(wc, card, 19, 0xff);
+    wctdm_setreg(wc, card, 20, 0xff);
+    wctdm_setreg(wc, card, 73, 0x04);
+	if (fxshonormode) {
+		fxsmode = acim2tiss[fxo_modes[_opermode].acim];
+		wctdm_setreg(wc, card, 10, 0x08 | fxsmode);
+		if (fxo_modes[_opermode].ring_osc)
+			wctdm_proslic_setreg_indirect(wc, card, 20, fxo_modes[_opermode].ring_osc);
+		if (fxo_modes[_opermode].ring_x)
+			wctdm_proslic_setreg_indirect(wc, card, 21, fxo_modes[_opermode].ring_x);
+	}
+    if (lowpower)
+    	wctdm_setreg(wc, card, 72, 0x10);
+
+#if 0
+    wctdm_setreg(wc, card, 21, 0x00); 	// enable interrupt
+    wctdm_setreg(wc, card, 22, 0x02); 	// Loop detection interrupt
+    wctdm_setreg(wc, card, 23, 0x01); 	// DTMF detection interrupt
+#endif
+
+#if 0
+    /* Enable loopback */
+    wctdm_setreg(wc, card, 8, 0x2);
+    wctdm_setreg(wc, card, 14, 0x0);
+    wctdm_setreg(wc, card, 64, 0x0);
+    wctdm_setreg(wc, card, 1, 0x08);
+#endif
+
+	if (fastringer) {
+		/* Speed up Ringer */
+		wctdm_proslic_setreg_indirect(wc, card, 20, 0x7e6d);
+		wctdm_proslic_setreg_indirect(wc, card, 21, 0x01b9);
+		/* Beef up Ringing voltage to 89V */
+		if (boostringer) {
+			wctdm_setreg(wc, card, 74, 0x3f);
+			if (wctdm_proslic_setreg_indirect(wc, card, 21, 0x247)) 
+				return -1;
+			printk(KERN_INFO  "Boosting fast ringer on slot %d (89V peak)\n", card + 1);
+		} else if (lowpower) {
+			if (wctdm_proslic_setreg_indirect(wc, card, 21, 0x14b)) 
+				return -1;
+			printk(KERN_INFO  "Reducing fast ring power on slot %d (50V peak)\n", card + 1);
+		} else
+			printk(KERN_INFO  "Speeding up ringer on slot %d (25Hz)\n", card + 1);
+	} else {
+		/* Beef up Ringing voltage to 89V */
+		if (boostringer) {
+			wctdm_setreg(wc, card, 74, 0x3f);
+			if (wctdm_proslic_setreg_indirect(wc, card, 21, 0x1d1)) 
+				return -1;
+			printk(KERN_INFO  "Boosting ringer on slot %d (89V peak)\n", card + 1);
+		} else if (lowpower) {
+			if (wctdm_proslic_setreg_indirect(wc, card, 21, 0x108)) 
+				return -1;
+			printk(KERN_INFO  "Reducing ring power on slot %d (50V peak)\n", card + 1);
+		}
+	}
+
+	if(fxstxgain || fxsrxgain) {
+		r9 = wctdm_getreg(wc, card, 9);
+		switch (fxstxgain) {
+		
+			case 35:
+				r9+=8;
+				break;
+			case -35:
+				r9+=4;
+				break;
+			case 0: 
+				break;
+		}
+	
+		switch (fxsrxgain) {
+			
+			case 35:
+				r9+=2;
+				break;
+			case -35:
+				r9+=1;
+				break;
+			case 0:
+				break;
+		}
+		wctdm_setreg(wc,card,9,r9);
+	}
+
+	if(debug)
+		printk(KERN_DEBUG "DEBUG: fxstxgain:%s fxsrxgain:%s\n",((wctdm_getreg(wc, card, 9)/8) == 1)?"3.5":(((wctdm_getreg(wc,card,9)/4) == 1)?"-3.5":"0.0"),((wctdm_getreg(wc, card, 9)/2) == 1)?"3.5":((wctdm_getreg(wc,card,9)%2)?"-3.5":"0.0"));
+
+	wctdm_setreg(wc, card, 64, 0x01);
+	return 0;
+}
+
+
+static int wctdm_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
+{
+	struct wctdm_stats stats;
+	struct wctdm_regs regs;
+	struct wctdm_regop regop;
+	struct wctdm_echo_coefs echoregs;
+	struct dahdi_hwgain hwgain;
+	struct wctdm *wc = chan->pvt;
+	int x;
+	switch (cmd) {
+	case DAHDI_ONHOOKTRANSFER:
+		if (wc->modtype[chan->chanpos - 1] != MOD_TYPE_FXS)
+			return -EINVAL;
+		if (get_user(x, (__user  int *)data))
+			return -EFAULT;
+		wc->mod[chan->chanpos - 1].fxs.ohttimer = x << 3;
+		if (reversepolarity)
+			wc->mod[chan->chanpos - 1].fxs.idletxhookstate = 0x6;	/* OHT mode when idle */
+		else
+			wc->mod[chan->chanpos - 1].fxs.idletxhookstate = 0x2;
+		if (wc->mod[chan->chanpos - 1].fxs.lasttxhook == 0x1 || wc->mod[chan->chanpos - 1].fxs.lasttxhook == 0x5) {
+				/* Apply the change if appropriate */
+				if (reversepolarity)
+					wc->mod[chan->chanpos - 1].fxs.lasttxhook = 0x6;
+				else
+					wc->mod[chan->chanpos - 1].fxs.lasttxhook = 0x2;
+				wctdm_setreg(wc, chan->chanpos - 1, 64, wc->mod[chan->chanpos - 1].fxs.lasttxhook);
+		}
+		break;
+	case DAHDI_SETPOLARITY:
+		if (get_user(x, (__user int *)data))
+			return -EFAULT;
+		if (wc->modtype[chan->chanpos - 1] != MOD_TYPE_FXS)
+			return -EINVAL;
+		/* Can't change polarity while ringing or when open */
+		if ((wc->mod[chan->chanpos -1 ].fxs.lasttxhook == 0x04) ||
+		    (wc->mod[chan->chanpos -1 ].fxs.lasttxhook == 0x00))
+			return -EINVAL;
+
+		if ((x && !reversepolarity) || (!x && reversepolarity))
+			wc->mod[chan->chanpos - 1].fxs.lasttxhook |= 0x04;
+		else
+			wc->mod[chan->chanpos - 1].fxs.lasttxhook &= ~0x04;
+		wctdm_setreg(wc, chan->chanpos - 1, 64, wc->mod[chan->chanpos - 1].fxs.lasttxhook);
+		break;
+	case WCTDM_GET_STATS:
+		if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXS) {
+			stats.tipvolt = wctdm_getreg(wc, chan->chanpos - 1, 80) * -376;
+			stats.ringvolt = wctdm_getreg(wc, chan->chanpos - 1, 81) * -376;
+			stats.batvolt = wctdm_getreg(wc, chan->chanpos - 1, 82) * -376;
+		} else if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXO) {
+			stats.tipvolt = (signed char)wctdm_getreg(wc, chan->chanpos - 1, 29) * 1000;
+			stats.ringvolt = (signed char)wctdm_getreg(wc, chan->chanpos - 1, 29) * 1000;
+			stats.batvolt = (signed char)wctdm_getreg(wc, chan->chanpos - 1, 29) * 1000;
+		} else 
+			return -EINVAL;
+		if (copy_to_user((__user void *)data, &stats, sizeof(stats)))
+			return -EFAULT;
+		break;
+	case WCTDM_GET_REGS:
+		if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXS) {
+			for (x=0;x<NUM_INDIRECT_REGS;x++)
+				regs.indirect[x] = wctdm_proslic_getreg_indirect(wc, chan->chanpos -1, x);
+			for (x=0;x<NUM_REGS;x++)
+				regs.direct[x] = wctdm_getreg(wc, chan->chanpos - 1, x);
+		} else {
+			memset(&regs, 0, sizeof(regs));
+			for (x=0;x<NUM_FXO_REGS;x++)
+				regs.direct[x] = wctdm_getreg(wc, chan->chanpos - 1, x);
+		}
+		if (copy_to_user((__user void *)data, &regs, sizeof(regs)))
+			return -EFAULT;
+		break;
+	case WCTDM_SET_REG:
+		if (copy_from_user(&regop, (__user void *)data, sizeof(regop)))
+			return -EFAULT;
+		if (regop.indirect) {
+			if (wc->modtype[chan->chanpos - 1] != MOD_TYPE_FXS)
+				return -EINVAL;
+			printk(KERN_INFO  "Setting indirect %d to 0x%04x on %d\n", regop.reg, regop.val, chan->chanpos);
+			wctdm_proslic_setreg_indirect(wc, chan->chanpos - 1, regop.reg, regop.val);
+		} else {
+			regop.val &= 0xff;
+			printk(KERN_INFO  "Setting direct %d to %04x on %d\n", regop.reg, regop.val, chan->chanpos);
+			wctdm_setreg(wc, chan->chanpos - 1, regop.reg, regop.val);
+		}
+		break;
+	case WCTDM_SET_ECHOTUNE:
+		printk(KERN_INFO  "-- Setting echo registers: \n");
+		if (copy_from_user(&echoregs, (__user void *)data, sizeof(echoregs)))
+			return -EFAULT;
+
+		if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXO) {
+			/* Set the ACIM register */
+			wctdm_setreg(wc, chan->chanpos - 1, 30, (fxofullscale==1) ? (echoregs.acim|0x10) : echoregs.acim);
+
+			/* Set the digital echo canceller registers */
+			wctdm_setreg(wc, chan->chanpos - 1, 45, echoregs.coef1);
+			wctdm_setreg(wc, chan->chanpos - 1, 46, echoregs.coef2);
+			wctdm_setreg(wc, chan->chanpos - 1, 47, echoregs.coef3);
+			wctdm_setreg(wc, chan->chanpos - 1, 48, echoregs.coef4);
+			wctdm_setreg(wc, chan->chanpos - 1, 49, echoregs.coef5);
+			wctdm_setreg(wc, chan->chanpos - 1, 50, echoregs.coef6);
+			wctdm_setreg(wc, chan->chanpos - 1, 51, echoregs.coef7);
+			wctdm_setreg(wc, chan->chanpos - 1, 52, echoregs.coef8);
+
+			printk(KERN_INFO  "-- Set echo registers successfully\n");
+
+			break;
+		} else {
+			return -EINVAL;
+
+		}
+		break;
+	case DAHDI_SET_HWGAIN:
+		if (copy_from_user(&hwgain, (__user void *) data, sizeof(hwgain)))
+			return -EFAULT;
+
+		wctdm_set_hwgain(wc, chan->chanpos-1, hwgain.newgain, hwgain.tx);
+
+		if (debug)
+			printk(KERN_DEBUG  "Setting hwgain on channel %d to %d for %s direction\n", 
+				chan->chanpos-1, hwgain.newgain, hwgain.tx ? "tx" : "rx");
+		break;
+	default:
+		return -ENOTTY;
+	}
+	return 0;
+
+}
+
+static int wctdm_open(struct dahdi_chan *chan)
+{
+	struct wctdm *wc = chan->pvt;
+	if (!(wc->cardflag & (1 << (chan->chanpos - 1))))
+		return -ENODEV;
+	if (wc->dead)
+		return -ENODEV;
+	wc->usecount++;
+
+	/*MOD_INC_USE_COUNT; */
+	try_module_get(THIS_MODULE);
+	return 0;
+}
+
+static inline struct wctdm *wctdm_from_span(struct dahdi_span *span)
+{
+	return container_of(span, struct wctdm, span);
+}
+
+static int wctdm_watchdog(struct dahdi_span *span, int event)
+{
+	printk(KERN_INFO "opvxa1200: Restarting DMA\n");
+	wctdm_restart_dma(wctdm_from_span(span));
+	return 0;
+}
+
+static int wctdm_close(struct dahdi_chan *chan)
+{
+	struct wctdm *wc = chan->pvt;
+	wc->usecount--;
+
+	/*MOD_DEC_USE_COUNT;*/
+	module_put(THIS_MODULE);
+
+	if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXS) {
+		if (reversepolarity)
+			wc->mod[chan->chanpos - 1].fxs.idletxhookstate = 5;
+		else
+			wc->mod[chan->chanpos - 1].fxs.idletxhookstate = 1;
+	}
+	/* If we're dead, release us now */
+	if (!wc->usecount && wc->dead) 
+		wctdm_release(wc);
+	return 0;
+}
+
+static int wctdm_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig)
+{
+	struct wctdm *wc = chan->pvt;
+	int reg=0;
+	if (wc->modtype[chan->chanpos - 1] == MOD_TYPE_FXO) {
+		/* XXX Enable hooksig for FXO XXX */
+		switch(txsig) {
+		case DAHDI_TXSIG_START:
+		case DAHDI_TXSIG_OFFHOOK:
+			wc->mod[chan->chanpos - 1].fxo.offhook = 1;
+			wctdm_setreg(wc, chan->chanpos - 1, 5, 0x9);
+			if(cidbeforering)
+			{
+				wc->cid_state[chan->chanpos - 1] = CID_STATE_IDLE;
+				wc->cid_history_clone_cnt[chan->chanpos - 1] = 0;
+				wc->cid_history_ptr[chan->chanpos - 1] = 0;
+				memset(wc->cid_history_buf[chan->chanpos - 1], DAHDI_LIN2X(0, chan), cidbuflen * DAHDI_MAX_CHUNKSIZE);
+			}
+			break;
+		case DAHDI_TXSIG_ONHOOK:
+			wc->mod[chan->chanpos - 1].fxo.offhook = 0;
+			wctdm_setreg(wc, chan->chanpos - 1, 5, 0x8);
+			break;
+		default:
+			printk(KERN_NOTICE "wcfxo: Can't set tx state to %d\n", txsig);
+		}
+	} else {
+		switch(txsig) {
+		case DAHDI_TXSIG_ONHOOK:
+			switch(chan->sig) {
+			case DAHDI_SIG_EM:
+			case DAHDI_SIG_FXOKS:
+			case DAHDI_SIG_FXOLS:
+				wc->mod[chan->chanpos-1].fxs.lasttxhook = wc->mod[chan->chanpos-1].fxs.idletxhookstate;
+				break;
+			case DAHDI_SIG_FXOGS:
+				wc->mod[chan->chanpos-1].fxs.lasttxhook = 3;
+				break;
+			}
+			break;
+		case DAHDI_TXSIG_OFFHOOK:
+			switch(chan->sig) {
+			case DAHDI_SIG_EM:
+				wc->mod[chan->chanpos-1].fxs.lasttxhook = 5;
+				break;
+			default:
+				wc->mod[chan->chanpos-1].fxs.lasttxhook = wc->mod[chan->chanpos-1].fxs.idletxhookstate;
+				break;
+			}
+			break;
+		case DAHDI_TXSIG_START:
+			wc->mod[chan->chanpos-1].fxs.lasttxhook = 4;
+			break;
+		case DAHDI_TXSIG_KEWL:
+			wc->mod[chan->chanpos-1].fxs.lasttxhook = 0;
+			break;
+		default:
+			printk(KERN_NOTICE "opvxa1200: Can't set tx state to %d\n", txsig);
+		}
+		if (debug)
+			printk(KERN_DEBUG "Setting FXS hook state to %d (%02x)\n", txsig, reg);
+
+#if 1
+		wctdm_setreg(wc, chan->chanpos - 1, 64, wc->mod[chan->chanpos-1].fxs.lasttxhook);
+#endif
+	}
+	return 0;
+}
+
+#ifdef DAHDI_SPAN_OPS
+static const struct dahdi_span_ops wctdm_span_ops = {
+	.owner = THIS_MODULE,
+	.hooksig = wctdm_hooksig,
+	.open = wctdm_open,
+	.close = wctdm_close,
+	.ioctl = wctdm_ioctl,
+	.watchdog = wctdm_watchdog,
+};
+#endif
+
+static int wctdm_initialize(struct wctdm *wc)
+{
+	int x;
+
+	/* Dahdi stuff */
+	sprintf(wc->span.name, "OPVXA1200/%d", wc->pos);
+	snprintf(wc->span.desc, sizeof(wc->span.desc)-1, "%s Board %d", wc->variety, wc->pos + 1);
+	wc->ddev->location = kasprintf(GFP_KERNEL,
+				      "PCI Bus %02d Slot %02d",
+				      wc->dev->bus->number,
+				      PCI_SLOT(wc->dev->devfn) + 1);
+	if (!wc->ddev->location) {
+		dahdi_free_device(wc->ddev);
+		wc->ddev = NULL;
+		return -ENOMEM;
+	}
+	wc->ddev->manufacturer = "OpenVox";
+	wc->ddev->devicetype = wc->variety;
+	if (alawoverride) {
+		printk(KERN_INFO "ALAW override parameter detected.  Device will be operating in ALAW\n");
+		wc->span.deflaw = DAHDI_LAW_ALAW;
+	} else
+		wc->span.deflaw = DAHDI_LAW_MULAW;
+		
+	x = __wctdm_getcreg(wc, WC_VER);
+	wc->fwversion = x;
+	if( x & FLAG_A800)
+	{
+		wc->card_name = A800P_Name;
+		wc->max_cards = 8;
+	}
+	else
+	{
+		wc->card_name = A1200P_Name;
+		wc->max_cards = 12;
+	}
+		
+	for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) {
+		sprintf(wc->chans[x]->name, "OPVXA1200/%d/%d", wc->pos, x);
+		wc->chans[x]->sigcap = DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_SF | DAHDI_SIG_EM | DAHDI_SIG_CLEAR;
+		wc->chans[x]->sigcap |= DAHDI_SIG_FXSKS | DAHDI_SIG_FXSLS | DAHDI_SIG_SF | DAHDI_SIG_CLEAR;
+		wc->chans[x]->chanpos = x+1;
+		wc->chans[x]->pvt = wc;
+	}
+
+#ifdef DAHDI_SPAN_MODULE	
+		wc->span.owner = THIS_MODULE;
+#endif
+
+#ifdef DAHDI_SPAN_OPS
+	wc->span.ops = &wctdm_span_ops;
+#else
+		wc->span.hooksig = wctdm_hooksig,
+		wc->span.watchdog = wctdm_watchdog,
+		wc->span.open = wctdm_open;
+		wc->span.close  = wctdm_close;
+		wc->span.ioctl = wctdm_ioctl;
+		wc->span.pvt = wc;
+#endif
+	wc->span.chans = wc->chans;
+	wc->span.channels = wc->max_cards;	/*MAX_NUM_CARDS;*/
+	wc->span.flags = DAHDI_FLAG_RBS;
+	wc->span.ops = &wctdm_span_ops;
+
+	list_add_tail(&wc->span.device_node, &wc->ddev->spans);
+	if (dahdi_register_device(wc->ddev, &wc->dev->dev)) {
+		printk(KERN_NOTICE "Unable to register device %s with DAHDI\n",
+				wc->span.name);
+		kfree(wc->ddev->location);
+		dahdi_free_device(wc->ddev);
+		wc->ddev = NULL;
+		return -1;
+	}
+	return 0;
+}
+
+static void wctdm_post_initialize(struct wctdm *wc)
+{
+	int x;
+
+	/* Finalize signalling  */
+	for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) {
+		if (wc->cardflag & (1 << x)) {
+			if (wc->modtype[x] == MOD_TYPE_FXO)
+				wc->chans[x]->sigcap = DAHDI_SIG_FXSKS | DAHDI_SIG_FXSLS | DAHDI_SIG_SF | DAHDI_SIG_CLEAR;
+			else
+				wc->chans[x]->sigcap = DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_SF | DAHDI_SIG_EM | DAHDI_SIG_CLEAR;
+		} else if (!(wc->chans[x]->sigcap & DAHDI_SIG_BROKEN)) {
+			wc->chans[x]->sigcap = 0;
+		}
+	}
+}
+
+static int wctdm_hardware_init(struct wctdm *wc)
+{
+	/* Hardware stuff */
+	unsigned char ver;
+	unsigned char x,y;
+	int failed;
+	long origjiffies; //ml.
+	
+	/* Signal Reset */
+	printk("before raise reset\n");
+	outb(0x01, wc->ioaddr + WC_CNTL);
+
+	/* Wait for 5 second */
+	
+	origjiffies = jiffies;
+
+	while(1) 
+	{
+		if ((jiffies - origjiffies) >= (HZ*5))
+			break;;
+	}
+
+	/* printk(KERN_INFO "after raise reset\n");*/
+
+	/* Check OpenVox chip */
+	x=inb(wc->ioaddr + WC_CNTL);
+	ver = __wctdm_getcreg(wc, WC_VER);
+	wc->fwversion = ver;
+	/*if( ver & FLAG_A800)
+	{
+		wc->card_name = A800P_Name;
+		wc->max_cards = 8;
+	}
+	else
+	{
+		wc->card_name = A1200P_Name;
+		wc->max_cards = 12;
+	}*/
+	printk(KERN_NOTICE "OpenVox %s version: %01x.%01x\n", wc->card_name, (ver&(~FLAG_A800))>>4, ver&0x0f);
+	
+	failed = 0;
+	if (ver != 0x00) {
+		for (x=0;x<16;x++) {
+			/* Test registers */
+			__wctdm_setcreg(wc, WC_CS, x);
+			y = __wctdm_getcreg(wc, WC_CS) & 0x0f;
+			if (x != y) {
+				printk(KERN_INFO "%02x != %02x\n", x, y);
+				failed++;
+			}
+		}
+
+		if (!failed) {
+			printk(KERN_INFO "OpenVox %s passed register test\n", wc->card_name);
+		} else {
+			printk(KERN_NOTICE "OpenVox %s failed register test\n", wc->card_name);
+			return -1;
+		}
+	} else {
+		printk(KERN_INFO "No OpenVox chip %02x\n", ver);
+	}
+
+	if (spibyhw)
+		__wctdm_setcreg(wc, WC_SPICTRL, BIT_SPI_BYHW);	// spi controled by hw MiaoLin;
+	else
+		__wctdm_setcreg(wc, WC_SPICTRL, 0);	
+		
+	/* Reset PCI Interface chip and registers (and serial) */
+	outb(0x06, wc->ioaddr + WC_CNTL);
+	/* Setup our proper outputs for when we switch for our "serial" port */
+	wc->ios = BIT_CS | BIT_SCLK | BIT_SDI;
+
+	outb(wc->ios, wc->ioaddr + WC_AUXD);
+
+	/* Set all to outputs except AUX 5, which is an input */
+	outb(0xdf, wc->ioaddr + WC_AUXC);
+
+	/* Select alternate function for AUX0 */  /* Useless in OpenVox by MiaoLin. */
+	/* outb(0x4, wc->ioaddr + WC_AUXFUNC); */
+	
+	/* Wait 1/4 of a sec */
+	wait_just_a_bit(HZ/4);
+
+	/* Back to normal, with automatic DMA wrap around */
+	outb(0x30 | 0x01, wc->ioaddr + WC_CNTL);
+	wc->ledstate = 0;
+	wctdm_set_led(wc, 0, 0);
+	
+	/* Make sure serial port and DMA are out of reset */
+	outb(inb(wc->ioaddr + WC_CNTL) & 0xf9, wc->ioaddr + WC_CNTL);
+	
+	/* Configure serial port for MSB->LSB operation */
+	outb(0xc1, wc->ioaddr + WC_SERCTL);
+
+	/* Delay FSC by 0 so it's properly aligned */
+	outb(0x01, wc->ioaddr + WC_FSCDELAY);  /* Modify to 1 by MiaoLin */
+
+	/* Setup DMA Addresses */
+	outl(wc->writedma,                    wc->ioaddr + WC_DMAWS);		/* Write start */
+	outl(wc->writedma + DAHDI_CHUNKSIZE * 4 * 4 - 4, wc->ioaddr + WC_DMAWI);		/* Middle (interrupt) */
+	outl(wc->writedma + DAHDI_CHUNKSIZE * 8 * 4 - 4, wc->ioaddr + WC_DMAWE);			/* End */
+	
+	outl(wc->readdma,                    	 wc->ioaddr + WC_DMARS);	/* Read start */
+	outl(wc->readdma + DAHDI_CHUNKSIZE * 4 * 4 - 4, 	 wc->ioaddr + WC_DMARI);	/* Middle (interrupt) */
+	outl(wc->readdma + DAHDI_CHUNKSIZE * 8 * 4 - 4, wc->ioaddr + WC_DMARE);	/* End */
+	
+	/* Clear interrupts */
+	outb(0xff, wc->ioaddr + WC_INTSTAT);
+
+	/* Wait 1/4 of a second more */
+	wait_just_a_bit(HZ/4);
+
+	for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) {
+		int sane=0,ret=0,readi=0;
+#if 1
+		touch_softlockup_watchdog();  // avoid showing CPU softlock message
+		/* Init with Auto Calibration */
+		if (!(ret=wctdm_init_proslic(wc, x, 0, 0, sane))) {
+			wc->cardflag |= (1 << x);
+                        if (debug) {
+                                readi = wctdm_getreg(wc,x,LOOP_I_LIMIT);
+                                printk("Proslic module %d loop current is %dmA\n",x,
+                                ((readi*3)+20));
+                        }
+			printk(KERN_INFO "Module %d: Installed -- AUTO FXS/DPO\n",x);
+			wctdm_set_led(wc, (unsigned int)x, 1);
+		} else {
+			if(ret!=-2) {
+				sane=1;
+				
+				printk(KERN_INFO "Init ProSlic with Manual Calibration \n");
+				/* Init with Manual Calibration */
+				if (!wctdm_init_proslic(wc, x, 0, 1, sane)) {
+					wc->cardflag |= (1 << x);
+                                if (debug) {
+                                        readi = wctdm_getreg(wc,x,LOOP_I_LIMIT);
+                                        printk("Proslic module %d loop current is %dmA\n",x,
+                                        ((readi*3)+20));
+                                }
+					printk(KERN_INFO "Module %d: Installed -- MANUAL FXS\n",x);
+				} else {
+					printk(KERN_NOTICE "Module %d: FAILED FXS (%s)\n", x, fxshonormode ? fxo_modes[_opermode].name : "FCC");
+					wc->chans[x]->sigcap = __DAHDI_SIG_FXO | DAHDI_SIG_BROKEN;
+				} 
+			} else if (!(ret = wctdm_init_voicedaa(wc, x, 0, 0, sane))) {
+				wc->cardflag |= (1 << x);
+				printk(KERN_INFO "Module %d: Installed -- AUTO FXO (%s mode)\n",x, fxo_modes[_opermode].name);
+				wctdm_set_led(wc, (unsigned int)x, 1);
+			} else
+				printk(KERN_NOTICE "Module %d: Not installed\n", x);
+		}
+#endif
+	}
+
+	/* Return error if nothing initialized okay. */
+	if (!wc->cardflag && !timingonly)
+		return -1;
+	/*__wctdm_setcreg(wc, WC_SYNC, (wc->cardflag << 1) | 0x1); */  /* removed by MiaoLin */
+	return 0;
+}
+
+static void wctdm_enable_interrupts(struct wctdm *wc)
+{
+	/* Clear interrupts */
+	outb(0xff, wc->ioaddr + WC_INTSTAT);
+
+	/* Enable interrupts (we care about all of them) */
+	outb(0x3c, wc->ioaddr + WC_MASK0);
+	/* No external interrupts */
+	outb(0x00, wc->ioaddr + WC_MASK1);
+}
+
+static void wctdm_restart_dma(struct wctdm *wc)
+{
+	/* Reset Master and TDM */
+	outb(0x01, wc->ioaddr + WC_CNTL);
+	outb(0x01, wc->ioaddr + WC_OPER);
+}
+
+static void wctdm_start_dma(struct wctdm *wc)
+{
+	/* Reset Master and TDM */
+	outb(0x0f, wc->ioaddr + WC_CNTL);
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout(1);
+	outb(0x01, wc->ioaddr + WC_CNTL);
+	outb(0x01, wc->ioaddr + WC_OPER);
+}
+
+static void wctdm_stop_dma(struct wctdm *wc)
+{
+	outb(0x00, wc->ioaddr + WC_OPER);
+}
+
+static void wctdm_reset_tdm(struct wctdm *wc)
+{
+	/* Reset TDM */
+	outb(0x0f, wc->ioaddr + WC_CNTL);
+}
+
+static void wctdm_disable_interrupts(struct wctdm *wc)	
+{
+	outb(0x00, wc->ioaddr + WC_MASK0);
+	outb(0x00, wc->ioaddr + WC_MASK1);
+}
+
+static int __devinit wctdm_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	int res;
+	struct wctdm *wc;
+	struct wctdm_desc *d = (struct wctdm_desc *)ent->driver_data;
+	int x;
+	int y;
+
+	static int initd_ifaces=0;
+	
+	if(initd_ifaces){
+		memset((void *)ifaces,0,(sizeof(struct wctdm *))*WC_MAX_IFACES);
+		initd_ifaces=1;
+	}
+	for (x=0;x<WC_MAX_IFACES;x++)
+		if (!ifaces[x]) break;
+	if (x >= WC_MAX_IFACES) {
+		printk(KERN_NOTICE "Too many interfaces\n");
+		return -EIO;
+	}
+	
+	if (pci_enable_device(pdev)) {
+		res = -EIO;
+	} else {
+		wc = kmalloc(sizeof(struct wctdm), GFP_KERNEL);
+		if (wc) {
+			int cardcount = 0;
+			
+			wc->lastchan = -1;	/* first channel offset = -1; */
+			wc->ledstate = 0;
+			
+			ifaces[x] = wc;
+			memset(wc, 0, sizeof(struct wctdm));
+			for (x=0; x < sizeof(wc->chans)/sizeof(wc->chans[0]); ++x) {
+				wc->chans[x] = &wc->_chans[x];
+			}
+
+			spin_lock_init(&wc->lock);
+			wc->curcard = -1;
+			wc->ioaddr = pci_resource_start(pdev, 0);
+			wc->mem_region = pci_resource_start(pdev, 1);
+			wc->mem_len = pci_resource_len(pdev, 1);
+			wc->mem32 = (unsigned long)ioremap(wc->mem_region, wc->mem_len);
+			wc->dev = pdev;
+			wc->pos = x;
+			wc->variety = d->name;
+			for (y=0;y<MAX_NUM_CARDS;y++)
+				wc->flags[y] = d->flags;
+			/* Keep track of whether we need to free the region */
+			if (request_region(wc->ioaddr, 0xff, "opvxa1200")) 
+				wc->freeregion = 1;
+			else
+				wc->freeregion = 0;
+			
+			if (request_mem_region(wc->mem_region, wc->mem_len, "opvxa1200"))
+				wc->freeregion |= 0x02;
+
+			/* Allocate enough memory for two zt chunks, receive and transmit.  Each sample uses
+			   8 bits.  */
+			wc->writechunk = pci_alloc_consistent(pdev, DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2 * 2, &wc->writedma);
+			if (!wc->writechunk) {
+				printk(KERN_NOTICE "opvxa1200: Unable to allocate DMA-able memory\n");
+				if (wc->freeregion & 0x01)
+					release_region(wc->ioaddr, 0xff);
+				if (wc->freeregion & 0x02)
+				{
+					release_mem_region(wc->mem_region, wc->mem_len);
+					iounmap((void *)wc->mem32);
+				}
+				return -ENOMEM;
+			}
+
+			wc->readchunk = wc->writechunk + DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2;	/* in bytes */
+			wc->readdma = wc->writedma + DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2;	/* in bytes */
+			
+			if (wctdm_initialize(wc)) {
+				printk(KERN_NOTICE "opvxa1200: Unable to intialize FXS\n");
+				/* Set Reset Low */
+				x=inb(wc->ioaddr + WC_CNTL);
+				outb((~0x1)&x, wc->ioaddr + WC_CNTL);
+				/* Free Resources */
+				free_irq(pdev->irq, wc);
+				if (wc->freeregion & 0x01)
+					release_region(wc->ioaddr, 0xff);
+				if (wc->freeregion & 0x02)
+				{
+					release_mem_region(wc->mem_region, wc->mem_len);
+					iounmap((void *)wc->mem32);
+				}
+			}
+
+			/* Enable bus mastering */
+			pci_set_master(pdev);
+
+			/* Keep track of which device we are */
+			pci_set_drvdata(pdev, wc);
+
+
+			if (request_irq(pdev->irq, wctdm_interrupt, DAHDI_IRQ_SHARED, "opvxa1200", wc)) {
+				printk(KERN_NOTICE "opvxa1200: Unable to request IRQ %d\n", pdev->irq);
+				if (wc->freeregion & 0x01)
+					release_region(wc->ioaddr, 0xff);
+				if (wc->freeregion & 0x02)
+				{
+					release_mem_region(wc->mem_region, wc->mem_len);
+					iounmap((void *)wc->mem32);
+				}
+				pci_free_consistent(pdev,  DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2 * 2, (void *)wc->writechunk, wc->writedma);
+				pci_set_drvdata(pdev, NULL);
+				kfree(wc);
+				return -EIO;
+			}
+
+			if (wctdm_hardware_init(wc)) {
+				unsigned char w;
+
+				/* Set Reset Low */
+				w=inb(wc->ioaddr + WC_CNTL);
+				outb((~0x1)&w, wc->ioaddr + WC_CNTL);
+				/* Free Resources */
+				free_irq(pdev->irq, wc);
+				if (wc->freeregion & 0x01)
+					release_region(wc->ioaddr, 0xff);
+				if (wc->freeregion & 0x02)
+				{
+					release_mem_region(wc->mem_region, wc->mem_len);
+					iounmap((void *)wc->mem32);
+				}
+				pci_free_consistent(pdev,  DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2 * 2, (void *)wc->writechunk, wc->writedma);
+				pci_set_drvdata(pdev, NULL);
+				dahdi_unregister_device(wc->ddev);
+				kfree(wc->ddev->location);
+				dahdi_free_device(wc->ddev);
+				kfree(wc);
+				return -EIO;
+
+			}
+
+#ifdef TEST_LOG_INCOME_VOICE
+			for(x=0; x<MAX_NUM_CARDS+NUM_FLAG; x++)
+			{
+				wc->voc_buf[x] = kmalloc(voc_buffer_size, GFP_KERNEL);
+				wc->voc_ptr[x] = 0;
+			}
+#endif
+
+			if(cidbeforering) 
+			{		
+				int len = cidbuflen * DAHDI_MAX_CHUNKSIZE;
+				if(debug)
+					printk("cidbeforering support enabled, length is %d msec\n", cidbuflen);
+				for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) 
+				{
+					wc->cid_history_buf[x] = kmalloc(len, GFP_KERNEL);
+					wc->cid_history_ptr[x] = 0;
+					wc->cid_history_clone_cnt[x] = 0;
+					wc->cid_state[x] = CID_STATE_IDLE;
+				}
+			}
+			
+			wctdm_post_initialize(wc);
+
+			/* Enable interrupts */
+			wctdm_enable_interrupts(wc);
+			/* Initialize Write/Buffers to all blank data */
+			memset((void *)wc->writechunk,0, DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2 * 2);
+
+			/* Start DMA */
+			wctdm_start_dma(wc);
+
+			for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) {
+				if (wc->cardflag & (1 << x))
+					cardcount++;
+			}
+
+			printk(KERN_INFO "Found an OpenVox %s: Version %x.%x (%d modules)\n", wc->card_name, (wc->fwversion&(~FLAG_A800))>>4, wc->fwversion&0x0f, cardcount);
+			if(debug)
+				printk(KERN_DEBUG "OpenVox %s debug On\n", wc->card_name);
+			
+			res = 0;
+		} else
+			res = -ENOMEM;
+	}
+	return res;
+}
+
+static void wctdm_release(struct wctdm *wc)
+{
+#ifdef TEST_LOG_INCOME_VOICE
+	struct file * f = NULL;
+	mm_segment_t orig_fs;
+	int i;
+	char fname[20];
+#endif
+	
+	dahdi_unregister_device(wc->ddev);
+	kfree(wc->ddev->location);
+	dahdi_free_device(wc->ddev);
+	if (wc->freeregion & 0x01)
+		release_region(wc->ioaddr, 0xff);
+	if (wc->freeregion & 0x02)
+	{
+		release_mem_region(wc->mem_region, wc->mem_len);
+		iounmap((void *)wc->mem32);
+	}
+	
+#ifdef TEST_LOG_INCOME_VOICE
+	for(i=0; i<MAX_NUM_CARDS + NUM_FLAG; i++)
+	{
+		sprintf(fname, "//usr//%d.pcm", i); 
+		f = filp_open(fname, O_RDWR|O_CREAT, 00);
+	
+		if (!f || !f->f_op || !f->f_op->read)
+		{
+			printk("WARNING: File (read) object is a null pointer!!!\n");
+			continue;
+		}
+	
+		f->f_pos = 0;
+		
+		orig_fs = get_fs();
+		set_fs(KERNEL_DS); 
+		
+		if(wc->voc_buf[i])
+		{
+			f->f_op->write(f, wc->voc_buf[i], voc_buffer_size, &f->f_pos);
+			kfree(wc->voc_buf[i]);
+		}
+		
+		set_fs(orig_fs); 
+		fput(f);
+	}
+#endif
+ 
+	if(cidbeforering) 
+	{
+		int x;
+		for (x = 0; x < wc->max_cards/*MAX_NUM_CARDS*/; x++) 
+			kfree(wc->cid_history_buf[x]);
+	}
+ 
+	kfree(wc);
+	printk(KERN_INFO "Free an OpenVox A1200 card\n");
+}
+
+static void __devexit wctdm_remove_one(struct pci_dev *pdev)
+{
+	struct wctdm *wc = pci_get_drvdata(pdev);
+	if (wc) {
+
+		/* Stop any DMA */
+		wctdm_stop_dma(wc);
+		wctdm_reset_tdm(wc);
+
+		/* In case hardware is still there */
+		wctdm_disable_interrupts(wc);
+		
+		/* Immediately free resources */
+		pci_free_consistent(pdev,  DAHDI_MAX_CHUNKSIZE * (MAX_NUM_CARDS+NUM_FLAG) * 2 * 2, (void *)wc->writechunk, wc->writedma);
+		free_irq(pdev->irq, wc);
+
+		/* Reset PCI chip and registers */
+		if(wc->fwversion > 0x11)
+			outb(0x0e, wc->ioaddr + WC_CNTL);
+		else
+		{
+			wc->ledstate = 0;
+			wctdm_set_led(wc,0,0);	// power off all leds.
+		}
+
+		/* Release span, possibly delayed */
+		if (!wc->usecount)
+			wctdm_release(wc);
+		else
+			wc->dead = 1;
+	}
+}
+
+static struct pci_device_id wctdm_pci_tbl[] = {
+	{ 0xe159, 0x0001, 0x9100, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x9519, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x95D9, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x9500, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x9532, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme }, 
+	{ 0xe159, 0x0001, 0x8519, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x9559, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0xe159, 0x0001, 0x9599, PCI_ANY_ID, 0, 0, (unsigned long) &wctdme },
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, wctdm_pci_tbl);
+
+static struct pci_driver wctdm_driver = {
+	.name = "opvxa1200",
+	.probe =	wctdm_init_one,
+	.remove =	__devexit_p(wctdm_remove_one),
+	.suspend = NULL,
+	.resume =	NULL,
+	.id_table = wctdm_pci_tbl,
+};
+
+static int __init wctdm_init(void)
+{
+	int res;
+	int x;
+	for (x=0;x<(sizeof(fxo_modes) / sizeof(fxo_modes[0])); x++) {
+		if (!strcmp(fxo_modes[x].name, opermode))
+			break;
+	}
+	if (x < sizeof(fxo_modes) / sizeof(fxo_modes[0])) {
+		_opermode = x;
+	} else {
+		printk(KERN_NOTICE "Invalid/unknown operating mode '%s' specified.  Please choose one of:\n", opermode);
+		for (x=0;x<sizeof(fxo_modes) / sizeof(fxo_modes[0]); x++)
+			printk(KERN_INFO "  %s\n", fxo_modes[x].name);
+		printk(KERN_INFO "Note this option is CASE SENSITIVE!\n");
+		return -ENODEV;
+	}
+	if (!strcmp(fxo_modes[_opermode].name, "AUSTRALIA")) {
+		boostringer=1;
+		fxshonormode=1;
+}
+	if (battdebounce == 0) {
+		battdebounce = fxo_modes[_opermode].battdebounce;
+	}
+	if (battalarm == 0) {
+		battalarm = fxo_modes[_opermode].battalarm;
+	}
+	if (battthresh == 0) {
+		battthresh = fxo_modes[_opermode].battthresh;
+	}
+
+	res = dahdi_pci_module(&wctdm_driver);
+	if (res)
+		return -ENODEV;
+	return 0;
+}
+
+static void __exit wctdm_cleanup(void)
+{
+	pci_unregister_driver(&wctdm_driver);
+}
+
+module_param(debug, int, 0600);
+module_param(loopcurrent, int, 0600);
+module_param(reversepolarity, int, 0600);
+module_param(robust, int, 0600);
+module_param(opermode, charp, 0600);
+module_param(timingonly, int, 0600);
+module_param(lowpower, int, 0600);
+module_param(boostringer, int, 0600);
+module_param(fastringer, int, 0600);
+module_param(fxshonormode, int, 0600);
+module_param(battdebounce, uint, 0600);
+module_param(battthresh, uint, 0600);
+module_param(battalarm, uint, 0600);
+module_param(ringdebounce, int, 0600);
+module_param(dialdebounce, int, 0600);
+module_param(fwringdetect, int, 0600);
+module_param(alawoverride, int, 0600);
+module_param(fastpickup, int, 0600);
+module_param(fxotxgain, int, 0600);
+module_param(fxorxgain, int, 0600);
+module_param(fxstxgain, int, 0600);
+module_param(fxsrxgain, int, 0600);
+module_param(spibyhw, int, 0600);
+module_param(usememio, int, 0600);
+module_param(cidbeforering, int, 0600);
+module_param(cidbuflen, int, 0600);
+module_param(cidtimeout, int, 0600);
+module_param(fxofullscale, int, 0600);
+module_param(fixedtimepolarity, int, 0600);
+
+MODULE_DESCRIPTION("OpenVox A1200 Driver");
+MODULE_AUTHOR("MiaoLin <miaolin@openvox.com.cn>");
+MODULE_LICENSE("GPL v2");
+
+module_init(wctdm_init);
+module_exit(wctdm_cleanup);
diff --git a/drivers/dahdi/opvxd115/Kbuild b/drivers/dahdi/opvxd115/Kbuild
new file mode 100644
index 0000000..474997d
--- /dev/null
+++ b/drivers/dahdi/opvxd115/Kbuild
@@ -0,0 +1,32 @@
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_OPVXD115) += opvxd115.o
+
+FIRM_DIR	:= ../firmware
+
+EXTRA_CFLAGS += -I$(src)/.. $(shell $(src)/../oct612x/octasic-helper cflags $(src)/../oct612x) -Wno-undef
+
+ifeq ($(HOTPLUG_FIRMWARE),yes)
+  EXTRA_CFLAGS+=-DHOTPLUG_FIRMWARE
+endif
+
+opvxd115-objs := base.o vpm450m.o $(shell $(src)/../oct612x/octasic-helper objects ../oct612x)
+
+DAHDI_KERNEL_H_NAME:=kernel.h
+DAHDI_KERNEL_H_PATH:=$(DAHDI_INCLUDE)/dahdi/$(DAHDI_KERNEL_H_NAME)
+ifneq ($(DAHDI_KERNEL_H_PATH),)
+        DAHDI_SPAN_MODULE:=$(shell if grep -C 5 "struct dahdi_span {" $(DAHDI_KERNEL_H_PATH) | grep -q "struct module \*owner"; then echo "yes"; else echo "no"; fi)
+        DAHDI_SPAN_OPS:=$(shell if grep -q "struct dahdi_span_ops {" $(DAHDI_KERNEL_H_PATH); then echo "yes"; else echo "no"; fi)
+        ifeq ($(DAHDI_SPAN_MODULE),yes)
+                EXTRA_CFLAGS+=-DDAHDI_SPAN_MODULE
+        else
+                ifeq ($(DAHDI_SPAN_OPS),yes)
+                        EXTRA_CFLAGS+=-DDAHDI_SPAN_OPS
+                endif
+        endif
+endif
+
+ifneq ($(HOTPLUG_FIRMWARE),yes)
+opvxd115-objs += $(FIRM_DIR)/dahdi-fw-oct6114-032.o
+endif
+
+$(obj)/$(FIRM_DIR)/dahdi-fw-oct6114-032.o: $(obj)/base.o
+	$(MAKE) -C $(obj)/$(FIRM_DIR) dahdi-fw-oct6114-032.o
diff --git a/drivers/dahdi/opvxd115/Makefile b/drivers/dahdi/opvxd115/Makefile
new file mode 100644
index 0000000..baaab35
--- /dev/null
+++ b/drivers/dahdi/opvxd115/Makefile
@@ -0,0 +1,8 @@
+ifdef KBUILD_EXTMOD
+# We only get here on kernels 2.6.0-2.6.9 .
+# For newer kernels, Kbuild will be included directly by the kernel
+# build system.
+include $(src)/Kbuild
+
+else
+endif
diff --git a/drivers/dahdi/opvxd115/base.c b/drivers/dahdi/opvxd115/base.c
new file mode 100644
index 0000000..d3f37a2
--- /dev/null
+++ b/drivers/dahdi/opvxd115/base.c
@@ -0,0 +1,4908 @@
+/*
+ * OpenVox D115P/D115E PCI/PCI-E Driver version 0.1 01/07/2011
+ *
+ * Written by Mark Spencer <markster@digium.com>
+ * Modify from wct4xxp module by mark.liu@openvox.cn
+
+ * Based on previous works, designs, and archetectures conceived and
+ * written by Jim Dixon <jim@lambdatel.com>.
+ *
+ * Copyright (C) 2001 Jim Dixon / Zapata Telephony.
+ * Copyright (C) 2001-2010, Digium, Inc.
+ *
+ * All rights reserved.
+ *
+ */
+
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/version.h>
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+
+#include <dahdi/kernel.h>
+
+#include "opvxd115.h"
+#include "vpm450m.h"
+
+/* Work queues are a way to better distribute load on SMP systems */
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20))
+/*
+ * Work queues can significantly improve performance and scalability
+ * on multi-processor machines, but requires bypassing some kernel
+ * API's, so it's not guaranteed to be compatible with all kernels.
+ */
+/* #define ENABLE_WORKQUEUES */
+#endif
+
+/* Enable prefetching may help performance */
+#define ENABLE_PREFETCH
+
+/* Support first generation cards? */
+#define SUPPORT_GEN1 
+
+/* Define to get more attention-grabbing but slightly more I/O using
+   alarm status */
+#define FANCY_ALARM
+
+/* Define to support Digium Voice Processing Module expansion card */
+#define VPM_SUPPORT
+
+#define DEBUG_MAIN 		(1 << 0)
+#define DEBUG_DTMF 		(1 << 1)
+#define DEBUG_REGS 		(1 << 2)
+#define DEBUG_TSI  		(1 << 3)
+#define DEBUG_ECHOCAN 	(1 << 4)
+#define DEBUG_RBS 		(1 << 5)
+#define DEBUG_FRAMER		(1 << 6)
+
+/* Maximum latency to be used with Gen 5 */
+#define GEN5_MAX_LATENCY	127
+
+#define T4_BASE_SIZE (DAHDI_MAX_CHUNKSIZE * 32 * 4) 
+
+#ifdef ENABLE_WORKQUEUES
+#include <linux/cpu.h>
+
+/* XXX UGLY!!!! XXX  We have to access the direct structures of the workqueue which
+  are only defined within workqueue.c because they don't give us a routine to allow us
+  to nail a work to a particular thread of the CPU.  Nailing to threads gives us substantially
+  higher scalability in multi-CPU environments though! */
+
+/*
+ * The per-CPU workqueue (if single thread, we always use cpu 0's).
+ *
+ * The sequence counters are for flush_scheduled_work().  It wants to wait
+ * until until all currently-scheduled works are completed, but it doesn't
+ * want to be livelocked by new, incoming ones.  So it waits until
+ * remove_sequence is >= the insert_sequence which pertained when
+ * flush_scheduled_work() was called.
+ */
+ 
+struct cpu_workqueue_struct {
+
+	spinlock_t lock;
+
+	long remove_sequence;	/* Least-recently added (next to run) */
+	long insert_sequence;	/* Next to add */
+
+	struct list_head worklist;
+	wait_queue_head_t more_work;
+	wait_queue_head_t work_done;
+
+	struct workqueue_struct *wq;
+	task_t *thread;
+
+	int run_depth;		/* Detect run_workqueue() recursion depth */
+} ____cacheline_aligned;
+
+/*
+ * The externally visible workqueue abstraction is an array of
+ * per-CPU workqueues:
+ */
+struct workqueue_struct {
+	/* TODO: Find out exactly where the API changed */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,15)
+	struct cpu_workqueue_struct *cpu_wq;
+#else
+	struct cpu_workqueue_struct cpu_wq[NR_CPUS];
+#endif
+	const char *name;
+	struct list_head list; 	/* Empty if single thread */
+};
+
+/* Preempt must be disabled. */
+static void __t4_queue_work(struct cpu_workqueue_struct *cwq,
+			 struct work_struct *work)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&cwq->lock, flags);
+	work->wq_data = cwq;
+	list_add_tail(&work->entry, &cwq->worklist);
+	cwq->insert_sequence++;
+	wake_up(&cwq->more_work);
+	spin_unlock_irqrestore(&cwq->lock, flags);
+}
+
+/*
+ * Queue work on a workqueue. Return non-zero if it was successfully
+ * added.
+ *
+ * We queue the work to the CPU it was submitted, but there is no
+ * guarantee that it will be processed by that CPU.
+ */
+static inline int t4_queue_work(struct workqueue_struct *wq, struct work_struct *work, int cpu)
+{
+	int ret = 0;
+	get_cpu();
+	if (!test_and_set_bit(0, &work->pending)) {
+		BUG_ON(!list_empty(&work->entry));
+		__t4_queue_work(wq->cpu_wq + cpu, work);
+		ret = 1;
+	}
+	put_cpu();
+	return ret;
+}
+
+#endif
+
+/*
+ * Define CONFIG_EXTENDED_RESET to allow the qfalc framer extra time
+ * to reset itself upon hardware initialization. This exits for rare
+ * cases for customers who are seeing the qfalc returning unexpected
+ * information at initialization
+ */
+#undef CONFIG_EXTENDED_RESET
+
+static int pedanticpci = 1;
+static int debug=0;
+static int timingcable = 0;
+static int t1e1override = -1;  /* 0xff for E1, 0x00 for T1 */
+static int j1mode = 0;
+static int sigmode = FRMR_MODE_NO_ADDR_CMP;
+static int alarmdebounce = 2500; /* LOF/LFA def to 2.5s AT&T TR54016*/
+static int losalarmdebounce = 2500;/* LOS def to 2.5s AT&T TR54016*/
+static int aisalarmdebounce = 2500;/* AIS(blue) def to 2.5s AT&T TR54016*/
+static int yelalarmdebounce = 500;/* RAI(yellow) def to 0.5s AT&T devguide */
+static int max_latency = GEN5_MAX_LATENCY;  /* Used to set a maximum latency (if you don't wish it to hard cap it at a certain value) in milliseconds */
+#ifdef VPM_SUPPORT
+static int vpmsupport = 1;
+/* If set to auto, vpmdtmfsupport is enabled for VPM400M and disabled for VPM450M */
+static int vpmdtmfsupport = -1; /* -1=auto, 0=disabled, 1=enabled*/
+static int vpmspans = 1;
+#define VPM_DEFAULT_DTMFTHRESHOLD 1000
+static int dtmfthreshold = VPM_DEFAULT_DTMFTHRESHOLD;
+static int lastdtmfthreshold = VPM_DEFAULT_DTMFTHRESHOLD;
+#endif
+/* Enabling bursting can more efficiently utilize PCI bus bandwidth, but
+   can also cause PCI bus starvation, especially in combination with other
+   aggressive cards.  Please note that burst mode has no effect on CPU
+   utilization / max number of calls / etc. */
+static int noburst;
+/* For 56kbps links, set this module parameter to 0x7f */
+static int hardhdlcmode = 0xff;
+
+static int latency = 1;
+
+static int ms_per_irq = 1;
+
+#ifdef FANCY_ALARM
+static int altab[] = {
+0, 0, 0, 1, 2, 3, 4, 6, 8, 9, 11, 13, 16, 18, 20, 22, 24, 25, 27, 28, 29, 30, 31, 31, 32, 31, 31, 30, 29, 28, 27, 25, 23, 22, 20, 18, 16, 13, 11, 9, 8, 6, 4, 3, 2, 1, 0, 0, 
+};
+#endif
+
+#define MAX_SPANS 16
+
+#define FLAG_STARTED (1 << 0)
+#define FLAG_NMF (1 << 1)
+#define FLAG_SENDINGYELLOW (1 << 2)
+
+
+#define	TYPE_T1	1		/* is a T1 card */
+#define	TYPE_E1	2		/* is an E1 card */
+#define TYPE_J1 3		/* is a running J1 */
+
+#define FLAG_2NDGEN  (1 << 3)
+#define FLAG_2PORT   (1 << 4)
+#define FLAG_VPM2GEN (1 << 5)
+#define FLAG_OCTOPT  (1 << 6)
+#define FLAG_3RDGEN  (1 << 7)
+#define FLAG_BURST   (1 << 8)
+#define FLAG_5THGEN  (1 << 10)
+
+#define CANARY 0xc0de
+
+
+#define PORTS_PER_FRAMER 4
+
+struct devtype {
+	char *desc;
+	unsigned int flags;
+};
+
+static struct devtype opvxd115 = { "OpenVox D115P/D115E ", FLAG_2NDGEN};
+static struct devtype opvxd130 = { "OpenVox D130P/D130E", FLAG_5THGEN | FLAG_BURST | FLAG_2NDGEN | FLAG_3RDGEN};
+	
+
+struct t4;
+
+struct t4_span {
+	struct t4 *owner;
+	unsigned int *writechunk;					/* Double-word aligned write memory */
+	unsigned int *readchunk;					/* Double-word aligned read memory */
+	int spantype;		/* card type, T1 or E1 or J1 */
+	int sync;
+	int psync;
+	int alarmtimer;
+	int redalarms;
+	int notclear;
+	int alarmcount;
+	int losalarmcount;
+	int aisalarmcount;
+	int yelalarmcount;
+	int spanflags;
+	int syncpos;
+#ifdef SUPPORT_GEN1
+	int e1check;			/* E1 check */
+#endif
+	struct dahdi_span span;
+	unsigned char txsigs[16];	/* Transmit sigs */
+	int loopupcnt;
+	int loopdowncnt;
+#ifdef SUPPORT_GEN1
+	unsigned char ec_chunk1[31][DAHDI_CHUNKSIZE]; /* first EC chunk buffer */
+	unsigned char ec_chunk2[31][DAHDI_CHUNKSIZE]; /* second EC chunk buffer */
+#endif
+	int irqmisses;
+	
+	/* HDLC controller fields */
+	struct dahdi_chan *sigchan;
+	unsigned char sigmode;
+	int sigactive;
+	int frames_out;
+	int frames_in;
+
+#ifdef VPM_SUPPORT
+	unsigned long dtmfactive;
+	unsigned long dtmfmask;
+	unsigned long dtmfmutemask;
+	short dtmfenergy[31];
+	short dtmfdigit[31];
+#endif
+#ifdef ENABLE_WORKQUEUES
+	struct work_struct swork;
+#endif	
+	struct dahdi_chan *chans[32];		/* Individual channels */
+	struct dahdi_echocan_state *ec[32];	/* Echocan state for each channel */
+};
+
+struct t4 {
+	/* This structure exists one per card */
+	struct pci_dev *dev;		/* Pointer to PCI device */
+	struct dahdi_device *ddev;	/* Pointer to DAHDI device */
+	unsigned int intcount;
+	int num;			/* Which card we are */
+	int t1e1;			/* T1/E1 select pins */
+	int globalconfig;	/* Whether global setup has been done */
+	int syncsrc;			/* active sync source */
+	struct t4_span *tspans[4];	/* Individual spans */
+	int numspans;			/* Number of spans on the card */
+	int blinktimer;
+#ifdef FANCY_ALARM
+	int alarmpos;
+#endif
+	int irq;			/* IRQ used by device */
+	int order;			/* Order */
+	int flags;                      /* Device flags */
+	unsigned int falc31 : 1;	/* are we falc v3.1 (atomic not necessary) */
+	int master;				/* Are we master */
+	int ledreg;				/* LED Register */
+	unsigned int gpio;
+	unsigned int gpioctl;
+	int e1recover;			/* E1 recovery timer */
+	spinlock_t reglock;		/* lock register access */
+	int spansstarted;		/* number of spans started */
+	volatile unsigned int *writechunk;					/* Double-word aligned write memory */
+	volatile unsigned int *readchunk;					/* Double-word aligned read memory */
+	unsigned short canary;
+#ifdef ENABLE_WORKQUEUES
+	atomic_t worklist;
+	struct workqueue_struct *workq;
+#endif
+	unsigned int passno;	/* number of interrupt passes */
+	char *variety;
+	int last0;		/* for detecting double-missed IRQ */
+
+	/* DMA related fields */
+	unsigned int dmactrl;
+	dma_addr_t 	readdma;
+	dma_addr_t	writedma;
+	unsigned long memaddr;		/* Base address of card */
+	unsigned long memlen;
+	__iomem volatile unsigned int *membase;	/* Base address of card */
+
+	/* Add this for our softlockup protector */
+	unsigned int oct_rw_count;
+
+	/* Flags for our bottom half */
+	unsigned long checkflag;
+	struct tasklet_struct t4_tlet;
+	unsigned int vpm400checkstatus;
+	/* Latency related additions */
+	unsigned char rxident;
+	unsigned char lastindex;
+	int numbufs;
+	int needed_latency;
+	
+#ifdef VPM_SUPPORT
+	struct vpm450m *vpm450m;
+	int vpm;
+#endif	
+
+};
+
+#define T4_VPM_PRESENT (1 << 28)
+
+#ifdef VPM_SUPPORT
+static void t4_vpm400_init(struct t4 *wc);
+static void t4_vpm450_init(struct t4 *wc);
+static void t4_vpm_set_dtmf_threshold(struct t4 *wc, unsigned int threshold);
+
+static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec);
+
+static const struct dahdi_echocan_features vpm400m_ec_features = {
+	.NLP_automatic = 1,
+	.CED_tx_detect = 1,
+	.CED_rx_detect = 1,
+};
+
+static const struct dahdi_echocan_features vpm450m_ec_features = {
+	.NLP_automatic = 1,
+	.CED_tx_detect = 1,
+	.CED_rx_detect = 1,
+};
+
+static const struct dahdi_echocan_ops vpm400m_ec_ops = {
+	.echocan_free = echocan_free,
+};
+
+static const struct dahdi_echocan_ops vpm450m_ec_ops = {
+	.echocan_free = echocan_free,
+};
+#endif
+
+static void __set_clear(struct t4 *wc, int span);
+static int t4_startup(struct file *file, struct dahdi_span *span);
+static int t4_shutdown(struct dahdi_span *span);
+static int t4_rbsbits(struct dahdi_chan *chan, int bits);
+static int t4_maint(struct dahdi_span *span, int cmd);
+static int t4_clear_maint(struct dahdi_span *span);
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+static int t4_reset_counters(struct dahdi_span *span);
+#endif
+#ifdef SUPPORT_GEN1
+static int t4_reset_dma(struct t4 *wc);
+#endif
+static void t4_hdlc_hard_xmit(struct dahdi_chan *chan);
+static int t4_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data);
+static void t4_tsi_assign(struct t4 *wc, int fromspan, int fromchan, int tospan, int tochan);
+static void t4_tsi_unassign(struct t4 *wc, int tospan, int tochan);
+static void __t4_set_rclk_src(struct t4 *wc, int span);
+static void __t4_set_sclk_src(struct t4 *wc, int mode, int master, int slave);
+static void t4_check_alarms(struct t4 *wc, int span);
+static void t4_check_sigbits(struct t4 *wc, int span);
+
+#define WC_RDADDR	0
+#define WC_WRADDR	1
+#define WC_COUNT	2
+#define WC_DMACTRL	3	
+#define WC_INTR		4
+/* #define WC_GPIO		5 */
+#define WC_VERSION	6
+#define WC_LEDS		7
+#define WC_GPIOCTL	8
+#define WC_GPIO		9
+#define WC_LADDR	10
+#define WC_LDATA		11
+#define WC_LCS		(1 << 11)
+#define WC_LCS2		(1 << 12)
+#define WC_LALE			(1 << 13)
+#define WC_LFRMR_CS	(1 << 10)	/* Framer's ChipSelect signal */
+#define WC_ACTIVATE	(1 << 12)
+#define WC_LREAD			(1 << 15)
+#define WC_LWRITE		(1 << 16)
+
+#define WC_OFF    (0)
+#define WC_RED    (1)
+#define WC_GREEN  (2)
+#define WC_YELLOW (3)
+
+#define WC_RECOVER 	0
+#define WC_SELF 	1
+
+#define LIM0_T 0x36 		/* Line interface mode 0 register */
+#define LIM0_LL (1 << 1)	/* Local Loop */
+#define LIM1_T 0x37		/* Line interface mode 1 register */
+#define LIM1_RL (1 << 1)	/* Remote Loop */
+
+#define FMR0 0x1C		/* Framer Mode Register 0 */
+#define FMR0_SIM (1 << 0)	/* Alarm Simulation */
+#define FMR1_T 0x1D		/* Framer Mode Register 1 */
+#define FMR1_ECM (1 << 2)	/* Error Counter 1sec Interrupt Enable */
+#define DEC_T 0x60		/* Diable Error Counter */
+#define IERR_T 0x1B		/* Single Bit Defect Insertion Register */
+#define IBV	0	 /* Bipolar violation */
+#define IPE	(1 << 1) /* PRBS defect */
+#define ICASE	(1 << 2) /* CAS defect */
+#define ICRCE	(1 << 3) /* CRC defect */
+#define IMFE	(1 << 4) /* Multiframe defect */
+#define IFASE	(1 << 5) /* FAS defect */
+#define ISR3_SEC (1 << 6)	/* Internal one-second interrupt bit mask */
+#define ISR3_ES (1 << 7)	/* Errored Second interrupt bit mask */
+#define ESM 0x47		/* Errored Second mask register */
+
+#define FMR2_T 0x1E		/* Framer Mode Register 2 */
+#define FMR2_PLB (1 << 2)	/* Framer Mode Register 2 */
+
+#define FECL_T 0x50		/* Framing Error Counter Lower Byte */
+#define FECH_T 0x51		/* Framing Error Counter Higher Byte */
+#define CVCL_T 0x52		/* Code Violation Counter Lower Byte */
+#define CVCH_T 0x53		/* Code Violation Counter Higher Byte */
+#define CEC1L_T 0x54		/* CRC Error Counter 1 Lower Byte */
+#define CEC1H_T 0x55		/* CRC Error Counter 1 Higher Byte */
+#define EBCL_T 0x56		/* E-Bit Error Counter Lower Byte */
+#define EBCH_T 0x57		/* E-Bit Error Counter Higher Byte */
+#define BECL_T 0x58		/* Bit Error Counter Lower Byte */
+#define BECH_T 0x59		/* Bit Error Counter Higher Byte */
+#define COEC_T 0x5A		/* COFA Event Counter */
+#define PRBSSTA_T 0xDA		/* PRBS Status Register */
+
+#define LCR1_T 0x3B		/* Loop Code Register 1 */
+#define EPRM (1 << 7)		/* Enable PRBS rx */
+#define XPRBS (1 << 6)		/* Enable PRBS tx */
+#define FLLB (1 << 1)		/* Framed line loop/Invert */
+#define LLBP (1 << 0)		/* Line Loopback Pattern */
+#define TPC0_T 0xA8		/* Test Pattern Control Register */
+#define FRA (1 << 6)		/* Framed/Unframed Selection */
+#define PRBS23 (3 << 4)		/* Pattern selection (23 poly) */
+#define PRM (1 << 2)		/* Non framed mode */
+#define FRS1_T 0x4D		/* Framer Receive Status Reg 1 */
+#define LLBDD (1 << 4)
+#define LLBAD (1 << 3)
+
+#define MAX_T4_CARDS 64
+
+static void t4_isr_bh(unsigned long data);
+
+static struct t4 *cards[MAX_T4_CARDS];
+
+
+#define MAX_TDM_CHAN 32
+#define MAX_DTMF_DET 16
+
+#define HDLC_IMR0_MASK (FRMR_IMR0_RME | FRMR_IMR0_RPF)
+#if 0
+#define HDLC_IMR1_MASK (FRMR_IMR1_ALLS | FRMR_IMR1_XDU | FRMR_IMR1_XPR)
+#else
+#define HDLC_IMR1_MASK	(FRMR_IMR1_XDU | FRMR_IMR1_XPR)
+#endif
+
+static inline unsigned int __t4_pci_in(struct t4 *wc, const unsigned int addr)
+{
+	unsigned int res = readl(&wc->membase[addr]);
+	return res;
+}
+
+static inline void __t4_pci_out(struct t4 *wc, const unsigned int addr, const unsigned int value)
+{
+	unsigned int tmp;
+	writel(value, &wc->membase[addr]);
+	if (pedanticpci) {
+		tmp = __t4_pci_in(wc, WC_VERSION);
+		if ((tmp & 0xffff0000) != 0xc01a0000)
+			dev_notice(&wc->dev->dev,
+					"Version Synchronization Error!\n");
+	}
+#if 0
+	tmp = __t4_pci_in(wc, addr);
+	if ((value != tmp) && (addr != WC_LEDS) && (addr != WC_LDATA) &&
+		(addr != WC_GPIO) && (addr != WC_INTR))
+		dev_info(&wc->dev->dev, "Tried to load %08x into %08x, "
+				"but got %08x instead\n", value, addr, tmp);
+#endif		
+}
+
+static inline void __t4_gpio_set(struct t4 *wc, unsigned bits, unsigned int val)
+{
+	unsigned int newgpio;
+	newgpio = wc->gpio & (~bits);
+	newgpio |= val;
+	if (newgpio != wc->gpio) {
+		wc->gpio = newgpio;
+		__t4_pci_out(wc, WC_GPIO, wc->gpio);
+	}	
+}
+
+static inline void __t4_gpio_setdir(struct t4 *wc, unsigned int bits, unsigned int val)
+{
+	unsigned int newgpioctl;
+	newgpioctl = wc->gpioctl & (~bits);
+	newgpioctl |= val;
+	if (newgpioctl != wc->gpioctl) {
+		wc->gpioctl = newgpioctl;
+		__t4_pci_out(wc, WC_GPIOCTL, wc->gpioctl);
+	}
+}
+
+static inline void t4_gpio_setdir(struct t4 *wc, unsigned int bits, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_gpio_setdir(wc, bits, val);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static inline void t4_gpio_set(struct t4 *wc, unsigned int bits, unsigned int val)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_gpio_set(wc, bits, val);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static inline void t4_pci_out(struct t4 *wc, const unsigned int addr, const unsigned int value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_pci_out(wc, addr, value);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static inline void __t4_set_led(struct t4 *wc, int span, int color)
+{
+	int oldreg = wc->ledreg;
+	wc->ledreg &= ~(0x3 << (span << 1));
+	wc->ledreg |= (color << (span << 1));
+	if (oldreg != wc->ledreg)
+		__t4_pci_out(wc, WC_LEDS, wc->ledreg);
+}
+
+static inline void t4_activate(struct t4 *wc)
+{
+	wc->ledreg |= WC_ACTIVATE;
+	t4_pci_out(wc, WC_LEDS, wc->ledreg);
+}
+
+static inline unsigned int t4_pci_in(struct t4 *wc, const unsigned int addr)
+{
+	unsigned int ret;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&wc->reglock, flags);
+	ret = __t4_pci_in(wc, addr);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	return ret;
+}
+
+static inline unsigned int __t4_framer_in(struct t4 *wc, int unit, const unsigned int addr)
+{
+	unsigned int ret;
+	unit &= 0x3;
+	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff) | WC_LFRMR_CS | WC_LREAD);
+	if (!pedanticpci) {
+		__t4_pci_in(wc, WC_VERSION);
+	} else {
+		__t4_pci_out(wc, WC_VERSION, 0);
+	}
+	ret = __t4_pci_in(wc, WC_LDATA);
+ 	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff));
+
+	if (unlikely(debug & DEBUG_REGS))
+		dev_info(&wc->dev->dev, "Reading unit %d address %02x is "
+				"%02x\n", unit, addr, ret & 0xff);
+
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+
+	return ret & 0xff;
+}
+
+static inline unsigned int t4_framer_in(struct t4 *wc, int unit, const unsigned int addr)
+{
+	unsigned long flags;
+	unsigned int ret;
+	spin_lock_irqsave(&wc->reglock, flags);
+	ret = __t4_framer_in(wc, unit, addr);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	return ret;
+
+}
+
+static inline void __t4_framer_out(struct t4 *wc, int unit, const unsigned int addr, const unsigned int value)
+{
+	unit &= 0x3;
+	if (unlikely(debug & DEBUG_REGS))
+		dev_info(&wc->dev->dev, "Writing %02x to address %02x of "
+				"unit %d\n", value, addr, unit);
+	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff));
+	__t4_pci_out(wc, WC_LDATA, value);
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff) | WC_LFRMR_CS | WC_LWRITE);
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	__t4_pci_out(wc, WC_LADDR, (unit << 8) | (addr & 0xff));	
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	if (unlikely(debug & DEBUG_REGS))
+		dev_info(&wc->dev->dev, "Write complete\n");
+#if 0
+	if ((addr != FRMR_TXFIFO) && (addr != FRMR_CMDR) && (addr != 0xbc))
+	{ unsigned int tmp;
+	tmp = __t4_framer_in(wc, unit, addr);
+	if (tmp != value) {
+		dev_notice(&wc->dev->dev, "Expected %d from unit %d "
+				"register %d but got %d instead\n",
+				value, unit, addr, tmp);
+	} }
+#endif	
+}
+
+static inline void t4_framer_out(struct t4 *wc, int unit, const unsigned int addr, const unsigned int value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_framer_out(wc, unit, addr, value);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+#ifdef VPM_SUPPORT
+
+static inline void wait_a_little(void)
+{
+	unsigned long newjiffies=jiffies+2;
+	while(jiffies < newjiffies);
+}
+
+static inline unsigned int __t4_vpm_in(struct t4 *wc, int unit, const unsigned int addr)
+{
+	unsigned int ret;
+	unit &= 0x7;
+	__t4_pci_out(wc, WC_LADDR, (addr & 0x1ff) | ( unit << 12));
+	__t4_pci_out(wc, WC_LADDR, (addr & 0x1ff) | ( unit << 12) | (1 << 11) | WC_LREAD);
+	ret = __t4_pci_in(wc, WC_LDATA);
+	__t4_pci_out(wc, WC_LADDR, 0);
+	return ret & 0xff;
+}
+
+static inline void __t4_raw_oct_out(struct t4 *wc, const unsigned int addr, const unsigned int value)
+{
+	int octopt = wc->tspans[0]->spanflags & FLAG_OCTOPT;
+	if (!octopt) 
+		__t4_gpio_set(wc, 0xff, (addr >> 8));
+	__t4_pci_out(wc, WC_LDATA, 0x10000 | (addr & 0xffff));
+	if (!octopt)
+		__t4_pci_out(wc, WC_LADDR, (WC_LWRITE));
+	__t4_pci_out(wc, WC_LADDR, (WC_LWRITE | WC_LALE));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	if (!octopt)
+		__t4_gpio_set(wc, 0xff, (value >> 8));
+	__t4_pci_out(wc, WC_LDATA, (value & 0xffff));
+	__t4_pci_out(wc, WC_LADDR, (WC_LWRITE | WC_LALE | WC_LCS));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	__t4_pci_out(wc, WC_LADDR, (0));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+}
+
+static inline unsigned int __t4_raw_oct_in(struct t4 *wc, const unsigned int addr)
+{
+	unsigned int ret;
+	int octopt = wc->tspans[0]->spanflags & FLAG_OCTOPT;
+	if (!octopt)
+		__t4_gpio_set(wc, 0xff, (addr >> 8));
+	__t4_pci_out(wc, WC_LDATA, 0x10000 | (addr & 0xffff));
+	if (!octopt)
+		__t4_pci_out(wc, WC_LADDR, (WC_LWRITE));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	__t4_pci_out(wc, WC_LADDR, (WC_LWRITE | WC_LALE));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+#ifdef PEDANTIC_OCTASIC_CHECKING 
+	__t4_pci_out(wc, WC_LADDR, (WC_LALE));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+#endif
+	if (!octopt) {
+		__t4_gpio_setdir(wc, 0xff, 0x00);
+		__t4_gpio_set(wc, 0xff, 0x00);
+	}
+	__t4_pci_out(wc, WC_LADDR, (WC_LREAD | WC_LALE | WC_LCS));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	if (octopt) {
+		ret = __t4_pci_in(wc, WC_LDATA) & 0xffff;
+	} else {
+		ret = __t4_pci_in(wc, WC_LDATA) & 0xff;
+		ret |= (__t4_pci_in(wc, WC_GPIO) & 0xff) << 8;
+	}
+	__t4_pci_out(wc, WC_LADDR, (0));
+	if (!pedanticpci)
+		__t4_pci_in(wc, WC_VERSION);
+	if (!octopt)
+		__t4_gpio_setdir(wc, 0xff, 0xff);
+	return ret & 0xffff;
+}
+
+static inline unsigned int __t4_oct_in(struct t4 *wc, unsigned int addr)
+{
+#ifdef PEDANTIC_OCTASIC_CHECKING
+	int count = 1000;
+#endif
+	__t4_raw_oct_out(wc, 0x0008, (addr >> 20));
+	__t4_raw_oct_out(wc, 0x000a, (addr >> 4) & ((1 << 16) - 1));
+	__t4_raw_oct_out(wc, 0x0000, (((addr >> 1) & 0x7) << 9) | (1 << 8) | (1));
+#ifdef PEDANTIC_OCTASIC_CHECKING
+	while((__t4_raw_oct_in(wc, 0x0000) & (1 << 8)) && --count);
+	if (count != 1000)
+		dev_notice(&wc->dev->dev, "Yah, read can be slow...\n");
+	if (!count)
+		dev_notice(&wc->dev->dev, "Read timed out!\n");
+#endif
+	return __t4_raw_oct_in(wc, 0x0004);
+}
+
+static inline unsigned int t4_oct_in(struct t4 *wc, const unsigned int addr)
+{
+	unsigned long flags;
+	unsigned int ret;
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	ret = __t4_oct_in(wc, addr);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	return ret;
+}
+
+static inline unsigned int t4_vpm_in(struct t4 *wc, int unit, const unsigned int addr)
+{
+	unsigned long flags;
+	unsigned int ret;
+	spin_lock_irqsave(&wc->reglock, flags);
+	ret = __t4_vpm_in(wc, unit, addr);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	return ret;
+}
+
+static inline void __t4_vpm_out(struct t4 *wc, int unit, const unsigned int addr, const unsigned int value)
+{
+	unit &= 0x7;
+	if (debug & DEBUG_REGS)
+		dev_notice(&wc->dev->dev, "Writing %02x to address %02x of "
+				"ec unit %d\n", value, addr, unit);
+	__t4_pci_out(wc, WC_LADDR, (addr & 0xff));
+	__t4_pci_out(wc, WC_LDATA, value);
+	__t4_pci_out(wc, WC_LADDR, (unit << 12) | (addr & 0x1ff) | (1 << 11));
+	__t4_pci_out(wc, WC_LADDR, (unit << 12) | (addr & 0x1ff) | (1 << 11) | WC_LWRITE);
+	__t4_pci_out(wc, WC_LADDR, (unit << 12) | (addr & 0x1ff) | (1 << 11));
+	__t4_pci_out(wc, WC_LADDR, (unit << 12) | (addr & 0x1ff));	
+	__t4_pci_out(wc, WC_LADDR, 0);
+	if (debug & DEBUG_REGS)
+		dev_notice(&wc->dev->dev, "Write complete\n");
+
+      
+#if 0
+	{ unsigned int tmp;
+	tmp = t4_vpm_in(wc, unit, addr);
+	if (tmp != value) {
+		dev_notice(&wc->dev->dev, "Expected %d from unit %d echo "
+				"register %d but got %d instead\n",
+				value, unit, addr, tmp);
+	} }
+#endif
+}
+
+static inline void __t4_oct_out(struct t4 *wc, unsigned int addr, unsigned int value)
+{
+#ifdef PEDANTIC_OCTASIC_CHECKING
+	int count = 1000;
+#endif
+	__t4_raw_oct_out(wc, 0x0008, (addr >> 20));
+	__t4_raw_oct_out(wc, 0x000a, (addr >> 4) & ((1 << 16) - 1));
+	__t4_raw_oct_out(wc, 0x0004, value);
+	__t4_raw_oct_out(wc, 0x0000, (((addr >> 1) & 0x7) << 9) | (1 << 8) | (3 << 12) | 1);
+#ifdef PEDANTIC_OCTASIC_CHECKING
+	while((__t4_raw_oct_in(wc, 0x0000) & (1 << 8)) && --count);
+	if (count != 1000)
+		dev_notice(&wc->dev->dev, "Yah, write can be slow\n");
+	if (!count)
+		dev_notice(&wc->dev->dev, "Write timed out!\n");
+#endif
+}
+
+static inline void t4_oct_out(struct t4 *wc, const unsigned int addr, const unsigned int value)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_oct_out(wc, addr, value);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static inline void t4_vpm_out(struct t4 *wc, int unit, const unsigned int addr, const unsigned int value)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_vpm_out(wc, unit, addr, value);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static const char vpm_digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', '*', '#'};
+
+static void t4_check_vpm450(struct t4 *wc)
+{
+	int channel, tone, start, span;
+
+	if (vpm450m_checkirq(wc->vpm450m)) {
+		while(vpm450m_getdtmf(wc->vpm450m, &channel, &tone, &start)) {
+			span = channel & 0x3;
+			channel >>= 2;
+			if (!wc->t1e1)
+				channel -= 5;
+			else
+				channel -= 1;
+			if (unlikely(debug))
+				dev_info(&wc->dev->dev, "Got tone %s of '%c' "
+					"on channel %d of span %d\n",
+					(start ? "START" : "STOP"),
+					tone, channel, span + 1);
+			if (test_bit(channel, &wc->tspans[span]->dtmfmask) && (tone != 'u')) {
+				if (start) {
+					/* The octasic is supposed to mute us, but...  Yah, you
+					   guessed it.  */
+					if (test_bit(channel, &wc->tspans[span]->dtmfmutemask)) {
+						unsigned long flags;
+						struct dahdi_chan *chan = wc->tspans[span]->span.chans[channel];
+						int y;
+						spin_lock_irqsave(&chan->lock, flags);
+						for (y=0;y<chan->numbufs;y++) {
+							if ((chan->inreadbuf > -1) && (chan->readidx[y]))
+								memset(chan->readbuf[chan->inreadbuf], DAHDI_XLAW(0, chan), chan->readidx[y]);
+						}
+						spin_unlock_irqrestore(&chan->lock, flags);
+					}
+					set_bit(channel, &wc->tspans[span]->dtmfactive);
+					dahdi_qevent_lock(wc->tspans[span]->span.chans[channel], (DAHDI_EVENT_DTMFDOWN | tone));
+				} else {
+					clear_bit(channel, &wc->tspans[span]->dtmfactive);
+					dahdi_qevent_lock(wc->tspans[span]->span.chans[channel], (DAHDI_EVENT_DTMFUP | tone));
+				}
+			}
+		}
+	}
+}
+
+static void t4_check_vpm400(struct t4 *wc, unsigned int newio)
+{
+	unsigned int digit, regval = 0;
+	unsigned int regbyte;
+	int x, i;
+	short energy=0;
+	static unsigned int lastio = 0;
+	struct t4_span *ts;
+
+	if (debug && (newio != lastio)) 
+		dev_notice(&wc->dev->dev, "Last was %08x, new is %08x\n",
+				lastio, newio);
+
+	lastio = newio;
+ 
+	for(x = 0; x < 8; x++) {
+		if (newio & (1 << (7 - x)))
+			continue;
+		ts = wc->tspans[x%4];
+		/* Start of DTMF detection process */	
+		regbyte = t4_vpm_in(wc, x, 0xb8);
+		t4_vpm_out(wc, x, 0xb8, regbyte); /* Write 1 to clear */
+		regval = regbyte << 8;
+		regbyte = t4_vpm_in(wc, x, 0xb9);
+		t4_vpm_out(wc, x, 0xb9, regbyte);
+		regval |= regbyte;
+
+		for(i = 0; (i < MAX_DTMF_DET) && regval; i++) {
+			if(regval & 0x0001) {
+				int channel = (i << 1) + (x >> 2);
+				int base = channel - 1;
+
+				if (!wc->t1e1)
+					base -= 4;
+				regbyte = t4_vpm_in(wc, x, 0xa8 + i);
+				digit = vpm_digits[regbyte];
+				if (!(wc->tspans[0]->spanflags & FLAG_VPM2GEN)) {
+					energy = t4_vpm_in(wc, x, 0x58 + channel);
+					energy = DAHDI_XLAW(energy, ts->chans[0]);
+					ts->dtmfenergy[base] = energy;
+				}
+				set_bit(base, &ts->dtmfactive);
+				if (ts->dtmfdigit[base]) {
+					if (ts->dtmfmask & (1 << base))
+						dahdi_qevent_lock(ts->span.chans[base], (DAHDI_EVENT_DTMFUP | ts->dtmfdigit[base]));
+				}
+				ts->dtmfdigit[base] = digit;
+				if (test_bit(base, &ts->dtmfmask))
+					dahdi_qevent_lock(ts->span.chans[base], (DAHDI_EVENT_DTMFDOWN | digit));
+				if (test_bit(base, &ts->dtmfmutemask)) {
+					/* Mute active receive buffer*/
+					unsigned long flags;
+					struct dahdi_chan *chan = ts->span.chans[base];
+					int y;
+					spin_lock_irqsave(&chan->lock, flags);
+					for (y=0;y<chan->numbufs;y++) {
+						if ((chan->inreadbuf > -1) && (chan->readidx[y]))
+							memset(chan->readbuf[chan->inreadbuf], DAHDI_XLAW(0, chan), chan->readidx[y]);
+					}
+					spin_unlock_irqrestore(&chan->lock, flags);
+				}
+				if (debug)
+					dev_notice(&wc->dev->dev, "Digit "
+						"Seen: %d, Span: %d, channel:"
+						" %d, energy: %02x, 'channel "
+						"%d' chip %d\n", digit, x % 4,
+						base + 1, energy, channel, x);
+				
+			}
+			regval = regval >> 1;
+		}
+		if (!(wc->tspans[0]->spanflags & FLAG_VPM2GEN))
+			continue;
+
+		/* Start of DTMF off detection process */	
+		regbyte = t4_vpm_in(wc, x, 0xbc);
+		t4_vpm_out(wc, x, 0xbc, regbyte); /* Write 1 to clear */
+		regval = regbyte << 8;
+		regbyte = t4_vpm_in(wc, x, 0xbd);
+		t4_vpm_out(wc, x, 0xbd, regbyte);
+		regval |= regbyte;
+
+		for(i = 0; (i < MAX_DTMF_DET) && regval; i++) {
+			if(regval & 0x0001) {
+				int channel = (i << 1) + (x >> 2);
+				int base = channel - 1;
+
+				if (!wc->t1e1)
+					base -= 4;
+				clear_bit(base, &ts->dtmfactive);
+				if (ts->dtmfdigit[base]) {
+					if (test_bit(base, &ts->dtmfmask))
+						dahdi_qevent_lock(ts->span.chans[base], (DAHDI_EVENT_DTMFUP | ts->dtmfdigit[base]));
+				}
+				digit = ts->dtmfdigit[base];
+				ts->dtmfdigit[base] = 0;
+				if (debug)
+					dev_notice(&wc->dev->dev, "Digit "
+						"Gone: %d, Span: %d, channel:"
+						" %d, energy: %02x, 'channel "
+						"%d' chip %d\n", digit, x % 4,
+						base + 1, energy, channel, x);
+				
+			}
+			regval = regval >> 1;
+		}
+
+	}
+}
+#endif
+
+static void hdlc_stop(struct t4 *wc, unsigned int span)
+{
+	struct t4_span *t = wc->tspans[span];
+	unsigned char imr0, imr1, mode;
+	int i = 0;
+
+	if (debug & DEBUG_FRAMER)
+		dev_notice(&wc->dev->dev, "Stopping HDLC controller on span "
+				"%d\n", span+1);
+	
+	/* Clear receive and transmit timeslots */
+	for (i = 0; i < 4; i++) {
+		t4_framer_out(wc, span, FRMR_RTR_BASE + i, 0x00);
+		t4_framer_out(wc, span, FRMR_TTR_BASE + i, 0x00);
+	}
+
+	imr0 = t4_framer_in(wc, span, FRMR_IMR0);
+	imr1 = t4_framer_in(wc, span, FRMR_IMR1);
+
+	/* Disable HDLC interrupts */
+	imr0 |= HDLC_IMR0_MASK;
+	t4_framer_out(wc, span, FRMR_IMR0, imr0);
+
+	imr1 |= HDLC_IMR1_MASK;
+	t4_framer_out(wc, span, FRMR_IMR1, imr1);
+
+	mode = t4_framer_in(wc, span, FRMR_MODE);
+	mode &= ~FRMR_MODE_HRAC;
+	t4_framer_out(wc, span, FRMR_MODE, mode);
+
+	t->sigactive = 0;
+}
+
+static inline void __t4_framer_cmd(struct t4 *wc, unsigned int span, int cmd)
+{
+	__t4_framer_out(wc, span, FRMR_CMDR, cmd);
+}
+
+static inline void t4_framer_cmd_wait(struct t4 *wc, unsigned int span, int cmd)
+{
+	int sis;
+	int loops = 0;
+
+	/* XXX could be time consuming XXX */
+	for (;;) {
+		sis = t4_framer_in(wc, span, FRMR_SIS);
+		if (!(sis & 0x04))
+			break;
+		if (!loops++ && (debug & DEBUG_FRAMER)) {
+			dev_notice(&wc->dev->dev, "!!!SIS Waiting before cmd "
+					"%02x\n", cmd);
+		}
+	}
+	if (loops && (debug & DEBUG_FRAMER))
+		dev_notice(&wc->dev->dev, "!!!SIS waited %d loops\n", loops);
+
+	t4_framer_out(wc, span, FRMR_CMDR, cmd);
+}
+
+static int hdlc_start(struct t4 *wc, unsigned int span, struct dahdi_chan *chan, unsigned char mode)
+{
+	struct t4_span *t = wc->tspans[span];
+	unsigned char imr0, imr1;
+	int offset = chan->chanpos;
+	unsigned long flags;
+
+	if (debug & DEBUG_FRAMER)
+		dev_info(&wc->dev->dev, "Starting HDLC controller for channel "
+				"%d span %d\n", offset, span+1);
+
+	if (mode != FRMR_MODE_NO_ADDR_CMP)
+		return -1;
+
+	mode |= FRMR_MODE_HRAC;
+
+	/* Make sure we're in the right mode */
+	t4_framer_out(wc, span, FRMR_MODE, mode);
+	t4_framer_out(wc, span, FRMR_TSEO, 0x00);
+	t4_framer_out(wc, span, FRMR_TSBS1, hardhdlcmode);
+
+	/* Set the interframe gaps, etc */
+	t4_framer_out(wc, span, FRMR_CCR1, FRMR_CCR1_ITF|FRMR_CCR1_EITS);
+
+	t4_framer_out(wc, span, FRMR_CCR2, FRMR_CCR2_RCRC);
+	
+	/* Set up the time slot that we want to tx/rx on */
+	t4_framer_out(wc, span, FRMR_TTR_BASE + (offset / 8), (0x80 >> (offset % 8)));
+	t4_framer_out(wc, span, FRMR_RTR_BASE + (offset / 8), (0x80 >> (offset % 8)));
+
+	imr0 = t4_framer_in(wc, span, FRMR_IMR0);
+	imr1 = t4_framer_in(wc, span, FRMR_IMR1);
+
+	/* Enable our interrupts again */
+	imr0 &= ~HDLC_IMR0_MASK;
+	t4_framer_out(wc, span, FRMR_IMR0, imr0);
+
+	imr1 &= ~HDLC_IMR1_MASK;
+	t4_framer_out(wc, span, FRMR_IMR1, imr1);
+
+	/* Reset the signaling controller */
+	t4_framer_cmd_wait(wc, span, FRMR_CMDR_SRES);
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	t->sigchan = chan;
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	t->sigactive = 0;
+
+	return 0;
+}
+
+static void __set_clear(struct t4 *wc, int span)
+{
+	int i,j;
+	int oldnotclear;
+	unsigned short val=0;
+	struct t4_span *ts = wc->tspans[span];
+
+	oldnotclear = ts->notclear;
+	if ((ts->spantype == TYPE_T1) || (ts->spantype == TYPE_J1)) {
+		for (i=0;i<24;i++) {
+			j = (i/8);
+			if (ts->span.chans[i]->flags & DAHDI_FLAG_CLEAR) {
+				val |= 1 << (7 - (i % 8));
+				ts->notclear &= ~(1 << i);
+			} else
+				ts->notclear |= (1 << i);
+			if ((i % 8)==7) {
+				if (debug)
+					dev_notice(&wc->dev->dev, "Putting %d "
+						"in register %02x on span %d"
+						"\n", val, 0x2f + j, span + 1);
+				__t4_framer_out(wc, span, 0x2f + j, val);
+				val = 0;
+			}
+		}
+	} else {
+		for (i=0;i<31;i++) {
+			if (ts->span.chans[i]->flags & DAHDI_FLAG_CLEAR)
+				ts->notclear &= ~(1 << i);
+			else 
+				ts->notclear |= (1 << i);
+		}
+	}
+	if (ts->notclear != oldnotclear) {
+		unsigned char reg;
+		reg = __t4_framer_in(wc, span, FRMR_IMR0);
+		if (ts->notclear)
+			reg &= ~0x08;
+		else
+			reg |= 0x08;
+		__t4_framer_out(wc, span, FRMR_IMR0, reg);
+	}
+}
+
+#if 0
+static void set_clear(struct t4 *wc, int span)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wc->reglock, flags);
+	__set_clear(wc, span);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+#endif
+
+static int t4_dacs(struct dahdi_chan *dst, struct dahdi_chan *src)
+{
+	struct t4 *wc;
+	struct t4_span *ts;
+	wc = dst->pvt;
+	ts = wc->tspans[dst->span->offset];
+	if (src && (src->pvt != dst->pvt)) {
+		if (ts->spanflags & FLAG_2NDGEN)
+			t4_tsi_unassign(wc, dst->span->offset, dst->chanpos);
+		wc = src->pvt;
+		if (ts->spanflags & FLAG_2NDGEN)
+			t4_tsi_unassign(wc, src->span->offset, src->chanpos);
+		if (debug)
+			dev_notice(&wc->dev->dev, "Unassigning %d/%d by "
+				"default and...\n", src->span->offset,
+				src->chanpos);
+		if (debug)
+			dev_notice(&wc->dev->dev, "Unassigning %d/%d by "
+				"default\n", dst->span->offset, dst->chanpos);
+		return -1;
+	}
+	if (src) {
+		t4_tsi_assign(wc, src->span->offset, src->chanpos, dst->span->offset, dst->chanpos);
+		if (debug)
+			dev_notice(&wc->dev->dev, "Assigning channel %d/%d -> "
+				"%d/%d!\n", src->span->offset, src->chanpos,
+				dst->span->offset, dst->chanpos);
+	} else {
+		t4_tsi_unassign(wc, dst->span->offset, dst->chanpos);
+		if (debug)
+			dev_notice(&wc->dev->dev, "Unassigning channel %d/%d!"
+				"\n", dst->span->offset, dst->chanpos);
+	}
+	return 0;
+}
+
+#ifdef VPM_SUPPORT
+
+void oct_set_reg(void *data, unsigned int reg, unsigned int val)
+{
+	struct t4 *wc = data;
+	t4_oct_out(wc, reg, val);
+}
+
+unsigned int oct_get_reg(void *data, unsigned int reg)
+{
+	struct t4 *wc = data;
+	unsigned int ret;
+	ret = t4_oct_in(wc, reg);
+	return ret;
+}
+
+static int t4_vpm_unit(int span, int channel)
+{
+	int unit = 0;
+	switch(vpmspans) {
+	case 4:
+		unit = span;
+		unit += (channel & 1) << 2;
+		break;
+	case 2:
+		unit = span;
+		unit += (channel & 0x3) << 1;
+		break;
+	case 1:
+		unit = span;
+		unit += (channel & 0x7);
+	}
+	return unit;
+}
+
+static inline struct t4_span *t4_from_span(struct dahdi_span *span)
+{
+	return container_of(span, struct t4_span, span);
+}
+
+static int t4_echocan_create(struct dahdi_chan *chan, struct dahdi_echocanparams *ecp,
+			  struct dahdi_echocanparam *p, struct dahdi_echocan_state **ec)
+{
+	struct t4 *wc = chan->pvt;
+	struct t4_span *tspan = container_of(chan->span, struct t4_span, span);
+	int channel;
+	const struct dahdi_echocan_ops *ops;
+	const struct dahdi_echocan_features *features;
+
+	if (!vpmsupport || !wc->vpm)
+		return -ENODEV;
+
+	if (chan->span->offset >= vpmspans)
+		return -ENODEV;
+
+	if (wc->vpm450m) {
+		ops = &vpm450m_ec_ops;
+		features = &vpm450m_ec_features;
+	} else {
+		ops = &vpm400m_ec_ops;
+		features = &vpm400m_ec_features;
+	}
+
+	if (ecp->param_count > 0) {
+		dev_warn(&wc->dev->dev, "echo canceller does not support "
+				"parameters; failing request\n");
+		return -EINVAL;
+	}
+
+	*ec = tspan->ec[chan->chanpos - 1];
+	(*ec)->ops = ops;
+	(*ec)->features = *features;
+
+	channel = wc->t1e1 ? chan->chanpos : chan->chanpos + 4;
+
+	if (wc->vpm450m) {
+		channel = channel << 2;
+		channel |= chan->span->offset;
+		if (debug & DEBUG_ECHOCAN)
+			dev_notice(&wc->dev->dev, "echocan: Card is %d, "
+				"Channel is %d, Span is %d, offset is %d "
+				"length %d\n", wc->num, chan->chanpos,
+				chan->span->offset, channel, ecp->tap_length);
+		vpm450m_setec(wc->vpm450m, channel, ecp->tap_length);
+	} else {
+		int unit = t4_vpm_unit(chan->span->offset, channel);
+
+		if (debug & DEBUG_ECHOCAN)
+			dev_notice(&wc->dev->dev, "echocan: Card is %d, "
+				"Channel is %d, Span is %d, unit is %d, "
+				"unit offset is %d length %d\n", wc->num,
+				chan->chanpos, chan->span->offset, unit,
+				channel, ecp->tap_length);
+		t4_vpm_out(wc, unit, channel, 0x3e);
+	}
+
+	return 0;
+}
+
+static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec)
+{
+	struct t4 *wc = chan->pvt;
+	int channel;
+
+	memset(ec, 0, sizeof(*ec));
+
+	channel = wc->t1e1 ? chan->chanpos : chan->chanpos + 4;
+
+	if (wc->vpm450m) {
+		channel = channel << 2;
+		channel |= chan->span->offset;
+		if (debug & DEBUG_ECHOCAN)
+			dev_notice(&wc->dev->dev, "echocan: Card is %d, "
+				"Channel is %d, Span is %d, offset is %d "
+				"length 0\n", wc->num, chan->chanpos,
+				chan->span->offset, channel);
+		vpm450m_setec(wc->vpm450m, channel, 0);
+	} else {
+		int unit = t4_vpm_unit(chan->span->offset, channel);
+
+		if (debug & DEBUG_ECHOCAN)
+			dev_notice(&wc->dev->dev, "echocan: Card is %d, "
+				"Channel is %d, Span is %d, unit is %d, "
+				"unit offset is %d length 0\n", wc->num,
+				chan->chanpos, chan->span->offset, unit,
+				channel);
+		t4_vpm_out(wc, unit, channel, 0x01);
+	}
+}
+#endif
+
+static int t4_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
+{
+	struct t4_regs regs;
+	int x;
+	struct t4 *wc = chan->pvt;
+#ifdef VPM_SUPPORT
+	int j;
+	int channel;
+	struct t4_span *ts = wc->tspans[chan->span->offset];
+#endif
+
+#ifdef VPM_SUPPORT
+	if (dtmfthreshold == 0)
+		dtmfthreshold = VPM_DEFAULT_DTMFTHRESHOLD;
+	if (lastdtmfthreshold != dtmfthreshold) {
+		lastdtmfthreshold = dtmfthreshold;
+		t4_vpm_set_dtmf_threshold(wc, dtmfthreshold);
+	}
+#endif
+
+	switch(cmd) {
+	case WCT4_GET_REGS:
+		for (x=0;x<NUM_PCI;x++)
+			regs.pci[x] = t4_pci_in(wc, x);
+		for (x=0;x<NUM_REGS;x++)
+			regs.regs[x] = t4_framer_in(wc, chan->span->offset, x);
+		if (copy_to_user((__user void *) data, &regs, sizeof(regs)))
+			return -EFAULT;
+		break;
+#ifdef VPM_SUPPORT
+	case DAHDI_TONEDETECT:
+		if (get_user(j, (__user int *) data))
+			return -EFAULT;
+		if (!wc->vpm)
+			return -ENOSYS;
+		if (j && (vpmdtmfsupport == 0))
+			return -ENOSYS;
+		if (j & DAHDI_TONEDETECT_ON)
+			set_bit(chan->chanpos - 1, &ts->dtmfmask);
+		else
+			clear_bit(chan->chanpos - 1, &ts->dtmfmask);
+		if (j & DAHDI_TONEDETECT_MUTE)
+			set_bit(chan->chanpos - 1, &ts->dtmfmutemask);
+		else
+			clear_bit(chan->chanpos - 1, &ts->dtmfmutemask);
+		if (wc->vpm450m) {
+			channel = (chan->chanpos) << 2;
+			if (!wc->t1e1)
+				channel += (4 << 2);
+			channel |= chan->span->offset;
+			vpm450m_setdtmf(wc->vpm450m, channel, j & DAHDI_TONEDETECT_ON, j & DAHDI_TONEDETECT_MUTE);
+		}
+		return 0;
+#endif
+	default:
+		return -ENOTTY;
+	}
+	return 0;
+}
+
+static void inline t4_hdlc_xmit_fifo(struct t4 *wc, unsigned int span, struct t4_span *ts)
+{
+	int res, i;
+	unsigned int size = 32;
+	unsigned char buf[32];
+
+	res = dahdi_hdlc_getbuf(ts->sigchan, buf, &size);
+	if (debug & DEBUG_FRAMER)
+		dev_notice(&wc->dev->dev, "Got buffer sized %d and res %d "
+				"for %d\n", size, res, span);
+	if (size > 0) {
+		ts->sigactive = 1;
+
+		if (debug & DEBUG_FRAMER) {
+			dev_notice(&wc->dev->dev, "TX(");
+			for (i = 0; i < size; i++)
+				dev_notice(&wc->dev->dev, "%s%02x",
+						(i ? " " : ""), buf[i]);
+			dev_notice(&wc->dev->dev, ")\n");
+		}
+
+		for (i = 0; i < size; i++)
+			t4_framer_out(wc, span, FRMR_TXFIFO, buf[i]);
+
+		if (res) /* End of message */ {
+			if (debug & DEBUG_FRAMER)
+				dev_notice(&wc->dev->dev,
+					"transmiting XHF|XME\n");
+			t4_framer_cmd_wait(wc, span, FRMR_CMDR_XHF | FRMR_CMDR_XME);
+#if 0
+			ts->sigactive = (__t4_framer_in(wc, span, FRMR_SIS) & FRMR_SIS_XFW) ? 0 : 1;
+#endif
+			++ts->frames_out;
+			if ((debug & DEBUG_FRAMER) && !(ts->frames_out & 0x0f))
+				dev_notice(&wc->dev->dev, "Transmitted %d "
+					"frames on span %d\n", ts->frames_out,
+					span);
+		} else { /* Still more to transmit */
+			if (debug & DEBUG_FRAMER)
+				dev_notice(&wc->dev->dev, "transmiting XHF\n");
+			t4_framer_cmd_wait(wc, span, FRMR_CMDR_XHF);
+		}
+	}
+	else if (res < 0)
+		ts->sigactive = 0;
+}
+
+static void t4_hdlc_hard_xmit(struct dahdi_chan *chan)
+{
+	struct t4 *wc = chan->pvt;
+	int span = chan->span->offset;
+	struct t4_span *ts = wc->tspans[span];
+	unsigned long flags; 
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	if (!ts->sigchan) {
+		dev_notice(&wc->dev->dev, "t4_hdlc_hard_xmit: Invalid (NULL) "
+				"signalling channel\n");
+		spin_unlock_irqrestore(&wc->reglock, flags);
+		return;
+	}
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	if (debug & DEBUG_FRAMER)
+		dev_notice(&wc->dev->dev, "t4_hdlc_hard_xmit on channel %s "
+				"(sigchan %s), sigactive=%d\n", chan->name,
+				ts->sigchan->name, ts->sigactive);
+
+	if ((ts->sigchan == chan) && !ts->sigactive)
+		t4_hdlc_xmit_fifo(wc, span, ts);
+}
+
+static int t4_maint(struct dahdi_span *span, int cmd)
+{
+	struct t4_span *ts = t4_from_span(span);
+	struct t4 *wc = ts->owner;
+	unsigned int reg;
+#ifdef DAHDI_SPAN_OPS
+	unsigned long flags;
+#endif
+
+	if (ts->spantype == TYPE_E1) {
+		switch(cmd) {
+		case DAHDI_MAINT_NONE:
+			dev_info(&wc->dev->dev, "Clearing all maint modes\n");
+			t4_clear_maint(span);
+			break;
+		case DAHDI_MAINT_LOCALLOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on local loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, LIM0_T);
+			t4_framer_out(wc, span->offset, LIM0_T, (reg|LIM0_LL));
+			break;
+#ifdef DAHDI_SPAN_OPS
+		case DAHDI_MAINT_NETWORKLINELOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on network line loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, LIM1_T);
+			t4_framer_out(wc, span->offset, LIM1_T, (reg|LIM1_RL));
+			break;
+		case DAHDI_MAINT_NETWORKPAYLOADLOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on network payload loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, FMR2_T);
+			t4_framer_out(wc, span->offset, FMR2_T, (reg|FMR2_PLB));
+			break;
+#endif
+		case DAHDI_MAINT_LOOPUP:
+		case DAHDI_MAINT_LOOPDOWN:
+			dev_info(&wc->dev->dev,
+				"Loopup & loopdown supported in E1 mode\n");
+			return -ENOSYS;
+#ifdef DAHDI_SPAN_OPS
+		case DAHDI_MAINT_FAS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IFASE);
+			break;
+		case DAHDI_MAINT_MULTI_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IMFE);
+			break;
+		case DAHDI_MAINT_CRC_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, ICRCE);
+			break;
+		case DAHDI_MAINT_CAS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, ICASE);
+			break;
+		case DAHDI_MAINT_PRBS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IPE);
+			break;
+		case DAHDI_MAINT_BIPOLAR_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IBV);
+			break;
+		case DAHDI_RESET_COUNTERS:
+			t4_reset_counters(span);
+			break;
+		case DAHDI_MAINT_ALARM_SIM:
+			dev_info(&wc->dev->dev, "Invoking alarm state");
+			reg = t4_framer_in(wc, span->offset, FMR0);
+			t4_framer_out(wc, span->offset, FMR0, (reg|FMR0_SIM));
+			break;
+#endif
+		default:
+			dev_info(&wc->dev->dev,
+					"Unknown E1 maint command: %d\n", cmd);
+			return -ENOSYS;
+		}
+	} else {
+		switch(cmd) {
+		case DAHDI_MAINT_NONE:
+			dev_info(&wc->dev->dev, "Clearing all maint modes\n");
+			t4_clear_maint(span);
+			break;
+		case DAHDI_MAINT_LOCALLOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on local loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, LIM0_T);
+			t4_framer_out(wc, span->offset, LIM0_T, (reg|LIM0_LL));
+			break;
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+		case DAHDI_MAINT_NETWORKLINELOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on network line loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, LIM1_T);
+			t4_framer_out(wc, span->offset, LIM1_T, (reg|LIM1_RL));
+			break;
+		case DAHDI_MAINT_NETWORKPAYLOADLOOP:
+			dev_info(&wc->dev->dev,
+				 "Turning on network payload loopback\n");
+			t4_clear_maint(span);
+			reg = t4_framer_in(wc, span->offset, FMR2_T);
+			t4_framer_out(wc, span->offset, FMR2_T, (reg|FMR2_PLB));
+			break;
+#endif
+		case DAHDI_MAINT_LOOPUP:
+			dev_info(&wc->dev->dev, "Transmitting loopup code\n");
+			t4_clear_maint(span);
+			t4_framer_out(wc, span->offset, 0x21, 0x50);
+			break;
+		case DAHDI_MAINT_LOOPDOWN:
+			dev_info(&wc->dev->dev, "Transmitting loopdown code\n");
+			t4_clear_maint(span);
+			t4_framer_out(wc, span->offset, 0x21, 0x60);
+			break;
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+		case DAHDI_MAINT_FAS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IFASE);
+			break;
+		case DAHDI_MAINT_MULTI_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IMFE);
+			break;
+		case DAHDI_MAINT_CRC_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, ICRCE);
+			break;
+		case DAHDI_MAINT_CAS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, ICASE);
+			break;
+		case DAHDI_MAINT_PRBS_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IPE);
+			break;
+		case DAHDI_MAINT_BIPOLAR_DEFECT:
+			t4_framer_out(wc, span->offset, IERR_T, IBV);
+			break;
+		case DAHDI_MAINT_PRBS:
+			dev_info(&wc->dev->dev, "PRBS not supported\n");
+#if 0
+			dev_notice(&wc->dev->dev, "Enabling PRBS!\n");
+			span->mainttimer = 1;
+			/* Enable PRBS monitor */
+			reg = t4_framer_in(wc, span->offset, LCR1_T);
+			reg |= EPRM;
+
+			/* Setup PRBS xmit */
+			t4_framer_out(wc, span->offset, TPC0_T, 0);
+
+			/* Enable PRBS transmit */
+			reg |= XPRBS;
+			reg &= ~LLBP;
+			reg &= ~FLLB;
+			t4_framer_out(wc, span->offset, LCR1_T, reg);
+#endif
+			return -ENOSYS;
+		case DAHDI_RESET_COUNTERS:
+			t4_reset_counters(span);
+			break;
+#endif
+#ifdef DAHDI_SPAN_OPS
+		case DAHDI_MAINT_ALARM_SIM:
+			reg = t4_framer_in(wc, span->offset, FMR0);
+
+			/*
+			 * The alarm simulation state machine requires us to
+			 * bring this bit up and down for at least 1 clock cycle
+			 */
+			spin_lock_irqsave(&wc->reglock, flags);
+			__t4_framer_out(wc, span->offset,
+					FMR0, (reg | FMR0_SIM));
+			udelay(1);
+			__t4_framer_out(wc, span->offset,
+					FMR0, (reg & ~FMR0_SIM));
+			udelay(1);
+			spin_unlock_irqrestore(&wc->reglock, flags);
+
+			reg = t4_framer_in(wc, span->offset, 0x4e);
+			if (debug & DEBUG_MAIN) {
+				dev_info(&wc->dev->dev,
+					"FRS2(alarm state): %d\n",
+					((reg & 0xe0) >> 5));
+			}
+			break;
+#endif
+		default:
+			dev_info(&wc->dev->dev, "Unknown T1 maint command:%d\n",
+									cmd);
+			break;
+	   }
+    }
+	return 0;
+}
+
+static int t4_clear_maint(struct dahdi_span *span)
+{
+	struct t4_span *ts = t4_from_span(span);
+	struct t4 *wc = ts->owner;
+	unsigned int reg;
+
+	/* Clear local loop */
+	reg = t4_framer_in(wc, span->offset, LIM0_T);
+	t4_framer_out(wc, span->offset, LIM0_T, (reg & ~LIM0_LL));
+
+	/* Clear Remote Loop */
+	reg = t4_framer_in(wc, span->offset, LIM1_T);
+	t4_framer_out(wc, span->offset, LIM1_T, (reg & ~LIM1_RL));
+
+	/* Clear Remote Payload Loop */
+	reg = t4_framer_in(wc, span->offset, FMR2_T);
+	t4_framer_out(wc, span->offset, FMR2_T, (reg & ~FMR2_PLB));
+
+	/* Clear PRBS */
+	reg = t4_framer_in(wc, span->offset, LCR1_T);
+	t4_framer_out(wc, span->offset, LCR1_T, (reg & ~(XPRBS | EPRM)));
+
+	span->mainttimer = 0;
+
+	return 0;
+}
+
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+static int t4_reset_counters(struct dahdi_span *span)
+{
+	struct t4_span *ts = t4_from_span(span);
+	memset(&ts->span.count, 0, sizeof(ts->span.count));
+	return 0;
+}
+#endif
+
+static int t4_rbsbits(struct dahdi_chan *chan, int bits)
+{
+	u_char m,c;
+	int k,n,b;
+	struct t4 *wc = chan->pvt;
+	struct t4_span *ts = wc->tspans[chan->span->offset];
+	unsigned long flags;
+	
+	if (debug & DEBUG_RBS)
+		dev_notice(&wc->dev->dev, "Setting bits to %d on channel %s\n",
+				bits, chan->name);
+	spin_lock_irqsave(&wc->reglock, flags);	
+	k = chan->span->offset;
+	if (ts->spantype == TYPE_E1) { /* do it E1 way */
+		if (chan->chanpos == 16) {
+			spin_unlock_irqrestore(&wc->reglock, flags);
+			return 0;
+		}
+		n = chan->chanpos - 1;
+		if (chan->chanpos > 15) n--;
+		b = (n % 15);
+		c = ts->txsigs[b];
+		m = (n / 15) << 2; /* nibble selector */
+		c &= (0xf << m); /* keep the other nibble */
+		c |= (bits & 0xf) << (4 - m); /* put our new nibble here */
+		ts->txsigs[b] = c;
+		  /* output them to the chip */
+		__t4_framer_out(wc,k,0x71 + b,c); 
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_D4) {
+		n = chan->chanpos - 1;
+		b = (n/4);
+		c = ts->txsigs[b];
+		m = ((3 - (n % 4)) << 1); /* nibble selector */
+		c &= ~(0x3 << m); /* keep the other nibble */
+		c |= ((bits >> 2) & 0x3) << m; /* put our new nibble here */
+		ts->txsigs[b] = c;
+		  /* output them to the chip */
+		__t4_framer_out(wc,k,0x70 + b,c); 
+		__t4_framer_out(wc,k,0x70 + b + 6,c); 
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_ESF) {
+		n = chan->chanpos - 1;
+		b = (n/2);
+		c = ts->txsigs[b];
+		m = ((n % 2) << 2); /* nibble selector */
+		c &= (0xf << m); /* keep the other nibble */
+		c |= (bits & 0xf) << (4 - m); /* put our new nibble here */
+		ts->txsigs[b] = c;
+		  /* output them to the chip */
+		__t4_framer_out(wc,k,0x70 + b,c); 
+	} 
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	if (debug & DEBUG_RBS)
+		dev_notice(&wc->dev->dev, "Finished setting RBS bits\n");
+	return 0;
+}
+
+static int t4_shutdown(struct dahdi_span *span)
+{
+	int tspan;
+	int wasrunning;
+	unsigned long flags;
+	struct t4_span *ts = t4_from_span(span);
+	struct t4 *wc = ts->owner;
+
+	tspan = span->offset + 1;
+	if (tspan < 0) {
+		dev_notice(&wc->dev->dev, "opvxd115: Span '%d' isn't us?\n",
+				span->spanno);
+		return -1;
+	}
+
+	if (debug & DEBUG_MAIN)
+		dev_notice(&wc->dev->dev, "Shutting down span %d (%s)\n",
+				span->spanno, span->name);
+
+	/* Stop HDLC controller if runned */
+	if (ts->sigchan)
+		hdlc_stop(wc, span->offset);
+	
+	spin_lock_irqsave(&wc->reglock, flags);
+	wasrunning = span->flags & DAHDI_FLAG_RUNNING;
+
+	span->flags &= ~DAHDI_FLAG_RUNNING;
+	__t4_set_led(wc, span->offset, WC_OFF);
+	if ((wc->numspans == 1) && 
+	    (!(wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING))) {
+		/* No longer in use, disable interrupts */
+		dev_info(&wc->dev->dev, "opvxd115: Disabling interrupts since "
+				"there are no active spans\n");
+		set_bit(T4_STOP_DMA, &wc->checkflag);
+	} else
+		set_bit(T4_CHECK_TIMING, &wc->checkflag);
+
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	/* Wait for interrupt routine to shut itself down */
+	msleep(10);
+	if (wasrunning)
+		wc->spansstarted--;
+
+	if (debug & DEBUG_MAIN)
+		dev_notice(&wc->dev->dev, "Span %d (%s) shutdown\n",
+				span->spanno, span->name);
+	return 0;
+}
+
+static void t4_chan_set_sigcap(struct dahdi_span *span, int x)
+{
+	struct t4_span *wc = container_of(span, struct t4_span, span);
+	struct dahdi_chan *chan = wc->chans[x];
+	chan->sigcap = DAHDI_SIG_CLEAR;
+	/* E&M variant supported depends on span type */
+	if (wc->spantype == TYPE_E1) {
+		/* E1 sigcap setup */
+		if (span->lineconfig & DAHDI_CONFIG_CCS) {
+			/* CCS setup */
+			chan->sigcap |= DAHDI_SIG_MTP2 | DAHDI_SIG_SF |
+				DAHDI_SIG_HARDHDLC;
+			return;
+		}
+		/* clear out sig and sigcap for channel 16 on E1 CAS
+		 * lines, otherwise, set it correctly */
+		if (x == 15) {
+			/* CAS signaling channel setup */
+			wc->chans[15]->sigcap = 0;
+			wc->chans[15]->sig = 0;
+			return;
+		}
+		/* normal CAS setup */
+		chan->sigcap |= DAHDI_SIG_EM_E1 | DAHDI_SIG_FXSLS |
+			DAHDI_SIG_FXSGS | DAHDI_SIG_FXSKS | DAHDI_SIG_SF |
+			DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_FXOKS |
+			DAHDI_SIG_CAS | DAHDI_SIG_DACS_RBS;
+	} else {
+		/* T1 sigcap setup */
+		chan->sigcap |= DAHDI_SIG_EM | DAHDI_SIG_FXSLS |
+			DAHDI_SIG_FXSGS | DAHDI_SIG_FXSKS | DAHDI_SIG_MTP2 |
+			DAHDI_SIG_SF | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS |
+			DAHDI_SIG_FXOKS | DAHDI_SIG_CAS | DAHDI_SIG_DACS_RBS |
+			DAHDI_SIG_HARDHDLC;
+	}
+}
+
+static int t4_spanconfig(struct file *file, struct dahdi_span *span,
+		struct dahdi_lineconfig *lc)
+{
+	int i;
+	struct t4_span *ts = t4_from_span(span);
+	struct t4 *wc = ts->owner;
+
+	if (debug)
+		dev_info(&wc->dev->dev, "About to enter spanconfig!\n");
+	if (debug & DEBUG_MAIN)
+		dev_notice(&wc->dev->dev, "opvxd115: Configuring span %d\n",
+				span->spanno);
+
+	if (lc->sync < 0)
+		lc->sync = 0;
+	if (lc->sync > wc->numspans)
+		lc->sync = 0;
+	
+	/* remove this span number from the current sync sources, if there */
+	for(i = 0; i < wc->numspans; i++) {
+		if (wc->tspans[i]->sync == span->spanno) {
+			wc->tspans[i]->sync = 0;
+			wc->tspans[i]->psync = 0;
+		}
+	}
+	wc->tspans[span->offset]->syncpos = lc->sync;
+	/* if a sync src, put it in proper place */
+	if (lc->sync) {
+		wc->tspans[lc->sync - 1]->sync = span->spanno;
+		wc->tspans[lc->sync - 1]->psync = span->offset + 1;
+	}
+	set_bit(T4_CHECK_TIMING, &wc->checkflag);
+
+	/* Make sure this is clear in case of multiple startup and shutdown
+	 * iterations */
+	clear_bit(T4_STOP_DMA, &wc->checkflag);
+	
+	/* make sure that sigcaps gets updated if necessary */
+	for (i = 0; i < span->channels; i++)
+		t4_chan_set_sigcap(span, i);
+
+	/* If we're already running, then go ahead and apply the changes */
+	if (span->flags & DAHDI_FLAG_RUNNING)
+		return t4_startup(file, span);
+
+	if (debug)
+		dev_info(&wc->dev->dev, "Done with spanconfig!\n");
+	return 0;
+}
+
+static int t4_chanconfig(struct file *file, struct dahdi_chan *chan,
+		int sigtype)
+{
+	int alreadyrunning;
+	unsigned long flags;
+	struct t4 *wc = chan->pvt;
+	struct t4_span *ts = wc->tspans[chan->span->offset];
+
+	alreadyrunning = ts->span.flags & DAHDI_FLAG_RUNNING;
+	if (debug & DEBUG_MAIN) {
+		if (alreadyrunning)
+			dev_notice(&wc->dev->dev, "opvxd115: Reconfigured "
+				"channel %d (%s) sigtype %d\n",
+				chan->channo, chan->name, sigtype);
+		else
+			dev_notice(&wc->dev->dev, "opvxd115: Configured channel"
+				" %d (%s) sigtype %d\n",
+				chan->channo, chan->name, sigtype);
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);	
+
+	if (alreadyrunning)
+		__set_clear(wc, chan->span->offset);
+
+	spin_unlock_irqrestore(&wc->reglock, flags);	
+
+	/* (re)configure signalling channel */
+	if ((sigtype == DAHDI_SIG_HARDHDLC) || (ts->sigchan == chan)) {
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "%sonfiguring hardware HDLC "
+				"on %s\n",
+				((sigtype == DAHDI_SIG_HARDHDLC) ? "C" : "Unc"),
+				chan->name);
+		if (alreadyrunning) {
+			if (ts->sigchan)
+				hdlc_stop(wc, ts->sigchan->span->offset);
+			if (sigtype == DAHDI_SIG_HARDHDLC) {
+				if (hdlc_start(wc, chan->span->offset, chan, ts->sigmode)) {
+					dev_notice(&wc->dev->dev, "Error "
+						"initializing signalling "
+						"controller\n");
+					return -1;
+				}
+			} else {
+				spin_lock_irqsave(&wc->reglock, flags);
+				ts->sigchan = NULL;
+				spin_unlock_irqrestore(&wc->reglock, flags);
+			}
+		
+		}
+		else {
+			spin_lock_irqsave(&wc->reglock, flags);
+			ts->sigchan = (sigtype == DAHDI_SIG_HARDHDLC) ? chan : NULL;
+			spin_unlock_irqrestore(&wc->reglock, flags);
+			ts->sigactive = 0;
+		}
+	}
+	return 0;
+}
+
+static int t4_open(struct dahdi_chan *chan)
+{
+	return 0;
+}
+
+static int t4_close(struct dahdi_chan *chan)
+{
+	return 0;
+}
+
+static void set_span_devicetype(struct t4 *wc)
+{
+	struct dahdi_device *ddev = wc->ddev;
+	const char *devicetype_old = ddev->devicetype;
+	char *extra_str = "";
+
+	if (wc->vpm == T4_VPM_PRESENT)
+		extra_str = (!wc->vpm450m) ? " (VPM400M)" : " (VPMOCT032)",
+	wc->ddev->devicetype = kasprintf(GFP_KERNEL, "%s%s",
+			wc->variety, extra_str);
+
+	/* On the off chance that we were able to allocate it previously. */
+	if (!wc->ddev->devicetype)
+		wc->ddev->devicetype = devicetype_old;
+	else
+		kfree(devicetype_old);
+}
+
+/* The number of cards we have seen with each
+   possible 'order' switch setting.
+*/
+static unsigned int order_index[16];
+
+static void setup_chunks(struct t4 *wc, int which)
+{
+	struct t4_span *ts;
+	int offset = 1;
+	int x, y;
+	int gen2;
+
+	if (!wc->t1e1)
+		offset += 4;
+
+	gen2 = (wc->tspans[0]->spanflags & FLAG_2NDGEN);
+
+	for (x = 0; x < wc->numspans; x++) {
+		ts = wc->tspans[x];
+		ts->writechunk = (void *)(wc->writechunk + (x * 32 * 2) + (which * (1024 >> 2)));
+		ts->readchunk = (void *)(wc->readchunk + (x * 32 * 2) + (which * (1024 >> 2)));
+		for (y=0;y<wc->tspans[x]->span.channels;y++) {
+			struct dahdi_chan *mychans = ts->chans[y];
+			if (gen2) {
+				mychans->writechunk = (void *)(wc->writechunk + ((x * 32 + y + offset) * 2) + (which * (1024 >> 2)));
+				mychans->readchunk = (void *)(wc->readchunk + ((x * 32 + y + offset) * 2) + (which * (1024 >> 2)));
+			}
+		}
+	}
+}
+
+#ifdef DAHDI_SPAN_OPS
+static const struct dahdi_span_ops t4_gen1_span_ops = {
+	.owner = THIS_MODULE,
+	.spanconfig = t4_spanconfig,
+	.chanconfig = t4_chanconfig,
+	.startup = t4_startup,
+	.shutdown = t4_shutdown,
+	.rbsbits = t4_rbsbits,
+	.maint = t4_maint,
+	.open = t4_open,
+	.close  = t4_close,
+	.ioctl = t4_ioctl,
+	.hdlc_hard_xmit = t4_hdlc_hard_xmit,
+};
+
+static const struct dahdi_span_ops t4_gen2_span_ops = {
+	.owner = THIS_MODULE,
+	.spanconfig = t4_spanconfig,
+	.chanconfig = t4_chanconfig,
+	.startup = t4_startup,
+	.shutdown = t4_shutdown,
+	.rbsbits = t4_rbsbits,
+	.maint = t4_maint,
+	.open = t4_open,
+	.close  = t4_close,
+	.ioctl = t4_ioctl,
+	.hdlc_hard_xmit = t4_hdlc_hard_xmit,
+	.dacs = t4_dacs,
+#ifdef VPM_SUPPORT
+	.echocan_create = t4_echocan_create,
+#endif
+};
+#endif
+
+static void init_spans(struct t4 *wc)
+{
+	int x,y;
+	int gen2;
+	struct t4_span *ts;
+	unsigned int reg;
+
+	wc->ddev->manufacturer = "OpenVox";
+	if (order_index[wc->order] == 1)
+		wc->ddev->location = kasprintf(GFP_KERNEL,
+				"Board ID Switch %d", wc->order);
+	else
+		wc->ddev->location = kasprintf(GFP_KERNEL,
+				"PCI Bus %02d Slot %02d",
+				wc->dev->bus->number,
+				PCI_SLOT(wc->dev->devfn) + 1);
+	if (!wc->ddev->location)
+		return; /* FIXME: Error handling */
+
+	gen2 = (wc->tspans[0]->spanflags & FLAG_2NDGEN);
+	for (x = 0; x < wc->numspans; x++) {
+		ts = wc->tspans[x];
+		sprintf(ts->span.name, "D115/D130/%d/%d", wc->num, x + 1);
+		snprintf(ts->span.desc, sizeof(ts->span.desc) - 1,
+			 "D115/D130 (E1|T1) Card %d Span %d", wc->num, x+1);
+		switch (ts->spantype) {
+		case TYPE_T1:
+			ts->span.spantype = "T1";
+			break;
+		case TYPE_E1:
+			ts->span.spantype = "E1";
+			break;
+		case TYPE_J1:
+			ts->span.spantype = "J1";
+			break;
+		}
+#ifdef DAHDI_SPAN_MODULE	
+		ts->span.owner = THIS_MODULE;
+#endif
+#ifdef DAHDI_SPAN_OPS
+		if (gen2) {
+			ts->span.ops = &t4_gen2_span_ops;
+		} else {
+			ts->span.ops = &t4_gen1_span_ops;
+		}
+#else
+		ts->span.spanconfig = t4_spanconfig;
+		ts->span.chanconfig = t4_chanconfig;
+		ts->span.startup = t4_startup;
+		ts->span.shutdown = t4_shutdown;
+		ts->span.rbsbits = t4_rbsbits;
+		ts->span.maint = t4_maint;
+		ts->span.open = t4_open;
+		ts->span.close  = t4_close;
+		ts->span.ioctl = t4_ioctl;
+		ts->span.hdlc_hard_xmit = t4_hdlc_hard_xmit;
+		if (gen2) {
+#ifdef VPM_SUPPORT
+		if (vpmsupport)
+			ts->span.echocan_create = t4_echocan_create;
+#endif			
+			ts->span.dacs = t4_dacs;
+		}
+		ts->span.pvt = ts;
+#endif
+
+		/* HDLC Specific init */
+		ts->sigchan = NULL;
+		ts->sigmode = sigmode;
+		ts->sigactive = 0;
+		
+		if (ts->spantype == TYPE_T1 || ts->spantype == TYPE_J1) {
+			ts->span.channels = 24;
+			ts->span.deflaw = DAHDI_LAW_MULAW;
+			ts->span.linecompat = DAHDI_CONFIG_AMI |
+				DAHDI_CONFIG_B8ZS | DAHDI_CONFIG_D4 |
+				DAHDI_CONFIG_ESF;
+		} else {
+			ts->span.channels = 31;
+			ts->span.deflaw = DAHDI_LAW_ALAW;
+			ts->span.linecompat = DAHDI_CONFIG_AMI |
+				DAHDI_CONFIG_HDB3 | DAHDI_CONFIG_CCS |
+				DAHDI_CONFIG_CRC4;
+		}
+		ts->span.chans = ts->chans;
+		ts->span.flags = DAHDI_FLAG_RBS;
+
+		ts->owner = wc;
+		ts->span.offset = x;
+		ts->writechunk = (void *)(wc->writechunk + x * 32 * 2);
+		ts->readchunk = (void *)(wc->readchunk + x * 32 * 2);
+
+		for (y=0;y<wc->tspans[x]->span.channels;y++) {
+			struct dahdi_chan *mychans = ts->chans[y];
+			sprintf(mychans->name, "D115/D130/%d/%d/%d", wc->num, x + 1, y + 1);
+			t4_chan_set_sigcap(&ts->span, x);
+			mychans->pvt = wc;
+			mychans->chanpos = y + 1;
+		}
+
+		/* Enable 1sec timer interrupt */
+		reg = t4_framer_in(wc, x, FMR1_T);
+		t4_framer_out(wc, x, FMR1_T, (reg | FMR1_ECM));
+
+		/* Enable Errored Second interrupt */
+		t4_framer_out(wc, x, ESM, 0);
+
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+		t4_reset_counters(&ts->span);
+#endif
+	}
+
+	set_span_devicetype(wc);
+	setup_chunks(wc, 0);
+	wc->lastindex = 0;
+}
+
+static void t4_serial_setup(struct t4 *wc, int unit)
+{
+	if (!wc->globalconfig) {
+		wc->globalconfig = 1;
+		if (debug)
+			dev_info(&wc->dev->dev, "opvxd115: Setting up global "
+					"serial parameters\n");
+		t4_framer_out(wc, 0, 0x85, 0xe0);	/* GPC1: Multiplex mode enabled, FSC is output, active low, RCLK from channel 0 */
+		t4_framer_out(wc, 0, 0x08, 0x01);	/* IPC: Interrupt push/pull active low */
+	
+		/* Global clocks (8.192 Mhz CLK) */
+		t4_framer_out(wc, 0, 0x92, 0x00);	
+		t4_framer_out(wc, 0, 0x93, 0x18);
+		t4_framer_out(wc, 0, 0x94, 0xfb);
+		t4_framer_out(wc, 0, 0x95, 0x0b);
+		t4_framer_out(wc, 0, 0x96, 0x00);
+		t4_framer_out(wc, 0, 0x97, 0x0b);
+		t4_framer_out(wc, 0, 0x98, 0xdb);
+		t4_framer_out(wc, 0, 0x99, 0xdf);
+	}
+
+	/* Configure interrupts */	
+	t4_framer_out(wc, unit, FRMR_GCR, 0x00);	/* GCR: Interrupt on Activation/Deactivation of each */
+
+	/* Configure system interface */
+	t4_framer_out(wc, unit, FRMR_SIC1, 0xc2);	/* SIC1: 8.192 Mhz clock/bus, double buffer receive / transmit, byte interleaved */
+	t4_framer_out(wc, unit, FRMR_SIC2, 0x20 | (unit << 1)); /* SIC2: No FFS, no center receive eliastic buffer, phase */
+	t4_framer_out(wc, unit, FRMR_SIC3, 0x04);	/* SIC3: Edges for capture */
+	t4_framer_out(wc, unit, FRMR_CMR2, 0x00);	/* CMR2: We provide sync and clock for tx and rx. */
+	if (!wc->t1e1) { /* T1 mode */
+		t4_framer_out(wc, unit, FRMR_XC0, 0x03);	/* XC0: Normal operation of Sa-bits */
+		t4_framer_out(wc, unit, FRMR_XC1, 0x84);	/* XC1: 0 offset */
+		if (wc->tspans[unit]->spantype == TYPE_J1)
+			t4_framer_out(wc, unit, FRMR_RC0, 0x83);	/* RC0: Just shy of 1023 */
+		else
+			t4_framer_out(wc, unit, FRMR_RC0, 0x03);	/* RC0: Just shy of 1023 */
+		t4_framer_out(wc, unit, FRMR_RC1, 0x84);	/* RC1: The rest of RC0 */
+	} else { /* E1 mode */
+		t4_framer_out(wc, unit, FRMR_XC0, 0x00);	/* XC0: Normal operation of Sa-bits */
+		t4_framer_out(wc, unit, FRMR_XC1, 0x04);	/* XC1: 0 offset */
+		t4_framer_out(wc, unit, FRMR_RC0, 0x04);	/* RC0: Just shy of 1023 */
+		t4_framer_out(wc, unit, FRMR_RC1, 0x04);	/* RC1: The rest of RC0 */
+	}
+	
+	/* Configure ports */
+	t4_framer_out(wc, unit, 0x80, 0x00);	/* PC1: SPYR/SPYX input on RPA/XPA */
+	if (wc->falc31) {
+			  t4_framer_out(wc, unit, 0x81, 0xBB);	/* PC2: RMFB/XSIG output/input on RPB/XPB */
+			  t4_framer_out(wc, unit, 0x82, 0xBB);	/* PC3: Some unused stuff */
+			  t4_framer_out(wc, unit, 0x83, 0xBB);	/* PC4: Some more unused stuff */
+	} else {
+			  t4_framer_out(wc, unit, 0x81, 0x22);	/* PC2: RMFB/XSIG output/input on RPB/XPB */
+			  t4_framer_out(wc, unit, 0x82, 0x65);	/* PC3: Some unused stuff */
+			  t4_framer_out(wc, unit, 0x83, 0x35);	/* PC4: Some more unused stuff */
+	}
+	t4_framer_out(wc, unit, 0x84, 0x01);	/* PC5: XMFS active low, SCLKR is input, RCLK is output */
+	if (debug & DEBUG_MAIN)
+		dev_notice(&wc->dev->dev, "Successfully initialized serial "
+				"bus for unit %d\n", unit);
+}
+
+static int syncsrc = 0;
+static int syncnum = 0 /* -1 */;
+static int syncspan = 0;
+#ifdef DEFINE_SPINLOCK
+static DEFINE_SPINLOCK(synclock);
+#else
+static spinlock_t synclock = SPIN_LOCK_UNLOCKED;
+#endif
+
+static void __t4_set_rclk_src(struct t4 *wc, int span)
+{
+	int cmr1 = 0x38;	/* Clock Mode: RCLK sourced by DCO-R1
+				   by default, Disable Clock-Switching */
+
+	cmr1 |= (span << 6);
+	__t4_framer_out(wc, 0, 0x44, cmr1);
+
+	dev_info(&wc->dev->dev, "RCLK source set to span %d\n", span+1);
+}
+
+static void __t4_set_sclk_src(struct t4 *wc, int mode, int master, int slave)
+{
+	if (slave) {
+		wc->dmactrl |= (1 << 25);
+		dev_info(&wc->dev->dev, "SCLK is slaved to timing cable\n");
+	} else {
+		wc->dmactrl &= ~(1 << 25);
+	}
+
+	if (master) {
+		wc->dmactrl |= (1 << 24);
+		dev_info(&wc->dev->dev, "SCLK is master to timing cable\n");
+	} else {
+		wc->dmactrl &= ~(1 << 24);
+	}
+
+	if (mode == WC_RECOVER)
+		wc->dmactrl |= (1 << 29); /* Recover timing from RCLK */
+
+	if (mode == WC_SELF)
+		wc->dmactrl &= ~(1 << 29);/* Provide timing from MCLK */
+
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18))
+static ssize_t t4_timing_master_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct t4 *wc = dev_get_drvdata(dev);
+	if (wc->dmactrl & (1 << 29))
+		return sprintf(buf, "%d\n", wc->syncsrc);
+	else
+		return sprintf(buf, "%d\n", -1);
+}
+
+static DEVICE_ATTR(timing_master, 0400, t4_timing_master_show, NULL);
+
+static void create_sysfs_files(struct t4 *wc)
+{
+	int ret;
+	ret = device_create_file(&wc->dev->dev,
+				 &dev_attr_timing_master);
+	if (ret) {
+		dev_info(&wc->dev->dev,
+			"Failed to create device attributes.\n");
+	}
+}
+
+static void remove_sysfs_files(struct t4 *wc)
+{
+	device_remove_file(&wc->dev->dev,
+			   &dev_attr_timing_master);
+}
+
+#else
+
+static inline void create_sysfs_files(struct t4 *wc) { return; }
+static inline void remove_sysfs_files(struct t4 *wc) { return; }
+
+#endif /* LINUX_KERNEL > 2.6.18 */
+
+static inline void __t4_update_timing(struct t4 *wc)
+{
+	int i;
+	/* update sync src info */
+	if (wc->syncsrc != syncsrc) {
+		dev_info(&wc->dev->dev, "Swapping card %d from %d to %d\n",
+				wc->num, wc->syncsrc, syncsrc);
+		wc->syncsrc = syncsrc;
+		/* Update sync sources */
+		for (i = 0; i < wc->numspans; i++) {
+			wc->tspans[i]->span.syncsrc = wc->syncsrc;
+		}
+		if (syncnum == wc->num) {
+			__t4_set_rclk_src(wc, syncspan-1);
+			__t4_set_sclk_src(wc, WC_RECOVER, 1, 0);
+			if (debug)
+				dev_notice(&wc->dev->dev, "Card %d, using sync "
+					"span %d, master\n", wc->num, syncspan);
+		} else {
+			__t4_set_sclk_src(wc, WC_RECOVER, 0, 1);
+			if (debug)
+				dev_notice(&wc->dev->dev, "Card %d, using "
+					"Timing Bus, NOT master\n", wc->num);
+		}
+	}
+}
+
+static int __t4_findsync(struct t4 *wc)
+{
+	int i;
+	int x;
+	unsigned long flags;
+	int p;
+	int nonzero;
+	int newsyncsrc = 0;			/* DAHDI span number */
+	int newsyncnum = 0;			/* opvxd115 card number */
+	int newsyncspan = 0;		/* span on given opvxd115 card */
+	spin_lock_irqsave(&synclock, flags);
+#if 1
+	if (!wc->num) {
+		/* If we're the first card, go through all the motions, up to 8 levels
+		   of sync source */
+		p = 1;
+		while (p < 8) {
+			nonzero = 0;
+			for (x=0;cards[x];x++) {
+				for (i = 0; i < wc->numspans; i++) {
+					if (cards[x]->tspans[i]->syncpos) {
+						nonzero = 1;
+						if ((cards[x]->tspans[i]->syncpos == p) &&
+						    !(cards[x]->tspans[i]->span.alarms & (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE | DAHDI_ALARM_LOOPBACK)) &&
+							(cards[x]->tspans[i]->span.flags & DAHDI_FLAG_RUNNING)) {
+								/* This makes a good sync source */
+								newsyncsrc = cards[x]->tspans[i]->span.spanno;
+								newsyncnum = x;
+								newsyncspan = i + 1;
+								/* Jump out */
+								goto found;
+						}
+					}
+				}		
+			}
+			if (nonzero)
+				p++;
+			else 
+				break;
+		}
+found:		
+		if ((syncnum != newsyncnum) || (syncsrc != newsyncsrc) || (newsyncspan != syncspan)) {
+			if (debug)
+				dev_notice(&wc->dev->dev, "New syncnum: %d "
+					"(was %d), syncsrc: %d (was %d), "
+					"syncspan: %d (was %d)\n", newsyncnum,
+					syncnum, newsyncsrc, syncsrc,
+					newsyncspan, syncspan);
+			syncnum = newsyncnum;
+			syncsrc = newsyncsrc;
+			syncspan = newsyncspan;
+			for (x=0;cards[x];x++) {
+				__t4_update_timing(cards[x]);
+			}
+		}
+	}
+	__t4_update_timing(wc);
+#endif	
+	spin_unlock_irqrestore(&synclock, flags);
+	return 0;
+}
+
+static void __t4_set_timing_source_auto(struct t4 *wc)
+{
+	int x;
+	int firstprio, secondprio;
+	firstprio = secondprio = 4;
+
+	if (debug)
+		dev_info(&wc->dev->dev, "timing source auto\n");
+	clear_bit(T4_CHECK_TIMING, &wc->checkflag);
+	if (timingcable) {
+		__t4_findsync(wc);
+	} else {
+		if (debug)
+			dev_info(&wc->dev->dev, "Evaluating spans for timing "
+					"source\n");
+		for (x=0;x<wc->numspans;x++) {
+			if ((wc->tspans[x]->span.flags & DAHDI_FLAG_RUNNING) &&
+			   !(wc->tspans[x]->span.alarms & (DAHDI_ALARM_RED |
+							   DAHDI_ALARM_BLUE))) {
+				if (debug)
+					dev_info(&wc->dev->dev, "span %d is "
+						"green : syncpos %d\n", x+1,
+						wc->tspans[x]->syncpos);
+				if (wc->tspans[x]->syncpos) {
+					/* Valid rsync source in recovered
+					   timing mode */
+					if (firstprio == 4)
+						firstprio = x;
+					else if (wc->tspans[x]->syncpos <
+						wc->tspans[firstprio]->syncpos)
+						firstprio = x;
+				} else {
+					/* Valid rsync source in system timing
+					   mode */
+					if (secondprio == 4)
+						secondprio = x;
+				}
+			}
+		}
+		if (firstprio != 4) {
+			wc->syncsrc = firstprio;
+			__t4_set_rclk_src(wc, firstprio);
+			__t4_set_sclk_src(wc, WC_RECOVER, 0, 0);
+			dev_info(&wc->dev->dev, "Recovered timing mode, "\
+						"RCLK set to span %d\n",
+						firstprio+1);
+		} else if (secondprio != 4) {
+			wc->syncsrc = -1;
+			__t4_set_rclk_src(wc, secondprio);
+			__t4_set_sclk_src(wc, WC_SELF, 0, 0);
+			dev_info(&wc->dev->dev, "System timing mode, "\
+						"RCLK set to span %d\n",
+						secondprio+1);
+		} else {
+			wc->syncsrc = -1;
+			dev_info(&wc->dev->dev, "All spans in alarm : No valid"\
+						"span to source RCLK from\n");
+			/* Default rclk to lock with span 1 */
+			__t4_set_rclk_src(wc, 0);
+			__t4_set_sclk_src(wc, WC_SELF, 0, 0);
+		}
+	}
+}
+
+static void __t4_configure_t1(struct t4 *wc, int unit, int lineconfig, int txlevel)
+{
+	unsigned int fmr4, fmr2, fmr1, fmr0, lim2;
+	char *framing, *line;
+	int mytxlevel;
+	if ((txlevel > 7) || (txlevel < 4))
+		mytxlevel = 0;
+	else
+		mytxlevel = txlevel - 4;
+	fmr1 = 0x9c; /* FMR1: Mode 1, T1 mode, CRC on for ESF, 8.192 Mhz system data rate, no XAIS */
+	fmr2 = 0x20; /* FMR2: no payload loopback, don't auto yellow */
+	fmr4 = 0x0c; /* FMR4: Lose sync on 2 out of 5 framing bits, auto resync */
+	lim2 = 0x21; /* LIM2: 50% peak is a "1", Advanced Loss recovery */
+	lim2 |= (mytxlevel << 6);	/* LIM2: Add line buildout */
+	__t4_framer_out(wc, unit, 0x1d, fmr1);
+	__t4_framer_out(wc, unit, 0x1e, fmr2);
+
+	/* Configure line interface */
+	if (lineconfig & DAHDI_CONFIG_AMI) {
+		line = "AMI";
+		/* workaround for errata #2 in ES v3 09-10-16 */
+		fmr0 = (wc->falc31) ? 0xb0 : 0xa0;
+	} else {
+		line = "B8ZS";
+		fmr0 = 0xf0;
+	}
+	if (lineconfig & DAHDI_CONFIG_D4) {
+		framing = "D4";
+	} else {
+		framing = "ESF";
+		fmr4 |= 0x2;
+		fmr2 |= 0xc0;
+	}
+	__t4_framer_out(wc, unit, 0x1c, fmr0);
+	__t4_framer_out(wc, unit, 0x20, fmr4);
+	__t4_framer_out(wc, unit, 0x21, 0x40);	/* FMR5: Enable RBS mode */
+
+	__t4_framer_out(wc, unit, 0x37, 0xf0 );	/* LIM1: Clear data in case of LOS, Set receiver threshold (0.5V), No remote loop, no DRS */
+	__t4_framer_out(wc, unit, 0x36, 0x08);	/* LIM0: Enable auto long haul mode, no local loop (must be after LIM1) */
+
+	__t4_framer_out(wc, unit, 0x02, 0x50);	/* CMDR: Reset the receiver and transmitter line interface */
+	__t4_framer_out(wc, unit, 0x02, 0x00);	/* CMDR: Reset the receiver and transmitter line interface */
+
+	if (wc->falc31) {
+		if (debug)
+			dev_info(&wc->dev->dev, "card %d span %d: setting Rtx "
+					"to 0ohm for T1\n", wc->num, unit);
+		__t4_framer_out(wc, unit, 0x86, 0x00);	/* PC6: set Rtx to 0ohm for T1 */
+
+		// Hitting the bugfix register to fix errata #3
+		__t4_framer_out(wc, unit, 0xbd, 0x05);
+	}
+
+	__t4_framer_out(wc, unit, 0x3a, lim2);	/* LIM2: 50% peak amplitude is a "1" */
+	__t4_framer_out(wc, unit, 0x38, 0x0a);	/* PCD: LOS after 176 consecutive "zeros" */
+	__t4_framer_out(wc, unit, 0x39, 0x15);	/* PCR: 22 "ones" clear LOS */
+	
+	/* Generate pulse mask for T1 */
+	switch(mytxlevel) {
+	case 3:
+		__t4_framer_out(wc, unit, 0x26, 0x07);	/* XPM0 */
+		__t4_framer_out(wc, unit, 0x27, 0x01);	/* XPM1 */
+		__t4_framer_out(wc, unit, 0x28, 0x00);	/* XPM2 */
+		break;
+	case 2:
+		__t4_framer_out(wc, unit, 0x26, 0x8c);	/* XPM0 */
+		__t4_framer_out(wc, unit, 0x27, 0x11);	/* XPM1 */
+		__t4_framer_out(wc, unit, 0x28, 0x01);	/* XPM2 */
+		break;
+	case 1:
+		__t4_framer_out(wc, unit, 0x26, 0x8c);	/* XPM0 */
+		__t4_framer_out(wc, unit, 0x27, 0x01);	/* XPM1 */
+		__t4_framer_out(wc, unit, 0x28, 0x00);	/* XPM2 */
+		break;
+	case 0:
+	default:
+		__t4_framer_out(wc, unit, 0x26, 0xd7);	/* XPM0 */
+		__t4_framer_out(wc, unit, 0x27, 0x22);	/* XPM1 */
+		__t4_framer_out(wc, unit, 0x28, 0x01);	/* XPM2 */
+		break;
+	}
+
+	/* Don't mask framer interrupts if hardware HDLC is in use */
+	__t4_framer_out(wc, unit, FRMR_IMR0, 0xff & ~((wc->tspans[unit]->sigchan) ? HDLC_IMR0_MASK : 0));	/* IMR0: We care about CAS changes, etc */
+	__t4_framer_out(wc, unit, FRMR_IMR1, 0xff & ~((wc->tspans[unit]->sigchan) ? HDLC_IMR1_MASK : 0));	/* IMR1: We care about nothing */
+	__t4_framer_out(wc, unit, 0x16, 0x00);	/* IMR2: All the alarm stuff! */
+	__t4_framer_out(wc, unit, 0x17, 0x34);	/* IMR3: AIS and friends */
+	__t4_framer_out(wc, unit, 0x18, 0x3f);  /* IMR4: Slips on transmit */
+
+	dev_info(&wc->dev->dev, "Span %d configured for %s/%s\n", unit + 1,
+			framing, line);
+}
+
+static void __t4_configure_e1(struct t4 *wc, int unit, int lineconfig)
+{
+	unsigned int fmr2, fmr1, fmr0;
+	unsigned int cas = 0;
+	unsigned int imr3extra=0;
+	char *crc4 = "";
+	char *framing, *line;
+	fmr1 = 0x44; /* FMR1: E1 mode, Automatic force resync, PCM30 mode, 8.192 Mhz backplane, no XAIS */
+	fmr2 = 0x03; /* FMR2: Auto transmit remote alarm, auto loss of multiframe recovery, no payload loopback */
+	if (lineconfig & DAHDI_CONFIG_CRC4) {
+		fmr1 |= 0x08;	/* CRC4 transmit */
+		fmr2 |= 0xc0;	/* CRC4 receive */
+		crc4 = "/CRC4";
+	}
+	__t4_framer_out(wc, unit, 0x1d, fmr1);
+	__t4_framer_out(wc, unit, 0x1e, fmr2);
+
+	/* Configure line interface */
+	if (lineconfig & DAHDI_CONFIG_AMI) {
+		line = "AMI";
+		/* workaround for errata #2 in ES v3 09-10-16 */
+		fmr0 = (wc->falc31) ? 0xb0 : 0xa0;
+	} else {
+		line = "HDB3";
+		fmr0 = 0xf0;
+	}
+	if (lineconfig & DAHDI_CONFIG_CCS) {
+		framing = "CCS";
+		imr3extra = 0x28;
+	} else {
+		framing = "CAS";
+		cas = 0x40;
+	}
+	__t4_framer_out(wc, unit, 0x1c, fmr0);
+
+	__t4_framer_out(wc, unit, 0x37, 0xf0 /*| 0x6 */ );	/* LIM1: Clear data in case of LOS, Set receiver threshold (0.5V), No remote loop, no DRS */
+	__t4_framer_out(wc, unit, 0x36, 0x08);	/* LIM0: Enable auto long haul mode, no local loop (must be after LIM1) */
+
+	__t4_framer_out(wc, unit, 0x02, 0x50);	/* CMDR: Reset the receiver and transmitter line interface */
+	__t4_framer_out(wc, unit, 0x02, 0x00);	/* CMDR: Reset the receiver and transmitter line interface */
+
+	if (wc->falc31) {
+		if (debug)
+			dev_info(&wc->dev->dev,
+					"setting Rtx to 7.5ohm for E1\n");
+		__t4_framer_out(wc, unit, 0x86, 0x40);	/* PC6: turn on 7.5ohm Rtx for E1 */
+	}
+
+	/* Condition receive line interface for E1 after reset */
+	__t4_framer_out(wc, unit, 0xbb, 0x17);
+	__t4_framer_out(wc, unit, 0xbc, 0x55);
+	__t4_framer_out(wc, unit, 0xbb, 0x97);
+	__t4_framer_out(wc, unit, 0xbb, 0x11);
+	__t4_framer_out(wc, unit, 0xbc, 0xaa);
+	__t4_framer_out(wc, unit, 0xbb, 0x91);
+	__t4_framer_out(wc, unit, 0xbb, 0x12);
+	__t4_framer_out(wc, unit, 0xbc, 0x55);
+	__t4_framer_out(wc, unit, 0xbb, 0x92);
+	__t4_framer_out(wc, unit, 0xbb, 0x0c);
+	__t4_framer_out(wc, unit, 0xbb, 0x00);
+	__t4_framer_out(wc, unit, 0xbb, 0x8c);
+	
+	__t4_framer_out(wc, unit, 0x3a, 0x20);	/* LIM2: 50% peak amplitude is a "1" */
+	__t4_framer_out(wc, unit, 0x38, 0x0a);	/* PCD: LOS after 176 consecutive "zeros" */
+	__t4_framer_out(wc, unit, 0x39, 0x15);	/* PCR: 22 "ones" clear LOS */
+	
+	__t4_framer_out(wc, unit, 0x20, 0x9f);	/* XSW: Spare bits all to 1 */
+	__t4_framer_out(wc, unit, 0x21, 0x1c|cas);	/* XSP: E-bit set when async. AXS auto, XSIF to 1 */
+	
+	
+	/* Generate pulse mask for E1 */
+	__t4_framer_out(wc, unit, 0x26, 0x54);	/* XPM0 */
+	__t4_framer_out(wc, unit, 0x27, 0x02);	/* XPM1 */
+	__t4_framer_out(wc, unit, 0x28, 0x00);	/* XPM2 */
+
+	/* Don't mask framer interrupts if hardware HDLC is in use */
+	__t4_framer_out(wc, unit, FRMR_IMR0, 0xff & ~((wc->tspans[unit]->sigchan) ? HDLC_IMR0_MASK : 0));	/* IMR0: We care about CRC errors, CAS changes, etc */
+	__t4_framer_out(wc, unit, FRMR_IMR1, 0x3f & ~((wc->tspans[unit]->sigchan) ? HDLC_IMR1_MASK : 0));	/* IMR1: We care about loopup / loopdown */
+	__t4_framer_out(wc, unit, 0x16, 0x00);	/* IMR2: We care about all the alarm stuff! */
+	__t4_framer_out(wc, unit, 0x17, 0x04 | imr3extra); /* IMR3: AIS */
+	__t4_framer_out(wc, unit, 0x18, 0x3f);  /* IMR4: We care about slips on transmit */
+
+	dev_info(&wc->dev->dev, "opvxd115: Span %d configured for %s/%s%s\n",
+			unit + 1, framing, line, crc4);
+}
+
+static int t4_startup(struct file *file, struct dahdi_span *span)
+{
+#ifdef SUPPORT_GEN1
+	int i;
+#endif
+	int tspan;
+	unsigned long flags;
+	int alreadyrunning;
+	struct t4_span *ts = t4_from_span(span);
+	struct t4 *wc = ts->owner;
+
+	set_bit(T4_IGNORE_LATENCY, &wc->checkflag);
+	if (debug)
+		dev_info(&wc->dev->dev, "About to enter startup!\n");
+	tspan = span->offset + 1;
+	if (tspan < 0) {
+		dev_info(&wc->dev->dev, "opvxd115: Span '%d' isn't us?\n",
+				span->spanno);
+		return -1;
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	alreadyrunning = span->flags & DAHDI_FLAG_RUNNING;
+
+#ifdef SUPPORT_GEN1
+	/* initialize the start value for the entire chunk of last ec buffer */
+	for(i = 0; i < span->channels; i++)
+	{
+		memset(ts->ec_chunk1[i],
+			DAHDI_LIN2X(0,span->chans[i]),DAHDI_CHUNKSIZE);
+		memset(ts->ec_chunk2[i],
+			DAHDI_LIN2X(0,span->chans[i]),DAHDI_CHUNKSIZE);
+	}
+#endif
+	/* Force re-evaluation of timing source */
+	wc->syncsrc = -1;
+	set_bit(T4_CHECK_TIMING, &wc->checkflag);
+
+	if (ts->spantype == TYPE_E1) { /* if this is an E1 card */
+		__t4_configure_e1(wc, span->offset, span->lineconfig);
+	} else { /* is a T1 card */
+		__t4_configure_t1(wc, span->offset, span->lineconfig, span->txlevel);
+	}
+
+	/* Note clear channel status */
+	wc->tspans[span->offset]->notclear = 0;
+	__set_clear(wc, span->offset);
+	
+	if (!alreadyrunning) {
+		span->flags |= DAHDI_FLAG_RUNNING;
+		wc->spansstarted++;
+
+		if (wc->flags & FLAG_5THGEN)
+			__t4_pci_out(wc, 5, (ms_per_irq << 16) | wc->numbufs);
+		/* enable interrupts */
+		/* Start DMA, enabling DMA interrupts on read only */
+#if 0
+		/* Enable framer only interrupts */
+		wc->dmactrl |= 1 << 27;
+#endif
+		wc->dmactrl |= (ts->spanflags & FLAG_2NDGEN) ? 0xc0000000 : 0xc0000003;
+#ifdef VPM_SUPPORT
+		wc->dmactrl |= wc->vpm;
+#endif
+		/* Seed interrupt register */
+		__t4_pci_out(wc, WC_INTR, 0x0c);
+		if (noburst || !(ts->spanflags & FLAG_BURST))
+			wc->dmactrl |= (1 << 26);
+		__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+
+		/* Startup HDLC controller too */
+	}
+
+	if (ts->sigchan) {
+		struct dahdi_chan *sigchan = ts->sigchan;
+
+		spin_unlock_irqrestore(&wc->reglock, flags);
+		if (hdlc_start(wc, span->offset, sigchan, ts->sigmode)) {
+			dev_notice(&wc->dev->dev, "Error initializing "
+					"signalling controller\n");
+			return -1;
+		}
+		spin_lock_irqsave(&wc->reglock, flags);
+	}
+
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	t4_check_alarms(wc, span->offset);
+	t4_check_sigbits(wc, span->offset);
+
+	if (wc->tspans[0]->sync == span->spanno)
+		dev_info(&wc->dev->dev, "SPAN %d: Primary Sync Source\n",
+				span->spanno);
+#ifdef VPM_SUPPORT
+	if (!alreadyrunning && !wc->vpm) {
+		wait_a_little();
+		t4_vpm400_init(wc);
+		if (!wc->vpm)
+			t4_vpm450_init(wc);
+		wc->dmactrl |= wc->vpm;
+		t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+		if (wc->vpm)
+			set_span_devicetype(wc);
+	}
+#endif
+	if (debug)
+		dev_info(&wc->dev->dev, "Completed startup!\n");
+	clear_bit(T4_IGNORE_LATENCY, &wc->checkflag);
+	return 0;
+}
+
+#ifdef SUPPORT_GEN1
+static inline void e1_check(struct t4 *wc, int span, int val)
+{
+	struct t4_span *ts = wc->tspans[span];
+	if ((ts->span.channels > 24) &&
+	    (ts->span.flags & DAHDI_FLAG_RUNNING) &&
+	    !(ts->span.alarms) &&
+	    (!wc->e1recover))   {
+		if (val != 0x1b) {
+			ts->e1check++;
+		} else
+			ts->e1check = 0;
+		if (ts->e1check > 100) {
+			/* Wait 1000 ms */
+			wc->e1recover = 1000 * 8;
+			wc->tspans[0]->e1check = 0;
+			if (debug & DEBUG_MAIN)
+				dev_notice(&wc->dev->dev, "Detected loss of "
+					"E1 alignment on span %d!\n", span);
+			t4_reset_dma(wc);
+		}
+	}
+}
+
+static void t4_receiveprep(struct t4 *wc, int irq)
+{
+	volatile unsigned int *readchunk;
+	int dbl = 0;
+	int x,y,z;
+	unsigned int tmp;
+	int offset=0;
+	if (!wc->t1e1)
+		offset = 4;
+	if (irq & 1) {
+		/* First part */
+		readchunk = wc->readchunk;
+		if (!wc->last0) 
+			dbl = 1;
+		wc->last0 = 0;
+	} else {
+		readchunk = wc->readchunk + DAHDI_CHUNKSIZE * 32;
+		if (wc->last0) 
+			dbl = 1;
+		wc->last0 = 1;
+	}
+	if (dbl) {
+		for (x=0;x<wc->numspans;x++)
+			wc->ddev->irqmisses++;
+		if (debug & DEBUG_MAIN)
+			dev_notice(&wc->dev->dev, "opvxd115: Double/missed "
+				"interrupt detected\n");
+	}
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+		for (z=0;z<24;z++) {
+			/* All T1/E1 channels */
+			tmp = readchunk[z+1+offset];
+			wc->tspans[0]->span.chans[z]->readchunk[x] = tmp >> 24;
+		}
+		if (wc->t1e1) {
+			if (wc->e1recover > 0)
+				wc->e1recover--;
+			tmp = readchunk[0];
+			e1_check(wc, 0, (tmp & 0x7f000000) >> 24);
+			for (z=24;z<31;z++) {
+				/* Only E1 channels now */
+				tmp = readchunk[z+1];
+				if (wc->tspans[0]->span.channels > 24)
+					wc->tspans[0]->span.chans[z]->readchunk[x] = tmp >> 24;
+			}
+		}
+		/* Advance pointer by 4 TDM frame lengths */
+		readchunk += 32;
+	}
+	for (x=0;x<wc->numspans;x++) {
+		if (wc->tspans[x]->span.flags & DAHDI_FLAG_RUNNING) {
+			for (y=0;y<wc->tspans[x]->span.channels;y++) {
+				/* Echo cancel double buffered data */
+				dahdi_ec_chunk(wc->tspans[x]->span.chans[y], 
+				    wc->tspans[x]->span.chans[y]->readchunk, 
+					wc->tspans[x]->ec_chunk2[y]);
+				memcpy(wc->tspans[x]->ec_chunk2[y],wc->tspans[x]->ec_chunk1[y],
+					DAHDI_CHUNKSIZE);
+				memcpy(wc->tspans[x]->ec_chunk1[y],
+					wc->tspans[x]->span.chans[y]->writechunk,
+						DAHDI_CHUNKSIZE);
+			}
+			dahdi_receive(&wc->tspans[x]->span);
+		}
+	}
+}
+#endif
+
+#if (DAHDI_CHUNKSIZE != 8)
+#error Sorry, nextgen does not support chunksize != 8
+#endif
+
+static inline void __receive_span(struct t4_span *ts)
+{
+#ifdef VPM_SUPPORT
+	int y;
+	unsigned long merged;
+	merged = ts->dtmfactive & ts->dtmfmutemask;
+	if (merged) {
+		for (y=0;y<ts->span.channels;y++) {
+			/* Mute any DTMFs which are supposed to be muted */
+			if (test_bit(y, &merged)) {
+				memset(ts->span.chans[y]->readchunk, DAHDI_XLAW(0, ts->span.chans[y]), DAHDI_CHUNKSIZE);
+			}
+		}
+	}
+#endif	
+
+#ifdef ENABLE_PREFETCH
+	prefetch((void *)(ts->readchunk));
+	prefetch((void *)(ts->writechunk));
+	prefetch((void *)(ts->readchunk + 8));
+	prefetch((void *)(ts->writechunk + 8));
+	prefetch((void *)(ts->readchunk + 16));
+	prefetch((void *)(ts->writechunk + 16));
+	prefetch((void *)(ts->readchunk + 24));
+	prefetch((void *)(ts->writechunk + 24));
+	prefetch((void *)(ts->readchunk + 32));
+	prefetch((void *)(ts->writechunk + 32));
+	prefetch((void *)(ts->readchunk + 40));
+	prefetch((void *)(ts->writechunk + 40));
+	prefetch((void *)(ts->readchunk + 48));
+	prefetch((void *)(ts->writechunk + 48));
+	prefetch((void *)(ts->readchunk + 56));
+	prefetch((void *)(ts->writechunk + 56));
+#endif
+
+	dahdi_ec_span(&ts->span);
+	dahdi_receive(&ts->span);
+}
+
+static inline void __transmit_span(struct t4_span *ts)
+{
+	dahdi_transmit(&ts->span);
+}
+
+#ifdef ENABLE_WORKQUEUES
+static void workq_handlespan(void *data)
+{
+	struct t4_span *ts = data;
+	struct t4 *wc = ts->owner;
+	
+	__receive_span(ts);
+	__transmit_span(ts);
+	atomic_dec(&wc->worklist);
+	if (!atomic_read(&wc->worklist))
+		t4_pci_out(wc, WC_INTR, 0);
+}
+#else
+static void t4_prep_gen2(struct t4 *wc)
+{
+	int x;
+	for (x=0;x<wc->numspans;x++) {
+		if (wc->tspans[x]->span.flags & DAHDI_FLAG_RUNNING) {
+			__receive_span(wc->tspans[x]);
+			__transmit_span(wc->tspans[x]);
+		}
+	}
+}
+
+#endif
+#ifdef SUPPORT_GEN1
+static void t4_transmitprep(struct t4 *wc, int irq)
+{
+	volatile unsigned int *writechunk;
+	int x,y,z;
+	unsigned int tmp;
+	int offset=0;
+	if (!wc->t1e1)
+		offset = 4;
+	if (irq & 1) {
+		/* First part */
+		writechunk = wc->writechunk + 1;
+	} else {
+		writechunk = wc->writechunk + DAHDI_CHUNKSIZE * 32  + 1;
+	}
+	for (y=0;y<wc->numspans;y++) {
+		if (wc->tspans[y]->span.flags & DAHDI_FLAG_RUNNING) 
+			dahdi_transmit(&wc->tspans[y]->span);
+	}
+
+	for (x=0;x<DAHDI_CHUNKSIZE;x++) {
+		/* Once per chunk */
+		for (z=0;z<24;z++) {
+			/* All T1/E1 channels */
+			tmp = (wc->tspans[0]->span.chans[z]->writechunk[x] << 24);
+			writechunk[z+offset] = tmp;
+		}
+		if (wc->t1e1) {
+			for (z=24;z<31;z++) {
+				/* Only E1 channels now */
+				tmp = 0;
+				if (wc->tspans[0]->span.channels > 24)
+					tmp |= (wc->tspans[0]->span.chans[z]->writechunk[x] << 24);
+				writechunk[z] = tmp;
+			}
+		}
+		/* Advance pointer by 4 TDM frame lengths */
+		writechunk += 32;
+	}
+
+}
+#endif
+
+static void t4_check_sigbits(struct t4 *wc, int span)
+{
+	int a,i,rxs;
+	struct t4_span *ts = wc->tspans[span];
+
+	if (debug & DEBUG_RBS)
+		dev_notice(&wc->dev->dev, "Checking sigbits on span %d\n",
+				span + 1);
+
+	if (!(ts->span.flags & DAHDI_FLAG_RUNNING))
+		return;
+	if (ts->spantype == TYPE_E1) {
+		for (i = 0; i < 15; i++) {
+			a = t4_framer_in(wc, span, 0x71 + i);
+			/* Get high channel in low bits */
+			rxs = (a & 0xf);
+			if (!(ts->span.chans[i+16]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i+16]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i+16], rxs);
+			}
+			rxs = (a >> 4) & 0xf;
+			if (!(ts->span.chans[i]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i], rxs);
+			}
+		}
+	} else if (ts->span.lineconfig & DAHDI_CONFIG_D4) {
+		for (i = 0; i < 24; i+=4) {
+			a = t4_framer_in(wc, span, 0x70 + (i>>2));
+			/* Get high channel in low bits */
+			rxs = (a & 0x3) << 2;
+			if (!(ts->span.chans[i+3]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i+3]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i+3], rxs);
+			}
+			rxs = (a & 0xc);
+			if (!(ts->span.chans[i+2]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i+2]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i+2], rxs);
+			}
+			rxs = (a >> 2) & 0xc;
+			if (!(ts->span.chans[i+1]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i+1]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i+1], rxs);
+			}
+			rxs = (a >> 4) & 0xc;
+			if (!(ts->span.chans[i]->sig & DAHDI_SIG_CLEAR)) {
+				if (ts->span.chans[i]->rxsig != rxs)
+					dahdi_rbsbits(ts->span.chans[i], rxs);
+			}
+		}
+	} else {
+		for (i = 0; i < 24; i+=2) {
+			a = t4_framer_in(wc, span, 0x70 + (i>>1));
+			/* Get high channel in low bits */
+			rxs = (a & 0xf);
+			if (!(ts->span.chans[i+1]->sig & DAHDI_SIG_CLEAR)) {
+				/* XXX Not really reset on every trans! XXX */
+				if (ts->span.chans[i+1]->rxsig != rxs) {
+					dahdi_rbsbits(ts->span.chans[i+1], rxs);
+				}
+			}
+			rxs = (a >> 4) & 0xf;
+			if (!(ts->span.chans[i]->sig & DAHDI_SIG_CLEAR)) {
+				/* XXX Not really reset on every trans! XXX */
+				if (ts->span.chans[i]->rxsig != rxs) {
+					dahdi_rbsbits(ts->span.chans[i], rxs);
+				}
+			}
+		}
+	}
+}
+
+static void t4_check_alarms(struct t4 *wc, int span)
+{
+	unsigned char c, d, e;
+	int alarms;
+	int x,j;
+	struct t4_span *ts = wc->tspans[span];
+	unsigned long flags;
+
+	if (!(ts->span.flags & DAHDI_FLAG_RUNNING))
+		return;
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	c = __t4_framer_in(wc, span, 0x4c);
+	d = __t4_framer_in(wc, span, 0x4d);
+
+	/* Assume no alarms */
+	alarms = 0;
+
+	/* And consider only carrier alarms */
+	ts->span.alarms &= (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE | DAHDI_ALARM_NOTOPEN);
+
+	if (ts->spantype == TYPE_E1) {
+		if (c & 0x04) {
+			/* No multiframe found, force RAI high after 400ms only if
+			   we haven't found a multiframe since last loss
+			   of frame */
+			if (!(ts->spanflags & FLAG_NMF)) {
+				__t4_framer_out(wc, span, 0x20, 0x9f | 0x20);	/* LIM0: Force RAI High */
+				ts->spanflags |= FLAG_NMF;
+				dev_notice(&wc->dev->dev,
+					"NMF workaround on!\n");
+			}
+			__t4_framer_out(wc, span, 0x1e, 0xc3);	/* Reset to CRC4 mode */
+			__t4_framer_out(wc, span, 0x1c, 0xf2);	/* Force Resync */
+			__t4_framer_out(wc, span, 0x1c, 0xf0);	/* Force Resync */
+		} else if (!(c & 0x02)) {
+			if ((ts->spanflags & FLAG_NMF)) {
+				__t4_framer_out(wc, span, 0x20, 0x9f);	/* LIM0: Clear forced RAI */
+				ts->spanflags &= ~FLAG_NMF;
+				dev_notice(&wc->dev->dev,
+					"NMF workaround off!\n");
+			}
+		}
+	} else {
+		/* Detect loopup code if we're not sending one */
+		if ((!ts->span.mainttimer) && (d & 0x08)) {
+			/* Loop-up code detected */
+			if ((ts->loopupcnt++ > 80)  && (ts->span.maintstat != DAHDI_MAINT_REMOTELOOP)) {
+				__t4_framer_out(wc, span, 0x36, 0x08);	/* LIM0: Disable any local loop */
+				__t4_framer_out(wc, span, 0x37, 0xf6 );	/* LIM1: Enable remote loop */
+				ts->span.maintstat = DAHDI_MAINT_REMOTELOOP;
+			}
+		} else
+			ts->loopupcnt = 0;
+		/* Same for loopdown code */
+		if ((!ts->span.mainttimer) && (d & 0x10)) {
+			/* Loop-down code detected */
+			if ((ts->loopdowncnt++ > 80)  && (ts->span.maintstat == DAHDI_MAINT_REMOTELOOP)) {
+				__t4_framer_out(wc, span, 0x36, 0x08);	/* LIM0: Disable any local loop */
+				__t4_framer_out(wc, span, 0x37, 0xf0 );	/* LIM1: Disable remote loop */
+				ts->span.maintstat = DAHDI_MAINT_NONE;
+			}
+		} else
+			ts->loopdowncnt = 0;
+	}
+
+	if (ts->span.lineconfig & DAHDI_CONFIG_NOTOPEN) {
+		for (x=0,j=0;x < ts->span.channels;x++)
+			if ((ts->span.chans[x]->flags & DAHDI_FLAG_OPEN)
+#ifdef CONFIG_DAHDI_NET
+					||
+			    (ts->span.chans[x]->flags & DAHDI_FLAG_NETDEV)
+#endif
+			    )
+				j++;
+		if (!j)
+			alarms |= DAHDI_ALARM_NOTOPEN;
+	}
+
+	/* Loss of Frame Alignment */
+	if (c & 0x20) {
+		if (ts->alarmcount >= alarmdebounce) {
+
+			/* Disable Slip Interrupts */
+			e = __t4_framer_in(wc, span, 0x17);
+			__t4_framer_out(wc, span, 0x17, (e|0x03));
+
+			alarms |= DAHDI_ALARM_RED;
+		} else {
+			if (unlikely(debug && !ts->alarmcount)) {
+				/* starting to debounce LOF/LFA */
+				dev_info(&wc->dev->dev, "opvxd115: LOF/LFA "
+					"detected on span %d but debouncing "
+					"for %d ms\n", span + 1,
+					alarmdebounce);
+			}
+			ts->alarmcount++;
+		}
+	} else
+		ts->alarmcount = 0;
+
+	/* Loss of Signal */
+	if (c & 0x80) {
+		if (ts->losalarmcount >= losalarmdebounce) {
+			/* Disable Slip Interrupts */
+			e = __t4_framer_in(wc, span, 0x17);
+			__t4_framer_out(wc, span, 0x17, (e|0x03));
+
+			alarms |= DAHDI_ALARM_RED;
+		} else {
+			if (unlikely(debug && !ts->losalarmcount)) {
+				/* starting to debounce LOS */
+				dev_info(&wc->dev->dev, "opvxd115: LOS "
+					"detected on span %d but debouncing "
+					"for %d ms\n",
+					span + 1, losalarmdebounce);
+			}
+			ts->losalarmcount++;
+		}
+	} else
+		ts->losalarmcount = 0;
+
+	/* Alarm Indication Signal */
+	if (c & 0x40) {
+		if (ts->aisalarmcount >= aisalarmdebounce)
+			alarms |= DAHDI_ALARM_BLUE;
+		else {
+			if (unlikely(debug && !ts->aisalarmcount)) {
+				/* starting to debounce AIS */
+				dev_info(&wc->dev->dev, "opvxd115: AIS "
+					"detected on span %d but debouncing "
+					"for %d ms\n",
+					span + 1, aisalarmdebounce);
+			}
+			ts->aisalarmcount++;
+		}
+	} else
+		ts->aisalarmcount = 0;
+
+#ifdef DAHDI_SPAN_OPS
+	/* Add detailed alarm status information to a red alarm state */
+	if (alarms & DAHDI_ALARM_RED) {
+		if (c & FRS0_LOS)
+			alarms |= DAHDI_ALARM_LOS;
+		if (c & FRS0_LFA)
+			alarms |= DAHDI_ALARM_LFA;
+		if (c & FRS0_LMFA)
+			alarms |= DAHDI_ALARM_LMFA;
+	}
+
+	if (unlikely(debug)) {
+		/* Check to ensure the xmit line isn't shorted */
+		if (unlikely(d & FRS1_XLS)) {
+			dev_info(&wc->dev->dev,
+				"Detected a possible hardware malfunction"\
+				" this card may need servicing\n");
+		}
+	}
+#endif
+
+	if (((!ts->span.alarms) && alarms) || 
+	    (ts->span.alarms && (!alarms))) 
+		set_bit(T4_CHECK_TIMING, &wc->checkflag);
+
+	/* Keep track of recovering */
+	if ((!alarms) && ts->span.alarms) 
+		ts->alarmtimer = DAHDI_ALARMSETTLE_TIME;
+	if (ts->alarmtimer)
+		alarms |= DAHDI_ALARM_RECOVER;
+
+	/* If receiving alarms, go into Yellow alarm state */
+	if (alarms && !(ts->spanflags & FLAG_SENDINGYELLOW)) {
+		/* We manually do yellow alarm to handle RECOVER and NOTOPEN, otherwise it's auto anyway */
+		unsigned char fmr4;
+		fmr4 = __t4_framer_in(wc, span, 0x20);
+		__t4_framer_out(wc, span, 0x20, fmr4 | 0x20);
+		dev_info(&wc->dev->dev, "Setting yellow alarm span %d\n",
+								span+1);
+		ts->spanflags |= FLAG_SENDINGYELLOW;
+	} else if ((!alarms) && (ts->spanflags & FLAG_SENDINGYELLOW)) {
+		unsigned char fmr4;
+		/* We manually do yellow alarm to handle RECOVER  */
+		fmr4 = __t4_framer_in(wc, span, 0x20);
+		__t4_framer_out(wc, span, 0x20, fmr4 & ~0x20);
+		dev_info(&wc->dev->dev, "Clearing yellow alarm span %d\n",
+								span+1);
+
+		/* Re-enable timing slip interrupts */
+		e = __t4_framer_in(wc, span, 0x17);
+
+		__t4_framer_out(wc, span, 0x17, (e & ~(0x03)));
+
+		ts->spanflags &= ~FLAG_SENDINGYELLOW;
+	}
+
+	/* Re-check the timing source when we enter/leave alarm, not withstanding
+	   yellow alarm */
+	if (c & 0x10) { /* receiving yellow (RAI) */
+		if (ts->yelalarmcount >= yelalarmdebounce)
+			alarms |= DAHDI_ALARM_YELLOW;
+		else {
+			if (unlikely(debug && !ts->yelalarmcount)) {
+				/* starting to debounce AIS */
+				dev_info(&wc->dev->dev, "wct%dxxp: yellow "
+					"(RAI) detected on span %d but "
+					"debouncing for %d ms\n",
+					wc->numspans, span + 1,
+					yelalarmdebounce);
+			}
+			ts->yelalarmcount++;
+		}
+	} else
+		ts->yelalarmcount = 0;
+
+	if (ts->span.mainttimer || ts->span.maintstat) 
+		alarms |= DAHDI_ALARM_LOOPBACK;
+	ts->span.alarms = alarms;
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	dahdi_alarm_notify(&ts->span);
+}
+
+static void t4_do_counters(struct t4 *wc)
+{
+	int span;
+	for (span=0;span<wc->numspans;span++) {
+		struct t4_span *ts = wc->tspans[span];
+		int docheck=0;
+
+		spin_lock(&wc->reglock);
+		if (ts->loopupcnt || ts->loopdowncnt || ts->alarmcount
+			|| ts->losalarmcount || ts->aisalarmcount
+			|| ts->yelalarmcount)
+			docheck++;
+
+		if (ts->alarmtimer) {
+			if (!--ts->alarmtimer) {
+				docheck++;
+				ts->span.alarms &= ~(DAHDI_ALARM_RECOVER);
+			}
+		}
+		spin_unlock(&wc->reglock);
+		if (docheck) {
+			t4_check_alarms(wc, span);
+			dahdi_alarm_notify(&ts->span);
+		}
+	}
+}
+
+static inline void __handle_leds(struct t4 *wc)
+{
+	int x;
+
+	wc->blinktimer++;
+	for (x=0;x<wc->numspans;x++) {
+		struct t4_span *ts = wc->tspans[x];
+		if (ts->span.flags & DAHDI_FLAG_RUNNING) {
+			if ((ts->span.alarms & (DAHDI_ALARM_RED | DAHDI_ALARM_BLUE)) || ts->losalarmcount) {
+#ifdef FANCY_ALARM
+				if (wc->blinktimer == (altab[wc->alarmpos] >> 1)) {
+					__t4_set_led(wc, x, WC_RED);
+				}
+				if (wc->blinktimer == 0xf) {
+					__t4_set_led(wc, x, WC_OFF);
+				}
+#else
+				if (wc->blinktimer == 160) {
+					__t4_set_led(wc, x, WC_RED);
+				} else if (wc->blinktimer == 480) {
+					__t4_set_led(wc, x, WC_OFF);
+				}
+#endif
+			} else if (ts->span.alarms & DAHDI_ALARM_YELLOW) {
+				/* Yellow Alarm */
+				__t4_set_led(wc, x, WC_YELLOW);
+			} else if (ts->span.mainttimer || ts->span.maintstat) {
+#ifdef FANCY_ALARM
+				if (wc->blinktimer == (altab[wc->alarmpos] >> 1)) {
+					__t4_set_led(wc, x, WC_GREEN);
+				}
+				if (wc->blinktimer == 0xf) {
+					__t4_set_led(wc, x, WC_OFF);
+				}
+#else
+				if (wc->blinktimer == 160) {
+					__t4_set_led(wc, x, WC_GREEN);
+				} else if (wc->blinktimer == 480) {
+					__t4_set_led(wc, x, WC_OFF);
+				}
+#endif
+			} else {
+				/* No Alarm */
+				__t4_set_led(wc, x, WC_GREEN);
+			}
+		}	else
+				__t4_set_led(wc, x, WC_OFF);
+
+	}
+#ifdef FANCY_ALARM
+	if (wc->blinktimer == 0xf) {
+		wc->blinktimer = -1;
+		wc->alarmpos++;
+		if (wc->alarmpos >= (sizeof(altab) / sizeof(altab[0])))
+			wc->alarmpos = 0;
+	}
+#else
+	if (wc->blinktimer == 480)
+		wc->blinktimer = 0;
+#endif
+}
+
+static inline void t4_framer_interrupt(struct t4 *wc, int span)
+{
+	unsigned char gis, isr0, isr1, isr2, isr3, isr4;
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+	/* Check interrupts for a given span */
+	unsigned char reg;
+#endif
+	int readsize = -1;
+	struct t4_span *ts = wc->tspans[span];
+	struct dahdi_chan *sigchan;
+	unsigned long flags;
+
+
+	/* 1st gen cards isn't used interrupts */
+	gis = t4_framer_in(wc, span, FRMR_GIS);
+	isr0 = (gis & FRMR_GIS_ISR0) ? t4_framer_in(wc, span, FRMR_ISR0) : 0;
+	isr1 = (gis & FRMR_GIS_ISR1) ? t4_framer_in(wc, span, FRMR_ISR1) : 0;
+	isr2 = (gis & FRMR_GIS_ISR2) ? t4_framer_in(wc, span, FRMR_ISR2) : 0;
+	isr3 = (gis & FRMR_GIS_ISR3) ? t4_framer_in(wc, span, FRMR_ISR3) : 0;
+	isr4 = (gis & FRMR_GIS_ISR4) ? t4_framer_in(wc, span, FRMR_ISR4) : 0;
+
+ 	if ((debug & DEBUG_FRAMER) && !(isr3 & ISR3_SEC)) {
+ 		dev_info(&wc->dev->dev, "gis: %02x, isr0: %02x, isr1: %02x, "\
+ 			"isr2: %02x, isr3: %08x, isr4: %02x, intcount=%u\n",
+ 			gis, isr0, isr1, isr2, isr3, isr4, wc->intcount);
+ 	}
+ 
+#if (defined(DAHDI_SPAN_OPS) || defined(DAHDI_SPAN_MODULE) )
+	/* Collect performance counters once per second */
+ 	if (isr3 & ISR3_SEC) {
+ 		ts->span.count.fe += t4_framer_in(wc, span, FECL_T);
+ 		ts->span.count.crc4 += t4_framer_in(wc, span, CEC1L_T);
+ 		ts->span.count.cv += t4_framer_in(wc, span, CVCL_T);
+ 		ts->span.count.ebit += t4_framer_in(wc, span, EBCL_T);
+ 		ts->span.count.be += t4_framer_in(wc, span, BECL_T);
+ 		ts->span.count.prbs = t4_framer_in(wc, span, FRS1_T);
+ 	}
+ 
+	/* Collect errored second counter once per second */
+ 	if (isr3 & ISR3_ES) {
+ 		ts->span.count.errsec += 1;
+ 	}
+ 
+ 	if (isr3 & 0x08) {
+ 		reg = t4_framer_in(wc, span, FRS1_T);
+		dev_info(&wc->dev->dev, "FRS1: %d\n", reg);
+ 		if (reg & LLBDD) {
+ 			dev_info(&wc->dev->dev, "Line loop-back activation "\
+ 					"signal detected with status: %01d "\
+ 					"for span %d\n", reg & LLBAD, span+1);
+ 		}
+ 	}
+#endif
+
+	if (isr0)
+		t4_check_sigbits(wc, span);
+
+	if (ts->spantype == TYPE_E1) {
+		/* E1 checks */
+		if ((isr3 & 0x38) || isr2 || isr1)
+			t4_check_alarms(wc, span);
+	} else {
+		/* T1 checks */
+		if (isr2 || (isr3 & 0x08))
+			t4_check_alarms(wc, span);
+	}
+	if (!ts->span.alarms) {
+		if ((isr3 & 0x3) || (isr4 & 0xc0))
+			ts->span.timingslips++;
+
+		if (debug & DEBUG_MAIN) {
+			if (isr3 & 0x02)
+				dev_notice(&wc->dev->dev, "opvxd115: RECEIVE "
+					"slip NEGATIVE on span %d\n",
+					span + 1);
+			if (isr3 & 0x01)
+				dev_notice(&wc->dev->dev, "opvxd115: RECEIVE "
+					"slip POSITIVE on span %d\n",
+					span + 1);
+			if (isr4 & 0x80)
+				dev_notice(&wc->dev->dev, "opvxd115: TRANSMIT "
+					"slip POSITIVE on span %d\n",
+					span + 1);
+			if (isr4 & 0x40)
+				dev_notice(&wc->dev->dev, "opvxd115: TRANSMIT "
+					"slip NEGATIVE on span %d\n",
+					span + 1);
+		}
+	} else
+		ts->span.timingslips = 0;
+
+	spin_lock_irqsave(&wc->reglock, flags);
+	/* HDLC controller checks - receive side */
+	if (!ts->sigchan) {
+		spin_unlock_irqrestore(&wc->reglock, flags);
+		return;
+	}
+
+	sigchan = ts->sigchan;
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	if (isr0 & FRMR_ISR0_RME) {
+		readsize = (t4_framer_in(wc, span, FRMR_RBCH) << 8) | t4_framer_in(wc, span, FRMR_RBCL);
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "Received data length is %d "
+				"(%d)\n", readsize,
+				readsize & FRMR_RBCL_MAX_SIZE);
+		/* RPF isn't set on last part of frame */
+		if ((readsize > 0) && ((readsize &= FRMR_RBCL_MAX_SIZE) == 0))
+			readsize = FRMR_RBCL_MAX_SIZE + 1;
+	} else if (isr0 & FRMR_ISR0_RPF)
+		readsize = FRMR_RBCL_MAX_SIZE + 1;
+
+	if (readsize > 0) {
+		int i;
+		unsigned char readbuf[FRMR_RBCL_MAX_SIZE + 1];
+
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "Framer %d: Got RPF/RME! "
+				"readsize is %d\n", sigchan->span->offset,
+				readsize);
+
+		for (i = 0; i < readsize; i++)
+			readbuf[i] = t4_framer_in(wc, span, FRMR_RXFIFO);
+
+		/* Tell the framer to clear the RFIFO */
+		t4_framer_cmd_wait(wc, span, FRMR_CMDR_RMC);
+
+		if (debug & DEBUG_FRAMER) {
+			dev_notice(&wc->dev->dev, "RX(");
+			for (i = 0; i < readsize; i++)
+				dev_notice(&wc->dev->dev, "%s%02x",
+					(i ? " " : ""), readbuf[i]);
+			dev_notice(&wc->dev->dev, ")\n");
+		}
+
+		if (isr0 & FRMR_ISR0_RME) {
+			/* Do checks for HDLC problems */
+			unsigned char rsis = readbuf[readsize-1];
+#if 0
+			unsigned int olddebug = debug;
+#endif
+			unsigned char rsis_reg = t4_framer_in(wc, span, FRMR_RSIS);
+
+#if 0
+			if ((rsis != 0xA2) || (rsis != rsis_reg))
+				debug |= DEBUG_FRAMER;
+#endif
+
+			++ts->frames_in;
+			if ((debug & DEBUG_FRAMER) && !(ts->frames_in & 0x0f))
+				dev_notice(&wc->dev->dev, "Received %d frames "
+					"on span %d\n", ts->frames_in, span);
+			if (debug & DEBUG_FRAMER)
+				dev_notice(&wc->dev->dev, "Received HDLC frame"
+					" %d.  RSIS = 0x%x (%x)\n",
+					ts->frames_in, rsis, rsis_reg);
+			if (!(rsis & FRMR_RSIS_CRC16)) {
+				if (debug & DEBUG_FRAMER)
+					dev_notice(&wc->dev->dev, "CRC check "
+							"failed %d\n", span);
+				dahdi_hdlc_abort(sigchan, DAHDI_EVENT_BADFCS);
+			} else if (rsis & FRMR_RSIS_RAB) {
+				if (debug & DEBUG_FRAMER)
+					dev_notice(&wc->dev->dev, "ABORT of "
+						"current frame due to "
+						"overflow %d\n", span);
+				dahdi_hdlc_abort(sigchan, DAHDI_EVENT_ABORT);
+			} else if (rsis & FRMR_RSIS_RDO) {
+				if (debug & DEBUG_FRAMER)
+					dev_notice(&wc->dev->dev, "HDLC "
+						"overflow occured %d\n",
+						span);
+				dahdi_hdlc_abort(sigchan, DAHDI_EVENT_OVERRUN);
+			} else if (!(rsis & FRMR_RSIS_VFR)) {
+				if (debug & DEBUG_FRAMER)
+					dev_notice(&wc->dev->dev, "Valid Frame"
+						" check failed on span %d\n",
+						span);
+				dahdi_hdlc_abort(sigchan, DAHDI_EVENT_ABORT);
+			} else {
+				dahdi_hdlc_putbuf(sigchan, readbuf, readsize - 1);
+				dahdi_hdlc_finish(sigchan);
+				if (debug & DEBUG_FRAMER)
+					dev_notice(&wc->dev->dev, "Received "
+						"valid HDLC frame on span %d"
+						"\n", span);
+			}
+#if 0
+			debug = olddebug;
+#endif
+		} else if (isr0 & FRMR_ISR0_RPF)
+			dahdi_hdlc_putbuf(sigchan, readbuf, readsize);
+	}
+
+	/* Transmit side */
+	if (isr1 & FRMR_ISR1_XDU) {
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "XDU: Resetting signal "
+					"controller!\n");
+		t4_framer_cmd_wait(wc, span, FRMR_CMDR_SRES);
+	} else if (isr1 & FRMR_ISR1_XPR) {
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "Sigchan %d is %p\n",
+					sigchan->chanpos, sigchan);
+
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "Framer %d: Got XPR!\n",
+					sigchan->span->offset);
+		t4_hdlc_xmit_fifo(wc, span, ts);
+	}
+
+	if (isr1 & FRMR_ISR1_ALLS) {
+		if (debug & DEBUG_FRAMER)
+			dev_notice(&wc->dev->dev, "ALLS received\n");
+	}
+}
+
+#ifdef SUPPORT_GEN1
+DAHDI_IRQ_HANDLER(t4_interrupt)
+{
+	struct t4 *wc = dev_id;
+	unsigned long flags;
+	int x;
+	
+	unsigned int status;
+	unsigned int status2;
+
+#if 0
+	if (wc->intcount < 20)
+		dev_notice(&wc->dev->dev, "Pre-interrupt\n");
+#endif
+	
+	/* Make sure it's really for us */
+	status = __t4_pci_in(wc, WC_INTR);
+
+	/* Process framer interrupts */
+	status2 = t4_framer_in(wc, 0, FRMR_CIS);
+	if (status2 & 0x0f) {
+		for (x = 0; x < wc->numspans; ++x) {
+			if (status2 & (1 << x))
+				t4_framer_interrupt(wc, x);
+		}
+	}
+
+	/* Ignore if it's not for us */
+	if (!status)
+		return IRQ_NONE;
+
+	__t4_pci_out(wc, WC_INTR, 0);
+
+	if (!wc->spansstarted) {
+		dev_notice(&wc->dev->dev, "Not prepped yet!\n");
+		return IRQ_NONE;
+	}
+
+	wc->intcount++;
+#if 0
+	if (wc->intcount < 20)
+		dev_notice(&wc->dev->dev, "Got interrupt, status = %08x\n",
+				status);
+#endif		
+
+	if (status & 0x3) {
+		t4_receiveprep(wc, status);
+		t4_transmitprep(wc, status);
+	}
+	
+#if 0
+	if ((wc->intcount < 10) || !(wc->intcount % 1000)) {
+		status2 = t4_framer_in(wc, 0, FRMR_CIS);
+		dev_notice(&wc->dev->dev, "Status2: %04x\n", status2);
+		for (x = 0;x<wc->numspans;x++) {
+			status2 = t4_framer_in(wc, x, FRMR_FRS0);
+			dev_notice(&wc->dev->dev, "FRS0/%d: %04x\n", x,
+					status2);
+		}
+	}
+#endif
+	t4_do_counters(wc);
+
+	x = wc->intcount & 15 /* 63 */;
+	switch(x) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		t4_check_sigbits(wc, x);
+		break;
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		t4_check_alarms(wc, x - 4);
+		break;
+	}
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	__handle_leds(wc);
+
+	if (test_bit(T4_CHECK_TIMING, &wc->checkflag))
+		__t4_set_timing_source_auto(wc);
+
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	return IRQ_RETVAL(1);
+}
+#endif
+
+static int t4_allocate_buffers(struct t4 *wc, int numbufs, volatile unsigned int **oldalloc, dma_addr_t *oldwritedma)
+{
+	volatile unsigned int *alloc;
+	dma_addr_t writedma;
+
+	alloc =
+		/* 32 channels, Double-buffer, Read/Write, 4 spans */
+		(unsigned int *)pci_alloc_consistent(wc->dev, numbufs * T4_BASE_SIZE * 2, &writedma);
+
+	if (!alloc) {
+		dev_notice(&wc->dev->dev, "wct%dxxp: Unable to allocate "
+				"DMA-able memory\n", wc->numspans);
+		return -ENOMEM;
+	}
+
+	if (oldwritedma)
+		*oldwritedma = wc->writedma;
+	if (oldalloc)
+		*oldalloc = wc->writechunk;
+
+	wc->writechunk = alloc;
+	wc->writedma = writedma;
+
+	/* Read is after the whole write piece (in words) */
+	wc->readchunk = wc->writechunk + (T4_BASE_SIZE * numbufs) / 4;
+	
+	/* Same thing but in bytes...  */
+	wc->readdma = wc->writedma + (T4_BASE_SIZE * numbufs);
+
+	wc->numbufs = numbufs;
+	
+	/* Initialize Write/Buffers to all blank data */
+	memset((void *)wc->writechunk,0x00, T4_BASE_SIZE * numbufs);
+	memset((void *)wc->readchunk,0xff, T4_BASE_SIZE * numbufs);
+
+	dev_notice(&wc->dev->dev, "DMA memory base of size %d at %p.  Read: "
+		"%p and Write %p\n", numbufs * T4_BASE_SIZE * 2,
+		wc->writechunk, wc->readchunk, wc->writechunk);
+
+	return 0;
+}
+
+static void t4_increase_latency(struct t4 *wc, int newlatency)
+{
+	unsigned long flags;
+	volatile unsigned int *oldalloc;
+	dma_addr_t oldaddr;
+	int oldbufs;
+
+	spin_lock_irqsave(&wc->reglock, flags);
+
+	__t4_pci_out(wc, WC_DMACTRL, 0x00000000);
+	/* Acknowledge any pending interrupts */
+	__t4_pci_out(wc, WC_INTR, 0x00000000);
+
+	__t4_pci_in(wc, WC_VERSION);
+
+	oldbufs = wc->numbufs;
+
+	if (t4_allocate_buffers(wc, newlatency, &oldalloc, &oldaddr)) {
+		dev_info(&wc->dev->dev, "Error allocating latency buffers for "
+				"latency of %d\n", newlatency);
+		__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+		spin_unlock_irqrestore(&wc->reglock, flags);
+		return;
+	}
+
+	__t4_pci_out(wc, WC_RDADDR, wc->readdma);
+	__t4_pci_out(wc, WC_WRADDR, wc->writedma);
+
+	__t4_pci_in(wc, WC_VERSION);
+
+	__t4_pci_out(wc, 5, (ms_per_irq << 16) | newlatency);
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+
+	__t4_pci_in(wc, WC_VERSION);
+
+	wc->rxident = 0;
+	wc->lastindex = 0;
+
+	spin_unlock_irqrestore(&wc->reglock, flags);
+
+	pci_free_consistent(wc->dev, T4_BASE_SIZE * oldbufs * 2, (void *)oldalloc, oldaddr);
+
+	dev_info(&wc->dev->dev, "Increased latency to %d\n", newlatency);
+
+}
+
+static void t4_isr_bh(unsigned long data)
+{
+	struct t4 *wc = (struct t4 *)data;
+
+	if (test_bit(T4_CHANGE_LATENCY, &wc->checkflag)) {
+		if (wc->needed_latency != wc->numbufs) {
+			t4_increase_latency(wc, wc->needed_latency);
+			clear_bit(T4_CHANGE_LATENCY, &wc->checkflag);
+		}
+	}
+#ifdef VPM_SUPPORT
+	if (wc->vpm) {
+		if (test_and_clear_bit(T4_CHECK_VPM, &wc->checkflag)) {
+			if (wc->vpm450m) {
+				/* How stupid is it that the octasic can't generate an
+				   interrupt when there's a tone, in spite of what their
+				   documentation says? */
+				t4_check_vpm450(wc);
+			} else
+				t4_check_vpm400(wc, wc->vpm400checkstatus);
+		}
+	}
+#endif
+}
+
+DAHDI_IRQ_HANDLER(t4_interrupt_gen2)
+{
+	struct t4 *wc = dev_id;
+	unsigned int status;
+	unsigned char rxident, expected;
+	
+	/* Check this first in case we get a spurious interrupt */
+	if (unlikely(test_bit(T4_STOP_DMA, &wc->checkflag))) {
+		/* Stop DMA cleanly if requested */
+		wc->dmactrl = 0x0;
+		t4_pci_out(wc, WC_DMACTRL, 0x00000000);
+		/* Acknowledge any pending interrupts */
+		t4_pci_out(wc, WC_INTR, 0x00000000);
+		spin_lock(&wc->reglock);
+		__t4_set_sclk_src(wc, WC_SELF, 0, 0);
+		spin_unlock(&wc->reglock);
+		return IRQ_RETVAL(1);
+	}
+
+	/* Make sure it's really for us */
+	status = __t4_pci_in(wc, WC_INTR);
+
+	/* Ignore if it's not for us */
+	if (!(status & 0x7)) {
+		return IRQ_NONE;
+	}
+
+#ifdef ENABLE_WORKQUEUES
+	__t4_pci_out(wc, WC_INTR, status & 0x00000008);
+#endif
+
+	if (unlikely(!wc->spansstarted)) {
+		dev_info(&wc->dev->dev, "Not prepped yet!\n");
+		return IRQ_NONE;
+	}
+
+	wc->intcount++;
+
+	if ((wc->flags & FLAG_5THGEN) && (status & 0x2)) {
+		rxident = (status >> 16) & 0x7f;
+		expected = (wc->rxident + ms_per_irq) % 128;
+	
+		if ((rxident != expected) && !test_bit(T4_IGNORE_LATENCY, &wc->checkflag)) {
+			int needed_latency;
+			int smallest_max;
+
+			if (debug & DEBUG_MAIN)
+				dev_warn(&wc->dev->dev, "Missed interrupt.  "
+					"Expected ident of %d and got ident "
+					"of %d\n", expected, rxident);
+
+			if (test_bit(T4_IGNORE_LATENCY, &wc->checkflag)) {
+				dev_info(&wc->dev->dev,
+					"Should have ignored latency\n");
+			}
+			if (rxident > wc->rxident) {
+				needed_latency = rxident - wc->rxident;
+			} else {
+				needed_latency = (128 - wc->rxident) + rxident;
+			}
+
+			needed_latency += 1;
+
+			smallest_max = (max_latency >= GEN5_MAX_LATENCY) ? GEN5_MAX_LATENCY : max_latency;
+
+			if (needed_latency > smallest_max) {
+				dev_info(&wc->dev->dev, "Truncating latency "
+					"request to %d instead of %d\n",
+					smallest_max, needed_latency);
+				needed_latency = smallest_max;
+			}
+
+			if (needed_latency > wc->numbufs) {
+				int x;
+
+				dev_info(&wc->dev->dev, "Need to increase "
+					"latency.  Estimated latency should "
+					"be %d\n", needed_latency);
+				for (x = 0; x < wc->numspans; x++)
+					wc->ddev->irqmisses++;
+				wc->needed_latency = needed_latency;
+				__t4_pci_out(wc, WC_DMACTRL, 0x00000000);
+				set_bit(T4_CHANGE_LATENCY, &wc->checkflag);
+				goto out;
+			}
+		}
+	
+		wc->rxident = rxident;
+	}
+
+	if (unlikely((wc->intcount < 20)))
+
+		dev_info(&wc->dev->dev, "2G: Got interrupt, status = %08x, "
+			"CIS = %04x\n", status, t4_framer_in(wc, 0, FRMR_CIS));
+
+	if (likely(status & 0x2)) {
+#ifdef ENABLE_WORKQUEUES
+		int cpus = num_online_cpus();
+		atomic_set(&wc->worklist, wc->numspans);
+		if (wc->tspans[0]->span.flags & DAHDI_FLAG_RUNNING)
+			t4_queue_work(wc->workq, &wc->tspans[0]->swork, 0);
+		else
+			atomic_dec(&wc->worklist);
+#else
+#if 1
+		unsigned int reg5 = __t4_pci_in(wc, 5);
+		if (wc->intcount < 20) {
+
+			dev_info(&wc->dev->dev, "Reg 5 is %08x\n", reg5);
+		}
+#endif
+
+		if (wc->flags & FLAG_5THGEN) {
+			unsigned int current_index = (reg5 >> 8) & 0x7f;
+
+			while (((wc->lastindex + 1) % wc->numbufs) != current_index) {
+				wc->lastindex = (wc->lastindex + 1) % wc->numbufs;
+				setup_chunks(wc, wc->lastindex);
+				t4_prep_gen2(wc);
+			}
+		} else {
+			t4_prep_gen2(wc);
+		}
+
+#endif
+		t4_do_counters(wc);
+		spin_lock(&wc->reglock);
+		__handle_leds(wc);
+		spin_unlock(&wc->reglock);
+
+	}
+
+	if (unlikely(status & 0x1)) {
+		unsigned char cis;
+
+		cis = t4_framer_in(wc, 0, FRMR_CIS);
+		if (cis & FRMR_CIS_GIS1)
+			t4_framer_interrupt(wc, 0);
+		if (cis & FRMR_CIS_GIS2)
+			t4_framer_interrupt(wc, 1);
+		if (cis & FRMR_CIS_GIS3)
+			t4_framer_interrupt(wc, 2);
+		if (cis & FRMR_CIS_GIS4)
+			t4_framer_interrupt(wc, 3);
+	}
+
+	if (wc->vpm && vpmdtmfsupport) {
+		if (wc->vpm450m) {
+			/* How stupid is it that the octasic can't generate an
+			   interrupt when there's a tone, in spite of what their
+			   documentation says? */
+			if (!(wc->intcount & 0xf)) {
+				set_bit(T4_CHECK_VPM, &wc->checkflag);
+			}
+		} else if ((status & 0xff00) != 0xff00) {
+			wc->vpm400checkstatus = (status & 0xff00) >> 8;
+			set_bit(T4_CHECK_VPM, &wc->checkflag);
+		}
+	}
+
+	spin_lock(&wc->reglock);
+
+	if (unlikely(test_bit(T4_CHECK_TIMING, &wc->checkflag))) {
+		__t4_set_timing_source_auto(wc);
+	}
+
+	spin_unlock(&wc->reglock);
+
+out:
+	if (unlikely(test_bit(T4_CHANGE_LATENCY, &wc->checkflag) || test_bit(T4_CHECK_VPM, &wc->checkflag)))
+		tasklet_schedule(&wc->t4_tlet);
+
+#ifndef ENABLE_WORKQUEUES
+	__t4_pci_out(wc, WC_INTR, 0);
+#endif	
+
+	return IRQ_RETVAL(1);
+}
+
+#ifdef SUPPORT_GEN1
+static int t4_reset_dma(struct t4 *wc)
+{
+	/* Turn off DMA and such */
+	wc->dmactrl = 0x0;
+	t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	t4_pci_out(wc, WC_COUNT, 0);
+	t4_pci_out(wc, WC_RDADDR, 0);
+	t4_pci_out(wc, WC_WRADDR, 0);
+	t4_pci_out(wc, WC_INTR, 0);
+	/* Turn it all back on */
+	t4_pci_out(wc, WC_RDADDR, wc->readdma);
+	t4_pci_out(wc, WC_WRADDR, wc->writedma);
+	t4_pci_out(wc, WC_COUNT, ((DAHDI_MAX_CHUNKSIZE * 2 * 32 - 1) << 18) | ((DAHDI_MAX_CHUNKSIZE * 2 * 32 - 1) << 2));
+	t4_pci_out(wc, WC_INTR, 0);
+#ifdef VPM_SUPPORT
+	wc->dmactrl = 0xc0000000 | (1 << 29) | wc->vpm;
+#else	
+	wc->dmactrl = 0xc0000000 | (1 << 29);
+#endif
+	if (noburst)
+		wc->dmactrl |= (1 << 26);
+	t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	return 0;
+}
+#endif
+
+#ifdef VPM_SUPPORT
+static void t4_vpm_set_dtmf_threshold(struct t4 *wc, unsigned int threshold)
+{
+	unsigned int x;
+
+	for (x = 0; x < 8; x++) {
+		t4_vpm_out(wc, x, 0xC4, (threshold >> 8) & 0xFF);
+		t4_vpm_out(wc, x, 0xC5, (threshold & 0xFF));
+	}
+	dev_info(&wc->dev->dev, "VPM: DTMF threshold set to %d\n", threshold);
+}
+
+static unsigned int t4_vpm_mask(int chip)
+{
+	unsigned int mask=0;
+	switch(vpmspans) {
+	case 4:
+		mask = 0x55555555 << (chip >> 2);
+		break;
+	case 2:
+		mask = 0x11111111 << (chip >> 1);
+		break;
+	case 1:
+		mask = 0x01010101 << chip;
+		break;
+	}
+	return mask;
+}
+
+static int t4_vpm_spanno(int chip)
+{
+	int spanno = 0;
+	switch(vpmspans) {
+	case 4:
+		spanno = chip & 0x3;
+		break;
+	case 2:
+		spanno = chip & 0x1;
+		break;
+	/* Case 1 is implicit */
+	}
+	return spanno;
+}
+
+static int t4_vpm_echotail(void)
+{
+	int echotail = 0x01ff;
+	switch(vpmspans) {
+	case 4:
+		echotail = 0x007f;
+		break;
+	case 2:
+		echotail = 0x00ff;
+		break;
+	/* Case 1 is implicit */
+	}
+	return echotail;
+}
+
+static void t4_vpm450_init(struct t4 *wc)
+{
+	unsigned int check1, check2;
+	int laws[1] = { 0, };
+	int x;
+	unsigned int vpm_capacity;
+	struct firmware embedded_firmware;
+	const struct firmware *firmware = &embedded_firmware;
+#if !defined(HOTPLUG_FIRMWARE)
+	extern void _binary_dahdi_fw_oct6114_032_bin_size;
+	extern void _binary_dahdi_fw_oct6114_064_bin_size;
+	extern void _binary_dahdi_fw_oct6114_128_bin_size;
+	extern u8 _binary_dahdi_fw_oct6114_032_bin_start[];
+	extern u8 _binary_dahdi_fw_oct6114_064_bin_start[];
+	extern u8 _binary_dahdi_fw_oct6114_128_bin_start[];
+#else
+	static const char oct032_firmware[] = "dahdi-fw-oct6114-032.bin";
+	static const char oct064_firmware[] = "dahdi-fw-oct6114-064.bin";
+	static const char oct128_firmware[] = "dahdi-fw-oct6114-128.bin";
+#endif
+
+	if (!vpmsupport) {
+		dev_info(&wc->dev->dev, "VPM450: Support Disabled\n");
+		return;
+	}
+
+	/* Turn on GPIO/DATA mux if supported */
+	t4_gpio_setdir(wc, (1 << 24), (1 << 24));
+	__t4_raw_oct_out(wc, 0x000a, 0x5678);
+	__t4_raw_oct_out(wc, 0x0004, 0x1234);
+	check1 = __t4_raw_oct_in(wc, 0x0004);
+	check2 = __t4_raw_oct_in(wc, 0x000a);
+	if (debug)
+		dev_notice(&wc->dev->dev, "OCT Result: %04x/%04x\n",
+			__t4_raw_oct_in(wc, 0x0004),
+			__t4_raw_oct_in(wc, 0x000a));
+	if (__t4_raw_oct_in(wc, 0x0004) != 0x1234) {
+		dev_notice(&wc->dev->dev, "VPM450: Not Present\n");
+		return;
+	}
+
+	/* Setup alaw vs ulaw rules */
+	for (x = 0;x < wc->numspans; x++) {
+		if (wc->tspans[x]->span.channels > 24)
+			laws[x] = 1;
+	}
+
+	switch ((vpm_capacity = get_vpm450m_capacity(wc))) {
+	case 32:
+#if defined(HOTPLUG_FIRMWARE)
+		if ((request_firmware(&firmware, oct032_firmware, &wc->dev->dev) != 0) ||
+		    !firmware) {
+			dev_notice(&wc->dev->dev, "VPM450: firmware %s not "
+				"available from userspace\n", oct032_firmware);
+			return;
+		}
+#else
+		embedded_firmware.data = _binary_dahdi_fw_oct6114_032_bin_start;
+		/* Yes... this is weird. objcopy gives us a symbol containing
+		   the size of the firmware, not a pointer a variable containing
+		   the size. The only way we can get the value of the symbol
+		   is to take its address, so we define it as a pointer and
+		   then cast that value to the proper type.
+	      */
+		embedded_firmware.size = (size_t) &_binary_dahdi_fw_oct6114_032_bin_size;
+#endif
+		break;
+	case 64:
+#if defined(HOTPLUG_FIRMWARE)
+		if ((request_firmware(&firmware, oct064_firmware, &wc->dev->dev) != 0) ||
+		    !firmware) {
+			dev_notice(&wc->dev->dev, "VPM450: firmware %s not "
+				"available from userspace\n", oct064_firmware);
+			return;
+		}
+#else
+		embedded_firmware.data = _binary_dahdi_fw_oct6114_064_bin_start;
+		/* Yes... this is weird. objcopy gives us a symbol containing
+		   the size of the firmware, not a pointer a variable containing
+		   the size. The only way we can get the value of the symbol
+		   is to take its address, so we define it as a pointer and
+		   then cast that value to the proper type.
+	      */
+		embedded_firmware.size = (size_t) &_binary_dahdi_fw_oct6114_064_bin_size;
+#endif
+		break;
+	case 128:
+#if defined(HOTPLUG_FIRMWARE)
+		if ((request_firmware(&firmware, oct128_firmware, &wc->dev->dev) != 0) ||
+		    !firmware) {
+			dev_notice(&wc->dev->dev, "VPM450: firmware %s not "
+				"available from userspace\n", oct128_firmware);
+			return;
+		}
+#else
+		embedded_firmware.data = _binary_dahdi_fw_oct6114_128_bin_start;
+		/* Yes... this is weird. objcopy gives us a symbol containing
+		   the size of the firmware, not a pointer a variable containing
+		   the size. The only way we can get the value of the symbol
+		   is to take its address, so we define it as a pointer and
+		   then cast that value to the proper type.
+		*/
+		embedded_firmware.size = (size_t) &_binary_dahdi_fw_oct6114_128_bin_size;
+#endif
+		break;
+	default:
+		dev_notice(&wc->dev->dev, "Unsupported channel capacity found "
+				"on VPM module (%d).\n", vpm_capacity);
+		return;
+	}
+
+	if (!(wc->vpm450m = init_vpm450m(wc, laws, wc->numspans, firmware))) {
+		dev_notice(&wc->dev->dev, "VPM450: Failed to initialize\n");
+		if (firmware != &embedded_firmware)
+			release_firmware(firmware);
+		return;
+	}
+
+	if (firmware != &embedded_firmware)
+		release_firmware(firmware);
+
+	if (vpmdtmfsupport == -1) {
+		dev_notice(&wc->dev->dev, "VPM450: hardware DTMF disabled.\n");
+		vpmdtmfsupport = 0;
+	}
+
+	wc->vpm = T4_VPM_PRESENT;
+	dev_info(&wc->dev->dev, "VPM450: Present and operational servicing %d "
+			"span(s)\n", wc->numspans);
+		
+}
+
+static void t4_vpm400_init(struct t4 *wc)
+{
+	unsigned char reg;
+	unsigned int mask;
+	unsigned int ver;
+	unsigned int i, x, y, gen2vpm=0;
+
+	if (!vpmsupport) {
+		dev_info(&wc->dev->dev, "VPM400: Support Disabled\n");
+		return;
+	}
+
+	switch(vpmspans) {
+	case 4:
+	case 2:
+	case 1:
+		break;
+	default:
+		dev_notice(&wc->dev->dev, "VPM400: %d is not a valid vpmspans "
+				"value, using 4\n", vpmspans);
+		vpmspans = 1;
+	}
+
+	for (x=0;x<8;x++) {
+		int spanno = t4_vpm_spanno(x);
+		struct t4_span *ts = wc->tspans[spanno];
+		int echotail = t4_vpm_echotail();
+
+		ver = t4_vpm_in(wc, x, 0x1a0); /* revision */
+		if ((ver != 0x26) && (ver != 0x33)) {
+			if (x)
+				dev_notice(&wc->dev->dev,
+					"VPM400: Inoperable\n");
+			return;
+		}
+		if (ver == 0x33) {
+			if (x && !gen2vpm) {
+				dev_notice(&wc->dev->dev,
+					"VPM400: Inconsistent\n");
+				return;
+			}
+			ts->spanflags |= FLAG_VPM2GEN;
+			gen2vpm++;
+		} else if (gen2vpm) {
+			dev_notice(&wc->dev->dev,
+				"VPM400: Inconsistent\n");
+			return;
+		}
+
+
+		/* Setup GPIO's */
+		for (y=0;y<4;y++) {
+			t4_vpm_out(wc, x, 0x1a8 + y, 0x00); /* GPIO out */
+			t4_vpm_out(wc, x, 0x1ac + y, 0x00); /* GPIO dir */
+			t4_vpm_out(wc, x, 0x1b0 + y, 0x00); /* GPIO sel */
+		}
+
+		/* Setup TDM path - sets fsync and tdm_clk as inputs */
+		reg = t4_vpm_in(wc, x, 0x1a3); /* misc_con */
+		t4_vpm_out(wc, x, 0x1a3, reg & ~2);
+
+		/* Setup timeslots */
+		t4_vpm_out(wc, x, 0x02f, 0x20 | (spanno << 3)); 
+
+		/* Setup Echo length (128 taps) */
+		t4_vpm_out(wc, x, 0x022, (echotail >> 8));
+		t4_vpm_out(wc, x, 0x023, (echotail & 0xff));
+		
+		/* Setup the tdm channel masks for all chips*/
+		mask = t4_vpm_mask(x);
+		for (i = 0; i < 4; i++)
+			t4_vpm_out(wc, x, 0x30 + i, (mask >> (i << 3)) & 0xff);
+
+		/* Setup convergence rate */
+		reg = t4_vpm_in(wc,x,0x20);
+		reg &= 0xE0;
+		if (ts->spantype == TYPE_E1) {
+			if (x < vpmspans)
+				dev_info(&wc->dev->dev, "VPM400: Span %d "
+						"A-law mode\n", spanno);
+			reg |= 0x01;
+		} else {
+			if (x < vpmspans)
+				dev_info(&wc->dev->dev, "VPM400: Span %d "
+						"U-law mode\n", spanno);
+			reg &= ~0x01;
+		}
+		t4_vpm_out(wc,x,0x20,(reg | 0x20));
+		
+		/* Initialize echo cans */
+		for (i = 0 ; i < MAX_TDM_CHAN; i++) {
+			if (mask & (0x00000001 << i))
+				t4_vpm_out(wc,x,i,0x00);
+		}
+
+		wait_a_little();
+
+		/* Put in bypass mode */
+		for (i = 0 ; i < MAX_TDM_CHAN ; i++) {
+			if (mask & (0x00000001 << i)) {
+				t4_vpm_out(wc,x,i,0x01);
+			}
+		}
+
+		/* Enable bypass */
+		for (i = 0 ; i < MAX_TDM_CHAN ; i++) {
+			if (mask & (0x00000001 << i))
+				t4_vpm_out(wc,x,0x78 + i,0x01);
+		}
+      
+		/* set DTMF detection threshold */
+		t4_vpm_set_dtmf_threshold(wc, dtmfthreshold);
+
+		/* Enable DTMF detectors (always DTMF detect all spans) */
+		for (i = 0; i < MAX_DTMF_DET; i++) {
+			t4_vpm_out(wc, x, 0x98 + i, 0x40 | (i * 2) | ((x < 4) ? 0 : 1));
+		}
+		for (i = 0x34; i < 0x38; i++)
+			t4_vpm_out(wc, x, i, 0x00);
+		for (i = 0x3C; i < 0x40; i++)
+			t4_vpm_out(wc, x, i, 0x00);
+
+		for (i = 0x48; i < 0x4B; i++)
+			t4_vpm_out(wc, x, i, 0x00);
+		for (i = 0x50; i < 0x53; i++)
+			t4_vpm_out(wc, x, i, 0x00);
+		for (i = 0xB8; i < 0xBE; i++)
+			t4_vpm_out(wc, x, i, 0xFF);
+		if (gen2vpm) {
+			for (i = 0xBE; i < 0xC0; i++)
+				t4_vpm_out(wc, x, i, 0xFF);
+		} else {
+			for (i = 0xBE; i < 0xC0; i++)
+				t4_vpm_out(wc, x, i, 0x00);
+		}
+		for (i = 0xC0; i < 0xC4; i++)
+			t4_vpm_out(wc, x, i, (x < 4) ? 0x55 : 0xAA);
+
+	} 
+	if (vpmdtmfsupport == -1) {
+		dev_info(&wc->dev->dev, "VPM400: hardware DTMF enabled.\n");
+		vpmdtmfsupport = 0;
+	}
+	dev_info(&wc->dev->dev, "VPM400%s: Present and operational servicing "
+		"%d span(s)\n", (gen2vpm ? " (2nd Gen)" : ""), wc->numspans);
+	wc->vpm = T4_VPM_PRESENT;
+}
+
+#endif
+
+static void t4_tsi_reset(struct t4 *wc) 
+{
+	int x;
+	for (x=0;x<128;x++) {
+		wc->dmactrl &= ~0x00007fff;
+		wc->dmactrl |= (0x00004000 | (x << 7));
+		t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	}
+	wc->dmactrl &= ~0x00007fff;
+	t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+}
+
+/* Note that channels here start from 1 */
+static void t4_tsi_assign(struct t4 *wc, int fromspan, int fromchan, int tospan, int tochan)
+{
+	unsigned long flags;
+	int fromts, tots;
+
+	fromts = (fromspan << 5) |(fromchan);
+	tots = (tospan << 5) | (tochan);
+
+	if (!wc->t1e1) {
+		fromts += 4;
+		tots += 4;
+	}
+	spin_lock_irqsave(&wc->reglock, flags);
+	wc->dmactrl &= ~0x00007fff;
+	wc->dmactrl |= (0x00004000 | (tots << 7) | (fromts));
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	wc->dmactrl &= ~0x00007fff;
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+
+static void t4_tsi_unassign(struct t4 *wc, int tospan, int tochan)
+{
+	unsigned long flags;
+	int tots;
+
+	tots = (tospan << 5) | (tochan);
+
+	if (!wc->t1e1) 
+		tots += 4;
+	spin_lock_irqsave(&wc->reglock, flags);
+	wc->dmactrl &= ~0x00007fff;
+	wc->dmactrl |= (0x00004000 | (tots << 7));
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	if (debug & DEBUG_TSI)
+		dev_notice(&wc->dev->dev, "Sending '%08x\n", wc->dmactrl);
+	wc->dmactrl &= ~0x00007fff;
+	__t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+}
+#ifdef CONFIG_EXTENDED_RESET
+static void t4_extended_reset(struct t4 *wc)
+{
+	unsigned int oldreg = t4_pci_in(wc, 0x4);
+
+	udelay(1000);
+
+	t4_pci_out(wc, 0x4, 0x42000000);
+	t4_pci_out(wc, 0xa, 0x42000000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0xa, 0x00080000);
+	t4_pci_out(wc, 0xa, 0x00180000);
+	t4_pci_out(wc, 0x4, oldreg);
+
+	udelay(1000);
+}
+#endif
+
+static int t4_hardware_init_1(struct t4 *wc, unsigned int cardflags)
+{
+	unsigned int version;
+
+	version = t4_pci_in(wc, WC_VERSION);
+	dev_info(&wc->dev->dev, "Firmware Version: %08x\n", version);
+	dev_info(&wc->dev->dev, "Burst Mode: %s\n",
+		(!(cardflags & FLAG_BURST) && noburst) ? "Off" : "On");
+#ifdef ENABLE_WORKQUEUES
+	dev_info(&wc->dev->dev, "Work Queues: Enabled\n");
+#endif
+
+#ifdef CONFIG_EXTENDED_RESET
+	t4_extended_reset(wc);
+#endif
+
+	/* Make sure DMA engine is not running and interrupts are acknowledged */
+	wc->dmactrl = 0x0;
+	t4_pci_out(wc, WC_DMACTRL, wc->dmactrl);
+	/* Reset Framer and friends */
+	t4_pci_out(wc, WC_LEDS, 0x00000000);
+
+	/* Set DMA addresses */
+	t4_pci_out(wc, WC_RDADDR, wc->readdma);
+	t4_pci_out(wc, WC_WRADDR, wc->writedma);
+
+	/* Setup counters, interrupt flags (ignored in Gen2) */
+	if (cardflags & FLAG_2NDGEN) {
+		t4_tsi_reset(wc);
+	} else {
+		t4_pci_out(wc, WC_COUNT, ((DAHDI_MAX_CHUNKSIZE * 2 * 32 - 1) << 18) | ((DAHDI_MAX_CHUNKSIZE * 2 * 32 - 1) << 2));
+	}
+	
+	/* Reset pending interrupts */
+	t4_pci_out(wc, WC_INTR, 0x00000000);
+
+	/* Read T1/E1 status */
+	if (t1e1override > -1)
+		wc->t1e1 = t1e1override;
+	else
+		wc->t1e1 = ((t4_pci_in(wc, WC_LEDS)) & 0x0f00) >> 8;
+	wc->order = ((t4_pci_in(wc, WC_LEDS)) & 0xf0000000) >> 28;
+	order_index[wc->order]++;
+	return 0;
+}
+
+static int t4_hardware_init_2(struct t4 *wc)
+{
+	int x;
+	unsigned int regval;
+
+	if (t4_pci_in(wc, WC_VERSION) >= 0xc01a0165) {
+		wc->tspans[0]->spanflags |= FLAG_OCTOPT;
+		dev_info(&wc->dev->dev, "Octasic Optimizations: Enabled\n");
+	}
+	/* Setup LEDS, take out of reset */
+	t4_pci_out(wc, WC_LEDS, 0x000000ff);
+	t4_activate(wc);
+
+	/* 
+	 * In order to find out the QFALC framer version, we have to temporarily term off compat
+	 * mode and take a peak at VSTR.  We turn compat back on when we are done.
+	 */
+	if (t4_framer_in(wc, 0, 0x4a) != 0x05)
+		dev_info(&wc->dev->dev, "WARNING: FALC framer not intialized "
+				"in compatibility mode.\n");
+	regval = t4_framer_in(wc, 0 ,0xd6);
+	regval |= (1 << 5); /* set COMP_DIS*/
+	t4_framer_out(wc, 0, 0xd6, regval);
+	regval = t4_framer_in(wc, 0, 0x4a);
+	if (regval == 0x05)
+		dev_info(&wc->dev->dev, "FALC Framer Version: 2.1 or "
+				"earlier\n");
+	else if (regval == 0x20) {
+		dev_info(&wc->dev->dev, "FALC Framer Version: 3.1\n");
+		wc->falc31 = 1;
+	} else
+		dev_info(&wc->dev->dev, "FALC Framer Version: Unknown "
+				"(VSTR = 0x%02x)\n", regval);
+	regval = t4_framer_in(wc, 0 ,0xd6);
+	regval &= ~(1 << 5); /* clear COMP_DIS*/
+	t4_framer_out(wc, 0, 0xd6, regval);
+	
+	t4_framer_out(wc, 0, 0x4a, 0xaa);
+	dev_info(&wc->dev->dev, "Board ID: %02x\n", wc->order);
+
+	for (x=0;x< 11;x++)
+		dev_info(&wc->dev->dev, "Reg %d: 0x%08x\n", x,
+				t4_pci_in(wc, x));
+	return 0;
+}
+
+static int __devinit t4_launch(struct t4 *wc)
+{
+	int x;
+	unsigned long flags;
+	if (test_bit(DAHDI_FLAGBIT_REGISTERED, &wc->tspans[0]->span.flags))
+		return 0;
+	dev_info(&wc->dev->dev, "opvxd115: Launching card: %d\n",
+			wc->order);
+
+	/* Setup serial parameters and system interface */
+	for (x=0;x<PORTS_PER_FRAMER;x++)
+		t4_serial_setup(wc, x);
+
+	for (x = 0; x < wc->numspans; ++x) {
+		list_add_tail(&wc->tspans[x]->span.device_node,
+			      &wc->ddev->spans);
+	}
+	if (dahdi_register_device(wc->ddev, &wc->dev->dev)) {
+		dev_err(&wc->dev->dev, "Unable to register span %s\n",
+				wc->tspans[0]->span.name);
+		return -1;
+	}
+	set_bit(T4_CHECK_TIMING, &wc->checkflag);
+	spin_lock_irqsave(&wc->reglock, flags);
+	__t4_set_sclk_src(wc, WC_SELF, 0, 0);
+	spin_unlock_irqrestore(&wc->reglock, flags);
+	tasklet_init(&wc->t4_tlet, t4_isr_bh, (unsigned long)wc);
+	return 0;
+}
+
+static void free_wc(struct t4 *wc)
+{
+	unsigned int x, y;
+
+	for (x = 0; x < sizeof(wc->tspans)/sizeof(wc->tspans[0]); x++) {
+		if (!wc->tspans[x]) {
+			continue;
+		}
+
+		for (y = 0; y < sizeof(wc->tspans[x]->chans)/sizeof(wc->tspans[x]->chans[0]); y++) {
+			if (wc->tspans[x]->chans[y]) {
+				kfree(wc->tspans[x]->chans[y]);
+			}
+			if (wc->tspans[x]->ec[y])
+				kfree(wc->tspans[x]->ec[y]);
+		}
+		kfree(wc->tspans[x]);
+	}
+	kfree(wc);
+}
+
+static int __devinit t4_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct t4 *wc;
+	struct devtype *dt;
+	unsigned int x, f;
+	int init_latency;
+	
+	if (pci_enable_device(pdev)) {
+		return -EIO;
+	}
+
+	if (!(wc = kmalloc(sizeof(*wc), GFP_KERNEL))) {
+		return -ENOMEM;
+	}
+
+	memset(wc, 0x0, sizeof(*wc));
+	spin_lock_init(&wc->reglock);
+	dt = (struct devtype *) (ent->driver_data);
+
+	wc->flags = dt->flags;
+
+	wc->numspans = 1;
+	
+	wc->variety = dt->desc;
+	
+	wc->memaddr = pci_resource_start(pdev, 0);
+	wc->memlen = pci_resource_len(pdev, 0);
+	wc->membase = ioremap(wc->memaddr, wc->memlen);
+	/* This rids of the Double missed interrupt message after loading */
+	wc->last0 = 1;
+#if 0
+	if (!request_mem_region(wc->memaddr, wc->memlen, wc->variety))
+		dev_info(&wc->dev->dev, "opvxd115: Unable to request memory "
+				"region :(, using anyway...\n");
+#endif
+	if (pci_request_regions(pdev, wc->variety))
+		dev_info(&pdev->dev, "opvxd115: Unable to request regions\n");
+	
+	dev_info(&pdev->dev, "Found opvxd115 at base address %08lx, remapped "
+			"to %p\n", wc->memaddr, wc->membase);
+	
+	wc->dev = pdev;
+	
+	/* Enable bus mastering */
+	pci_set_master(pdev);
+
+	/* Keep track of which device we are */
+	pci_set_drvdata(pdev, wc);
+
+	if (wc->flags & FLAG_5THGEN) {
+		if ((ms_per_irq > 1) && (latency <= ((ms_per_irq) << 1))) {
+			init_latency = ms_per_irq << 1;
+		} else {
+			if (latency > 2)
+				init_latency = latency;
+			else
+				init_latency = 2;
+		}
+		dev_info(&wc->dev->dev, "5th gen card with initial latency of "
+			"%d and %d ms per IRQ\n", init_latency, ms_per_irq);
+	} else {
+		if (wc->flags & FLAG_2NDGEN)
+			init_latency = 1;
+		else
+			init_latency = 2;
+	}
+
+	if (max_latency < init_latency) {
+		printk(KERN_INFO "maxlatency must be set to something greater than %d ms, increasing it to %d\n", init_latency, init_latency);
+		max_latency = init_latency;
+	}
+	
+	if (t4_allocate_buffers(wc, init_latency, NULL, NULL)) {
+		return -ENOMEM;
+	}
+
+	/* Initialize hardware */
+	t4_hardware_init_1(wc, wc->flags);
+	
+	for(x = 0; x < MAX_T4_CARDS; x++) {
+		if (!cards[x])
+			break;
+	}
+	
+	if (x >= MAX_T4_CARDS) {
+		dev_notice(&wc->dev->dev, "No cards[] slot available!!\n");
+		kfree(wc);
+		return -ENOMEM;
+	}
+	
+	wc->num = x;
+	cards[x] = wc;
+	
+#ifdef ENABLE_WORKQUEUES
+	if (wc->flags & FLAG_2NDGEN) {
+		char tmp[20];
+
+		sprintf(tmp, "opvxd115");
+		wc->workq = create_workqueue(tmp);
+	}
+#endif			
+
+	/* Allocate pieces we need here */
+	for (x = 0; x < PORTS_PER_FRAMER; x++) {
+		if (!(wc->tspans[x] = kmalloc(sizeof(*wc->tspans[x]), GFP_KERNEL))) {
+			free_wc(wc);
+			return -ENOMEM;
+		}
+
+		memset(wc->tspans[x], 0, sizeof(*wc->tspans[x]));
+
+		if (wc->t1e1 & (1 << x)) {
+			wc->tspans[x]->spantype = TYPE_E1;
+		} else {
+			if (j1mode)
+				wc->tspans[x]->spantype = TYPE_J1;
+			else
+				wc->tspans[x]->spantype = TYPE_T1;
+		}
+
+		for (f = 0; f < (wc->tspans[x]->spantype == TYPE_E1 ? 31 : 24); f++) {
+			if (!(wc->tspans[x]->chans[f] = kmalloc(sizeof(*wc->tspans[x]->chans[f]), GFP_KERNEL))) {
+				free_wc(wc);
+				return -ENOMEM;
+			}
+			memset(wc->tspans[x]->chans[f], 0, sizeof(*wc->tspans[x]->chans[f]));
+			if (!(wc->tspans[x]->ec[f] = kmalloc(sizeof(*wc->tspans[x]->ec[f]), GFP_KERNEL))) {
+				free_wc(wc);
+				return -ENOMEM;
+			}
+			memset(wc->tspans[x]->ec[f], 0, sizeof(*wc->tspans[x]->ec[f]));
+		}
+
+#ifdef ENABLE_WORKQUEUES
+		INIT_WORK(&wc->tspans[x]->swork, workq_handlespan, wc->tspans[x]);
+#endif				
+		wc->tspans[x]->spanflags |= wc->flags;
+	}
+	
+	/* Continue hardware intiialization */
+	t4_hardware_init_2(wc);
+	
+#ifdef SUPPORT_GEN1
+	if (request_irq(pdev->irq, (wc->flags & FLAG_2NDGEN) ? t4_interrupt_gen2 :t4_interrupt, DAHDI_IRQ_SHARED_DISABLED, "opvxd115", wc)) 
+#else
+		if (!(wc->tspans[0]->spanflags & FLAG_2NDGEN)) {
+			dev_notice(&wc->dev->dev, "This driver does not "
+					"support 1st gen modules\n");
+			free_wc(wc);
+			return -ENODEV;
+		}	
+			if (request_irq(pdev->irq, t4_interrupt_gen2, DAHDI_IRQ_SHARED_DISABLED, "opvxd115", wc)) 
+#endif
+	{
+		dev_notice(&wc->dev->dev, "opvxd115: Unable to request IRQ %d\n",
+				pdev->irq);
+		free_wc(wc);
+		return -EIO;
+	}
+	
+	init_spans(wc);
+	/* get the current number of probed cards and run a slice of a tail
+	 * insertion sort */
+	for (x = 0; x < MAX_T4_CARDS; x++) {
+		if (!cards[x+1])
+			break;
+	}
+	for ( ; x > 0; x--) {
+		if (cards[x]->order < cards[x-1]->order) {
+			struct t4 *tmp = cards[x];
+			cards[x] = cards[x-1];
+			cards[x-1] = tmp;
+		} else {
+			/* if we're not moving it, we won't move any more
+			 * since all cards are sorted on addition */
+			break;
+		}
+	}
+	
+	dev_info(&wc->dev->dev, "Found an OpenVox Card: %s\n", wc->variety);
+	wc->gpio = 0x00000000;
+	t4_pci_out(wc, WC_GPIO, wc->gpio);
+	t4_gpio_setdir(wc, (1 << 17), (1 << 17));
+	t4_gpio_setdir(wc, (0xff), (0xff));
+
+	create_sysfs_files(wc);
+	
+#if 0
+	for (x=0;x<0x10000;x++) {
+		__t4_raw_oct_out(wc, 0x0004, x);
+		__t4_raw_oct_out(wc, 0x000a, x ^ 0xffff);
+		if (__t4_raw_oct_in(wc, 0x0004) != x) 
+			dev_notice(&wc->dev->dev, "Register 4 failed %04x\n",
+					x);
+		if (__t4_raw_oct_in(wc, 0x000a) != (x ^ 0xffff))
+			dev_notice(&wc->dev->dev, "Register 10 failed %04x\n",
+					x);
+	}
+#endif
+
+	return 0;
+}
+
+static int t4_hardware_stop(struct t4 *wc)
+{
+
+	/* Turn off DMA, leave interrupts enabled */
+	set_bit(T4_STOP_DMA, &wc->checkflag);
+
+	/* Wait for interrupts to stop */
+	msleep(25);
+
+	/* Turn off counter, address, etc */
+	if (wc->tspans[0]->spanflags & FLAG_2NDGEN) {
+		t4_tsi_reset(wc);
+	} else {
+		t4_pci_out(wc, WC_COUNT, 0x000000);
+	}
+	t4_pci_out(wc, WC_RDADDR, 0x0000000);
+	t4_pci_out(wc, WC_WRADDR, 0x0000000);
+	wc->gpio = 0x00000000;
+	t4_pci_out(wc, WC_GPIO, wc->gpio);
+	t4_pci_out(wc, WC_LEDS, 0x00000000);
+
+	dev_notice(&wc->dev->dev, "\nStopped opvxd115, Turned off DMA\n");
+	return 0;
+}
+
+static void __devexit t4_remove_one(struct pci_dev *pdev)
+{
+	struct t4 *wc = pci_get_drvdata(pdev);
+	int basesize;
+
+	if (!wc) {
+		return;
+	}
+
+	remove_sysfs_files(wc);
+
+	/* Stop hardware */
+	t4_hardware_stop(wc);
+	
+	/* Release vpm450m */
+	if (wc->vpm450m)
+		release_vpm450m(wc->vpm450m);
+	wc->vpm450m = NULL;
+	/* Unregister spans */
+
+	basesize = DAHDI_MAX_CHUNKSIZE * 32 * 4;
+	if (!(wc->tspans[0]->spanflags & FLAG_2NDGEN))
+		basesize = basesize * 2;
+
+	dahdi_unregister_device(wc->ddev);
+	kfree(wc->ddev->location);
+	kfree(wc->ddev->devicetype);
+	dahdi_free_device(wc->ddev);
+#ifdef ENABLE_WORKQUEUES
+	if (wc->workq) {
+		flush_workqueue(wc->workq);
+		destroy_workqueue(wc->workq);
+	}
+#endif			
+	
+	free_irq(pdev->irq, wc);
+	
+	if (wc->membase)
+		iounmap(wc->membase);
+	
+	pci_release_regions(pdev);		
+	
+	/* Immediately free resources */
+	pci_free_consistent(pdev, T4_BASE_SIZE * wc->numbufs * 2, (void *)wc->writechunk, wc->writedma);
+	
+	order_index[wc->order]--;
+	
+	cards[wc->num] = NULL;
+	pci_set_drvdata(pdev, NULL);
+	free_wc(wc);
+}
+
+
+static struct pci_device_id t4_pci_tbl[] __devinitdata =
+{
+	{ 0x1b74, 0x0115, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)&opvxd115 }, /* OpenVox D115P/D115E */
+	{ 0x1b74, 0xd130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)&opvxd130 }, /* OpenVox D130P/D130E */
+	{ 0, }
+};
+
+static struct pci_driver t4_driver = {
+	.name = "opvxd115",
+	.probe = t4_init_one,
+	.remove = __devexit_p(t4_remove_one),
+	.id_table = t4_pci_tbl,
+};
+
+static int __init t4_init(void)
+{
+	int res;
+	res = dahdi_pci_module(&t4_driver);
+	if (res)
+		return -ENODEV;
+	/* initialize cards since we have all of them */
+	/* warn for missing zero and duplicate numbers */
+	if (cards[0] && cards[0]->order != 0) {
+		printk(KERN_NOTICE "opvxd115: Ident of first card is not zero (%d)\n",
+			cards[0]->order);
+	}
+	for (res = 0; cards[res]; res++) {
+		/* warn the user of duplicate ident values it is probably
+		 * unintended */
+		if (debug && res < 15 && cards[res+1] &&
+		    cards[res]->order == cards[res+1]->order) {
+			printk(KERN_NOTICE "opvxd115: Duplicate ident value found (%d)\n",
+				cards[res]->order);
+		}
+		t4_launch(cards[res]);
+	}
+	return 0;
+}
+
+static void __exit t4_cleanup(void)
+{
+	pci_unregister_driver(&t4_driver);
+}
+
+
+MODULE_AUTHOR("mark.liu <mark.liu@openvox.cn>");
+MODULE_DESCRIPTION("Unified OpenVox Single T1/E1/J1 Card Driver");
+MODULE_ALIAS("opvxd115");
+MODULE_LICENSE("GPL v2");
+
+module_param(pedanticpci, int, 0600);
+module_param(debug, int, 0600);
+module_param(noburst, int, 0600);
+module_param(timingcable, int, 0600);
+module_param(t1e1override, int, 0600);
+module_param(alarmdebounce, int, 0600);
+module_param(losalarmdebounce, int, 0600);
+module_param(aisalarmdebounce, int, 0600);
+module_param(yelalarmdebounce, int, 0600);
+module_param(max_latency, int, 0600);
+module_param(j1mode, int, 0600);
+module_param(sigmode, int, 0600);
+module_param(latency, int, 0600);
+module_param(ms_per_irq, int, 0600);
+#ifdef VPM_SUPPORT
+module_param(vpmsupport, int, 0600);
+module_param(vpmdtmfsupport, int, 0600);
+module_param(vpmspans, int, 0600);
+module_param(dtmfthreshold, int, 0600);
+#endif
+
+MODULE_DEVICE_TABLE(pci, t4_pci_tbl);
+
+module_init(t4_init);
+module_exit(t4_cleanup);
diff --git a/drivers/dahdi/opvxd115/opvxd115-diag.c b/drivers/dahdi/opvxd115/opvxd115-diag.c
new file mode 100644
index 0000000..7d1dfdb
--- /dev/null
+++ b/drivers/dahdi/opvxd115/opvxd115-diag.c
@@ -0,0 +1,427 @@
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <string.h>
+#include <dahdi/user.h>
+#include "opvxd115.h"
+
+struct t4_reg_def {
+	int reg;
+	char *name;
+	int global;
+};
+static struct t4_reg_def xreginfo[] = {
+	{ 0x00, "RDADDR" },
+	{ 0x01, "WRADDR" },
+	{ 0x02, "COUNT" },
+	{ 0x03, "DMACTRL" },
+	{ 0x04, "WCINTR" },
+	{ 0x06, "VERSION" },
+	{ 0x07, "LEDS" },
+	{ 0x08, "GPIOCTL" },
+	{ 0x09, "GPIO" }, 
+	{ 0x0A, "LADDR" },
+	{ 0x0b, "LDATA" },
+};
+
+static struct t4_reg_def reginfo[] = {
+	{ 0x00, "XFIFO" },
+	{ 0x01, "XFIFO" },
+	{ 0x02, "CMDR" },
+	{ 0x03, "MODE" },
+	{ 0x04, "RAH1" },
+	{ 0x05, "RAH2" },
+	{ 0x06, "RAL1" },
+	{ 0x07, "RAL2" },
+	{ 0x08, "IPC", 1 },
+	{ 0x09, "CCR1" },
+	{ 0x0a, "CCR2" },
+	{ 0x0c, "RTR1" },
+	{ 0x0d, "RTR2" },
+	{ 0x0e, "RTR3" },
+	{ 0x0f, "RTR4" },
+	{ 0x10, "TTR1" },
+	{ 0x11, "TTR2" },
+	{ 0x12, "TTR3" },
+	{ 0x13, "TTR4" },
+	{ 0x14, "IMR0" },
+	{ 0x15, "IMR1" },
+	{ 0x16, "IMR2" },
+	{ 0x17, "IMR3" },
+	{ 0x18, "IMR4" },
+	{ 0x1b, "IERR" },
+	{ 0x1c, "FMR0" },
+	{ 0x1d, "FMR1" },
+	{ 0x1e, "FMR2" },
+	{ 0x1f, "LOOP" },
+	{ 0x20, "XSW" },
+	{ 0x21, "XSP" },
+	{ 0x22, "XC0" },
+	{ 0x23, "XC1" },
+	{ 0x24, "RC0" },
+	{ 0x25, "RC1" },
+	{ 0x26, "XPM0" },
+	{ 0x27, "XPM1" },
+	{ 0x28, "XPM2" },
+	{ 0x29, "TSWM" },
+	{ 0x2b, "IDLE" },
+	{ 0x2c, "XSA4" },
+	{ 0x2d, "XSA5" },
+	{ 0x2e, "XSA6" },
+	{ 0x2f, "XSA7" },
+	{ 0x30, "XSA8" },
+	{ 0x31, "FMR3" },
+	{ 0x32, "ICB1" },
+	{ 0x33, "ICB2" },
+	{ 0x34, "ICB3" },
+	{ 0x35, "ICB4" },
+	{ 0x36, "LIM0" },
+	{ 0x37, "LIM1" },
+	{ 0x38, "PCD" },
+	{ 0x39, "PCR" },
+	{ 0x3a, "LIM2" },
+	{ 0x3b, "LCR1" },
+	{ 0x3c, "LCR2" },
+	{ 0x3d, "LCR3" },
+	{ 0x3e, "SIC1" },
+	{ 0x3f, "SIC2" },
+	{ 0x40, "SIC3" },
+	{ 0x44, "CMR1" },
+	{ 0x45, "CMR2" },
+	{ 0x46, "GCR" },
+	{ 0x47, "ESM" },
+	{ 0x60, "DEC" },
+	{ 0x70, "XS1" },
+	{ 0x71, "XS2" },
+	{ 0x72, "XS3" },
+	{ 0x73, "XS4" },
+	{ 0x74, "XS5" },
+	{ 0x75, "XS6" },
+	{ 0x76, "XS7" },
+	{ 0x77, "XS8" },
+	{ 0x78, "XS9" },
+	{ 0x79, "XS10" },
+	{ 0x7a, "XS11" },
+	{ 0x7b, "XS12" },
+	{ 0x7c, "XS13" },
+	{ 0x7d, "XS14" },
+	{ 0x7e, "XS15" },
+	{ 0x7f, "XS16" },
+	{ 0x80, "PC1" },
+	{ 0x81, "PC2" },
+	{ 0x82, "PC3" },
+	{ 0x83, "PC4" },
+	{ 0x84, "PC5" },
+	{ 0x85, "GPC1", 1 },
+	{ 0x87, "CMDR2" },
+	{ 0x8d, "CCR5" },
+	{ 0x92, "GCM1", 1 },
+	{ 0x93, "GCM2", 1 },
+	{ 0x94, "GCM3", 1 },
+	{ 0x95, "GCM4", 1 },
+	{ 0x96, "GCM5", 1 },
+	{ 0x97, "GCM6", 1 },
+	{ 0x98, "GCM7", 1 },
+	{ 0x99, "GCM8", 1 },
+	{ 0xa0, "TSEO" },
+	{ 0xa1, "TSBS1" },
+	{ 0xa8, "TPC0" },	
+};
+
+static struct t4_reg_def t1_reginfo[] = {
+	{ 0x00, "XFIFO" },
+	{ 0x01, "XFIFO" },
+	{ 0x02, "CMDR" },
+	{ 0x03, "MODE" },
+	{ 0x04, "RAH1" },
+	{ 0x05, "RAH2" },
+	{ 0x06, "RAL1" },
+	{ 0x07, "RAL2" },
+	{ 0x08, "IPC", 1 },
+	{ 0x09, "CCR1" },
+	{ 0x0a, "CCR2" },
+	{ 0x0c, "RTR1" },
+	{ 0x0d, "RTR2" },
+	{ 0x0e, "RTR3" },
+	{ 0x0f, "RTR4" },
+	{ 0x10, "TTR1" },
+	{ 0x11, "TTR2" },
+	{ 0x12, "TTR3" },
+	{ 0x13, "TTR4" },
+	{ 0x14, "IMR0" },
+	{ 0x15, "IMR1" },
+	{ 0x16, "IMR2" },
+	{ 0x17, "IMR3" },
+	{ 0x18, "IMR4" },
+	{ 0x1b, "IERR" },
+	{ 0x1c, "FMR0" },
+	{ 0x1d, "FMR1" },
+	{ 0x1e, "FMR2" },
+	{ 0x1f, "LOOP" },
+	{ 0x20, "FMR4" },
+	{ 0x21, "FMR5" },
+	{ 0x22, "XC0" },
+	{ 0x23, "XC1" },
+	{ 0x24, "RC0" },
+	{ 0x25, "RC1" },
+	{ 0x26, "XPM0" },
+	{ 0x27, "XPM1" },
+	{ 0x28, "XPM2" },
+	{ 0x2b, "IDLE" },
+	{ 0x2c, "XDL1" },
+	{ 0x2d, "XDL2" },
+	{ 0x2e, "XDL3" },
+	{ 0x2f, "CCB1" },
+	{ 0x30, "CCB2" },
+	{ 0x31, "CCB3" },
+	{ 0x32, "ICB1" },
+	{ 0x33, "ICB2" },
+	{ 0x34, "ICB3" },
+	{ 0x36, "LIM0" },
+	{ 0x37, "LIM1" },
+	{ 0x38, "PCD" },
+	{ 0x39, "PCR" },
+	{ 0x3a, "LIM2" },
+	{ 0x3b, "LCR1" },
+	{ 0x3c, "LCR2" },
+	{ 0x3d, "LCR3" },
+	{ 0x3e, "SIC1" },
+	{ 0x3f, "SIC2" },
+	{ 0x40, "SIC3" },
+	{ 0x44, "CMR1" },
+	{ 0x45, "CMR2" },
+	{ 0x46, "GCR" },
+	{ 0x47, "ESM" },
+	{ 0x60, "DEC" },
+	{ 0x70, "XS1" },
+	{ 0x71, "XS2" },
+	{ 0x72, "XS3" },
+	{ 0x73, "XS4" },
+	{ 0x74, "XS5" },
+	{ 0x75, "XS6" },
+	{ 0x76, "XS7" },
+	{ 0x77, "XS8" },
+	{ 0x78, "XS9" },
+	{ 0x79, "XS10" },
+	{ 0x7a, "XS11" },
+	{ 0x7b, "XS12" },
+	{ 0x80, "PC1" },
+	{ 0x81, "PC2" },
+	{ 0x82, "PC3" },
+	{ 0x83, "PC4" },
+	{ 0x84, "PC5" },
+	{ 0x85, "GPC1", 1 },
+	{ 0x87, "CMDR2" },
+	{ 0x8d, "CCR5" },
+	{ 0x92, "GCM1", 1 },
+	{ 0x93, "GCM2", 1 },
+	{ 0x94, "GCM3", 1 },
+	{ 0x95, "GCM4", 1 },
+	{ 0x96, "GCM5", 1 },
+	{ 0x97, "GCM6", 1 },
+	{ 0x98, "GCM7", 1 },
+	{ 0x99, "GCM8", 1 },
+	{ 0xa0, "TSEO" },
+	{ 0xa1, "TSBS1" },
+	{ 0xa8, "TPC0" },	
+};
+
+static struct t4_reg_def t1_sreginfo[] = {
+	{ 0x00, "RFIFO" },
+	{ 0x01, "RFIFO" },
+	{ 0x49, "RBD" },
+	{ 0x4a, "VSTR", 1 },
+	{ 0x4b, "RES" },
+	{ 0x4c, "FRS0" },
+	{ 0x4d, "FRS1" },
+	{ 0x4e, "FRS2" },
+	{ 0x4f, "Old FRS1" },
+	{ 0x50, "FECL" },
+	{ 0x51, "FECH" },
+	{ 0x52, "CVCL" },
+	{ 0x53, "CVCH" },
+	{ 0x54, "CECL" },
+	{ 0x55, "CECH" },
+	{ 0x56, "EBCL" },
+	{ 0x57, "EBCH" },
+	{ 0x58, "BECL" },
+	{ 0x59, "BECH" },
+	{ 0x5a, "COEC" },
+	{ 0x5c, "RDL1" },
+	{ 0x5d, "RDL2" },
+	{ 0x5e, "RDL3" },
+	{ 0x62, "RSP1" },
+	{ 0x63, "RSP2" },
+	{ 0x64, "SIS" },
+	{ 0x65, "RSIS" },
+	{ 0x66, "RBCL" },
+	{ 0x67, "RBCH" },
+	{ 0x68, "ISR0" },
+	{ 0x69, "ISR1" },
+	{ 0x6a, "ISR2" },
+	{ 0x6b, "ISR3" },
+	{ 0x6c, "ISR4" },
+	{ 0x6e, "GIS" },
+	{ 0x6f, "CIS", 1 },
+	{ 0x70, "RS1" },
+	{ 0x71, "RS2" },
+	{ 0x72, "RS3" },
+	{ 0x73, "RS4" },
+	{ 0x74, "RS5" },
+	{ 0x75, "RS6" },
+	{ 0x76, "RS7" },
+	{ 0x77, "RS8" },
+	{ 0x78, "RS9" },
+	{ 0x79, "RS10" },
+	{ 0x7a, "RS11" },
+	{ 0x7b, "RS12" },
+};
+
+static struct t4_reg_def sreginfo[] = {
+	{ 0x00, "RFIFO" },
+	{ 0x01, "RFIFO" },
+	{ 0x49, "RBD" },
+	{ 0x4a, "VSTR", 1 },
+	{ 0x4b, "RES" },
+	{ 0x4c, "FRS0" },
+	{ 0x4d, "FRS1" },
+	{ 0x4e, "RSW" },
+	{ 0x4f, "RSP" },
+	{ 0x50, "FECL" },
+	{ 0x51, "FECH" },
+	{ 0x52, "CVCL" },
+	{ 0x53, "CVCH" },
+	{ 0x54, "CEC1L" },
+	{ 0x55, "CEC1H" },
+	{ 0x56, "EBCL" },
+	{ 0x57, "EBCH" },
+	{ 0x58, "CEC2L" },
+	{ 0x59, "CEC2H" },
+	{ 0x5a, "CEC3L" },
+	{ 0x5b, "CEC3H" },
+	{ 0x5c, "RSA4" },
+	{ 0x5d, "RSA5" },
+	{ 0x5e, "RSA6" },
+	{ 0x5f, "RSA7" },
+	{ 0x60, "RSA8" },
+	{ 0x61, "RSA6S" },
+	{ 0x62, "RSP1" },
+	{ 0x63, "RSP2" },
+	{ 0x64, "SIS" },
+	{ 0x65, "RSIS" },
+	{ 0x66, "RBCL" },
+	{ 0x67, "RBCH" },
+	{ 0x68, "ISR0" },
+	{ 0x69, "ISR1" },
+	{ 0x6a, "ISR2" },
+	{ 0x6b, "ISR3" },
+	{ 0x6c, "ISR4" },
+	{ 0x6e, "GIS" },
+	{ 0x6f, "CIS", 1 },
+	{ 0x70, "RS1" },
+	{ 0x71, "RS2" },
+	{ 0x72, "RS3" },
+	{ 0x73, "RS4" },
+	{ 0x74, "RS5" },
+	{ 0x75, "RS6" },
+	{ 0x76, "RS7" },
+	{ 0x77, "RS8" },
+	{ 0x78, "RS9" },
+	{ 0x79, "RS10" },
+	{ 0x7a, "RS11" },
+	{ 0x7b, "RS12" },
+	{ 0x7c, "RS13" },
+	{ 0x7d, "RS14" },
+	{ 0x7e, "RS15" },
+	{ 0x7f, "RS16" },
+};
+
+static char *tobin(int x)
+{
+	static char s[9] = "";
+	int y,z=0;
+	for (y=7;y>=0;y--) {
+		if (x & (1 << y))
+			s[z++] = '1';
+		else
+			s[z++] = '0';
+	}
+	s[z] = '\0';
+	return s;
+}
+
+static char *tobin32(unsigned int x)
+{
+	static char s[33] = "";
+	int y,z=0;
+	for (y=31;y>=0;y--) {
+		if (x & (1 << y))
+			s[z++] = '1';
+		else
+			s[z++] = '0';
+	}
+	s[z] = '\0';
+	return s;
+}
+
+int main(int argc, char *argv[])
+{
+	int fd;
+	int x;
+	char fn[256];
+	struct t4_regs regs;
+	if ((argc < 2) || ((*(argv[1]) != '/') && !atoi(argv[1]))) {
+		fprintf(stderr, "Usage: opvxd115-diag <channel>\n");
+		exit(1);
+	}
+	if (*(argv[1]) == '/')
+		dahdi_copy_string(fn, argv[1], sizeof(fn));
+	else
+		snprintf(fn, sizeof(fn), "/dev/dahdi/%d", atoi(argv[1]));
+	fd = open(fn, O_RDWR);
+	if (fd <0) {
+		fprintf(stderr, "Unable to open '%s': %s\n", fn, strerror(errno));
+		exit(1);
+	}
+	if (ioctl(fd, WCT4_GET_REGS, &regs)) {
+		fprintf(stderr, "Unable to get registers: %s\n", strerror(errno));
+		exit(1);
+	}
+	printf("PCI Registers:\n");
+	for (x=0;x<sizeof(xreginfo) / sizeof(xreginfo[0]);x++) {
+		fprintf(stdout, "%s (%02x): %08x (%s)\n", xreginfo[x].name, xreginfo[x].reg, regs.pci[xreginfo[x].reg], tobin32(regs.pci[xreginfo[x].reg]));
+	}
+	printf("\nE1 Control Registers:\n");
+	for (x=0;x<sizeof(reginfo) / sizeof(reginfo[0]);x++) {
+		fprintf(stdout, "%s (%02x): %02x (%s)\n", reginfo[x].name, reginfo[x].reg, regs.regs[reginfo[x].reg], tobin(regs.regs[reginfo[x].reg]));
+	}
+	printf("\nE1 Status Registers:\n");
+	for (x=0;x<sizeof(sreginfo) / sizeof(sreginfo[0]);x++) {
+		fprintf(stdout, "%s (%02x): %02x (%s)\n", sreginfo[x].name, sreginfo[x].reg, regs.regs[sreginfo[x].reg], tobin(regs.regs[sreginfo[x].reg]));
+	}
+	printf("\nT1 Control Registers:\n");
+	for (x=0;x<sizeof(t1_reginfo) / sizeof(t1_reginfo[0]);x++) {
+		fprintf(stdout, "%s (%02x): %02x (%s)\n", t1_reginfo[x].name, t1_reginfo[x].reg, regs.regs[t1_reginfo[x].reg], tobin(regs.regs[t1_reginfo[x].reg]));
+	}
+	printf("\nT1 Status Registers:\n");
+	for (x=0;x<sizeof(t1_sreginfo) / sizeof(t1_sreginfo[0]);x++) {
+		fprintf(stdout, "%s (%02x): %02x (%s)\n", t1_sreginfo[x].name, t1_sreginfo[x].reg, regs.regs[t1_sreginfo[x].reg], tobin(regs.regs[t1_sreginfo[x].reg]));
+	}
+	exit(0);
+}
diff --git a/drivers/dahdi/opvxd115/opvxd115.h b/drivers/dahdi/opvxd115/opvxd115.h
new file mode 100644
index 0000000..d89d5b3
--- /dev/null
+++ b/drivers/dahdi/opvxd115/opvxd115.h
@@ -0,0 +1,124 @@
+/*
+ * Wildcard T400P FXS Interface Driver for DAHDI Telephony interface
+ *
+ * Written by Mark Spencer <markster@linux-support.net>
+ *
+ * Copyright (C) 2001-2008, Digium, Inc.
+ *
+ * All rights reserved.
+ *
+ */
+
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#include <linux/ioctl.h>
+
+#define FRMR_TTR_BASE 0x10
+#define FRMR_RTR_BASE 0x0c
+#define FRMR_TSEO 0xa0
+#define FRMR_TSBS1 0xa1
+#define FRMR_CCR1 0x09
+#define FRMR_CCR1_ITF 0x08
+#define FRMR_CCR1_EITS 0x10
+#define FRMR_CCR2 0x0a
+#define FRMR_CCR2_RCRC 0x04
+#define FRMR_CCR2_RADD 0x10
+#define FRMR_MODE 0x03
+#define FRMR_MODE_NO_ADDR_CMP 0x80
+#define FRMR_MODE_SS7 0x20
+#define FRMR_MODE_HRAC 0x08
+#define FRMR_IMR0 0x14
+#define FRMR_IMR0_RME 0x80
+#define FRMR_IMR0_RPF 0x01
+#define FRMR_IMR1 0x15
+#define FRMR_IMR1_ALLS 0x20
+#define FRMR_IMR1_XDU 0x10
+#define FRMR_IMR1_XPR 0x01
+#define FRMR_XC0 0x22
+#define FRMR_XC1 0x23
+#define FRMR_RC0 0x24
+#define FRMR_RC1 0x25
+#define FRMR_SIC1 0x3e
+#define FRMR_SIC2 0x3f
+#define FRMR_SIC3 0x40
+#define FRMR_CMR1 0x44
+#define FRMR_CMR2 0x45
+#define FRMR_GCR 0x46
+#define FRMR_ISR0 0x68
+#define FRMR_ISR0_RME 0x80
+#define FRMR_ISR0_RPF 0x01
+#define FRMR_ISR1 0x69
+#define FRMR_ISR1_ALLS 0x20
+#define FRMR_ISR1_XDU 0x10
+#define FRMR_ISR1_XPR 0x01
+#define FRMR_ISR2 0x6a
+#define FRMR_ISR3 0x6b
+#define FRMR_ISR4 0x6c
+#define FRMR_GIS  0x6e
+#define FRMR_GIS_ISR0 0x01
+#define FRMR_GIS_ISR1 0x02
+#define FRMR_GIS_ISR2 0x04
+#define FRMR_GIS_ISR3 0x08
+#define FRMR_GIS_ISR4 0x10
+#define FRMR_CIS 0x6f
+#define FRMR_CIS_GIS1 0x01
+#define FRMR_CIS_GIS2 0x02
+#define FRMR_CIS_GIS3 0x04
+#define FRMR_CIS_GIS4 0x08
+#define FRMR_CMDR 0x02
+#define FRMR_CMDR_SRES 0x01
+#define FRMR_CMDR_XRES 0x10
+#define FRMR_CMDR_RMC 0x80
+#define FRMR_CMDR_XTF 0x04
+#define FRMR_CMDR_XHF 0x08
+#define FRMR_CMDR_XME 0x02
+#define FRMR_RSIS 0x65
+#define FRMR_RSIS_VFR 0x80
+#define FRMR_RSIS_RDO 0x40
+#define FRMR_RSIS_CRC16 0x20
+#define FRMR_RSIS_RAB 0x10
+#define FRMR_RBCL 0x66
+#define FRMR_RBCL_MAX_SIZE 0x1f
+#define FRMR_RBCH 0x67
+#define FRMR_RXFIFO 0x00
+#define FRMR_SIS 0x64
+#define FRMR_SIS_XFW 0x40
+#define FRMR_TXFIFO 0x00
+
+#define FRS0 0x4c
+#define FRS0_LOS (1<<7)
+#define FRS0_LFA (1<<5)
+#define FRS0_LMFA (1<<1)
+
+#define FRS1 0x4d
+#define FRS1_XLS (1<<1)
+#define FRS1_XLO (1<<0)
+
+#define NUM_REGS 0xa9
+#define NUM_PCI 12
+
+struct t4_regs {
+	unsigned int pci[NUM_PCI];
+	unsigned char regs[NUM_REGS];
+};
+
+#define T4_CHECK_VPM		0
+#define T4_LOADING_FW		1
+#define T4_STOP_DMA		2
+#define T4_CHECK_TIMING		3
+#define T4_CHANGE_LATENCY	4
+#define T4_IGNORE_LATENCY	5
+
+#define WCT4_GET_REGS	_IOW (DAHDI_CODE, 60, struct t4_regs)
+
diff --git a/drivers/dahdi/opvxd115/vpm450m.c b/drivers/dahdi/opvxd115/vpm450m.c
new file mode 100644
index 0000000..f1ed421
--- /dev/null
+++ b/drivers/dahdi/opvxd115/vpm450m.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2005-2006 Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ * Modified by mark.liu@openvox.cn 06/16/2009
+ 
+ * All Rights Reserved
+ */
+
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/version.h>
+
+#include "vpm450m.h"
+#include "oct6100api/oct6100_api.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
+#include <linux/config.h>
+#endif
+
+/* API for Octasic access */
+UINT32 Oct6100UserGetTime(tPOCT6100_GET_TIME f_pTime)
+{
+	/* Why couldn't they just take a timeval like everyone else? */
+	struct timeval tv;
+	unsigned long long total_usecs;
+	unsigned int mask = ~0;
+	
+	do_gettimeofday(&tv);
+	total_usecs = (((unsigned long long)(tv.tv_sec)) * 1000000) + 
+				  (((unsigned long long)(tv.tv_usec)));
+	f_pTime->aulWallTimeUs[0] = (total_usecs & mask);
+	f_pTime->aulWallTimeUs[1] = (total_usecs >> 32);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserMemSet(PVOID f_pAddress, UINT32 f_ulPattern, UINT32 f_ulLength)
+{
+	memset(f_pAddress, f_ulPattern, f_ulLength);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserMemCopy(PVOID f_pDestination, const void *f_pSource, UINT32 f_ulLength)
+{
+	memcpy(f_pDestination, f_pSource, f_ulLength);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserCreateSerializeObject(tPOCT6100_CREATE_SERIALIZE_OBJECT f_pCreate)
+{
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDestroySerializeObject(tPOCT6100_DESTROY_SERIALIZE_OBJECT f_pDestroy)
+{
+#ifdef OCTASIC_DEBUG
+	printk(KERN_DEBUG "I should never be called! (destroy serialize object)\n");
+#endif
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserSeizeSerializeObject(tPOCT6100_SEIZE_SERIALIZE_OBJECT f_pSeize)
+{
+	/* Not needed */
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserReleaseSerializeObject(tPOCT6100_RELEASE_SERIALIZE_OBJECT f_pRelease)
+{
+	/* Not needed */
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteApi(tPOCT6100_WRITE_PARAMS f_pWriteParams)
+{
+	oct_set_reg(f_pWriteParams->pProcessContext, f_pWriteParams->ulWriteAddress, f_pWriteParams->usWriteData);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteSmearApi(tPOCT6100_WRITE_SMEAR_PARAMS f_pSmearParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pSmearParams->ulWriteLength;x++) {
+		oct_set_reg(f_pSmearParams->pProcessContext, f_pSmearParams->ulWriteAddress + (x << 1), f_pSmearParams->usWriteData);
+	}
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverWriteBurstApi(tPOCT6100_WRITE_BURST_PARAMS f_pBurstParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pBurstParams->ulWriteLength;x++) {
+		oct_set_reg(f_pBurstParams->pProcessContext, f_pBurstParams->ulWriteAddress + (x << 1), f_pBurstParams->pusWriteData[x]);
+	}
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverReadApi(tPOCT6100_READ_PARAMS f_pReadParams)
+{
+	*(f_pReadParams->pusReadData) = oct_get_reg(f_pReadParams->pProcessContext, f_pReadParams->ulReadAddress);
+	return cOCT6100_ERR_OK;
+}
+
+UINT32 Oct6100UserDriverReadBurstApi(tPOCT6100_READ_BURST_PARAMS f_pBurstParams)
+{
+	unsigned int x;
+	for (x=0;x<f_pBurstParams->ulReadLength;x++) {
+		f_pBurstParams->pusReadData[x] = oct_get_reg(f_pBurstParams->pProcessContext, f_pBurstParams->ulReadAddress + (x << 1));
+	}
+	return cOCT6100_ERR_OK;
+}
+
+#define SOUT_G168_1100GB_ON 0x40000004
+#define SOUT_DTMF_1 0x40000011
+#define SOUT_DTMF_2 0x40000012
+#define SOUT_DTMF_3 0x40000013
+#define SOUT_DTMF_A 0x4000001A
+#define SOUT_DTMF_4 0x40000014
+#define SOUT_DTMF_5 0x40000015
+#define SOUT_DTMF_6 0x40000016
+#define SOUT_DTMF_B 0x4000001B
+#define SOUT_DTMF_7 0x40000017
+#define SOUT_DTMF_8 0x40000018
+#define SOUT_DTMF_9 0x40000019
+#define SOUT_DTMF_C 0x4000001C
+#define SOUT_DTMF_STAR 0x4000001E
+#define SOUT_DTMF_0 0x40000010
+#define SOUT_DTMF_POUND 0x4000001F
+#define SOUT_DTMF_D 0x4000001D
+
+#define ROUT_G168_2100GB_ON 0x10000000
+#define ROUT_G168_2100GB_WSPR 0x10000002
+#define ROUT_SOUT_G168_2100HB_END 0x50000003
+#define ROUT_G168_1100GB_ON 0x10000004
+
+#define ROUT_DTMF_1 0x10000011
+#define ROUT_DTMF_2 0x10000012
+#define ROUT_DTMF_3 0x10000013
+#define ROUT_DTMF_A 0x1000001A
+#define ROUT_DTMF_4 0x10000014
+#define ROUT_DTMF_5 0x10000015
+#define ROUT_DTMF_6 0x10000016
+#define ROUT_DTMF_B 0x1000001B
+#define ROUT_DTMF_7 0x10000017
+#define ROUT_DTMF_8 0x10000018
+#define ROUT_DTMF_9 0x10000019
+#define ROUT_DTMF_C 0x1000001C
+#define ROUT_DTMF_STAR 0x1000001E
+#define ROUT_DTMF_0 0x10000010
+#define ROUT_DTMF_POUND 0x1000001F
+#define ROUT_DTMF_D 0x1000001D
+
+#if 0 
+#define cOCT6100_ECHO_OP_MODE_DIGITAL cOCT6100_ECHO_OP_MODE_HT_FREEZE
+#else
+#define cOCT6100_ECHO_OP_MODE_DIGITAL cOCT6100_ECHO_OP_MODE_POWER_DOWN
+#endif
+
+struct vpm450m {
+	tPOCT6100_INSTANCE_API pApiInstance;
+	UINT32 aulEchoChanHndl[ 128 ];
+	int chanflags[128];
+	int ecmode[128];
+	int numchans;
+};
+
+#define FLAG_DTMF	 (1 << 0)
+#define FLAG_MUTE	 (1 << 1)
+#define FLAG_ECHO	 (1 << 2)
+
+static unsigned int tones[] = {
+	SOUT_DTMF_1,
+	SOUT_DTMF_2,
+	SOUT_DTMF_3,
+	SOUT_DTMF_A,
+	SOUT_DTMF_4,
+	SOUT_DTMF_5,
+	SOUT_DTMF_6,
+	SOUT_DTMF_B,
+	SOUT_DTMF_7,
+	SOUT_DTMF_8,
+	SOUT_DTMF_9,
+	SOUT_DTMF_C,
+	SOUT_DTMF_STAR,
+	SOUT_DTMF_0,
+	SOUT_DTMF_POUND,
+	SOUT_DTMF_D,
+	SOUT_G168_1100GB_ON,
+
+	ROUT_DTMF_1,
+	ROUT_DTMF_2,
+	ROUT_DTMF_3,
+	ROUT_DTMF_A,
+	ROUT_DTMF_4,
+	ROUT_DTMF_5,
+	ROUT_DTMF_6,
+	ROUT_DTMF_B,
+	ROUT_DTMF_7,
+	ROUT_DTMF_8,
+	ROUT_DTMF_9,
+	ROUT_DTMF_C,
+	ROUT_DTMF_STAR,
+	ROUT_DTMF_0,
+	ROUT_DTMF_POUND,
+	ROUT_DTMF_D,
+	ROUT_G168_1100GB_ON,
+};
+
+static void vpm450m_setecmode(struct vpm450m *vpm450m, int channel, int mode)
+{
+	tOCT6100_CHANNEL_MODIFY *modify;
+	UINT32 ulResult;
+
+	if (vpm450m->ecmode[channel] == mode)
+		return;
+	modify = kmalloc(sizeof(tOCT6100_CHANNEL_MODIFY), GFP_ATOMIC);
+	if (!modify) {
+		printk(KERN_NOTICE "opvxd115: Unable to allocate memory for setec!\n");
+		return;
+	}
+	Oct6100ChannelModifyDef(modify);
+	modify->ulEchoOperationMode = mode;
+	modify->ulChannelHndl = vpm450m->aulEchoChanHndl[channel];
+	ulResult = Oct6100ChannelModify(vpm450m->pApiInstance, modify);
+	if (ulResult != GENERIC_OK) {
+		printk(KERN_NOTICE "Failed to apply echo can changes on channel %d!\n", channel);
+	} else {
+#ifdef OCTASIC_DEBUG
+		printk(KERN_DEBUG "Echo can on channel %d set to %d\n", channel, mode);
+#endif
+		vpm450m->ecmode[channel] = mode;
+	}
+	kfree(modify);
+}
+
+void vpm450m_setdtmf(struct vpm450m *vpm450m, int channel, int detect, int mute)
+{
+	tOCT6100_CHANNEL_MODIFY *modify;
+	UINT32 ulResult;
+
+	modify = kmalloc(sizeof(tOCT6100_CHANNEL_MODIFY), GFP_KERNEL);
+	if (!modify) {
+		printk(KERN_NOTICE "opvxd115: Unable to allocate memory for setdtmf!\n");
+		return;
+	}
+	Oct6100ChannelModifyDef(modify);
+	modify->ulChannelHndl = vpm450m->aulEchoChanHndl[channel];
+	if (mute) {
+		vpm450m->chanflags[channel] |= FLAG_MUTE;
+		modify->VqeConfig.fDtmfToneRemoval = TRUE;
+	} else {
+		vpm450m->chanflags[channel] &= ~FLAG_MUTE;
+		modify->VqeConfig.fDtmfToneRemoval = FALSE;
+	}
+	if (detect)
+		vpm450m->chanflags[channel] |= FLAG_DTMF;
+	else
+		vpm450m->chanflags[channel] &= ~FLAG_DTMF;
+	if (vpm450m->chanflags[channel] & (FLAG_DTMF|FLAG_MUTE)) {
+		if (!(vpm450m->chanflags[channel] & FLAG_ECHO)) {
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_HT_RESET);
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_HT_FREEZE);
+		}
+	} else {
+		if (!(vpm450m->chanflags[channel] & FLAG_ECHO))
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_DIGITAL);
+	}
+
+	ulResult = Oct6100ChannelModify(vpm450m->pApiInstance, modify);
+	if (ulResult != GENERIC_OK) {
+		printk(KERN_NOTICE "Failed to apply dtmf mute changes on channel %d!\n", channel);
+	}
+/*	printk(KERN_DEBUG "VPM450m: Setting DTMF on channel %d: %s / %s\n", channel, (detect ? "DETECT" : "NO DETECT"), (mute ? "MUTE" : "NO MUTE")); */
+	kfree(modify);
+}
+
+void vpm450m_setec(struct vpm450m *vpm450m, int channel, int eclen)
+{
+	if (eclen) {
+		vpm450m->chanflags[channel] |= FLAG_ECHO;
+		vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_HT_RESET);
+		vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_NORMAL);
+	} else {
+		vpm450m->chanflags[channel] &= ~FLAG_ECHO;
+		if (vpm450m->chanflags[channel] & (FLAG_DTMF | FLAG_MUTE)) {
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_HT_RESET);
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_HT_FREEZE);
+		} else
+			vpm450m_setecmode(vpm450m, channel, cOCT6100_ECHO_OP_MODE_DIGITAL);
+	}
+/*	printk(KERN_DEBUG "VPM450m: Setting EC on channel %d to %d\n", channel, eclen); */
+}
+
+int vpm450m_checkirq(struct vpm450m *vpm450m)
+{
+	tOCT6100_INTERRUPT_FLAGS InterruptFlags;
+	
+	Oct6100InterruptServiceRoutineDef(&InterruptFlags);
+	Oct6100InterruptServiceRoutine(vpm450m->pApiInstance, &InterruptFlags);
+
+	return InterruptFlags.fToneEventsPending ? 1 : 0;
+}
+
+int vpm450m_getdtmf(struct vpm450m *vpm450m, int *channel, int *tone, int *start)
+{
+	tOCT6100_TONE_EVENT tonefound;
+	tOCT6100_EVENT_GET_TONE tonesearch;
+	UINT32 ulResult;
+	
+	Oct6100EventGetToneDef(&tonesearch);
+	tonesearch.pToneEvent = &tonefound;
+	tonesearch.ulMaxToneEvent = 1;
+	ulResult = Oct6100EventGetTone(vpm450m->pApiInstance, &tonesearch);
+	if (tonesearch.ulNumValidToneEvent) {
+		if (channel)
+			*channel = tonefound.ulUserChanId;
+		if (tone) {
+			switch(tonefound.ulToneDetected) {
+			case SOUT_DTMF_1:
+				*tone = '1';
+				break;
+			case SOUT_DTMF_2:
+				*tone = '2';
+				break;
+			case SOUT_DTMF_3:
+				*tone = '3';
+				break;
+			case SOUT_DTMF_A:
+				*tone = 'A';
+				break;
+			case SOUT_DTMF_4:
+				*tone = '4';
+				break;
+			case SOUT_DTMF_5:
+				*tone = '5';
+				break;
+			case SOUT_DTMF_6:
+				*tone = '6';
+				break;
+			case SOUT_DTMF_B:
+				*tone = 'B';
+				break;
+			case SOUT_DTMF_7:
+				*tone = '7';
+				break;
+			case SOUT_DTMF_8:
+				*tone = '8';
+				break;
+			case SOUT_DTMF_9:
+				*tone = '9';
+				break;
+			case SOUT_DTMF_C:
+				*tone = 'C';
+				break;
+			case SOUT_DTMF_STAR:
+				*tone = '*';
+				break;
+			case SOUT_DTMF_0:
+				*tone = '0';
+				break;
+			case SOUT_DTMF_POUND:
+				*tone = '#';
+				break;
+			case SOUT_DTMF_D:
+				*tone = 'D';
+				break;
+			case SOUT_G168_1100GB_ON:
+				*tone = 'f';
+				break;
+			default:
+#ifdef OCTASIC_DEBUG
+				printk(KERN_DEBUG "Unknown tone value %08x\n", tonefound.ulToneDetected);
+#endif
+				*tone = 'u';
+				break;
+			}
+		}
+		if (start)
+			*start = (tonefound.ulEventType == cOCT6100_TONE_PRESENT);
+		return 1;
+	}
+	return 0;
+}
+
+unsigned int get_vpm450m_capacity(void *wc)
+{
+	UINT32 ulResult;
+
+	tOCT6100_API_GET_CAPACITY_PINS CapacityPins;
+
+	Oct6100ApiGetCapacityPinsDef(&CapacityPins);
+	CapacityPins.pProcessContext = wc;
+	CapacityPins.ulMemoryType = cOCT6100_MEM_TYPE_DDR;
+	CapacityPins.fEnableMemClkOut = TRUE;
+	CapacityPins.ulMemClkFreq = cOCT6100_MCLK_FREQ_133_MHZ;
+
+	ulResult = Oct6100ApiGetCapacityPins(&CapacityPins);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk(KERN_DEBUG "Failed to get chip capacity, code %08x!\n", ulResult);
+		return 0;
+	}
+
+	return CapacityPins.ulCapacityValue;
+}
+
+struct vpm450m *init_vpm450m(void *wc, int *isalaw, int numspans, const struct firmware *firmware)
+{
+	tOCT6100_CHIP_OPEN *ChipOpen;
+	tOCT6100_GET_INSTANCE_SIZE InstanceSize;
+	tOCT6100_CHANNEL_OPEN *ChannelOpen;
+	UINT32 ulResult;
+	struct vpm450m *vpm450m;
+	int x,y,law;
+#ifdef CONFIG_4KSTACKS
+	unsigned long flags;
+#endif
+	
+	if (!(vpm450m = kmalloc(sizeof(struct vpm450m), GFP_KERNEL)))
+		return NULL;
+
+	memset(vpm450m, 0, sizeof(struct vpm450m));
+
+	if (!(ChipOpen = kmalloc(sizeof(tOCT6100_CHIP_OPEN), GFP_KERNEL))) {
+		kfree(vpm450m);
+		return NULL;
+	}
+
+	memset(ChipOpen, 0, sizeof(tOCT6100_CHIP_OPEN));
+
+	if (!(ChannelOpen = kmalloc(sizeof(tOCT6100_CHANNEL_OPEN), GFP_KERNEL))) {
+		kfree(vpm450m);
+		kfree(ChipOpen);
+		return NULL;
+	}
+
+	memset(ChannelOpen, 0, sizeof(tOCT6100_CHANNEL_OPEN));
+
+	for (x=0;x<128;x++)
+		vpm450m->ecmode[x] = -1;
+
+	vpm450m->numchans = numspans * 32;
+	printk(KERN_INFO "VPM450: echo cancellation for %d channels\n", vpm450m->numchans);
+		
+	Oct6100ChipOpenDef(ChipOpen);
+
+	/* Setup Chip Open Parameters */
+	ChipOpen->ulUpclkFreq = cOCT6100_UPCLK_FREQ_33_33_MHZ;
+	Oct6100GetInstanceSizeDef(&InstanceSize);
+
+	ChipOpen->pProcessContext = wc;
+
+	ChipOpen->pbyImageFile = firmware->data;
+	ChipOpen->ulImageSize = firmware->size;
+	ChipOpen->fEnableMemClkOut = TRUE;
+	ChipOpen->ulMemClkFreq = cOCT6100_MCLK_FREQ_133_MHZ;
+	ChipOpen->ulMaxChannels = vpm450m->numchans;
+	ChipOpen->ulMemoryType = cOCT6100_MEM_TYPE_DDR;
+	ChipOpen->ulMemoryChipSize = cOCT6100_MEMORY_CHIP_SIZE_32MB;
+	ChipOpen->ulNumMemoryChips = 1;
+	ChipOpen->ulMaxTdmStreams = 4;
+	ChipOpen->aulTdmStreamFreqs[0] = cOCT6100_TDM_STREAM_FREQ_8MHZ;
+	ChipOpen->ulTdmSampling = cOCT6100_TDM_SAMPLE_AT_FALLING_EDGE;
+#if 0
+	ChipOpen->fEnableAcousticEcho = TRUE;
+#endif		
+
+	ulResult = Oct6100GetInstanceSize(ChipOpen, &InstanceSize);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk(KERN_NOTICE "Failed to get instance size, code %08x!\n", ulResult);
+		kfree(vpm450m);
+		kfree(ChipOpen);
+		kfree(ChannelOpen);
+		return NULL;
+	}
+	
+	
+	vpm450m->pApiInstance = vmalloc(InstanceSize.ulApiInstanceSize);
+	if (!vpm450m->pApiInstance) {
+		printk(KERN_NOTICE "Out of memory (can't allocate %d bytes)!\n", InstanceSize.ulApiInstanceSize);
+		kfree(vpm450m);
+		kfree(ChipOpen);
+		kfree(ChannelOpen);
+		return NULL;
+	}
+
+	/* I don't know what to curse more in this comment, the problems caused by
+	 * the 4K kernel stack limit change or the octasic API for being so darn
+	 * stack unfriendly.  Stupid, stupid, stupid.  So we disable IRQs so we
+	 * don't run the risk of overflowing the stack while we initialize the
+	 * octasic. */
+#ifdef CONFIG_4KSTACKS
+	local_irq_save(flags);
+#endif
+	ulResult = Oct6100ChipOpen(vpm450m->pApiInstance, ChipOpen);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk(KERN_NOTICE "Failed to open chip, code %08x!\n", ulResult);
+#ifdef CONFIG_4KSTACKS
+		local_irq_restore(flags);
+#endif
+		kfree(vpm450m);
+		kfree(ChipOpen);
+		kfree(ChannelOpen);
+		return NULL;
+	}
+	for (x=0;x<128;x++) {
+		if ((x & 0x03) < numspans) {
+			/* span timeslots are interleaved 12341234... 
+		 	*  therefore, the lower 2 bits tell us which span this 
+			*  timeslot/channel
+		 	*/
+			if (isalaw[x & 0x03]) 
+				law = cOCT6100_PCM_A_LAW;
+			else
+				law = cOCT6100_PCM_U_LAW;
+			Oct6100ChannelOpenDef(ChannelOpen);
+			ChannelOpen->pulChannelHndl = &vpm450m->aulEchoChanHndl[x];
+			ChannelOpen->ulUserChanId = x;
+			ChannelOpen->TdmConfig.ulRinPcmLaw = law;
+			ChannelOpen->TdmConfig.ulRinStream = 0;
+			ChannelOpen->TdmConfig.ulRinTimeslot = x;
+			ChannelOpen->TdmConfig.ulSinPcmLaw = law;
+			ChannelOpen->TdmConfig.ulSinStream = 1;
+			ChannelOpen->TdmConfig.ulSinTimeslot = x;
+			ChannelOpen->TdmConfig.ulSoutPcmLaw = law;
+			ChannelOpen->TdmConfig.ulSoutStream = 2;
+			ChannelOpen->TdmConfig.ulSoutTimeslot = x;
+			ChannelOpen->TdmConfig.ulRoutPcmLaw = law;
+			ChannelOpen->TdmConfig.ulRoutStream = 3;
+			ChannelOpen->TdmConfig.ulRoutTimeslot = x;
+			ChannelOpen->VqeConfig.fEnableNlp = TRUE;
+			ChannelOpen->VqeConfig.fRinDcOffsetRemoval = TRUE;
+			ChannelOpen->VqeConfig.fSinDcOffsetRemoval = TRUE;
+			
+			ChannelOpen->fEnableToneDisabler = TRUE;
+			ChannelOpen->ulEchoOperationMode = cOCT6100_ECHO_OP_MODE_DIGITAL;
+			
+			ulResult = Oct6100ChannelOpen(vpm450m->pApiInstance, ChannelOpen);
+			if (ulResult != GENERIC_OK) {
+				printk(KERN_NOTICE "Failed to open channel %d!\n", x);
+			}
+			for (y=0;y<sizeof(tones) / sizeof(tones[0]); y++) {
+				tOCT6100_TONE_DETECTION_ENABLE enable;
+				Oct6100ToneDetectionEnableDef(&enable);
+				enable.ulChannelHndl = vpm450m->aulEchoChanHndl[x];
+				enable.ulToneNumber = tones[y];
+				if (Oct6100ToneDetectionEnable(vpm450m->pApiInstance, &enable) != GENERIC_OK) 
+					printk(KERN_NOTICE "Failed to enable tone detection on channel %d for tone %d!\n", x, y);
+			}
+		}
+	}
+
+#ifdef CONFIG_4KSTACKS
+	local_irq_restore(flags);
+#endif
+	kfree(ChipOpen);
+	kfree(ChannelOpen);
+	return vpm450m;
+}
+
+void release_vpm450m(struct vpm450m *vpm450m)
+{
+	UINT32 ulResult;
+	tOCT6100_CHIP_CLOSE ChipClose;
+
+	Oct6100ChipCloseDef(&ChipClose);
+	ulResult = Oct6100ChipClose(vpm450m->pApiInstance, &ChipClose);
+	if (ulResult != cOCT6100_ERR_OK) {
+		printk(KERN_NOTICE "Failed to close chip, code %08x!\n", ulResult);
+	}
+	vfree(vpm450m->pApiInstance);
+	kfree(vpm450m);
+}
diff --git a/drivers/dahdi/opvxd115/vpm450m.h b/drivers/dahdi/opvxd115/vpm450m.h
new file mode 100644
index 0000000..735a2cc
--- /dev/null
+++ b/drivers/dahdi/opvxd115/vpm450m.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2006 Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * All Rights Reserved
+ *
+ */
+
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#ifndef _VPM450M_H
+#define _VPM450M_H
+
+#include <linux/firmware.h>
+
+struct vpm450m;
+
+/* From driver */
+unsigned int oct_get_reg(void *data, unsigned int reg);
+void oct_set_reg(void *data, unsigned int reg, unsigned int val);
+
+/* From vpm450m */
+struct vpm450m *init_vpm450m(void *wc, int *isalaw, int numspans, const struct firmware *firmware);
+unsigned int get_vpm450m_capacity(void *wc);
+void vpm450m_setec(struct vpm450m *instance, int channel, int eclen);
+void vpm450m_setdtmf(struct vpm450m *instance, int channel, int dtmfdetect, int dtmfmute);
+int vpm450m_checkirq(struct vpm450m *vpm450m);
+int vpm450m_getdtmf(struct vpm450m *vpm450m, int *channel, int *tone, int *start);
+void release_vpm450m(struct vpm450m *instance);
+
+#endif
diff --git a/drivers/dahdi/wcopenpci.c b/drivers/dahdi/wcopenpci.c
new file mode 100644
index 0000000..214d396
--- /dev/null
+++ b/drivers/dahdi/wcopenpci.c
@@ -0,0 +1,1868 @@
+/*
+ * Voicetronix OpenPCI Interface Driver for Zapata Telephony interface
+ *
+ * Written by Mark Spencer <markster@linux-support.net>
+ *            Matthew Fredrickson <creslin@linux-support.net>
+ *            Ben Kramer <ben@voicetronix.com.au>
+ *            Ron Lee <ron@voicetronix.com.au>
+ *
+ * Copyright (C) 2001, Linux Support Services, Inc.
+ * Copyright (C) 2005 - 2007, Voicetronix
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
+ *
+ */
+
+/* Conditional debug options */
+#define VERBOSE_TIMING 0
+
+/* Driver constants */
+#define DRIVER_DESCRIPTION  "Voicetronix OpenPCI DAHDI driver"
+#define DRIVER_AUTHOR       "Mark Spencer <markster@digium.com> "\
+                            "Voicetronix <support@voicetronix.com.au>"
+
+#define NAME      "wcopenpci"
+#define MAX_PORTS 8	    /* Maximum number of ports on each carrier */
+#define MAX_CARDS 8	    /* Maximum number of carriers per host */
+
+#define DEFAULT_COUNTRY  "AUSTRALIA"
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <dahdi/kernel.h>
+#include <dahdi/version.h>
+#include "proslic.h"
+#include <dahdi/wctdm_user.h>
+
+
+
+/* Compatibility helpers */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
+ #include <linux/interrupt.h>
+#else
+ typedef void irqreturn_t;
+ #define IRQ_NONE
+ #define IRQ_HANDLED
+ #define IRQ_RETVAL(x)
+ #define __devexit_p(x) x
+#endif
+
+// Centos4.3 uses a modified 2.6.9 kernel, with no indication that
+// it is different from the mainstream (or even Centos4.2 2.6.9)
+// kernel, so we must crowbar off the dunce-hat manually here.
+#if !defined CENTOS4_3 && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
+ typedef int gfp_t;
+ static inline void *kzalloc( size_t n, gfp_t flags ){
+	void *p = kmalloc(n,flags);
+	if (p) memset(p, 0, n);
+	return p;
+ }
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16)
+ #define DEFINE_MUTEX(x)		DECLARE_MUTEX(x)
+ #define mutex_init(x)			init_MUTEX(x)
+ #define mutex_lock(x)			down(x)
+ #define mutex_lock_interruptible(x)	down_interruptible(x)
+ #define mutex_trylock(x)		down_trylock(x)
+ #define mutex_unlock(x)		up(x)
+#else
+ #include <linux/mutex.h>
+#endif
+
+
+static struct fxo_mode {
+	char *name;
+	int ohs;
+	int ohs2;
+	int rz;
+	int rt;
+	int ilim;
+	int dcv;
+	int mini;
+	int acim;
+	int ring_osc;
+	int ring_x;
+} fxo_modes[] =
+{
+	{ "FCC", 0, 0, 0, 1, 0, 0x3, 0, 0, }, 	/* US, Canada */
+	{ "TBR21", 0, 0, 0, 0, 1, 0x3, 0, 0x2, 0x7e6c, 0x023a, },
+		/* Austria, Belgium, Denmark, Finland, France, Germany, 
+		   Greece, Iceland, Ireland, Italy, Luxembourg, Netherlands,
+		   Norway, Portugal, Spain, Sweden, Switzerland, and UK */
+	{ "ARGENTINA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "AUSTRALIA", 1, 0, 0, 0, 0, 0, 0x3, 0x3, },
+	{ "AUSTRIA", 0, 1, 0, 0, 1, 0x3, 0, 0x3, },
+	{ "BAHRAIN", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "BELGIUM", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "BRAZIL", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "BULGARIA", 0, 0, 0, 0, 1, 0x3, 0x0, 0x3, },
+	{ "CANADA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "CHILE", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "CHINA", 0, 0, 0, 0, 0, 0, 0x3, 0xf, },
+	{ "COLUMBIA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "CROATIA", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "CYRPUS", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "CZECH", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "DENMARK", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "ECUADOR", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "EGYPT", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "ELSALVADOR", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "FINLAND", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "FRANCE", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "GERMANY", 0, 1, 0, 0, 1, 0x3, 0, 0x3, },
+	{ "GREECE", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "GUAM", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "HONGKONG", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "HUNGARY", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "ICELAND", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "INDIA", 0, 0, 0, 0, 0, 0x3, 0, 0x4, },
+	{ "INDONESIA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "IRELAND", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "ISRAEL", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "ITALY", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "JAPAN", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "JORDAN", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "KAZAKHSTAN", 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "KUWAIT", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "LATVIA", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "LEBANON", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "LUXEMBOURG", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "MACAO", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "MALAYSIA", 0, 0, 0, 0, 0, 0, 0x3, 0, },	/* Current loop >= 20ma */
+	{ "MALTA", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "MEXICO", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "MOROCCO", 0, 0, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "NETHERLANDS", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "NEWZEALAND", 0, 0, 0, 0, 0, 0x3, 0, 0x4, },
+	{ "NIGERIA", 0, 0, 0, 0, 0x1, 0x3, 0, 0x2, },
+	{ "NORWAY", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "OMAN", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "PAKISTAN", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "PERU", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "PHILIPPINES", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "POLAND", 0, 0, 1, 1, 0, 0x3, 0, 0, },
+	{ "PORTUGAL", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "ROMANIA", 0, 0, 0, 0, 0, 3, 0, 0, },
+	{ "RUSSIA", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "SAUDIARABIA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "SINGAPORE", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "SLOVAKIA", 0, 0, 0, 0, 0, 0x3, 0, 0x3, },
+	{ "SLOVENIA", 0, 0, 0, 0, 0, 0x3, 0, 0x2, },
+	{ "SOUTHAFRICA", 1, 0, 1, 0, 0, 0x3, 0, 0x3, },
+	{ "SOUTHKOREA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "SPAIN", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "SWEDEN", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "SWITZERLAND", 0, 1, 0, 0, 1, 0x3, 0, 0x2, },
+	{ "SYRIA", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "TAIWAN", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "THAILAND", 0, 0, 0, 0, 0, 0, 0x3, 0, },
+	{ "UAE", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "UK", 0, 1, 0, 0, 1, 0x3, 0, 0x5, },
+	{ "USA", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+	{ "YEMEN", 0, 0, 0, 0, 0, 0x3, 0, 0, },
+};
+
+static struct ps_country_reg {
+	const char *country;
+	unsigned short value;
+} ps_country_regs[] = {
+	{"ARGENTINA",  0x8},
+	{"AUSTRALIA",  0xD},
+	{"AUSTRIA",    0xD},
+	{"BAHRAIN",    0xC},
+	{"BELGIUM",    0xC},
+	{"BRAZIL",     0x8},
+	{"BULGARIA",   0xD},
+	{"CANADA",     0x8},
+	{"CHILE",      0x8},
+	{"CHINA",      0xC},
+	{"COLOMBIA",   0x8},
+	{"CROATIA",    0xC},
+	{"CYPRUS",     0xC},
+	{"CZECH",      0xC},
+	{"DENMARK",    0xC},
+	{"ECUADOR",    0x8},
+	{"EGYPT",      0x8},
+	{"ELSALVADOR", 0x8},
+	{"FINLAND",    0xC},
+	{"FRANCE",     0xC},
+	{"GERMANY",    0xD},
+	{"GREECE",     0xC},
+	{"GUAM",       0x8},
+	{"HONGKONG",   0x8},
+	{"HUNGARY",    0x8},
+	{"ICELAND",    0xC},
+	{"INDIA",      0xF},
+	{"INDONESIA",  0x8},
+	{"IRELAND",    0xC},
+	{"ISRAEL",     0xC},
+	{"ITALY",      0xC},
+	{"JAPAN",      0x8},
+	{"JORDAN",     0x8},
+	{"KAZAKHSTAN", 0x8},
+	{"KUWAIT",     0x8},
+	{"LATVIA",     0xC},
+	{"LEBANON",    0xC},
+	{"LUXEMBOURG", 0xC},
+	{"MACAO",      0x8},
+	{"MALAYSIA",   0x8},
+	{"MALTA",      0xC},
+	{"MEXICO",     0x8},
+	{"MOROCCO",    0xC},
+	{"NETHERLANDS",0xC},
+	{"NEWZEALAND", 0xF},
+	{"NIGERIA",    0xC},
+	{"NORWAY",     0xC},
+	{"OMAN",       0x8},
+	{"PAKISTAN",   0x8},
+	{"PERU",       0x8},
+	{"PHILIPPINES",0x8},
+	{"POLAND",     0x8},
+	{"PORTUGAL",   0xC},
+	{"ROMANIA",    0x8},
+	{"RUSSIA",     0x8},
+	{"SAUDIARABIA",0x8},
+	{"SINGAPORE",  0x8},
+	{"SLOVAKIA",   0xE},
+	{"SLOVENIA",   0xE},
+	{"SOUTHAFRICA",0xE},
+	{"SOUTHKOREA", 0x8},
+	{"SPAIN",      0xC},
+	{"SWEDEN",     0xC},
+	{"SWITZERLAND",0xC},
+	{"SYRIA",      0x8},
+	{"TAIWAN",     0x8},
+	{"THAILAND",   0x8},
+	{"UAE",        0x8},
+	{"UK",         0xC},
+	{"USA",        0x8},
+	{"YEMEN",      0x8}
+};
+
+#define INOUT 2
+
+/* Allocate enough memory for two zt chunks, receive and transmit.  Each sample uses
+   32 bits.  Allocate an extra set just for control too */
+#define VT_PCIDMA_BLOCKSIZE (DAHDI_MAX_CHUNKSIZE * INOUT * MAX_PORTS * 2 * 2)
+#define VT_PCIDMA_MIDDLE    (DAHDI_MAX_CHUNKSIZE * MAX_PORTS - 4)
+#define VT_PCIDMA_END       (DAHDI_MAX_CHUNKSIZE * MAX_PORTS * 2 - 4)
+
+#define ID_DATA_MAXSIZE         30
+
+#define NUM_CAL_REGS 12
+#define NUM_FXO_REGS 60
+
+#define TREG(addr)      (wc->ioaddr + addr)
+
+#define TJ_CNTL         TREG(0x00)
+#define TJ_OPER         TREG(0x01)
+#define TJ_AUXC         TREG(0x02)
+#define TJ_AUXD         TREG(0x03)
+#define TJ_MASK0        TREG(0x04)
+#define TJ_MASK1        TREG(0x05)
+#define TJ_INTSTAT      TREG(0x06)
+#define TJ_AUXR         TREG(0x07)
+
+#define TJ_DMAWS        TREG(0x08)
+#define TJ_DMAWI        TREG(0x0c)
+#define TJ_DMAWE        TREG(0x10)
+#define TJ_DMAWC        TREG(0x14)
+#define TJ_DMARS        TREG(0x18)
+#define TJ_DMARI        TREG(0x1c)
+#define TJ_DMARE        TREG(0x20)
+#define TJ_DMARC        TREG(0x24)
+
+#define TJ_AUXINTPOL    TREG(0x2A)
+
+#define TJ_AUXFUNC      TREG(0x2b)
+#define TJ_SFDELAY      TREG(0x2c)
+#define TJ_SERCTL       TREG(0x2d)
+#define TJ_SFLC         TREG(0x2e)
+#define TJ_FSCDELAY     TREG(0x2f)
+
+#define TJ_REGBASE      TREG(0xc0)
+
+#define PIB(addr)       (TJ_REGBASE + addr * 4)
+
+#define HTXF_READY      (inb(PIB(0)) & 0x10)
+#define HRXF_READY      (inb(PIB(0)) & 0x20)
+
+
+#define VT_PORT_EMPTY	0
+#define VT_PORT_VDAA	1   /* Voice DAA - FXO */
+#define VT_PORT_PROSLIC	2   /* ProSLIC - FXS */
+
+#define VBAT 0xC7
+
+#define HKMODE_FWDACT   1
+#define HKMODE_FWDONACT	2
+#define HKMODE_RINGING	4
+
+#define HOOK_ONHOOK     0
+#define HOOK_OFFHOOK    1
+
+#define	DSP_CODEC_RING		12	/* RING rising edge detected		*/
+#define	DSP_CODEC_HKOFF		22	/* station port off hook                */
+#define	DSP_CODEC_HKON		23	/* station port on hook                 */
+#define	DSP_RING_OFF		24	/* RING falling edge detected		*/
+#define DSP_DROP		25
+
+#define	DSP_CODEC_FLASH		26	/* station port hook flash              */
+
+#define DSP_LOOP_OFFHOOK	38	/* Loop Off hook from OpenPCI           */
+#define DSP_LOOP_ONHOOK		39	/* Loop On hook from OpenPCI            */
+#define DSP_LOOP_POLARITY	40	/* Loop Polarity from OpenPCI           */
+#define DSP_LOOP_NOBATT		41
+
+#define DSP_PROSLIC_SANITY	50	/* Sanity alert from a ProSLIC port 	*/
+#define DSP_PROSLIC_PWR_ALARM	51	/* Power Alarm from a ProSLIC port 	*/
+#define DSP_VDAA_ISO_FRAME_E	52	/* ISO-cap frame sync lost on VDAA port*/
+
+#if VERBOSE_TIMING
+ #define REPORT_WAIT(n,x)						    \
+	 cardinfo(card->cardnum, #n " wait at %d, " #x " = %d", __LINE__, x )
+#else
+ #define REPORT_WAIT(n,x)
+#endif
+
+#define BUSY_WAIT(countvar,cond,delay,iter,failret)			    \
+	countvar=0;							    \
+	while(cond){							    \
+	    udelay(delay);						    \
+	    if(++countvar > iter){					    \
+		cardcrit(wc->boardnum, "busy wait FAILED at %d", __LINE__); \
+		return failret;						    \
+	    }								    \
+	}								    \
+	REPORT_WAIT(busy,i)
+
+#define LOCKED_WAIT(countvar,cond,delay,iter,failret)			    \
+	countvar=0;							    \
+	while(cond){							    \
+	    udelay(delay);						    \
+	    if(++countvar > iter){					    \
+		dbginfo(wc->boardnum,"busy wait failed at %d",__LINE__);    \
+		spin_unlock_irqrestore(&wc->lock, flags);		    \
+		return failret;						    \
+	    }								    \
+	}								    \
+	REPORT_WAIT(locked,i)
+
+#define HTXF_WAIT()                 BUSY_WAIT(i,HTXF_READY,5,500,RET_FAIL)
+#define HRXF_WAIT()                 BUSY_WAIT(i,!HRXF_READY,5,70000,RET_FAIL)
+#define HTXF_WAIT_RET(failret)      BUSY_WAIT(i,HTXF_READY,5,500,failret)
+#define HRXF_WAIT_RET(failret)      BUSY_WAIT(i,!HRXF_READY,5,1000,failret)
+
+#define HTXF_WAIT_LOCKED()	    LOCKED_WAIT(i,HTXF_READY,5,500,RET_FAIL)
+#define HRXF_WAIT_LOCKED()	    LOCKED_WAIT(i,!HRXF_READY,5,1000,RET_FAIL)
+#define HTXF_WAIT_LOCKED_RET(failret) LOCKED_WAIT(i,HTXF_READY,5,500,failret)
+#define HRXF_WAIT_LOCKED_RET(failret) LOCKED_WAIT(i,!HRXF_READY,5,1000,failret)
+
+
+struct openpci {
+	struct pci_dev *dev;
+	char *variety;
+	int boardnum;
+	int portcount;
+	int porttype[MAX_PORTS];
+
+        int firmware;
+        char serial[ID_DATA_MAXSIZE];
+
+	spinlock_t lock;
+
+	//XXX Replace these with proper try_module_get locking in the dahdi driver.
+	//int usecount;	//XXX
+	//int dead;	//XXX
+	union {
+		struct {
+			int offhook;
+		} fxo;
+		struct {
+			int ohttimer;
+			int idletxhookstate;  /* IDLE changing hook state */
+			int lasttxhook;
+		} fxs;
+	} mod[MAX_PORTS];
+
+	unsigned long		ioaddr;
+	dma_addr_t		readdma;
+	dma_addr_t		writedma;
+	volatile unsigned int  *writechunk;  /* Double-word aligned write memory */
+	volatile unsigned int  *readchunk;   /* Double-word aligned read memory */
+
+	struct dahdi_chan _chans[MAX_PORTS];
+	struct dahdi_chan *chans[MAX_PORTS];
+	struct dahdi_device *ddev;
+	struct dahdi_span span;
+} *cards[MAX_CARDS];
+
+// You must hold this lock anytime you access or modify the cards[] array.
+DEFINE_MUTEX(cards_mutex);
+
+static unsigned char fxo_port_lookup[8] = { 0x0, 0x8, 0x4, 0xc, 0x10, 0x18, 0x14, 0x1c};
+static unsigned char fxs_port_lookup[8] = { 0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13};
+static char wcopenpci[] = "Voicetronix OpenPCI";
+
+static char *country = DEFAULT_COUNTRY;
+static int reversepolarity;  // = 0
+static int debug;            // = 0
+
+module_param(country, charp, 0444);
+module_param(debug, int, 0600);
+module_param(reversepolarity, int, 0600);
+MODULE_PARM_DESC(country, "Set the default country name");
+MODULE_PARM_DESC(debug, "Enable verbose logging");
+
+//#define DEBUG_LOOP_VOLTAGE 1
+#ifdef DEBUG_LOOP_VOLTAGE
+ // This param is a 32 bit bitfield where bit 1 << cardnum * 8 << portnum
+ // will enable voltage monitoring on that port (fxo only presently)
+ static int voltmeter;        // = 0
+ module_param(voltmeter, int, 0600);
+ MODULE_PARM_DESC(voltmeter, "Enable loop voltage metering");
+#endif
+
+
+/* boolean return values */
+#define RET_OK   1
+#define RET_FAIL 0
+
+/* Convenience macros for logging */
+#define info(format,...) printk(KERN_INFO NAME ": " format "\n" , ## __VA_ARGS__)
+#define warn(format,...) printk(KERN_WARNING NAME ": " format "\n" , ## __VA_ARGS__)
+#define crit(format,...) printk(KERN_CRIT NAME ": " format "\n" , ## __VA_ARGS__)
+#define cardinfo(cardnum,format,...) info("[%02d] " format, cardnum , ## __VA_ARGS__)
+#define cardwarn(cardnum,format,...) warn("[%02d] " format, cardnum , ## __VA_ARGS__)
+#define cardcrit(cardnum,format,...) crit("[%02d] " format, cardnum , ## __VA_ARGS__)
+#define dbginfo(cardnum,format,...) if(debug) info("[%02d] " format, cardnum , ## __VA_ARGS__)
+
+
+static inline const char *porttype(struct openpci *wc, int port)
+{ //{{{
+	switch( wc->porttype[port] ) {
+	    case VT_PORT_VDAA:    return "VDAA";
+	    case VT_PORT_PROSLIC: return "ProSLIC";
+	    case VT_PORT_EMPTY:   return "empty port";
+	    default:              return "unknown type";
+	}
+} //}}}
+
+
+static int __read_reg_fxo(struct openpci *wc, int port, unsigned char reg, unsigned char *value)
+{ //{{{
+	unsigned char portadr = fxo_port_lookup[port];
+	int i;
+
+	if (HRXF_READY) *value = inb(PIB(1));
+
+	outb(0x11, PIB(1));    HTXF_WAIT();
+	outb(0x2, PIB(1));     HTXF_WAIT();
+	outb(portadr, PIB(1)); HTXF_WAIT();
+	outb(reg, PIB(1));     HTXF_WAIT();
+	HRXF_WAIT(); *value = inb(PIB(1));
+
+	return RET_OK;
+} //}}}
+
+static int read_reg_fxo(struct openpci *wc, int port, unsigned char reg, unsigned char *value)
+{ //{{{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __read_reg_fxo(wc, port, reg, value) ){
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	cardcrit(wc->boardnum, "FXO port %d, reg %d, read failed!", port, reg);
+	return RET_FAIL;
+} //}}}
+
+static int __read_reg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned char *value)
+{ //{{{
+	unsigned char portadr = fxs_port_lookup[port];
+	int i;
+
+	if (HRXF_READY) *value = inb(PIB(1));
+
+	outb(0x13, PIB(1));    HTXF_WAIT();
+	outb(0x2, PIB(1));     HTXF_WAIT();
+	outb(portadr, PIB(1)); HTXF_WAIT();
+	outb(reg, PIB(1));     HTXF_WAIT();
+	HRXF_WAIT(); *value = inb(PIB(1));
+
+	return RET_OK;
+} //}}}
+
+static int read_reg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned char *value)
+{ //{{{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __read_reg_fxs(wc, port, reg, value) ) {
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	cardcrit(wc->boardnum, "FXS port %d, reg %d, read failed!", port, reg);
+	return RET_FAIL;
+} //}}}
+
+static int __write_reg_fxo(struct openpci *wc, int port, unsigned char reg, unsigned char value)
+{ //{{{
+	unsigned char portadr = fxo_port_lookup[port];
+	int i;
+
+        outb(0x10, PIB(1) );   HTXF_WAIT();
+        outb(0x3, PIB(1));     HTXF_WAIT();
+        outb(portadr, PIB(1)); HTXF_WAIT();
+        outb(reg, PIB(1));     HTXF_WAIT();
+        outb(value, PIB(1));   HTXF_WAIT();
+
+	return RET_OK;
+} //}}}
+
+static int write_reg_fxo(struct openpci *wc, int port, unsigned char reg, unsigned char value)
+{ //{{{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __write_reg_fxo(wc, port, reg, value) ){
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	cardcrit(wc->boardnum, "FXO port %d, reg %d, write(%d) failed!", port, reg, value);
+	return RET_FAIL;
+} //}}}
+
+static int __write_reg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned char value)
+{ //{{{
+	unsigned char portadr = fxs_port_lookup[port];
+	int i;
+
+        outb(0x12, PIB(1) );   HTXF_WAIT();
+        outb(0x3, PIB(1));     HTXF_WAIT();
+        outb(portadr, PIB(1)); HTXF_WAIT();
+        outb(reg, PIB(1));     HTXF_WAIT();
+        outb(value, PIB(1));   HTXF_WAIT();
+
+	return RET_OK;
+} //}}}
+
+static int write_reg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned char value)
+{ //{{{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __write_reg_fxs(wc, port, reg, value) ){
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	cardcrit(wc->boardnum, "FXS port %d, reg %d, write(%d) failed!", port, reg, value);
+	return RET_FAIL;
+} //}}}
+
+static int __wait_indreg_fxs(struct openpci *wc, int port)
+{ //{{{
+	unsigned char value;
+	int count = 100;
+
+	while (--count)
+	{
+		if( __read_reg_fxs(wc, port, I_STATUS, &value) ){
+			if( value == 0 )
+				return RET_OK;
+		} else {
+			cardcrit(wc->boardnum,
+				 "failed to read port %d PS_IND_ADDR_ST, retrying...",
+				 port);
+		}
+		udelay(5);
+	}
+	cardcrit(wc->boardnum, "Failed to wait for indirect reg write to port %d", port);
+	return RET_FAIL;
+} //}}}
+
+static int write_indreg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned short value)
+{ //{{{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __wait_indreg_fxs(wc, port)
+	 && __write_reg_fxs(wc, port, IDA_LO, value & 0xff)
+	 && __write_reg_fxs(wc, port, IDA_HI, (value & 0xff00)>>8)
+	 && __write_reg_fxs(wc, port, IAA, reg)
+	 && __wait_indreg_fxs(wc, port) )
+	{
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	cardcrit(wc->boardnum, "FXS indreg %d write failed on port %d", reg, port);
+	return RET_FAIL;
+} //}}}
+
+static int read_indreg_fxs(struct openpci *wc, int port, unsigned char reg, unsigned short *value)
+{ //{{{
+	unsigned long flags;
+	unsigned char lo, hi;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	if( __wait_indreg_fxs(wc, port)
+	 && __write_reg_fxs(wc, port, IAA, reg)
+	 && __wait_indreg_fxs(wc, port)
+	 && __read_reg_fxs(wc, port, IDA_LO, &lo)
+	 && __read_reg_fxs(wc, port, IDA_HI, &hi) )
+	{
+		*value = lo | hi << 8;
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return RET_OK;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return RET_FAIL;
+} //}}}
+
+static void start_dma(struct openpci *wc)
+{ //{{{
+	outb(0x0f, TJ_CNTL);
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout(1);
+	outb(0x01, TJ_CNTL);
+	outb(0x01, TJ_OPER);
+} //}}}
+
+static void restart_dma(struct openpci *wc)
+{ //{{{
+	/* Reset Master and TDM */
+	outb(0x01, TJ_CNTL);
+	outb(0x01, TJ_OPER);
+} //}}}
+
+/* You must hold the card spinlock to call this function */
+static int __ping_arm(struct openpci *wc)
+{ //{{{
+	int i;
+	int pong=0;
+
+	while(pong != 0x02){
+		outb(0x02, PIB(1)); HTXF_WAIT();
+		HRXF_WAIT(); pong = inb(PIB(1));
+		dbginfo(wc->boardnum, "ping_arm returned %x", pong);
+	}
+	while(pong == 0x02){
+		// Poke no-ops into the arm while it is still returning data,
+		// if 500 usec elapses with no further response from it then
+		// the message queue is should be completely cleared.
+		outb(0x00, PIB(1)); HTXF_WAIT();
+		i = 100;
+		while( !HRXF_READY && --i ) udelay(5);
+		if( i == 0 ) break;
+		pong = inb(PIB(1));
+		dbginfo(wc->boardnum, "ping_arm returned %x.", pong);
+	}
+	return RET_OK;
+} //}}}
+
+static void arm_event(struct openpci *wc, char *msg)
+{ //{{{
+	int port = msg[0];
+
+	switch(msg[1]){
+		case DSP_LOOP_OFFHOOK:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_OFFHOOK);
+			dbginfo(wc->boardnum, "Port %d Loop OffHook", port);
+			break;
+
+		case DSP_LOOP_ONHOOK:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_ONHOOK);
+			dbginfo(wc->boardnum, "Port %d Loop OnHook", port);
+			break;
+
+		case DSP_LOOP_POLARITY:
+			dahdi_qevent_lock(wc->chans[port], DAHDI_EVENT_POLARITY);
+			dbginfo(wc->boardnum, "Port %d Loop Polarity", port);
+			break;
+
+		case DSP_CODEC_RING:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_RING);
+			dbginfo(wc->boardnum, "Port %d Ring On", port);
+			break;
+
+		case DSP_RING_OFF:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_OFFHOOK);
+			dbginfo(wc->boardnum, "Port %d Ring Off", port);
+			break;
+
+		case DSP_CODEC_HKOFF:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_OFFHOOK);
+			dbginfo(wc->boardnum, "Port %d Station OffHook", port);
+			if (reversepolarity)
+				wc->mod[port].fxs.idletxhookstate = 5;
+			else
+				wc->mod[port].fxs.idletxhookstate = 1;
+			break;
+
+		case DSP_CODEC_HKON:
+			dahdi_hooksig(wc->chans[port], DAHDI_RXSIG_ONHOOK);
+			dbginfo(wc->boardnum, "Port %d Station OnHook", port);
+			if (reversepolarity)
+				wc->mod[port].fxs.idletxhookstate = 6;
+			else
+				wc->mod[port].fxs.idletxhookstate = 2;
+			break;
+
+		case DSP_CODEC_FLASH:
+			dahdi_qevent_lock(wc->chans[port], DAHDI_EVENT_WINKFLASH);
+			dbginfo(wc->boardnum, "Port %d Station Flash", port);
+			break;
+
+		case DSP_DROP:
+		case DSP_LOOP_NOBATT:
+			break;
+
+			//XXX What to do to recover from these?
+		case DSP_PROSLIC_SANITY:
+			dbginfo(wc->boardnum, "Port %d ProSlic has gone insane!", port);
+			break;
+
+		case DSP_PROSLIC_PWR_ALARM:
+		{
+			char errbuf[32] = " Unknown", *p = errbuf;
+			int i = 49;
+
+			msg[2] >>= 2;
+			for(; i < 55; ++i, msg[2] >>= 1 )
+			    if(msg[2] & 1){ *(++p)='Q'; *(++p)=i; *(++p)=','; }
+			if( p != errbuf ) *p = '\0';
+			cardcrit(wc->boardnum,"%d: ProSlic power ALARM:%s",msg[0],errbuf);
+			//write_reg_fxs(wc, port, 64, wc->mod[port].fxs.lasttxhook );
+			return;
+		}
+
+		case DSP_VDAA_ISO_FRAME_E:
+			dbginfo(wc->boardnum, "Port %d VDAA has lost ISO-Cap frame lock", port);
+			break;
+
+		default:
+			cardwarn(wc->boardnum, "Unknown message from Arm[%d] for port %d",
+						msg[1], port);
+			break;
+	}
+} //}}}
+
+/* You must hold the card spinlock to call this function */
+static inline int __read_arm_byte( struct openpci *wc, unsigned char *msg )
+{ //{{{
+	int i;
+
+	HRXF_WAIT(); *msg = inb(PIB(1));
+	return RET_OK;
+} //}}}
+
+static inline int read_arm_msg( struct openpci *wc, unsigned char *msg )
+{ //{{{
+	unsigned long flags;
+	int i, d, count;
+	int ret = RET_OK;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0x08, PIB(1)); HTXF_WAIT_LOCKED();
+	//XXX Do we need to clear the interrupt flag even if this fails?
+	HRXF_WAIT_LOCKED(); count = inb(PIB(1));
+	if( count == 0 ){
+		ret = RET_FAIL;
+	} else if( count < 3 || count > 4 ){
+		cardcrit(wc->boardnum, "BOGUS arm message size %d, flushing queue", count);
+		// NB: This may take a while (up to 500usec or more) to complete
+		//     and we are in the isr at present when this is called, so
+		//     we may miss an interrupt or two while this is done in the
+		//     bottom half, but we are already in trouble, so...
+		d = debug; debug = 5; __ping_arm( wc ); debug = d;
+		ret = RET_FAIL;
+	} else while( --count ){
+		if( ! __read_arm_byte(wc, msg) ){
+			cardcrit(wc->boardnum,
+				 "Failed to read arm message %d more bytes expected",
+				 count);
+			ret = RET_FAIL;
+			break;
+		}
+		++msg;
+	}
+	outb(0x09, PIB(1)); HTXF_WAIT_LOCKED();
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return ret;
+} //}}}
+
+static void openpci_arm_work( void *cardptr )
+{ //{{{
+	struct openpci *wc = (struct openpci*)cardptr;
+	unsigned char armmsg[4];
+
+	if( read_arm_msg(wc, armmsg) ) arm_event(wc, armmsg);
+} //}}}
+
+
+static inline void openpci_write(struct openpci *wc, unsigned char flags)
+{ //{{{
+	int x,y;
+	volatile unsigned int *writechunk;
+
+	if (flags & 0x01)
+		writechunk = wc->writechunk;
+	else if (flags & 0x02)
+		writechunk = wc->writechunk + DAHDI_CHUNKSIZE*2;
+	else {
+		cardcrit(wc->boardnum, "bad write interrupt flags %x, at %x",
+					flags, inb(TJ_DMAWC) );
+		return;
+	}
+	/* get data */
+	dahdi_transmit(&wc->span);
+	for (y=0,x=0;x<DAHDI_CHUNKSIZE;++x) {
+		/* Send a sample, as a 32-bit word */
+#ifdef __BIG_ENDIAN
+#warning No big endian support (yet)
+#else
+		/* transmit second 4 ports */
+		writechunk[y]=0;
+		if (wc->porttype[4])
+			writechunk[y] |= (wc->chans[4]->writechunk[x] << 24);
+		else
+			writechunk[y] |= (0x01 << 24);
+		if (wc->porttype[5])
+			writechunk[y] |= (wc->chans[5]->writechunk[x] << 16);
+		if (wc->porttype[6])
+			writechunk[y] |= (wc->chans[6]->writechunk[x] << 8);
+		if (wc->porttype[7])
+			writechunk[y] |= (wc->chans[7]->writechunk[x]);
+		++y;
+
+		/* transmit first 4 ports */
+		writechunk[y]=0x01000000;
+		/* Make sure first port doesnt equal 0x00 */
+		if (wc->porttype[0]){
+			if (wc->chans[0]->writechunk[x] == 0)
+				writechunk[y] |= (0x01 << 24);
+			else
+				writechunk[y] |= (wc->chans[0]->writechunk[x] << 24);
+		}
+		//else writechunk[y] |= (0x00 << 24);
+		if (wc->porttype[1])
+			writechunk[y] |= (wc->chans[1]->writechunk[x] << 16);
+		if (wc->porttype[2])
+			writechunk[y] |= (wc->chans[2]->writechunk[x] << 8);
+		if (wc->porttype[3])
+			writechunk[y] |= (wc->chans[3]->writechunk[x]);
+		++y;
+#endif
+	}
+} //}}}
+
+static inline void openpci_read(struct openpci *wc, unsigned char flags)
+{ //{{{
+	int x,y;
+	volatile unsigned int *readchunk;
+
+	if (flags & 0x08)
+		readchunk = wc->readchunk + DAHDI_CHUNKSIZE*2;
+	else if (flags & 0x04)
+		readchunk = wc->readchunk;
+	else {
+		cardcrit(wc->boardnum, "bad read interrupt flags %x, at %x",
+					flags, inb(TJ_DMARC));
+		return;
+	}
+
+	for (y=0,x=0;x<DAHDI_CHUNKSIZE;++x) {
+#ifdef __BIG_ENDIAN
+#warning No big endian support (yet)
+#else
+		/* Receive first 4 ports */
+
+		if (wc->porttype[0])
+			wc->chans[0]->readchunk[x] = (readchunk[y] >> 24) & 0xff;
+		if (wc->porttype[1])
+			wc->chans[1]->readchunk[x] = (readchunk[y] >> 16) & 0xff;
+		if (wc->porttype[2])
+			wc->chans[2]->readchunk[x] = (readchunk[y] >> 8) & 0xff;
+		if (wc->porttype[3])
+			wc->chans[3]->readchunk[x] = (readchunk[y]) & 0xff;
+		++y;
+		/* Receive second 4 ports */
+		if (wc->porttype[4])
+			wc->chans[4]->readchunk[x] = (readchunk[y] >> 24) & 0xff;
+		if (wc->porttype[5])
+			wc->chans[5]->readchunk[x] = (readchunk[y] >> 16) & 0xff;
+		if (wc->porttype[6])
+			wc->chans[6]->readchunk[x] = (readchunk[y] >> 8) & 0xff;
+		if (wc->porttype[7])
+			wc->chans[7]->readchunk[x] = (readchunk[y]) & 0xff;
+		++y;
+#endif
+	}
+	/* XXX We're wasting 8 taps.  We should get closer :( */
+	for (x = 0; x < MAX_PORTS; x++) {
+		if (wc->porttype[x])
+			dahdi_ec_chunk(wc->chans[x], wc->chans[x]->readchunk, wc->chans[x]->writechunk);
+	}
+	dahdi_receive(&wc->span);
+} //}}}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static irqreturn_t openpci_isr(int irq, void *dev_id, struct pt_regs *regs)
+#else
+static irqreturn_t openpci_isr(int irq, void *dev_id)
+#endif
+{ //{{{
+	struct openpci *wc = dev_id;
+	unsigned long flags;
+	unsigned char status;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	status = inb(TJ_INTSTAT);
+	outb(status, TJ_INTSTAT);
+
+	if (!status) {
+		if(inb(TJ_AUXR) & 0x02) {
+			spin_unlock_irqrestore(&wc->lock, flags);
+			return IRQ_NONE;
+		}
+		spin_unlock_irqrestore(&wc->lock, flags);
+		openpci_arm_work(wc);
+		return IRQ_HANDLED;
+	}
+	if (status & 0x10){
+		/* PCI Master abort */
+		cardcrit(wc->boardnum, "PCI Master Abort.");
+		/* Stop DMA, wait for watchdog */
+		outb(0x00, TJ_OPER);
+		spin_unlock_irqrestore(&wc->lock, flags);
+		return IRQ_HANDLED;
+	}
+	spin_unlock_irqrestore(&wc->lock, flags);
+
+	if (status & 0x20){
+		/* PCI Target abort */
+		cardcrit(wc->boardnum, "PCI Target Abort.");
+		return IRQ_HANDLED;
+	}
+	if (status & 0x03){
+		openpci_write(wc, status);
+	}
+	if (status & 0x0c){
+	    #ifdef DEBUG_LOOP_VOLTAGE
+	    //{{{
+		static int counter[MAX_CARDS];
+		int card = wc->boardnum;
+		int port = ++counter[card] & 0x07;
+		int ignore = counter[card] & 0xf0;
+
+		if( ! ignore && (voltmeter & ((1 << (card * 8)) << port)) ) {
+			unsigned char lv;
+			if( wc->porttype[port] == VT_PORT_VDAA && read_reg_fxo(wc, port, 29, &lv) )
+				cardinfo(wc->boardnum, "Port %d loop voltage %d",
+							port, lv < 128 ? lv : lv - 256);
+		}
+	    //}}}
+	    #endif
+		openpci_read(wc, status);
+	}
+
+	return IRQ_HANDLED;
+} //}}}
+
+static int openpci_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
+{ //{{{
+	struct wctdm_stats stats;
+	struct wctdm_regs regs;
+	struct wctdm_regop regop;
+	struct wctdm_echo_coefs echoregs;
+	struct openpci *wc = chan->pvt;
+	int port = chan->chanpos - 1;
+	int x;
+
+	switch (cmd) {
+	case DAHDI_ONHOOKTRANSFER:
+		if (wc->porttype[port] != VT_PORT_PROSLIC)
+			return -EINVAL;
+		if (get_user(x, (int *)data))
+			return -EFAULT;
+		wc->mod[port].fxs.ohttimer = x << 3;
+		if (reversepolarity)
+			wc->mod[port].fxs.idletxhookstate = 0x6;	/* OHT mode when idle */
+		else
+			wc->mod[port].fxs.idletxhookstate = 0x2;
+		switch(wc->mod[port].fxs.lasttxhook) {
+		    case 0x1:
+		    case 0x5:
+			if (reversepolarity)
+				wc->mod[port].fxs.lasttxhook = 0x6;
+			else
+				wc->mod[port].fxs.lasttxhook = 0x2;
+			if( ! write_reg_fxs(wc, port, 64, wc->mod[port].fxs.lasttxhook) )
+				return -EIO;
+		}
+		break;
+	case DAHDI_SETPOLARITY:
+		if (get_user(x, (int *)data))
+			return -EFAULT;
+		if (wc->porttype[port] != VT_PORT_PROSLIC)
+			return -EINVAL;
+		/* Can't change polarity while ringing or when open */
+		if ((wc->mod[port].fxs.lasttxhook == 0x04) ||
+		    (wc->mod[port].fxs.lasttxhook == 0x00))
+			return -EINVAL;
+
+		if ((x && !reversepolarity) || (!x && reversepolarity))
+			wc->mod[port].fxs.lasttxhook |= 0x04;
+		else
+			wc->mod[port].fxs.lasttxhook &= ~0x04;
+		if( ! write_reg_fxs(wc, port, 64, wc->mod[port].fxs.lasttxhook) )
+			return -EIO;
+		break;
+	case WCTDM_GET_STATS:
+		if (wc->porttype[port] == VT_PORT_PROSLIC) {
+			unsigned char	linevolt;
+			if( read_reg_fxs(wc, port, 80, &linevolt) )
+				stats.tipvolt = linevolt * -376;
+			else
+				return -EIO;
+			if( read_reg_fxs(wc, port, 81, &linevolt) )
+				stats.ringvolt = linevolt * -376;
+			else
+				return -EIO;
+			if( read_reg_fxs(wc, port, 82, &linevolt) )
+				stats.batvolt = linevolt * -376;
+			else
+				return -EIO;
+		} else if (wc->porttype[port] == VT_PORT_VDAA) {
+			unsigned char	linevolt;
+			if( read_reg_fxo(wc, port, 29, &linevolt) )
+				stats.tipvolt = stats.ringvolt = stats.batvolt = linevolt * 1000;
+			else
+				return -EIO;
+		} else
+			return -EINVAL;
+		if (copy_to_user((struct wctdm_stats *)data, &stats, sizeof(stats)))
+			return -EFAULT;
+		break;
+	case WCTDM_GET_REGS:
+		if (wc->porttype[port] == VT_PORT_PROSLIC) {
+			for (x=0;x<NUM_INDIRECT_REGS;x++)
+				if( ! read_indreg_fxs(wc, port, x, &regs.indirect[x]) )
+					return -EIO;
+			for (x=0;x<NUM_REGS;x++)
+				if( ! read_reg_fxs(wc, port, x, &regs.direct[x]) )
+					return -EIO;
+		} else {
+			memset(&regs, 0, sizeof(regs));
+			for (x=0;x<NUM_FXO_REGS;x++){
+				if( ! read_reg_fxo(wc, port, x, &regs.direct[x]) )
+					return -EIO;
+			}
+		}
+		if (copy_to_user((struct wctdm_regs *)data, &regs, sizeof(regs)))
+			return -EFAULT;
+		break;
+	case WCTDM_SET_REG:
+		if (copy_from_user(&regop, (struct wctdm_regop *)data, sizeof(regop)))
+			return -EFAULT;
+		if (regop.indirect) {
+			if (wc->porttype[port] != VT_PORT_PROSLIC)
+				return -EINVAL;
+			printk("Setting indirect %d to 0x%04x on %d\n",
+				regop.reg, regop.val, chan->chanpos);
+			if( ! write_indreg_fxs(wc, port, regop.reg, regop.val) )
+				return -EIO;
+		} else {
+			regop.val &= 0xff;
+			printk("Setting direct %d to %04x on %d\n",
+				regop.reg, regop.val, chan->chanpos);
+			if (wc->porttype[port] == VT_PORT_PROSLIC) {
+				if( ! write_reg_fxs(wc, port, regop.reg, regop.val) )
+					return -EIO;
+			} else {
+				if( ! write_reg_fxo(wc, port, regop.reg, regop.val) )
+					return -EIO;
+			}
+		}
+		break;
+	case WCTDM_SET_ECHOTUNE:
+		cardinfo(wc->boardnum, "Setting echo registers");
+		if (copy_from_user(&echoregs, (struct wctdm_echo_coefs*)data, sizeof(echoregs)))
+			return -EFAULT;
+
+		if (wc->porttype[port] == VT_PORT_VDAA) {
+			/* Set the ACIM and digital echo canceller registers */
+			if( ! write_reg_fxo(wc, port, 30, echoregs.acim)
+			 || ! write_reg_fxo(wc, port, 45, echoregs.coef1)
+			 || ! write_reg_fxo(wc, port, 46, echoregs.coef2)
+			 || ! write_reg_fxo(wc, port, 47, echoregs.coef3)
+			 || ! write_reg_fxo(wc, port, 48, echoregs.coef4)
+			 || ! write_reg_fxo(wc, port, 49, echoregs.coef5)
+			 || ! write_reg_fxo(wc, port, 50, echoregs.coef6)
+			 || ! write_reg_fxo(wc, port, 51, echoregs.coef7)
+			 || ! write_reg_fxo(wc, port, 52, echoregs.coef8) )
+			{
+				cardcrit(wc->boardnum, "Failed to set echo registers");
+				return -EIO;
+			}
+			break;
+		} else {
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -ENOTTY;
+	}
+	return 0;
+} //}}}
+
+static int openpci_open(struct dahdi_chan *chan)
+{
+	struct openpci *wc = chan->pvt;
+	if( ! wc->porttype[chan->chanpos-1] )
+		return -ENODEV;
+
+	//XXX This is WRONG and can prang in a race.  We must pass THIS_MODULE
+	//    as the owner of the span that holds the pointer to this function,
+	//    then bump the refcount in the dahdi code _BEFORE_ the potentially
+	//    fatal call to an invalid pointer is made.
+	//if( wc->dead ) return -ENODEV;
+	//wc->usecount++;
+	try_module_get(THIS_MODULE);  //XXX
+
+	return 0;
+}
+
+static inline struct openpci* openpci_from_span(struct dahdi_span *span) {
+	return container_of(span, struct openpci, span);
+}
+
+static int openpci_watchdog(struct dahdi_span *span, int event)
+{
+	info("TDM: Restarting DMA");
+	restart_dma(openpci_from_span(span));
+	return 0;
+}
+
+static int openpci_close(struct dahdi_chan *chan)
+{
+	struct openpci *wc = chan->pvt;
+	int port = chan->chanpos - 1;
+
+	//XXX wc->usecount--;
+	//XXX This is WRONG and can prang in a race.  We must pass THIS_MODULE
+	//    as the owner of the span that holds the pointer to this function,
+	//    then bump the refcount in the dahdi code _BEFORE_ the potentially
+	//    fatal call to an invalid pointer is made.
+	module_put(THIS_MODULE);
+	if (wc->porttype[port] == VT_PORT_PROSLIC) {
+		if (reversepolarity)
+			wc->mod[port].fxs.idletxhookstate = 5;
+		else
+			wc->mod[port].fxs.idletxhookstate = 1;
+	}
+	/* If we're dead, release us now */
+	//XXX if (!wc->usecount && wc->dead) openpci_release(wc);
+
+	return 0;
+}
+
+static int openpci_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig)
+{ //{{{
+	struct openpci *wc = chan->pvt;
+	int port = chan->chanpos - 1;
+	int new_hk_state;
+
+	dbginfo(wc->boardnum, "Setting %s port %d hook state %s",
+		 wc->porttype[port] == VT_PORT_VDAA ? "FXO" : "FXS",
+		 port,
+		 txsig == 0 ? "ONHOOK" :
+		 txsig == 1 ? "OFFHOOK" :
+		 txsig == 2 ? "START" :
+		 txsig == 3 ? "KEWL" : "UNKNOWN" );
+
+	switch(wc->porttype[port]) {
+	    case VT_PORT_VDAA:
+		switch(txsig) {
+		    case DAHDI_TXSIG_START:
+		    case DAHDI_TXSIG_OFFHOOK:
+			if( write_reg_fxo(wc, port, 5, 0x9)
+			 && write_reg_fxo(wc, port, 0x20, 0x0) )
+				wc->mod[port].fxo.offhook = 1;
+			else
+				cardcrit(wc->boardnum, "Failed set fxo off-hook");
+			break;
+
+		    case DAHDI_TXSIG_ONHOOK:
+			if( write_reg_fxo(wc, port, 5, 0x8)
+			 && write_reg_fxo(wc, port, 0x20, 0x3) )
+				wc->mod[port].fxo.offhook = 0;
+			else
+				cardcrit(wc->boardnum, "Failed set fxo on-hook");
+			break;
+
+		    default:
+			cardcrit(wc->boardnum,
+				 "Can't set FXO port %d tx state to %d",
+				 port, txsig);
+		}
+		break;
+
+	    case VT_PORT_PROSLIC:
+		new_hk_state = wc->mod[port].fxs.lasttxhook;
+		switch(txsig) {
+		    case DAHDI_TXSIG_ONHOOK:
+			switch(chan->sig) {
+			case DAHDI_SIG_EM:
+			case DAHDI_SIG_FXOKS:
+			case DAHDI_SIG_FXOLS:
+				new_hk_state = wc->mod[port].fxs.idletxhookstate;
+				break;
+			case DAHDI_SIG_FXOGS:
+				new_hk_state = 3;
+				break;
+			}
+			break;
+
+		    case DAHDI_TXSIG_OFFHOOK:
+			switch(chan->sig) {
+			case DAHDI_SIG_EM:
+				new_hk_state = 5;
+				break;
+			default:
+				new_hk_state = wc->mod[port].fxs.idletxhookstate;
+				break;
+			}
+			break;
+
+		    case DAHDI_TXSIG_START:
+			new_hk_state = 4;
+			break;
+
+		    case DAHDI_TXSIG_KEWL:
+			new_hk_state = 0;
+			break;
+
+		    default:
+			cardinfo(wc->boardnum,
+				 "Can't set FXS port %d tx state to %d",
+				 port, txsig);
+		}
+		dbginfo(wc->boardnum, "%s port %d hook state old %d, new %d",
+			 wc->porttype[port] == VT_PORT_VDAA ? "FXO" : "FXS",
+			 port, wc->mod[port].fxs.lasttxhook, new_hk_state );
+
+		if (new_hk_state != wc->mod[port].fxs.lasttxhook){
+			if( write_reg_fxs(wc, port, 64, new_hk_state) )
+				wc->mod[port].fxs.lasttxhook = new_hk_state;
+			else
+				cardcrit(wc->boardnum,
+					 "Failed to set port %d fxs hookstate from %d to %d",
+					 port, wc->mod[port].fxs.lasttxhook, new_hk_state);
+		}
+		break;
+
+	    default:
+		cardcrit(wc->boardnum,
+			 "Unknown module type %d in openpci_hooksig",
+			 wc->porttype[port] );
+	}
+	return 0;
+} //}}}
+
+static const struct dahdi_span_ops openpci_span_ops = {
+	.owner = THIS_MODULE,
+	.hooksig = openpci_hooksig,
+	.open = openpci_open,
+	.close = openpci_close,
+	.ioctl = openpci_ioctl,
+	.watchdog = openpci_watchdog
+};
+
+static int span_initialize(struct openpci *wc)
+{ //{{{
+	int x;
+
+	wc->ddev = dahdi_create_device();
+	//XXX Set a THIS_MODULE as the owner of the span...
+	/* Zapata stuff */
+	sprintf(wc->span.name, "WCTDM/%d", wc->boardnum);
+	sprintf(wc->span.desc, "%s Board %d", wc->variety, wc->boardnum + 1);
+	wc->ddev->location = kasprintf(GFP_KERNEL, "PCI Bus %02d Slot %02d",
+				      wc->dev->bus->number,
+				      PCI_SLOT(wc->dev->devfn) + 1);
+	if (!wc->ddev->location)
+		return -ENOMEM;
+	for (x = 0; x < MAX_PORTS; x++) {
+		struct dahdi_chan *chan = &wc->_chans[x];
+		wc->chans[x] = chan;
+		sprintf(chan->name, "WCTDM/%d/%d", wc->boardnum, x);
+		chan->sigcap = DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS
+				    | DAHDI_SIG_SF | DAHDI_SIG_EM | DAHDI_SIG_CLEAR;
+		chan->sigcap |= DAHDI_SIG_FXSKS | DAHDI_SIG_FXSLS | DAHDI_SIG_SF | DAHDI_SIG_CLEAR;
+		chan->chanpos = x+1;
+		chan->pvt = wc;
+	}
+	wc->ddev->manufacturer = "Voicetronix";
+	wc->ddev->devicetype = wc->variety;
+	wc->span.deflaw = DAHDI_LAW_MULAW;
+	wc->span.chans = wc->chans;
+	wc->span.channels = MAX_PORTS;
+	wc->span.flags = DAHDI_FLAG_RBS;
+	wc->span.ops = &openpci_span_ops;
+
+	list_add_tail(&wc->span.device_node, &wc->ddev->spans);
+	if (dahdi_register_device(wc->ddev, &wc->dev->dev)) {
+		cardcrit(wc->boardnum, "Unable to register device with dahdi");
+		return RET_FAIL;
+	}
+	return RET_OK;
+} //}}}
+
+static int get_port_type(struct openpci *wc, int port)
+{ //{{{
+	int i, type;
+	unsigned long flags;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0x20, PIB(1)); HTXF_WAIT_LOCKED_RET(VT_PORT_EMPTY);
+	outb(port, PIB(1)); HTXF_WAIT_LOCKED_RET(VT_PORT_EMPTY);
+	HRXF_WAIT_LOCKED_RET(VT_PORT_EMPTY); type = inb(PIB(1));
+	spin_unlock_irqrestore(&wc->lock, flags);
+
+	return type;
+} //}}}
+
+static int check_ports(struct openpci *wc)
+{ //{{{
+	int i = 0;
+
+	wc->portcount = 0;
+	for(; i < MAX_PORTS; ++i ){
+		wc->porttype[i] = get_port_type(wc, i);
+		dbginfo(wc->boardnum,"%d: %s", i, porttype(wc,i));
+
+		switch( wc->porttype[i] ) {
+		    case VT_PORT_PROSLIC:
+			/* By default, don't send on hook */
+			if (reversepolarity)
+				wc->mod[i].fxs.idletxhookstate = 5;
+			else
+				wc->mod[i].fxs.idletxhookstate = 1;
+
+		    case VT_PORT_VDAA:
+			++wc->portcount;
+		}
+	}
+	// we 'succeed' if any ports were discovered.
+	return wc->portcount ? RET_OK : RET_FAIL;
+} //}}}
+
+static int configure_vdaa_country(struct openpci *wc, int port, char *name)
+{ //{{{
+	unsigned char value;
+	int i;
+
+	for (i=0; i < sizeof(fxo_modes)/sizeof(struct fxo_mode); ++i){
+		if(!strcmp(fxo_modes[i].name, name)){
+			dbginfo(wc->boardnum, "%d: Setting country to %s", port, name);
+			goto part2;
+		}
+	}
+	i = 3;
+	cardinfo(wc->boardnum, "Using default country %s", fxo_modes[i].name);
+
+    part2:
+	value  = (fxo_modes[i].ohs << 6);
+	value |= (fxo_modes[i].rz << 1);
+	value |= (fxo_modes[i].rt << 0);
+	if( ! write_reg_fxo(wc, port, 16, value) ) goto hell;
+
+	/* DC Termination Control - Register 26 */
+	value  = (fxo_modes[i].dcv << 6);
+	value |= (fxo_modes[i].mini << 4);
+	value |= (fxo_modes[i].ilim << 1);
+	if( ! write_reg_fxo(wc, port, 26, value) ) goto hell;
+
+	/* AC Termination Control - Register 30 */
+	value = (fxo_modes[i].acim << 0);
+	if( ! write_reg_fxo(wc, port, 30, value) ) goto hell;
+
+	/* DAA Control 5 - Register 31 */
+	msleep(1);
+	if( ! read_reg_fxo(wc, port, 31, &value) ) goto hell;
+
+	value = (value & 0xf7) | (fxo_modes[i].ohs2 << 3);
+	value = value | 0x02;
+	if( ! write_reg_fxo(wc, port, 31, value) ) goto hell;
+
+	return RET_OK;
+
+    hell:
+	cardcrit(wc->boardnum, "port %d failed configure vdaa country", port);
+	return RET_FAIL;
+} //}}}
+
+// Do not call this from an interrupt context, it may sleep.
+static void configure_vdaa_port(struct openpci *wc, int port)
+{ //{{{
+	/* Set Country - default to Australia */
+	if( configure_vdaa_country(wc, port, country) )
+		++wc->portcount;
+	else {
+		cardcrit(wc->boardnum, "FAILED to configure vdaa port %d.  Disabled.", port);
+		wc->porttype[port] = VT_PORT_EMPTY;
+	}
+} //}}}
+
+static int configure_proslic_country(struct openpci *wc, int port, const char *name)
+{ //{{{
+	int i;
+
+	for(i=0; i < sizeof(ps_country_regs)/sizeof(struct ps_country_reg); ++i) {
+		if(!strcmp(ps_country_regs[i].country, name)){
+			dbginfo(wc->boardnum, "%d: Setting country to %s", port, name);
+			goto part2;
+		}
+	}
+	return -EINVAL;
+
+    part2:
+
+	if( ! write_reg_fxs(wc, port, 10, ps_country_regs[i].value) ){
+		cardcrit(wc->boardnum,"%d: failed to write PS_IMPEDANCE", port);
+		return -EIO;
+	}
+	return 0;
+} //}}}
+
+// Do not call this from an interrupt context, it may sleep.
+static void configure_proslic_port(struct openpci *wc, int port)
+{ //{{{
+	/* Set Country - default to Australia */
+	switch( configure_proslic_country(wc, port, country) ){
+	    case 0:
+		break;
+
+	    case -EINVAL:
+		cardwarn(wc->boardnum,"%d: Country '%s' unknown, using default", port, country);
+		if( configure_proslic_country(wc, port, DEFAULT_COUNTRY) == 0 )
+			goto hell;
+
+	    default:
+		goto hell;
+	}
+
+	++wc->portcount;
+	return;
+
+    hell:
+	cardcrit(wc->boardnum, "FAILED to configure proslic port %d.  Disabled.", port);
+	wc->porttype[port] = VT_PORT_EMPTY;
+} //}}}
+
+// Do not call this from an interrupt context, it may (indirectly) sleep.
+static int configure_ports(struct openpci *wc)
+{ //{{{
+	unsigned long flags;
+	int i;
+
+	wc->portcount = 0;
+	for(i=0; i < MAX_PORTS; ++i){
+		switch (wc->porttype[i]){
+		    case VT_PORT_VDAA:    configure_vdaa_port(wc,i);    break;
+		    case VT_PORT_PROSLIC: configure_proslic_port(wc,i); break;
+		}
+	}
+
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0x2c, PIB(1)); HTXF_WAIT_LOCKED();
+	outb(0xff, PIB(1)); HTXF_WAIT_LOCKED();
+	spin_unlock_irqrestore(&wc->lock, flags);
+
+	// otherwise we 'succeed' if any ports were configured successfully.
+	return wc->portcount ? RET_OK : RET_FAIL;
+} //}}}
+
+static int __get_arm_id(struct openpci *wc, int field, char *value)
+{ //{{{
+	int i;
+	int x=0;
+	int count=0;
+
+	outb(0x01, PIB(1));  HTXF_WAIT();
+	outb(field, PIB(1)); HTXF_WAIT();
+	HRXF_WAIT(); count = inb(PIB(1));
+	if (count > ID_DATA_MAXSIZE){
+		cardcrit(wc->boardnum, "Too many bytes of id(%d) data %d/%d",
+					 field, count, ID_DATA_MAXSIZE);
+		return RET_FAIL;
+	}
+	//cardinfo(wc->boardnum, "get_arm_id(%d): byte count %d",field,count);
+	for(; x < count; ++x){
+		HRXF_WAIT(); *value = inb(PIB(1));
+		//cardinfo(wc->boardnum, "get_arm_id(%d): byte %d => 0x%02x",field,x,tmp);
+		++value;
+	}
+	return RET_OK;
+} //}}}
+
+static void enable_interrupts(struct openpci *wc)
+{ //{{{
+	outb(0x3f, TJ_MASK0);
+	outb(0x02, TJ_MASK1);
+} //}}}
+
+static void disable_interrupts(struct openpci *wc)
+{ //{{{
+	outb(0x00, TJ_MASK0);
+	outb(0x00, TJ_MASK1);
+} //}}}
+
+// Do not call this from an interrupt context, it may sleep.
+static int check_arm(struct openpci *wc)
+{ //{{{
+	char model[ID_DATA_MAXSIZE+1] = { 0 };
+	char date[ID_DATA_MAXSIZE+1]  = { 0 };
+	unsigned long flags;
+	int i=0;
+	int tmp=0;
+
+	spin_lock_irqsave(&wc->lock, flags);
+	while ((tmp != 0x88)&&(++i<100)){
+		outb(0x88, PIB(0));
+		msleep(1);
+		tmp = inb(PIB(1));
+	}
+	if (i>=1000) goto limbo;
+	dbginfo(wc->boardnum, "Arm responded on attempt %d",i);
+
+	// Flush out the queue if we sent several pings before a response.
+	if(i>1)	__ping_arm(wc);
+
+	if( ! __get_arm_id(wc, 0, model) )  goto hell;
+	sscanf(model, "OpenPCI8.%02d", &(wc->firmware));
+	cardinfo(wc->boardnum, "  model: %s", model);
+
+	if( ! __get_arm_id(wc, 1, date) )   goto hell;
+	cardinfo(wc->boardnum, "  date: %s", date);
+
+	if( ! __get_arm_id(wc, 2, wc->serial) ) goto hell;
+	cardinfo(wc->boardnum, "  serial: %s", wc->serial);
+
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return RET_OK;
+
+    hell:
+	spin_unlock_irqrestore(&wc->lock, flags);
+        cardwarn(wc->boardnum, "Found ARM processor, dumb firmware.");
+	return RET_OK;
+
+    limbo:
+	spin_unlock_irqrestore(&wc->lock, flags);
+	return RET_FAIL;
+} //}}}
+
+static int arm_monitor(struct openpci *wc, int on)
+{ //{{{
+	int i;
+	outb( on ? 0x06 : 0x07, PIB(1) ); HTXF_WAIT();
+	return RET_OK;
+} //}}}
+
+static int __devinit openpci_probe_board(struct pci_dev *pdev, const struct pci_device_id *ent)
+{ //{{{
+	struct openpci *wc;
+	int boardnum = 0;
+	int failret = -ENOMEM;
+	int tmp = 0;
+	int i;
+	unsigned long flags;
+
+	if( ent->driver_data != (kernel_ulong_t)&wcopenpci )
+	{
+	    info("Probe of non-OpenPCI card, ignoring.");
+	    return -EINVAL;
+	}
+	wc = kzalloc(sizeof(struct openpci), GFP_KERNEL);
+	if (!wc){
+		return -ENOMEM;
+	}
+
+	mutex_lock(&cards_mutex);
+	for (; boardnum < MAX_CARDS && cards[boardnum]; ++boardnum);
+	if (boardnum >= MAX_CARDS){
+		crit("Too many OpenPCI cards(%d), max is %d.", boardnum, MAX_CARDS);
+		mutex_unlock(&cards_mutex);
+		goto hell;
+	}
+	cards[boardnum] = wc;
+	mutex_unlock(&cards_mutex);
+
+	spin_lock_init(&wc->lock);
+	pci_set_drvdata(pdev, wc);
+
+	wc->boardnum = boardnum;
+	wc->dev      = pdev;
+	wc->variety  = wcopenpci;
+
+	cardinfo(boardnum, "Initialising card");
+	if (pci_enable_device(pdev)) {
+		failret = -EIO;
+		goto hell_2;
+	}
+	wc->ioaddr = pci_resource_start(pdev, 0);
+	if( ! request_region(wc->ioaddr, 0xff, NAME) ){
+                cardcrit(boardnum, "Failed to lock IO region, another driver already using it");
+		failret = -EBUSY;
+		goto hell_2;
+	}
+
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0xff, TJ_AUXD);            /* Set up TJ to access the ARM */
+	outb(0x78, TJ_AUXC);            /* Set up for Jtag */
+	outb(0x00, TJ_CNTL);            /* pull ERST low */
+	spin_unlock_irqrestore(&wc->lock, flags);
+	msleep(1);	                /* Wait a bit */
+
+	dbginfo(boardnum,"Starting ARM");
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0x01, TJ_CNTL);            /* pull ERST high again */
+	spin_unlock_irqrestore(&wc->lock, flags);
+	msleep(100);                    /* Give it all a chance to boot */
+
+	if( ! check_arm(wc) ){
+		cardcrit(boardnum, "Couldnt find ARM processor");
+		failret = -EIO;
+		goto hell_3;
+	}
+	if( wc->firmware < 11 ){
+		cardcrit(boardnum,
+			 "Firmware version %d not supported by this driver",
+			 wc->firmware);
+		cardcrit(boardnum, " contact Voicetronix to have it updated");
+		failret = -ENODEV;
+		goto hell_3;
+	}
+	if( ! check_ports(wc) ){
+		cardcrit(boardnum, "Couldnt find ports!");
+		failret = -EIO;
+		goto hell_3;
+	}
+
+	wc->writechunk = pci_alloc_consistent(pdev, VT_PCIDMA_BLOCKSIZE, &wc->writedma);
+	if (!wc->writechunk) {
+		cardcrit(boardnum, "Couldnt get DMA memory.");
+		goto hell_3;
+	}
+	wc->readchunk = wc->writechunk + DAHDI_MAX_CHUNKSIZE * (MAX_PORTS*2 / sizeof(int));
+	wc->readdma = wc->writedma + DAHDI_MAX_CHUNKSIZE * (MAX_PORTS*2);
+
+	memset((void*)wc->writechunk,0,VT_PCIDMA_BLOCKSIZE);
+
+	spin_lock_irqsave(&wc->lock, flags);
+	outb(0xc1, TJ_SERCTL);
+	outb(0x0, TJ_FSCDELAY);
+
+	outl(wc->writedma,                    TJ_DMAWS);
+	outl(wc->writedma + VT_PCIDMA_MIDDLE, TJ_DMAWI);
+	outl(wc->writedma + VT_PCIDMA_END,    TJ_DMAWE);
+	outl(wc->readdma,                     TJ_DMARS);
+	outl(wc->readdma + VT_PCIDMA_MIDDLE,  TJ_DMARI);
+	outl(wc->readdma + VT_PCIDMA_END,     TJ_DMARE);
+
+	/* Clear interrupts */
+	outb(0xff, TJ_INTSTAT);
+	spin_unlock_irqrestore(&wc->lock, flags);
+
+	if( ! arm_monitor(wc, 1) ){
+		cardcrit(boardnum, "failed to start arm monitoring");
+		failret = -EIO;
+		goto hell_4;
+	}
+	msleep(1000);
+
+	i = 0;
+	while(tmp != 0x88 && ++i < 1000) {
+		outb(0x88, PIB(0));
+		msleep(250);
+		tmp = inb(PIB(1));
+	}
+	if(i>=1000) {
+		cardcrit(boardnum, "FAILED to initialise board");
+		goto hell_4;
+	}
+
+	if( ! check_ports(wc) ) {
+		cardcrit(boardnum, "FAILED to initialise ports");
+		failret = -EIO;
+		goto hell_4;
+	}
+	if( ! configure_ports(wc) ){
+		cardcrit(boardnum, "Failed to configure ports.");
+		failret = -EIO;
+		goto hell_4;
+	}
+	cardinfo(wc->boardnum, "have %d configured ports", wc->portcount);
+
+	if( ! span_initialize(wc) ) {
+		cardcrit(boardnum, "Failed to register with dahdi driver");
+		failret = -EFAULT;
+		goto hell_4;
+	}
+
+	/* Finalize signalling  */
+	for (i=0; i < MAX_PORTS; ++i) {
+		if (wc->porttype[i] == VT_PORT_VDAA)
+		    wc->chans[i]->sigcap = DAHDI_SIG_FXSKS | DAHDI_SIG_FXSLS
+					| DAHDI_SIG_CLEAR | DAHDI_SIG_SF;
+		else if (wc->porttype[i] == VT_PORT_PROSLIC)
+		    wc->chans[i]->sigcap = DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS
+					| DAHDI_SIG_FXOGS | DAHDI_SIG_SF
+					| DAHDI_SIG_CLEAR | DAHDI_SIG_EM;
+		else if (wc->porttype[i])
+		    cardcrit(wc->boardnum, "Port %d has unknown type (%d)",
+					   i, wc->porttype[i]);
+	}
+
+	/* Enable bus mastering */
+	pci_set_master(pdev);
+
+	if (request_irq(pdev->irq, openpci_isr, DAHDI_IRQ_SHARED, NAME, wc)) {
+		cardcrit(boardnum, "Cant get IRQ!");
+		failret = -EIO;
+		goto hell_5;
+	}
+	cardinfo(boardnum, "Got IRQ %d", pdev->irq);
+
+	enable_interrupts(wc);
+	start_dma(wc);
+
+	cardinfo(boardnum,"Initialised card.");
+	return 0;
+
+    hell_5:
+	dahdi_unregister_device(wc->ddev);
+    hell_4:
+	if (wc->writechunk){
+		pci_free_consistent(pdev, VT_PCIDMA_BLOCKSIZE,
+				    (void*)wc->writechunk, wc->writedma);
+	}
+    hell_3:
+	outb(0x00, TJ_CNTL);
+	release_region(wc->ioaddr, 0xff);
+    hell_2:
+	cards[boardnum] = NULL;
+    hell:
+	kfree(wc->ddev->location);
+	dahdi_free_device(wc->ddev);
+	kfree(wc);
+	return failret;
+} //}}}
+
+static void __devexit openpci_remove_board(struct pci_dev *pdev)
+{ //{{{
+	struct openpci *wc = pci_get_drvdata(pdev);
+
+	if(!wc) return;
+
+	arm_monitor(wc,0);
+
+	/* Stop DMA */
+	outb(0x00, TJ_OPER);
+	disable_interrupts(wc);
+
+	//XXX Replace this usecount business...
+	//    and do this BEFORE we invalidate everything above...
+	//    check that we wont try to write to it in the meantime.
+	/* Release span, possibly delayed */
+	//XXX if (!wc->usecount) openpci_release(wc); else wc->dead = 1;
+
+	dahdi_unregister_device(wc->ddev);
+	outb(0x00, TJ_CNTL);
+
+	pci_free_consistent(pdev, VT_PCIDMA_BLOCKSIZE, (void *)wc->writechunk, wc->writedma);
+	free_irq(pdev->irq, wc);
+
+	release_region(wc->ioaddr, 0xff);
+
+	mutex_lock(&cards_mutex);
+	cards[wc->boardnum] = NULL;
+	mutex_unlock(&cards_mutex);
+
+	kfree(wc->ddev->location);
+	dahdi_free_device(wc->ddev);
+	kfree(wc);
+	cardinfo(wc->boardnum, "Removed OpenPCI card.");
+} //}}}
+
+static struct pci_device_id openpci_pci_tbl[] = {
+	{ 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t) &wcopenpci },
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, openpci_pci_tbl);
+
+static struct pci_driver openpci_driver = {
+	name: 	  NAME,
+	probe: 	  openpci_probe_board,
+	remove:	  __devexit_p(openpci_remove_board),
+	suspend:  NULL,
+	resume:	  NULL,
+	id_table: openpci_pci_tbl,
+};
+
+static int __init openpci_init(void)
+{
+#ifdef __BIG_ENDIAN
+	warn("No big endian support (yet)");
+	return -ENODEV;
+#endif
+
+	if( dahdi_pci_module(&openpci_driver) )
+		return -ENODEV;
+
+	info("Module loaded %s", debug ? "with debug enabled" : "");
+	return 0;
+}
+
+static void __exit openpci_cleanup(void)
+{
+	pci_unregister_driver(&openpci_driver);
+	info("Module exit");
+}
+
+module_init(openpci_init);
+module_exit(openpci_cleanup);
+
+MODULE_DESCRIPTION(DRIVER_DESCRIPTION);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_VERSION(DAHDI_VERSION);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/dahdi/zaphfc/Kbuild b/drivers/dahdi/zaphfc/Kbuild
new file mode 100644
index 0000000..960fb3a
--- /dev/null
+++ b/drivers/dahdi/zaphfc/Kbuild
@@ -0,0 +1,10 @@
+obj-m += zaphfc.o
+
+EXTRA_CFLAGS := -I$(src)/.. -Wno-undef
+
+zaphfc-objs := base.o fifo.o
+
+$(obj)/base.o: $(src)/zaphfc.h
+$(obj)/fifo.o: $(src)/fifo.h
+
+
diff --git a/drivers/dahdi/zaphfc/base.c b/drivers/dahdi/zaphfc/base.c
new file mode 100644
index 0000000..65c6dae
--- /dev/null
+++ b/drivers/dahdi/zaphfc/base.c
@@ -0,0 +1,1719 @@
+/*
+ * zaphfc.c - Dahdi driver for HFC-S PCI A based ISDN BRI cards
+ *
+ * Dahdi rewrite in hardhdlc mode
+ * Jose A. Deniz <odicha@hotmail.com>
+ *
+ * Copyright (C) 2009, Jose A. Deniz
+ * Copyright (C) 2006, headiisue GmbH; Jens Wilke
+ * Copyright (C) 2004 Daniele Orlandi
+ * Copyright (C) 2002, 2003, 2004, Junghanns.NET GmbH
+ *
+ * Jens Wilke <jw_vzaphfc@headissue.com>
+ *
+ * Original author of this code is
+ * Daniele "Vihai" Orlandi <daniele@orlandi.com>
+ *
+ * Major rewrite of the driver made by
+ * Klaus-Peter Junghanns <kpj@junghanns.net>
+ *
+ * This program is free software and may be modified and
+ * distributed under the terms of the GNU Public License.
+ *
+ * Please read the README file for important infos.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/if_arp.h>
+
+#include <dahdi/kernel.h>
+
+#include "zaphfc.h"
+#include "fifo.h"
+
+#if CONFIG_PCI
+
+#define DAHDI_B1 0
+#define DAHDI_B2 1
+#define DAHDI_D 2
+
+#define D 0
+#define B1 1
+#define B2 2
+
+/*
+ * Mode Te for all
+ */
+static int modes;
+static int nt_modes[hfc_MAX_BOARDS];
+static int nt_modes_count;
+static int force_l1_up;
+static struct proc_dir_entry *hfc_proc_zaphfc_dir;
+
+#ifdef DEBUG
+int debug_level;
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
+#define	SET_PROC_DIRENTRY_OWNER(p)  do { (p)->owner = THIS_MODULE; } while (0);
+#else
+#define	SET_PROC_DIRENTRY_OWNER(p)	do { } while (0);
+#endif
+
+static struct pci_device_id hfc_pci_ids[] = {
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_2BD0,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B000,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B006,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B007,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B008,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B009,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00A,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00B,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B00C,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_CCD, PCI_DEVICE_ID_CCD_B100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_ABOCOM, PCI_DEVICE_ID_ABOCOM_2BD1,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_ASUSTEK, PCI_DEVICE_ID_ASUSTEK_0675,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_T_CONCEPT,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_BERKOM, PCI_DEVICE_ID_BERKOM_A1T,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_ANIGMA, PCI_DEVICE_ID_ANIGMA_MC145575,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_ZOLTRIX, PCI_DEVICE_ID_ZOLTRIX_2BD0,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_IOM2_A,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_DIGI, PCI_DEVICE_ID_DIGI_DF_M_A,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{PCI_VENDOR_ID_SITECOM, PCI_DEVICE_ID_SITECOM_3069,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, hfc_pci_ids);
+
+static int __devinit hfc_probe(struct pci_dev *dev
+			, const struct pci_device_id *ent);
+static void __devexit hfc_remove(struct pci_dev *dev);
+
+static struct pci_driver hfc_driver = {
+	.name     = hfc_DRIVER_NAME,
+	.id_table = hfc_pci_ids,
+	.probe    = hfc_probe,
+	.remove   = hfc_remove,
+};
+
+/******************************************
+ * HW routines
+ ******************************************/
+
+static void hfc_softreset(struct hfc_card *card)
+{
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		"card %d: "
+		"resetting\n",
+		card->cardnum);
+
+/*
+ * Softreset procedure. Put it on, wait and off again
+ */
+	hfc_outb(card, hfc_CIRM, hfc_CIRM_RESET);
+	udelay(6);
+	hfc_outb(card, hfc_CIRM, 0);
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout((hfc_RESET_DELAY * HZ) / 1000);
+}
+
+static void hfc_resetCard(struct hfc_card *card)
+{
+	card->regs.m1 = 0;
+	hfc_outb(card, hfc_INT_M1, card->regs.m1);
+
+	card->regs.m2 = 0;
+	hfc_outb(card, hfc_INT_M2, card->regs.m2);
+
+	hfc_softreset(card);
+
+	card->regs.trm = 0;
+	hfc_outb(card, hfc_TRM, card->regs.trm);
+
+	/*
+	 * Select the non-capacitive line mode for the S/T interface
+	 */
+	card->regs.sctrl = hfc_SCTRL_NONE_CAP;
+
+	if (card->nt_mode) {
+		/*
+		 * ST-Bit delay for NT-Mode
+		 */
+		hfc_outb(card, hfc_CLKDEL, hfc_CLKDEL_NT);
+
+		card->regs.sctrl |= hfc_SCTRL_MODE_NT;
+	} else {
+		/*
+		 * ST-Bit delay for TE-Mode
+		 */
+		hfc_outb(card, hfc_CLKDEL, hfc_CLKDEL_TE);
+
+		card->regs.sctrl |= hfc_SCTRL_MODE_TE;
+	}
+
+	hfc_outb(card, hfc_SCTRL, card->regs.sctrl);
+
+	/*
+	 * S/T Auto awake
+	 */
+	card->regs.sctrl_e = hfc_SCTRL_E_AUTO_AWAKE;
+	hfc_outb(card, hfc_SCTRL_E, card->regs.sctrl_e);
+
+	/*
+	 * No B-channel enabled at startup
+	 */
+	card->regs.sctrl_r = 0;
+	hfc_outb(card, hfc_SCTRL_R, card->regs.sctrl_r);
+
+	/*
+	 * HFC Master Mode
+	 */
+	hfc_outb(card, hfc_MST_MODE, hfc_MST_MODE_MASTER);
+
+	/*
+	 * Connect internal blocks
+	 */
+	card->regs.connect =
+		hfc_CONNECT_B1_HFC_from_ST |
+		hfc_CONNECT_B1_ST_from_HFC |
+		hfc_CONNECT_B1_GCI_from_HFC |
+		hfc_CONNECT_B2_HFC_from_ST |
+		hfc_CONNECT_B2_ST_from_HFC |
+		hfc_CONNECT_B2_GCI_from_HFC;
+	hfc_outb(card, hfc_CONNECT, card->regs.connect);
+
+	/*
+	 * All bchans are HDLC by default, not useful, actually
+	 * since mode is set during open()
+	 */
+	hfc_outb(card, hfc_CTMT, 0);
+
+	/*
+	 * bit order
+	 */
+	hfc_outb(card, hfc_CIRM, 0);
+
+	/*
+	 * Enable D-rx FIFO. At least one FIFO must be enabled (by specs)
+	 */
+	card->regs.fifo_en = hfc_FIFOEN_DRX;
+	hfc_outb(card, hfc_FIFO_EN, card->regs.fifo_en);
+
+	card->late_irqs = 0;
+
+	/*
+	 * Clear already pending ints
+	 */
+	hfc_inb(card, hfc_INT_S1);
+	hfc_inb(card, hfc_INT_S2);
+
+	/*
+	 * Enable IRQ output
+	 */
+	card->regs.m1 = hfc_INTS_DREC | hfc_INTS_L1STATE | hfc_INTS_TIMER;
+	hfc_outb(card, hfc_INT_M1, card->regs.m1);
+
+	card->regs.m2 = hfc_M2_IRQ_ENABLE;
+	hfc_outb(card, hfc_INT_M2, card->regs.m2);
+
+	/*
+	 * Unlocks the states machine
+	 */
+	hfc_outb(card, hfc_STATES, 0);
+
+	/*
+	 * There's no need to explicitly activate L1 now.
+	 * Activation is managed inside the interrupt routine.
+	 */
+}
+
+static void hfc_update_fifo_state(struct hfc_card *card)
+{
+	/*
+	 * I'm not sure if irqsave is needed but there could be a race
+	 * condition since hfc_update_fifo_state could be called from
+	 * both the IRQ handler and the *_(open|close) functions
+	 */
+
+	unsigned long flags;
+	spin_lock_irqsave(&card->chans[B1].lock, flags);
+	if (!card->fifo_suspended &&
+		(card->chans[B1].status == open_framed ||
+		card->chans[B1].status == open_voice)) {
+
+		if (!(card->regs.fifo_en & hfc_FIFOEN_B1RX)) {
+			card->regs.fifo_en |= hfc_FIFOEN_B1RX;
+			hfc_clear_fifo_rx(&card->chans[B1].rx);
+		}
+
+		if (!(card->regs.fifo_en & hfc_FIFOEN_B1TX)) {
+			card->regs.fifo_en |= hfc_FIFOEN_B1TX;
+			hfc_clear_fifo_tx(&card->chans[B1].tx);
+		}
+	} else {
+		if (card->regs.fifo_en & hfc_FIFOEN_B1RX)
+			card->regs.fifo_en &= ~hfc_FIFOEN_B1RX;
+		if (card->regs.fifo_en & hfc_FIFOEN_B1TX)
+			card->regs.fifo_en &= ~hfc_FIFOEN_B1TX;
+	}
+	spin_unlock_irqrestore(&card->chans[B1].lock, flags);
+
+	spin_lock_irqsave(&card->chans[B2].lock, flags);
+	if (!card->fifo_suspended &&
+		(card->chans[B2].status == open_framed ||
+		card->chans[B2].status == open_voice ||
+		card->chans[B2].status == sniff_aux)) {
+
+		if (!(card->regs.fifo_en & hfc_FIFOEN_B2RX)) {
+			card->regs.fifo_en |= hfc_FIFOEN_B2RX;
+			hfc_clear_fifo_rx(&card->chans[B2].rx);
+		}
+
+		if (!(card->regs.fifo_en & hfc_FIFOEN_B2TX)) {
+			card->regs.fifo_en |= hfc_FIFOEN_B2TX;
+			hfc_clear_fifo_tx(&card->chans[B2].tx);
+		}
+	} else {
+		if (card->regs.fifo_en & hfc_FIFOEN_B2RX)
+			card->regs.fifo_en &= ~hfc_FIFOEN_B2RX;
+		if (card->regs.fifo_en & hfc_FIFOEN_B2TX)
+			card->regs.fifo_en &= ~hfc_FIFOEN_B2TX;
+	}
+	spin_unlock_irqrestore(&card->chans[B2].lock, flags);
+
+	spin_lock_irqsave(&card->chans[D].lock, flags);
+	if (!card->fifo_suspended &&
+		card->chans[D].status == open_framed) {
+
+		if (!(card->regs.fifo_en & hfc_FIFOEN_DTX)) {
+			card->regs.fifo_en |= hfc_FIFOEN_DTX;
+
+			card->chans[D].tx.ugly_framebuf_size = 0;
+			card->chans[D].tx.ugly_framebuf_off = 0;
+		}
+	} else {
+		if (card->regs.fifo_en & hfc_FIFOEN_DTX)
+			card->regs.fifo_en &= ~hfc_FIFOEN_DTX;
+	}
+	spin_unlock_irqrestore(&card->chans[D].lock, flags);
+
+	hfc_outb(card, hfc_FIFO_EN, card->regs.fifo_en);
+}
+
+static inline void hfc_suspend_fifo(struct hfc_card *card)
+{
+	card->fifo_suspended = TRUE;
+
+	hfc_update_fifo_state(card);
+
+	/*
+	 * When L1 goes down D rx receives garbage; it is nice to
+	 * clear it to avoid a CRC error on reactivation
+	 * udelay is needed because the FIFO deactivation happens
+	 * in 250us
+	 */
+	udelay(250);
+	hfc_clear_fifo_rx(&card->chans[D].rx);
+
+#ifdef DEBUG
+	if (debug_level >= 3) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"FIFOs suspended\n",
+			card->cardnum);
+	}
+#endif
+}
+
+static inline void hfc_resume_fifo(struct hfc_card *card)
+{
+	card->fifo_suspended = FALSE;
+
+	hfc_update_fifo_state(card);
+
+#ifdef DEBUG
+	if (debug_level >= 3) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"FIFOs resumed\n",
+			card->cardnum);
+	}
+#endif
+}
+
+static void hfc_check_l1_up(struct hfc_card *card)
+{
+	if ((!card->nt_mode && card->l1_state != 7)
+		|| (card->nt_mode && card->l1_state != 3)) {
+
+		hfc_outb(card, hfc_STATES, hfc_STATES_DO_ACTION |
+			hfc_STATES_ACTIVATE|
+				hfc_STATES_NT_G2_G3);
+
+	/*
+	 * 0 because this is quite verbose when an inferface is unconnected, jaw
+	 */
+#if 0
+		if (debug_level >= 1) {
+			printk(KERN_DEBUG hfc_DRIVER_PREFIX
+				"card %d: "
+				"L1 is down, bringing up L1.\n",
+				card->cardnum);
+		}
+#endif
+	}
+}
+
+
+/*******************
+ * Dahdi interface *
+ *******************/
+
+static int hfc_zap_open(struct dahdi_chan *zaptel_chan)
+{
+	struct hfc_chan_duplex *chan = zaptel_chan->pvt;
+	struct hfc_card *card = chan->card;
+
+	spin_lock(&chan->lock);
+
+	switch (chan->number) {
+	case D:
+		if (chan->status != free &&
+			chan->status != open_framed) {
+			spin_unlock(&chan->lock);
+			return -EBUSY;
+		}
+		chan->status = open_framed;
+	break;
+
+	case B1:
+	case B2:
+		if (chan->status != free) {
+			spin_unlock(&chan->lock);
+			return -EBUSY;
+		}
+		chan->status = open_voice;
+	break;
+	}
+
+	chan->open_by_zaptel = TRUE;
+	try_module_get(THIS_MODULE);
+	spin_unlock(&chan->lock);
+
+	switch (chan->number) {
+	case D:
+	break;
+
+	case B1:
+		card->regs.m2 |= hfc_M2_PROC_TRANS;
+		/*
+		 * Enable transparent mode
+		 */
+		card->regs.ctmt |= hfc_CTMT_TRANSB1;
+		/*
+		* Reversed bit order
+		*/
+		card->regs.cirm |= hfc_CIRM_B1_REV;
+		/*
+		 * Enable transmission
+		 */
+		card->regs.sctrl |= hfc_SCTRL_B1_ENA;
+		/*
+		 * Enable reception
+		 */
+		card->regs.sctrl_r |= hfc_SCTRL_R_B1_ENA;
+	break;
+
+	case B2:
+		card->regs.m2 |= hfc_M2_PROC_TRANS;
+		card->regs.ctmt |= hfc_CTMT_TRANSB2;
+		card->regs.cirm |= hfc_CIRM_B2_REV;
+		card->regs.sctrl |= hfc_SCTRL_B2_ENA;
+		card->regs.sctrl_r |= hfc_SCTRL_R_B2_ENA;
+	break;
+
+	}
+
+	/*
+	 * If not already enabled, enable processing transition (8KHz)
+	 * interrupt
+	 */
+	hfc_outb(card, hfc_INT_M2, card->regs.m2);
+	hfc_outb(card, hfc_CTMT, card->regs.ctmt);
+	hfc_outb(card, hfc_CIRM, card->regs.cirm);
+	hfc_outb(card, hfc_SCTRL, card->regs.sctrl);
+	hfc_outb(card, hfc_SCTRL_R, card->regs.sctrl_r);
+
+	hfc_update_fifo_state(card);
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		"card %d: "
+		"chan %s opened as %s.\n",
+		card->cardnum,
+		chan->name,
+		zaptel_chan->name);
+
+	return 0;
+}
+
+static int hfc_zap_close(struct dahdi_chan *zaptel_chan)
+{
+	struct hfc_chan_duplex *chan = zaptel_chan->pvt;
+	struct hfc_card *card = chan->card;
+
+	if (!card) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"hfc_zap_close called with NULL card\n");
+		return -1;
+	}
+
+	spin_lock(&chan->lock);
+
+	if (chan->status == free) {
+		spin_unlock(&chan->lock);
+		return -EINVAL;
+	}
+
+	chan->status = free;
+	chan->open_by_zaptel = FALSE;
+
+	spin_unlock(&chan->lock);
+
+	switch (chan->number) {
+	case D:
+	break;
+
+	case B1:
+		card->regs.ctmt &= ~hfc_CTMT_TRANSB1;
+		card->regs.cirm &= ~hfc_CIRM_B1_REV;
+		card->regs.sctrl &= ~hfc_SCTRL_B1_ENA;
+		card->regs.sctrl_r &= ~hfc_SCTRL_R_B1_ENA;
+	break;
+
+	case B2:
+		card->regs.ctmt &= ~hfc_CTMT_TRANSB2;
+		card->regs.cirm &= ~hfc_CIRM_B2_REV;
+		card->regs.sctrl &= ~hfc_SCTRL_B2_ENA;
+		card->regs.sctrl_r &= ~hfc_SCTRL_R_B2_ENA;
+	break;
+	}
+
+	if (card->chans[B1].status == free &&
+		card->chans[B2].status == free)
+		card->regs.m2 &= ~hfc_M2_PROC_TRANS;
+
+	hfc_outb(card, hfc_INT_M2, card->regs.m2);
+	hfc_outb(card, hfc_CTMT, card->regs.ctmt);
+	hfc_outb(card, hfc_CIRM, card->regs.cirm);
+	hfc_outb(card, hfc_SCTRL, card->regs.sctrl);
+	hfc_outb(card, hfc_SCTRL_R, card->regs.sctrl_r);
+
+	hfc_update_fifo_state(card);
+
+	module_put(THIS_MODULE);
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		"card %d: "
+		"chan %s closed as %s.\n",
+		card->cardnum,
+		chan->name,
+		zaptel_chan->name);
+
+	return 0;
+}
+
+static int hfc_zap_rbsbits(struct dahdi_chan *chan, int bits)
+{
+	return 0;
+}
+
+static int hfc_zap_ioctl(struct dahdi_chan *chan,
+		unsigned int cmd, unsigned long data)
+{
+	switch (cmd) {
+
+	default:
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static void hfc_hdlc_hard_xmit(struct dahdi_chan *d_chan)
+{
+	struct hfc_chan_duplex *chan = d_chan->pvt;
+	struct hfc_card *card = chan->card;
+	struct dahdi_hfc *hfccard = card->ztdev;
+
+	atomic_inc(&hfccard->hdlc_pending);
+
+}
+
+static int hfc_zap_startup(struct file *file, struct dahdi_span *span)
+{
+	struct dahdi_hfc *zthfc = dahdi_hfc_from_span(span);
+	struct hfc_card *hfctmp = zthfc->card;
+	int alreadyrunning;
+
+	if (!hfctmp) {
+		printk(KERN_INFO hfc_DRIVER_PREFIX
+			"card %d: "
+			"no card for span at startup!\n",
+			hfctmp->cardnum);
+	}
+
+	alreadyrunning = span->flags & DAHDI_FLAG_RUNNING;
+
+	if (!alreadyrunning)
+		span->flags |= DAHDI_FLAG_RUNNING;
+
+	return 0;
+}
+
+static int hfc_zap_shutdown(struct dahdi_span *span)
+{
+	return 0;
+}
+
+static int hfc_zap_maint(struct dahdi_span *span, int cmd)
+{
+	return 0;
+}
+
+static int hfc_zap_chanconfig(struct file *file, struct dahdi_chan *d_chan,
+		int sigtype)
+{
+	struct hfc_chan_duplex *chan = d_chan->pvt;
+	struct hfc_card *card = chan->card;
+	struct dahdi_hfc *hfccard = card->ztdev;
+
+	if ((sigtype == DAHDI_SIG_HARDHDLC) && (hfccard->sigchan == d_chan)) {
+		hfccard->sigactive = 0;
+		atomic_set(&hfccard->hdlc_pending, 0);
+	}
+
+	return 0;
+}
+
+static int hfc_zap_spanconfig(struct file *file, struct dahdi_span *span,
+		struct dahdi_lineconfig *lc)
+{
+	span->lineconfig = lc->lineconfig;
+
+	return 0;
+}
+
+static const struct dahdi_span_ops hfc_zap_span_ops = {
+	.owner = THIS_MODULE,
+	.chanconfig = hfc_zap_chanconfig,
+	.spanconfig = hfc_zap_spanconfig,
+	.startup = hfc_zap_startup,
+	.shutdown = hfc_zap_shutdown,
+	.maint = hfc_zap_maint,
+	.rbsbits = hfc_zap_rbsbits,
+	.open = hfc_zap_open,
+	.close = hfc_zap_close,
+	.ioctl = hfc_zap_ioctl,
+	.hdlc_hard_xmit = hfc_hdlc_hard_xmit
+};
+
+static int hfc_zap_initialize(struct dahdi_hfc *hfccard)
+{
+	 struct hfc_card *hfctmp = hfccard->card;
+	int i;
+
+	hfccard->ddev = dahdi_create_device();
+	hfccard->ddev->manufacturer = "Cologne Chips";
+	hfccard->ddev->devicetype = "HFC-S PCI-A ISDN";
+	hfccard->ddev->location = kasprintf(GFP_KERNEL,
+			"PCI Bus %02d Slot %02d",
+			hfctmp->pcidev->bus->number,
+			PCI_SLOT(hfctmp->pcidev->devfn) + 1);
+	memset(&hfccard->span, 0x0, sizeof(struct dahdi_span));
+	sprintf(hfccard->span.name, "ZTHFC%d", hfctmp->cardnum + 1);
+	sprintf(hfccard->span.desc,
+			"HFC-S PCI A ISDN card %d [%s] ",
+			hfctmp->cardnum,
+			hfctmp->nt_mode ? "NT" : "TE");
+	hfccard->span.spantype = hfctmp->nt_mode ? "NT" : "TE";
+	hfccard->span.flags = 0;
+	hfccard->span.ops = &hfc_zap_span_ops;
+	hfccard->span.chans = hfccard->_chans;
+	hfccard->span.channels = 3;
+	for (i = 0; i < hfccard->span.channels; i++)
+		hfccard->_chans[i] = &hfccard->chans[i];
+	hfccard->span.deflaw = DAHDI_LAW_ALAW;
+	hfccard->span.linecompat = DAHDI_CONFIG_AMI | DAHDI_CONFIG_CCS;
+	hfccard->span.offset = 0;
+
+	for (i = 0; i < hfccard->span.channels; i++) {
+		memset(&hfccard->chans[i], 0x0, sizeof(struct dahdi_chan));
+
+		sprintf(hfccard->chans[i].name,
+				"ZTHFC%d/%d/%d",
+				hfctmp->cardnum + 1, 0, i + 1);
+
+		printk(KERN_INFO hfc_DRIVER_PREFIX
+			"card %d: "
+			"registered %s\n",
+			hfctmp->cardnum,
+			hfccard->chans[i].name);
+
+		if (i == hfccard->span.channels - 1) {
+			hfccard->chans[i].sigcap = DAHDI_SIG_HARDHDLC;
+			hfccard->sigchan = &hfccard->chans[DAHDI_D];
+			hfccard->sigactive = 0;
+			atomic_set(&hfccard->hdlc_pending, 0);
+		} else {
+			hfccard->chans[i].sigcap =
+				DAHDI_SIG_CLEAR | DAHDI_SIG_DACS;
+		}
+
+		hfccard->chans[i].chanpos = i + 1;
+	}
+
+	hfccard->chans[DAHDI_D].readchunk  =
+		hfctmp->chans[D].rx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_D].writechunk =
+		hfctmp->chans[D].tx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_D].pvt = &hfctmp->chans[D];
+
+	hfccard->chans[DAHDI_B1].readchunk  =
+		hfctmp->chans[B1].rx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_B1].writechunk =
+		hfctmp->chans[B1].tx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_B1].pvt = &hfctmp->chans[B1];
+
+	hfccard->chans[DAHDI_B2].readchunk  =
+		hfctmp->chans[B2].rx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_B2].writechunk =
+		hfctmp->chans[B2].tx.zaptel_buffer;
+
+	hfccard->chans[DAHDI_B2].pvt = &hfctmp->chans[B2];
+
+	list_add_tail(&hfccard->span.device_node, &hfccard->ddev->spans);
+	if (dahdi_register_device(hfccard->ddev, &hfctmp->pcidev->dev)) {
+		printk(KERN_ERR "unable to register DAHDI device %s!\n",
+				hfccard->span.name);
+		return -1; /* FIXME: release resources? */
+	}
+
+	return 0;
+}
+
+static void hfc_dahdi_free(struct dahdi_hfc *hfccard)
+{
+	kfree(hfccard->ddev->location);
+	dahdi_free_device(hfccard->ddev);
+}
+
+static void hfc_zap_transmit(struct hfc_chan_simplex *chan)
+{
+	hfc_fifo_put(chan, chan->zaptel_buffer, DAHDI_CHUNKSIZE);
+}
+
+static void hfc_zap_receive(struct hfc_chan_simplex *chan)
+{
+	hfc_fifo_get(chan, chan->zaptel_buffer, DAHDI_CHUNKSIZE);
+}
+
+/******************************************
+ * Interrupt Handler
+ ******************************************/
+
+static void hfc_handle_timer_interrupt(struct hfc_card *card);
+static void hfc_handle_state_interrupt(struct hfc_card *card);
+static void hfc_handle_processing_interrupt(struct hfc_card *card);
+static void hfc_frame_arrived(struct hfc_chan_duplex *chan);
+static void hfc_handle_voice(struct hfc_card *card);
+
+#if (KERNEL_VERSION(2, 6, 24) < LINUX_VERSION_CODE)
+static irqreturn_t hfc_interrupt(int irq, void *dev_id)
+#else
+static irqreturn_t hfc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	struct hfc_card *card = dev_id;
+	unsigned long flags;
+	u8 status, s1, s2;
+
+	if (!card) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"spurious interrupt (IRQ %d)\n",
+			irq);
+		return IRQ_NONE;
+	}
+
+	spin_lock_irqsave(&card->lock, flags);
+	status = hfc_inb(card, hfc_STATUS);
+	if (!(status & hfc_STATUS_ANYINT)) {
+		/*
+		 * maybe we are sharing the irq
+		 */
+		spin_unlock_irqrestore(&card->lock, flags);
+		return IRQ_NONE;
+	}
+
+	/* We used to ingore the IRQ when the card was in processing
+	 * state but apparently there is no restriction to access the
+	 * card in such state:
+	 *
+	 * Joerg Ciesielski wrote:
+	 * > There is no restriction for the IRQ handler to access
+	 * > HFC-S PCI during processing phase. A IRQ latency of 375 us
+	 * > is also no problem since there are no interrupt sources in
+	 * > HFC-S PCI which must be handled very fast.
+	 * > Due to its deep fifos the IRQ latency can be several ms with
+	 * > out the risk of loosing data. Even the S/T state interrupts
+	 * > must not be handled with a latency less than <5ms.
+	 * >
+	 * > The processing phase only indicates that HFC-S PCI is
+	 * > processing the Fifos as PCI master so that data is read and
+	 * > written in the 32k memory window. But there is no restriction
+	 * > to access data in the memory window during this time.
+	 *
+	 * // if (status & hfc_STATUS_PCI_PROC) {
+	 * // return IRQ_HANDLED;
+	 * // }
+	 */
+
+	s1 = hfc_inb(card, hfc_INT_S1);
+	s2 = hfc_inb(card, hfc_INT_S2);
+
+	if (s1 != 0) {
+		if (s1 & hfc_INTS_TIMER) {
+			/*
+			 * timer (bit 7)
+			 */
+			hfc_handle_timer_interrupt(card);
+		}
+
+		if (s1 & hfc_INTS_L1STATE) {
+			/*
+			 * state machine (bit 6)
+			 */
+			hfc_handle_state_interrupt(card);
+		}
+
+		if (s1 & hfc_INTS_DREC) {
+			/*
+			 * D chan RX (bit 5)
+			 */
+			hfc_frame_arrived(&card->chans[D]);
+		}
+
+		if (s1 & hfc_INTS_B1REC) {
+			/*
+			 * B1 chan RX (bit 3)
+			 */
+			hfc_frame_arrived(&card->chans[B1]);
+		}
+
+		if (s1 & hfc_INTS_B2REC) {
+			/*
+			 * B2 chan RX (bit 4)
+			 */
+			hfc_frame_arrived(&card->chans[B2]);
+		}
+
+		if (s1 & hfc_INTS_DTRANS) {
+			/*
+			 * D chan TX (bit 2)
+			 */
+		}
+
+		if (s1 & hfc_INTS_B1TRANS) {
+			/*
+			 * B1 chan TX (bit 0)
+			 */
+		}
+
+		if (s1 & hfc_INTS_B2TRANS) {
+			/*
+			 * B2 chan TX (bit 1)
+			 */
+		}
+
+	}
+
+	if (s2 != 0) {
+		if (s2 & hfc_M2_PMESEL) {
+			/*
+			 * kaboom irq (bit 7)
+			 *
+			 * CologneChip says:
+			 *
+			 * the meaning of this fatal error bit is that HFC-S
+			 * PCI as PCI master could not access the PCI bus
+			 * within 125us to finish its data processing. If this
+			 * happens only very seldom it does not cause big
+			 * problems but of course some B-channel or D-channel
+			 * data will be corrupted due to this event.
+			 *
+			 * Unfortunately this bit is only set once after the
+			 * problem occurs and can only be reseted by a
+			 * software reset. That means it is not easily
+			 * possible to check how often this fatal error
+			 * happens.
+			 *
+			 */
+
+			if (!card->sync_loss_reported) {
+				printk(KERN_CRIT hfc_DRIVER_PREFIX
+					"card %d: "
+					"sync lost, pci performance too low!\n",
+					card->cardnum);
+
+				card->sync_loss_reported = TRUE;
+			}
+		}
+
+		if (s2 & hfc_M2_GCI_MON_REC) {
+			/*
+			 * RxR monitor channel (bit 2)
+			 */
+		}
+
+		if (s2 & hfc_M2_GCI_I_CHG) {
+			/*
+			 * GCI I-change  (bit 1)
+			 */
+		}
+
+		if (s2 & hfc_M2_PROC_TRANS) {
+			/*
+			 * processing/non-processing transition  (bit 0)
+			 */
+			hfc_handle_processing_interrupt(card);
+		}
+
+	}
+
+	spin_unlock_irqrestore(&card->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+static void hfc_handle_timer_interrupt(struct hfc_card *card)
+{
+	if (card->ignore_first_timer_interrupt) {
+		card->ignore_first_timer_interrupt = FALSE;
+		return;
+	}
+
+	if ((card->nt_mode && card->l1_state == 3) ||
+		(!card->nt_mode && card->l1_state == 7)) {
+
+		card->regs.ctmt &= ~hfc_CTMT_TIMER_MASK;
+		hfc_outb(card, hfc_CTMT, card->regs.ctmt);
+
+		hfc_resume_fifo(card);
+	}
+}
+
+static void hfc_handle_state_interrupt(struct hfc_card *card)
+{
+	u8 new_state = hfc_inb(card, hfc_STATES)  & hfc_STATES_STATE_MASK;
+
+#ifdef DEBUG
+	if (debug_level >= 1) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"layer 1 state = %c%d\n",
+			card->cardnum,
+			card->nt_mode ? 'G' : 'F',
+			new_state);
+	}
+#endif
+
+	if (card->nt_mode) {
+		/*
+		 * NT mode
+		 */
+
+		if (new_state == 3) {
+			/*
+			 * fix to G3 state (see specs)
+			 */
+			hfc_outb(card, hfc_STATES, hfc_STATES_LOAD_STATE | 3);
+		}
+
+		if (new_state == 3 && card->l1_state != 3)
+			hfc_resume_fifo(card);
+
+		if (new_state != 3 && card->l1_state == 3)
+			hfc_suspend_fifo(card);
+
+	} else {
+		if (new_state == 3) {
+			/*
+			 * Keep L1 up... zaptel & libpri expects
+			 * a always up L1...
+			 * Enable only  when using an unpatched libpri
+			 */
+
+			if (force_l1_up) {
+				hfc_outb(card, hfc_STATES,
+					hfc_STATES_DO_ACTION |
+					hfc_STATES_ACTIVATE|
+					hfc_STATES_NT_G2_G3);
+			}
+		}
+
+		if (new_state == 7 && card->l1_state != 7) {
+			/*
+			 * TE is now active, schedule FIFO activation after
+			 * some time, otherwise the first frames are lost
+			 */
+
+			card->regs.ctmt |= hfc_CTMT_TIMER_50 |
+				hfc_CTMT_TIMER_CLEAR;
+			hfc_outb(card, hfc_CTMT, card->regs.ctmt);
+
+			/*
+			 * Activating the timer firest an
+			 * interrupt immediately, we
+			 * obviously need to ignore it
+			 */
+			card->ignore_first_timer_interrupt = TRUE;
+		}
+
+		if (new_state != 7 && card->l1_state == 7) {
+			/*
+			 * TE has become inactive, disable FIFO
+			 */
+			hfc_suspend_fifo(card);
+		}
+	}
+
+	card->l1_state = new_state;
+}
+
+static void hfc_handle_processing_interrupt(struct hfc_card *card)
+{
+	int available_bytes = 0;
+
+	/*
+	 * Synchronize with the first enabled channel
+	 */
+	if (card->regs.fifo_en & hfc_FIFOEN_B1RX)
+		available_bytes = hfc_fifo_used_rx(&card->chans[B1].rx);
+	if (card->regs.fifo_en & hfc_FIFOEN_B2RX)
+		available_bytes = hfc_fifo_used_rx(&card->chans[B2].rx);
+	else
+		available_bytes = -1;
+
+	if ((available_bytes == -1 && card->ticks == 8) ||
+		available_bytes >= DAHDI_CHUNKSIZE + hfc_RX_FIFO_PRELOAD) {
+		card->ticks = 0;
+
+		if (available_bytes > DAHDI_CHUNKSIZE*2 + hfc_RX_FIFO_PRELOAD) {
+			card->late_irqs++;
+			/*
+			 * we are out of sync, clear fifos, jaw
+			 */
+			hfc_clear_fifo_rx(&card->chans[B1].rx);
+			hfc_clear_fifo_tx(&card->chans[B1].tx);
+			hfc_clear_fifo_rx(&card->chans[B2].rx);
+			hfc_clear_fifo_tx(&card->chans[B2].tx);
+
+#ifdef DEBUG
+			if (debug_level >= 4) {
+				printk(KERN_DEBUG hfc_DRIVER_PREFIX
+					"card %d: "
+					"late IRQ, %d bytes late\n",
+					card->cardnum,
+					available_bytes -
+						(DAHDI_CHUNKSIZE +
+						 hfc_RX_FIFO_PRELOAD));
+			}
+#endif
+		} else {
+			hfc_handle_voice(card);
+		}
+	}
+
+	card->ticks++;
+}
+
+
+static void hfc_handle_voice(struct hfc_card *card)
+{
+	struct dahdi_hfc *hfccard = card->ztdev;
+	int frame_left, res;
+	unsigned char buf[hfc_HDLC_BUF_LEN];
+	unsigned int size = sizeof(buf) / sizeof(buf[0]);
+
+
+	if (card->chans[B1].status != open_voice &&
+		card->chans[B2].status != open_voice)
+		return;
+
+	dahdi_transmit(&hfccard->span);
+
+	if (card->regs.fifo_en & hfc_FIFOEN_B1TX)
+		hfc_zap_transmit(&card->chans[B1].tx);
+	if (card->regs.fifo_en & hfc_FIFOEN_B2TX)
+		hfc_zap_transmit(&card->chans[B2].tx);
+
+	/*
+	 * dahdi hdlc frame tx
+	 */
+
+	if (atomic_read(&hfccard->hdlc_pending)) {
+		hfc_check_l1_up(card);
+		res = dahdi_hdlc_getbuf(hfccard->sigchan, buf, &size);
+			if (size > 0) {
+				hfccard->sigactive = 1;
+				memcpy(card->chans[D].tx.ugly_framebuf +
+				card->chans[D].tx.ugly_framebuf_size,
+				buf, size);
+				card->chans[D].tx.ugly_framebuf_size += size;
+			if (res != 0) {
+					hfc_fifo_put_frame(&card->chans[D].tx,
+					card->chans[D].tx.ugly_framebuf,
+					card->chans[D].tx.ugly_framebuf_size);
+					++hfccard->frames_out;
+					hfccard->sigactive = 0;
+					card->chans[D].tx.ugly_framebuf_size
+						= 0;
+					atomic_dec(&hfccard->hdlc_pending);
+				}
+			}
+	}
+	/*
+	 * dahdi hdlc frame tx done
+	 */
+
+	if (card->regs.fifo_en & hfc_FIFOEN_B1RX)
+		hfc_zap_receive(&card->chans[B1].rx);
+	else
+		memset(&card->chans[B1].rx.zaptel_buffer, 0x7f,
+			sizeof(card->chans[B1].rx.zaptel_buffer));
+
+	if (card->regs.fifo_en & hfc_FIFOEN_B2RX)
+		hfc_zap_receive(&card->chans[B2].rx);
+	else
+		memset(&card->chans[B2].rx.zaptel_buffer, 0x7f,
+			sizeof(card->chans[B1].rx.zaptel_buffer));
+
+	/*
+	 * Echo cancellation
+	 */
+	dahdi_ec_chunk(&hfccard->chans[DAHDI_B1],
+			card->chans[B1].rx.zaptel_buffer,
+			card->chans[B1].tx.zaptel_buffer);
+	dahdi_ec_chunk(&hfccard->chans[DAHDI_B2],
+			card->chans[B2].rx.zaptel_buffer,
+			card->chans[B2].tx.zaptel_buffer);
+
+	/*
+	 * dahdi hdlc frame rx
+	 */
+	if (hfc_fifo_has_frames(&card->chans[D].rx))
+		hfc_frame_arrived(&card->chans[D]);
+
+	if (card->chans[D].rx.ugly_framebuf_size) {
+		frame_left = card->chans[D].rx.ugly_framebuf_size -
+			card->chans[D].rx.ugly_framebuf_off ;
+		if (frame_left > hfc_HDLC_BUF_LEN) {
+			dahdi_hdlc_putbuf(hfccard->sigchan,
+					card->chans[D].rx.ugly_framebuf +
+					card->chans[D].rx.ugly_framebuf_off,
+					hfc_HDLC_BUF_LEN);
+			card->chans[D].rx.ugly_framebuf_off +=
+				hfc_HDLC_BUF_LEN;
+		} else {
+			dahdi_hdlc_putbuf(hfccard->sigchan,
+					card->chans[D].rx.ugly_framebuf +
+					card->chans[D].rx.ugly_framebuf_off,
+					frame_left);
+			dahdi_hdlc_finish(hfccard->sigchan);
+			card->chans[D].rx.ugly_framebuf_size = 0;
+			card->chans[D].rx.ugly_framebuf_off = 0;
+		}
+	}
+	/*
+	 * dahdi hdlc frame rx done
+	 */
+
+	if (hfccard->span.flags & DAHDI_FLAG_RUNNING)
+		dahdi_receive(&hfccard->span);
+
+}
+
+static void hfc_frame_arrived(struct hfc_chan_duplex *chan)
+{
+	struct hfc_card *card = chan->card;
+	int antiloop = 16;
+	struct sk_buff *skb;
+
+	while (hfc_fifo_has_frames(&chan->rx) && --antiloop) {
+		int frame_size = hfc_fifo_get_frame_size(&chan->rx);
+
+		if (frame_size < 3) {
+#ifdef DEBUG
+			if (debug_level >= 2)
+				printk(KERN_DEBUG hfc_DRIVER_PREFIX
+					"card %d: "
+					"chan %s: "
+					"invalid frame received, "
+					"just %d bytes\n",
+					card->cardnum,
+					chan->name,
+					frame_size);
+#endif
+
+			hfc_fifo_drop_frame(&chan->rx);
+
+
+			continue;
+		} else if (frame_size == 3) {
+#ifdef DEBUG
+			if (debug_level >= 2)
+				printk(KERN_DEBUG hfc_DRIVER_PREFIX
+					"card %d: "
+					"chan %s: "
+					"empty frame received\n",
+					card->cardnum,
+					chan->name);
+#endif
+
+			hfc_fifo_drop_frame(&chan->rx);
+
+
+			continue;
+		}
+
+		if (chan->open_by_zaptel &&
+			card->chans[D].rx.ugly_framebuf_size) {
+
+				/*
+				 * We have to wait for Dahdi to transmit the
+				 * frame... wait for next time
+				 */
+
+				 break;
+		}
+
+		skb = dev_alloc_skb(frame_size - 3);
+
+		if (!skb) {
+			printk(KERN_ERR hfc_DRIVER_PREFIX
+				"card %d: "
+				"chan %s: "
+				"cannot allocate skb: frame dropped\n",
+				card->cardnum,
+				chan->name);
+
+			hfc_fifo_drop_frame(&chan->rx);
+
+
+			continue;
+		}
+
+
+		/*
+		* HFC does the checksum
+		*/
+#ifndef CHECKSUM_HW
+		skb->ip_summed = CHECKSUM_COMPLETE;
+#else
+		skb->ip_summed = CHECKSUM_HW;
+#endif
+
+		if (chan->open_by_zaptel) {
+			card->chans[D].rx.ugly_framebuf_size = frame_size - 1;
+
+			if (hfc_fifo_get_frame(&card->chans[D].rx,
+				card->chans[D].rx.ugly_framebuf,
+				frame_size - 1) == -1) {
+				dev_kfree_skb(skb);
+				continue;
+			}
+
+			memcpy(skb_put(skb, frame_size - 3),
+				card->chans[D].rx.ugly_framebuf,
+				frame_size - 3);
+		} else {
+			if (hfc_fifo_get_frame(&chan->rx,
+				skb_put(skb, frame_size - 3),
+				frame_size - 3) == -1) {
+				dev_kfree_skb(skb);
+				continue;
+			}
+		}
+	}
+
+	if (!antiloop)
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"Infinite loop detected\n",
+			card->cardnum);
+}
+
+/******************************************
+ * Module initialization and cleanup
+ ******************************************/
+
+static int __devinit hfc_probe(struct pci_dev *pci_dev,
+	const struct pci_device_id *ent)
+{
+	static int cardnum;
+	int err;
+	int i;
+
+	struct hfc_card *card = NULL;
+	struct dahdi_hfc *zthfc = NULL;
+	card = kmalloc(sizeof(struct hfc_card), GFP_KERNEL);
+	if (!card) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"unable to kmalloc!\n");
+		err = -ENOMEM;
+		goto err_alloc_hfccard;
+	}
+
+	memset(card, 0x00, sizeof(struct hfc_card));
+	card->cardnum = cardnum;
+	card->pcidev = pci_dev;
+	spin_lock_init(&card->lock);
+
+	pci_set_drvdata(pci_dev, card);
+
+	err = pci_enable_device(pci_dev);
+	if (err)
+		goto err_pci_enable_device;
+
+	err = pci_set_dma_mask(pci_dev, PCI_DMA_32BIT);
+	if (err) {
+		printk(KERN_ERR hfc_DRIVER_PREFIX
+			"card %d: "
+			"No suitable DMA configuration available.\n",
+			card->cardnum);
+		goto err_pci_set_dma_mask;
+	}
+
+	pci_write_config_word(pci_dev, PCI_COMMAND, PCI_COMMAND_MEMORY);
+	err = pci_request_regions(pci_dev, hfc_DRIVER_NAME);
+	if (err) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"cannot request I/O memory region\n",
+			card->cardnum);
+		goto err_pci_request_regions;
+	}
+
+	pci_set_master(pci_dev);
+
+	if (!pci_dev->irq) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"no irq!\n",
+			card->cardnum);
+		err = -ENODEV;
+		goto err_noirq;
+	}
+
+	card->io_bus_mem = pci_resource_start(pci_dev, 1);
+	if (!card->io_bus_mem) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"no iomem!\n",
+			card->cardnum);
+		err = -ENODEV;
+		goto err_noiobase;
+	}
+
+	card->io_mem = ioremap(card->io_bus_mem, hfc_PCI_MEM_SIZE);
+	if (!(card->io_mem)) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"cannot ioremap I/O memory\n",
+			card->cardnum);
+		err = -ENODEV;
+		goto err_ioremap;
+	}
+
+	/*
+	 * pci_alloc_consistent guarantees alignment
+	 * (Documentation/DMA-mapping.txt)
+	 */
+	card->fifo_mem = pci_alloc_consistent(pci_dev,
+			hfc_FIFO_SIZE, &card->fifo_bus_mem);
+	if (!card->fifo_mem) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"unable to allocate FIFO DMA memory!\n",
+			card->cardnum);
+		err = -ENOMEM;
+		goto err_alloc_fifo;
+	}
+
+	memset(card->fifo_mem, 0x00, hfc_FIFO_SIZE);
+
+	card->fifos = card->fifo_mem;
+
+	pci_write_config_dword(card->pcidev, hfc_PCI_MWBA, card->fifo_bus_mem);
+
+	err = request_irq(card->pcidev->irq, &hfc_interrupt,
+
+#if (KERNEL_VERSION(2, 6, 23) < LINUX_VERSION_CODE)
+		IRQF_SHARED, hfc_DRIVER_NAME, card);
+#else
+		SA_SHIRQ, hfc_DRIVER_NAME, card);
+#endif
+
+	if (err) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"unable to register irq\n",
+			card->cardnum);
+		goto err_request_irq;
+	}
+
+	card->nt_mode = FALSE;
+
+	if (modes & (1 << card->cardnum))
+		card->nt_mode = TRUE;
+
+	for (i = 0; i < nt_modes_count; i++) {
+		if (nt_modes[i] == card->cardnum)
+			card->nt_mode = TRUE;
+	}
+
+	/*
+	 * D Channel
+	 */
+	card->chans[D].card = card;
+	card->chans[D].name = "D";
+	card->chans[D].status = free;
+	card->chans[D].number = D;
+	spin_lock_init(&card->chans[D].lock);
+
+	card->chans[D].rx.chan      = &card->chans[D];
+	card->chans[D].rx.fifo_base = card->fifos + 0x4000;
+	card->chans[D].rx.z_base    = card->fifos + 0x4000;
+	card->chans[D].rx.z1_base   = card->fifos + 0x6080;
+	card->chans[D].rx.z2_base   = card->fifos + 0x6082;
+	card->chans[D].rx.z_min     = 0x0000;
+	card->chans[D].rx.z_max     = 0x01FF;
+	card->chans[D].rx.f_min     = 0x10;
+	card->chans[D].rx.f_max     = 0x1F;
+	card->chans[D].rx.f1        = card->fifos + 0x60a0;
+	card->chans[D].rx.f2        = card->fifos + 0x60a1;
+	card->chans[D].rx.fifo_size = card->chans[D].rx.z_max
+		- card->chans[D].rx.z_min + 1;
+	card->chans[D].rx.f_num     = card->chans[D].rx.f_max
+		- card->chans[D].rx.f_min + 1;
+
+	card->chans[D].tx.chan      = &card->chans[D];
+	card->chans[D].tx.fifo_base = card->fifos + 0x0000;
+	card->chans[D].tx.z_base    = card->fifos + 0x0000;
+	card->chans[D].tx.z1_base   = card->fifos + 0x2080;
+	card->chans[D].tx.z2_base   = card->fifos + 0x2082;
+	card->chans[D].tx.z_min     = 0x0000;
+	card->chans[D].tx.z_max     = 0x01FF;
+	card->chans[D].tx.f_min     = 0x10;
+	card->chans[D].tx.f_max     = 0x1F;
+	card->chans[D].tx.f1        = card->fifos + 0x20a0;
+	card->chans[D].tx.f2        = card->fifos + 0x20a1;
+	card->chans[D].tx.fifo_size = card->chans[D].tx.z_max -
+		card->chans[D].tx.z_min + 1;
+	card->chans[D].tx.f_num     = card->chans[D].tx.f_max -
+		card->chans[D].tx.f_min + 1;
+
+	/*
+	 * B1 Channel
+	 */
+	card->chans[B1].card = card;
+	card->chans[B1].name = "B1";
+	card->chans[B1].status = free;
+	card->chans[B1].number = B1;
+	card->chans[B1].protocol = 0;
+	spin_lock_init(&card->chans[B1].lock);
+
+	card->chans[B1].rx.chan      = &card->chans[B1];
+	card->chans[B1].rx.fifo_base = card->fifos + 0x4200;
+	card->chans[B1].rx.z_base    = card->fifos + 0x4000;
+	card->chans[B1].rx.z1_base   = card->fifos + 0x6000;
+	card->chans[B1].rx.z2_base   = card->fifos + 0x6002;
+	card->chans[B1].rx.z_min     = 0x0200;
+	card->chans[B1].rx.z_max     = 0x1FFF;
+	card->chans[B1].rx.f_min     = 0x00;
+	card->chans[B1].rx.f_max     = 0x1F;
+	card->chans[B1].rx.f1        = card->fifos + 0x6080;
+	card->chans[B1].rx.f2        = card->fifos + 0x6081;
+	card->chans[B1].rx.fifo_size = card->chans[B1].rx.z_max -
+		card->chans[B1].rx.z_min + 1;
+	card->chans[B1].rx.f_num     = card->chans[B1].rx.f_max -
+		card->chans[B1].rx.f_min + 1;
+
+	card->chans[B1].tx.chan      = &card->chans[B1];
+	card->chans[B1].tx.fifo_base = card->fifos + 0x0200;
+	card->chans[B1].tx.z_base    = card->fifos + 0x0000;
+	card->chans[B1].tx.z1_base   = card->fifos + 0x2000;
+	card->chans[B1].tx.z2_base   = card->fifos + 0x2002;
+	card->chans[B1].tx.z_min     = 0x0200;
+	card->chans[B1].tx.z_max     = 0x1FFF;
+	card->chans[B1].tx.f_min     = 0x00;
+	card->chans[B1].tx.f_max     = 0x1F;
+	card->chans[B1].tx.f1        = card->fifos + 0x2080;
+	card->chans[B1].tx.f2        = card->fifos + 0x2081;
+	card->chans[B1].tx.fifo_size = card->chans[B1].tx.z_max -
+		card->chans[B1].tx.z_min + 1;
+	card->chans[B1].tx.f_num     = card->chans[B1].tx.f_max -
+		card->chans[B1].tx.f_min + 1;
+
+	/*
+	 * B2 Channel
+	 */
+	card->chans[B2].card = card;
+	card->chans[B2].name = "B2";
+	card->chans[B2].status = free;
+	card->chans[B2].number = B2;
+	card->chans[B2].protocol = 0;
+	spin_lock_init(&card->chans[B2].lock);
+
+	card->chans[B2].rx.chan      = &card->chans[B2];
+	card->chans[B2].rx.fifo_base = card->fifos + 0x6200,
+	card->chans[B2].rx.z_base    = card->fifos + 0x6000;
+	card->chans[B2].rx.z1_base   = card->fifos + 0x6100;
+	card->chans[B2].rx.z2_base   = card->fifos + 0x6102;
+	card->chans[B2].rx.z_min     = 0x0200;
+	card->chans[B2].rx.z_max     = 0x1FFF;
+	card->chans[B2].rx.f_min     = 0x00;
+	card->chans[B2].rx.f_max     = 0x1F;
+	card->chans[B2].rx.f1        = card->fifos + 0x6180;
+	card->chans[B2].rx.f2        = card->fifos + 0x6181;
+	card->chans[B2].rx.fifo_size = card->chans[B2].rx.z_max -
+		card->chans[B2].rx.z_min + 1;
+	card->chans[B2].rx.f_num     = card->chans[B2].rx.f_max -
+		card->chans[B2].rx.f_min + 1;
+
+	card->chans[B2].tx.chan      = &card->chans[B2];
+	card->chans[B2].tx.fifo_base = card->fifos + 0x2200;
+	card->chans[B2].tx.z_base    = card->fifos + 0x2000;
+	card->chans[B2].tx.z1_base   = card->fifos + 0x2100;
+	card->chans[B2].tx.z2_base   = card->fifos + 0x2102;
+	card->chans[B2].tx.z_min     = 0x0200;
+	card->chans[B2].tx.z_max     = 0x1FFF;
+	card->chans[B2].tx.f_min     = 0x00;
+	card->chans[B2].tx.f_max     = 0x1F;
+	card->chans[B2].tx.f1        = card->fifos + 0x2180;
+	card->chans[B2].tx.f2        = card->fifos + 0x2181;
+	card->chans[B2].tx.fifo_size = card->chans[B2].tx.z_max -
+		card->chans[B2].tx.z_min + 1;
+	card->chans[B2].tx.f_num     = card->chans[B2].tx.f_max -
+		card->chans[B2].tx.f_min + 1;
+
+	/*
+	 * All done
+	 */
+
+	zthfc = kmalloc(sizeof(struct dahdi_hfc), GFP_KERNEL);
+	if (!zthfc) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"unable to kmalloc!\n");
+		goto err_request_irq;
+	}
+	memset(zthfc, 0x0, sizeof(struct dahdi_hfc));
+
+	zthfc->card = card;
+	hfc_zap_initialize(zthfc);
+	card->ztdev = zthfc;
+
+	snprintf(card->proc_dir_name,
+			sizeof(card->proc_dir_name),
+			"%d", card->cardnum);
+	card->proc_dir = proc_mkdir(card->proc_dir_name, hfc_proc_zaphfc_dir);
+	SET_PROC_DIRENTRY_OWNER(card->proc_dir);
+
+	hfc_resetCard(card);
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		"card %d configured for %s mode at mem %#lx (0x%p) IRQ %u\n",
+		card->cardnum,
+		card->nt_mode ? "NT" : "TE",
+		card->io_bus_mem,
+		card->io_mem,
+		card->pcidev->irq);
+
+	cardnum++;
+
+	return 0;
+
+err_request_irq:
+	pci_free_consistent(pci_dev, hfc_FIFO_SIZE,
+		card->fifo_mem, card->fifo_bus_mem);
+err_alloc_fifo:
+	iounmap(card->io_mem);
+err_ioremap:
+err_noiobase:
+err_noirq:
+	pci_release_regions(pci_dev);
+err_pci_request_regions:
+err_pci_set_dma_mask:
+err_pci_enable_device:
+	kfree(card);
+err_alloc_hfccard:
+	return err;
+}
+
+static void __devexit hfc_remove(struct pci_dev *pci_dev)
+{
+	struct hfc_card *card = pci_get_drvdata(pci_dev);
+
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		"card %d: "
+		"shutting down card at %p.\n",
+		card->cardnum,
+		card->io_mem);
+
+	hfc_softreset(card);
+
+	dahdi_unregister_device(card->ztdev->ddev);
+	hfc_dahdi_free(card->ztdev);
+
+
+	/*
+	 * disable memio and bustmaster
+	 */
+	pci_write_config_word(pci_dev, PCI_COMMAND, 0);
+
+	remove_proc_entry("bufs", card->proc_dir);
+	remove_proc_entry("fifos", card->proc_dir);
+	remove_proc_entry("info", card->proc_dir);
+	remove_proc_entry(card->proc_dir_name, hfc_proc_zaphfc_dir);
+
+	free_irq(pci_dev->irq, card);
+
+	pci_free_consistent(pci_dev, hfc_FIFO_SIZE,
+		card->fifo_mem, card->fifo_bus_mem);
+
+	iounmap(card->io_mem);
+
+	pci_release_regions(pci_dev);
+
+	pci_disable_device(pci_dev);
+
+	kfree(card);
+}
+
+/******************************************
+ * Module stuff
+ ******************************************/
+
+static int __init hfc_init_module(void)
+{
+	int ret;
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		hfc_DRIVER_STRING " loading\n");
+
+#if (KERNEL_VERSION(2, 6, 26) <= LINUX_VERSION_CODE)
+	hfc_proc_zaphfc_dir = proc_mkdir(hfc_DRIVER_NAME, NULL);
+#else
+	hfc_proc_zaphfc_dir = proc_mkdir(hfc_DRIVER_NAME, proc_root_driver);
+#endif
+
+	ret = dahdi_pci_module(&hfc_driver);
+	return ret;
+}
+
+module_init(hfc_init_module);
+
+static void __exit hfc_module_exit(void)
+{
+	pci_unregister_driver(&hfc_driver);
+
+#if (KERNEL_VERSION(2, 6, 26) <= LINUX_VERSION_CODE)
+	remove_proc_entry(hfc_DRIVER_NAME, NULL);
+#else
+	remove_proc_entry(hfc_DRIVER_NAME, proc_root_driver);
+#endif
+
+	printk(KERN_INFO hfc_DRIVER_PREFIX
+		hfc_DRIVER_STRING " unloaded\n");
+}
+
+module_exit(hfc_module_exit);
+
+#endif
+
+MODULE_DESCRIPTION(hfc_DRIVER_DESCR);
+MODULE_AUTHOR("Jens Wilke <jw_vzaphfc@headissue.com>, "
+		"Daniele (Vihai) Orlandi <daniele@orlandi.com>, "
+		"Jose A. Deniz <odicha@hotmail.com>");
+MODULE_ALIAS("vzaphfc");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+
+
+module_param(modes, int, 0444);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 10)
+module_param_array(nt_modes, int, &nt_modes_count, 0444);
+#else
+module_param_array(nt_modes, int, nt_modes_count, 0444);
+#endif
+
+module_param(force_l1_up, int, 0444);
+#ifdef DEBUG
+module_param(debug_level, int, 0444);
+#endif
+
+MODULE_PARM_DESC(modes, "[Deprecated] bit-mask to configure NT mode");
+MODULE_PARM_DESC(nt_modes,
+		"Comma-separated list of card IDs to configure in NT mode");
+MODULE_PARM_DESC(force_l1_up, "Don't allow L1 to go down");
+#ifdef DEBUG
+MODULE_PARM_DESC(debug_level, "Debug verbosity level");
+#endif
diff --git a/drivers/dahdi/zaphfc/fifo.c b/drivers/dahdi/zaphfc/fifo.c
new file mode 100644
index 0000000..8c23fa3
--- /dev/null
+++ b/drivers/dahdi/zaphfc/fifo.c
@@ -0,0 +1,375 @@
+/*
+ * fifo.c - HFC FIFO management routines
+ *
+ * Copyright (C) 2006 headissue GmbH; Jens Wilke
+ * Copyright (C) 2004 Daniele Orlandi
+ * Copyright (C) 2002, 2003, 2004, Junghanns.NET GmbH
+ *
+ * Original author of this code is
+ * Daniele "Vihai" Orlandi <daniele@orlandi.com>
+ *
+ * This program is free software and may be modified and
+ * distributed under the terms of the GNU Public License.
+ *
+ */
+
+#include <linux/kernel.h>
+
+#include <dahdi/kernel.h>
+
+#include "fifo.h"
+
+static void hfc_fifo_mem_read(struct hfc_chan_simplex *chan,
+	int z_start,
+	void *data, int size)
+{
+	int bytes_to_boundary = chan->z_max - z_start + 1;
+	if (bytes_to_boundary >= size) {
+		memcpy(data,
+			chan->z_base + z_start,
+			size);
+	} else {
+		/*
+		 * Buffer wrap
+		 */
+		memcpy(data,
+			chan->z_base + z_start,
+			bytes_to_boundary);
+
+		memcpy(data + bytes_to_boundary,
+			chan->fifo_base,
+			size - bytes_to_boundary);
+	}
+}
+
+static void hfc_fifo_mem_write(struct hfc_chan_simplex *chan,
+	void *data, int size)
+{
+	int bytes_to_boundary = chan->z_max - *Z1_F1(chan) + 1;
+	if (bytes_to_boundary >= size) {
+		memcpy(chan->z_base + *Z1_F1(chan),
+			data,
+			size);
+	} else {
+		/*
+		 * FIFO wrap
+		 */
+
+		memcpy(chan->z_base + *Z1_F1(chan),
+			data,
+			bytes_to_boundary);
+
+		memcpy(chan->fifo_base,
+			data + bytes_to_boundary,
+			size - bytes_to_boundary);
+	}
+}
+
+int hfc_fifo_get(struct hfc_chan_simplex *chan,
+		void *data, int size)
+{
+	int available_bytes;
+
+	/*
+	 * Some useless statistic
+	 */
+	chan->bytes += size;
+
+	available_bytes = hfc_fifo_used_rx(chan);
+
+	if (available_bytes < size && !chan->fifo_underrun++) {
+		/*
+		 * print the warning only once
+		 */
+		printk(KERN_WARNING hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"RX FIFO not enough (%d) bytes to receive!\n",
+			chan->chan->card->cardnum,
+			chan->chan->name,
+			available_bytes);
+		return -1;
+	}
+
+	hfc_fifo_mem_read(chan, *Z2_F2(chan), data, size);
+	*Z2_F2(chan) = Z_inc(chan, *Z2_F2(chan), size);
+	return available_bytes - size;
+}
+
+void hfc_fifo_put(struct hfc_chan_simplex *chan,
+			void *data, int size)
+{
+	struct hfc_card *card = chan->chan->card;
+	int used_bytes = hfc_fifo_used_tx(chan);
+	int free_bytes = hfc_fifo_free_tx(chan);
+
+	if (!used_bytes && !chan->fifo_underrun++) {
+		/*
+		 * print warning only once, to make timing not worse
+		 */
+		printk(KERN_WARNING hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"TX FIFO has become empty\n",
+			card->cardnum,
+			chan->chan->name);
+	}
+	if (free_bytes < size) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"TX FIFO full!\n",
+			chan->chan->card->cardnum,
+			chan->chan->name);
+		chan->fifo_full++;
+		hfc_clear_fifo_tx(chan);
+	}
+
+	hfc_fifo_mem_write(chan, data, size);
+	chan->bytes += size;
+	*Z1_F1(chan) = Z_inc(chan, *Z1_F1(chan), size);
+}
+
+int hfc_fifo_get_frame(struct hfc_chan_simplex *chan, void *data, int max_size)
+{
+	int frame_size;
+	u16 newz2 ;
+
+	if (*chan->f1 == *chan->f2) {
+		/*
+		 * nothing received, strange uh?
+		 */
+		printk(KERN_WARNING hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"get_frame called with no frame in FIFO.\n",
+			chan->chan->card->cardnum,
+			chan->chan->name);
+
+		return -1;
+	}
+
+	/*
+	 * frame_size includes CRC+CRC+STAT
+	 */
+	frame_size = hfc_fifo_get_frame_size(chan);
+
+#ifdef DEBUG
+	if (debug_level == 3) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"RX len %2d: ",
+			chan->chan->card->cardnum,
+			chan->chan->name,
+			frame_size);
+	} else if (debug_level >= 4) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"RX (f1=%02x, f2=%02x, z1=%04x, z2=%04x) len %2d: ",
+			chan->chan->card->cardnum,
+			chan->chan->name,
+			*chan->f1, *chan->f2, *Z1_F2(chan), *Z2_F2(chan),
+			frame_size);
+	}
+
+	if (debug_level >= 3) {
+		int i;
+		for (i = 0; i < frame_size; i++) {
+			printk("%02x", hfc_fifo_u8(chan,
+				Z_inc(chan, *Z2_F2(chan), i)));
+		}
+
+		printk("\n");
+	}
+#endif
+
+	if (frame_size <= 0) {
+#ifdef DEBUG
+		if (debug_level >= 2) {
+			printk(KERN_DEBUG hfc_DRIVER_PREFIX
+				"card %d: "
+				"chan %s: "
+				"invalid (empty) frame received.\n",
+				chan->chan->card->cardnum,
+				chan->chan->name);
+		}
+#endif
+
+		hfc_fifo_drop_frame(chan);
+		return -1;
+	}
+
+	/*
+	 * STAT is not really received
+	 */
+	chan->bytes += frame_size - 1;
+
+	/*
+	 * Calculate beginning of the next frame
+	 */
+	newz2 = Z_inc(chan, *Z2_F2(chan), frame_size);
+
+	/*
+	 * We cannot use hfc_fifo_get because of different semantic of
+	 * "available bytes" and to avoid useless increment of Z2
+	 */
+	hfc_fifo_mem_read(chan, *Z2_F2(chan), data,
+		frame_size < max_size ? frame_size : max_size);
+
+	if (hfc_fifo_u8(chan, Z_inc(chan, *Z2_F2(chan),
+		frame_size - 1)) != 0x00) {
+		/*
+		 * CRC not ok, frame broken, skipping
+		 */
+#ifdef DEBUG
+		if (debug_level >= 2) {
+			printk(KERN_WARNING hfc_DRIVER_PREFIX
+				"card %d: "
+				"chan %s: "
+				"Received frame with wrong CRC\n",
+				chan->chan->card->cardnum,
+				chan->chan->name);
+		}
+#endif
+
+		chan->crc++;
+
+		hfc_fifo_drop_frame(chan);
+		return -1;
+	}
+
+	chan->frames++;
+
+	*chan->f2 = F_inc(chan, *chan->f2, 1);
+
+	/*
+	 * Set Z2 for the next frame we're going to receive
+	 */
+	*Z2_F2(chan) = newz2;
+
+	return frame_size;
+}
+
+void hfc_fifo_drop_frame(struct hfc_chan_simplex *chan)
+{
+	int available_bytes;
+	u16 newz2;
+
+	if (*chan->f1 == *chan->f2) {
+		/*
+		 * nothing received, strange eh?
+		 */
+		printk(KERN_WARNING hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"skip_frame called with no frame in FIFO.\n",
+			chan->chan->card->cardnum,
+			chan->chan->name);
+
+		return;
+	}
+
+	available_bytes = hfc_fifo_used_rx(chan) + 1;
+
+	/*
+	 * Calculate beginning of the next frame
+	 */
+	newz2 = Z_inc(chan, *Z2_F2(chan), available_bytes);
+
+	*chan->f2 = F_inc(chan, *chan->f2, 1);
+
+	/*
+	 * Set Z2 for the next frame we're going to receive
+	 */
+	*Z2_F2(chan) = newz2;
+}
+
+void hfc_fifo_put_frame(struct hfc_chan_simplex *chan,
+		 void *data, int size)
+{
+	u16 newz1;
+	int available_frames;
+
+#ifdef DEBUG
+	if (debug_level == 3) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"TX len %2d: ",
+			chan->chan->card->cardnum,
+			chan->chan->name,
+			size);
+	} else if (debug_level >= 4) {
+		printk(KERN_DEBUG hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"TX (f1=%02x, f2=%02x, z1=%04x, z2=%04x) len %2d: ",
+			chan->chan->card->cardnum,
+			chan->chan->name,
+			*chan->f1, *chan->f2, *Z1_F1(chan), *Z2_F1(chan),
+			size);
+	}
+
+	if (debug_level >= 3) {
+		int i;
+		for (i = 0; i < size; i++)
+			printk("%02x", ((u8 *)data)[i]);
+
+		printk("\n");
+	}
+#endif
+
+	available_frames = hfc_fifo_free_frames(chan);
+
+	if (available_frames >= chan->f_num) {
+		printk(KERN_CRIT hfc_DRIVER_PREFIX
+			"card %d: "
+			"chan %s: "
+			"TX FIFO total number of frames exceeded!\n",
+			chan->chan->card->cardnum,
+			chan->chan->name);
+
+		chan->fifo_full++;
+
+		return;
+	}
+
+	hfc_fifo_put(chan, data, size);
+
+	newz1 = *Z1_F1(chan);
+
+	*chan->f1 = F_inc(chan, *chan->f1, 1);
+
+	*Z1_F1(chan) = newz1;
+
+	chan->frames++;
+}
+
+void hfc_clear_fifo_rx(struct hfc_chan_simplex *chan)
+{
+	*chan->f2 = *chan->f1;
+	*Z2_F2(chan) = *Z1_F2(chan);
+}
+
+void hfc_clear_fifo_tx(struct hfc_chan_simplex *chan)
+{
+	*chan->f1 = *chan->f2;
+	*Z1_F1(chan) = *Z2_F1(chan);
+
+	if (chan->chan->status == open_voice) {
+		/*
+		 * Make sure that at least hfc_TX_FIFO_PRELOAD bytes are
+		 * present in the TX FIFOs
+		 * Create hfc_TX_FIFO_PRELOAD bytes of empty data
+		 * (0x7f is mute audio)
+		 */
+		u8 empty_fifo[hfc_TX_FIFO_PRELOAD +
+			DAHDI_CHUNKSIZE + hfc_RX_FIFO_PRELOAD];
+		memset(empty_fifo, 0x7f, sizeof(empty_fifo));
+
+		hfc_fifo_put(chan, empty_fifo, sizeof(empty_fifo));
+	}
+}
+
diff --git a/drivers/dahdi/zaphfc/fifo.h b/drivers/dahdi/zaphfc/fifo.h
new file mode 100644
index 0000000..1866f30
--- /dev/null
+++ b/drivers/dahdi/zaphfc/fifo.h
@@ -0,0 +1,139 @@
+/*
+ * fifo.h - Dahdi driver for HFC-S PCI A based ISDN BRI cards
+ *
+ * Copyright (C) 2004 Daniele Orlandi
+ * Copyright (C) 2002, 2003, 2004, Junghanns.NET GmbH
+ *
+ * Daniele "Vihai" Orlandi <daniele@orlandi.com>
+ *
+ * Major rewrite of the driver made by
+ * Klaus-Peter Junghanns <kpj@junghanns.net>
+ *
+ * This program is free software and may be modified and
+ * distributed under the terms of the GNU Public License.
+ *
+ */
+
+#ifndef _HFC_FIFO_H
+#define _HFC_FIFO_H
+
+#include "zaphfc.h"
+
+static inline u16 *Z1_F1(struct hfc_chan_simplex *chan)
+{
+	return chan->z1_base + (*chan->f1 * 4);
+}
+
+static inline u16 *Z2_F1(struct hfc_chan_simplex *chan)
+{
+	return chan->z2_base + (*chan->f1 * 4);
+}
+
+static inline u16 *Z1_F2(struct hfc_chan_simplex *chan)
+{
+	return chan->z1_base + (*chan->f2 * 4);
+}
+
+static inline u16 *Z2_F2(struct hfc_chan_simplex *chan)
+{
+	return chan->z2_base + (*chan->f2 * 4);
+}
+
+static inline u16 Z_inc(struct hfc_chan_simplex *chan, u16 z, u16 inc)
+{
+	/*
+	 * declared as u32 in order to manage overflows
+	 */
+	u32 newz = z + inc;
+	if (newz > chan->z_max)
+		newz -= chan->fifo_size;
+
+	return newz;
+}
+
+static inline u8 F_inc(struct hfc_chan_simplex *chan, u8 f, u8 inc)
+{
+	/*
+	 * declared as u16 in order to manage overflows
+	 */
+	u16 newf = f + inc;
+	if (newf > chan->f_max)
+		newf -= chan->f_num;
+
+	return newf;
+}
+
+static inline u16 hfc_fifo_used_rx(struct hfc_chan_simplex *chan)
+{
+	return (*Z1_F2(chan) - *Z2_F2(chan) +
+			chan->fifo_size) % chan->fifo_size;
+}
+
+static inline u16 hfc_fifo_get_frame_size(struct hfc_chan_simplex *chan)
+{
+ /*
+  * This +1 is needed because in frame mode the available bytes are Z2-Z1+1
+  * while in transparent mode I wouldn't consider the byte pointed by Z2 to
+  * be available, otherwise, the FIFO would always contain one byte, even
+  * when Z1==Z2
+  */
+
+	return hfc_fifo_used_rx(chan) + 1;
+}
+
+static inline u8 hfc_fifo_u8(struct hfc_chan_simplex *chan, u16 z)
+{
+	return *((u8 *)(chan->z_base + z));
+}
+
+static inline u16 hfc_fifo_used_tx(struct hfc_chan_simplex *chan)
+{
+	return (*Z1_F1(chan) - *Z2_F1(chan) +
+			chan->fifo_size) % chan->fifo_size;
+}
+
+static inline u16 hfc_fifo_free_rx(struct hfc_chan_simplex *chan)
+{
+	u16 free_bytes = *Z2_F1(chan) - *Z1_F1(chan);
+
+	if (free_bytes > 0)
+		return free_bytes;
+	else
+		return free_bytes + chan->fifo_size;
+}
+
+static inline u16 hfc_fifo_free_tx(struct hfc_chan_simplex *chan)
+{
+	u16 free_bytes = *Z2_F1(chan) - *Z1_F1(chan);
+
+	if (free_bytes > 0)
+		return free_bytes;
+	else
+		return free_bytes + chan->fifo_size;
+}
+
+static inline int hfc_fifo_has_frames(struct hfc_chan_simplex *chan)
+{
+	return *chan->f1 != *chan->f2;
+}
+
+static inline u8 hfc_fifo_used_frames(struct hfc_chan_simplex *chan)
+{
+	return (*chan->f1 - *chan->f2 + chan->f_num) % chan->f_num;
+}
+
+static inline u8 hfc_fifo_free_frames(struct hfc_chan_simplex *chan)
+{
+	return (*chan->f2 - *chan->f1 + chan->f_num) % chan->f_num;
+}
+
+int hfc_fifo_get(struct hfc_chan_simplex *chan, void *data, int size);
+void hfc_fifo_put(struct hfc_chan_simplex *chan, void *data, int size);
+void hfc_fifo_drop(struct hfc_chan_simplex *chan, int size);
+int hfc_fifo_get_frame(struct hfc_chan_simplex *chan, void *data, int max_size);
+void hfc_fifo_drop_frame(struct hfc_chan_simplex *chan);
+void hfc_fifo_put_frame(struct hfc_chan_simplex *chan, void *data, int size);
+void hfc_clear_fifo_rx(struct hfc_chan_simplex *chan);
+void hfc_clear_fifo_tx(struct hfc_chan_simplex *chan);
+
+#endif
diff --git a/drivers/dahdi/zaphfc/zaphfc.h b/drivers/dahdi/zaphfc/zaphfc.h
new file mode 100644
index 0000000..d3cceef
--- /dev/null
+++ b/drivers/dahdi/zaphfc/zaphfc.h
@@ -0,0 +1,419 @@
+/*
+ * zaphfc.h - Dahdi driver for HFC-S PCI A based ISDN BRI cards
+ *
+ * Dahdi port by Jose A. Deniz <odicha@hotmail.com>
+ *
+ * Copyright (C) 2009 Jose A. Deniz
+ * Copyright (C) 2006 headissue GmbH; Jens Wilke
+ * Copyright (C) 2004 Daniele Orlandi
+ * Copyright (C) 2002, 2003, 2004, Junghanns.NET GmbH
+ *
+ * Jens Wilke <jw_vzaphfc@headissue.com>
+ *
+ * Orginal author of this code is
+ * Daniele "Vihai" Orlandi <daniele@orlandi.com>
+ *
+ * Major rewrite of the driver made by
+ * Klaus-Peter Junghanns <kpj@junghanns.net>
+ *
+ * This program is free software and may be modified and
+ * distributed under the terms of the GNU Public License.
+ *
+ */
+
+#ifndef _HFC_ZAPHFC_H
+#define _HFC_ZAPHFC_H
+
+#include <asm/io.h>
+
+#define hfc_DRIVER_NAME "vzaphfc"
+#define hfc_DRIVER_PREFIX hfc_DRIVER_NAME ": "
+#define hfc_DRIVER_DESCR "HFC-S PCI A ISDN"
+#define hfc_DRIVER_VERSION "1.42"
+#define hfc_DRIVER_STRING hfc_DRIVER_DESCR " (V" hfc_DRIVER_VERSION ")"
+
+#define hfc_MAX_BOARDS 32
+
+#ifndef PCI_DMA_32BIT
+#define PCI_DMA_32BIT	0x00000000ffffffffULL
+#endif
+
+#ifndef PCI_VENDOR_ID_SITECOM
+#define PCI_VENDOR_ID_SITECOM 0x182D
+#endif
+
+#ifndef PCI_DEVICE_ID_SITECOM_3069
+#define PCI_DEVICE_ID_SITECOM_3069 0x3069
+#endif
+
+#define hfc_RESET_DELAY 20
+
+#define hfc_CLKDEL_TE	0x0f	/* CLKDEL in TE mode */
+#define hfc_CLKDEL_NT	0x6c	/* CLKDEL in NT mode */
+
+/* PCI memory mapped I/O */
+
+#define hfc_PCI_MEM_SIZE	0x0100
+#define hfc_PCI_MWBA		0x80
+
+/* GCI/IOM bus monitor registers */
+
+#define hfc_C_I       0x08
+#define hfc_TRxR      0x0C
+#define hfc_MON1_D    0x28
+#define hfc_MON2_D    0x2C
+
+
+/* GCI/IOM bus timeslot registers */
+
+#define hfc_B1_SSL    0x80
+#define hfc_B2_SSL    0x84
+#define hfc_AUX1_SSL  0x88
+#define hfc_AUX2_SSL  0x8C
+#define hfc_B1_RSL    0x90
+#define hfc_B2_RSL    0x94
+#define hfc_AUX1_RSL  0x98
+#define hfc_AUX2_RSL  0x9C
+
+/* GCI/IOM bus data registers */
+
+#define hfc_B1_D      0xA0
+#define hfc_B2_D      0xA4
+#define hfc_AUX1_D    0xA8
+#define hfc_AUX2_D    0xAC
+
+/* GCI/IOM bus configuration registers */
+
+#define hfc_MST_EMOD  0xB4
+#define hfc_MST_MODE	 0xB8
+#define hfc_CONNECT	 0xBC
+
+
+/* Interrupt and status registers */
+
+#define hfc_FIFO_EN   0x44
+#define hfc_TRM       0x48
+#define hfc_B_MODE    0x4C
+#define hfc_CHIP_ID   0x58
+#define hfc_CIRM	 0x60
+#define hfc_CTMT	 0x64
+#define hfc_INT_M1	 0x68
+#define hfc_INT_M2	 0x6C
+#define hfc_INT_S1	 0x78
+#define hfc_INT_S2	 0x7C
+#define hfc_STATUS	 0x70
+
+/* S/T section registers */
+
+#define hfc_STATES	 0xC0
+#define hfc_SCTRL	 0xC4
+#define hfc_SCTRL_E   0xC8
+#define hfc_SCTRL_R   0xCC
+#define hfc_SQ	 0xD0
+#define hfc_CLKDEL	 0xDC
+#define hfc_B1_REC    0xF0
+#define hfc_B1_SEND   0xF0
+#define hfc_B2_REC    0xF4
+#define hfc_B2_SEND   0xF4
+#define hfc_D_REC     0xF8
+#define hfc_D_SEND    0xF8
+#define hfc_E_REC     0xFC
+
+/* Bits and values in various HFC PCI registers */
+
+/* bits in status register (READ) */
+#define hfc_STATUS_PCI_PROC	0x02
+#define hfc_STATUS_NBUSY	0x04
+#define hfc_STATUS_TIMER_ELAP	0x10
+#define hfc_STATUS_STATINT	0x20
+#define hfc_STATUS_FRAMEINT	0x40
+#define hfc_STATUS_ANYINT	0x80
+
+/* bits in CTMT (Write) */
+#define hfc_CTMT_TRANSB1	0x01
+#define hfc_CTMT_TRANSB2	0x02
+#define hfc_CTMT_TIMER_CLEAR	0x80
+#define hfc_CTMT_TIMER_MASK	0x1C
+#define hfc_CTMT_TIMER_3_125	(0x01 << 2)
+#define hfc_CTMT_TIMER_6_25	(0x02 << 2)
+#define hfc_CTMT_TIMER_12_5	(0x03 << 2)
+#define hfc_CTMT_TIMER_25	(0x04 << 2)
+#define hfc_CTMT_TIMER_50	(0x05 << 2)
+#define hfc_CTMT_TIMER_400	(0x06 << 2)
+#define hfc_CTMT_TIMER_800	(0x07 << 2)
+#define hfc_CTMT_AUTO_TIMER	0x20
+
+/* bits in CIRM (Write) */
+#define hfc_CIRM_AUX_MSK	0x07
+#define hfc_CIRM_RESET		0x08
+#define hfc_CIRM_B1_REV		0x40
+#define hfc_CIRM_B2_REV		0x80
+
+/* bits in INT_M1 and INT_S1 */
+#define hfc_INTS_B1TRANS	0x01
+#define hfc_INTS_B2TRANS	0x02
+#define hfc_INTS_DTRANS		0x04
+#define hfc_INTS_B1REC		0x08
+#define hfc_INTS_B2REC		0x10
+#define hfc_INTS_DREC		0x20
+#define hfc_INTS_L1STATE	0x40
+#define hfc_INTS_TIMER		0x80
+
+/* bits in INT_M2 */
+#define hfc_M2_PROC_TRANS	0x01
+#define hfc_M2_GCI_I_CHG	0x02
+#define hfc_M2_GCI_MON_REC	0x04
+#define hfc_M2_IRQ_ENABLE	0x08
+#define hfc_M2_PMESEL		0x80
+
+/* bits in STATES */
+#define hfc_STATES_STATE_MASK	0x0F
+#define hfc_STATES_LOAD_STATE	0x10
+#define hfc_STATES_ACTIVATE	0x20
+#define hfc_STATES_DO_ACTION	0x40
+#define hfc_STATES_NT_G2_G3	0x80
+
+/* bits in HFCD_MST_MODE */
+#define hfc_MST_MODE_MASTER	0x01
+#define hfc_MST_MODE_SLAVE	0x00
+/* remaining bits are for codecs control */
+
+/* bits in HFCD_SCTRL */
+#define hfc_SCTRL_B1_ENA	0x01
+#define hfc_SCTRL_B2_ENA	0x02
+#define hfc_SCTRL_MODE_TE       0x00
+#define hfc_SCTRL_MODE_NT       0x04
+#define hfc_SCTRL_LOW_PRIO	0x08
+#define hfc_SCTRL_SQ_ENA	0x10
+#define hfc_SCTRL_TEST		0x20
+#define hfc_SCTRL_NONE_CAP	0x40
+#define hfc_SCTRL_PWR_DOWN	0x80
+
+/* bits in SCTRL_E  */
+#define hfc_SCTRL_E_AUTO_AWAKE	0x01
+#define hfc_SCTRL_E_DBIT_1	0x04
+#define hfc_SCTRL_E_IGNORE_COL	0x08
+#define hfc_SCTRL_E_CHG_B1_B2	0x80
+
+/* bits in SCTRL_R  */
+#define hfc_SCTRL_R_B1_ENA	0x01
+#define hfc_SCTRL_R_B2_ENA	0x02
+
+/* bits in FIFO_EN register */
+#define hfc_FIFOEN_B1TX		0x01
+#define hfc_FIFOEN_B1RX		0x02
+#define hfc_FIFOEN_B2TX		0x04
+#define hfc_FIFOEN_B2RX		0x08
+#define hfc_FIFOEN_DTX		0x10
+#define hfc_FIFOEN_DRX		0x20
+
+#define hfc_FIFOEN_B1		(hfc_FIFOEN_B1TX|hfc_FIFOEN_B1RX)
+#define hfc_FIFOEN_B2		(hfc_FIFOEN_B2TX|hfc_FIFOEN_B2RX)
+#define hfc_FIFOEN_D		(hfc_FIFOEN_DTX|hfc_FIFOEN_DRX)
+
+/* bits in the CONNECT register */
+#define	hfc_CONNECT_B1_HFC_from_ST		0x00
+#define	hfc_CONNECT_B1_HFC_from_GCI		0x01
+#define hfc_CONNECT_B1_ST_from_HFC		0x00
+#define hfc_CONNECT_B1_ST_from_GCI		0x02
+#define hfc_CONNECT_B1_GCI_from_HFC		0x00
+#define hfc_CONNECT_B1_GCI_from_ST		0x04
+
+#define	hfc_CONNECT_B2_HFC_from_ST		0x00
+#define	hfc_CONNECT_B2_HFC_from_GCI		0x08
+#define hfc_CONNECT_B2_ST_from_HFC		0x00
+#define hfc_CONNECT_B2_ST_from_GCI		0x10
+#define hfc_CONNECT_B2_GCI_from_HFC		0x00
+#define hfc_CONNECT_B2_GCI_from_ST		0x20
+
+/* bits in the TRM register */
+#define hfc_TRM_TRANS_INT_00	0x00
+#define hfc_TRM_TRANS_INT_01	0x01
+#define hfc_TRM_TRANS_INT_10	0x02
+#define hfc_TRM_TRANS_INT_11	0x04
+#define hfc_TRM_ECHO		0x20
+#define hfc_TRM_B1_PLUS_B2	0x40
+#define hfc_TRM_IOM_TEST_LOOP	0x80
+
+/* bits in the __SSL and __RSL registers */
+#define	hfc_SRSL_STIO		0x40
+#define hfc_SRSL_ENABLE		0x80
+#define hfc_SRCL_SLOT_MASK	0x1f
+
+/* FIFO memory definitions */
+
+#define hfc_FIFO_SIZE		0x8000
+
+#define hfc_UGLY_FRAMEBUF	0x2000
+
+#define hfc_TX_FIFO_PRELOAD	(DAHDI_CHUNKSIZE + 2)
+#define hfc_RX_FIFO_PRELOAD	4
+
+/* HDLC STUFF */
+#define hfc_HDLC_BUF_LEN	32
+/* arbitrary, just the max # of byts we will send to DAHDI per call */
+
+
+/* NOTE: FIFO pointers are not declared volatile because accesses to the
+ *       FIFOs are inherently safe.
+ */
+
+#ifdef DEBUG
+extern int debug_level;
+#endif
+
+struct hfc_chan;
+
+struct hfc_chan_simplex {
+	struct hfc_chan_duplex *chan;
+
+	u8 zaptel_buffer[DAHDI_CHUNKSIZE];
+
+	u8 ugly_framebuf[hfc_UGLY_FRAMEBUF];
+	int ugly_framebuf_size;
+	u16 ugly_framebuf_off;
+
+	void *z1_base, *z2_base;
+	void *fifo_base;
+	void *z_base;
+	u16 z_min;
+	u16 z_max;
+	u16 fifo_size;
+
+	u8 *f1, *f2;
+	u8 f_min;
+	u8 f_max;
+	u8 f_num;
+
+	unsigned long long frames;
+	unsigned long long bytes;
+	unsigned long long fifo_full;
+	unsigned long long crc;
+	unsigned long long fifo_underrun;
+};
+
+enum hfc_chan_status {
+	free,
+	open_framed,
+	open_voice,
+	sniff_aux,
+	loopback,
+};
+
+struct hfc_chan_duplex {
+	struct hfc_card *card;
+
+	char *name;
+	int number;
+
+	enum hfc_chan_status status;
+	int open_by_netdev;
+	int open_by_zaptel;
+
+	unsigned short protocol;
+
+	spinlock_t lock;
+
+	struct hfc_chan_simplex rx;
+	struct hfc_chan_simplex tx;
+
+};
+
+typedef struct hfc_card {
+	int cardnum;
+	struct pci_dev *pcidev;
+	struct dahdi_hfc *ztdev;
+	struct proc_dir_entry *proc_dir;
+	char proc_dir_name[32];
+
+	struct proc_dir_entry *proc_info;
+	struct proc_dir_entry *proc_fifos;
+	struct proc_dir_entry *proc_bufs;
+
+	unsigned long io_bus_mem;
+	void __iomem *io_mem;
+
+	dma_addr_t fifo_bus_mem;
+	void *fifo_mem;
+	void *fifos;
+
+	int nt_mode;
+	int sync_loss_reported;
+	int late_irqs;
+
+	u8 l1_state;
+	int fifo_suspended;
+	int ignore_first_timer_interrupt;
+
+	struct {
+		u8 m1;
+		u8 m2;
+		u8 fifo_en;
+		u8 trm;
+		u8 connect;
+		u8 sctrl;
+		u8 sctrl_r;
+		u8 sctrl_e;
+		u8 ctmt;
+		u8 cirm;
+	} regs;
+
+	struct hfc_chan_duplex chans[3];
+	int echo_enabled;
+
+
+
+	int debug_event;
+
+	spinlock_t lock;
+	unsigned int irq;
+	unsigned int iomem;
+	int ticks;
+	int clicks;
+	unsigned char *pci_io;
+	void *fifomem;		/* start of the shared mem */
+
+	unsigned int pcibus;
+	unsigned int pcidevfn;
+
+	int	drecinframe;
+
+	unsigned char cardno;
+	struct hfc_card *next;
+
+} hfc_card;
+
+typedef struct dahdi_hfc {
+	unsigned int usecount;
+	struct dahdi_device *ddev;
+	struct dahdi_span span;
+	struct dahdi_chan chans[3];
+	struct dahdi_chan *_chans[3];
+	struct hfc_card *card;
+
+	/* pointer to the signalling channel for this span */
+	struct dahdi_chan *sigchan;
+	/* nonzero means we're in the middle of sending an HDLC frame */
+	int sigactive;
+	/* hdlc_hard_xmit() increments, hdlc_tx_frame() decrements */
+	atomic_t hdlc_pending;
+	int frames_out;
+	int frames_in;
+} dahdi_hfc;
+
+static inline struct dahdi_hfc *dahdi_hfc_from_span(struct dahdi_span *span)
+{
+	return container_of(span, struct dahdi_hfc, span);
+}
+
+static inline u8 hfc_inb(struct hfc_card *card, int offset)
+{
+	return readb(card->io_mem + offset);
+}
+
+static inline void hfc_outb(struct hfc_card *card, int offset, u8 value)
+{
+	writeb(value, card->io_mem + offset);
+}
+
+#endif
diff --git a/drivers/staging/echo/Kbuild b/drivers/staging/echo/Kbuild
new file mode 100644
index 0000000..f813006
--- /dev/null
+++ b/drivers/staging/echo/Kbuild
@@ -0,0 +1,6 @@
+ifdef DAHDI_USE_MMX
+EXTRA_CFLAGS += -DUSE_MMX
+endif
+
+# An explicit 'obj-m' , unlike the Makefile
+obj-m += echo.o
diff --git a/drivers/staging/echo/echo.c b/drivers/staging/echo/echo.c
new file mode 100644
index 0000000..4cc4f2e
--- /dev/null
+++ b/drivers/staging/echo/echo.c
@@ -0,0 +1,662 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * echo.c - A line echo canceller.  This code is being developed
+ *          against and partially complies with G168.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *         and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe
+ *
+ * Based on a bit from here, a bit from there, eye of toad, ear of
+ * bat, 15 years of failed attempts by David and a few fried brain
+ * cells.
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*! \file */
+
+/* Implementation Notes
+   David Rowe
+   April 2007
+
+   This code started life as Steve's NLMS algorithm with a tap
+   rotation algorithm to handle divergence during double talk.  I
+   added a Geigel Double Talk Detector (DTD) [2] and performed some
+   G168 tests.  However I had trouble meeting the G168 requirements,
+   especially for double talk - there were always cases where my DTD
+   failed, for example where near end speech was under the 6dB
+   threshold required for declaring double talk.
+
+   So I tried a two path algorithm [1], which has so far given better
+   results.  The original tap rotation/Geigel algorithm is available
+   in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit.
+   It's probably possible to make it work if some one wants to put some
+   serious work into it.
+
+   At present no special treatment is provided for tones, which
+   generally cause NLMS algorithms to diverge.  Initial runs of a
+   subset of the G168 tests for tones (e.g ./echo_test 6) show the
+   current algorithm is passing OK, which is kind of surprising.  The
+   full set of tests needs to be performed to confirm this result.
+
+   One other interesting change is that I have managed to get the NLMS
+   code to work with 16 bit coefficients, rather than the original 32
+   bit coefficents.  This reduces the MIPs and storage required.
+   I evaulated the 16 bit port using g168_tests.sh and listening tests
+   on 4 real-world samples.
+
+   I also attempted the implementation of a block based NLMS update
+   [2] but although this passes g168_tests.sh it didn't converge well
+   on the real-world samples.  I have no idea why, perhaps a scaling
+   problem.  The block based code is also available in SVN
+   http://svn.rowetel.com/software/oslec/tags/before_16bit.  If this
+   code can be debugged, it will lead to further reduction in MIPS, as
+   the block update code maps nicely onto DSP instruction sets (it's a
+   dot product) compared to the current sample-by-sample update.
+
+   Steve also has some nice notes on echo cancellers in echo.h
+
+   References:
+
+   [1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo
+       Path Models", IEEE Transactions on communications, COM-25,
+       No. 6, June
+       1977.
+       http://www.rowetel.com/images/echo/dual_path_paper.pdf
+
+   [2] The classic, very useful paper that tells you how to
+       actually build a real world echo canceller:
+	 Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice
+	 Echo Canceller with a TMS320020,
+	 http://www.rowetel.com/images/echo/spra129.pdf
+
+   [3] I have written a series of blog posts on this work, here is
+       Part 1: http://www.rowetel.com/blog/?p=18
+
+   [4] The source code http://svn.rowetel.com/software/oslec/
+
+   [5] A nice reference on LMS filters:
+	 http://en.wikipedia.org/wiki/Least_mean_squares_filter
+
+   Credits:
+
+   Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan
+   Muthukrishnan for their suggestions and email discussions.  Thanks
+   also to those people who collected echo samples for me such as
+   Mark, Pawel, and Pavel.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "echo.h"
+
+#define MIN_TX_POWER_FOR_ADAPTION	64
+#define MIN_RX_POWER_FOR_ADAPTION	64
+#define DTD_HANGOVER			600	/* 600 samples, or 75ms     */
+#define DC_LOG2BETA			3	/* log2() of DC filter Beta */
+
+
+/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */
+
+#ifdef __bfin__
+static inline void lms_adapt_bg(struct oslec_state *ec, int clean,
+				    int shift)
+{
+	int i, j;
+	int offset1;
+	int offset2;
+	int factor;
+	int exp;
+	int16_t *phist;
+	int n;
+
+	if (shift > 0)
+		factor = clean << shift;
+	else
+		factor = clean >> -shift;
+
+	/* Update the FIR taps */
+
+	offset2 = ec->curr_pos;
+	offset1 = ec->taps - offset2;
+	phist = &ec->fir_state_bg.history[offset2];
+
+	/* st: and en: help us locate the assembler in echo.s */
+
+	/* asm("st:"); */
+	n = ec->taps;
+	for (i = 0, j = offset2; i < n; i++, j++) {
+		exp = *phist++ * factor;
+		ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+	}
+	/* asm("en:"); */
+
+	/* Note the asm for the inner loop above generated by Blackfin gcc
+	   4.1.1 is pretty good (note even parallel instructions used):
+
+	   R0 = W [P0++] (X);
+	   R0 *= R2;
+	   R0 = R0 + R3 (NS) ||
+	   R1 = W [P1] (X) ||
+	   nop;
+	   R0 >>>= 15;
+	   R0 = R0 + R1;
+	   W [P1++] = R0;
+
+	   A block based update algorithm would be much faster but the
+	   above can't be improved on much.  Every instruction saved in
+	   the loop above is 2 MIPs/ch!  The for loop above is where the
+	   Blackfin spends most of it's time - about 17 MIPs/ch measured
+	   with speedtest.c with 256 taps (32ms).  Write-back and
+	   Write-through cache gave about the same performance.
+	 */
+}
+
+/*
+   IDEAS for further optimisation of lms_adapt_bg():
+
+   1/ The rounding is quite costly.  Could we keep as 32 bit coeffs
+   then make filter pluck the MS 16-bits of the coeffs when filtering?
+   However this would lower potential optimisation of filter, as I
+   think the dual-MAC architecture requires packed 16 bit coeffs.
+
+   2/ Block based update would be more efficient, as per comments above,
+   could use dual MAC architecture.
+
+   3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC
+   packing.
+
+   4/ Execute the whole e/c in a block of say 20ms rather than sample
+   by sample.  Processing a few samples every ms is inefficient.
+*/
+
+#else
+static inline void lms_adapt_bg(struct oslec_state *ec, int clean,
+				    int shift)
+{
+	int i;
+
+	int offset1;
+	int offset2;
+	int factor;
+	int exp;
+
+	if (shift > 0)
+		factor = clean << shift;
+	else
+		factor = clean >> -shift;
+
+	/* Update the FIR taps */
+
+	offset2 = ec->curr_pos;
+	offset1 = ec->taps - offset2;
+
+	for (i = ec->taps - 1; i >= offset1; i--) {
+		exp = (ec->fir_state_bg.history[i - offset1] * factor);
+		ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+	}
+	for (; i >= 0; i--) {
+		exp = (ec->fir_state_bg.history[i + offset2] * factor);
+		ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15);
+	}
+}
+#endif
+
+static inline int top_bit(unsigned int bits)
+{
+	if (bits == 0)
+		return -1;
+	else
+		return (int)fls((int32_t)bits)-1;
+}
+
+struct oslec_state *oslec_create(int len, int adaption_mode)
+{
+	struct oslec_state *ec;
+	int i;
+
+	ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+	if (!ec)
+		return NULL;
+
+	ec->taps = len;
+	ec->log2taps = top_bit(len);
+	ec->curr_pos = ec->taps - 1;
+
+	for (i = 0; i < 2; i++) {
+		ec->fir_taps16[i] =
+		    kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
+		if (!ec->fir_taps16[i])
+			goto error_oom;
+	}
+
+	fir16_create(&ec->fir_state, ec->fir_taps16[0], ec->taps);
+	fir16_create(&ec->fir_state_bg, ec->fir_taps16[1], ec->taps);
+
+	for (i = 0; i < 5; i++)
+		ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0;
+
+	ec->cng_level = 1000;
+	oslec_adaption_mode(ec, adaption_mode);
+
+	ec->snapshot = kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL);
+	if (!ec->snapshot)
+		goto error_oom;
+
+	ec->cond_met = 0;
+	ec->Pstates = 0;
+	ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0;
+	ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0;
+	ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
+	ec->Lbgn = ec->Lbgn_acc = 0;
+	ec->Lbgn_upper = 200;
+	ec->Lbgn_upper_acc = ec->Lbgn_upper << 13;
+
+	return ec;
+
+error_oom:
+	for (i = 0; i < 2; i++)
+		kfree(ec->fir_taps16[i]);
+
+	kfree(ec);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(oslec_create);
+
+void oslec_free(struct oslec_state *ec)
+{
+	int i;
+
+	fir16_free(&ec->fir_state);
+	fir16_free(&ec->fir_state_bg);
+	for (i = 0; i < 2; i++)
+		kfree(ec->fir_taps16[i]);
+	kfree(ec->snapshot);
+	kfree(ec);
+}
+EXPORT_SYMBOL_GPL(oslec_free);
+
+void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode)
+{
+	ec->adaption_mode = adaption_mode;
+}
+EXPORT_SYMBOL_GPL(oslec_adaption_mode);
+
+void oslec_flush(struct oslec_state *ec)
+{
+	int i;
+
+	ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0;
+	ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0;
+	ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0;
+
+	ec->Lbgn = ec->Lbgn_acc = 0;
+	ec->Lbgn_upper = 200;
+	ec->Lbgn_upper_acc = ec->Lbgn_upper << 13;
+
+	ec->nonupdate_dwell = 0;
+
+	fir16_flush(&ec->fir_state);
+	fir16_flush(&ec->fir_state_bg);
+	ec->fir_state.curr_pos = ec->taps - 1;
+	ec->fir_state_bg.curr_pos = ec->taps - 1;
+	for (i = 0; i < 2; i++)
+		memset(ec->fir_taps16[i], 0, ec->taps * sizeof(int16_t));
+
+	ec->curr_pos = ec->taps - 1;
+	ec->Pstates = 0;
+}
+EXPORT_SYMBOL_GPL(oslec_flush);
+
+void oslec_snapshot(struct oslec_state *ec)
+{
+	memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps * sizeof(int16_t));
+}
+EXPORT_SYMBOL_GPL(oslec_snapshot);
+
+/* Dual Path Echo Canceller */
+
+int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx)
+{
+	int32_t echo_value;
+	int clean_bg;
+	int tmp, tmp1;
+
+	/*
+	 * Input scaling was found be required to prevent problems when tx
+	 * starts clipping.  Another possible way to handle this would be the
+	 * filter coefficent scaling.
+	 */
+
+	ec->tx = tx;
+	ec->rx = rx;
+	tx >>= 1;
+	rx >>= 1;
+
+	/*
+	 * Filter DC, 3dB point is 160Hz (I think), note 32 bit precision
+	 * required otherwise values do not track down to 0. Zero at DC, Pole
+	 * at (1-Beta) on real axis.  Some chip sets (like Si labs) don't
+	 * need this, but something like a $10 X100P card does.  Any DC really
+	 * slows down convergence.
+	 *
+	 * Note: removes some low frequency from the signal, this reduces the
+	 * speech quality when listening to samples through headphones but may
+	 * not be obvious through a telephone handset.
+	 *
+	 * Note that the 3dB frequency in radians is approx Beta, e.g. for Beta
+	 * = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz.
+	 */
+
+	if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) {
+		tmp = rx << 15;
+
+		/*
+		 * Make sure the gain of the HPF is 1.0. This can still
+		 * saturate a little under impulse conditions, and it might
+		 * roll to 32768 and need clipping on sustained peak level
+		 * signals. However, the scale of such clipping is small, and
+		 * the error due to any saturation should not markedly affect
+		 * the downstream processing.
+		 */
+		tmp -= (tmp >> 4);
+
+		ec->rx_1 += -(ec->rx_1 >> DC_LOG2BETA) + tmp - ec->rx_2;
+
+		/*
+		 * hard limit filter to prevent clipping.  Note that at this
+		 * stage rx should be limited to +/- 16383 due to right shift
+		 * above
+		 */
+		tmp1 = ec->rx_1 >> 15;
+		if (tmp1 > 16383)
+			tmp1 = 16383;
+		if (tmp1 < -16383)
+			tmp1 = -16383;
+		rx = tmp1;
+		ec->rx_2 = tmp;
+	}
+
+	/* Block average of power in the filter states.  Used for
+	   adaption power calculation. */
+
+	{
+		int new, old;
+
+		/* efficient "out with the old and in with the new" algorithm so
+		   we don't have to recalculate over the whole block of
+		   samples. */
+		new = (int)tx * (int)tx;
+		old = (int)ec->fir_state.history[ec->fir_state.curr_pos] *
+		    (int)ec->fir_state.history[ec->fir_state.curr_pos];
+		ec->Pstates +=
+		    ((new - old) + (1 << (ec->log2taps-1))) >> ec->log2taps;
+		if (ec->Pstates < 0)
+			ec->Pstates = 0;
+	}
+
+	/* Calculate short term average levels using simple single pole IIRs */
+
+	ec->Ltxacc += abs(tx) - ec->Ltx;
+	ec->Ltx = (ec->Ltxacc + (1 << 4)) >> 5;
+	ec->Lrxacc += abs(rx) - ec->Lrx;
+	ec->Lrx = (ec->Lrxacc + (1 << 4)) >> 5;
+
+	/* Foreground filter */
+
+	ec->fir_state.coeffs = ec->fir_taps16[0];
+	echo_value = fir16(&ec->fir_state, tx);
+	ec->clean = rx - echo_value;
+	ec->Lcleanacc += abs(ec->clean) - ec->Lclean;
+	ec->Lclean = (ec->Lcleanacc + (1 << 4)) >> 5;
+
+	/* Background filter */
+
+	echo_value = fir16(&ec->fir_state_bg, tx);
+	clean_bg = rx - echo_value;
+	ec->Lclean_bgacc += abs(clean_bg) - ec->Lclean_bg;
+	ec->Lclean_bg = (ec->Lclean_bgacc + (1 << 4)) >> 5;
+
+	/* Background Filter adaption */
+
+	/* Almost always adap bg filter, just simple DT and energy
+	   detection to minimise adaption in cases of strong double talk.
+	   However this is not critical for the dual path algorithm.
+	 */
+	ec->factor = 0;
+	ec->shift = 0;
+	if ((ec->nonupdate_dwell == 0)) {
+		int P, logP, shift;
+
+		/* Determine:
+
+		   f = Beta * clean_bg_rx/P ------ (1)
+
+		   where P is the total power in the filter states.
+
+		   The Boffins have shown that if we obey (1) we converge
+		   quickly and avoid instability.
+
+		   The correct factor f must be in Q30, as this is the fixed
+		   point format required by the lms_adapt_bg() function,
+		   therefore the scaled version of (1) is:
+
+		   (2^30) * f  = (2^30) * Beta * clean_bg_rx/P
+		   factor      = (2^30) * Beta * clean_bg_rx/P     ----- (2)
+
+		   We have chosen Beta = 0.25 by experiment, so:
+
+		   factor      = (2^30) * (2^-2) * clean_bg_rx/P
+
+						(30 - 2 - log2(P))
+		   factor      = clean_bg_rx 2                     ----- (3)
+
+		   To avoid a divide we approximate log2(P) as top_bit(P),
+		   which returns the position of the highest non-zero bit in
+		   P.  This approximation introduces an error as large as a
+		   factor of 2, but the algorithm seems to handle it OK.
+
+		   Come to think of it a divide may not be a big deal on a
+		   modern DSP, so its probably worth checking out the cycles
+		   for a divide versus a top_bit() implementation.
+		 */
+
+		P = MIN_TX_POWER_FOR_ADAPTION + ec->Pstates;
+		logP = top_bit(P) + ec->log2taps;
+		shift = 30 - 2 - logP;
+		ec->shift = shift;
+
+		lms_adapt_bg(ec, clean_bg, shift);
+	}
+
+	/* very simple DTD to make sure we dont try and adapt with strong
+	   near end speech */
+
+	ec->adapt = 0;
+	if ((ec->Lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->Lrx > ec->Ltx))
+		ec->nonupdate_dwell = DTD_HANGOVER;
+	if (ec->nonupdate_dwell)
+		ec->nonupdate_dwell--;
+
+	/* Transfer logic */
+
+	/* These conditions are from the dual path paper [1], I messed with
+	   them a bit to improve performance. */
+
+	if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) &&
+	    (ec->nonupdate_dwell == 0) &&
+	    /* (ec->Lclean_bg < 0.875*ec->Lclean) */
+	    (8 * ec->Lclean_bg < 7 * ec->Lclean) &&
+	    /* (ec->Lclean_bg < 0.125*ec->Ltx) */
+	    (8 * ec->Lclean_bg < ec->Ltx)) {
+		if (ec->cond_met == 6) {
+			/*
+			 * BG filter has had better results for 6 consecutive
+			 * samples
+			 */
+			ec->adapt = 1;
+			memcpy(ec->fir_taps16[0], ec->fir_taps16[1],
+				ec->taps * sizeof(int16_t));
+		} else
+			ec->cond_met++;
+	} else
+		ec->cond_met = 0;
+
+	/* Non-Linear Processing */
+
+	ec->clean_nlp = ec->clean;
+	if (ec->adaption_mode & ECHO_CAN_USE_NLP) {
+		/*
+		 * Non-linear processor - a fancy way to say "zap small
+		 * signals, to avoid residual echo due to (uLaw/ALaw)
+		 * non-linearity in the channel.".
+		 */
+
+		if ((16 * ec->Lclean < ec->Ltx)) {
+			/*
+			 * Our e/c has improved echo by at least 24 dB (each
+			 * factor of 2 is 6dB, so 2*2*2*2=16 is the same as
+			 * 6+6+6+6=24dB)
+			 */
+			if (ec->adaption_mode & ECHO_CAN_USE_CNG) {
+				ec->cng_level = ec->Lbgn;
+
+				/*
+				 * Very elementary comfort noise generation.
+				 * Just random numbers rolled off very vaguely
+				 * Hoth-like.  DR: This noise doesn't sound
+				 * quite right to me - I suspect there are some
+				 * overlfow issues in the filtering as it's too
+				 * "crackly".
+				 * TODO: debug this, maybe just play noise at
+				 * high level or look at spectrum.
+				 */
+
+				ec->cng_rndnum =
+				    1664525U * ec->cng_rndnum + 1013904223U;
+				ec->cng_filter =
+				    ((ec->cng_rndnum & 0xFFFF) - 32768 +
+				     5 * ec->cng_filter) >> 3;
+				ec->clean_nlp =
+				    (ec->cng_filter * ec->cng_level * 8) >> 14;
+
+			} else if (ec->adaption_mode & ECHO_CAN_USE_CLIP) {
+				/* This sounds much better than CNG */
+				if (ec->clean_nlp > ec->Lbgn)
+					ec->clean_nlp = ec->Lbgn;
+				if (ec->clean_nlp < -ec->Lbgn)
+					ec->clean_nlp = -ec->Lbgn;
+			} else {
+				/*
+				 * just mute the residual, doesn't sound very
+				 * good, used mainly in G168 tests
+				 */
+				ec->clean_nlp = 0;
+			}
+		} else {
+			/*
+			 * Background noise estimator.  I tried a few
+			 * algorithms here without much luck.  This very simple
+			 * one seems to work best, we just average the level
+			 * using a slow (1 sec time const) filter if the
+			 * current level is less than a (experimentally
+			 * derived) constant.  This means we dont include high
+			 * level signals like near end speech.  When combined
+			 * with CNG or especially CLIP seems to work OK.
+			 */
+			if (ec->Lclean < 40) {
+				ec->Lbgn_acc += abs(ec->clean) - ec->Lbgn;
+				ec->Lbgn = (ec->Lbgn_acc + (1 << 11)) >> 12;
+			}
+		}
+	}
+
+	/* Roll around the taps buffer */
+	if (ec->curr_pos <= 0)
+		ec->curr_pos = ec->taps;
+	ec->curr_pos--;
+
+	if (ec->adaption_mode & ECHO_CAN_DISABLE)
+		ec->clean_nlp = rx;
+
+	/* Output scaled back up again to match input scaling */
+
+	return (int16_t) ec->clean_nlp << 1;
+}
+EXPORT_SYMBOL_GPL(oslec_update);
+
+/* This function is seperated from the echo canceller is it is usually called
+   as part of the tx process.  See rx HP (DC blocking) filter above, it's
+   the same design.
+
+   Some soft phones send speech signals with a lot of low frequency
+   energy, e.g. down to 20Hz.  This can make the hybrid non-linear
+   which causes the echo canceller to fall over.  This filter can help
+   by removing any low frequency before it gets to the tx port of the
+   hybrid.
+
+   It can also help by removing and DC in the tx signal.  DC is bad
+   for LMS algorithms.
+
+   This is one of the classic DC removal filters, adjusted to provide
+   sufficient bass rolloff to meet the above requirement to protect hybrids
+   from things that upset them. The difference between successive samples
+   produces a lousy HPF, and then a suitably placed pole flattens things out.
+   The final result is a nicely rolled off bass end. The filtering is
+   implemented with extended fractional precision, which noise shapes things,
+   giving very clean DC removal.
+*/
+
+int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx)
+{
+	int tmp, tmp1;
+
+	if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) {
+		tmp = tx << 15;
+
+		/*
+		 * Make sure the gain of the HPF is 1.0. The first can still
+		 * saturate a little under impulse conditions, and it might
+		 * roll to 32768 and need clipping on sustained peak level
+		 * signals. However, the scale of such clipping is small, and
+		 * the error due to any saturation should not markedly affect
+		 * the downstream processing.
+		 */
+		tmp -= (tmp >> 4);
+
+		ec->tx_1 += -(ec->tx_1 >> DC_LOG2BETA) + tmp - ec->tx_2;
+		tmp1 = ec->tx_1 >> 15;
+		if (tmp1 > 32767)
+			tmp1 = 32767;
+		if (tmp1 < -32767)
+			tmp1 = -32767;
+		tx = tmp1;
+		ec->tx_2 = tmp;
+	}
+
+	return tx;
+}
+EXPORT_SYMBOL_GPL(oslec_hpf_tx);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Rowe");
+MODULE_DESCRIPTION("Open Source Line Echo Canceller");
+MODULE_VERSION("0.3.0");
diff --git a/drivers/staging/echo/echo.h b/drivers/staging/echo/echo.h
new file mode 100644
index 0000000..754e66d
--- /dev/null
+++ b/drivers/staging/echo/echo.h
@@ -0,0 +1,175 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * echo.c - A line echo canceller.  This code is being developed
+ *          against and partially complies with G168.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *         and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001 Steve Underwood and 2007 David Rowe
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __ECHO_H
+#define __ECHO_H
+
+/*
+Line echo cancellation for voice
+
+What does it do?
+
+This module aims to provide G.168-2002 compliant echo cancellation, to remove
+electrical echoes (e.g. from 2-4 wire hybrids) from voice calls.
+
+
+How does it work?
+
+The heart of the echo cancellor is FIR filter. This is adapted to match the
+echo impulse response of the telephone line. It must be long enough to
+adequately cover the duration of that impulse response. The signal transmitted
+to the telephone line is passed through the FIR filter. Once the FIR is
+properly adapted, the resulting output is an estimate of the echo signal
+received from the line. This is subtracted from the received signal. The result
+is an estimate of the signal which originated at the far end of the line, free
+from echos of our own transmitted signal.
+
+The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and
+was introduced in 1960. It is the commonest form of filter adaption used in
+things like modem line equalisers and line echo cancellers. There it works very
+well.  However, it only works well for signals of constant amplitude. It works
+very poorly for things like speech echo cancellation, where the signal level
+varies widely.  This is quite easy to fix. If the signal level is normalised -
+similar to applying AGC - LMS can work as well for a signal of varying
+amplitude as it does for a modem signal. This normalised least mean squares
+(NLMS) algorithm is the commonest one used for speech echo cancellation. Many
+other algorithms exist - e.g. RLS (essentially the same as Kalman filtering),
+FAP, etc. Some perform significantly better than NLMS.  However, factors such
+as computational complexity and patents favour the use of NLMS.
+
+A simple refinement to NLMS can improve its performance with speech. NLMS tends
+to adapt best to the strongest parts of a signal. If the signal is white noise,
+the NLMS algorithm works very well. However, speech has more low frequency than
+high frequency content. Pre-whitening (i.e. filtering the signal to flatten its
+spectrum) the echo signal improves the adapt rate for speech, and ensures the
+final residual signal is not heavily biased towards high frequencies. A very
+low complexity filter is adequate for this, so pre-whitening adds little to the
+compute requirements of the echo canceller.
+
+An FIR filter adapted using pre-whitened NLMS performs well, provided certain
+conditions are met:
+
+    - The transmitted signal has poor self-correlation.
+    - There is no signal being generated within the environment being
+      cancelled.
+
+The difficulty is that neither of these can be guaranteed.
+
+If the adaption is performed while transmitting noise (or something fairly
+noise like, such as voice) the adaption works very well. If the adaption is
+performed while transmitting something highly correlative (typically narrow
+band energy such as signalling tones or DTMF), the adaption can go seriously
+wrong. The reason is there is only one solution for the adaption on a near
+random signal - the impulse response of the line. For a repetitive signal,
+there are any number of solutions which converge the adaption, and nothing
+guides the adaption to choose the generalised one. Allowing an untrained
+canceller to converge on this kind of narrowband energy probably a good thing,
+since at least it cancels the tones. Allowing a well converged canceller to
+continue converging on such energy is just a way to ruin its generalised
+adaption. A narrowband detector is needed, so adapation can be suspended at
+appropriate times.
+
+The adaption process is based on trying to eliminate the received signal. When
+there is any signal from within the environment being cancelled it may upset
+the adaption process. Similarly, if the signal we are transmitting is small,
+noise may dominate and disturb the adaption process. If we can ensure that the
+adaption is only performed when we are transmitting a significant signal level,
+and the environment is not, things will be OK. Clearly, it is easy to tell when
+we are sending a significant signal. Telling, if the environment is generating
+a significant signal, and doing it with sufficient speed that the adaption will
+not have diverged too much more we stop it, is a little harder.
+
+The key problem in detecting when the environment is sourcing significant
+energy is that we must do this very quickly. Given a reasonably long sample of
+the received signal, there are a number of strategies which may be used to
+assess whether that signal contains a strong far end component. However, by the
+time that assessment is complete the far end signal will have already caused
+major mis-convergence in the adaption process. An assessment algorithm is
+needed which produces a fairly accurate result from a very short burst of far
+end energy.
+
+How do I use it?
+
+The echo cancellor processes both the transmit and receive streams sample by
+sample. The processing function is not declared inline. Unfortunately,
+cancellation requires many operations per sample, so the call overhead is only
+a minor burden.
+*/
+
+#include "fir.h"
+#include "oslec.h"
+
+/*
+    G.168 echo canceller descriptor. This defines the working state for a line
+    echo canceller.
+*/
+struct oslec_state {
+	int16_t tx, rx;
+	int16_t clean;
+	int16_t clean_nlp;
+
+	int nonupdate_dwell;
+	int curr_pos;
+	int taps;
+	int log2taps;
+	int adaption_mode;
+
+	int cond_met;
+	int32_t Pstates;
+	int16_t adapt;
+	int32_t factor;
+	int16_t shift;
+
+	/* Average levels and averaging filter states */
+	int Ltxacc, Lrxacc, Lcleanacc, Lclean_bgacc;
+	int Ltx, Lrx;
+	int Lclean;
+	int Lclean_bg;
+	int Lbgn, Lbgn_acc, Lbgn_upper, Lbgn_upper_acc;
+
+	/* foreground and background filter states */
+	struct fir16_state_t fir_state;
+	struct fir16_state_t fir_state_bg;
+	int16_t *fir_taps16[2];
+
+	/* DC blocking filter states */
+	int tx_1, tx_2, rx_1, rx_2;
+
+	/* optional High Pass Filter states */
+	int32_t xvtx[5], yvtx[5];
+	int32_t xvrx[5], yvrx[5];
+
+	/* Parameters for the optional Hoth noise generator */
+	int cng_level;
+	int cng_rndnum;
+	int cng_filter;
+
+	/* snapshot sample of coeffs used for development */
+	int16_t *snapshot;
+};
+
+#endif /* __ECHO_H */
diff --git a/drivers/staging/echo/fir.h b/drivers/staging/echo/fir.h
new file mode 100644
index 0000000..288bffc
--- /dev/null
+++ b/drivers/staging/echo/fir.h
@@ -0,0 +1,286 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * fir.h - General telephony FIR routines
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2002 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#if !defined(_FIR_H_)
+#define _FIR_H_
+
+/*
+   Blackfin NOTES & IDEAS:
+
+   A simple dot product function is used to implement the filter.  This performs
+   just one MAC/cycle which is inefficient but was easy to implement as a first
+   pass.  The current Blackfin code also uses an unrolled form of the filter
+   history to avoid 0 length hardware loop issues.  This is wasteful of
+   memory.
+
+   Ideas for improvement:
+
+   1/ Rewrite filter for dual MAC inner loop.  The issue here is handling
+   history sample offsets that are 16 bit aligned - the dual MAC needs
+   32 bit aligmnent.  There are some good examples in libbfdsp.
+
+   2/ Use the hardware circular buffer facility tohalve memory usage.
+
+   3/ Consider using internal memory.
+
+   Using less memory might also improve speed as cache misses will be
+   reduced. A drop in MIPs and memory approaching 50% should be
+   possible.
+
+   The foreground and background filters currenlty use a total of
+   about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo
+   can.
+*/
+
+#if defined(USE_MMX)  ||  defined(USE_SSE2)
+#include "mmx.h"
+#endif
+
+/*
+ * 16 bit integer FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using 16 bit integer coefficients.
+ */
+struct fir16_state_t {
+	int taps;
+	int curr_pos;
+	const int16_t *coeffs;
+	int16_t *history;
+};
+
+/*
+ * 32 bit integer FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using 32 bit integer coefficients, and filtering
+ * 16 bit integer data.
+ */
+struct fir32_state_t {
+	int taps;
+	int curr_pos;
+	const int32_t *coeffs;
+	int16_t *history;
+};
+
+/*
+ * Floating point FIR descriptor. This defines the working state for a single
+ * instance of an FIR filter using floating point coefficients and data.
+ */
+struct fir_float_state_t {
+	int taps;
+	int curr_pos;
+	const float *coeffs;
+	float *history;
+};
+
+static inline const int16_t *fir16_create(struct fir16_state_t *fir,
+					      const int16_t *coeffs, int taps)
+{
+	fir->taps = taps;
+	fir->curr_pos = taps - 1;
+	fir->coeffs = coeffs;
+#if defined(USE_MMX)  ||  defined(USE_SSE2) || defined(__bfin__)
+	fir->history = kcalloc(2 * taps, sizeof(int16_t), GFP_KERNEL);
+#else
+	fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
+#endif
+	return fir->history;
+}
+
+static inline void fir16_flush(struct fir16_state_t *fir)
+{
+#if defined(USE_MMX)  ||  defined(USE_SSE2) || defined(__bfin__)
+	memset(fir->history, 0, 2 * fir->taps * sizeof(int16_t));
+#else
+	memset(fir->history, 0, fir->taps * sizeof(int16_t));
+#endif
+}
+
+static inline void fir16_free(struct fir16_state_t *fir)
+{
+	kfree(fir->history);
+}
+
+#ifdef __bfin__
+static inline int32_t dot_asm(short *x, short *y, int len)
+{
+	int dot;
+
+	len--;
+
+	__asm__("I0 = %1;\n\t"
+		"I1 = %2;\n\t"
+		"A0 = 0;\n\t"
+		"R0.L = W[I0++] || R1.L = W[I1++];\n\t"
+		"LOOP dot%= LC0 = %3;\n\t"
+		"LOOP_BEGIN dot%=;\n\t"
+		"A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t"
+		"LOOP_END dot%=;\n\t"
+		"A0 += R0.L*R1.L (IS);\n\t"
+		"R0 = A0;\n\t"
+		"%0 = R0;\n\t"
+		: "=&d"(dot)
+		: "a"(x), "a"(y), "a"(len)
+		: "I0", "I1", "A1", "A0", "R0", "R1"
+	);
+
+	return dot;
+}
+#endif
+
+static inline int16_t fir16(struct fir16_state_t *fir, int16_t sample)
+{
+	int32_t y;
+#if defined(USE_MMX)
+	int i;
+	union mmx_t *mmx_coeffs;
+	union mmx_t *mmx_hist;
+
+	fir->history[fir->curr_pos] = sample;
+	fir->history[fir->curr_pos + fir->taps] = sample;
+
+	mmx_coeffs = (union mmx_t *) fir->coeffs;
+	mmx_hist = (union mmx_t *) &fir->history[fir->curr_pos];
+	i = fir->taps;
+	pxor_r2r(mm4, mm4);
+	/* 8 samples per iteration, so the filter must be a multiple of
+	   8 long. */
+	while (i > 0) {
+		movq_m2r(mmx_coeffs[0], mm0);
+		movq_m2r(mmx_coeffs[1], mm2);
+		movq_m2r(mmx_hist[0], mm1);
+		movq_m2r(mmx_hist[1], mm3);
+		mmx_coeffs += 2;
+		mmx_hist += 2;
+		pmaddwd_r2r(mm1, mm0);
+		pmaddwd_r2r(mm3, mm2);
+		paddd_r2r(mm0, mm4);
+		paddd_r2r(mm2, mm4);
+		i -= 8;
+	}
+	movq_r2r(mm4, mm0);
+	psrlq_i2r(32, mm0);
+	paddd_r2r(mm0, mm4);
+	movd_r2m(mm4, y);
+	emms();
+#elif defined(USE_SSE2)
+	int i;
+	union xmm_t *xmm_coeffs;
+	union xmm_t *xmm_hist;
+
+	fir->history[fir->curr_pos] = sample;
+	fir->history[fir->curr_pos + fir->taps] = sample;
+
+	xmm_coeffs = (union xmm_t *) fir->coeffs;
+	xmm_hist = (union xmm_t *) &fir->history[fir->curr_pos];
+	i = fir->taps;
+	pxor_r2r(xmm4, xmm4);
+	/* 16 samples per iteration, so the filter must be a multiple of
+	   16 long. */
+	while (i > 0) {
+		movdqu_m2r(xmm_coeffs[0], xmm0);
+		movdqu_m2r(xmm_coeffs[1], xmm2);
+		movdqu_m2r(xmm_hist[0], xmm1);
+		movdqu_m2r(xmm_hist[1], xmm3);
+		xmm_coeffs += 2;
+		xmm_hist += 2;
+		pmaddwd_r2r(xmm1, xmm0);
+		pmaddwd_r2r(xmm3, xmm2);
+		paddd_r2r(xmm0, xmm4);
+		paddd_r2r(xmm2, xmm4);
+		i -= 16;
+	}
+	movdqa_r2r(xmm4, xmm0);
+	psrldq_i2r(8, xmm0);
+	paddd_r2r(xmm0, xmm4);
+	movdqa_r2r(xmm4, xmm0);
+	psrldq_i2r(4, xmm0);
+	paddd_r2r(xmm0, xmm4);
+	movd_r2m(xmm4, y);
+#elif defined(__bfin__)
+	fir->history[fir->curr_pos] = sample;
+	fir->history[fir->curr_pos + fir->taps] = sample;
+	y = dot_asm((int16_t *) fir->coeffs, &fir->history[fir->curr_pos],
+		    fir->taps);
+#else
+	int i;
+	int offset1;
+	int offset2;
+
+	fir->history[fir->curr_pos] = sample;
+
+	offset2 = fir->curr_pos;
+	offset1 = fir->taps - offset2;
+	y = 0;
+	for (i = fir->taps - 1; i >= offset1; i--)
+		y += fir->coeffs[i] * fir->history[i - offset1];
+	for (; i >= 0; i--)
+		y += fir->coeffs[i] * fir->history[i + offset2];
+#endif
+	if (fir->curr_pos <= 0)
+		fir->curr_pos = fir->taps;
+	fir->curr_pos--;
+	return (int16_t) (y >> 15);
+}
+
+static inline const int16_t *fir32_create(struct fir32_state_t *fir,
+					      const int32_t *coeffs, int taps)
+{
+	fir->taps = taps;
+	fir->curr_pos = taps - 1;
+	fir->coeffs = coeffs;
+	fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL);
+	return fir->history;
+}
+
+static inline void fir32_flush(struct fir32_state_t *fir)
+{
+	memset(fir->history, 0, fir->taps * sizeof(int16_t));
+}
+
+static inline void fir32_free(struct fir32_state_t *fir)
+{
+	kfree(fir->history);
+}
+
+static inline int16_t fir32(struct fir32_state_t *fir, int16_t sample)
+{
+	int i;
+	int32_t y;
+	int offset1;
+	int offset2;
+
+	fir->history[fir->curr_pos] = sample;
+	offset2 = fir->curr_pos;
+	offset1 = fir->taps - offset2;
+	y = 0;
+	for (i = fir->taps - 1; i >= offset1; i--)
+		y += fir->coeffs[i] * fir->history[i - offset1];
+	for (; i >= 0; i--)
+		y += fir->coeffs[i] * fir->history[i + offset2];
+	if (fir->curr_pos <= 0)
+		fir->curr_pos = fir->taps;
+	fir->curr_pos--;
+	return (int16_t) (y >> 15);
+}
+
+#endif
diff --git a/drivers/staging/echo/mmx.h b/drivers/staging/echo/mmx.h
new file mode 100644
index 0000000..c030cdf
--- /dev/null
+++ b/drivers/staging/echo/mmx.h
@@ -0,0 +1,288 @@
+/*
+ * mmx.h
+ * Copyright (C) 1997-2001 H. Dietz and R. Fisher
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef AVCODEC_I386MMX_H
+#define AVCODEC_I386MMX_H
+
+/*
+ * The type of an value that fits in an MMX register (note that long
+ * long constant values MUST be suffixed by LL and unsigned long long
+ * values by ULL, lest they be truncated by the compiler)
+ */
+
+union mmx_t {
+	long long               q;      /* Quadword (64-bit) value */
+	unsigned long long      uq;     /* Unsigned Quadword */
+	int                     d[2];   /* 2 Doubleword (32-bit) values */
+	unsigned int            ud[2];  /* 2 Unsigned Doubleword */
+	short                   w[4];   /* 4 Word (16-bit) values */
+	unsigned short          uw[4];  /* 4 Unsigned Word */
+	char                    b[8];   /* 8 Byte (8-bit) values */
+	unsigned char           ub[8];  /* 8 Unsigned Byte */
+	float                   s[2];   /* Single-precision (32-bit) value */
+};        /* On an 8-byte (64-bit) boundary */
+
+/* SSE registers */
+union xmm_t {
+	char b[16];
+};
+
+
+#define         mmx_i2r(op, imm, reg) \
+	__asm__ __volatile__ (#op " %0, %%" #reg \
+			: /* nothing */ \
+			: "i" (imm))
+
+#define         mmx_m2r(op, mem, reg) \
+	__asm__ __volatile__ (#op " %0, %%" #reg \
+			: /* nothing */ \
+			: "m" (mem))
+
+#define         mmx_r2m(op, reg, mem) \
+	__asm__ __volatile__ (#op " %%" #reg ", %0" \
+			: "=m" (mem) \
+			: /* nothing */)
+
+#define         mmx_r2r(op, regs, regd) \
+	__asm__ __volatile__ (#op " %" #regs ", %" #regd)
+
+
+#define         emms() __asm__ __volatile__ ("emms")
+
+#define         movd_m2r(var, reg)           mmx_m2r(movd, var, reg)
+#define         movd_r2m(reg, var)           mmx_r2m(movd, reg, var)
+#define         movd_r2r(regs, regd)         mmx_r2r(movd, regs, regd)
+
+#define         movq_m2r(var, reg)           mmx_m2r(movq, var, reg)
+#define         movq_r2m(reg, var)           mmx_r2m(movq, reg, var)
+#define         movq_r2r(regs, regd)         mmx_r2r(movq, regs, regd)
+
+#define         packssdw_m2r(var, reg)       mmx_m2r(packssdw, var, reg)
+#define         packssdw_r2r(regs, regd)     mmx_r2r(packssdw, regs, regd)
+#define         packsswb_m2r(var, reg)       mmx_m2r(packsswb, var, reg)
+#define         packsswb_r2r(regs, regd)     mmx_r2r(packsswb, regs, regd)
+
+#define         packuswb_m2r(var, reg)       mmx_m2r(packuswb, var, reg)
+#define         packuswb_r2r(regs, regd)     mmx_r2r(packuswb, regs, regd)
+
+#define         paddb_m2r(var, reg)          mmx_m2r(paddb, var, reg)
+#define         paddb_r2r(regs, regd)        mmx_r2r(paddb, regs, regd)
+#define         paddd_m2r(var, reg)          mmx_m2r(paddd, var, reg)
+#define         paddd_r2r(regs, regd)        mmx_r2r(paddd, regs, regd)
+#define         paddw_m2r(var, reg)          mmx_m2r(paddw, var, reg)
+#define         paddw_r2r(regs, regd)        mmx_r2r(paddw, regs, regd)
+
+#define         paddsb_m2r(var, reg)         mmx_m2r(paddsb, var, reg)
+#define         paddsb_r2r(regs, regd)       mmx_r2r(paddsb, regs, regd)
+#define         paddsw_m2r(var, reg)         mmx_m2r(paddsw, var, reg)
+#define         paddsw_r2r(regs, regd)       mmx_r2r(paddsw, regs, regd)
+
+#define         paddusb_m2r(var, reg)        mmx_m2r(paddusb, var, reg)
+#define         paddusb_r2r(regs, regd)      mmx_r2r(paddusb, regs, regd)
+#define         paddusw_m2r(var, reg)        mmx_m2r(paddusw, var, reg)
+#define         paddusw_r2r(regs, regd)      mmx_r2r(paddusw, regs, regd)
+
+#define         pand_m2r(var, reg)           mmx_m2r(pand, var, reg)
+#define         pand_r2r(regs, regd)         mmx_r2r(pand, regs, regd)
+
+#define         pandn_m2r(var, reg)          mmx_m2r(pandn, var, reg)
+#define         pandn_r2r(regs, regd)        mmx_r2r(pandn, regs, regd)
+
+#define         pcmpeqb_m2r(var, reg)        mmx_m2r(pcmpeqb, var, reg)
+#define         pcmpeqb_r2r(regs, regd)      mmx_r2r(pcmpeqb, regs, regd)
+#define         pcmpeqd_m2r(var, reg)        mmx_m2r(pcmpeqd, var, reg)
+#define         pcmpeqd_r2r(regs, regd)      mmx_r2r(pcmpeqd, regs, regd)
+#define         pcmpeqw_m2r(var, reg)        mmx_m2r(pcmpeqw, var, reg)
+#define         pcmpeqw_r2r(regs, regd)      mmx_r2r(pcmpeqw, regs, regd)
+
+#define         pcmpgtb_m2r(var, reg)        mmx_m2r(pcmpgtb, var, reg)
+#define         pcmpgtb_r2r(regs, regd)      mmx_r2r(pcmpgtb, regs, regd)
+#define         pcmpgtd_m2r(var, reg)        mmx_m2r(pcmpgtd, var, reg)
+#define         pcmpgtd_r2r(regs, regd)      mmx_r2r(pcmpgtd, regs, regd)
+#define         pcmpgtw_m2r(var, reg)        mmx_m2r(pcmpgtw, var, reg)
+#define         pcmpgtw_r2r(regs, regd)      mmx_r2r(pcmpgtw, regs, regd)
+
+#define         pmaddwd_m2r(var, reg)        mmx_m2r(pmaddwd, var, reg)
+#define         pmaddwd_r2r(regs, regd)      mmx_r2r(pmaddwd, regs, regd)
+
+#define         pmulhw_m2r(var, reg)         mmx_m2r(pmulhw, var, reg)
+#define         pmulhw_r2r(regs, regd)       mmx_r2r(pmulhw, regs, regd)
+
+#define         pmullw_m2r(var, reg)         mmx_m2r(pmullw, var, reg)
+#define         pmullw_r2r(regs, regd)       mmx_r2r(pmullw, regs, regd)
+
+#define         por_m2r(var, reg)            mmx_m2r(por, var, reg)
+#define         por_r2r(regs, regd)          mmx_r2r(por, regs, regd)
+
+#define         pslld_i2r(imm, reg)          mmx_i2r(pslld, imm, reg)
+#define         pslld_m2r(var, reg)          mmx_m2r(pslld, var, reg)
+#define         pslld_r2r(regs, regd)        mmx_r2r(pslld, regs, regd)
+#define         psllq_i2r(imm, reg)          mmx_i2r(psllq, imm, reg)
+#define         psllq_m2r(var, reg)          mmx_m2r(psllq, var, reg)
+#define         psllq_r2r(regs, regd)        mmx_r2r(psllq, regs, regd)
+#define         psllw_i2r(imm, reg)          mmx_i2r(psllw, imm, reg)
+#define         psllw_m2r(var, reg)          mmx_m2r(psllw, var, reg)
+#define         psllw_r2r(regs, regd)        mmx_r2r(psllw, regs, regd)
+
+#define         psrad_i2r(imm, reg)          mmx_i2r(psrad, imm, reg)
+#define         psrad_m2r(var, reg)          mmx_m2r(psrad, var, reg)
+#define         psrad_r2r(regs, regd)        mmx_r2r(psrad, regs, regd)
+#define         psraw_i2r(imm, reg)          mmx_i2r(psraw, imm, reg)
+#define         psraw_m2r(var, reg)          mmx_m2r(psraw, var, reg)
+#define         psraw_r2r(regs, regd)        mmx_r2r(psraw, regs, regd)
+
+#define         psrld_i2r(imm, reg)          mmx_i2r(psrld, imm, reg)
+#define         psrld_m2r(var, reg)          mmx_m2r(psrld, var, reg)
+#define         psrld_r2r(regs, regd)        mmx_r2r(psrld, regs, regd)
+#define         psrlq_i2r(imm, reg)          mmx_i2r(psrlq, imm, reg)
+#define         psrlq_m2r(var, reg)          mmx_m2r(psrlq, var, reg)
+#define         psrlq_r2r(regs, regd)        mmx_r2r(psrlq, regs, regd)
+#define         psrlw_i2r(imm, reg)          mmx_i2r(psrlw, imm, reg)
+#define         psrlw_m2r(var, reg)          mmx_m2r(psrlw, var, reg)
+#define         psrlw_r2r(regs, regd)        mmx_r2r(psrlw, regs, regd)
+
+#define         psubb_m2r(var, reg)          mmx_m2r(psubb, var, reg)
+#define         psubb_r2r(regs, regd)        mmx_r2r(psubb, regs, regd)
+#define         psubd_m2r(var, reg)          mmx_m2r(psubd, var, reg)
+#define         psubd_r2r(regs, regd)        mmx_r2r(psubd, regs, regd)
+#define         psubw_m2r(var, reg)          mmx_m2r(psubw, var, reg)
+#define         psubw_r2r(regs, regd)        mmx_r2r(psubw, regs, regd)
+
+#define         psubsb_m2r(var, reg)         mmx_m2r(psubsb, var, reg)
+#define         psubsb_r2r(regs, regd)       mmx_r2r(psubsb, regs, regd)
+#define         psubsw_m2r(var, reg)         mmx_m2r(psubsw, var, reg)
+#define         psubsw_r2r(regs, regd)       mmx_r2r(psubsw, regs, regd)
+
+#define         psubusb_m2r(var, reg)        mmx_m2r(psubusb, var, reg)
+#define         psubusb_r2r(regs, regd)      mmx_r2r(psubusb, regs, regd)
+#define         psubusw_m2r(var, reg)        mmx_m2r(psubusw, var, reg)
+#define         psubusw_r2r(regs, regd)      mmx_r2r(psubusw, regs, regd)
+
+#define         punpckhbw_m2r(var, reg)      mmx_m2r(punpckhbw, var, reg)
+#define         punpckhbw_r2r(regs, regd)    mmx_r2r(punpckhbw, regs, regd)
+#define         punpckhdq_m2r(var, reg)      mmx_m2r(punpckhdq, var, reg)
+#define         punpckhdq_r2r(regs, regd)    mmx_r2r(punpckhdq, regs, regd)
+#define         punpckhwd_m2r(var, reg)      mmx_m2r(punpckhwd, var, reg)
+#define         punpckhwd_r2r(regs, regd)    mmx_r2r(punpckhwd, regs, regd)
+
+#define         punpcklbw_m2r(var, reg)      mmx_m2r(punpcklbw, var, reg)
+#define         punpcklbw_r2r(regs, regd)    mmx_r2r(punpcklbw, regs, regd)
+#define         punpckldq_m2r(var, reg)      mmx_m2r(punpckldq, var, reg)
+#define         punpckldq_r2r(regs, regd)    mmx_r2r(punpckldq, regs, regd)
+#define         punpcklwd_m2r(var, reg)      mmx_m2r(punpcklwd, var, reg)
+#define         punpcklwd_r2r(regs, regd)    mmx_r2r(punpcklwd, regs, regd)
+
+#define         pxor_m2r(var, reg)           mmx_m2r(pxor, var, reg)
+#define         pxor_r2r(regs, regd)         mmx_r2r(pxor, regs, regd)
+
+
+/* 3DNOW extensions */
+
+#define         pavgusb_m2r(var, reg)        mmx_m2r(pavgusb, var, reg)
+#define         pavgusb_r2r(regs, regd)      mmx_r2r(pavgusb, regs, regd)
+
+
+/* AMD MMX extensions - also available in intel SSE */
+
+
+#define         mmx_m2ri(op, mem, reg, imm) \
+	__asm__ __volatile__ (#op " %1, %0, %%" #reg \
+			: /* nothing */ \
+			: "m" (mem), "i" (imm))
+#define         mmx_r2ri(op, regs, regd, imm) \
+	__asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \
+			: /* nothing */ \
+			: "i" (imm))
+
+#define         mmx_fetch(mem, hint) \
+	__asm__ __volatile__ ("prefetch" #hint " %0" \
+			: /* nothing */ \
+			: "m" (mem))
+
+
+#define         maskmovq(regs, maskreg)      mmx_r2ri(maskmovq, regs, maskreg)
+
+#define         movntq_r2m(mmreg, var)       mmx_r2m(movntq, mmreg, var)
+
+#define         pavgb_m2r(var, reg)          mmx_m2r(pavgb, var, reg)
+#define         pavgb_r2r(regs, regd)        mmx_r2r(pavgb, regs, regd)
+#define         pavgw_m2r(var, reg)          mmx_m2r(pavgw, var, reg)
+#define         pavgw_r2r(regs, regd)        mmx_r2r(pavgw, regs, regd)
+
+#define         pextrw_r2r(mmreg, reg, imm)  mmx_r2ri(pextrw, mmreg, reg, imm)
+
+#define         pinsrw_r2r(reg, mmreg, imm)  mmx_r2ri(pinsrw, reg, mmreg, imm)
+
+#define         pmaxsw_m2r(var, reg)         mmx_m2r(pmaxsw, var, reg)
+#define         pmaxsw_r2r(regs, regd)       mmx_r2r(pmaxsw, regs, regd)
+
+#define         pmaxub_m2r(var, reg)         mmx_m2r(pmaxub, var, reg)
+#define         pmaxub_r2r(regs, regd)       mmx_r2r(pmaxub, regs, regd)
+
+#define         pminsw_m2r(var, reg)         mmx_m2r(pminsw, var, reg)
+#define         pminsw_r2r(regs, regd)       mmx_r2r(pminsw, regs, regd)
+
+#define         pminub_m2r(var, reg)         mmx_m2r(pminub, var, reg)
+#define         pminub_r2r(regs, regd)       mmx_r2r(pminub, regs, regd)
+
+#define         pmovmskb(mmreg, reg) \
+	__asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg)
+
+#define         pmulhuw_m2r(var, reg)        mmx_m2r(pmulhuw, var, reg)
+#define         pmulhuw_r2r(regs, regd)      mmx_r2r(pmulhuw, regs, regd)
+
+#define         prefetcht0(mem)              mmx_fetch(mem, t0)
+#define         prefetcht1(mem)              mmx_fetch(mem, t1)
+#define         prefetcht2(mem)              mmx_fetch(mem, t2)
+#define         prefetchnta(mem)             mmx_fetch(mem, nta)
+
+#define         psadbw_m2r(var, reg)         mmx_m2r(psadbw, var, reg)
+#define         psadbw_r2r(regs, regd)       mmx_r2r(psadbw, regs, regd)
+
+#define         pshufw_m2r(var, reg, imm)    mmx_m2ri(pshufw, var, reg, imm)
+#define         pshufw_r2r(regs, regd, imm)  mmx_r2ri(pshufw, regs, regd, imm)
+
+#define         sfence() __asm__ __volatile__ ("sfence\n\t")
+
+/* SSE2 */
+#define         pshufhw_m2r(var, reg, imm)   mmx_m2ri(pshufhw, var, reg, imm)
+#define         pshufhw_r2r(regs, regd, imm) mmx_r2ri(pshufhw, regs, regd, imm)
+#define         pshuflw_m2r(var, reg, imm)   mmx_m2ri(pshuflw, var, reg, imm)
+#define         pshuflw_r2r(regs, regd, imm) mmx_r2ri(pshuflw, regs, regd, imm)
+
+#define         pshufd_r2r(regs, regd, imm)  mmx_r2ri(pshufd, regs, regd, imm)
+
+#define         movdqa_m2r(var, reg)         mmx_m2r(movdqa, var, reg)
+#define         movdqa_r2m(reg, var)         mmx_r2m(movdqa, reg, var)
+#define         movdqa_r2r(regs, regd)       mmx_r2r(movdqa, regs, regd)
+#define         movdqu_m2r(var, reg)         mmx_m2r(movdqu, var, reg)
+#define         movdqu_r2m(reg, var)         mmx_r2m(movdqu, reg, var)
+#define         movdqu_r2r(regs, regd)       mmx_r2r(movdqu, regs, regd)
+
+#define         pmullw_r2m(reg, var)         mmx_r2m(pmullw, reg, var)
+
+#define         pslldq_i2r(imm, reg)         mmx_i2r(pslldq, imm, reg)
+#define         psrldq_i2r(imm, reg)         mmx_i2r(psrldq, imm, reg)
+
+#define         punpcklqdq_r2r(regs, regd)   mmx_r2r(punpcklqdq, regs, regd)
+#define         punpckhqdq_r2r(regs, regd)   mmx_r2r(punpckhqdq, regs, regd)
+
+
+#endif /* AVCODEC_I386MMX_H */
diff --git a/drivers/staging/echo/oslec.h b/drivers/staging/echo/oslec.h
new file mode 100644
index 0000000..f417536
--- /dev/null
+++ b/drivers/staging/echo/oslec.h
@@ -0,0 +1,94 @@
+/*
+ *  OSLEC - A line echo canceller.  This code is being developed
+ *          against and partially complies with G168. Using code from SpanDSP
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *         and David Rowe <david_at_rowetel_dot_com>
+ *
+ * Copyright (C) 2001 Steve Underwood and 2007-2008 David Rowe
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef __OSLEC_H
+#define __OSLEC_H
+
+/* Mask bits for the adaption mode */
+#define ECHO_CAN_USE_ADAPTION	0x01
+#define ECHO_CAN_USE_NLP	0x02
+#define ECHO_CAN_USE_CNG	0x04
+#define ECHO_CAN_USE_CLIP	0x08
+#define ECHO_CAN_USE_TX_HPF	0x10
+#define ECHO_CAN_USE_RX_HPF	0x20
+#define ECHO_CAN_DISABLE	0x40
+
+/**
+ * oslec_state: G.168 echo canceller descriptor.
+ *
+ * This defines the working state for a line echo canceller.
+ */
+struct oslec_state;
+
+/**
+ * oslec_create - Create a voice echo canceller context.
+ * @len: The length of the canceller, in samples.
+ * @return: The new canceller context, or NULL if the canceller could not be
+ * created.
+ */
+struct oslec_state *oslec_create(int len, int adaption_mode);
+
+/**
+ * oslec_free - Free a voice echo canceller context.
+ * @ec: The echo canceller context.
+ */
+void oslec_free(struct oslec_state *ec);
+
+/**
+ * oslec_flush - Flush (reinitialise) a voice echo canceller context.
+ * @ec: The echo canceller context.
+ */
+void oslec_flush(struct oslec_state *ec);
+
+/**
+ * oslec_adaption_mode - set the adaption mode of a voice echo canceller context.
+ * @ec The echo canceller context.
+ * @adaption_mode: The mode.
+ */
+void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode);
+
+void oslec_snapshot(struct oslec_state *ec);
+
+/**
+ * oslec_update: Process a sample through a voice echo canceller.
+ * @ec: The echo canceller context.
+ * @tx: The transmitted audio sample.
+ * @rx: The received audio sample.
+ *
+ * The return value is the clean (echo cancelled) received sample.
+ */
+int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx);
+
+/**
+ * oslec_hpf_tx: Process to high pass filter the tx signal.
+ * @ec: The echo canceller context.
+ * @tx: The transmitted auio sample.
+ *
+ * The return value is the HP filtered transmit sample, send this to your D/A.
+ */
+int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx);
+
+#endif /* __OSLEC_H */
