summaryrefslogtreecommitdiff
path: root/wcfxs.c
diff options
context:
space:
mode:
authormarkster <markster@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2001-12-05 18:38:19 +0000
committermarkster <markster@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2001-12-05 18:38:19 +0000
commit07d2942df868bbed234e7c7c3c23ba7356c977f4 (patch)
tree410041528d87b3f173d39e16a86bc4b6a977b4e2 /wcfxs.c
parent021d482685b38bddbb3cf3b282363a657e072be5 (diff)
Version 0.1.4 from FTP
git-svn-id: http://svn.digium.com/svn/zaptel/trunk@34 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'wcfxs.c')
-rwxr-xr-xwcfxs.c834
1 files changed, 834 insertions, 0 deletions
diff --git a/wcfxs.c b/wcfxs.c
new file mode 100755
index 0000000..88e39ff
--- /dev/null
+++ b/wcfxs.c
@@ -0,0 +1,834 @@
+/*
+ * Wilcard S100P FXS Interface Driver for Zapata Telephony interface
+ *
+ * Written by Mark Spencer <markster@linux-support.net>
+ * Matthew Fredrickson <creslin@linux-support.net>
+ *
+ * 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 "proslic.h"
+
+static alpha indirect_regs[] =
+{
+{0,"DTMF_ROW_0_PEAK",0x55C2},
+{1,"DTMF_ROW_1_PEAK",0x51E6},
+{2,"DTMF_ROW2_PEAK",0x4B85},
+{3,"DTMF_ROW3_PEAK",0x4937},
+{4,"DTMF_COL1_PEAK",0x3333},
+{5,"DTMF_FWD_TWIST",0x0202},
+{6,"DTMF_RVS_TWIST",0x0202},
+{7,"DTMF_ROW_RATIO_TRES",0x0198},
+{8,"DTMF_COL_RATIO_TRES",0x0198},
+{9,"DTMF_ROW_2ND_ARM",0x0611},
+{10,"DTMF_COL_2ND_ARM",0x0202},
+{11,"DTMF_PWR_MIN_TRES",0x00E5},
+{12,"DTMF_OT_LIM_TRES",0x0A1C},
+{13,"OSC1_COEF",0x6D40},
+{14,"OSC1X",0x0470},
+{15,"OSC1Y",0x0000},
+{16,"OSC2_COEF",0x4A80},
+{17,"OSC2X",0x0830},
+{18,"OSC2Y",0x0000},
+{19,"RING_V_OFF",0x0000},
+{20,"RING_OSC",0x7EF0},
+{21,"RING_X",0x0160},
+{22,"RING_Y",0x0000},
+{23,"PULSE_ENVEL",0x2000},
+{24,"PULSE_X",0x2000},
+{25,"PULSE_Y",0x0000},
+//{26,"RECV_DIGITAL_GAIN",0x4000}, // playback volume set lower
+{26,"RECV_DIGITAL_GAIN",0x2000}, // playback volume set lower
+{27,"XMIT_DIGITAL_GAIN",0x4000},
+{28,"LOOP_CLOSE_TRES",0x1000},
+{29,"RING_TRIP_TRES",0x3600},
+{30,"COMMON_MIN_TRES",0x1000},
+{31,"COMMON_MAX_TRES",0x0200},
+{32,"PWR_ALARM_Q1Q2",0x0550},
+{33,"PWR_ALARM_Q3Q4",0x2600},
+{34,"PWR_ALARM_Q5Q6",0x1B80},
+{35,"LOOP_CLOSURE_FILTER",0x8000},
+{36,"RING_TRIP_FILTER",0x0320},
+{37,"TERM_LP_POLE_Q1Q2",0x0100},
+{38,"TERM_LP_POLE_Q3Q4",0x0100},
+{39,"TERM_LP_POLE_Q5Q6",0x0010},
+{40,"CM_BIAS_RINGING",0x0C00},
+{41,"DCDC_MIN_V",0x0C00},
+{42,"DCDC_XTRA",0x1000},
+};
+
+#ifdef STANDALONE_ZAPATA
+#include "zaptel.h"
+#else
+#include <linux/zaptel.h>
+#endif
+
+#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_AUXR 0x07
+
+#define WC_DMAWS 0x08
+#define WC_DMAWI 0x0c
+#define WC_DMAWE 0x10
+#define WC_DMARS 0x18
+#define WC_DMARI 0x1c
+#define WC_DMARE 0x20
+
+#define WC_AUXFUNC 0x2b
+#define WC_SERCTL 0x2d
+#define WC_FSCDELAY 0x2f
+
+#define BIT_CS (1 << 2)
+#define BIT_SCLK (1 << 3)
+#define BIT_SDI (1 << 4)
+#define BIT_SDO (1 << 5)
+
+#define FLAG_EMPTY 0
+#define FLAG_WRITE 1
+#define FLAG_READ 2
+
+#define RING_DEBOUNCE 64 /* Ringer Debounce (in ms) */
+#define BATT_DEBOUNCE 8 /* Battery debounce (in ms) */
+
+#define FLAG_DOUBLE_CLOCK (1 << 0)
+
+struct wcfxs {
+ struct pci_dev *dev;
+ char *variety;
+ struct zt_span span;
+ struct zt_chan chan;
+ unsigned char ios;
+ int usecount;
+ int intcount;
+ int dead;
+ int pos;
+ int flags;
+ int freeregion;
+ int alt;
+
+ /* Receive hook state and debouncing */
+ int oldrxhook;
+ int debouncehook;
+ int lastrxhook;
+ int debounce;
+
+ int idletxhookstate; /* IDLE changing hook state */
+ 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 */
+};
+
+
+struct wcfxs_desc {
+ char *name;
+ int flags;
+};
+
+static struct wcfxs_desc wcfxs = { "Wildcard Prototype", 0 };
+
+static struct wcfxs *ifaces[WC_MAX_IFACES];
+
+static void wcfxs_release(struct wcfxs *wc);
+
+static int debug = 0;
+
+static inline void wcfxs_transmitprep(struct wcfxs *wc, unsigned char ints)
+{
+ volatile unsigned int *writechunk;
+ int x;
+ if (ints & 0x01)
+ /* Write is at interrupt address. Start writing from normal offset */
+ writechunk = wc->writechunk;
+ else
+ writechunk = wc->writechunk + ZT_CHUNKSIZE;
+ /* Calculate Transmission */
+ zt_transmit(&wc->span);
+
+ for (x=0;x<ZT_CHUNKSIZE;x++) {
+ /* Send a sample, as a 32-bit word */
+ writechunk[x] = wc->chan.writechunk[x] << 24;
+
+ }
+
+}
+
+static inline void wcfxs_receiveprep(struct wcfxs *wc, unsigned char ints)
+{
+ volatile unsigned int *readchunk;
+ int x;
+
+ if (ints & 0x08)
+ /* Read is at interrupt address. Valid data is available at normal offset */
+ readchunk = wc->readchunk;
+ else
+ readchunk = wc->readchunk + ZT_CHUNKSIZE;
+ for (x=0;x<ZT_CHUNKSIZE;x++) {
+ wc->chan.readchunk[x] = (readchunk[x] >> 24) & 0xff;
+ }
+
+ zt_receive(&wc->span);
+}
+
+static inline void wcfxs_check_hook(struct wcfxs *wc);
+
+static void wcfxs_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct wcfxs *wc = dev_id;
+ unsigned char ints;
+
+ ints = inb(wc->ioaddr + WC_INTSTAT);
+ outb(ints, wc->ioaddr + WC_INTSTAT);
+
+
+ if (!ints)
+ return;
+ if (ints & 0x10) {
+ printk("PCI Master abort\n");
+ return;
+ }
+
+ if (ints & 0x20) {
+ printk("PCI Target abort\n");
+ return;
+ }
+ if (ints & 0x0f) {
+ wc->intcount++;
+ if (!(wc->intcount % 10))
+ wcfxs_check_hook(wc);
+ wcfxs_transmitprep(wc, ints);
+ wcfxs_receiveprep(wc, ints);
+ }
+
+}
+
+static inline void write_8bits(struct wcfxs *wc, unsigned char bits)
+{
+ /* Drop chip select */
+ int x;
+ wc->ios &= ~BIT_CS;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ for (x=0;x<8;x++) {
+ /* Send out each bit, MSB first, drop SCLK as we do so */
+ if (bits & 0x80)
+ wc->ios |= BIT_SDI;
+ else
+ wc->ios &= ~BIT_SDI;
+ wc->ios &= ~BIT_SCLK;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ /* Now raise SCLK high again and repeat */
+ wc->ios |= BIT_SCLK;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ bits <<= 1;
+ }
+ /* Finally raise CS back high again */
+ wc->ios |= BIT_CS;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+
+}
+static inline unsigned char read_8bits(struct wcfxs *wc)
+{
+ unsigned char res=0, c;
+ int x;
+ /* Drop chip select */
+ wc->ios &= ~BIT_CS;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ for (x=0;x<8;x++) {
+ res <<= 1;
+ /* Get SCLK */
+ wc->ios &= ~BIT_SCLK;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ /* Now raise SCLK high again */
+ wc->ios |= BIT_SCLK;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+
+ /* Read back the value */
+ c = inb(wc->ioaddr + WC_AUXR);
+ if (c & BIT_SDO)
+ res |= 1;
+ }
+ /* Finally raise CS back high again */
+ wc->ios |= BIT_CS;
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+
+ /* And return our result */
+ return res;
+}
+
+static void wcfxs_setreg(struct wcfxs *wc, unsigned char reg, unsigned char value)
+{
+ write_8bits(wc, reg & 0x7f);
+ write_8bits(wc, value);
+}
+
+static unsigned char wcfxs_getreg(struct wcfxs *wc, unsigned char reg)
+{
+ write_8bits(wc, reg | 0x80);
+ return read_8bits(wc);
+}
+
+static int wait_access(struct wcfxs *wc)
+{
+ unsigned char count, data;
+ count = 0;
+ #define MAX 60
+
+
+ /* Wait for indirect access */
+ while (count++ < MAX)
+ {
+ data = wcfxs_getreg(wc, I_STATUS);
+
+ if (!data)
+ return 0;
+
+ }
+
+ if(count > (MAX-1)) printk(" ##### Loop error #####\n");
+
+ return -1;
+}
+
+static int wcfxs_setreg_indirect(struct wcfxs *wc, unsigned char address, unsigned short data)
+{
+
+ if(!wait_access(wc))
+ {
+ wcfxs_setreg(wc, IDA_LO,(unsigned char)(data & 0xFF));
+ wcfxs_setreg(wc, IDA_HI,(unsigned char)((data & 0xFF00)>>8));
+ wcfxs_setreg(wc, IAA,address);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int wcfxs_getreg_indirect(struct wcfxs *wc, unsigned char address)
+{
+ if (!wait_access(wc)) {
+ wcfxs_setreg(wc, IAA, address);
+ if (!wait_access(wc)) {
+ unsigned char data1, data2;
+ data1 = wcfxs_getreg(wc, IDA_LO);
+ data2 = wcfxs_getreg(wc, IDA_HI);
+ return data1 | (data2 << 8);
+ } else
+ printk("Failed to wait inside\n");
+ } else
+ printk("failed to wait\n");
+
+ return -1;
+}
+
+static int wcfxs_init_indirect_regs(struct wcfxs *wc)
+{
+ unsigned char i;
+
+ for (i=0; i<43; i++)
+ {
+ if(wcfxs_setreg_indirect(wc, i,indirect_regs[i].initial))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int wcfxs_verify_indirect_regs(struct wcfxs *wc)
+{
+ int passed = 1;
+ unsigned short i, initial;
+ int j;
+
+ for (i=0; i<43; i++)
+ {
+ if((j = wcfxs_getreg_indirect(wc, (unsigned char) i)) < 0) {
+ printk("Failed to read indirect register %d\n", i);
+ return -1;
+ }
+ initial= indirect_regs[i].initial;
+
+ if ( j != initial )
+ {
+ printk("!!!!!!! %s iREG %X = %X should be %X\n",
+ indirect_regs[i].name,i,j,initial );
+ passed = 0;
+ }
+ }
+
+ if (passed) {
+ if (debug)
+ printk("Init Indirect Registers completed successfully.\n");
+ } else {
+ printk(" !!!!! Init Indirect Registers UNSUCCESSFULLY.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int wcfxs_calibrate(struct wcfxs *wc)
+{
+ unsigned char x;
+
+ wcfxs_setreg(wc, 92, 0xc8);
+ wcfxs_setreg(wc, 97, 0);
+ wcfxs_setreg(wc, 93, 0x19);
+ wcfxs_setreg(wc, 14, 0);
+ wcfxs_setreg(wc, 93, 0x99);
+
+ x = wcfxs_getreg(wc, 93);
+ if (debug)
+ printk("DC Cal x=%x\n",x);
+ wcfxs_setreg(wc, 97, 0);
+ wcfxs_setreg(wc, CALIBR1, CALIBRATE_LINE);
+ x = wcfxs_getreg(wc, CALIBR1);
+ wcfxs_setreg(wc, LINE_STATE, ACTIVATE_LINE);
+
+ return 0;
+}
+
+static int wcfxs_init_proslic(struct wcfxs *wc)
+{
+ int blah;
+
+ /* By default, always send on hook */
+ wc->idletxhookstate = 2;
+
+ /* Disable Auto Power Alarm Detect and other "features" */
+ wcfxs_setreg(wc, 67, 0x0e);
+ blah = wcfxs_getreg(wc, 67);
+
+ if (wcfxs_init_indirect_regs(wc)) {
+ printk(KERN_INFO "Indirect Registers failed to initialize.\n");
+ return -1;
+ }
+ if (wcfxs_verify_indirect_regs(wc)) {
+ printk(KERN_INFO "Indirect Registers failed verification.\n");
+ return -1;
+ }
+ if (wcfxs_calibrate(wc)) {
+ printk(KERN_INFO "ProSlic Died on Activation.\n");
+ return -1;
+ }
+ if (wcfxs_setreg_indirect(wc, 97, 0x0)) { // Stanley: for the bad recording fix
+ printk(KERN_INFO "ProSlic IndirectReg Died.\n");
+ return -1;
+ }
+ wcfxs_setreg(wc, 1, 0x2a);
+ // U-Law GCI 8-bit interface
+ wcfxs_setreg(wc, 2, 0); // Tx Start count low byte 0
+ wcfxs_setreg(wc, 3, 0); // Tx Start count high byte 0
+ wcfxs_setreg(wc, 4, 0); // Rx Start count low byte 0
+ wcfxs_setreg(wc, 5, 0); // Rx Start count high byte 0
+ wcfxs_setreg(wc, 8, 0x0); // disable loopback
+ wcfxs_setreg(wc, 18, 0xff); // clear all interrupt
+ wcfxs_setreg(wc, 19, 0xff);
+ wcfxs_setreg(wc, 20, 0xff);
+ wcfxs_setreg(wc, 21, 0x00); // enable interrupt
+ wcfxs_setreg(wc, 22, 0x02); // Loop detection interrupt
+ wcfxs_setreg(wc, 23, 0x01); // DTMF detection interrupt
+ wcfxs_setreg(wc, 72, 0x20);
+#ifdef BOOST_RINGER
+ /* Beef up Ringing voltage to 89V */
+ if (wcfxs_setreg_indirect(wc, 23, 0x1d1))
+ return -1;
+#endif
+ return 0;
+}
+
+static inline void wcfxs_check_hook(struct wcfxs *wc)
+{
+ char res;
+ int hook;
+
+ /* For some reason we have to debounce the
+ hook detector. */
+
+ res = wcfxs_getreg(wc, 68);
+ hook = (res & 1);
+ if (hook != wc->lastrxhook) {
+ /* Reset the debounce */
+ wc->debounce = 3;
+ } else {
+ if (wc->debounce > -1)
+ wc->debounce--;
+ }
+ wc->lastrxhook = hook;
+ if (!wc->debounce)
+ wc->debouncehook = hook;
+
+ if (!wc->oldrxhook && wc->debouncehook) {
+ /* Off hook */
+ if (debug)
+ printk("wcfxs: Going off hook\n");
+ zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK);
+ wc->oldrxhook = 1;
+
+ } else if (wc->oldrxhook && !wc->debouncehook) {
+ /* On hook */
+ if (debug)
+ printk("wcfxs: Going on hook\n");
+ zt_hooksig(&wc->chan, ZT_RXSIG_ONHOOK);
+ wc->oldrxhook = 0;
+ }
+
+}
+
+static int wcfxs_open(struct zt_chan *chan)
+{
+ struct wcfxs *wc = chan->pvt;
+ if (wc->dead)
+ return -ENODEV;
+ wc->usecount++;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int wcfxs_close(struct zt_chan *chan)
+{
+ struct wcfxs *wc = chan->pvt;
+ wc->usecount--;
+ MOD_DEC_USE_COUNT;
+ /* If we're dead, release us now */
+ if (!wc->usecount && wc->dead)
+ wcfxs_release(wc);
+ return 0;
+}
+
+static int wcfxs_hooksig(struct zt_chan *chan, zt_txsig_t txsig)
+{
+ struct wcfxs *wc = chan->pvt;
+ int reg=0;
+ unsigned char txhook = 0;
+ switch(txsig) {
+ case ZT_TXSIG_ONHOOK:
+ switch(chan->sig) {
+ case ZT_SIG_FXOKS:
+ case ZT_SIG_FXOLS:
+ txhook = wc->idletxhookstate;
+ break;
+ case ZT_SIG_FXOGS:
+ txhook = 3;
+ break;
+ }
+ break;
+ case ZT_TXSIG_OFFHOOK:
+ txhook = wc->idletxhookstate;
+ break;
+ case ZT_TXSIG_START:
+ txhook = 4;
+ break;
+ case ZT_TXSIG_KEWL:
+ txhook = 0;
+ break;
+ default:
+ printk("wcfxs: Can't set tx state to %d\n", txsig);
+ }
+ if (debug)
+ printk("Setting hook state to %d (%02x)\n", txsig, reg);
+
+ wcfxs_setreg(wc, 64, txhook);
+ return 0;
+}
+
+static int wcfxs_initialize(struct wcfxs *wc)
+{
+ /* Zapata stuff */
+ sprintf(wc->span.name, "WCFXS/%d", wc->pos);
+ sprintf(wc->span.desc, "%s Board %d\n", wc->variety, wc->pos + 1);
+ wc->span.deflaw = ZT_LAW_MULAW;
+ sprintf(wc->chan.name, "WCFXS/%d/%d", wc->pos, 0);
+ wc->chan.sigcap = ZT_SIG_FXOKS | ZT_SIG_FXOLS | ZT_SIG_FXOGS;
+ wc->chan.chanpos = 1;
+ wc->span.chans = &wc->chan;
+ wc->span.channels = 1;
+ wc->span.hooksig = wcfxs_hooksig;
+ wc->span.open = wcfxs_open;
+ wc->span.close = wcfxs_close;
+ wc->span.flags = ZT_FLAG_RBS;
+ 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 wcfxs_hardware_init(struct wcfxs *wc)
+{
+ /* Hardware stuff */
+ long oldjiffies;
+ /* Reset PCI Interface chip and registers (and serial) */
+ outb(0x0e, wc->ioaddr + WC_CNTL);
+ /* Setup our proper outputs for when we switch for our "serial" port */
+ wc->ios = BIT_CS | BIT_SCLK | BIT_SDI;
+
+ outb(wc->ios, wc->ioaddr + WC_AUXD);
+
+ /* Set all to outputs except AUX 5 and 0, which are inputs */
+ outb(0xde, wc->ioaddr + WC_AUXC);
+
+ /* Wait a sec */
+ oldjiffies = jiffies;
+ while(jiffies - oldjiffies < 2);
+ /* Back to normal, with automatic DMA wrap around */
+ outb(0x30 | 0x01, wc->ioaddr + WC_CNTL);
+
+ /* Make sure serial port and DMA are out of reset */
+ outb(inb(wc->ioaddr + WC_CNTL) & 0xf9, 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);
+
+ /* Delay FSC by 0 so it's properly aligned */
+ outb(0x0, wc->ioaddr + WC_FSCDELAY);
+
+ /* Setup DMA Addresses */
+ outl(wc->writedma, wc->ioaddr + WC_DMAWS); /* Write start */
+ outl(wc->writedma + ZT_CHUNKSIZE * 4, wc->ioaddr + WC_DMAWI); /* Middle (interrupt) */
+ outl(wc->writedma + ZT_CHUNKSIZE * 8 - 4, wc->ioaddr + WC_DMAWE); /* End */
+
+ outl(wc->readdma, wc->ioaddr + WC_DMARS); /* Read start */
+ outl(wc->readdma + ZT_CHUNKSIZE * 4, wc->ioaddr + WC_DMARI); /* Middle (interrupt) */
+ outl(wc->readdma + ZT_CHUNKSIZE * 8 - 4, wc->ioaddr + WC_DMARE); /* End */
+
+ /* Clear interrupts */
+ outb(0xff, wc->ioaddr + WC_INTSTAT);
+ return wcfxs_init_proslic(wc);
+}
+
+static void wcfxs_enable_interrupts(struct wcfxs *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 wcfxs_start_dma(struct wcfxs *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 wcfxs_stop_dma(struct wcfxs *wc)
+{
+ outb(0x00, wc->ioaddr + WC_OPER);
+}
+
+static void wcfxs_disable_interrupts(struct wcfxs *wc)
+{
+ outb(0x00, wc->ioaddr + WC_MASK0);
+ outb(0x00, wc->ioaddr + WC_MASK1);
+}
+
+static int __devinit wcfxs_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int res;
+ struct wcfxs *wc;
+ struct wcfxs_desc *d = (struct wcfxs_desc *)ent->driver_data;
+ int x;
+ static int initd_ifaces=0;
+
+ if(initd_ifaces){
+ memset((void *)ifaces,0,(sizeof(struct wcfxs *))*WC_MAX_IFACES);
+ initd_ifaces=1;
+ }
+ for (x=0;x<WC_MAX_IFACES;x++)
+ if (!ifaces[x]) break;
+ if (x >= WC_MAX_IFACES) {
+ printk("Too many interfaces\n");
+ return -EIO;
+ }
+
+ if (pci_enable_device(pdev)) {
+ res = -EIO;
+ } else {
+ wc = kmalloc(sizeof(struct wcfxs), GFP_KERNEL);
+ if (wc) {
+ ifaces[x] = wc;
+ memset(wc, 0, sizeof(struct wcfxs));
+ 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, "wcfxs"))
+ 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("wcfxs: Unable to allocate DMA-able memory\n");
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ return -ENOMEM;
+ }
+
+ wc->readchunk = wc->writechunk + ZT_MAX_CHUNKSIZE * 2; /* in doublewords */
+ wc->readdma = wc->writedma + ZT_MAX_CHUNKSIZE * 8; /* in bytes */
+
+ if (wcfxs_initialize(wc)) {
+ printk("wcfxs: Unable to intialize FXS\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, wcfxs_interrupt, SA_SHIRQ, "wcfxs", wc)) {
+ printk("wcfxs: Unable to request IRQ %d\n", pdev->irq);
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ return -EIO;
+ }
+
+
+ if (wcfxs_hardware_init(wc)) {
+ zt_unregister(&wc->span);
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ return -EIO;
+ }
+ /* Enable interrupts */
+ wcfxs_enable_interrupts(wc);
+ /* Initialize Write/Buffers to all blank data */
+ memset((void *)wc->writechunk,0,ZT_MAX_CHUNKSIZE * 2 * 2 * 4);
+
+ /* Start DMA */
+ wcfxs_start_dma(wc);
+
+ printk("Found a Wildcard FXS: %s\n", wc->variety);
+ res = 0;
+ } else
+ res = -ENOMEM;
+ }
+ return res;
+}
+
+static void wcfxs_release(struct wcfxs *wc)
+{
+ zt_unregister(&wc->span);
+ if (wc->freeregion)
+ release_region(wc->ioaddr, 0xff);
+ kfree(wc);
+ printk("Freed a Wildcard\n");
+}
+
+static void __devexit wcfxs_remove_one(struct pci_dev *pdev)
+{
+ struct wcfxs *wc = pci_get_drvdata(pdev);
+ if (wc) {
+
+ /* Stop any DMA */
+ wcfxs_stop_dma(wc);
+
+ /* In case hardware is still there */
+ wcfxs_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)
+ wcfxs_release(wc);
+ else
+ wc->dead = 1;
+ }
+}
+
+static struct pci_device_id wcfxs_pci_tbl[] __devinitdata = {
+ { 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) &wcfxs },
+};
+
+static struct pci_driver wcfxs_driver = {
+ name: "wcfxs",
+ probe: wcfxs_init_one,
+ remove: wcfxs_remove_one,
+ suspend: NULL,
+ resume: NULL,
+ id_table: wcfxs_pci_tbl,
+};
+
+static int __init wcfxs_init(void)
+{
+ int res;
+ res = pci_module_init(&wcfxs_driver);
+ if (res)
+ return -ENODEV;
+ return 0;
+}
+
+static void __exit wcfxs_cleanup(void)
+{
+ pci_unregister_driver(&wcfxs_driver);
+}
+
+MODULE_PARM(debug, "i");
+MODULE_DESCRIPTION("Wildcard S100P Zaptel Driver");
+MODULE_AUTHOR("Mark Spencer <markster@linux-support.net>");
+
+module_init(wcfxs_init);
+module_exit(wcfxs_cleanup);