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