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