[svn-commits] sruffell: linux/trunk r5148 - in /linux/trunk: ./ drivers/dahdi/ drivers/dahd...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Oct 28 13:12:48 CDT 2008


Author: sruffell
Date: Tue Oct 28 13:12:48 2008
New Revision: 5148

URL: http://svn.digium.com/view/dahdi?view=rev&rev=5148
Log:
Adding the wcb4xxp driver, a native dahdi driver for the B410P module.

Added:
    linux/trunk/drivers/dahdi/wcb4xxp/
      - copied from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/
    linux/trunk/drivers/dahdi/wcb4xxp/Kbuild
      - copied, made public unchanged from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/Kbuild
    linux/trunk/drivers/dahdi/wcb4xxp/base.c
      - copied, made public, changed from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/base.c
    linux/trunk/drivers/dahdi/wcb4xxp/wcb4xxp.h
      - copied, made public unchanged from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/wcb4xxp.h
Modified:
    linux/trunk/   (props changed)
    linux/trunk/README
    linux/trunk/drivers/dahdi/Kbuild

Propchange: linux/trunk/
------------------------------------------------------------------------------
    automerge = yes

Propchange: linux/trunk/
------------------------------------------------------------------------------
    svnmerge-integrated = /linux/trunk:1-5146

Modified: linux/trunk/README
URL: http://svn.digium.com/view/dahdi/linux/trunk/README?view=diff&rev=5148&r1=5147&r2=5148
==============================================================================
--- linux/trunk/README (original)
+++ linux/trunk/README Tue Oct 28 13:12:48 2008
@@ -107,15 +107,17 @@
   make MODULES_EXTRA="mod1 mod2" SUBDIRS_EXTRA="subdir1/ subdir1/"
 
 
-Installing the B410P drivers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-With Zaptel it was possible to install mISDN and the B410P driver by typing
-'make b410p' from the command-line.  This is no longer possible with DAHDI as
-part of the changes to make DAHDI friendlier to binary packagers.  If you
-would like to install support for the B410P with asterisk you will need to
-install it manually. Please see http://www.misdn.org for more information, but
-the following sequence of steps is roughly equivalent to 'make b410p' from
-previous releases.
+Installing the B410P drivers with MISDN
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+DAHDI includes the wcb4xxp driver for the B410P. However, support for the
+B410P was historically provided by misdn.  If you would like to use the misdn
+driver with the B410P, please comment out the wcb4xxp line in the
+/etc/dahdi/modules.  This will prevent dahdi from loading wcb4xxp which will
+conflict with the misdn driver.
+
+To install the misdn driver for the B410P, please see http://www.misdn.org for
+more information, but the following sequence of steps is roughly equivalent to
+'make b410p' from previous releases.
 
   wget http://www.misdn.org/downloads/releases/mISDN-1_1_8.tar.gz
   wget http://www.misdn.org/downloads/releases/mISDNuser-1_1_8.tar.gz

Modified: linux/trunk/drivers/dahdi/Kbuild
URL: http://svn.digium.com/view/dahdi/linux/trunk/drivers/dahdi/Kbuild?view=diff&rev=5148&r1=5147&r2=5148
==============================================================================
--- linux/trunk/drivers/dahdi/Kbuild (original)
+++ linux/trunk/drivers/dahdi/Kbuild Tue Oct 28 13:12:48 2008
@@ -10,6 +10,7 @@
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTDM24XXP)	+= wctdm24xxp/
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTE12XP)		+= wcte12xp/
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTDM)		+= wctdm.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCB4XXP)		+= wcb4xxp/
 
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCT1XXP)		+= wct1xxp.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTE11XP)		+= wcte11xp.o

Copied: linux/trunk/drivers/dahdi/wcb4xxp/Kbuild (from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/Kbuild)
URL: http://svn.digium.com/view/dahdi/linux/trunk/drivers/dahdi/wcb4xxp/Kbuild?view=diff&rev=5148&p1=linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/Kbuild&r1=5147&p2=linux/trunk/drivers/dahdi/wcb4xxp/Kbuild&r2=5148
==============================================================================
--- linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/Kbuild (original)
+++ linux/trunk/drivers/dahdi/wcb4xxp/Kbuild Tue Oct 28 13:12:48 2008
@@ -1,0 +1,7 @@
+obj-m += wcb4xxp.o
+
+EXTRA_CFLAGS := -I$(src)/.. -Wno-undef
+
+wcb4xxp-objs := base.o
+
+$(obj)/base.o: $(src)/wcb4xxp.h

Copied: linux/trunk/drivers/dahdi/wcb4xxp/base.c (from r5147, linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/base.c)
URL: http://svn.digium.com/view/dahdi/linux/trunk/drivers/dahdi/wcb4xxp/base.c?view=diff&rev=5148&p1=linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/base.c&r1=5147&p2=linux/trunk/drivers/dahdi/wcb4xxp/base.c&r2=5148
==============================================================================
--- linux/team/sruffell/private/dahdi-linux-wcb4xxp/drivers/dahdi/wcb4xxp/base.c (original)
+++ linux/trunk/drivers/dahdi/wcb4xxp/base.c Tue Oct 28 13:12:48 2008
@@ -1,0 +1,2652 @@
+/*
+ * WCB410P  Quad-BRI PCI Driver
+ *
+ * Written by Andrew Kohlsmith <akohlsmith at mixdown.ca>
+ *
+ * Copyright (C) 2008 Digium, Inc.
+ * All rights reserved.
+ *
+ */
+
+/*
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2 as published by the
+ * Free Software Foundation. See the LICENSE file included with
+ * this program for more details.
+ */
+
+#include <linux/autoconf.h>
+#include <linux/init.h>
+
+#include <linux/kernel.h>	/* printk() */
+#include <linux/errno.h>	/* error codes */
+#include <linux/module.h>
+#include <linux/types.h>	/* size_t */
+#include <linux/fcntl.h>	/* O_ACCMODE */
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/pci.h>		/* for PCI structures */
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/device.h>	/* dev_err() */
+#include <linux/interrupt.h>
+#include <asm/system.h>		/* cli(), *_flags */
+#include <asm/uaccess.h>	/* copy_*_user */
+#include <linux/workqueue.h>	/* work_struct */
+#include <linux/timer.h>	/* timer_struct */
+#include <linux/moduleparam.h>
+#include <linux/proc_fs.h>
+
+#include <dahdi/kernel.h>
+
+#include "wcb4xxp.h"
+
+#if (DAHDI_CHUNKSIZE != 8)
+#error Sorry, wcb4xxp does not support chunksize != 8
+#endif
+
+//#define SIMPLE_BCHAN_FIFO
+//#define DEBUG_LOWLEVEL_REGS			/* debug __pci_in/out, not b4xxp_setreg */
+
+#define DEBUG_GENERAL 		(1 << 0)	/* general debug messages */
+#define DEBUG_DTMF 		(1 << 1)	/* emit DTMF detector messages */
+#define DEBUG_REGS 		(1 << 2)	/* emit register read/write, but only if the kernel's DEBUG is defined */
+#define DEBUG_FOPS  		(1 << 3)	/* emit file operation messages */
+#define DEBUG_ECHOCAN 		(1 << 4)
+#define DEBUG_ST_STATE		(1 << 5)	/* S/T state machine */
+#define DEBUG_HDLC		(1 << 6)	/* HDLC controller */
+#define DEBUG_ALARM		(1 << 7)	/* alarm changes */
+
+#define DBG			(debug & DEBUG_GENERAL)
+#define DBG_DTMF		(debug & DEBUG_DTMF)
+#define DBG_REGS		(debug & DEBUG_REGS)
+#define DBG_FOPS		(debug & DEBUG_FOPS)
+#define DBG_EC			(debug & DEBUG_ECHOCAN)
+#define DBG_ST			(debug & DEBUG_ST_STATE)
+#define DBG_HDLC		(debug & DEBUG_HDLC)
+#define DBG_ALARM		(debug & DEBUG_ALARM)
+
+static int debug = 0;
+static int loopback = 0;
+static int milliwatt = 0;
+static int pedanticpci = 0;
+static int teignorered = 0;
+static int alarmdebounce = 0;
+static int vpmsupport = 1;
+static int timer_1_ms = 2000;
+static int timer_3_ms = 30000;
+
+#define MAX_B4_CARDS 64
+static struct b4xxp *cards[MAX_B4_CARDS];
+
+static int led_fader_table[] = {
+	 0,  0,  0,  1,  2,  3,  4,  6,  8,  9, 11, 13, 16, 18, 20, 22, 24,
+	25, 27, 28, 29, 30, 31, 31, 32, 31, 31, 30, 29, 28, 27, 25, 23, 22,
+	20, 18, 16, 13, 11,  9,  8,  6,  4,  3,  2,  1,  0,  0,
+};
+
+#define PROCFS_MAX_SIZE		1024
+#define PROCFS_NAME 		"wcb4xxp"
+static struct proc_dir_entry *myproc;
+
+/* Expansion; right now there's just one card and all of its idiosyncrasies. */
+
+#define FLAG_yyy	(1 << 0)
+#define FLAG_zzz	(1 << 1)
+
+struct devtype {
+	char *desc;
+	unsigned int flags;
+};
+
+static struct devtype wcb4xxp = { "Wildcard B410P", 0 };
+
+
+const char *wcb4xxp_rcsdata = "$RCSfile: base.c,v $ $Revision$";
+const char *build_stamp = "" __DATE__ " " __TIME__ "";
+
+
+/*
+ * lowlevel PCI access functions
+ * These are simply wrappers for the normal PCI access functions that the kernel provides,
+ * except that they allow us to work around specific PCI quirks with module options.
+ * Currently the only option supported is pedanticpci, which injects a (min.) 3us delay
+ * after any PCI access to forcibly disable fast back-to-back transactions.
+ * In the case of a PCI write, pedanticpci will also read from the status register, which
+ * has the effect of flushing any pending PCI writes.
+ */
+
+static inline unsigned char __pci_in8(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned char ret = ioread8(b4->addr + reg);
+
+#ifdef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		drv_dbg(b4->dev, "read 0x%02x from 0x%08x\n", ret, b4->addr + reg);
+	}
+#endif
+	if(unlikely(pedanticpci)) {
+		udelay(3);
+	}
+
+	return ret;
+}
+
+static inline unsigned short __pci_in16(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned short ret = ioread16(b4->addr + reg);
+
+#ifdef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		drv_dbg(b4->dev, "read 0x%04x from 0x%08x\n", ret, b4->addr + reg);
+	}
+#endif
+	if(unlikely(pedanticpci)) {
+		udelay(3);
+	}
+
+	return ret;
+}
+
+static inline unsigned int __pci_in32(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned int ret = ioread32(b4->addr + reg);
+
+#ifdef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		drv_dbg(b4->dev, "read 0x%04x from 0x%08x\n", ret, b4->addr + reg);
+	}
+#endif
+	if(unlikely(pedanticpci)) {
+		udelay(3);
+	}
+
+	return ret;
+}
+
+static inline void __pci_out32(struct b4xxp *b4, const unsigned int reg, const unsigned int val)
+{
+#ifdef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		drv_dbg(b4->dev, "writing 0x%02x to 0x%08x\n", val, b4->addr + reg);
+	}
+#endif
+	iowrite32(val, b4->addr + reg);
+
+	if(unlikely(pedanticpci)) {
+		udelay(3);
+		(void)ioread8(b4->addr + R_STATUS);
+	}
+}
+
+static inline void __pci_out8(struct b4xxp *b4, const unsigned int reg, const unsigned char val)
+{
+#ifdef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		drv_dbg(b4->dev, "writing 0x%02x to 0x%08x\n", val, b4->addr + reg);
+	}
+#endif
+	iowrite8(val, b4->addr + reg);
+
+	if(unlikely(pedanticpci)) {
+		udelay(3);
+		(void)ioread8(b4->addr + R_STATUS);
+	}
+}
+
+
+/*
+ * Standard I/O access functions
+ * uses spinlocks to protect against multiple I/O accesses
+ * DOES NOT automatically memory barrier
+ */
+static inline unsigned char b4xxp_getreg8(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned int ret;
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->reglock, irq_flags);
+#undef RETRY_REGISTER_READS
+#ifdef RETRY_REGISTER_READS
+	switch (reg) {
+	case A_Z1:
+	case A_Z1H:
+	case A_F2:
+	case R_IRQ_OVIEW:
+	case R_BERT_STA:
+	case A_ST_RD_STA:
+	case R_IRQ_FIFO_BL0:
+	case A_Z2:
+	case A_Z2H:
+	case A_F1:
+	case R_RAM_USE:
+	case R_F0_CNTL:
+	case A_ST_SQ_RD:
+	case R_IRQ_FIFO_BL7:
+		/* On pg 53 of the data sheet for the hfc, it states that we must
+		 * retry certain registers until we get two consecutive reads that are
+		 * the same. */
+retry:
+		ret = __pci_in8(b4, reg);
+		if (ret != __pci_in8(b4, reg)) 
+			goto retry;
+		break;
+	default:
+#endif
+		ret = __pci_in8(b4, reg);
+#ifdef RETRY_REGISTER_READS
+		break;
+	}
+#endif
+	spin_unlock_irqrestore(&b4->reglock, irq_flags);
+
+#ifndef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		dev_dbg(b4->dev, "read 0x%02x from 0x%p\n", ret, b4->addr + reg);
+	}
+#endif
+	return ret;
+}
+
+static inline unsigned int b4xxp_getreg32(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned int ret;
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->reglock, irq_flags);
+	ret = __pci_in32(b4, reg);
+	spin_unlock_irqrestore(&b4->reglock, irq_flags);
+
+#ifndef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		dev_dbg(b4->dev, "read 0x%04x from 0x%p\n", ret, b4->addr + reg);
+	}
+#endif
+	return ret;
+}
+
+static inline unsigned short b4xxp_getreg16(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned int ret;
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->reglock, irq_flags);
+	ret = __pci_in16(b4, reg);
+	spin_unlock_irqrestore(&b4->reglock, irq_flags);
+
+#ifndef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		dev_dbg(b4->dev, "read 0x%04x from 0x%p\n", ret, b4->addr + reg);
+	}
+#endif
+	return ret;
+}
+
+static inline void b4xxp_setreg32(struct b4xxp *b4, const unsigned int reg, const unsigned int val)
+{
+	unsigned long irq_flags;
+
+#ifndef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		dev_dbg(b4->dev, "writing 0x%02x to 0x%p\n", val, b4->addr + reg);
+	}
+#endif
+	spin_lock_irqsave(&b4->reglock, irq_flags);
+	__pci_out32(b4, reg, val);
+	spin_unlock_irqrestore(&b4->reglock, irq_flags);
+}
+
+static inline void b4xxp_setreg8(struct b4xxp *b4, const unsigned int reg, const unsigned char val)
+{
+	unsigned long irq_flags;
+
+#ifndef DEBUG_LOWLEVEL_REGS
+	if(unlikely(DBG_REGS)) {
+		dev_dbg(b4->dev, "writing 0x%02x to 0x%p\n", val, b4->addr + reg);
+	}
+#endif
+	spin_lock_irqsave(&b4->reglock, irq_flags);
+	__pci_out8(b4, reg, val);
+	spin_unlock_irqrestore(&b4->reglock, irq_flags);
+}
+
+/*
+ * A lot of the registers in the HFC are indexed.
+ * this function sets the index, and then writes to the indexed register in an ordered fashion.
+ * memory barriers are useless unless spinlocked, so that's what these wrapper functions do.
+ */
+static void b4xxp_setreg_ra(struct b4xxp *b4, unsigned char r, unsigned char rd, unsigned char a, unsigned char ad)
+{
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->seqlock, irq_flags);
+
+	b4xxp_setreg8(b4, r, rd);
+	wmb();
+	b4xxp_setreg8(b4, a, ad);
+
+	mmiowb();
+	spin_unlock_irqrestore(&b4->seqlock, irq_flags);
+}
+
+static unsigned char b4xxp_getreg_ra(struct b4xxp *b4, unsigned char r, unsigned char rd, unsigned char a)
+{
+	unsigned long irq_flags;
+	unsigned char val;
+
+	spin_lock_irqsave(&b4->seqlock, irq_flags);
+
+	b4xxp_setreg8(b4, r, rd);
+	wmb();
+	val = b4xxp_getreg8(b4, a);
+	
+	mmiowb();
+	spin_unlock_irqrestore(&b4->seqlock, irq_flags);
+	return val;
+}
+
+
+/*
+ * HFC-4S GPIO routines
+ *
+ * the B410P uses the HFC-4S GPIO as follows:
+ * GPIO 8..10: output, CPLD register select
+ * GPIO12..15: output, 1 = enable power for port 1-4
+ * GPI16: input, 0 = echo can #1 interrupt
+ * GPI17: input, 0 = echo can #2 interrupt
+ * GPI23: input, 1 = NT power module installed
+ * GPI24..27: input, NT power module problem on port 1-4
+ * GPI28..31: input, 1 = port 1-4 in NT mode
+ */
+
+/* initialize HFC-4S GPIO. Set up pin drivers before setting GPIO mode */
+static void hfc_gpio_init(struct b4xxp *b4)
+{
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->seqlock, irq_flags);
+
+	flush_pci();				/* flush any pending PCI writes */
+
+	mb();
+
+	b4xxp_setreg8(b4, R_GPIO_EN0, 0x00);	/* GPIO0..7 input */
+	b4xxp_setreg8(b4, R_GPIO_EN1, 0xf7);	/* GPIO8..10,12..15 outputs, GPIO11 input */
+	b4xxp_setreg8(b4, R_GPIO_OUT1, 0x00);	/* disable power, CPLD reg 0 */
+
+	mb();
+
+	b4xxp_setreg8(b4, R_GPIO_SEL, 0xf0);	/* GPIO0..7 S/T, 8..15 GPIO */
+
+	mb();
+
+	spin_unlock_irqrestore(&b4->seqlock, irq_flags);
+}
+
+/*
+ * HFC SRAM interface code.
+ * This came from mattf, I don't even pretend to understand it,
+ * It seems to be using undocumented features in the HFC.
+ * I just added the __pci_in8() to ensure the PCI writes made it
+ * to hardware by the time these functions return. 
+ */
+static inline void enablepcibridge(struct b4xxp *b4)
+{
+	b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x03);
+	flush_pci();
+}
+
+static inline void disablepcibridge(struct b4xxp *b4)
+{
+	b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x02);
+	flush_pci();
+}
+
+/* NOTE: read/writepcibridge do not use __pci_in/out because they are using b4->ioaddr not b4->addr */
+static inline unsigned char readpcibridge(struct b4xxp *b4, unsigned char address)
+{
+	unsigned short cipv;
+	unsigned char data;
+
+
+/* slow down a PCI read access by 1 PCI clock cycle */
+	b4xxp_setreg8(b4, R_CTRL, 0x4);
+	wmb();
+
+	if (address == 0)
+		cipv=0x4000;
+	else
+		cipv=0x5800;
+
+/* select local bridge port address by writing to CIP port */
+	iowrite16(cipv, b4->ioaddr + 4);
+	wmb();
+	data = ioread8(b4->ioaddr);
+
+/* restore R_CTRL for normal PCI read cycle speed */
+	b4xxp_setreg8(b4, R_CTRL, 0x0);
+	wmb();
+	flush_pci();
+
+	return data;
+}
+
+static inline void writepcibridge(struct b4xxp *b4, unsigned char address, unsigned char data)
+{
+	unsigned short cipv;
+	unsigned int datav;
+
+
+	if (address == 0)
+		cipv=0x4000;
+	else
+		cipv=0x5800;
+
+/* select local bridge port address by writing to CIP port */
+	iowrite16(cipv, b4->ioaddr + 4);
+	wmb();
+
+/* define a 32 bit dword with 4 identical bytes for write sequence */
+	datav = data | ( (__u32) data <<8) | ( (__u32) data <<16) | ( (__u32) data <<24);
+
+/*
+ * write this 32 bit dword to the bridge data port
+ * this will initiate a write sequence of up to 4 writes to the same address on the local bus
+ * interface
+ * the number of write accesses is undefined but >=1 and depends on the next PCI transaction
+ * during write sequence on the local bus
+ */
+	iowrite32(datav, b4->ioaddr);
+	wmb();
+	flush_pci();
+}
+
+/* CPLD access code, more or less copied verbatim from code provided by mattf. */
+static inline void cpld_select_reg(struct b4xxp *b4, unsigned char reg)
+{
+	b4xxp_setreg8(b4, R_GPIO_OUT1, reg);
+	flush_pci();
+}
+
+static inline void cpld_setreg(struct b4xxp *b4, unsigned char reg, unsigned char val)
+{
+	cpld_select_reg(b4, reg);
+
+	enablepcibridge(b4);
+	writepcibridge(b4, 1, val);
+	disablepcibridge(b4);
+}
+
+static inline unsigned char cpld_getreg(struct b4xxp *b4, unsigned char reg)
+{
+	unsigned char data;
+
+	cpld_select_reg(b4, reg);
+
+	enablepcibridge(b4);
+	data = readpcibridge(b4, 1);
+	disablepcibridge(b4);
+
+	return data;
+}
+
+
+/*
+ * echo canceller code, verbatim from mattf.
+ * I don't pretend to understand it.
+ */
+static inline void ec_select_addr(struct b4xxp *b4, unsigned short addr)
+{
+	cpld_setreg(b4, 0, 0xff & addr);
+	cpld_setreg(b4, 1, 0x01 & (addr >> 8));
+}
+
+static inline unsigned short ec_read_data(struct b4xxp *b4)
+{
+	unsigned short addr;
+	unsigned short highbit;
+	
+	addr = cpld_getreg(b4, 0);
+	highbit = cpld_getreg(b4, 1);
+
+	addr = addr | (highbit << 8);
+
+	return addr & 0x1ff;
+}
+
+static inline unsigned char ec_read(struct b4xxp *b4, int which, unsigned short addr)
+{
+	unsigned char data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&b4->seqlock, flags);
+	ec_select_addr(b4, addr);
+
+	if (!which)
+		cpld_select_reg(b4, 2);
+	else
+		cpld_select_reg(b4, 3);
+
+	enablepcibridge(b4);
+	data = readpcibridge(b4, 1);
+	disablepcibridge(b4);
+
+	cpld_select_reg(b4, 0);
+	spin_unlock_irqrestore(&b4->seqlock, flags);
+
+	return data;
+}
+
+static inline void ec_write(struct b4xxp *b4, int which, unsigned short addr, unsigned char data)
+{
+	unsigned char in;
+	unsigned long flags;
+
+	spin_lock_irqsave(&b4->seqlock, flags);
+
+	ec_select_addr(b4, addr);
+
+	enablepcibridge(b4);
+
+	if (!which)
+		cpld_select_reg(b4, 2);
+	else
+		cpld_select_reg(b4, 3);
+
+	writepcibridge(b4, 1, data);
+	cpld_select_reg(b4, 0);
+	disablepcibridge(b4);
+
+	spin_unlock_irqrestore(&b4->seqlock, flags);
+
+	in = ec_read(b4, which, addr);
+
+	if(in != data)
+		dev_warn(b4->dev, "ec_write: Wrote 0x%02x to register 0x%02x of VPM %d but got back 0x%02x\n", data, addr, which, in);
+}
+
+#define NUM_EC 2
+#define MAX_TDM_CHAN 32
+
+static void ec_init(struct b4xxp *b4)
+{
+	unsigned char b;
+	unsigned int i, j, mask;
+
+/* Setup GPIO */
+	for(i=0; i < NUM_EC; i++) {
+		b = ec_read(b4, i, 0x1a0);
+
+		dev_info(b4->dev, "VPM %d/%d init: chip ver %02x\n", i, NUM_EC - 1, b);
+
+		for(j=0; j < 4; j++) {
+			ec_write(b4, i, 0x1a8 + j, 0x00);	/* GPIO out */
+			ec_write(b4, i, 0x1ac + j, 0x00);	/* GPIO dir */
+			ec_write(b4, i, 0x1b0 + j, 0x00);	/* GPIO sel */
+		}
+
+/* Setup TDM path - sets fsync and tdm_clk as inputs */
+		b = ec_read(b4, i, 0x1a3);			/* misc_con */
+		ec_write(b4, i, 0x1a3, b & ~0x02);
+
+/* Setup Echo length (256 taps) */
+		ec_write(b4, i, 0x022, 1);
+		ec_write(b4, i, 0x023, 0xff);
+
+/* Setup timeslots */
+		ec_write(b4, i, 0x02f, 0x00);
+		mask = 0x02020202 << (i * 4);
+
+/* Setup the tdm channel masks for all chips*/
+		for(j=0; j < 4; j++)
+			ec_write(b4, i, 0x33 - j, (mask >> (j << 3)) & 0xff);
+
+/* Setup convergence rate */
+		if(DBG)
+			dev_info(b4->dev, "setting A-law mode\n");
+
+		b = 0x11;
+		ec_write(b4, i, 0x20, b);
+		if(DBG)
+			dev_info(b4->dev, "reg 0x20 is 0x%02x\n", b);
+
+//		ec_write(b4, i, 0x20, 0x38);
+
+		ec_write(b4, i, 0x24, 0x02);
+		b = ec_read(b4, i, 0x24);
+		if(DBG)
+			dev_info(b4->dev, "NLP threshold is set to %d (0x%02x)\n", b, b);
+
+/* Initialize echo cans */
+		for(j=0; j < MAX_TDM_CHAN; j++) {
+			if(mask & (0x00000001 << j))
+				ec_write(b4, i, j, 0x00);
+		}
+
+		mdelay(10);
+
+/* Put in bypass mode */
+		for(j=0; j < MAX_TDM_CHAN; j++) {
+			if(mask & (0x00000001 << j)) {
+				ec_write(b4, i, j, 0x01);
+			}
+		}
+
+/* Enable bypass */
+		for(j=0; j < MAX_TDM_CHAN; j++) {
+			if(mask & (0x00000001 << j))
+				ec_write(b4, i, 0x78 + j, 0x01);
+		}
+	}
+}
+
+/* performs a register write and then waits for the HFC "busy" bit to clear */
+static inline void hfc_setreg_waitbusy(struct b4xxp *b4, const unsigned int reg, const unsigned int val)
+{
+	unsigned long maxwait;
+
+	maxwait = 1048576;
+	while(unlikely((b4xxp_getreg8(b4, R_STATUS) & V_BUSY))) {
+		maxwait--; /* FIXME: do what? it isn't busy for long */
+	};
+
+	mb();
+	b4xxp_setreg8(b4, reg, val);
+	mb();
+
+	maxwait = 1048576;
+	while(likely((b4xxp_getreg8(b4, R_STATUS) & V_BUSY))) {
+		maxwait--; /* FIXME: do what? it isn't busy for long */
+	};
+
+	if(!maxwait) {
+		if(printk_ratelimit())
+			dev_warn(b4->dev, "hfc_setreg_waitbusy(write 0x%02x to 0x%02x) timed out waiting for busy flag to clear!\n", val, reg);
+	}
+}
+
+/*
+ * reads an 8-bit register over over and over until the same value is read twice, then returns that value.
+ */
+static inline unsigned char hfc_readcounter8(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned char r1, r2;
+	unsigned long maxwait = 1048576;
+
+	do {
+		r1 = b4xxp_getreg8(b4, reg);
+		r2 = b4xxp_getreg8(b4, reg);
+	} while((r1 != r2) && maxwait--);
+
+	if(!maxwait) {
+		if(printk_ratelimit())
+			dev_warn(b4->dev, "hfc_readcounter8(reg 0x%02x) timed out waiting for data to settle!\n", reg);
+	}
+
+	return r1;
+}
+
+/*
+ * reads a 16-bit register over over and over until the same value is read twice, then returns that value.
+ */
+static inline unsigned short hfc_readcounter16(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned short r1, r2;
+	unsigned long maxwait = 1048576;
+
+	do {
+		r1 = b4xxp_getreg16(b4, reg);
+		r2 = b4xxp_getreg16(b4, reg);
+	} while((r1 != r2) && maxwait--);
+
+	if(!maxwait) {
+		if(printk_ratelimit())
+			dev_warn(b4->dev, "hfc_readcounter16(reg 0x%02x) timed out waiting for data to settle!\n", reg);
+	}
+
+	return r1;
+}
+
+static inline unsigned int hfc_readcounter32(struct b4xxp *b4, const unsigned int reg)
+{
+	unsigned int r1, r2;
+	unsigned long maxwait = 1048576;
+
+	do {
+		r1 = b4xxp_getreg32(b4, reg);
+		r2 = b4xxp_getreg32(b4, reg);
+	} while((r1 != r2) && maxwait--);
+
+	if(!maxwait) {
+		if(printk_ratelimit())
+			dev_warn(b4->dev, "hfc_readcounter32(reg 0x%02x) timed out waiting for data to settle!\n", reg);
+	}
+
+	return r1;
+}
+
+/* performs a soft-reset of the HFC-4S.  This is as clean-slate as you can get to a hardware reset. */
+static void hfc_reset(struct b4xxp *b4)
+{
+	int b, c;
+
+/* all 32 FIFOs the same size (384 bytes), channel select data flow mode, sized for internal RAM */
+	b4xxp_setreg8(b4, R_FIFO_MD, V_FIFO_MD_00 | V_DF_MD_CSM | V_FIFO_SZ_00);
+	flush_pci();
+
+/* reset everything, wait 500us, then bring everything BUT the PCM system out of reset */
+	b4xxp_setreg8(b4, R_CIRM, HFC_FULL_RESET);
+	flush_pci();
+
+	udelay(500);
+
+	b4xxp_setreg8(b4, R_CIRM, V_PCM_RES);
+	flush_pci();
+
+	udelay(500);
+
+/*
+ * Now bring PCM out of reset and do a very basic setup of the PCM system to allow it to finish resetting correctly.
+ * set F0IO as an output, and set up a 32-timeslot PCM bus
+ * See Section 8.3 in the HFC-4S datasheet for more details.
+ */
+	b4xxp_setreg8(b4, R_CIRM, 0x00);
+	b4xxp_setreg8(b4, R_PCM_MD0, V_PCM_MD | V_PCM_IDX_MD1);
+	flush_pci();
+
+	b4xxp_setreg8(b4, R_PCM_MD1, V_PLL_ADJ_00 | V_PCM_DR_2048);
+	flush_pci();
+
+/* now wait for R_F0_CNTL to reach at least 2 before continuing */
+	c=10;
+	while((b = b4xxp_getreg8(b4, R_F0_CNTL)) < 2 && c) { udelay(100); c--; }
+
+	if(!c && b < 2) {
+		dev_warn(b4->dev, "hfc_reset() did not get the green light from the PCM system!\n");
+	}
+}
+
+static inline void hfc_enable_fifo_irqs(struct b4xxp *b4)
+{
+	b4xxp_setreg8(b4, R_IRQ_CTRL, V_FIFO_IRQ | V_GLOB_IRQ_EN);
+	flush_pci();
+}
+
+static inline void hfc_disable_fifo_irqs(struct b4xxp *b4)
+{
+	b4xxp_setreg8(b4, R_IRQ_CTRL, V_GLOB_IRQ_EN);
+	flush_pci();
+}
+
+static void hfc_enable_interrupts(struct b4xxp *b4)
+{
+	b4->running = 1;
+
+/* clear any pending interrupts */
+	b4xxp_getreg8(b4, R_STATUS);
+	b4xxp_getreg8(b4, R_IRQ_MISC);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL0);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL1);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL2);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL3);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL4);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL5);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL6);
+	b4xxp_getreg8(b4, R_IRQ_FIFO_BL7);
+
+	b4xxp_setreg8(b4, R_IRQMSK_MISC, V_TI_IRQ);
+	hfc_enable_fifo_irqs(b4);
+}
+
+static void hfc_disable_interrupts(struct b4xxp *b4)
+{
+	b4xxp_setreg8(b4, R_IRQMSK_MISC, 0);
+	b4xxp_setreg8(b4, R_IRQ_CTRL, 0);
+	flush_pci();
+	b4->running = 0;
+}
+
+/*
+ * Connects an S/T port's B channel to a host-facing FIFO through the PCM busses.
+ * This bchan flow plan should match up with the EC requirements.
+ * TODO: Interrupts are only enabled on the host FIFO RX side, since everything is (should be) synchronous.
+ * *** performs no error checking of parameters ***
+ */
+static void hfc_assign_bchan_fifo_ec(struct b4xxp *b4, int port, int bchan)
+{
+	int fifo, hfc_chan, ts;
+	unsigned long irq_flags;
+	static int first=1;
+
+	if(first) {
+		first = 0;
+		dev_info(b4->dev, "Hardware echo cancellation enabled.\n");
+	}
+
+	fifo = port * 2;
+	hfc_chan = port * 4;
+	ts = port * 8;
+
+	if(bchan) {
+		fifo += 1;
+		hfc_chan += 1;
+		ts += 4;
+	}
+
+/* record the host's FIFO # in the span fifo array */
+	b4->spans[port].fifos[bchan] = fifo;
+	spin_lock_irqsave(&b4->fifolock, irq_flags);
+
+	if(DBG)
+		dev_info(b4->dev, "port %d, B channel %d\n\tS/T -> PCM ts %d uses HFC chan %d via FIFO %d\n", port, bchan, ts + 1, hfc_chan, 16 + fifo);
+
+/* S/T RX -> PCM TX FIFO, transparent mode, no IRQ. */
+	hfc_setreg_waitbusy(b4, R_FIFO, ((16 + fifo) << V_FIFO_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_110);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT));
+	b4xxp_setreg8(b4, R_SLOT, ((ts + 1) << V_SL_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_SL_CFG, V_ROUT_TX_STIO1 | (hfc_chan << V_CH_SNUM_SHIFT));
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tPCM ts %d -> host uses HFC chan %d via FIFO %d\n", ts + 1, 16 + hfc_chan, fifo);
+
+/* PCM RX -> Host TX FIFO, transparent mode, enable IRQ. */
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_001); 
+	b4xxp_setreg8(b4, A_CHANNEL, ((16 + hfc_chan) << V_CH_FNUM_SHIFT) | V_CH_FDIR);
+	b4xxp_setreg8(b4, R_SLOT, ((ts + 1) << V_SL_NUM_SHIFT) | 1);
+	b4xxp_setreg8(b4, A_SL_CFG, V_ROUT_RX_STIO1 | ((16 + hfc_chan) << V_CH_SNUM_SHIFT) | 1);
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+//	b4xxp_setreg8(b4, A_IRQ_MSK, V_IRQ);
+
+	if(DBG)
+		pr_info("\thost -> PCM ts %d uses HFC chan %d via FIFO %d\n", ts, 16 + hfc_chan, fifo);
+
+/* Host FIFO -> PCM TX */
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_001);
+	b4xxp_setreg8(b4, A_CHANNEL, ((16 + hfc_chan) << V_CH_FNUM_SHIFT));
+	b4xxp_setreg8(b4, R_SLOT, (ts << V_SL_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_SL_CFG, V_ROUT_RX_STIO2 | ((16 + hfc_chan) << V_CH_SNUM_SHIFT));
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tPCM ts %d -> S/T uses HFC chan %d via FIFO %d\n", ts, hfc_chan, 16 + fifo);
+
+/* PCM -> S/T */
+	hfc_setreg_waitbusy(b4, R_FIFO, ((16 + fifo) << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_110);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT) | V_CH_FDIR);
+	b4xxp_setreg8(b4, R_SLOT, (ts  << V_SL_NUM_SHIFT) | V_SL_DIR);
+	b4xxp_setreg8(b4, A_SL_CFG, V_ROUT_TX_STIO2 | (hfc_chan << V_CH_SNUM_SHIFT) | V_CH_SDIR);
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tPCM ts %d -> S/T uses HFC chan %d via FIFO %d\n", ts, hfc_chan, 16 + fifo);
+
+	flush_pci();			/* ensure all those writes actually hit hardware */
+	spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+}
+
+static void hfc_assign_bchan_fifo_noec(struct b4xxp *b4, int port, int bchan)
+{
+	int fifo, hfc_chan, ts;
+	unsigned long irq_flags;
+	static int first=1;
+
+	if(first) {
+		first = 0;
+		dev_info(b4->dev, "NOTE: hardware echo cancellation has been disabled\n");
+	}
+
+	fifo = port * 2;
+	hfc_chan = port * 4;
+	ts = port * 8;
+
+	if(bchan) {
+		fifo += 1;
+		hfc_chan += 1;
+		ts += 4;
+	}
+
+/* record the host's FIFO # in the span fifo array */
+	b4->spans[port].fifos[bchan] = fifo;
+	spin_lock_irqsave(&b4->fifolock, irq_flags);
+
+	if(DBG)
+		dev_info(b4->dev, "port %d, B channel %d\n\thost -> S/T uses HFC chan %d via FIFO %d\n", port, bchan, hfc_chan, fifo);
+
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_000);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT));
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tS/T -> host uses HFC chan %d via FIFO %d\n", hfc_chan, fifo);
+
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_HDLC_TRP | V_DATA_FLOW_000);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT) | V_CH_FDIR);
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tPCM ts %d -> S/T uses HFC chan %d via FIFO %d\n", ts, hfc_chan, 16 + fifo);
+
+	flush_pci();			/* ensure all those writes actually hit hardware */
+	spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+}
+
+/*
+ * Connects an S/T port's D channel to a host-facing FIFO.
+ * Both TX and RX interrupts are enabled!
+ * *** performs no error checking of parameters ***
+ */
+static void hfc_assign_dchan_fifo(struct b4xxp *b4, int port)
+{
+	int fifo, hfc_chan;
+	unsigned long irq_flags;
+
+	fifo = port + 8;
+	hfc_chan = (port * 4) + 2;
+
+/* record the host's FIFO # in the span fifo array */
+	b4->spans[port].fifos[2] = fifo;
+
+	if(DBG)
+		dev_info(b4->dev, "port %d, D channel\n\thost -> S/T uses HFC chan %d via FIFO %d\n", port, hfc_chan, fifo);
+
+	spin_lock_irqsave(&b4->fifolock, irq_flags);
+
+/* Host FIFO -> S/T TX, HDLC mode, no IRQ. */
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_TRP_IRQ | V_DATA_FLOW_000);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT));
+	b4xxp_setreg8(b4, A_SUBCH_CFG, 0x02);
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\tS/T -> host uses HFC chan %d via FIFO %d\n", hfc_chan, fifo);
+
+/* S/T RX -> Host FIFO, HDLC mode, IRQ will be enabled when port opened. */
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+	b4xxp_setreg8(b4, A_CON_HDLC, V_IFF | V_TRP_IRQ | V_DATA_FLOW_000);
+	b4xxp_setreg8(b4, A_CHANNEL, (hfc_chan << V_CH_FNUM_SHIFT) | V_CH_FDIR);
+	b4xxp_setreg8(b4, A_SUBCH_CFG, 0x02);
+	hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	if(DBG)
+		pr_info("\n");
+
+	flush_pci();			/* ensure all those writes actually hit hardware */
+	spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+}
+
+/* takes a read/write fifo pair and optionally resets it, optionally enabling the rx/tx interrupt */
+static void hfc_reset_fifo_pair(struct b4xxp *b4, int fifo, int reset, int force_no_irq)
+{
+	unsigned long irq_flags;
+
+	spin_lock_irqsave(&b4->fifolock, irq_flags);
+
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+	b4xxp_setreg8(b4, A_IRQ_MSK, (!force_no_irq && b4->fifo_en_txint & (1 << fifo)) ? V_IRQ : 0);
+
+	if(reset)
+		hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+	b4xxp_setreg8(b4, A_IRQ_MSK, (!force_no_irq && b4->fifo_en_rxint & (1 << fifo)) ? V_IRQ : 0);
+
+	if(reset)
+		hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_RES_FIFO);
+
+	spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+}
+
+
+static void b4xxp_set_sync_src(struct b4xxp *b4, int port)
+{
+	int b;
+
+	if(port == -1) 		/* automatic */
+		b = 0;
+	else
+		b = (port & V_SYNC_SEL_MASK) | V_MAN_SYNC;
+
+	b4xxp_setreg8(b4, R_ST_SYNC, b);
+}
+
+/*
+ * Finds the highest-priority sync span that is not in alarm and returns it.
+ * Note: the span #s in b4->spans[].sync are 1-based, and this returns
+ * a 0-based span, or -1 if no spans are found.
+ */
+static int b4xxp_find_sync(struct b4xxp *b4)
+{
+	int i, psrc, src;
+
+	src = -1;		/* default to automatic */
+
+	for(i=0; i < b4->numspans; i++) {
+		psrc = b4->spans[i].sync;
+		if(psrc > 0 && !b4->spans[psrc - 1].span.alarms) {
+			src = psrc;
+			break;
+		}
+	}
+
+	return src - 1;
+}
+
+/*
+ * allocates memory and pretty-prints a given S/T state engine state to it.
+ * calling routine is responsible for freeing the pointer returned!
+ * Performs no hardware access whatsoever, but does use GFP_KERNEL so do not call from IRQ context.
+ * if full == 1, prints a "full" dump; otherwise just prints current state.
+ */
+static char *hfc_decode_st_state(struct b4xxp *b4, int port, unsigned char state, int full)
+{
+	int nt, sta;
+	char s[128], *str;
+	const char *ststr[2][16] = {	/* TE, NT */
+		{ "RESET", "?", "SENSING", "DEACT.", "AWAIT.SIG", "IDENT.INPUT", "SYNCD", "ACTIVATED",
+			"LOSTFRAMING", "?", "?", "?", "?", "?", "?", "?" },
+		{ "RESET", "DEACT.", "PEND.ACT", "ACTIVE", "PEND.DEACT", "?", "?", "?",
+			"?", "?", "?", "?", "?", "?", "?", "?" }
+	};
+
+	if(!(str = kmalloc(256, GFP_KERNEL))) {
+		dev_warn(b4->dev, "could not allocate mem for ST state decode string!\n");
+		return NULL;
+	}
+
+	nt = (b4->spans[port].te_mode == 0);
+	sta = (state & V_ST_STA_MASK);
+
+	sprintf(str, "P%d: %s state %c%d (%s)", port + 1, (nt ? "NT" : "TE"), (nt ? 'G' : 'F'), sta, ststr[nt][sta]);
+
+	if(full) {
+		sprintf(s, " SYNC: %s, RX INFO0: %s", ((state & V_FR_SYNC) ? "yes" : "no"), ((state & V_INFO0) ? "yes" : "no"));
+		strcat(str, s);
+
+		if(nt) {
+			sprintf(s, ", T2 %s, auto G2->G3: %s", ((state & V_T2_EXP) ? "expired" : "OK"),
+				((state & V_G2_G3) ? "yes" : "no"));
+			strcat(str, s);
+		}
+	}
+
+	return str;
+}
+
+/*
+ * sets an S/T port state machine to a given state.
+ * if 'auto' is nonzero, will put the state machine back in auto mode after setting the state.
+ */
+static void hfc_handle_state(struct b4xxp_span *s);
+static void hfc_force_st_state(struct b4xxp *b4, int port, int state, int resume_auto)
+{
+	b4xxp_setreg_ra(b4, R_ST_SEL, port, A_ST_RD_STA, state | V_ST_LD_STA);
+
+	udelay(6);
+
+	if(resume_auto) {
+		b4xxp_setreg_ra(b4, R_ST_SEL, port, A_ST_RD_STA, state);
+	}
+
+	if(DBG_ST) {
+		char *x;
+
+		x = hfc_decode_st_state(b4, port, state, 1);
+		dev_info(b4->dev, "forced port %d to state %d (auto: %d), new decode: %s\n", port + 1, state, resume_auto, x);
+		kfree(x);
+	}
+
+/* make sure that we activate any timers/etc needed by this state change */
+	hfc_handle_state(&b4->spans[port]);
+}
+
+/* figures out what to do when an S/T port's timer expires. */
+static void hfc_timer_expire(struct b4xxp_span *s, int t_no)
+{
+	struct b4xxp *b4 = s->parent;
+
+	if(DBG_ST)
+		dev_info(b4->dev, "%lu: hfc_timer_expire, Port %d T%d expired (value=%lu ena=%d)\n", b4->ticks, s->port + 1, t_no + 1, s->hfc_timers[t_no], s->hfc_timer_on[t_no]);
+/*
+ * there are three timers associated with every HFC S/T port.
+ * T1 is used by the NT state machine, and is the maximum time the NT side should wait for G3 (active) state.
+ * T2 is not actually used in the driver, it is handled by the HFC-4S internally.
+ * T3 is used by the TE state machine; it is the maximum time the TE side should wait for the INFO4 (activated) signal.
+ */
+	switch(t_no) {
+	case HFC_T1:					/* switch to G4 (pending deact.), resume auto mode */
+		hfc_force_st_state(b4, s->port, 4, 1);
+		break;
+	case HFC_T2:					/* switch to G1 (deactivated), resume auto mode */
+		hfc_force_st_state(b4, s->port, 1, 1);
+		break;
+	case HFC_T3:					/* switch to F3 (deactivated), resume auto mode */
+		hfc_force_st_state(b4, s->port, 3, 1);
+		break;
+	default:
+		if(printk_ratelimit())
+			dev_warn(b4->dev, "hfc_timer_expire found an unknown expired timer (%d)??\n", t_no);
+	}
+
+/* disable the expired timer */
+	s->hfc_timer_on[t_no] = 0;
+}
+
+/*
+ * Run through the active timers on a card and deal with any expiries.
+ * Also see if the alarm debounce time has expired and if it has, tell DAHDI.
+ */
+static void hfc_update_st_timers(struct b4xxp *b4)
+{
+	int i, j;
+	struct b4xxp_span *s;
+
+	for(i=0; i < 4; i++) {
+		s = &b4->spans[i];
+
+		for(j=HFC_T1; j <= HFC_T3; j++) {
+
+/* we don't really do timer2, it is expired by the state change handler */
+			if(j == HFC_T2)
+				continue;
+
+			if(s->hfc_timer_on[j] && time_after_eq(b4->ticks, s->hfc_timers[j])) {
+				hfc_timer_expire(s, j);
+			}
+		}
+
+		if(s->newalarm != s->span.alarms && time_after_eq(b4->ticks, s->alarmtimer)) {
+			if(!s->te_mode || !teignorered) {
+				s->span.alarms = s->newalarm;
+				if(DBG_ALARM)
+					dev_info(b4->dev, "span %d: alarm %d debounced\n", i + 1, s->newalarm);
+				if(!s->te_mode)
+					b4xxp_set_sync_src(b4, b4xxp_find_sync(b4));
+			}
+		}
+	}
+}
+
+/* this is the driver-level state machine for an S/T port */
+static void hfc_handle_state(struct b4xxp_span *s)
+{
+	struct b4xxp *b4;
+	unsigned char state, sta;
+	int nt, newsync, oldalarm;
+	unsigned long oldtimer;
+
+	b4 = s->parent;
+	nt = !s->te_mode;
+
+	state = b4xxp_getreg_ra(b4, R_ST_SEL, s->port, A_ST_RD_STA);
+	sta = (state & V_ST_STA_MASK);
+
+	if(DBG_ST) {
+		char *x;
+
+		x = hfc_decode_st_state(b4, s->port, state, 1);
+		dev_info(b4->dev, "port %d A_ST_RD_STA old=0x%02x now=0x%02x, decoded: %s\n", s->port + 1, s->oldstate, state, x);
+		kfree(x);
+	}
+
+	oldalarm = s->newalarm;
+	oldtimer = s->alarmtimer;
+
+	if(nt) {
+		switch(sta) {
+		default:			/* Invalid NT state */
+		case 0x0:			/* NT state G0: Reset */
+		case 0x1:			/* NT state G1: Deactivated */
+		case 0x4:			/* NT state G4: Pending Deactivation */
+			s->newalarm = DAHDI_ALARM_RED;
+			break;
+		case 0x2:			/* NT state G2: Pending Activation */
+			s->newalarm = DAHDI_ALARM_YELLOW;
+			break;
+		case 0x3:			/* NT state G3: Active */
+			s->hfc_timer_on[HFC_T1] = 0;

[... 1961 lines stripped ...]



More information about the svn-commits mailing list