/* * Zapata Telephony "Tormenta" ISA card LINUX driver, version 2.0 X/X/01 * * 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. * * Modified from original tor.c by Mark Spencer * original by Jim Dixon */ #include #include #include #include #include #include #include #include #include #include #ifdef STANDALONE_ZAPATA #include "zaptel.h" #include "torisa.h" #else #include #include #endif /* Board address offsets (specified in word (not byte) offsets) */ #define DDATA 0 /* Data I/O Register */ #define DADDR 0x100 /* Dallas Card Address Reg., 0x200 in byte offset higher */ #define CTLREG 0x100 /* Control/Status Reg., 0x200 in byte offset */ /* Control register bits */ #define MASTERCLOCK 0 /* Value for Master Clock */ #define OUTBIT 8 /* Status output bit (for external measurements) */ #define INTENA 4 /* Interrupt enable bit */ #define ENA16 0x80 /* 16 bit bus cycle enable bit */ /* signalling bits */ #define TOR_ABIT 8 #define TOR_BBIT 4 static int syncsrc; static int syncs[2]; static int debug; /* clock values */ static u_char clockvals[] = {0,0x12,0x22,0}; /* translations of data channels for 24 channels in a 32 bit PCM highway */ unsigned datxlt[] = { 0, 1 ,2 ,3 ,5 ,6 ,7 ,9 ,10,11,13,14,15,17,18,19,21,22,23,25,26,27,29,30,31 }; /* This is the order that the data (audio) channels get scanned in. This was done in this rather poopy manner because when outputting (and inputting) a sine wave, such as in the case of TDD, any repeated samples (because of PCM bus contention) will result in nasty-sounding distortion. The Mitel STPA chips (MT8920) have a contention mechanism, which results in a situation where, if the processor accesses a timeslot that is currently being transmitted or received, it will HOLD the bus until it is done with the timeslot. This means that there can be cases where we are trying to write to a timeslot, and its already outputting the same value as the last one (since we didnt get there in time), and in a sine-wave output, distortion will occur. In any other output, it will be utterly un-noticeable. So, what we do is use a pattern that gives us the most flexibility in how long our interrupt latency is (note: Even with this, our interrupt latency must be between 4 and 28 microseconds!!!) Essentially we receive the interrupt just after the 24th channel is read. It will take us AT LEAST 30 microseconds to read it, but could take as much as 35 microseconds to read all the channels. In any case it's the very first thing we do in the interrupt handler. Worst case (30 microseconds) is that the MT8920 has only moved 7 channels. That's where the 6 comes from. */ static int chseq[] = { 6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,1,2,3,4,5 } ; struct torisa_pvt { int span; }; static struct zt_span spans[2]; static struct zt_chan chans[48]; static struct torisa_pvt pvts[48]; static u_char txsigs[2][6]; static int loopupcnt[2]; static int loopdowncnt[2]; static int alarmtimer[2]; static int prefmaster = 0; static int spansstarted = 0; static rwlock_t torisa; static u_char readdata[2][48][ZT_MAX_CHUNKSIZE]; static u_char writedata[2][48][ZT_MAX_CHUNKSIZE]; static int curread; static unsigned long base; volatile static unsigned short *maddr; static int irq; static unsigned int irqcount = 0; static unsigned int taskletsched = 0; static unsigned int taskletrun = 0; static unsigned int taskletexec = 0; /* set the control register */ static void setctlreg(unsigned char val) { volatile register char *cp; cp = (char *) &maddr[CTLREG]; *cp = val; } /* output a byte to one of the registers in one of the Dallas T-1 chips */ static void t1out(int spanno, int loc, unsigned char val) { register int n; volatile register char *cp; /* get the memory offset */ n = spanno << 9; /* point a char * at the address location */ cp = (char *) &maddr[DADDR + n]; *cp = loc; /* set address in T1 chip */ /* point a char * at the data location */ cp = (char *) &maddr[DDATA + n]; *cp = val; /* out the value */ } /* get a byte from one of the registers in one of the Dallas T-1 chips */ static unsigned char t1in(int spanno, int loc) { register int n; volatile register char *cp; /* get the memory offset */ n = spanno << 9; /* point a char * at the address location */ cp = (char *) &maddr[DADDR + n]; *cp = loc; /* set address in T1 chip */ cp = (char *) &maddr[DDATA + n]; /* point a char * at the data location */ return(*cp); } /* get input from the status register */ static unsigned char getctlreg(void) { register char *cp; cp = (char *) &maddr[CTLREG]; return(*cp); } static void set_clear(void) { int i,j,s; unsigned short val=0; for (s=0;s<2;s++) { for (i=0;i<24;i++) { j = (i/8); if (spans[s].chans[i].flags & ZT_FLAG_CLEAR) val |= 1 << (i % 8); if ((i % 8)==7) { #if 0 printk("Putting %d in register %02x on span %d\n", val, 0x39 + j, 1 + s); #endif t1out(1 + s, 0x39 + j, val); val = 0; } } } } /* device probe routine .. determines if the Tormenta device is present in the system */ static int tor_probe(void) { int i,status; u_char c1,c2; maddr = phys_to_virt(base); status = -1; /* default status return is 'not present' */ /* initialize control register */ setctlreg(MASTERCLOCK); /* init all the registers in first T-1 chip to 0 */ for(i = 0x20; i < 0x40; i++) t1out(1,i,0); /* set register to 0 */ for(i = 0x60; i < 0x80; i++) t1out(1,i,0); /* set register to 0 */ /* simple test that will fail if tried in an array of standard memory */ /* put an 0x55 here */ t1out(1,0x2b,0x55); /* put an 0xaa here */ t1out(1,0x2c,0xaa); /* get input from first location */ c1 = t1in(1,0x2b); /* get input from second location */ c2 = t1in(1,0x2c); /* see if we read back what we put in */ if ((c1 == 0x55) && (c2 == 0xaa)) { /* Try to get the irq if the user didn't specify one */ if (irq < 1) { autoirq_setup(0); setctlreg(MASTERCLOCK|INTENA); /* Wait a jiffie -- that's plenty of time */ irq = autoirq_report(5); } /* disable interrupts having gotten one */ setctlreg(MASTERCLOCK); if (irq == 2) irq = 9; if (irq) { /* init both STPA's to all silence */ for(i = 0; i < 32; i++) maddr[i] = 0x7f7f; status = 0; /* found */ if (debug) printk("ISA Tormenta Card found at base addr 0x%lx, irq %d\n",base,irq); } else printk("ISA Tormenta Card found at base addr 0x%lx, but unable to determine IRQ. Try using irq= option\n", base); } return status; } static void make_chans(void) { int x,y; int c; for (x=0;x<2;x++) for (y=0;y<24;y++) { c = x * 24 + y; sprintf(chans[c].name, "TorISA/%d/%d", x + 1, y + 1); chans[c].sigcap = ZT_SIG_EM | ZT_SIG_CLEAR | ZT_SIG_FXSLS | ZT_SIG_FXSGS | ZT_SIG_FXSKS | ZT_SIG_FXOLS | ZT_SIG_FXOGS | ZT_SIG_FXOKS; chans[c].pvt = &pvts[c]; pvts[c].span = x; chans[c].chanpos = y + 1; } } static int torisa_rbsbits(struct zt_chan *chan, int bits) { u_char m,c; int k,n,b; struct torisa_pvt *p = chan->pvt; unsigned int flags; #if 0 printk("Setting bits to %d on channel %s\n", bits, chan->name); #endif n = chan->chanpos - 1; k = p->span; b = (n / 8); /* get byte number */ m = 1 << (n & 7); /* get mask */ c = txsigs[k][b]; c &= ~m; /* clear mask bit */ /* set mask bit, if bit is to be set */ if (bits & ZT_ABIT) c |= m; txsigs[k][b] = c; write_lock_irqsave(&torisa, flags); t1out(k + 1,0x70 + b,c); t1out(k + 1,0x76 + b,c); b += 3; /* now points to b bit stuff */ /* get current signalling values */ c = txsigs[k][b]; c &= ~m; /* clear mask bit */ /* set mask bit, if bit is to be set */ if (bits & ZT_BBIT) c |= m; /* save new signalling values */ txsigs[k][b] = c; /* output them into the chip */ t1out(k + 1,0x70 + b,c); t1out(k + 1,0x76 + b,c); write_unlock_irqrestore(&torisa, flags); return 0; } static inline int getspan(struct zt_span *span) { if (span == spans) return 1; if (span == spans + 1) return 2; return -1; } static int torisa_shutdown(struct zt_span *span) { int i; int tspan; int wasrunning; unsigned int flags; tspan = getspan(span); if (tspan < 0) { printk("TorISA: Span '%d' isn't us?\n", span->spanno); return -1; } write_lock_irqsave(&torisa, flags); wasrunning = span->flags & ZT_FLAG_RUNNING; span->flags &= ~ZT_FLAG_RUNNING; /* Zero out all registers */ for (i = 0x20; i< 0x40; i++) t1out(tspan, i, 0); for (i = 0x60; i< 0x80; i++) t1out(tspan, i, 0); if (wasrunning) spansstarted--; write_unlock_irqrestore(&torisa, flags); if (!spans[0].flags & ZT_FLAG_RUNNING && !spans[1].flags & ZT_FLAG_RUNNING) /* No longer in use, disable interrupts */ setctlreg(clockvals[syncsrc]); if (debug) printk("Span %d (%s) shutdown\n", span->spanno, span->name); return 0; } static int torisa_startup(struct zt_span *span) { unsigned long endjif; int i; int tspan; unsigned int flags; char *coding; char *framing; int alreadyrunning; tspan = getspan(span); if (tspan < 0) { printk("TorISA: Span '%d' isn't us?\n", span->spanno); return -1; } write_lock_irqsave(&torisa, flags); alreadyrunning = span->flags & ZT_FLAG_RUNNING; if (!alreadyrunning) { setctlreg(MASTERCLOCK); /* Zero out all registers */ for (i = 0x20; i< 0x40; i++) t1out(tspan, i, 0); for (i = 0x60; i< 0x80; i++) t1out(tspan, i, 0); /* Full-on Sync required (RCR1) */ t1out(tspan, 0x2b, 8); /* RSYNC is an input (RCR2) */ t1out(tspan, 0x2c, 8); /* RBS enable (TCR1) */ t1out(tspan, 0x35, 0x10); /* TSYNC to be output (TCR2) */ t1out(tspan, 0x36, 4); /* Tx & Rx Elastic store, sysclk = 2.048 mhz, loopback controls (CCR1) */ t1out(tspan, 0x37, 0x8c); } /* Enable F bits pattern */ i = 0x20; if (span->lineconfig & ZT_CONFIG_ESF) i = 0x88; if (span->lineconfig & ZT_CONFIG_B8ZS) i |= 0x44; t1out(tspan, 0x38, i); if (i & 0x80) coding = "ESF"; else coding = "SF"; if (i & 0x40) framing = "B8ZS"; else { framing = "AMI"; t1out(tspan,0x7e,0x1c); /* F bits pattern (0x1c) into FDL register */ } t1out(tspan, 0x7c, span->txlevel << 5); if (!alreadyrunning) { /* LIRST to 1 in CCR3 */ t1out(tspan, 0x30, 1); /* Wait 100 ms */ endjif = jiffies + 10; write_unlock_irqrestore(&torisa, flags); while(jiffies < endjif); /* wait 100 ms */ write_lock_irqsave(&torisa, flags); t1out(tspan,0x30,0x40); /* set CCR3 to 0x40, resetting Elastic Store */ span->flags |= ZT_FLAG_RUNNING; spansstarted++; #if 0 printk("Enabling interrupts: %d\n", clockvals[syncsrc] | INTENA); #endif /* output the clock info and enable interrupts */ setctlreg(clockvals[syncsrc] | INTENA); } set_clear(); write_unlock_irqrestore(&torisa, flags); if (debug) { if (alreadyrunning) printk("TorISA: Reconfigured span %d (%s/%s) LBO: %s\n", span->spanno, coding, framing, zt_lboname(span->txlevel)); else printk("TorISA: Startup span %d (%s/%s) LBO: %s\n", span->spanno, coding, framing, zt_lboname(span->txlevel)); } if (syncs[0] == span->spanno) printk("SPAN %d: Primary Sync Source\n",span->spanno); if (syncs[1] == span->spanno) printk("SPAN %d: Secondary Sync Source\n",span->spanno); return 0; } static int torisa_spanconfig(struct zt_span *span, struct zt_lineconfig *lc) { if (debug) printk("TorISA: Configuring span %d\n", span->spanno); /* XXX We assume lineconfig is okay and shouldn't XXX */ span->lineconfig = lc->lineconfig; span->txlevel = lc->lbo; span->rxlevel = 0; span->syncsrc = syncsrc; /* remove this span number from the current sync sources, if there */ if (syncs[0] == span->spanno) syncs[0] = 0; if (syncs[1] == span->spanno) syncs[1] = 0; /* if a sync src, put it in proper place */ if (lc->sync) syncs[lc->sync - 1] = span->spanno; /* If we're already running, then go ahead and apply the changes */ if (span->flags & ZT_FLAG_RUNNING) return torisa_startup(span); return 0; } static int torisa_chanconfig(struct zt_chan *chan, int sigtype) { int alreadyrunning; unsigned int flags; alreadyrunning = chan->span->flags & ZT_FLAG_RUNNING; if (debug) { if (alreadyrunning) printk("TorISA: Reconfigured channel %d (%s) sigtype %d\n", chan->channo, chan->name, sigtype); else printk("TorISA: Configured channel %d (%s) sigtype %d\n", chan->channo, chan->name, sigtype); } write_lock_irqsave(&torisa, flags); if (alreadyrunning) set_clear(); write_unlock_irqrestore(&torisa, flags); return 0; } static int torisa_open(struct zt_chan *chan) { MOD_INC_USE_COUNT; return 0; } static int torisa_close(struct zt_chan *chan) { MOD_DEC_USE_COUNT; return 0; } static int torisa_maint(struct zt_span *span, int cmd) { int tspan = getspan(span); switch(cmd) { case ZT_MAINT_NONE: t1out(tspan,0x37,0x8c); /* clear system */ break; case ZT_MAINT_LOCALLOOP: t1out(tspan,0x37,0xcc); /* local loopback */ break; case ZT_MAINT_REMOTELOOP: t1out(tspan,0x37,0x9c); /* remote loopback */ break; case ZT_MAINT_LOOPUP: t1out(tspan,0x30,2); /* send loopup code */ break; case ZT_MAINT_LOOPDOWN: t1out(tspan,0x30,4); /* send loopdown code */ break; case ZT_MAINT_LOOPSTOP: t1out(tspan,0x30,0); /* stop sending loopup code */ break; default: printk("torisa: Unknown maint command: %d\n", cmd); break; } return 0; } static int taskletpending; static struct tasklet_struct torisa_tlet; static void torisa_tasklet(unsigned long data) { taskletrun++; if (taskletpending) { taskletexec++; /* Perform receive data calculations. Reverse to run most likely master last */ if (spans[1].flags & ZT_FLAG_RUNNING) zt_receive(&spans[1]); if (spans[0].flags & ZT_FLAG_RUNNING) zt_receive(&spans[0]); /* Prepare next set for transmission */ if (spans[1].flags & ZT_FLAG_RUNNING) zt_transmit(&spans[1]); if (spans[0].flags & ZT_FLAG_RUNNING) zt_transmit(&spans[0]); } taskletpending = 0; } static int txerrors; static void torisa_intr(int irq, void *dev_id, struct pt_regs *regs) { static unsigned int passno = 0; int n, n1, i, j, k, x; static unsigned short rxword[25],txword[25]; unsigned char txc, rxc, c; unsigned char abits, bbits; irqcount++; /* 1. Do all I/O Immediately -- Normally we would ask for the transmission first, but because of the incredibly tight timing we're lucky to be able to do the I/O at this point */ /* make sure its a real interrupt for us */ if (!(getctlreg() & 1)) /* if not, just return */ { return; } /* set outbit and put int 16 bit bus mode, reset interrupt enable */ setctlreg(clockvals[syncsrc] | OUTBIT | ENA16); #if 0 if (!passno) printk("Interrupt handler\n"); #endif /* Do the actual transmit and receive in poopy order */ for(n1 = 0; n1 < 24; n1++) { n = chseq[n1]; maddr[DDATA + datxlt[n]] = txword[n]; rxword[n] = maddr[DDATA + datxlt[n]]; /* get rx word */ } setctlreg(clockvals[syncsrc] | OUTBIT); /* clear 16 bit mode */ /* Calculate the transmit, go thru all the chans */ for(n1 = 0; n1 < 24; n1++) { n = chseq[n1]; txword[n] = 0; /* go thru both spans */ for(j = 0; j < 2; j++) { /* enter the transmit stuff with i being channel number, leaving with txc being character to transmit */ txc = writedata[curread][j * 24 + n-1][passno % ZT_CHUNKSIZE]; txword[n] |= txc << (j * 8); } } /* do the receive for all chans, both spans */ for(n1 = 0; n1 < 24; n1++) { n = chseq[n1]; /* go thru both spans */ for(j = 0; j <= 1; j++) { i = n + (j * 24); /* calc chan number */ rxc = (rxword[n] >> (j * 8)) & 0xff; readdata[curread][j * 24 + n - 1][passno % ZT_CHUNKSIZE] = rxc; } } i = passno & 511; if (i < 6) { k = (i / 3); /* get span */ n = (i % 3); /* get base */ abits = t1in(k + 1, 0x60 + n); bbits = t1in(k + 1, 0x63 + n); for (j=0; j< 8; j++) { /* Get channel number */ i = (k * 24) + (n * 8) + j; rxc = 0; if (abits & (1 << j)) rxc |= ZT_ABIT; if (bbits & (1 << j)) rxc |= ZT_BBIT; if (chans[i].rxsig != rxc) { /* Check for changes in received bits */ if (!(chans[i].sig & ZT_SIG_CLEAR)) zt_rbsbits(&chans[i], rxc); } } } if (!(passno & 0x7)) { for(i = 0; i < 2; i++) { /* if alarm timer, and it's timed out */ if (alarmtimer[i]) { if (!--alarmtimer[i]) { /* clear recover status */ spans[i].alarms &= ~ZT_ALARM_RECOVER; t1out(i + 1,0x35,0x10); /* turn off yel */ zt_alarm_notify(&spans[i]); /* let them know */ } } } } i = passno & 511; if ((i == 100) || (i == 101)) { j = 0; /* clear this alarm status */ i -= 100; c = t1in(i + 1,0x31); /* get RIR2 */ spans[i].rxlevel = c >> 6; /* get rx level */ t1out(i + 1,0x20,0xff); c = t1in(i + 1,0x20); /* get the status */ /* detect the code, only if we are not sending one */ if ((!spans[i].mainttimer) && (c & 0x80)) /* if loop-up code detected */ { /* set into remote loop, if not there already */ if ((loopupcnt[i]++ > 80) && (spans[i].maintstat != ZT_MAINT_REMOTELOOP)) { t1out(i + 1,0x37,0x9c); /* remote loopback */ spans[i].maintstat = ZT_MAINT_REMOTELOOP; } } else loopupcnt[i] = 0; /* detect the code, only if we are not sending one */ if ((!spans[i].mainttimer) && (c & 0x40)) /* if loop-down code detected */ { /* if in remote loop, get out of it */ if ((loopdowncnt[i]++ > 80) && (spans[i].maintstat == ZT_MAINT_REMOTELOOP)) { t1out(i + 1,0x37,0x8c); /* normal */ spans[i].maintstat = ZT_MAINT_NONE; } } else loopdowncnt[i] = 0; if (c & 3) /* if red alarm */ { j |= ZT_ALARM_RED; } if (c & 8) /* if blue alarm */ { j |= ZT_ALARM_BLUE; } /* only consider previous carrier alarm state */ spans[i].alarms &= (ZT_ALARM_RED | ZT_ALARM_BLUE | ZT_ALARM_NOTOPEN); n = 1; /* set to 1 so will not be in yellow alarm if we dont care about open channels */ /* if to have yellow alarm if nothing open */ if (spans[i].lineconfig & ZT_CONFIG_NOTOPEN) { /* go thru all chans, and count # open */ for(n = 0,k = (i * 24); k < (i * 24) + 24; k++) { if ((chans[k].flags & ZT_FLAG_OPEN) || (chans[k].flags & ZT_FLAG_NETDEV)) n++; } /* if none open, set alarm condition */ if (!n) j |= ZT_ALARM_NOTOPEN; } /* if no more alarms, and we had some */ if ((!j) && spans[i].alarms) { alarmtimer[i] = ZT_ALARMSETTLE_TIME; } if (alarmtimer[i]) j |= ZT_ALARM_RECOVER; /* if going into alarm state, set yellow alarm */ if ((j) && (!spans[i].alarms)) t1out(i + 1,0x35,0x11); if (c & 4) /* if yellow alarm */ j |= ZT_ALARM_YELLOW; if (spans[i].maintstat || spans[i].mainttimer) j |= ZT_ALARM_LOOPBACK; spans[i].alarms = j; zt_alarm_notify(&spans[i]); } if (!(passno % 8000)) /* even second boundary */ { /* do both spans */ for(i = 1; i <= 2; i++) { /* add this second's BPV count to total one */ spans[i - 1].bpvcount += t1in(i,0x24) + (t1in(i,0x23) << 8); } } /* re-evaluate active sync src */ syncsrc = 0; /* if primary sync specified, see if we can use it */ if (syncs[0]) { /* if no alarms, use it */ if (!(spans[syncs[0] - 1].alarms & (ZT_ALARM_RED | ZT_ALARM_BLUE | ZT_ALARM_LOOPBACK))) syncsrc = syncs[0]; } /* if we dont have one yet, and there is a secondary, see if we can use it */ if ((!syncsrc) && (syncs[1])) { /* if no alarms, use it */ if (!(spans[syncs[1] - 1].alarms & (ZT_ALARM_RED | ZT_ALARM_BLUE | ZT_ALARM_LOOPBACK))) syncsrc = syncs[1]; } /* update sync src info */ spans[0].syncsrc = spans[1].syncsrc = syncsrc; /* If this is the last pass, then prepare the next set */ if ((passno % ZT_CHUNKSIZE) == (ZT_CHUNKSIZE - 1)) { /* Swap buffers */ for (x = 0;x<48;x++) { chans[x].readchunk = readdata[curread][x]; chans[x].writechunk = writedata[curread][x]; } /* Lets work with the others now which presumably have been filled */ curread = 1 - curread; if (!taskletpending) { taskletpending = 1; taskletsched++; tasklet_hi_schedule(&torisa_tlet); } else { txerrors++; } } passno++; /* clear outbit, restore interrupt enable */ setctlreg(clockvals[syncsrc] | INTENA); } static int torisa_ioctl(struct zt_chan *chan, unsigned int cmd, unsigned long data) { struct torisa_debug td; switch(cmd) { case TORISA_GETDEBUG: td.txerrors = txerrors; td.irqcount = irqcount; td.taskletsched = taskletsched; td.taskletrun = taskletrun; td.taskletexec = taskletexec; td.span1flags = spans[0].flags; td.span2flags = spans[1].flags; if (copy_to_user((struct torisa_debug *)data, &td, sizeof(td))) return -EFAULT; return 0; default: return -ENOTTY; } return 0; } static int __init tor_init(void) { if (!base) { printk("Specify address with base=0xNNNNN\n"); return -EIO; } if (tor_probe()) { printk(KERN_ERR "No ISA tormenta card found at %05lx\n", base); return -EIO; } if (request_irq(irq, torisa_intr, SA_INTERRUPT, "torisa", NULL)) { printk(KERN_ERR "Unable to request tormenta IRQ %d\n", irq); return -EIO; } if (!request_mem_region(base, 4096, "Tormenta ISA")) { printk(KERN_ERR "Unable to request 4k memory window at %lx\n", base); free_irq(irq, NULL); return -EIO; } strcpy(spans[0].name, "TorISA/1"); strcpy(spans[0].desc, "ISA Tormenta Span 1"); spans[0].spanconfig = torisa_spanconfig; spans[0].chanconfig = torisa_chanconfig; spans[0].startup = torisa_startup; spans[0].shutdown = torisa_shutdown; spans[0].rbsbits = torisa_rbsbits; spans[0].maint = torisa_maint; spans[0].open = torisa_open; spans[0].close = torisa_close; spans[0].channels = 24; spans[0].chans = &chans[0]; spans[0].flags = ZT_FLAG_RBS; spans[0].linecompat = ZT_CONFIG_AMI | ZT_CONFIG_B8ZS | ZT_CONFIG_D4 | ZT_CONFIG_ESF; spans[0].ioctl = torisa_ioctl; spans[0].irq = irq; init_waitqueue_head(&spans[0].maintq); strcpy(spans[1].name, "TorISA/2"); strcpy(spans[1].desc, "ISA Tormenta Span 2"); spans[1].spanconfig = torisa_spanconfig; spans[1].chanconfig = torisa_chanconfig; spans[1].startup = torisa_startup; spans[1].shutdown = torisa_shutdown; spans[1].rbsbits = torisa_rbsbits; spans[1].maint = torisa_maint; spans[1].open = torisa_open; spans[1].close = torisa_close; spans[1].channels = 24; spans[1].chans = &chans[24]; spans[1].flags = ZT_FLAG_RBS; spans[1].linecompat = ZT_CONFIG_AMI | ZT_CONFIG_B8ZS | ZT_CONFIG_D4 | ZT_CONFIG_ESF; spans[1].ioctl = torisa_ioctl; spans[1].irq = irq; init_waitqueue_head(&spans[1].maintq); make_chans(); if (zt_register(&spans[0], prefmaster)) { printk(KERN_ERR "Unable to register span %s\n", spans[0].name); return -EIO; } if (zt_register(&spans[1], 0)) { printk(KERN_ERR "Unable to register span %s\n", spans[1].name); zt_unregister(&spans[0]); return -EIO; } tasklet_init(&torisa_tlet, torisa_tasklet, (long)0); printk("TORISA Loaded\n"); return 0; } static int __init set_tor_base(char *str) { base = simple_strtol(str, NULL, 0); return 1; } __setup("tor=", set_tor_base); static void __exit tor_exit(void) { free_irq(irq, NULL); release_mem_region(base, 4096); if (spans[0].flags & ZT_FLAG_REGISTERED) zt_unregister(&spans[0]); if (spans[1].flags & ZT_FLAG_REGISTERED) zt_unregister(&spans[1]); } MODULE_AUTHOR("Mark Spencer "); MODULE_DESCRIPTION("Tormenta ISA Zapata Telephony Driver"); MODULE_PARM(prefmaster, "i"); MODULE_PARM(base, "i"); MODULE_PARM(irq, "i"); MODULE_PARM(syncsrc, "i"); MODULE_PARM(debug, "i"); module_init(tor_init); module_exit(tor_exit);