diff options
-rwxr-xr-x | wcfxo.c | 734 | ||||
-rwxr-xr-x | wct1xxp.c | 514 |
2 files changed, 1248 insertions, 0 deletions
@@ -0,0 +1,734 @@ +/* + * Wilcard X100P FXO 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> +#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_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 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) */ + +struct reg { + int flags; + unsigned char index; + unsigned char reg; + unsigned char value; +}; + +static int wecareregs[] = +{ 5, 6, 9, 11, 12, 13, 17, 19, }; + +struct wcfxo { + struct pci_dev *dev; + char *variety; + struct zt_span span; + struct zt_chan chan; + int usecount; + int dead; + int pos; + int flags; + int freeregion; + int ring; + int battery; + int wregcount; + int readpos; + int rreadpos; + int ringdebounce; + int battdebounce; + 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 midregs[ZT_CHUNKSIZE]; + struct reg oldregs[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 */ +}; + +#define FLAG_INVERTSER (1 << 0) +#define FLAG_USE_XTAL (1 << 1) +#define FLAG_DOUBLE_CLOCK (1 << 2) +#define FLAG_RESET_ON_AUX5 (1 << 3) + +struct wcfxo_desc { + char *name; + int flags; +}; + +static struct wcfxo_desc wcfxo = { "Wildcard Prototype", 0}; +static struct wcfxo_desc wcx100p = { "Wildcard X100P", + FLAG_INVERTSER | 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 inline void wcfxo_transmitprep(struct wcfxo *wc, unsigned char ints) +{ + volatile int *writechunk; + int x; + int written=0; + unsigned short cmd; + if (ints & 0x01) + /* Write is at interrupt address. Start writing from normal offset */ + writechunk = wc->writechunk; + else + writechunk = wc->writechunk + ZT_CHUNKSIZE * 2; + /* Calculate Transmission */ + 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] = + ~((unsigned short)(zt_mulaw[wc->chan.writechunk[x]])| 0x1) << 16; + else + writechunk[x << 1] = + ((unsigned short)(zt_mulaw[wc->chan.writechunk[x]])| 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] = ~(cmd << 16); + else + writechunk[(x << 1) + 1] = cmd << 16; + } + if (written) + wc->readpos = 0; + wc->wregcount = 0; + +} + +static inline void wcfxo_receiveprep(struct wcfxo *wc, unsigned char ints) +{ + volatile int *readchunk; + int x; + int realreg; + int realval; + if (ints & 0x08) + /* Read is at interrupt address. Valid data is available at normal offset */ + readchunk = wc->readchunk; + else + readchunk = wc->readchunk + ZT_CHUNKSIZE * 2; + 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 = (readchunk[(x << 1) +wc->alt] >> 16) & 0xff; + if ((realval == 0x89) && (realreg != 0x9)) { + /* Some sort of slippage, correct for it */ + while(realreg != 0x9) { + /* 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; + } + wc->chan.readchunk[x] = zt_lin2mu[((short)(readchunk[(x << 1) + (1 - wc->alt)] >> 16)) + 32768]; + } + for (x=0;x<ZT_CHUNKSIZE;x++) { + /* Rotate through registers */ + wc->oldregs[x] = wc->midregs[x]; + wc->midregs[x] = wc->regs[x]; + wc->regs[x].flags = FLAG_EMPTY; + } + if (wc->ignoreread) + wc->ignoreread--; + zt_receive(&wc->span); +} + +static void wcfxo_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct wcfxo *wc = dev_id; + unsigned char ints; + unsigned char b; + + ints = inb(wc->ioaddr + WC_INTSTAT); + outb(ints, wc->ioaddr + WC_INTSTAT); + + if (ints & 0x0f) { + wcfxo_transmitprep(wc, ints); + wcfxo_receiveprep(wc, ints); + } + + if (ints & 0x10) + printk("PCI Master abort\n"); + + if (ints & 0x20) + printk("PCI Target abort\n"); + + if (1 /* !(wc->report % 0xf) */) { + /* Check RING from register and debounce for 8ms */ + b = wc->readregs[0x5] & 0x60; + if (!b) { + if (wc->ring && !wc->ringdebounce) { + if (wc->ring == 2) { + if (debug) + printk("NO RING!\n"); + zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK); + } + wc->ring = 0; + wc->ringdebounce = RING_DEBOUNCE; + } + } else { + /* RING */ + if ((wc->ring < 2) && !wc->ringdebounce) { + if (wc->ring == 1) { + /* It's read as ringing for at least 8ms */ + if (debug) + printk("RING!\n"); + zt_hooksig(&wc->chan, ZT_RXSIG_RING); + wc->ring = 2; + } else { + /* Ooh, we saw a ring, now lets see if it hangs around + or is just someone plugging the phone in or something + along those lines */ + wc->ring = 1; + } + wc->ringdebounce = RING_DEBOUNCE; + } else if (wc->ring == 2) { /* Reset debounce if we're ringing */ + wc->ringdebounce = RING_DEBOUNCE; + } +#if 0 + else if (wc->ring == 1) { + printk("Ring1: Debounce: %d\n", wc->ringdebounce); + } +#endif + } + + /* Check for BATTERY from register and debounce for 8 ms */ + b = wc->readregs[0xc] & 0xf; + if (!b) { +#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; + zt_hooksig(&wc->chan, ZT_RXSIG_ONHOOK); + wc->battdebounce = BATT_DEBOUNCE; + } else if (!wc->battery) + wc->battdebounce = BATT_DEBOUNCE; + } else if (b == 0xf) { + if (!wc->battery && !wc->battdebounce) { + if (debug) + printk("BATTERY!\n"); + zt_hooksig(&wc->chan, ZT_RXSIG_OFFHOOK); + wc->battery = 1; + wc->battdebounce = BATT_DEBOUNCE; + } else if (wc->battery) + wc->battdebounce = BATT_DEBOUNCE; + } else { + /* It's something else... */ + wc->battdebounce = BATT_DEBOUNCE; + } + + if (wc->ringdebounce) + wc->ringdebounce--; + if (wc->battdebounce) + wc->battdebounce--; + } +} + +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++; + MOD_INC_USE_COUNT; + return 0; +} + +static int wcfxo_close(struct zt_chan *chan) +{ + struct wcfxo *wc = chan->pvt; + wc->usecount--; + MOD_DEC_USE_COUNT; + /* 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[0x5] & ~0x08; + wcfxo_setreg(wc, 0x5, reg); + + reg = reg | 0x1; + wcfxo_setreg(wc, 0x5, reg); + break; + case ZT_TXSIG_ONHOOK: + /* Put on hook and enable on hook line monitor */ + reg = wc->readregs[0x5] & 0xfe; + wcfxo_setreg(wc, 0x5, reg); + + reg = reg | 0x08; + wcfxo_setreg(wc, 0x5, reg); + 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, "TjModem/%d", wc->pos); + sprintf(wc->span.desc, "%s Board %d\n", wc->variety, wc->pos + 1); + sprintf(wc->chan.name, "TjModem/%d/%d", wc->pos, 0); + wc->chan.sigcap = ZT_SIG_FXSKS | ZT_SIG_FXSLS; + wc->chan.chanpos = 1; + wc->span.chans = &wc->chan; + wc->span.channels = 1; + wc->span.hooksig = wcfxo_hooksig; + wc->span.open = wcfxo_open; + wc->span.close = wcfxo_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 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_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, 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, 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_stop_dma(struct wcfxo *wc) +{ + outb(0x00, wc->ioaddr + WC_OPER); +} + +static void wcfxo_disable_interrupts(struct wcfxo *wc) +{ + outb(0x00, wc->ioaddr + WC_MASK0); + outb(0x00, wc->ioaddr + WC_MASK1); +} + +static int wcfxo_init_daa(struct wcfxo *wc) +{ + /* This must not be called in an interrupt */ + /* We let things settle for a bit */ +// set_current_state(TASK_INTERRUPTIBLE); +// schedule_timeout(10); + + /* Soft-reset it */ + wcfxo_setreg(wc, 0x1, 0x80); + + /* Let the reset go */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + + /* We have a clock at 18.432 Mhz, so N1=1, M1=2, CGM=0 */ + wcfxo_setreg(wc, 0x7, 0x0); /* This value is N1 - 1 */ + wcfxo_setreg(wc, 0x8, 0x1); /* This value is M1 - 1 */ + /* We want to sample at 8khz, so N2 = 9, M2 = 10 (N2-1, M2-1) */ + wcfxo_setreg(wc, 0x9, 0x89); + + /* Wait until the PLL's are locked. Time is between 100 uSec and 1 mSec */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + + /* No additional ration is applied to the PLL and faster lock times + * are possible */ + wcfxo_setreg(wc, 0xa, 0x0); + /* Enable off hook pin */ + wcfxo_setreg(wc, 0x5, 0x0a); + /* Enable ISOcap and external speaker and charge pump if present */ + wcfxo_setreg(wc, 0x6, 0x80); + + + /* Wait a couple of jiffies for our writes to finish */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + + /* Didn't get it right. Register 9 is still garbage */ + if (wc->readregs[0x9] != 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) +{ + int res; + struct wcfxo *wc; + struct wcfxo_desc *d = (struct wcfxo_desc *)ent->driver_data; + int x; + static int initd_ifaces=0; + + if(initd_ifaces){ + memset((void *)ifaces,0,(sizeof(struct wcfxo *))*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 wcfxo), GFP_KERNEL); + if (wc) { + 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, SA_SHIRQ, "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; + } + + printk("Found a Wildcard FXO: %s\n", wc->variety); + res = 0; + } else + res = -ENOMEM; + } + return res; +} + +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); + + /* 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[] __devinitdata = { + { 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) &wcfxo }, + { 0x1057, 0x5608, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) &wcx100p }, +}; + +static struct pci_driver wcfxo_driver = { + name: "wcfxo", + probe: wcfxo_init_one, + remove: wcfxo_remove_one, + suspend: NULL, + resume: NULL, + id_table: wcfxo_pci_tbl, +}; + +static int __init wcfxo_init(void) +{ + int res; + res = pci_module_init(&wcfxo_driver); + if (res) + return -ENODEV; + return 0; +} + +static void __exit wcfxo_cleanup(void) +{ + pci_unregister_driver(&wcfxo_driver); +} + +MODULE_PARM(debug, "i"); +MODULE_DESCRIPTION("Wildcard X100P Zaptel Driver"); +MODULE_AUTHOR("Mark Spencer <markster@linux-support.net>"); + +module_init(wcfxo_init); +module_exit(wcfxo_cleanup); diff --git a/wct1xxp.c b/wct1xxp.c new file mode 100755 index 0000000..be821cf --- /dev/null +++ b/wct1xxp.c @@ -0,0 +1,514 @@ +/* + * Linux Support Services, Inc. Wildcard T100P T1/PRI card Driver + * + * Written by Mark Spencer <markster@linux-support.net> + * Matthew Fredrickson <creslin@linux-support.net> + * William Meadows <wmeadows@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> +#ifdef STANDALONE_ZAPATA +#include "zaptel.h" +#else +#include <linux/zaptel.h> +#endif + +#define DELAY 0x30 /* 30 = 15 cycles, 10 = 8 cycles, 0 = 3 cycles */ + +#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_CURPOS 0x24 + +#define WC_SERC 0x2d + +#define WC_USERREG 0xc0 + +#define WC_CLOCK 0x0 +#define WC_LEDTEST 0x1 +#define WC_VERSION 0x2 + +#define BIT_CS (1 << 5) + +#define BIT_OK (1 << 0) +#define BIT_TEST (1 << 1) +#define BIT_ERROR (1 << 2) +#define BIT_ALARM (1 << 3) + +struct t1xxp { + struct pci_dev *dev; + char *variety; + int usecount; + int dead; + int alarmtimer; + int alarm; + int alarmthreshold; + unsigned char ledtestreg; + unsigned char outbyte; + 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 zt_span span; /* Span */ + struct zt_chan chans[24]; /* Channels */ +}; + +static inline void select_framer(struct t1xxp *wc) +{ + if (wc->outbyte & BIT_CS) { + wc->outbyte &= ~BIT_CS; + outb(wc->outbyte, wc->ioaddr + WC_AUXD); + } +} + +static inline void select_control(struct t1xxp *wc) +{ + if (!(wc->outbyte & BIT_CS)) { + wc->outbyte |= BIT_CS; + outb(wc->outbyte, wc->ioaddr + WC_AUXD); + } +} + +static inline void select_page(struct t1xxp *wc, unsigned char reg) +{ + int page = (reg & 0x30) << 2; + if (wc->outbyte != page) { + /* Clear high bits */ + wc->outbyte &= 0x3f; + /* Set page */ + wc->outbyte |= page; + /* Make sure we've turned on the framer */ + wc->outbyte &= ~BIT_CS; + outb(wc->outbyte, wc->ioaddr + WC_AUXD); + } else if (wc->outbyte & BIT_CS) { + /* Check to be sure we don't still have to enable the framer */ + select_framer(wc); + } +} + +static int t1xxp_open(struct zt_chan *chan) +{ + struct t1xxp *wc = chan->pvt; + if (wc->dead) + return -ENODEV; + wc->usecount++; + MOD_INC_USE_COUNT; + return 0; +} + +static int t1_get_reg(struct t1xxp *wc, int reg) +{ + unsigned char res; + select_page(wc, reg); + res = inb(wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); + return res; +} + +static int t1_set_reg(struct t1xxp *wc, int reg, unsigned char val) +{ + select_page(wc, reg); + outb(val, wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); + return 0; +} + +static int control_set_reg(struct t1xxp *wc, int reg, unsigned char val) +{ + select_control(wc); + outb(val, wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); + return 0; +} + +static int control_get_reg(struct t1xxp *wc, int reg) +{ + unsigned char res; + select_control(wc); + res = inb(wc->ioaddr + WC_USERREG + ((reg & 0xf) << 2)); + return res; +} + +static void t1xxp_release(struct t1xxp *wc) +{ + kfree(wc); + printk("Freed a Wildcard\n"); +} + +static int t1xxp_close(struct zt_chan *chan) +{ + struct t1xxp *wc = chan->pvt; + wc->usecount--; + MOD_DEC_USE_COUNT; + /* If we're dead, release us now */ + if (!wc->usecount && wc->dead) + t1xxp_release(wc); + return 0; +} + +static inline void handle_leds(struct t1xxp *wc) +{ + if (wc->alarm) { + wc->alarmtimer++; + if (wc->alarmtimer == 40) { + wc->ledtestreg = wc->ledtestreg | BIT_ALARM; + control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); + } else if (wc->alarmtimer == 160) { + wc->ledtestreg = wc->ledtestreg & ~BIT_ALARM; + control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); + wc->alarmtimer = 0; + } + } +#if 0 + if (wc->alarmtimer == wc->alarmthreshold >> 2) { + wc->ledtestreg = wc->ledtestreg ^ BIT_ALARM; + control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); + } + if (wc->alarmtimer == 0xf) { + wc->ledtestreg = wc->ledtestreg ^ BIT_ALARM; + control_set_reg(wc, WC_LEDTEST, wc->ledtestreg); + wc->alarmtimer = -1; + wc->alarmthreshold = (wc->alarmthreshold + 1 & 0x1f); + } +#endif +} + +static void t1xxp_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct t1xxp *wc = dev_id; + unsigned char ints; + unsigned char b; + static int gotint = 0; + + ints = inb(wc->ioaddr + WC_INTSTAT); + outb(ints, wc->ioaddr + WC_INTSTAT); + if (!gotint) { + printk("Got interrupt: 0x%04x\n", ints); + gotint++; + } + /* Initialize Write/Buffers to all blank data */ + memset((void *)wc->writechunk,0x55,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); + handle_leds(wc); + + +#if 0 + if (ints & 0x0f) { + tjmodem_transmitprep(wc, ints); + tjmodem_receiveprep(wc, ints); + } +#endif + + if (ints & 0x10) + printk("PCI Master abort\n"); + + if (ints & 0x20) + printk("PCI Target abort\n"); + +} + +static int t1xxp_reset(struct t1xxp *wc) +{ + char c; + c = t1_get_reg(wc, 0x30); + /* Reset the Line Interface */ + t1_set_reg(wc, 0x30, c | 0x1); + + /* Wait 50ms for this to finish */ + mdelay(50); + + /* Clear it */ + t1_set_reg(wc, 0x30, c); + + /* Reset the elastic stores */ + t1_set_reg(wc, 0x30, c | 0x40); + mdelay(1); + t1_set_reg(wc, 0x30, c); + + return 0; +} + +static int t1xxp_hardware_init(struct t1xxp *wc) +{ + int x; + /* Hardware Tigerjet stuff */ + /* Reset TJ chip and registers */ + outb(DELAY | 0x0e, wc->ioaddr + WC_CNTL); + /* Set all outputs to 0 */ + outb(0x00, wc->ioaddr + WC_AUXD); + /* Set all to outputs except AUX1 (TDO). */ + outb(0xfd, wc->ioaddr + WC_AUXC); + /* Configure the serial port: double clock, 20ns width, no inversion, + MSB first */ + outb(0xc8, wc->ioaddr + WC_SERC); + + /* Internally delay FSC by one */ + outb(0x01, wc->ioaddr + 0x2f); + + /* Back to normal, with automatic DMA wrap around */ + outb(DELAY | 0x01, wc->ioaddr + WC_CNTL); + + /* Make sure serial port and DMA are out of reset */ + outb(inb(wc->ioaddr + WC_CNTL) & 0xf9, WC_CNTL); + + /* Setup DMA Addresses */ + outl(wc->writedma, wc->ioaddr + WC_DMAWS); /* Write start */ + outl(wc->writedma + ZT_CHUNKSIZE * 96, wc->ioaddr + WC_DMAWI); /* Middle (interrupt) */ + outl(wc->writedma + ZT_CHUNKSIZE * 192 - 4, wc->ioaddr + WC_DMAWE); /* End */ + + outl(wc->readdma, wc->ioaddr + WC_DMARS); /* Read start */ + outl(wc->readdma + ZT_CHUNKSIZE * 96, wc->ioaddr + WC_DMARI); /* Middle (interrupt) */ + outl(wc->readdma + ZT_CHUNKSIZE * 192 - 4, wc->ioaddr + WC_DMARE); /* End */ + + printk("Setting up DMA (write/read = %08lx/%08lx)\n", wc->writedma, wc->readdma); + + /* Clear interrupts */ + outb(0xff, wc->ioaddr + WC_INTSTAT); + + /* Check out the controller */ + printk("Controller version: %02x\n", control_get_reg(wc, WC_VERSION)); + control_set_reg(wc, WC_LEDTEST, 0xff); + control_set_reg(wc, WC_CLOCK, 0x00); + /* Pretend we're in alarm */ + wc->alarm = 1; +#if 0 + printk("LED/Test Reg test: expected %02x, got %02x\n", 0x55, control_get_reg(wc, WC_LEDTEST)); + control_set_reg(wc, WC_LEDTEST, 0xAA); + printk("LED/Test Reg test: expected %02x, got %02x\n", 0xAA, control_get_reg(wc, WC_LEDTEST)); + + control_set_reg(wc, WC_CLOCK, 0x55); + printk("Clock Reg test: expected %02x, got %02x\n", 0x55, control_get_reg(wc, WC_CLOCK)); + control_set_reg(wc, WC_CLOCK, 0xAA); + printk("Clock Reg test: expected %02x, got %02x\n", 0xAA, control_get_reg(wc, WC_CLOCK)); +#endif + return 0; +#if 0 + /* Reset all registers on the 2151 */ + for (x=0x20;x<0x40;x++) + t1_set_reg(wc, x, 0); + for (x=0x60;x<0x80;x++) + t1_set_reg(wc, x, 0); + /* Sanity check the T1 */ + if (t1_get_reg(wc, 0x7c) != 0) { + printk("Unable to talk to T1 part\n"); + return -ENOSYS; + } + /* Enable elastic store for transmit and receive, + 2.048 Mhz bus */ + t1_set_reg(wc, 0x37, 0x8c | 0x01); + + /* TSync is an output */ + t1_set_reg(wc, 0x36, 0x04); + + /* RSync is an input (Elastic Store) */ + t1_set_reg(wc, 0x2c, 0x08); + + /* ESF/B8ZS */ + t1_set_reg(wc, 0x38, 0xcc); + { + /* XXX Take me out XXX */ + t1_set_reg(wc, 0x2d, 0xff); + t1_set_reg(wc, 0x2e, 0xff); + t1_set_reg(wc, 0x2f, 0xff); + } + return t1xxp_reset(wc); +#endif + +} + +static void t1xxp_enable_interrupts(struct t1xxp *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 t1xxp_start_dma(struct t1xxp *wc) +{ + /* Reset Master and TDM */ + outb(DELAY | 0x0f, wc->ioaddr + WC_CNTL); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + outb(DELAY | 0x01, wc->ioaddr + WC_CNTL); + outb(0x01, wc->ioaddr + WC_OPER); + printk("Started DMA\n"); + { int x; + for (x=0;x<10;x++) { + printk("%d) Read at %08lx\n", x, inl(wc->ioaddr + WC_CURPOS)); + udelay(10000); + } } + printk("0x0: %02x\n", (unsigned int)inb(wc->ioaddr + 0x0)); + printk("0x1: %02x\n", (unsigned int)inb(wc->ioaddr + 0x1)); + printk("0x2b: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2b)); + printk("0x2c: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2c)); + printk("0x2d: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2d)); + printk("0x2e: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2e)); + printk("0x2f: %02x\n", (unsigned int)inb(wc->ioaddr + 0x2f)); +} + +static void t1xxp_stop_dma(struct t1xxp *wc) +{ + outb(0x00, wc->ioaddr + WC_OPER); +} + +static void t1xxp_disable_interrupts(struct t1xxp *wc) +{ + outb(0x00, wc->ioaddr + WC_MASK0); + outb(0x00, wc->ioaddr + WC_MASK1); +} + + +static int __devinit t1xxp_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int res; + struct t1xxp *wc; + + if (pci_enable_device(pdev)) { + res = -EIO; + } else { + wc = kmalloc(sizeof(struct t1xxp), GFP_KERNEL); + if (wc) { + memset(wc, 0x0, sizeof(struct t1xxp)); + wc->ioaddr = pci_resource_start(pdev, 0); + wc->dev = pdev; + wc->variety = (char *)(ent->driver_data); + + wc->writechunk = + (int *)pci_alloc_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4, &wc->writedma); + if (!wc->writechunk) { + printk("wcmodem: Unable to allocate DMA-able memory\n"); + return -ENOMEM; + } + + /* Read is after the whole write piece (in double words) */ + wc->readchunk = wc->writechunk + ZT_CHUNKSIZE * 48; + + /* Same thing, but in bytes */ + wc->readdma = wc->writedma + ZT_CHUNKSIZE * 192; + + /* Initialize Write/Buffers to all blank data */ + memset((void *)wc->writechunk,0xff,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); + + /* Enable bus mastering */ + pci_set_master(pdev); + + /* Keep track of which device we are */ + pci_set_drvdata(pdev, wc); + + if (request_irq(pdev->irq, t1xxp_interrupt, SA_SHIRQ, "t1xxp", wc)) { + printk("t1xxp: Unable to request IRQ %d\n", pdev->irq); + kfree(wc); + return -EIO; + } + + + t1xxp_hardware_init(wc); + /* Enable interrupts */ + t1xxp_enable_interrupts(wc); + + /* Initialize Write/Buffers to all blank data */ + memset((void *)wc->writechunk,0,ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4); + /* Start DMA */ + t1xxp_start_dma(wc); + + printk("Found a Wildcard: %s\n", wc->variety); + res = 0; + } else + res = -ENOMEM; + } + return res; +} + +static void __devexit t1xxp_remove_one(struct pci_dev *pdev) +{ + struct t1xxp *wc = pci_get_drvdata(pdev); + if (wc) { + + /* Stop any DMA */ + t1xxp_stop_dma(wc); + + /* In case hardware is still there */ + t1xxp_disable_interrupts(wc); + + /* Immediately free resources */ + pci_free_consistent(pdev, ZT_MAX_CHUNKSIZE * 2 * 2 * 24 * 4, (void *)wc->writechunk, wc->writedma); + free_irq(pdev->irq, wc); + + /* Reset TJ chip and registers */ + outb(DELAY | 0x0e, wc->ioaddr + WC_CNTL); + + /* Release span, possibly delayed */ + if (!wc->usecount) + t1xxp_release(wc); + else + wc->dead = 1; + } +} + +static struct pci_device_id t1xxp_pci_tbl[] __devinitdata = { + { 0xe159, 0x0001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (unsigned long) "LSS Wildcard T100P T1/PRI Board" }, +}; + +static struct pci_driver t1xxp_driver = { + name: "t1xxp", + probe: t1xxp_init_one, + remove: t1xxp_remove_one, + suspend: NULL, + resume: NULL, + id_table: t1xxp_pci_tbl, +}; + +static int __init t1xxp_init(void) +{ + int res; + res = pci_module_init(&t1xxp_driver); + if (res) + return -ENODEV; + return 0; +} + +static void __exit t1xxp_cleanup(void) +{ + pci_unregister_driver(&t1xxp_driver); +} + +MODULE_PARM(debug, "i"); +MODULE_DESCRIPTION("Wildcard T100P Zaptel Driver"); +MODULE_AUTHOR("Mark Spencer <markster@linux-support.net>"); + +module_init(t1xxp_init); +module_exit(t1xxp_cleanup); |