2 comedi/drivers/das800.c
3 Driver for Keitley das800 series boards and compatibles
4 Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
6 COMEDI - Linux Control and Measurement Device Interface
7 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ************************************************************************
27 Description: Keithley Metrabyte DAS800 (& compatibles)
28 Author: Frank Mori Hess <fmhess@users.sourceforge.net>
29 Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801),
31 [Measurement Computing] CIO-DAS800 (cio-das800),
32 CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802),
33 CIO-DAS802/16 (cio-das802/16)
34 Status: works, cio-das802/16 untested - email me if you have tested it
36 Configuration options:
37 [0] - I/O port base address
38 [1] - IRQ (optional, required for timed or externally triggered conversions)
41 IRQ can be omitted, although the cmd interface will not work without it.
43 All entries in the channel/gain list must use the same gain and be
44 consecutive channels counting upwards in channel number (these are
45 hardware limitations.)
47 I've never tested the gain setting stuff since I only have a
48 DAS-800 board with fixed gain.
50 The cio-das802/16 does not have a fifo-empty status bit! Therefore
51 only fifo-half-full transfers are possible with this card.
55 cmd triggers supported:
56 start_src: TRIG_NOW | TRIG_EXT
57 scan_begin_src: TRIG_FOLLOW
58 scan_end_src: TRIG_COUNT
59 convert_src: TRIG_TIMER | TRIG_EXT
60 stop_src: TRIG_NONE | TRIG_COUNT
65 #include <linux/interrupt.h>
66 #include "../comedidev.h"
68 #include <linux/ioport.h>
69 #include <linux/delay.h>
72 #include "comedi_fc.h"
75 #define TIMER_BASE 1000
76 #define N_CHAN_AI 8 /* number of analog input channels */
78 /* Registers for the das800 */
81 #define FIFO_EMPTY 0x1
84 #define DAS800_CONTROL1 2
85 #define CONTROL1_INTE 0x8
86 #define DAS800_CONV_CONTROL 2
92 #define CONV_HCEN 0x80
93 #define DAS800_SCAN_LIMITS 2
94 #define DAS800_STATUS 2
98 #define CIO_FFOV 0x8 /* fifo overflow for cio-das802/16 */
99 #define CIO_ENHF 0x90 /* interrupt fifo half full for cio-das802/16 */
100 #define CONTROL1 0x80
101 #define CONV_CONTROL 0xa0
102 #define SCAN_LIMITS 0xc0
104 #define DAS800_8254 4
105 #define DAS800_STATUS2 7
106 #define STATUS2_HCEN 0x80
107 #define STATUS2_INTE 0X20
110 struct das800_board {
113 const struct comedi_lrange *ai_range;
117 /* analog input ranges */
118 static const struct comedi_lrange range_das800_ai = {
125 static const struct comedi_lrange range_das801_ai = {
140 static const struct comedi_lrange range_cio_das801_ai = {
150 RANGE(-0.005, 0.005),
155 static const struct comedi_lrange range_das802_ai = {
165 RANGE(-0.625, 0.625),
170 static const struct comedi_lrange range_das80216_ai = {
184 enum { das800, ciodas800, das801, ciodas801, das802, ciodas802, ciodas80216 };
186 static const struct das800_board das800_boards[] = {
190 .ai_range = &range_das800_ai,
194 .name = "cio-das800",
196 .ai_range = &range_das800_ai,
202 .ai_range = &range_das801_ai,
206 .name = "cio-das801",
208 .ai_range = &range_cio_das801_ai,
214 .ai_range = &range_das802_ai,
218 .name = "cio-das802",
220 .ai_range = &range_das802_ai,
224 .name = "cio-das802/16",
226 .ai_range = &range_das80216_ai,
232 * Useful for shorthand access to the particular board structure
234 #define thisboard ((const struct das800_board *)dev->board_ptr)
236 struct das800_private {
237 volatile unsigned int count; /* number of data points left to be taken */
238 volatile int forever; /* flag indicating whether we should take data forever */
239 unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */
240 unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */
241 volatile int do_bits; /* digital output bits */
244 #define devpriv ((struct das800_private *)dev->private)
246 static int das800_attach(struct comedi_device *dev,
247 struct comedi_devconfig *it);
248 static void das800_detach(struct comedi_device *dev);
249 static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s);
251 static struct comedi_driver driver_das800 = {
252 .driver_name = "das800",
253 .module = THIS_MODULE,
254 .attach = das800_attach,
255 .detach = das800_detach,
256 .num_names = ARRAY_SIZE(das800_boards),
257 .board_name = &das800_boards[0].name,
258 .offset = sizeof(struct das800_board),
261 static irqreturn_t das800_interrupt(int irq, void *d);
262 static void enable_das800(struct comedi_device *dev);
263 static void disable_das800(struct comedi_device *dev);
264 static int das800_ai_do_cmdtest(struct comedi_device *dev,
265 struct comedi_subdevice *s,
266 struct comedi_cmd *cmd);
267 static int das800_ai_do_cmd(struct comedi_device *dev,
268 struct comedi_subdevice *s);
269 static int das800_ai_rinsn(struct comedi_device *dev,
270 struct comedi_subdevice *s, struct comedi_insn *insn,
272 static int das800_di_rbits(struct comedi_device *dev,
273 struct comedi_subdevice *s, struct comedi_insn *insn,
275 static int das800_do_wbits(struct comedi_device *dev,
276 struct comedi_subdevice *s, struct comedi_insn *insn,
278 static int das800_probe(struct comedi_device *dev);
279 static int das800_set_frequency(struct comedi_device *dev);
281 /* checks and probes das-800 series board type */
282 static int das800_probe(struct comedi_device *dev)
285 unsigned long irq_flags;
288 /* 'comedi spin lock irqsave' disables even rt interrupts, we use them to protect indirect addressing */
289 spin_lock_irqsave(&dev->spinlock, irq_flags);
290 outb(ID, dev->iobase + DAS800_GAIN); /* select base address + 7 to be ID register */
291 id_bits = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */
292 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
294 board = thisboard - das800_boards;
298 if (board == das800) {
299 dev_dbg(dev->class_dev, "Board model: DAS-800\n");
302 if (board == ciodas800) {
303 dev_dbg(dev->class_dev, "Board model: CIO-DAS800\n");
306 dev_dbg(dev->class_dev, "Board model (probed): DAS-800\n");
310 if (board == das801) {
311 dev_dbg(dev->class_dev, "Board model: DAS-801\n");
314 if (board == ciodas801) {
315 dev_dbg(dev->class_dev, "Board model: CIO-DAS801\n");
318 dev_dbg(dev->class_dev, "Board model (probed): DAS-801\n");
322 if (board == das802) {
323 dev_dbg(dev->class_dev, "Board model: DAS-802\n");
326 if (board == ciodas802) {
327 dev_dbg(dev->class_dev, "Board model: CIO-DAS802\n");
330 if (board == ciodas80216) {
331 dev_dbg(dev->class_dev, "Board model: CIO-DAS802/16\n");
334 dev_dbg(dev->class_dev, "Board model (probed): DAS-802\n");
338 dev_dbg(dev->class_dev,
339 "Board model: probe returned 0x%x (unknown)\n",
348 * A convenient macro that defines init_module() and cleanup_module(),
351 static int __init driver_das800_init_module(void)
353 return comedi_driver_register(&driver_das800);
356 static void __exit driver_das800_cleanup_module(void)
358 comedi_driver_unregister(&driver_das800);
361 module_init(driver_das800_init_module);
362 module_exit(driver_das800_cleanup_module);
364 /* interrupt service routine */
365 static irqreturn_t das800_interrupt(int irq, void *d)
367 short i; /* loop index */
369 struct comedi_device *dev = d;
370 struct comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */
371 struct comedi_async *async;
373 unsigned long irq_flags;
374 static const int max_loops = 128; /* half-fifo size for cio-das802/16 */
377 int fifo_overflow = 0;
379 status = inb(dev->iobase + DAS800_STATUS);
380 /* if interrupt was not generated by board or driver not attached, quit */
383 if (!(dev->attached))
386 /* wait until here to initialize async, since we will get null dereference
387 * if interrupt occurs before driver is fully attached!
391 /* if hardware conversions are not enabled, then quit */
392 spin_lock_irqsave(&dev->spinlock, irq_flags);
393 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */
394 status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN;
395 /* don't release spinlock yet since we want to make sure no one else disables hardware conversions */
397 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
401 /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */
402 for (i = 0; i < max_loops; i++) {
403 /* read 16 bits from dev->iobase and dev->iobase + 1 */
404 dataPoint = inb(dev->iobase + DAS800_LSB);
405 dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
406 if (thisboard->resolution == 12) {
407 fifo_empty = dataPoint & FIFO_EMPTY;
408 fifo_overflow = dataPoint & FIFO_OVF;
412 fifo_empty = 0; /* cio-das802/16 has no fifo empty status bit */
416 /* strip off extraneous bits for 12 bit cards */
417 if (thisboard->resolution == 12)
418 dataPoint = (dataPoint >> 4) & 0xfff;
419 /* if there are more data points to collect */
420 if (devpriv->count > 0 || devpriv->forever == 1) {
421 /* write data point to buffer */
422 cfc_write_to_buffer(s, dataPoint);
423 if (devpriv->count > 0)
427 async->events |= COMEDI_CB_BLOCK;
428 /* check for fifo overflow */
429 if (thisboard->resolution == 12) {
430 fifo_overflow = dataPoint & FIFO_OVF;
431 /* else cio-das802/16 */
433 fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV;
436 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
437 comedi_error(dev, "DAS800 FIFO overflow");
438 das800_cancel(dev, dev->subdevices + 0);
439 async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
440 comedi_event(dev, s);
444 if (devpriv->count > 0 || devpriv->forever == 1) {
445 /* Re-enable card's interrupt.
446 * We already have spinlock, so indirect addressing is safe */
447 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
448 outb(CONTROL1_INTE | devpriv->do_bits,
449 dev->iobase + DAS800_CONTROL1);
450 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
451 /* otherwise, stop taking data */
453 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
454 disable_das800(dev); /* disable hardware triggered conversions */
455 async->events |= COMEDI_CB_EOA;
457 comedi_event(dev, s);
462 static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
464 struct comedi_subdevice *s;
465 unsigned long iobase = it->options[0];
466 unsigned int irq = it->options[1];
467 unsigned long irq_flags;
471 dev_info(dev->class_dev, "das800: io 0x%lx\n", iobase);
473 dev_dbg(dev->class_dev, "irq %u\n", irq);
475 /* allocate and initialize dev->private */
476 if (alloc_private(dev, sizeof(struct das800_private)) < 0)
480 dev_err(dev->class_dev,
481 "io base address required for das800\n");
485 /* check if io addresses are available */
486 if (!request_region(iobase, DAS800_SIZE, "das800")) {
487 dev_err(dev->class_dev, "I/O port conflict\n");
490 dev->iobase = iobase;
492 board = das800_probe(dev);
494 dev_dbg(dev->class_dev, "unable to determine board type\n");
497 dev->board_ptr = das800_boards + board;
500 if (irq == 1 || irq > 7) {
501 dev_err(dev->class_dev, "irq out of range\n");
505 if (request_irq(irq, das800_interrupt, 0, "das800", dev)) {
506 dev_err(dev->class_dev, "unable to allocate irq %u\n",
513 dev->board_name = thisboard->name;
515 ret = comedi_alloc_subdevices(dev, 3);
519 /* analog input subdevice */
520 s = dev->subdevices + 0;
521 dev->read_subdev = s;
522 s->type = COMEDI_SUBD_AI;
523 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
526 s->maxdata = (1 << thisboard->resolution) - 1;
527 s->range_table = thisboard->ai_range;
528 s->do_cmd = das800_ai_do_cmd;
529 s->do_cmdtest = das800_ai_do_cmdtest;
530 s->insn_read = das800_ai_rinsn;
531 s->cancel = das800_cancel;
534 s = dev->subdevices + 1;
535 s->type = COMEDI_SUBD_DI;
536 s->subdev_flags = SDF_READABLE;
539 s->range_table = &range_digital;
540 s->insn_bits = das800_di_rbits;
543 s = dev->subdevices + 2;
544 s->type = COMEDI_SUBD_DO;
545 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
548 s->range_table = &range_digital;
549 s->insn_bits = das800_do_wbits;
553 /* initialize digital out channels */
554 spin_lock_irqsave(&dev->spinlock, irq_flags);
555 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
556 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
557 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
562 static void das800_detach(struct comedi_device *dev)
565 release_region(dev->iobase, DAS800_SIZE);
567 free_irq(dev->irq, dev);
570 static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
572 devpriv->forever = 0;
578 /* enable_das800 makes the card start taking hardware triggered conversions */
579 static void enable_das800(struct comedi_device *dev)
581 unsigned long irq_flags;
582 spin_lock_irqsave(&dev->spinlock, irq_flags);
583 /* enable fifo-half full interrupts for cio-das802/16 */
584 if (thisboard->resolution == 16)
585 outb(CIO_ENHF, dev->iobase + DAS800_GAIN);
586 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
587 outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */
588 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
589 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */
590 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
593 /* disable_das800 stops hardware triggered conversions */
594 static void disable_das800(struct comedi_device *dev)
596 unsigned long irq_flags;
597 spin_lock_irqsave(&dev->spinlock, irq_flags);
598 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
599 outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */
600 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
603 static int das800_ai_do_cmdtest(struct comedi_device *dev,
604 struct comedi_subdevice *s,
605 struct comedi_cmd *cmd)
612 /* step 1: make sure trigger sources are trivially valid */
614 tmp = cmd->start_src;
615 cmd->start_src &= TRIG_NOW | TRIG_EXT;
616 if (!cmd->start_src || tmp != cmd->start_src)
619 tmp = cmd->scan_begin_src;
620 cmd->scan_begin_src &= TRIG_FOLLOW;
621 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
624 tmp = cmd->convert_src;
625 cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
626 if (!cmd->convert_src || tmp != cmd->convert_src)
629 tmp = cmd->scan_end_src;
630 cmd->scan_end_src &= TRIG_COUNT;
631 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
635 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
636 if (!cmd->stop_src || tmp != cmd->stop_src)
642 /* step 2: make sure trigger sources are unique and mutually compatible */
644 if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
646 if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT)
648 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
654 /* step 3: make sure arguments are trivially compatible */
656 if (cmd->start_arg != 0) {
660 if (cmd->convert_src == TRIG_TIMER) {
661 if (cmd->convert_arg < thisboard->ai_speed) {
662 cmd->convert_arg = thisboard->ai_speed;
666 if (!cmd->chanlist_len) {
667 cmd->chanlist_len = 1;
670 if (cmd->scan_end_arg != cmd->chanlist_len) {
671 cmd->scan_end_arg = cmd->chanlist_len;
674 if (cmd->stop_src == TRIG_COUNT) {
675 if (!cmd->stop_arg) {
679 } else { /* TRIG_NONE */
680 if (cmd->stop_arg != 0) {
689 /* step 4: fix up any arguments */
691 if (cmd->convert_src == TRIG_TIMER) {
692 tmp = cmd->convert_arg;
693 /* calculate counter values that give desired timing */
694 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
695 &(devpriv->divisor2),
697 cmd->flags & TRIG_ROUND_MASK);
698 if (tmp != cmd->convert_arg)
705 /* check channel/gain list against card's limitations */
707 gain = CR_RANGE(cmd->chanlist[0]);
708 startChan = CR_CHAN(cmd->chanlist[0]);
709 for (i = 1; i < cmd->chanlist_len; i++) {
710 if (CR_CHAN(cmd->chanlist[i]) !=
711 (startChan + i) % N_CHAN_AI) {
713 "entries in chanlist must be consecutive channels, counting upwards\n");
716 if (CR_RANGE(cmd->chanlist[i]) != gain) {
718 "entries in chanlist must all have the same gain\n");
730 static int das800_ai_do_cmd(struct comedi_device *dev,
731 struct comedi_subdevice *s)
733 int startChan, endChan, scan, gain;
735 unsigned long irq_flags;
736 struct comedi_async *async = s->async;
740 "no irq assigned for das-800, cannot do hardware conversions");
746 /* set channel scan limits */
747 startChan = CR_CHAN(async->cmd.chanlist[0]);
748 endChan = (startChan + async->cmd.chanlist_len - 1) % 8;
749 scan = (endChan << 3) | startChan;
751 spin_lock_irqsave(&dev->spinlock, irq_flags);
752 outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */
753 outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
754 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
757 gain = CR_RANGE(async->cmd.chanlist[0]);
758 if (thisboard->resolution == 12 && gain > 0)
761 outb(gain, dev->iobase + DAS800_GAIN);
763 switch (async->cmd.stop_src) {
765 devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len;
766 devpriv->forever = 0;
769 devpriv->forever = 1;
776 /* enable auto channel scan, send interrupts on end of conversion
777 * and set clock source to internal or external
780 conv_bits |= EACS | IEOC;
781 if (async->cmd.start_src == TRIG_EXT)
783 switch (async->cmd.convert_src) {
785 conv_bits |= CASC | ITE;
786 /* set conversion frequency */
787 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
788 &(devpriv->divisor2),
789 &(async->cmd.convert_arg),
791 flags & TRIG_ROUND_MASK);
792 if (das800_set_frequency(dev) < 0) {
793 comedi_error(dev, "Error setting up counters");
803 spin_lock_irqsave(&dev->spinlock, irq_flags);
804 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
805 outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL);
806 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
812 static int das800_ai_rinsn(struct comedi_device *dev,
813 struct comedi_subdevice *s, struct comedi_insn *insn,
821 unsigned long irq_flags;
823 disable_das800(dev); /* disable hardware conversions (enables software conversions) */
825 /* set multiplexer */
826 chan = CR_CHAN(insn->chanspec);
828 spin_lock_irqsave(&dev->spinlock, irq_flags);
829 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
830 outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
831 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
833 /* set gain / range */
834 range = CR_RANGE(insn->chanspec);
835 if (thisboard->resolution == 12 && range)
838 outb(range, dev->iobase + DAS800_GAIN);
842 for (n = 0; n < insn->n; n++) {
843 /* trigger conversion */
844 outb_p(0, dev->iobase + DAS800_MSB);
846 for (i = 0; i < timeout; i++) {
847 if (!(inb(dev->iobase + DAS800_STATUS) & BUSY))
851 comedi_error(dev, "timeout");
854 lsb = inb(dev->iobase + DAS800_LSB);
855 msb = inb(dev->iobase + DAS800_MSB);
856 if (thisboard->resolution == 12) {
857 data[n] = (lsb >> 4) & 0xff;
858 data[n] |= (msb << 4);
860 data[n] = (msb << 8) | lsb;
867 static int das800_di_rbits(struct comedi_device *dev,
868 struct comedi_subdevice *s, struct comedi_insn *insn,
873 bits = inb(dev->iobase + DAS800_STATUS) >> 4;
881 static int das800_do_wbits(struct comedi_device *dev,
882 struct comedi_subdevice *s, struct comedi_insn *insn,
886 unsigned long irq_flags;
888 /* only set bits that have been masked */
890 wbits = devpriv->do_bits >> 4;
892 wbits |= data[0] & data[1];
893 devpriv->do_bits = wbits << 4;
895 spin_lock_irqsave(&dev->spinlock, irq_flags);
896 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
897 outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1);
898 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
905 /* loads counters with divisor1, divisor2 from private structure */
906 static int das800_set_frequency(struct comedi_device *dev)
910 if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2))
912 if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2))
920 MODULE_AUTHOR("Comedi http://www.comedi.org");
921 MODULE_DESCRIPTION("Comedi low-level driver");
922 MODULE_LICENSE("GPL");