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