summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun Ruffell <sruffell@digium.com>2008-10-28 18:12:48 +0000
committerShaun Ruffell <sruffell@digium.com>2008-10-28 18:12:48 +0000
commite2da3ac7a9e70915c1daa3085da5bf7956a2468b (patch)
treebdcee4be1fec0ecc68c96b1113cee421fa49d2cc
parent3bfce0a76e1e8857212e1c4b39d0d95cd3f99e5f (diff)
Adding the wcb4xxp driver, a native dahdi driver for the B410P module.
git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@5148 a0bf4364-ded3-4de4-8d8a-66a801d63aff
-rw-r--r--README20
-rw-r--r--drivers/dahdi/Kbuild1
-rw-r--r--drivers/dahdi/wcb4xxp/Kbuild7
-rw-r--r--drivers/dahdi/wcb4xxp/base.c2652
-rw-r--r--drivers/dahdi/wcb4xxp/wcb4xxp.h523
5 files changed, 3194 insertions, 9 deletions
diff --git a/README b/README
index 38defa0..13d12cb 100644
--- a/README
+++ b/README
@@ -107,15 +107,17 @@ SUBDIRS_EXTRA:
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
diff --git a/drivers/dahdi/Kbuild b/drivers/dahdi/Kbuild
index a1a2aa9..c5742b9 100644
--- a/drivers/dahdi/Kbuild
+++ b/drivers/dahdi/Kbuild
@@ -10,6 +10,7 @@ obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTC4XXP) += wctc4xxp/
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
diff --git a/drivers/dahdi/wcb4xxp/Kbuild b/drivers/dahdi/wcb4xxp/Kbuild
new file mode 100644
index 0000000..96399cb
--- /dev/null
+++ b/drivers/dahdi/wcb4xxp/Kbuild
@@ -0,0 +1,7 @@
+obj-m += wcb4xxp.o
+
+EXTRA_CFLAGS := -I$(src)/.. -Wno-undef
+
+wcb4xxp-objs := base.o
+
+$(obj)/base.o: $(src)/wcb4xxp.h
diff --git a/drivers/dahdi/wcb4xxp/base.c b/drivers/dahdi/wcb4xxp/base.c
new file mode 100644
index 0000000..64cfe45
--- /dev/null
+++ b/drivers/dahdi/wcb4xxp/base.c
@@ -0,0 +1,2652 @@
+/*
+ * WCB410P Quad-BRI PCI Driver
+ *
+ * Written by Andrew Kohlsmith <akohlsmith@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;
+ s->newalarm = 0;
+ break;
+ }
+ } else {
+ switch(sta) {
+ default: /* Invalid TE state */
+ case 0x0: /* TE state F0: Reset */
+ case 0x2: /* TE state F2: Sensing */
+ case 0x3: /* TE state F3: Deactivated */
+ case 0x4: /* TE state F4: Awaiting Signal */
+ case 0x8: /* TE state F8: Lost Framing */
+ s->newalarm = DAHDI_ALARM_RED;
+ break;
+ case 0x5: /* TE state F5: Identifying Input */
+ case 0x6: /* TE state F6: Synchronized */
+ s->newalarm = DAHDI_ALARM_YELLOW;
+ break;
+ case 0x7: /* TE state F7: Activated */
+ s->hfc_timer_on[HFC_T3] = 0;
+ s->newalarm = 0;
+ break;
+ }
+ }
+
+ s->alarmtimer = b4->ticks + alarmdebounce;
+ s->oldstate = state;
+
+ if(DBG_ALARM) {
+ dev_info(b4->dev, "span %d: old alarm %d expires %ld, new alarm %d expires %ld\n",
+ s->port + 1, oldalarm, oldtimer, s->newalarm, s->alarmtimer);
+ }
+
+/* we only care about T2 expiry in G4. */
+ if(nt && (sta == 4) && (state & V_T2_EXP)) {
+ if(s->hfc_timer_on[HFC_T2])
+ hfc_timer_expire(s, HFC_T2); /* handle T2 expiry */
+ }
+
+/* If we're in F3 and receiving INFO0, start T3 and jump to F4 */
+ if(!nt && (sta == 3) && (state & V_INFO0)) {
+ s->hfc_timers[HFC_T3] = b4->ticks + timer_3_ms;
+ s->hfc_timer_on[HFC_T3] = 1;
+ if(DBG_ST) {
+ dev_info(b4->dev, "port %d: receiving INFO0 in state 3, setting T3 and jumping to F4\n", s->port + 1);
+ }
+ hfc_force_st_state(b4, s->port, 4, 1);
+ }
+
+/* read in R_BERT_STA to determine where our current sync source is */
+ newsync = b4xxp_getreg8(b4, R_BERT_STA) & 0x07;
+ if(newsync != b4->syncspan) {
+ if(printk_ratelimit())
+ dev_info(b4->dev, "new card sync source: port %d\n", newsync + 1);
+ b4->syncspan = newsync;
+ }
+}
+
+/*
+ * resets an S/T interface to a given NT/TE mode
+ */
+static void hfc_reset_st(struct b4xxp_span *s)
+{
+ int b;
+ struct b4xxp *b4;
+
+ b4 = s->parent;
+
+/* force state G0/F0 (reset), then force state 1/2 (deactivated/sensing) */
+ b4xxp_setreg_ra(b4, R_ST_SEL, s->port, A_ST_WR_STA, V_ST_LD_STA);
+ flush_pci(); /* make sure write hit hardware */
+
+ udelay(10);
+
+/* set up the clock control register. Must be done before we activate the interface. */
+ if(s->te_mode)
+ b = 0x0e;
+ else
+ b = 0x0c | (6 << V_ST_SMPL_SHIFT);
+
+ b4xxp_setreg8(b4, A_ST_CLK_DLY, b);
+
+/* set TE/NT mode, enable B and D channels. */
+ b4xxp_setreg8(b4, A_ST_CTRL0, V_B1_EN | V_B2_EN | (s->te_mode ? 0 : V_ST_MD));
+ b4xxp_setreg8(b4, A_ST_CTRL1, V_G2_G3_EN | V_E_IGNO);
+ b4xxp_setreg8(b4, A_ST_CTRL2, V_B1_RX_EN | V_B2_RX_EN);
+
+/* enable the state machine. */
+ b4xxp_setreg8(b4, A_ST_WR_STA, 0x00);
+ flush_pci();
+
+ udelay(100);
+}
+
+static void hfc_start_st(struct b4xxp_span *s)
+{
+ struct b4xxp *b4 = s->parent;
+
+ b4xxp_setreg_ra(b4, R_ST_SEL, s->port, A_ST_WR_STA, V_ST_ACT_ACTIVATE);
+
+/* start T1 if in NT mode, T3 if in TE mode */
+ if(s->te_mode) {
+ s->hfc_timers[HFC_T3] = b4->ticks + 500; /* 500ms wait first time, timer_t3_ms afterward. */
+ s->hfc_timer_on[HFC_T3] = 1;
+ s->hfc_timer_on[HFC_T1] = 0;
+ if(DBG_ST)
+ dev_info(b4->dev, "setting port %d t3 timer to %lu\n", s->port + 1, s->hfc_timers[HFC_T3]);
+ } else {
+ s->hfc_timers[HFC_T1] = b4->ticks + timer_1_ms;
+ s->hfc_timer_on[HFC_T1] = 1;
+ s->hfc_timer_on[HFC_T3] = 0;
+ if(DBG_ST)
+ dev_info(b4->dev, "setting port %d t1 timer to %lu\n", s->port + 1, s->hfc_timers[HFC_T1]);
+ }
+}
+
+#if 0 /* TODO: This function is not called anywhere */
+static void hfc_stop_st(struct b4xxp_span *s)
+{
+ b4xxp_setreg_ra(s->parent, R_ST_SEL, s->port, A_ST_WR_STA, V_ST_ACT_DEACTIVATE);
+
+ s->hfc_timer_on[HFC_T1] = 0;
+ s->hfc_timer_on[HFC_T2] = 0;
+ s->hfc_timer_on[HFC_T3] = 0;
+}
+#endif
+
+/*
+ * read in the HFC GPIO to determine each port's mode (TE or NT).
+ * Then, reset and start the port.
+ * the flow controller should be set up before this is called.
+ */
+static void hfc_init_all_st(struct b4xxp *b4)
+{
+ int i, gpio, nt;
+ struct b4xxp_span *s;
+
+ gpio = b4xxp_getreg8(b4, R_GPI_IN3);
+
+ for(i=0; i < 4; i++) {
+ s = &b4->spans[i];
+ s->parent = b4;
+ s->port = i;
+
+ nt = ((gpio & (1 << (i + 4))) == 0); /* GPIO=0 = NT mode */
+ s->te_mode = !nt;
+
+ dev_info(b4->dev, "Port %d: %s mode\n", i + 1, (nt ? "NT" : "TE"));
+
+ hfc_reset_st(s);
+ hfc_start_st(s);
+ }
+}
+
+/*
+ * Look at one B-channel FIFO and determine if we should exchange data with it.
+ * It is assumed that the S/T port is active.
+ * returns 1 if data was exchanged, 0 otherwise.
+ */
+static int hfc_poll_one_bchan_fifo(struct b4xxp_span *span, int c)
+{
+ int fifo, zlen, z1, z2, ret;
+ unsigned long irq_flags;
+ struct b4xxp *b4;
+ struct dahdi_chan *chan;
+
+ ret = 0;
+ b4 = span->parent;
+ fifo = span->fifos[c];
+ chan = span->chans[c];
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+
+/* select RX FIFO */
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR | V_REV);
+
+ get_Z32(z1, z2, zlen);
+
+/* TODO: error checking, full FIFO mostly */
+
+ if(zlen >= DAHDI_CHUNKSIZE) {
+ *(unsigned int *)&chan->readchunk[0] = b4xxp_getreg32(b4, A_FIFO_DATA2);
+ *(unsigned int *)&chan->readchunk[4] = b4xxp_getreg32(b4, A_FIFO_DATA2);
+/*
+ * now TX FIFO
+ *
+ * Note that we won't write to the TX FIFO if there wasn't room in the RX FIFO.
+ * The TX and RX sides should be kept pretty much lock-step.
+ *
+ * Write the last byte _NOINC so that if we don't get more data in time, we aren't leaking unknown data
+ * (See HFC datasheet)
+ */
+
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_REV);
+
+ b4xxp_setreg32(b4, A_FIFO_DATA2, *(unsigned int *) &chan->writechunk[0]);
+ b4xxp_setreg32(b4, A_FIFO_DATA2, *(unsigned int *) &chan->writechunk[4]);
+ ret = 1;
+ }
+
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+ return ret;
+}
+
+/*
+ * Run through all of the host-facing B-channel RX FIFOs, looking for at least 8 bytes available.
+ * If a B channel RX fifo has enough data, perform the data transfer in both directions.
+ * D channel is done in an interrupt handler.
+ * The S/T port state must be active or we ignore the fifo.
+ * Returns nonzero if there was at least DAHDI_CHUNKSIZE bytes in the FIFO
+ */
+static int hfc_poll_fifos(struct b4xxp *b4)
+{
+ int ret=0, span;
+ unsigned long irq_flags;
+
+ for(span=0; span < b4->numspans; span++) {
+
+/* Make sure DAHDI's got this span up */
+ if(!(b4->spans[span].span.flags & DAHDI_FLAG_RUNNING))
+ continue;
+
+/* TODO: Make sure S/T port is in active state */
+#if 0
+ if(span_not_active(s))
+ continue;
+#endif
+ ret = hfc_poll_one_bchan_fifo(&b4->spans[span], 0);
+ ret |= hfc_poll_one_bchan_fifo(&b4->spans[span], 1);
+ }
+
+/* change the active FIFO one last time to make sure the last-changed FIFO updates its pointers (as per the datasheet) */
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, R_FIFO, (31 << V_FIFO_NUM_SHIFT));
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+
+ return ret;
+}
+
+/* NOTE: assumes fifo lock is held */
+static inline void debug_fz(struct b4xxp *b4, int fifo, const char *prefix)
+{
+ int f1, f2, flen, z1, z2, zlen;
+
+ get_F(f1, f2, flen);
+ get_Z32(z1, z2, zlen);
+
+ pr_info("%s: (fifo %d): f1/f2/flen=%d/%d/%d, z1/z2/zlen=%d/%d/%d\n", prefix, fifo, f1, f2, flen, z1, z2, zlen);
+}
+
+/* enable FIFO RX int and reset the FIFO */
+static int hdlc_start(struct b4xxp *b4, int fifo)
+{
+ b4->fifo_en_txint |= (1 << fifo);
+ b4->fifo_en_rxint |= (1 << fifo);
+
+ hfc_reset_fifo_pair(b4, fifo, 1, 0);
+ return 0;
+}
+
+/* disable FIFO ints and reset the FIFO */
+static void hdlc_stop(struct b4xxp *b4, int fifo)
+{
+ b4->fifo_en_txint &= ~(1 << fifo);
+ b4->fifo_en_rxint &= ~(1 << fifo);
+
+ hfc_reset_fifo_pair(b4, fifo, 1, 0);
+}
+
+/*
+ * Inner loop for D-channel receive function.
+ * Retrieves a full HDLC frame from the hardware.
+ * If the hardware indicates that the frame is complete, checks the FCS and updates
+ * DAHDI as needed.
+ * Returns 1 if an HDLC frame was correctly received, 0 otherwise.
+ */
+static int hdlc_rx_frame(struct b4xxp_span *bspan)
+{
+ int fifo, i, j, zlen, zleft, z1, z2, ret;
+ unsigned char buf[32]; /* arbitrary */
+ unsigned long irq_flags;
+ struct b4xxp *b4 = bspan->parent;
+
+ ret = 0;
+ fifo = bspan->fifos[2];
+ ++bspan->frames_in;
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+ get_Z32(z1, z2, zlen);
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+
+ zlen++; /* include STAT byte that the HFC injects after FCS */
+ zleft = zlen;
+
+ do {
+ if(zleft > 32)
+ j = 32;
+ else
+ j = zleft;
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+ for(i=0; i < j; i++)
+ buf[i] = b4xxp_getreg8(b4, A_FIFO_DATA0);
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+
+/* don't send STAT byte to DAHDI */
+ dahdi_hdlc_putbuf(bspan->sigchan, buf, (j == 32) ? j : j - 1);
+
+ zleft -= j;
+ if(DBG_HDLC) {
+ dev_info(b4->dev, "hdlc_rx_frame(span %d): z1/z2/zlen=%d/%d/%d, zleft=%d\n",
+ bspan->port, z1, z2, zlen, zleft);
+ for(i=0; i < j; i++) printk("%02x%c", buf[i], (i < ( j - 1)) ? ' ':'\n');
+ }
+ } while(zleft > 0);
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_INC_F);
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+
+ if(zlen < 3) {
+ if(DBG_HDLC)
+ dev_notice(b4->dev, "odd, zlen less then 3?\n");
+ dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_ABORT);
+ } else {
+ unsigned char stat = buf[i - 1];
+
+/* STAT != 0 = bad frame */
+ if(stat != 0x00) {
+ if(DBG_HDLC)
+ dev_info(b4->dev, "(span %d) STAT=0x%02x indicates frame problem: ", bspan->port, stat);
+ if(stat == 0xff) {
+ if(DBG_HDLC)
+ printk("HDLC Abort\n");
+ dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_ABORT);
+ } else {
+ if(DBG_HDLC)
+ printk("Bad FCS\n");
+ dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_BADFCS);
+ }
+/* STAT == 0 = frame was OK */
+ } else {
+ if(DBG_HDLC)
+ dev_info(b4->dev, "(span %d) Frame %d is good!\n", bspan->port, bspan->frames_in);
+ dahdi_hdlc_finish(bspan->sigchan);
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+
+/*
+ * Takes one blob of data from DAHDI and shoots it out to the hardware.
+ * The blob may or may not be a complete HDLC frame.
+ * If it isn't, the D-channel FIFO interrupt handler will take care of pulling the rest.
+ * Returns nonzero if there is still data to send in the current HDLC frame.
+ */
+static int hdlc_tx_frame(struct b4xxp_span *bspan)
+{
+ struct b4xxp *b4 = bspan->parent;
+ int res, i, fifo, size=32;
+ int z1, z2, zlen;
+ unsigned char buf[32];
+ unsigned long irq_flags;
+
+/* if we're ignoring TE red alarms and we are in alarm, restart the S/T state machine */
+ if(bspan->te_mode && teignorered && bspan->newalarm == DAHDI_ALARM_RED) {
+ hfc_force_st_state(b4, bspan->port, 3, 1);
+ }
+
+ fifo = bspan->fifos[2];
+ res = dahdi_hdlc_getbuf(bspan->sigchan, buf, &size);
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+
+ get_Z32(z1, z2, zlen);
+
+/* TODO: check zlen, etc. */
+
+ if(size > 0) {
+ bspan->sigactive = 1;
+
+ for(i=0; i < size; i++)
+ b4xxp_setreg8(b4, A_FIFO_DATA0, buf[i]);
+
+/* increment F and kick-start the FIFO if we have a complete frame to send. */
+ if(res != 0) {
+ ++bspan->frames_out;
+ bspan->sigactive = 0;
+ hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_INC_F);
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+ } else {
+ b4xxp_setreg8(b4, A_IRQ_MSK, V_IRQ);
+ }
+ }
+
+/* if there are no more frames pending, disable the interrupt. */
+ if(res == -1) {
+ b4xxp_setreg8(b4, A_IRQ_MSK, 0);
+ }
+
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+
+ if(DBG_HDLC) {
+ dev_info(b4->dev, "hdlc_tx_frame(span %d): DAHDI gave %d bytes for FIFO %d (res=%d)\n",
+ bspan->port, size, fifo, res);
+ for(i=0; i < size; i++)
+ printk("%02x%c", buf[i], (i < (size - 1)) ? ' ' : '\n');
+
+ if(size && res != 0)
+ pr_info("Transmitted frame %d on span %d\n", bspan->frames_out - 1, bspan->port);
+ }
+
+ return(res == 0);
+}
+
+/*
+ * b4xxp lowlevel functions
+ * These are functions which impact more than just the HFC controller.
+ * (those are named hfc_xxx())
+ */
+
+/*
+ * Performs a total reset of the card, reinitializes GPIO.
+ * The card is initialized enough to have LEDs running, and that's about it.
+ * Anything to do with audio and enabling any kind of processing is done in stage2.
+ */
+static void b4xxp_init_stage1(struct b4xxp *b4)
+{
+ int i;
+
+ hfc_reset(b4); /* total reset of controller */
+ hfc_gpio_init(b4); /* initialize controller GPIO for CPLD access */
+ ec_init(b4); /* initialize VPM and VPM GPIO */
+
+ b4xxp_setreg8(b4, R_IRQ_CTRL, 0x00); /* make sure interrupts are disabled */
+ flush_pci(); /* make sure PCI write hits hardware */
+
+/* disable all FIFO interrupts */
+ for(i=0; i < HFC_NR_FIFOS; i++) {
+ hfc_setreg_waitbusy(b4, R_FIFO, (i << V_FIFO_NUM_SHIFT));
+ b4xxp_setreg8(b4, A_IRQ_MSK, 0x00); /* disable the interrupt */
+ hfc_setreg_waitbusy(b4, R_FIFO, (i << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
+ b4xxp_setreg8(b4, A_IRQ_MSK, 0x00); /* disable the interrupt */
+ flush_pci();
+ }
+
+/* clear any pending FIFO interrupts */
+ 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_SCI_MSK, 0x00); /* mask off all S/T interrupts */
+ b4xxp_setreg8(b4, R_IRQMSK_MISC, 0x00); /* nothing else can generate an interrupt */
+
+/*
+ * set up the clock controller
+ * we have a 24.576MHz crystal, so the PCM clock is 2x the incoming clock.
+ */
+ b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x02);
+ flush_pci();
+
+ udelay(100); /* wait a bit for clock to settle */
+}
+
+/*
+ * Stage 2 hardware init.
+ * Sets up the flow controller, PCM and FIFOs.
+ * Initializes the echo cancellers.
+ * S/T interfaces are not initialized here, that is done later, in hfc_init_all_st().
+ * Interrupts are enabled and once the s/t interfaces are configured, chip should be pretty much operational.
+ */
+static void b4xxp_init_stage2(struct b4xxp *b4)
+{
+ int span;
+
+/*
+ * set up PCM bus.
+ * HFC is PCM master.
+ * C4IO, SYNC_I and SYNC_O unused.
+ * 32 channels, frame signal positive polarity, active for 2 C4 clocks.
+ * only the first two timeslots in each quad are active
+ * STIO0 is transmit-only, STIO1 is receive-only.
+ */
+ 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);
+
+/*
+ * set up the flow controller.
+ * B channel map:
+ * FIFO 0 connects Port 1 B0 using HFC channel 16 and PCM timeslots 0/1.
+ * FIFO 1 connects Port 1 B1 using HFC channel 17 and PCM timeslots 4/5.
+ * FIFO 2 connects Port 2 B0 using HFC channel 20 and PCM timeslots 8/9.
+ * FIFO 3 connects Port 2 B1 using HFC channel 21 and PCM timeslots 12/13.
+ * FIFO 4 connects Port 3 B0 using HFC channel 24 and PCM timeslots 16/17.
+ * FIFO 5 connects Port 3 B1 using HFC channel 25 and PCM timeslots 20/21.
+ * FIFO 6 connects Port 4 B0 using HFC channel 28 and PCM timeslots 24/25.
+ * FIFO 7 connects Port 4 B1 using HFC channel 29 and PCM timeslots 28/29.
+ *
+ * All B channel FIFOs have their HDLC controller in transparent mode,
+ * and only the FIFO for B0 on each port has its interrupt operational.
+ *
+ * D channels are handled by FIFOs 8-11.
+ * FIFO 8 connects Port 1 D using HFC channel 3
+ * FIFO 9 connects Port 1 D using HFC channel 7
+ * FIFO 10 connects Port 1 D using HFC channel 11
+ * FIFO 11 connects Port 1 D using HFC channel 15
+ *
+ * D channel FIFOs are operated in HDLC mode and interrupt on end of frame.
+ */
+ for(span=0; span < b4->numspans; span++) {
+ if(vpmsupport) {
+ hfc_assign_bchan_fifo_ec(b4, span, 0);
+ hfc_assign_bchan_fifo_ec(b4, span, 1);
+ } else {
+ hfc_assign_bchan_fifo_noec(b4, span, 0);
+ hfc_assign_bchan_fifo_noec(b4, span, 1);
+ }
+ hfc_assign_dchan_fifo(b4, span);
+ }
+
+/* set up the timer interrupt for 1ms intervals */
+ b4xxp_setreg8(b4, R_TI_WD, (2 << V_EV_TS_SHIFT));
+
+/*
+ * At this point, everything's set up and ready to go.
+ * Don't actually enable the global interrupt pin.
+ * DAHDI still needs to start up the spans, and we don't know exactly when.
+ */
+}
+
+static void b4xxp_setleds(struct b4xxp *b4, unsigned char val)
+{
+ ec_write(b4, 0, 0x1a8 + 3, val);
+}
+
+static void b4xxp_set_span_led(struct b4xxp *b4, int span, unsigned char val)
+{
+ int shift, spanmask;
+
+ shift = span << 1;
+ spanmask = ~(0x03 << shift);
+
+ b4->ledreg &= spanmask;
+ b4->ledreg |= (val << shift);
+ b4xxp_setleds(b4, b4->ledreg);
+}
+
+static void b4xxp_update_leds(struct b4xxp *b4)
+{
+ int i;
+ struct b4xxp_span *bspan;
+
+ b4->blinktimer++;
+ for(i=0; i < b4->numspans; i++) {
+ bspan = &b4->spans[i];
+
+ if(bspan->span.flags & DAHDI_FLAG_RUNNING) {
+ if(bspan->span.alarms) {
+ if(b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+ b4xxp_set_span_led(b4, i, LED_RED);
+ if(b4->blinktimer == 0xf)
+ b4xxp_set_span_led(b4, i, LED_OFF);
+ } else if(bspan->span.mainttimer || bspan->span.maintstat) {
+ if(b4->blinktimer == (led_fader_table[b4->alarmpos] >> 1))
+ b4xxp_set_span_led(b4, i, LED_GREEN);
+ if(b4->blinktimer == 0xf)
+ b4xxp_set_span_led(b4, i, LED_OFF);
+ } else {
+ /* No Alarm */
+ b4xxp_set_span_led(b4, i, LED_GREEN);
+ }
+ } else
+ b4xxp_set_span_led(b4, i, LED_OFF);
+ }
+
+ if(b4->blinktimer == 0xf) {
+ b4->blinktimer = -1;
+ b4->alarmpos++;
+ if(b4->alarmpos >= (sizeof(led_fader_table) / sizeof(led_fader_table[0])))
+ b4->alarmpos = 0;
+ }
+}
+
+static int b4xxp_echocan(struct dahdi_chan *chan, int eclen)
+{
+ struct b4xxp *b4 = chan->pvt;
+ int channel;
+ int unit;
+
+ if (chan->chanpos != 3)
+ unit = chan->chanpos - 1;
+ else
+ return 0;
+
+ channel = (chan->span->offset * 8) + ((chan->chanpos - 1) * 4) + 1;
+
+ if (eclen) { /* Enable */
+ if (DBG_EC)
+ printk("Enabling echo cancellation on chan %d span %d\n", chan->chanpos, chan->span->offset);
+ ec_write(b4, unit, channel, 0x3e);
+ } else { /* Disable */
+ if (DBG_EC)
+ printk("Disabling echo cancellation on chan %d span %d\n", chan->chanpos, chan->span->offset);
+ ec_write(b4, unit, channel, 0x01);
+ }
+
+ return 0;
+
+}
+
+
+/*
+ * Filesystem and DAHDI interfaces
+ */
+static int b4xxp_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
+{
+ switch(cmd) {
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static int b4xxp_startup(struct dahdi_span *span)
+{
+ struct b4xxp_span *bspan = span->pvt;
+ struct b4xxp *b4 = bspan->parent;
+
+ if(!b4->running)
+ hfc_enable_interrupts(bspan->parent);
+
+ return 0;
+}
+
+static int b4xxp_shutdown(struct dahdi_span *span)
+{
+ struct b4xxp_span *bspan = span->pvt;
+
+ hfc_disable_interrupts(bspan->parent);
+ return 0;
+}
+
+/* resets all the FIFOs for a given span. Disables IRQs for the span FIFOs */
+static void b4xxp_reset_span(struct b4xxp_span *bspan)
+{
+ int i;
+ struct b4xxp *b4 = bspan->parent;
+
+ for(i=0; i < 3; i++) {
+ hfc_reset_fifo_pair(b4, bspan->fifos[i], (i == 2) ? 1 : 0, 1);
+ }
+
+ b4xxp_set_sync_src(b4, b4xxp_find_sync(b4));
+}
+
+/* spanconfig for us means to set up the HFC FIFO and channel mapping */
+static int b4xxp_spanconfig(struct dahdi_span *span, struct dahdi_lineconfig *lc)
+{
+ int i;
+ struct b4xxp_span *bspan = span->pvt;
+ struct b4xxp *b4 = bspan->parent;
+
+ if(DBG)
+ dev_info(b4->dev, "Configuring span %d\n", span->spanno);
+
+#if 0
+ if(lc->sync > 0 && bspan->te_mode) {
+ dev_info(b4->dev, "Span %d is not in NT mode, removing from sync source list\n", span->spanno);
+ lc->sync = 0;
+ }
+#endif
+ if(lc->sync < 0 || lc->sync > 4) {
+ dev_info(b4->dev, "Span %d has invalid sync priority (%d), removing from sync source list\n", span->spanno, lc->sync);
+ lc->sync = 0;
+ }
+
+ /* remove this span number from the current sync sources, if there */
+ for(i = 0; i < b4->numspans; i++) {
+ if(b4->spans[i].sync == span->spanno) {
+ b4->spans[i].sync = 0;
+ }
+ }
+
+ /* if a sync src, put it in proper place */
+ b4->spans[span->offset].syncpos = lc->sync;
+ if(lc->sync) {
+ b4->spans[lc->sync - 1].sync = span->spanno;
+ }
+
+ b4xxp_reset_span(bspan);
+
+/* call startup() manually here, because DAHDI won't call the startup function unless it receives an IOCTL to do so, and dahdi_cfg doesn't. */
+ b4xxp_startup(&bspan->span);
+
+ span->flags |= DAHDI_FLAG_RUNNING;
+
+ return 0;
+}
+
+/* chanconfig for us means to configure the HDLC controller, if appropriate */
+static int b4xxp_chanconfig(struct dahdi_chan *chan, int sigtype)
+{
+ int alreadyrunning;
+ struct b4xxp *b4 = chan->pvt;
+ struct b4xxp_span *bspan = &b4->spans[chan->span->offset];
+ int fifo = bspan->fifos[2];
+
+ alreadyrunning = bspan->span.flags & DAHDI_FLAG_RUNNING;
+
+ if(DBG_FOPS) {
+ dev_info(b4->dev, "%s channel %d (%s) sigtype %08x\n",
+ alreadyrunning ? "Reconfigured" : "Configured", chan->channo, chan->name, sigtype);
+ }
+
+ /* (re)configure signalling channel */
+ if((sigtype == DAHDI_SIG_HARDHDLC) || (bspan->sigchan == chan)) {
+ if(DBG_FOPS)
+ dev_info(b4->dev, "%sonfiguring hardware HDLC on %s\n",
+ ((sigtype == DAHDI_SIG_HARDHDLC) ? "C" : "Unc"), chan->name);
+
+ if(alreadyrunning && bspan->sigchan) {
+ hdlc_stop(b4, fifo);
+ bspan->sigchan = NULL;
+ }
+
+ if(sigtype == DAHDI_SIG_HARDHDLC) {
+ if(hdlc_start(b4, fifo)) {
+ dev_warn(b4->dev, "Error initializing signalling controller\n");
+ return -1;
+ }
+ }
+
+ bspan->sigchan = (sigtype == DAHDI_SIG_HARDHDLC) ? chan : NULL;
+ bspan->sigactive = 0;
+ } else {
+/* FIXME: shouldn't I be returning an error? */
+ }
+
+ return 0;
+}
+
+static int b4xxp_open(struct dahdi_chan *chan)
+{
+ struct b4xxp *b4 = chan->pvt;
+ struct b4xxp_span *bspan = &b4->spans[chan->span->offset];
+
+ if(!try_module_get(THIS_MODULE)) {
+ return -EBUSY;
+ }
+
+ if(DBG_FOPS)
+ dev_info(b4->dev, "open() on chan %s (%i/%i)\n", chan->name, chan->channo, chan->chanpos);
+
+ hfc_reset_fifo_pair(b4, bspan->fifos[chan->chanpos], 0, 0);
+ return 0;
+}
+
+
+static int b4xxp_close(struct dahdi_chan *chan)
+{
+ struct b4xxp *b4 = chan->pvt;
+ struct b4xxp_span *bspan = &b4->spans[chan->span->offset];
+
+ module_put(THIS_MODULE);
+
+ if(DBG_FOPS)
+ dev_info(b4->dev, "close() on chan %s (%i/%i)\n", chan->name, chan->channo, chan->chanpos);
+
+ hfc_reset_fifo_pair(b4, bspan->fifos[chan->chanpos], 0, 1);
+ return 0;
+}
+
+/* DAHDI calls this when it has data it wants to send to the HDLC controller */
+static void b4xxp_hdlc_hard_xmit(struct dahdi_chan *chan)
+{
+ struct b4xxp *b4 = chan->pvt;
+ int span = chan->span->offset;
+ struct b4xxp_span *bspan = &b4->spans[span];
+ int fifo, f1, f2, flen;
+ unsigned long irq_flags;
+
+ if(DBG_FOPS || DBG_HDLC)
+ dev_info(b4->dev, "hdlc_hard_xmit on chan %s (%i/%i), span=%i, sigchan=%p\n",
+ chan->name, chan->channo, chan->chanpos, span, bspan->sigchan);
+
+ if ((bspan->sigchan == chan) && !bspan->sigactive) {
+ fifo = bspan->fifos[2];
+
+ spin_lock_irqsave(&b4->fifolock, irq_flags);
+ hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
+ get_F(f1, f2, flen);
+
+/* check flen, etc. */
+
+/*
+ * WOW, more HFC suckage.
+ * If the TX FIFO is empty and interrupts are enabled, I do not get an IRQ. (??)
+ * If I increment F without writing any bytes (a zero-length HDLC frame),
+ * I will get an interrupt immediately.
+ * I can't just send it here, because I can't guarantee I'm not talking to another HFC
+ * on the other side. The HDLC controller is not supposed to give me an interrupt until
+ * the entire frame is received, but it interrupts me anyway.
+ *
+ * SO: enable interrupts and increment F with 0 data to actually GET an IRQ.
+ * Inside the interrupt bottom half, loop until all HDLC frame data is in the FIFO,
+ * then increment F again to add on CRC and send it.
+ * I can do it this way because I'm guaranteed not to process any RX interrupt
+ * until my TX is done (by virtue of how the bottom half is written).
+ * If I do it outside of interrupt context, I either have to hold the FIFO lock while
+ * calling DAHDI (much wailing and gnashing of teeth) or I have to have a long enough
+ * intermediate buffer so that I can do it all in one shot.
+ *
+ * Way to make FIFOs easy, Cologne Chip AG.
+ */
+ b4xxp_setreg8(b4, A_IRQ_MSK, V_IRQ);
+ hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_INC_F);
+ spin_unlock_irqrestore(&b4->fifolock, irq_flags);
+ }
+}
+
+/* internal functions, not specific to the hardware or DAHDI */
+
+/* initialize the span/chan structures. Doesn't touch hardware, although the callbacks might. */
+static void init_spans(struct b4xxp *b4)
+{
+ int i, j;
+ struct b4xxp_span *bspan;
+ struct dahdi_chan *chan;
+
+/* for each span on the card */
+ for(i=0; i < b4->numspans; i++) {
+ bspan = &b4->spans[i];
+ bspan->parent = b4;
+
+ bspan->span.irq = b4->pdev->irq;
+ bspan->span.pvt = bspan;
+ bspan->span.offset = i;
+ bspan->span.channels = WCB4XXP_CHANNELS_PER_SPAN;
+ bspan->span.flags = 0;
+
+ if(1 /* FIXME: some config parameter for europe/north america */) {
+ bspan->span.deflaw = DAHDI_LAW_MULAW;
+ bspan->span.linecompat = DAHDI_CONFIG_AMI | DAHDI_CONFIG_B8ZS | DAHDI_CONFIG_D4 | DAHDI_CONFIG_ESF;
+ } else {
+ bspan->span.deflaw = DAHDI_LAW_ALAW;
+ bspan->span.linecompat = DAHDI_CONFIG_HDB3 | DAHDI_CONFIG_CCS | DAHDI_CONFIG_CRC4;
+ }
+
+ sprintf(bspan->span.name, "B4/%d/%d", b4->cardno, i+1);
+ sprintf(bspan->span.desc, "B4XXP (PCI) Card %d Span %d", b4->cardno, i+1);
+ bspan->span.manufacturer = "Digium";
+ dahdi_copy_string(bspan->span.devicetype, b4->variety, sizeof(bspan->span.devicetype));
+ sprintf(bspan->span.location, "PCI Bus %02d Slot %02d",
+ b4->pdev->bus->number, PCI_SLOT(b4->pdev->devfn) + 1);
+
+ bspan->span.spanconfig = b4xxp_spanconfig;
+ bspan->span.chanconfig = b4xxp_chanconfig;
+ bspan->span.startup = b4xxp_startup;
+ bspan->span.shutdown = b4xxp_shutdown;
+ bspan->span.open = b4xxp_open;
+ bspan->span.close = b4xxp_close;
+ bspan->span.ioctl = b4xxp_ioctl;
+ bspan->span.hdlc_hard_xmit = b4xxp_hdlc_hard_xmit;
+ bspan->span.echocan = b4xxp_echocan;
+
+/* HDLC stuff */
+ bspan->sigchan = NULL;
+ bspan->sigactive = 0;
+
+ bspan->span.chans = bspan->chans;
+ init_waitqueue_head(&bspan->span.maintq);
+
+/* now initialize each channel in the span */
+ for(j=0; j < WCB4XXP_CHANNELS_PER_SPAN; j++) {
+ bspan->chans[j] = &bspan->_chans[j];
+ chan = bspan->chans[j];
+ chan->pvt = b4;
+
+ sprintf(chan->name, "B4/%d/%d/%d", b4->cardno, i + 1, j + 1);
+ /* The last channel in the span is the D-channel */
+ if(j == WCB4XXP_CHANNELS_PER_SPAN - 1) {
+ chan->sigcap = DAHDI_SIG_HARDHDLC;
+ } else {
+ chan->sigcap = DAHDI_SIG_CLEAR;
+ }
+ chan->chanpos = j + 1;
+ chan->writechunk = (void *)(bspan->writechunk + j * DAHDI_CHUNKSIZE);
+ chan->readchunk = (void *)(bspan->readchunk + j * DAHDI_CHUNKSIZE);
+ }
+ }
+}
+
+
+static void b4xxp_bottom_half(unsigned long data);
+
+/* top-half interrupt handler */
+DAHDI_IRQ_HANDLER(b4xxp_interrupt)
+{
+ struct b4xxp *b4 = dev_id;
+ unsigned char status;
+ int i;
+
+ /* Make sure it's really for us */
+ status = __pci_in8(b4, R_STATUS);
+ if(!(status & HFC_INTS))
+ return IRQ_NONE;
+
+/*
+ * since the interrupt is for us, read in the FIFO and misc IRQ status registers.
+ * Don't replace the struct copies; OR in the new bits instead.
+ * That way if we get behind, we don't lose anything.
+ * We don't actually do any processing here, we simply flag the bottom-half to do the heavy lifting.
+ */
+ if(status & V_FR_IRQSTA) {
+ b4->fifo_irqstatus[0] |= __pci_in8(b4, R_IRQ_FIFO_BL0);
+ b4->fifo_irqstatus[1] |= __pci_in8(b4, R_IRQ_FIFO_BL1);
+ b4->fifo_irqstatus[2] |= __pci_in8(b4, R_IRQ_FIFO_BL2);
+ b4->fifo_irqstatus[3] |= __pci_in8(b4, R_IRQ_FIFO_BL3);
+ b4->fifo_irqstatus[4] |= __pci_in8(b4, R_IRQ_FIFO_BL4);
+ b4->fifo_irqstatus[5] |= __pci_in8(b4, R_IRQ_FIFO_BL5);
+ b4->fifo_irqstatus[6] |= __pci_in8(b4, R_IRQ_FIFO_BL6);
+ b4->fifo_irqstatus[7] |= __pci_in8(b4, R_IRQ_FIFO_BL7);
+ }
+
+ if(status & V_MISC_IRQSTA) {
+ b4->misc_irqstatus |= __pci_in8(b4, R_IRQ_MISC);
+ }
+
+/*
+ * Well, that was the plan. It appears that I can't do this in the bottom half
+ * or I start to see data corruption (too long a time between IRQ and tasklet??)
+ * So, I do the B-channel stuff right here in interrupt context. yuck.
+ */
+ if(b4->misc_irqstatus & V_TI_IRQ) {
+ hfc_poll_fifos(b4);
+ for(i=0; i < b4->numspans; i++) {
+ if(b4->spans[i].span.flags & DAHDI_FLAG_RUNNING) {
+ dahdi_ec_span(&b4->spans[i].span);
+ dahdi_receive(&b4->spans[i].span);
+ dahdi_transmit(&b4->spans[i].span);
+ }
+ }
+ }
+
+/* kick off bottom-half handler */
+ /* tasklet_hi_schedule(&b4->b4xxp_tlet); */
+ b4xxp_bottom_half((unsigned long)b4);
+
+ return IRQ_RETVAL(1);
+}
+
+
+/*
+ * The bottom half of course does all the heavy lifting for the interrupt.
+ *
+ * The original plan was to have the B channel RX FIFO interrupts enabled, and
+ * to do the actual work here. Since that doesn't seem to work so well, we
+ * poll the B channel FIFOs right in the interrupt handler and take care of the B
+ * channel stuff there. The bottom half works for the timer interrupt and D
+ * channel stuff.
+ *
+ * The HFC-4S timer interrupt is used to for several things:
+ * - Update the S/T state machines, expire their timers, etc.
+ * - Provide DAHDI's timing source, if so configured
+ * - Update LEDs
+ */
+static void b4xxp_bottom_half(unsigned long data)
+{
+ struct b4xxp *b4 = (struct b4xxp *)data;
+ int i, j, k, gotrxfifo, fifo;
+ unsigned char b, b2;
+
+ if(b4->shutdown)
+ return;
+
+ gotrxfifo = 0;
+
+ for(i=0; i < 8; i++) {
+ b = b2 = b4->fifo_irqstatus[i];
+
+ for(j=0; j < b4->numspans; j++) {
+ fifo = i*4 + j;
+
+ if(b & V_IRQ_FIFOx_TX) {
+ if(fifo >=8 && fifo <= 11) { /* d-chan fifo */
+/*
+ * WOW I don't like this.
+ * It's bad enough that I have to send a fake frame to get an HDLC TX FIFO interrupt,
+ * but now, I have to loop until the whole frame is read, or I get RX interrupts
+ * (even though the chip says HDLC mode gives an IRQ when a *full frame* is received).
+ * Yuck. It works well, but yuck.
+ */
+ do {
+ k = hdlc_tx_frame(&b4->spans[fifo - 8]);
+ } while(k);
+ } else {
+ if(printk_ratelimit())
+ dev_warn(b4->dev, "Got FIFO TX int from non-d-chan FIFO %d??\n", fifo);
+ }
+ }
+
+ if(b & V_IRQ_FIFOx_RX) {
+ if(fifo >=8 && fifo <= 11) {
+ hdlc_rx_frame(&b4->spans[fifo - 8]);
+ } else {
+ if(printk_ratelimit())
+ dev_warn(b4->dev, "Got FIFO RX int from non-d-chan FIFO %d??\n", fifo);
+ }
+ }
+
+ b >>= 2;
+ }
+
+/* zero the bits we just processed */
+ b4->fifo_irqstatus[i] &= ~b2;
+ }
+
+/*
+ * timer interrupt
+ * every tick (1ms), check the FIFOs and run through the S/T port timers.
+ * every 100ms or so, look for S/T state machine changes.
+ */
+ if(b4->misc_irqstatus & V_TI_IRQ) {
+
+/*
+ * We should check the FIFOs here, but I'm seeing this tasklet getting scheduled FAR too late to be useful.
+ * For now, we're handling that in the IRQ handler itself. (ICK!!)
+ */
+ b4->ticks++;
+
+ hfc_update_st_timers(b4);
+
+ b4xxp_update_leds(b4);
+
+/* every 100ms or so, look at the S/T interfaces to see if they changed state */
+ if(!(b4->ticks % 100)) {
+ b = b4xxp_getreg8(b4, R_SCI);
+ if(b) {
+ for(i=0; i < b4->numspans; i++) {
+ if(b & (1 << i))
+ hfc_handle_state(&b4->spans[i]);
+ }
+ }
+ }
+
+/* We're supposed to kick DAHDI here, too, but again, seeing too much latency between the interrupt and the bottom-half. */
+
+/* clear the timer interrupt flag. */
+ b4->misc_irqstatus &= ~V_TI_IRQ;
+ }
+}
+
+
+/********************************************************************************* proc stuff *****/
+
+int b4xxp_proc_read_one(char *buf, struct b4xxp *b4)
+{
+ struct dahdi_chan *chan;
+ int len, i, j;
+ char str[80], sBuf[4096];
+
+ *sBuf=0;
+ sprintf(sBuf, "Card %d, PCI identifier %s, IRQ %d\n", b4->cardno + 1, b4->dev->bus_id, b4->irq);
+
+ strcat(sBuf,"Tx:\n");
+ for(j=0; j<8; j++) {
+ for(i=0; i<12; i++) {
+ chan = b4->spans[i/3].chans[i%3];
+ sprintf(str, "%02x ", chan->writechunk[j]);
+ strcat(sBuf, str);
+ }
+
+ strcat(sBuf, "\n");
+ }
+
+ strcat(sBuf, "\nRx:\n");
+ for(j=0; j < 8; j++) {
+ for(i=0; i < 12; i++) {
+ chan = b4->spans[i / 3].chans[i % 3];
+ sprintf(str, "%02x%c", chan->readchunk[j], (i == 11) ? '\n' : ' ');
+ strcat(sBuf, str);
+ }
+ }
+
+ strcat(sBuf, "\nPort states:\n");
+ for(i=0; i < 4; i++) {
+ int state;
+ char *x;
+ struct b4xxp_span *s = &b4->spans[i];
+
+ state = b4xxp_getreg_ra(b4, R_ST_SEL, s->port, A_ST_RD_STA);
+ x = hfc_decode_st_state(b4, s->port, state, 0);
+ sprintf(str, "%s\n", x);
+ strcat(sBuf, str);
+ kfree(x);
+ }
+
+ len = sprintf(buf, "%s\n%s\nTicks: %ld\n", sBuf, str, b4->ticks);
+ return len;
+}
+
+int b4xxp_proc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data)
+{
+ struct b4xxp **b4_cards = data;
+ char sBuf[256];
+ int i, len;
+
+ len = sprintf(buf, "WCB4XXP Card Information\n");
+ for(i=0; b4_cards[i] != NULL; i++) {
+ if(i)
+ len += sprintf(buf + len, "\n-----\n");
+ len += b4xxp_proc_read_one(buf + len, b4_cards[i]);
+ }
+
+ *sBuf = 0;
+ strcat(sBuf, "\n-----\n\nAudio: ");
+ if(loopback >= 3)
+ strcat(sBuf, "DAHDI and S/T");
+ else if(loopback == 2)
+ strcat(sBuf, "DAHDI");
+ else if(loopback == 1)
+ strcat(sBuf, "S/T");
+ else
+ strcat(sBuf, "not");
+ strcat(sBuf, " looped back");
+
+ if(milliwatt)
+ strcat(sBuf, ", outgoing S/T replaced with mu-law milliwatt tone");
+
+ len += sprintf(buf + len, "%s\n", sBuf);
+
+ if(alarmdebounce)
+ sprintf(sBuf, "Alarms: debounced (%dms)", alarmdebounce);
+ else
+ strcpy(sBuf, "Alarms: not debounced");
+
+ len += sprintf(buf + len, "%s\nT1 timer period %dms\nT3 timer period %dms\n", sBuf, timer_1_ms, timer_3_ms);
+
+ *eof = 1;
+ return len;
+}
+
+
+static int __devinit b4xx_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int x, ret;
+ struct b4xxp *b4;
+ struct devtype *dt;
+
+ dt = (struct devtype *)(ent->driver_data);
+ dev_info(&pdev->dev, "probe called for b4xx...\n");
+
+ if((ret = pci_enable_device(pdev)))
+ goto err_out_disable_pdev;
+
+ if((ret = pci_request_regions(pdev, dt->desc))) {
+ dev_err(&pdev->dev, "Unable to request regions!\n");
+ goto err_out_disable_pdev;
+ }
+
+ if(!pdev->irq) { /* we better have an IRQ */
+ dev_err(&pdev->dev, "Device has no associated IRQ?\n");
+ ret = -EIO;
+ goto err_out_release_regions;
+ }
+
+ if(!(b4 = kzalloc(sizeof(struct b4xxp), GFP_KERNEL))) {
+ dev_err(&pdev->dev, "Couldn't allocate memory for b4xxp structure!\n");
+ ret = -ENOMEM;
+ goto err_out_release_regions;
+ }
+
+/* card found, enabled and main struct allocated. Fill it out. */
+ b4->magic = WCB4XXP_MAGIC;
+ b4->variety = dt->desc;
+
+ b4->pdev = pdev;
+ b4->dev = &pdev->dev;
+ pci_set_drvdata(pdev, b4);
+
+ b4->ioaddr = pci_iomap(pdev, 0, 0);
+ b4->addr = pci_iomap(pdev, 1, 0);
+ b4->irq = pdev->irq;
+
+ spin_lock_init(&b4->reglock);
+ spin_lock_init(&b4->seqlock);
+ spin_lock_init(&b4->fifolock);
+
+ x = b4xxp_getreg8(b4, R_CHIP_ID);
+ if(x != 0xc0) { /* wrong chip? */
+ dev_err(&pdev->dev, "Unknown/unsupported controller detected (R_CHIP_ID = 0x%02x)\n", x);
+ goto err_out_free_mem;
+ }
+
+/* future proofing */
+ b4->chiprev = b4xxp_getreg8(b4, R_CHIP_RV);
+
+/* check for various board-specific flags and modify init as necessary */
+/*
+ if (dt->flags & FLAG_XXX)
+ use_flag_somehow();
+*/
+
+/* TODO: determine whether this is a 2, 4 or 8 port card */
+ b4->numspans = 4;
+ b4->syncspan = -1; /* sync span is unknown */
+ if(b4->numspans > MAX_SPANS_PER_CARD) {
+ dev_err(b4->dev, "Driver does not know how to handle a %d span card!\n", b4->numspans);
+ goto err_out_free_mem;
+ }
+
+ dev_info(b4->dev, "Identified %s (controller rev %d) at %p, IRQ %i\n",
+ b4->variety, b4->chiprev, b4->ioaddr, b4->irq);
+
+/* look for the next free card structure */
+ for(x=0; x < MAX_B4_CARDS; x++) {
+ if(!cards[x])
+ break;
+ }
+
+ if(x >= MAX_B4_CARDS) {
+ dev_err(&pdev->dev, "Attempt to register more than %i cards, aborting!\n", MAX_B4_CARDS);
+ goto err_out_free_mem;
+ }
+
+/* update the cards array, make sure the b4xxp struct knows where in the array it is */
+ b4->cardno = x;
+ cards[x] = b4;
+
+ b4xxp_init_stage1(b4);
+
+ if(request_irq(pdev->irq, b4xxp_interrupt, DAHDI_IRQ_SHARED_DISABLED, "b4xxp", b4)) {
+ dev_err(b4->dev, "Unable to request IRQ %d\n", pdev->irq);
+ ret = -EIO;
+ goto err_out_del_from_card_array;
+ }
+
+/* initialize the tasklet structure */
+/* TODO: perhaps only one tasklet for any number of cards in the system... don't need one per card I don't think. */
+ tasklet_init(&b4->b4xxp_tlet, b4xxp_bottom_half, (unsigned long)b4);
+
+/* interrupt allocated and tasklet initialized, it's now safe to finish initializing the hardware */
+ b4xxp_init_stage2(b4);
+ hfc_init_all_st(b4);
+
+/* initialize the DAHDI structures, and let DAHDI know it has some new hardware to play with */
+ init_spans(b4);
+ for(x=0; x < b4->numspans; x++) {
+ if(dahdi_register(&b4->spans[x].span, 0)) {
+ dev_err(b4->dev, "Unable to register span %s\n", b4->spans[x].span.name);
+ goto err_out_unreg_spans;
+ }
+ }
+
+ if(!(myproc = create_proc_entry(PROCFS_NAME, 0444, NULL))) {
+ remove_proc_entry(PROCFS_NAME, &proc_root);
+ dev_err(b4->dev, "Error: Could not initialize /proc/%s\n", PROCFS_NAME);
+ goto err_out_unreg_spans;
+ }
+
+ myproc->read_proc = b4xxp_proc_read;
+ myproc->data = cards;
+ myproc->owner = THIS_MODULE;
+ myproc->mode = S_IFREG | S_IRUGO;
+ myproc->uid = 0;
+ myproc->gid = 0;
+ myproc->size = 37;
+
+#if 0
+ /* Launch cards as appropriate */
+ for(;;) {
+ /* Find a card to activate */
+ f = 0;
+ for (x=0; cards[x]; x++) {
+ if(cards[x]->order <= highestorder) {
+ b4_launch(cards[x]);
+ if(cards[x]->order == highestorder)
+ f = 1;
+ }
+ }
+ /* If we found at least one, increment the highest order and search again, otherwise stop */
+ if (f)
+ highestorder++;
+ else
+ break;
+ }
+#else
+ dev_info(b4->dev, "Did not do the highestorder stuff\n");
+#endif
+
+ ret = 0;
+ return ret;
+
+/* 'x' will have the failing span #. (0-3). We need to unregister everything before it. */
+err_out_unreg_spans:
+ while(x) {
+ dahdi_unregister(&b4->spans[x].span);
+ x--;
+ };
+
+ b4xxp_init_stage1(b4); /* full reset, re-init to "no-irq" state */
+ free_irq(pdev->irq, b4);
+
+err_out_del_from_card_array:
+ for(x=0; x < MAX_B4_CARDS; x++) {
+ if(cards[x] == b4) {
+ b4->cardno = -1;
+ cards[x] = NULL;
+ break;
+ }
+ }
+
+ if(x >= MAX_B4_CARDS)
+ dev_err(&pdev->dev, "b4 struct @ %p should be in cards array but isn't?!\n", b4);
+
+err_out_free_mem:
+ pci_set_drvdata(pdev, NULL);
+ pci_iounmap(pdev, b4->ioaddr);
+ pci_iounmap(pdev, b4->addr);
+ kfree(b4);
+
+err_out_release_regions:
+ pci_release_regions(pdev);
+
+err_out_disable_pdev:
+ pci_disable_device(pdev);
+
+ return ret;
+}
+
+static void __devexit b4xxp_remove(struct pci_dev *pdev)
+{
+ struct b4xxp *b4 = pci_get_drvdata(pdev);
+ int i;
+
+ if(b4) {
+ b4->shutdown = 1;
+
+ for(i=b4->numspans - 1; i >= 0; i--) {
+ dahdi_unregister(&b4->spans[i].span);
+ }
+
+ remove_proc_entry(PROCFS_NAME, &proc_root);
+ b4xxp_init_stage1(b4);
+ free_irq(pdev->irq, b4);
+ pci_set_drvdata(pdev, NULL);
+ pci_iounmap(pdev, b4->ioaddr);
+ pci_iounmap(pdev, b4->addr);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+
+ b4->ioaddr = b4->addr = NULL;
+ b4->magic = 0;
+
+ tasklet_kill(&b4->b4xxp_tlet);
+
+ kfree(b4);
+ }
+
+ dev_info(&pdev->dev, "Driver unloaded.\n");
+ return;
+}
+
+static struct pci_device_id b4xx_ids[] __devinitdata =
+{
+ { 0xd161, 0xb410, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)&wcb4xxp },
+ { 0, }
+};
+
+static struct pci_driver b4xx_driver = {
+ name: "wcb4xxp",
+ probe: b4xx_probe,
+ remove: __devexit_p(b4xxp_remove),
+ suspend: NULL,
+ resume: NULL,
+ id_table: b4xx_ids,
+};
+
+static int __init b4xx_init(void)
+{
+ if(dahdi_pci_module(&b4xx_driver))
+ return -ENODEV;
+
+ return 0;
+}
+
+static void __exit b4xx_exit(void)
+{
+ pci_unregister_driver(&b4xx_driver);
+}
+
+module_param(debug, int, S_IRUGO | S_IWUSR);
+module_param(loopback, int, S_IRUGO | S_IWUSR);
+module_param(milliwatt, int, S_IRUGO | S_IWUSR);
+module_param(pedanticpci, int, S_IRUGO);
+module_param(alarmdebounce, int, S_IRUGO | S_IWUSR);
+module_param(vpmsupport, int, S_IRUGO);
+module_param(timer_1_ms, int, S_IRUGO | S_IWUSR);
+module_param(timer_3_ms, int, S_IRUGO | S_IWUSR);
+
+MODULE_PARM_DESC(debug, "bitmap: 1=general 2=dtmf 4=regops 8=fops 16=ec 32=st state 64=hdlc 128=alarm");
+MODULE_PARM_DESC(loopback, "TODO: bitmap: 1=loop back S/T port 2=loop back DAHDI");
+MODULE_PARM_DESC(milliwatt, "1=replace outgoing S/T data with mu-law milliwatt");
+MODULE_PARM_DESC(pedanticpci, "1=disable PCI back-to-back transfers and flush all PCI writes immediately");
+MODULE_PARM_DESC(teignorered, "1=ignore (do not inform DAHDI) if a red alarm exists in TE mode");
+MODULE_PARM_DESC(alarmdebounce, "msec to wait before set/clear alarm condition");
+MODULE_PARM_DESC(vpmsupport, "1=enable hardware EC, 0=disable hardware EC");
+MODULE_PARM_DESC(timer_1_ms, "NT: msec to wait for link activation, TE: unused.");
+MODULE_PARM_DESC(timer_3_ms, "TE: msec to wait for link activation, NT: unused.");
+
+MODULE_AUTHOR("Digium Incorporated <support@digium.com>");
+MODULE_DESCRIPTION("B410P quad-port BRI module driver.");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(pci, b4xx_ids);
+
+module_init(b4xx_init);
+module_exit(b4xx_exit);
diff --git a/drivers/dahdi/wcb4xxp/wcb4xxp.h b/drivers/dahdi/wcb4xxp/wcb4xxp.h
new file mode 100644
index 0000000..c53389f
--- /dev/null
+++ b/drivers/dahdi/wcb4xxp/wcb4xxp.h
@@ -0,0 +1,523 @@
+/*
+ * Wilcard B410P Quad-BRI Interface Driver for Zapata Telephony interface
+ * Written by Andrew Kohlsmith <akohlsmith@mixdown.ca>
+ */
+
+#ifndef _B4XX_H_
+#define _B4XX_H_
+
+#include <linux/ioctl.h>
+
+#define HFC_NR_FIFOS 32
+#define HFC_ZMIN 0x80 /* from datasheet */
+#define HFC_ZMAX 0x1ff
+#define HFC_FMIN 0x00
+#define HFC_FMAX 0x0f
+
+/*
+ * yuck. Any reg which is not mandated read/write or read-only is write-only.
+ * Also, there are dozens of registers with the same address.
+ * Additionally, there are array registers (A_) which have an index register
+ * These A_ registers require an index register to be written to indicate WHICH in the array you want.
+ */
+
+#define R_CIRM 0x00 /* WO */
+#define R_CTRL 0x01 /* WO */
+#define R_BRG_PCM_CFG 0x02 /* WO */
+#define A_Z12 0x04 /* RO */
+#define A_Z1L 0x04 /* RO */
+#define A_Z1 0x04 /* RO */
+#define A_Z1H 0x05 /* RO */
+#define A_Z2L 0x06 /* RO */
+#define A_Z2 0x06 /* RO */
+#define A_Z2H 0x07 /* RO */
+#define R_RAM_ADDR0 0x08 /* WO */
+#define R_RAM_ADDR1 0x09 /* WO */
+#define R_RAM_ADDR2 0x0a /* WO */
+#define R_FIRST_FIFO 0x0b /* WO */
+#define R_RAM_MISC 0x0c /* WO */
+#define A_F1 0x0c /* RO */
+#define A_F12 0x0c /* RO */
+#define R_FIFO_MD 0x0d /* WO */
+#define A_F2 0x0d /* RO */
+#define A_INC_RES_FIFO 0x0e /* WO */
+#define R_FSM_IDX 0x0f /* WO */
+#define R_FIFO 0x0f /* WO */
+
+#define R_SLOT 0x10 /* WO */
+#define R_IRQ_OVIEW 0x10 /* RO */
+#define R_IRQMSK_MISC 0x11 /* WO */
+#define R_IRQ_MISC 0x11 /* RO */
+#define R_SCI_MSK 0x12 /* WO */
+#define R_SCI 0x12 /* RO */
+#define R_IRQ_CTRL 0x13 /* WO */
+#define R_PCM_MD0 0x14 /* WO */
+#define R_CONF_OFLOW 0x14 /* RO */
+#define R_PCM_MD1 0x15 /* WO */
+#define R_PCM_MD2 0x15 /* WO */
+#define R_SH0H 0x15 /* WO */
+#define R_SH1H 0x15 /* WO */
+#define R_SH0L 0x15 /* WO */
+#define R_SH1L 0x15 /* WO */
+#define R_SL_SEL0 0x15 /* WO */
+#define R_SL_SEL1 0x15 /* WO */
+#define R_SL_SEL2 0x15 /* WO */
+#define R_SL_SEL3 0x15 /* WO */
+#define R_SL_SEL4 0x15 /* WO */
+#define R_SL_SEL5 0x15 /* WO */
+#define R_SL_SEL6 0x15 /* WO */
+#define R_SL_SEL7 0x15 /* WO */
+#define R_RAM_USE 0x15 /* RO */
+#define R_ST_SEL 0x16 /* WO */
+#define R_CHIP_ID 0x16 /* RO */
+#define R_ST_SYNC 0x17 /* WO */
+#define R_BERT_STA 0x17 /* RO */
+#define R_CONF_EN 0x18 /* WO */
+#define R_F0_CNTL 0x18 /* RO */
+#define R_F0_CNTH 0x19 /* RO */
+#define R_TI_WD 0x1a /* WO */
+#define R_BERT_ECL 0x1a /* RO */
+#define R_BERT_WD_MD 0x1b /* WO */
+#define R_BERT_ECH 0x1b /* RO */
+#define R_DTMF 0x1c /* WO */
+#define R_STATUS 0x1c /* RO */
+#define R_DTMF_N 0x1d /* WO */
+#define R_CHIP_RV 0x1f /* RO */
+
+#define A_ST_WR_STA 0x30 /* WO */
+#define A_ST_RD_STA 0x30 /* RO */
+#define A_ST_CTRL0 0x31 /* WO */
+#define A_ST_CTRL1 0x32 /* WO */
+#define A_ST_CTRL2 0x33 /* WO */
+#define A_ST_SQ_WR 0x34 /* WO */
+#define A_ST_SQ_RD 0x34 /* RO */
+#define A_ST_CLK_DLY 0x37 /* WO */
+
+#define R_PWM0 0x38 /* WO */
+#define R_PWM1 0x39 /* WO */
+
+#define A_ST_B1_TX 0x3c /* WO */
+#define A_ST_B1_RX 0x3c /* RO */
+#define A_ST_B2_TX 0x3d /* WO */
+#define A_ST_B2_RX 0x3d /* RO */
+#define A_ST_D_TX 0x3e /* WO */
+#define A_ST_D_RX 0x3e /* RO */
+#define A_ST_E_RX 0x3f /* RO */
+
+#define R_GPIO_OUT0 0x40 /* WO */
+#define R_GPIO_IN0 0x40 /* RO */
+#define R_GPIO_OUT1 0x41 /* WO */
+#define R_GPIO_IN1 0x41 /* RO */
+#define R_GPIO_EN0 0x42 /* WO */
+#define R_GPIO_EN1 0x43 /* WO */
+#define R_GPIO_SEL 0x44 /* WO */
+#define R_GPI_IN0 0x44 /* RO */
+#define R_GPI_IN1 0x45 /* RO */
+#define R_PWM_MD 0x46 /* WO */
+#define R_GPI_IN2 0x46 /* RO */
+#define R_GPI_IN3 0x47 /* RO */
+
+#define A_FIFO_DATA2 0x80 /* RW */
+#define A_FIFO_DATA0 0x80 /* RW */
+#define A_FIFO_DATA1 0x80 /* RW */
+
+#define A_FIFO_DATA2_NOINC 0x84 /* WO */
+#define A_FIFO_DATA0_NOINC 0x84 /* WO */
+#define A_FIFO_DATA1_NOINC 0x84 /* WO */
+
+#define R_INT_DATA 0x88 /* RO */
+
+#define R_RAM_DATA 0xc0 /* RW */
+
+#define R_IRQ_FIFO_BL0 0xc8 /* RO */
+#define R_IRQ_FIFO_BL1 0xc9 /* RO */
+#define R_IRQ_FIFO_BL2 0xca /* RO */
+#define R_IRQ_FIFO_BL3 0xcb /* RO */
+#define R_IRQ_FIFO_BL4 0xcc /* RO */
+#define R_IRQ_FIFO_BL5 0xcd /* RO */
+#define R_IRQ_FIFO_BL6 0xce /* RO */
+#define R_IRQ_FIFO_BL7 0xcf /* RO */
+
+#define A_SL_CFG 0xd0 /* WO */
+#define A_CONF 0xd1 /* WO */
+#define A_CH_MSK 0xf4 /* WO */
+#define A_CON_HDLC 0xfa /* WO */
+#define A_SUBCH_CFG 0xfb /* WO */
+#define A_CHANNEL 0xfc /* WO */
+#define A_FIFO_SEQ 0xfd /* WO */
+#define A_IRQ_MSK 0xff /* WO */
+
+/* R_CIRM bits */
+#define V_SRES (1 << 3) /* soft reset (group 0) */
+#define V_HFC_RES (1 << 4) /* HFC reset (group 1) */
+#define V_PCM_RES (1 << 5) /* PCM reset (group 2) */
+#define V_ST_RES (1 << 6) /* S/T reset (group 3) */
+#define V_RLD_EPR (1 << 7) /* EEPROM reload */
+#define HFC_FULL_RESET (V_SRES | V_HFC_RES | V_PCM_RES | V_ST_RES | V_RLD_EPR)
+
+/* A_IRQ_MSK bits */
+#define V_IRQ (1 << 0) /* FIFO interrupt enable */
+#define V_BERT_EN (1 << 1) /* enable BERT */
+#define V_MIX_IRQ (1 << 2) /* mixed interrupt enable (frame + transparent mode) */
+
+/* R_STATUS bits */
+#define V_BUSY (1 << 0) /* 1=HFC busy, limited register access */
+#define V_PROC (1 << 1) /* 1=HFC in processing phase */
+#define V_LOST_STA (1 << 3) /* 1=frames have been lost */
+#define V_SYNC_IN (1 << 4) /* level on SYNC_I pin */
+#define V_EXT_IRQSTA (1 << 5) /* 1=external interrupt */
+#define V_MISC_IRQSTA (1 << 6) /* 1=misc interrupt has occurred */
+#define V_FR_IRQSTA (1 << 7) /* 1=fifo interrupt has occured */
+#define HFC_INTS (V_EXT_IRQSTA | V_MISC_IRQSTA | V_FR_IRQSTA)
+
+/* R_SCI/R_SCI_MSK bits */
+#define V_SCI_ST0 (1 << 0) /* state change for port 1 */
+#define V_SCI_ST1 (1 << 1) /* state change for port 2 */
+#define V_SCI_ST2 (1 << 2) /* state change for port 3 */
+#define V_SCI_ST3 (1 << 3) /* state change for port 4 */
+
+/* R_IRQ_FIFO_BLx bits */
+#define V_IRQ_FIFOx_TX (1 << 0) /* FIFO TX interrupt occurred */
+#define V_IRQ_FIFOx_RX (1 << 1) /* FIFO RX interrupt occurred */
+#define IRQ_FIFOx_TXRX (V_IRQ_FIFOx_TX | V_IRQ_FIFOx_RX)
+
+/* R_IRQ_MISC / R_IRQMSK_MISC bits */
+#define V_TI_IRQ (1 << 1) /* timer elapsed */
+#define V_IRQ_PROC (1 << 2) /* processing/non-processing transition */
+#define V_DTMF_IRQ (1 << 3) /* DTMF detection completed */
+#define V_EXT_IRQ (1 << 5) /* external interrupt occured */
+
+/* R_IRQ_CTRL bits */
+#define V_FIFO_IRQ (1 << 0) /* enable any unmasked FIFO IRQs */
+#define V_GLOB_IRQ_EN (1 << 3) /* enable any unmasked IRQs */
+#define V_IRQ_POL (1 << 4) /* 1=IRQ active high */
+
+/* R_BERT_WD_MD bits */
+#define V_BERT_ERR (1 << 3) /* 1=generate an error bit in BERT stream */
+#define V_AUTO_WD_RES (1 << 5) /* 1=automatically kick the watchdog */
+#define V_WD_RES (1 << 7) /* 1=kick the watchdog (bit auto clears) */
+
+/* R_TI_WS bits */
+#define V_EV_TS_SHIFT (0)
+#define V_EV_TS_MASK (0x0f)
+#define V_WD_TS_SHIFT (4)
+#define V_WD_TS_MASK (0xf0)
+
+/* R_BRG_PCM_CFG bits */
+#define V_PCM_CLK (1 << 5) /* 1=PCM clk = OSC, 0 = PCM clk is 2x OSC */
+
+/* R_PCM_MD0 bits */
+#define V_PCM_MD (1 << 0) /* 1=PCM master */
+#define V_C4_POL (1 << 1) /* 1=F0IO sampled on rising edge of C4IO */
+#define V_F0_NEG (1 << 2) /* 1=negative polarity of F0IO */
+#define V_F0_LEN (1 << 3) /* 1=F0IO active for 2 C4IO clocks */
+#define V_PCM_IDX_SEL0 (0x0 << 4) /* reg15 = R_SL_SEL0 */
+#define V_PCM_IDX_SEL1 (0x1 << 4) /* reg15 = R_SL_SEL1 */
+#define V_PCM_IDX_SEL2 (0x2 << 4) /* reg15 = R_SL_SEL2 */
+#define V_PCM_IDX_SEL3 (0x3 << 4) /* reg15 = R_SL_SEL3 */
+#define V_PCM_IDX_SEL4 (0x4 << 4) /* reg15 = R_SL_SEL4 */
+#define V_PCM_IDX_SEL5 (0x5 << 4) /* reg15 = R_SL_SEL5 */
+#define V_PCM_IDX_SEL6 (0x6 << 4) /* reg15 = R_SL_SEL6 */
+#define V_PCM_IDX_SEL7 (0x7 << 4) /* reg15 = R_SL_SEL7 */
+#define V_PCM_IDX_MD1 (0x9 << 4) /* reg15 = R_PCM_MD1 */
+#define V_PCM_IDX_MD2 (0xa << 4) /* reg15 = R_PCM_MD2 */
+#define V_PCM_IDX_SH0L (0xc << 4) /* reg15 = R_SH0L */
+#define V_PCM_IDX_SH0H (0xd << 4) /* reg15 = R_SH0H */
+#define V_PCM_IDX_SH1L (0xe << 4) /* reg15 = R_SH1L */
+#define V_PCM_IDX_SH1H (0xf << 4) /* reg15 = R_SH1H */
+#define V_PCM_IDX_MASK (0xf0)
+
+/* R_PCM_MD1 bits */
+#define V_CODEC_MD (1 << 0) /* no damn idea */
+#define V_PLL_ADJ_00 (0x0 << 2) /* adj 4 times by 0.5 system clk cycles */
+#define V_PLL_ADJ_01 (0x1 << 2) /* adj 3 times by 0.5 system clk cycles */
+#define V_PLL_ADJ_10 (0x2 << 2) /* adj 2 times by 0.5 system clk cycles */
+#define V_PLL_ADJ_11 (0x3 << 2) /* adj 1 time by 0.5 system clk cycles */
+#define V_PCM_DR_2048 (0x0 << 4) /* 2.048Mbps, 32 timeslots */
+#define V_PCM_DR_4096 (0x1 << 4) /* 4.096Mbps, 64 timeslots */
+#define V_PCM_DR_8192 (0x2 << 4) /* 8.192Mbps, 128 timeslots */
+#define V_PCM_LOOP (1 << 6) /* 1=internal loopback */
+#define V_PLL_ADJ_MASK (0x3 << 2)
+#define V_PCM_DR_MASK (0x3 << 4)
+
+/* A_SL_CFG bits */
+#define V_CH_SDIR (1 << 0) /* 1=HFC channel receives data from PCM TS */
+#define V_ROUT_TX_DIS (0x0 << 6) /* disabled, output disabled */
+#define V_ROUT_TX_LOOP (0x1 << 6) /* internally looped, output disabled */
+#define V_ROUT_TX_STIO1 (0x2 << 6) /* output data to STIO1 */
+#define V_ROUT_TX_STIO2 (0x3 << 6) /* output data to STIO2 */
+#define V_ROUT_RX_DIS (0x0 << 6) /* disabled, input data ignored */
+#define V_ROUT_RX_LOOP (0x1 << 6) /* internally looped, input data ignored */
+#define V_ROUT_RX_STIO2 (0x2 << 6) /* channel data comes from STIO1 */
+#define V_ROUT_RX_STIO1 (0x3 << 6) /* channel data comes from STIO2 */
+
+#define V_CH_SNUM_SHIFT (1)
+#define V_CH_SNUM_MASK (31 << 1)
+
+/* A_CON_HDLC bits */
+#define V_IFF (1 << 0) /* Inter-Frame Fill: 0=0x7e, 1=0xff */
+#define V_HDLC_TRP (1 << 1) /* 0=HDLC mode, 1=transparent */
+#define V_TRP_IRQ_0 (0x0 << 2) /* FIFO enabled, no interrupt */
+#define V_TRP_IRQ_64 (0x1 << 2) /* FIFO enabled, int @ 64 bytes */
+#define V_TRP_IRQ_128 (0x2 << 2) /* FIFO enabled, int @ 128 bytes */
+#define V_TRP_IRQ_256 (0x3 << 2) /* FIFO enabled, int @ 256 bytes */
+#define V_TRP_IRQ_512 (0x4 << 2) /* FIFO enabled, int @ 512 bytes */
+#define V_TRP_IRQ_1024 (0x5 << 2) /* FIFO enabled, int @ 1024 bytes */
+#define V_TRP_IRQ_2048 (0x6 << 2) /* FIFO enabled, int @ 2048 bytes */
+#define V_TRP_IRQ_4096 (0x7 << 2) /* FIFO enabled, int @ 4096 bytes */
+#define V_TRP_IRQ (0x1 << 2) /* FIFO enabled, interrupt at end of frame (HDLC mode) */
+#define V_DATA_FLOW_000 (0x0 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_001 (0x1 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_010 (0x2 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_011 (0x3 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_100 (0x4 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_101 (0x5 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_110 (0x6 << 5) /* see A_CON_HDLC reg description in datasheet */
+#define V_DATA_FLOW_111 (0x7 << 5) /* see A_CON_HDLC reg description in datasheet */
+
+/* R_FIFO bits */
+#define V_FIFO_DIR (1 << 0) /* 1=RX FIFO data */
+#define V_REV (1 << 7) /* 1=MSB first */
+#define V_FIFO_NUM_SHIFT (1)
+#define V_FIFO_NUM_MASK (0x3e)
+
+/* A_CHANNEL bits */
+#define V_CH_FDIR (1 << 0) /* 1=HFC chan for RX data */
+#define V_CH_FNUM_SHIFT (1)
+#define V_CH_FNUM_MASK (0x3e)
+
+/* R_SLOT bits */
+#define V_SL_DIR (1 << 0) /* 1=timeslot will RX PCM data from bus */
+#define V_SL_NUM_SHIFT (1)
+#define V_SL_NUM_MASK (0xfe)
+
+/* A_INC_RES_FIFO bits */
+#define V_INC_F (1 << 0) /* 1=increment FIFO F-counter (bit auto-clears) */
+#define V_RES_FIFO (1 << 1) /* 1=reset FIFO (bit auto-clears) */
+#define V_RES_LOST (1 << 2) /* 1=reset LOST error (bit auto-clears) */
+
+/* R_FIFO_MD bits */
+#define V_FIFO_MD_00 (0x0 << 0)
+#define V_FIFO_MD_01 (0x1 << 0)
+#define V_FIFO_MD_10 (0x2 << 0)
+#define V_FIFO_MD_11 (0x3 << 0)
+#define V_DF_MD_SM (0x0 << 2) /* simple data flow mode */
+#define V_DF_MD_CSM (0x1 << 2) /* channel select mode */
+#define V_DF_MD_FSM (0x3 << 2) /* FIFO sequence mode */
+#define V_FIFO_SZ_00 (0x0 << 4)
+#define V_FIFO_SZ_01 (0x1 << 4)
+#define V_FIFO_SZ_10 (0x2 << 4)
+#define V_FIFO_SZ_11 (0x3 << 4)
+
+/* A_SUBCH_CFG bits */
+#define V_BIT_CNT_8BIT (0) /* process 8 bits */
+#define V_BIT_CNT_1BIT (1) /* process 1 bit */
+#define V_BIT_CNT_2BIT (2) /* process 2 bits */
+#define V_BIT_CNT_3BIT (3) /* process 3 bits */
+#define V_BIT_CNT_4BIT (4) /* process 4 bits */
+#define V_BIT_CNT_5BIT (5) /* process 5 bits */
+#define V_BIT_CNT_6BIT (6) /* process 6 bits */
+#define V_BIT_CNT_7BIT (7) /* process 7 bits */
+#define V_LOOP_FIFO (1 << 6) /* loop FIFO data */
+#define V_INV_DATA (1 << 7) /* invert FIFO data */
+#define V_START_BIT_SHIFT (3)
+#define V_START_BIT_MASK (0x38)
+
+/* R_ST_SYNC bits */
+#define V_MAN_SYNC (1 << 3) /* 1=manual sync mode */
+#define V_SYNC_SEL_MASK (0x03)
+
+/* A_ST_WR_STA bits */
+#define V_ST_SET_STA_MASK (0x0f)
+#define V_ST_LD_STA (1 << 4) /* 1=force ST_SET_STA mode, must be manually cleared 6us later */
+#define V_ST_ACT_NOP (0x0 << 5) /* NOP */
+#define V_ST_ACT_DEACTIVATE (0x2 << 5) /* start deactivation. auto-clears */
+#define V_ST_ACT_ACTIVATE (0x3 << 5) /* start activation. auto-clears. */
+#define V_SET_G2_G3 (1 << 7) /* 1=auto G2->G3 in NT mode. auto-clears after transition. */
+
+/* A_ST_RD_STA */
+#define V_ST_STA_MASK (0x0f)
+#define V_FR_SYNC (1 << 4) /* 1=synchronized */
+#define V_T2_EXP (1 << 5) /* 1=T2 expired (NT only) */
+#define V_INFO0 (1 << 6) /* 1=INFO0 */
+#define V_G2_G3 (1 << 7) /* 1=allows G2->G3 (NT only, auto-clears) */
+
+/* A_ST_CLK_DLY bits */
+#define V_ST_SMPL_SHIFT (4)
+
+/* A_ST_CTRL0 bits */
+#define V_B1_EN (1 << 0) /* 1=B1-channel transmit */
+#define V_B2_EN (1 << 1) /* 1=B2-channel transmit */
+#define V_ST_MD (1 << 2) /* 0=TE, 1=NT */
+#define V_D_PRIO (1 << 3) /* D-Chan priority 0=high, 1=low */
+#define V_SQ_EN (1 << 4) /* S/Q bits transmit (1=enabled) */
+#define V_96KHZ (1 << 5) /* 1=transmit test signal */
+#define V_TX_LI (1 << 6) /* 0=capacitive line mode, 1=non-capacitive */
+#define V_ST_STOP (1 << 7) /* 1=power down */
+
+/* A_ST_CTRL1 bits */
+#define V_G2_G3_EN (1 << 0) /* 1=G2->G3 allowed without V_SET_G2_G3 */
+#define V_D_HI (1 << 2) /* 1=D-chan reset */
+#define V_E_IGNO (1 << 3) /* TE:1=ignore Echan, NT:should always be 1. */
+#define V_E_LO (1 << 4) /* NT only: 1=force Echan low */
+#define V_B12_SWAP (1 << 7) /* 1=swap B1/B2 */
+
+/* A_ST_CTRL2 bits */
+#define V_B1_RX_EN (1 << 0) /* 1=enable B1 RX */
+#define V_B2_RX_EN (1 << 1) /* 1=enable B2 RX */
+#define V_ST_TRI (1 << 6) /* 1=tristate S/T output buffer */
+
+#define NUM_REGS 0xff
+#define NUM_PCI 12
+
+/* From this point down, things only the kernel needs to know about */
+#ifdef __KERNEL__
+
+#define HFC_T1 0
+#define HFC_T2 1
+#define HFC_T3 2
+
+#define WCB4XXP_MAGIC 0xb410c0de
+#define MAX_SPANS_PER_CARD 4
+
+#define WCB4XXP_CHANNELS_PER_SPAN 3 /* 2 B-channels and 1 D-Channel for each BRI span */
+
+struct b4xxp_span {
+ struct b4xxp *parent;
+ int port; /* which S/T port this span belongs to */
+
+ unsigned char writechunk[WCB4XXP_CHANNELS_PER_SPAN * DAHDI_CHUNKSIZE];
+ unsigned char readchunk[WCB4XXP_CHANNELS_PER_SPAN * DAHDI_CHUNKSIZE];
+
+ int sync; /* sync priority */
+ int syncpos; /* sync priority */
+
+ int oldstate; /* old state machine state */
+ int newalarm; /* alarm to send to zaptel once alarm timer expires */
+ unsigned long alarmtimer;
+
+ int te_mode; /* 1=TE, 0=NT */
+ unsigned long hfc_timers[WCB4XXP_CHANNELS_PER_SPAN]; /* T1, T2, T3 */
+ int hfc_timer_on[WCB4XXP_CHANNELS_PER_SPAN]; /* 1=timer active */
+ int fifos[WCB4XXP_CHANNELS_PER_SPAN]; /* B1, B2, D <--> host fifo numbers */
+
+ /* HDLC controller fields */
+ struct dahdi_chan *sigchan; /* pointer to the signalling channel for this span */
+ int sigactive; /* nonzero means we're in the middle of sending an HDLC frame */
+ int frames_out;
+ int frames_in;
+
+ struct dahdi_span span; /* zaptel span info for this span */
+ struct dahdi_chan *chans[WCB4XXP_CHANNELS_PER_SPAN]; /* Individual channels */
+ struct dahdi_chan _chans[WCB4XXP_CHANNELS_PER_SPAN]; /* Backing memory */
+};
+
+/* This structure exists one per card */
+struct b4xxp {
+ unsigned magic; /* magic value to make sure we're looking at our struct */
+ char *variety;
+ int chiprev; /* revision of HFC-4S */
+
+ struct pci_dev *pdev; /* Pointer to PCI device */
+ struct device *dev; /* kernel dev struct (from pdev->dev) */
+ void __iomem *addr; /* I/O address (memory mapped) */
+ void __iomem *ioaddr; /* I/O address (index based) */
+ int irq; /* IRQ used by device */
+
+ spinlock_t reglock; /* lock for all register accesses */
+ spinlock_t seqlock; /* lock for "sequence" accesses that must be ordered */
+ spinlock_t fifolock; /* lock for all FIFO accesses (reglock must be available) */
+
+ volatile unsigned long ticks;
+
+ unsigned long fifo_en_rxint; /* each bit is the RX int enable for that FIFO */
+ unsigned long fifo_en_txint; /* each bit is the TX int enable for that FIFO */
+
+ unsigned char fifo_irqstatus[8]; /* top-half ORs in new interrupts, bottom-half ANDs them out */
+ unsigned char misc_irqstatus; /* same for this */
+ unsigned char st_irqstatus; /* same for this too */
+
+ unsigned int numspans;
+
+ int blinktimer; /* for the fancy LED alarms */
+ int alarmpos; /* ditto */
+
+ int cardno; /* Which card we are */
+ int globalconfig; /* Whether global setup has been done */
+ int syncspan; /* span that HFC uses for sync on this card */
+ int running; /* interrupts are enabled */
+
+ struct b4xxp_span spans[MAX_SPANS_PER_CARD]; /* Individual spans */
+ int order; /* Order */
+ int flags; /* Device flags */
+ int master; /* Are we master */
+ int ledreg; /* copy of the LED Register */
+ unsigned int gpio;
+ unsigned int gpioctl;
+ int spansstarted; /* number of spans started */
+
+ /* Flags for our bottom half */
+ unsigned int shutdown; /* 1=bottom half doesn't process anything, just returns */
+ struct tasklet_struct b4xxp_tlet;
+};
+
+/* CPLD access bits */
+#define B4_RDADDR 0
+#define B4_WRADDR 1
+#define B4_COUNT 2
+#define B4_DMACTRL 3
+#define B4_INTR 4
+#define B4_VERSION 6
+#define B4_LEDS 7
+#define B4_GPIOCTL 8
+#define B4_GPIO 9
+#define B4_LADDR 10
+#define B4_LDATA 11
+
+#define B4_LCS (1 << 11)
+#define B4_LCS2 (1 << 12)
+#define B4_LALE (1 << 13)
+#define B4_LFRMR_CS (1 << 10) /* Framer's ChipSelect signal */
+#define B4_ACTIVATE (1 << 12)
+#define B4_LREAD (1 << 15)
+#define B4_LWRITE (1 << 16)
+
+#define LED_OFF (0)
+#define LED_RED (1)
+#define LED_GREEN (2)
+
+#define get_F(f1, f2, flen) { \
+ f1 = hfc_readcounter8(b4, A_F1); \
+ f2 = hfc_readcounter8(b4, A_F2); \
+ flen = f1 - f2; \
+ \
+ if(flen < 0) \
+ flen += (HFC_FMAX - HFC_FMIN) + 1; \
+ }
+
+#define get_Z(z1, z2, zlen) { \
+ z1 = hfc_readcounter16(b4, A_Z1); \
+ z2 = hfc_readcounter16(b4, A_Z2); \
+ zlen = z1 - z2; \
+ \
+ if(zlen < 0) \
+ zlen += (HFC_ZMAX - HFC_ZMIN) + 1; \
+ }
+
+#define get_Z32(z1, z2, zlen) { \
+ { \
+ unsigned int tmp; \
+ tmp = hfc_readcounter32(b4, A_Z12); \
+ z1 = tmp & 0xffff; \
+ z2 = (tmp >> 16); \
+ zlen = z1 - z2; \
+ \
+ if(zlen < 0) \
+ zlen += (HFC_ZMAX - HFC_ZMIN) + 1; \
+ } \
+ }
+
+#define flush_pci() (void)ioread8(b4->addr + R_STATUS)
+
+#endif /* __KERNEL__ */
+#endif /* _B4XX_H_ */