]>
Commit | Line | Data |
---|---|---|
8cb9b9fb EP |
1 | /* |
2 | ||
3 | comedi/drivers/adl_pci9111.c | |
4 | ||
5 | Hardware driver for PCI9111 ADLink cards: | |
6 | ||
7 | PCI-9111HR | |
8 | ||
9 | Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> | |
10 | ||
11 | This program is free software; you can redistribute it and/or modify | |
12 | it under the terms of the GNU General Public License as published by | |
13 | the Free Software Foundation; either version 2 of the License, or | |
14 | (at your option) any later version. | |
15 | ||
16 | This program is distributed in the hope that it will be useful, | |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | GNU General Public License for more details. | |
20 | ||
21 | You should have received a copy of the GNU General Public License | |
22 | along with this program; if not, write to the Free Software | |
23 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
24 | */ | |
25 | ||
26 | /* | |
27 | Driver: adl_pci9111 | |
28 | Description: Adlink PCI-9111HR | |
29 | Author: Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> | |
30 | Devices: [ADLink] PCI-9111HR (adl_pci9111) | |
31 | Status: experimental | |
32 | ||
33 | Supports: | |
34 | ||
35 | - ai_insn read | |
36 | - ao_insn read/write | |
37 | - di_insn read | |
38 | - do_insn read/write | |
39 | - ai_do_cmd mode with the following sources: | |
40 | ||
41 | - start_src TRIG_NOW | |
42 | - scan_begin_src TRIG_FOLLOW TRIG_TIMER TRIG_EXT | |
43 | - convert_src TRIG_TIMER TRIG_EXT | |
44 | - scan_end_src TRIG_COUNT | |
45 | - stop_src TRIG_COUNT TRIG_NONE | |
46 | ||
47 | The scanned channels must be consecutive and start from 0. They must | |
48 | all have the same range and aref. | |
49 | ||
50 | Configuration options: | |
51 | ||
52 | [0] - PCI bus number (optional) | |
53 | [1] - PCI slot number (optional) | |
54 | ||
55 | If bus/slot is not specified, the first available PCI | |
56 | device will be used. | |
57 | ||
58 | */ | |
59 | ||
60 | /* | |
61 | CHANGELOG: | |
62 | ||
63 | 2005/02/17 Extend AI streaming capabilities. Now, scan_begin_arg can be | |
64 | a multiple of chanlist_len*convert_arg. | |
65 | 2002/02/19 Fixed the two's complement conversion in pci9111_(hr_)ai_get_data. | |
66 | 2002/02/18 Added external trigger support for analog input. | |
67 | ||
68 | TODO: | |
69 | ||
70 | - Really test implemented functionality. | |
71 | - Add support for the PCI-9111DG with a probe routine to identify the card type | |
72 | (perhaps with the help of the channel number readback of the A/D Data register). | |
73 | - Add external multiplexer support. | |
74 | ||
75 | */ | |
76 | ||
77 | #include "../comedidev.h" | |
78 | ||
79 | #include <linux/delay.h> | |
70265d24 | 80 | #include <linux/interrupt.h> |
8cb9b9fb EP |
81 | |
82 | #include "8253.h" | |
83 | #include "comedi_pci.h" | |
84 | #include "comedi_fc.h" | |
85 | ||
86 | #define PCI9111_DRIVER_NAME "adl_pci9111" | |
87 | #define PCI9111_HR_DEVICE_ID 0x9111 | |
88 | ||
52f8ac98 | 89 | /* TODO: Add other pci9111 board id */ |
8cb9b9fb EP |
90 | |
91 | #define PCI9111_IO_RANGE 0x0100 | |
92 | ||
93 | #define PCI9111_FIFO_HALF_SIZE 512 | |
94 | ||
95 | #define PCI9111_AI_CHANNEL_NBR 16 | |
96 | ||
97 | #define PCI9111_AI_RESOLUTION 12 | |
98 | #define PCI9111_AI_RESOLUTION_MASK 0x0FFF | |
99 | #define PCI9111_AI_RESOLUTION_2_CMP_BIT 0x0800 | |
100 | ||
101 | #define PCI9111_HR_AI_RESOLUTION 16 | |
102 | #define PCI9111_HR_AI_RESOLUTION_MASK 0xFFFF | |
103 | #define PCI9111_HR_AI_RESOLUTION_2_CMP_BIT 0x8000 | |
104 | ||
105 | #define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000 | |
106 | #define PCI9111_AO_CHANNEL_NBR 1 | |
107 | #define PCI9111_AO_RESOLUTION 12 | |
108 | #define PCI9111_AO_RESOLUTION_MASK 0x0FFF | |
109 | #define PCI9111_DI_CHANNEL_NBR 16 | |
110 | #define PCI9111_DO_CHANNEL_NBR 16 | |
111 | #define PCI9111_DO_MASK 0xFFFF | |
112 | ||
113 | #define PCI9111_RANGE_SETTING_DELAY 10 | |
114 | #define PCI9111_AI_INSTANT_READ_UDELAY_US 2 | |
115 | #define PCI9111_AI_INSTANT_READ_TIMEOUT 100 | |
116 | ||
117 | #define PCI9111_8254_CLOCK_PERIOD_NS 500 | |
118 | ||
119 | #define PCI9111_8254_COUNTER_0 0x00 | |
120 | #define PCI9111_8254_COUNTER_1 0x40 | |
121 | #define PCI9111_8254_COUNTER_2 0x80 | |
122 | #define PCI9111_8254_COUNTER_LATCH 0x00 | |
123 | #define PCI9111_8254_READ_LOAD_LSB_ONLY 0x10 | |
124 | #define PCI9111_8254_READ_LOAD_MSB_ONLY 0x20 | |
125 | #define PCI9111_8254_READ_LOAD_LSB_MSB 0x30 | |
126 | #define PCI9111_8254_MODE_0 0x00 | |
127 | #define PCI9111_8254_MODE_1 0x02 | |
128 | #define PCI9111_8254_MODE_2 0x04 | |
129 | #define PCI9111_8254_MODE_3 0x06 | |
130 | #define PCI9111_8254_MODE_4 0x08 | |
131 | #define PCI9111_8254_MODE_5 0x0A | |
132 | #define PCI9111_8254_BINARY_COUNTER 0x00 | |
133 | #define PCI9111_8254_BCD_COUNTER 0x01 | |
134 | ||
135 | /* IO address map */ | |
136 | ||
52f8ac98 | 137 | #define PCI9111_REGISTER_AD_FIFO_VALUE 0x00 /* AD Data stored in FIFO */ |
8cb9b9fb EP |
138 | #define PCI9111_REGISTER_DA_OUTPUT 0x00 |
139 | #define PCI9111_REGISTER_DIGITAL_IO 0x02 | |
140 | #define PCI9111_REGISTER_EXTENDED_IO_PORTS 0x04 | |
52f8ac98 | 141 | #define PCI9111_REGISTER_AD_CHANNEL_CONTROL 0x06 /* Channel selection */ |
8cb9b9fb EP |
142 | #define PCI9111_REGISTER_AD_CHANNEL_READBACK 0x06 |
143 | #define PCI9111_REGISTER_INPUT_SIGNAL_RANGE 0x08 | |
144 | #define PCI9111_REGISTER_RANGE_STATUS_READBACK 0x08 | |
145 | #define PCI9111_REGISTER_TRIGGER_MODE_CONTROL 0x0A | |
146 | #define PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK 0x0A | |
147 | #define PCI9111_REGISTER_SOFTWARE_TRIGGER 0x0E | |
148 | #define PCI9111_REGISTER_INTERRUPT_CONTROL 0x0C | |
149 | #define PCI9111_REGISTER_8254_COUNTER_0 0x40 | |
150 | #define PCI9111_REGISTER_8254_COUNTER_1 0x42 | |
151 | #define PCI9111_REGISTER_8254_COUNTER_2 0X44 | |
152 | #define PCI9111_REGISTER_8254_CONTROL 0x46 | |
153 | #define PCI9111_REGISTER_INTERRUPT_CLEAR 0x48 | |
154 | ||
155 | #define PCI9111_TRIGGER_MASK 0x0F | |
156 | #define PCI9111_PTRG_OFF (0 << 3) | |
157 | #define PCI9111_PTRG_ON (1 << 3) | |
158 | #define PCI9111_EITS_EXTERNAL (1 << 2) | |
159 | #define PCI9111_EITS_INTERNAL (0 << 2) | |
160 | #define PCI9111_TPST_SOFTWARE_TRIGGER (0 << 1) | |
161 | #define PCI9111_TPST_TIMER_PACER (1 << 1) | |
162 | #define PCI9111_ASCAN_ON (1 << 0) | |
163 | #define PCI9111_ASCAN_OFF (0 << 0) | |
164 | ||
165 | #define PCI9111_ISC0_SET_IRQ_ON_ENDING_OF_AD_CONVERSION (0 << 0) | |
166 | #define PCI9111_ISC0_SET_IRQ_ON_FIFO_HALF_FULL (1 << 0) | |
167 | #define PCI9111_ISC1_SET_IRQ_ON_TIMER_TICK (0 << 1) | |
168 | #define PCI9111_ISC1_SET_IRQ_ON_EXT_TRG (1 << 1) | |
169 | #define PCI9111_FFEN_SET_FIFO_ENABLE (0 << 2) | |
170 | #define PCI9111_FFEN_SET_FIFO_DISABLE (1 << 2) | |
171 | ||
172 | #define PCI9111_CHANNEL_MASK 0x0F | |
173 | ||
174 | #define PCI9111_RANGE_MASK 0x07 | |
175 | #define PCI9111_FIFO_EMPTY_MASK 0x10 | |
176 | #define PCI9111_FIFO_HALF_FULL_MASK 0x20 | |
177 | #define PCI9111_FIFO_FULL_MASK 0x40 | |
178 | #define PCI9111_AD_BUSY_MASK 0x80 | |
179 | ||
180 | #define PCI9111_IO_BASE dev->iobase | |
181 | ||
182 | /* | |
183 | * Define inlined function | |
184 | */ | |
185 | ||
186 | #define pci9111_trigger_and_autoscan_get() \ | |
187 | (inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK)&0x0F) | |
188 | ||
189 | #define pci9111_trigger_and_autoscan_set(flags) \ | |
f7cbd7aa | 190 | outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_TRIGGER_MODE_CONTROL) |
8cb9b9fb EP |
191 | |
192 | #define pci9111_interrupt_and_fifo_get() \ | |
193 | ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK) >> 4) &0x03) | |
194 | ||
195 | #define pci9111_interrupt_and_fifo_set(flags) \ | |
f7cbd7aa | 196 | outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL) |
8cb9b9fb EP |
197 | |
198 | #define pci9111_interrupt_clear() \ | |
f7cbd7aa | 199 | outb(0, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CLEAR) |
8cb9b9fb EP |
200 | |
201 | #define pci9111_software_trigger() \ | |
f7cbd7aa | 202 | outb(0, PCI9111_IO_BASE+PCI9111_REGISTER_SOFTWARE_TRIGGER) |
8cb9b9fb EP |
203 | |
204 | #define pci9111_fifo_reset() \ | |
f7cbd7aa BP |
205 | outb(PCI9111_FFEN_SET_FIFO_ENABLE, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL); \ |
206 | outb(PCI9111_FFEN_SET_FIFO_DISABLE, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL); \ | |
207 | outb(PCI9111_FFEN_SET_FIFO_ENABLE, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL) | |
8cb9b9fb EP |
208 | |
209 | #define pci9111_is_fifo_full() \ | |
210 | ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ | |
211 | PCI9111_FIFO_FULL_MASK)==0) | |
212 | ||
213 | #define pci9111_is_fifo_half_full() \ | |
214 | ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ | |
215 | PCI9111_FIFO_HALF_FULL_MASK)==0) | |
216 | ||
217 | #define pci9111_is_fifo_empty() \ | |
218 | ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ | |
219 | PCI9111_FIFO_EMPTY_MASK)==0) | |
220 | ||
221 | #define pci9111_ai_channel_set(channel) \ | |
f7cbd7aa | 222 | outb((channel)&PCI9111_CHANNEL_MASK, PCI9111_IO_BASE+PCI9111_REGISTER_AD_CHANNEL_CONTROL) |
8cb9b9fb EP |
223 | |
224 | #define pci9111_ai_channel_get() \ | |
225 | inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_CHANNEL_READBACK)&PCI9111_CHANNEL_MASK | |
226 | ||
227 | #define pci9111_ai_range_set(range) \ | |
f7cbd7aa | 228 | outb((range)&PCI9111_RANGE_MASK, PCI9111_IO_BASE+PCI9111_REGISTER_INPUT_SIGNAL_RANGE) |
8cb9b9fb EP |
229 | |
230 | #define pci9111_ai_range_get() \ | |
231 | inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)&PCI9111_RANGE_MASK | |
232 | ||
233 | #define pci9111_ai_get_data() \ | |
234 | ((inw(PCI9111_IO_BASE+PCI9111_REGISTER_AD_FIFO_VALUE)>>4)&PCI9111_AI_RESOLUTION_MASK) \ | |
235 | ^ PCI9111_AI_RESOLUTION_2_CMP_BIT | |
236 | ||
237 | #define pci9111_hr_ai_get_data() \ | |
238 | (inw(PCI9111_IO_BASE+PCI9111_REGISTER_AD_FIFO_VALUE) & PCI9111_HR_AI_RESOLUTION_MASK) \ | |
239 | ^ PCI9111_HR_AI_RESOLUTION_2_CMP_BIT | |
240 | ||
241 | #define pci9111_ao_set_data(data) \ | |
f7cbd7aa | 242 | outw(data&PCI9111_AO_RESOLUTION_MASK, PCI9111_IO_BASE+PCI9111_REGISTER_DA_OUTPUT) |
8cb9b9fb EP |
243 | |
244 | #define pci9111_di_get_bits() \ | |
245 | inw(PCI9111_IO_BASE+PCI9111_REGISTER_DIGITAL_IO) | |
246 | ||
247 | #define pci9111_do_set_bits(bits) \ | |
f7cbd7aa | 248 | outw(bits, PCI9111_IO_BASE+PCI9111_REGISTER_DIGITAL_IO) |
8cb9b9fb EP |
249 | |
250 | #define pci9111_8254_control_set(flags) \ | |
f7cbd7aa | 251 | outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_8254_CONTROL) |
8cb9b9fb EP |
252 | |
253 | #define pci9111_8254_counter_0_set(data) \ | |
254 | outb(data & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_0); \ | |
53106ae6 | 255 | outb((data >> 8) & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_0) |
8cb9b9fb EP |
256 | |
257 | #define pci9111_8254_counter_1_set(data) \ | |
258 | outb(data & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_1); \ | |
53106ae6 | 259 | outb((data >> 8) & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_1) |
8cb9b9fb EP |
260 | |
261 | #define pci9111_8254_counter_2_set(data) \ | |
262 | outb(data & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_2); \ | |
53106ae6 | 263 | outb((data >> 8) & 0xFF, PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_2) |
8cb9b9fb | 264 | |
52f8ac98 | 265 | /* Function prototypes */ |
8cb9b9fb | 266 | |
0a85b6f0 MT |
267 | static int pci9111_attach(struct comedi_device *dev, |
268 | struct comedi_devconfig *it); | |
da91b269 | 269 | static int pci9111_detach(struct comedi_device *dev); |
0a85b6f0 MT |
270 | static void pci9111_ai_munge(struct comedi_device *dev, |
271 | struct comedi_subdevice *s, void *data, | |
272 | unsigned int num_bytes, | |
273 | unsigned int start_chan_index); | |
8cb9b9fb | 274 | |
9ced1de6 | 275 | static const struct comedi_lrange pci9111_hr_ai_range = { |
8cb9b9fb EP |
276 | 5, |
277 | { | |
0a85b6f0 MT |
278 | BIP_RANGE(10), |
279 | BIP_RANGE(5), | |
280 | BIP_RANGE(2.5), | |
281 | BIP_RANGE(1.25), | |
282 | BIP_RANGE(0.625) | |
283 | } | |
8cb9b9fb EP |
284 | }; |
285 | ||
286 | static DEFINE_PCI_DEVICE_TABLE(pci9111_pci_table) = { | |
0a85b6f0 MT |
287 | { |
288 | PCI_VENDOR_ID_ADLINK, PCI9111_HR_DEVICE_ID, PCI_ANY_ID, | |
289 | PCI_ANY_ID, 0, 0, 0}, | |
290 | /* { PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ | |
291 | { | |
292 | 0} | |
8cb9b9fb EP |
293 | }; |
294 | ||
295 | MODULE_DEVICE_TABLE(pci, pci9111_pci_table); | |
296 | ||
52f8ac98 BP |
297 | /* */ |
298 | /* Board specification structure */ | |
299 | /* */ | |
8cb9b9fb | 300 | |
940579fb | 301 | struct pci9111_board { |
52f8ac98 | 302 | const char *name; /* driver name */ |
8cb9b9fb | 303 | int device_id; |
52f8ac98 BP |
304 | int ai_channel_nbr; /* num of A/D chans */ |
305 | int ao_channel_nbr; /* num of D/A chans */ | |
306 | int ai_resolution; /* resolution of A/D */ | |
8cb9b9fb | 307 | int ai_resolution_mask; |
52f8ac98 | 308 | int ao_resolution; /* resolution of D/A */ |
8cb9b9fb | 309 | int ao_resolution_mask; |
52f8ac98 BP |
310 | const struct comedi_lrange *ai_range_list; /* rangelist for A/D */ |
311 | const struct comedi_lrange *ao_range_list; /* rangelist for D/A */ | |
8cb9b9fb | 312 | unsigned int ai_acquisition_period_min_ns; |
940579fb | 313 | }; |
8cb9b9fb | 314 | |
940579fb | 315 | static const struct pci9111_board pci9111_boards[] = { |
8cb9b9fb | 316 | { |
0a85b6f0 MT |
317 | .name = "pci9111_hr", |
318 | .device_id = PCI9111_HR_DEVICE_ID, | |
319 | .ai_channel_nbr = PCI9111_AI_CHANNEL_NBR, | |
320 | .ao_channel_nbr = PCI9111_AO_CHANNEL_NBR, | |
321 | .ai_resolution = PCI9111_HR_AI_RESOLUTION, | |
322 | .ai_resolution_mask = PCI9111_HR_AI_RESOLUTION_MASK, | |
323 | .ao_resolution = PCI9111_AO_RESOLUTION, | |
324 | .ao_resolution_mask = PCI9111_AO_RESOLUTION_MASK, | |
325 | .ai_range_list = &pci9111_hr_ai_range, | |
326 | .ao_range_list = &range_bipolar10, | |
327 | .ai_acquisition_period_min_ns = PCI9111_AI_ACQUISITION_PERIOD_MIN_NS} | |
8cb9b9fb EP |
328 | }; |
329 | ||
330 | #define pci9111_board_nbr \ | |
940579fb | 331 | (sizeof(pci9111_boards)/sizeof(struct pci9111_board)) |
8cb9b9fb | 332 | |
139dfbdf | 333 | static struct comedi_driver pci9111_driver = { |
68c3dbff BP |
334 | .driver_name = PCI9111_DRIVER_NAME, |
335 | .module = THIS_MODULE, | |
336 | .attach = pci9111_attach, | |
337 | .detach = pci9111_detach, | |
8cb9b9fb EP |
338 | }; |
339 | ||
340 | COMEDI_PCI_INITCLEANUP(pci9111_driver, pci9111_pci_table); | |
341 | ||
52f8ac98 | 342 | /* Private data structure */ |
8cb9b9fb | 343 | |
c350fa19 | 344 | struct pci9111_private_data { |
8cb9b9fb | 345 | struct pci_dev *pci_device; |
52f8ac98 | 346 | unsigned long io_range; /* PCI6503 io range */ |
8cb9b9fb | 347 | |
52f8ac98 | 348 | unsigned long lcr_io_base; /* Local configuration register base address */ |
8cb9b9fb EP |
349 | unsigned long lcr_io_range; |
350 | ||
351 | int stop_counter; | |
352 | int stop_is_none; | |
353 | ||
354 | unsigned int scan_delay; | |
355 | unsigned int chanlist_len; | |
356 | unsigned int chunk_counter; | |
357 | unsigned int chunk_num_samples; | |
358 | ||
52f8ac98 | 359 | int ao_readback; /* Last written analog output data */ |
8cb9b9fb | 360 | |
525d1b13 GKH |
361 | unsigned int timer_divisor_1; /* Divisor values for the 8254 timer pacer */ |
362 | unsigned int timer_divisor_2; | |
8cb9b9fb | 363 | |
52f8ac98 | 364 | int is_valid; /* Is device valid */ |
8cb9b9fb | 365 | |
790c5541 | 366 | short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE]; |
c350fa19 | 367 | }; |
8cb9b9fb | 368 | |
c350fa19 | 369 | #define dev_private ((struct pci9111_private_data *)dev->private) |
8cb9b9fb | 370 | |
52f8ac98 BP |
371 | /* ------------------------------------------------------------------ */ |
372 | /* PLX9050 SECTION */ | |
373 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb EP |
374 | |
375 | #define PLX9050_REGISTER_INTERRUPT_CONTROL 0x4c | |
376 | ||
377 | #define PLX9050_LINTI1_ENABLE (1 << 0) | |
378 | #define PLX9050_LINTI1_ACTIVE_HIGH (1 << 1) | |
379 | #define PLX9050_LINTI1_STATUS (1 << 2) | |
380 | #define PLX9050_LINTI2_ENABLE (1 << 3) | |
381 | #define PLX9050_LINTI2_ACTIVE_HIGH (1 << 4) | |
382 | #define PLX9050_LINTI2_STATUS (1 << 5) | |
383 | #define PLX9050_PCI_INTERRUPT_ENABLE (1 << 6) | |
384 | #define PLX9050_SOFTWARE_INTERRUPT (1 << 7) | |
385 | ||
386 | static void plx9050_interrupt_control(unsigned long io_base, | |
0a85b6f0 MT |
387 | bool LINTi1_enable, |
388 | bool LINTi1_active_high, | |
389 | bool LINTi2_enable, | |
390 | bool LINTi2_active_high, | |
391 | bool interrupt_enable) | |
8cb9b9fb EP |
392 | { |
393 | int flags = 0; | |
394 | ||
395 | if (LINTi1_enable) | |
396 | flags |= PLX9050_LINTI1_ENABLE; | |
397 | if (LINTi1_active_high) | |
398 | flags |= PLX9050_LINTI1_ACTIVE_HIGH; | |
399 | if (LINTi2_enable) | |
400 | flags |= PLX9050_LINTI2_ENABLE; | |
401 | if (LINTi2_active_high) | |
402 | flags |= PLX9050_LINTI2_ACTIVE_HIGH; | |
403 | ||
404 | if (interrupt_enable) | |
405 | flags |= PLX9050_PCI_INTERRUPT_ENABLE; | |
406 | ||
407 | outb(flags, io_base + PLX9050_REGISTER_INTERRUPT_CONTROL); | |
408 | } | |
409 | ||
52f8ac98 BP |
410 | /* ------------------------------------------------------------------ */ |
411 | /* MISCELLANEOUS SECTION */ | |
412 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 413 | |
52f8ac98 | 414 | /* 8254 timer */ |
8cb9b9fb | 415 | |
da91b269 | 416 | static void pci9111_timer_set(struct comedi_device *dev) |
8cb9b9fb EP |
417 | { |
418 | pci9111_8254_control_set(PCI9111_8254_COUNTER_0 | | |
0a85b6f0 MT |
419 | PCI9111_8254_READ_LOAD_LSB_MSB | |
420 | PCI9111_8254_MODE_0 | | |
421 | PCI9111_8254_BINARY_COUNTER); | |
8cb9b9fb EP |
422 | |
423 | pci9111_8254_control_set(PCI9111_8254_COUNTER_1 | | |
0a85b6f0 MT |
424 | PCI9111_8254_READ_LOAD_LSB_MSB | |
425 | PCI9111_8254_MODE_2 | | |
426 | PCI9111_8254_BINARY_COUNTER); | |
8cb9b9fb EP |
427 | |
428 | pci9111_8254_control_set(PCI9111_8254_COUNTER_2 | | |
0a85b6f0 MT |
429 | PCI9111_8254_READ_LOAD_LSB_MSB | |
430 | PCI9111_8254_MODE_2 | | |
431 | PCI9111_8254_BINARY_COUNTER); | |
8cb9b9fb | 432 | |
5f74ea14 | 433 | udelay(1); |
8cb9b9fb EP |
434 | |
435 | pci9111_8254_counter_2_set(dev_private->timer_divisor_2); | |
436 | pci9111_8254_counter_1_set(dev_private->timer_divisor_1); | |
437 | } | |
438 | ||
655f78f6 | 439 | enum pci9111_trigger_sources { |
8cb9b9fb EP |
440 | software, |
441 | timer_pacer, | |
442 | external | |
655f78f6 | 443 | }; |
8cb9b9fb | 444 | |
da91b269 | 445 | static void pci9111_trigger_source_set(struct comedi_device *dev, |
0a85b6f0 | 446 | enum pci9111_trigger_sources source) |
8cb9b9fb EP |
447 | { |
448 | int flags; | |
449 | ||
450 | flags = pci9111_trigger_and_autoscan_get() & 0x09; | |
451 | ||
452 | switch (source) { | |
453 | case software: | |
454 | flags |= PCI9111_EITS_INTERNAL | PCI9111_TPST_SOFTWARE_TRIGGER; | |
455 | break; | |
456 | ||
457 | case timer_pacer: | |
458 | flags |= PCI9111_EITS_INTERNAL | PCI9111_TPST_TIMER_PACER; | |
459 | break; | |
460 | ||
461 | case external: | |
462 | flags |= PCI9111_EITS_EXTERNAL; | |
463 | break; | |
464 | } | |
465 | ||
466 | pci9111_trigger_and_autoscan_set(flags); | |
467 | } | |
468 | ||
da91b269 | 469 | static void pci9111_pretrigger_set(struct comedi_device *dev, bool pretrigger) |
8cb9b9fb EP |
470 | { |
471 | int flags; | |
472 | ||
473 | flags = pci9111_trigger_and_autoscan_get() & 0x07; | |
474 | ||
475 | if (pretrigger) | |
476 | flags |= PCI9111_PTRG_ON; | |
477 | ||
478 | pci9111_trigger_and_autoscan_set(flags); | |
479 | } | |
480 | ||
da91b269 | 481 | static void pci9111_autoscan_set(struct comedi_device *dev, bool autoscan) |
8cb9b9fb EP |
482 | { |
483 | int flags; | |
484 | ||
485 | flags = pci9111_trigger_and_autoscan_get() & 0x0e; | |
486 | ||
487 | if (autoscan) | |
488 | flags |= PCI9111_ASCAN_ON; | |
489 | ||
490 | pci9111_trigger_and_autoscan_set(flags); | |
491 | } | |
492 | ||
3ba97b3c | 493 | enum pci9111_ISC0_sources { |
8cb9b9fb EP |
494 | irq_on_eoc, |
495 | irq_on_fifo_half_full | |
3ba97b3c | 496 | }; |
8cb9b9fb | 497 | |
52f8ac98 | 498 | enum pci9111_ISC1_sources { |
8cb9b9fb EP |
499 | irq_on_timer_tick, |
500 | irq_on_external_trigger | |
52f8ac98 | 501 | }; |
8cb9b9fb | 502 | |
da91b269 | 503 | static void pci9111_interrupt_source_set(struct comedi_device *dev, |
0a85b6f0 MT |
504 | enum pci9111_ISC0_sources irq_0_source, |
505 | enum pci9111_ISC1_sources irq_1_source) | |
8cb9b9fb EP |
506 | { |
507 | int flags; | |
508 | ||
509 | flags = pci9111_interrupt_and_fifo_get() & 0x04; | |
510 | ||
511 | if (irq_0_source == irq_on_fifo_half_full) | |
512 | flags |= PCI9111_ISC0_SET_IRQ_ON_FIFO_HALF_FULL; | |
513 | ||
514 | if (irq_1_source == irq_on_external_trigger) | |
515 | flags |= PCI9111_ISC1_SET_IRQ_ON_EXT_TRG; | |
516 | ||
517 | pci9111_interrupt_and_fifo_set(flags); | |
518 | } | |
519 | ||
52f8ac98 BP |
520 | /* ------------------------------------------------------------------ */ |
521 | /* HARDWARE TRIGGERED ANALOG INPUT SECTION */ | |
522 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 523 | |
52f8ac98 | 524 | /* Cancel analog input autoscan */ |
8cb9b9fb EP |
525 | |
526 | #undef AI_DO_CMD_DEBUG | |
527 | ||
0a85b6f0 MT |
528 | static int pci9111_ai_cancel(struct comedi_device *dev, |
529 | struct comedi_subdevice *s) | |
8cb9b9fb | 530 | { |
52f8ac98 | 531 | /* Disable interrupts */ |
8cb9b9fb EP |
532 | |
533 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, | |
0a85b6f0 | 534 | true, false); |
8cb9b9fb EP |
535 | |
536 | pci9111_trigger_source_set(dev, software); | |
537 | ||
538 | pci9111_autoscan_set(dev, false); | |
539 | ||
540 | pci9111_fifo_reset(); | |
541 | ||
542 | #ifdef AI_DO_CMD_DEBUG | |
543 | printk(PCI9111_DRIVER_NAME ": ai_cancel\n"); | |
544 | #endif | |
545 | ||
546 | return 0; | |
547 | } | |
548 | ||
52f8ac98 | 549 | /* Test analog input command */ |
8cb9b9fb | 550 | |
f7cbd7aa | 551 | #define pci9111_check_trigger_src(src, flags) \ |
8cb9b9fb EP |
552 | tmp = src; \ |
553 | src &= flags; \ | |
554 | if (!src || tmp != src) error++ | |
555 | ||
556 | static int | |
da91b269 | 557 | pci9111_ai_do_cmd_test(struct comedi_device *dev, |
0a85b6f0 | 558 | struct comedi_subdevice *s, struct comedi_cmd *cmd) |
8cb9b9fb EP |
559 | { |
560 | int tmp; | |
561 | int error = 0; | |
562 | int range, reference; | |
563 | int i; | |
0a85b6f0 | 564 | struct pci9111_board *board = (struct pci9111_board *)dev->board_ptr; |
8cb9b9fb | 565 | |
52f8ac98 | 566 | /* Step 1 : check if trigger are trivialy valid */ |
8cb9b9fb EP |
567 | |
568 | pci9111_check_trigger_src(cmd->start_src, TRIG_NOW); | |
569 | pci9111_check_trigger_src(cmd->scan_begin_src, | |
0a85b6f0 | 570 | TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); |
8cb9b9fb EP |
571 | pci9111_check_trigger_src(cmd->convert_src, TRIG_TIMER | TRIG_EXT); |
572 | pci9111_check_trigger_src(cmd->scan_end_src, TRIG_COUNT); | |
573 | pci9111_check_trigger_src(cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
574 | ||
575 | if (error) | |
576 | return 1; | |
577 | ||
52f8ac98 | 578 | /* step 2 : make sure trigger sources are unique and mutually compatible */ |
8cb9b9fb EP |
579 | |
580 | if (cmd->start_src != TRIG_NOW) | |
581 | error++; | |
582 | ||
583 | if ((cmd->scan_begin_src != TRIG_TIMER) && | |
0a85b6f0 MT |
584 | (cmd->scan_begin_src != TRIG_FOLLOW) && |
585 | (cmd->scan_begin_src != TRIG_EXT)) | |
8cb9b9fb EP |
586 | error++; |
587 | ||
2306d9b1 | 588 | if ((cmd->convert_src != TRIG_TIMER) && (cmd->convert_src != TRIG_EXT)) |
8cb9b9fb | 589 | error++; |
8cb9b9fb | 590 | if ((cmd->convert_src == TRIG_TIMER) && |
0a85b6f0 | 591 | !((cmd->scan_begin_src == TRIG_TIMER) || |
2306d9b1 | 592 | (cmd->scan_begin_src == TRIG_FOLLOW))) |
8cb9b9fb | 593 | error++; |
8cb9b9fb | 594 | if ((cmd->convert_src == TRIG_EXT) && |
0a85b6f0 | 595 | !((cmd->scan_begin_src == TRIG_EXT) || |
2306d9b1 | 596 | (cmd->scan_begin_src == TRIG_FOLLOW))) |
8cb9b9fb | 597 | error++; |
2306d9b1 | 598 | |
8cb9b9fb EP |
599 | |
600 | if (cmd->scan_end_src != TRIG_COUNT) | |
601 | error++; | |
602 | if ((cmd->stop_src != TRIG_COUNT) && (cmd->stop_src != TRIG_NONE)) | |
603 | error++; | |
604 | ||
605 | if (error) | |
606 | return 2; | |
607 | ||
52f8ac98 | 608 | /* Step 3 : make sure arguments are trivialy compatible */ |
8cb9b9fb EP |
609 | |
610 | if (cmd->chanlist_len < 1) { | |
611 | cmd->chanlist_len = 1; | |
612 | error++; | |
613 | } | |
614 | ||
615 | if (cmd->chanlist_len > board->ai_channel_nbr) { | |
616 | cmd->chanlist_len = board->ai_channel_nbr; | |
617 | error++; | |
618 | } | |
619 | ||
620 | if ((cmd->start_src == TRIG_NOW) && (cmd->start_arg != 0)) { | |
621 | cmd->start_arg = 0; | |
622 | error++; | |
623 | } | |
624 | ||
625 | if ((cmd->convert_src == TRIG_TIMER) && | |
0a85b6f0 | 626 | (cmd->convert_arg < board->ai_acquisition_period_min_ns)) { |
8cb9b9fb EP |
627 | cmd->convert_arg = board->ai_acquisition_period_min_ns; |
628 | error++; | |
629 | } | |
630 | if ((cmd->convert_src == TRIG_EXT) && (cmd->convert_arg != 0)) { | |
631 | cmd->convert_arg = 0; | |
632 | error++; | |
633 | } | |
634 | ||
635 | if ((cmd->scan_begin_src == TRIG_TIMER) && | |
0a85b6f0 | 636 | (cmd->scan_begin_arg < board->ai_acquisition_period_min_ns)) { |
8cb9b9fb EP |
637 | cmd->scan_begin_arg = board->ai_acquisition_period_min_ns; |
638 | error++; | |
639 | } | |
640 | if ((cmd->scan_begin_src == TRIG_FOLLOW) && (cmd->scan_begin_arg != 0)) { | |
641 | cmd->scan_begin_arg = 0; | |
642 | error++; | |
643 | } | |
644 | if ((cmd->scan_begin_src == TRIG_EXT) && (cmd->scan_begin_arg != 0)) { | |
645 | cmd->scan_begin_arg = 0; | |
646 | error++; | |
647 | } | |
648 | ||
649 | if ((cmd->scan_end_src == TRIG_COUNT) && | |
0a85b6f0 | 650 | (cmd->scan_end_arg != cmd->chanlist_len)) { |
8cb9b9fb EP |
651 | cmd->scan_end_arg = cmd->chanlist_len; |
652 | error++; | |
653 | } | |
654 | ||
655 | if ((cmd->stop_src == TRIG_COUNT) && (cmd->stop_arg < 1)) { | |
656 | cmd->stop_arg = 1; | |
657 | error++; | |
658 | } | |
659 | if ((cmd->stop_src == TRIG_NONE) && (cmd->stop_arg != 0)) { | |
660 | cmd->stop_arg = 0; | |
661 | error++; | |
662 | } | |
663 | ||
664 | if (error) | |
665 | return 3; | |
666 | ||
52f8ac98 | 667 | /* Step 4 : fix up any arguments */ |
8cb9b9fb EP |
668 | |
669 | if (cmd->convert_src == TRIG_TIMER) { | |
670 | tmp = cmd->convert_arg; | |
671 | i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, | |
0a85b6f0 MT |
672 | &(dev_private->timer_divisor_1), |
673 | &(dev_private->timer_divisor_2), | |
674 | &(cmd->convert_arg), | |
675 | cmd->flags & TRIG_ROUND_MASK); | |
8cb9b9fb EP |
676 | if (tmp != cmd->convert_arg) |
677 | error++; | |
678 | } | |
52f8ac98 BP |
679 | /* There's only one timer on this card, so the scan_begin timer must */ |
680 | /* be a multiple of chanlist_len*convert_arg */ | |
8cb9b9fb EP |
681 | |
682 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
683 | ||
684 | unsigned int scan_begin_min; | |
685 | unsigned int scan_begin_arg; | |
686 | unsigned int scan_factor; | |
687 | ||
688 | scan_begin_min = cmd->chanlist_len * cmd->convert_arg; | |
689 | ||
690 | if (cmd->scan_begin_arg != scan_begin_min) { | |
691 | if (scan_begin_min < cmd->scan_begin_arg) { | |
692 | scan_factor = | |
0a85b6f0 | 693 | cmd->scan_begin_arg / scan_begin_min; |
8cb9b9fb EP |
694 | scan_begin_arg = scan_factor * scan_begin_min; |
695 | if (cmd->scan_begin_arg != scan_begin_arg) { | |
696 | cmd->scan_begin_arg = scan_begin_arg; | |
697 | error++; | |
698 | } | |
699 | } else { | |
700 | cmd->scan_begin_arg = scan_begin_min; | |
701 | error++; | |
702 | } | |
703 | } | |
704 | } | |
705 | ||
706 | if (error) | |
707 | return 4; | |
708 | ||
52f8ac98 | 709 | /* Step 5 : check channel list */ |
8cb9b9fb EP |
710 | |
711 | if (cmd->chanlist) { | |
712 | ||
713 | range = CR_RANGE(cmd->chanlist[0]); | |
714 | reference = CR_AREF(cmd->chanlist[0]); | |
715 | ||
716 | if (cmd->chanlist_len > 1) { | |
717 | for (i = 0; i < cmd->chanlist_len; i++) { | |
718 | if (CR_CHAN(cmd->chanlist[i]) != i) { | |
719 | comedi_error(dev, | |
0a85b6f0 MT |
720 | "entries in chanlist must be consecutive " |
721 | "channels,counting upwards from 0\n"); | |
8cb9b9fb EP |
722 | error++; |
723 | } | |
724 | if (CR_RANGE(cmd->chanlist[i]) != range) { | |
725 | comedi_error(dev, | |
0a85b6f0 | 726 | "entries in chanlist must all have the same gain\n"); |
8cb9b9fb EP |
727 | error++; |
728 | } | |
729 | if (CR_AREF(cmd->chanlist[i]) != reference) { | |
730 | comedi_error(dev, | |
0a85b6f0 | 731 | "entries in chanlist must all have the same reference\n"); |
8cb9b9fb EP |
732 | error++; |
733 | } | |
734 | } | |
735 | } else { | |
736 | if ((CR_CHAN(cmd->chanlist[0]) > | |
0a85b6f0 MT |
737 | (board->ai_channel_nbr - 1)) |
738 | || (CR_CHAN(cmd->chanlist[0]) < 0)) { | |
8cb9b9fb | 739 | comedi_error(dev, |
0a85b6f0 | 740 | "channel number is out of limits\n"); |
8cb9b9fb EP |
741 | error++; |
742 | } | |
743 | } | |
744 | } | |
745 | ||
746 | if (error) | |
747 | return 5; | |
748 | ||
749 | return 0; | |
750 | ||
751 | } | |
752 | ||
52f8ac98 | 753 | /* Analog input command */ |
8cb9b9fb | 754 | |
0a85b6f0 MT |
755 | static int pci9111_ai_do_cmd(struct comedi_device *dev, |
756 | struct comedi_subdevice *subdevice) | |
8cb9b9fb | 757 | { |
ea6d0d4c | 758 | struct comedi_cmd *async_cmd = &subdevice->async->cmd; |
8cb9b9fb EP |
759 | |
760 | if (!dev->irq) { | |
761 | comedi_error(dev, | |
0a85b6f0 | 762 | "no irq assigned for PCI9111, cannot do hardware conversion"); |
8cb9b9fb EP |
763 | return -1; |
764 | } | |
52f8ac98 BP |
765 | /* Set channel scan limit */ |
766 | /* PCI9111 allows only scanning from channel 0 to channel n */ | |
767 | /* TODO: handle the case of an external multiplexer */ | |
8cb9b9fb EP |
768 | |
769 | if (async_cmd->chanlist_len > 1) { | |
770 | pci9111_ai_channel_set((async_cmd->chanlist_len) - 1); | |
771 | pci9111_autoscan_set(dev, true); | |
772 | } else { | |
773 | pci9111_ai_channel_set(CR_CHAN(async_cmd->chanlist[0])); | |
774 | pci9111_autoscan_set(dev, false); | |
775 | } | |
776 | ||
52f8ac98 BP |
777 | /* Set gain */ |
778 | /* This is the same gain on every channel */ | |
8cb9b9fb EP |
779 | |
780 | pci9111_ai_range_set(CR_RANGE(async_cmd->chanlist[0])); | |
781 | ||
782 | /* Set counter */ | |
783 | ||
784 | switch (async_cmd->stop_src) { | |
785 | case TRIG_COUNT: | |
786 | dev_private->stop_counter = | |
0a85b6f0 | 787 | async_cmd->stop_arg * async_cmd->chanlist_len; |
8cb9b9fb EP |
788 | dev_private->stop_is_none = 0; |
789 | break; | |
790 | ||
791 | case TRIG_NONE: | |
792 | dev_private->stop_counter = 0; | |
793 | dev_private->stop_is_none = 1; | |
794 | break; | |
795 | ||
796 | default: | |
797 | comedi_error(dev, "Invalid stop trigger"); | |
798 | return -1; | |
799 | } | |
800 | ||
52f8ac98 | 801 | /* Set timer pacer */ |
8cb9b9fb EP |
802 | |
803 | dev_private->scan_delay = 0; | |
804 | switch (async_cmd->convert_src) { | |
805 | case TRIG_TIMER: | |
806 | i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, | |
0a85b6f0 MT |
807 | &(dev_private->timer_divisor_1), |
808 | &(dev_private->timer_divisor_2), | |
809 | &(async_cmd->convert_arg), | |
810 | async_cmd-> | |
811 | flags & TRIG_ROUND_MASK); | |
8cb9b9fb EP |
812 | #ifdef AI_DO_CMD_DEBUG |
813 | printk(PCI9111_DRIVER_NAME ": divisors = %d, %d\n", | |
0a85b6f0 MT |
814 | dev_private->timer_divisor_1, |
815 | dev_private->timer_divisor_2); | |
8cb9b9fb EP |
816 | #endif |
817 | ||
818 | pci9111_trigger_source_set(dev, software); | |
819 | pci9111_timer_set(dev); | |
820 | pci9111_fifo_reset(); | |
821 | pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, | |
0a85b6f0 | 822 | irq_on_timer_tick); |
8cb9b9fb EP |
823 | pci9111_trigger_source_set(dev, timer_pacer); |
824 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, | |
0a85b6f0 | 825 | false, true, true); |
8cb9b9fb | 826 | |
6c2fd308 IA |
827 | if (async_cmd->scan_begin_src == TRIG_TIMER) { |
828 | dev_private->scan_delay = | |
829 | (async_cmd->scan_begin_arg / | |
830 | (async_cmd->convert_arg * | |
831 | async_cmd->chanlist_len)) - 1; | |
832 | } | |
8cb9b9fb EP |
833 | |
834 | break; | |
835 | ||
836 | case TRIG_EXT: | |
837 | ||
838 | pci9111_trigger_source_set(dev, external); | |
839 | pci9111_fifo_reset(); | |
840 | pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, | |
0a85b6f0 | 841 | irq_on_timer_tick); |
8cb9b9fb | 842 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, |
0a85b6f0 | 843 | false, true, true); |
8cb9b9fb EP |
844 | |
845 | break; | |
846 | ||
847 | default: | |
848 | comedi_error(dev, "Invalid convert trigger"); | |
849 | return -1; | |
850 | } | |
851 | ||
852 | dev_private->stop_counter *= (1 + dev_private->scan_delay); | |
853 | dev_private->chanlist_len = async_cmd->chanlist_len; | |
854 | dev_private->chunk_counter = 0; | |
855 | dev_private->chunk_num_samples = | |
0a85b6f0 | 856 | dev_private->chanlist_len * (1 + dev_private->scan_delay); |
8cb9b9fb EP |
857 | |
858 | #ifdef AI_DO_CMD_DEBUG | |
859 | printk(PCI9111_DRIVER_NAME ": start interruptions!\n"); | |
860 | printk(PCI9111_DRIVER_NAME ": trigger source = %2x\n", | |
0a85b6f0 | 861 | pci9111_trigger_and_autoscan_get()); |
8cb9b9fb | 862 | printk(PCI9111_DRIVER_NAME ": irq source = %2x\n", |
0a85b6f0 | 863 | pci9111_interrupt_and_fifo_get()); |
8cb9b9fb EP |
864 | printk(PCI9111_DRIVER_NAME ": ai_do_cmd\n"); |
865 | printk(PCI9111_DRIVER_NAME ": stop counter = %d\n", | |
0a85b6f0 | 866 | dev_private->stop_counter); |
8cb9b9fb | 867 | printk(PCI9111_DRIVER_NAME ": scan delay = %d\n", |
0a85b6f0 | 868 | dev_private->scan_delay); |
8cb9b9fb | 869 | printk(PCI9111_DRIVER_NAME ": chanlist_len = %d\n", |
0a85b6f0 | 870 | dev_private->chanlist_len); |
8cb9b9fb | 871 | printk(PCI9111_DRIVER_NAME ": chunk num samples = %d\n", |
0a85b6f0 | 872 | dev_private->chunk_num_samples); |
8cb9b9fb EP |
873 | #endif |
874 | ||
875 | return 0; | |
876 | } | |
877 | ||
0a85b6f0 MT |
878 | static void pci9111_ai_munge(struct comedi_device *dev, |
879 | struct comedi_subdevice *s, void *data, | |
880 | unsigned int num_bytes, | |
881 | unsigned int start_chan_index) | |
8cb9b9fb | 882 | { |
790c5541 BP |
883 | unsigned int i, num_samples = num_bytes / sizeof(short); |
884 | short *array = data; | |
8cb9b9fb | 885 | int resolution = |
0a85b6f0 | 886 | ((struct pci9111_board *)dev->board_ptr)->ai_resolution; |
8cb9b9fb EP |
887 | |
888 | for (i = 0; i < num_samples; i++) { | |
889 | if (resolution == PCI9111_HR_AI_RESOLUTION) | |
890 | array[i] = | |
0a85b6f0 MT |
891 | (array[i] & PCI9111_HR_AI_RESOLUTION_MASK) ^ |
892 | PCI9111_HR_AI_RESOLUTION_2_CMP_BIT; | |
8cb9b9fb EP |
893 | else |
894 | array[i] = | |
0a85b6f0 MT |
895 | ((array[i] >> 4) & PCI9111_AI_RESOLUTION_MASK) ^ |
896 | PCI9111_AI_RESOLUTION_2_CMP_BIT; | |
8cb9b9fb EP |
897 | } |
898 | } | |
899 | ||
52f8ac98 BP |
900 | /* ------------------------------------------------------------------ */ |
901 | /* INTERRUPT SECTION */ | |
902 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb EP |
903 | |
904 | #undef INTERRUPT_DEBUG | |
905 | ||
70265d24 | 906 | static irqreturn_t pci9111_interrupt(int irq, void *p_device) |
8cb9b9fb | 907 | { |
71b5f4f1 | 908 | struct comedi_device *dev = p_device; |
34c43922 | 909 | struct comedi_subdevice *subdevice = dev->read_subdev; |
d163679c | 910 | struct comedi_async *async; |
8cb9b9fb EP |
911 | unsigned long irq_flags; |
912 | unsigned char intcsr; | |
913 | ||
914 | if (!dev->attached) { | |
52f8ac98 BP |
915 | /* Ignore interrupt before device fully attached. */ |
916 | /* Might not even have allocated subdevices yet! */ | |
8cb9b9fb EP |
917 | return IRQ_NONE; |
918 | } | |
919 | ||
920 | async = subdevice->async; | |
921 | ||
5f74ea14 | 922 | spin_lock_irqsave(&dev->spinlock, irq_flags); |
8cb9b9fb | 923 | |
52f8ac98 | 924 | /* Check if we are source of interrupt */ |
8cb9b9fb | 925 | intcsr = inb(dev_private->lcr_io_base + |
0a85b6f0 | 926 | PLX9050_REGISTER_INTERRUPT_CONTROL); |
8cb9b9fb | 927 | if (!(((intcsr & PLX9050_PCI_INTERRUPT_ENABLE) != 0) |
0a85b6f0 MT |
928 | && (((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) |
929 | == (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) | |
930 | || ((intcsr & (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS)) | |
931 | == (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS))))) { | |
52f8ac98 BP |
932 | /* Not the source of the interrupt. */ |
933 | /* (N.B. not using PLX9050_SOFTWARE_INTERRUPT) */ | |
5f74ea14 | 934 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb EP |
935 | return IRQ_NONE; |
936 | } | |
937 | ||
938 | if ((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) == | |
0a85b6f0 | 939 | (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) { |
52f8ac98 | 940 | /* Interrupt comes from fifo_half-full signal */ |
8cb9b9fb EP |
941 | |
942 | if (pci9111_is_fifo_full()) { | |
0a85b6f0 | 943 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb EP |
944 | comedi_error(dev, PCI9111_DRIVER_NAME " fifo overflow"); |
945 | pci9111_interrupt_clear(); | |
946 | pci9111_ai_cancel(dev, subdevice); | |
947 | async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; | |
948 | comedi_event(dev, subdevice); | |
949 | ||
950 | return IRQ_HANDLED; | |
951 | } | |
952 | ||
953 | if (pci9111_is_fifo_half_full()) { | |
954 | unsigned int num_samples; | |
955 | unsigned int bytes_written = 0; | |
956 | ||
957 | #ifdef INTERRUPT_DEBUG | |
958 | printk(PCI9111_DRIVER_NAME ": fifo is half full\n"); | |
959 | #endif | |
960 | ||
961 | num_samples = | |
0a85b6f0 MT |
962 | PCI9111_FIFO_HALF_SIZE > |
963 | dev_private->stop_counter | |
964 | && !dev_private-> | |
965 | stop_is_none ? dev_private->stop_counter : | |
966 | PCI9111_FIFO_HALF_SIZE; | |
8cb9b9fb | 967 | insw(PCI9111_IO_BASE + PCI9111_REGISTER_AD_FIFO_VALUE, |
0a85b6f0 | 968 | dev_private->ai_bounce_buffer, num_samples); |
8cb9b9fb EP |
969 | |
970 | if (dev_private->scan_delay < 1) { | |
971 | bytes_written = | |
0a85b6f0 MT |
972 | cfc_write_array_to_buffer(subdevice, |
973 | dev_private-> | |
974 | ai_bounce_buffer, | |
975 | num_samples * | |
976 | sizeof(short)); | |
8cb9b9fb EP |
977 | } else { |
978 | int position = 0; | |
979 | int to_read; | |
980 | ||
981 | while (position < num_samples) { | |
982 | if (dev_private->chunk_counter < | |
0a85b6f0 | 983 | dev_private->chanlist_len) { |
8cb9b9fb | 984 | to_read = |
0a85b6f0 MT |
985 | dev_private->chanlist_len - |
986 | dev_private->chunk_counter; | |
8cb9b9fb EP |
987 | |
988 | if (to_read > | |
0a85b6f0 | 989 | num_samples - position) |
8cb9b9fb | 990 | to_read = |
0a85b6f0 MT |
991 | num_samples - |
992 | position; | |
8cb9b9fb EP |
993 | |
994 | bytes_written += | |
0a85b6f0 MT |
995 | cfc_write_array_to_buffer |
996 | (subdevice, | |
997 | dev_private->ai_bounce_buffer | |
998 | + position, | |
999 | to_read * sizeof(short)); | |
8cb9b9fb EP |
1000 | } else { |
1001 | to_read = | |
0a85b6f0 MT |
1002 | dev_private->chunk_num_samples |
1003 | - | |
1004 | dev_private->chunk_counter; | |
8cb9b9fb | 1005 | if (to_read > |
0a85b6f0 | 1006 | num_samples - position) |
8cb9b9fb | 1007 | to_read = |
0a85b6f0 MT |
1008 | num_samples - |
1009 | position; | |
8cb9b9fb EP |
1010 | |
1011 | bytes_written += | |
0a85b6f0 | 1012 | sizeof(short) * to_read; |
8cb9b9fb EP |
1013 | } |
1014 | ||
1015 | position += to_read; | |
1016 | dev_private->chunk_counter += to_read; | |
1017 | ||
1018 | if (dev_private->chunk_counter >= | |
0a85b6f0 | 1019 | dev_private->chunk_num_samples) |
8cb9b9fb EP |
1020 | dev_private->chunk_counter = 0; |
1021 | } | |
1022 | } | |
1023 | ||
1024 | dev_private->stop_counter -= | |
0a85b6f0 | 1025 | bytes_written / sizeof(short); |
8cb9b9fb EP |
1026 | } |
1027 | } | |
1028 | ||
1029 | if ((dev_private->stop_counter == 0) && (!dev_private->stop_is_none)) { | |
1030 | async->events |= COMEDI_CB_EOA; | |
1031 | pci9111_ai_cancel(dev, subdevice); | |
1032 | } | |
1033 | ||
1034 | /* Very important, otherwise another interrupt request will be inserted | |
1035 | * and will cause driver hangs on processing interrupt event. */ | |
1036 | ||
1037 | pci9111_interrupt_clear(); | |
1038 | ||
5f74ea14 | 1039 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb EP |
1040 | |
1041 | comedi_event(dev, subdevice); | |
1042 | ||
1043 | return IRQ_HANDLED; | |
1044 | } | |
1045 | ||
52f8ac98 BP |
1046 | /* ------------------------------------------------------------------ */ |
1047 | /* INSTANT ANALOG INPUT OUTPUT SECTION */ | |
1048 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 1049 | |
52f8ac98 | 1050 | /* analog instant input */ |
8cb9b9fb EP |
1051 | |
1052 | #undef AI_INSN_DEBUG | |
1053 | ||
da91b269 | 1054 | static int pci9111_ai_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
1055 | struct comedi_subdevice *subdevice, |
1056 | struct comedi_insn *insn, unsigned int *data) | |
8cb9b9fb EP |
1057 | { |
1058 | int resolution = | |
0a85b6f0 | 1059 | ((struct pci9111_board *)dev->board_ptr)->ai_resolution; |
8cb9b9fb EP |
1060 | |
1061 | int timeout, i; | |
1062 | ||
1063 | #ifdef AI_INSN_DEBUG | |
1064 | printk(PCI9111_DRIVER_NAME ": ai_insn set c/r/n = %2x/%2x/%2x\n", | |
0a85b6f0 MT |
1065 | CR_CHAN((&insn->chanspec)[0]), |
1066 | CR_RANGE((&insn->chanspec)[0]), insn->n); | |
8cb9b9fb EP |
1067 | #endif |
1068 | ||
1069 | pci9111_ai_channel_set(CR_CHAN((&insn->chanspec)[0])); | |
1070 | ||
2306d9b1 | 1071 | if ((pci9111_ai_range_get()) != CR_RANGE((&insn->chanspec)[0])) |
8cb9b9fb | 1072 | pci9111_ai_range_set(CR_RANGE((&insn->chanspec)[0])); |
8cb9b9fb EP |
1073 | |
1074 | pci9111_fifo_reset(); | |
1075 | ||
1076 | for (i = 0; i < insn->n; i++) { | |
1077 | pci9111_software_trigger(); | |
1078 | ||
1079 | timeout = PCI9111_AI_INSTANT_READ_TIMEOUT; | |
1080 | ||
1081 | while (timeout--) { | |
1082 | if (!pci9111_is_fifo_empty()) | |
1083 | goto conversion_done; | |
1084 | } | |
1085 | ||
1086 | comedi_error(dev, "A/D read timeout"); | |
1087 | data[i] = 0; | |
1088 | pci9111_fifo_reset(); | |
1089 | return -ETIME; | |
1090 | ||
0a85b6f0 | 1091 | conversion_done: |
8cb9b9fb | 1092 | |
2306d9b1 | 1093 | if (resolution == PCI9111_HR_AI_RESOLUTION) |
8cb9b9fb | 1094 | data[i] = pci9111_hr_ai_get_data(); |
2306d9b1 | 1095 | else |
8cb9b9fb | 1096 | data[i] = pci9111_ai_get_data(); |
8cb9b9fb EP |
1097 | } |
1098 | ||
1099 | #ifdef AI_INSN_DEBUG | |
1100 | printk(PCI9111_DRIVER_NAME ": ai_insn get c/r/t = %2x/%2x/%2x\n", | |
0a85b6f0 MT |
1101 | pci9111_ai_channel_get(), |
1102 | pci9111_ai_range_get(), pci9111_trigger_and_autoscan_get()); | |
8cb9b9fb EP |
1103 | #endif |
1104 | ||
1105 | return i; | |
1106 | } | |
1107 | ||
52f8ac98 | 1108 | /* Analog instant output */ |
8cb9b9fb EP |
1109 | |
1110 | static int | |
da91b269 | 1111 | pci9111_ao_insn_write(struct comedi_device *dev, |
0a85b6f0 MT |
1112 | struct comedi_subdevice *s, struct comedi_insn *insn, |
1113 | unsigned int *data) | |
8cb9b9fb EP |
1114 | { |
1115 | int i; | |
1116 | ||
1117 | for (i = 0; i < insn->n; i++) { | |
1118 | pci9111_ao_set_data(data[i]); | |
1119 | dev_private->ao_readback = data[i]; | |
1120 | } | |
1121 | ||
1122 | return i; | |
1123 | } | |
1124 | ||
52f8ac98 | 1125 | /* Analog output readback */ |
8cb9b9fb | 1126 | |
da91b269 | 1127 | static int pci9111_ao_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
1128 | struct comedi_subdevice *s, |
1129 | struct comedi_insn *insn, unsigned int *data) | |
8cb9b9fb EP |
1130 | { |
1131 | int i; | |
1132 | ||
2306d9b1 | 1133 | for (i = 0; i < insn->n; i++) |
8cb9b9fb | 1134 | data[i] = dev_private->ao_readback & PCI9111_AO_RESOLUTION_MASK; |
8cb9b9fb EP |
1135 | |
1136 | return i; | |
1137 | } | |
1138 | ||
52f8ac98 BP |
1139 | /* ------------------------------------------------------------------ */ |
1140 | /* DIGITAL INPUT OUTPUT SECTION */ | |
1141 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 1142 | |
52f8ac98 | 1143 | /* Digital inputs */ |
8cb9b9fb | 1144 | |
da91b269 | 1145 | static int pci9111_di_insn_bits(struct comedi_device *dev, |
0a85b6f0 MT |
1146 | struct comedi_subdevice *subdevice, |
1147 | struct comedi_insn *insn, unsigned int *data) | |
8cb9b9fb | 1148 | { |
790c5541 | 1149 | unsigned int bits; |
8cb9b9fb EP |
1150 | |
1151 | bits = pci9111_di_get_bits(); | |
1152 | data[1] = bits; | |
1153 | ||
1154 | return 2; | |
1155 | } | |
1156 | ||
52f8ac98 | 1157 | /* Digital outputs */ |
8cb9b9fb | 1158 | |
da91b269 | 1159 | static int pci9111_do_insn_bits(struct comedi_device *dev, |
0a85b6f0 MT |
1160 | struct comedi_subdevice *subdevice, |
1161 | struct comedi_insn *insn, unsigned int *data) | |
8cb9b9fb | 1162 | { |
790c5541 | 1163 | unsigned int bits; |
8cb9b9fb | 1164 | |
52f8ac98 BP |
1165 | /* Only set bits that have been masked */ |
1166 | /* data[0] = mask */ | |
1167 | /* data[1] = bit state */ | |
8cb9b9fb EP |
1168 | |
1169 | data[0] &= PCI9111_DO_MASK; | |
1170 | ||
1171 | bits = subdevice->state; | |
1172 | bits &= ~data[0]; | |
1173 | bits |= data[0] & data[1]; | |
1174 | subdevice->state = bits; | |
1175 | ||
1176 | pci9111_do_set_bits(bits); | |
1177 | ||
1178 | data[1] = bits; | |
1179 | ||
1180 | return 2; | |
1181 | } | |
1182 | ||
52f8ac98 BP |
1183 | /* ------------------------------------------------------------------ */ |
1184 | /* INITIALISATION SECTION */ | |
1185 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 1186 | |
52f8ac98 | 1187 | /* Reset device */ |
8cb9b9fb | 1188 | |
da91b269 | 1189 | static int pci9111_reset(struct comedi_device *dev) |
8cb9b9fb | 1190 | { |
52f8ac98 | 1191 | /* Set trigger source to software */ |
8cb9b9fb EP |
1192 | |
1193 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, | |
0a85b6f0 | 1194 | true, false); |
8cb9b9fb EP |
1195 | |
1196 | pci9111_trigger_source_set(dev, software); | |
1197 | pci9111_pretrigger_set(dev, false); | |
1198 | pci9111_autoscan_set(dev, false); | |
1199 | ||
52f8ac98 | 1200 | /* Reset 8254 chip */ |
8cb9b9fb EP |
1201 | |
1202 | dev_private->timer_divisor_1 = 0; | |
1203 | dev_private->timer_divisor_2 = 0; | |
1204 | ||
1205 | pci9111_timer_set(dev); | |
1206 | ||
1207 | return 0; | |
1208 | } | |
1209 | ||
52f8ac98 BP |
1210 | /* Attach */ |
1211 | /* - Register PCI device */ | |
1212 | /* - Declare device driver capability */ | |
8cb9b9fb | 1213 | |
0a85b6f0 MT |
1214 | static int pci9111_attach(struct comedi_device *dev, |
1215 | struct comedi_devconfig *it) | |
8cb9b9fb | 1216 | { |
34c43922 | 1217 | struct comedi_subdevice *subdevice; |
8cb9b9fb EP |
1218 | unsigned long io_base, io_range, lcr_io_base, lcr_io_range; |
1219 | struct pci_dev *pci_device; | |
1220 | int error, i; | |
940579fb | 1221 | const struct pci9111_board *board; |
8cb9b9fb | 1222 | |
2306d9b1 | 1223 | if (alloc_private(dev, sizeof(struct pci9111_private_data)) < 0) |
8cb9b9fb | 1224 | return -ENOMEM; |
52f8ac98 | 1225 | /* Probe the device to determine what device in the series it is. */ |
8cb9b9fb EP |
1226 | |
1227 | printk("comedi%d: " PCI9111_DRIVER_NAME " driver\n", dev->minor); | |
1228 | ||
1229 | for (pci_device = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); | |
0a85b6f0 MT |
1230 | pci_device != NULL; |
1231 | pci_device = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pci_device)) { | |
8cb9b9fb EP |
1232 | if (pci_device->vendor == PCI_VENDOR_ID_ADLINK) { |
1233 | for (i = 0; i < pci9111_board_nbr; i++) { | |
1234 | if (pci9111_boards[i].device_id == | |
0a85b6f0 | 1235 | pci_device->device) { |
52f8ac98 | 1236 | /* was a particular bus/slot requested? */ |
8cb9b9fb | 1237 | if ((it->options[0] != 0) |
0a85b6f0 | 1238 | || (it->options[1] != 0)) { |
52f8ac98 | 1239 | /* are we on the wrong bus/slot? */ |
8cb9b9fb | 1240 | if (pci_device->bus->number != |
0a85b6f0 MT |
1241 | it->options[0] |
1242 | || | |
1243 | PCI_SLOT(pci_device->devfn) | |
1244 | != it->options[1]) { | |
8cb9b9fb EP |
1245 | continue; |
1246 | } | |
1247 | } | |
1248 | ||
1249 | dev->board_ptr = pci9111_boards + i; | |
0a85b6f0 MT |
1250 | board = |
1251 | (struct pci9111_board *) | |
1252 | dev->board_ptr; | |
8cb9b9fb EP |
1253 | dev_private->pci_device = pci_device; |
1254 | goto found; | |
1255 | } | |
1256 | } | |
1257 | } | |
1258 | } | |
1259 | ||
1260 | printk("comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", | |
0a85b6f0 | 1261 | dev->minor, it->options[0], it->options[1]); |
8cb9b9fb EP |
1262 | return -EIO; |
1263 | ||
0a85b6f0 | 1264 | found: |
8cb9b9fb EP |
1265 | |
1266 | printk("comedi%d: found %s (b:s:f=%d:%d:%d) , irq=%d\n", | |
0a85b6f0 MT |
1267 | dev->minor, |
1268 | pci9111_boards[i].name, | |
1269 | pci_device->bus->number, | |
1270 | PCI_SLOT(pci_device->devfn), | |
1271 | PCI_FUNC(pci_device->devfn), pci_device->irq); | |
8cb9b9fb | 1272 | |
52f8ac98 | 1273 | /* TODO: Warn about non-tested boards. */ |
8cb9b9fb | 1274 | |
52f8ac98 | 1275 | /* Read local configuration register base address [PCI_BASE_ADDRESS #1]. */ |
8cb9b9fb EP |
1276 | |
1277 | lcr_io_base = pci_resource_start(pci_device, 1); | |
1278 | lcr_io_range = pci_resource_len(pci_device, 1); | |
1279 | ||
0a85b6f0 MT |
1280 | printk |
1281 | ("comedi%d: local configuration registers at address 0x%4lx [0x%4lx]\n", | |
1282 | dev->minor, lcr_io_base, lcr_io_range); | |
8cb9b9fb | 1283 | |
52f8ac98 | 1284 | /* Enable PCI device and request regions */ |
8cb9b9fb | 1285 | if (comedi_pci_enable(pci_device, PCI9111_DRIVER_NAME) < 0) { |
0a85b6f0 MT |
1286 | printk |
1287 | ("comedi%d: Failed to enable PCI device and request regions\n", | |
1288 | dev->minor); | |
8cb9b9fb EP |
1289 | return -EIO; |
1290 | } | |
52f8ac98 | 1291 | /* Read PCI6308 register base address [PCI_BASE_ADDRESS #2]. */ |
8cb9b9fb EP |
1292 | |
1293 | io_base = pci_resource_start(pci_device, 2); | |
1294 | io_range = pci_resource_len(pci_device, 2); | |
1295 | ||
1296 | printk("comedi%d: 6503 registers at address 0x%4lx [0x%4lx]\n", | |
0a85b6f0 | 1297 | dev->minor, io_base, io_range); |
8cb9b9fb EP |
1298 | |
1299 | dev->iobase = io_base; | |
1300 | dev->board_name = board->name; | |
1301 | dev_private->io_range = io_range; | |
1302 | dev_private->is_valid = 0; | |
1303 | dev_private->lcr_io_base = lcr_io_base; | |
1304 | dev_private->lcr_io_range = lcr_io_range; | |
1305 | ||
1306 | pci9111_reset(dev); | |
1307 | ||
52f8ac98 | 1308 | /* Irq setup */ |
8cb9b9fb EP |
1309 | |
1310 | dev->irq = 0; | |
1311 | if (pci_device->irq > 0) { | |
5f74ea14 | 1312 | if (request_irq(pci_device->irq, pci9111_interrupt, |
8cb9b9fb EP |
1313 | IRQF_SHARED, PCI9111_DRIVER_NAME, dev) != 0) { |
1314 | printk("comedi%d: unable to allocate irq %u\n", | |
0a85b6f0 | 1315 | dev->minor, pci_device->irq); |
8cb9b9fb EP |
1316 | return -EINVAL; |
1317 | } | |
1318 | } | |
1319 | dev->irq = pci_device->irq; | |
1320 | ||
52f8ac98 | 1321 | /* TODO: Add external multiplexer setup (according to option[2]). */ |
8cb9b9fb | 1322 | |
c3744138 BP |
1323 | error = alloc_subdevices(dev, 4); |
1324 | if (error < 0) | |
8cb9b9fb EP |
1325 | return error; |
1326 | ||
1327 | subdevice = dev->subdevices + 0; | |
1328 | dev->read_subdev = subdevice; | |
1329 | ||
1330 | subdevice->type = COMEDI_SUBD_AI; | |
1331 | subdevice->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; | |
1332 | ||
52f8ac98 BP |
1333 | /* TODO: Add external multiplexer data */ |
1334 | /* if (devpriv->usemux) { subdevice->n_chan = devpriv->usemux; } */ | |
1335 | /* else { subdevice->n_chan = this_board->n_aichan; } */ | |
8cb9b9fb EP |
1336 | |
1337 | subdevice->n_chan = board->ai_channel_nbr; | |
1338 | subdevice->maxdata = board->ai_resolution_mask; | |
1339 | subdevice->len_chanlist = board->ai_channel_nbr; | |
1340 | subdevice->range_table = board->ai_range_list; | |
1341 | subdevice->cancel = pci9111_ai_cancel; | |
1342 | subdevice->insn_read = pci9111_ai_insn_read; | |
1343 | subdevice->do_cmdtest = pci9111_ai_do_cmd_test; | |
1344 | subdevice->do_cmd = pci9111_ai_do_cmd; | |
1345 | subdevice->munge = pci9111_ai_munge; | |
1346 | ||
1347 | subdevice = dev->subdevices + 1; | |
1348 | subdevice->type = COMEDI_SUBD_AO; | |
1349 | subdevice->subdev_flags = SDF_WRITABLE | SDF_COMMON; | |
1350 | subdevice->n_chan = board->ao_channel_nbr; | |
1351 | subdevice->maxdata = board->ao_resolution_mask; | |
1352 | subdevice->len_chanlist = board->ao_channel_nbr; | |
1353 | subdevice->range_table = board->ao_range_list; | |
1354 | subdevice->insn_write = pci9111_ao_insn_write; | |
1355 | subdevice->insn_read = pci9111_ao_insn_read; | |
1356 | ||
1357 | subdevice = dev->subdevices + 2; | |
1358 | subdevice->type = COMEDI_SUBD_DI; | |
1359 | subdevice->subdev_flags = SDF_READABLE; | |
1360 | subdevice->n_chan = PCI9111_DI_CHANNEL_NBR; | |
1361 | subdevice->maxdata = 1; | |
1362 | subdevice->range_table = &range_digital; | |
1363 | subdevice->insn_bits = pci9111_di_insn_bits; | |
1364 | ||
1365 | subdevice = dev->subdevices + 3; | |
1366 | subdevice->type = COMEDI_SUBD_DO; | |
1367 | subdevice->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
1368 | subdevice->n_chan = PCI9111_DO_CHANNEL_NBR; | |
1369 | subdevice->maxdata = 1; | |
1370 | subdevice->range_table = &range_digital; | |
1371 | subdevice->insn_bits = pci9111_do_insn_bits; | |
1372 | ||
1373 | dev_private->is_valid = 1; | |
1374 | ||
1375 | return 0; | |
1376 | } | |
1377 | ||
52f8ac98 | 1378 | /* Detach */ |
8cb9b9fb | 1379 | |
da91b269 | 1380 | static int pci9111_detach(struct comedi_device *dev) |
8cb9b9fb | 1381 | { |
52f8ac98 | 1382 | /* Reset device */ |
8cb9b9fb | 1383 | |
525d1b13 | 1384 | if (dev->private != NULL) { |
8cb9b9fb EP |
1385 | if (dev_private->is_valid) |
1386 | pci9111_reset(dev); | |
1387 | ||
1388 | } | |
52f8ac98 | 1389 | /* Release previously allocated irq */ |
8cb9b9fb | 1390 | |
2306d9b1 | 1391 | if (dev->irq != 0) |
5f74ea14 | 1392 | free_irq(dev->irq, dev); |
8cb9b9fb | 1393 | |
525d1b13 | 1394 | if (dev_private != NULL && dev_private->pci_device != NULL) { |
2306d9b1 | 1395 | if (dev->iobase) |
8cb9b9fb | 1396 | comedi_pci_disable(dev_private->pci_device); |
8cb9b9fb EP |
1397 | pci_dev_put(dev_private->pci_device); |
1398 | } | |
1399 | ||
1400 | return 0; | |
1401 | } |