3 * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
4 * Project manager: S. Weber
9 * Tel: +19(0)7223/9493-0
10 * Fax: +49(0)7223/9493-92
11 * http://www.addi-data.com
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the
16 * Free Software Foundation; either version 2 of the License, or (at your
17 * option) any later version.
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
25 #include <linux/module.h>
26 #include <linux/pci.h>
27 #include <linux/interrupt.h>
29 #include "../comedidev.h"
31 #include "comedi_fc.h"
33 #define CONV_UNIT_NS (1 << 0)
34 #define CONV_UNIT_US (1 << 1)
35 #define CONV_UNIT_MS (1 << 2)
37 static const struct comedi_lrange apci3xxx_ai_range = {
50 static const struct comedi_lrange apci3xxx_ao_range = {
57 enum apci3xxx_boardid {
85 struct apci3xxx_boardinfo {
89 unsigned int ai_maxdata;
90 unsigned char ai_conv_units;
91 unsigned int ai_min_acq_ns;
92 unsigned int has_ao:1;
93 unsigned int has_dig_in:1;
94 unsigned int has_dig_out:1;
95 unsigned int has_ttl_io:1;
98 static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
99 [BOARD_APCI3000_16] = {
100 .name = "apci3000-16",
101 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
103 .ai_maxdata = 0x0fff,
104 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
105 .ai_min_acq_ns = 10000,
108 [BOARD_APCI3000_8] = {
109 .name = "apci3000-8",
110 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
112 .ai_maxdata = 0x0fff,
113 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
114 .ai_min_acq_ns = 10000,
117 [BOARD_APCI3000_4] = {
118 .name = "apci3000-4",
119 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
121 .ai_maxdata = 0x0fff,
122 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
123 .ai_min_acq_ns = 10000,
126 [BOARD_APCI3006_16] = {
127 .name = "apci3006-16",
128 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
130 .ai_maxdata = 0xffff,
131 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
132 .ai_min_acq_ns = 10000,
135 [BOARD_APCI3006_8] = {
136 .name = "apci3006-8",
137 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
139 .ai_maxdata = 0xffff,
140 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
141 .ai_min_acq_ns = 10000,
144 [BOARD_APCI3006_4] = {
145 .name = "apci3006-4",
146 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
148 .ai_maxdata = 0xffff,
149 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
150 .ai_min_acq_ns = 10000,
153 [BOARD_APCI3010_16] = {
154 .name = "apci3010-16",
155 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
157 .ai_maxdata = 0x0fff,
158 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
159 .ai_min_acq_ns = 5000,
164 [BOARD_APCI3010_8] = {
165 .name = "apci3010-8",
166 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
168 .ai_maxdata = 0x0fff,
169 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
170 .ai_min_acq_ns = 5000,
175 [BOARD_APCI3010_4] = {
176 .name = "apci3010-4",
177 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
179 .ai_maxdata = 0x0fff,
180 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
181 .ai_min_acq_ns = 5000,
186 [BOARD_APCI3016_16] = {
187 .name = "apci3016-16",
188 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
190 .ai_maxdata = 0xffff,
191 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
192 .ai_min_acq_ns = 5000,
197 [BOARD_APCI3016_8] = {
198 .name = "apci3016-8",
199 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
201 .ai_maxdata = 0xffff,
202 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
203 .ai_min_acq_ns = 5000,
208 [BOARD_APCI3016_4] = {
209 .name = "apci3016-4",
210 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
212 .ai_maxdata = 0xffff,
213 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
214 .ai_min_acq_ns = 5000,
219 [BOARD_APCI3100_16_4] = {
220 .name = "apci3100-16-4",
221 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
223 .ai_maxdata = 0x0fff,
224 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
225 .ai_min_acq_ns = 10000,
229 [BOARD_APCI3100_8_4] = {
230 .name = "apci3100-8-4",
231 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
233 .ai_maxdata = 0x0fff,
234 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
235 .ai_min_acq_ns = 10000,
239 [BOARD_APCI3106_16_4] = {
240 .name = "apci3106-16-4",
241 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
243 .ai_maxdata = 0xffff,
244 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
245 .ai_min_acq_ns = 10000,
249 [BOARD_APCI3106_8_4] = {
250 .name = "apci3106-8-4",
251 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
253 .ai_maxdata = 0xffff,
254 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
255 .ai_min_acq_ns = 10000,
259 [BOARD_APCI3110_16_4] = {
260 .name = "apci3110-16-4",
261 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
263 .ai_maxdata = 0x0fff,
264 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
265 .ai_min_acq_ns = 5000,
271 [BOARD_APCI3110_8_4] = {
272 .name = "apci3110-8-4",
273 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
275 .ai_maxdata = 0x0fff,
276 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
277 .ai_min_acq_ns = 5000,
283 [BOARD_APCI3116_16_4] = {
284 .name = "apci3116-16-4",
285 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
287 .ai_maxdata = 0xffff,
288 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
289 .ai_min_acq_ns = 5000,
295 [BOARD_APCI3116_8_4] = {
296 .name = "apci3116-8-4",
297 .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
299 .ai_maxdata = 0xffff,
300 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
301 .ai_min_acq_ns = 5000,
309 .ai_subdev_flags = SDF_DIFF,
311 .ai_maxdata = 0xffff,
312 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US |
314 .ai_min_acq_ns = 2500,
318 [BOARD_APCI3002_16] = {
319 .name = "apci3002-16",
320 .ai_subdev_flags = SDF_DIFF,
322 .ai_maxdata = 0xffff,
323 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
324 .ai_min_acq_ns = 5000,
328 [BOARD_APCI3002_8] = {
329 .name = "apci3002-8",
330 .ai_subdev_flags = SDF_DIFF,
332 .ai_maxdata = 0xffff,
333 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
334 .ai_min_acq_ns = 5000,
338 [BOARD_APCI3002_4] = {
339 .name = "apci3002-4",
340 .ai_subdev_flags = SDF_DIFF,
342 .ai_maxdata = 0xffff,
343 .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
344 .ai_min_acq_ns = 5000,
355 struct apci3xxx_private {
357 unsigned int ai_timer;
358 unsigned char ai_time_base;
361 static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
363 struct comedi_device *dev = d;
364 struct apci3xxx_private *devpriv = dev->private;
365 struct comedi_subdevice *s = dev->read_subdev;
369 /* Test if interrupt occur */
370 status = readl(devpriv->mmio + 16);
371 if ((status & 0x2) == 0x2) {
372 /* Reset the interrupt */
373 writel(status, devpriv->mmio + 16);
375 val = readl(devpriv->mmio + 28);
376 comedi_buf_put(s->async, val);
378 s->async->events |= COMEDI_CB_EOA;
379 comedi_event(dev, s);
386 static int apci3xxx_ai_started(struct comedi_device *dev)
388 struct apci3xxx_private *devpriv = dev->private;
390 if ((readl(devpriv->mmio + 8) & 0x80000) == 0x80000)
397 static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec)
399 struct apci3xxx_private *devpriv = dev->private;
400 unsigned int chan = CR_CHAN(chanspec);
401 unsigned int range = CR_RANGE(chanspec);
402 unsigned int aref = CR_AREF(chanspec);
403 unsigned int delay_mode;
406 if (apci3xxx_ai_started(dev))
410 writel(0x10000, devpriv->mmio + 12);
412 /* Get and save the delay mode */
413 delay_mode = readl(devpriv->mmio + 4);
414 delay_mode &= 0xfffffef0;
416 /* Channel configuration selection */
417 writel(delay_mode, devpriv->mmio + 4);
419 /* Make the configuration */
420 val = (range & 3) | ((range >> 2) << 6) |
421 ((aref == AREF_DIFF) << 7);
422 writel(val, devpriv->mmio + 0);
424 /* Channel selection */
425 writel(delay_mode | 0x100, devpriv->mmio + 4);
426 writel(chan, devpriv->mmio + 0);
428 /* Restore delay mode */
429 writel(delay_mode, devpriv->mmio + 4);
431 /* Set the number of sequence to 1 */
432 writel(1, devpriv->mmio + 48);
437 static int apci3xxx_ai_insn_read(struct comedi_device *dev,
438 struct comedi_subdevice *s,
439 struct comedi_insn *insn,
442 struct apci3xxx_private *devpriv = dev->private;
447 ret = apci3xxx_ai_setup(dev, insn->chanspec);
451 for (i = 0; i < insn->n; i++) {
452 /* Start the conversion */
453 writel(0x80000, devpriv->mmio + 8);
457 val = readl(devpriv->mmio + 20);
461 /* Read the analog value */
462 data[i] = readl(devpriv->mmio + 28);
468 static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev,
469 unsigned int *ns, int round_mode)
471 const struct apci3xxx_boardinfo *board = comedi_board(dev);
472 struct apci3xxx_private *devpriv = dev->private;
477 /* time_base: 0 = ns, 1 = us, 2 = ms */
478 for (time_base = 0; time_base < 3; time_base++) {
479 /* skip unsupported time bases */
480 if (!(board->ai_conv_units & (1 << time_base)))
495 switch (round_mode) {
496 case TRIG_ROUND_NEAREST:
498 timer = (*ns + base / 2) / base;
500 case TRIG_ROUND_DOWN:
504 timer = (*ns + base - 1) / base;
508 if (timer < 0x10000) {
509 devpriv->ai_time_base = time_base;
510 devpriv->ai_timer = timer;
511 *ns = timer * time_base;
518 static int apci3xxx_ai_cmdtest(struct comedi_device *dev,
519 struct comedi_subdevice *s,
520 struct comedi_cmd *cmd)
522 const struct apci3xxx_boardinfo *board = comedi_board(dev);
526 /* Step 1 : check if triggers are trivially valid */
528 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
529 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
530 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
531 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
532 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
537 /* Step 2a : make sure trigger sources are unique */
539 err |= cfc_check_trigger_is_unique(cmd->stop_src);
541 /* Step 2b : and mutually compatible */
546 /* Step 3: check if arguments are trivially valid */
548 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
549 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
550 err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
551 board->ai_min_acq_ns);
552 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
554 if (cmd->stop_src == TRIG_COUNT)
555 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
557 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
562 /* step 4: fix up any arguments */
565 * FIXME: The hardware supports multiple scan modes but the original
566 * addi-data driver only supported reading a single channel with
567 * interrupts. Need a proper datasheet to fix this.
569 * The following scan modes are supported by the hardware:
570 * 1) Single software scan
571 * 2) Single hardware triggered scan
572 * 3) Continuous software scan
573 * 4) Continuous software scan with timer delay
574 * 5) Continuous hardware triggered scan
575 * 6) Continuous hardware triggered scan with timer delay
577 * For now, limit the chanlist to a single channel.
579 if (cmd->chanlist_len > 1) {
580 cmd->chanlist_len = 1;
584 tmp = cmd->convert_arg;
585 err |= apci3xxx_ai_ns_to_timer(dev, &cmd->convert_arg,
586 cmd->flags & TRIG_ROUND_MASK);
587 if (tmp != cmd->convert_arg)
596 static int apci3xxx_ai_cmd(struct comedi_device *dev,
597 struct comedi_subdevice *s)
599 struct apci3xxx_private *devpriv = dev->private;
600 struct comedi_cmd *cmd = &s->async->cmd;
603 ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]);
607 /* Set the convert timing unit */
608 writel(devpriv->ai_time_base, devpriv->mmio + 36);
610 /* Set the convert timing */
611 writel(devpriv->ai_timer, devpriv->mmio + 32);
613 /* Start the conversion */
614 writel(0x180000, devpriv->mmio + 8);
619 static int apci3xxx_ai_cancel(struct comedi_device *dev,
620 struct comedi_subdevice *s)
625 static int apci3xxx_ao_insn_write(struct comedi_device *dev,
626 struct comedi_subdevice *s,
627 struct comedi_insn *insn,
630 struct apci3xxx_private *devpriv = dev->private;
631 unsigned int chan = CR_CHAN(insn->chanspec);
632 unsigned int range = CR_RANGE(insn->chanspec);
636 for (i = 0; i < insn->n; i++) {
637 /* Set the range selection */
638 writel(range, devpriv->mmio + 96);
640 /* Write the analog value to the selected channel */
641 writel((data[i] << 8) | chan, devpriv->mmio + 100);
643 /* Wait the end of transfer */
645 status = readl(devpriv->mmio + 96);
646 } while ((status & 0x100) != 0x100);
652 static int apci3xxx_di_insn_bits(struct comedi_device *dev,
653 struct comedi_subdevice *s,
654 struct comedi_insn *insn,
657 data[1] = inl(dev->iobase + 32) & 0xf;
662 static int apci3xxx_do_insn_bits(struct comedi_device *dev,
663 struct comedi_subdevice *s,
664 struct comedi_insn *insn,
667 s->state = inl(dev->iobase + 48) & 0xf;
669 if (comedi_dio_update_state(s, data))
670 outl(s->state, dev->iobase + 48);
677 static int apci3xxx_dio_insn_config(struct comedi_device *dev,
678 struct comedi_subdevice *s,
679 struct comedi_insn *insn,
682 unsigned int chan = CR_CHAN(insn->chanspec);
687 * Port 0 (channels 0-7) are always inputs
688 * Port 1 (channels 8-15) are always outputs
689 * Port 2 (channels 16-23) are programmable i/o
692 if (data[0] != INSN_CONFIG_DIO_QUERY)
695 /* changing any channel in port 2 changes the entire port */
699 ret = comedi_dio_insn_config(dev, s, insn, data, mask);
703 /* update port 2 configuration */
704 outl((s->io_bits >> 24) & 0xff, dev->iobase + 224);
709 static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
710 struct comedi_subdevice *s,
711 struct comedi_insn *insn,
717 mask = comedi_dio_update_state(s, data);
720 outl(s->state & 0xff, dev->iobase + 80);
722 outl((s->state >> 16) & 0xff, dev->iobase + 112);
725 val = inl(dev->iobase + 80);
726 val |= (inl(dev->iobase + 64) << 8);
727 if (s->io_bits & 0xff0000)
728 val |= (inl(dev->iobase + 112) << 16);
730 val |= (inl(dev->iobase + 96) << 16);
737 static int apci3xxx_reset(struct comedi_device *dev)
739 struct apci3xxx_private *devpriv = dev->private;
743 /* Disable the interrupt */
744 disable_irq(dev->irq);
746 /* Clear the start command */
747 writel(0, devpriv->mmio + 8);
749 /* Reset the interrupt flags */
750 val = readl(devpriv->mmio + 16);
751 writel(val, devpriv->mmio + 16);
754 readl(devpriv->mmio + 20);
757 for (i = 0; i < 16; i++)
758 val = readl(devpriv->mmio + 28);
760 /* Enable the interrupt */
761 enable_irq(dev->irq);
766 static int apci3xxx_auto_attach(struct comedi_device *dev,
767 unsigned long context)
769 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
770 const struct apci3xxx_boardinfo *board = NULL;
771 struct apci3xxx_private *devpriv;
772 struct comedi_subdevice *s;
777 if (context < ARRAY_SIZE(apci3xxx_boardtypes))
778 board = &apci3xxx_boardtypes[context];
781 dev->board_ptr = board;
782 dev->board_name = board->name;
784 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
788 ret = comedi_pci_enable(dev);
792 dev->iobase = pci_resource_start(pcidev, 2);
793 devpriv->mmio = pci_ioremap_bar(pcidev, 3);
795 if (pcidev->irq > 0) {
796 ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
797 IRQF_SHARED, dev->board_name, dev);
799 dev->irq = pcidev->irq;
802 n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao +
803 board->has_dig_in + board->has_dig_out +
805 ret = comedi_alloc_subdevices(dev, n_subdevices);
811 /* Analog Input subdevice */
812 if (board->ai_n_chan) {
813 s = &dev->subdevices[subdev];
814 s->type = COMEDI_SUBD_AI;
815 s->subdev_flags = SDF_READABLE | board->ai_subdev_flags;
816 s->n_chan = board->ai_n_chan;
817 s->maxdata = board->ai_maxdata;
818 s->len_chanlist = s->n_chan;
819 s->range_table = &apci3xxx_ai_range;
820 s->insn_read = apci3xxx_ai_insn_read;
822 dev->read_subdev = s;
823 s->subdev_flags |= SDF_CMD_READ;
824 s->do_cmdtest = apci3xxx_ai_cmdtest;
825 s->do_cmd = apci3xxx_ai_cmd;
826 s->cancel = apci3xxx_ai_cancel;
832 /* Analog Output subdevice */
834 s = &dev->subdevices[subdev];
835 s->type = COMEDI_SUBD_AO;
836 s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
839 s->range_table = &apci3xxx_ao_range;
840 s->insn_write = apci3xxx_ao_insn_write;
845 /* Digital Input subdevice */
846 if (board->has_dig_in) {
847 s = &dev->subdevices[subdev];
848 s->type = COMEDI_SUBD_DI;
849 s->subdev_flags = SDF_READABLE;
852 s->range_table = &range_digital;
853 s->insn_bits = apci3xxx_di_insn_bits;
858 /* Digital Output subdevice */
859 if (board->has_dig_out) {
860 s = &dev->subdevices[subdev];
861 s->type = COMEDI_SUBD_DO;
862 s->subdev_flags = SDF_WRITEABLE;
865 s->range_table = &range_digital;
866 s->insn_bits = apci3xxx_do_insn_bits;
871 /* TTL Digital I/O subdevice */
872 if (board->has_ttl_io) {
873 s = &dev->subdevices[subdev];
874 s->type = COMEDI_SUBD_DIO;
875 s->subdev_flags = SDF_READABLE | SDF_WRITEABLE;
878 s->io_bits = 0xff; /* channels 0-7 are always outputs */
879 s->range_table = &range_digital;
880 s->insn_config = apci3xxx_dio_insn_config;
881 s->insn_bits = apci3xxx_dio_insn_bits;
890 static void apci3xxx_detach(struct comedi_device *dev)
892 struct apci3xxx_private *devpriv = dev->private;
898 free_irq(dev->irq, dev);
900 iounmap(devpriv->mmio);
902 comedi_pci_disable(dev);
905 static struct comedi_driver apci3xxx_driver = {
906 .driver_name = "addi_apci_3xxx",
907 .module = THIS_MODULE,
908 .auto_attach = apci3xxx_auto_attach,
909 .detach = apci3xxx_detach,
912 static int apci3xxx_pci_probe(struct pci_dev *dev,
913 const struct pci_device_id *id)
915 return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
918 static DEFINE_PCI_DEVICE_TABLE(apci3xxx_pci_table) = {
919 { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
920 { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
921 { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
922 { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
923 { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
924 { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
925 { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
926 { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
927 { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
928 { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
929 { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
930 { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
931 { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
932 { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
933 { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
934 { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
935 { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
936 { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
937 { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
938 { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
939 { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
940 { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
941 { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
942 { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
943 { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
946 MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
948 static struct pci_driver apci3xxx_pci_driver = {
949 .name = "addi_apci_3xxx",
950 .id_table = apci3xxx_pci_table,
951 .probe = apci3xxx_pci_probe,
952 .remove = comedi_pci_auto_unconfig,
954 module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
956 MODULE_AUTHOR("Comedi http://www.comedi.org");
957 MODULE_DESCRIPTION("Comedi low-level driver");
958 MODULE_LICENSE("GPL");