linux/drivers/staging/comedi/drivers/icp_multi.c
<<
>>
Prefs
   1/*
   2    comedi/drivers/icp_multi.c
   3
   4    COMEDI - Linux Control and Measurement Device Interface
   5    Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
   6
   7    This program is free software; you can redistribute it and/or modify
   8    it under the terms of the GNU General Public License as published by
   9    the Free Software Foundation; either version 2 of the License, or
  10    (at your option) any later version.
  11
  12    This program is distributed in the hope that it will be useful,
  13    but WITHOUT ANY WARRANTY; without even the implied warranty of
  14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15    GNU General Public License for more details.
  16
  17    You should have received a copy of the GNU General Public License
  18    along with this program; if not, write to the Free Software
  19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20
  21*/
  22
  23/*
  24Driver: icp_multi
  25Description: Inova ICP_MULTI
  26Author: Anne Smorthit <anne.smorthit@sfwte.ch>
  27Devices: [Inova] ICP_MULTI (icp_multi)
  28Status: works
  29
  30The driver works for analog input and output and digital input and output.
  31It does not work with interrupts or with the counters.  Currently no support
  32for DMA.
  33
  34It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
  35resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
  36ranges can be individually programmed for each channel.  Voltage or current
  37measurement is selected by jumper.
  38
  39There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
  40
  4116 x Digital Inputs, 24V
  42
  438 x Digital Outputs, 24V, 1A
  44
  454 x 16-bit counters
  46
  47Configuration options: not applicable, uses PCI auto config
  48*/
  49
  50#include <linux/pci.h>
  51#include <linux/delay.h>
  52#include <linux/interrupt.h>
  53
  54#include "../comedidev.h"
  55
  56#define PCI_DEVICE_ID_ICP_MULTI 0x8000
  57
  58#define ICP_MULTI_ADC_CSR       0       /* R/W: ADC command/status register */
  59#define ICP_MULTI_AI            2       /* R:   Analogue input data */
  60#define ICP_MULTI_DAC_CSR       4       /* R/W: DAC command/status register */
  61#define ICP_MULTI_AO            6       /* R/W: Analogue output data */
  62#define ICP_MULTI_DI            8       /* R/W: Digital inouts */
  63#define ICP_MULTI_DO            0x0A    /* R/W: Digital outputs */
  64#define ICP_MULTI_INT_EN        0x0C    /* R/W: Interrupt enable register */
  65#define ICP_MULTI_INT_STAT      0x0E    /* R/W: Interrupt status register */
  66#define ICP_MULTI_CNTR0         0x10    /* R/W: Counter 0 */
  67#define ICP_MULTI_CNTR1         0x12    /* R/W: counter 1 */
  68#define ICP_MULTI_CNTR2         0x14    /* R/W: Counter 2 */
  69#define ICP_MULTI_CNTR3         0x16    /* R/W: Counter 3 */
  70
  71/*  Define bits from ADC command/status register */
  72#define ADC_ST          0x0001  /* Start ADC */
  73#define ADC_BSY         0x0001  /* ADC busy */
  74#define ADC_BI          0x0010  /* Bipolar input range 1 = bipolar */
  75#define ADC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
  76#define ADC_DI          0x0040  /* Differential input mode 1 = differential */
  77
  78/*  Define bits from DAC command/status register */
  79#define DAC_ST          0x0001  /* Start DAC */
  80#define DAC_BSY         0x0001  /* DAC busy */
  81#define DAC_BI          0x0010  /* Bipolar input range 1 = bipolar */
  82#define DAC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
  83
  84/*  Define bits from interrupt enable/status registers */
  85#define ADC_READY       0x0001  /* A/d conversion ready interrupt */
  86#define DAC_READY       0x0002  /* D/a conversion ready interrupt */
  87#define DOUT_ERROR      0x0004  /* Digital output error interrupt */
  88#define DIN_STATUS      0x0008  /* Digital input status change interrupt */
  89#define CIE0            0x0010  /* Counter 0 overrun interrupt */
  90#define CIE1            0x0020  /* Counter 1 overrun interrupt */
  91#define CIE2            0x0040  /* Counter 2 overrun interrupt */
  92#define CIE3            0x0080  /* Counter 3 overrun interrupt */
  93
  94/*  Useful definitions */
  95#define Status_IRQ      0x00ff  /*  All interrupts */
  96
  97/*  Define analogue range */
  98static const struct comedi_lrange range_analog = { 4, {
  99                                                       UNI_RANGE(5),
 100                                                       UNI_RANGE(10),
 101                                                       BIP_RANGE(5),
 102                                                       BIP_RANGE(10)
 103                                                       }
 104};
 105
 106static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
 107
 108/*
 109==============================================================================
 110        Data & Structure declarations
 111==============================================================================
 112*/
 113
 114struct icp_multi_private {
 115        char valid;             /*  card is usable */
 116        void __iomem *io_addr;          /*  Pointer to mapped io address */
 117        unsigned int AdcCmdStatus;      /*  ADC Command/Status register */
 118        unsigned int DacCmdStatus;      /*  DAC Command/Status register */
 119        unsigned int IntEnable; /*  Interrupt Enable register */
 120        unsigned int IntStatus; /*  Interrupt Status register */
 121        unsigned int act_chanlist[32];  /*  list of scanned channel */
 122        unsigned char act_chanlist_len; /*  len of scanlist */
 123        unsigned char act_chanlist_pos; /*  actual position in MUX list */
 124        unsigned int *ai_chanlist;      /*  actaul chanlist */
 125        short *ai_data;         /*  data buffer */
 126        short ao_data[4];       /*  data output buffer */
 127        short di_data;          /*  Digital input data */
 128        unsigned int do_data;   /*  Remember digital output data */
 129};
 130
 131static void setup_channel_list(struct comedi_device *dev,
 132                               struct comedi_subdevice *s,
 133                               unsigned int *chanlist, unsigned int n_chan)
 134{
 135        struct icp_multi_private *devpriv = dev->private;
 136        unsigned int i, range, chanprog;
 137        unsigned int diff;
 138
 139        devpriv->act_chanlist_len = n_chan;
 140        devpriv->act_chanlist_pos = 0;
 141
 142        for (i = 0; i < n_chan; i++) {
 143                /*  Get channel */
 144                chanprog = CR_CHAN(chanlist[i]);
 145
 146                /*  Determine if it is a differential channel (Bit 15  = 1) */
 147                if (CR_AREF(chanlist[i]) == AREF_DIFF) {
 148                        diff = 1;
 149                        chanprog &= 0x0007;
 150                } else {
 151                        diff = 0;
 152                        chanprog &= 0x000f;
 153                }
 154
 155                /*  Clear channel, range and input mode bits
 156                 *  in A/D command/status register */
 157                devpriv->AdcCmdStatus &= 0xf00f;
 158
 159                /*  Set channel number and differential mode status bit */
 160                if (diff) {
 161                        /*  Set channel number, bits 9-11 & mode, bit 6 */
 162                        devpriv->AdcCmdStatus |= (chanprog << 9);
 163                        devpriv->AdcCmdStatus |= ADC_DI;
 164                } else
 165                        /*  Set channel number, bits 8-11 */
 166                        devpriv->AdcCmdStatus |= (chanprog << 8);
 167
 168                /*  Get range for current channel */
 169                range = range_codes_analog[CR_RANGE(chanlist[i])];
 170                /*  Set range. bits 4-5 */
 171                devpriv->AdcCmdStatus |= range;
 172
 173                /* Output channel, range, mode to ICP Multi */
 174                writew(devpriv->AdcCmdStatus,
 175                       devpriv->io_addr + ICP_MULTI_ADC_CSR);
 176        }
 177}
 178
 179static int icp_multi_insn_read_ai(struct comedi_device *dev,
 180                                  struct comedi_subdevice *s,
 181                                  struct comedi_insn *insn, unsigned int *data)
 182{
 183        struct icp_multi_private *devpriv = dev->private;
 184        int n, timeout;
 185
 186        /*  Disable A/D conversion ready interrupt */
 187        devpriv->IntEnable &= ~ADC_READY;
 188        writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
 189
 190        /*  Clear interrupt status */
 191        devpriv->IntStatus |= ADC_READY;
 192        writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
 193
 194        /*  Set up appropriate channel, mode and range data, for specified ch */
 195        setup_channel_list(dev, s, &insn->chanspec, 1);
 196
 197        for (n = 0; n < insn->n; n++) {
 198                /*  Set start ADC bit */
 199                devpriv->AdcCmdStatus |= ADC_ST;
 200                writew(devpriv->AdcCmdStatus,
 201                       devpriv->io_addr + ICP_MULTI_ADC_CSR);
 202                devpriv->AdcCmdStatus &= ~ADC_ST;
 203
 204                udelay(1);
 205
 206                /*  Wait for conversion to complete, or get fed up waiting */
 207                timeout = 100;
 208                while (timeout--) {
 209                        if (!(readw(devpriv->io_addr +
 210                                    ICP_MULTI_ADC_CSR) & ADC_BSY))
 211                                goto conv_finish;
 212
 213                        udelay(1);
 214                }
 215
 216                /*  If we reach here, a timeout has occurred */
 217                comedi_error(dev, "A/D insn timeout");
 218
 219                /*  Disable interrupt */
 220                devpriv->IntEnable &= ~ADC_READY;
 221                writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
 222
 223                /*  Clear interrupt status */
 224                devpriv->IntStatus |= ADC_READY;
 225                writew(devpriv->IntStatus,
 226                       devpriv->io_addr + ICP_MULTI_INT_STAT);
 227
 228                /*  Clear data received */
 229                data[n] = 0;
 230
 231                return -ETIME;
 232
 233conv_finish:
 234                data[n] =
 235                    (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
 236        }
 237
 238        /*  Disable interrupt */
 239        devpriv->IntEnable &= ~ADC_READY;
 240        writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
 241
 242        /*  Clear interrupt status */
 243        devpriv->IntStatus |= ADC_READY;
 244        writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
 245
 246        return n;
 247}
 248
 249static int icp_multi_insn_write_ao(struct comedi_device *dev,
 250                                   struct comedi_subdevice *s,
 251                                   struct comedi_insn *insn, unsigned int *data)
 252{
 253        struct icp_multi_private *devpriv = dev->private;
 254        int n, chan, range, timeout;
 255
 256        /*  Disable D/A conversion ready interrupt */
 257        devpriv->IntEnable &= ~DAC_READY;
 258        writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
 259
 260        /*  Clear interrupt status */
 261        devpriv->IntStatus |= DAC_READY;
 262        writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
 263
 264        /*  Get channel number and range */
 265        chan = CR_CHAN(insn->chanspec);
 266        range = CR_RANGE(insn->chanspec);
 267
 268        /*  Set up range and channel data */
 269        /*  Bit 4 = 1 : Bipolar */
 270        /*  Bit 5 = 0 : 5V */
 271        /*  Bit 5 = 1 : 10V */
 272        /*  Bits 8-9 : Channel number */
 273        devpriv->DacCmdStatus &= 0xfccf;
 274        devpriv->DacCmdStatus |= range_codes_analog[range];
 275        devpriv->DacCmdStatus |= (chan << 8);
 276
 277        writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
 278
 279        for (n = 0; n < insn->n; n++) {
 280                /*  Wait for analogue output data register to be
 281                 *  ready for new data, or get fed up waiting */
 282                timeout = 100;
 283                while (timeout--) {
 284                        if (!(readw(devpriv->io_addr +
 285                                    ICP_MULTI_DAC_CSR) & DAC_BSY))
 286                                goto dac_ready;
 287
 288                        udelay(1);
 289                }
 290
 291                /*  If we reach here, a timeout has occurred */
 292                comedi_error(dev, "D/A insn timeout");
 293
 294                /*  Disable interrupt */
 295                devpriv->IntEnable &= ~DAC_READY;
 296                writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
 297
 298                /*  Clear interrupt status */
 299                devpriv->IntStatus |= DAC_READY;
 300                writew(devpriv->IntStatus,
 301                       devpriv->io_addr + ICP_MULTI_INT_STAT);
 302
 303                /*  Clear data received */
 304                devpriv->ao_data[chan] = 0;
 305
 306                return -ETIME;
 307
 308dac_ready:
 309                /*  Write data to analogue output data register */
 310                writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
 311
 312                /*  Set DAC_ST bit to write the data to selected channel */
 313                devpriv->DacCmdStatus |= DAC_ST;
 314                writew(devpriv->DacCmdStatus,
 315                       devpriv->io_addr + ICP_MULTI_DAC_CSR);
 316                devpriv->DacCmdStatus &= ~DAC_ST;
 317
 318                /*  Save analogue output data */
 319                devpriv->ao_data[chan] = data[n];
 320        }
 321
 322        return n;
 323}
 324
 325static int icp_multi_insn_read_ao(struct comedi_device *dev,
 326                                  struct comedi_subdevice *s,
 327                                  struct comedi_insn *insn, unsigned int *data)
 328{
 329        struct icp_multi_private *devpriv = dev->private;
 330        int n, chan;
 331
 332        /*  Get channel number */
 333        chan = CR_CHAN(insn->chanspec);
 334
 335        /*  Read analogue outputs */
 336        for (n = 0; n < insn->n; n++)
 337                data[n] = devpriv->ao_data[chan];
 338
 339        return n;
 340}
 341
 342static int icp_multi_insn_bits_di(struct comedi_device *dev,
 343                                  struct comedi_subdevice *s,
 344                                  struct comedi_insn *insn, unsigned int *data)
 345{
 346        struct icp_multi_private *devpriv = dev->private;
 347
 348        data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
 349
 350        return insn->n;
 351}
 352
 353static int icp_multi_insn_bits_do(struct comedi_device *dev,
 354                                  struct comedi_subdevice *s,
 355                                  struct comedi_insn *insn, unsigned int *data)
 356{
 357        struct icp_multi_private *devpriv = dev->private;
 358
 359        if (data[0]) {
 360                s->state &= ~data[0];
 361                s->state |= (data[0] & data[1]);
 362
 363                printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
 364
 365                writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
 366        }
 367
 368        data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
 369
 370        return insn->n;
 371}
 372
 373static int icp_multi_insn_read_ctr(struct comedi_device *dev,
 374                                   struct comedi_subdevice *s,
 375                                   struct comedi_insn *insn, unsigned int *data)
 376{
 377        return 0;
 378}
 379
 380static int icp_multi_insn_write_ctr(struct comedi_device *dev,
 381                                    struct comedi_subdevice *s,
 382                                    struct comedi_insn *insn,
 383                                    unsigned int *data)
 384{
 385        return 0;
 386}
 387
 388static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
 389{
 390        struct comedi_device *dev = d;
 391        struct icp_multi_private *devpriv = dev->private;
 392        int int_no;
 393
 394        /*  Is this interrupt from our board? */
 395        int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
 396        if (!int_no)
 397                /*  No, exit */
 398                return IRQ_NONE;
 399
 400        /*  Determine which interrupt is active & handle it */
 401        switch (int_no) {
 402        case ADC_READY:
 403                break;
 404        case DAC_READY:
 405                break;
 406        case DOUT_ERROR:
 407                break;
 408        case DIN_STATUS:
 409                break;
 410        case CIE0:
 411                break;
 412        case CIE1:
 413                break;
 414        case CIE2:
 415                break;
 416        case CIE3:
 417                break;
 418        default:
 419                break;
 420
 421        }
 422
 423        return IRQ_HANDLED;
 424}
 425
 426#if 0
 427static int check_channel_list(struct comedi_device *dev,
 428                              struct comedi_subdevice *s,
 429                              unsigned int *chanlist, unsigned int n_chan)
 430{
 431        unsigned int i;
 432
 433        /*  Check that we at least have one channel to check */
 434        if (n_chan < 1) {
 435                comedi_error(dev, "range/channel list is empty!");
 436                return 0;
 437        }
 438        /*  Check all channels */
 439        for (i = 0; i < n_chan; i++) {
 440                /*  Check that channel number is < maximum */
 441                if (CR_AREF(chanlist[i]) == AREF_DIFF) {
 442                        if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
 443                                comedi_error(dev,
 444                                             "Incorrect differential ai ch-nr");
 445                                return 0;
 446                        }
 447                } else {
 448                        if (CR_CHAN(chanlist[i]) > s->n_chan) {
 449                                comedi_error(dev,
 450                                             "Incorrect ai channel number");
 451                                return 0;
 452                        }
 453                }
 454        }
 455        return 1;
 456}
 457#endif
 458
 459static int icp_multi_reset(struct comedi_device *dev)
 460{
 461        struct icp_multi_private *devpriv = dev->private;
 462        unsigned int i;
 463
 464        /*  Clear INT enables and requests */
 465        writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
 466        writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
 467
 468        /* Set DACs to 0..5V range and 0V output */
 469        for (i = 0; i < 4; i++) {
 470                devpriv->DacCmdStatus &= 0xfcce;
 471
 472                /*  Set channel number */
 473                devpriv->DacCmdStatus |= (i << 8);
 474
 475                /*  Output 0V */
 476                writew(0, devpriv->io_addr + ICP_MULTI_AO);
 477
 478                /*  Set start conversion bit */
 479                devpriv->DacCmdStatus |= DAC_ST;
 480
 481                /*  Output to command / status register */
 482                writew(devpriv->DacCmdStatus,
 483                        devpriv->io_addr + ICP_MULTI_DAC_CSR);
 484
 485                /*  Delay to allow DAC time to recover */
 486                udelay(1);
 487        }
 488
 489        /* Digital outputs to 0 */
 490        writew(0, devpriv->io_addr + ICP_MULTI_DO);
 491
 492        return 0;
 493}
 494
 495static int icp_multi_auto_attach(struct comedi_device *dev,
 496                                           unsigned long context_unused)
 497{
 498        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 499        struct icp_multi_private *devpriv;
 500        struct comedi_subdevice *s;
 501        int ret;
 502
 503        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 504        if (!devpriv)
 505                return -ENOMEM;
 506        dev->private = devpriv;
 507
 508        ret = comedi_pci_enable(dev);
 509        if (ret)
 510                return ret;
 511
 512        devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
 513        if (!devpriv->io_addr)
 514                return -ENOMEM;
 515
 516        ret = comedi_alloc_subdevices(dev, 5);
 517        if (ret)
 518                return ret;
 519
 520        icp_multi_reset(dev);
 521
 522        if (pcidev->irq) {
 523                ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
 524                                  IRQF_SHARED, dev->board_name, dev);
 525                if (ret == 0)
 526                        dev->irq = pcidev->irq;
 527        }
 528
 529        s = &dev->subdevices[0];
 530        dev->read_subdev = s;
 531        s->type = COMEDI_SUBD_AI;
 532        s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
 533        s->n_chan = 16;
 534        s->maxdata = 0x0fff;
 535        s->len_chanlist = 16;
 536        s->range_table = &range_analog;
 537        s->insn_read = icp_multi_insn_read_ai;
 538
 539        s = &dev->subdevices[1];
 540        s->type = COMEDI_SUBD_AO;
 541        s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
 542        s->n_chan = 4;
 543        s->maxdata = 0x0fff;
 544        s->len_chanlist = 4;
 545        s->range_table = &range_analog;
 546        s->insn_write = icp_multi_insn_write_ao;
 547        s->insn_read = icp_multi_insn_read_ao;
 548
 549        s = &dev->subdevices[2];
 550        s->type = COMEDI_SUBD_DI;
 551        s->subdev_flags = SDF_READABLE;
 552        s->n_chan = 16;
 553        s->maxdata = 1;
 554        s->len_chanlist = 16;
 555        s->range_table = &range_digital;
 556        s->io_bits = 0;
 557        s->insn_bits = icp_multi_insn_bits_di;
 558
 559        s = &dev->subdevices[3];
 560        s->type = COMEDI_SUBD_DO;
 561        s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
 562        s->n_chan = 8;
 563        s->maxdata = 1;
 564        s->len_chanlist = 8;
 565        s->range_table = &range_digital;
 566        s->io_bits = 0xff;
 567        s->state = 0;
 568        s->insn_bits = icp_multi_insn_bits_do;
 569
 570        s = &dev->subdevices[4];
 571        s->type = COMEDI_SUBD_COUNTER;
 572        s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
 573        s->n_chan = 4;
 574        s->maxdata = 0xffff;
 575        s->len_chanlist = 4;
 576        s->state = 0;
 577        s->insn_read = icp_multi_insn_read_ctr;
 578        s->insn_write = icp_multi_insn_write_ctr;
 579
 580        devpriv->valid = 1;
 581
 582        dev_info(dev->class_dev, "%s attached, irq %sabled\n",
 583                dev->board_name, dev->irq ? "en" : "dis");
 584
 585        return 0;
 586}
 587
 588static void icp_multi_detach(struct comedi_device *dev)
 589{
 590        struct icp_multi_private *devpriv = dev->private;
 591
 592        if (devpriv)
 593                if (devpriv->valid)
 594                        icp_multi_reset(dev);
 595        if (dev->irq)
 596                free_irq(dev->irq, dev);
 597        if (devpriv && devpriv->io_addr)
 598                iounmap(devpriv->io_addr);
 599        comedi_pci_disable(dev);
 600}
 601
 602static struct comedi_driver icp_multi_driver = {
 603        .driver_name    = "icp_multi",
 604        .module         = THIS_MODULE,
 605        .auto_attach    = icp_multi_auto_attach,
 606        .detach         = icp_multi_detach,
 607};
 608
 609static int icp_multi_pci_probe(struct pci_dev *dev,
 610                               const struct pci_device_id *id)
 611{
 612        return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
 613}
 614
 615static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
 616        { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
 617        { 0 }
 618};
 619MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
 620
 621static struct pci_driver icp_multi_pci_driver = {
 622        .name           = "icp_multi",
 623        .id_table       = icp_multi_pci_table,
 624        .probe          = icp_multi_pci_probe,
 625        .remove         = comedi_pci_auto_unconfig,
 626};
 627module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
 628
 629MODULE_AUTHOR("Comedi http://www.comedi.org");
 630MODULE_DESCRIPTION("Comedi low-level driver");
 631MODULE_LICENSE("GPL");
 632