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.
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.
25 Description: Inova ICP_MULTI
26 Author: Anne Smorthit <anne.smorthit@sfwte.ch>
27 Devices: [Inova] ICP_MULTI (icp_multi)
30 The driver works for analog input and output and digital input and output.
31 It does not work with interrupts or with the counters. Currently no support
34 It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
35 resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input
36 ranges can be individually programmed for each channel. Voltage or current
37 measurement is selected by jumper.
39 There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V
41 16 x Digital Inputs, 24V
43 8 x Digital Outputs, 24V, 1A
47 Configuration options: not applicable, uses PCI auto config
50 #include <linux/pci.h>
51 #include <linux/delay.h>
52 #include <linux/interrupt.h>
54 #include "../comedidev.h"
56 #define PCI_DEVICE_ID_ICP_MULTI 0x8000
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 */
71 /* Define bits from ADC command/status register */
72 #define ADC_ST 0x0001 /* Start ADC */
73 #define ADC_BSY 0x0001 /* ADC busy */
74 #define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */
75 #define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
76 #define ADC_DI 0x0040 /* Differential input mode 1 = differential */
78 /* Define bits from DAC command/status register */
79 #define DAC_ST 0x0001 /* Start DAC */
80 #define DAC_BSY 0x0001 /* DAC busy */
81 #define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */
82 #define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */
84 /* Define bits from interrupt enable/status registers */
85 #define ADC_READY 0x0001 /* A/d conversion ready interrupt */
86 #define DAC_READY 0x0002 /* D/a conversion ready interrupt */
87 #define DOUT_ERROR 0x0004 /* Digital output error interrupt */
88 #define DIN_STATUS 0x0008 /* Digital input status change interrupt */
89 #define CIE0 0x0010 /* Counter 0 overrun interrupt */
90 #define CIE1 0x0020 /* Counter 1 overrun interrupt */
91 #define CIE2 0x0040 /* Counter 2 overrun interrupt */
92 #define CIE3 0x0080 /* Counter 3 overrun interrupt */
94 /* Useful definitions */
95 #define Status_IRQ 0x00ff /* All interrupts */
97 /* Define analogue range */
98 static const struct comedi_lrange range_analog = { 4, {
106 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
109 ==============================================================================
110 Data & Structure declarations
111 ==============================================================================
114 struct icp_multi_private {
115 char valid; /* card is usable */
116 void __iomem *io_addr; /* Pointer to mapped io address */
117 unsigned int AdcCmdStatus; /* ADC Command/Status register */
118 unsigned int DacCmdStatus; /* DAC Command/Status register */
119 unsigned int IntEnable; /* Interrupt Enable register */
120 unsigned int IntStatus; /* Interrupt Status register */
121 unsigned int act_chanlist[32]; /* list of scanned channel */
122 unsigned char act_chanlist_len; /* len of scanlist */
123 unsigned char act_chanlist_pos; /* actual position in MUX list */
124 unsigned int *ai_chanlist; /* actaul chanlist */
125 short *ai_data; /* data buffer */
126 short ao_data[4]; /* data output buffer */
127 short di_data; /* Digital input data */
128 unsigned int do_data; /* Remember digital output data */
131 static void setup_channel_list(struct comedi_device *dev,
132 struct comedi_subdevice *s,
133 unsigned int *chanlist, unsigned int n_chan)
135 struct icp_multi_private *devpriv = dev->private;
136 unsigned int i, range, chanprog;
139 devpriv->act_chanlist_len = n_chan;
140 devpriv->act_chanlist_pos = 0;
142 for (i = 0; i < n_chan; i++) {
144 chanprog = CR_CHAN(chanlist[i]);
146 /* Determine if it is a differential channel (Bit 15 = 1) */
147 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
155 /* Clear channel, range and input mode bits
156 * in A/D command/status register */
157 devpriv->AdcCmdStatus &= 0xf00f;
159 /* Set channel number and differential mode status bit */
161 /* Set channel number, bits 9-11 & mode, bit 6 */
162 devpriv->AdcCmdStatus |= (chanprog << 9);
163 devpriv->AdcCmdStatus |= ADC_DI;
165 /* Set channel number, bits 8-11 */
166 devpriv->AdcCmdStatus |= (chanprog << 8);
168 /* Get range for current channel */
169 range = range_codes_analog[CR_RANGE(chanlist[i])];
170 /* Set range. bits 4-5 */
171 devpriv->AdcCmdStatus |= range;
173 /* Output channel, range, mode to ICP Multi */
174 writew(devpriv->AdcCmdStatus,
175 devpriv->io_addr + ICP_MULTI_ADC_CSR);
179 static int icp_multi_insn_read_ai(struct comedi_device *dev,
180 struct comedi_subdevice *s,
181 struct comedi_insn *insn, unsigned int *data)
183 struct icp_multi_private *devpriv = dev->private;
186 /* Disable A/D conversion ready interrupt */
187 devpriv->IntEnable &= ~ADC_READY;
188 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
190 /* Clear interrupt status */
191 devpriv->IntStatus |= ADC_READY;
192 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
194 /* Set up appropriate channel, mode and range data, for specified ch */
195 setup_channel_list(dev, s, &insn->chanspec, 1);
197 for (n = 0; n < insn->n; n++) {
198 /* Set start ADC bit */
199 devpriv->AdcCmdStatus |= ADC_ST;
200 writew(devpriv->AdcCmdStatus,
201 devpriv->io_addr + ICP_MULTI_ADC_CSR);
202 devpriv->AdcCmdStatus &= ~ADC_ST;
206 /* Wait for conversion to complete, or get fed up waiting */
209 if (!(readw(devpriv->io_addr +
210 ICP_MULTI_ADC_CSR) & ADC_BSY))
216 /* If we reach here, a timeout has occurred */
217 comedi_error(dev, "A/D insn timeout");
219 /* Disable interrupt */
220 devpriv->IntEnable &= ~ADC_READY;
221 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
223 /* Clear interrupt status */
224 devpriv->IntStatus |= ADC_READY;
225 writew(devpriv->IntStatus,
226 devpriv->io_addr + ICP_MULTI_INT_STAT);
228 /* Clear data received */
235 (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
238 /* Disable interrupt */
239 devpriv->IntEnable &= ~ADC_READY;
240 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
242 /* Clear interrupt status */
243 devpriv->IntStatus |= ADC_READY;
244 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
249 static int icp_multi_insn_write_ao(struct comedi_device *dev,
250 struct comedi_subdevice *s,
251 struct comedi_insn *insn, unsigned int *data)
253 struct icp_multi_private *devpriv = dev->private;
254 int n, chan, range, timeout;
256 /* Disable D/A conversion ready interrupt */
257 devpriv->IntEnable &= ~DAC_READY;
258 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
260 /* Clear interrupt status */
261 devpriv->IntStatus |= DAC_READY;
262 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
264 /* Get channel number and range */
265 chan = CR_CHAN(insn->chanspec);
266 range = CR_RANGE(insn->chanspec);
268 /* Set up range and channel data */
269 /* Bit 4 = 1 : Bipolar */
271 /* Bit 5 = 1 : 10V */
272 /* Bits 8-9 : Channel number */
273 devpriv->DacCmdStatus &= 0xfccf;
274 devpriv->DacCmdStatus |= range_codes_analog[range];
275 devpriv->DacCmdStatus |= (chan << 8);
277 writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
279 for (n = 0; n < insn->n; n++) {
280 /* Wait for analogue output data register to be
281 * ready for new data, or get fed up waiting */
284 if (!(readw(devpriv->io_addr +
285 ICP_MULTI_DAC_CSR) & DAC_BSY))
291 /* If we reach here, a timeout has occurred */
292 comedi_error(dev, "D/A insn timeout");
294 /* Disable interrupt */
295 devpriv->IntEnable &= ~DAC_READY;
296 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
298 /* Clear interrupt status */
299 devpriv->IntStatus |= DAC_READY;
300 writew(devpriv->IntStatus,
301 devpriv->io_addr + ICP_MULTI_INT_STAT);
303 /* Clear data received */
304 devpriv->ao_data[chan] = 0;
309 /* Write data to analogue output data register */
310 writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
312 /* Set DAC_ST bit to write the data to selected channel */
313 devpriv->DacCmdStatus |= DAC_ST;
314 writew(devpriv->DacCmdStatus,
315 devpriv->io_addr + ICP_MULTI_DAC_CSR);
316 devpriv->DacCmdStatus &= ~DAC_ST;
318 /* Save analogue output data */
319 devpriv->ao_data[chan] = data[n];
325 static int icp_multi_insn_read_ao(struct comedi_device *dev,
326 struct comedi_subdevice *s,
327 struct comedi_insn *insn, unsigned int *data)
329 struct icp_multi_private *devpriv = dev->private;
332 /* Get channel number */
333 chan = CR_CHAN(insn->chanspec);
335 /* Read analogue outputs */
336 for (n = 0; n < insn->n; n++)
337 data[n] = devpriv->ao_data[chan];
342 static int icp_multi_insn_bits_di(struct comedi_device *dev,
343 struct comedi_subdevice *s,
344 struct comedi_insn *insn, unsigned int *data)
346 struct icp_multi_private *devpriv = dev->private;
348 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
353 static int icp_multi_insn_bits_do(struct comedi_device *dev,
354 struct comedi_subdevice *s,
355 struct comedi_insn *insn, unsigned int *data)
357 struct icp_multi_private *devpriv = dev->private;
360 s->state &= ~data[0];
361 s->state |= (data[0] & data[1]);
363 printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
365 writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
368 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
373 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
374 struct comedi_subdevice *s,
375 struct comedi_insn *insn, unsigned int *data)
380 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
381 struct comedi_subdevice *s,
382 struct comedi_insn *insn,
388 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
390 struct comedi_device *dev = d;
391 struct icp_multi_private *devpriv = dev->private;
394 /* Is this interrupt from our board? */
395 int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
400 /* Determine which interrupt is active & handle it */
427 static int check_channel_list(struct comedi_device *dev,
428 struct comedi_subdevice *s,
429 unsigned int *chanlist, unsigned int n_chan)
433 /* Check that we at least have one channel to check */
435 comedi_error(dev, "range/channel list is empty!");
438 /* Check all channels */
439 for (i = 0; i < n_chan; i++) {
440 /* Check that channel number is < maximum */
441 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
442 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
444 "Incorrect differential ai ch-nr");
448 if (CR_CHAN(chanlist[i]) > s->n_chan) {
450 "Incorrect ai channel number");
459 static int icp_multi_reset(struct comedi_device *dev)
461 struct icp_multi_private *devpriv = dev->private;
464 /* Clear INT enables and requests */
465 writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
466 writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
468 /* Set DACs to 0..5V range and 0V output */
469 for (i = 0; i < 4; i++) {
470 devpriv->DacCmdStatus &= 0xfcce;
472 /* Set channel number */
473 devpriv->DacCmdStatus |= (i << 8);
476 writew(0, devpriv->io_addr + ICP_MULTI_AO);
478 /* Set start conversion bit */
479 devpriv->DacCmdStatus |= DAC_ST;
481 /* Output to command / status register */
482 writew(devpriv->DacCmdStatus,
483 devpriv->io_addr + ICP_MULTI_DAC_CSR);
485 /* Delay to allow DAC time to recover */
489 /* Digital outputs to 0 */
490 writew(0, devpriv->io_addr + ICP_MULTI_DO);
495 static int icp_multi_auto_attach(struct comedi_device *dev,
496 unsigned long context_unused)
498 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
499 struct icp_multi_private *devpriv;
500 struct comedi_subdevice *s;
503 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
506 dev->private = devpriv;
508 ret = comedi_pci_enable(dev);
512 devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
513 if (!devpriv->io_addr)
516 ret = comedi_alloc_subdevices(dev, 5);
520 icp_multi_reset(dev);
523 ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
524 IRQF_SHARED, dev->board_name, dev);
526 dev->irq = pcidev->irq;
529 s = &dev->subdevices[0];
530 dev->read_subdev = s;
531 s->type = COMEDI_SUBD_AI;
532 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
535 s->len_chanlist = 16;
536 s->range_table = &range_analog;
537 s->insn_read = icp_multi_insn_read_ai;
539 s = &dev->subdevices[1];
540 s->type = COMEDI_SUBD_AO;
541 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
545 s->range_table = &range_analog;
546 s->insn_write = icp_multi_insn_write_ao;
547 s->insn_read = icp_multi_insn_read_ao;
549 s = &dev->subdevices[2];
550 s->type = COMEDI_SUBD_DI;
551 s->subdev_flags = SDF_READABLE;
554 s->len_chanlist = 16;
555 s->range_table = &range_digital;
557 s->insn_bits = icp_multi_insn_bits_di;
559 s = &dev->subdevices[3];
560 s->type = COMEDI_SUBD_DO;
561 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
565 s->range_table = &range_digital;
568 s->insn_bits = icp_multi_insn_bits_do;
570 s = &dev->subdevices[4];
571 s->type = COMEDI_SUBD_COUNTER;
572 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
577 s->insn_read = icp_multi_insn_read_ctr;
578 s->insn_write = icp_multi_insn_write_ctr;
582 dev_info(dev->class_dev, "%s attached, irq %sabled\n",
583 dev->board_name, dev->irq ? "en" : "dis");
588 static void icp_multi_detach(struct comedi_device *dev)
590 struct icp_multi_private *devpriv = dev->private;
594 icp_multi_reset(dev);
596 free_irq(dev->irq, dev);
597 if (devpriv && devpriv->io_addr)
598 iounmap(devpriv->io_addr);
599 comedi_pci_disable(dev);
602 static struct comedi_driver icp_multi_driver = {
603 .driver_name = "icp_multi",
604 .module = THIS_MODULE,
605 .auto_attach = icp_multi_auto_attach,
606 .detach = icp_multi_detach,
609 static int icp_multi_pci_probe(struct pci_dev *dev,
610 const struct pci_device_id *id)
612 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
615 static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
616 { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
619 MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
621 static struct pci_driver icp_multi_pci_driver = {
623 .id_table = icp_multi_pci_table,
624 .probe = icp_multi_pci_probe,
625 .remove = comedi_pci_auto_unconfig,
627 module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
629 MODULE_AUTHOR("Comedi http://www.comedi.org");
630 MODULE_DESCRIPTION("Comedi low-level driver");
631 MODULE_LICENSE("GPL");