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