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