diff options
Diffstat (limited to 'wctdm.c')
-rwxr-xr-x | wctdm.c | 834 |
1 files changed, 834 insertions, 0 deletions
@@ -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); |