3 * Comedi driver for Inova ICP_MULTI board
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
21 * Description: Inova ICP_MULTI
22 * Devices: [Inova] ICP_MULTI (icp_multi)
23 * Author: Anne Smorthit <anne.smorthit@sfwte.ch>
26 * Configuration options: not applicable, uses PCI auto config
28 * The driver works for analog input and output and digital input and
29 * output. It does not work with interrupts or with the counters. Currently
32 * It has 16 single-ended or 8 differential Analogue Input channels with
33 * 12-bit resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.
34 * Input ranges can be individually programmed for each channel. Voltage or
35 * current measurement is selected by jumper.
37 * There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V
39 * 16 x Digital Inputs, 24V
41 * 8 x Digital Outputs, 24V, 1A
43 * 4 x 16-bit counters - not implemented
46 #include <linux/module.h>
47 #include <linux/delay.h>
49 #include "../comedi_pci.h"
51 #define ICP_MULTI_ADC_CSR 0x00 /* R/W: ADC command/status register */
52 #define ICP_MULTI_ADC_CSR_ST BIT(0) /* Start ADC */
53 #define ICP_MULTI_ADC_CSR_BSY BIT(0) /* ADC busy */
54 #define ICP_MULTI_ADC_CSR_BI BIT(4) /* Bipolar input range */
55 #define ICP_MULTI_ADC_CSR_RA BIT(5) /* Input range 0 = 5V, 1 = 10V */
56 #define ICP_MULTI_ADC_CSR_DI BIT(6) /* Input mode 1 = differential */
57 #define ICP_MULTI_ADC_CSR_DI_CHAN(x) (((x) & 0x7) << 9)
58 #define ICP_MULTI_ADC_CSR_SE_CHAN(x) (((x) & 0xf) << 8)
59 #define ICP_MULTI_AI 2 /* R: Analogue input data */
60 #define ICP_MULTI_DAC_CSR 0x04 /* R/W: DAC command/status register */
61 #define ICP_MULTI_DAC_CSR_ST BIT(0) /* Start DAC */
62 #define ICP_MULTI_DAC_CSR_BSY BIT(0) /* DAC busy */
63 #define ICP_MULTI_DAC_CSR_BI BIT(4) /* Bipolar output range */
64 #define ICP_MULTI_DAC_CSR_RA BIT(5) /* Output range 0 = 5V, 1 = 10V */
65 #define ICP_MULTI_DAC_CSR_CHAN(x) (((x) & 0x3) << 8)
66 #define ICP_MULTI_AO 6 /* R/W: Analogue output data */
67 #define ICP_MULTI_DI 8 /* R/W: Digital inputs */
68 #define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */
69 #define ICP_MULTI_INT_EN 0x0c /* R/W: Interrupt enable register */
70 #define ICP_MULTI_INT_STAT 0x0e /* R/W: Interrupt status register */
71 #define ICP_MULTI_INT_ADC_RDY BIT(0) /* A/D conversion ready interrupt */
72 #define ICP_MULTI_INT_DAC_RDY BIT(1) /* D/A conversion ready interrupt */
73 #define ICP_MULTI_INT_DOUT_ERR BIT(2) /* Digital output error interrupt */
74 #define ICP_MULTI_INT_DIN_STAT BIT(3) /* Digital input status change int. */
75 #define ICP_MULTI_INT_CIE0 BIT(4) /* Counter 0 overrun interrupt */
76 #define ICP_MULTI_INT_CIE1 BIT(5) /* Counter 1 overrun interrupt */
77 #define ICP_MULTI_INT_CIE2 BIT(6) /* Counter 2 overrun interrupt */
78 #define ICP_MULTI_INT_CIE3 BIT(7) /* Counter 3 overrun interrupt */
79 #define ICP_MULTI_INT_MASK 0xff /* All interrupts */
80 #define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */
81 #define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */
82 #define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */
83 #define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */
85 /* analog input and output have the same range options */
86 static const struct comedi_lrange icp_multi_ranges
= {
95 static const char range_codes_analog
[] = { 0x00, 0x20, 0x10, 0x30 };
97 static int icp_multi_ai_eoc(struct comedi_device
*dev
,
98 struct comedi_subdevice
*s
,
99 struct comedi_insn
*insn
,
100 unsigned long context
)
104 status
= readw(dev
->mmio
+ ICP_MULTI_ADC_CSR
);
105 if ((status
& ICP_MULTI_ADC_CSR_BSY
) == 0)
110 static int icp_multi_ai_insn_read(struct comedi_device
*dev
,
111 struct comedi_subdevice
*s
,
112 struct comedi_insn
*insn
,
115 unsigned int chan
= CR_CHAN(insn
->chanspec
);
116 unsigned int range
= CR_RANGE(insn
->chanspec
);
117 unsigned int aref
= CR_AREF(insn
->chanspec
);
118 unsigned int adc_csr
;
122 /* Set mode and range data for specified channel */
123 if (aref
== AREF_DIFF
) {
124 adc_csr
= ICP_MULTI_ADC_CSR_DI_CHAN(chan
) |
125 ICP_MULTI_ADC_CSR_DI
;
127 adc_csr
= ICP_MULTI_ADC_CSR_SE_CHAN(chan
);
129 adc_csr
|= range_codes_analog
[range
];
130 writew(adc_csr
, dev
->mmio
+ ICP_MULTI_ADC_CSR
);
132 for (n
= 0; n
< insn
->n
; n
++) {
133 /* Set start ADC bit */
134 writew(adc_csr
| ICP_MULTI_ADC_CSR_ST
,
135 dev
->mmio
+ ICP_MULTI_ADC_CSR
);
139 /* Wait for conversion to complete, or get fed up waiting */
140 ret
= comedi_timeout(dev
, s
, insn
, icp_multi_ai_eoc
, 0);
144 data
[n
] = (readw(dev
->mmio
+ ICP_MULTI_AI
) >> 4) & 0x0fff;
147 return ret
? ret
: n
;
150 static int icp_multi_ao_ready(struct comedi_device
*dev
,
151 struct comedi_subdevice
*s
,
152 struct comedi_insn
*insn
,
153 unsigned long context
)
157 status
= readw(dev
->mmio
+ ICP_MULTI_DAC_CSR
);
158 if ((status
& ICP_MULTI_DAC_CSR_BSY
) == 0)
163 static int icp_multi_ao_insn_write(struct comedi_device
*dev
,
164 struct comedi_subdevice
*s
,
165 struct comedi_insn
*insn
,
168 unsigned int chan
= CR_CHAN(insn
->chanspec
);
169 unsigned int range
= CR_RANGE(insn
->chanspec
);
170 unsigned int dac_csr
;
173 /* Select channel and range */
174 dac_csr
= ICP_MULTI_DAC_CSR_CHAN(chan
);
175 dac_csr
|= range_codes_analog
[range
];
176 writew(dac_csr
, dev
->mmio
+ ICP_MULTI_DAC_CSR
);
178 for (i
= 0; i
< insn
->n
; i
++) {
179 unsigned int val
= data
[i
];
182 /* Wait for analog output to be ready for new data */
183 ret
= comedi_timeout(dev
, s
, insn
, icp_multi_ao_ready
, 0);
187 writew(val
, dev
->mmio
+ ICP_MULTI_AO
);
189 /* Set start conversion bit to write data to channel */
190 writew(dac_csr
| ICP_MULTI_DAC_CSR_ST
,
191 dev
->mmio
+ ICP_MULTI_DAC_CSR
);
193 s
->readback
[chan
] = val
;
199 static int icp_multi_di_insn_bits(struct comedi_device
*dev
,
200 struct comedi_subdevice
*s
,
201 struct comedi_insn
*insn
,
204 data
[1] = readw(dev
->mmio
+ ICP_MULTI_DI
);
209 static int icp_multi_do_insn_bits(struct comedi_device
*dev
,
210 struct comedi_subdevice
*s
,
211 struct comedi_insn
*insn
,
214 if (comedi_dio_update_state(s
, data
))
215 writew(s
->state
, dev
->mmio
+ ICP_MULTI_DO
);
222 static int icp_multi_reset(struct comedi_device
*dev
)
226 /* Disable all interrupts and clear any requests */
227 writew(0, dev
->mmio
+ ICP_MULTI_INT_EN
);
228 writew(ICP_MULTI_INT_MASK
, dev
->mmio
+ ICP_MULTI_INT_STAT
);
230 /* Reset the analog output channels to 0V */
231 for (i
= 0; i
< 4; i
++) {
232 unsigned int dac_csr
= ICP_MULTI_DAC_CSR_CHAN(i
);
234 /* Select channel and 0..5V range */
235 writew(dac_csr
, dev
->mmio
+ ICP_MULTI_DAC_CSR
);
238 writew(0, dev
->mmio
+ ICP_MULTI_AO
);
240 /* Set start conversion bit to write data to channel */
241 writew(dac_csr
| ICP_MULTI_DAC_CSR_ST
,
242 dev
->mmio
+ ICP_MULTI_DAC_CSR
);
246 /* Digital outputs to 0 */
247 writew(0, dev
->mmio
+ ICP_MULTI_DO
);
252 static int icp_multi_auto_attach(struct comedi_device
*dev
,
253 unsigned long context_unused
)
255 struct pci_dev
*pcidev
= comedi_to_pci_dev(dev
);
256 struct comedi_subdevice
*s
;
259 ret
= comedi_pci_enable(dev
);
263 dev
->mmio
= pci_ioremap_bar(pcidev
, 2);
267 ret
= comedi_alloc_subdevices(dev
, 4);
271 icp_multi_reset(dev
);
273 /* Analog Input subdevice */
274 s
= &dev
->subdevices
[0];
275 s
->type
= COMEDI_SUBD_AI
;
276 s
->subdev_flags
= SDF_READABLE
| SDF_COMMON
| SDF_GROUND
| SDF_DIFF
;
279 s
->range_table
= &icp_multi_ranges
;
280 s
->insn_read
= icp_multi_ai_insn_read
;
282 /* Analog Output subdevice */
283 s
= &dev
->subdevices
[1];
284 s
->type
= COMEDI_SUBD_AO
;
285 s
->subdev_flags
= SDF_WRITABLE
| SDF_GROUND
| SDF_COMMON
;
288 s
->range_table
= &icp_multi_ranges
;
289 s
->insn_write
= icp_multi_ao_insn_write
;
291 ret
= comedi_alloc_subdev_readback(s
);
295 /* Digital Input subdevice */
296 s
= &dev
->subdevices
[2];
297 s
->type
= COMEDI_SUBD_DI
;
298 s
->subdev_flags
= SDF_READABLE
;
301 s
->range_table
= &range_digital
;
302 s
->insn_bits
= icp_multi_di_insn_bits
;
304 /* Digital Output subdevice */
305 s
= &dev
->subdevices
[3];
306 s
->type
= COMEDI_SUBD_DO
;
307 s
->subdev_flags
= SDF_WRITABLE
;
310 s
->range_table
= &range_digital
;
311 s
->insn_bits
= icp_multi_do_insn_bits
;
316 static struct comedi_driver icp_multi_driver
= {
317 .driver_name
= "icp_multi",
318 .module
= THIS_MODULE
,
319 .auto_attach
= icp_multi_auto_attach
,
320 .detach
= comedi_pci_detach
,
323 static int icp_multi_pci_probe(struct pci_dev
*dev
,
324 const struct pci_device_id
*id
)
326 return comedi_pci_auto_config(dev
, &icp_multi_driver
, id
->driver_data
);
329 static const struct pci_device_id icp_multi_pci_table
[] = {
330 { PCI_DEVICE(PCI_VENDOR_ID_ICP
, 0x8000) },
333 MODULE_DEVICE_TABLE(pci
, icp_multi_pci_table
);
335 static struct pci_driver icp_multi_pci_driver
= {
337 .id_table
= icp_multi_pci_table
,
338 .probe
= icp_multi_pci_probe
,
339 .remove
= comedi_pci_auto_unconfig
,
341 module_comedi_pci_driver(icp_multi_driver
, icp_multi_pci_driver
);
343 MODULE_AUTHOR("Comedi http://www.comedi.org");
344 MODULE_DESCRIPTION("Comedi driver for Inova ICP_MULTI board");
345 MODULE_LICENSE("GPL");