addi_apci_3xxx.c revision fa81e2f186fb95dac1641e1e8d6740ed559e5204
1/*
2 * addi_apci_3xxx.c
3 * Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
4 * Project manager: S. Weber
5 *
6 *	ADDI-DATA GmbH
7 *	Dieselstrasse 3
8 *	D-77833 Ottersweier
9 *	Tel: +19(0)7223/9493-0
10 *	Fax: +49(0)7223/9493-92
11 *	http://www.addi-data.com
12 *	info@addi-data.com
13 *
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.
18 *
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
22 * more details.
23 */
24
25#include <linux/pci.h>
26#include <linux/interrupt.h>
27#include <linux/sched.h>
28
29#include "../comedidev.h"
30
31static const struct comedi_lrange apci3xxx_ai_range = {
32	8, {
33		BIP_RANGE(10),
34		BIP_RANGE(5),
35		BIP_RANGE(2),
36		BIP_RANGE(1),
37		UNI_RANGE(10),
38		UNI_RANGE(5),
39		UNI_RANGE(2),
40		UNI_RANGE(1)
41	}
42};
43
44static const struct comedi_lrange apci3xxx_ao_range = {
45	2, {
46		BIP_RANGE(10),
47		UNI_RANGE(10)
48	}
49};
50
51enum apci3xxx_boardid {
52	BOARD_APCI3000_16,
53	BOARD_APCI3000_8,
54	BOARD_APCI3000_4,
55	BOARD_APCI3006_16,
56	BOARD_APCI3006_8,
57	BOARD_APCI3006_4,
58	BOARD_APCI3010_16,
59	BOARD_APCI3010_8,
60	BOARD_APCI3010_4,
61	BOARD_APCI3016_16,
62	BOARD_APCI3016_8,
63	BOARD_APCI3016_4,
64	BOARD_APCI3100_16_4,
65	BOARD_APCI3100_8_4,
66	BOARD_APCI3106_16_4,
67	BOARD_APCI3106_8_4,
68	BOARD_APCI3110_16_4,
69	BOARD_APCI3110_8_4,
70	BOARD_APCI3116_16_4,
71	BOARD_APCI3116_8_4,
72	BOARD_APCI3003,
73	BOARD_APCI3002_16,
74	BOARD_APCI3002_8,
75	BOARD_APCI3002_4,
76	BOARD_APCI3500,
77};
78
79struct apci3xxx_boardinfo {
80	const char *pc_DriverName;
81	int i_NbrAiChannel;
82	int i_NbrAiChannelDiff;
83	int i_AiChannelList;
84	int i_AiMaxdata;
85	unsigned char b_AvailableConvertUnit;
86	unsigned int ui_MinAcquisitiontimeNs;
87	unsigned int has_ao:1;
88	unsigned int has_dig_in:1;
89	unsigned int has_dig_out:1;
90	unsigned int has_ttl_io:1;
91};
92
93static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
94	[BOARD_APCI3000_16] = {
95		.pc_DriverName		= "apci3000-16",
96		.i_NbrAiChannel		= 16,
97		.i_NbrAiChannelDiff	= 8,
98		.i_AiChannelList	= 16,
99		.i_AiMaxdata		= 4095,
100		.b_AvailableConvertUnit	= 6,
101		.ui_MinAcquisitiontimeNs = 10000,
102		.has_ttl_io		= 1,
103	},
104	[BOARD_APCI3000_8] = {
105		.pc_DriverName		= "apci3000-8",
106		.i_NbrAiChannel		= 8,
107		.i_NbrAiChannelDiff	= 4,
108		.i_AiChannelList	= 8,
109		.i_AiMaxdata		= 4095,
110		.b_AvailableConvertUnit	= 6,
111		.ui_MinAcquisitiontimeNs = 10000,
112		.has_ttl_io		= 1,
113	},
114	[BOARD_APCI3000_4] = {
115		.pc_DriverName		= "apci3000-4",
116		.i_NbrAiChannel		= 4,
117		.i_NbrAiChannelDiff	= 2,
118		.i_AiChannelList	= 4,
119		.i_AiMaxdata		= 4095,
120		.b_AvailableConvertUnit	= 6,
121		.ui_MinAcquisitiontimeNs = 10000,
122		.has_ttl_io		= 1,
123	},
124	[BOARD_APCI3006_16] = {
125		.pc_DriverName		= "apci3006-16",
126		.i_NbrAiChannel		= 16,
127		.i_NbrAiChannelDiff	= 8,
128		.i_AiChannelList	= 16,
129		.i_AiMaxdata		= 65535,
130		.b_AvailableConvertUnit	= 6,
131		.ui_MinAcquisitiontimeNs = 10000,
132		.has_ttl_io		= 1,
133	},
134	[BOARD_APCI3006_8] = {
135		.pc_DriverName		= "apci3006-8",
136		.i_NbrAiChannel		= 8,
137		.i_NbrAiChannelDiff	= 4,
138		.i_AiChannelList	= 8,
139		.i_AiMaxdata		= 65535,
140		.b_AvailableConvertUnit	= 6,
141		.ui_MinAcquisitiontimeNs = 10000,
142		.has_ttl_io		= 1,
143	},
144	[BOARD_APCI3006_4] = {
145		.pc_DriverName		= "apci3006-4",
146		.i_NbrAiChannel		= 4,
147		.i_NbrAiChannelDiff	= 2,
148		.i_AiChannelList	= 4,
149		.i_AiMaxdata		= 65535,
150		.b_AvailableConvertUnit	= 6,
151		.ui_MinAcquisitiontimeNs = 10000,
152		.has_ttl_io		= 1,
153	},
154	[BOARD_APCI3010_16] = {
155		.pc_DriverName		= "apci3010-16",
156		.i_NbrAiChannel		= 16,
157		.i_NbrAiChannelDiff	= 8,
158		.i_AiChannelList	= 16,
159		.i_AiMaxdata		= 4095,
160		.b_AvailableConvertUnit	= 6,
161		.ui_MinAcquisitiontimeNs = 5000,
162		.has_dig_in		= 1,
163		.has_dig_out		= 1,
164		.has_ttl_io		= 1,
165	},
166	[BOARD_APCI3010_8] = {
167		.pc_DriverName		= "apci3010-8",
168		.i_NbrAiChannel		= 8,
169		.i_NbrAiChannelDiff	= 4,
170		.i_AiChannelList	= 8,
171		.i_AiMaxdata		= 4095,
172		.b_AvailableConvertUnit	= 6,
173		.ui_MinAcquisitiontimeNs = 5000,
174		.has_dig_in		= 1,
175		.has_dig_out		= 1,
176		.has_ttl_io		= 1,
177	},
178	[BOARD_APCI3010_4] = {
179		.pc_DriverName		= "apci3010-4",
180		.i_NbrAiChannel		= 4,
181		.i_NbrAiChannelDiff	= 2,
182		.i_AiChannelList	= 4,
183		.i_AiMaxdata		= 4095,
184		.b_AvailableConvertUnit	= 6,
185		.ui_MinAcquisitiontimeNs = 5000,
186		.has_dig_in		= 1,
187		.has_dig_out		= 1,
188		.has_ttl_io		= 1,
189	},
190	[BOARD_APCI3016_16] = {
191		.pc_DriverName		= "apci3016-16",
192		.i_NbrAiChannel		= 16,
193		.i_NbrAiChannelDiff	= 8,
194		.i_AiChannelList	= 16,
195		.i_AiMaxdata		= 65535,
196		.b_AvailableConvertUnit	= 6,
197		.ui_MinAcquisitiontimeNs = 5000,
198		.has_dig_in		= 1,
199		.has_dig_out		= 1,
200		.has_ttl_io		= 1,
201	},
202	[BOARD_APCI3016_8] = {
203		.pc_DriverName		= "apci3016-8",
204		.i_NbrAiChannel		= 8,
205		.i_NbrAiChannelDiff	= 4,
206		.i_AiChannelList	= 8,
207		.i_AiMaxdata		= 65535,
208		.b_AvailableConvertUnit	= 6,
209		.ui_MinAcquisitiontimeNs = 5000,
210		.has_dig_in		= 1,
211		.has_dig_out		= 1,
212		.has_ttl_io		= 1,
213	},
214	[BOARD_APCI3016_4] = {
215		.pc_DriverName		= "apci3016-4",
216		.i_NbrAiChannel		= 4,
217		.i_NbrAiChannelDiff	= 2,
218		.i_AiChannelList	= 4,
219		.i_AiMaxdata		= 65535,
220		.b_AvailableConvertUnit	= 6,
221		.ui_MinAcquisitiontimeNs = 5000,
222		.has_dig_in		= 1,
223		.has_dig_out		= 1,
224		.has_ttl_io		= 1,
225	},
226	[BOARD_APCI3100_16_4] = {
227		.pc_DriverName		= "apci3100-16-4",
228		.i_NbrAiChannel		= 16,
229		.i_NbrAiChannelDiff	= 8,
230		.i_AiChannelList	= 16,
231		.i_AiMaxdata		= 4095,
232		.b_AvailableConvertUnit	= 6,
233		.ui_MinAcquisitiontimeNs = 10000,
234		.has_ao			= 1,
235		.has_ttl_io		= 1,
236	},
237	[BOARD_APCI3100_8_4] = {
238		.pc_DriverName		= "apci3100-8-4",
239		.i_NbrAiChannel		= 8,
240		.i_NbrAiChannelDiff	= 4,
241		.i_AiChannelList	= 8,
242		.i_AiMaxdata		= 4095,
243		.b_AvailableConvertUnit	= 6,
244		.ui_MinAcquisitiontimeNs = 10000,
245		.has_ao			= 1,
246		.has_ttl_io		= 1,
247	},
248	[BOARD_APCI3106_16_4] = {
249		.pc_DriverName		= "apci3106-16-4",
250		.i_NbrAiChannel		= 16,
251		.i_NbrAiChannelDiff	= 8,
252		.i_AiChannelList	= 16,
253		.i_AiMaxdata		= 65535,
254		.b_AvailableConvertUnit	= 6,
255		.ui_MinAcquisitiontimeNs = 10000,
256		.has_ao			= 1,
257		.has_ttl_io		= 1,
258	},
259	[BOARD_APCI3106_8_4] = {
260		.pc_DriverName		= "apci3106-8-4",
261		.i_NbrAiChannel		= 8,
262		.i_NbrAiChannelDiff	= 4,
263		.i_AiChannelList	= 8,
264		.i_AiMaxdata		= 65535,
265		.b_AvailableConvertUnit	= 6,
266		.ui_MinAcquisitiontimeNs = 10000,
267		.has_ao			= 1,
268		.has_ttl_io		= 1,
269	},
270	[BOARD_APCI3110_16_4] = {
271		.pc_DriverName		= "apci3110-16-4",
272		.i_NbrAiChannel		= 16,
273		.i_NbrAiChannelDiff	= 8,
274		.i_AiChannelList	= 16,
275		.i_AiMaxdata		= 4095,
276		.b_AvailableConvertUnit	= 6,
277		.ui_MinAcquisitiontimeNs = 5000,
278		.has_ao			= 1,
279		.has_dig_in		= 1,
280		.has_dig_out		= 1,
281		.has_ttl_io		= 1,
282	},
283	[BOARD_APCI3110_8_4] = {
284		.pc_DriverName		= "apci3110-8-4",
285		.i_NbrAiChannel		= 8,
286		.i_NbrAiChannelDiff	= 4,
287		.i_AiChannelList	= 8,
288		.i_AiMaxdata		= 4095,
289		.b_AvailableConvertUnit	= 6,
290		.ui_MinAcquisitiontimeNs = 5000,
291		.has_ao			= 1,
292		.has_dig_in		= 1,
293		.has_dig_out		= 1,
294		.has_ttl_io		= 1,
295	},
296	[BOARD_APCI3116_16_4] = {
297		.pc_DriverName		= "apci3116-16-4",
298		.i_NbrAiChannel		= 16,
299		.i_NbrAiChannelDiff	= 8,
300		.i_AiChannelList	= 16,
301		.i_AiMaxdata		= 65535,
302		.b_AvailableConvertUnit	= 6,
303		.ui_MinAcquisitiontimeNs = 5000,
304		.has_ao			= 1,
305		.has_dig_in		= 1,
306		.has_dig_out		= 1,
307		.has_ttl_io		= 1,
308	},
309	[BOARD_APCI3116_8_4] = {
310		.pc_DriverName		= "apci3116-8-4",
311		.i_NbrAiChannel		= 8,
312		.i_NbrAiChannelDiff	= 4,
313		.i_AiChannelList	= 8,
314		.i_AiMaxdata		= 65535,
315		.b_AvailableConvertUnit	= 6,
316		.ui_MinAcquisitiontimeNs = 5000,
317		.has_ao			= 1,
318		.has_dig_in		= 1,
319		.has_dig_out		= 1,
320		.has_ttl_io		= 1,
321	},
322	[BOARD_APCI3003] = {
323		.pc_DriverName		= "apci3003",
324		.i_NbrAiChannelDiff	= 4,
325		.i_AiChannelList	= 4,
326		.i_AiMaxdata		= 65535,
327		.b_AvailableConvertUnit	= 7,
328		.ui_MinAcquisitiontimeNs = 2500,
329		.has_dig_in		= 1,
330		.has_dig_out		= 1,
331	},
332	[BOARD_APCI3002_16] = {
333		.pc_DriverName		= "apci3002-16",
334		.i_NbrAiChannelDiff	= 16,
335		.i_AiChannelList	= 16,
336		.i_AiMaxdata		= 65535,
337		.b_AvailableConvertUnit	= 6,
338		.ui_MinAcquisitiontimeNs = 5000,
339		.has_dig_in		= 1,
340		.has_dig_out		= 1,
341	},
342	[BOARD_APCI3002_8] = {
343		.pc_DriverName		= "apci3002-8",
344		.i_NbrAiChannelDiff	= 8,
345		.i_AiChannelList	= 8,
346		.i_AiMaxdata		= 65535,
347		.b_AvailableConvertUnit	= 6,
348		.ui_MinAcquisitiontimeNs = 5000,
349		.has_dig_in		= 1,
350		.has_dig_out		= 1,
351	},
352	[BOARD_APCI3002_4] = {
353		.pc_DriverName		= "apci3002-4",
354		.i_NbrAiChannelDiff	= 4,
355		.i_AiChannelList	= 4,
356		.i_AiMaxdata		= 65535,
357		.b_AvailableConvertUnit	= 6,
358		.ui_MinAcquisitiontimeNs = 5000,
359		.has_dig_in		= 1,
360		.has_dig_out		= 1,
361	},
362	[BOARD_APCI3500] = {
363		.pc_DriverName		= "apci3500",
364		.has_ao			= 1,
365		.has_ttl_io		= 1,
366	},
367};
368
369struct apci3xxx_private {
370	int iobase;
371	int i_IobaseReserved;
372	void __iomem *dw_AiBase;
373	unsigned char b_AiInitialisation;
374	unsigned int ui_AiNbrofChannels;	/*  how many channels is measured */
375	unsigned int ui_AiReadData[32];
376	unsigned char b_EocEosInterrupt;
377	unsigned int ui_EocEosConversionTime;
378	unsigned char b_EocEosConversionTimeBase;
379	unsigned char b_SingelDiff;
380	struct task_struct *tsk_Current;
381	unsigned int ul_TTLPortConfiguration[10];
382};
383
384#include "addi-data/hwdrv_apci3xxx.c"
385
386static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
387{
388	struct comedi_device *dev = d;
389	struct apci3xxx_private *devpriv = dev->private;
390	unsigned int status;
391	int i;
392
393	/* Test if interrupt occur */
394	status = readl(devpriv->dw_AiBase + 16);
395	if ((status & 0x2) == 0x2) {
396		/* Reset the interrupt */
397		writel(status, devpriv->dw_AiBase + 16);
398
399		/* Test if interrupt enabled */
400		if (devpriv->b_EocEosInterrupt == 1) {
401			/* Read all analog inputs value */
402			for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) {
403				unsigned int val;
404
405				val = readl(devpriv->dw_AiBase + 28);
406				devpriv->ui_AiReadData[i] = val;
407			}
408
409			/* Set the interrupt flag */
410			devpriv->b_EocEosInterrupt = 2;
411
412			/* Send a signal to from kernel to user space */
413			send_sig(SIGIO, devpriv->tsk_Current, 0);
414		}
415	}
416	return IRQ_RETVAL(1);
417}
418
419static int apci3xxx_di_insn_bits(struct comedi_device *dev,
420				 struct comedi_subdevice *s,
421				 struct comedi_insn *insn,
422				 unsigned int *data)
423{
424	struct apci3xxx_private *devpriv = dev->private;
425
426	data[1] = inl(devpriv->iobase + 32) & 0xf;
427
428	return insn->n;
429}
430
431static int apci3xxx_do_insn_bits(struct comedi_device *dev,
432				 struct comedi_subdevice *s,
433				 struct comedi_insn *insn,
434				 unsigned int *data)
435{
436	struct apci3xxx_private *devpriv = dev->private;
437	unsigned int mask = data[0];
438	unsigned int bits = data[1];
439
440	s->state = inl(devpriv->iobase + 48) & 0xf;
441	if (mask) {
442		s->state &= ~mask;
443		s->state |= (bits & mask);
444
445		outl(s->state, devpriv->iobase + 48);
446	}
447
448	data[1] = s->state;
449
450	return insn->n;
451}
452
453static int apci3xxx_reset(struct comedi_device *dev)
454{
455	struct apci3xxx_private *devpriv = dev->private;
456	unsigned int val;
457	int i;
458
459	/* Disable the interrupt */
460	disable_irq(dev->irq);
461
462	/* Reset the interrupt flag */
463	devpriv->b_EocEosInterrupt = 0;
464
465	/* Clear the start command */
466	writel(0, devpriv->dw_AiBase + 8);
467
468	/* Reset the interrupt flags */
469	val = readl(devpriv->dw_AiBase + 16);
470	writel(val, devpriv->dw_AiBase + 16);
471
472	/* clear the EOS */
473	readl(devpriv->dw_AiBase + 20);
474
475	/* Clear the FIFO */
476	for (i = 0; i < 16; i++)
477		val = readl(devpriv->dw_AiBase + 28);
478
479	/* Enable the interrupt */
480	enable_irq(dev->irq);
481
482	return 0;
483}
484
485static int apci3xxx_auto_attach(struct comedi_device *dev,
486				unsigned long context)
487{
488	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
489	const struct apci3xxx_boardinfo *board = NULL;
490	struct apci3xxx_private *devpriv;
491	struct comedi_subdevice *s;
492	int ret, n_subdevices;
493
494	if (context < ARRAY_SIZE(apci3xxx_boardtypes))
495		board = &apci3xxx_boardtypes[context];
496	if (!board)
497		return -ENODEV;
498	dev->board_ptr = board;
499	dev->board_name = board->pc_DriverName;
500
501	devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
502	if (!devpriv)
503		return -ENOMEM;
504	dev->private = devpriv;
505
506	ret = comedi_pci_enable(dev);
507	if (ret)
508		return ret;
509
510	/* board has an ADDIDATA_9054 eeprom */
511	dev->iobase = pci_resource_start(pcidev, 2);
512	devpriv->iobase = pci_resource_start(pcidev, 2);
513	devpriv->dw_AiBase = pci_ioremap_bar(pcidev, 3);
514	devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3);
515
516	if (pcidev->irq > 0) {
517		ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
518				  IRQF_SHARED, dev->board_name, dev);
519		if (ret == 0)
520			dev->irq = pcidev->irq;
521	}
522
523	n_subdevices = 7;
524	ret = comedi_alloc_subdevices(dev, n_subdevices);
525	if (ret)
526		return ret;
527
528	/*  Allocate and Initialise AI Subdevice Structures */
529	s = &dev->subdevices[0];
530	if (board->i_NbrAiChannel || board->i_NbrAiChannelDiff) {
531		dev->read_subdev = s;
532		s->type = COMEDI_SUBD_AI;
533		s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND |
534				  SDF_DIFF;
535		if (board->i_NbrAiChannel) {
536			s->n_chan = board->i_NbrAiChannel;
537			devpriv->b_SingelDiff = 0;
538		} else {
539			s->n_chan = board->i_NbrAiChannelDiff;
540			devpriv->b_SingelDiff = 1;
541		}
542		s->maxdata = board->i_AiMaxdata;
543		s->len_chanlist = board->i_AiChannelList;
544		s->range_table = &apci3xxx_ai_range;
545
546		/* Set the initialisation flag */
547		devpriv->b_AiInitialisation = 1;
548
549		s->insn_config = i_APCI3XXX_InsnConfigAnalogInput;
550		s->insn_read = i_APCI3XXX_InsnReadAnalogInput;
551
552	} else {
553		s->type = COMEDI_SUBD_UNUSED;
554	}
555
556	/*  Allocate and Initialise AO Subdevice Structures */
557	s = &dev->subdevices[1];
558	if (board->has_ao) {
559		s->type = COMEDI_SUBD_AO;
560		s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
561		s->n_chan = 4;
562		s->maxdata = 0x0fff;
563		s->range_table = &apci3xxx_ao_range;
564		s->insn_write = i_APCI3XXX_InsnWriteAnalogOutput;
565	} else {
566		s->type = COMEDI_SUBD_UNUSED;
567	}
568
569	/* Digital Input subdevice */
570	s = &dev->subdevices[2];
571	if (board->has_dig_in) {
572		s->type		= COMEDI_SUBD_DI;
573		s->subdev_flags	= SDF_READABLE;
574		s->n_chan	= 4;
575		s->maxdata	= 1;
576		s->range_table	= &range_digital;
577		s->insn_bits	= apci3xxx_di_insn_bits;
578	} else {
579		s->type		= COMEDI_SUBD_UNUSED;
580	}
581
582	/* Digital Output subdevice */
583	s = &dev->subdevices[3];
584	if (board->has_dig_out) {
585		s->type		= COMEDI_SUBD_DO;
586		s->subdev_flags	= SDF_WRITEABLE;
587		s->n_chan	= 4;
588		s->maxdata	= 1;
589		s->range_table	= &range_digital;
590		s->insn_bits	= apci3xxx_do_insn_bits;
591	} else {
592		s->type		= COMEDI_SUBD_UNUSED;
593	}
594
595	/*  Allocate and Initialise Timer Subdevice Structures */
596	s = &dev->subdevices[4];
597	s->type = COMEDI_SUBD_UNUSED;
598
599	/* TTL Digital I/O subdevice */
600	s = &dev->subdevices[5];
601	if (board->has_ttl_io) {
602		s->type		= COMEDI_SUBD_DIO;
603		s->subdev_flags	= SDF_READABLE | SDF_WRITEABLE;
604		s->n_chan	= 24;
605		s->maxdata	= 1;
606		s->io_bits	= 0xff;	/* channels 0-7 are always outputs */
607		s->range_table	= &range_digital;
608		s->insn_config	= i_APCI3XXX_InsnConfigInitTTLIO;
609		s->insn_bits	= i_APCI3XXX_InsnBitsTTLIO;
610		s->insn_read	= i_APCI3XXX_InsnReadTTLIO;
611		s->insn_write	= i_APCI3XXX_InsnWriteTTLIO;
612	} else {
613		s->type = COMEDI_SUBD_UNUSED;
614	}
615
616	/* EEPROM */
617	s = &dev->subdevices[6];
618	s->type = COMEDI_SUBD_UNUSED;
619
620	apci3xxx_reset(dev);
621	return 0;
622}
623
624static void apci3xxx_detach(struct comedi_device *dev)
625{
626	struct apci3xxx_private *devpriv = dev->private;
627
628	if (devpriv) {
629		if (dev->iobase)
630			apci3xxx_reset(dev);
631		if (dev->irq)
632			free_irq(dev->irq, dev);
633		if (devpriv->dw_AiBase)
634			iounmap(devpriv->dw_AiBase);
635	}
636	comedi_pci_disable(dev);
637}
638
639static struct comedi_driver apci3xxx_driver = {
640	.driver_name	= "addi_apci_3xxx",
641	.module		= THIS_MODULE,
642	.auto_attach	= apci3xxx_auto_attach,
643	.detach		= apci3xxx_detach,
644};
645
646static int apci3xxx_pci_probe(struct pci_dev *dev,
647			      const struct pci_device_id *id)
648{
649	return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
650}
651
652static DEFINE_PCI_DEVICE_TABLE(apci3xxx_pci_table) = {
653	{ PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
654	{ PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
655	{ PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
656	{ PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
657	{ PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
658	{ PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
659	{ PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
660	{ PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
661	{ PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
662	{ PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
663	{ PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
664	{ PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
665	{ PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
666	{ PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
667	{ PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
668	{ PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
669	{ PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
670	{ PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
671	{ PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
672	{ PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
673	{ PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
674	{ PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
675	{ PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
676	{ PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
677	{ PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
678	{ 0 }
679};
680MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
681
682static struct pci_driver apci3xxx_pci_driver = {
683	.name		= "addi_apci_3xxx",
684	.id_table	= apci3xxx_pci_table,
685	.probe		= apci3xxx_pci_probe,
686	.remove		= comedi_pci_auto_unconfig,
687};
688module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
689
690MODULE_AUTHOR("Comedi http://www.comedi.org");
691MODULE_DESCRIPTION("Comedi low-level driver");
692MODULE_LICENSE("GPL");
693