2010-08-28 05:59:27 +08:00
|
|
|
/*
|
|
|
|
* WCB410P Quad-BRI PCI Driver
|
|
|
|
* Written by Andrew Kohlsmith <akohlsmith@mixdown.ca>
|
|
|
|
*
|
2011-04-25 22:22:31 +08:00
|
|
|
* Copyright (C) 2009-2011 Digium, Inc.
|
2010-08-28 05:59:27 +08:00
|
|
|
* 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/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/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"
|
|
|
|
|
|
|
|
#ifndef BIT /* added in 2.6.24 */
|
|
|
|
#define BIT(i) (1UL << (i))
|
|
|
|
#endif
|
|
|
|
#define BIT_SET(x, i) ((x) |= BIT(i))
|
|
|
|
#define BIT_CLR(x, i) ((x) &= ~BIT(i))
|
|
|
|
#define IS_SET(x, i) (((x) & BIT(i)) != 0)
|
|
|
|
#define BITMASK(i) (((u64)1 << (i)) - 1)
|
|
|
|
|
|
|
|
|
|
|
|
#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)
|
|
|
|
|
|
|
|
#define DBG_SPANFILTER (BIT(bspan->port) & spanfilter)
|
|
|
|
|
|
|
|
static int debug = 0;
|
|
|
|
static int spanfilter = 0xFF; /* Bitmap for ports 1-8 */
|
|
|
|
#ifdef LOOPBACK_SUPPORTED
|
|
|
|
static int loopback = 0;
|
|
|
|
#endif
|
|
|
|
static int milliwatt = 0;
|
|
|
|
static int pedanticpci = 0;
|
|
|
|
static int teignorered = 0;
|
|
|
|
static int alarmdebounce = 500;
|
|
|
|
static int vpmsupport = 1;
|
|
|
|
static int timer_1_ms = 2000;
|
|
|
|
static int timer_3_ms = 30000;
|
|
|
|
static char *companding = "alaw";
|
|
|
|
|
|
|
|
#if !defined(mmiowb)
|
|
|
|
#define mmiowb() barrier()
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#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 CREATE_WCB4XXP_PROCFS_ENTRY
|
|
|
|
#ifdef CREATE_WCB4XXP_PROCFS_ENTRY
|
|
|
|
#define PROCFS_NAME "wcb4xxp"
|
|
|
|
static struct proc_dir_entry *myproc;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
int ports; /* Number of ports the card has */
|
|
|
|
enum cards_ids card_type; /* Card type - Digium B410P, ... */
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct devtype wcb4xxp = {"Wildcard B410P", .ports = 4, .card_type = B410P };
|
|
|
|
static struct devtype hfc2s = {"HFC-2S Junghanns.NET duoBRI PCI", .ports = 2, .card_type = DUOBRI };
|
|
|
|
static struct devtype hfc4s = {"HFC-4S Junghanns.NET quadBRI PCI", .ports = 4, .card_type = QUADBRI };
|
|
|
|
static struct devtype hfc8s = {"HFC-8S Junghanns.NET octoBRI PCI", .ports = 8, .card_type = OCTOBRI };
|
|
|
|
static struct devtype hfc2s_OV = {"OpenVox B200P", .ports = 2, .card_type = B200P_OV };
|
|
|
|
static struct devtype hfc4s_OV = {"OpenVox B400P", .ports = 4, .card_type = B400P_OV };
|
|
|
|
static struct devtype hfc8s_OV = {"OpenVox B800P", .ports = 8, .card_type = B800P_OV };
|
|
|
|
static struct devtype hfc2s_BN = {"BeroNet BN2S0", .ports = 2, .card_type = BN2S0 };
|
|
|
|
static struct devtype hfc4s_BN = {"BeroNet BN4S0", .ports = 4, .card_type = BN4S0 };
|
|
|
|
static struct devtype hfc8s_BN = {"BeroNet BN8S0", .ports = 8, .card_type = BN8S0 };
|
|
|
|
static struct devtype hfc4s_SW = {"Swyx 4xS0 SX2 QuadBri", .ports = 4, .card_type = BSWYX_SX2 };
|
|
|
|
static struct devtype hfc4s_EV = {"CCD HFC-4S Eval. Board", .ports = 4,
|
|
|
|
.card_type = QUADBRI_EVAL };
|
|
|
|
|
|
|
|
#define CARD_HAS_EC(card) ((card)->card_type == B410P)
|
|
|
|
|
|
|
|
static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec);
|
|
|
|
|
|
|
|
static const struct dahdi_echocan_features my_ec_features = {
|
|
|
|
.NLP_automatic = 1,
|
|
|
|
.CED_tx_detect = 1,
|
|
|
|
.CED_rx_detect = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct dahdi_echocan_ops my_ec_ops = {
|
|
|
|
.echocan_free = echocan_free,
|
|
|
|
};
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static const char *wcb4xxp_rcsdata = "$RCSfile: base.c,v $ $Revision$";
|
|
|
|
static const char *build_stamp = "" __DATE__ " " __TIME__ "";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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
|
2010-12-16 01:53:35 +08:00
|
|
|
if (unlikely(DBG_REGS)) {
|
|
|
|
drv_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%02x from 0x%p\n", ret, b4->addr + reg);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
#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
|
2010-12-16 01:53:35 +08:00
|
|
|
if (unlikely(DBG_REGS)) {
|
|
|
|
drv_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%04x from 0x%p\n", ret, b4->addr + reg);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
#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
|
2010-12-16 01:53:35 +08:00
|
|
|
if (unlikely(DBG_REGS)) {
|
|
|
|
drv_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%04x from 0x%p\n", ret, b4->addr + reg);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
#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
|
2010-12-16 01:53:35 +08:00
|
|
|
if (unlikely(DBG_REGS)) {
|
|
|
|
drv_dbg(&b4->pdev->dev,
|
|
|
|
"writing 0x%02x to 0x%p\n", val, b4->addr + reg);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
#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
|
2010-12-16 01:53:35 +08:00
|
|
|
if (unlikely(DBG_REGS)) {
|
|
|
|
drv_dbg(&b4->pdev->dev,
|
|
|
|
"writing 0x%02x to 0x%p\n", val, b4->addr + reg);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
#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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%02x from 0x%p\n", ret, b4->addr + reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
#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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%04x from 0x%p\n", ret, b4->addr + reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
#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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_dbg(&b4->pdev->dev,
|
|
|
|
"read 0x%04x from 0x%p\n", ret, b4->addr + reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
#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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_dbg(&b4->pdev->dev,
|
|
|
|
"writing 0x%02x to 0x%p\n", val, b4->addr + reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
#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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_dbg(&b4->pdev->dev,
|
|
|
|
"writing 0x%02x to 0x%p\n", val, b4->addr + reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
#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();
|
|
|
|
|
|
|
|
switch (b4->card_type) {
|
|
|
|
case OCTOBRI: /* fall through */
|
|
|
|
case B800P_OV: /* fall through */
|
|
|
|
case BN8S0:
|
|
|
|
/* GPIO0..15 S/T - HFC-8S uses GPIO8-15 for S/T ports 5-8 */
|
|
|
|
b4xxp_setreg8(b4, R_GPIO_SEL, 0x00);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* GPIO0..7 S/T, 8..15 GPIO */
|
|
|
|
b4xxp_setreg8(b4, R_GPIO_SEL, 0xf0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if ((in != data) && printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"ec_write: Wrote 0x%02x to register 0x%02x "
|
|
|
|
"of VPM %d but got back 0x%02x\n",
|
|
|
|
data, addr, which, in);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define NUM_EC 2
|
|
|
|
#define MAX_TDM_CHAN 32
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
void ec_set_dtmf_threshold(struct b4xxp *b4, int threshold)
|
|
|
|
{
|
|
|
|
unsigned int x;
|
|
|
|
|
|
|
|
for (x = 0; x < NUM_EC; x++) {
|
|
|
|
ec_write(b4, x, 0xC4, (threshold >> 8) & 0xFF);
|
|
|
|
ec_write(b4, x, 0xC5, (threshold & 0xFF));
|
|
|
|
}
|
|
|
|
printk("VPM: DTMF threshold set to %d\n", threshold);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
static void ec_init(struct b4xxp *b4)
|
|
|
|
{
|
|
|
|
unsigned char b;
|
|
|
|
unsigned int i, j, mask;
|
|
|
|
|
|
|
|
if (!CARD_HAS_EC(b4))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Setup GPIO */
|
|
|
|
for (i=0; i < NUM_EC; i++) {
|
|
|
|
b = ec_read(b4, i, 0x1a0);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"VPM %d/%d init: chip ver %02x\n", i, NUM_EC - 1, b);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
for (j=0; j < b4->numspans; 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 (512 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 */
|
|
|
|
b = ec_read(b4, i, 0x20);
|
|
|
|
b &= 0xe0;
|
|
|
|
b |= 0x12;
|
|
|
|
if (!strcasecmp(companding, "alaw")) {
|
|
|
|
if (DBG)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Setting alaw mode\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
b |= 0x01;
|
|
|
|
} else {
|
|
|
|
if (DBG)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Setting ulaw mode");
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ec_write(b4, i, 0x20, b);
|
|
|
|
if (DBG)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "reg 0x20 is 0x%02x\n", b);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
// ec_write(b4, i, 0x20, 0x38);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
ec_write(b4, i, 0x24, 0x02);
|
|
|
|
b = ec_read(b4, i, 0x24);
|
|
|
|
#endif
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"NLP threshold is set to %d (0x%02x)\n", b, b);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
ec_set_dtmf_threshold(b4, 1250);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* performs a register write and then waits for the HFC "busy" bit to clear */
|
|
|
|
static void hfc_setreg_waitbusy(struct b4xxp *b4, const unsigned int reg, const unsigned int val)
|
|
|
|
{
|
|
|
|
int timeout = 0;
|
|
|
|
unsigned long start;
|
|
|
|
const int TIMEOUT = HZ/4; /* 250ms */
|
|
|
|
|
|
|
|
start = jiffies;
|
|
|
|
while (unlikely((b4xxp_getreg8(b4, R_STATUS) & V_BUSY))) {
|
|
|
|
if (time_after(jiffies, start + TIMEOUT)) {
|
|
|
|
timeout = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
mb();
|
|
|
|
b4xxp_setreg8(b4, reg, val);
|
|
|
|
mb();
|
|
|
|
|
|
|
|
start = jiffies;
|
|
|
|
while (likely((b4xxp_getreg8(b4, R_STATUS) & V_BUSY))) {
|
|
|
|
if (time_after(jiffies, start + TIMEOUT)) {
|
|
|
|
timeout = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (timeout && printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"hfc_setreg_waitbusy(write 0x%02x to 0x%02x) timed "
|
|
|
|
"out waiting for busy flag to clear!\n", val, reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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--);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (!maxwait && printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"hfc_readcounter8(reg 0x%02x) timed out waiting "
|
|
|
|
"for data to settle!\n", reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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--);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (!maxwait && printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"hfc_readcounter16(reg 0x%02x) timed out waiting "
|
|
|
|
"for data to settle!\n", reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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--);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (!maxwait && printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"hfc_readcounter32(reg 0x%02x) timed out waiting "
|
|
|
|
"for data to settle!\n", reg);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_warn(&b4->pdev->dev, "hfc_reset() did not get the green light from the PCM system!\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Hardware echo cancellation enabled.\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG) {
|
|
|
|
dev_info(&b4->pdev->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);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
/* 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_STIO2 | ((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;
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "NOTE: hardware echo cancellation has been disabled\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"port %d, B channel %d\n\thost -> S/T "
|
|
|
|
"uses HFC chan %d via FIFO %d\n",
|
|
|
|
port, bchan, hfc_chan, fifo);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
switch (b4->card_type) {
|
|
|
|
case B800P_OV: /* fall through */
|
|
|
|
case OCTOBRI: /* fall through */
|
|
|
|
case BN8S0:
|
|
|
|
/* In HFC-8S cards we can't use ports 8-11 for dchan FIFOs */
|
|
|
|
fifo = port + 16;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fifo = port + 8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
hfc_chan = (port * 4) + 2;
|
|
|
|
|
|
|
|
/* record the host's FIFO # in the span fifo array */
|
|
|
|
b4->spans[port].fifos[2] = fifo;
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"port %d, D channel\n\thost -> S/T uses HFC chan %d "
|
|
|
|
"via FIFO %d\n", port, hfc_chan, fifo);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
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 0
|
|
|
|
printk("Setting sync to be port %d\n", (port >= 0) ? port + 1 : port);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (port == -1) /* automatic */
|
|
|
|
b = 0;
|
|
|
|
else
|
|
|
|
b = (port & V_SYNC_SEL_MASK) | V_MAN_SYNC;
|
|
|
|
|
|
|
|
b4xxp_setreg8(b4, R_ST_SYNC, b);
|
|
|
|
b4->syncspan = port;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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++) {
|
2011-01-28 04:58:40 +08:00
|
|
|
if (DBG)
|
|
|
|
dev_info(&b4->pdev->dev, "Checking sync pos %d, have span %d\n", i, b4->spans[i].sync);
|
2010-08-28 05:59:27 +08:00
|
|
|
psrc = b4->spans[i].sync;
|
|
|
|
if (psrc > 0 && !b4->spans[psrc - 1].span.alarms) {
|
2011-01-28 04:58:40 +08:00
|
|
|
if (DBG)
|
|
|
|
dev_info(&b4->pdev->dev, "chosen\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
src = psrc;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src >= 0)
|
|
|
|
return src - 1;
|
|
|
|
else
|
|
|
|
return src;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18))
|
|
|
|
static ssize_t b4_timing_master_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct b4xxp *b4 = dev_get_drvdata(dev);
|
|
|
|
return sprintf(buf, "%d\n", b4->syncspan);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(timing_master, 0400, b4_timing_master_show, NULL);
|
|
|
|
|
|
|
|
static void create_sysfs_files(struct b4xxp *b4)
|
|
|
|
{
|
|
|
|
int ret;
|
2010-12-16 01:53:35 +08:00
|
|
|
ret = device_create_file(&b4->pdev->dev,
|
2010-08-28 05:59:27 +08:00
|
|
|
&dev_attr_timing_master);
|
|
|
|
if (ret) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
2010-08-28 05:59:27 +08:00
|
|
|
"Failed to create device attributes.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_sysfs_files(struct b4xxp *b4)
|
|
|
|
{
|
2010-12-16 01:53:35 +08:00
|
|
|
device_remove_file(&b4->pdev->dev,
|
2010-08-28 05:59:27 +08:00
|
|
|
&dev_attr_timing_master);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static inline void create_sysfs_files(struct b4xxp *b4) { return; }
|
|
|
|
static inline void remove_sysfs_files(struct b4xxp *b4) { return; }
|
|
|
|
|
|
|
|
#endif /* LINUX_KERNEL > 2.6.18 */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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))) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_warn(&b4->pdev->dev, "could not allocate mem for ST state decode string!\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
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);
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"forced port %d to state %d (auto: %d), "
|
|
|
|
"new decode: %s\n", port + 1, state, resume_auto, x);
|
2010-08-28 05:59:27 +08:00
|
|
|
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;
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_ST) {
|
|
|
|
dev_info(&b4->pdev->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]);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* First, disable the expired timer; hfc_force_st_state() may activate it again. */
|
|
|
|
s->hfc_timer_on[t_no] = 0;
|
|
|
|
|
|
|
|
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:
|
2010-12-16 01:53:35 +08:00
|
|
|
if (printk_ratelimit()) {
|
|
|
|
dev_warn(&b4->pdev->dev,
|
|
|
|
"hfc_timer_expire found an unknown expired "
|
|
|
|
"timer (%d)??\n", t_no);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 < b4->numspans; 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)) {
|
2011-01-28 04:58:40 +08:00
|
|
|
s->span.alarms = s->newalarm;
|
|
|
|
if ((!s->newalarm && teignorered) || (!teignorered)) {
|
2010-08-28 05:59:27 +08:00
|
|
|
dahdi_alarm_notify(&s->span);
|
2011-01-28 04:58:40 +08:00
|
|
|
}
|
|
|
|
b4xxp_set_sync_src(b4, b4xxp_find_sync(b4));
|
|
|
|
if (DBG_ALARM) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"span %d: alarm %d "
|
|
|
|
"debounced\n",
|
|
|
|
i + 1, s->newalarm);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"port %d A_ST_RD_STA old=0x%02x now=0x%02x, "
|
|
|
|
"decoded: %s\n", s->port + 1, s->oldstate, state, x);
|
2010-08-28 05:59:27 +08:00
|
|
|
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) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "span %d: old alarm %d expires %ld, new alarm %d expires %ld\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
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) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"port %d: receiving INFO0 in state 3, "
|
|
|
|
"setting T3 and jumping to F4\n", s->port + 1);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
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) {
|
2011-01-28 04:58:40 +08:00
|
|
|
if (printk_ratelimit() || DBG) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"new card sync source: port %d\n",
|
|
|
|
newsync + 1);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
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;
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_ST) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"setting port %d t3 timer to %lu\n",
|
|
|
|
s->port + 1, s->hfc_timers[HFC_T3]);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
} 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;
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_ST) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"setting port %d t1 timer to %lu\n",
|
|
|
|
s->port + 1, s->hfc_timers[HFC_T1]);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#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 < b4->numspans; i++) {
|
|
|
|
s = &b4->spans[i];
|
|
|
|
s->parent = b4;
|
|
|
|
s->port = i;
|
|
|
|
|
|
|
|
/* The way the Digium B410P card reads the NT/TE mode
|
|
|
|
* jumper is the oposite of how other HFC-4S cards do:
|
|
|
|
* - In B410P: GPIO=0: NT
|
|
|
|
* - In Junghanns: GPIO=0: TE
|
|
|
|
*/
|
|
|
|
if (b4->card_type == B410P)
|
|
|
|
nt = ((gpio & (1 << (i + 4))) == 0);
|
|
|
|
else
|
|
|
|
nt = ((gpio & (1 << (i + 4))) != 0);
|
|
|
|
|
|
|
|
s->te_mode = !nt;
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"Port %d: %s mode\n", i + 1, (nt ? "NT" : "TE"));
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
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_Z(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, char *buf)
|
|
|
|
{
|
|
|
|
int f1, f2, flen, z1, z2, zlen;
|
|
|
|
|
|
|
|
get_F(f1, f2, flen);
|
|
|
|
get_Z(z1, z2, zlen);
|
|
|
|
|
|
|
|
sprintf(buf, "%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,
|
|
|
|
* we check the HDLC engine's STAT byte and update DAHDI as needed.
|
|
|
|
*
|
|
|
|
* Returns the number of HDLC frames left in the FIFO.
|
|
|
|
*/
|
|
|
|
static int hdlc_rx_frame(struct b4xxp_span *bspan)
|
|
|
|
{
|
|
|
|
int fifo, i, j, zleft;
|
|
|
|
int z1, z2, zlen, f1, f2, flen;
|
|
|
|
unsigned char buf[WCB4XXP_HDLC_BUF_LEN];
|
|
|
|
char debugbuf[256];
|
|
|
|
unsigned long irq_flags;
|
|
|
|
struct b4xxp *b4 = bspan->parent;
|
|
|
|
unsigned char stat;
|
|
|
|
|
|
|
|
fifo = bspan->fifos[2];
|
|
|
|
|
|
|
|
spin_lock_irqsave(&b4->fifolock, irq_flags);
|
|
|
|
hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT) | V_FIFO_DIR);
|
|
|
|
get_F(f1, f2, flen);
|
|
|
|
get_Z(z1, z2, zlen);
|
|
|
|
debug_fz(b4, fifo, "hdlc_rx_frame", debugbuf);
|
|
|
|
spin_unlock_irqrestore(&b4->fifolock, irq_flags);
|
|
|
|
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER) {
|
|
|
|
pr_info("%s", debugbuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* first check to make sure we really do have HDLC frames available to retrieve */
|
|
|
|
if (flen == 0) {
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "hdlc_rx_frame(span %d): no frames available?\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
bspan->port + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return flen;
|
|
|
|
}
|
|
|
|
|
|
|
|
zleft = zlen + 1; /* include STAT byte that the HFC injects after FCS */
|
|
|
|
|
|
|
|
do {
|
|
|
|
int truncated;
|
|
|
|
if (zleft > WCB4XXP_HDLC_BUF_LEN) {
|
|
|
|
truncated = 1;
|
|
|
|
j = WCB4XXP_HDLC_BUF_LEN;
|
|
|
|
} else {
|
|
|
|
truncated = 0;
|
|
|
|
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 */
|
|
|
|
if ((bspan->sigchan) && (j > 1))
|
|
|
|
dahdi_hdlc_putbuf(bspan->sigchan, buf, truncated ? j : j - 1);
|
|
|
|
|
|
|
|
zleft -= j;
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "hdlc_rx_frame(span %d): z1/z2/zlen=%d/%d/%d, zleft=%d\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
bspan->port + 1, z1, z2, zlen, zleft);
|
|
|
|
for (i=0; i < j; i++) printk("%02x%c", buf[i], (i < ( j - 1)) ? ' ':'\n');
|
|
|
|
}
|
|
|
|
} while (zleft > 0);
|
|
|
|
stat = buf[j - 1];
|
|
|
|
|
|
|
|
/* Frame received, increment F2 and get an updated count of frames left */
|
|
|
|
spin_lock_irqsave(&b4->fifolock, irq_flags);
|
|
|
|
hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_INC_F);
|
|
|
|
get_F(f1, f2, flen);
|
|
|
|
spin_unlock_irqrestore(&b4->fifolock, irq_flags);
|
|
|
|
|
|
|
|
/* If this channel is not configured with a signalling span we don't
|
|
|
|
* need to notify the rest of dahdi about this frame. */
|
|
|
|
if (!bspan->sigchan)
|
|
|
|
return flen;
|
|
|
|
|
|
|
|
++bspan->frames_in;
|
|
|
|
if (zlen < 3) {
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_notice(&b4->pdev->dev, "odd, zlen less then 3?\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_ABORT);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* if STAT != 0, indicates bad frame */
|
|
|
|
if (stat != 0x00) {
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_HDLC && DBG_SPANFILTER) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"(span %d) STAT=0x%02x indicates "
|
|
|
|
"frame problem: ",
|
|
|
|
bspan->port + 1, stat);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
if (stat == 0xff) {
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER)
|
|
|
|
printk("HDLC Abort\n");
|
|
|
|
dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_ABORT);
|
|
|
|
} else {
|
|
|
|
if (DBG_HDLC && DBG_SPANFILTER)
|
|
|
|
printk("Bad FCS\n");
|
|
|
|
dahdi_hdlc_abort(bspan->sigchan, DAHDI_EVENT_BADFCS);
|
|
|
|
}
|
|
|
|
/* STAT == 0, means frame was OK */
|
|
|
|
} else {
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_HDLC && DBG_SPANFILTER) {
|
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"(span %d) Frame %d is good!\n",
|
|
|
|
bspan->port + 1, bspan->frames_in);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
dahdi_hdlc_finish(bspan->sigchan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return flen;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
int z1, z2, zlen;
|
|
|
|
unsigned char buf[WCB4XXP_HDLC_BUF_LEN];
|
|
|
|
unsigned int size = sizeof(buf) / sizeof(buf[0]);
|
|
|
|
char debugbuf[256];
|
|
|
|
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_Z(z1, z2, zlen);
|
|
|
|
debug_fz(b4, fifo, "hdlc_tx_frame", debugbuf);
|
|
|
|
|
|
|
|
/* TODO: check zlen, etc. */
|
|
|
|
|
|
|
|
if (size > 0) {
|
|
|
|
bspan->sigactive = 1;
|
|
|
|
|
|
|
|
for (i=0; i < size; i++)
|
|
|
|
b4xxp_setreg8(b4, A_FIFO_DATA0, buf[i]);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we got a full frame from DAHDI, increment F and decrement our HDLC pending counter.
|
|
|
|
* Otherwise, select the FIFO again (to start transmission) and make sure the
|
|
|
|
* TX IRQ is enabled so we will get called again to finish off the data
|
|
|
|
*/
|
|
|
|
if (res != 0) {
|
|
|
|
++bspan->frames_out;
|
|
|
|
bspan->sigactive = 0;
|
|
|
|
hfc_setreg_waitbusy(b4, A_INC_RES_FIFO, V_INC_F);
|
|
|
|
atomic_dec(&bspan->hdlc_pending);
|
|
|
|
} else {
|
|
|
|
hfc_setreg_waitbusy(b4, R_FIFO, (fifo << V_FIFO_NUM_SHIFT));
|
|
|
|
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 && DBG_SPANFILTER) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "%s", debugbuf);
|
|
|
|
dev_info(&b4->pdev->dev, "hdlc_tx_frame(span %d): DAHDI gave %d bytes for FIFO %d (res=%d)\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
bspan->port + 1, size, fifo, res);
|
|
|
|
for (i=0; i < size; i++)
|
|
|
|
printk("%02x%c", buf[i], (i < (size - 1)) ? ' ' : '\n');
|
|
|
|
|
2011-01-03 21:06:54 +08:00
|
|
|
if (size && res != 0) {
|
|
|
|
pr_info("Transmitted frame %d on span %d\n",
|
|
|
|
bspan->frames_out - 1, bspan->port + 1);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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 B410P & Cologne Eval Board have a
|
|
|
|
* 24.576MHz crystal, so the PCM clock is 2x the incoming clock.
|
|
|
|
* Other cards have a 49.152Mhz crystal, so the PCM clock equals
|
|
|
|
* incoming clock.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((b4->card_type == B410P) || (b4->card_type == QUADBRI_EVAL))
|
|
|
|
b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x02);
|
|
|
|
else
|
|
|
|
b4xxp_setreg8(b4, R_BRG_PCM_CFG, V_PCM_CLK);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
b4xxp_setreg8(b4, R_PWM_MD, 0xa0);
|
|
|
|
b4xxp_setreg8(b4, R_PWM0, 0x1b);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set up the flow controller.
|
|
|
|
* B channel map: (4 ports cards with Hardware Echo Cancel present & active)
|
|
|
|
* 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 2 D using HFC channel 7
|
|
|
|
* FIFO 10 connects Port 3 D using HFC channel 11
|
|
|
|
* FIFO 11 connects Port 4 D using HFC channel 15
|
|
|
|
*
|
|
|
|
* D channel FIFOs are operated in HDLC mode and interrupt on end of frame.
|
|
|
|
*
|
|
|
|
* B channel map: (8 ports cards without Hardware Echo Cancel)
|
|
|
|
* FIFO 0 connects Port 1 B0 using HFC channel 0
|
|
|
|
* FIFO 1 connects Port 1 B1 using HFC channel 1
|
|
|
|
* FIFO 2 connects Port 2 B0 using HFC channel 4
|
|
|
|
* FIFO 3 connects Port 2 B1 using HFC channel 5
|
|
|
|
* .........................
|
|
|
|
* FIFO 14 connects Port 8 B0 using HFC channel 28
|
|
|
|
* FIFO 15 connects Port 8 B1 using HFC channel 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 16-23.
|
|
|
|
* FIFO 16 connects Port 1 D using HFC channel 3
|
|
|
|
* FIFO 17 connects Port 2 D using HFC channel 7
|
|
|
|
* FIFO 18 connects Port 3 D using HFC channel 11
|
|
|
|
* FIFO 19 connects Port 4 D using HFC channel 15
|
|
|
|
* ................
|
|
|
|
* FIFO 23 connects Port 8 D using HFC channel 31
|
|
|
|
* D channel FIFOs are operated in HDLC mode and interrupt on end of frame.
|
|
|
|
*/
|
|
|
|
for (span=0; span < b4->numspans; span++) {
|
|
|
|
if ((vpmsupport) && (CARD_HAS_EC(b4))) {
|
|
|
|
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_update_leds_hfc_8s(struct b4xxp *b4)
|
|
|
|
{
|
|
|
|
unsigned long lled = 0; /* A bit set is a led OFF */
|
|
|
|
unsigned long leddw;
|
|
|
|
int j;
|
|
|
|
struct b4xxp_span *bspan;
|
|
|
|
|
|
|
|
b4->blinktimer++;
|
|
|
|
for (j = 7; j >= 0; j--) {
|
|
|
|
bspan = &b4->spans[7 - j];
|
|
|
|
if (!(bspan->span.flags & DAHDI_FLAG_RUNNING) ||
|
|
|
|
bspan->span.alarms) {
|
|
|
|
BIT_SET(lled, j);
|
|
|
|
continue; /* Led OFF */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bspan->span.mainttimer || bspan->span.maintstat) {
|
|
|
|
/* Led Blinking in maint state */
|
|
|
|
if (b4->blinktimer >= 0x7f)
|
|
|
|
BIT_SET(lled, j);
|
|
|
|
}
|
|
|
|
/* Else: Led on */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write Leds...*/
|
|
|
|
leddw = lled << 24 | lled << 16 | lled << 8 | lled;
|
|
|
|
b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x21);
|
|
|
|
iowrite16(0x4000, b4->ioaddr + 4);
|
|
|
|
iowrite32(leddw, b4->ioaddr);
|
|
|
|
b4xxp_setreg8(b4, R_BRG_PCM_CFG, 0x20);
|
|
|
|
|
|
|
|
if (b4->blinktimer == 0xff)
|
|
|
|
b4->blinktimer = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* So far only tested for OpenVox cards. Please test it for other hardware */
|
|
|
|
static void b4xxp_update_leds_hfc(struct b4xxp *b4)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int leds = 0, green_leds = 0; /* Default: off */
|
|
|
|
struct b4xxp_span *bspan;
|
|
|
|
|
|
|
|
b4->blinktimer++;
|
|
|
|
for (i=0; i < b4->numspans; i++) {
|
|
|
|
bspan = &b4->spans[i];
|
|
|
|
|
|
|
|
if (!(bspan->span.flags & DAHDI_FLAG_RUNNING))
|
|
|
|
continue; /* Leds are off */
|
|
|
|
|
|
|
|
if (bspan->span.alarms) {
|
|
|
|
/* Red blinking -> Alarm */
|
|
|
|
if (b4->blinktimer >= 0x7f)
|
|
|
|
BIT_SET(leds, i);
|
|
|
|
} else if (bspan->span.mainttimer || bspan->span.maintstat) {
|
|
|
|
/* Green blinking -> Maint status */
|
|
|
|
if (b4->blinktimer >= 0x7f)
|
|
|
|
BIT_SET(green_leds, i);
|
|
|
|
} else {
|
|
|
|
/* Steady grean -> No Alarm */
|
|
|
|
BIT_SET(green_leds, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Actually set them. for red: just set the bit in R_GPIO_EN1.
|
|
|
|
For green: in both R_GPIO_EN1 and R_GPIO_OUT1. */
|
|
|
|
leds |= green_leds;
|
|
|
|
b4xxp_setreg8(b4, R_GPIO_EN1, leds);
|
|
|
|
b4xxp_setreg8(b4, R_GPIO_OUT1, green_leds);
|
|
|
|
|
|
|
|
if (b4->blinktimer == 0xff)
|
|
|
|
b4->blinktimer = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (b4->numspans == 8) {
|
|
|
|
/* Use the alternative function for non-Digium HFC-8S cards */
|
|
|
|
b4xxp_update_leds_hfc_8s(b4);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b4->card_type != B410P) {
|
|
|
|
/* Use the alternative function for non-Digium HFC-4S cards */
|
|
|
|
b4xxp_update_leds_hfc(b4);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-10 04:19:26 +08:00
|
|
|
static const char *b4xxp_echocan_name(const struct dahdi_chan *chan)
|
|
|
|
{
|
|
|
|
struct b4xxp_span *bspan = container_of(chan->span, struct b4xxp_span,
|
|
|
|
span);
|
|
|
|
if (bspan->parent->card_type == B410P)
|
2011-07-01 23:45:19 +08:00
|
|
|
return "LASVEGAS2";
|
|
|
|
return NULL;
|
2010-12-10 04:19:26 +08:00
|
|
|
}
|
|
|
|
|
2010-08-28 05:59:27 +08:00
|
|
|
static int b4xxp_echocan_create(struct dahdi_chan *chan,
|
|
|
|
struct dahdi_echocanparams *ecp,
|
|
|
|
struct dahdi_echocanparam *p,
|
|
|
|
struct dahdi_echocan_state **ec)
|
|
|
|
{
|
|
|
|
struct b4xxp_span *bspan = container_of(chan->span, struct b4xxp_span, span);
|
|
|
|
int channel;
|
|
|
|
|
|
|
|
if (!vpmsupport || !CARD_HAS_EC(bspan->parent))
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (chan->chanpos == 3) {
|
|
|
|
printk(KERN_WARNING "Cannot enable echo canceller on D channel of span %d; failing request\n", chan->span->offset);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ecp->param_count > 0) {
|
|
|
|
printk(KERN_WARNING "wcb4xxp echo canceller does not support parameters; failing request\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ec = &bspan->ec[chan->chanpos];
|
|
|
|
(*ec)->ops = &my_ec_ops;
|
|
|
|
(*ec)->features = my_ec_features;
|
|
|
|
|
|
|
|
if (DBG_EC)
|
|
|
|
printk("Enabling echo cancellation on chan %d span %d\n", chan->chanpos, chan->span->offset);
|
|
|
|
|
|
|
|
channel = (chan->span->offset * 8) + ((chan->chanpos - 1) * 4) + 1;
|
|
|
|
|
|
|
|
ec_write(bspan->parent, chan->chanpos - 1, channel, 0x7e);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec)
|
|
|
|
{
|
|
|
|
struct b4xxp_span *bspan = container_of(chan->span, struct b4xxp_span, span);
|
|
|
|
int channel;
|
|
|
|
|
|
|
|
memset(ec, 0, sizeof(*ec));
|
|
|
|
|
|
|
|
if (DBG_EC)
|
|
|
|
printk("Disabling echo cancellation on chan %d span %d\n", chan->chanpos, chan->span->offset);
|
|
|
|
|
|
|
|
channel = (chan->span->offset * 8) + ((chan->chanpos - 1) * 4) + 1;
|
|
|
|
|
|
|
|
ec_write(bspan->parent, chan->chanpos - 1, channel, 0x01);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2011-06-03 04:01:34 +08:00
|
|
|
static int b4xxp_startup(struct file *file, struct dahdi_span *span)
|
2010-08-28 05:59:27 +08:00
|
|
|
{
|
|
|
|
struct b4xxp_span *bspan = container_of(span, struct b4xxp_span, span);
|
|
|
|
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 = container_of(span, struct b4xxp_span, span);
|
|
|
|
|
|
|
|
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 */
|
2011-06-03 04:01:34 +08:00
|
|
|
static int b4xxp_spanconfig(struct file *file, struct dahdi_span *span,
|
|
|
|
struct dahdi_lineconfig *lc)
|
2010-08-28 05:59:27 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct b4xxp_span *bspan = container_of(span, struct b4xxp_span, span);
|
|
|
|
struct b4xxp *b4 = bspan->parent;
|
|
|
|
|
|
|
|
if (DBG)
|
2011-01-28 04:58:40 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Configuring span %d offset %d to be sync %d\n", span->spanno, span->offset, lc->sync);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
if (lc->sync > 0 && !bspan->te_mode) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Span %d is not in NT mode, removing "
|
|
|
|
"from sync source list\n", span->spanno);
|
2010-08-28 05:59:27 +08:00
|
|
|
lc->sync = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (lc->sync < 0 || lc->sync > 4) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev,
|
|
|
|
"Span %d has invalid sync priority (%d), removing "
|
|
|
|
"from sync source list\n", span->spanno, lc->sync);
|
2010-08-28 05:59:27 +08:00
|
|
|
lc->sync = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove this span number from the current sync sources, if there */
|
|
|
|
for (i = 0; i < b4->numspans; i++) {
|
2011-01-28 04:58:40 +08:00
|
|
|
if (b4->spans[i].sync == (span->offset + 1)) {
|
2010-08-28 05:59:27 +08:00
|
|
|
b4->spans[i].sync = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-21 03:49:05 +08:00
|
|
|
if (lc->sync)
|
2011-01-28 04:58:40 +08:00
|
|
|
b4->spans[lc->sync - 1].sync = (span->offset + 1);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
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. */
|
2011-06-03 04:01:34 +08:00
|
|
|
b4xxp_startup(file, &bspan->span);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
span->flags |= DAHDI_FLAG_RUNNING;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* chanconfig for us means to configure the HDLC controller, if appropriate */
|
2011-06-03 04:01:34 +08:00
|
|
|
static int
|
|
|
|
b4xxp_chanconfig(struct file *file, struct dahdi_chan *chan, int sigtype)
|
2010-08-28 05:59:27 +08:00
|
|
|
{
|
|
|
|
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) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "%s channel %d (%s) sigtype %08x\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
alreadyrunning ? "Reconfigured" : "Configured", chan->channo, chan->name, sigtype);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* (re)configure signalling channel */
|
|
|
|
if ((sigtype == DAHDI_SIG_HARDHDLC) || (bspan->sigchan == chan)) {
|
|
|
|
if (DBG_FOPS)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "%sonfiguring hardware HDLC on %s\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
((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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_warn(&b4->pdev->dev, "Error initializing signalling controller\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bspan->sigchan = (sigtype == DAHDI_SIG_HARDHDLC) ? chan : NULL;
|
|
|
|
bspan->sigactive = 0;
|
|
|
|
atomic_set(&bspan->hdlc_pending, 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];
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_FOPS && DBG_SPANFILTER) {
|
|
|
|
dev_info(&b4->pdev->dev, "open() on chan %s (%i/%i)\n",
|
|
|
|
chan->name, chan->channo, chan->chanpos);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
2011-01-03 21:06:54 +08:00
|
|
|
hfc_reset_fifo_pair(b4, bspan->fifos[chan->chanpos - 1], 0, 0);
|
2010-08-28 05:59:27 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int b4xxp_close(struct dahdi_chan *chan)
|
|
|
|
{
|
|
|
|
struct b4xxp *b4 = chan->pvt;
|
|
|
|
struct b4xxp_span *bspan = &b4->spans[chan->span->offset];
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
if (DBG_FOPS && DBG_SPANFILTER) {
|
|
|
|
dev_info(&b4->pdev->dev, "close() on chan %s (%i/%i)\n",
|
|
|
|
chan->name, chan->channo, chan->chanpos);
|
|
|
|
}
|
2010-08-28 05:59:27 +08:00
|
|
|
|
2011-01-03 21:06:54 +08:00
|
|
|
hfc_reset_fifo_pair(b4, bspan->fifos[chan->chanpos - 1], 1, 1);
|
2010-08-28 05:59:27 +08:00
|
|
|
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];
|
|
|
|
|
|
|
|
if ((DBG_FOPS || DBG_HDLC) && DBG_SPANFILTER)
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "hdlc_hard_xmit on chan %s (%i/%i), span=%i\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
chan->name, chan->channo, chan->chanpos, span + 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* increment the hdlc_pending counter and trigger the bottom-half so it
|
|
|
|
* will be picked up and sent.
|
|
|
|
*/
|
|
|
|
if (bspan->sigchan == chan) {
|
|
|
|
atomic_inc(&bspan->hdlc_pending);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* internal functions, not specific to the hardware or DAHDI */
|
|
|
|
|
|
|
|
static const struct dahdi_span_ops b4xxp_span_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.spanconfig = b4xxp_spanconfig,
|
|
|
|
.chanconfig = b4xxp_chanconfig,
|
|
|
|
.startup = b4xxp_startup,
|
|
|
|
.shutdown = b4xxp_shutdown,
|
|
|
|
.open = b4xxp_open,
|
|
|
|
.close = b4xxp_close,
|
|
|
|
.ioctl = b4xxp_ioctl,
|
|
|
|
.hdlc_hard_xmit = b4xxp_hdlc_hard_xmit,
|
|
|
|
.echocan_create = b4xxp_echocan_create,
|
2010-12-10 04:19:26 +08:00
|
|
|
.echocan_name = b4xxp_echocan_name,
|
2010-08-28 05:59:27 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* 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.spantype = (bspan->te_mode) ? "TE" : "NT";
|
|
|
|
bspan->span.offset = i;
|
|
|
|
bspan->span.channels = WCB4XXP_CHANNELS_PER_SPAN;
|
|
|
|
bspan->span.flags = 0;
|
|
|
|
|
|
|
|
if (!strcasecmp(companding, "ulaw"))
|
|
|
|
bspan->span.deflaw = DAHDI_LAW_MULAW;
|
|
|
|
else
|
|
|
|
bspan->span.deflaw = DAHDI_LAW_ALAW;
|
|
|
|
/* For simplicty, we'll accept all line modes since BRI
|
|
|
|
* ignores this setting anyway.*/
|
|
|
|
bspan->span.linecompat = DAHDI_CONFIG_AMI |
|
|
|
|
DAHDI_CONFIG_B8ZS | DAHDI_CONFIG_D4 |
|
|
|
|
DAHDI_CONFIG_ESF | 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";
|
2011-01-04 02:26:29 +08:00
|
|
|
strlcpy(bspan->span.devicetype, b4->variety,
|
|
|
|
sizeof(bspan->span.devicetype));
|
2010-08-28 05:59:27 +08:00
|
|
|
sprintf(bspan->span.location, "PCI Bus %02d Slot %02d",
|
|
|
|
b4->pdev->bus->number, PCI_SLOT(b4->pdev->devfn) + 1);
|
|
|
|
|
|
|
|
bspan->span.ops = &b4xxp_span_ops;
|
|
|
|
/* HDLC stuff */
|
|
|
|
bspan->sigchan = NULL;
|
|
|
|
bspan->sigactive = 0;
|
|
|
|
|
|
|
|
bspan->span.chans = bspan->chans;
|
|
|
|
|
|
|
|
/* 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 | DAHDI_SIG_DACS;
|
|
|
|
}
|
|
|
|
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;
|
2011-06-03 04:00:36 +08:00
|
|
|
int i, j, k, fifo, fifo_low, fifo_high;
|
2010-08-28 05:59:27 +08:00
|
|
|
unsigned char b, b2;
|
|
|
|
|
|
|
|
if (b4->shutdown)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* HFC-4S d-chan fifos 8-11 *** HFC-8S d-chan fifos 16-23 */
|
|
|
|
if (b4->numspans == 8) {
|
|
|
|
fifo_low = 16;
|
|
|
|
fifo_high = 23;
|
|
|
|
} else {
|
|
|
|
fifo_low = 8;
|
|
|
|
fifo_high = 11;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 >= fifo_low && fifo <= fifo_high) {
|
|
|
|
/* d-chan fifos */
|
|
|
|
/*
|
|
|
|
* 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 - fifo_low]);
|
|
|
|
} while (k);
|
|
|
|
} else {
|
|
|
|
if (printk_ratelimit())
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_warn(&b4->pdev->dev, "Got FIFO TX int from non-d-chan FIFO %d??\n", fifo);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b & V_IRQ_FIFOx_RX) {
|
|
|
|
if (fifo >= fifo_low && fifo <= fifo_high) { /* dchan fifos */
|
|
|
|
/*
|
|
|
|
* I have to loop here until hdlc_rx_frame says there are no more frames waiting.
|
|
|
|
* for whatever reason, the HFC will not generate another interrupt if there are
|
|
|
|
* still HDLC frames waiting to be received.
|
|
|
|
* i.e. I get an int when F1 changes, not when F1 != F2.
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
k = hdlc_rx_frame(&b4->spans[fifo - fifo_low]);
|
|
|
|
} while (k);
|
|
|
|
} else {
|
|
|
|
if (printk_ratelimit())
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_warn(&b4->pdev->dev, "Got FIFO RX int from non-d-chan FIFO %d??\n", fifo);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for outgoing HDLC frame requests
|
|
|
|
* The HFC does not generate TX interrupts when there is room to send, so
|
|
|
|
* I use an atomic counter that is incremented every time DAHDI wants to send
|
|
|
|
* a frame, and decremented every time I send a frame. It'd be better if I could
|
|
|
|
* just use the interrupt handler, but the HFC seems to trigger a FIFO TX IRQ
|
|
|
|
* only when it has finished sending a frame, not when one can be sent.
|
|
|
|
*/
|
|
|
|
for (i=0; i < b4->numspans; i++) {
|
|
|
|
struct b4xxp_span *bspan = &b4->spans[i];
|
|
|
|
|
|
|
|
if (atomic_read(&bspan->hdlc_pending)) {
|
|
|
|
do {
|
|
|
|
k = hdlc_tx_frame(bspan);
|
|
|
|
} while (k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/********************************************************************************* proc stuff *****/
|
|
|
|
|
|
|
|
#ifdef CREATE_WCB4XXP_PROCFS_ENTRY
|
|
|
|
static 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;
|
2010-12-16 01:53:35 +08:00
|
|
|
sprintf(sBuf, "Card %d, PCI identifier %s, IRQ %d\n",
|
|
|
|
b4->cardno + 1, b4->pdev->dev.bus_id, b4->irq);
|
2010-08-28 05:59:27 +08:00
|
|
|
|
|
|
|
strcat(sBuf,"Tx:\n");
|
|
|
|
for (j=0; j<(b4->numspans * 2) ; j++) { /* B Channels */
|
|
|
|
for (i=0; i<(b4->numspans * 3) ; i++) { /* All Channels */
|
|
|
|
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 < (b4->numspans * 2); j++) { /* B Channels */
|
|
|
|
for (i=0; i < (b4->numspans * 3); i++) { /* All Channels */
|
|
|
|
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 < b4->numspans; 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static 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: ");
|
|
|
|
#ifdef LOOPBACK_SUPPORTED
|
|
|
|
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");
|
|
|
|
#else
|
|
|
|
strcat(sBuf, "not looped back");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
#endif /* CREATE_WCB4XXP_PROCFS_ENTRY */
|
|
|
|
|
|
|
|
static int b4xxp_startdefaultspan(struct b4xxp *b4)
|
|
|
|
{
|
|
|
|
struct dahdi_lineconfig lc = {0,};
|
2011-06-03 04:01:34 +08:00
|
|
|
return b4xxp_spanconfig(NULL, &b4->spans[0].span, &lc);
|
2010-08-28 05:59:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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->variety = dt->desc;
|
|
|
|
b4->card_type = dt->card_type;
|
|
|
|
b4->pdev = pdev;
|
|
|
|
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) && (x != 0x80)) { /* 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 = dt->ports;
|
|
|
|
b4->syncspan = -1; /* sync span is unknown */
|
|
|
|
if (b4->numspans > MAX_SPANS_PER_CARD) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_err(&b4->pdev->dev,
|
|
|
|
"Driver does not know how to handle a %d span card!\n",
|
|
|
|
b4->numspans);
|
2010-08-28 05:59:27 +08:00
|
|
|
goto err_out_free_mem;
|
|
|
|
}
|
|
|
|
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Identified %s (controller rev %d) at %p, IRQ %i\n",
|
2010-08-28 05:59:27 +08:00
|
|
|
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);
|
|
|
|
|
2011-04-05 00:26:11 +08:00
|
|
|
create_sysfs_files(b4);
|
|
|
|
|
2010-08-28 05:59:27 +08:00
|
|
|
if (request_irq(pdev->irq, b4xxp_interrupt, DAHDI_IRQ_SHARED_DISABLED, "b4xxp", b4)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_err(&b4->pdev->dev, "Unable to request IRQ %d\n",
|
|
|
|
pdev->irq);
|
2010-08-28 05:59:27 +08:00
|
|
|
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)) {
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_err(&b4->pdev->dev,
|
|
|
|
"Unable to register span %s\n",
|
|
|
|
b4->spans[x].span.name);
|
2010-08-28 05:59:27 +08:00
|
|
|
goto err_out_unreg_spans;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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
|
2010-12-16 01:53:35 +08:00
|
|
|
dev_info(&b4->pdev->dev, "Did not do the highestorder stuff\n");
|
2010-08-28 05:59:27 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
ret = b4xxp_startdefaultspan(b4);
|
|
|
|
if (ret)
|
|
|
|
goto err_out_unreg_spans;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
b4xxp_init_stage1(b4);
|
|
|
|
remove_sysfs_files(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;
|
|
|
|
|
|
|
|
tasklet_kill(&b4->b4xxp_tlet);
|
|
|
|
|
|
|
|
kfree(b4);
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_info(&pdev->dev, "Driver unloaded.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-01-04 02:26:24 +08:00
|
|
|
static DEFINE_PCI_DEVICE_TABLE(b4xx_ids) =
|
2010-08-28 05:59:27 +08:00
|
|
|
{
|
|
|
|
{ 0xd161, 0xb410, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long)&wcb4xxp },
|
|
|
|
{ 0x1397, 0x16b8, 0x1397, 0xb552, 0, 0, (unsigned long)&hfc8s },
|
|
|
|
{ 0x1397, 0x16b8, 0x1397, 0xb55b, 0, 0, (unsigned long)&hfc8s },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb520, 0, 0, (unsigned long)&hfc4s },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb550, 0, 0, (unsigned long)&hfc4s },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb752, 0, 0, (unsigned long)&hfc4s },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb556, 0, 0, (unsigned long)&hfc2s },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xe884, 0, 0, (unsigned long)&hfc2s_OV },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xe888, 0, 0, (unsigned long)&hfc4s_OV },
|
|
|
|
{ 0x1397, 0x16b8, 0x1397, 0xe998, 0, 0, (unsigned long)&hfc8s_OV },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb566, 0, 0, (unsigned long)&hfc2s_BN },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb761, 0, 0, (unsigned long)&hfc2s_BN },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb560, 0, 0, (unsigned long)&hfc4s_BN },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb550, 0, 0, (unsigned long)&hfc4s_BN },
|
2010-10-21 01:52:47 +08:00
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb762, 0, 0, (unsigned long)&hfc4s_BN },
|
2010-08-28 05:59:27 +08:00
|
|
|
{ 0x1397, 0x16b8, 0x1397, 0xb562, 0, 0, (unsigned long)&hfc8s_BN },
|
|
|
|
{ 0x1397, 0x16b8, 0x1397, 0xb56b, 0, 0, (unsigned long)&hfc8s_BN },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0xb540, 0, 0, (unsigned long)&hfc4s_SW },
|
|
|
|
{ 0x1397, 0x08b4, 0x1397, 0x08b4, 0, 0, (unsigned long)&hfc4s_EV },
|
|
|
|
{0, }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2011-04-05 00:25:09 +08:00
|
|
|
static int b4xx_suspend(struct pci_dev *pdev, pm_message_t state)
|
|
|
|
{
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
2010-08-28 05:59:27 +08:00
|
|
|
static struct pci_driver b4xx_driver = {
|
|
|
|
.name = "wcb4xxp",
|
|
|
|
.probe = b4xx_probe,
|
|
|
|
.remove = __devexit_p(b4xxp_remove),
|
|
|
|
.id_table = b4xx_ids,
|
2011-04-05 00:25:09 +08:00
|
|
|
.suspend = b4xx_suspend,
|
2010-08-28 05:59:27 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init b4xx_init(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
#ifdef CREATE_WCB4XXP_PROCFS_ENTRY
|
|
|
|
if (!(myproc = create_proc_read_entry(PROCFS_NAME, 0444, NULL,
|
|
|
|
b4xxp_proc_read, cards))) {
|
|
|
|
printk(KERN_ERR "%s: ERROR: Could not initialize /proc/%s\n",THIS_MODULE->name, PROCFS_NAME);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (dahdi_pci_module(&b4xx_driver))
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit b4xx_exit(void)
|
|
|
|
{
|
|
|
|
#ifdef CREATE_WCB4XXP_PROCFS_ENTRY
|
|
|
|
remove_proc_entry(PROCFS_NAME, NULL);
|
|
|
|
#endif
|
|
|
|
pci_unregister_driver(&b4xx_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_param(debug, int, S_IRUGO | S_IWUSR);
|
|
|
|
module_param(spanfilter, int, S_IRUGO | S_IWUSR);
|
2010-09-27 23:57:13 +08:00
|
|
|
#ifdef LOOPBACK_SUPPORTED
|
2010-08-28 05:59:27 +08:00
|
|
|
module_param(loopback, int, S_IRUGO | S_IWUSR);
|
|
|
|
#endif
|
|
|
|
module_param(milliwatt, int, S_IRUGO | S_IWUSR);
|
|
|
|
module_param(pedanticpci, int, S_IRUGO);
|
|
|
|
module_param(teignorered, int, S_IRUGO | S_IWUSR);
|
|
|
|
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_param(companding, charp, S_IRUGO);
|
|
|
|
|
|
|
|
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(spanfilter, "debug filter for spans. bitmap: 1=port 1, 2=port 2, 4=port 3, 8=port 4");
|
2010-09-27 23:57:13 +08:00
|
|
|
#ifdef LOOPBACK_SUPPORTED
|
2010-08-28 05:59:27 +08:00
|
|
|
MODULE_PARM_DESC(loopback, "TODO: bitmap: 1=loop back S/T port 2=loop back DAHDI");
|
|
|
|
#endif
|
|
|
|
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_PARM_DESC(companding, "Change the companding to \"alaw\" or \"ulaw\""\
|
|
|
|
"(alaw by default)");
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Digium Incorporated <support@digium.com>");
|
|
|
|
MODULE_DESCRIPTION("B410P & Similars multi-port BRI module driver.");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(pci, b4xx_ids);
|
|
|
|
|
|
|
|
module_init(b4xx_init);
|
|
|
|
module_exit(b4xx_exit);
|