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 int 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 printk(" Board model: DAS-800\n");
302 if (board == ciodas800) {
303 printk(" Board model: CIO-DAS800\n");
306 printk(" Board model (probed): DAS-800\n");
310 if (board == das801) {
311 printk(" Board model: DAS-801\n");
314 if (board == ciodas801) {
315 printk(" Board model: CIO-DAS801\n");
318 printk(" Board model (probed): DAS-801\n");
322 if (board == das802) {
323 printk(" Board model: DAS-802\n");
326 if (board == ciodas802) {
327 printk(" Board model: CIO-DAS802\n");
330 if (board == ciodas80216) {
331 printk(" Board model: CIO-DAS802/16\n");
334 printk(" Board model (probed): DAS-802\n");
338 printk(" Board model: probe returned 0x%x (unknown)\n",
347 * A convenient macro that defines init_module() and cleanup_module(),
350 COMEDI_INITCLEANUP(driver_das800);
352 /* interrupt service routine */
353 static irqreturn_t das800_interrupt(int irq, void *d)
355 short i; /* loop index */
357 struct comedi_device *dev = d;
358 struct comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */
359 struct comedi_async *async;
361 unsigned long irq_flags;
362 static const int max_loops = 128; /* half-fifo size for cio-das802/16 */
365 int fifo_overflow = 0;
367 status = inb(dev->iobase + DAS800_STATUS);
368 /* if interrupt was not generated by board or driver not attached, quit */
371 if (!(dev->attached))
374 /* wait until here to initialize async, since we will get null dereference
375 * if interrupt occurs before driver is fully attached!
379 /* if hardware conversions are not enabled, then quit */
380 spin_lock_irqsave(&dev->spinlock, irq_flags);
381 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */
382 status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN;
383 /* don't release spinlock yet since we want to make sure noone else disables hardware conversions */
385 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
389 /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */
390 for (i = 0; i < max_loops; i++) {
391 /* read 16 bits from dev->iobase and dev->iobase + 1 */
392 dataPoint = inb(dev->iobase + DAS800_LSB);
393 dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
394 if (thisboard->resolution == 12) {
395 fifo_empty = dataPoint & FIFO_EMPTY;
396 fifo_overflow = dataPoint & FIFO_OVF;
400 fifo_empty = 0; /* cio-das802/16 has no fifo empty status bit */
405 /* strip off extraneous bits for 12 bit cards */
406 if (thisboard->resolution == 12)
407 dataPoint = (dataPoint >> 4) & 0xfff;
408 /* if there are more data points to collect */
409 if (devpriv->count > 0 || devpriv->forever == 1) {
410 /* write data point to buffer */
411 cfc_write_to_buffer(s, dataPoint);
412 if (devpriv->count > 0)
416 async->events |= COMEDI_CB_BLOCK;
417 /* check for fifo overflow */
418 if (thisboard->resolution == 12) {
419 fifo_overflow = dataPoint & FIFO_OVF;
420 /* else cio-das802/16 */
422 fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV;
425 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
426 comedi_error(dev, "DAS800 FIFO overflow");
427 das800_cancel(dev, dev->subdevices + 0);
428 async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
429 comedi_event(dev, s);
433 if (devpriv->count > 0 || devpriv->forever == 1) {
434 /* Re-enable card's interrupt.
435 * We already have spinlock, so indirect addressing is safe */
436 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
437 outb(CONTROL1_INTE | devpriv->do_bits,
438 dev->iobase + DAS800_CONTROL1);
439 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
440 /* otherwise, stop taking data */
442 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
443 disable_das800(dev); /* diable hardware triggered conversions */
444 async->events |= COMEDI_CB_EOA;
446 comedi_event(dev, s);
451 static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
453 struct comedi_subdevice *s;
454 unsigned long iobase = it->options[0];
455 unsigned int irq = it->options[1];
456 unsigned long irq_flags;
459 printk("comedi%d: das800: io 0x%lx", dev->minor, iobase);
461 printk(", irq %u", irq);
465 /* allocate and initialize dev->private */
466 if (alloc_private(dev, sizeof(struct das800_private)) < 0)
470 printk("io base address required for das800\n");
474 /* check if io addresses are available */
475 if (!request_region(iobase, DAS800_SIZE, "das800")) {
476 printk("I/O port conflict\n");
479 dev->iobase = iobase;
481 board = das800_probe(dev);
483 printk("unable to determine board type\n");
486 dev->board_ptr = das800_boards + board;
489 if (irq == 1 || irq > 7) {
490 printk("irq out of range\n");
494 if (request_irq(irq, das800_interrupt, 0, "das800", dev)) {
495 printk("unable to allocate irq %u\n", irq);
501 dev->board_name = thisboard->name;
503 if (alloc_subdevices(dev, 3) < 0)
506 /* analog input subdevice */
507 s = dev->subdevices + 0;
508 dev->read_subdev = s;
509 s->type = COMEDI_SUBD_AI;
510 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
513 s->maxdata = (1 << thisboard->resolution) - 1;
514 s->range_table = thisboard->ai_range;
515 s->do_cmd = das800_ai_do_cmd;
516 s->do_cmdtest = das800_ai_do_cmdtest;
517 s->insn_read = das800_ai_rinsn;
518 s->cancel = das800_cancel;
521 s = dev->subdevices + 1;
522 s->type = COMEDI_SUBD_DI;
523 s->subdev_flags = SDF_READABLE;
526 s->range_table = &range_digital;
527 s->insn_bits = das800_di_rbits;
530 s = dev->subdevices + 2;
531 s->type = COMEDI_SUBD_DO;
532 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
535 s->range_table = &range_digital;
536 s->insn_bits = das800_do_wbits;
540 /* initialize digital out channels */
541 spin_lock_irqsave(&dev->spinlock, irq_flags);
542 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
543 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
544 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
549 static int das800_detach(struct comedi_device *dev)
551 printk("comedi%d: das800: remove\n", dev->minor);
553 /* only free stuff if it has been allocated by _attach */
555 release_region(dev->iobase, DAS800_SIZE);
557 free_irq(dev->irq, dev);
561 static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
563 devpriv->forever = 0;
569 /* enable_das800 makes the card start taking hardware triggered conversions */
570 static void enable_das800(struct comedi_device *dev)
572 unsigned long irq_flags;
573 spin_lock_irqsave(&dev->spinlock, irq_flags);
574 /* enable fifo-half full interrupts for cio-das802/16 */
575 if (thisboard->resolution == 16)
576 outb(CIO_ENHF, dev->iobase + DAS800_GAIN);
577 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
578 outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */
579 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
580 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */
581 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
584 /* disable_das800 stops hardware triggered conversions */
585 static void disable_das800(struct comedi_device *dev)
587 unsigned long irq_flags;
588 spin_lock_irqsave(&dev->spinlock, irq_flags);
589 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
590 outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */
591 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
594 static int das800_ai_do_cmdtest(struct comedi_device *dev,
595 struct comedi_subdevice *s,
596 struct comedi_cmd *cmd)
603 /* step 1: make sure trigger sources are trivially valid */
605 tmp = cmd->start_src;
606 cmd->start_src &= TRIG_NOW | TRIG_EXT;
607 if (!cmd->start_src || tmp != cmd->start_src)
610 tmp = cmd->scan_begin_src;
611 cmd->scan_begin_src &= TRIG_FOLLOW;
612 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
615 tmp = cmd->convert_src;
616 cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
617 if (!cmd->convert_src || tmp != cmd->convert_src)
620 tmp = cmd->scan_end_src;
621 cmd->scan_end_src &= TRIG_COUNT;
622 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
626 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
627 if (!cmd->stop_src || tmp != cmd->stop_src)
633 /* step 2: make sure trigger sources are unique and mutually compatible */
635 if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
637 if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT)
639 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
645 /* step 3: make sure arguments are trivially compatible */
647 if (cmd->start_arg != 0) {
651 if (cmd->convert_src == TRIG_TIMER) {
652 if (cmd->convert_arg < thisboard->ai_speed) {
653 cmd->convert_arg = thisboard->ai_speed;
657 if (!cmd->chanlist_len) {
658 cmd->chanlist_len = 1;
661 if (cmd->scan_end_arg != cmd->chanlist_len) {
662 cmd->scan_end_arg = cmd->chanlist_len;
665 if (cmd->stop_src == TRIG_COUNT) {
666 if (!cmd->stop_arg) {
670 } else { /* TRIG_NONE */
671 if (cmd->stop_arg != 0) {
680 /* step 4: fix up any arguments */
682 if (cmd->convert_src == TRIG_TIMER) {
683 tmp = cmd->convert_arg;
684 /* calculate counter values that give desired timing */
685 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
686 &(devpriv->divisor2),
688 cmd->flags & TRIG_ROUND_MASK);
689 if (tmp != cmd->convert_arg)
696 /* check channel/gain list against card's limitations */
698 gain = CR_RANGE(cmd->chanlist[0]);
699 startChan = CR_CHAN(cmd->chanlist[0]);
700 for (i = 1; i < cmd->chanlist_len; i++) {
701 if (CR_CHAN(cmd->chanlist[i]) !=
702 (startChan + i) % N_CHAN_AI) {
704 "entries in chanlist must be consecutive channels, counting upwards\n");
707 if (CR_RANGE(cmd->chanlist[i]) != gain) {
709 "entries in chanlist must all have the same gain\n");
721 static int das800_ai_do_cmd(struct comedi_device *dev,
722 struct comedi_subdevice *s)
724 int startChan, endChan, scan, gain;
726 unsigned long irq_flags;
727 struct comedi_async *async = s->async;
731 "no irq assigned for das-800, cannot do hardware conversions");
737 /* set channel scan limits */
738 startChan = CR_CHAN(async->cmd.chanlist[0]);
739 endChan = (startChan + async->cmd.chanlist_len - 1) % 8;
740 scan = (endChan << 3) | startChan;
742 spin_lock_irqsave(&dev->spinlock, irq_flags);
743 outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */
744 outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
745 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
748 gain = CR_RANGE(async->cmd.chanlist[0]);
749 if (thisboard->resolution == 12 && gain > 0)
752 outb(gain, dev->iobase + DAS800_GAIN);
754 switch (async->cmd.stop_src) {
756 devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len;
757 devpriv->forever = 0;
760 devpriv->forever = 1;
767 /* enable auto channel scan, send interrupts on end of conversion
768 * and set clock source to internal or external
771 conv_bits |= EACS | IEOC;
772 if (async->cmd.start_src == TRIG_EXT)
774 switch (async->cmd.convert_src) {
776 conv_bits |= CASC | ITE;
777 /* set conversion frequency */
778 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
779 &(devpriv->divisor2),
780 &(async->cmd.convert_arg),
782 flags & TRIG_ROUND_MASK);
783 if (das800_set_frequency(dev) < 0) {
784 comedi_error(dev, "Error setting up counters");
794 spin_lock_irqsave(&dev->spinlock, irq_flags);
795 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
796 outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL);
797 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
803 static int das800_ai_rinsn(struct comedi_device *dev,
804 struct comedi_subdevice *s, struct comedi_insn *insn,
812 unsigned long irq_flags;
814 disable_das800(dev); /* disable hardware conversions (enables software conversions) */
816 /* set multiplexer */
817 chan = CR_CHAN(insn->chanspec);
819 spin_lock_irqsave(&dev->spinlock, irq_flags);
820 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
821 outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
822 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
824 /* set gain / range */
825 range = CR_RANGE(insn->chanspec);
826 if (thisboard->resolution == 12 && range)
829 outb(range, dev->iobase + DAS800_GAIN);
833 for (n = 0; n < insn->n; n++) {
834 /* trigger conversion */
835 outb_p(0, dev->iobase + DAS800_MSB);
837 for (i = 0; i < timeout; i++) {
838 if (!(inb(dev->iobase + DAS800_STATUS) & BUSY))
842 comedi_error(dev, "timeout");
845 lsb = inb(dev->iobase + DAS800_LSB);
846 msb = inb(dev->iobase + DAS800_MSB);
847 if (thisboard->resolution == 12) {
848 data[n] = (lsb >> 4) & 0xff;
849 data[n] |= (msb << 4);
851 data[n] = (msb << 8) | lsb;
858 static int das800_di_rbits(struct comedi_device *dev,
859 struct comedi_subdevice *s, struct comedi_insn *insn,
864 bits = inb(dev->iobase + DAS800_STATUS) >> 4;
872 static int das800_do_wbits(struct comedi_device *dev,
873 struct comedi_subdevice *s, struct comedi_insn *insn,
877 unsigned long irq_flags;
879 /* only set bits that have been masked */
881 wbits = devpriv->do_bits >> 4;
883 wbits |= data[0] & data[1];
884 devpriv->do_bits = wbits << 4;
886 spin_lock_irqsave(&dev->spinlock, irq_flags);
887 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
888 outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1);
889 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
896 /* loads counters with divisor1, divisor2 from private structure */
897 static int das800_set_frequency(struct comedi_device *dev)
901 if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2))
903 if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2))