Commit | Line | Data |
---|---|---|
9afbebe4 FMH |
1 | /* |
2 | comedi/drivers/das16m1.c | |
3 | CIO-DAS16/M1 driver | |
4 | Author: Frank Mori Hess, based on code from the das16 | |
5 | driver. | |
6 | Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net> | |
7 | ||
8 | COMEDI - Linux Control and Measurement Device Interface | |
9 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
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 | /* | |
28 | Driver: das16m1 | |
29 | Description: CIO-DAS16/M1 | |
30 | Author: Frank Mori Hess <fmhess@users.sourceforge.net> | |
31 | Devices: [Measurement Computing] CIO-DAS16/M1 (cio-das16/m1) | |
32 | Status: works | |
33 | ||
34 | This driver supports a single board - the CIO-DAS16/M1. | |
35 | As far as I know, there are no other boards that have | |
36 | the same register layout. Even the CIO-DAS16/M1/16 is | |
37 | significantly different. | |
38 | ||
39 | I was _barely_ able to reach the full 1 MHz capability | |
40 | of this board, using a hard real-time interrupt | |
41 | (set the TRIG_RT flag in your comedi_cmd and use | |
42 | rtlinux or RTAI). The board can't do dma, so the bottleneck is | |
43 | pulling the data across the ISA bus. I timed the interrupt | |
44 | handler, and it took my computer ~470 microseconds to pull 512 | |
45 | samples from the board. So at 1 Mhz sampling rate, | |
46 | expect your CPU to be spending almost all of its | |
47 | time in the interrupt handler. | |
48 | ||
49 | This board has some unusual restrictions for its channel/gain list. If the | |
50 | list has 2 or more channels in it, then two conditions must be satisfied: | |
51 | (1) - even/odd channels must appear at even/odd indices in the list | |
52 | (2) - the list must have an even number of entries. | |
53 | ||
54 | Options: | |
55 | [0] - base io address | |
56 | [1] - irq (optional, but you probably want it) | |
57 | ||
58 | irq can be omitted, although the cmd interface will not work without it. | |
59 | */ | |
60 | ||
61 | #include <linux/ioport.h> | |
62 | #include "../comedidev.h" | |
63 | ||
64 | #include "8255.h" | |
65 | #include "8253.h" | |
66 | #include "comedi_fc.h" | |
67 | ||
68 | #define DAS16M1_SIZE 16 | |
69 | #define DAS16M1_SIZE2 8 | |
70 | ||
71 | #define DAS16M1_XTAL 100 //10 MHz master clock | |
72 | ||
73 | #define FIFO_SIZE 1024 // 1024 sample fifo | |
74 | ||
75 | /* | |
76 | CIO-DAS16_M1.pdf | |
77 | ||
78 | "cio-das16/m1" | |
79 | ||
80 | 0 a/d bits 0-3, mux start 12 bit | |
81 | 1 a/d bits 4-11 unused | |
82 | 2 status control | |
83 | 3 di 4 bit do 4 bit | |
84 | 4 unused clear interrupt | |
85 | 5 interrupt, pacer | |
86 | 6 channel/gain queue address | |
87 | 7 channel/gain queue data | |
88 | 89ab 8254 | |
89 | cdef 8254 | |
90 | 400 8255 | |
91 | 404-407 8254 | |
92 | ||
93 | */ | |
94 | ||
95 | #define DAS16M1_AI 0 // 16-bit wide register | |
96 | #define AI_CHAN(x) ((x) & 0xf) | |
97 | #define DAS16M1_CS 2 | |
98 | #define EXT_TRIG_BIT 0x1 | |
99 | #define OVRUN 0x20 | |
100 | #define IRQDATA 0x80 | |
101 | #define DAS16M1_DIO 3 | |
102 | #define DAS16M1_CLEAR_INTR 4 | |
103 | #define DAS16M1_INTR_CONTROL 5 | |
104 | #define EXT_PACER 0x2 | |
105 | #define INT_PACER 0x3 | |
106 | #define PACER_MASK 0x3 | |
107 | #define INTE 0x80 | |
108 | #define DAS16M1_QUEUE_ADDR 6 | |
109 | #define DAS16M1_QUEUE_DATA 7 | |
110 | #define Q_CHAN(x) ((x) & 0x7) | |
111 | #define Q_RANGE(x) (((x) & 0xf) << 4) | |
112 | #define UNIPOLAR 0x40 | |
113 | #define DAS16M1_8254_FIRST 0x8 | |
114 | #define DAS16M1_8254_FIRST_CNTRL 0xb | |
115 | #define TOTAL_CLEAR 0x30 | |
116 | #define DAS16M1_8254_SECOND 0xc | |
117 | #define DAS16M1_82C55 0x400 | |
118 | #define DAS16M1_8254_THIRD 0x404 | |
119 | ||
120 | static const comedi_lrange range_das16m1 = { 9, | |
121 | { | |
122 | BIP_RANGE(5), | |
123 | BIP_RANGE(2.5), | |
124 | BIP_RANGE(1.25), | |
125 | BIP_RANGE(0.625), | |
126 | UNI_RANGE(10), | |
127 | UNI_RANGE(5), | |
128 | UNI_RANGE(2.5), | |
129 | UNI_RANGE(1.25), | |
130 | BIP_RANGE(10), | |
131 | } | |
132 | }; | |
133 | ||
134 | static int das16m1_do_wbits(comedi_device * dev, comedi_subdevice * s, | |
790c5541 | 135 | comedi_insn * insn, unsigned int * data); |
9afbebe4 | 136 | static int das16m1_di_rbits(comedi_device * dev, comedi_subdevice * s, |
790c5541 | 137 | comedi_insn * insn, unsigned int * data); |
9afbebe4 | 138 | static int das16m1_ai_rinsn(comedi_device * dev, comedi_subdevice * s, |
790c5541 | 139 | comedi_insn * insn, unsigned int * data); |
9afbebe4 FMH |
140 | |
141 | static int das16m1_cmd_test(comedi_device * dev, comedi_subdevice * s, | |
142 | comedi_cmd * cmd); | |
143 | static int das16m1_cmd_exec(comedi_device * dev, comedi_subdevice * s); | |
144 | static int das16m1_cancel(comedi_device * dev, comedi_subdevice * s); | |
145 | ||
146 | static int das16m1_poll(comedi_device * dev, comedi_subdevice * s); | |
147 | static irqreturn_t das16m1_interrupt(int irq, void *d PT_REGS_ARG); | |
148 | static void das16m1_handler(comedi_device * dev, unsigned int status); | |
149 | ||
150 | static unsigned int das16m1_set_pacer(comedi_device * dev, unsigned int ns, | |
151 | int round_flag); | |
152 | ||
153 | static int das16m1_irq_bits(unsigned int irq); | |
154 | ||
155 | typedef struct das16m1_board_struct { | |
156 | const char *name; | |
157 | unsigned int ai_speed; | |
158 | } das16m1_board; | |
159 | ||
160 | static const das16m1_board das16m1_boards[] = { | |
161 | { | |
162 | name: "cio-das16/m1", // CIO-DAS16_M1.pdf | |
163 | ai_speed:1000, // 1MHz max speed | |
164 | }, | |
165 | }; | |
166 | ||
167 | #define das16m1_num_boards ((sizeof(das16m1_boards)) / (sizeof(das16m1_boards[0]))) | |
168 | ||
169 | static int das16m1_attach(comedi_device * dev, comedi_devconfig * it); | |
170 | static int das16m1_detach(comedi_device * dev); | |
171 | static comedi_driver driver_das16m1 = { | |
172 | driver_name:"das16m1", | |
173 | module:THIS_MODULE, | |
174 | attach:das16m1_attach, | |
175 | detach:das16m1_detach, | |
176 | board_name:&das16m1_boards[0].name, | |
177 | num_names:das16m1_num_boards, | |
178 | offset:sizeof(das16m1_boards[0]), | |
179 | }; | |
180 | ||
181 | struct das16m1_private_struct { | |
182 | unsigned int control_state; | |
183 | volatile unsigned int adc_count; // number of samples completed | |
184 | /* initial value in lower half of hardware conversion counter, | |
185 | * needed to keep track of whether new count has been loaded into | |
186 | * counter yet (loaded by first sample conversion) */ | |
187 | u16 initial_hw_count; | |
790c5541 | 188 | short ai_buffer[FIFO_SIZE]; |
9afbebe4 FMH |
189 | unsigned int do_bits; // saves status of digital output bits |
190 | unsigned int divisor1; // divides master clock to obtain conversion speed | |
191 | unsigned int divisor2; // divides master clock to obtain conversion speed | |
192 | }; | |
193 | #define devpriv ((struct das16m1_private_struct *)(dev->private)) | |
194 | #define thisboard ((const das16m1_board *)(dev->board_ptr)) | |
195 | ||
196 | COMEDI_INITCLEANUP(driver_das16m1); | |
197 | ||
790c5541 | 198 | static inline short munge_sample(short data) |
9afbebe4 FMH |
199 | { |
200 | return (data >> 4) & 0xfff; | |
201 | } | |
202 | ||
203 | static int das16m1_cmd_test(comedi_device * dev, comedi_subdevice * s, | |
204 | comedi_cmd * cmd) | |
205 | { | |
206 | unsigned int err = 0, tmp, i; | |
207 | ||
208 | /* make sure triggers are valid */ | |
209 | tmp = cmd->start_src; | |
210 | cmd->start_src &= TRIG_NOW | TRIG_EXT; | |
211 | if (!cmd->start_src || tmp != cmd->start_src) | |
212 | err++; | |
213 | ||
214 | tmp = cmd->scan_begin_src; | |
215 | cmd->scan_begin_src &= TRIG_FOLLOW; | |
216 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
217 | err++; | |
218 | ||
219 | tmp = cmd->convert_src; | |
220 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
221 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
222 | err++; | |
223 | ||
224 | tmp = cmd->scan_end_src; | |
225 | cmd->scan_end_src &= TRIG_COUNT; | |
226 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
227 | err++; | |
228 | ||
229 | tmp = cmd->stop_src; | |
230 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
231 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
232 | err++; | |
233 | ||
234 | if (err) | |
235 | return 1; | |
236 | ||
237 | /* step 2: make sure trigger sources are unique and mutually compatible */ | |
238 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
239 | err++; | |
240 | if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) | |
241 | err++; | |
242 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
243 | err++; | |
244 | ||
245 | if (err) | |
246 | return 2; | |
247 | ||
248 | /* step 3: make sure arguments are trivially compatible */ | |
249 | if (cmd->start_arg != 0) { | |
250 | cmd->start_arg = 0; | |
251 | err++; | |
252 | } | |
253 | ||
254 | if (cmd->scan_begin_src == TRIG_FOLLOW) { | |
255 | /* internal trigger */ | |
256 | if (cmd->scan_begin_arg != 0) { | |
257 | cmd->scan_begin_arg = 0; | |
258 | err++; | |
259 | } | |
260 | } | |
261 | ||
262 | if (cmd->convert_src == TRIG_TIMER) { | |
263 | if (cmd->convert_arg < thisboard->ai_speed) { | |
264 | cmd->convert_arg = thisboard->ai_speed; | |
265 | err++; | |
266 | } | |
267 | } | |
268 | ||
269 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
270 | cmd->scan_end_arg = cmd->chanlist_len; | |
271 | err++; | |
272 | } | |
273 | ||
274 | if (cmd->stop_src == TRIG_COUNT) { | |
275 | /* any count is allowed */ | |
276 | } else { | |
277 | /* TRIG_NONE */ | |
278 | if (cmd->stop_arg != 0) { | |
279 | cmd->stop_arg = 0; | |
280 | err++; | |
281 | } | |
282 | } | |
283 | ||
284 | if (err) | |
285 | return 3; | |
286 | ||
287 | /* step 4: fix up arguments */ | |
288 | ||
289 | if (cmd->convert_src == TRIG_TIMER) { | |
290 | tmp = cmd->convert_arg; | |
291 | /* calculate counter values that give desired timing */ | |
292 | i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, | |
293 | &(devpriv->divisor1), &(devpriv->divisor2), | |
294 | &(cmd->convert_arg), cmd->flags & TRIG_ROUND_MASK); | |
295 | if (tmp != cmd->convert_arg) | |
296 | err++; | |
297 | } | |
298 | ||
299 | if (err) | |
300 | return 4; | |
301 | ||
302 | // check chanlist against board's peculiarities | |
303 | if (cmd->chanlist && cmd->chanlist_len > 1) { | |
304 | for (i = 0; i < cmd->chanlist_len; i++) { | |
305 | // even/odd channels must go into even/odd queue addresses | |
306 | if ((i % 2) != (CR_CHAN(cmd->chanlist[i]) % 2)) { | |
307 | comedi_error(dev, "bad chanlist:\n" | |
308 | " even/odd channels must go have even/odd chanlist indices"); | |
309 | err++; | |
310 | } | |
311 | } | |
312 | if ((cmd->chanlist_len % 2) != 0) { | |
313 | comedi_error(dev, | |
314 | "chanlist must be of even length or length 1"); | |
315 | err++; | |
316 | } | |
317 | } | |
318 | ||
319 | if (err) | |
320 | return 5; | |
321 | ||
322 | return 0; | |
323 | } | |
324 | ||
325 | static int das16m1_cmd_exec(comedi_device * dev, comedi_subdevice * s) | |
326 | { | |
327 | comedi_async *async = s->async; | |
328 | comedi_cmd *cmd = &async->cmd; | |
329 | unsigned int byte, i; | |
330 | ||
331 | if (dev->irq == 0) { | |
332 | comedi_error(dev, "irq required to execute comedi_cmd"); | |
333 | return -1; | |
334 | } | |
335 | ||
336 | /* disable interrupts and internal pacer */ | |
337 | devpriv->control_state &= ~INTE & ~PACER_MASK; | |
338 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
339 | ||
340 | // set software count | |
341 | devpriv->adc_count = 0; | |
342 | /* Initialize lower half of hardware counter, used to determine how | |
343 | * many samples are in fifo. Value doesn't actually load into counter | |
344 | * until counter's next clock (the next a/d conversion) */ | |
345 | i8254_load(dev->iobase + DAS16M1_8254_FIRST, 0, 1, 0, 2); | |
346 | /* remember current reading of counter so we know when counter has | |
347 | * actually been loaded */ | |
348 | devpriv->initial_hw_count = | |
349 | i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1); | |
350 | /* setup channel/gain queue */ | |
351 | for (i = 0; i < cmd->chanlist_len; i++) { | |
352 | outb(i, dev->iobase + DAS16M1_QUEUE_ADDR); | |
353 | byte = Q_CHAN(CR_CHAN(cmd-> | |
354 | chanlist[i])) | Q_RANGE(CR_RANGE(cmd-> | |
355 | chanlist[i])); | |
356 | outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); | |
357 | } | |
358 | ||
359 | /* set counter mode and counts */ | |
360 | cmd->convert_arg = | |
361 | das16m1_set_pacer(dev, cmd->convert_arg, | |
362 | cmd->flags & TRIG_ROUND_MASK); | |
363 | ||
364 | // set control & status register | |
365 | byte = 0; | |
366 | /* if we are using external start trigger (also board dislikes having | |
367 | * both start and conversion triggers external simultaneously) */ | |
368 | if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT) { | |
369 | byte |= EXT_TRIG_BIT; | |
370 | } | |
371 | outb(byte, dev->iobase + DAS16M1_CS); | |
372 | /* clear interrupt bit */ | |
373 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
374 | ||
375 | /* enable interrupts and internal pacer */ | |
376 | devpriv->control_state &= ~PACER_MASK; | |
377 | if (cmd->convert_src == TRIG_TIMER) { | |
378 | devpriv->control_state |= INT_PACER; | |
379 | } else { | |
380 | devpriv->control_state |= EXT_PACER; | |
381 | } | |
382 | devpriv->control_state |= INTE; | |
383 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
384 | ||
385 | return 0; | |
386 | } | |
387 | ||
388 | static int das16m1_cancel(comedi_device * dev, comedi_subdevice * s) | |
389 | { | |
390 | devpriv->control_state &= ~INTE & ~PACER_MASK; | |
391 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
396 | static int das16m1_ai_rinsn(comedi_device * dev, comedi_subdevice * s, | |
790c5541 | 397 | comedi_insn * insn, unsigned int * data) |
9afbebe4 FMH |
398 | { |
399 | int i, n; | |
400 | int byte; | |
401 | const int timeout = 1000; | |
402 | ||
403 | /* disable interrupts and internal pacer */ | |
404 | devpriv->control_state &= ~INTE & ~PACER_MASK; | |
405 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
406 | ||
407 | /* setup channel/gain queue */ | |
408 | outb(0, dev->iobase + DAS16M1_QUEUE_ADDR); | |
409 | byte = Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn-> | |
410 | chanspec)); | |
411 | outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); | |
412 | ||
413 | for (n = 0; n < insn->n; n++) { | |
414 | /* clear IRQDATA bit */ | |
415 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
416 | /* trigger conversion */ | |
417 | outb(0, dev->iobase); | |
418 | ||
419 | for (i = 0; i < timeout; i++) { | |
420 | if (inb(dev->iobase + DAS16M1_CS) & IRQDATA) | |
421 | break; | |
422 | } | |
423 | if (i == timeout) { | |
424 | comedi_error(dev, "timeout"); | |
425 | return -ETIME; | |
426 | } | |
427 | data[n] = munge_sample(inw(dev->iobase)); | |
428 | } | |
429 | ||
430 | return n; | |
431 | } | |
432 | ||
433 | static int das16m1_di_rbits(comedi_device * dev, comedi_subdevice * s, | |
790c5541 | 434 | comedi_insn * insn, unsigned int * data) |
9afbebe4 | 435 | { |
790c5541 | 436 | unsigned int bits; |
9afbebe4 FMH |
437 | |
438 | bits = inb(dev->iobase + DAS16M1_DIO) & 0xf; | |
439 | data[1] = bits; | |
440 | data[0] = 0; | |
441 | ||
442 | return 2; | |
443 | } | |
444 | ||
445 | static int das16m1_do_wbits(comedi_device * dev, comedi_subdevice * s, | |
790c5541 | 446 | comedi_insn * insn, unsigned int * data) |
9afbebe4 | 447 | { |
790c5541 | 448 | unsigned int wbits; |
9afbebe4 FMH |
449 | |
450 | // only set bits that have been masked | |
451 | data[0] &= 0xf; | |
452 | wbits = devpriv->do_bits; | |
453 | // zero bits that have been masked | |
454 | wbits &= ~data[0]; | |
455 | // set masked bits | |
456 | wbits |= data[0] & data[1]; | |
457 | devpriv->do_bits = wbits; | |
458 | data[1] = wbits; | |
459 | ||
460 | outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO); | |
461 | ||
462 | return 2; | |
463 | } | |
464 | ||
465 | static int das16m1_poll(comedi_device * dev, comedi_subdevice * s) | |
466 | { | |
467 | unsigned long flags; | |
468 | unsigned int status; | |
469 | ||
470 | // prevent race with interrupt handler | |
471 | comedi_spin_lock_irqsave(&dev->spinlock, flags); | |
472 | status = inb(dev->iobase + DAS16M1_CS); | |
473 | das16m1_handler(dev, status); | |
474 | comedi_spin_unlock_irqrestore(&dev->spinlock, flags); | |
475 | ||
476 | return s->async->buf_write_count - s->async->buf_read_count; | |
477 | } | |
478 | ||
479 | static irqreturn_t das16m1_interrupt(int irq, void *d PT_REGS_ARG) | |
480 | { | |
481 | int status; | |
482 | comedi_device *dev = d; | |
483 | ||
484 | if (dev->attached == 0) { | |
485 | comedi_error(dev, "premature interrupt"); | |
486 | return IRQ_HANDLED; | |
487 | } | |
488 | // prevent race with comedi_poll() | |
489 | spin_lock(&dev->spinlock); | |
490 | ||
491 | status = inb(dev->iobase + DAS16M1_CS); | |
492 | ||
493 | if ((status & (IRQDATA | OVRUN)) == 0) { | |
494 | comedi_error(dev, "spurious interrupt"); | |
495 | spin_unlock(&dev->spinlock); | |
496 | return IRQ_NONE; | |
497 | } | |
498 | ||
499 | das16m1_handler(dev, status); | |
500 | ||
501 | /* clear interrupt */ | |
502 | outb(0, dev->iobase + DAS16M1_CLEAR_INTR); | |
503 | ||
504 | spin_unlock(&dev->spinlock); | |
505 | return IRQ_HANDLED; | |
506 | } | |
507 | ||
790c5541 | 508 | static void munge_sample_array(short * array, unsigned int num_elements) |
9afbebe4 FMH |
509 | { |
510 | unsigned int i; | |
511 | ||
512 | for (i = 0; i < num_elements; i++) { | |
513 | array[i] = munge_sample(array[i]); | |
514 | } | |
515 | } | |
516 | ||
517 | static void das16m1_handler(comedi_device * dev, unsigned int status) | |
518 | { | |
519 | comedi_subdevice *s; | |
520 | comedi_async *async; | |
521 | comedi_cmd *cmd; | |
522 | u16 num_samples; | |
523 | u16 hw_counter; | |
524 | ||
525 | s = dev->read_subdev; | |
526 | async = s->async; | |
527 | async->events = 0; | |
528 | cmd = &async->cmd; | |
529 | ||
530 | // figure out how many samples are in fifo | |
531 | hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1); | |
532 | /* make sure hardware counter reading is not bogus due to initial value | |
533 | * not having been loaded yet */ | |
534 | if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) { | |
535 | num_samples = 0; | |
536 | } else { | |
537 | /* The calculation of num_samples looks odd, but it uses the following facts. | |
538 | * 16 bit hardware counter is initialized with value of zero (which really | |
539 | * means 0x1000). The counter decrements by one on each conversion | |
540 | * (when the counter decrements from zero it goes to 0xffff). num_samples | |
541 | * is a 16 bit variable, so it will roll over in a similar fashion to the | |
542 | * hardware counter. Work it out, and this is what you get. */ | |
543 | num_samples = -hw_counter - devpriv->adc_count; | |
544 | } | |
545 | // check if we only need some of the points | |
546 | if (cmd->stop_src == TRIG_COUNT) { | |
547 | if (num_samples > cmd->stop_arg * cmd->chanlist_len) | |
548 | num_samples = cmd->stop_arg * cmd->chanlist_len; | |
549 | } | |
550 | // make sure we dont try to get too many points if fifo has overrun | |
551 | if (num_samples > FIFO_SIZE) | |
552 | num_samples = FIFO_SIZE; | |
553 | insw(dev->iobase, devpriv->ai_buffer, num_samples); | |
554 | munge_sample_array(devpriv->ai_buffer, num_samples); | |
555 | cfc_write_array_to_buffer(s, devpriv->ai_buffer, | |
790c5541 | 556 | num_samples * sizeof(short)); |
9afbebe4 FMH |
557 | devpriv->adc_count += num_samples; |
558 | ||
559 | if (cmd->stop_src == TRIG_COUNT) { | |
560 | if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) { /* end of acquisition */ | |
561 | das16m1_cancel(dev, s); | |
562 | async->events |= COMEDI_CB_EOA; | |
563 | } | |
564 | } | |
565 | ||
566 | /* this probably won't catch overruns since the card doesn't generate | |
567 | * overrun interrupts, but we might as well try */ | |
568 | if (status & OVRUN) { | |
569 | das16m1_cancel(dev, s); | |
570 | async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; | |
571 | comedi_error(dev, "fifo overflow"); | |
572 | } | |
573 | ||
574 | comedi_event(dev, s); | |
575 | ||
576 | } | |
577 | ||
578 | /* This function takes a time in nanoseconds and sets the * | |
579 | * 2 pacer clocks to the closest frequency possible. It also * | |
580 | * returns the actual sampling period. */ | |
581 | static unsigned int das16m1_set_pacer(comedi_device * dev, unsigned int ns, | |
582 | int rounding_flags) | |
583 | { | |
584 | i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1), | |
585 | &(devpriv->divisor2), &ns, rounding_flags & TRIG_ROUND_MASK); | |
586 | ||
587 | /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ | |
588 | i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 1, devpriv->divisor1, | |
589 | 2); | |
590 | i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 2, devpriv->divisor2, | |
591 | 2); | |
592 | ||
593 | return ns; | |
594 | } | |
595 | ||
596 | static int das16m1_irq_bits(unsigned int irq) | |
597 | { | |
598 | int ret; | |
599 | ||
600 | switch (irq) { | |
601 | case 10: | |
602 | ret = 0x0; | |
603 | break; | |
604 | case 11: | |
605 | ret = 0x1; | |
606 | break; | |
607 | case 12: | |
608 | ret = 0x2; | |
609 | break; | |
610 | case 15: | |
611 | ret = 0x3; | |
612 | break; | |
613 | case 2: | |
614 | ret = 0x4; | |
615 | break; | |
616 | case 3: | |
617 | ret = 0x5; | |
618 | break; | |
619 | case 5: | |
620 | ret = 0x6; | |
621 | break; | |
622 | case 7: | |
623 | ret = 0x7; | |
624 | break; | |
625 | default: | |
626 | return -1; | |
627 | break; | |
628 | } | |
629 | return (ret << 4); | |
630 | } | |
631 | ||
632 | /* | |
633 | * Options list: | |
634 | * 0 I/O base | |
635 | * 1 IRQ | |
636 | */ | |
637 | ||
638 | static int das16m1_attach(comedi_device * dev, comedi_devconfig * it) | |
639 | { | |
640 | comedi_subdevice *s; | |
641 | int ret; | |
642 | unsigned int irq; | |
643 | unsigned long iobase; | |
644 | ||
645 | iobase = it->options[0]; | |
646 | ||
647 | printk("comedi%d: das16m1:", dev->minor); | |
648 | ||
649 | if ((ret = alloc_private(dev, | |
650 | sizeof(struct das16m1_private_struct))) < 0) | |
651 | return ret; | |
652 | ||
653 | dev->board_name = thisboard->name; | |
654 | ||
655 | printk(" io 0x%lx-0x%lx 0x%lx-0x%lx", | |
656 | iobase, iobase + DAS16M1_SIZE, | |
657 | iobase + DAS16M1_82C55, iobase + DAS16M1_82C55 + DAS16M1_SIZE2); | |
658 | if (!request_region(iobase, DAS16M1_SIZE, driver_das16m1.driver_name)) { | |
659 | printk(" I/O port conflict\n"); | |
660 | return -EIO; | |
661 | } | |
662 | if (!request_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2, | |
663 | driver_das16m1.driver_name)) { | |
664 | release_region(iobase, DAS16M1_SIZE); | |
665 | printk(" I/O port conflict\n"); | |
666 | return -EIO; | |
667 | } | |
668 | dev->iobase = iobase; | |
669 | ||
670 | /* now for the irq */ | |
671 | irq = it->options[1]; | |
672 | // make sure it is valid | |
673 | if (das16m1_irq_bits(irq) >= 0) { | |
674 | ret = comedi_request_irq(irq, das16m1_interrupt, 0, | |
675 | driver_das16m1.driver_name, dev); | |
676 | if (ret < 0) { | |
677 | printk(", irq unavailable\n"); | |
678 | return ret; | |
679 | } | |
680 | dev->irq = irq; | |
681 | printk(", irq %u\n", irq); | |
682 | } else if (irq == 0) { | |
683 | printk(", no irq\n"); | |
684 | } else { | |
685 | printk(", invalid irq\n" | |
686 | " valid irqs are 2, 3, 5, 7, 10, 11, 12, or 15\n"); | |
687 | return -EINVAL; | |
688 | } | |
689 | ||
690 | if ((ret = alloc_subdevices(dev, 4)) < 0) | |
691 | return ret; | |
692 | ||
693 | s = dev->subdevices + 0; | |
694 | dev->read_subdev = s; | |
695 | /* ai */ | |
696 | s->type = COMEDI_SUBD_AI; | |
697 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; | |
698 | s->n_chan = 8; | |
699 | s->subdev_flags = SDF_DIFF; | |
700 | s->len_chanlist = 256; | |
701 | s->maxdata = (1 << 12) - 1; | |
702 | s->range_table = &range_das16m1; | |
703 | s->insn_read = das16m1_ai_rinsn; | |
704 | s->do_cmdtest = das16m1_cmd_test; | |
705 | s->do_cmd = das16m1_cmd_exec; | |
706 | s->cancel = das16m1_cancel; | |
707 | s->poll = das16m1_poll; | |
708 | ||
709 | s = dev->subdevices + 1; | |
710 | /* di */ | |
711 | s->type = COMEDI_SUBD_DI; | |
712 | s->subdev_flags = SDF_READABLE; | |
713 | s->n_chan = 4; | |
714 | s->maxdata = 1; | |
715 | s->range_table = &range_digital; | |
716 | s->insn_bits = das16m1_di_rbits; | |
717 | ||
718 | s = dev->subdevices + 2; | |
719 | /* do */ | |
720 | s->type = COMEDI_SUBD_DO; | |
721 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
722 | s->n_chan = 4; | |
723 | s->maxdata = 1; | |
724 | s->range_table = &range_digital; | |
725 | s->insn_bits = das16m1_do_wbits; | |
726 | ||
727 | s = dev->subdevices + 3; | |
728 | /* 8255 */ | |
729 | subdev_8255_init(dev, s, NULL, dev->iobase + DAS16M1_82C55); | |
730 | ||
731 | // disable upper half of hardware conversion counter so it doesn't mess with us | |
732 | outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL); | |
733 | ||
734 | // initialize digital output lines | |
735 | outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO); | |
736 | ||
737 | /* set the interrupt level */ | |
738 | if (dev->irq) | |
739 | devpriv->control_state = das16m1_irq_bits(dev->irq); | |
740 | else | |
741 | devpriv->control_state = 0; | |
742 | outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); | |
743 | ||
744 | return 0; | |
745 | } | |
746 | ||
747 | static int das16m1_detach(comedi_device * dev) | |
748 | { | |
749 | printk("comedi%d: das16m1: remove\n", dev->minor); | |
750 | ||
751 | // das16m1_reset(dev); | |
752 | ||
753 | if (dev->subdevices) | |
754 | subdev_8255_cleanup(dev, dev->subdevices + 3); | |
755 | ||
756 | if (dev->irq) | |
757 | comedi_free_irq(dev->irq, dev); | |
758 | ||
759 | if (dev->iobase) { | |
760 | release_region(dev->iobase, DAS16M1_SIZE); | |
761 | release_region(dev->iobase + DAS16M1_82C55, DAS16M1_SIZE2); | |
762 | } | |
763 | ||
764 | return 0; | |
765 | } |