addi_apci_3xxx.c revision 47bd1ad800470f4786f7d35379798a7a10aff84c
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
28#include "../comedidev.h"
29
30#define CONV_UNIT_NS		(1 << 0)
31#define CONV_UNIT_US		(1 << 1)
32#define CONV_UNIT_MS		(1 << 2)
33
34static const struct comedi_lrange apci3xxx_ai_range = {
35	8, {
36		BIP_RANGE(10),
37		BIP_RANGE(5),
38		BIP_RANGE(2),
39		BIP_RANGE(1),
40		UNI_RANGE(10),
41		UNI_RANGE(5),
42		UNI_RANGE(2),
43		UNI_RANGE(1)
44	}
45};
46
47static const struct comedi_lrange apci3xxx_ao_range = {
48	2, {
49		BIP_RANGE(10),
50		UNI_RANGE(10)
51	}
52};
53
54enum apci3xxx_boardid {
55	BOARD_APCI3000_16,
56	BOARD_APCI3000_8,
57	BOARD_APCI3000_4,
58	BOARD_APCI3006_16,
59	BOARD_APCI3006_8,
60	BOARD_APCI3006_4,
61	BOARD_APCI3010_16,
62	BOARD_APCI3010_8,
63	BOARD_APCI3010_4,
64	BOARD_APCI3016_16,
65	BOARD_APCI3016_8,
66	BOARD_APCI3016_4,
67	BOARD_APCI3100_16_4,
68	BOARD_APCI3100_8_4,
69	BOARD_APCI3106_16_4,
70	BOARD_APCI3106_8_4,
71	BOARD_APCI3110_16_4,
72	BOARD_APCI3110_8_4,
73	BOARD_APCI3116_16_4,
74	BOARD_APCI3116_8_4,
75	BOARD_APCI3003,
76	BOARD_APCI3002_16,
77	BOARD_APCI3002_8,
78	BOARD_APCI3002_4,
79	BOARD_APCI3500,
80};
81
82struct apci3xxx_boardinfo {
83	const char *name;
84	int ai_subdev_flags;
85	int ai_n_chan;
86	int i_NbrAiChannelDiff;
87	unsigned int ai_maxdata;
88	unsigned char ai_conv_units;
89	unsigned int ai_min_acq_ns;
90	unsigned int has_ao:1;
91	unsigned int has_dig_in:1;
92	unsigned int has_dig_out:1;
93	unsigned int has_ttl_io:1;
94};
95
96static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
97	[BOARD_APCI3000_16] = {
98		.name			= "apci3000-16",
99		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
100		.ai_n_chan		= 16,
101		.i_NbrAiChannelDiff	= 8,
102		.ai_maxdata		= 0x0fff,
103		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
104		.ai_min_acq_ns		= 10000,
105		.has_ttl_io		= 1,
106	},
107	[BOARD_APCI3000_8] = {
108		.name			= "apci3000-8",
109		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
110		.ai_n_chan		= 8,
111		.i_NbrAiChannelDiff	= 4,
112		.ai_maxdata		= 0x0fff,
113		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
114		.ai_min_acq_ns		= 10000,
115		.has_ttl_io		= 1,
116	},
117	[BOARD_APCI3000_4] = {
118		.name			= "apci3000-4",
119		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
120		.ai_n_chan		= 4,
121		.i_NbrAiChannelDiff	= 2,
122		.ai_maxdata		= 0x0fff,
123		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
124		.ai_min_acq_ns		= 10000,
125		.has_ttl_io		= 1,
126	},
127	[BOARD_APCI3006_16] = {
128		.name			= "apci3006-16",
129		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
130		.ai_n_chan		= 16,
131		.i_NbrAiChannelDiff	= 8,
132		.ai_maxdata		= 0xffff,
133		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
134		.ai_min_acq_ns		= 10000,
135		.has_ttl_io		= 1,
136	},
137	[BOARD_APCI3006_8] = {
138		.name			= "apci3006-8",
139		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
140		.ai_n_chan		= 8,
141		.i_NbrAiChannelDiff	= 4,
142		.ai_maxdata		= 0xffff,
143		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
144		.ai_min_acq_ns		= 10000,
145		.has_ttl_io		= 1,
146	},
147	[BOARD_APCI3006_4] = {
148		.name			= "apci3006-4",
149		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
150		.ai_n_chan		= 4,
151		.i_NbrAiChannelDiff	= 2,
152		.ai_maxdata		= 0xffff,
153		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
154		.ai_min_acq_ns		= 10000,
155		.has_ttl_io		= 1,
156	},
157	[BOARD_APCI3010_16] = {
158		.name			= "apci3010-16",
159		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
160		.ai_n_chan		= 16,
161		.i_NbrAiChannelDiff	= 8,
162		.ai_maxdata		= 0x0fff,
163		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
164		.ai_min_acq_ns		= 5000,
165		.has_dig_in		= 1,
166		.has_dig_out		= 1,
167		.has_ttl_io		= 1,
168	},
169	[BOARD_APCI3010_8] = {
170		.name			= "apci3010-8",
171		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
172		.ai_n_chan		= 8,
173		.i_NbrAiChannelDiff	= 4,
174		.ai_maxdata		= 0x0fff,
175		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
176		.ai_min_acq_ns		= 5000,
177		.has_dig_in		= 1,
178		.has_dig_out		= 1,
179		.has_ttl_io		= 1,
180	},
181	[BOARD_APCI3010_4] = {
182		.name			= "apci3010-4",
183		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
184		.ai_n_chan		= 4,
185		.i_NbrAiChannelDiff	= 2,
186		.ai_maxdata		= 0x0fff,
187		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
188		.ai_min_acq_ns		= 5000,
189		.has_dig_in		= 1,
190		.has_dig_out		= 1,
191		.has_ttl_io		= 1,
192	},
193	[BOARD_APCI3016_16] = {
194		.name			= "apci3016-16",
195		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
196		.ai_n_chan		= 16,
197		.i_NbrAiChannelDiff	= 8,
198		.ai_maxdata		= 0xffff,
199		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
200		.ai_min_acq_ns		= 5000,
201		.has_dig_in		= 1,
202		.has_dig_out		= 1,
203		.has_ttl_io		= 1,
204	},
205	[BOARD_APCI3016_8] = {
206		.name			= "apci3016-8",
207		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
208		.ai_n_chan		= 8,
209		.i_NbrAiChannelDiff	= 4,
210		.ai_maxdata		= 0xffff,
211		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
212		.ai_min_acq_ns		= 5000,
213		.has_dig_in		= 1,
214		.has_dig_out		= 1,
215		.has_ttl_io		= 1,
216	},
217	[BOARD_APCI3016_4] = {
218		.name			= "apci3016-4",
219		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
220		.ai_n_chan		= 4,
221		.i_NbrAiChannelDiff	= 2,
222		.ai_maxdata		= 0xffff,
223		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
224		.ai_min_acq_ns		= 5000,
225		.has_dig_in		= 1,
226		.has_dig_out		= 1,
227		.has_ttl_io		= 1,
228	},
229	[BOARD_APCI3100_16_4] = {
230		.name			= "apci3100-16-4",
231		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
232		.ai_n_chan		= 16,
233		.i_NbrAiChannelDiff	= 8,
234		.ai_maxdata		= 0x0fff,
235		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
236		.ai_min_acq_ns		= 10000,
237		.has_ao			= 1,
238		.has_ttl_io		= 1,
239	},
240	[BOARD_APCI3100_8_4] = {
241		.name			= "apci3100-8-4",
242		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
243		.ai_n_chan		= 8,
244		.i_NbrAiChannelDiff	= 4,
245		.ai_maxdata		= 0x0fff,
246		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
247		.ai_min_acq_ns		= 10000,
248		.has_ao			= 1,
249		.has_ttl_io		= 1,
250	},
251	[BOARD_APCI3106_16_4] = {
252		.name			= "apci3106-16-4",
253		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
254		.ai_n_chan		= 16,
255		.i_NbrAiChannelDiff	= 8,
256		.ai_maxdata		= 0xffff,
257		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
258		.ai_min_acq_ns		= 10000,
259		.has_ao			= 1,
260		.has_ttl_io		= 1,
261	},
262	[BOARD_APCI3106_8_4] = {
263		.name			= "apci3106-8-4",
264		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
265		.ai_n_chan		= 8,
266		.i_NbrAiChannelDiff	= 4,
267		.ai_maxdata		= 0xffff,
268		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
269		.ai_min_acq_ns		= 10000,
270		.has_ao			= 1,
271		.has_ttl_io		= 1,
272	},
273	[BOARD_APCI3110_16_4] = {
274		.name			= "apci3110-16-4",
275		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
276		.ai_n_chan		= 16,
277		.i_NbrAiChannelDiff	= 8,
278		.ai_maxdata		= 0x0fff,
279		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
280		.ai_min_acq_ns		= 5000,
281		.has_ao			= 1,
282		.has_dig_in		= 1,
283		.has_dig_out		= 1,
284		.has_ttl_io		= 1,
285	},
286	[BOARD_APCI3110_8_4] = {
287		.name			= "apci3110-8-4",
288		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
289		.ai_n_chan		= 8,
290		.i_NbrAiChannelDiff	= 4,
291		.ai_maxdata		= 0x0fff,
292		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
293		.ai_min_acq_ns		= 5000,
294		.has_ao			= 1,
295		.has_dig_in		= 1,
296		.has_dig_out		= 1,
297		.has_ttl_io		= 1,
298	},
299	[BOARD_APCI3116_16_4] = {
300		.name			= "apci3116-16-4",
301		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
302		.ai_n_chan		= 16,
303		.i_NbrAiChannelDiff	= 8,
304		.ai_maxdata		= 0xffff,
305		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
306		.ai_min_acq_ns		= 5000,
307		.has_ao			= 1,
308		.has_dig_in		= 1,
309		.has_dig_out		= 1,
310		.has_ttl_io		= 1,
311	},
312	[BOARD_APCI3116_8_4] = {
313		.name			= "apci3116-8-4",
314		.ai_subdev_flags	= SDF_COMMON | SDF_GROUND | SDF_DIFF,
315		.ai_n_chan		= 8,
316		.i_NbrAiChannelDiff	= 4,
317		.ai_maxdata		= 0xffff,
318		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
319		.ai_min_acq_ns		= 5000,
320		.has_ao			= 1,
321		.has_dig_in		= 1,
322		.has_dig_out		= 1,
323		.has_ttl_io		= 1,
324	},
325	[BOARD_APCI3003] = {
326		.name			= "apci3003",
327		.ai_subdev_flags	= SDF_DIFF,
328		.i_NbrAiChannelDiff	= 4,
329		.ai_maxdata		= 0xffff,
330		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US |
331					  CONV_UNIT_NS,
332		.ai_min_acq_ns		= 2500,
333		.has_dig_in		= 1,
334		.has_dig_out		= 1,
335	},
336	[BOARD_APCI3002_16] = {
337		.name			= "apci3002-16",
338		.ai_subdev_flags	= SDF_DIFF,
339		.i_NbrAiChannelDiff	= 16,
340		.ai_maxdata		= 0xffff,
341		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
342		.ai_min_acq_ns		= 5000,
343		.has_dig_in		= 1,
344		.has_dig_out		= 1,
345	},
346	[BOARD_APCI3002_8] = {
347		.name			= "apci3002-8",
348		.ai_subdev_flags	= SDF_DIFF,
349		.i_NbrAiChannelDiff	= 8,
350		.ai_maxdata		= 0xffff,
351		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
352		.ai_min_acq_ns		= 5000,
353		.has_dig_in		= 1,
354		.has_dig_out		= 1,
355	},
356	[BOARD_APCI3002_4] = {
357		.name			= "apci3002-4",
358		.ai_subdev_flags	= SDF_DIFF,
359		.i_NbrAiChannelDiff	= 4,
360		.ai_maxdata		= 0xffff,
361		.ai_conv_units		= CONV_UNIT_MS | CONV_UNIT_US,
362		.ai_min_acq_ns		= 5000,
363		.has_dig_in		= 1,
364		.has_dig_out		= 1,
365	},
366	[BOARD_APCI3500] = {
367		.name			= "apci3500",
368		.has_ao			= 1,
369		.has_ttl_io		= 1,
370	},
371};
372
373struct apci3xxx_private {
374	void __iomem *mmio;
375	unsigned int ui_AiNbrofChannels;	/*  how many channels is measured */
376	unsigned int ui_AiReadData[32];
377	unsigned char b_EocEosInterrupt;
378	unsigned int ui_EocEosConversionTime;
379	unsigned char b_EocEosConversionTimeBase;
380	unsigned char b_SingelDiff;
381};
382
383#include "addi-data/hwdrv_apci3xxx.c"
384
385static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
386{
387	struct comedi_device *dev = d;
388	struct apci3xxx_private *devpriv = dev->private;
389	unsigned int status;
390	int i;
391
392	/* Test if interrupt occur */
393	status = readl(devpriv->mmio + 16);
394	if ((status & 0x2) == 0x2) {
395		/* Reset the interrupt */
396		writel(status, devpriv->mmio + 16);
397
398		/* Test if interrupt enabled */
399		if (devpriv->b_EocEosInterrupt == 1) {
400			/* Read all analog inputs value */
401			for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) {
402				unsigned int val;
403
404				val = readl(devpriv->mmio + 28);
405				devpriv->ui_AiReadData[i] = val;
406			}
407
408			/* Set the interrupt flag */
409			devpriv->b_EocEosInterrupt = 2;
410
411			/* FIXME: comedi_event() */
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->mmio + 96);
431
432		/* Write the analog value to the selected channel */
433		writel((data[i] << 8) | chan, devpriv->mmio + 100);
434
435		/* Wait the end of transfer */
436		do {
437			status = readl(devpriv->mmio + 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	data[1] = inl(dev->iobase + 32) & 0xf;
450
451	return insn->n;
452}
453
454static int apci3xxx_do_insn_bits(struct comedi_device *dev,
455				 struct comedi_subdevice *s,
456				 struct comedi_insn *insn,
457				 unsigned int *data)
458{
459	unsigned int mask = data[0];
460	unsigned int bits = data[1];
461
462	s->state = inl(dev->iobase + 48) & 0xf;
463	if (mask) {
464		s->state &= ~mask;
465		s->state |= (bits & mask);
466
467		outl(s->state, dev->iobase + 48);
468	}
469
470	data[1] = s->state;
471
472	return insn->n;
473}
474
475static int apci3xxx_dio_insn_config(struct comedi_device *dev,
476				    struct comedi_subdevice *s,
477				    struct comedi_insn *insn,
478				    unsigned int *data)
479{
480	unsigned int chan = CR_CHAN(insn->chanspec);
481	unsigned int mask = 1 << chan;
482	unsigned int bits;
483
484	/*
485	 * Port 0 (channels 0-7) are always inputs
486	 * Port 1 (channels 8-15) are always outputs
487	 * Port 2 (channels 16-23) are programmable i/o
488	 *
489	 * Changing any channel in port 2 changes the entire port.
490	 */
491	if (mask & 0xff0000)
492		bits = 0xff0000;
493	else
494		bits = 0;
495
496	switch (data[0]) {
497	case INSN_CONFIG_DIO_INPUT:
498		s->io_bits &= ~bits;
499		break;
500	case INSN_CONFIG_DIO_OUTPUT:
501		s->io_bits |= bits;
502		break;
503	case INSN_CONFIG_DIO_QUERY:
504		data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
505		return insn->n;
506	default:
507		return -EINVAL;
508	}
509
510	/* update port 2 configuration */
511	if (bits)
512		outl((s->io_bits >> 24) & 0xff, dev->iobase + 224);
513
514	return insn->n;
515}
516
517static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
518				  struct comedi_subdevice *s,
519				  struct comedi_insn *insn,
520				  unsigned int *data)
521{
522	unsigned int mask = data[0];
523	unsigned int bits = data[1];
524	unsigned int val;
525
526	/* only update output channels */
527	mask &= s->io_bits;
528	if (mask) {
529		s->state &= ~mask;
530		s->state |= (bits & mask);
531
532		if (mask & 0xff)
533			outl(s->state & 0xff, dev->iobase + 80);
534		if (mask & 0xff0000)
535			outl((s->state >> 16) & 0xff, dev->iobase + 112);
536	}
537
538	val = inl(dev->iobase + 80);
539	val |= (inl(dev->iobase + 64) << 8);
540	if (s->io_bits & 0xff0000)
541		val |= (inl(dev->iobase + 112) << 16);
542	else
543		val |= (inl(dev->iobase + 96) << 16);
544
545	data[1] = val;
546
547	return insn->n;
548}
549
550static int apci3xxx_reset(struct comedi_device *dev)
551{
552	struct apci3xxx_private *devpriv = dev->private;
553	unsigned int val;
554	int i;
555
556	/* Disable the interrupt */
557	disable_irq(dev->irq);
558
559	/* Reset the interrupt flag */
560	devpriv->b_EocEosInterrupt = 0;
561
562	/* Clear the start command */
563	writel(0, devpriv->mmio + 8);
564
565	/* Reset the interrupt flags */
566	val = readl(devpriv->mmio + 16);
567	writel(val, devpriv->mmio + 16);
568
569	/* clear the EOS */
570	readl(devpriv->mmio + 20);
571
572	/* Clear the FIFO */
573	for (i = 0; i < 16; i++)
574		val = readl(devpriv->mmio + 28);
575
576	/* Enable the interrupt */
577	enable_irq(dev->irq);
578
579	return 0;
580}
581
582static int apci3xxx_auto_attach(struct comedi_device *dev,
583				unsigned long context)
584{
585	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
586	const struct apci3xxx_boardinfo *board = NULL;
587	struct apci3xxx_private *devpriv;
588	struct comedi_subdevice *s;
589	int ret, n_subdevices;
590
591	if (context < ARRAY_SIZE(apci3xxx_boardtypes))
592		board = &apci3xxx_boardtypes[context];
593	if (!board)
594		return -ENODEV;
595	dev->board_ptr = board;
596	dev->board_name = board->name;
597
598	devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
599	if (!devpriv)
600		return -ENOMEM;
601	dev->private = devpriv;
602
603	ret = comedi_pci_enable(dev);
604	if (ret)
605		return ret;
606
607	dev->iobase = pci_resource_start(pcidev, 2);
608	devpriv->mmio = pci_ioremap_bar(pcidev, 3);
609
610	if (pcidev->irq > 0) {
611		ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
612				  IRQF_SHARED, dev->board_name, dev);
613		if (ret == 0)
614			dev->irq = pcidev->irq;
615	}
616
617	n_subdevices = 7;
618	ret = comedi_alloc_subdevices(dev, n_subdevices);
619	if (ret)
620		return ret;
621
622	/*  Allocate and Initialise AI Subdevice Structures */
623	s = &dev->subdevices[0];
624	if (board->ai_n_chan || board->i_NbrAiChannelDiff) {
625		dev->read_subdev = s;
626		s->type = COMEDI_SUBD_AI;
627		s->subdev_flags = SDF_READABLE | board->ai_subdev_flags;
628		if (board->ai_n_chan) {
629			s->n_chan = board->ai_n_chan;
630			devpriv->b_SingelDiff = 0;
631		} else {
632			s->n_chan = board->i_NbrAiChannelDiff;
633			devpriv->b_SingelDiff = 1;
634		}
635		s->maxdata = board->ai_maxdata;
636		s->len_chanlist = s->n_chan;
637		s->range_table = &apci3xxx_ai_range;
638
639		s->insn_config = apci3xxx_ai_insn_config;
640		s->insn_read = apci3xxx_ai_insn_read;
641
642	} else {
643		s->type = COMEDI_SUBD_UNUSED;
644	}
645
646	/* Analog Output subdevice */
647	s = &dev->subdevices[1];
648	if (board->has_ao) {
649		s->type		= COMEDI_SUBD_AO;
650		s->subdev_flags	= SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
651		s->n_chan	= 4;
652		s->maxdata	= 0x0fff;
653		s->range_table	= &apci3xxx_ao_range;
654		s->insn_write	= apci3xxx_ao_insn_write;
655	} else {
656		s->type		= COMEDI_SUBD_UNUSED;
657	}
658
659	/* Digital Input subdevice */
660	s = &dev->subdevices[2];
661	if (board->has_dig_in) {
662		s->type		= COMEDI_SUBD_DI;
663		s->subdev_flags	= SDF_READABLE;
664		s->n_chan	= 4;
665		s->maxdata	= 1;
666		s->range_table	= &range_digital;
667		s->insn_bits	= apci3xxx_di_insn_bits;
668	} else {
669		s->type		= COMEDI_SUBD_UNUSED;
670	}
671
672	/* Digital Output subdevice */
673	s = &dev->subdevices[3];
674	if (board->has_dig_out) {
675		s->type		= COMEDI_SUBD_DO;
676		s->subdev_flags	= SDF_WRITEABLE;
677		s->n_chan	= 4;
678		s->maxdata	= 1;
679		s->range_table	= &range_digital;
680		s->insn_bits	= apci3xxx_do_insn_bits;
681	} else {
682		s->type		= COMEDI_SUBD_UNUSED;
683	}
684
685	/*  Allocate and Initialise Timer Subdevice Structures */
686	s = &dev->subdevices[4];
687	s->type = COMEDI_SUBD_UNUSED;
688
689	/* TTL Digital I/O subdevice */
690	s = &dev->subdevices[5];
691	if (board->has_ttl_io) {
692		s->type		= COMEDI_SUBD_DIO;
693		s->subdev_flags	= SDF_READABLE | SDF_WRITEABLE;
694		s->n_chan	= 24;
695		s->maxdata	= 1;
696		s->io_bits	= 0xff;	/* channels 0-7 are always outputs */
697		s->range_table	= &range_digital;
698		s->insn_config	= apci3xxx_dio_insn_config;
699		s->insn_bits	= apci3xxx_dio_insn_bits;
700	} else {
701		s->type = COMEDI_SUBD_UNUSED;
702	}
703
704	/* EEPROM */
705	s = &dev->subdevices[6];
706	s->type = COMEDI_SUBD_UNUSED;
707
708	apci3xxx_reset(dev);
709	return 0;
710}
711
712static void apci3xxx_detach(struct comedi_device *dev)
713{
714	struct apci3xxx_private *devpriv = dev->private;
715
716	if (devpriv) {
717		if (dev->iobase)
718			apci3xxx_reset(dev);
719		if (dev->irq)
720			free_irq(dev->irq, dev);
721		if (devpriv->mmio)
722			iounmap(devpriv->mmio);
723	}
724	comedi_pci_disable(dev);
725}
726
727static struct comedi_driver apci3xxx_driver = {
728	.driver_name	= "addi_apci_3xxx",
729	.module		= THIS_MODULE,
730	.auto_attach	= apci3xxx_auto_attach,
731	.detach		= apci3xxx_detach,
732};
733
734static int apci3xxx_pci_probe(struct pci_dev *dev,
735			      const struct pci_device_id *id)
736{
737	return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
738}
739
740static DEFINE_PCI_DEVICE_TABLE(apci3xxx_pci_table) = {
741	{ PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
742	{ PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
743	{ PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
744	{ PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
745	{ PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
746	{ PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
747	{ PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
748	{ PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
749	{ PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
750	{ PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
751	{ PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
752	{ PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
753	{ PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
754	{ PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
755	{ PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
756	{ PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
757	{ PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
758	{ PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
759	{ PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
760	{ PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
761	{ PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
762	{ PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
763	{ PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
764	{ PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
765	{ PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
766	{ 0 }
767};
768MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
769
770static struct pci_driver apci3xxx_pci_driver = {
771	.name		= "addi_apci_3xxx",
772	.id_table	= apci3xxx_pci_table,
773	.probe		= apci3xxx_pci_probe,
774	.remove		= comedi_pci_auto_unconfig,
775};
776module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
777
778MODULE_AUTHOR("Comedi http://www.comedi.org");
779MODULE_DESCRIPTION("Comedi low-level driver");
780MODULE_LICENSE("GPL");
781