Subject: dahdi-extra: out-of-tree DAHDI drivers
Origin: https://notabug.org/tzafrir/dahdi-linux-extra
Forwarded: No
Last-Update: 2015-08-12
 
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.

Currently only includes OSLEC and opvxa1200.
 
Git-Commit: 3b5e1e0b2f6a449144282ef640efd16e2d6d7c87
Dahdi: v2.10.2
---
diff --git a/drivers/dahdi/Kbuild b/drivers/dahdi/Kbuild
index ee2cd59..45686de 100644
--- a/drivers/dahdi/Kbuild
+++ b/drivers/dahdi/Kbuild
@@ -56,6 +56,10 @@ 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_OPVXA1200)		+= opvxa1200/
+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..444b7c2 100644
--- a/drivers/dahdi/Kconfig
+++ b/drivers/dahdi/Kconfig
@@ -292,3 +292,34 @@ config DAHDI_WCTE11XP
 	  If unsure, say Y.
 
 source "drivers/dahdi/xpp/Kconfig"
+
+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 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/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..8b69c94
--- /dev/null
+++ b/drivers/dahdi/opvxa1200/base.c
@@ -0,0 +1,3052 @@
+/*
+ * 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;
+	}
+
+	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;
+	wc->span.spantype = SPANTYPE_ANALOG_MIXED;
+
+	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, IRQF_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/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 */
