Logo Search packages:      
Sourcecode: dahdi-linux version File versions  Download package

pciradio.c

/*
 * PCI RADIO Card DAHDI Telephony PCI Quad Radio Interface driver
 *
 * Written by Jim Dixon <jim@lambdatel.com>
 * Based on previous work by Mark Spencer <markster@digium.com>
 * Based on previous works, designs, and archetectures conceived and
 * written by Jim Dixon <jim@lambdatel.com>.
 *
 * Copyright (C) 2001-2007 Jim Dixon / Zapata Telephony.
 *
 * All rights reserved.
 *
 *
 */

/*
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2 as published by the
 * Free Software Foundation. See the LICENSE file included with
 * this program for more details.
 */

/*
  The PCI Radio Interface card interfaces up to 4 two-way radios (either
  a base/mobile radio or repeater system) to DAHDI channels. The driver
  may work either independent of an application, or with it, through
  the driver;s ioctl() interface. This file gives you access to specify
  load-time parameters for Radio channels, so that the driver may run
  by itself, and just act like a generic DAHDI radio interface.
*/

/* Latency tests:

Without driver:   308496
With driver:      303826  (1.5 %)

*/


#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/delay.h> 

#include <dahdi/kernel.h>

#define RAD_MAX_IFACES 128

#define     NUM_CODES 15

#define     SERIAL_BUFLEN 128

#define     SRX_TIMEOUT 300

#define RAD_CNTL        0x00
#define RAD_OPER  0x01
#define RAD_AUXC        0x02
#define RAD_AUXD        0x03
      #define     XPGM 4
      #define     XCS 2

#define RAD_MASK0       0x04
#define RAD_MASK1       0x05
#define RAD_INTSTAT     0x06
#define RAD_AUXR  0x07
      #define     XINIT 8
      #define     XDONE 0x10

#define RAD_DMAWS 0x08
#define RAD_DMAWI 0x0c
#define RAD_DMAWE 0x10
#define RAD_DMARS 0x18
#define RAD_DMARI 0x1c
#define RAD_DMARE 0x20

#define RAD_AUXFUNC     0x2b
#define RAD_SERCTL      0x2d
#define RAD_FSCDELAY    0x2f

#define RAD_REGBASE     0xc0

#define     RAD_CTCSSMASK     0xf
#define     RAD_CTCSSOTHER    0xf
#define     RAD_CTCSSVALID    0x10

#define NUM_CHANS 4

#define     RAD_GOTRX_DEBOUNCE_TIME 75
#define RAD_CTCSS_ACQUIRE_TIME 10
#define RAD_CTCSS_TALKOFF_TIME 1000

#define DAHDI_RADPAR_CTCSSACQUIRETIME 18 /* DEBUG only, this belongs in dahdi.h */
#define DAHDI_RADPAR_CTCSSTALKOFFTIME 19 /* DEBUG only, this belongs in dahdi.h */

/*
* MX828 Commands
*/
                                                                                
#define MX828_GEN_RESET         0x01            /* W */
#define MX828_SAUDIO_CTRL       0x80            /* W */
#define MX828_SAUDIO_STATUS     0x81            /* R */
#define MX828_SAUDIO_SETUP      0x82            /* W */
#define MX828_TX_TONE           0x83            /* W16 */
#define MX828_RX_TONE           0x84            /* W16 */
#define MX828_DCS3              0x85            /* W */
#define MX828_DCS2              0x86            /* W */
#define MX828_DCS1              0x87            /* W */
#define MX828_GEN_CTRL          0x88            /* W */
#define MX828_GPT               0x8B            /* W */
#define MX828_IRQ_MASK          0x8E            /* W */
#define MX828_SELCALL           0x8D            /* W16 */
#define MX828_AUD_CTRL          0x8A            /* W16 */
#define MX828_IRQ_FLAG          0x8F            /* R */
                                                                                

struct encdec
{
      unsigned char state;  /* 0 = idle */
      int chan;
      unsigned char req[NUM_CHANS];
      unsigned char dcsrx[NUM_CHANS];
      unsigned char ctrx[NUM_CHANS];
      unsigned char dcstx[NUM_CHANS];
      unsigned char cttx[NUM_CHANS];
      unsigned char saudio_ctrl[NUM_CHANS];
      unsigned char saudio_setup[NUM_CHANS];
      unsigned char txcode[NUM_CHANS];
      unsigned long lastcmd;
      int myindex[NUM_CHANS];
      unsigned long waittime;
      unsigned char retstate;
} ;


struct pciradio {
      struct pci_dev *dev;
      struct dahdi_span span;
      unsigned char ios;
      int usecount;
      unsigned int intcount;
      int dead;
      int pos;
      int freeregion;
      int nchans;
      spinlock_t lock;
      int remote_locked;
      unsigned char rxbuf[SERIAL_BUFLEN];
      unsigned short rxindex;
      unsigned long srxtimer;
      unsigned char txbuf[SERIAL_BUFLEN];
      unsigned short txindex;
      unsigned short txlen;
      unsigned char pasave;
      unsigned char pfsave;
      volatile unsigned long ioaddr;
      dma_addr_t  readdma;
      dma_addr_t  writedma;
      volatile unsigned int *writechunk;  /* Double-word aligned write memory */
      volatile unsigned int *readchunk;   /* Double-word aligned read memory */
      unsigned char saudio_status[NUM_CHANS];
      char gotcor[NUM_CHANS];
      char gotct[NUM_CHANS];
      char newctcssstate[NUM_CHANS];
      char ctcssstate[NUM_CHANS];
      char gotrx[NUM_CHANS];
      char gotrx1[NUM_CHANS];
      char gottx[NUM_CHANS];
      char lasttx[NUM_CHANS];
      int gotrxtimer[NUM_CHANS];
      int ctcsstimer[NUM_CHANS];
      int debouncetime[NUM_CHANS];
      int ctcssacquiretime[NUM_CHANS];
      int ctcsstalkofftime[NUM_CHANS];
      int bursttime[NUM_CHANS];
      int bursttimer[NUM_CHANS];
      unsigned char remmode[NUM_CHANS];
      unsigned short present_code[NUM_CHANS];
      unsigned short last_code[NUM_CHANS];
      unsigned short rxcode[NUM_CHANS][NUM_CODES + 1];
      unsigned short rxclass[NUM_CHANS][NUM_CODES + 1];
      unsigned short txcode[NUM_CHANS][NUM_CODES + 1];;
      unsigned char radmode[NUM_CHANS]; 
#define     RADMODE_INVERTCOR 1
#define     RADMODE_IGNORECOR 2
#define     RADMODE_EXTTONE   4
#define     RADMODE_EXTINVERT 8
#define     RADMODE_IGNORECT 16
#define     RADMODE_NOENCODE 32
      unsigned char corthresh[NUM_CHANS];
      struct dahdi_chan _chans[NUM_CHANS];
      struct dahdi_chan *chans;
      unsigned char mx828_addr;
      struct encdec encdec;
      unsigned long lastremcmd;
};


static struct pciradio *ifaces[RAD_MAX_IFACES];

static void pciradio_release(struct pciradio *rad);

static int debug = 0;

struct tonedef {
      int code;
      unsigned char b1;
      unsigned char b2;
} ;

#include "radfw.h"

static struct tonedef cttable_tx [] = {
{0,0,0},
{670,0xE,0xB1},
{693,0xE,0x34},
{719,0xD,0xB1},
{744,0xD,0x3B},
{770,0xC,0xC9},
{797,0xC,0x5A},
{825,0xB,0xEF},
{854,0xB,0x87},
{885,0xB,0x1F},
{915,0xA,0xC2},
{948,0xA,0x62},
{974,0xA,0x1B},
{1000,0x9,0xD8},
{1035,0x9,0x83},
{1072,0x9,0x2F},
{1109,0x8,0xE0},
{1148,0x8,0x93},
{1188,0x8,0x49},
{1230,0x8,0x1},
{1273,0x7,0xBC},
{1318,0x7,0x78},
{1365,0x7,0x36},
{1413,0x6,0xF7},
{1462,0x6,0xBC},
{1514,0x6,0x80},
{1567,0x6,0x48},
{1598,0x6,0x29},
{1622,0x6,0x12},
{1679,0x5,0xDD},
{1738,0x5,0xAA},
{1799,0x5,0x79},
{1835,0x5,0x5D},
{1862,0x5,0x49},
{1899,0x5,0x2F},
{1928,0x5,0x1B},
{1966,0x5,0x2},
{1995,0x4,0xEF},
{2035,0x4,0xD6},
{2065,0x4,0xC4},
{2107,0x4,0xAC},
{2181,0x4,0x83},
{2257,0x4,0x5D},
{2291,0x4,0x4C},
{2336,0x4,0x37},
{2418,0x4,0x12},
{2503,0x3,0xEF},
{2541,0x3,0xE0},
{0,0,0}
} ;

static struct tonedef cttable_rx [] = {
{0,0,0},
{670,0x3,0xD8},
{693,0x4,0x9},
{719,0x4,0x1B},
{744,0x4,0x4E},
{770,0x4,0x83},
{797,0x4,0x94},
{825,0x4,0xCB},
{854,0x5,0x2},
{885,0x5,0x14},
{915,0x5,0x4C},
{948,0x5,0x87},
{974,0x5,0x94},
{1000,0x5,0xCB},
{1035,0x6,0x7},
{1072,0x6,0x45},
{1109,0x6,0x82},
{1148,0x6,0xC0},
{1188,0x6,0xD1},
{1230,0x7,0x10},
{1273,0x7,0x50},
{1318,0x7,0xC0},
{1365,0x8,0x2},
{1413,0x8,0x44},
{1462,0x8,0x86},
{1514,0x8,0xC9},
{1567,0x9,0xC},
{1598,0x9,0x48},
{1622,0x9,0x82},
{1679,0x9,0xC6},
{1738,0xA,0xB},
{1799,0xA,0x84},
{1835,0xA,0xC2},
{1862,0xA,0xC9},
{1899,0xB,0x8},
{1928,0xB,0x44},
{1966,0xB,0x83},
{1995,0xB,0x8A},
{2035,0xB,0xC9},
{2065,0xC,0x6},
{2107,0xC,0x46},
{2181,0xC,0xC3},
{2257,0xD,0x41},
{2291,0xD,0x48},
{2336,0xD,0x89},
{2418,0xE,0x8},
{2503,0xE,0x88},
{2541,0xE,0xC7},
{0,0,0}
};

static struct {
      int code;
      char b3;
      char b2;
      char b1;
} dcstable[] = {
{0,0,0,0},
{23,0x76,0x38,0x13},
{25,0x6B,0x78,0x15},
{26,0x65,0xD8,0x16},
{31,0x51,0xF8,0x19},
{32,0x5F,0x58,0x1A},
{43,0x5B,0x68,0x23},
{47,0x0F,0xD8,0x27},
{51,0x7C,0xA8,0x29},
{54,0x6F,0x48,0x2C},
{65,0x5D,0x18,0x35},
{71,0x67,0x98,0x39},
{72,0x69,0x38,0x3A},
{73,0x2E,0x68,0x3B},
{74,0x74,0x78,0x3C},
{114,0x35,0xE8,0x4C},
{115,0x72,0xB8,0x4D},
{116,0x7C,0x18,0x4E},
{125,0x07,0xB8,0x55},
{131,0x3D,0x38,0x59},
{132,0x33,0x98,0x5A},
{134,0x2E,0xD8,0x5C},
{143,0x37,0xA8,0x63},
{152,0x1E,0xC8,0x6A},
{155,0x44,0xD8,0x6D},
{156,0x4A,0x78,0x6E},
{162,0x6B,0xC8,0x72},
{165,0x31,0xD8,0x75},
{172,0x05,0xF8,0x7A},
{174,0x18,0xB8,0x7C},
{205,0x6E,0x98,0x85},
{223,0x68,0xE8,0x93},
{226,0x7B,0x08,0x96},
{243,0x45,0xB8,0xA3},
{244,0x1F,0xA8,0xA4},
{245,0x58,0xF8,0xA5},
{251,0x62,0x78,0xA9},
{261,0x17,0x78,0xB1},
{263,0x5E,0x88,0xB3},
{265,0x43,0xC8,0xB5},
{271,0x79,0x48,0xB9},
{306,0x0C,0xF8,0xC6},
{311,0x38,0xD8,0xC9},
{315,0x6C,0x68,0xCD},
{331,0x23,0xE8,0xD9},
{343,0x29,0x78,0xE3},
{346,0x3A,0x98,0xE6},
{351,0x0E,0xB8,0xE9},
{364,0x68,0x58,0xF4},
{365,0x2F,0x08,0xF5},
{371,0x15,0x88,0xF9},
{411,0x77,0x69,0x09},
{412,0x79,0xC9,0x0A},
{413,0x3E,0x99,0x0B},
{423,0x4B,0x99,0x13},
{431,0x6C,0x59,0x19},
{432,0x62,0xF9,0x1A},
{445,0x7B,0x89,0x25},
{464,0x27,0xE9,0x34},
{465,0x60,0xB9,0x35},
{466,0x6E,0x19,0x36},
{503,0x3C,0x69,0x43},
{506,0x2F,0x89,0x46},
{516,0x41,0xB9,0x4E},
{532,0x0E,0x39,0x5A},
{546,0x19,0xE9,0x66},
{565,0x0C,0x79,0x75},
{606,0x5D,0x99,0x86},
{612,0x67,0x19,0x8A},
{624,0x0F,0x59,0x94},
{627,0x01,0xF9,0x97},
{631,0x72,0x89,0x99},
{632,0x7C,0x29,0x9A},
{654,0x4C,0x39,0xAC},
{662,0x24,0x79,0xB2},
{664,0x39,0x39,0xB4},
{703,0x22,0xB9,0xC3},
{712,0x0B,0xD9,0xCA},
{723,0x39,0x89,0xD3},
{731,0x1E,0x49,0xD9},
{732,0x10,0xE9,0xDA},
{734,0x0D,0xA9,0xDC},
{743,0x14,0xD9,0xE3},
{754,0x20,0xF9,0xEC},
{0,0,0,0}
};

static int gettxtone(int code)
{
int   i;

      if (!code) return(0);
      for(i = 0; cttable_tx[i].code || (!i); i++)
      {
            if (cttable_tx[i].code == code)
            {
                  return (i);
            }
      }
      return(-1);
}

static int getrxtone(int code)
{
int   i;

      if (!code) return(0);
      for(i = 0; cttable_rx[i].code || (!i); i++)
      {
            if (cttable_rx[i].code == code)
            {
                  return (i);
            }
      }
      return(-1);
}


static int getdcstone(int code)
{
int   i;

      if (!code) return(0);
      for(i = 0; dcstable[i].code || (!i); i++)
      {
            if (dcstable[i].code == code)
            {
                  return (i);
            }
      }
      return(-1);
}


static void __pciradio_setcreg(struct pciradio *rad, unsigned char reg, unsigned char val)
{
      outb(val, rad->ioaddr + RAD_REGBASE + ((reg & 0xf) << 2));
}

static unsigned char __pciradio_getcreg(struct pciradio *rad, unsigned char reg)
{
      return inb(rad->ioaddr + RAD_REGBASE + ((reg & 0xf) << 2));
}

static void rbi_out(struct pciradio *rad, int n, unsigned char *rbicmd)
{
unsigned long flags;
int   x;
DECLARE_WAIT_QUEUE_HEAD(mywait);


      for(;;)
      {
            spin_lock_irqsave(&rad->lock,flags);
            x = rad->remote_locked || (__pciradio_getcreg(rad,0xc) & 2);
            if (!x) rad->remote_locked = 1;
            spin_unlock_irqrestore(&rad->lock,flags);
            if (x) interruptible_sleep_on_timeout(&mywait,2);
            else break;
      }     
      spin_lock_irqsave(&rad->lock,flags);
      /* enable and address RBI serializer */
      __pciradio_setcreg(rad,0xf,rad->pfsave | (n << 4) | 0x40);
      /* output commands */
      for(x = 0; x < 5; x++) __pciradio_setcreg(rad,0xc,rbicmd[x]);
      /* output it */
      __pciradio_setcreg(rad,0xb,1);
      rad->remote_locked = 0;
      spin_unlock_irqrestore(&rad->lock,flags);
      return;
}


/*
* Output a command to the MX828 over the serial bus
*/


static void mx828_command(struct pciradio *rad,int channel, unsigned char command, unsigned char *byte1, unsigned char *byte2)
{

      if(channel > 3)
            return;

      rad->mx828_addr = channel;
      __pciradio_setcreg(rad,0,channel);
      if (byte1) __pciradio_setcreg(rad,1,*byte1);
      if (byte2) __pciradio_setcreg(rad,2,*byte2);
      __pciradio_setcreg(rad,3,command);
      
}

static void mx828_command_wait(struct pciradio *rad,int channel, unsigned char command, unsigned char *byte1, unsigned char *byte2)
{
DECLARE_WAIT_QUEUE_HEAD(mywait);
unsigned long flags;


      spin_lock_irqsave(&rad->lock,flags);  
      while(rad->encdec.state)
      {
            spin_unlock_irqrestore(&rad->lock,flags);  
            interruptible_sleep_on_timeout(&mywait,2);   
            spin_lock_irqsave(&rad->lock,flags);  
      }
      rad->encdec.lastcmd = jiffies + 1000;
      spin_unlock_irqrestore(&rad->lock,flags);  
      while(__pciradio_getcreg(rad,0xc) & 1);
      rad->encdec.lastcmd = jiffies + 1000;
      spin_lock_irqsave(&rad->lock,flags);  
      rad->encdec.lastcmd = jiffies + 1000;
      mx828_command(rad,channel,command,byte1,byte2);
      spin_unlock_irqrestore(&rad->lock,flags);  
      rad->encdec.lastcmd = jiffies + 1000;
      while(__pciradio_getcreg(rad,0xc) & 1);
      rad->encdec.lastcmd = jiffies;
}

static void _do_encdec(struct pciradio *rad)
{
int   i,n;
unsigned char byte1 = 0,byte2 = 0;

      /* return doing nothing if busy */
      if ((rad->encdec.lastcmd + 2) > jiffies) return;
      if (__pciradio_getcreg(rad,0xc) & 1) return;
      n = 0;
      byte2 = 0;
      switch(rad->encdec.state)
      {
          case 0:
            for(i = 0; i < rad->nchans; i++)
            {
                  n = (unsigned)(i - rad->intcount) % rad->nchans;
                  if (rad->encdec.req[n]) break;
            }
            if (i >= rad->nchans) return;
            rad->encdec.req[n] = 0;
            rad->encdec.dcsrx[n] = 0;
            rad->encdec.ctrx[n] = 0;
            rad->encdec.dcstx[n] = 0;
            rad->encdec.cttx[n] = 0;
            rad->encdec.myindex[n] = 0;
            rad->encdec.req[n] = 0;
            rad->encdec.chan = n;

            /* if something in code 0 for rx, is DCS */
            if (rad->rxcode[n][0]) rad->encdec.dcsrx[n] = 1;
            else { /* otherwise, if something in other codes, is CT rx */
                  for(i = 1; i <= NUM_CODES; i++)
                  {
                        if (rad->rxcode[n][1]) rad->encdec.ctrx[n] = 1;
                  }
            }           
            /* get index for tx code. Will be 0 if not receiving a CT */
            rad->encdec.myindex[n] = 0;
            if (rad->gotrx[n] && rad->encdec.ctrx[n] && (rad->present_code[n])) 
                  rad->encdec.myindex[n] = rad->present_code[n];
            /* get actual tx code from array */
            rad->encdec.txcode[n] = rad->txcode[n][rad->encdec.myindex[n]];
            if (rad->encdec.txcode[n] & 0x8000) rad->encdec.dcstx[n] = 1;
                 else if (rad->encdec.txcode[n]) rad->encdec.cttx[n] = 1;
            if (rad->radmode[n] & RADMODE_NOENCODE) 
                  rad->encdec.dcstx[n] = rad->encdec.cttx[n] = 0;
            if ((!rad->gottx[n]) || rad->bursttimer[n]) 
                  rad->encdec.dcstx[n] = rad->encdec.cttx[n] = 0;
            rad->encdec.saudio_ctrl[n] = 0;
            rad->encdec.saudio_setup[n] = 0;
            rad->encdec.state = 1;
            break;
          case 1:
            if (rad->encdec.dcstx[rad->encdec.chan] && (!rad->encdec.dcsrx[rad->encdec.chan])) /* if to transmit DCS */
            {
                  rad->encdec.saudio_setup[rad->encdec.chan] |= 3;
                  rad->encdec.saudio_ctrl[rad->encdec.chan] |= 0x80;
                  byte1 = dcstable[rad->encdec.txcode[rad->encdec.chan] & 0x7fff].b1;
                  mx828_command(rad,rad->encdec.chan, MX828_DCS1, &byte1, &byte2 );
                  rad->encdec.state = 2;
                  break;
            } 
            rad->encdec.state = 4;
            break;
          case 2: 
            byte1 = dcstable[rad->encdec.txcode[rad->encdec.chan] & 0x7fff].b2;
            mx828_command(rad,rad->encdec.chan, MX828_DCS2, &byte1, &byte2 );
            rad->encdec.state = 3;
            break;
          case 3:
            byte1 = dcstable[rad->encdec.txcode[rad->encdec.chan] & 0x7fff].b3;
            mx828_command(rad,rad->encdec.chan, MX828_DCS3, &byte1, &byte2 );
            rad->encdec.state = 4;
            break;
          case 4:
            if (rad->encdec.cttx[rad->encdec.chan])
            {
                  rad->encdec.saudio_ctrl[rad->encdec.chan] |= 0x80;
                  byte1 = cttable_tx[rad->encdec.txcode[rad->encdec.chan]].b1;
                  byte2 = cttable_tx[rad->encdec.txcode[rad->encdec.chan]].b2;
                  mx828_command(rad,rad->encdec.chan, MX828_TX_TONE, &byte1, &byte2 );
            } 
            rad->encdec.state = 5;
            break;
          case 5:
            if (rad->encdec.dcsrx[rad->encdec.chan])
            {
                  rad->encdec.saudio_setup[rad->encdec.chan] |= 1;
                  rad->encdec.saudio_ctrl[rad->encdec.chan] |= 0x41;
                  byte1 = dcstable[rad->rxcode[rad->encdec.chan][0]].b1;
                  mx828_command(rad,rad->encdec.chan, MX828_DCS1, &byte1, &byte2 );
                  rad->encdec.state = 6;
                  break;
            }
            rad->encdec.state = 8;
            break;
          case 6:
            byte1 = dcstable[rad->rxcode[rad->encdec.chan][0]].b2;
            mx828_command(rad,rad->encdec.chan, MX828_DCS2, &byte1, &byte2 );
            rad->encdec.state = 7;
            break;
          case 7:
            byte1 = dcstable[rad->rxcode[rad->encdec.chan][0]].b3;
            mx828_command(rad,rad->encdec.chan, MX828_DCS3, &byte1, &byte2 );
            rad->encdec.state = 8;
            break;
          case 8:
            if (rad->encdec.ctrx[rad->encdec.chan])
            {
                  rad->encdec.saudio_setup[rad->encdec.chan] |= 0x80;
                  rad->encdec.saudio_ctrl[rad->encdec.chan] |= 0x60;
            }
            byte1 = rad->encdec.saudio_setup[rad->encdec.chan];
            mx828_command(rad,rad->encdec.chan, MX828_SAUDIO_SETUP, &byte1, &byte2 );
            rad->encdec.state = 9;
            break;
          case 9:
            byte1 = rad->encdec.saudio_ctrl[rad->encdec.chan];
            mx828_command(rad,rad->encdec.chan, MX828_SAUDIO_CTRL, &byte1, &byte2 );
            rad->encdec.state = 10;
            break;
          case 10:
            rad->encdec.chan = 0;
            rad->encdec.state = 0;
            break;
      }
}

static inline void pciradio_transmitprep(struct pciradio *rad, unsigned char ints)
{
      volatile unsigned int *writechunk;
      int x;
      if (ints & 0x01) 
            /* Write is at interrupt address.  Start writing from normal offset */
            writechunk = rad->writechunk;
      else 
            writechunk = rad->writechunk + DAHDI_CHUNKSIZE;

      /* Calculate Transmission */
      dahdi_transmit(&rad->span);

      for (x=0;x<DAHDI_CHUNKSIZE;x++) {
            /* Send a sample, as a 32-bit word */
            writechunk[x] = 0;
            writechunk[x] |= (rad->chans[0].writechunk[x] << 24);
            writechunk[x] |= (rad->chans[1].writechunk[x] << 16);
            writechunk[x] |= (rad->chans[2].writechunk[x] << 8);
            writechunk[x] |= (rad->chans[3].writechunk[x]);
      }
}

static inline void pciradio_receiveprep(struct pciradio *rad, unsigned char ints)
{
      volatile unsigned int *readchunk;
      int x;

      if (ints & 0x08)
            readchunk = rad->readchunk + DAHDI_CHUNKSIZE;
      else
            /* Read is at interrupt address.  Valid data is available at normal offset */
            readchunk = rad->readchunk;
      for (x=0;x<DAHDI_CHUNKSIZE;x++) {
            rad->chans[0].readchunk[x] = (readchunk[x] >> 24) & 0xff;
            rad->chans[1].readchunk[x] = (readchunk[x] >> 16) & 0xff;
            rad->chans[2].readchunk[x] = (readchunk[x] >> 8) & 0xff;
            rad->chans[3].readchunk[x] = (readchunk[x]) & 0xff;
      }
      for (x=0;x<rad->nchans;x++) {
            dahdi_ec_chunk(&rad->chans[x], rad->chans[x].readchunk, rad->chans[x].writechunk);
      }
      dahdi_receive(&rad->span);
}

static void pciradio_stop_dma(struct pciradio *rad);
static void pciradio_reset_serial(struct pciradio *rad);
static void pciradio_restart_dma(struct pciradio *rad);

#ifdef      LEAVE_THIS_COMMENTED_OUT
static irqreturn_t pciradio_interrupt(int irq, void *dev_id, struct pt_regs *regs)
#endif

DAHDI_IRQ_HANDLER(pciradio_interrupt)
{
      struct pciradio *rad = dev_id;
      unsigned char ints,byte1,byte2,gotcor,gotctcss,gotslowctcss,ctcss;
      int i,x,gotrx;

      ints = inb(rad->ioaddr + RAD_INTSTAT);
      outb(ints, rad->ioaddr + RAD_INTSTAT);

      if (!ints)
            return IRQ_NONE;

      if (ints & 0x10) {
            /* Stop DMA, wait for watchdog */
            printk(KERN_INFO "RADIO PCI Master abort\n");
            pciradio_stop_dma(rad);
            return IRQ_RETVAL(1);
      }
      
      if (ints & 0x20) {
            printk(KERN_INFO "RADIO PCI Target abort\n");
            return IRQ_RETVAL(1);
      }

      if (ints & 0x0f) {

            rad->intcount++;
            x = rad->intcount % rad->nchans;
            /* freeze */
            __pciradio_setcreg(rad,0,rad->mx828_addr | 4);
            /* read SAUDIO_STATUS for the proper channel */
            byte1 = rad->saudio_status[x] = __pciradio_getcreg(rad,x);
            /* thaw */
            __pciradio_setcreg(rad,0,rad->mx828_addr);
            /* get COR input */
            byte2 = __pciradio_getcreg(rad,9);
            /* get bit for this channel */
            gotcor = byte2 & (1 << x);
            if (rad->radmode[x] & RADMODE_INVERTCOR) gotcor = !gotcor;
            rad->gotcor[x] = gotcor;
            if (rad->radmode[x] & RADMODE_IGNORECOR) gotcor = 1;
            gotslowctcss = 0;
            if ((byte1 & RAD_CTCSSVALID) && 
                  ((byte1 & RAD_CTCSSMASK) != RAD_CTCSSOTHER)) gotslowctcss = 1;
            gotctcss = 1;
            ctcss = 0;
            /* set ctcss to 1 if decoding ctcss */
            if (!rad->rxcode[x][0])
            {
                  for(i = 1; i <= NUM_CODES; i++)
                  {
                        if (rad->rxcode[x][i])
                        {
                              ctcss = 1;
                              break;
                        }
                  }
            }
            if (ctcss)
            {
                  if ((!(byte1 & 0x40)) || 
                        ((!rad->gotrx[x]) && (!gotslowctcss))) gotctcss = 0; 
            }
            rad->present_code[x] = 0;
            if (rad->rxcode[x][0]) 
            {
                  if (byte1 & 0x80) gotctcss = gotslowctcss = 1; else gotctcss = 0;
            } else if (gotslowctcss) rad->present_code[x] = (byte1 & RAD_CTCSSMASK) + 1;
            if (rad->radmode[x] & RADMODE_EXTTONE)
            {
                  unsigned mask = 1 << (x + 4); /* they're on the UIOB's */
                  unsigned char byteuio;

                  /* set UIOB as input */
                  byteuio = __pciradio_getcreg(rad,0xe);
                  byteuio |= mask;
                  __pciradio_setcreg(rad,0xe,byteuio);
                  /* get UIO input */
                  byteuio = __pciradio_getcreg(rad,8);
                  if (rad->radmode[x] & RADMODE_EXTINVERT)
                        gotctcss = gotslowctcss = ((byteuio & mask) == 0);
                  else
                        gotctcss = gotslowctcss = ((byteuio & mask) != 0);
            }
            rad->gotct[x] = gotslowctcss;
            if ((rad->radmode[x] & RADMODE_IGNORECT) || 
               ((!(rad->radmode[x] & RADMODE_EXTTONE)) && (!ctcss))) 
            {
                  gotctcss = 1;
                  gotslowctcss = 1;
                  rad->present_code[x] = 0;
            }
            if(rad->newctcssstate[x] != gotctcss){
                  rad->newctcssstate[x] = gotctcss;
                  if(rad->newctcssstate[x])
                        rad->ctcsstimer[x]=rad->ctcssacquiretime[x];
                  else
                        rad->ctcsstimer[x]=rad->ctcsstalkofftime[x];
            }
            else{
                   if(!rad->ctcsstimer[x])
                        rad->ctcssstate[x] = rad->newctcssstate[x];
                  else
                        rad->ctcsstimer[x]--;
            }
            gotrx = gotcor && rad->ctcssstate[x];
            if (gotrx != rad->gotrx[x])
            {
                  rad->gotrxtimer[x] = rad->debouncetime[x];
            }
            rad->gotrx[x] = gotrx;
            if (rad->present_code[x] != rad->last_code[x])
            {
                  rad->encdec.req[x] = 1;
                  rad->last_code[x] = rad->present_code[x];
            }
            _do_encdec(rad);
            for(x = 0; x < rad->nchans; x++)
            {
                  unsigned char mask = 1 << x;

                  if (rad->gottx[x] != rad->lasttx[x])
                  {
                        if (rad->gottx[x])
                        {
                              rad->bursttimer[x] = 0;
                              rad->pasave |= mask;
                              __pciradio_setcreg(rad, 0xa, rad->pasave);
                        }
                        else
                        {
                              if (!rad->bursttime[x])
                              {
                                    rad->pasave &= ~mask;
                                    __pciradio_setcreg(rad, 0xa, rad->pasave);
                              }
                              else
                              {
                                    rad->bursttimer[x] = rad->bursttime[x];
                              }
                        }
                        rad->encdec.req[x] = 1;
                        rad->lasttx[x] = rad->gottx[x];
                  }
                  if (rad->bursttimer[x])
                  {
                        /* if just getting to zero */
                        if (!(--rad->bursttimer[x]))
                        {
                              rad->pasave &= ~mask;
                              __pciradio_setcreg(rad, 0xa, rad->pasave);
                        }
                  }

                  /* if timer active */
                  if (rad->gotrxtimer[x])
                  {
                        /* if just getting to zero */
                        if (!(--rad->gotrxtimer[x]))
                        {
                              mask = 1 << (x + 4);
                              rad->pasave &= ~mask;
                              if (gotctcss) rad->pasave |= mask;
                              __pciradio_setcreg(rad, 0xa, rad->pasave);

                              if (rad->gotrx[x] != rad->gotrx1[x])
                              {
                                    if (rad->gotrx[x]) {
                                        if (debug)
                                          {
                                                if (rad->present_code[x])
                                                    printk(KERN_DEBUG "Chan %d got rx (ctcss code %d)\n",x + 1,
                                                      cttable_rx[rad->rxcode[x][rad->present_code[x]]].code);
                                                else
                                                    printk(KERN_DEBUG "Chan %d got rx\n",x + 1);
                                          }
                                        dahdi_hooksig(&rad->chans[x],DAHDI_RXSIG_OFFHOOK);
                                    } else {
                                        if (debug) printk(KERN_DEBUG "Chan %d lost rx\n",x + 1);
                                        dahdi_hooksig(&rad->chans[x],DAHDI_RXSIG_ONHOOK);
                                    }
                                    rad->encdec.req[x] = 1; 
                              }
                              rad->gotrx1[x] = rad->gotrx[x];
                        }
                  }
            }
            /* process serial if any */
            /* send byte if there is one in buffer to send */
            if (rad->txlen && (rad->txlen != rad->txindex))
            {
                  /* if tx not busy */
                  if (!(__pciradio_getcreg(rad,9) & 0x80))
                  {
                        __pciradio_setcreg(rad, 4, rad->txbuf[rad->txindex++]);
                  }                       
            }
            rad->srxtimer++;
            /* if something in rx to read */
            while(__pciradio_getcreg(rad,9) & 0x10)
            {
                  unsigned char c = __pciradio_getcreg(rad,4);
                  rad->srxtimer = 0;
                  if (rad->rxindex < RAD_SERIAL_BUFLEN)
                  {
                        rad->rxbuf[rad->rxindex++] = c;
                  }
                  udelay(1);
            }
            pciradio_receiveprep(rad, ints);
            pciradio_transmitprep(rad, ints);
            i = 0;
            for(x = 0; x < 4; x++)
            {
                  if (rad->gottx[x]) i |= (1 << (x * 2));
                  if (rad->gotrx[x]) i |= (2 << (x * 2));
            }  
            /* output LED's */
            __pciradio_setcreg(rad, 9, i);
      }

      return IRQ_RETVAL(1);
}

static int pciradio_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data)
{
      int i,mycode;
      unsigned long flags;
      unsigned char byte1,byte2,mask;
      union {
            struct dahdi_radio_stat s;
            struct dahdi_radio_param p;
      } stack;

      struct pciradio *rad = chan->pvt;
      DECLARE_WAIT_QUEUE_HEAD(mywait);

      switch (cmd) {
      case DAHDI_RADIO_GETPARAM:
            if (copy_from_user(&stack.p, (__user void *) data, sizeof(stack.p))) return -EFAULT;
            spin_lock_irqsave(&rad->lock,flags);
            stack.p.data = 0; /* start with 0 value in output */
            switch(stack.p.radpar) {
            case DAHDI_RADPAR_INVERTCOR:
                  if (rad->radmode[chan->chanpos - 1] & RADMODE_INVERTCOR)
                        stack.p.data = 1;
                  break;
            case DAHDI_RADPAR_IGNORECOR:
                  if (rad->radmode[chan->chanpos - 1] & RADMODE_IGNORECOR)
                        stack.p.data = 1;
                  break;
            case DAHDI_RADPAR_IGNORECT:
                  if (rad->radmode[chan->chanpos - 1] & RADMODE_IGNORECT)
                        stack.p.data = 1;
                  break;
            case DAHDI_RADPAR_NOENCODE:
                  if (rad->radmode[chan->chanpos - 1] & RADMODE_NOENCODE)
                        stack.p.data = 1;
                  break;
            case DAHDI_RADPAR_CORTHRESH:
                  stack.p.data = rad->corthresh[chan->chanpos - 1] & 7;
                  break;
            case DAHDI_RADPAR_EXTRXTONE:
                  if (rad->radmode[chan->chanpos - 1] & RADMODE_EXTTONE)
                  {
                        stack.p.data = 1;
                        if (rad->radmode[chan->chanpos - 1] & RADMODE_EXTINVERT)
                        {
                              stack.p.data = 2;
                        }
                  }
                  break;
            case DAHDI_RADPAR_NUMTONES:
                  stack.p.data = NUM_CODES;
                  break;
            case DAHDI_RADPAR_RXTONE:
                  if ((stack.p.index < 1) || (stack.p.index > NUM_CODES)) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  stack.p.data = 
                        cttable_rx[rad->rxcode[chan->chanpos - 1][stack.p.index] & 0x7fff].code;
                  break;
            case DAHDI_RADPAR_RXTONECLASS:
                  if ((stack.p.index < 1) || (stack.p.index > NUM_CODES)) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  stack.p.data = rad->rxclass[chan->chanpos - 1][stack.p.index] & 0xffff;
                  break;
            case DAHDI_RADPAR_TXTONE:
                  if (stack.p.index > NUM_CODES) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  stack.p.data = cttable_tx[rad->txcode[chan->chanpos - 1][stack.p.index] & 0x7fff].code;
                  /* if a DCS tone, return as such */
                  if (rad->txcode[chan->chanpos - 1][stack.p.index] & 0x8000)
                        stack.p.data |= 0x8000;
                  break;
            case DAHDI_RADPAR_DEBOUNCETIME:
                  stack.p.data = rad->debouncetime[chan->chanpos - 1];
                  break;

            case DAHDI_RADPAR_CTCSSACQUIRETIME:
                  stack.p.data = rad->ctcssacquiretime[chan->chanpos - 1];
                  break;

            case DAHDI_RADPAR_CTCSSTALKOFFTIME:
                  stack.p.data = rad->ctcsstalkofftime[chan->chanpos - 1];
                  break;

            case DAHDI_RADPAR_BURSTTIME:
                  stack.p.data = rad->bursttime[chan->chanpos - 1];
                  break;
            case DAHDI_RADPAR_UIODATA:
                  stack.p.data = 0;
                  byte1 = __pciradio_getcreg(rad,8);
                  if (byte1 & (1 << (chan->chanpos - 1))) stack.p.data |= 1;
                  if (byte1 & (1 << (chan->chanpos + 3))) stack.p.data |= 2;
                  break;
            case DAHDI_RADPAR_UIOMODE:
                  stack.p.data = 0;
                  byte1 = __pciradio_getcreg(rad,0xe);
                  if (byte1 & (1 << (chan->chanpos - 1))) stack.p.data |= 1;
                  if (byte1 & (1 << (chan->chanpos + 3))) stack.p.data |= 2;
                  break;
            case DAHDI_RADPAR_REMMODE:
                  stack.p.data = rad->remmode[chan->chanpos - 1];
                  break;
            default:
                  spin_unlock_irqrestore(&rad->lock,flags);
                  return -EINVAL;
            }
            spin_unlock_irqrestore(&rad->lock,flags);
            if (copy_to_user((__user void *) data, &stack.p, sizeof(stack.p))) return -EFAULT;
            break;
      case DAHDI_RADIO_SETPARAM:
            if (copy_from_user(&stack.p, (__user void *) data, sizeof(stack.p))) return -EFAULT;
            spin_lock_irqsave(&rad->lock,flags);
            switch(stack.p.radpar) {
            case DAHDI_RADPAR_INVERTCOR:
                  if (stack.p.data)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_INVERTCOR;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_INVERTCOR;
                  break;
            case DAHDI_RADPAR_IGNORECOR:
                  if (stack.p.data)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_IGNORECOR;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_IGNORECOR;
                  break;
            case DAHDI_RADPAR_IGNORECT:
                  if (stack.p.data)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_IGNORECT;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_IGNORECT;
                  break;
            case DAHDI_RADPAR_NOENCODE:
                  if (stack.p.data)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_NOENCODE;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_NOENCODE;
                  break;
            case DAHDI_RADPAR_CORTHRESH:
                  if ((stack.p.data < 0) || (stack.p.data > 7)) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  rad->corthresh[chan->chanpos - 1] = stack.p.data;
                  byte1 = 0xc0 | (rad->corthresh[chan->chanpos - 1] << 2);
                  spin_unlock_irqrestore(&rad->lock,flags);
                  mx828_command_wait(rad,chan->chanpos - 1, MX828_GEN_CTRL, &byte1, &byte2);
                  spin_lock_irqsave(&rad->lock,flags);
                  break;
            case DAHDI_RADPAR_EXTRXTONE:
                  if (stack.p.data)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_EXTTONE;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_EXTTONE;
                  if (stack.p.data > 1)
                        rad->radmode[chan->chanpos - 1] |= RADMODE_EXTINVERT;
                  else
                        rad->radmode[chan->chanpos - 1] &= ~RADMODE_EXTINVERT;
                  break;
            case DAHDI_RADPAR_INITTONE:
                  for(i = 0; i <= NUM_CODES; i++)
                  {
                        rad->rxcode[chan->chanpos - 1][i] = 0;
                        rad->rxclass[chan->chanpos - 1][i] = 0;
                        rad->txcode[chan->chanpos - 1][i] = 0;
                  }
                  spin_unlock_irqrestore(&rad->lock,flags);
                  for(i = 0; i < NUM_CODES; i++)
                  {
                        /* set to no encode/decode */
                        byte1 = 0;
                        mx828_command_wait(rad,chan->chanpos - 1, MX828_SAUDIO_CTRL, &byte1, &byte2 );
                        /* set rx tone to none */
                        byte1 = i << 4;
                        byte2 = 0;
                        mx828_command_wait(rad,chan->chanpos - 1, MX828_RX_TONE, &byte1, &byte2 );
                  }
                  spin_lock_irqsave(&rad->lock,flags);
                  break;
            case DAHDI_RADPAR_RXTONE:
                  if (!stack.p.index) /* if RX DCS mode */
                  {
                        if ((stack.p.data < 0) || (stack.p.data > 777)) {
                              spin_unlock_irqrestore(&rad->lock,flags);
                              return -EINVAL;
                        }
                        mycode = getdcstone(stack.p.data);
                        if (mycode < 0) {
                              spin_unlock_irqrestore(&rad->lock,flags);
                              return -EINVAL;
                        }
                        rad->rxcode[chan->chanpos - 1][0] = mycode;
                        rad->encdec.req[chan->chanpos - 1] = 1;
                        break;
                  }
                  if ((stack.p.index < 1) || (stack.p.index > NUM_CODES)) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  mycode = getrxtone(stack.p.data);
                  if (mycode < 0) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  rad->rxcode[chan->chanpos - 1][stack.p.index] = mycode;
                  byte1 = cttable_rx[mycode].b1 | ((stack.p.index - 1) << 4);
                  byte2 = cttable_rx[mycode].b2;
                  spin_unlock_irqrestore(&rad->lock,flags);
                  mx828_command_wait(rad,chan->chanpos - 1, MX828_RX_TONE, &byte1, &byte2 );
                  spin_lock_irqsave(&rad->lock,flags);
                  /* zot out DCS one if there */
                  rad->rxcode[chan->chanpos - 1][0] = 0;
                  rad->encdec.req[chan->chanpos - 1] = 1;
                  break;
            case DAHDI_RADPAR_RXTONECLASS:
                  if ((stack.p.index < 1) || (stack.p.index > NUM_CODES)) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  rad->rxclass[chan->chanpos - 1][stack.p.index] = stack.p.data & 0xffff;
                  break;
            case DAHDI_RADPAR_TXTONE:
                  if (stack.p.index > NUM_CODES) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  if (stack.p.data & 0x8000) /* if dcs */
                        mycode = getdcstone(stack.p.data & 0x7fff);
                  else
                        mycode = gettxtone(stack.p.data);
                  if (mycode < 0) {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  if (stack.p.data & 0x8000) mycode |= 0x8000;
                  rad->txcode[chan->chanpos - 1][stack.p.index] = mycode;
                  rad->encdec.req[chan->chanpos - 1] = 1;
                  break;
            case DAHDI_RADPAR_DEBOUNCETIME:
                  rad->debouncetime[chan->chanpos - 1] = stack.p.data;
                  break;

            case DAHDI_RADPAR_CTCSSACQUIRETIME:
                  rad->ctcssacquiretime[chan->chanpos - 1] = stack.p.data;
                  break;

            case DAHDI_RADPAR_CTCSSTALKOFFTIME:
                  rad->ctcsstalkofftime[chan->chanpos - 1] = stack.p.data;
                  break;

            case DAHDI_RADPAR_BURSTTIME:
                  rad->bursttime[chan->chanpos - 1] = stack.p.data;
                  break;
            case DAHDI_RADPAR_UIODATA:
                  byte1 = __pciradio_getcreg(rad,8);
                  byte1 &= ~(1 << (chan->chanpos - 1));
                  byte1 &= ~(1 << (chan->chanpos + 3));
                  if (stack.p.data & 1) byte1 |= (1 << (chan->chanpos - 1));
                  if (stack.p.data & 2) byte1 |= (1 << (chan->chanpos + 3));
                  __pciradio_setcreg(rad,8,byte1);
                  break;
            case DAHDI_RADPAR_UIOMODE:
                  byte1 = __pciradio_getcreg(rad,0xe);
                  byte1 &= ~(1 << (chan->chanpos - 1));
                  byte1 &= ~(1 << (chan->chanpos + 3));
                  if (stack.p.data & 1) byte1 |= (1 << (chan->chanpos - 1));
                  if (stack.p.data & 2) byte1 |= (1 << (chan->chanpos + 3));
                  __pciradio_setcreg(rad,0xe,byte1);
                  break;
            case DAHDI_RADPAR_REMMODE:
                  rad->remmode[chan->chanpos - 1] = stack.p.data;
                  break;
            case DAHDI_RADPAR_REMCOMMAND:
                  /* if no remote mode, return an error */
                  if (rad->remmode[chan->chanpos - 1] == DAHDI_RADPAR_REM_NONE)
                  {
                        spin_unlock_irqrestore(&rad->lock,flags);
                        return -EINVAL;
                  }
                  i = 0;
                  if (rad->remmode[chan->chanpos - 1] == DAHDI_RADPAR_REM_RBI1)
                  {
                        /* set UIOA and UIOB for output */
                        byte1 = __pciradio_getcreg(rad,0xe);
                        mask = (1 << (chan->chanpos - 1)) | 
                              (1 << (chan->chanpos + 3));
                        byte2 = byte1 & (~mask);
                        i = (byte2 != byte1);
                        __pciradio_setcreg(rad,0xe,byte2);
                        byte1 = __pciradio_getcreg(rad,8);
                        mask = 1 << (chan->chanpos - 1);
                        byte2 = byte1 | mask;
                        i = (byte2 != byte1);
                        __pciradio_setcreg(rad,8,byte2);
                        spin_unlock_irqrestore(&rad->lock,flags);
                        if (i || (jiffies < rad->lastremcmd + 10))
                              interruptible_sleep_on_timeout(&mywait,10);
                        rad->lastremcmd = jiffies;
                        rbi_out(rad,chan->chanpos - 1,(unsigned char *)&stack.p.data);
                        spin_lock_irqsave(&rad->lock,flags);
                        break;
                  }
                  spin_unlock_irqrestore(&rad->lock,flags);
                  for(;;)
                  {
                        int x;

                        spin_lock_irqsave(&rad->lock,flags);
                        x = rad->remote_locked || (__pciradio_getcreg(rad,0xc) & 2);
                        if (!x) rad->remote_locked = 1;
                        spin_unlock_irqrestore(&rad->lock,flags);
                        if (x) interruptible_sleep_on_timeout(&mywait,2);
                        else break;
                  }     
                  spin_lock_irqsave(&rad->lock,flags);
                  /* set UIOA for input and UIOB for output */
                  byte1 = __pciradio_getcreg(rad,0xe);
                  mask = 1 << (chan->chanpos + 3); /* B an output */
                  byte2 = byte1 & (~mask);
                  byte2 |= 1 << (chan->chanpos - 1); /* A in input */
                  __pciradio_setcreg(rad,0xe,byte2);
                  byte1 = __pciradio_getcreg(rad,8);
                  byte2 = byte1 | mask;
                  byte2 |= 1 << (chan->chanpos - 1);
                  byte2 |= 1 << (chan->chanpos + 3);
                  __pciradio_setcreg(rad,8,byte2);
                  spin_unlock_irqrestore(&rad->lock,flags);
                  if (byte1 != byte2) 
                        interruptible_sleep_on_timeout(&mywait,3);
                  while (jiffies < rad->lastremcmd + 10)
                        interruptible_sleep_on_timeout(&mywait,10);
                  rad->lastremcmd = jiffies;
                  for(;;)
                  {
                        if (!(__pciradio_getcreg(rad,0xc) & 2)) break;
                        interruptible_sleep_on_timeout(&mywait,2);
                  }
                  spin_lock_irqsave(&rad->lock,flags);
                  /* enable and address async serializer */
                  __pciradio_setcreg(rad,0xf,rad->pfsave | ((chan->chanpos - 1) << 4) | 0x80);
                  /* copy tx buffer */
                  memcpy(rad->txbuf,stack.p.buf,stack.p.index);
                  rad->txlen = stack.p.index;
                  rad->txindex = 0;
                  rad->rxindex = 0;
                  rad->srxtimer = 0;
                  memset(stack.p.buf,0,SERIAL_BUFLEN);
                  stack.p.index = 0;
                  if (stack.p.data) for(;;)
                  {                 
                        rad->rxbuf[rad->rxindex] = 0;
                        if ((rad->rxindex < stack.p.data) &&
                          (rad->srxtimer < SRX_TIMEOUT) &&
                            ((rad->remmode[chan->chanpos - 1] == DAHDI_RADPAR_REM_SERIAL) ||
                              (!strchr((char *)rad->rxbuf,'\r'))))
                        {
                              spin_unlock_irqrestore(&rad->lock,flags);
                              interruptible_sleep_on_timeout(&mywait,2);
                              spin_lock_irqsave(&rad->lock,flags);
                              continue;
                        }
                        memset(stack.p.buf,0,SERIAL_BUFLEN);
                        if (stack.p.data && (rad->rxindex > stack.p.data))
                              rad->rxindex = stack.p.data;
                        if (rad->rxindex)
                              memcpy(stack.p.buf,rad->rxbuf,rad->rxindex);
                        stack.p.index = rad->rxindex;
                        break;
                  }
                  /* wait for done if in SERIAL_ASCII mode, or if no Rx aftwards */
                  if ((rad->remmode[chan->chanpos - 1] == DAHDI_RADPAR_REM_SERIAL_ASCII) ||
                        (!stack.p.data))
                  {                 
                        /* wait for TX to be done if not already */
                        while(rad->txlen && (rad->txindex < rad->txlen))
                        {
                              spin_unlock_irqrestore(&rad->lock,flags);
                              interruptible_sleep_on_timeout(&mywait,2);
                              spin_lock_irqsave(&rad->lock,flags);
                        }
                        /* disable and un-address async serializer */
                        __pciradio_setcreg(rad,0xf,rad->pfsave); 
                  }
                  rad->remote_locked = 0;
                  spin_unlock_irqrestore(&rad->lock,flags);
                  if (rad->remmode[chan->chanpos - 1] == DAHDI_RADPAR_REM_SERIAL_ASCII)
                        interruptible_sleep_on_timeout(&mywait,100);
                  if (copy_to_user((__user void *) data, &stack.p, sizeof(stack.p))) return -EFAULT;
                  return 0;
            default:
                  spin_unlock_irqrestore(&rad->lock,flags);
                  return -EINVAL;
            }
            spin_unlock_irqrestore(&rad->lock,flags);
            break;
      case DAHDI_RADIO_GETSTAT:
            spin_lock_irqsave(&rad->lock,flags);
            /* start with clean object */
            memset(&stack.s, 0, sizeof(stack.s));
            /* if we have rx */
            if (rad->gotrx[chan->chanpos - 1])
            {
                  stack.s.radstat |= DAHDI_RADSTAT_RX;
                  if (rad->rxcode[chan->chanpos - 1][0])
                      stack.s.ctcode_rx = 
                        dcstable[rad->rxcode[chan->chanpos - 1][0]].code | 0x8000;
                  else {
                        stack.s.ctcode_rx = 
                            cttable_rx[rad->rxcode[chan->chanpos - 1][rad->present_code[chan->chanpos - 1]]].code;
                        stack.s.ctclass = 
                            rad->rxclass[chan->chanpos - 1][rad->present_code[chan->chanpos - 1]];
                  }
            }
            /* if we have tx */
            if (rad->gottx[chan->chanpos - 1])
            {
                  unsigned short x,myindex;

                  stack.s.radstat |= DAHDI_RADSTAT_TX;
                  stack.s.radstat |= DAHDI_RADSTAT_TX;

                  myindex = 0;
                  if ((!rad->rxcode[chan->chanpos - 1][0]) 
                        && (rad->present_code[chan->chanpos - 1])) 
                              myindex = rad->present_code[chan->chanpos - 1];
                  x = rad->txcode[chan->chanpos - 1][myindex];
                  if (x & 0x8000)
                        stack.s.ctcode_tx = dcstable[x & 0x7fff].code | 0x8000;
                  else
                        stack.s.ctcode_tx = cttable_tx[x].code;
                  
            }

            if (rad->radmode[chan->chanpos - 1] & RADMODE_IGNORECOR)
                  stack.s.radstat |= DAHDI_RADSTAT_IGNCOR;
            if (rad->radmode[chan->chanpos - 1] & RADMODE_IGNORECT)
                  stack.s.radstat |= DAHDI_RADSTAT_IGNCT;
            if (rad->radmode[chan->chanpos - 1] & RADMODE_NOENCODE)
                  stack.s.radstat |= DAHDI_RADSTAT_NOENCODE;
            if (rad->gotcor[chan->chanpos - 1])
                  stack.s.radstat |= DAHDI_RADSTAT_RXCOR;
            if (rad->gotct[chan->chanpos - 1])
                  stack.s.radstat |= DAHDI_RADSTAT_RXCT;
            spin_unlock_irqrestore(&rad->lock,flags);
            if (copy_to_user((__user void *) data, &stack.s, sizeof(stack.s))) return -EFAULT;
            break;
      default:
            return -ENOTTY;
      }
      return 0;

}

static int pciradio_open(struct dahdi_chan *chan)
{
      struct pciradio *rad = chan->pvt;
      if (rad->dead)
            return -ENODEV;
      rad->usecount++;
      return 0;
}

static int pciradio_watchdog(struct dahdi_span *span, int event)
{
      printk(KERN_INFO "PCI RADIO: Restarting DMA\n");
      pciradio_restart_dma(span->pvt);
      return 0;
}

static int pciradio_close(struct dahdi_chan *chan)
{
      struct pciradio *rad = chan->pvt;
      rad->usecount--;
      /* If we're dead, release us now */
      if (!rad->usecount && rad->dead) 
            pciradio_release(rad);
      return 0;
}

static int pciradio_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig)
{
      struct pciradio *rad = chan->pvt;

      switch(txsig) {
      case DAHDI_TXSIG_START:
      case DAHDI_TXSIG_OFFHOOK:
            rad->gottx[chan->chanpos - 1] = 1;
            break;
      case DAHDI_TXSIG_ONHOOK:
            rad->gottx[chan->chanpos - 1] = 0;
            break;
      default:
            printk(KERN_DEBUG "pciradio: Can't set tx state to %d\n", txsig);
            break;
      }
      if (debug) 
            printk(KERN_DEBUG "pciradio: Setting Radio hook state to %d on chan %d\n", txsig, chan->chanpos);
      return 0;
}

static int pciradio_initialize(struct pciradio *rad)
{
      int x;

      /* DAHDI stuff */
      sprintf(rad->span.name, "PCIRADIO/%d", rad->pos);
      sprintf(rad->span.desc, "Board %d", rad->pos + 1);
      rad->span.deflaw = DAHDI_LAW_MULAW;
      for (x=0;x<rad->nchans;x++) {
            sprintf(rad->chans[x].name, "PCIRADIO/%d/%d", rad->pos, x);
            rad->chans[x].sigcap = DAHDI_SIG_SF | DAHDI_SIG_EM;
            rad->chans[x].chanpos = x+1;
            rad->chans[x].pvt = rad;
            rad->debouncetime[x] = RAD_GOTRX_DEBOUNCE_TIME;
            rad->ctcssacquiretime[x] = RAD_CTCSS_ACQUIRE_TIME;
            rad->ctcsstalkofftime[x] = RAD_CTCSS_TALKOFF_TIME;
      }
      rad->span.chans = &rad->chans;
      rad->span.channels = rad->nchans;
      rad->span.hooksig = pciradio_hooksig;
      rad->span.open = pciradio_open;
      rad->span.close = pciradio_close;
      rad->span.flags = DAHDI_FLAG_RBS;
      rad->span.ioctl = pciradio_ioctl;
      rad->span.watchdog = pciradio_watchdog;
      init_waitqueue_head(&rad->span.maintq);

      rad->span.pvt = rad;
      if (dahdi_register(&rad->span, 0)) {
            printk(KERN_NOTICE "Unable to register span with DAHDI\n");
            return -1;
      }
      return 0;
}

static void wait_just_a_bit(int foo)
{
      long newjiffies;
      newjiffies = jiffies + foo;
      while(jiffies < newjiffies);
}

static int pciradio_hardware_init(struct pciradio *rad)
{
unsigned char byte1,byte2;
int   x;
unsigned long endjif;

      /* Signal Reset */
      outb(0x01, rad->ioaddr + RAD_CNTL);

      /* Reset PCI Interface chip and registers (and serial) */
      outb(0x06, rad->ioaddr + RAD_CNTL);
      /* Setup our proper outputs */
      rad->ios = 0xfe;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);

      /* Set all to outputs except AUX 3 & 4, which are inputs */
      outb(0x67, rad->ioaddr + RAD_AUXC);

      /* Select alternate function for AUX0 */
      outb(0x4, rad->ioaddr + RAD_AUXFUNC);
      
      /* Wait 1/4 of a sec */
      wait_just_a_bit(HZ/4);

      /* attempt to load the Xilinx Chip */
      /* De-assert CS+Write */
      rad->ios |= XCS;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);
      /* Assert PGM */
      rad->ios &= ~XPGM;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);
      /* wait for INIT and DONE to go low */
      endjif = jiffies + 10;
      while (inb(rad->ioaddr + RAD_AUXR) & (XINIT | XDONE) && (jiffies <= endjif));
      if (endjif < jiffies) {
            printk(KERN_DEBUG "Timeout waiting for INIT and DONE to go low\n");
            return -1;
      }
      if (debug) printk(KERN_DEBUG "fwload: Init and done gone to low\n");
      /* De-assert PGM */
      rad->ios |= XPGM;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);
      /* wait for INIT to go high (clearing done */
      endjif = jiffies + 10;
      while (!(inb(rad->ioaddr + RAD_AUXR) & XINIT) && (jiffies <= endjif));
      if (endjif < jiffies) {
            printk(KERN_DEBUG "Timeout waiting for INIT to go high\n");
            return -1;
      }
      if (debug) printk(KERN_DEBUG "fwload: Init went high (clearing done)\nNow loading...\n");
      /* Assert CS+Write */
      rad->ios &= ~XCS;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);
      for (x = 0; x < sizeof(radfw); x++)
         {
              /* write the byte */
            outb(radfw[x],rad->ioaddr + RAD_REGBASE);
              /* if DONE signal, we're done, exit */
            if (inb(rad->ioaddr + RAD_AUXR) & XDONE) break;
              /* if INIT drops, we're screwed, exit */
            if (!(inb(rad->ioaddr + RAD_AUXR) & XINIT)) break;
         }
      if (debug) printk(KERN_DEBUG "fwload: Transferred %d bytes into chip\n",x);
      /* Wait for FIFO to clear */
      endjif = jiffies + 2;
      while (jiffies < endjif); /* wait */
      printk(KERN_DEBUG "Transfered %d bytes into chip\n",x);
      /* De-assert CS+Write */
      rad->ios |= XCS;
      outb(rad->ios, rad->ioaddr + RAD_AUXD);
      if (debug) printk(KERN_INFO "fwload: Loading done!\n");     
      /* Wait for FIFO to clear */
      endjif = jiffies + 2;
      while (jiffies < endjif); /* wait */
      if (!(inb(rad->ioaddr + RAD_AUXR) & XINIT))
         {
            printk(KERN_NOTICE "Drove Init low!! CRC Error!!!\n");
            return -1;
         }
      if (!(inb(rad->ioaddr + RAD_AUXR) & XDONE))
         {
            printk(KERN_INFO "Did not get DONE signal. Short file maybe??\n");
            return -1;
         }
      wait_just_a_bit(2);
      /* get the thingy started */
      outb(0,rad->ioaddr + RAD_REGBASE);
      outb(0,rad->ioaddr + RAD_REGBASE);
      printk(KERN_INFO "Xilinx Chip successfully loaded, configured and started!!\n");

      wait_just_a_bit(HZ/4);


      /* Back to normal, with automatic DMA wrap around */
      outb(0x30 | 0x01, rad->ioaddr + RAD_CNTL);
      
      /* Configure serial port for MSB->LSB operation */
      outb(0xc1, rad->ioaddr + RAD_SERCTL); /* DEBUG set double dlck to 0 SR */

      rad->pasave = 0;
      __pciradio_setcreg(rad,0xa,rad->pasave);

      __pciradio_setcreg(rad,0xf,rad->pfsave);
      __pciradio_setcreg(rad,8,0xff);
      __pciradio_setcreg(rad,0xe,0xff);
      __pciradio_setcreg(rad,9,0);
      rad->pfsave = 0;

      /* Delay FSC by 0 so it's properly aligned */
      outb(/* 1 */ 0, rad->ioaddr + RAD_FSCDELAY);

      /* Setup DMA Addresses */
      outl(rad->writedma,                    rad->ioaddr + RAD_DMAWS);        /* Write start */
      outl(rad->writedma + DAHDI_CHUNKSIZE * 4 - 4, rad->ioaddr + RAD_DMAWI);       /* Middle (interrupt) */
      outl(rad->writedma + DAHDI_CHUNKSIZE * 8 - 4, rad->ioaddr + RAD_DMAWE);             /* End */
      
      outl(rad->readdma,                         rad->ioaddr + RAD_DMARS);    /* Read start */
      outl(rad->readdma + DAHDI_CHUNKSIZE * 4 - 4,     rad->ioaddr + RAD_DMARI);    /* Middle (interrupt) */
      outl(rad->readdma + DAHDI_CHUNKSIZE * 8 - 4, rad->ioaddr + RAD_DMARE);  /* End */
      
      /* Clear interrupts */
      outb(0xff, rad->ioaddr + RAD_INTSTAT);

      /* Wait 1/4 of a second more */
      wait_just_a_bit(HZ/4);

      for(x = 0; x < rad->nchans; x++)
      {
            mx828_command_wait(rad,x, MX828_GEN_RESET, &byte1, &byte2 );
            byte1 = 0x3f;
            byte2 = 0x3f;
            mx828_command_wait(rad,x, MX828_AUD_CTRL, &byte1, &byte2 );
            byte1 = 0;
            mx828_command_wait(rad,x, MX828_SAUDIO_SETUP, &byte1, &byte2 );
            byte1 = 0;
            mx828_command_wait(rad,x, MX828_SAUDIO_CTRL, &byte1, &byte2 );
            byte1 = 0xc8;  /* default COR thresh is 2 */
            mx828_command_wait(rad,x, MX828_GEN_CTRL, &byte1, &byte2);
            rad->corthresh[x] = 2;
      }
      /* Wait 1/4 of a sec */
      wait_just_a_bit(HZ/4);

      return 0;
}

static void pciradio_enable_interrupts(struct pciradio *rad)
{
      /* Enable interrupts (we care about all of them) */
      outb(0x3f, rad->ioaddr + RAD_MASK0);
      /* No external interrupts */
      outb(0x00, rad->ioaddr + RAD_MASK1);
}

static void pciradio_restart_dma(struct pciradio *rad)
{
      /* Reset Master and serial */
      outb(0x31, rad->ioaddr + RAD_CNTL);
      outb(0x01, rad->ioaddr + RAD_OPER);
}

static void pciradio_start_dma(struct pciradio *rad)
{
      /* Reset Master and serial */
      outb(0x3f, rad->ioaddr + RAD_CNTL);
      set_current_state(TASK_INTERRUPTIBLE);
      schedule_timeout(1);
      outb(0x31, rad->ioaddr + RAD_CNTL);
      outb(0x01, rad->ioaddr + RAD_OPER);
}

static void pciradio_stop_dma(struct pciradio *rad)
{
      outb(0x00, rad->ioaddr + RAD_OPER);
}

static void pciradio_reset_serial(struct pciradio *rad)
{
      /* Reset serial */
      outb(0x3f, rad->ioaddr + RAD_CNTL);
}

static void pciradio_disable_interrupts(struct pciradio *rad)     
{
      outb(0x00, rad->ioaddr + RAD_MASK0);
      outb(0x00, rad->ioaddr + RAD_MASK1);
}

static int __devinit pciradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
      int res;
      struct pciradio *rad;
      int x;
      static int initd_ifaces=0;
      
      if(initd_ifaces){
            memset((void *)ifaces,0,(sizeof(struct pciradio *))*RAD_MAX_IFACES);
            initd_ifaces=1;
      }
      for (x=0;x<RAD_MAX_IFACES;x++)
            if (!ifaces[x]) break;
      if (x >= RAD_MAX_IFACES) {
            printk(KERN_NOTICE "Too many interfaces\n");
            return -EIO;
      }
      
      if (pci_enable_device(pdev)) {
            res = -EIO;
      } else {
            rad = kmalloc(sizeof(struct pciradio), GFP_KERNEL);
            if (rad) {
                  int i;

                  ifaces[x] = rad;
                  rad->chans = rad->_chans;
                  memset(rad, 0, sizeof(struct pciradio));
                  spin_lock_init(&rad->lock);
                  rad->nchans = 4;
                  rad->ioaddr = pci_resource_start(pdev, 0);
                  rad->dev = pdev;
                  rad->pos = x;
                  for(i = 0; i < rad->nchans; i++) rad->lasttx[x] = rad->gotrx1[i] = -1;
                  /* Keep track of whether we need to free the region */
                  if (request_region(rad->ioaddr, 0xff, "pciradio")) 
                        rad->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 */
                  rad->writechunk = pci_alloc_consistent(pdev, DAHDI_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, &rad->writedma);
                  if (!rad->writechunk) {
                        printk(KERN_NOTICE "pciradio: Unable to allocate DMA-able memory\n");
                        if (rad->freeregion)
                              release_region(rad->ioaddr, 0xff);
                        return -ENOMEM;
                  }

                  rad->readchunk = rad->writechunk + DAHDI_MAX_CHUNKSIZE * 2; /* in doublewords */
                  rad->readdma = rad->writedma + DAHDI_MAX_CHUNKSIZE * 8;           /* in bytes */

                  if (pciradio_initialize(rad)) {
                        printk(KERN_INFO "pciradio: Unable to intialize\n");
                        /* Set Reset Low */
                        x=inb(rad->ioaddr + RAD_CNTL);
                        outb((~0x1)&x, rad->ioaddr + RAD_CNTL);
                        outb(x, rad->ioaddr + RAD_CNTL);
                        __pciradio_setcreg(rad,8,0xff);
                        __pciradio_setcreg(rad,0xe,0xff);
                        /* Free Resources */
                        free_irq(pdev->irq, rad);
                        if (rad->freeregion)
                              release_region(rad->ioaddr, 0xff);
                        pci_free_consistent(pdev, DAHDI_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, (void *)rad->writechunk, rad->writedma);
                        kfree(rad);
                        return -EIO;
                  }

                  /* Enable bus mastering */
                  pci_set_master(pdev);

                  /* Keep track of which device we are */
                  pci_set_drvdata(pdev, rad);

                  if (pciradio_hardware_init(rad)) {
                        /* Set Reset Low */
                        x=inb(rad->ioaddr + RAD_CNTL);
                        outb((~0x1)&x, rad->ioaddr + RAD_CNTL);
                        outb(x, rad->ioaddr + RAD_CNTL);
                        __pciradio_setcreg(rad,8,0xff);
                        __pciradio_setcreg(rad,0xe,0xff);
                        /* Free Resources */
                        free_irq(pdev->irq, rad);
                        if (rad->freeregion)
                              release_region(rad->ioaddr, 0xff);
                        pci_free_consistent(pdev, DAHDI_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, (void *)rad->writechunk, rad->writedma);
                        pci_set_drvdata(pdev, NULL);
                        dahdi_unregister(&rad->span);
                        kfree(rad);
                        return -EIO;

                  }

                  if (request_irq(pdev->irq, pciradio_interrupt, DAHDI_IRQ_SHARED, "pciradio", rad)) {
                        printk(KERN_NOTICE "pciradio: Unable to request IRQ %d\n", pdev->irq);
                        if (rad->freeregion)
                              release_region(rad->ioaddr, 0xff);
                        pci_free_consistent(pdev, DAHDI_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, (void *)rad->writechunk, rad->writedma);
                        pci_set_drvdata(pdev, NULL);
                        kfree(rad);
                        return -EIO;
                  }

                  /* Enable interrupts */
                  pciradio_enable_interrupts(rad);
                  /* Initialize Write/Buffers to all blank data */
                  memset((void *)rad->writechunk,0,DAHDI_MAX_CHUNKSIZE * 2 * 2 * 4);

                  /* Start DMA */
                  pciradio_start_dma(rad);
                  printk(KERN_INFO "Found a PCI Radio Card\n");
                  res = 0;
            } else
                  res = -ENOMEM;
      }
      return res;
}

static void pciradio_release(struct pciradio *rad)
{
      dahdi_unregister(&rad->span);
      if (rad->freeregion)
            release_region(rad->ioaddr, 0xff);
      kfree(rad);
      printk(KERN_INFO "Freed a PCI RADIO card\n");
}

static void __devexit pciradio_remove_one(struct pci_dev *pdev)
{
      struct pciradio *rad = pci_get_drvdata(pdev);
      if (rad) {

            /* Stop any DMA */
            pciradio_stop_dma(rad);
            pciradio_reset_serial(rad);

            /* In case hardware is still there */
            pciradio_disable_interrupts(rad);
            
            /* Immediately free resources */
            pci_free_consistent(pdev, DAHDI_MAX_CHUNKSIZE * 2 * 2 * 2 * 4, (void *)rad->writechunk, rad->writedma);
            free_irq(pdev->irq, rad);

            /* Reset PCI chip and registers */
            outb(0x3e, rad->ioaddr + RAD_CNTL); 

            /* Clear Reset Line */
            outb(0x3f, rad->ioaddr + RAD_CNTL); 

            __pciradio_setcreg(rad,8,0xff);
            __pciradio_setcreg(rad,0xe,0xff);

            /* Release span, possibly delayed */
            if (!rad->usecount)
                  pciradio_release(rad);
            else
                  rad->dead = 1;
      }
}

static struct pci_device_id pciradio_pci_tbl[] = {
      { 0xe159, 0x0001, 0xe16b, PCI_ANY_ID, 0, 0, (unsigned long)"PCIRADIO" },
      { 0 }
};

MODULE_DEVICE_TABLE(pci, pciradio_pci_tbl);

static struct pci_driver pciradio_driver = {
      .name = "pciradio",
      .probe = pciradio_init_one,
      .remove = __devexit_p(pciradio_remove_one),
      .id_table = pciradio_pci_tbl,
};

static int __init pciradio_init(void)
{
      int res;

      res = dahdi_pci_module(&pciradio_driver);
      if (res)
            return -ENODEV;
      return 0;
}

static void __exit pciradio_cleanup(void)
{
      pci_unregister_driver(&pciradio_driver);
}

module_param(debug, int, 0600);

MODULE_DESCRIPTION("DAHDI Telephony PCI Radio Card Driver");
MODULE_AUTHOR("Jim Dixon <jim@lambdatel.com>");
MODULE_LICENSE("GPL v2");

module_init(pciradio_init);
module_exit(pciradio_cleanup);

Generated by  Doxygen 1.6.0   Back to index