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 ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */
53 #define ICP_MULTI_AI 2 /* R: Analogue input data */
54 #define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */
55 #define ICP_MULTI_AO 6 /* R/W: Analogue output data */
56 #define ICP_MULTI_DI 8 /* R/W: Digital inouts */
57 #define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */
58 #define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */
59 #define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */
60 #define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */
61 #define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */
62 #define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */
63 #define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */
65 /* Define bits from ADC command/status register */
66 #define ADC_ST 0x0001 /* Start ADC */
67 #define ADC_BSY 0x0001 /* ADC busy */
68 #define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */
69 #define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
70 #define ADC_DI 0x0040 /* Differential input mode 1 = differential */
72 /* Define bits from DAC command/status register */
73 #define DAC_ST 0x0001 /* Start DAC */
74 #define DAC_BSY 0x0001 /* DAC busy */
75 #define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */
76 #define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
78 /* Define bits from interrupt enable/status registers */
79 #define ADC_READY 0x0001 /* A/d conversion ready interrupt */
80 #define DAC_READY 0x0002 /* D/a conversion ready interrupt */
81 #define DOUT_ERROR 0x0004 /* Digital output error interrupt */
82 #define DIN_STATUS 0x0008 /* Digital input status change interrupt */
83 #define CIE0 0x0010 /* Counter 0 overrun interrupt */
84 #define CIE1 0x0020 /* Counter 1 overrun interrupt */
85 #define CIE2 0x0040 /* Counter 2 overrun interrupt */
86 #define CIE3 0x0080 /* Counter 3 overrun interrupt */
88 /* Useful definitions */
89 #define Status_IRQ 0x00ff /* All interrupts */
91 /* Define analogue range */
92 static const struct comedi_lrange range_analog = {
101 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
104 ==============================================================================
105 Data & Structure declarations
106 ==============================================================================
109 struct icp_multi_private {
110 char valid; /* card is usable */
111 unsigned int AdcCmdStatus; /* ADC Command/Status register */
112 unsigned int DacCmdStatus; /* DAC Command/Status register */
113 unsigned int IntEnable; /* Interrupt Enable register */
114 unsigned int IntStatus; /* Interrupt Status register */
115 unsigned int act_chanlist[32]; /* list of scanned channel */
116 unsigned char act_chanlist_len; /* len of scanlist */
117 unsigned char act_chanlist_pos; /* actual position in MUX list */
118 unsigned int *ai_chanlist; /* actaul chanlist */
119 unsigned short ao_data[4]; /* data output buffer */
120 unsigned int do_data; /* Remember digital output data */
123 static void setup_channel_list(struct comedi_device *dev,
124 struct comedi_subdevice *s,
125 unsigned int *chanlist, unsigned int n_chan)
127 struct icp_multi_private *devpriv = dev->private;
128 unsigned int i, range, chanprog;
131 devpriv->act_chanlist_len = n_chan;
132 devpriv->act_chanlist_pos = 0;
134 for (i = 0; i < n_chan; i++) {
136 chanprog = CR_CHAN(chanlist[i]);
138 /* Determine if it is a differential channel (Bit 15 = 1) */
139 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
147 /* Clear channel, range and input mode bits
148 * in A/D command/status register */
149 devpriv->AdcCmdStatus &= 0xf00f;
151 /* Set channel number and differential mode status bit */
153 /* Set channel number, bits 9-11 & mode, bit 6 */
154 devpriv->AdcCmdStatus |= (chanprog << 9);
155 devpriv->AdcCmdStatus |= ADC_DI;
157 /* Set channel number, bits 8-11 */
158 devpriv->AdcCmdStatus |= (chanprog << 8);
160 /* Get range for current channel */
161 range = range_codes_analog[CR_RANGE(chanlist[i])];
162 /* Set range. bits 4-5 */
163 devpriv->AdcCmdStatus |= range;
165 /* Output channel, range, mode to ICP Multi */
166 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
170 static int icp_multi_ai_eoc(struct comedi_device *dev,
171 struct comedi_subdevice *s,
172 struct comedi_insn *insn,
173 unsigned long context)
177 status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
178 if ((status & ADC_BSY) == 0)
183 static int icp_multi_insn_read_ai(struct comedi_device *dev,
184 struct comedi_subdevice *s,
185 struct comedi_insn *insn,
188 struct icp_multi_private *devpriv = dev->private;
192 /* Disable A/D conversion ready interrupt */
193 devpriv->IntEnable &= ~ADC_READY;
194 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
196 /* Clear interrupt status */
197 devpriv->IntStatus |= ADC_READY;
198 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
200 /* Set up appropriate channel, mode and range data, for specified ch */
201 setup_channel_list(dev, s, &insn->chanspec, 1);
203 for (n = 0; n < insn->n; n++) {
204 /* Set start ADC bit */
205 devpriv->AdcCmdStatus |= ADC_ST;
206 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
207 devpriv->AdcCmdStatus &= ~ADC_ST;
211 /* Wait for conversion to complete, or get fed up waiting */
212 ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
216 data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
219 /* Disable interrupt */
220 devpriv->IntEnable &= ~ADC_READY;
221 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
223 /* Clear interrupt status */
224 devpriv->IntStatus |= ADC_READY;
225 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
227 return ret ? ret : n;
230 static int icp_multi_ao_eoc(struct comedi_device *dev,
231 struct comedi_subdevice *s,
232 struct comedi_insn *insn,
233 unsigned long context)
237 status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
238 if ((status & DAC_BSY) == 0)
243 static int icp_multi_insn_write_ao(struct comedi_device *dev,
244 struct comedi_subdevice *s,
245 struct comedi_insn *insn,
248 struct icp_multi_private *devpriv = dev->private;
252 /* Disable D/A conversion ready interrupt */
253 devpriv->IntEnable &= ~DAC_READY;
254 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
256 /* Clear interrupt status */
257 devpriv->IntStatus |= DAC_READY;
258 writew(devpriv->IntStatus, dev->mmio + 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, dev->mmio + 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 */
278 ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
280 /* Disable interrupt */
281 devpriv->IntEnable &= ~DAC_READY;
282 writew(devpriv->IntEnable,
283 dev->mmio + ICP_MULTI_INT_EN);
285 /* Clear interrupt status */
286 devpriv->IntStatus |= DAC_READY;
287 writew(devpriv->IntStatus,
288 dev->mmio + ICP_MULTI_INT_STAT);
290 /* Clear data received */
291 devpriv->ao_data[chan] = 0;
296 /* Write data to analogue output data register */
297 writew(data[n], dev->mmio + ICP_MULTI_AO);
299 /* Set DAC_ST bit to write the data to selected channel */
300 devpriv->DacCmdStatus |= DAC_ST;
301 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
302 devpriv->DacCmdStatus &= ~DAC_ST;
304 /* Save analogue output data */
305 devpriv->ao_data[chan] = data[n];
311 static int icp_multi_insn_read_ao(struct comedi_device *dev,
312 struct comedi_subdevice *s,
313 struct comedi_insn *insn, unsigned int *data)
315 struct icp_multi_private *devpriv = dev->private;
318 /* Get channel number */
319 chan = CR_CHAN(insn->chanspec);
321 /* Read analogue outputs */
322 for (n = 0; n < insn->n; n++)
323 data[n] = devpriv->ao_data[chan];
328 static int icp_multi_insn_bits_di(struct comedi_device *dev,
329 struct comedi_subdevice *s,
330 struct comedi_insn *insn,
333 data[1] = readw(dev->mmio + ICP_MULTI_DI);
338 static int icp_multi_insn_bits_do(struct comedi_device *dev,
339 struct comedi_subdevice *s,
340 struct comedi_insn *insn,
343 if (comedi_dio_update_state(s, data))
344 writew(s->state, dev->mmio + ICP_MULTI_DO);
346 data[1] = readw(dev->mmio + ICP_MULTI_DI);
351 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
352 struct comedi_subdevice *s,
353 struct comedi_insn *insn, unsigned int *data)
358 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
359 struct comedi_subdevice *s,
360 struct comedi_insn *insn,
366 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
368 struct comedi_device *dev = d;
371 /* Is this interrupt from our board? */
372 int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ;
377 /* Determine which interrupt is active & handle it */
404 static int check_channel_list(struct comedi_device *dev,
405 struct comedi_subdevice *s,
406 unsigned int *chanlist, unsigned int n_chan)
410 /* Check that we at least have one channel to check */
412 dev_err(dev->class_dev, "range/channel list is empty!\n");
415 /* Check all channels */
416 for (i = 0; i < n_chan; i++) {
417 /* Check that channel number is < maximum */
418 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
419 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
420 dev_err(dev->class_dev,
421 "Incorrect differential ai ch-nr\n");
425 if (CR_CHAN(chanlist[i]) > s->n_chan) {
426 dev_err(dev->class_dev,
427 "Incorrect ai channel number\n");
436 static int icp_multi_reset(struct comedi_device *dev)
438 struct icp_multi_private *devpriv = dev->private;
441 /* Clear INT enables and requests */
442 writew(0, dev->mmio + ICP_MULTI_INT_EN);
443 writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT);
445 /* Set DACs to 0..5V range and 0V output */
446 for (i = 0; i < 4; i++) {
447 devpriv->DacCmdStatus &= 0xfcce;
449 /* Set channel number */
450 devpriv->DacCmdStatus |= (i << 8);
453 writew(0, dev->mmio + ICP_MULTI_AO);
455 /* Set start conversion bit */
456 devpriv->DacCmdStatus |= DAC_ST;
458 /* Output to command / status register */
459 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
461 /* Delay to allow DAC time to recover */
465 /* Digital outputs to 0 */
466 writew(0, dev->mmio + ICP_MULTI_DO);
471 static int icp_multi_auto_attach(struct comedi_device *dev,
472 unsigned long context_unused)
474 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
475 struct icp_multi_private *devpriv;
476 struct comedi_subdevice *s;
479 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
483 ret = comedi_pci_enable(dev);
487 dev->mmio = pci_ioremap_bar(pcidev, 2);
491 ret = comedi_alloc_subdevices(dev, 5);
495 icp_multi_reset(dev);
498 ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
499 IRQF_SHARED, dev->board_name, dev);
501 dev->irq = pcidev->irq;
504 s = &dev->subdevices[0];
505 dev->read_subdev = s;
506 s->type = COMEDI_SUBD_AI;
507 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
510 s->len_chanlist = 16;
511 s->range_table = &range_analog;
512 s->insn_read = icp_multi_insn_read_ai;
514 s = &dev->subdevices[1];
515 s->type = COMEDI_SUBD_AO;
516 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
520 s->range_table = &range_analog;
521 s->insn_write = icp_multi_insn_write_ao;
522 s->insn_read = icp_multi_insn_read_ao;
524 s = &dev->subdevices[2];
525 s->type = COMEDI_SUBD_DI;
526 s->subdev_flags = SDF_READABLE;
529 s->len_chanlist = 16;
530 s->range_table = &range_digital;
531 s->insn_bits = icp_multi_insn_bits_di;
533 s = &dev->subdevices[3];
534 s->type = COMEDI_SUBD_DO;
535 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
539 s->range_table = &range_digital;
540 s->insn_bits = icp_multi_insn_bits_do;
542 s = &dev->subdevices[4];
543 s->type = COMEDI_SUBD_COUNTER;
544 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
549 s->insn_read = icp_multi_insn_read_ctr;
550 s->insn_write = icp_multi_insn_write_ctr;
557 static void icp_multi_detach(struct comedi_device *dev)
559 struct icp_multi_private *devpriv = dev->private;
563 icp_multi_reset(dev);
565 free_irq(dev->irq, dev);
568 comedi_pci_disable(dev);
571 static struct comedi_driver icp_multi_driver = {
572 .driver_name = "icp_multi",
573 .module = THIS_MODULE,
574 .auto_attach = icp_multi_auto_attach,
575 .detach = icp_multi_detach,
578 static int icp_multi_pci_probe(struct pci_dev *dev,
579 const struct pci_device_id *id)
581 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
584 static const struct pci_device_id icp_multi_pci_table[] = {
585 { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
588 MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
590 static struct pci_driver icp_multi_pci_driver = {
592 .id_table = icp_multi_pci_table,
593 .probe = icp_multi_pci_probe,
594 .remove = comedi_pci_auto_unconfig,
596 module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
598 MODULE_AUTHOR("Comedi http://www.comedi.org");
599 MODULE_DESCRIPTION("Comedi low-level driver");
600 MODULE_LICENSE("GPL");