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