addi_apci_3xxx.c revision 08be3097fa1597f13b97ab390b0e80ce07cb4796
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 int ui_AiNbrofChannels;	/*  how many channels is measured */
374	unsigned int ui_AiReadData[32];
375	unsigned char b_EocEosInterrupt;
376	unsigned int ui_EocEosConversionTime;
377	unsigned char b_EocEosConversionTimeBase;
378	unsigned char b_SingelDiff;
379	struct task_struct *tsk_Current;
380};
381
382#include "addi-data/hwdrv_apci3xxx.c"
383
384static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
385{
386	struct comedi_device *dev = d;
387	struct apci3xxx_private *devpriv = dev->private;
388	unsigned int status;
389	int i;
390
391	/* Test if interrupt occur */
392	status = readl(devpriv->dw_AiBase + 16);
393	if ((status & 0x2) == 0x2) {
394		/* Reset the interrupt */
395		writel(status, devpriv->dw_AiBase + 16);
396
397		/* Test if interrupt enabled */
398		if (devpriv->b_EocEosInterrupt == 1) {
399			/* Read all analog inputs value */
400			for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) {
401				unsigned int val;
402
403				val = readl(devpriv->dw_AiBase + 28);
404				devpriv->ui_AiReadData[i] = val;
405			}
406
407			/* Set the interrupt flag */
408			devpriv->b_EocEosInterrupt = 2;
409
410			/* Send a signal to from kernel to user space */
411			send_sig(SIGIO, devpriv->tsk_Current, 0);
412		}
413	}
414	return IRQ_RETVAL(1);
415}
416
417static int apci3xxx_ao_insn_write(struct comedi_device *dev,
418				  struct comedi_subdevice *s,
419				  struct comedi_insn *insn,
420				  unsigned int *data)
421{
422	struct apci3xxx_private *devpriv = dev->private;
423	unsigned int chan = CR_CHAN(insn->chanspec);
424	unsigned int range = CR_RANGE(insn->chanspec);
425	unsigned int status;
426	int i;
427
428	for (i = 0; i < insn->n; i++) {
429		/* Set the range selection */
430		writel(range, devpriv->dw_AiBase + 96);
431
432		/* Write the analog value to the selected channel */
433		writel((data[i] << 8) | chan, devpriv->dw_AiBase + 100);
434
435		/* Wait the end of transfer */
436		do {
437			status = readl(devpriv->dw_AiBase + 96);
438		} while ((status & 0x100) != 0x100);
439	}
440
441	return insn->n;
442}
443
444static int apci3xxx_di_insn_bits(struct comedi_device *dev,
445				 struct comedi_subdevice *s,
446				 struct comedi_insn *insn,
447				 unsigned int *data)
448{
449	struct apci3xxx_private *devpriv = dev->private;
450
451	data[1] = inl(devpriv->iobase + 32) & 0xf;
452
453	return insn->n;
454}
455
456static int apci3xxx_do_insn_bits(struct comedi_device *dev,
457				 struct comedi_subdevice *s,
458				 struct comedi_insn *insn,
459				 unsigned int *data)
460{
461	struct apci3xxx_private *devpriv = dev->private;
462	unsigned int mask = data[0];
463	unsigned int bits = data[1];
464
465	s->state = inl(devpriv->iobase + 48) & 0xf;
466	if (mask) {
467		s->state &= ~mask;
468		s->state |= (bits & mask);
469
470		outl(s->state, devpriv->iobase + 48);
471	}
472
473	data[1] = s->state;
474
475	return insn->n;
476}
477
478static int apci3xxx_dio_insn_config(struct comedi_device *dev,
479				    struct comedi_subdevice *s,
480				    struct comedi_insn *insn,
481				    unsigned int *data)
482{
483	struct apci3xxx_private *devpriv = dev->private;
484	unsigned int chan = CR_CHAN(insn->chanspec);
485	unsigned int mask = 1 << chan;
486	unsigned int bits;
487
488	/*
489	 * Port 0 (channels 0-7) are always inputs
490	 * Port 1 (channels 8-15) are always outputs
491	 * Port 2 (channels 16-23) are programmable i/o
492	 *
493	 * Changing any channel in port 2 changes the entire port.
494	 */
495	if (mask & 0xff0000)
496		bits = 0xff0000;
497	else
498		bits = 0;
499
500	switch (data[0]) {
501	case INSN_CONFIG_DIO_INPUT:
502		s->io_bits &= ~bits;
503		break;
504	case INSN_CONFIG_DIO_OUTPUT:
505		s->io_bits |= bits;
506		break;
507	case INSN_CONFIG_DIO_QUERY:
508		data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
509		return insn->n;
510	default:
511		return -EINVAL;
512	}
513
514	/* update port 2 configuration */
515	if (bits)
516		outl((s->io_bits >> 24) & 0xff, devpriv->iobase + 224);
517
518	return insn->n;
519}
520
521static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
522				  struct comedi_subdevice *s,
523				  struct comedi_insn *insn,
524				  unsigned int *data)
525{
526	struct apci3xxx_private *devpriv = dev->private;
527	unsigned int mask = data[0];
528	unsigned int bits = data[1];
529	unsigned int val;
530
531	/* only update output channels */
532	mask &= s->io_bits;
533	if (mask) {
534		s->state &= ~mask;
535		s->state |= (bits & mask);
536
537		if (mask & 0xff)
538			outl(s->state & 0xff, devpriv->iobase + 80);
539		if (mask & 0xff0000)
540			outl((s->state >> 16) & 0xff, devpriv->iobase + 112);
541	}
542
543	val = inl(devpriv->iobase + 80);
544	val |= (inl(devpriv->iobase + 64) << 8);
545	if (s->io_bits & 0xff0000)
546		val |= (inl(devpriv->iobase + 112) << 16);
547	else
548		val |= (inl(devpriv->iobase + 96) << 16);
549
550	data[1] = val;
551
552	return insn->n;
553}
554
555static int apci3xxx_reset(struct comedi_device *dev)
556{
557	struct apci3xxx_private *devpriv = dev->private;
558	unsigned int val;
559	int i;
560
561	/* Disable the interrupt */
562	disable_irq(dev->irq);
563
564	/* Reset the interrupt flag */
565	devpriv->b_EocEosInterrupt = 0;
566
567	/* Clear the start command */
568	writel(0, devpriv->dw_AiBase + 8);
569
570	/* Reset the interrupt flags */
571	val = readl(devpriv->dw_AiBase + 16);
572	writel(val, devpriv->dw_AiBase + 16);
573
574	/* clear the EOS */
575	readl(devpriv->dw_AiBase + 20);
576
577	/* Clear the FIFO */
578	for (i = 0; i < 16; i++)
579		val = readl(devpriv->dw_AiBase + 28);
580
581	/* Enable the interrupt */
582	enable_irq(dev->irq);
583
584	return 0;
585}
586
587static int apci3xxx_auto_attach(struct comedi_device *dev,
588				unsigned long context)
589{
590	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
591	const struct apci3xxx_boardinfo *board = NULL;
592	struct apci3xxx_private *devpriv;
593	struct comedi_subdevice *s;
594	int ret, n_subdevices;
595
596	if (context < ARRAY_SIZE(apci3xxx_boardtypes))
597		board = &apci3xxx_boardtypes[context];
598	if (!board)
599		return -ENODEV;
600	dev->board_ptr = board;
601	dev->board_name = board->pc_DriverName;
602
603	devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
604	if (!devpriv)
605		return -ENOMEM;
606	dev->private = devpriv;
607
608	ret = comedi_pci_enable(dev);
609	if (ret)
610		return ret;
611
612	/* board has an ADDIDATA_9054 eeprom */
613	dev->iobase = pci_resource_start(pcidev, 2);
614	devpriv->iobase = pci_resource_start(pcidev, 2);
615	devpriv->dw_AiBase = pci_ioremap_bar(pcidev, 3);
616	devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3);
617
618	if (pcidev->irq > 0) {
619		ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
620				  IRQF_SHARED, dev->board_name, dev);
621		if (ret == 0)
622			dev->irq = pcidev->irq;
623	}
624
625	n_subdevices = 7;
626	ret = comedi_alloc_subdevices(dev, n_subdevices);
627	if (ret)
628		return ret;
629
630	/*  Allocate and Initialise AI Subdevice Structures */
631	s = &dev->subdevices[0];
632	if (board->i_NbrAiChannel || board->i_NbrAiChannelDiff) {
633		dev->read_subdev = s;
634		s->type = COMEDI_SUBD_AI;
635		s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND |
636				  SDF_DIFF;
637		if (board->i_NbrAiChannel) {
638			s->n_chan = board->i_NbrAiChannel;
639			devpriv->b_SingelDiff = 0;
640		} else {
641			s->n_chan = board->i_NbrAiChannelDiff;
642			devpriv->b_SingelDiff = 1;
643		}
644		s->maxdata = board->i_AiMaxdata;
645		s->len_chanlist = board->i_AiChannelList;
646		s->range_table = &apci3xxx_ai_range;
647
648		s->insn_config = i_APCI3XXX_InsnConfigAnalogInput;
649		s->insn_read = i_APCI3XXX_InsnReadAnalogInput;
650
651	} else {
652		s->type = COMEDI_SUBD_UNUSED;
653	}
654
655	/* Analog Output subdevice */
656	s = &dev->subdevices[1];
657	if (board->has_ao) {
658		s->type		= COMEDI_SUBD_AO;
659		s->subdev_flags	= SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
660		s->n_chan	= 4;
661		s->maxdata	= 0x0fff;
662		s->range_table	= &apci3xxx_ao_range;
663		s->insn_write	= apci3xxx_ao_insn_write;
664	} else {
665		s->type		= COMEDI_SUBD_UNUSED;
666	}
667
668	/* Digital Input subdevice */
669	s = &dev->subdevices[2];
670	if (board->has_dig_in) {
671		s->type		= COMEDI_SUBD_DI;
672		s->subdev_flags	= SDF_READABLE;
673		s->n_chan	= 4;
674		s->maxdata	= 1;
675		s->range_table	= &range_digital;
676		s->insn_bits	= apci3xxx_di_insn_bits;
677	} else {
678		s->type		= COMEDI_SUBD_UNUSED;
679	}
680
681	/* Digital Output subdevice */
682	s = &dev->subdevices[3];
683	if (board->has_dig_out) {
684		s->type		= COMEDI_SUBD_DO;
685		s->subdev_flags	= SDF_WRITEABLE;
686		s->n_chan	= 4;
687		s->maxdata	= 1;
688		s->range_table	= &range_digital;
689		s->insn_bits	= apci3xxx_do_insn_bits;
690	} else {
691		s->type		= COMEDI_SUBD_UNUSED;
692	}
693
694	/*  Allocate and Initialise Timer Subdevice Structures */
695	s = &dev->subdevices[4];
696	s->type = COMEDI_SUBD_UNUSED;
697
698	/* TTL Digital I/O subdevice */
699	s = &dev->subdevices[5];
700	if (board->has_ttl_io) {
701		s->type		= COMEDI_SUBD_DIO;
702		s->subdev_flags	= SDF_READABLE | SDF_WRITEABLE;
703		s->n_chan	= 24;
704		s->maxdata	= 1;
705		s->io_bits	= 0xff;	/* channels 0-7 are always outputs */
706		s->range_table	= &range_digital;
707		s->insn_config	= apci3xxx_dio_insn_config;
708		s->insn_bits	= apci3xxx_dio_insn_bits;
709	} else {
710		s->type = COMEDI_SUBD_UNUSED;
711	}
712
713	/* EEPROM */
714	s = &dev->subdevices[6];
715	s->type = COMEDI_SUBD_UNUSED;
716
717	apci3xxx_reset(dev);
718	return 0;
719}
720
721static void apci3xxx_detach(struct comedi_device *dev)
722{
723	struct apci3xxx_private *devpriv = dev->private;
724
725	if (devpriv) {
726		if (dev->iobase)
727			apci3xxx_reset(dev);
728		if (dev->irq)
729			free_irq(dev->irq, dev);
730		if (devpriv->dw_AiBase)
731			iounmap(devpriv->dw_AiBase);
732	}
733	comedi_pci_disable(dev);
734}
735
736static struct comedi_driver apci3xxx_driver = {
737	.driver_name	= "addi_apci_3xxx",
738	.module		= THIS_MODULE,
739	.auto_attach	= apci3xxx_auto_attach,
740	.detach		= apci3xxx_detach,
741};
742
743static int apci3xxx_pci_probe(struct pci_dev *dev,
744			      const struct pci_device_id *id)
745{
746	return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
747}
748
749static DEFINE_PCI_DEVICE_TABLE(apci3xxx_pci_table) = {
750	{ PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
751	{ PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
752	{ PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
753	{ PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
754	{ PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
755	{ PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
756	{ PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
757	{ PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
758	{ PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
759	{ PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
760	{ PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
761	{ PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
762	{ PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
763	{ PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
764	{ PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
765	{ PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
766	{ PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
767	{ PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
768	{ PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
769	{ PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
770	{ PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
771	{ PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
772	{ PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
773	{ PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
774	{ PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
775	{ 0 }
776};
777MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
778
779static struct pci_driver apci3xxx_pci_driver = {
780	.name		= "addi_apci_3xxx",
781	.id_table	= apci3xxx_pci_table,
782	.probe		= apci3xxx_pci_probe,
783	.remove		= comedi_pci_auto_unconfig,
784};
785module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
786
787MODULE_AUTHOR("Comedi http://www.comedi.org");
788MODULE_DESCRIPTION("Comedi low-level driver");
789MODULE_LICENSE("GPL");
790