summaryrefslogtreecommitdiff
path: root/drivers/dahdi/wcfxo.c
diff options
context:
space:
mode:
authorKevin P. Fleming <kpfleming@digium.com>2008-05-21 15:11:48 +0000
committerKevin P. Fleming <kpfleming@digium.com>2008-05-21 15:11:48 +0000
commit802b567e6c7ba7803a950324cbed13f7d57944cb (patch)
tree6b90ca3119aaa2e4073d3b651ac965dea5d3430e /drivers/dahdi/wcfxo.c
parentec5ce88e015b41c2f46f6c9b783339b945f9502a (diff)
start copying kernel bits
git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@4315 a0bf4364-ded3-4de4-8d8a-66a801d63aff
Diffstat (limited to 'drivers/dahdi/wcfxo.c')
-rw-r--r--drivers/dahdi/wcfxo.c1103
1 files changed, 1103 insertions, 0 deletions
diff --git a/drivers/dahdi/wcfxo.c b/drivers/dahdi/wcfxo.c
new file mode 100644
index 0000000..241053b
--- /dev/null
+++ b/drivers/dahdi/wcfxo.c
@@ -0,0 +1,1103 @@
+/*
+ * Wilcard X100P FXO Interface Driver for Zapata Telephony interface
+ *
+ * Written by Mark Spencer <markster@digium.com>
+ * Matthew Fredrickson <creslin@digium.com>
+ *
+ * Copyright (C) 2001, Linux Support Services, Inc.
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#ifdef STANDALONE_ZAPATA
+#include "zaptel.h"
+#else
+#include <zaptel/zaptel.h>
+#endif
+#ifdef LINUX26
+#include <linux/moduleparam.h>
+#endif
+
+/* Uncomment to enable tasklet handling in the FXO driver. Not recommended
+ in general, but may improve interactive performance */
+
+/* #define ENABLE_TASKLETS */
+
+/* Un-comment the following for POTS line support for Japan */
+/* #define JAPAN */
+
+/* Un-comment for lines (eg from and ISDN TA) that remove */
+/* phone power during ringing */
+/* #define ZERO_BATT_RING */
+
+#define WC_MAX_IFACES 128
+
+#define WC_CNTL 0x00
+#define WC_OPER 0x01
+#define WC_AUXC 0x02
+#define WC_AUXD 0x03
+#define WC_MASK0 0x04
+#define WC_MASK1 0x05
+#define WC_INTSTAT 0x06
+
+#define WC_DMAWS 0x08
+#define WC_DMAWI 0x0c
+#define WC_DMAWE 0x10
+#define WC_DMARS 0x18
+#define WC_DMARI 0x1c
+#define WC_DMARE 0x20
+
+#define WC_AUXFUNC 0x2b
+#define WC_SERCTL 0x2d
+#define WC_FSCDELAY 0x2f
+
+
+/* DAA registers */
+#define WC_DAA_CTL1 1
+#define WC_DAA_CTL2 2
+#define WC_DAA_DCTL1 5
+#define WC_DAA_DCTL2 6
+#define WC_DAA_PLL1_N1 7
+#define WC_DAA_PLL1_M1 8
+#define WC_DAA_PLL2_N2_M2 9
+#define WC_DAA_PLL_CTL 10
+#define WC_DAA_CHIPA_REV 11
+#define WC_DAA_LINE_STAT 12
+#define WC_DAA_CHIPB_REV 13
+#define WC_DAA_DAISY_CTL 14
+#define WC_DAA_TXRX_GCTL 15
+#define WC_DAA_INT_CTL1 16
+#define WC_DAA_INT_CTL2 17
+#define WC_DAA_INT_CTL3 18
+#define WC_DAA_INT_CTL4 19
+
+
+#define FLAG_EMPTY 0
+#define FLAG_WRITE 1
+#define FLAG_READ 2
+
+#ifdef ZERO_BATT_RING /* Need to debounce Off/On hook too */
+#define JAPAN
+#endif
+
+#define RING_DEBOUNCE 64 /* Ringer Debounce (in ms) */
+#ifdef JAPAN
+#define BATT_DEBOUNCE 30 /* Battery debounce (in ms) */
+#define OH_DEBOUNCE 350 /* Off/On hook debounce (in ms) */
+#else
+#define BATT_DEBOUNCE 80 /* Battery debounce (in ms) */
+#endif
+
+#define MINPEGTIME 10 * 8 /* 30 ms peak to peak gets us no more than 100 Hz */
+#define PEGTIME 50 * 8 /* 50ms peak to peak gets us rings of 10 Hz or more */
+#define PEGCOUNT 5 /* 5 cycles of pegging means RING */
+
+#define wcfxo_printk(level, span, fmt, ...) \
+ printk(KERN_ ## level "%s-%s: %s: " fmt, #level, \
+ THIS_MODULE->name, (span).name, ## __VA_ARGS__)
+
+#define wcfxo_notice(span, fmt, ...) \
+ wcfxo_printk(NOTICE, span, fmt, ## __VA_ARGS__)
+
+#define wcfxo_dbg(span, fmt, ...) \
+ ((void)((debug) && wcfxo_printk(DEBUG, span, "%s: " fmt, \
+ __FUNCTION__, ## __VA_ARGS__) ) )
+
+struct reg {
+ unsigned long flags;
+ unsigned char index;
+ unsigned char reg;
+ unsigned char value;
+};
+
+static int wecareregs[] =
+{
+ WC_DAA_DCTL1, WC_DAA_DCTL2, WC_DAA_PLL2_N2_M2, WC_DAA_CHIPA_REV,
+ WC_DAA_LINE_STAT, WC_DAA_CHIPB_REV, WC_DAA_INT_CTL2, WC_DAA_INT_CTL4,
+};
+
+struct wcfxo {
+ struct pci_dev *dev;
+ char *variety;
+ struct zt_span span;
+ struct zt_chan chan;
+ int usecount;
+ int dead;
+ int pos;
+ unsigned long flags;
+ int freeregion;
+ int ring;
+ int offhook;
+ int battery;
+ int wregcount;
+ int readpos;
+ int rreadpos;
+ unsigned int pegtimer;
+ int pegcount;
+ int peg;
+ int battdebounce;
+ int nobatttimer;
+ int ringdebounce;
+#ifdef JAPAN
+ int ohdebounce;
+#endif
+ int allread;
+ int regoffset; /* How far off our registers are from what we expect */
+ int alt;
+ int ignoreread;
+ int reset;
+ /* Up to 6 register can be written at a time */
+ struct reg regs[ZT_CHUNKSIZE];
+ struct reg oldregs[ZT_CHUNKSIZE];
+ unsigned char lasttx[ZT_CHUNKSIZE];
+ /* Up to 32 registers of whatever we most recently read */
+ unsigned char readregs[32];
+ unsigned long ioaddr;
+ dma_addr_t readdma;
+ dma_addr_t writedma;
+ volatile int *writechunk; /* Double-word aligned write memory */
+ volatile int *readchunk; /* Double-word aligned read memory */
+#ifdef ZERO_BATT_RING
+ int onhook;
+#endif
+#ifdef ENABLE_TASKLETS
+ int taskletrun;
+ int taskletsched;
+ int taskletpending;
+ int taskletexec;
+ int txerrors;
+ int ints;
+ struct tasklet_struct wcfxo_tlet;
+#endif
+};
+
+#define FLAG_INVERTSER (1 << 0)
+#define FLAG_USE_XTAL (1 << 1)
+#define FLAG_DOUBLE_CLOCK (1 << 2)
+#define FLAG_RESET_ON_AUX5 (1 << 3)
+#define FLAG_NO_I18N_REGS (1 << 4) /*!< Uses si3035, rather si3034 */
+
+struct wcfxo_desc {
+ char *name;
+ unsigned long flags;
+};
+
+
+static struct wcfxo_desc wcx100p = { "Wildcard X100P",
+ FLAG_INVERTSER | FLAG_USE_XTAL | FLAG_DOUBLE_CLOCK };
+
+static struct wcfxo_desc wcx101p = { "Wildcard X101P",
+ FLAG_USE_XTAL | FLAG_DOUBLE_CLOCK };
+
+static struct wcfxo_desc generic = { "Generic Clone",
+ FLAG_USE_XTAL | FLAG_DOUBLE_CLOCK };
+
+static struct wcfxo *ifaces[WC_MAX_IFACES];
+
+static void wcfxo_release(struct wcfxo *wc);
+
+static int debug = 0;
+
+static int monitor = 0;
+
+static int quiet = 0;
+
+static int boost = 0;
+
+static int opermode = 0;
+
+static struct fxo_mode {
+ char *name;
+ int ohs;
+ int act;
+ int dct;
+ int rz;
+ int rt;
+ int lim;
+ int vol;
+} fxo_modes[] =
+{
+ { "FCC", 0, 0, 2, 0, 0, 0, 0 }, /* US */
+ { "CTR21", 0, 0, 3, 0, 0, 3, 0 }, /* Austria, Belgium, Denmark, Finland, France, Germany,
+ Greece, Iceland, Ireland, Italy, Luxembourg, Netherlands,
+ Norway, Portugal, Spain, Sweden, Switzerland, and UK */
+};
+
+static inline void wcfxo_transmitprep(struct wcfxo *wc, unsigned char ints)
+{
+ volatile int *writechunk;
+ int x;
+ int written=0;
+ unsigned short cmd;
+
+ /* if nothing to transmit, have to do the zt_transmit() anyway */
+ if (!(ints & 3)) {
+ /* Calculate Transmission */
+ zt_transmit(&wc->span);
+ return;
+ }
+
+ /* Remember what it was we just sent */
+ memcpy(wc->lasttx, wc->chan.writechunk, ZT_CHUNKSIZE);
+
+ if (ints & 0x01) {
+ /* Write is at interrupt address. Start writing from normal offset */
+ writechunk = wc->writechunk;
+ } else {
+ writechunk = wc->writechunk + ZT_CHUNKSIZE * 2;
+ }
+
+ zt_transmit(&wc->span);
+
+ for (x=0;x<ZT_CHUNKSIZE;x++) {
+ /* Send a sample, as a 32-bit word, and be sure to indicate that a command follows */
+ if (wc->flags & FLAG_INVERTSER)
+ writechunk[x << 1] = cpu_to_le32(
+ ~((unsigned short)(ZT_XLAW(wc->chan.writechunk[x], (&wc->chan)))| 0x1) << 16
+ );
+ else
+ writechunk[x << 1] = cpu_to_le32(
+ ((unsigned short)(ZT_XLAW(wc->chan.writechunk[x], (&wc->chan)))| 0x1) << 16
+ );
+
+ /* We always have a command to follow our signal */
+ if (!wc->regs[x].flags) {
+ /* Fill in an empty register command with a read for a potentially useful register */
+ wc->regs[x].flags = FLAG_READ;
+ wc->regs[x].reg = wecareregs[wc->readpos];
+ wc->regs[x].index = wc->readpos;
+ wc->readpos++;
+ if (wc->readpos >= (sizeof(wecareregs) / sizeof(wecareregs[0]))) {
+ wc->allread = 1;
+ wc->readpos = 0;
+ }
+ }
+
+ /* Prepare the command to follow it */
+ switch(wc->regs[x].flags) {
+ case FLAG_READ:
+ cmd = (wc->regs[x].reg | 0x20) << 8;
+ break;
+ case FLAG_WRITE:
+ cmd = (wc->regs[x].reg << 8) | (wc->regs[x].value & 0xff);
+ written = 1;
+ /* Wait at least four samples before reading */
+ wc->ignoreread = 4;
+ break;
+ default:
+ printk("wcfxo: Huh? No read or write??\n");
+ cmd = 0;
+ }
+ /* Setup the write chunk */
+ if (wc->flags & FLAG_INVERTSER)
+ writechunk[(x << 1) + 1] = cpu_to_le32(~(cmd << 16));
+ else
+ writechunk[(x << 1) + 1] = cpu_to_le32(cmd << 16);
+ }
+ if (written)
+ wc->readpos = 0;
+ wc->wregcount = 0;
+
+ for (x=0;x<ZT_CHUNKSIZE;x++) {
+ /* Rotate through registers */
+ wc->oldregs[x] = wc->regs[x];
+ wc->regs[x].flags = FLAG_EMPTY;
+ }
+
+}
+
+static inline void wcfxo_receiveprep(struct wcfxo *wc, unsigned char ints)
+{
+ volatile int *readchunk;
+ int x;
+ int realreg;
+ int realval;
+ int sample;
+ if (ints & 0x04)
+ /* Read is at interrupt address. Valid data is available at normal offset */
+ readchunk = wc->readchunk;
+ else
+ readchunk = wc->readchunk + ZT_CHUNKSIZE * 2;
+
+ /* Keep track of how quickly our peg alternates */
+ wc->pegtimer+=ZT_CHUNKSIZE;
+ for (x=0;x<ZT_CHUNKSIZE;x++) {
+
+ /* We always have a command to follow our signal. */
+ if (wc->oldregs[x].flags == FLAG_READ && !wc->ignoreread) {
+ realreg = wecareregs[(wc->regs[x].index + wc->regoffset) %
+ (sizeof(wecareregs) / sizeof(wecareregs[0]))];
+ realval = (le32_to_cpu(readchunk[(x << 1) +wc->alt]) >> 16) & 0xff;
+ if ((realval == 0x89) && (realreg != WC_DAA_PLL2_N2_M2)) {
+ /* Some sort of slippage, correct for it */
+ while(realreg != WC_DAA_PLL2_N2_M2) {
+ /* Find register 9 */
+ realreg = wecareregs[(wc->regs[x].index + ++wc->regoffset) %
+ (sizeof(wecareregs) / sizeof(wecareregs[0]))];
+ wc->regoffset = wc->regoffset % (sizeof(wecareregs) / sizeof(wecareregs[0]));
+ }
+ if (debug)
+ printk("New regoffset: %d\n", wc->regoffset);
+ }
+ /* Receive into the proper register */
+ wc->readregs[realreg] = realval;
+ }
+ /* Look for pegging to indicate ringing */
+ sample = (short)(le32_to_cpu(readchunk[(x << 1) + (1 - wc->alt)]) >> 16);
+ if ((sample > 32000) && (wc->peg != 1)) {
+ if ((wc->pegtimer < PEGTIME) && (wc->pegtimer > MINPEGTIME))
+ wc->pegcount++;
+ wc->pegtimer = 0;
+ wc->peg = 1;
+ } else if ((sample < -32000) && (wc->peg != -1)) {
+ if ((wc->pegtimer < PEGTIME) && (wc->pegtimer > MINPEGTIME))
+ wc->pegcount++;
+ wc->pegtimer = 0;
+ wc->peg = -1;
+ }
+ wc->chan.readchunk[x] = ZT_LIN2X((sample), (&wc->chan));
+ }
+ if (wc->pegtimer > PEGTIME) {
+ /* Reset pegcount if our timer expires */
+ wc->pegcount = 0;
+ }
+ /* Decrement debouncer if appropriate */
+ if (wc->ringdebounce)
+ wc->ringdebounce--;
+ if (!wc->offhook && !wc->ringdebounce) {
+ if (!wc->ring && (wc->pegcount > PEGCOUNT)) {
+ /* It's ringing */
+ if (debug)
+ printk("RING!\n");
+ zt_hooksig(&wc->chan, ZT_RXSIG_RING);
+ wc->ring = 1;
+ }
+ if (wc->ring && !wc->pegcount) {
+ /* No more ring */
+ if (debug)
+ printk("NO RING!\n");
+ zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK);
+ wc->ring = 0;
+ }
+ }
+ if (wc->ignoreread)
+ wc->ignoreread--;
+
+ /* Do the echo cancellation... We are echo cancelling against
+ what we sent two chunks ago*/
+ zt_ec_chunk(&wc->chan, wc->chan.readchunk, wc->lasttx);
+
+ /* Receive the result */
+ zt_receive(&wc->span);
+}
+
+#ifdef ENABLE_TASKLETS
+static void wcfxo_tasklet(unsigned long data)
+{
+ struct wcfxo *wc = (struct wcfxo *)data;
+ wc->taskletrun++;
+ /* Run tasklet */
+ if (wc->taskletpending) {
+ wc->taskletexec++;
+ wcfxo_receiveprep(wc, wc->ints);
+ wcfxo_transmitprep(wc, wc->ints);
+ }
+ wc->taskletpending = 0;
+}
+#endif
+
+static void wcfxo_stop_dma(struct wcfxo *wc);
+static void wcfxo_restart_dma(struct wcfxo *wc);
+
+ZAP_IRQ_HANDLER(wcfxo_interrupt)
+{
+ struct wcfxo *wc = dev_id;
+ unsigned char ints;
+ unsigned char b;
+#ifdef DEBUG_RING
+ static int oldb = 0;
+ static int oldcnt = 0;
+#endif
+
+ ints = inb(wc->ioaddr + WC_INTSTAT);
+
+
+ if (!ints)
+#ifdef LINUX26
+ return IRQ_NONE;
+#else
+ return;
+#endif
+
+ outb(ints, wc->ioaddr + WC_INTSTAT);
+
+ if (ints & 0x0c) { /* if there is a rx interrupt pending */
+#ifdef ENABLE_TASKLETS
+ wc->ints = ints;
+ if (!wc->taskletpending) {
+ wc->taskletpending = 1;
+ wc->taskletsched++;
+ tasklet_hi_schedule(&wc->wcfxo_tlet);
+ } else
+ wc->txerrors++;
+#else
+ wcfxo_receiveprep(wc, ints);
+ /* transmitprep looks to see if there is anything to transmit
+ and returns by itself if there is nothing */
+ wcfxo_transmitprep(wc, ints);
+#endif
+ }
+
+ if (ints & 0x10) {
+ printk("FXO PCI Master abort\n");
+ /* Stop DMA andlet the watchdog start it again */
+ wcfxo_stop_dma(wc);
+#ifdef LINUX26
+ return IRQ_RETVAL(1);
+#else
+ return;
+#endif
+ }
+
+ if (ints & 0x20) {
+ printk("PCI Target abort\n");
+#ifdef LINUX26
+ return IRQ_RETVAL(1);
+#else
+ return;
+#endif
+ }
+ if (1 /* !(wc->report % 0xf) */) {
+ /* Check for BATTERY from register and debounce for 8 ms */
+ b = wc->readregs[WC_DAA_LINE_STAT] & 0xf;
+ if (!b) {
+ wc->nobatttimer++;
+#if 0
+ if (wc->battery)
+ printk("Battery loss: %d (%d debounce)\n", b, wc->battdebounce);
+#endif
+ if (wc->battery && !wc->battdebounce) {
+ if (debug)
+ printk("NO BATTERY!\n");
+ wc->battery = 0;
+#ifdef JAPAN
+ if ((!wc->ohdebounce) && wc->offhook) {
+ zt_hooksig(&wc->chan, ZT_RXSIG_ONHOOK);
+ if (debug)
+ printk("Signalled On Hook\n");
+#ifdef ZERO_BATT_RING
+ wc->onhook++;
+#endif
+ }
+#else
+ zt_hooksig(&wc->chan, ZT_RXSIG_ONHOOK);
+#endif
+ wc->battdebounce = BATT_DEBOUNCE;
+ } else if (!wc->battery)
+ wc->battdebounce = BATT_DEBOUNCE;
+ if ((wc->nobatttimer > 5000) &&
+#ifdef ZERO_BATT_RING
+ !(wc->readregs[WC_DAA_DCTL1] & 0x04) &&
+#endif
+ (!wc->span.alarms)) {
+ wc->span.alarms = ZT_ALARM_RED;
+ zt_alarm_notify(&wc->span);
+ }
+ } else if (b == 0xf) {
+ if (!wc->battery && !wc->battdebounce) {
+ if (debug)
+ printk("BATTERY!\n");
+#ifdef ZERO_BATT_RING
+ if (wc->onhook) {
+ wc->onhook = 0;
+ zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK);
+ if (debug)
+ printk("Signalled Off Hook\n");
+ }
+#else
+ zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK);
+#endif
+ wc->battery = 1;
+ wc->nobatttimer = 0;
+ wc->battdebounce = BATT_DEBOUNCE;
+ if (wc->span.alarms) {
+ wc->span.alarms = 0;
+ zt_alarm_notify(&wc->span);
+ }
+ } else if (wc->battery)
+ wc->battdebounce = BATT_DEBOUNCE;
+ } else {
+ /* It's something else... */
+ wc->battdebounce = BATT_DEBOUNCE;
+ }
+
+ if (wc->battdebounce)
+ wc->battdebounce--;
+#ifdef JAPAN
+ if (wc->ohdebounce)
+ wc->ohdebounce--;
+#endif
+
+ }
+#ifdef LINUX26
+ return IRQ_RETVAL(1);
+#endif
+}
+
+static int wcfxo_setreg(struct wcfxo *wc, unsigned char reg, unsigned char value)
+{
+ int x;
+ if (wc->wregcount < ZT_CHUNKSIZE) {
+ x = wc->wregcount;
+ wc->regs[x].reg = reg;
+ wc->regs[x].value = value;
+ wc->regs[x].flags = FLAG_WRITE;
+ wc->wregcount++;
+ return 0;
+ }
+ printk("wcfxo: Out of space to write register %02x with %02x\n", reg, value);
+ return -1;
+}
+
+static int wcfxo_open(struct zt_chan *chan)
+{
+ struct wcfxo *wc = chan->pvt;
+ if (wc->dead)
+ return -ENODEV;
+ wc->usecount++;
+#ifndef LINUX26
+ MOD_INC_USE_COUNT;
+#endif
+ return 0;
+}
+
+static int wcfxo_watchdog(struct zt_span *span, int event)
+{
+ printk("FXO: Restarting DMA\n");
+ wcfxo_restart_dma(span->pvt);
+ return 0;
+}
+
+static int wcfxo_close(struct zt_chan *chan)
+{
+ struct wcfxo *wc = chan->pvt;
+ wc->usecount--;
+#ifndef LINUX26
+ MOD_DEC_USE_COUNT;
+#endif
+ /* If we're dead, release us now */
+ if (!wc->usecount && wc->dead)
+ wcfxo_release(wc);
+ return 0;
+}
+
+static int wcfxo_hooksig(struct zt_chan *chan, zt_txsig_t txsig)
+{
+ struct wcfxo *wc = chan->pvt;
+ int reg=0;
+ switch(txsig) {
+ case ZT_TXSIG_START:
+ case ZT_TXSIG_OFFHOOK:
+ /* Take off hook and enable normal mode reception. This must
+ be done in two steps because of a hardware bug. */
+ reg = wc->readregs[WC_DAA_DCTL1] & ~0x08;
+ wcfxo_setreg(wc, WC_DAA_DCTL1, reg);
+
+ reg = reg | 0x1;
+ wcfxo_setreg(wc, WC_DAA_DCTL1, reg);
+ wc->offhook = 1;
+#ifdef JAPAN
+ wc->battery = 1;
+ wc->battdebounce = BATT_DEBOUNCE;
+ wc->ohdebounce = OH_DEBOUNCE;
+#endif
+ break;
+ case ZT_TXSIG_ONHOOK:
+ /* Put on hook and enable on hook line monitor */
+ reg = wc->readregs[WC_DAA_DCTL1] & 0xfe;
+ wcfxo_setreg(wc, WC_DAA_DCTL1, reg);
+
+ reg = reg | 0x08;
+ wcfxo_setreg(wc, WC_DAA_DCTL1, reg);
+ wc->offhook = 0;
+ /* Don't accept a ring for another 1000 ms */
+ wc->ringdebounce = 1000;
+#ifdef JAPAN
+ wc->ohdebounce = OH_DEBOUNCE;
+#endif
+ break;
+ default:
+ printk("wcfxo: Can't set tx state to %d\n", txsig);
+ }
+ if (debug)
+ printk("Setting hook state to %d (%02x)\n", txsig, reg);
+ return 0;
+}
+
+static int wcfxo_initialize(struct wcfxo *wc)
+{
+ /* Zapata stuff */
+ sprintf(wc->span.name, "WCFXO/%d", wc->pos);
+ snprintf(wc->span.desc, sizeof(wc->span.desc) - 1, "%s Board %d", wc->variety, wc->pos + 1);
+ sprintf(wc->chan.name, "WCFXO/%d/%d", wc->pos, 0);
+ snprintf(wc->span.location, sizeof(wc->span.location) - 1,
+ "PCI Bus %02d Slot %02d", wc->dev->bus->number, PCI_SLOT(wc->dev->devfn) + 1);
+ wc->span.manufacturer = "Digium";
+ zap_copy_string(wc->span.devicetype, wc->variety, sizeof(wc->span.devicetype));
+ wc->chan.sigcap = ZT_SIG_FXSKS | ZT_SIG_FXSLS | ZT_SIG_SF;
+ wc->chan.chanpos = 1;
+ wc->span.chans = &wc->chan;
+ wc->span.channels = 1;
+ wc->span.hooksig = wcfxo_hooksig;
+ wc->span.irq = wc->dev->irq;
+ wc->span.open = wcfxo_open;
+ wc->span.close = wcfxo_close;
+ wc->span.flags = ZT_FLAG_RBS;
+ wc->span.deflaw = ZT_LAW_MULAW;
+ wc->span.watchdog = wcfxo_watchdog;
+#ifdef ENABLE_TASKLETS
+ tasklet_init(&wc->wcfxo_tlet, wcfxo_tasklet, (unsigned long)wc);
+#endif
+ init_waitqueue_head(&wc->span.maintq);
+
+ wc->span.pvt = wc;
+ wc->chan.pvt = wc;
+ if (zt_register(&wc->span, 0)) {
+ printk("Unable to register span with zaptel\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int wcfxo_hardware_init(struct wcfxo *wc)
+{
+ /* Hardware stuff */
+ /* Reset PCI Interface chip and registers */
+ outb(0x0e, wc->ioaddr + WC_CNTL);
+ if (wc->flags & FLAG_RESET_ON_AUX5) {
+ /* Set hook state to on hook for when we switch.
+ Make sure reset is high */
+ outb(0x34, wc->ioaddr + WC_AUXD);
+ } else {
+ /* Set hook state to on hook for when we switch */
+ outb(0x24, wc->ioaddr + WC_AUXD);
+ }
+ /* Set all to outputs except AUX 4, which is an input */
+ outb(0xef, wc->ioaddr + WC_AUXC);
+
+ /* Back to normal, with automatic DMA wrap around */
+ outb(0x01, wc->ioaddr + WC_CNTL);
+
+ /* Make sure serial port and DMA are out of reset */
+ outb(inb(wc->ioaddr + WC_CNTL) & 0xf9, wc->ioaddr + WC_CNTL);
+
+ /* Configure serial port for MSB->LSB operation */
+ if (wc->flags & FLAG_DOUBLE_CLOCK)
+ outb(0xc1, wc->ioaddr + WC_SERCTL);
+ else
+ outb(0xc0, wc->ioaddr + WC_SERCTL);
+
+ if (wc->flags & FLAG_USE_XTAL) {
+ /* Use the crystal oscillator */
+ outb(0x04, wc->ioaddr + WC_AUXFUNC);
+ }
+
+ /* Delay FSC by 2 so it's properly aligned */
+ outb(0x2, wc->ioaddr + WC_FSCDELAY);
+
+ /* Setup DMA Addresses */
+ outl(wc->writedma, wc->ioaddr + WC_DMAWS); /* Write start */
+ outl(wc->writedma + ZT_CHUNKSIZE * 8 - 4, wc->ioaddr + WC_DMAWI); /* Middle (interrupt) */
+ outl(wc->writedma + ZT_CHUNKSIZE * 16 - 4, wc->ioaddr + WC_DMAWE); /* End */
+
+ outl(wc->readdma, wc->ioaddr + WC_DMARS); /* Read start */
+ outl(wc->readdma + ZT_CHUNKSIZE * 8 - 4, wc->ioaddr + WC_DMARI); /* Middle (interrupt) */
+ outl(wc->readdma + ZT_CHUNKSIZE * 16 - 4, wc->ioaddr + WC_DMARE); /* End */
+
+ /* Clear interrupts */
+ outb(0xff, wc->ioaddr + WC_INTSTAT);
+ return 0;
+}
+
+static void wcfxo_enable_interrupts(struct wcfxo *wc)
+{
+ /* Enable interrupts (we care about all of them) */
+ outb(0x3f, wc->ioaddr + WC_MASK0);
+ /* No external interrupts */
+ outb(0x00, wc->ioaddr + WC_MASK1);
+}
+
+static void wcfxo_start_dma(struct wcfxo *wc)
+{
+ /* Reset Master and TDM */
+ outb(0x0f, wc->ioaddr + WC_CNTL);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ outb(0x01, wc->ioaddr + WC_CNTL);
+ outb(0x01, wc->ioaddr + WC_OPER);
+}
+
+static void wcfxo_restart_dma(struct wcfxo *wc)
+{
+ /* Reset Master and TDM */
+ outb(0x01, wc->ioaddr + WC_CNTL);
+ outb(0x01, wc->ioaddr + WC_OPER);
+}
+
+
+static void wcfxo_stop_dma(struct wcfxo *wc)
+{
+ outb(0x00, wc->ioaddr + WC_OPER);
+}
+
+static void wcfxo_reset_tdm(struct wcfxo *wc)
+{
+ /* Reset TDM */
+ outb(0x0f, wc->ioaddr + WC_CNTL);
+}
+
+static void wcfxo_disable_interrupts(struct wcfxo *wc)
+{
+ outb(0x00, wc->ioaddr + WC_MASK0);
+ outb(0x00, wc->ioaddr + WC_MASK1);
+}
+
+static void wcfxo_set_daa_mode(struct wcfxo *wc)
+{
+ /* Set country specific parameters (OHS, ACT, DCT, RZ, RT, LIM, VOL) */
+ int reg16 = ((fxo_modes[opermode].ohs & 0x1) << 6) |
+ ((fxo_modes[opermode].act & 0x1) << 5) |
+ ((fxo_modes[opermode].dct & 0x3) << 2) |
+ ((fxo_modes[opermode].rz & 0x1) << 1) |
+ ((fxo_modes[opermode].rt & 0x1) << 0);
+ int reg17 = ((fxo_modes[opermode].lim & 0x3) << 3);
+ int reg18 = ((fxo_modes[opermode].vol & 0x3) << 3);
+
+ if (wc->flags & FLAG_NO_I18N_REGS) {
+ wcfxo_dbg(wc->span, "This card does not support international settings.\n");
+ return;
+ }
+
+ wcfxo_setreg(wc, WC_DAA_INT_CTL1, reg16);
+ wcfxo_setreg(wc, WC_DAA_INT_CTL2, reg17);
+ wcfxo_setreg(wc, WC_DAA_INT_CTL3, reg18);
+
+
+ /* Wait a couple of jiffies for our writes to finish */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1 + (ZT_CHUNKSIZE * HZ) / 800);
+
+ printk("wcfxo: DAA mode is '%s'\n", fxo_modes[opermode].name);
+}
+
+static int wcfxo_init_daa(struct wcfxo *wc)
+{
+ /* This must not be called in an interrupt */
+ /* We let things settle for a bit */
+ unsigned char reg15;
+ int chip_revb;
+// set_current_state(TASK_INTERRUPTIBLE);
+// schedule_timeout(10);
+
+ /* Soft-reset it */
+ wcfxo_setreg(wc, WC_DAA_CTL1, 0x80);
+
+ /* Let the reset go */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1 + (ZT_CHUNKSIZE * HZ) / 800);
+
+ /* We have a clock at 18.432 Mhz, so N1=1, M1=2, CGM=0 */
+ wcfxo_setreg(wc, WC_DAA_PLL1_N1, 0x0); /* This value is N1 - 1 */
+ wcfxo_setreg(wc, WC_DAA_PLL1_M1, 0x1); /* This value is M1 - 1 */
+ /* We want to sample at 8khz, so N2 = 9, M2 = 10 (N2-1, M2-1) */
+ wcfxo_setreg(wc, WC_DAA_PLL2_N2_M2, 0x89);
+
+ /* Wait until the PLL's are locked. Time is between 100 uSec and 1 mSec */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1 + HZ/1000 + (ZT_CHUNKSIZE * HZ) / 800);
+
+ /* No additional ration is applied to the PLL and faster lock times
+ * are possible */
+ wcfxo_setreg(wc, WC_DAA_PLL_CTL, 0x0);
+ /* Enable off hook pin */
+ wcfxo_setreg(wc, WC_DAA_DCTL1, 0x0a);
+ if (monitor) {
+ /* Enable ISOcap and external speaker and charge pump if present */
+ wcfxo_setreg(wc, WC_DAA_DCTL2, 0x80);
+ } else {
+ /* Enable ISOcap and charge pump if present (leave speaker disabled) */
+ wcfxo_setreg(wc, WC_DAA_DCTL2, 0xe0);
+ }
+
+ /* Wait a couple of jiffies for our writes to finish */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1 + (ZT_CHUNKSIZE * HZ) / 800);
+ reg15 = 0x0;
+ /* Go ahead and attenuate transmit signal by 6 db */
+ if (quiet) {
+ printk("wcfxo: Attenuating transmit signal for quiet operation\n");
+ reg15 |= (quiet & 0x3) << 4;
+ }
+ if (boost) {
+ printk("wcfxo: Boosting receive signal\n");
+ reg15 |= (boost & 0x3);
+ }
+ wcfxo_setreg(wc, WC_DAA_TXRX_GCTL, reg15);
+
+ /* REVB: reg. 13, bits 5:2 */
+ chip_revb = (wc->readregs[WC_DAA_CHIPB_REV] >> 2) & 0xF;
+ wcfxo_dbg(wc->span, "DAA chip REVB is %x\n", chip_revb);
+ switch(chip_revb) {
+ case 1: case 2: case 3:
+ /* This is a si3034. Nothing to do */
+ break;
+ case 4: case 5: case 7:
+ /* This is 3035. Has no support for international registers */
+ wc->flags |= FLAG_NO_I18N_REGS;
+ break;
+ default:
+ wcfxo_notice(wc->span, "Unknown DAA chip revision: REVB=%d\n",
+ chip_revb);
+ }
+
+ /* Didn't get it right. Register 9 is still garbage */
+ if (wc->readregs[WC_DAA_PLL2_N2_M2] != 0x89)
+ return -1;
+#if 0
+ { int x;
+ int y;
+ for (y=0;y<100;y++) {
+ printk(" reg dump ====== %d ======\n", y);
+ for (x=0;x<sizeof(wecareregs) / sizeof(wecareregs[0]);x++) {
+ printk("daa: Reg %d: %02x\n", wecareregs[x], wc->readregs[wecareregs[x]]);
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(100);
+ } }
+#endif
+ return 0;
+}
+
+static int __devinit wcfxo_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct wcfxo *wc;
+ struct wcfxo_desc *d = (struct wcfxo_desc *)ent->driver_data;
+ int x;
+
+ for (x=0;x<WC_MAX_IFACES;x++)
+ if (!ifaces[x]) break;
+ if (x >= WC_MAX_IFACES) {
+ printk(KERN_ERR "Too many interfaces: Found %d, can only handle %d.\n",
+ x, WC_MAX_IFACES - 1);
+ return -EIO;
+ }
+
+ if (pci_enable_device(pdev))
+ return -EIO;
+
+ wc = kmalloc(sizeof(struct wcfxo), GFP_KERNEL);
+ if (!wc) {
+ printk(KERN_ERR "wcfxo: Failed initializinf card. Not enough memory.");
+ return -ENOMEM;
+ }
+
+ ifaces[x] = wc;
+ memset(wc, 0, sizeof(struct wcfxo));
+ wc->ioaddr = pci_resource_start(pdev, 0);
+ wc->dev = pdev;
+ wc->pos = x;
+ wc->variety = d->name;
+ wc->flags = d->flags;
+ /* Keep track of whether we need to free the region */
+ if (request_region(wc->ioaddr, 0xff, "wcfxo"))
+ wc->freeregion = 1;
+
+ /* Allocate enough memory for two zt chunks, receive and transmit. Each sample uses
+ 32 bits. Allocate an extra set just for control too */
+ wc->writechunk = (int *)pci_alloc_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, &wc->writedma);
+ if (!wc->writechunk) {
+ printk("wcfxo: Unable to allocate DMA-able memory\n");
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ return -ENOMEM;
+ }
+
+ wc->readchunk = wc->writechunk + ZT_MAX_CHUNKSIZE * 4; /* in doublewords */
+ wc->readdma = wc->writedma + ZT_MAX_CHUNKSIZE * 16; /* in bytes */
+
+ if (wcfxo_initialize(wc)) {
+ printk("wcfxo: Unable to intialize modem\n");
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ return -EIO;
+ }
+
+ /* Enable bus mastering */
+ pci_set_master(pdev);
+
+ /* Keep track of which device we are */
+ pci_set_drvdata(pdev, wc);
+
+ if (request_irq(pdev->irq, wcfxo_interrupt, ZAP_IRQ_SHARED, "wcfxo", wc)) {
+ printk("wcfxo: Unable to request IRQ %d\n", pdev->irq);
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ return -EIO;
+ }
+
+
+ wcfxo_hardware_init(wc);
+ /* Enable interrupts */
+ wcfxo_enable_interrupts(wc);
+ /* Initialize Write/Buffers to all blank data */
+ memset((void *)wc->writechunk,0,ZT_MAX_CHUNKSIZE * 2 * 2 * 2 * 4);
+ /* Start DMA */
+ wcfxo_start_dma(wc);
+
+ /* Initialize DAA (after it's started) */
+ if (wcfxo_init_daa(wc)) {
+ printk("Failed to initailize DAA, giving up...\n");
+ wcfxo_stop_dma(wc);
+ wcfxo_disable_interrupts(wc);
+ zt_unregister(&wc->span);
+ free_irq(pdev->irq, wc);
+
+ /* Reset PCI chip and registers */
+ outb(0x0e, wc->ioaddr + WC_CNTL);
+
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ return -EIO;
+ }
+ wcfxo_set_daa_mode(wc);
+ printk("Found a Wildcard FXO: %s\n", wc->variety);
+
+ return 0;
+}
+
+static void wcfxo_release(struct wcfxo *wc)
+{
+ zt_unregister(&wc->span);
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ printk("Freed a Wildcard\n");
+}
+
+static void __devexit wcfxo_remove_one(struct pci_dev *pdev)
+{
+ struct wcfxo *wc = pci_get_drvdata(pdev);
+ if (wc) {
+
+ /* Stop any DMA */
+ wcfxo_stop_dma(wc);
+ wcfxo_reset_tdm(wc);
+
+ /* In case hardware is still there */
+ wcfxo_disable_interrupts(wc);
+
+ /* Immediately free resources */
+ pci_free_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, (void *)wc->writechunk, wc->writedma);
+ free_irq(pdev->irq, wc);
+
+ /* Reset PCI chip and registers */
+ outb(0x0e, wc->ioaddr + WC_CNTL);
+
+ /* Release span, possibly delayed */
+ if (!wc->usecount)
+ wcfxo_release(wc);
+ else
+ wc->dead = 1;
+ }
+}
+
+static struct pci_device_id wcfxo_pci_tbl[] = {
+ { 0xe159, 0x0001, 0x8084, PCI_ANY_ID, 0, 0, (unsigned long) &generic },
+ { 0xe159, 0x0001, 0x8085, PCI_ANY_ID, 0, 0, (unsigned long) &wcx101p },
+ { 0xe159, 0x0001, 0x8086, PCI_ANY_ID, 0, 0, (unsigned long) &generic },
+ { 0xe159, 0x0001, 0x8087, PCI_ANY_ID, 0, 0, (unsigned long) &generic },
+ { 0x1057, 0x5608, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) &wcx100p },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE (pci, wcfxo_pci_tbl);
+
+static struct pci_driver wcfxo_driver = {
+ name: "wcfxo",
+ probe: wcfxo_init_one,
+#ifdef LINUX26
+ remove: __devexit_p(wcfxo_remove_one),
+#else
+ remove: wcfxo_remove_one,
+#endif
+ id_table: wcfxo_pci_tbl,
+};
+
+static int __init wcfxo_init(void)
+{
+ int res;
+ int x;
+ if ((opermode >= sizeof(fxo_modes) / sizeof(fxo_modes[0])) || (opermode < 0)) {
+ printk("Invalid/unknown operating mode specified. Please choose one of:\n");
+ for (x=0;x<sizeof(fxo_modes) / sizeof(fxo_modes[0]); x++)
+ printk("%d: %s\n", x, fxo_modes[x].name);
+ return -ENODEV;
+ }
+ res = zap_pci_module(&wcfxo_driver);
+ if (res)
+ return -ENODEV;
+ return 0;
+}
+
+static void __exit wcfxo_cleanup(void)
+{
+ pci_unregister_driver(&wcfxo_driver);
+}
+
+#ifdef LINUX26
+module_param(debug, int, 0644);
+module_param(quiet, int, 0444);
+module_param(boost, int, 0444);
+module_param(monitor, int, 0444);
+module_param(opermode, int, 0444);
+#else
+MODULE_PARM(debug, "i");
+MODULE_PARM(quiet, "i");
+MODULE_PARM(boost, "i");
+MODULE_PARM(monitor, "i");
+MODULE_PARM(opermode, "i");
+#endif
+MODULE_DESCRIPTION("Wildcard X100P Zaptel Driver");
+MODULE_AUTHOR("Mark Spencer <markster@digium.com>");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+
+module_init(wcfxo_init);
+module_exit(wcfxo_cleanup);