Commit | Line | Data |
---|---|---|
96341f71 AS |
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 | /* | |
24 | Driver: icp_multi | |
25 | Description: Inova ICP_MULTI | |
26 | Author: Anne Smorthit <anne.smorthit@sfwte.ch> | |
27 | Devices: [Inova] ICP_MULTI (icp_multi) | |
28 | Status: works | |
29 | ||
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 | |
32 | for DMA. | |
33 | ||
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. | |
38 | ||
39 | There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V | |
40 | ||
41 | 16 x Digital Inputs, 24V | |
42 | ||
43 | 8 x Digital Outputs, 24V, 1A | |
44 | ||
45 | 4 x 16-bit counters | |
46 | ||
194b9e3c | 47 | Configuration options: not applicable, uses PCI auto config |
96341f71 AS |
48 | */ |
49 | ||
33782dd5 HS |
50 | #include <linux/pci.h> |
51 | #include <linux/delay.h> | |
25436dc9 | 52 | #include <linux/interrupt.h> |
96341f71 | 53 | |
33782dd5 | 54 | #include "../comedidev.h" |
96341f71 | 55 | |
554e02c9 | 56 | #define PCI_DEVICE_ID_ICP_MULTI 0x8000 |
96341f71 | 57 | |
96341f71 AS |
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 | ||
b6c77757 | 73 | /* Define bits from ADC command/status register */ |
96341f71 AS |
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 | ||
b6c77757 | 80 | /* Define bits from DAC command/status register */ |
96341f71 AS |
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 | ||
b6c77757 | 86 | /* Define bits from interrupt enable/status registers */ |
96341f71 AS |
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 | ||
b6c77757 BP |
96 | /* Useful definitions */ |
97 | #define Status_IRQ 0x00ff /* All interrupts */ | |
96341f71 | 98 | |
b6c77757 | 99 | /* Define analogue range */ |
9ced1de6 | 100 | static const struct comedi_lrange range_analog = { 4, { |
0a85b6f0 MT |
101 | UNI_RANGE(5), |
102 | UNI_RANGE(10), | |
103 | BIP_RANGE(5), | |
104 | BIP_RANGE(10) | |
105 | } | |
96341f71 AS |
106 | }; |
107 | ||
108 | static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; | |
109 | ||
96341f71 AS |
110 | /* |
111 | ============================================================================== | |
112 | Data & Structure declarations | |
113 | ============================================================================== | |
114 | */ | |
96341f71 | 115 | |
52bfe6c8 | 116 | struct icp_multi_private { |
b6c77757 | 117 | char valid; /* card is usable */ |
86a5eb8c | 118 | void __iomem *io_addr; /* Pointer to mapped io address */ |
b6c77757 BP |
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 */ | |
20ce161d | 123 | unsigned int act_chanlist[32]; /* list of scanned channel */ |
b6c77757 BP |
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 */ | |
0a85b6f0 | 127 | short *ai_data; /* data buffer */ |
790c5541 | 128 | short ao_data[4]; /* data output buffer */ |
0a85b6f0 | 129 | short di_data; /* Digital input data */ |
b6c77757 | 130 | unsigned int do_data; /* Remember digital output data */ |
52bfe6c8 | 131 | }; |
96341f71 | 132 | |
0a85b6f0 MT |
133 | static void setup_channel_list(struct comedi_device *dev, |
134 | struct comedi_subdevice *s, | |
d79fc8e1 HS |
135 | unsigned int *chanlist, unsigned int n_chan) |
136 | { | |
65e9e2dc | 137 | struct icp_multi_private *devpriv = dev->private; |
d79fc8e1 HS |
138 | unsigned int i, range, chanprog; |
139 | unsigned int diff; | |
96341f71 | 140 | |
d79fc8e1 HS |
141 | devpriv->act_chanlist_len = n_chan; |
142 | devpriv->act_chanlist_pos = 0; | |
143 | ||
144 | for (i = 0; i < n_chan; i++) { | |
145 | /* Get channel */ | |
146 | chanprog = CR_CHAN(chanlist[i]); | |
147 | ||
148 | /* Determine if it is a differential channel (Bit 15 = 1) */ | |
149 | if (CR_AREF(chanlist[i]) == AREF_DIFF) { | |
150 | diff = 1; | |
151 | chanprog &= 0x0007; | |
152 | } else { | |
153 | diff = 0; | |
154 | chanprog &= 0x000f; | |
155 | } | |
156 | ||
157 | /* Clear channel, range and input mode bits | |
158 | * in A/D command/status register */ | |
159 | devpriv->AdcCmdStatus &= 0xf00f; | |
160 | ||
161 | /* Set channel number and differential mode status bit */ | |
162 | if (diff) { | |
163 | /* Set channel number, bits 9-11 & mode, bit 6 */ | |
164 | devpriv->AdcCmdStatus |= (chanprog << 9); | |
165 | devpriv->AdcCmdStatus |= ADC_DI; | |
166 | } else | |
167 | /* Set channel number, bits 8-11 */ | |
168 | devpriv->AdcCmdStatus |= (chanprog << 8); | |
169 | ||
170 | /* Get range for current channel */ | |
d68a8635 | 171 | range = range_codes_analog[CR_RANGE(chanlist[i])]; |
d79fc8e1 HS |
172 | /* Set range. bits 4-5 */ |
173 | devpriv->AdcCmdStatus |= range; | |
174 | ||
175 | /* Output channel, range, mode to ICP Multi */ | |
176 | writew(devpriv->AdcCmdStatus, | |
177 | devpriv->io_addr + ICP_MULTI_ADC_CSR); | |
d79fc8e1 | 178 | } |
d79fc8e1 | 179 | } |
96341f71 | 180 | |
0a85b6f0 MT |
181 | static int icp_multi_insn_read_ai(struct comedi_device *dev, |
182 | struct comedi_subdevice *s, | |
183 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 184 | { |
65e9e2dc | 185 | struct icp_multi_private *devpriv = dev->private; |
96341f71 AS |
186 | int n, timeout; |
187 | ||
b6c77757 | 188 | /* Disable A/D conversion ready interrupt */ |
96341f71 AS |
189 | devpriv->IntEnable &= ~ADC_READY; |
190 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
191 | ||
b6c77757 | 192 | /* Clear interrupt status */ |
96341f71 AS |
193 | devpriv->IntStatus |= ADC_READY; |
194 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
195 | ||
a622afcb | 196 | /* Set up appropriate channel, mode and range data, for specified ch */ |
96341f71 AS |
197 | setup_channel_list(dev, s, &insn->chanspec, 1); |
198 | ||
96341f71 | 199 | for (n = 0; n < insn->n; n++) { |
b6c77757 | 200 | /* Set start ADC bit */ |
96341f71 AS |
201 | devpriv->AdcCmdStatus |= ADC_ST; |
202 | writew(devpriv->AdcCmdStatus, | |
0a85b6f0 | 203 | devpriv->io_addr + ICP_MULTI_ADC_CSR); |
96341f71 AS |
204 | devpriv->AdcCmdStatus &= ~ADC_ST; |
205 | ||
5f74ea14 | 206 | udelay(1); |
96341f71 | 207 | |
b6c77757 | 208 | /* Wait for conversion to complete, or get fed up waiting */ |
96341f71 AS |
209 | timeout = 100; |
210 | while (timeout--) { | |
211 | if (!(readw(devpriv->io_addr + | |
0a85b6f0 | 212 | ICP_MULTI_ADC_CSR) & ADC_BSY)) |
96341f71 AS |
213 | goto conv_finish; |
214 | ||
5f74ea14 | 215 | udelay(1); |
96341f71 AS |
216 | } |
217 | ||
b6c77757 | 218 | /* If we reach here, a timeout has occurred */ |
96341f71 AS |
219 | comedi_error(dev, "A/D insn timeout"); |
220 | ||
b6c77757 | 221 | /* Disable interrupt */ |
96341f71 AS |
222 | devpriv->IntEnable &= ~ADC_READY; |
223 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
224 | ||
b6c77757 | 225 | /* Clear interrupt status */ |
96341f71 AS |
226 | devpriv->IntStatus |= ADC_READY; |
227 | writew(devpriv->IntStatus, | |
0a85b6f0 | 228 | devpriv->io_addr + ICP_MULTI_INT_STAT); |
96341f71 | 229 | |
b6c77757 | 230 | /* Clear data received */ |
96341f71 AS |
231 | data[n] = 0; |
232 | ||
96341f71 AS |
233 | return -ETIME; |
234 | ||
0a85b6f0 | 235 | conv_finish: |
96341f71 | 236 | data[n] = |
0a85b6f0 | 237 | (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; |
96341f71 AS |
238 | } |
239 | ||
b6c77757 | 240 | /* Disable interrupt */ |
96341f71 AS |
241 | devpriv->IntEnable &= ~ADC_READY; |
242 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
243 | ||
b6c77757 | 244 | /* Clear interrupt status */ |
96341f71 AS |
245 | devpriv->IntStatus |= ADC_READY; |
246 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
247 | ||
96341f71 AS |
248 | return n; |
249 | } | |
250 | ||
0a85b6f0 MT |
251 | static int icp_multi_insn_write_ao(struct comedi_device *dev, |
252 | struct comedi_subdevice *s, | |
253 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 254 | { |
65e9e2dc | 255 | struct icp_multi_private *devpriv = dev->private; |
96341f71 AS |
256 | int n, chan, range, timeout; |
257 | ||
b6c77757 | 258 | /* Disable D/A conversion ready interrupt */ |
96341f71 AS |
259 | devpriv->IntEnable &= ~DAC_READY; |
260 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
261 | ||
b6c77757 | 262 | /* Clear interrupt status */ |
96341f71 AS |
263 | devpriv->IntStatus |= DAC_READY; |
264 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
265 | ||
b6c77757 | 266 | /* Get channel number and range */ |
96341f71 AS |
267 | chan = CR_CHAN(insn->chanspec); |
268 | range = CR_RANGE(insn->chanspec); | |
269 | ||
b6c77757 BP |
270 | /* Set up range and channel data */ |
271 | /* Bit 4 = 1 : Bipolar */ | |
272 | /* Bit 5 = 0 : 5V */ | |
273 | /* Bit 5 = 1 : 10V */ | |
274 | /* Bits 8-9 : Channel number */ | |
96341f71 | 275 | devpriv->DacCmdStatus &= 0xfccf; |
d68a8635 | 276 | devpriv->DacCmdStatus |= range_codes_analog[range]; |
96341f71 AS |
277 | devpriv->DacCmdStatus |= (chan << 8); |
278 | ||
279 | writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
280 | ||
281 | for (n = 0; n < insn->n; n++) { | |
a622afcb DH |
282 | /* Wait for analogue output data register to be |
283 | * ready for new data, or get fed up waiting */ | |
96341f71 AS |
284 | timeout = 100; |
285 | while (timeout--) { | |
286 | if (!(readw(devpriv->io_addr + | |
0a85b6f0 | 287 | ICP_MULTI_DAC_CSR) & DAC_BSY)) |
96341f71 AS |
288 | goto dac_ready; |
289 | ||
5f74ea14 | 290 | udelay(1); |
96341f71 AS |
291 | } |
292 | ||
b6c77757 | 293 | /* If we reach here, a timeout has occurred */ |
96341f71 AS |
294 | comedi_error(dev, "D/A insn timeout"); |
295 | ||
b6c77757 | 296 | /* Disable interrupt */ |
96341f71 AS |
297 | devpriv->IntEnable &= ~DAC_READY; |
298 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
299 | ||
b6c77757 | 300 | /* Clear interrupt status */ |
96341f71 AS |
301 | devpriv->IntStatus |= DAC_READY; |
302 | writew(devpriv->IntStatus, | |
0a85b6f0 | 303 | devpriv->io_addr + ICP_MULTI_INT_STAT); |
96341f71 | 304 | |
b6c77757 | 305 | /* Clear data received */ |
96341f71 AS |
306 | devpriv->ao_data[chan] = 0; |
307 | ||
96341f71 AS |
308 | return -ETIME; |
309 | ||
0a85b6f0 | 310 | dac_ready: |
b6c77757 | 311 | /* Write data to analogue output data register */ |
96341f71 AS |
312 | writew(data[n], devpriv->io_addr + ICP_MULTI_AO); |
313 | ||
b6c77757 | 314 | /* Set DAC_ST bit to write the data to selected channel */ |
96341f71 AS |
315 | devpriv->DacCmdStatus |= DAC_ST; |
316 | writew(devpriv->DacCmdStatus, | |
0a85b6f0 | 317 | devpriv->io_addr + ICP_MULTI_DAC_CSR); |
96341f71 AS |
318 | devpriv->DacCmdStatus &= ~DAC_ST; |
319 | ||
b6c77757 | 320 | /* Save analogue output data */ |
96341f71 AS |
321 | devpriv->ao_data[chan] = data[n]; |
322 | } | |
323 | ||
96341f71 AS |
324 | return n; |
325 | } | |
326 | ||
0a85b6f0 MT |
327 | static int icp_multi_insn_read_ao(struct comedi_device *dev, |
328 | struct comedi_subdevice *s, | |
329 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 330 | { |
65e9e2dc | 331 | struct icp_multi_private *devpriv = dev->private; |
96341f71 AS |
332 | int n, chan; |
333 | ||
b6c77757 | 334 | /* Get channel number */ |
96341f71 AS |
335 | chan = CR_CHAN(insn->chanspec); |
336 | ||
b6c77757 | 337 | /* Read analogue outputs */ |
96341f71 AS |
338 | for (n = 0; n < insn->n; n++) |
339 | data[n] = devpriv->ao_data[chan]; | |
340 | ||
341 | return n; | |
342 | } | |
343 | ||
0a85b6f0 MT |
344 | static int icp_multi_insn_bits_di(struct comedi_device *dev, |
345 | struct comedi_subdevice *s, | |
346 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 347 | { |
65e9e2dc HS |
348 | struct icp_multi_private *devpriv = dev->private; |
349 | ||
96341f71 AS |
350 | data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); |
351 | ||
a2714e3e | 352 | return insn->n; |
96341f71 AS |
353 | } |
354 | ||
0a85b6f0 MT |
355 | static int icp_multi_insn_bits_do(struct comedi_device *dev, |
356 | struct comedi_subdevice *s, | |
357 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 358 | { |
65e9e2dc HS |
359 | struct icp_multi_private *devpriv = dev->private; |
360 | ||
96341f71 AS |
361 | if (data[0]) { |
362 | s->state &= ~data[0]; | |
363 | s->state |= (data[0] & data[1]); | |
364 | ||
ca5edf2f | 365 | printk(KERN_DEBUG "Digital outputs = %4x \n", s->state); |
96341f71 AS |
366 | |
367 | writew(s->state, devpriv->io_addr + ICP_MULTI_DO); | |
368 | } | |
369 | ||
370 | data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); | |
371 | ||
a2714e3e | 372 | return insn->n; |
96341f71 AS |
373 | } |
374 | ||
0a85b6f0 MT |
375 | static int icp_multi_insn_read_ctr(struct comedi_device *dev, |
376 | struct comedi_subdevice *s, | |
377 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
378 | { |
379 | return 0; | |
380 | } | |
381 | ||
0a85b6f0 MT |
382 | static int icp_multi_insn_write_ctr(struct comedi_device *dev, |
383 | struct comedi_subdevice *s, | |
384 | struct comedi_insn *insn, | |
385 | unsigned int *data) | |
96341f71 AS |
386 | { |
387 | return 0; | |
388 | } | |
389 | ||
70265d24 | 390 | static irqreturn_t interrupt_service_icp_multi(int irq, void *d) |
96341f71 | 391 | { |
71b5f4f1 | 392 | struct comedi_device *dev = d; |
65e9e2dc | 393 | struct icp_multi_private *devpriv = dev->private; |
96341f71 AS |
394 | int int_no; |
395 | ||
b6c77757 | 396 | /* Is this interrupt from our board? */ |
96341f71 AS |
397 | int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; |
398 | if (!int_no) | |
b6c77757 | 399 | /* No, exit */ |
96341f71 AS |
400 | return IRQ_NONE; |
401 | ||
b6c77757 | 402 | /* Determine which interrupt is active & handle it */ |
96341f71 AS |
403 | switch (int_no) { |
404 | case ADC_READY: | |
405 | break; | |
406 | case DAC_READY: | |
407 | break; | |
408 | case DOUT_ERROR: | |
409 | break; | |
410 | case DIN_STATUS: | |
411 | break; | |
412 | case CIE0: | |
413 | break; | |
414 | case CIE1: | |
415 | break; | |
416 | case CIE2: | |
417 | break; | |
418 | case CIE3: | |
419 | break; | |
420 | default: | |
421 | break; | |
422 | ||
423 | } | |
424 | ||
96341f71 AS |
425 | return IRQ_HANDLED; |
426 | } | |
427 | ||
428 | #if 0 | |
0a85b6f0 MT |
429 | static int check_channel_list(struct comedi_device *dev, |
430 | struct comedi_subdevice *s, | |
431 | unsigned int *chanlist, unsigned int n_chan) | |
96341f71 AS |
432 | { |
433 | unsigned int i; | |
434 | ||
b6c77757 | 435 | /* Check that we at least have one channel to check */ |
96341f71 AS |
436 | if (n_chan < 1) { |
437 | comedi_error(dev, "range/channel list is empty!"); | |
438 | return 0; | |
439 | } | |
b6c77757 | 440 | /* Check all channels */ |
96341f71 | 441 | for (i = 0; i < n_chan; i++) { |
b6c77757 | 442 | /* Check that channel number is < maximum */ |
96341f71 | 443 | if (CR_AREF(chanlist[i]) == AREF_DIFF) { |
08567ce9 | 444 | if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) { |
96341f71 | 445 | comedi_error(dev, |
ca5edf2f | 446 | "Incorrect differential ai ch-nr"); |
96341f71 AS |
447 | return 0; |
448 | } | |
449 | } else { | |
281ecb06 | 450 | if (CR_CHAN(chanlist[i]) > s->n_chan) { |
96341f71 | 451 | comedi_error(dev, |
0a85b6f0 | 452 | "Incorrect ai channel number"); |
96341f71 AS |
453 | return 0; |
454 | } | |
455 | } | |
456 | } | |
457 | return 1; | |
458 | } | |
459 | #endif | |
460 | ||
71b5f4f1 | 461 | static int icp_multi_reset(struct comedi_device *dev) |
96341f71 | 462 | { |
65e9e2dc | 463 | struct icp_multi_private *devpriv = dev->private; |
96341f71 AS |
464 | unsigned int i; |
465 | ||
b6c77757 | 466 | /* Clear INT enables and requests */ |
96341f71 AS |
467 | writew(0, devpriv->io_addr + ICP_MULTI_INT_EN); |
468 | writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
469 | ||
fafe91a8 HS |
470 | /* Set DACs to 0..5V range and 0V output */ |
471 | for (i = 0; i < 4; i++) { | |
472 | devpriv->DacCmdStatus &= 0xfcce; | |
96341f71 | 473 | |
fafe91a8 HS |
474 | /* Set channel number */ |
475 | devpriv->DacCmdStatus |= (i << 8); | |
96341f71 | 476 | |
fafe91a8 HS |
477 | /* Output 0V */ |
478 | writew(0, devpriv->io_addr + ICP_MULTI_AO); | |
96341f71 | 479 | |
fafe91a8 HS |
480 | /* Set start conversion bit */ |
481 | devpriv->DacCmdStatus |= DAC_ST; | |
96341f71 | 482 | |
fafe91a8 HS |
483 | /* Output to command / status register */ |
484 | writew(devpriv->DacCmdStatus, | |
485 | devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
96341f71 | 486 | |
fafe91a8 HS |
487 | /* Delay to allow DAC time to recover */ |
488 | udelay(1); | |
489 | } | |
490 | ||
491 | /* Digital outputs to 0 */ | |
96341f71 AS |
492 | writew(0, devpriv->io_addr + ICP_MULTI_DO); |
493 | ||
96341f71 AS |
494 | return 0; |
495 | } | |
496 | ||
a690b7e5 | 497 | static int icp_multi_auto_attach(struct comedi_device *dev, |
750af5e5 | 498 | unsigned long context_unused) |
96341f71 | 499 | { |
750af5e5 | 500 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
65e9e2dc | 501 | struct icp_multi_private *devpriv; |
34c43922 | 502 | struct comedi_subdevice *s; |
194b9e3c | 503 | resource_size_t iobase; |
12b4a097 | 504 | int ret; |
194b9e3c | 505 | |
194b9e3c | 506 | dev->board_name = dev->driver->driver_name; |
96341f71 | 507 | |
c34fa261 HS |
508 | devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); |
509 | if (!devpriv) | |
510 | return -ENOMEM; | |
511 | dev->private = devpriv; | |
96341f71 | 512 | |
194b9e3c HS |
513 | ret = comedi_pci_enable(pcidev, dev->board_name); |
514 | if (ret) | |
515 | return ret; | |
516 | iobase = pci_resource_start(pcidev, 2); | |
517 | dev->iobase = iobase; | |
96341f71 | 518 | |
96341f71 | 519 | devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE); |
194b9e3c | 520 | if (!devpriv->io_addr) |
96341f71 | 521 | return -ENOMEM; |
96341f71 | 522 | |
12b4a097 | 523 | ret = comedi_alloc_subdevices(dev, 5); |
8b6c5694 | 524 | if (ret) |
96341f71 | 525 | return ret; |
96341f71 AS |
526 | |
527 | icp_multi_reset(dev); | |
528 | ||
194b9e3c HS |
529 | if (pcidev->irq) { |
530 | ret = request_irq(pcidev->irq, interrupt_service_icp_multi, | |
531 | IRQF_SHARED, dev->board_name, dev); | |
532 | if (ret == 0) | |
533 | dev->irq = pcidev->irq; | |
798cdd05 | 534 | } |
96341f71 | 535 | |
12b4a097 | 536 | s = &dev->subdevices[0]; |
281ecb06 HS |
537 | dev->read_subdev = s; |
538 | s->type = COMEDI_SUBD_AI; | |
08567ce9 | 539 | s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; |
281ecb06 | 540 | s->n_chan = 16; |
efac035c | 541 | s->maxdata = 0x0fff; |
281ecb06 | 542 | s->len_chanlist = 16; |
abdeac3f | 543 | s->range_table = &range_analog; |
281ecb06 | 544 | s->insn_read = icp_multi_insn_read_ai; |
96341f71 | 545 | |
12b4a097 | 546 | s = &dev->subdevices[1]; |
fafe91a8 HS |
547 | s->type = COMEDI_SUBD_AO; |
548 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
549 | s->n_chan = 4; | |
68b82e09 | 550 | s->maxdata = 0x0fff; |
fafe91a8 HS |
551 | s->len_chanlist = 4; |
552 | s->range_table = &range_analog; | |
553 | s->insn_write = icp_multi_insn_write_ao; | |
554 | s->insn_read = icp_multi_insn_read_ao; | |
96341f71 | 555 | |
12b4a097 | 556 | s = &dev->subdevices[2]; |
48f31251 HS |
557 | s->type = COMEDI_SUBD_DI; |
558 | s->subdev_flags = SDF_READABLE; | |
559 | s->n_chan = 16; | |
560 | s->maxdata = 1; | |
561 | s->len_chanlist = 16; | |
562 | s->range_table = &range_digital; | |
563 | s->io_bits = 0; | |
564 | s->insn_bits = icp_multi_insn_bits_di; | |
96341f71 | 565 | |
12b4a097 | 566 | s = &dev->subdevices[3]; |
2aa70705 HS |
567 | s->type = COMEDI_SUBD_DO; |
568 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
569 | s->n_chan = 8; | |
570 | s->maxdata = 1; | |
571 | s->len_chanlist = 8; | |
572 | s->range_table = &range_digital; | |
573 | s->io_bits = 0xff; | |
574 | s->state = 0; | |
575 | s->insn_bits = icp_multi_insn_bits_do; | |
96341f71 | 576 | |
12b4a097 | 577 | s = &dev->subdevices[4]; |
5b93da54 HS |
578 | s->type = COMEDI_SUBD_COUNTER; |
579 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
580 | s->n_chan = 4; | |
581 | s->maxdata = 0xffff; | |
582 | s->len_chanlist = 4; | |
583 | s->state = 0; | |
584 | s->insn_read = icp_multi_insn_read_ctr; | |
585 | s->insn_write = icp_multi_insn_write_ctr; | |
96341f71 AS |
586 | |
587 | devpriv->valid = 1; | |
588 | ||
798cdd05 HS |
589 | dev_info(dev->class_dev, "%s attached, irq %sabled\n", |
590 | dev->board_name, dev->irq ? "en" : "dis"); | |
591 | ||
96341f71 AS |
592 | return 0; |
593 | } | |
594 | ||
484ecc95 | 595 | static void icp_multi_detach(struct comedi_device *dev) |
96341f71 | 596 | { |
194b9e3c | 597 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
65e9e2dc | 598 | struct icp_multi_private *devpriv = dev->private; |
194b9e3c | 599 | |
65e9e2dc | 600 | if (devpriv) |
96341f71 AS |
601 | if (devpriv->valid) |
602 | icp_multi_reset(dev); | |
96341f71 | 603 | if (dev->irq) |
5f74ea14 | 604 | free_irq(dev->irq, dev); |
65e9e2dc | 605 | if (devpriv && devpriv->io_addr) |
96341f71 | 606 | iounmap(devpriv->io_addr); |
194b9e3c HS |
607 | if (pcidev) { |
608 | if (dev->iobase) | |
609 | comedi_pci_disable(pcidev); | |
610 | } | |
96341f71 | 611 | } |
90f703d3 | 612 | |
d79fc8e1 HS |
613 | static struct comedi_driver icp_multi_driver = { |
614 | .driver_name = "icp_multi", | |
615 | .module = THIS_MODULE, | |
750af5e5 | 616 | .auto_attach = icp_multi_auto_attach, |
d79fc8e1 | 617 | .detach = icp_multi_detach, |
d79fc8e1 | 618 | }; |
554e02c9 | 619 | |
a690b7e5 | 620 | static int icp_multi_pci_probe(struct pci_dev *dev, |
b8f4ac23 | 621 | const struct pci_device_id *id) |
554e02c9 | 622 | { |
b8f4ac23 | 623 | return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data); |
554e02c9 HS |
624 | } |
625 | ||
554e02c9 HS |
626 | static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = { |
627 | { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) }, | |
628 | { 0 } | |
629 | }; | |
630 | MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); | |
631 | ||
632 | static struct pci_driver icp_multi_pci_driver = { | |
633 | .name = "icp_multi", | |
634 | .id_table = icp_multi_pci_table, | |
635 | .probe = icp_multi_pci_probe, | |
9901a4d7 | 636 | .remove = comedi_pci_auto_unconfig, |
554e02c9 HS |
637 | }; |
638 | module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); | |
d79fc8e1 | 639 | |
90f703d3 AT |
640 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
641 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
642 | MODULE_LICENSE("GPL"); |