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