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 */
404 /* strip off extraneous bits for 12 bit cards */
405 if (thisboard->resolution == 12)
406 dataPoint = (dataPoint >> 4) & 0xfff;
407 /* if there are more data points to collect */
408 if (devpriv->count > 0 || devpriv->forever == 1) {
409 /* write data point to buffer */
410 cfc_write_to_buffer(s, dataPoint);
411 if (devpriv->count > 0)
415 async->events |= COMEDI_CB_BLOCK;
416 /* check for fifo overflow */
417 if (thisboard->resolution == 12) {
418 fifo_overflow = dataPoint & FIFO_OVF;
419 /* else cio-das802/16 */
421 fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV;
424 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
425 comedi_error(dev, "DAS800 FIFO overflow");
426 das800_cancel(dev, dev->subdevices + 0);
427 async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
428 comedi_event(dev, s);
432 if (devpriv->count > 0 || devpriv->forever == 1) {
433 /* Re-enable card's interrupt.
434 * We already have spinlock, so indirect addressing is safe */
435 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
436 outb(CONTROL1_INTE | devpriv->do_bits,
437 dev->iobase + DAS800_CONTROL1);
438 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
439 /* otherwise, stop taking data */
441 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
442 disable_das800(dev); /* diable hardware triggered conversions */
443 async->events |= COMEDI_CB_EOA;
445 comedi_event(dev, s);
450 static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
452 struct comedi_subdevice *s;
453 unsigned long iobase = it->options[0];
454 unsigned int irq = it->options[1];
455 unsigned long irq_flags;
458 printk("comedi%d: das800: io 0x%lx", dev->minor, iobase);
460 printk(", irq %u", irq);
463 /* allocate and initialize dev->private */
464 if (alloc_private(dev, sizeof(struct das800_private)) < 0)
468 printk("io base address required for das800\n");
472 /* check if io addresses are available */
473 if (!request_region(iobase, DAS800_SIZE, "das800")) {
474 printk("I/O port conflict\n");
477 dev->iobase = iobase;
479 board = das800_probe(dev);
481 printk("unable to determine board type\n");
484 dev->board_ptr = das800_boards + board;
487 if (irq == 1 || irq > 7) {
488 printk("irq out of range\n");
492 if (request_irq(irq, das800_interrupt, 0, "das800", dev)) {
493 printk("unable to allocate irq %u\n", irq);
499 dev->board_name = thisboard->name;
501 if (alloc_subdevices(dev, 3) < 0)
504 /* analog input subdevice */
505 s = dev->subdevices + 0;
506 dev->read_subdev = s;
507 s->type = COMEDI_SUBD_AI;
508 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
511 s->maxdata = (1 << thisboard->resolution) - 1;
512 s->range_table = thisboard->ai_range;
513 s->do_cmd = das800_ai_do_cmd;
514 s->do_cmdtest = das800_ai_do_cmdtest;
515 s->insn_read = das800_ai_rinsn;
516 s->cancel = das800_cancel;
519 s = dev->subdevices + 1;
520 s->type = COMEDI_SUBD_DI;
521 s->subdev_flags = SDF_READABLE;
524 s->range_table = &range_digital;
525 s->insn_bits = das800_di_rbits;
528 s = dev->subdevices + 2;
529 s->type = COMEDI_SUBD_DO;
530 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
533 s->range_table = &range_digital;
534 s->insn_bits = das800_do_wbits;
538 /* initialize digital out channels */
539 spin_lock_irqsave(&dev->spinlock, irq_flags);
540 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
541 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
542 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
547 static int das800_detach(struct comedi_device *dev)
549 printk("comedi%d: das800: remove\n", dev->minor);
551 /* only free stuff if it has been allocated by _attach */
553 release_region(dev->iobase, DAS800_SIZE);
555 free_irq(dev->irq, dev);
559 static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
561 devpriv->forever = 0;
567 /* enable_das800 makes the card start taking hardware triggered conversions */
568 static void enable_das800(struct comedi_device *dev)
570 unsigned long irq_flags;
571 spin_lock_irqsave(&dev->spinlock, irq_flags);
572 /* enable fifo-half full interrupts for cio-das802/16 */
573 if (thisboard->resolution == 16)
574 outb(CIO_ENHF, dev->iobase + DAS800_GAIN);
575 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
576 outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */
577 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
578 outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */
579 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
582 /* disable_das800 stops hardware triggered conversions */
583 static void disable_das800(struct comedi_device *dev)
585 unsigned long irq_flags;
586 spin_lock_irqsave(&dev->spinlock, irq_flags);
587 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
588 outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */
589 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
592 static int das800_ai_do_cmdtest(struct comedi_device *dev,
593 struct comedi_subdevice *s,
594 struct comedi_cmd *cmd)
601 /* step 1: make sure trigger sources are trivially valid */
603 tmp = cmd->start_src;
604 cmd->start_src &= TRIG_NOW | TRIG_EXT;
605 if (!cmd->start_src || tmp != cmd->start_src)
608 tmp = cmd->scan_begin_src;
609 cmd->scan_begin_src &= TRIG_FOLLOW;
610 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
613 tmp = cmd->convert_src;
614 cmd->convert_src &= TRIG_TIMER | TRIG_EXT;
615 if (!cmd->convert_src || tmp != cmd->convert_src)
618 tmp = cmd->scan_end_src;
619 cmd->scan_end_src &= TRIG_COUNT;
620 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
624 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
625 if (!cmd->stop_src || tmp != cmd->stop_src)
631 /* step 2: make sure trigger sources are unique and mutually compatible */
633 if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
635 if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT)
637 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
643 /* step 3: make sure arguments are trivially compatible */
645 if (cmd->start_arg != 0) {
649 if (cmd->convert_src == TRIG_TIMER) {
650 if (cmd->convert_arg < thisboard->ai_speed) {
651 cmd->convert_arg = thisboard->ai_speed;
655 if (!cmd->chanlist_len) {
656 cmd->chanlist_len = 1;
659 if (cmd->scan_end_arg != cmd->chanlist_len) {
660 cmd->scan_end_arg = cmd->chanlist_len;
663 if (cmd->stop_src == TRIG_COUNT) {
664 if (!cmd->stop_arg) {
668 } else { /* TRIG_NONE */
669 if (cmd->stop_arg != 0) {
678 /* step 4: fix up any arguments */
680 if (cmd->convert_src == TRIG_TIMER) {
681 tmp = cmd->convert_arg;
682 /* calculate counter values that give desired timing */
683 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
684 &(devpriv->divisor2),
686 cmd->flags & TRIG_ROUND_MASK);
687 if (tmp != cmd->convert_arg)
694 /* check channel/gain list against card's limitations */
696 gain = CR_RANGE(cmd->chanlist[0]);
697 startChan = CR_CHAN(cmd->chanlist[0]);
698 for (i = 1; i < cmd->chanlist_len; i++) {
699 if (CR_CHAN(cmd->chanlist[i]) !=
700 (startChan + i) % N_CHAN_AI) {
702 "entries in chanlist must be consecutive channels, counting upwards\n");
705 if (CR_RANGE(cmd->chanlist[i]) != gain) {
707 "entries in chanlist must all have the same gain\n");
719 static int das800_ai_do_cmd(struct comedi_device *dev,
720 struct comedi_subdevice *s)
722 int startChan, endChan, scan, gain;
724 unsigned long irq_flags;
725 struct comedi_async *async = s->async;
729 "no irq assigned for das-800, cannot do hardware conversions");
735 /* set channel scan limits */
736 startChan = CR_CHAN(async->cmd.chanlist[0]);
737 endChan = (startChan + async->cmd.chanlist_len - 1) % 8;
738 scan = (endChan << 3) | startChan;
740 spin_lock_irqsave(&dev->spinlock, irq_flags);
741 outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */
742 outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
743 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
746 gain = CR_RANGE(async->cmd.chanlist[0]);
747 if (thisboard->resolution == 12 && gain > 0)
750 outb(gain, dev->iobase + DAS800_GAIN);
752 switch (async->cmd.stop_src) {
754 devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len;
755 devpriv->forever = 0;
758 devpriv->forever = 1;
765 /* enable auto channel scan, send interrupts on end of conversion
766 * and set clock source to internal or external
769 conv_bits |= EACS | IEOC;
770 if (async->cmd.start_src == TRIG_EXT)
772 switch (async->cmd.convert_src) {
774 conv_bits |= CASC | ITE;
775 /* set conversion frequency */
776 i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1),
777 &(devpriv->divisor2),
778 &(async->cmd.convert_arg),
780 flags & TRIG_ROUND_MASK);
781 if (das800_set_frequency(dev) < 0) {
782 comedi_error(dev, "Error setting up counters");
792 spin_lock_irqsave(&dev->spinlock, irq_flags);
793 outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */
794 outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL);
795 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
801 static int das800_ai_rinsn(struct comedi_device *dev,
802 struct comedi_subdevice *s, struct comedi_insn *insn,
810 unsigned long irq_flags;
812 disable_das800(dev); /* disable hardware conversions (enables software conversions) */
814 /* set multiplexer */
815 chan = CR_CHAN(insn->chanspec);
817 spin_lock_irqsave(&dev->spinlock, irq_flags);
818 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
819 outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1);
820 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
822 /* set gain / range */
823 range = CR_RANGE(insn->chanspec);
824 if (thisboard->resolution == 12 && range)
827 outb(range, dev->iobase + DAS800_GAIN);
831 for (n = 0; n < insn->n; n++) {
832 /* trigger conversion */
833 outb_p(0, dev->iobase + DAS800_MSB);
835 for (i = 0; i < timeout; i++) {
836 if (!(inb(dev->iobase + DAS800_STATUS) & BUSY))
840 comedi_error(dev, "timeout");
843 lsb = inb(dev->iobase + DAS800_LSB);
844 msb = inb(dev->iobase + DAS800_MSB);
845 if (thisboard->resolution == 12) {
846 data[n] = (lsb >> 4) & 0xff;
847 data[n] |= (msb << 4);
849 data[n] = (msb << 8) | lsb;
856 static int das800_di_rbits(struct comedi_device *dev,
857 struct comedi_subdevice *s, struct comedi_insn *insn,
862 bits = inb(dev->iobase + DAS800_STATUS) >> 4;
870 static int das800_do_wbits(struct comedi_device *dev,
871 struct comedi_subdevice *s, struct comedi_insn *insn,
875 unsigned long irq_flags;
877 /* only set bits that have been masked */
879 wbits = devpriv->do_bits >> 4;
881 wbits |= data[0] & data[1];
882 devpriv->do_bits = wbits << 4;
884 spin_lock_irqsave(&dev->spinlock, irq_flags);
885 outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */
886 outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1);
887 spin_unlock_irqrestore(&dev->spinlock, irq_flags);
894 /* loads counters with divisor1, divisor2 from private structure */
895 static int das800_set_frequency(struct comedi_device *dev)
899 if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2))
901 if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2))