2 comedi/drivers/icp_multi.c
4 COMEDI - Linux Control and Measurement Device Interface
5 Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
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.
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.
20 Description: Inova ICP_MULTI
21 Author: Anne Smorthit <anne.smorthit@sfwte.ch>
22 Devices: [Inova] ICP_MULTI (icp_multi)
25 The driver works for analog input and output and digital input and output.
26 It does not work with interrupts or with the counters. Currently no support
29 It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
30 resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input
31 ranges can be individually programmed for each channel. Voltage or current
32 measurement is selected by jumper.
34 There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V
36 16 x Digital Inputs, 24V
38 8 x Digital Outputs, 24V, 1A
42 Configuration options: not applicable, uses PCI auto config
45 #include <linux/module.h>
46 #include <linux/pci.h>
47 #include <linux/delay.h>
48 #include <linux/interrupt.h>
50 #include "../comedidev.h"
52 #define PCI_DEVICE_ID_ICP_MULTI 0x8000
54 #define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */
55 #define ICP_MULTI_AI 2 /* R: Analogue input data */
56 #define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */
57 #define ICP_MULTI_AO 6 /* R/W: Analogue output data */
58 #define ICP_MULTI_DI 8 /* R/W: Digital inouts */
59 #define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */
60 #define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */
61 #define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */
62 #define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */
63 #define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */
64 #define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */
65 #define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */
67 /* Define bits from ADC command/status register */
68 #define ADC_ST 0x0001 /* Start ADC */
69 #define ADC_BSY 0x0001 /* ADC busy */
70 #define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */
71 #define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
72 #define ADC_DI 0x0040 /* Differential input mode 1 = differential */
74 /* Define bits from DAC command/status register */
75 #define DAC_ST 0x0001 /* Start DAC */
76 #define DAC_BSY 0x0001 /* DAC busy */
77 #define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */
78 #define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
80 /* Define bits from interrupt enable/status registers */
81 #define ADC_READY 0x0001 /* A/d conversion ready interrupt */
82 #define DAC_READY 0x0002 /* D/a conversion ready interrupt */
83 #define DOUT_ERROR 0x0004 /* Digital output error interrupt */
84 #define DIN_STATUS 0x0008 /* Digital input status change interrupt */
85 #define CIE0 0x0010 /* Counter 0 overrun interrupt */
86 #define CIE1 0x0020 /* Counter 1 overrun interrupt */
87 #define CIE2 0x0040 /* Counter 2 overrun interrupt */
88 #define CIE3 0x0080 /* Counter 3 overrun interrupt */
90 /* Useful definitions */
91 #define Status_IRQ 0x00ff /* All interrupts */
93 /* Define analogue range */
94 static const struct comedi_lrange range_analog = { 4, {
102 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
105 ==============================================================================
106 Data & Structure declarations
107 ==============================================================================
110 struct icp_multi_private {
111 char valid; /* card is usable */
112 void __iomem *io_addr; /* Pointer to mapped io address */
113 unsigned int AdcCmdStatus; /* ADC Command/Status register */
114 unsigned int DacCmdStatus; /* DAC Command/Status register */
115 unsigned int IntEnable; /* Interrupt Enable register */
116 unsigned int IntStatus; /* Interrupt Status register */
117 unsigned int act_chanlist[32]; /* list of scanned channel */
118 unsigned char act_chanlist_len; /* len of scanlist */
119 unsigned char act_chanlist_pos; /* actual position in MUX list */
120 unsigned int *ai_chanlist; /* actaul chanlist */
121 short *ai_data; /* data buffer */
122 short ao_data[4]; /* data output buffer */
123 short di_data; /* Digital input data */
124 unsigned int do_data; /* Remember digital output data */
127 static void setup_channel_list(struct comedi_device *dev,
128 struct comedi_subdevice *s,
129 unsigned int *chanlist, unsigned int n_chan)
131 struct icp_multi_private *devpriv = dev->private;
132 unsigned int i, range, chanprog;
135 devpriv->act_chanlist_len = n_chan;
136 devpriv->act_chanlist_pos = 0;
138 for (i = 0; i < n_chan; i++) {
140 chanprog = CR_CHAN(chanlist[i]);
142 /* Determine if it is a differential channel (Bit 15 = 1) */
143 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
151 /* Clear channel, range and input mode bits
152 * in A/D command/status register */
153 devpriv->AdcCmdStatus &= 0xf00f;
155 /* Set channel number and differential mode status bit */
157 /* Set channel number, bits 9-11 & mode, bit 6 */
158 devpriv->AdcCmdStatus |= (chanprog << 9);
159 devpriv->AdcCmdStatus |= ADC_DI;
161 /* Set channel number, bits 8-11 */
162 devpriv->AdcCmdStatus |= (chanprog << 8);
164 /* Get range for current channel */
165 range = range_codes_analog[CR_RANGE(chanlist[i])];
166 /* Set range. bits 4-5 */
167 devpriv->AdcCmdStatus |= range;
169 /* Output channel, range, mode to ICP Multi */
170 writew(devpriv->AdcCmdStatus,
171 devpriv->io_addr + ICP_MULTI_ADC_CSR);
175 static int icp_multi_insn_read_ai(struct comedi_device *dev,
176 struct comedi_subdevice *s,
177 struct comedi_insn *insn, unsigned int *data)
179 struct icp_multi_private *devpriv = dev->private;
182 /* Disable A/D conversion ready interrupt */
183 devpriv->IntEnable &= ~ADC_READY;
184 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
186 /* Clear interrupt status */
187 devpriv->IntStatus |= ADC_READY;
188 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
190 /* Set up appropriate channel, mode and range data, for specified ch */
191 setup_channel_list(dev, s, &insn->chanspec, 1);
193 for (n = 0; n < insn->n; n++) {
194 /* Set start ADC bit */
195 devpriv->AdcCmdStatus |= ADC_ST;
196 writew(devpriv->AdcCmdStatus,
197 devpriv->io_addr + ICP_MULTI_ADC_CSR);
198 devpriv->AdcCmdStatus &= ~ADC_ST;
202 /* Wait for conversion to complete, or get fed up waiting */
205 if (!(readw(devpriv->io_addr +
206 ICP_MULTI_ADC_CSR) & ADC_BSY))
212 /* If we reach here, a timeout has occurred */
213 comedi_error(dev, "A/D insn timeout");
215 /* Disable interrupt */
216 devpriv->IntEnable &= ~ADC_READY;
217 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
219 /* Clear interrupt status */
220 devpriv->IntStatus |= ADC_READY;
221 writew(devpriv->IntStatus,
222 devpriv->io_addr + ICP_MULTI_INT_STAT);
224 /* Clear data received */
231 (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
234 /* Disable interrupt */
235 devpriv->IntEnable &= ~ADC_READY;
236 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
238 /* Clear interrupt status */
239 devpriv->IntStatus |= ADC_READY;
240 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
245 static int icp_multi_insn_write_ao(struct comedi_device *dev,
246 struct comedi_subdevice *s,
247 struct comedi_insn *insn, unsigned int *data)
249 struct icp_multi_private *devpriv = dev->private;
250 int n, chan, range, timeout;
252 /* Disable D/A conversion ready interrupt */
253 devpriv->IntEnable &= ~DAC_READY;
254 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
256 /* Clear interrupt status */
257 devpriv->IntStatus |= DAC_READY;
258 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
260 /* Get channel number and range */
261 chan = CR_CHAN(insn->chanspec);
262 range = CR_RANGE(insn->chanspec);
264 /* Set up range and channel data */
265 /* Bit 4 = 1 : Bipolar */
267 /* Bit 5 = 1 : 10V */
268 /* Bits 8-9 : Channel number */
269 devpriv->DacCmdStatus &= 0xfccf;
270 devpriv->DacCmdStatus |= range_codes_analog[range];
271 devpriv->DacCmdStatus |= (chan << 8);
273 writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
275 for (n = 0; n < insn->n; n++) {
276 /* Wait for analogue output data register to be
277 * ready for new data, or get fed up waiting */
280 if (!(readw(devpriv->io_addr +
281 ICP_MULTI_DAC_CSR) & DAC_BSY))
287 /* If we reach here, a timeout has occurred */
288 comedi_error(dev, "D/A insn timeout");
290 /* Disable interrupt */
291 devpriv->IntEnable &= ~DAC_READY;
292 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
294 /* Clear interrupt status */
295 devpriv->IntStatus |= DAC_READY;
296 writew(devpriv->IntStatus,
297 devpriv->io_addr + ICP_MULTI_INT_STAT);
299 /* Clear data received */
300 devpriv->ao_data[chan] = 0;
305 /* Write data to analogue output data register */
306 writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
308 /* Set DAC_ST bit to write the data to selected channel */
309 devpriv->DacCmdStatus |= DAC_ST;
310 writew(devpriv->DacCmdStatus,
311 devpriv->io_addr + ICP_MULTI_DAC_CSR);
312 devpriv->DacCmdStatus &= ~DAC_ST;
314 /* Save analogue output data */
315 devpriv->ao_data[chan] = data[n];
321 static int icp_multi_insn_read_ao(struct comedi_device *dev,
322 struct comedi_subdevice *s,
323 struct comedi_insn *insn, unsigned int *data)
325 struct icp_multi_private *devpriv = dev->private;
328 /* Get channel number */
329 chan = CR_CHAN(insn->chanspec);
331 /* Read analogue outputs */
332 for (n = 0; n < insn->n; n++)
333 data[n] = devpriv->ao_data[chan];
338 static int icp_multi_insn_bits_di(struct comedi_device *dev,
339 struct comedi_subdevice *s,
340 struct comedi_insn *insn, unsigned int *data)
342 struct icp_multi_private *devpriv = dev->private;
344 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
349 static int icp_multi_insn_bits_do(struct comedi_device *dev,
350 struct comedi_subdevice *s,
351 struct comedi_insn *insn, unsigned int *data)
353 struct icp_multi_private *devpriv = dev->private;
356 s->state &= ~data[0];
357 s->state |= (data[0] & data[1]);
359 printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
361 writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
364 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
369 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
370 struct comedi_subdevice *s,
371 struct comedi_insn *insn, unsigned int *data)
376 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
377 struct comedi_subdevice *s,
378 struct comedi_insn *insn,
384 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
386 struct comedi_device *dev = d;
387 struct icp_multi_private *devpriv = dev->private;
390 /* Is this interrupt from our board? */
391 int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
396 /* Determine which interrupt is active & handle it */
423 static int check_channel_list(struct comedi_device *dev,
424 struct comedi_subdevice *s,
425 unsigned int *chanlist, unsigned int n_chan)
429 /* Check that we at least have one channel to check */
431 comedi_error(dev, "range/channel list is empty!");
434 /* Check all channels */
435 for (i = 0; i < n_chan; i++) {
436 /* Check that channel number is < maximum */
437 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
438 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
440 "Incorrect differential ai ch-nr");
444 if (CR_CHAN(chanlist[i]) > s->n_chan) {
446 "Incorrect ai channel number");
455 static int icp_multi_reset(struct comedi_device *dev)
457 struct icp_multi_private *devpriv = dev->private;
460 /* Clear INT enables and requests */
461 writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
462 writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
464 /* Set DACs to 0..5V range and 0V output */
465 for (i = 0; i < 4; i++) {
466 devpriv->DacCmdStatus &= 0xfcce;
468 /* Set channel number */
469 devpriv->DacCmdStatus |= (i << 8);
472 writew(0, devpriv->io_addr + ICP_MULTI_AO);
474 /* Set start conversion bit */
475 devpriv->DacCmdStatus |= DAC_ST;
477 /* Output to command / status register */
478 writew(devpriv->DacCmdStatus,
479 devpriv->io_addr + ICP_MULTI_DAC_CSR);
481 /* Delay to allow DAC time to recover */
485 /* Digital outputs to 0 */
486 writew(0, devpriv->io_addr + ICP_MULTI_DO);
491 static int icp_multi_auto_attach(struct comedi_device *dev,
492 unsigned long context_unused)
494 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
495 struct icp_multi_private *devpriv;
496 struct comedi_subdevice *s;
499 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
503 ret = comedi_pci_enable(dev);
507 devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
508 if (!devpriv->io_addr)
511 ret = comedi_alloc_subdevices(dev, 5);
515 icp_multi_reset(dev);
518 ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
519 IRQF_SHARED, dev->board_name, dev);
521 dev->irq = pcidev->irq;
524 s = &dev->subdevices[0];
525 dev->read_subdev = s;
526 s->type = COMEDI_SUBD_AI;
527 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
530 s->len_chanlist = 16;
531 s->range_table = &range_analog;
532 s->insn_read = icp_multi_insn_read_ai;
534 s = &dev->subdevices[1];
535 s->type = COMEDI_SUBD_AO;
536 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
540 s->range_table = &range_analog;
541 s->insn_write = icp_multi_insn_write_ao;
542 s->insn_read = icp_multi_insn_read_ao;
544 s = &dev->subdevices[2];
545 s->type = COMEDI_SUBD_DI;
546 s->subdev_flags = SDF_READABLE;
549 s->len_chanlist = 16;
550 s->range_table = &range_digital;
552 s->insn_bits = icp_multi_insn_bits_di;
554 s = &dev->subdevices[3];
555 s->type = COMEDI_SUBD_DO;
556 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
560 s->range_table = &range_digital;
563 s->insn_bits = icp_multi_insn_bits_do;
565 s = &dev->subdevices[4];
566 s->type = COMEDI_SUBD_COUNTER;
567 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
572 s->insn_read = icp_multi_insn_read_ctr;
573 s->insn_write = icp_multi_insn_write_ctr;
577 dev_info(dev->class_dev, "%s attached, irq %sabled\n",
578 dev->board_name, dev->irq ? "en" : "dis");
583 static void icp_multi_detach(struct comedi_device *dev)
585 struct icp_multi_private *devpriv = dev->private;
589 icp_multi_reset(dev);
591 free_irq(dev->irq, dev);
592 if (devpriv && devpriv->io_addr)
593 iounmap(devpriv->io_addr);
594 comedi_pci_disable(dev);
597 static struct comedi_driver icp_multi_driver = {
598 .driver_name = "icp_multi",
599 .module = THIS_MODULE,
600 .auto_attach = icp_multi_auto_attach,
601 .detach = icp_multi_detach,
604 static int icp_multi_pci_probe(struct pci_dev *dev,
605 const struct pci_device_id *id)
607 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
610 static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
611 { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
614 MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
616 static struct pci_driver icp_multi_pci_driver = {
618 .id_table = icp_multi_pci_table,
619 .probe = icp_multi_pci_probe,
620 .remove = comedi_pci_auto_unconfig,
622 module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
624 MODULE_AUTHOR("Comedi http://www.comedi.org");
625 MODULE_DESCRIPTION("Comedi low-level driver");
626 MODULE_LICENSE("GPL");