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 "../comedidev.h"
67 #include <linux/ioport.h>
68 #include <linux/delay.h>
71 #include "comedi_fc.h"
74 #define TIMER_BASE 1000
75 #define N_CHAN_AI 8 // number of analog input channels
77 /* Registers for the das800 */
80 #define FIFO_EMPTY 0x1
83 #define DAS800_CONTROL1 2
84 #define CONTROL1_INTE 0x8
85 #define DAS800_CONV_CONTROL 2
91 #define CONV_HCEN 0x80
92 #define DAS800_SCAN_LIMITS 2
93 #define DAS800_STATUS 2
97 #define CIO_FFOV 0x8 // fifo overflow for cio-das802/16
98 #define CIO_ENHF 0x90 // interrupt fifo half full for cio-das802/16
100 #define CONV_CONTROL 0xa0
101 #define SCAN_LIMITS 0xc0
103 #define DAS800_8254 4
104 #define DAS800_STATUS2 7
105 #define STATUS2_HCEN 0x80
106 #define STATUS2_INTE 0X20
109 struct das800_board {
112 const struct comedi_lrange *ai_range;
116 //analog input ranges
117 static const struct comedi_lrange range_das800_ai = {
124 static const struct comedi_lrange range_das801_ai = {
139 static const struct comedi_lrange range_cio_das801_ai = {
149 RANGE(-0.005, 0.005),
154 static const struct comedi_lrange range_das802_ai = {
164 RANGE(-0.625, 0.625),
169 static const struct comedi_lrange range_das80216_ai = {
183 enum { das800, ciodas800, das801, ciodas801, das802, ciodas802, ciodas80216 };
185 static const struct das800_board das800_boards[] = {
189 ai_range:&range_das800_ai,
195 ai_range:&range_das800_ai,
201 ai_range:&range_das801_ai,
207 ai_range:&range_cio_das801_ai,
213 ai_range:&range_das802_ai,
219 ai_range:&range_das802_ai,
223 name: "cio-das802/16",
225 ai_range:&range_das80216_ai,
231 * Useful for shorthand access to the particular board structure
233 #define thisboard ((const struct das800_board *)dev->board_ptr)
235 struct das800_private {
236 volatile unsigned int count; /* number of data points left to be taken */
237 volatile int forever; /* flag indicating whether we should take data forever */
238 unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */
239 unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */
240 volatile int do_bits; /* digital output bits */
243 #define devpriv ((struct das800_private *)dev->private)
245 static int das800_attach(struct comedi_device * dev, struct comedi_devconfig * it);
246 static int das800_detach(struct comedi_device * dev);
247 static int das800_cancel(struct comedi_device * dev, struct comedi_subdevice * s);
249 static struct comedi_driver driver_das800 = {
250 driver_name:"das800",
252 attach:das800_attach,
253 detach:das800_detach,
254 num_names:sizeof(das800_boards) / sizeof(struct das800_board),
255 board_name:&das800_boards[0].name,
256 offset:sizeof(struct das800_board),
259 static irqreturn_t das800_interrupt(int irq, void *d PT_REGS_ARG);
260 static void enable_das800(struct comedi_device * dev);
261 static void disable_das800(struct comedi_device * dev);
262 static int das800_ai_do_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s,
263 struct comedi_cmd * cmd);
264 static int das800_ai_do_cmd(struct comedi_device * dev, struct comedi_subdevice * s);
265 static int das800_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s,
266 struct comedi_insn * insn, unsigned int * data);
267 static int das800_di_rbits(struct comedi_device * dev, struct comedi_subdevice * s,
268 struct comedi_insn * insn, unsigned int * data);
269 static int das800_do_wbits(struct comedi_device * dev, struct comedi_subdevice * s,
270 struct comedi_insn * insn, unsigned int * data);
271 static int das800_probe(struct comedi_device * dev);
272 static int das800_set_frequency(struct comedi_device * dev);
274 /* checks and probes das-800 series board type */
275 static int das800_probe(struct comedi_device * dev)
278 unsigned long irq_flags;
281 // 'comedi spin lock irqsave' disables even rt interrupts, we use them to protect indirect addressing
282 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
283 outb(ID, dev->iobase + DAS800_GAIN); /* select base address + 7 to be ID register */
284 id_bits = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */
285 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
287 board = thisboard - das800_boards;
291 if (board == das800) {
292 printk(" Board model: DAS-800\n");
295 if (board == ciodas800) {
296 printk(" Board model: CIO-DAS800\n");
299 printk(" Board model (probed): DAS-800\n");
303 if (board == das801) {
304 printk(" Board model: DAS-801\n");
307 if (board == ciodas801) {
308 printk(" Board model: CIO-DAS801\n");
311 printk(" Board model (probed): DAS-801\n");
315 if (board == das802) {
316 printk(" Board model: DAS-802\n");
319 if (board == ciodas802) {
320 printk(" Board model: CIO-DAS802\n");
323 if (board == ciodas80216) {
324 printk(" Board model: CIO-DAS802/16\n");
327 printk(" Board model (probed): DAS-802\n");
331 printk(" Board model: probe returned 0x%x (unknown)\n",
340 * A convenient macro that defines init_module() and cleanup_module(),
343 COMEDI_INITCLEANUP(driver_das800);
345 /* interrupt service routine */
346 static irqreturn_t das800_interrupt(int irq, void *d PT_REGS_ARG)
348 short i; /* loop index */
350 struct comedi_device *dev = d;
351 struct comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */
352 struct comedi_async *async;
354 unsigned long irq_flags;
355 static const int max_loops = 128; // half-fifo size for cio-das802/16
358 int fifo_overflow = 0;
360 status = inb(dev->iobase + DAS800_STATUS);
361 /* if interrupt was not generated by board or driver not attached, quit */
364 if (!(dev->attached))
367 /* wait until here to initialize async, since we will get null dereference
368 * if interrupt occurs before driver is fully attached!
372 // if hardware conversions are not enabled, then quit
373 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
374 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */
375 status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN;
376 /* don't release spinlock yet since we want to make sure noone else disables hardware conversions */
378 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
382 /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */
383 for (i = 0; i < max_loops; i++) {
384 /* read 16 bits from dev->iobase and dev->iobase + 1 */
385 dataPoint = inb(dev->iobase + DAS800_LSB);
386 dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
387 if (thisboard->resolution == 12) {
388 fifo_empty = dataPoint & FIFO_EMPTY;
389 fifo_overflow = dataPoint & FIFO_OVF;
393 fifo_empty = 0; // cio-das802/16 has no fifo empty status bit
398 /* strip off extraneous bits for 12 bit cards */
399 if (thisboard->resolution == 12)
400 dataPoint = (dataPoint >> 4) & 0xfff;
401 /* if there are more data points to collect */
402 if (devpriv->count > 0 || devpriv->forever == 1) {
403 /* write data point to buffer */
404 cfc_write_to_buffer(s, dataPoint);
405 if (devpriv->count > 0)
409 async->events |= COMEDI_CB_BLOCK;
410 /* check for fifo overflow */
411 if (thisboard->resolution == 12) {
412 fifo_overflow = dataPoint & FIFO_OVF;
413 // else cio-das802/16
415 fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV;
418 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
419 comedi_error(dev, "DAS800 FIFO overflow");
420 das800_cancel(dev, dev->subdevices + 0);
421 async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
422 comedi_event(dev, s);
426 if (devpriv->count > 0 || devpriv->forever == 1) {
427 /* Re-enable card's interrupt.
428 * We already have spinlock, so indirect addressing is safe */
429 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
430 outb(CONTROL1_INTE | devpriv->do_bits,
431 dev->iobase + DAS800_CONTROL1);
432 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
433 /* otherwise, stop taking data */
435 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
436 disable_das800(dev); /* diable hardware triggered conversions */
437 async->events |= COMEDI_CB_EOA;
439 comedi_event(dev, s);
444 static int das800_attach(struct comedi_device * dev, struct comedi_devconfig * it)
446 struct comedi_subdevice *s;
447 unsigned long iobase = it->options[0];
448 unsigned int irq = it->options[1];
449 unsigned long irq_flags;
452 printk("comedi%d: das800: io 0x%lx", dev->minor, iobase);
454 printk(", irq %u", irq);
458 /* allocate and initialize dev->private */
459 if (alloc_private(dev, sizeof(struct das800_private)) < 0)
463 printk("io base address required for das800\n");
467 /* check if io addresses are available */
468 if (!request_region(iobase, DAS800_SIZE, "das800")) {
469 printk("I/O port conflict\n");
472 dev->iobase = iobase;
474 board = das800_probe(dev);
476 printk("unable to determine board type\n");
479 dev->board_ptr = das800_boards + board;
482 if (irq == 1 || irq > 7) {
483 printk("irq out of range\n");
487 if (comedi_request_irq(irq, das800_interrupt, 0, "das800", dev)) {
488 printk("unable to allocate irq %u\n", irq);
494 dev->board_name = thisboard->name;
496 if (alloc_subdevices(dev, 3) < 0)
499 /* analog input subdevice */
500 s = dev->subdevices + 0;
501 dev->read_subdev = s;
502 s->type = COMEDI_SUBD_AI;
503 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
506 s->maxdata = (1 << thisboard->resolution) - 1;
507 s->range_table = thisboard->ai_range;
508 s->do_cmd = das800_ai_do_cmd;
509 s->do_cmdtest = das800_ai_do_cmdtest;
510 s->insn_read = das800_ai_rinsn;
511 s->cancel = das800_cancel;
514 s = dev->subdevices + 1;
515 s->type = COMEDI_SUBD_DI;
516 s->subdev_flags = SDF_READABLE;
519 s->range_table = &range_digital;
520 s->insn_bits = das800_di_rbits;
523 s = dev->subdevices + 2;
524 s->type = COMEDI_SUBD_DO;
525 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
528 s->range_table = &range_digital;
529 s->insn_bits = das800_do_wbits;
533 /* initialize digital out channels */
534 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
535 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
536 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
537 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
542 static int das800_detach(struct comedi_device * dev)
544 printk("comedi%d: das800: remove\n", dev->minor);
546 /* only free stuff if it has been allocated by _attach */
548 release_region(dev->iobase, DAS800_SIZE);
550 comedi_free_irq(dev->irq, dev);
554 static int das800_cancel(struct comedi_device * dev, struct comedi_subdevice * s)
556 devpriv->forever = 0;
562 /* enable_das800 makes the card start taking hardware triggered conversions */
563 static void enable_das800(struct comedi_device * dev)
565 unsigned long irq_flags;
566 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
567 // enable fifo-half full interrupts for cio-das802/16
568 if (thisboard->resolution == 16)
569 outb(CIO_ENHF, dev->iobase + DAS800_GAIN);
570 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
571 outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */
572 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
573 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */
574 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
577 /* disable_das800 stops hardware triggered conversions */
578 static void disable_das800(struct comedi_device * dev)
580 unsigned long irq_flags;
581 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
582 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
583 outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */
584 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
587 static int das800_ai_do_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s,
588 struct comedi_cmd * cmd)
595 /* step 1: make sure trigger sources are trivially valid */
597 tmp = cmd->start_src;
598 cmd->start_src &= TRIG_NOW | TRIG_EXT;
599 if (!cmd->start_src || tmp != cmd->start_src)
602 tmp = cmd->scan_begin_src;
603 cmd->scan_begin_src &= TRIG_FOLLOW;
604 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
607 tmp = cmd->convert_src;
608 cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
609 if (!cmd->convert_src || tmp != cmd->convert_src)
612 tmp = cmd->scan_end_src;
613 cmd->scan_end_src &= TRIG_COUNT;
614 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
618 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
619 if (!cmd->stop_src || tmp != cmd->stop_src)
625 /* step 2: make sure trigger sources are unique and mutually compatible */
627 if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
629 if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT)
631 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
637 /* step 3: make sure arguments are trivially compatible */
639 if (cmd->start_arg != 0) {
643 if (cmd->convert_src == TRIG_TIMER) {
644 if (cmd->convert_arg < thisboard->ai_speed) {
645 cmd->convert_arg = thisboard->ai_speed;
649 if (!cmd->chanlist_len) {
650 cmd->chanlist_len = 1;
653 if (cmd->scan_end_arg != cmd->chanlist_len) {
654 cmd->scan_end_arg = cmd->chanlist_len;
657 if (cmd->stop_src == TRIG_COUNT) {
658 if (!cmd->stop_arg) {
662 } else { /* TRIG_NONE */
663 if (cmd->stop_arg != 0) {
672 /* step 4: fix up any arguments */
674 if (cmd->convert_src == TRIG_TIMER) {
675 tmp = cmd->convert_arg;
676 /* calculate counter values that give desired timing */
677 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
678 &(devpriv->divisor2), &(cmd->convert_arg),
679 cmd->flags & TRIG_ROUND_MASK);
680 if (tmp != cmd->convert_arg)
687 // check channel/gain list against card's limitations
689 gain = CR_RANGE(cmd->chanlist[0]);
690 startChan = CR_CHAN(cmd->chanlist[0]);
691 for (i = 1; i < cmd->chanlist_len; i++) {
692 if (CR_CHAN(cmd->chanlist[i]) !=
693 (startChan + i) % N_CHAN_AI) {
695 "entries in chanlist must be consecutive channels, counting upwards\n");
698 if (CR_RANGE(cmd->chanlist[i]) != gain) {
700 "entries in chanlist must all have the same gain\n");
712 static int das800_ai_do_cmd(struct comedi_device * dev, struct comedi_subdevice * s)
714 int startChan, endChan, scan, gain;
716 unsigned long irq_flags;
717 struct comedi_async *async = s->async;
721 "no irq assigned for das-800, cannot do hardware conversions");
727 /* set channel scan limits */
728 startChan = CR_CHAN(async->cmd.chanlist[0]);
729 endChan = (startChan + async->cmd.chanlist_len - 1) % 8;
730 scan = (endChan << 3) | startChan;
732 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
733 outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */
734 outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
735 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
738 gain = CR_RANGE(async->cmd.chanlist[0]);
739 if (thisboard->resolution == 12 && gain > 0)
742 outb(gain, dev->iobase + DAS800_GAIN);
744 switch (async->cmd.stop_src) {
746 devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len;
747 devpriv->forever = 0;
750 devpriv->forever = 1;
757 /* enable auto channel scan, send interrupts on end of conversion
758 * and set clock source to internal or external
761 conv_bits |= EACS | IEOC;
762 if (async->cmd.start_src == TRIG_EXT)
764 switch (async->cmd.convert_src) {
766 conv_bits |= CASC | ITE;
767 /* set conversion frequency */
768 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
769 &(devpriv->divisor2), &(async->cmd.convert_arg),
770 async->cmd.flags & TRIG_ROUND_MASK);
771 if (das800_set_frequency(dev) < 0) {
772 comedi_error(dev, "Error setting up counters");
782 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
783 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
784 outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL);
785 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
791 static int das800_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s,
792 struct comedi_insn * insn, unsigned int * data)
799 unsigned long irq_flags;
801 disable_das800(dev); /* disable hardware conversions (enables software conversions) */
803 /* set multiplexer */
804 chan = CR_CHAN(insn->chanspec);
806 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
807 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
808 outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
809 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
811 /* set gain / range */
812 range = CR_RANGE(insn->chanspec);
813 if (thisboard->resolution == 12 && range)
816 outb(range, dev->iobase + DAS800_GAIN);
820 for (n = 0; n < insn->n; n++) {
821 /* trigger conversion */
822 outb_p(0, dev->iobase + DAS800_MSB);
824 for (i = 0; i < timeout; i++) {
825 if (!(inb(dev->iobase + DAS800_STATUS) & BUSY))
829 comedi_error(dev, "timeout");
832 lsb = inb(dev->iobase + DAS800_LSB);
833 msb = inb(dev->iobase + DAS800_MSB);
834 if (thisboard->resolution == 12) {
835 data[n] = (lsb >> 4) & 0xff;
836 data[n] |= (msb << 4);
838 data[n] = (msb << 8) | lsb;
845 static int das800_di_rbits(struct comedi_device * dev, struct comedi_subdevice * s,
846 struct comedi_insn * insn, unsigned int * data)
850 bits = inb(dev->iobase + DAS800_STATUS) >> 4;
858 static int das800_do_wbits(struct comedi_device * dev, struct comedi_subdevice * s,
859 struct comedi_insn * insn, unsigned int * data)
862 unsigned long irq_flags;
864 // only set bits that have been masked
866 wbits = devpriv->do_bits >> 4;
868 wbits |= data[0] & data[1];
869 devpriv->do_bits = wbits << 4;
871 comedi_spin_lock_irqsave(&dev->spinlock, irq_flags);
872 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
873 outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1);
874 comedi_spin_unlock_irqrestore(&dev->spinlock, irq_flags);
881 /* loads counters with divisor1, divisor2 from private structure */
882 static int das800_set_frequency(struct comedi_device * dev)
886 if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2))
888 if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2))