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 | ||
47 | Options: | |
48 | [0] - PCI bus number - if bus number and slot number are 0, | |
a622afcb | 49 | then driver search for first unused card |
96341f71 AS |
50 | [1] - PCI slot number |
51 | */ | |
52 | ||
25436dc9 | 53 | #include <linux/interrupt.h> |
96341f71 AS |
54 | #include "../comedidev.h" |
55 | ||
56 | #include <linux/delay.h> | |
57 | #include <linux/pci.h> | |
58 | ||
59 | #include "icp_multi.h" | |
60 | ||
554e02c9 | 61 | #define PCI_DEVICE_ID_ICP_MULTI 0x8000 |
96341f71 | 62 | |
96341f71 AS |
63 | #define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */ |
64 | #define ICP_MULTI_AI 2 /* R: Analogue input data */ | |
65 | #define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */ | |
66 | #define ICP_MULTI_AO 6 /* R/W: Analogue output data */ | |
67 | #define ICP_MULTI_DI 8 /* R/W: Digital inouts */ | |
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_CNTR0 0x10 /* R/W: Counter 0 */ | |
72 | #define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ | |
73 | #define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ | |
74 | #define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ | |
75 | ||
76 | #define ICP_MULTI_SIZE 0x20 /* 32 bytes */ | |
77 | ||
b6c77757 | 78 | /* Define bits from ADC command/status register */ |
96341f71 AS |
79 | #define ADC_ST 0x0001 /* Start ADC */ |
80 | #define ADC_BSY 0x0001 /* ADC busy */ | |
81 | #define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */ | |
82 | #define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ | |
83 | #define ADC_DI 0x0040 /* Differential input mode 1 = differential */ | |
84 | ||
b6c77757 | 85 | /* Define bits from DAC command/status register */ |
96341f71 AS |
86 | #define DAC_ST 0x0001 /* Start DAC */ |
87 | #define DAC_BSY 0x0001 /* DAC busy */ | |
88 | #define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */ | |
89 | #define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ | |
90 | ||
b6c77757 | 91 | /* Define bits from interrupt enable/status registers */ |
96341f71 AS |
92 | #define ADC_READY 0x0001 /* A/d conversion ready interrupt */ |
93 | #define DAC_READY 0x0002 /* D/a conversion ready interrupt */ | |
94 | #define DOUT_ERROR 0x0004 /* Digital output error interrupt */ | |
95 | #define DIN_STATUS 0x0008 /* Digital input status change interrupt */ | |
96 | #define CIE0 0x0010 /* Counter 0 overrun interrupt */ | |
97 | #define CIE1 0x0020 /* Counter 1 overrun interrupt */ | |
98 | #define CIE2 0x0040 /* Counter 2 overrun interrupt */ | |
99 | #define CIE3 0x0080 /* Counter 3 overrun interrupt */ | |
100 | ||
b6c77757 BP |
101 | /* Useful definitions */ |
102 | #define Status_IRQ 0x00ff /* All interrupts */ | |
96341f71 | 103 | |
b6c77757 | 104 | /* Define analogue range */ |
9ced1de6 | 105 | static const struct comedi_lrange range_analog = { 4, { |
0a85b6f0 MT |
106 | UNI_RANGE(5), |
107 | UNI_RANGE(10), | |
108 | BIP_RANGE(5), | |
109 | BIP_RANGE(10) | |
110 | } | |
96341f71 AS |
111 | }; |
112 | ||
113 | static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; | |
114 | ||
96341f71 AS |
115 | /* |
116 | ============================================================================== | |
117 | Data & Structure declarations | |
118 | ============================================================================== | |
119 | */ | |
932b3ee7 | 120 | static unsigned short pci_list_builded; /*>0 list of card is known */ |
96341f71 | 121 | |
52bfe6c8 | 122 | struct boardtype { |
b6c77757 | 123 | const char *name; /* driver name */ |
96341f71 | 124 | int device_id; |
52bfe6c8 | 125 | }; |
96341f71 | 126 | |
52bfe6c8 | 127 | struct icp_multi_private { |
b6c77757 BP |
128 | struct pcilst_struct *card; /* pointer to card */ |
129 | char valid; /* card is usable */ | |
86a5eb8c | 130 | void __iomem *io_addr; /* Pointer to mapped io address */ |
b6c77757 BP |
131 | resource_size_t phys_iobase; /* Physical io address */ |
132 | unsigned int AdcCmdStatus; /* ADC Command/Status register */ | |
133 | unsigned int DacCmdStatus; /* DAC Command/Status register */ | |
134 | unsigned int IntEnable; /* Interrupt Enable register */ | |
135 | unsigned int IntStatus; /* Interrupt Status register */ | |
136 | unsigned int act_chanlist[32]; /* list of scaned channel */ | |
137 | unsigned char act_chanlist_len; /* len of scanlist */ | |
138 | unsigned char act_chanlist_pos; /* actual position in MUX list */ | |
139 | unsigned int *ai_chanlist; /* actaul chanlist */ | |
0a85b6f0 | 140 | short *ai_data; /* data buffer */ |
790c5541 | 141 | short ao_data[4]; /* data output buffer */ |
0a85b6f0 | 142 | short di_data; /* Digital input data */ |
b6c77757 | 143 | unsigned int do_data; /* Remember digital output data */ |
52bfe6c8 | 144 | }; |
96341f71 | 145 | |
52bfe6c8 BP |
146 | #define devpriv ((struct icp_multi_private *)dev->private) |
147 | #define this_board ((const struct boardtype *)dev->board_ptr) | |
96341f71 AS |
148 | |
149 | /* | |
150 | ============================================================================== | |
d79fc8e1 HS |
151 | |
152 | Name: setup_channel_list | |
153 | ||
154 | Description: | |
155 | This function sets the appropriate channel selection, | |
156 | differential input mode and range bits in the ADC Command/ | |
157 | Status register. | |
158 | ||
159 | Parameters: | |
160 | struct comedi_device *dev Pointer to current service structure | |
161 | struct comedi_subdevice *s Pointer to current subdevice structure | |
162 | unsigned int *chanlist Pointer to packed channel list | |
163 | unsigned int n_chan Number of channels to scan | |
164 | ||
165 | Returns:Void | |
166 | ||
96341f71 AS |
167 | ============================================================================== |
168 | */ | |
0a85b6f0 MT |
169 | static void setup_channel_list(struct comedi_device *dev, |
170 | struct comedi_subdevice *s, | |
d79fc8e1 HS |
171 | unsigned int *chanlist, unsigned int n_chan) |
172 | { | |
173 | unsigned int i, range, chanprog; | |
174 | unsigned int diff; | |
96341f71 | 175 | |
d79fc8e1 HS |
176 | devpriv->act_chanlist_len = n_chan; |
177 | devpriv->act_chanlist_pos = 0; | |
178 | ||
179 | for (i = 0; i < n_chan; i++) { | |
180 | /* Get channel */ | |
181 | chanprog = CR_CHAN(chanlist[i]); | |
182 | ||
183 | /* Determine if it is a differential channel (Bit 15 = 1) */ | |
184 | if (CR_AREF(chanlist[i]) == AREF_DIFF) { | |
185 | diff = 1; | |
186 | chanprog &= 0x0007; | |
187 | } else { | |
188 | diff = 0; | |
189 | chanprog &= 0x000f; | |
190 | } | |
191 | ||
192 | /* Clear channel, range and input mode bits | |
193 | * in A/D command/status register */ | |
194 | devpriv->AdcCmdStatus &= 0xf00f; | |
195 | ||
196 | /* Set channel number and differential mode status bit */ | |
197 | if (diff) { | |
198 | /* Set channel number, bits 9-11 & mode, bit 6 */ | |
199 | devpriv->AdcCmdStatus |= (chanprog << 9); | |
200 | devpriv->AdcCmdStatus |= ADC_DI; | |
201 | } else | |
202 | /* Set channel number, bits 8-11 */ | |
203 | devpriv->AdcCmdStatus |= (chanprog << 8); | |
204 | ||
205 | /* Get range for current channel */ | |
d68a8635 | 206 | range = range_codes_analog[CR_RANGE(chanlist[i])]; |
d79fc8e1 HS |
207 | /* Set range. bits 4-5 */ |
208 | devpriv->AdcCmdStatus |= range; | |
209 | ||
210 | /* Output channel, range, mode to ICP Multi */ | |
211 | writew(devpriv->AdcCmdStatus, | |
212 | devpriv->io_addr + ICP_MULTI_ADC_CSR); | |
d79fc8e1 | 213 | } |
d79fc8e1 | 214 | } |
96341f71 AS |
215 | |
216 | /* | |
217 | ============================================================================== | |
218 | ||
a622afcb | 219 | Name: icp_multi_insn_read_ai |
96341f71 | 220 | |
a622afcb DH |
221 | Description: |
222 | This function reads a single analogue input. | |
96341f71 | 223 | |
a622afcb DH |
224 | Parameters: |
225 | struct comedi_device *dev Pointer to current device structure | |
226 | struct comedi_subdevice *s Pointer to current subdevice structure | |
227 | struct comedi_insn *insn Pointer to current comedi instruction | |
228 | unsigned int *data Pointer to analogue input data | |
96341f71 | 229 | |
a622afcb | 230 | Returns:int Nmuber of instructions executed |
96341f71 AS |
231 | |
232 | ============================================================================== | |
233 | */ | |
0a85b6f0 MT |
234 | static int icp_multi_insn_read_ai(struct comedi_device *dev, |
235 | struct comedi_subdevice *s, | |
236 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
237 | { |
238 | int n, timeout; | |
239 | ||
b6c77757 | 240 | /* Disable A/D conversion ready 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 | ||
a622afcb | 248 | /* Set up appropriate channel, mode and range data, for specified ch */ |
96341f71 AS |
249 | setup_channel_list(dev, s, &insn->chanspec, 1); |
250 | ||
96341f71 | 251 | for (n = 0; n < insn->n; n++) { |
b6c77757 | 252 | /* Set start ADC bit */ |
96341f71 AS |
253 | devpriv->AdcCmdStatus |= ADC_ST; |
254 | writew(devpriv->AdcCmdStatus, | |
0a85b6f0 | 255 | devpriv->io_addr + ICP_MULTI_ADC_CSR); |
96341f71 AS |
256 | devpriv->AdcCmdStatus &= ~ADC_ST; |
257 | ||
5f74ea14 | 258 | udelay(1); |
96341f71 | 259 | |
b6c77757 | 260 | /* Wait for conversion to complete, or get fed up waiting */ |
96341f71 AS |
261 | timeout = 100; |
262 | while (timeout--) { | |
263 | if (!(readw(devpriv->io_addr + | |
0a85b6f0 | 264 | ICP_MULTI_ADC_CSR) & ADC_BSY)) |
96341f71 AS |
265 | goto conv_finish; |
266 | ||
5f74ea14 | 267 | udelay(1); |
96341f71 AS |
268 | } |
269 | ||
b6c77757 | 270 | /* If we reach here, a timeout has occurred */ |
96341f71 AS |
271 | comedi_error(dev, "A/D insn timeout"); |
272 | ||
b6c77757 | 273 | /* Disable interrupt */ |
96341f71 AS |
274 | devpriv->IntEnable &= ~ADC_READY; |
275 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
276 | ||
b6c77757 | 277 | /* Clear interrupt status */ |
96341f71 AS |
278 | devpriv->IntStatus |= ADC_READY; |
279 | writew(devpriv->IntStatus, | |
0a85b6f0 | 280 | devpriv->io_addr + ICP_MULTI_INT_STAT); |
96341f71 | 281 | |
b6c77757 | 282 | /* Clear data received */ |
96341f71 AS |
283 | data[n] = 0; |
284 | ||
96341f71 AS |
285 | return -ETIME; |
286 | ||
0a85b6f0 | 287 | conv_finish: |
96341f71 | 288 | data[n] = |
0a85b6f0 | 289 | (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; |
96341f71 AS |
290 | } |
291 | ||
b6c77757 | 292 | /* Disable interrupt */ |
96341f71 AS |
293 | devpriv->IntEnable &= ~ADC_READY; |
294 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
295 | ||
b6c77757 | 296 | /* Clear interrupt status */ |
96341f71 AS |
297 | devpriv->IntStatus |= ADC_READY; |
298 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
299 | ||
96341f71 AS |
300 | return n; |
301 | } | |
302 | ||
303 | /* | |
304 | ============================================================================== | |
305 | ||
a622afcb | 306 | Name: icp_multi_insn_write_ao |
96341f71 | 307 | |
a622afcb DH |
308 | Description: |
309 | This function writes a single analogue output. | |
96341f71 | 310 | |
a622afcb DH |
311 | Parameters: |
312 | struct comedi_device *dev Pointer to current device structure | |
313 | struct comedi_subdevice *s Pointer to current subdevice structure | |
314 | struct comedi_insn *insn Pointer to current comedi instruction | |
315 | unsigned int *data Pointer to analogue output data | |
96341f71 | 316 | |
a622afcb | 317 | Returns:int Nmuber of instructions executed |
96341f71 AS |
318 | |
319 | ============================================================================== | |
320 | */ | |
0a85b6f0 MT |
321 | static int icp_multi_insn_write_ao(struct comedi_device *dev, |
322 | struct comedi_subdevice *s, | |
323 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
324 | { |
325 | int n, chan, range, timeout; | |
326 | ||
b6c77757 | 327 | /* Disable D/A conversion ready interrupt */ |
96341f71 AS |
328 | devpriv->IntEnable &= ~DAC_READY; |
329 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
330 | ||
b6c77757 | 331 | /* Clear interrupt status */ |
96341f71 AS |
332 | devpriv->IntStatus |= DAC_READY; |
333 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
334 | ||
b6c77757 | 335 | /* Get channel number and range */ |
96341f71 AS |
336 | chan = CR_CHAN(insn->chanspec); |
337 | range = CR_RANGE(insn->chanspec); | |
338 | ||
b6c77757 BP |
339 | /* Set up range and channel data */ |
340 | /* Bit 4 = 1 : Bipolar */ | |
341 | /* Bit 5 = 0 : 5V */ | |
342 | /* Bit 5 = 1 : 10V */ | |
343 | /* Bits 8-9 : Channel number */ | |
96341f71 | 344 | devpriv->DacCmdStatus &= 0xfccf; |
d68a8635 | 345 | devpriv->DacCmdStatus |= range_codes_analog[range]; |
96341f71 AS |
346 | devpriv->DacCmdStatus |= (chan << 8); |
347 | ||
348 | writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
349 | ||
350 | for (n = 0; n < insn->n; n++) { | |
a622afcb DH |
351 | /* Wait for analogue output data register to be |
352 | * ready for new data, or get fed up waiting */ | |
96341f71 AS |
353 | timeout = 100; |
354 | while (timeout--) { | |
355 | if (!(readw(devpriv->io_addr + | |
0a85b6f0 | 356 | ICP_MULTI_DAC_CSR) & DAC_BSY)) |
96341f71 AS |
357 | goto dac_ready; |
358 | ||
5f74ea14 | 359 | udelay(1); |
96341f71 AS |
360 | } |
361 | ||
b6c77757 | 362 | /* If we reach here, a timeout has occurred */ |
96341f71 AS |
363 | comedi_error(dev, "D/A insn timeout"); |
364 | ||
b6c77757 | 365 | /* Disable interrupt */ |
96341f71 AS |
366 | devpriv->IntEnable &= ~DAC_READY; |
367 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
368 | ||
b6c77757 | 369 | /* Clear interrupt status */ |
96341f71 AS |
370 | devpriv->IntStatus |= DAC_READY; |
371 | writew(devpriv->IntStatus, | |
0a85b6f0 | 372 | devpriv->io_addr + ICP_MULTI_INT_STAT); |
96341f71 | 373 | |
b6c77757 | 374 | /* Clear data received */ |
96341f71 AS |
375 | devpriv->ao_data[chan] = 0; |
376 | ||
96341f71 AS |
377 | return -ETIME; |
378 | ||
0a85b6f0 | 379 | dac_ready: |
b6c77757 | 380 | /* Write data to analogue output data register */ |
96341f71 AS |
381 | writew(data[n], devpriv->io_addr + ICP_MULTI_AO); |
382 | ||
b6c77757 | 383 | /* Set DAC_ST bit to write the data to selected channel */ |
96341f71 AS |
384 | devpriv->DacCmdStatus |= DAC_ST; |
385 | writew(devpriv->DacCmdStatus, | |
0a85b6f0 | 386 | devpriv->io_addr + ICP_MULTI_DAC_CSR); |
96341f71 AS |
387 | devpriv->DacCmdStatus &= ~DAC_ST; |
388 | ||
b6c77757 | 389 | /* Save analogue output data */ |
96341f71 AS |
390 | devpriv->ao_data[chan] = data[n]; |
391 | } | |
392 | ||
96341f71 AS |
393 | return n; |
394 | } | |
395 | ||
396 | /* | |
397 | ============================================================================== | |
398 | ||
a622afcb | 399 | Name: icp_multi_insn_read_ao |
96341f71 | 400 | |
a622afcb DH |
401 | Description: |
402 | This function reads a single analogue output. | |
96341f71 | 403 | |
a622afcb DH |
404 | Parameters: |
405 | struct comedi_device *dev Pointer to current device structure | |
406 | struct comedi_subdevice *s Pointer to current subdevice structure | |
407 | struct comedi_insn *insn Pointer to current comedi instruction | |
408 | unsigned int *data Pointer to analogue output data | |
96341f71 | 409 | |
a622afcb | 410 | Returns:int Nmuber of instructions executed |
96341f71 AS |
411 | |
412 | ============================================================================== | |
413 | */ | |
0a85b6f0 MT |
414 | static int icp_multi_insn_read_ao(struct comedi_device *dev, |
415 | struct comedi_subdevice *s, | |
416 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
417 | { |
418 | int n, chan; | |
419 | ||
b6c77757 | 420 | /* Get channel number */ |
96341f71 AS |
421 | chan = CR_CHAN(insn->chanspec); |
422 | ||
b6c77757 | 423 | /* Read analogue outputs */ |
96341f71 AS |
424 | for (n = 0; n < insn->n; n++) |
425 | data[n] = devpriv->ao_data[chan]; | |
426 | ||
427 | return n; | |
428 | } | |
429 | ||
430 | /* | |
431 | ============================================================================== | |
432 | ||
a622afcb | 433 | Name: icp_multi_insn_bits_di |
96341f71 | 434 | |
a622afcb DH |
435 | Description: |
436 | This function reads the digital inputs. | |
96341f71 | 437 | |
a622afcb DH |
438 | Parameters: |
439 | struct comedi_device *dev Pointer to current device structure | |
440 | struct comedi_subdevice *s Pointer to current subdevice structure | |
441 | struct comedi_insn *insn Pointer to current comedi instruction | |
442 | unsigned int *data Pointer to analogue output data | |
96341f71 | 443 | |
a622afcb | 444 | Returns:int Nmuber of instructions executed |
96341f71 AS |
445 | |
446 | ============================================================================== | |
447 | */ | |
0a85b6f0 MT |
448 | static int icp_multi_insn_bits_di(struct comedi_device *dev, |
449 | struct comedi_subdevice *s, | |
450 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
451 | { |
452 | data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); | |
453 | ||
a2714e3e | 454 | return insn->n; |
96341f71 AS |
455 | } |
456 | ||
457 | /* | |
458 | ============================================================================== | |
459 | ||
a622afcb | 460 | Name: icp_multi_insn_bits_do |
96341f71 | 461 | |
a622afcb DH |
462 | Description: |
463 | This function writes the appropriate digital outputs. | |
96341f71 | 464 | |
a622afcb DH |
465 | Parameters: |
466 | struct comedi_device *dev Pointer to current device structure | |
467 | struct comedi_subdevice *s Pointer to current subdevice structure | |
468 | struct comedi_insn *insn Pointer to current comedi instruction | |
469 | unsigned int *data Pointer to analogue output data | |
96341f71 | 470 | |
a622afcb | 471 | Returns:int Nmuber of instructions executed |
96341f71 AS |
472 | |
473 | ============================================================================== | |
474 | */ | |
0a85b6f0 MT |
475 | static int icp_multi_insn_bits_do(struct comedi_device *dev, |
476 | struct comedi_subdevice *s, | |
477 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 | 478 | { |
96341f71 AS |
479 | if (data[0]) { |
480 | s->state &= ~data[0]; | |
481 | s->state |= (data[0] & data[1]); | |
482 | ||
ca5edf2f | 483 | printk(KERN_DEBUG "Digital outputs = %4x \n", s->state); |
96341f71 AS |
484 | |
485 | writew(s->state, devpriv->io_addr + ICP_MULTI_DO); | |
486 | } | |
487 | ||
488 | data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); | |
489 | ||
a2714e3e | 490 | return insn->n; |
96341f71 AS |
491 | } |
492 | ||
493 | /* | |
494 | ============================================================================== | |
495 | ||
a622afcb | 496 | Name: icp_multi_insn_read_ctr |
96341f71 | 497 | |
a622afcb DH |
498 | Description: |
499 | This function reads the specified counter. | |
96341f71 | 500 | |
a622afcb DH |
501 | Parameters: |
502 | struct comedi_device *dev Pointer to current device structure | |
503 | struct comedi_subdevice *s Pointer to current subdevice structure | |
504 | struct comedi_insn *insn Pointer to current comedi instruction | |
505 | unsigned int *data Pointer to counter data | |
96341f71 | 506 | |
a622afcb | 507 | Returns:int Nmuber of instructions executed |
96341f71 AS |
508 | |
509 | ============================================================================== | |
510 | */ | |
0a85b6f0 MT |
511 | static int icp_multi_insn_read_ctr(struct comedi_device *dev, |
512 | struct comedi_subdevice *s, | |
513 | struct comedi_insn *insn, unsigned int *data) | |
96341f71 AS |
514 | { |
515 | return 0; | |
516 | } | |
517 | ||
518 | /* | |
519 | ============================================================================== | |
520 | ||
a622afcb | 521 | Name: icp_multi_insn_write_ctr |
96341f71 | 522 | |
a622afcb DH |
523 | Description: |
524 | This function write to the specified counter. | |
96341f71 | 525 | |
a622afcb DH |
526 | Parameters: |
527 | struct comedi_device *dev Pointer to current device structure | |
528 | struct comedi_subdevice *s Pointer to current subdevice structure | |
529 | struct comedi_insn *insn Pointer to current comedi instruction | |
530 | unsigned int *data Pointer to counter data | |
96341f71 | 531 | |
a622afcb | 532 | Returns:int Nmuber of instructions executed |
96341f71 AS |
533 | |
534 | ============================================================================== | |
535 | */ | |
0a85b6f0 MT |
536 | static int icp_multi_insn_write_ctr(struct comedi_device *dev, |
537 | struct comedi_subdevice *s, | |
538 | struct comedi_insn *insn, | |
539 | unsigned int *data) | |
96341f71 AS |
540 | { |
541 | return 0; | |
542 | } | |
543 | ||
544 | /* | |
545 | ============================================================================== | |
546 | ||
a622afcb | 547 | Name: interrupt_service_icp_multi |
96341f71 | 548 | |
a622afcb DH |
549 | Description: |
550 | This function is the interrupt service routine for all | |
551 | interrupts generated by the icp multi board. | |
96341f71 | 552 | |
a622afcb DH |
553 | Parameters: |
554 | int irq | |
555 | void *d Pointer to current device | |
96341f71 AS |
556 | |
557 | ============================================================================== | |
558 | */ | |
70265d24 | 559 | static irqreturn_t interrupt_service_icp_multi(int irq, void *d) |
96341f71 | 560 | { |
71b5f4f1 | 561 | struct comedi_device *dev = d; |
96341f71 AS |
562 | int int_no; |
563 | ||
b6c77757 | 564 | /* Is this interrupt from our board? */ |
96341f71 AS |
565 | int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; |
566 | if (!int_no) | |
b6c77757 | 567 | /* No, exit */ |
96341f71 AS |
568 | return IRQ_NONE; |
569 | ||
b6c77757 | 570 | /* Determine which interrupt is active & handle it */ |
96341f71 AS |
571 | switch (int_no) { |
572 | case ADC_READY: | |
573 | break; | |
574 | case DAC_READY: | |
575 | break; | |
576 | case DOUT_ERROR: | |
577 | break; | |
578 | case DIN_STATUS: | |
579 | break; | |
580 | case CIE0: | |
581 | break; | |
582 | case CIE1: | |
583 | break; | |
584 | case CIE2: | |
585 | break; | |
586 | case CIE3: | |
587 | break; | |
588 | default: | |
589 | break; | |
590 | ||
591 | } | |
592 | ||
96341f71 AS |
593 | return IRQ_HANDLED; |
594 | } | |
595 | ||
596 | #if 0 | |
597 | /* | |
598 | ============================================================================== | |
599 | ||
a622afcb | 600 | Name: check_channel_list |
96341f71 | 601 | |
a622afcb DH |
602 | Description: |
603 | This function checks if the channel list, provided by user | |
604 | is built correctly | |
96341f71 | 605 | |
a622afcb | 606 | Parameters: |
25985edc | 607 | struct comedi_device *dev Pointer to current service structure |
a622afcb DH |
608 | struct comedi_subdevice *s Pointer to current subdevice structure |
609 | unsigned int *chanlist Pointer to packed channel list | |
610 | unsigned int n_chan Number of channels to scan | |
96341f71 | 611 | |
a622afcb DH |
612 | Returns:int 0 = failure |
613 | 1 = success | |
96341f71 AS |
614 | |
615 | ============================================================================== | |
616 | */ | |
0a85b6f0 MT |
617 | static int check_channel_list(struct comedi_device *dev, |
618 | struct comedi_subdevice *s, | |
619 | unsigned int *chanlist, unsigned int n_chan) | |
96341f71 AS |
620 | { |
621 | unsigned int i; | |
622 | ||
b6c77757 | 623 | /* Check that we at least have one channel to check */ |
96341f71 AS |
624 | if (n_chan < 1) { |
625 | comedi_error(dev, "range/channel list is empty!"); | |
626 | return 0; | |
627 | } | |
b6c77757 | 628 | /* Check all channels */ |
96341f71 | 629 | for (i = 0; i < n_chan; i++) { |
b6c77757 | 630 | /* Check that channel number is < maximum */ |
96341f71 | 631 | if (CR_AREF(chanlist[i]) == AREF_DIFF) { |
08567ce9 | 632 | if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) { |
96341f71 | 633 | comedi_error(dev, |
ca5edf2f | 634 | "Incorrect differential ai ch-nr"); |
96341f71 AS |
635 | return 0; |
636 | } | |
637 | } else { | |
281ecb06 | 638 | if (CR_CHAN(chanlist[i]) > s->n_chan) { |
96341f71 | 639 | comedi_error(dev, |
0a85b6f0 | 640 | "Incorrect ai channel number"); |
96341f71 AS |
641 | return 0; |
642 | } | |
643 | } | |
644 | } | |
645 | return 1; | |
646 | } | |
647 | #endif | |
648 | ||
649 | /* | |
650 | ============================================================================== | |
651 | ||
a622afcb | 652 | Name: icp_multi_reset |
96341f71 | 653 | |
a622afcb DH |
654 | Description: |
655 | This function resets the icp multi device to a 'safe' state | |
96341f71 | 656 | |
a622afcb | 657 | Parameters: |
25985edc | 658 | struct comedi_device *dev Pointer to current service structure |
96341f71 | 659 | |
a622afcb | 660 | Returns:int 0 = success |
96341f71 AS |
661 | |
662 | ============================================================================== | |
663 | */ | |
71b5f4f1 | 664 | static int icp_multi_reset(struct comedi_device *dev) |
96341f71 AS |
665 | { |
666 | unsigned int i; | |
667 | ||
b6c77757 | 668 | /* Clear INT enables and requests */ |
96341f71 AS |
669 | writew(0, devpriv->io_addr + ICP_MULTI_INT_EN); |
670 | writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
671 | ||
fafe91a8 HS |
672 | /* Set DACs to 0..5V range and 0V output */ |
673 | for (i = 0; i < 4; i++) { | |
674 | devpriv->DacCmdStatus &= 0xfcce; | |
96341f71 | 675 | |
fafe91a8 HS |
676 | /* Set channel number */ |
677 | devpriv->DacCmdStatus |= (i << 8); | |
96341f71 | 678 | |
fafe91a8 HS |
679 | /* Output 0V */ |
680 | writew(0, devpriv->io_addr + ICP_MULTI_AO); | |
96341f71 | 681 | |
fafe91a8 HS |
682 | /* Set start conversion bit */ |
683 | devpriv->DacCmdStatus |= DAC_ST; | |
96341f71 | 684 | |
fafe91a8 HS |
685 | /* Output to command / status register */ |
686 | writew(devpriv->DacCmdStatus, | |
687 | devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
96341f71 | 688 | |
fafe91a8 HS |
689 | /* Delay to allow DAC time to recover */ |
690 | udelay(1); | |
691 | } | |
692 | ||
693 | /* Digital outputs to 0 */ | |
96341f71 AS |
694 | writew(0, devpriv->io_addr + ICP_MULTI_DO); |
695 | ||
96341f71 AS |
696 | return 0; |
697 | } | |
698 | ||
0a85b6f0 MT |
699 | static int icp_multi_attach(struct comedi_device *dev, |
700 | struct comedi_devconfig *it) | |
96341f71 | 701 | { |
34c43922 | 702 | struct comedi_subdevice *s; |
12b4a097 | 703 | int ret; |
96341f71 AS |
704 | unsigned int irq; |
705 | struct pcilst_struct *card = NULL; | |
706 | resource_size_t io_addr[5], iobase; | |
707 | unsigned char pci_bus, pci_slot, pci_func; | |
708 | ||
ca5edf2f DH |
709 | printk(KERN_WARNING |
710 | "icp_multi EDBG: BGN: icp_multi_attach(...)\n"); | |
96341f71 | 711 | |
bc04bec0 | 712 | /* Allocate private data storage space */ |
52bfe6c8 | 713 | ret = alloc_private(dev, sizeof(struct icp_multi_private)); |
197c82bf | 714 | if (ret < 0) |
96341f71 AS |
715 | return ret; |
716 | ||
b6c77757 | 717 | /* Initialise list of PCI cards in system, if not already done so */ |
88886662 HS |
718 | if (pci_list_builded++ == 0) |
719 | pci_card_list_init(PCI_VENDOR_ID_ICP, 0); | |
96341f71 | 720 | |
ca5edf2f DH |
721 | printk(KERN_WARNING |
722 | "Anne's comedi%d: icp_multi: board=%s", dev->minor, | |
0a85b6f0 | 723 | this_board->name); |
96341f71 | 724 | |
197c82bf BP |
725 | card = select_and_alloc_pci_card(PCI_VENDOR_ID_ICP, |
726 | this_board->device_id, it->options[0], | |
727 | it->options[1]); | |
728 | ||
729 | if (card == NULL) | |
96341f71 AS |
730 | return -EIO; |
731 | ||
732 | devpriv->card = card; | |
733 | ||
734 | if ((pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0], | |
0a85b6f0 | 735 | &irq)) < 0) { |
ca5edf2f | 736 | printk(KERN_WARNING " - Can't get configuration data!\n"); |
96341f71 AS |
737 | return -EIO; |
738 | } | |
739 | ||
740 | iobase = io_addr[2]; | |
741 | devpriv->phys_iobase = iobase; | |
742 | ||
ca5edf2f DH |
743 | printk(KERN_WARNING |
744 | ", b:s:f=%d:%d:%d, io=0x%8llx \n", pci_bus, pci_slot, pci_func, | |
0a85b6f0 | 745 | (unsigned long long)iobase); |
96341f71 AS |
746 | |
747 | devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE); | |
748 | ||
749 | if (devpriv->io_addr == NULL) { | |
ca5edf2f | 750 | printk(KERN_WARNING "ioremap failed.\n"); |
96341f71 AS |
751 | return -ENOMEM; |
752 | } | |
96341f71 AS |
753 | |
754 | dev->board_name = this_board->name; | |
755 | ||
12b4a097 | 756 | ret = comedi_alloc_subdevices(dev, 5); |
8b6c5694 | 757 | if (ret) |
96341f71 | 758 | return ret; |
96341f71 AS |
759 | |
760 | icp_multi_reset(dev); | |
761 | ||
aa1b2cb3 HS |
762 | if (irq) { |
763 | if (request_irq(irq, interrupt_service_icp_multi, | |
764 | IRQF_SHARED, "Inova Icp Multi", dev)) { | |
765 | printk(KERN_WARNING | |
766 | "unable to allocate IRQ %u, DISABLING IT", | |
767 | irq); | |
768 | irq = 0; /* Can't use IRQ */ | |
96341f71 | 769 | } else |
aa1b2cb3 | 770 | printk(KERN_WARNING ", irq=%u", irq); |
96341f71 | 771 | } else |
aa1b2cb3 | 772 | printk(KERN_WARNING ", IRQ disabled"); |
96341f71 AS |
773 | |
774 | dev->irq = irq; | |
775 | ||
ca5edf2f | 776 | printk(KERN_WARNING ".\n"); |
96341f71 | 777 | |
12b4a097 | 778 | s = &dev->subdevices[0]; |
281ecb06 HS |
779 | dev->read_subdev = s; |
780 | s->type = COMEDI_SUBD_AI; | |
08567ce9 | 781 | s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; |
281ecb06 | 782 | s->n_chan = 16; |
efac035c | 783 | s->maxdata = 0x0fff; |
281ecb06 | 784 | s->len_chanlist = 16; |
abdeac3f | 785 | s->range_table = &range_analog; |
281ecb06 | 786 | s->insn_read = icp_multi_insn_read_ai; |
96341f71 | 787 | |
12b4a097 | 788 | s = &dev->subdevices[1]; |
fafe91a8 HS |
789 | s->type = COMEDI_SUBD_AO; |
790 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
791 | s->n_chan = 4; | |
68b82e09 | 792 | s->maxdata = 0x0fff; |
fafe91a8 HS |
793 | s->len_chanlist = 4; |
794 | s->range_table = &range_analog; | |
795 | s->insn_write = icp_multi_insn_write_ao; | |
796 | s->insn_read = icp_multi_insn_read_ao; | |
96341f71 | 797 | |
12b4a097 | 798 | s = &dev->subdevices[2]; |
48f31251 HS |
799 | s->type = COMEDI_SUBD_DI; |
800 | s->subdev_flags = SDF_READABLE; | |
801 | s->n_chan = 16; | |
802 | s->maxdata = 1; | |
803 | s->len_chanlist = 16; | |
804 | s->range_table = &range_digital; | |
805 | s->io_bits = 0; | |
806 | s->insn_bits = icp_multi_insn_bits_di; | |
96341f71 | 807 | |
12b4a097 | 808 | s = &dev->subdevices[3]; |
2aa70705 HS |
809 | s->type = COMEDI_SUBD_DO; |
810 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
811 | s->n_chan = 8; | |
812 | s->maxdata = 1; | |
813 | s->len_chanlist = 8; | |
814 | s->range_table = &range_digital; | |
815 | s->io_bits = 0xff; | |
816 | s->state = 0; | |
817 | s->insn_bits = icp_multi_insn_bits_do; | |
96341f71 | 818 | |
12b4a097 | 819 | s = &dev->subdevices[4]; |
5b93da54 HS |
820 | s->type = COMEDI_SUBD_COUNTER; |
821 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
822 | s->n_chan = 4; | |
823 | s->maxdata = 0xffff; | |
824 | s->len_chanlist = 4; | |
825 | s->state = 0; | |
826 | s->insn_read = icp_multi_insn_read_ctr; | |
827 | s->insn_write = icp_multi_insn_write_ctr; | |
96341f71 AS |
828 | |
829 | devpriv->valid = 1; | |
830 | ||
96341f71 AS |
831 | return 0; |
832 | } | |
833 | ||
484ecc95 | 834 | static void icp_multi_detach(struct comedi_device *dev) |
96341f71 | 835 | { |
96341f71 AS |
836 | if (dev->private) |
837 | if (devpriv->valid) | |
838 | icp_multi_reset(dev); | |
96341f71 | 839 | if (dev->irq) |
5f74ea14 | 840 | free_irq(dev->irq, dev); |
96341f71 AS |
841 | if (dev->private && devpriv->io_addr) |
842 | iounmap(devpriv->io_addr); | |
96341f71 AS |
843 | if (dev->private && devpriv->card) |
844 | pci_card_free(devpriv->card); | |
82675f35 | 845 | if (--pci_list_builded == 0) |
96341f71 | 846 | pci_card_list_cleanup(PCI_VENDOR_ID_ICP); |
96341f71 | 847 | } |
90f703d3 | 848 | |
d79fc8e1 HS |
849 | static const struct boardtype boardtypes[] = { |
850 | { | |
851 | .name = "icp_multi", | |
554e02c9 | 852 | .device_id = PCI_DEVICE_ID_ICP_MULTI, |
d79fc8e1 HS |
853 | }, |
854 | }; | |
855 | ||
856 | static struct comedi_driver icp_multi_driver = { | |
857 | .driver_name = "icp_multi", | |
858 | .module = THIS_MODULE, | |
859 | .attach = icp_multi_attach, | |
860 | .detach = icp_multi_detach, | |
861 | .num_names = ARRAY_SIZE(boardtypes), | |
862 | .board_name = &boardtypes[0].name, | |
863 | .offset = sizeof(struct boardtype), | |
864 | }; | |
554e02c9 HS |
865 | |
866 | static int __devinit icp_multi_pci_probe(struct pci_dev *dev, | |
867 | const struct pci_device_id *ent) | |
868 | { | |
869 | return comedi_pci_auto_config(dev, &icp_multi_driver); | |
870 | } | |
871 | ||
872 | static void __devexit icp_multi_pci_remove(struct pci_dev *dev) | |
873 | { | |
874 | comedi_pci_auto_unconfig(dev); | |
875 | } | |
876 | ||
877 | static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = { | |
878 | { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) }, | |
879 | { 0 } | |
880 | }; | |
881 | MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); | |
882 | ||
883 | static struct pci_driver icp_multi_pci_driver = { | |
884 | .name = "icp_multi", | |
885 | .id_table = icp_multi_pci_table, | |
886 | .probe = icp_multi_pci_probe, | |
887 | .remove = __devexit_p(icp_multi_pci_remove), | |
888 | }; | |
889 | module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); | |
d79fc8e1 | 890 | |
90f703d3 AT |
891 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
892 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
893 | MODULE_LICENSE("GPL"); |