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 | ||
64 | // Hardware types of the cards | |
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 | ||
84 | // Define bits from ADC command/status register | |
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 | ||
91 | // Define bits from DAC command/status register | |
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 | ||
97 | // Define bits from interrupt enable/status registers | |
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 | ||
107 | // Useful definitions | |
108 | #define Status_IRQ 0x00ff // All interrupts | |
109 | ||
110 | // Define analogue range | |
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 | */ | |
126 | static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it); | |
127 | static int icp_multi_detach(comedi_device * dev); | |
128 | ||
129 | /* | |
130 | ============================================================================== | |
131 | Data & Structure declarations | |
132 | ============================================================================== | |
133 | */ | |
134 | static unsigned short pci_list_builded = 0; /*>0 list of card is known */ | |
135 | ||
136 | typedef struct { | |
137 | const char *name; // driver name | |
138 | int device_id; | |
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 | |
153 | } boardtype; | |
154 | ||
155 | static const boardtype boardtypes[] = { | |
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 | |
172 | }; | |
173 | ||
174 | #define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype)) | |
175 | ||
176 | static comedi_driver driver_icp_multi = { | |
177 | driver_name:"icp_multi", | |
178 | module:THIS_MODULE, | |
179 | attach:icp_multi_attach, | |
180 | detach:icp_multi_detach, | |
181 | num_names:n_boardtypes, | |
182 | board_name:&boardtypes[0].name, | |
183 | offset:sizeof(boardtype), | |
184 | }; | |
185 | ||
186 | COMEDI_INITCLEANUP(driver_icp_multi); | |
187 | ||
188 | typedef struct { | |
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 | |
205 | } icp_multi_private; | |
206 | ||
207 | #define devpriv ((icp_multi_private *)dev->private) | |
208 | #define this_board ((const boardtype *)dev->board_ptr) | |
209 | ||
210 | /* | |
211 | ============================================================================== | |
212 | More forward declarations | |
213 | ============================================================================== | |
214 | */ | |
215 | ||
216 | #if 0 | |
217 | static int check_channel_list(comedi_device * dev, comedi_subdevice * s, | |
218 | unsigned int *chanlist, unsigned int n_chan); | |
219 | #endif | |
220 | static void setup_channel_list(comedi_device * dev, comedi_subdevice * s, | |
221 | unsigned int *chanlist, unsigned int n_chan); | |
222 | static int icp_multi_reset(comedi_device * dev); | |
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 | */ | |
248 | static int icp_multi_insn_read_ai(comedi_device * dev, comedi_subdevice * s, | |
249 | comedi_insn * insn, lsampl_t * data) | |
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 | |
256 | // Disable A/D conversion ready interrupt | |
257 | devpriv->IntEnable &= ~ADC_READY; | |
258 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
259 | ||
260 | // Clear interrupt status | |
261 | devpriv->IntStatus |= ADC_READY; | |
262 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
263 | ||
264 | // Set up appropriate channel, mode and range data, for specified channel | |
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++) { | |
274 | // Set start ADC bit | |
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 | ||
292 | // Wait for conversion to complete, or get fed up waiting | |
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 | ||
310 | // If we reach here, a timeout has occurred | |
311 | comedi_error(dev, "A/D insn timeout"); | |
312 | ||
313 | // Disable interrupt | |
314 | devpriv->IntEnable &= ~ADC_READY; | |
315 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
316 | ||
317 | // Clear interrupt status | |
318 | devpriv->IntStatus |= ADC_READY; | |
319 | writew(devpriv->IntStatus, | |
320 | devpriv->io_addr + ICP_MULTI_INT_STAT); | |
321 | ||
322 | // Clear data received | |
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 | ||
335 | // Disable interrupt | |
336 | devpriv->IntEnable &= ~ADC_READY; | |
337 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
338 | ||
339 | // Clear interrupt status | |
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 | */ | |
367 | static int icp_multi_insn_write_ao(comedi_device * dev, comedi_subdevice * s, | |
368 | comedi_insn * insn, lsampl_t * data) | |
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 | |
375 | // Disable D/A conversion ready interrupt | |
376 | devpriv->IntEnable &= ~DAC_READY; | |
377 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
378 | ||
379 | // Clear interrupt status | |
380 | devpriv->IntStatus |= DAC_READY; | |
381 | writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); | |
382 | ||
383 | // Get channel number and range | |
384 | chan = CR_CHAN(insn->chanspec); | |
385 | range = CR_RANGE(insn->chanspec); | |
386 | ||
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 | |
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++) { | |
399 | // Wait for analogue output data register to be ready for new data, or get fed up waiting | |
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 | ||
417 | // If we reach here, a timeout has occurred | |
418 | comedi_error(dev, "D/A insn timeout"); | |
419 | ||
420 | // Disable interrupt | |
421 | devpriv->IntEnable &= ~DAC_READY; | |
422 | writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); | |
423 | ||
424 | // Clear interrupt status | |
425 | devpriv->IntStatus |= DAC_READY; | |
426 | writew(devpriv->IntStatus, | |
427 | devpriv->io_addr + ICP_MULTI_INT_STAT); | |
428 | ||
429 | // Clear data received | |
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: | |
438 | // Write data to analogue output data register | |
439 | writew(data[n], devpriv->io_addr + ICP_MULTI_AO); | |
440 | ||
441 | // Set DAC_ST bit to write the data to selected channel | |
442 | devpriv->DacCmdStatus |= DAC_ST; | |
443 | writew(devpriv->DacCmdStatus, | |
444 | devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
445 | devpriv->DacCmdStatus &= ~DAC_ST; | |
446 | ||
447 | // Save analogue output data | |
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 | */ | |
475 | static int icp_multi_insn_read_ao(comedi_device * dev, comedi_subdevice * s, | |
476 | comedi_insn * insn, lsampl_t * data) | |
477 | { | |
478 | int n, chan; | |
479 | ||
480 | // Get channel number | |
481 | chan = CR_CHAN(insn->chanspec); | |
482 | ||
483 | // Read analogue outputs | |
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 | */ | |
508 | static int icp_multi_insn_bits_di(comedi_device * dev, comedi_subdevice * s, | |
509 | comedi_insn * insn, lsampl_t * data) | |
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 | */ | |
534 | static int icp_multi_insn_bits_do(comedi_device * dev, comedi_subdevice * s, | |
535 | comedi_insn * insn, lsampl_t * data) | |
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 | */ | |
576 | static int icp_multi_insn_read_ctr(comedi_device * dev, comedi_subdevice * s, | |
577 | comedi_insn * insn, lsampl_t * data) | |
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 | */ | |
600 | static int icp_multi_insn_write_ctr(comedi_device * dev, comedi_subdevice * s, | |
601 | comedi_insn * insn, lsampl_t * data) | |
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 | ||
631 | // Is this interrupt from our board? | |
632 | int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; | |
633 | if (!int_no) | |
634 | // No, exit | |
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 | ||
642 | // Determine which interrupt is active & handle it | |
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 | */ | |
692 | static int check_channel_list(comedi_device * dev, comedi_subdevice * s, | |
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 | |
700 | // Check that we at least have one channel to check | |
701 | if (n_chan < 1) { | |
702 | comedi_error(dev, "range/channel list is empty!"); | |
703 | return 0; | |
704 | } | |
705 | // Check all channels | |
706 | for (i = 0; i < n_chan; i++) { | |
707 | // Check that channel number is < maximum | |
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 | */ | |
746 | static void setup_channel_list(comedi_device * dev, comedi_subdevice * s, | |
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++) { | |
759 | // Get channel | |
760 | chanprog = CR_CHAN(chanlist[i]); | |
761 | ||
762 | // Determine if it is a differential channel (Bit 15 = 1) | |
763 | if (CR_AREF(chanlist[i]) == AREF_DIFF) { | |
764 | diff = 1; | |
765 | chanprog &= 0x0007; | |
766 | } else { | |
767 | diff = 0; | |
768 | chanprog &= 0x000f; | |
769 | } | |
770 | ||
771 | // Clear channel, range and input mode bits in A/D command/status register | |
772 | devpriv->AdcCmdStatus &= 0xf00f; | |
773 | ||
774 | // Set channel number and differential mode status bit | |
775 | if (diff) { | |
776 | // Set channel number, bits 9-11 & mode, bit 6 | |
777 | devpriv->AdcCmdStatus |= (chanprog << 9); | |
778 | devpriv->AdcCmdStatus |= ADC_DI; | |
779 | } else | |
780 | // Set channel number, bits 8-11 | |
781 | devpriv->AdcCmdStatus |= (chanprog << 8); | |
782 | ||
783 | // Get range for current channel | |
784 | range = this_board->rangecode[CR_RANGE(chanlist[i])]; | |
785 | // Set range. bits 4-5 | |
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 | */ | |
815 | static int icp_multi_reset(comedi_device * dev) | |
816 | { | |
817 | unsigned int i; | |
818 | ||
819 | #ifdef ICP_MULTI_EXTDEBUG | |
820 | printk("icp_multi EDBG: BGN: icp_multi_reset(...)\n"); | |
821 | #endif | |
822 | // Clear INT enables and requests | |
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) | |
827 | // Set DACs to 0..5V range and 0V output | |
828 | for (i = 0; i < this_board->n_aochan; i++) { | |
829 | devpriv->DacCmdStatus &= 0xfcce; | |
830 | ||
831 | // Set channel number | |
832 | devpriv->DacCmdStatus |= (i << 8); | |
833 | ||
834 | // Output 0V | |
835 | writew(0, devpriv->io_addr + ICP_MULTI_AO); | |
836 | ||
837 | // Set start conversion bit | |
838 | devpriv->DacCmdStatus |= DAC_ST; | |
839 | ||
840 | // Output to command / status register | |
841 | writew(devpriv->DacCmdStatus, | |
842 | devpriv->io_addr + ICP_MULTI_DAC_CSR); | |
843 | ||
844 | // Delay to allow DAC time to recover | |
845 | comedi_udelay(1); | |
846 | } | |
847 | // Digital outputs to 0 | |
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 | */ | |
873 | static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it) | |
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 | ||
884 | // Alocate private data storage space | |
885 | if ((ret = alloc_private(dev, sizeof(icp_multi_private))) < 0) | |
886 | return ret; | |
887 | ||
888 | // Initialise list of PCI cards in system, if not already done so | |
889 | if (pci_list_builded++ == 0) { | |
890 | pci_card_list_init(PCI_VENDOR_ID_ICP, | |
891 | #ifdef ICP_MULTI_EXTDEBUG | |
892 | 1 | |
893 | #else | |
894 | 0 | |
895 | #endif | |
896 | ); | |
897 | } | |
898 | ||
899 | printk("Anne's comedi%d: icp_multi: board=%s", dev->minor, | |
900 | this_board->name); | |
901 | ||
902 | if ((card = select_and_alloc_pci_card(PCI_VENDOR_ID_ICP, | |
903 | this_board->device_id, it->options[0], | |
904 | it->options[1])) == NULL) | |
905 | return -EIO; | |
906 | ||
907 | devpriv->card = card; | |
908 | ||
909 | if ((pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0], | |
910 | &irq)) < 0) { | |
911 | printk(" - Can't get configuration data!\n"); | |
912 | return -EIO; | |
913 | } | |
914 | ||
915 | iobase = io_addr[2]; | |
916 | devpriv->phys_iobase = iobase; | |
917 | ||
918 | printk(", b:s:f=%d:%d:%d, io=0x%8llx \n", pci_bus, pci_slot, pci_func, | |
919 | (unsigned long long)iobase); | |
920 | ||
921 | devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE); | |
922 | ||
923 | if (devpriv->io_addr == NULL) { | |
924 | printk("ioremap failed.\n"); | |
925 | return -ENOMEM; | |
926 | } | |
927 | #ifdef ICP_MULTI_EXTDEBUG | |
928 | printk("0x%08llx mapped to %p, ", (unsigned long long)iobase, | |
929 | devpriv->io_addr); | |
930 | #endif | |
931 | ||
932 | dev->board_name = this_board->name; | |
933 | ||
934 | n_subdevices = 0; | |
935 | if (this_board->n_aichan) | |
936 | n_subdevices++; | |
937 | if (this_board->n_aochan) | |
938 | n_subdevices++; | |
939 | if (this_board->n_dichan) | |
940 | n_subdevices++; | |
941 | if (this_board->n_dochan) | |
942 | n_subdevices++; | |
943 | if (this_board->n_ctrs) | |
944 | n_subdevices++; | |
945 | ||
946 | if ((ret = alloc_subdevices(dev, n_subdevices)) < 0) { | |
947 | return ret; | |
948 | } | |
949 | ||
950 | icp_multi_reset(dev); | |
951 | ||
952 | if (this_board->have_irq) { | |
953 | if (irq) { | |
954 | if (comedi_request_irq(irq, interrupt_service_icp_multi, | |
955 | IRQF_SHARED, "Inova Icp Multi", dev)) { | |
956 | printk(", unable to allocate IRQ %u, DISABLING IT", irq); | |
957 | irq = 0; /* Can't use IRQ */ | |
958 | } else | |
959 | printk(", irq=%u", irq); | |
960 | } else | |
961 | printk(", IRQ disabled"); | |
962 | } else | |
963 | irq = 0; | |
964 | ||
965 | dev->irq = irq; | |
966 | ||
967 | printk(".\n"); | |
968 | ||
969 | subdev = 0; | |
970 | ||
971 | if (this_board->n_aichan) { | |
972 | s = dev->subdevices + subdev; | |
973 | dev->read_subdev = s; | |
974 | s->type = COMEDI_SUBD_AI; | |
975 | s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; | |
976 | if (this_board->n_aichand) | |
977 | s->subdev_flags |= SDF_DIFF; | |
978 | s->n_chan = this_board->n_aichan; | |
979 | s->maxdata = this_board->ai_maxdata; | |
980 | s->len_chanlist = this_board->n_aichan; | |
981 | s->range_table = this_board->rangelist_ai; | |
982 | s->insn_read = icp_multi_insn_read_ai; | |
983 | subdev++; | |
984 | } | |
985 | ||
986 | if (this_board->n_aochan) { | |
987 | s = dev->subdevices + subdev; | |
988 | s->type = COMEDI_SUBD_AO; | |
989 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
990 | s->n_chan = this_board->n_aochan; | |
991 | s->maxdata = this_board->ao_maxdata; | |
992 | s->len_chanlist = this_board->n_aochan; | |
993 | s->range_table = this_board->rangelist_ao; | |
994 | s->insn_write = icp_multi_insn_write_ao; | |
995 | s->insn_read = icp_multi_insn_read_ao; | |
996 | subdev++; | |
997 | } | |
998 | ||
999 | if (this_board->n_dichan) { | |
1000 | s = dev->subdevices + subdev; | |
1001 | s->type = COMEDI_SUBD_DI; | |
1002 | s->subdev_flags = SDF_READABLE; | |
1003 | s->n_chan = this_board->n_dichan; | |
1004 | s->maxdata = 1; | |
1005 | s->len_chanlist = this_board->n_dichan; | |
1006 | s->range_table = &range_digital; | |
1007 | s->io_bits = 0; | |
1008 | s->insn_bits = icp_multi_insn_bits_di; | |
1009 | subdev++; | |
1010 | } | |
1011 | ||
1012 | if (this_board->n_dochan) { | |
1013 | s = dev->subdevices + subdev; | |
1014 | s->type = COMEDI_SUBD_DO; | |
1015 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
1016 | s->n_chan = this_board->n_dochan; | |
1017 | s->maxdata = 1; | |
1018 | s->len_chanlist = this_board->n_dochan; | |
1019 | s->range_table = &range_digital; | |
1020 | s->io_bits = (1 << this_board->n_dochan) - 1; | |
1021 | s->state = 0; | |
1022 | s->insn_bits = icp_multi_insn_bits_do; | |
1023 | subdev++; | |
1024 | } | |
1025 | ||
1026 | if (this_board->n_ctrs) { | |
1027 | s = dev->subdevices + subdev; | |
1028 | s->type = COMEDI_SUBD_COUNTER; | |
1029 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; | |
1030 | s->n_chan = this_board->n_ctrs; | |
1031 | s->maxdata = 0xffff; | |
1032 | s->len_chanlist = this_board->n_ctrs; | |
1033 | s->state = 0; | |
1034 | s->insn_read = icp_multi_insn_read_ctr; | |
1035 | s->insn_write = icp_multi_insn_write_ctr; | |
1036 | subdev++; | |
1037 | } | |
1038 | ||
1039 | devpriv->valid = 1; | |
1040 | ||
1041 | #ifdef ICP_MULTI_EXTDEBUG | |
1042 | printk("icp multi EDBG: END: icp_multi_attach(...)\n"); | |
1043 | #endif | |
1044 | ||
1045 | return 0; | |
1046 | } | |
1047 | ||
1048 | /* | |
1049 | ============================================================================== | |
1050 | ||
1051 | Name: icp_multi_detach | |
1052 | ||
1053 | Description: | |
1054 | This function releases all the resources used by the current | |
1055 | device. | |
1056 | ||
1057 | Parameters: | |
1058 | comedi_device *dev Pointer to current device structure | |
1059 | ||
1060 | Returns:int 0 = success | |
1061 | ||
1062 | ============================================================================== | |
1063 | */ | |
1064 | static int icp_multi_detach(comedi_device * dev) | |
1065 | { | |
1066 | ||
1067 | if (dev->private) | |
1068 | if (devpriv->valid) | |
1069 | icp_multi_reset(dev); | |
1070 | ||
1071 | if (dev->irq) | |
1072 | comedi_free_irq(dev->irq, dev); | |
1073 | ||
1074 | if (dev->private && devpriv->io_addr) | |
1075 | iounmap(devpriv->io_addr); | |
1076 | ||
1077 | if (dev->private && devpriv->card) | |
1078 | pci_card_free(devpriv->card); | |
1079 | ||
1080 | if (--pci_list_builded == 0) { | |
1081 | pci_card_list_cleanup(PCI_VENDOR_ID_ICP); | |
1082 | } | |
1083 | ||
1084 | return 0; | |
1085 | } |