icp_multi.c revision 194b9e3cf49319231e8b47125dc5d043350f4406
1/*
2    comedi/drivers/icp_multi.c
3
4    COMEDI - Linux Control and Measurement Device Interface
5    Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21*/
22
23/*
24Driver: icp_multi
25Description: Inova ICP_MULTI
26Author: Anne Smorthit <anne.smorthit@sfwte.ch>
27Devices: [Inova] ICP_MULTI (icp_multi)
28Status: works
29
30The driver works for analog input and output and digital input and output.
31It does not work with interrupts or with the counters.  Currently no support
32for DMA.
33
34It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
35resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
36ranges can be individually programmed for each channel.  Voltage or current
37measurement is selected by jumper.
38
39There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
40
4116 x Digital Inputs, 24V
42
438 x Digital Outputs, 24V, 1A
44
454 x 16-bit counters
46
47Configuration options: not applicable, uses PCI auto config
48*/
49
50#include <linux/interrupt.h>
51#include "../comedidev.h"
52
53#include <linux/delay.h>
54#include <linux/pci.h>
55
56#define PCI_DEVICE_ID_ICP_MULTI	0x8000
57
58#define ICP_MULTI_ADC_CSR	0	/* R/W: ADC command/status register */
59#define ICP_MULTI_AI		2	/* R:   Analogue input data */
60#define ICP_MULTI_DAC_CSR	4	/* R/W: DAC command/status register */
61#define ICP_MULTI_AO		6	/* R/W: Analogue output data */
62#define ICP_MULTI_DI		8	/* R/W: Digital inouts */
63#define ICP_MULTI_DO		0x0A	/* R/W: Digital outputs */
64#define ICP_MULTI_INT_EN	0x0C	/* R/W: Interrupt enable register */
65#define ICP_MULTI_INT_STAT	0x0E	/* R/W: Interrupt status register */
66#define ICP_MULTI_CNTR0		0x10	/* R/W: Counter 0 */
67#define ICP_MULTI_CNTR1		0x12	/* R/W: counter 1 */
68#define ICP_MULTI_CNTR2		0x14	/* R/W: Counter 2 */
69#define ICP_MULTI_CNTR3		0x16	/* R/W: Counter 3 */
70
71#define ICP_MULTI_SIZE		0x20	/* 32 bytes */
72
73/*  Define bits from ADC command/status register */
74#define	ADC_ST		0x0001	/* Start ADC */
75#define	ADC_BSY		0x0001	/* ADC busy */
76#define ADC_BI		0x0010	/* Bipolar input range 1 = bipolar */
77#define ADC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
78#define	ADC_DI		0x0040	/* Differential input mode 1 = differential */
79
80/*  Define bits from DAC command/status register */
81#define	DAC_ST		0x0001	/* Start DAC */
82#define DAC_BSY		0x0001	/* DAC busy */
83#define	DAC_BI		0x0010	/* Bipolar input range 1 = bipolar */
84#define	DAC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
85
86/*  Define bits from interrupt enable/status registers */
87#define	ADC_READY	0x0001	/* A/d conversion ready interrupt */
88#define	DAC_READY	0x0002	/* D/a conversion ready interrupt */
89#define	DOUT_ERROR	0x0004	/* Digital output error interrupt */
90#define	DIN_STATUS	0x0008	/* Digital input status change interrupt */
91#define	CIE0		0x0010	/* Counter 0 overrun interrupt */
92#define	CIE1		0x0020	/* Counter 1 overrun interrupt */
93#define	CIE2		0x0040	/* Counter 2 overrun interrupt */
94#define	CIE3		0x0080	/* Counter 3 overrun interrupt */
95
96/*  Useful definitions */
97#define	Status_IRQ	0x00ff	/*  All interrupts */
98
99/*  Define analogue range */
100static const struct comedi_lrange range_analog = { 4, {
101						       UNI_RANGE(5),
102						       UNI_RANGE(10),
103						       BIP_RANGE(5),
104						       BIP_RANGE(10)
105						       }
106};
107
108static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
109
110/*
111==============================================================================
112	Data & Structure declarations
113==============================================================================
114*/
115
116struct icp_multi_private {
117	char valid;		/*  card is usable */
118	void __iomem *io_addr;		/*  Pointer to mapped io address */
119	unsigned int AdcCmdStatus;	/*  ADC Command/Status register */
120	unsigned int DacCmdStatus;	/*  DAC Command/Status register */
121	unsigned int IntEnable;	/*  Interrupt Enable register */
122	unsigned int IntStatus;	/*  Interrupt Status register */
123	unsigned int act_chanlist[32];	/*  list of scaned channel */
124	unsigned char act_chanlist_len;	/*  len of scanlist */
125	unsigned char act_chanlist_pos;	/*  actual position in MUX list */
126	unsigned int *ai_chanlist;	/*  actaul chanlist */
127	short *ai_data;		/*  data buffer */
128	short ao_data[4];	/*  data output buffer */
129	short di_data;		/*  Digital input data */
130	unsigned int do_data;	/*  Remember digital output data */
131};
132
133#define devpriv ((struct icp_multi_private *)dev->private)
134
135/*
136==============================================================================
137
138Name:	setup_channel_list
139
140Description:
141	This function sets the appropriate channel selection,
142	differential input mode and range bits in the ADC Command/
143	Status register.
144
145Parameters:
146	struct comedi_device *dev	Pointer to current service structure
147	struct comedi_subdevice *s	Pointer to current subdevice structure
148	unsigned int *chanlist	Pointer to packed channel list
149	unsigned int n_chan	Number of channels to scan
150
151Returns:Void
152
153==============================================================================
154*/
155static void setup_channel_list(struct comedi_device *dev,
156			       struct comedi_subdevice *s,
157			       unsigned int *chanlist, unsigned int n_chan)
158{
159	unsigned int i, range, chanprog;
160	unsigned int diff;
161
162	devpriv->act_chanlist_len = n_chan;
163	devpriv->act_chanlist_pos = 0;
164
165	for (i = 0; i < n_chan; i++) {
166		/*  Get channel */
167		chanprog = CR_CHAN(chanlist[i]);
168
169		/*  Determine if it is a differential channel (Bit 15  = 1) */
170		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
171			diff = 1;
172			chanprog &= 0x0007;
173		} else {
174			diff = 0;
175			chanprog &= 0x000f;
176		}
177
178		/*  Clear channel, range and input mode bits
179		 *  in A/D command/status register */
180		devpriv->AdcCmdStatus &= 0xf00f;
181
182		/*  Set channel number and differential mode status bit */
183		if (diff) {
184			/*  Set channel number, bits 9-11 & mode, bit 6 */
185			devpriv->AdcCmdStatus |= (chanprog << 9);
186			devpriv->AdcCmdStatus |= ADC_DI;
187		} else
188			/*  Set channel number, bits 8-11 */
189			devpriv->AdcCmdStatus |= (chanprog << 8);
190
191		/*  Get range for current channel */
192		range = range_codes_analog[CR_RANGE(chanlist[i])];
193		/*  Set range. bits 4-5 */
194		devpriv->AdcCmdStatus |= range;
195
196		/* Output channel, range, mode to ICP Multi */
197		writew(devpriv->AdcCmdStatus,
198		       devpriv->io_addr + ICP_MULTI_ADC_CSR);
199	}
200}
201
202/*
203==============================================================================
204
205Name:	icp_multi_insn_read_ai
206
207Description:
208	This function reads a single analogue input.
209
210Parameters:
211	struct comedi_device *dev	Pointer to current device structure
212	struct comedi_subdevice *s	Pointer to current subdevice structure
213	struct comedi_insn *insn	Pointer to current comedi instruction
214	unsigned int *data		Pointer to analogue input data
215
216Returns:int			Nmuber of instructions executed
217
218==============================================================================
219*/
220static int icp_multi_insn_read_ai(struct comedi_device *dev,
221				  struct comedi_subdevice *s,
222				  struct comedi_insn *insn, unsigned int *data)
223{
224	int n, timeout;
225
226	/*  Disable A/D conversion ready interrupt */
227	devpriv->IntEnable &= ~ADC_READY;
228	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
229
230	/*  Clear interrupt status */
231	devpriv->IntStatus |= ADC_READY;
232	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
233
234	/*  Set up appropriate channel, mode and range data, for specified ch */
235	setup_channel_list(dev, s, &insn->chanspec, 1);
236
237	for (n = 0; n < insn->n; n++) {
238		/*  Set start ADC bit */
239		devpriv->AdcCmdStatus |= ADC_ST;
240		writew(devpriv->AdcCmdStatus,
241		       devpriv->io_addr + ICP_MULTI_ADC_CSR);
242		devpriv->AdcCmdStatus &= ~ADC_ST;
243
244		udelay(1);
245
246		/*  Wait for conversion to complete, or get fed up waiting */
247		timeout = 100;
248		while (timeout--) {
249			if (!(readw(devpriv->io_addr +
250				    ICP_MULTI_ADC_CSR) & ADC_BSY))
251				goto conv_finish;
252
253			udelay(1);
254		}
255
256		/*  If we reach here, a timeout has occurred */
257		comedi_error(dev, "A/D insn timeout");
258
259		/*  Disable interrupt */
260		devpriv->IntEnable &= ~ADC_READY;
261		writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
262
263		/*  Clear interrupt status */
264		devpriv->IntStatus |= ADC_READY;
265		writew(devpriv->IntStatus,
266		       devpriv->io_addr + ICP_MULTI_INT_STAT);
267
268		/*  Clear data received */
269		data[n] = 0;
270
271		return -ETIME;
272
273conv_finish:
274		data[n] =
275		    (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
276	}
277
278	/*  Disable interrupt */
279	devpriv->IntEnable &= ~ADC_READY;
280	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
281
282	/*  Clear interrupt status */
283	devpriv->IntStatus |= ADC_READY;
284	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
285
286	return n;
287}
288
289/*
290==============================================================================
291
292Name:	icp_multi_insn_write_ao
293
294Description:
295	This function writes a single analogue output.
296
297Parameters:
298	struct comedi_device *dev	Pointer to current device structure
299	struct comedi_subdevice *s	Pointer to current subdevice structure
300	struct comedi_insn *insn	Pointer to current comedi instruction
301	unsigned int *data		Pointer to analogue output data
302
303Returns:int			Nmuber of instructions executed
304
305==============================================================================
306*/
307static int icp_multi_insn_write_ao(struct comedi_device *dev,
308				   struct comedi_subdevice *s,
309				   struct comedi_insn *insn, unsigned int *data)
310{
311	int n, chan, range, timeout;
312
313	/*  Disable D/A conversion ready interrupt */
314	devpriv->IntEnable &= ~DAC_READY;
315	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
316
317	/*  Clear interrupt status */
318	devpriv->IntStatus |= DAC_READY;
319	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
320
321	/*  Get channel number and range */
322	chan = CR_CHAN(insn->chanspec);
323	range = CR_RANGE(insn->chanspec);
324
325	/*  Set up range and channel data */
326	/*  Bit 4 = 1 : Bipolar */
327	/*  Bit 5 = 0 : 5V */
328	/*  Bit 5 = 1 : 10V */
329	/*  Bits 8-9 : Channel number */
330	devpriv->DacCmdStatus &= 0xfccf;
331	devpriv->DacCmdStatus |= range_codes_analog[range];
332	devpriv->DacCmdStatus |= (chan << 8);
333
334	writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
335
336	for (n = 0; n < insn->n; n++) {
337		/*  Wait for analogue output data register to be
338		 *  ready for new data, or get fed up waiting */
339		timeout = 100;
340		while (timeout--) {
341			if (!(readw(devpriv->io_addr +
342				    ICP_MULTI_DAC_CSR) & DAC_BSY))
343				goto dac_ready;
344
345			udelay(1);
346		}
347
348		/*  If we reach here, a timeout has occurred */
349		comedi_error(dev, "D/A insn timeout");
350
351		/*  Disable interrupt */
352		devpriv->IntEnable &= ~DAC_READY;
353		writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
354
355		/*  Clear interrupt status */
356		devpriv->IntStatus |= DAC_READY;
357		writew(devpriv->IntStatus,
358		       devpriv->io_addr + ICP_MULTI_INT_STAT);
359
360		/*  Clear data received */
361		devpriv->ao_data[chan] = 0;
362
363		return -ETIME;
364
365dac_ready:
366		/*  Write data to analogue output data register */
367		writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
368
369		/*  Set DAC_ST bit to write the data to selected channel */
370		devpriv->DacCmdStatus |= DAC_ST;
371		writew(devpriv->DacCmdStatus,
372		       devpriv->io_addr + ICP_MULTI_DAC_CSR);
373		devpriv->DacCmdStatus &= ~DAC_ST;
374
375		/*  Save analogue output data */
376		devpriv->ao_data[chan] = data[n];
377	}
378
379	return n;
380}
381
382/*
383==============================================================================
384
385Name:	icp_multi_insn_read_ao
386
387Description:
388	This function reads a single analogue output.
389
390Parameters:
391	struct comedi_device *dev	Pointer to current device structure
392	struct comedi_subdevice *s	Pointer to current subdevice structure
393	struct comedi_insn *insn	Pointer to current comedi instruction
394	unsigned int *data		Pointer to analogue output data
395
396Returns:int			Nmuber of instructions executed
397
398==============================================================================
399*/
400static int icp_multi_insn_read_ao(struct comedi_device *dev,
401				  struct comedi_subdevice *s,
402				  struct comedi_insn *insn, unsigned int *data)
403{
404	int n, chan;
405
406	/*  Get channel number */
407	chan = CR_CHAN(insn->chanspec);
408
409	/*  Read analogue outputs */
410	for (n = 0; n < insn->n; n++)
411		data[n] = devpriv->ao_data[chan];
412
413	return n;
414}
415
416/*
417==============================================================================
418
419Name:	icp_multi_insn_bits_di
420
421Description:
422	This function reads the digital inputs.
423
424Parameters:
425	struct comedi_device *dev	Pointer to current device structure
426	struct comedi_subdevice *s	Pointer to current subdevice structure
427	struct comedi_insn *insn	Pointer to current comedi instruction
428	unsigned int *data		Pointer to analogue output data
429
430Returns:int			Nmuber of instructions executed
431
432==============================================================================
433*/
434static int icp_multi_insn_bits_di(struct comedi_device *dev,
435				  struct comedi_subdevice *s,
436				  struct comedi_insn *insn, unsigned int *data)
437{
438	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
439
440	return insn->n;
441}
442
443/*
444==============================================================================
445
446Name:	icp_multi_insn_bits_do
447
448Description:
449	This function writes the appropriate digital outputs.
450
451Parameters:
452	struct comedi_device *dev	Pointer to current device structure
453	struct comedi_subdevice *s	Pointer to current subdevice structure
454	struct comedi_insn *insn	Pointer to current comedi instruction
455	unsigned int *data		Pointer to analogue output data
456
457Returns:int			Nmuber of instructions executed
458
459==============================================================================
460*/
461static int icp_multi_insn_bits_do(struct comedi_device *dev,
462				  struct comedi_subdevice *s,
463				  struct comedi_insn *insn, unsigned int *data)
464{
465	if (data[0]) {
466		s->state &= ~data[0];
467		s->state |= (data[0] & data[1]);
468
469		printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
470
471		writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
472	}
473
474	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
475
476	return insn->n;
477}
478
479/*
480==============================================================================
481
482Name:	icp_multi_insn_read_ctr
483
484Description:
485	This function reads the specified counter.
486
487Parameters:
488	struct comedi_device *dev	Pointer to current device structure
489	struct comedi_subdevice *s	Pointer to current subdevice structure
490	struct comedi_insn *insn	Pointer to current comedi instruction
491	unsigned int *data		Pointer to counter data
492
493Returns:int			Nmuber of instructions executed
494
495==============================================================================
496*/
497static int icp_multi_insn_read_ctr(struct comedi_device *dev,
498				   struct comedi_subdevice *s,
499				   struct comedi_insn *insn, unsigned int *data)
500{
501	return 0;
502}
503
504/*
505==============================================================================
506
507Name:	icp_multi_insn_write_ctr
508
509Description:
510	This function write to the specified counter.
511
512Parameters:
513	struct comedi_device *dev	Pointer to current device structure
514	struct comedi_subdevice *s	Pointer to current subdevice structure
515	struct comedi_insn *insn	Pointer to current comedi instruction
516	unsigned int *data		Pointer to counter data
517
518Returns:int			Nmuber of instructions executed
519
520==============================================================================
521*/
522static int icp_multi_insn_write_ctr(struct comedi_device *dev,
523				    struct comedi_subdevice *s,
524				    struct comedi_insn *insn,
525				    unsigned int *data)
526{
527	return 0;
528}
529
530/*
531==============================================================================
532
533Name:	interrupt_service_icp_multi
534
535Description:
536	This function is the interrupt service routine for all
537	interrupts generated by the icp multi board.
538
539Parameters:
540	int irq
541	void *d			Pointer to current device
542
543==============================================================================
544*/
545static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
546{
547	struct comedi_device *dev = d;
548	int int_no;
549
550	/*  Is this interrupt from our board? */
551	int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
552	if (!int_no)
553		/*  No, exit */
554		return IRQ_NONE;
555
556	/*  Determine which interrupt is active & handle it */
557	switch (int_no) {
558	case ADC_READY:
559		break;
560	case DAC_READY:
561		break;
562	case DOUT_ERROR:
563		break;
564	case DIN_STATUS:
565		break;
566	case CIE0:
567		break;
568	case CIE1:
569		break;
570	case CIE2:
571		break;
572	case CIE3:
573		break;
574	default:
575		break;
576
577	}
578
579	return IRQ_HANDLED;
580}
581
582#if 0
583/*
584==============================================================================
585
586Name:	check_channel_list
587
588Description:
589	This function checks if the channel list, provided by user
590	is built correctly
591
592Parameters:
593	struct comedi_device *dev	Pointer to current service structure
594	struct comedi_subdevice *s	Pointer to current subdevice structure
595	unsigned int *chanlist	Pointer to packed channel list
596	unsigned int n_chan	Number of channels to scan
597
598Returns:int 0 = failure
599	    1 = success
600
601==============================================================================
602*/
603static int check_channel_list(struct comedi_device *dev,
604			      struct comedi_subdevice *s,
605			      unsigned int *chanlist, unsigned int n_chan)
606{
607	unsigned int i;
608
609	/*  Check that we at least have one channel to check */
610	if (n_chan < 1) {
611		comedi_error(dev, "range/channel list is empty!");
612		return 0;
613	}
614	/*  Check all channels */
615	for (i = 0; i < n_chan; i++) {
616		/*  Check that channel number is < maximum */
617		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
618			if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
619				comedi_error(dev,
620					     "Incorrect differential ai ch-nr");
621				return 0;
622			}
623		} else {
624			if (CR_CHAN(chanlist[i]) > s->n_chan) {
625				comedi_error(dev,
626					     "Incorrect ai channel number");
627				return 0;
628			}
629		}
630	}
631	return 1;
632}
633#endif
634
635/*
636==============================================================================
637
638Name:	icp_multi_reset
639
640Description:
641	This function resets the icp multi device to a 'safe' state
642
643Parameters:
644	struct comedi_device *dev	Pointer to current service structure
645
646Returns:int	0 = success
647
648==============================================================================
649*/
650static int icp_multi_reset(struct comedi_device *dev)
651{
652	unsigned int i;
653
654	/*  Clear INT enables and requests */
655	writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
656	writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
657
658	/* Set DACs to 0..5V range and 0V output */
659	for (i = 0; i < 4; i++) {
660		devpriv->DacCmdStatus &= 0xfcce;
661
662		/*  Set channel number */
663		devpriv->DacCmdStatus |= (i << 8);
664
665		/*  Output 0V */
666		writew(0, devpriv->io_addr + ICP_MULTI_AO);
667
668		/*  Set start conversion bit */
669		devpriv->DacCmdStatus |= DAC_ST;
670
671		/*  Output to command / status register */
672		writew(devpriv->DacCmdStatus,
673			devpriv->io_addr + ICP_MULTI_DAC_CSR);
674
675		/*  Delay to allow DAC time to recover */
676		udelay(1);
677	}
678
679	/* Digital outputs to 0 */
680	writew(0, devpriv->io_addr + ICP_MULTI_DO);
681
682	return 0;
683}
684
685static int icp_multi_attach_pci(struct comedi_device *dev,
686				struct pci_dev *pcidev)
687{
688	struct comedi_subdevice *s;
689	resource_size_t iobase;
690	int ret;
691
692	comedi_set_hw_dev(dev, &pcidev->dev);
693	dev->board_name = dev->driver->driver_name;
694
695	ret = alloc_private(dev, sizeof(struct icp_multi_private));
696	if (ret < 0)
697		return ret;
698
699	ret = comedi_pci_enable(pcidev, dev->board_name);
700	if (ret)
701		return ret;
702	iobase = pci_resource_start(pcidev, 2);
703	dev->iobase = iobase;
704
705	devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE);
706	if (!devpriv->io_addr)
707		return -ENOMEM;
708
709	ret = comedi_alloc_subdevices(dev, 5);
710	if (ret)
711		return ret;
712
713	icp_multi_reset(dev);
714
715	if (pcidev->irq) {
716		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
717				  IRQF_SHARED, dev->board_name, dev);
718		if (ret == 0)
719			dev->irq = pcidev->irq;
720	}
721
722	s = &dev->subdevices[0];
723	dev->read_subdev = s;
724	s->type = COMEDI_SUBD_AI;
725	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
726	s->n_chan = 16;
727	s->maxdata = 0x0fff;
728	s->len_chanlist = 16;
729	s->range_table = &range_analog;
730	s->insn_read = icp_multi_insn_read_ai;
731
732	s = &dev->subdevices[1];
733	s->type = COMEDI_SUBD_AO;
734	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
735	s->n_chan = 4;
736	s->maxdata = 0x0fff;
737	s->len_chanlist = 4;
738	s->range_table = &range_analog;
739	s->insn_write = icp_multi_insn_write_ao;
740	s->insn_read = icp_multi_insn_read_ao;
741
742	s = &dev->subdevices[2];
743	s->type = COMEDI_SUBD_DI;
744	s->subdev_flags = SDF_READABLE;
745	s->n_chan = 16;
746	s->maxdata = 1;
747	s->len_chanlist = 16;
748	s->range_table = &range_digital;
749	s->io_bits = 0;
750	s->insn_bits = icp_multi_insn_bits_di;
751
752	s = &dev->subdevices[3];
753	s->type = COMEDI_SUBD_DO;
754	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
755	s->n_chan = 8;
756	s->maxdata = 1;
757	s->len_chanlist = 8;
758	s->range_table = &range_digital;
759	s->io_bits = 0xff;
760	s->state = 0;
761	s->insn_bits = icp_multi_insn_bits_do;
762
763	s = &dev->subdevices[4];
764	s->type = COMEDI_SUBD_COUNTER;
765	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
766	s->n_chan = 4;
767	s->maxdata = 0xffff;
768	s->len_chanlist = 4;
769	s->state = 0;
770	s->insn_read = icp_multi_insn_read_ctr;
771	s->insn_write = icp_multi_insn_write_ctr;
772
773	devpriv->valid = 1;
774
775	dev_info(dev->class_dev, "%s attached, irq %sabled\n",
776		dev->board_name, dev->irq ? "en" : "dis");
777
778	return 0;
779}
780
781static void icp_multi_detach(struct comedi_device *dev)
782{
783	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
784
785	if (dev->private)
786		if (devpriv->valid)
787			icp_multi_reset(dev);
788	if (dev->irq)
789		free_irq(dev->irq, dev);
790	if (dev->private && devpriv->io_addr)
791		iounmap(devpriv->io_addr);
792	if (pcidev) {
793		if (dev->iobase)
794			comedi_pci_disable(pcidev);
795	}
796}
797
798static struct comedi_driver icp_multi_driver = {
799	.driver_name	= "icp_multi",
800	.module		= THIS_MODULE,
801	.attach_pci	= icp_multi_attach_pci,
802	.detach		= icp_multi_detach,
803};
804
805static int __devinit icp_multi_pci_probe(struct pci_dev *dev,
806					   const struct pci_device_id *ent)
807{
808	return comedi_pci_auto_config(dev, &icp_multi_driver);
809}
810
811static void __devexit icp_multi_pci_remove(struct pci_dev *dev)
812{
813	comedi_pci_auto_unconfig(dev);
814}
815
816static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
817	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
818	{ 0 }
819};
820MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
821
822static struct pci_driver icp_multi_pci_driver = {
823	.name		= "icp_multi",
824	.id_table	= icp_multi_pci_table,
825	.probe		= icp_multi_pci_probe,
826	.remove		= __devexit_p(icp_multi_pci_remove),
827};
828module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
829
830MODULE_AUTHOR("Comedi http://www.comedi.org");
831MODULE_DESCRIPTION("Comedi low-level driver");
832MODULE_LICENSE("GPL");
833