Commit | Line | Data |
---|---|---|
8cb9b9fb EP |
1 | /* |
2 | ||
842ec6ba | 3 | comedi/drivers/adl_pci9111.c |
8cb9b9fb | 4 | |
842ec6ba | 5 | Hardware driver for PCI9111 ADLink cards: |
8cb9b9fb | 6 | |
842ec6ba | 7 | PCI-9111HR |
8cb9b9fb | 8 | |
842ec6ba | 9 | Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr> |
8cb9b9fb | 10 | |
842ec6ba MD |
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. | |
8cb9b9fb | 15 | |
842ec6ba MD |
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. | |
8cb9b9fb | 20 | |
842ec6ba MD |
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. | |
8cb9b9fb EP |
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 | ||
842ec6ba MD |
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: | |
8cb9b9fb | 40 | |
842ec6ba MD |
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 | |
8cb9b9fb | 46 | |
842ec6ba MD |
47 | The scanned channels must be consecutive and start from 0. They must |
48 | all have the same range and aref. | |
8cb9b9fb | 49 | |
3e5a0ba0 | 50 | Configuration options: not applicable, uses PCI auto config |
8cb9b9fb EP |
51 | */ |
52 | ||
53 | /* | |
54 | CHANGELOG: | |
55 | ||
842ec6ba MD |
56 | 2005/02/17 Extend AI streaming capabilities. Now, scan_begin_arg can be |
57 | a multiple of chanlist_len*convert_arg. | |
58 | 2002/02/19 Fixed the two's complement conversion in pci9111_(hr_)ai_get_data. | |
59 | 2002/02/18 Added external trigger support for analog input. | |
8cb9b9fb EP |
60 | |
61 | TODO: | |
62 | ||
842ec6ba MD |
63 | - Really test implemented functionality. |
64 | - Add support for the PCI-9111DG with a probe routine to identify | |
65 | the card type (perhaps with the help of the channel number readback | |
66 | of the A/D Data register). | |
67 | - Add external multiplexer support. | |
8cb9b9fb EP |
68 | |
69 | */ | |
70 | ||
71 | #include "../comedidev.h" | |
72 | ||
73 | #include <linux/delay.h> | |
70265d24 | 74 | #include <linux/interrupt.h> |
8cb9b9fb EP |
75 | |
76 | #include "8253.h" | |
8cb9b9fb EP |
77 | #include "comedi_fc.h" |
78 | ||
2f6df34c MR |
79 | #define PCI9111_DRIVER_NAME "adl_pci9111" |
80 | #define PCI9111_HR_DEVICE_ID 0x9111 | |
8cb9b9fb | 81 | |
8cb9b9fb EP |
82 | #define PCI9111_FIFO_HALF_SIZE 512 |
83 | ||
8cb9b9fb | 84 | #define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000 |
8cb9b9fb EP |
85 | |
86 | #define PCI9111_RANGE_SETTING_DELAY 10 | |
87 | #define PCI9111_AI_INSTANT_READ_UDELAY_US 2 | |
88 | #define PCI9111_AI_INSTANT_READ_TIMEOUT 100 | |
89 | ||
90 | #define PCI9111_8254_CLOCK_PERIOD_NS 500 | |
91 | ||
8c7524e6 HS |
92 | /* |
93 | * IO address map and bit defines | |
94 | */ | |
95 | #define PCI9111_AI_FIFO_REG 0x00 | |
96 | #define PCI9111_AO_REG 0x00 | |
97 | #define PCI9111_DIO_REG 0x02 | |
98 | #define PCI9111_EDIO_REG 0x04 | |
99 | #define PCI9111_AI_CHANNEL_REG 0x06 | |
100 | #define PCI9111_AI_RANGE_STAT_REG 0x08 | |
101 | #define PCI9111_AI_STAT_AD_BUSY (1 << 7) | |
102 | #define PCI9111_AI_STAT_FF_FF (1 << 6) | |
103 | #define PCI9111_AI_STAT_FF_HF (1 << 5) | |
104 | #define PCI9111_AI_STAT_FF_EF (1 << 4) | |
105 | #define PCI9111_AI_RANGE_MASK (7 << 0) | |
106 | #define PCI9111_AI_TRIG_CTRL_REG 0x0a | |
107 | #define PCI9111_AI_TRIG_CTRL_TRGEVENT (1 << 5) | |
108 | #define PCI9111_AI_TRIG_CTRL_POTRG (1 << 4) | |
109 | #define PCI9111_AI_TRIG_CTRL_PTRG (1 << 3) | |
110 | #define PCI9111_AI_TRIG_CTRL_ETIS (1 << 2) | |
111 | #define PCI9111_AI_TRIG_CTRL_TPST (1 << 1) | |
112 | #define PCI9111_AI_TRIG_CTRL_ASCAN (1 << 0) | |
113 | #define PCI9111_INT_CTRL_REG 0x0c | |
114 | #define PCI9111_INT_CTRL_ISC2 (1 << 3) | |
115 | #define PCI9111_INT_CTRL_FFEN (1 << 2) | |
116 | #define PCI9111_INT_CTRL_ISC1 (1 << 1) | |
117 | #define PCI9111_INT_CTRL_ISC0 (1 << 0) | |
118 | #define PCI9111_SOFT_TRIG_REG 0x0e | |
119 | #define PCI9111_8254_BASE_REG 0x40 | |
120 | #define PCI9111_INT_CLR_REG 0x48 | |
8cb9b9fb | 121 | |
afa6ac4a | 122 | static const struct comedi_lrange pci9111_ai_range = { |
8cb9b9fb EP |
123 | 5, |
124 | { | |
afa6ac4a HS |
125 | BIP_RANGE(10), |
126 | BIP_RANGE(5), | |
127 | BIP_RANGE(2.5), | |
128 | BIP_RANGE(1.25), | |
129 | BIP_RANGE(0.625) | |
130 | } | |
8cb9b9fb EP |
131 | }; |
132 | ||
52f8ac98 | 133 | /* Private data structure */ |
8cb9b9fb | 134 | |
c350fa19 | 135 | struct pci9111_private_data { |
7ecac4c3 M |
136 | unsigned long lcr_io_base; /* Local configuration register base |
137 | * address */ | |
8cb9b9fb EP |
138 | |
139 | int stop_counter; | |
140 | int stop_is_none; | |
141 | ||
142 | unsigned int scan_delay; | |
143 | unsigned int chanlist_len; | |
144 | unsigned int chunk_counter; | |
145 | unsigned int chunk_num_samples; | |
146 | ||
52f8ac98 | 147 | int ao_readback; /* Last written analog output data */ |
8cb9b9fb | 148 | |
eba16272 HS |
149 | unsigned int div1; |
150 | unsigned int div2; | |
8cb9b9fb | 151 | |
790c5541 | 152 | short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE]; |
c350fa19 | 153 | }; |
8cb9b9fb | 154 | |
52f8ac98 BP |
155 | /* ------------------------------------------------------------------ */ |
156 | /* PLX9050 SECTION */ | |
157 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb EP |
158 | |
159 | #define PLX9050_REGISTER_INTERRUPT_CONTROL 0x4c | |
160 | ||
161 | #define PLX9050_LINTI1_ENABLE (1 << 0) | |
162 | #define PLX9050_LINTI1_ACTIVE_HIGH (1 << 1) | |
163 | #define PLX9050_LINTI1_STATUS (1 << 2) | |
164 | #define PLX9050_LINTI2_ENABLE (1 << 3) | |
165 | #define PLX9050_LINTI2_ACTIVE_HIGH (1 << 4) | |
166 | #define PLX9050_LINTI2_STATUS (1 << 5) | |
167 | #define PLX9050_PCI_INTERRUPT_ENABLE (1 << 6) | |
168 | #define PLX9050_SOFTWARE_INTERRUPT (1 << 7) | |
169 | ||
170 | static void plx9050_interrupt_control(unsigned long io_base, | |
0a85b6f0 MT |
171 | bool LINTi1_enable, |
172 | bool LINTi1_active_high, | |
173 | bool LINTi2_enable, | |
174 | bool LINTi2_active_high, | |
175 | bool interrupt_enable) | |
8cb9b9fb EP |
176 | { |
177 | int flags = 0; | |
178 | ||
179 | if (LINTi1_enable) | |
180 | flags |= PLX9050_LINTI1_ENABLE; | |
181 | if (LINTi1_active_high) | |
182 | flags |= PLX9050_LINTI1_ACTIVE_HIGH; | |
183 | if (LINTi2_enable) | |
184 | flags |= PLX9050_LINTI2_ENABLE; | |
185 | if (LINTi2_active_high) | |
186 | flags |= PLX9050_LINTI2_ACTIVE_HIGH; | |
187 | ||
188 | if (interrupt_enable) | |
189 | flags |= PLX9050_PCI_INTERRUPT_ENABLE; | |
190 | ||
191 | outb(flags, io_base + PLX9050_REGISTER_INTERRUPT_CONTROL); | |
192 | } | |
193 | ||
52f8ac98 BP |
194 | /* ------------------------------------------------------------------ */ |
195 | /* MISCELLANEOUS SECTION */ | |
196 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 197 | |
52f8ac98 | 198 | /* 8254 timer */ |
8cb9b9fb | 199 | |
da91b269 | 200 | static void pci9111_timer_set(struct comedi_device *dev) |
8cb9b9fb | 201 | { |
98943079 | 202 | struct pci9111_private_data *dev_private = dev->private; |
9d093151 | 203 | unsigned long timer_base = dev->iobase + PCI9111_8254_BASE_REG; |
98943079 | 204 | |
9d093151 HS |
205 | i8254_set_mode(timer_base, 1, 0, I8254_MODE0 | I8254_BINARY); |
206 | i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY); | |
207 | i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY); | |
8cb9b9fb | 208 | |
5f74ea14 | 209 | udelay(1); |
8cb9b9fb | 210 | |
eba16272 HS |
211 | i8254_write(timer_base, 1, 2, dev_private->div2); |
212 | i8254_write(timer_base, 1, 1, dev_private->div1); | |
8cb9b9fb EP |
213 | } |
214 | ||
655f78f6 | 215 | enum pci9111_trigger_sources { |
8cb9b9fb EP |
216 | software, |
217 | timer_pacer, | |
218 | external | |
655f78f6 | 219 | }; |
8cb9b9fb | 220 | |
da91b269 | 221 | static void pci9111_trigger_source_set(struct comedi_device *dev, |
0a85b6f0 | 222 | enum pci9111_trigger_sources source) |
8cb9b9fb EP |
223 | { |
224 | int flags; | |
225 | ||
20614d96 | 226 | /* Read the current trigger mode control bits */ |
8c7524e6 | 227 | flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
20614d96 HS |
228 | /* Mask off the EITS and TPST bits */ |
229 | flags &= 0x9; | |
8cb9b9fb EP |
230 | |
231 | switch (source) { | |
232 | case software: | |
8cb9b9fb EP |
233 | break; |
234 | ||
235 | case timer_pacer: | |
8c7524e6 | 236 | flags |= PCI9111_AI_TRIG_CTRL_TPST; |
8cb9b9fb EP |
237 | break; |
238 | ||
239 | case external: | |
8c7524e6 | 240 | flags |= PCI9111_AI_TRIG_CTRL_ETIS; |
8cb9b9fb EP |
241 | break; |
242 | } | |
243 | ||
8c7524e6 | 244 | outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
8cb9b9fb EP |
245 | } |
246 | ||
da91b269 | 247 | static void pci9111_pretrigger_set(struct comedi_device *dev, bool pretrigger) |
8cb9b9fb EP |
248 | { |
249 | int flags; | |
250 | ||
20614d96 | 251 | /* Read the current trigger mode control bits */ |
8c7524e6 | 252 | flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
20614d96 HS |
253 | /* Mask off the PTRG bit */ |
254 | flags &= 0x7; | |
8cb9b9fb EP |
255 | |
256 | if (pretrigger) | |
8c7524e6 | 257 | flags |= PCI9111_AI_TRIG_CTRL_PTRG; |
8cb9b9fb | 258 | |
8c7524e6 | 259 | outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
8cb9b9fb EP |
260 | } |
261 | ||
da91b269 | 262 | static void pci9111_autoscan_set(struct comedi_device *dev, bool autoscan) |
8cb9b9fb EP |
263 | { |
264 | int flags; | |
265 | ||
20614d96 | 266 | /* Read the current trigger mode control bits */ |
8c7524e6 | 267 | flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
20614d96 HS |
268 | /* Mask off the ASCAN bit */ |
269 | flags &= 0xe; | |
8cb9b9fb EP |
270 | |
271 | if (autoscan) | |
8c7524e6 | 272 | flags |= PCI9111_AI_TRIG_CTRL_ASCAN; |
8cb9b9fb | 273 | |
8c7524e6 | 274 | outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
8cb9b9fb EP |
275 | } |
276 | ||
3ba97b3c | 277 | enum pci9111_ISC0_sources { |
8cb9b9fb EP |
278 | irq_on_eoc, |
279 | irq_on_fifo_half_full | |
3ba97b3c | 280 | }; |
8cb9b9fb | 281 | |
52f8ac98 | 282 | enum pci9111_ISC1_sources { |
8cb9b9fb EP |
283 | irq_on_timer_tick, |
284 | irq_on_external_trigger | |
52f8ac98 | 285 | }; |
8cb9b9fb | 286 | |
da91b269 | 287 | static void pci9111_interrupt_source_set(struct comedi_device *dev, |
0a85b6f0 MT |
288 | enum pci9111_ISC0_sources irq_0_source, |
289 | enum pci9111_ISC1_sources irq_1_source) | |
8cb9b9fb EP |
290 | { |
291 | int flags; | |
292 | ||
2959bc21 | 293 | /* Read the current interrupt control bits */ |
8c7524e6 | 294 | flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG); |
2959bc21 HS |
295 | /* Shift the bits so they are compatible with the write register */ |
296 | flags >>= 4; | |
297 | /* Mask off the ISCx bits */ | |
298 | flags &= 0xc0; | |
8cb9b9fb | 299 | |
2959bc21 | 300 | /* Now set the new ISCx bits */ |
8cb9b9fb | 301 | if (irq_0_source == irq_on_fifo_half_full) |
8c7524e6 | 302 | flags |= PCI9111_INT_CTRL_ISC0; |
8cb9b9fb EP |
303 | |
304 | if (irq_1_source == irq_on_external_trigger) | |
8c7524e6 | 305 | flags |= PCI9111_INT_CTRL_ISC1; |
8cb9b9fb | 306 | |
2959bc21 | 307 | outb(flags, dev->iobase + PCI9111_INT_CTRL_REG); |
8cb9b9fb EP |
308 | } |
309 | ||
6b228d8a HS |
310 | static void pci9111_fifo_reset(struct comedi_device *dev) |
311 | { | |
312 | unsigned long int_ctrl_reg = dev->iobase + PCI9111_INT_CTRL_REG; | |
313 | ||
314 | /* To reset the FIFO, set FFEN sequence as 0 -> 1 -> 0 */ | |
8c7524e6 HS |
315 | outb(0, int_ctrl_reg); |
316 | outb(PCI9111_INT_CTRL_FFEN, int_ctrl_reg); | |
317 | outb(0, int_ctrl_reg); | |
6b228d8a HS |
318 | } |
319 | ||
52f8ac98 BP |
320 | /* ------------------------------------------------------------------ */ |
321 | /* HARDWARE TRIGGERED ANALOG INPUT SECTION */ | |
322 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 323 | |
52f8ac98 | 324 | /* Cancel analog input autoscan */ |
8cb9b9fb | 325 | |
0a85b6f0 MT |
326 | static int pci9111_ai_cancel(struct comedi_device *dev, |
327 | struct comedi_subdevice *s) | |
8cb9b9fb | 328 | { |
98943079 HS |
329 | struct pci9111_private_data *dev_private = dev->private; |
330 | ||
52f8ac98 | 331 | /* Disable interrupts */ |
8cb9b9fb EP |
332 | |
333 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, | |
0a85b6f0 | 334 | true, false); |
8cb9b9fb EP |
335 | |
336 | pci9111_trigger_source_set(dev, software); | |
337 | ||
338 | pci9111_autoscan_set(dev, false); | |
339 | ||
6b228d8a | 340 | pci9111_fifo_reset(dev); |
8cb9b9fb | 341 | |
8cb9b9fb EP |
342 | return 0; |
343 | } | |
344 | ||
97e01bb1 HS |
345 | static int pci9111_ai_do_cmd_test(struct comedi_device *dev, |
346 | struct comedi_subdevice *s, | |
347 | struct comedi_cmd *cmd) | |
8cb9b9fb | 348 | { |
98943079 | 349 | struct pci9111_private_data *dev_private = dev->private; |
8cb9b9fb EP |
350 | int tmp; |
351 | int error = 0; | |
352 | int range, reference; | |
353 | int i; | |
8cb9b9fb | 354 | |
97e01bb1 | 355 | /* Step 1 : check if trigger are trivialy valid */ |
8cb9b9fb | 356 | |
97e01bb1 HS |
357 | error |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
358 | error |= cfc_check_trigger_src(&cmd->scan_begin_src, | |
359 | TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); | |
360 | error |= cfc_check_trigger_src(&cmd->convert_src, | |
361 | TRIG_TIMER | TRIG_EXT); | |
362 | error |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
363 | error |= cfc_check_trigger_src(&cmd->stop_src, | |
364 | TRIG_COUNT | TRIG_NONE); | |
8cb9b9fb EP |
365 | |
366 | if (error) | |
367 | return 1; | |
368 | ||
e990333d | 369 | /* Step 2a : make sure trigger sources are unique */ |
8cb9b9fb | 370 | |
e990333d HS |
371 | error |= cfc_check_trigger_is_unique(cmd->scan_begin_src); |
372 | error |= cfc_check_trigger_is_unique(cmd->convert_src); | |
373 | error |= cfc_check_trigger_is_unique(cmd->stop_src); | |
374 | ||
375 | /* Step 2b : and mutually compatible */ | |
8cb9b9fb | 376 | |
8cb9b9fb | 377 | if ((cmd->convert_src == TRIG_TIMER) && |
0a85b6f0 | 378 | !((cmd->scan_begin_src == TRIG_TIMER) || |
2306d9b1 | 379 | (cmd->scan_begin_src == TRIG_FOLLOW))) |
e990333d | 380 | error |= -EINVAL; |
8cb9b9fb | 381 | if ((cmd->convert_src == TRIG_EXT) && |
0a85b6f0 | 382 | !((cmd->scan_begin_src == TRIG_EXT) || |
2306d9b1 | 383 | (cmd->scan_begin_src == TRIG_FOLLOW))) |
e990333d | 384 | error |= -EINVAL; |
8cb9b9fb EP |
385 | |
386 | if (error) | |
387 | return 2; | |
388 | ||
52f8ac98 | 389 | /* Step 3 : make sure arguments are trivialy compatible */ |
8cb9b9fb | 390 | |
8cb9b9fb EP |
391 | if ((cmd->start_src == TRIG_NOW) && (cmd->start_arg != 0)) { |
392 | cmd->start_arg = 0; | |
393 | error++; | |
394 | } | |
395 | ||
396 | if ((cmd->convert_src == TRIG_TIMER) && | |
02baee8c HS |
397 | (cmd->convert_arg < PCI9111_AI_ACQUISITION_PERIOD_MIN_NS)) { |
398 | cmd->convert_arg = PCI9111_AI_ACQUISITION_PERIOD_MIN_NS; | |
8cb9b9fb EP |
399 | error++; |
400 | } | |
401 | if ((cmd->convert_src == TRIG_EXT) && (cmd->convert_arg != 0)) { | |
402 | cmd->convert_arg = 0; | |
403 | error++; | |
404 | } | |
405 | ||
406 | if ((cmd->scan_begin_src == TRIG_TIMER) && | |
02baee8c HS |
407 | (cmd->scan_begin_arg < PCI9111_AI_ACQUISITION_PERIOD_MIN_NS)) { |
408 | cmd->scan_begin_arg = PCI9111_AI_ACQUISITION_PERIOD_MIN_NS; | |
8cb9b9fb EP |
409 | error++; |
410 | } | |
7ecac4c3 M |
411 | if ((cmd->scan_begin_src == TRIG_FOLLOW) |
412 | && (cmd->scan_begin_arg != 0)) { | |
8cb9b9fb EP |
413 | cmd->scan_begin_arg = 0; |
414 | error++; | |
415 | } | |
416 | if ((cmd->scan_begin_src == TRIG_EXT) && (cmd->scan_begin_arg != 0)) { | |
417 | cmd->scan_begin_arg = 0; | |
418 | error++; | |
419 | } | |
420 | ||
421 | if ((cmd->scan_end_src == TRIG_COUNT) && | |
0a85b6f0 | 422 | (cmd->scan_end_arg != cmd->chanlist_len)) { |
8cb9b9fb EP |
423 | cmd->scan_end_arg = cmd->chanlist_len; |
424 | error++; | |
425 | } | |
426 | ||
427 | if ((cmd->stop_src == TRIG_COUNT) && (cmd->stop_arg < 1)) { | |
428 | cmd->stop_arg = 1; | |
429 | error++; | |
430 | } | |
431 | if ((cmd->stop_src == TRIG_NONE) && (cmd->stop_arg != 0)) { | |
432 | cmd->stop_arg = 0; | |
433 | error++; | |
434 | } | |
435 | ||
436 | if (error) | |
437 | return 3; | |
438 | ||
52f8ac98 | 439 | /* Step 4 : fix up any arguments */ |
8cb9b9fb EP |
440 | |
441 | if (cmd->convert_src == TRIG_TIMER) { | |
442 | tmp = cmd->convert_arg; | |
443 | i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, | |
eba16272 HS |
444 | &dev_private->div1, |
445 | &dev_private->div2, | |
0a85b6f0 MT |
446 | &(cmd->convert_arg), |
447 | cmd->flags & TRIG_ROUND_MASK); | |
8cb9b9fb EP |
448 | if (tmp != cmd->convert_arg) |
449 | error++; | |
450 | } | |
52f8ac98 BP |
451 | /* There's only one timer on this card, so the scan_begin timer must */ |
452 | /* be a multiple of chanlist_len*convert_arg */ | |
8cb9b9fb EP |
453 | |
454 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
455 | ||
456 | unsigned int scan_begin_min; | |
457 | unsigned int scan_begin_arg; | |
458 | unsigned int scan_factor; | |
459 | ||
460 | scan_begin_min = cmd->chanlist_len * cmd->convert_arg; | |
461 | ||
462 | if (cmd->scan_begin_arg != scan_begin_min) { | |
463 | if (scan_begin_min < cmd->scan_begin_arg) { | |
464 | scan_factor = | |
0a85b6f0 | 465 | cmd->scan_begin_arg / scan_begin_min; |
8cb9b9fb EP |
466 | scan_begin_arg = scan_factor * scan_begin_min; |
467 | if (cmd->scan_begin_arg != scan_begin_arg) { | |
468 | cmd->scan_begin_arg = scan_begin_arg; | |
469 | error++; | |
470 | } | |
471 | } else { | |
472 | cmd->scan_begin_arg = scan_begin_min; | |
473 | error++; | |
474 | } | |
475 | } | |
476 | } | |
477 | ||
478 | if (error) | |
479 | return 4; | |
480 | ||
52f8ac98 | 481 | /* Step 5 : check channel list */ |
8cb9b9fb EP |
482 | |
483 | if (cmd->chanlist) { | |
484 | ||
485 | range = CR_RANGE(cmd->chanlist[0]); | |
486 | reference = CR_AREF(cmd->chanlist[0]); | |
487 | ||
488 | if (cmd->chanlist_len > 1) { | |
489 | for (i = 0; i < cmd->chanlist_len; i++) { | |
490 | if (CR_CHAN(cmd->chanlist[i]) != i) { | |
491 | comedi_error(dev, | |
0a85b6f0 MT |
492 | "entries in chanlist must be consecutive " |
493 | "channels,counting upwards from 0\n"); | |
8cb9b9fb EP |
494 | error++; |
495 | } | |
496 | if (CR_RANGE(cmd->chanlist[i]) != range) { | |
497 | comedi_error(dev, | |
0a85b6f0 | 498 | "entries in chanlist must all have the same gain\n"); |
8cb9b9fb EP |
499 | error++; |
500 | } | |
501 | if (CR_AREF(cmd->chanlist[i]) != reference) { | |
502 | comedi_error(dev, | |
0a85b6f0 | 503 | "entries in chanlist must all have the same reference\n"); |
8cb9b9fb EP |
504 | error++; |
505 | } | |
506 | } | |
8cb9b9fb EP |
507 | } |
508 | } | |
509 | ||
510 | if (error) | |
511 | return 5; | |
512 | ||
513 | return 0; | |
514 | ||
515 | } | |
516 | ||
52f8ac98 | 517 | /* Analog input command */ |
8cb9b9fb | 518 | |
0a85b6f0 | 519 | static int pci9111_ai_do_cmd(struct comedi_device *dev, |
d1d7b20d | 520 | struct comedi_subdevice *s) |
8cb9b9fb | 521 | { |
98943079 | 522 | struct pci9111_private_data *dev_private = dev->private; |
d1d7b20d | 523 | struct comedi_cmd *async_cmd = &s->async->cmd; |
8cb9b9fb EP |
524 | |
525 | if (!dev->irq) { | |
526 | comedi_error(dev, | |
0a85b6f0 | 527 | "no irq assigned for PCI9111, cannot do hardware conversion"); |
8cb9b9fb EP |
528 | return -1; |
529 | } | |
52f8ac98 BP |
530 | /* Set channel scan limit */ |
531 | /* PCI9111 allows only scanning from channel 0 to channel n */ | |
532 | /* TODO: handle the case of an external multiplexer */ | |
8cb9b9fb EP |
533 | |
534 | if (async_cmd->chanlist_len > 1) { | |
0f0bde92 HS |
535 | outb(async_cmd->chanlist_len - 1, |
536 | dev->iobase + PCI9111_AI_CHANNEL_REG); | |
8cb9b9fb EP |
537 | pci9111_autoscan_set(dev, true); |
538 | } else { | |
0f0bde92 HS |
539 | outb(CR_CHAN(async_cmd->chanlist[0]), |
540 | dev->iobase + PCI9111_AI_CHANNEL_REG); | |
8cb9b9fb EP |
541 | pci9111_autoscan_set(dev, false); |
542 | } | |
543 | ||
52f8ac98 BP |
544 | /* Set gain */ |
545 | /* This is the same gain on every channel */ | |
8cb9b9fb | 546 | |
8c7524e6 HS |
547 | outb(CR_RANGE(async_cmd->chanlist[0]) & PCI9111_AI_RANGE_MASK, |
548 | dev->iobase + PCI9111_AI_RANGE_STAT_REG); | |
8cb9b9fb EP |
549 | |
550 | /* Set counter */ | |
551 | ||
552 | switch (async_cmd->stop_src) { | |
553 | case TRIG_COUNT: | |
554 | dev_private->stop_counter = | |
0a85b6f0 | 555 | async_cmd->stop_arg * async_cmd->chanlist_len; |
8cb9b9fb EP |
556 | dev_private->stop_is_none = 0; |
557 | break; | |
558 | ||
559 | case TRIG_NONE: | |
560 | dev_private->stop_counter = 0; | |
561 | dev_private->stop_is_none = 1; | |
562 | break; | |
563 | ||
564 | default: | |
565 | comedi_error(dev, "Invalid stop trigger"); | |
566 | return -1; | |
567 | } | |
568 | ||
52f8ac98 | 569 | /* Set timer pacer */ |
8cb9b9fb EP |
570 | |
571 | dev_private->scan_delay = 0; | |
572 | switch (async_cmd->convert_src) { | |
573 | case TRIG_TIMER: | |
574 | i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, | |
eba16272 HS |
575 | &dev_private->div1, |
576 | &dev_private->div2, | |
0a85b6f0 MT |
577 | &(async_cmd->convert_arg), |
578 | async_cmd-> | |
579 | flags & TRIG_ROUND_MASK); | |
8cb9b9fb EP |
580 | |
581 | pci9111_trigger_source_set(dev, software); | |
582 | pci9111_timer_set(dev); | |
6b228d8a | 583 | pci9111_fifo_reset(dev); |
8cb9b9fb | 584 | pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, |
0a85b6f0 | 585 | irq_on_timer_tick); |
8cb9b9fb EP |
586 | pci9111_trigger_source_set(dev, timer_pacer); |
587 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, | |
0a85b6f0 | 588 | false, true, true); |
8cb9b9fb | 589 | |
6c2fd308 IA |
590 | if (async_cmd->scan_begin_src == TRIG_TIMER) { |
591 | dev_private->scan_delay = | |
592 | (async_cmd->scan_begin_arg / | |
593 | (async_cmd->convert_arg * | |
594 | async_cmd->chanlist_len)) - 1; | |
595 | } | |
8cb9b9fb EP |
596 | |
597 | break; | |
598 | ||
599 | case TRIG_EXT: | |
600 | ||
601 | pci9111_trigger_source_set(dev, external); | |
6b228d8a | 602 | pci9111_fifo_reset(dev); |
8cb9b9fb | 603 | pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, |
0a85b6f0 | 604 | irq_on_timer_tick); |
8cb9b9fb | 605 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, |
0a85b6f0 | 606 | false, true, true); |
8cb9b9fb EP |
607 | |
608 | break; | |
609 | ||
610 | default: | |
611 | comedi_error(dev, "Invalid convert trigger"); | |
612 | return -1; | |
613 | } | |
614 | ||
615 | dev_private->stop_counter *= (1 + dev_private->scan_delay); | |
616 | dev_private->chanlist_len = async_cmd->chanlist_len; | |
617 | dev_private->chunk_counter = 0; | |
618 | dev_private->chunk_num_samples = | |
0a85b6f0 | 619 | dev_private->chanlist_len * (1 + dev_private->scan_delay); |
8cb9b9fb | 620 | |
8cb9b9fb EP |
621 | return 0; |
622 | } | |
623 | ||
0a85b6f0 MT |
624 | static void pci9111_ai_munge(struct comedi_device *dev, |
625 | struct comedi_subdevice *s, void *data, | |
626 | unsigned int num_bytes, | |
627 | unsigned int start_chan_index) | |
8cb9b9fb | 628 | { |
790c5541 | 629 | short *array = data; |
af031edf HS |
630 | unsigned int maxdata = s->maxdata; |
631 | unsigned int invert = (maxdata + 1) >> 1; | |
632 | unsigned int shift = (maxdata == 0xffff) ? 0 : 4; | |
633 | unsigned int num_samples = num_bytes / sizeof(short); | |
634 | unsigned int i; | |
635 | ||
636 | for (i = 0; i < num_samples; i++) | |
637 | array[i] = ((array[i] >> shift) & maxdata) ^ invert; | |
8cb9b9fb EP |
638 | } |
639 | ||
52f8ac98 BP |
640 | /* ------------------------------------------------------------------ */ |
641 | /* INTERRUPT SECTION */ | |
642 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 643 | |
70265d24 | 644 | static irqreturn_t pci9111_interrupt(int irq, void *p_device) |
8cb9b9fb | 645 | { |
71b5f4f1 | 646 | struct comedi_device *dev = p_device; |
98943079 | 647 | struct pci9111_private_data *dev_private = dev->private; |
d1d7b20d | 648 | struct comedi_subdevice *s = dev->read_subdev; |
d163679c | 649 | struct comedi_async *async; |
96764373 | 650 | unsigned int status; |
8cb9b9fb EP |
651 | unsigned long irq_flags; |
652 | unsigned char intcsr; | |
653 | ||
654 | if (!dev->attached) { | |
52f8ac98 BP |
655 | /* Ignore interrupt before device fully attached. */ |
656 | /* Might not even have allocated subdevices yet! */ | |
8cb9b9fb EP |
657 | return IRQ_NONE; |
658 | } | |
659 | ||
d1d7b20d | 660 | async = s->async; |
8cb9b9fb | 661 | |
5f74ea14 | 662 | spin_lock_irqsave(&dev->spinlock, irq_flags); |
8cb9b9fb | 663 | |
52f8ac98 | 664 | /* Check if we are source of interrupt */ |
8cb9b9fb | 665 | intcsr = inb(dev_private->lcr_io_base + |
0a85b6f0 | 666 | PLX9050_REGISTER_INTERRUPT_CONTROL); |
8cb9b9fb | 667 | if (!(((intcsr & PLX9050_PCI_INTERRUPT_ENABLE) != 0) |
0a85b6f0 MT |
668 | && (((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) |
669 | == (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) | |
670 | || ((intcsr & (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS)) | |
671 | == (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS))))) { | |
52f8ac98 BP |
672 | /* Not the source of the interrupt. */ |
673 | /* (N.B. not using PLX9050_SOFTWARE_INTERRUPT) */ | |
5f74ea14 | 674 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb EP |
675 | return IRQ_NONE; |
676 | } | |
677 | ||
678 | if ((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) == | |
0a85b6f0 | 679 | (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) { |
52f8ac98 | 680 | /* Interrupt comes from fifo_half-full signal */ |
8cb9b9fb | 681 | |
8c7524e6 | 682 | status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); |
96764373 HS |
683 | |
684 | /* '0' means FIFO is full, data may have been lost */ | |
8c7524e6 | 685 | if (!(status & PCI9111_AI_STAT_FF_FF)) { |
0a85b6f0 | 686 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb | 687 | comedi_error(dev, PCI9111_DRIVER_NAME " fifo overflow"); |
f123f287 | 688 | outb(0, dev->iobase + PCI9111_INT_CLR_REG); |
d1d7b20d | 689 | pci9111_ai_cancel(dev, s); |
8cb9b9fb | 690 | async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; |
d1d7b20d | 691 | comedi_event(dev, s); |
8cb9b9fb EP |
692 | |
693 | return IRQ_HANDLED; | |
694 | } | |
695 | ||
96764373 | 696 | /* '0' means FIFO is half-full */ |
8c7524e6 | 697 | if (!(status & PCI9111_AI_STAT_FF_HF)) { |
8cb9b9fb EP |
698 | unsigned int num_samples; |
699 | unsigned int bytes_written = 0; | |
700 | ||
8cb9b9fb | 701 | num_samples = |
0a85b6f0 MT |
702 | PCI9111_FIFO_HALF_SIZE > |
703 | dev_private->stop_counter | |
704 | && !dev_private-> | |
705 | stop_is_none ? dev_private->stop_counter : | |
706 | PCI9111_FIFO_HALF_SIZE; | |
b5d8d119 | 707 | insw(dev->iobase + PCI9111_AI_FIFO_REG, |
0a85b6f0 | 708 | dev_private->ai_bounce_buffer, num_samples); |
8cb9b9fb EP |
709 | |
710 | if (dev_private->scan_delay < 1) { | |
711 | bytes_written = | |
d1d7b20d | 712 | cfc_write_array_to_buffer(s, |
0a85b6f0 MT |
713 | dev_private-> |
714 | ai_bounce_buffer, | |
715 | num_samples * | |
716 | sizeof(short)); | |
8cb9b9fb EP |
717 | } else { |
718 | int position = 0; | |
719 | int to_read; | |
720 | ||
721 | while (position < num_samples) { | |
722 | if (dev_private->chunk_counter < | |
0a85b6f0 | 723 | dev_private->chanlist_len) { |
8cb9b9fb | 724 | to_read = |
0a85b6f0 MT |
725 | dev_private->chanlist_len - |
726 | dev_private->chunk_counter; | |
8cb9b9fb EP |
727 | |
728 | if (to_read > | |
0a85b6f0 | 729 | num_samples - position) |
8cb9b9fb | 730 | to_read = |
0a85b6f0 MT |
731 | num_samples - |
732 | position; | |
8cb9b9fb EP |
733 | |
734 | bytes_written += | |
0a85b6f0 | 735 | cfc_write_array_to_buffer |
d1d7b20d | 736 | (s, |
0a85b6f0 MT |
737 | dev_private->ai_bounce_buffer |
738 | + position, | |
739 | to_read * sizeof(short)); | |
8cb9b9fb EP |
740 | } else { |
741 | to_read = | |
0a85b6f0 MT |
742 | dev_private->chunk_num_samples |
743 | - | |
744 | dev_private->chunk_counter; | |
8cb9b9fb | 745 | if (to_read > |
0a85b6f0 | 746 | num_samples - position) |
8cb9b9fb | 747 | to_read = |
0a85b6f0 MT |
748 | num_samples - |
749 | position; | |
8cb9b9fb EP |
750 | |
751 | bytes_written += | |
0a85b6f0 | 752 | sizeof(short) * to_read; |
8cb9b9fb EP |
753 | } |
754 | ||
755 | position += to_read; | |
756 | dev_private->chunk_counter += to_read; | |
757 | ||
758 | if (dev_private->chunk_counter >= | |
0a85b6f0 | 759 | dev_private->chunk_num_samples) |
8cb9b9fb EP |
760 | dev_private->chunk_counter = 0; |
761 | } | |
762 | } | |
763 | ||
764 | dev_private->stop_counter -= | |
0a85b6f0 | 765 | bytes_written / sizeof(short); |
8cb9b9fb EP |
766 | } |
767 | } | |
768 | ||
769 | if ((dev_private->stop_counter == 0) && (!dev_private->stop_is_none)) { | |
770 | async->events |= COMEDI_CB_EOA; | |
d1d7b20d | 771 | pci9111_ai_cancel(dev, s); |
8cb9b9fb EP |
772 | } |
773 | ||
f123f287 | 774 | outb(0, dev->iobase + PCI9111_INT_CLR_REG); |
8cb9b9fb | 775 | |
5f74ea14 | 776 | spin_unlock_irqrestore(&dev->spinlock, irq_flags); |
8cb9b9fb | 777 | |
d1d7b20d | 778 | comedi_event(dev, s); |
8cb9b9fb EP |
779 | |
780 | return IRQ_HANDLED; | |
781 | } | |
782 | ||
52f8ac98 BP |
783 | /* ------------------------------------------------------------------ */ |
784 | /* INSTANT ANALOG INPUT OUTPUT SECTION */ | |
785 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 786 | |
52f8ac98 | 787 | /* analog instant input */ |
8cb9b9fb | 788 | |
da91b269 | 789 | static int pci9111_ai_insn_read(struct comedi_device *dev, |
d1d7b20d | 790 | struct comedi_subdevice *s, |
0a85b6f0 | 791 | struct comedi_insn *insn, unsigned int *data) |
8cb9b9fb | 792 | { |
ae479ee5 HS |
793 | unsigned int chan = CR_CHAN(insn->chanspec); |
794 | unsigned int range = CR_RANGE(insn->chanspec); | |
2f002cc9 HS |
795 | unsigned int maxdata = s->maxdata; |
796 | unsigned int invert = (maxdata + 1) >> 1; | |
797 | unsigned int shift = (maxdata == 0xffff) ? 0 : 4; | |
96764373 | 798 | unsigned int status; |
2f002cc9 HS |
799 | int timeout; |
800 | int i; | |
8cb9b9fb | 801 | |
0f0bde92 | 802 | outb(chan, dev->iobase + PCI9111_AI_CHANNEL_REG); |
8cb9b9fb | 803 | |
8c7524e6 HS |
804 | status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); |
805 | if ((status & PCI9111_AI_RANGE_MASK) != range) { | |
806 | outb(range & PCI9111_AI_RANGE_MASK, | |
807 | dev->iobase + PCI9111_AI_RANGE_STAT_REG); | |
c514bab7 | 808 | } |
8cb9b9fb | 809 | |
6b228d8a | 810 | pci9111_fifo_reset(dev); |
8cb9b9fb EP |
811 | |
812 | for (i = 0; i < insn->n; i++) { | |
3eb60d73 | 813 | /* Generate a software trigger */ |
8c7524e6 | 814 | outb(0, dev->iobase + PCI9111_SOFT_TRIG_REG); |
8cb9b9fb EP |
815 | |
816 | timeout = PCI9111_AI_INSTANT_READ_TIMEOUT; | |
817 | ||
818 | while (timeout--) { | |
8c7524e6 | 819 | status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG); |
96764373 | 820 | /* '1' means FIFO is not empty */ |
8c7524e6 | 821 | if (status & PCI9111_AI_STAT_FF_EF) |
8cb9b9fb EP |
822 | goto conversion_done; |
823 | } | |
824 | ||
825 | comedi_error(dev, "A/D read timeout"); | |
826 | data[i] = 0; | |
6b228d8a | 827 | pci9111_fifo_reset(dev); |
8cb9b9fb EP |
828 | return -ETIME; |
829 | ||
0a85b6f0 | 830 | conversion_done: |
8cb9b9fb | 831 | |
2f002cc9 HS |
832 | data[i] = inw(dev->iobase + PCI9111_AI_FIFO_REG); |
833 | data[i] = ((data[i] >> shift) & maxdata) ^ invert; | |
8cb9b9fb EP |
834 | } |
835 | ||
8cb9b9fb EP |
836 | return i; |
837 | } | |
838 | ||
2084fd19 HS |
839 | static int pci9111_ao_insn_write(struct comedi_device *dev, |
840 | struct comedi_subdevice *s, | |
841 | struct comedi_insn *insn, | |
842 | unsigned int *data) | |
8cb9b9fb | 843 | { |
98943079 | 844 | struct pci9111_private_data *dev_private = dev->private; |
2084fd19 | 845 | unsigned int val = 0; |
8cb9b9fb EP |
846 | int i; |
847 | ||
848 | for (i = 0; i < insn->n; i++) { | |
2084fd19 HS |
849 | val = data[i]; |
850 | outw(val, dev->iobase + PCI9111_AO_REG); | |
8cb9b9fb | 851 | } |
2084fd19 | 852 | dev_private->ao_readback = val; |
8cb9b9fb | 853 | |
2084fd19 | 854 | return insn->n; |
8cb9b9fb EP |
855 | } |
856 | ||
da91b269 | 857 | static int pci9111_ao_insn_read(struct comedi_device *dev, |
0a85b6f0 | 858 | struct comedi_subdevice *s, |
b3450faf HS |
859 | struct comedi_insn *insn, |
860 | unsigned int *data) | |
8cb9b9fb | 861 | { |
98943079 | 862 | struct pci9111_private_data *dev_private = dev->private; |
8cb9b9fb EP |
863 | int i; |
864 | ||
2306d9b1 | 865 | for (i = 0; i < insn->n; i++) |
b3450faf | 866 | data[i] = dev_private->ao_readback; |
8cb9b9fb | 867 | |
b3450faf | 868 | return insn->n; |
8cb9b9fb EP |
869 | } |
870 | ||
da91b269 | 871 | static int pci9111_di_insn_bits(struct comedi_device *dev, |
d1d7b20d | 872 | struct comedi_subdevice *s, |
bfa6d3b8 HS |
873 | struct comedi_insn *insn, |
874 | unsigned int *data) | |
8cb9b9fb | 875 | { |
bfa6d3b8 | 876 | data[1] = inw(dev->iobase + PCI9111_DIO_REG); |
8cb9b9fb | 877 | |
a2714e3e | 878 | return insn->n; |
8cb9b9fb EP |
879 | } |
880 | ||
da91b269 | 881 | static int pci9111_do_insn_bits(struct comedi_device *dev, |
d1d7b20d | 882 | struct comedi_subdevice *s, |
83dcfee0 HS |
883 | struct comedi_insn *insn, |
884 | unsigned int *data) | |
8cb9b9fb | 885 | { |
83dcfee0 HS |
886 | unsigned int mask = data[0]; |
887 | unsigned int bits = data[1]; | |
8cb9b9fb | 888 | |
83dcfee0 HS |
889 | if (mask) { |
890 | s->state &= ~mask; | |
891 | s->state |= (bits & mask); | |
8cb9b9fb | 892 | |
83dcfee0 HS |
893 | outw(s->state, dev->iobase + PCI9111_DIO_REG); |
894 | } | |
8cb9b9fb | 895 | |
83dcfee0 | 896 | data[1] = s->state; |
8cb9b9fb | 897 | |
a2714e3e | 898 | return insn->n; |
8cb9b9fb EP |
899 | } |
900 | ||
52f8ac98 BP |
901 | /* ------------------------------------------------------------------ */ |
902 | /* INITIALISATION SECTION */ | |
903 | /* ------------------------------------------------------------------ */ | |
8cb9b9fb | 904 | |
52f8ac98 | 905 | /* Reset device */ |
8cb9b9fb | 906 | |
da91b269 | 907 | static int pci9111_reset(struct comedi_device *dev) |
8cb9b9fb | 908 | { |
98943079 HS |
909 | struct pci9111_private_data *dev_private = dev->private; |
910 | ||
52f8ac98 | 911 | /* Set trigger source to software */ |
8cb9b9fb EP |
912 | |
913 | plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, | |
0a85b6f0 | 914 | true, false); |
8cb9b9fb EP |
915 | |
916 | pci9111_trigger_source_set(dev, software); | |
917 | pci9111_pretrigger_set(dev, false); | |
918 | pci9111_autoscan_set(dev, false); | |
919 | ||
eba16272 HS |
920 | /* Reset 8254 chip */ |
921 | dev_private->div1 = 0; | |
922 | dev_private->div2 = 0; | |
8cb9b9fb EP |
923 | pci9111_timer_set(dev); |
924 | ||
925 | return 0; | |
926 | } | |
927 | ||
3e5a0ba0 HS |
928 | static int pci9111_attach_pci(struct comedi_device *dev, |
929 | struct pci_dev *pcidev) | |
8cb9b9fb | 930 | { |
98943079 | 931 | struct pci9111_private_data *dev_private; |
d1d7b20d | 932 | struct comedi_subdevice *s; |
98943079 | 933 | int ret; |
3e5a0ba0 HS |
934 | |
935 | comedi_set_hw_dev(dev, &pcidev->dev); | |
936 | dev->board_name = dev->driver->driver_name; | |
8cb9b9fb | 937 | |
98943079 HS |
938 | ret = alloc_private(dev, sizeof(*dev_private)); |
939 | if (ret) | |
940 | return ret; | |
941 | dev_private = dev->private; | |
942 | ||
3e5a0ba0 HS |
943 | ret = comedi_pci_enable(pcidev, dev->board_name); |
944 | if (ret) | |
945 | return ret; | |
946 | dev_private->lcr_io_base = pci_resource_start(pcidev, 1); | |
947 | dev->iobase = pci_resource_start(pcidev, 2); | |
8cb9b9fb EP |
948 | |
949 | pci9111_reset(dev); | |
950 | ||
6a7b1b0c | 951 | if (pcidev->irq > 0) { |
3e5a0ba0 HS |
952 | ret = request_irq(dev->irq, pci9111_interrupt, |
953 | IRQF_SHARED, dev->board_name, dev); | |
954 | if (ret) | |
955 | return ret; | |
6a7b1b0c | 956 | dev->irq = pcidev->irq; |
8cb9b9fb | 957 | } |
8cb9b9fb | 958 | |
98943079 HS |
959 | ret = comedi_alloc_subdevices(dev, 4); |
960 | if (ret) | |
961 | return ret; | |
8cb9b9fb | 962 | |
573e31af | 963 | s = &dev->subdevices[0]; |
d1d7b20d | 964 | dev->read_subdev = s; |
02baee8c HS |
965 | s->type = COMEDI_SUBD_AI; |
966 | s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; | |
967 | s->n_chan = 16; | |
968 | s->maxdata = 0xffff; | |
969 | s->len_chanlist = 16; | |
afa6ac4a | 970 | s->range_table = &pci9111_ai_range; |
02baee8c HS |
971 | s->cancel = pci9111_ai_cancel; |
972 | s->insn_read = pci9111_ai_insn_read; | |
973 | s->do_cmdtest = pci9111_ai_do_cmd_test; | |
974 | s->do_cmd = pci9111_ai_do_cmd; | |
975 | s->munge = pci9111_ai_munge; | |
d1d7b20d | 976 | |
573e31af | 977 | s = &dev->subdevices[1]; |
05841b36 HS |
978 | s->type = COMEDI_SUBD_AO; |
979 | s->subdev_flags = SDF_WRITABLE | SDF_COMMON; | |
980 | s->n_chan = 1; | |
981 | s->maxdata = 0x0fff; | |
982 | s->len_chanlist = 1; | |
983 | s->range_table = &range_bipolar10; | |
984 | s->insn_write = pci9111_ao_insn_write; | |
985 | s->insn_read = pci9111_ao_insn_read; | |
d1d7b20d | 986 | |
573e31af | 987 | s = &dev->subdevices[2]; |
3acf3176 HS |
988 | s->type = COMEDI_SUBD_DI; |
989 | s->subdev_flags = SDF_READABLE; | |
990 | s->n_chan = 16; | |
991 | s->maxdata = 1; | |
992 | s->range_table = &range_digital; | |
993 | s->insn_bits = pci9111_di_insn_bits; | |
d1d7b20d | 994 | |
573e31af | 995 | s = &dev->subdevices[3]; |
3acf3176 HS |
996 | s->type = COMEDI_SUBD_DO; |
997 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
998 | s->n_chan = 16; | |
999 | s->maxdata = 1; | |
1000 | s->range_table = &range_digital; | |
1001 | s->insn_bits = pci9111_do_insn_bits; | |
8cb9b9fb | 1002 | |
034f8734 HS |
1003 | dev_info(dev->class_dev, "%s attached\n", dev->board_name); |
1004 | ||
8cb9b9fb EP |
1005 | return 0; |
1006 | } | |
1007 | ||
484ecc95 | 1008 | static void pci9111_detach(struct comedi_device *dev) |
8cb9b9fb | 1009 | { |
6a7b1b0c HS |
1010 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
1011 | ||
893be483 HS |
1012 | if (dev->iobase) |
1013 | pci9111_reset(dev); | |
2306d9b1 | 1014 | if (dev->irq != 0) |
5f74ea14 | 1015 | free_irq(dev->irq, dev); |
6a7b1b0c | 1016 | if (pcidev) { |
2306d9b1 | 1017 | if (dev->iobase) |
6a7b1b0c HS |
1018 | comedi_pci_disable(pcidev); |
1019 | pci_dev_put(pcidev); | |
8cb9b9fb | 1020 | } |
8cb9b9fb | 1021 | } |
90f703d3 | 1022 | |
75e6301b HS |
1023 | static struct comedi_driver adl_pci9111_driver = { |
1024 | .driver_name = "adl_pci9111", | |
e68a83fe | 1025 | .module = THIS_MODULE, |
3e5a0ba0 | 1026 | .attach_pci = pci9111_attach_pci, |
e68a83fe HS |
1027 | .detach = pci9111_detach, |
1028 | }; | |
1029 | ||
75e6301b HS |
1030 | static int __devinit pci9111_pci_probe(struct pci_dev *dev, |
1031 | const struct pci_device_id *ent) | |
e68a83fe | 1032 | { |
75e6301b | 1033 | return comedi_pci_auto_config(dev, &adl_pci9111_driver); |
e68a83fe HS |
1034 | } |
1035 | ||
75e6301b | 1036 | static void __devexit pci9111_pci_remove(struct pci_dev *dev) |
e68a83fe HS |
1037 | { |
1038 | comedi_pci_auto_unconfig(dev); | |
1039 | } | |
1040 | ||
1041 | static DEFINE_PCI_DEVICE_TABLE(pci9111_pci_table) = { | |
1042 | { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HR_DEVICE_ID) }, | |
1043 | /* { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID) }, */ | |
1044 | { 0 } | |
1045 | }; | |
1046 | MODULE_DEVICE_TABLE(pci, pci9111_pci_table); | |
1047 | ||
75e6301b HS |
1048 | static struct pci_driver adl_pci9111_pci_driver = { |
1049 | .name = "adl_pci9111", | |
e68a83fe | 1050 | .id_table = pci9111_pci_table, |
75e6301b HS |
1051 | .probe = pci9111_pci_probe, |
1052 | .remove = __devexit_p(pci9111_pci_remove), | |
e68a83fe | 1053 | }; |
75e6301b | 1054 | module_comedi_pci_driver(adl_pci9111_driver, adl_pci9111_pci_driver); |
e68a83fe | 1055 | |
90f703d3 AT |
1056 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
1057 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
1058 | MODULE_LICENSE("GPL"); |