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