update from Frank Mori Hess
authorDavid Schleef <ds@schleef.org>
Sat, 2 Dec 2000 17:51:16 +0000 (17:51 +0000)
committerDavid Schleef <ds@schleef.org>
Sat, 2 Dec 2000 17:51:16 +0000 (17:51 +0000)
Documentation/comedi/drivers.txt
comedi/drivers/das1800.c [new file with mode: 0644]
comedi/drivers/das800.c

index 73012a19d8a64502b1a9026d76ff8c7adaf5959d..d09bb217a1c44c0c4a8510ab97c995082148d933 100644 (file)
@@ -170,6 +170,13 @@ Options (probably wrong, verify with source):
 
 
 
+Driver: das1800.o
+Description: Keithley Metrabyte DAS1800 (& compatibles)
+Author: Frank Mori Hess <fmhess@uiuc.edu>
+Status: in development
+
+
+
 Driver: das6402.o
 Description: Keithley Metrabyte DAS6402 (& compatibles)
 Author: Oystein Svendsen <svendsen@pvv.org>
diff --git a/comedi/drivers/das1800.c b/comedi/drivers/das1800.c
new file mode 100644 (file)
index 0000000..a39142f
--- /dev/null
@@ -0,0 +1,724 @@
+/*
+    das1800.c driver for Keitley das1800st/hr series boards
+    Copyright (C) 2000 Frank Mori Hess <fmhess@uiuc.edu>
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+************************************************************************
+
+This driver supports the following Keithley boards:
+
+DAS-1801ST
+DAS-1802ST
+DAS-1802HR
+
+Options:
+       [0] - base io address
+       [1] - irq (supports shareable interrupts)
+
+cmd triggers supported:
+       start_src: TRIG_NOW
+       scan_begin_src: TRIG_FOLLOW
+       scan_end_src:   TRIG_COUNT
+       convert_src:    TRIG_TIMER
+       scan_end_src:   TRIG_COUNT
+       stop_src:       TRIG_END | TRIG_COUNT
+
+TODO:
+       Support unipolar gains and single ended inputs
+       Support more cmd triggers
+       Speed up interrupt routine
+       Support dma transfers
+       Add support for cards' digital i/o
+       Add support for cards with analog out
+
+NOTES:
+Only the DAS-1801ST has been tested by me.
+
+BUGS:
+The DAS-1802ST cards are identified as DAS-1801ST cards, since the
+two boards have identical id bits.  The only difference between the
+cards are their gains.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/comedidev.h>
+#include <asm/io.h>
+
+#define DAS1800_SIZE           16
+#define HALFFIFO               512
+
+/* Registers for the das800 */
+#define DAS1800_FIFO            0x0
+#define DAS1800_QRAM            0x0
+#define DAS1800_SELECT          0x2
+#define DAS1800_DIGITAL         0x3
+#define DAS1800_CONTROL_A       0x4
+#define   FFEN                    0x1
+#define   CGEN                    0x4
+#define DAS1800_CONTROL_B       0x5
+#define   FIMD                    0x40
+#define DAS1800_CONTROL_C       0X6
+#define   BMDE                    0x4
+#define   CMEN                    0x8
+#define   UQEN                    0x10
+#define   SD                      0x40
+#define   UB                      0x80
+#define DAS1800_STATUS          0x7
+#define   INT                     0x1
+#define   OVF                     0x10
+#define   FHF                     0x20
+#define   FNE                     0x40
+#define   CVEN                    0x80
+#define DAS1800_QRAM_ADDRESS    0xa
+#define DAS1800_COUNTER(a)        (0xc + a)
+#define DAS1800_COUNTER_CONTROL 0xf
+
+typedef struct das1800_board_struct{
+       char *name;
+       int ai_speed;
+       int resolution;
+}das1800_board;
+
+das1800_board das1800_boards[] =
+{
+       {
+               name:   "DAS-1801ST",
+               /* Warning: the maximum conversion speeds listed below are
+                * not always achievable depending on board setup (see
+                * user manual.)
+                */
+               ai_speed:       3000,
+               resolution:     12,
+       },
+       {
+               name:           "DAS-1802ST",
+               ai_speed:       3000,
+               resolution:     12,
+       },
+       {
+               name:           "DAS-1802HR",
+               ai_speed:       10000,
+               resolution:     16,
+       },
+};
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((das1800_board *)dev->board_ptr)
+
+typedef struct{
+       unsigned long count;  /* number of data points left to be taken */
+       int forever;  /* flag indicating whether we should take data forever */
+       unsigned short divisor1;        /* value to load into board's counter 1 for timed conversions */
+       unsigned short divisor2;        /* value to load into board's counter 2 for timed conversions */
+}das1800_private;
+
+#define devpriv ((das1800_private *)dev->private)
+
+static comedi_lrange range_das1801st_ai = {
+       4,
+       {
+               RANGE( -5, 5 ),
+               RANGE( -1, 1 ),
+               RANGE( -0.1, 0.1 ),
+               RANGE( -0.02, 0.02 ),
+/*             RANGE( 0, 5 ),
+               RANGE( 0, 1 ),
+               RANGE( 0, 0.1 ),
+               RANGE( 0, 0.02 ),
+*/     }
+};
+
+static comedi_lrange range_das1802st_ai = {
+       4,
+       {
+               RANGE(-10, 10),
+               RANGE(-5, 5),
+               RANGE(-2.5, 2.5),
+               RANGE(-1.25, 1.25),
+/*             RANGE(0, 10),
+               RANGE(0, 5),
+               RANGE(0, 2.5),
+               RANGE(0, 1.25),
+*/     }
+};
+
+static comedi_lrange range_das1802hr_ai = {
+       4,
+       {
+               RANGE(-10, 10),
+               RANGE(-5, 5),
+               RANGE(-2.5, 2.5),
+               RANGE(-1.25, 1.25),
+/*             RANGE(0, 10),
+               RANGE(0, 5),
+               RANGE(0, 2.5),
+               RANGE(0, 1.25),
+*/     }
+};
+
+static comedi_lrange *das1800_range_lkup[] = {
+       &range_das1801st_ai,
+       &range_das1802st_ai,
+       &range_das1802hr_ai,
+};
+
+static int das1800_attach(comedi_device *dev, comedi_devconfig *it);
+static int das1800_detach(comedi_device *dev);
+static int das1800_cancel(comedi_device *dev, comedi_subdevice *s);
+
+comedi_driver driver_das1800={
+       driver_name:    "das1800",
+       module:         THIS_MODULE,
+       attach:         das1800_attach,
+       detach:         das1800_detach,
+};
+
+static void das1800_interrupt(int irq, void *d, struct pt_regs *regs);
+void enable_das1800(comedi_device *dev);
+void disable_das1800(comedi_device *dev);
+static int das1800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd);
+static int das1800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s);
+static int das1800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+int das1800_probe(comedi_device *dev);
+int das1800_set_frequency( unsigned int period, comedi_device *dev);
+int das1800_load_counter(unsigned int counterNumber, int counterValue, comedi_device *dev);
+unsigned int das1800_find_divisors(unsigned int period, comedi_device *dev);
+
+/* probes das-1800st/hr series board type - stupid boards have incomplete id
+       this isn't going to work */
+int das1800_probe(comedi_device *dev)
+{
+       int id;
+       id = inb(dev->iobase + DAS1800_DIGITAL) >> 4; /* get id bits */
+       switch(id)
+       {
+               case 0x7:
+                       printk(" Board model: DAS-1800ST series\n");
+                       return 0;
+                       break;
+               case 0x3:
+                       printk(" Board model: DAS-1800ST-DA series\n");
+                       return 0;
+                       break;
+               case 0x6:
+                       printk(" Board model: DAS-1802HR\n");
+                       return 2;
+                       break;
+               case 0x4:
+                       printk(" Board model: DAS-1802HR-DA\n");
+                       return 2;
+                       break;
+               default :
+                       printk(" Board model: probe returned 0x%x (unknown)\n", id);
+                       return -1;
+                       break;
+       }
+       return -1;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_das1800);
+
+static void das1800_interrupt(int irq, void *d, struct pt_regs *regs)
+{
+       int i;          /* loop index */
+       int numPoints = HALFFIFO;       /* number of points to read */
+       int ret;
+       comedi_device *dev = d;
+       comedi_subdevice *s = dev->subdevices + 0;      /* analog input subdevice */
+       sampl_t dpnt;
+
+       ret = inb(dev->iobase + DAS1800_STATUS);
+       /* if interrupt was not caused by das-1800 */
+       if(!(ret & INT))
+               return;
+       if(ret & FHF)      /* if fifo half full */
+       {
+               /* if we only need some of the points */
+               if( devpriv->forever == 0 && devpriv->count < numPoints)
+                       numPoints = devpriv->count;
+               for( i = 0; i < numPoints; i++)
+               {
+                       /* write data point to buffer */
+                       if(s->buf_int_ptr + sizeof(sampl_t) > s->cur_trig.data_len )
+                       {
+                               s->buf_int_ptr = 0;
+                               comedi_eobuf(dev, s);
+                       }
+                       dpnt = inw(dev->iobase + DAS1800_FIFO);
+                       /* convert to offset binary */
+                       dpnt += 1 << (thisboard->resolution - 1); 
+                       *((sampl_t *)((void *)s->cur_trig.data + s->buf_int_ptr)) = dpnt;
+                       s->cur_chan++;
+                       if( s->cur_chan >= s->cur_chanlist_len )
+                       {
+                               s->cur_chan = 0;
+                               comedi_eos(dev, s);
+                       }
+                       s->buf_int_count += sizeof(sampl_t);
+                       s->buf_int_ptr += sizeof(sampl_t);
+                       if(devpriv->count > 0) devpriv->count--;
+               }
+       }
+       comedi_bufcheck(dev,s);
+       if(ret & OVF)
+       {
+               comedi_error(dev, "DAS1800 FIFO overflow");
+               das1800_cancel(dev, dev->subdevices + 0);
+               comedi_error_done(dev, s);
+               return;
+       }
+       /* if there are more data points to collect */
+       if(devpriv->count > 0 || devpriv->forever == 1)
+               /* Re-enable card's interrupt */
+               outb(CVEN, dev->iobase + DAS1800_STATUS);
+       /* otherwise, stop taking data */
+       else
+       {
+               disable_das1800(dev);           /* diable hardware triggered conversions */
+               comedi_done(dev, s);
+       }
+       return;
+}
+
+static int das1800_attach(comedi_device *dev, comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       unsigned char byte;
+       int iobase = it->options[0];
+       int irq = it->options[1];
+
+       printk("comedi%d: das1800: io 0x%x, irq %i\n", dev->minor, iobase, irq);
+
+       /* check if io addresses are available */
+       if(check_region(iobase, DAS1800_SIZE) < 0)
+       {
+               printk("I/O port conflict\n");
+               return -EIO;
+       }
+       request_region(iobase, DAS1800_SIZE, "das1800");
+       dev->iobase = iobase;
+       dev->iosize = DAS1800_SIZE;
+
+       /* grab our IRQ */
+       byte = FIMD;    /* interrupt on half full fifo */
+       switch(irq)
+       {
+               case 0:
+                       break;
+               case 3:
+                       byte |= 0x8;
+                       break;
+               case 5:
+                       byte |= 0x10;
+                       break;
+               case 7:
+                       byte |= 0x18;
+                       break;
+               case 10:
+                       byte |= 0x28;
+                       break;
+               case 11:
+                       byte |= 0x30;
+                       break;
+               case 15:
+                       byte |= 0x38;
+                       break;
+               default:
+                       printk("irq out of range\n");
+                       return -EINVAL;
+                       break;
+       }
+       outb(byte, dev->iobase + DAS1800_CONTROL_B); // tell board what irq to use
+
+       if(irq)
+       {
+               if(comedi_request_irq( irq, das1800_interrupt, SA_SHIRQ, "das1800", dev ))
+               {
+                       printk( "unable to allocate irq %d\n", irq);
+                       return -EINVAL;
+               }
+       }
+       dev->irq = irq;
+
+       dev->board = das1800_probe(dev);
+       if(dev->board < 0)
+       {
+               printk("unable to determine board type\n");
+               return -ENODEV;
+       }
+
+       dev->board_ptr = das1800_boards + dev->board;
+       dev->board_name = thisboard->name;
+
+       /* allocate and initialize dev->private */
+       if(alloc_private(dev, sizeof(das1800_private)) < 0)
+               return -ENOMEM;
+       devpriv->count = 0;
+       devpriv->forever = 0;
+
+       dev->n_subdevices = 1;
+       if(alloc_subdevices(dev) < 0)
+               return -ENOMEM;
+
+       /* analog input subdevice */
+       s = dev->subdevices + 0;
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE | SDF_DIFF;
+       s->n_chan = 256;
+       s->len_chanlist = 256;
+       s->maxdata = (1 << thisboard->resolution) - 1;
+       s->range_table = das1800_range_lkup[dev->board];
+       s->do_cmd = das1800_ai_do_cmd;
+       s->do_cmdtest = das1800_ai_do_cmdtest;
+       s->insn_read = das1800_ai_rinsn;
+       s->cancel = das1800_cancel;
+
+       return 0;
+};
+
+static int das1800_detach(comedi_device *dev)
+{
+       printk("comedi%d: das1800: remove\n", dev->minor);
+
+       /* only free stuff if it has been allocated by _attach */
+       if(dev->iobase)
+       {
+               release_region(dev->iobase, DAS1800_SIZE);
+               dev->iobase = 0;
+       }
+       if(dev->irq)
+       {
+               comedi_free_irq(dev->irq, dev);
+               dev->irq = 0;
+       }
+       return 0;
+};
+
+static int das1800_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+       devpriv->forever = 0;
+       devpriv->count = 0;
+       disable_das1800(dev);
+       return 0;
+}
+
+/* enable_das1800 makes the card start taking hardware triggered conversions */
+void enable_das1800(comedi_device *dev)
+{
+       outb(CGEN | FFEN, dev->iobase + DAS1800_CONTROL_A);     /* enable fifo and hardware triggering */
+       outb(CVEN, dev->iobase + DAS1800_STATUS);       /* enable conversions */
+}
+
+/* disable_das1800 stops hardware triggered conversions */
+void disable_das1800(comedi_device *dev)
+{
+       outb(0x0, dev->iobase + DAS1800_STATUS);        /* disable conversions */
+       outb(0x0, dev->iobase + DAS1800_CONTROL_A);     /* disable and clear fifo and stop triggering */
+}
+
+static int das1800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
+{
+       int err = 0;
+       int tmp;
+
+       /* step 1: make sure trigger sources are trivially valid */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW;
+       if(!cmd->start_src && tmp != cmd->start_src) err++;
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_FOLLOW;
+       if(!cmd->scan_begin_src && tmp != cmd->scan_begin_src) err++;
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_TIMER;
+       if(!cmd->convert_src && tmp != cmd->convert_src) err++;
+
+       tmp = cmd->scan_end_src;
+       cmd->scan_end_src &= TRIG_COUNT;
+       if(!cmd->scan_end_src && tmp != cmd->scan_end_src) err++;
+
+       tmp=cmd->stop_src;
+       cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
+       if(!cmd->stop_src && tmp!=cmd->stop_src) err++;
+
+       if(err) return 1;
+
+       /* step 2: make sure trigger sources are unique and mutually compatible */
+
+       if(cmd->start_src != TRIG_NOW) err++;
+       if(cmd->scan_begin_src != TRIG_FOLLOW) err++;
+       if(cmd->convert_src != TRIG_TIMER) err++;
+       if(cmd->scan_end_src != TRIG_COUNT) err++;
+       if(cmd->stop_src != TRIG_COUNT &&
+               cmd->stop_src != TRIG_NONE) err++;
+
+       if(err)return 2;
+
+       /* step 3: make sure arguments are trivially compatible */
+
+       if(cmd->start_arg != 0)
+       {
+               cmd->start_arg = 0;
+               err++;
+       }
+       if(cmd->convert_src == TRIG_TIMER)
+       {
+               if(cmd->convert_arg < thisboard->ai_speed)
+               {
+                       cmd->convert_arg = thisboard->ai_speed;
+                       err++;
+               }
+       }
+       if(!cmd->chanlist_len)
+       {
+               cmd->chanlist_len = 1;
+               err++;
+       }
+       if(cmd->chanlist_len > s->len_chanlist)
+       {
+               cmd->chanlist_len = s->len_chanlist;
+               err++;
+       }
+       if(cmd->scan_end_arg != cmd->chanlist_len)
+       {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+       if(cmd->stop_src == TRIG_COUNT)
+       {
+               if(!cmd->stop_arg)
+               {
+                       cmd->stop_arg = 1;
+                       err++;
+               }
+       } else
+       { /* TRIG_NONE */
+               if(cmd->stop_arg != 0)
+               {
+                       cmd->stop_arg = 0;
+                       err++;
+               }
+       }
+
+       if(err)return 3;
+
+       /* step 4: fix up any arguments */
+       if(cmd->convert_src == TRIG_TIMER)
+       {
+               tmp = cmd->convert_arg;
+               cmd->convert_arg = das1800_find_divisors(cmd->convert_arg, dev);
+               if(tmp != cmd->convert_arg) err++;
+       }
+
+       if(err)return 4;
+
+       return 0;
+}
+
+static int das1800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s)
+{
+       int i, n, chan_range;
+
+       if(!dev->irq)
+       {
+               comedi_error(dev, "no irq assigned for das-1800, cannot do hardware conversions");
+               return -1;
+       }
+
+       disable_das1800(dev);
+
+       n = s->cmd.chanlist_len;
+       outb(0x1, dev->iobase + DAS1800_SELECT); /* select QRAM for baseAddress + 0x0 */
+       outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS);        /*set QRAM address start */
+       for(i = 0; i < n; i++)  /* make channel / gain list */
+       {
+               chan_range = CR_CHAN(s->cmd.chanlist[i]) | (CR_RANGE(s->cmd.chanlist[i]) << 8);
+               outw(chan_range, dev->iobase + DAS1800_QRAM);
+       }
+       outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS);        /*finish write to QRAM */
+       outb(0x0, dev->iobase + DAS1800_SELECT);        /* select ADC for baseAddress + 0x0 */
+
+       switch(s->cmd.stop_src)
+       {
+               case TRIG_COUNT:
+                       devpriv->count = s->cmd.stop_arg;
+                       devpriv->forever = 0;
+                       break;
+               case TRIG_NONE:
+                       devpriv->forever = 1;
+                       devpriv->count = 0;
+                       break;
+               default :
+                       break;
+       }
+
+       /* enable auto channel scan, send interrupts on end of conversion
+        * and set clock source to internal or external
+        */
+       switch(s->cmd.convert_src)
+       {
+               case TRIG_TIMER:
+                       /* differential, bipolar, pacer clocks are clocks 1 and 2 */
+                       outb(UQEN | 0x1, dev->iobase + DAS1800_CONTROL_C);
+                       /* set conversion frequency */
+                       if(das1800_set_frequency(s->cmd.convert_arg, dev) < 0)
+                       {
+                               comedi_error(dev, "Error setting up counters");
+                               return -1;
+                       }
+                       break;
+/*             case TRIG_EXT:
+                       break;
+*/             default:
+                       break;
+       }
+
+       enable_das1800(dev);
+       return 0;
+}
+
+static int das1800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+       int i, n;
+       int chan, range, chan_range;
+       int timeout = 1000;
+       short dpnt;
+
+       outb(UQEN, dev->iobase + DAS1800_CONTROL_C);    /* software conversion enabled */
+       outb(CVEN, dev->iobase + DAS1800_STATUS);       /* enable conversions */
+       outb(0x0, dev->iobase + DAS1800_CONTROL_A);     /* reset fifo */
+       outb(FFEN, dev->iobase + DAS1800_CONTROL_A);
+
+
+       chan = CR_CHAN(insn->chanspec) & 0xff;
+       range = CR_RANGE(insn->chanspec) & 0x3;
+       chan_range = chan | (range << 8);
+       outb(0x1, dev->iobase + DAS1800_SELECT);        /* select QRAM for baseAddress + 0x0 */
+       outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS);  /* set QRAM address start */
+       outw(chan_range, dev->iobase + DAS1800_QRAM);
+       outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS);  /*finish write to QRAM */
+       outb(0x0, dev->iobase + DAS1800_SELECT);        /* select ADC for baseAddress + 0x0 */
+
+       udelay(5);
+
+       for(n = 0; n < insn->n; n++)
+       {
+               /* trigger conversion */
+               outb(0, dev->iobase + DAS1800_FIFO);
+               for(i = 0; i < timeout; i++)
+               {
+                       if(inb(dev->iobase + DAS1800_STATUS) & FNE)
+                               break;
+               }
+               if(i == timeout)
+               {
+                       comedi_error(dev, "timeout");
+                       return -ETIME;
+               }
+               /* shift data from true binary to offset binary */
+               dpnt = inw(dev->iobase + DAS1800_FIFO);
+               dpnt += 1 << (thisboard->resolution - 1);
+               data[n] = dpnt;
+       }
+
+       return n;
+}
+
+
+/* finds best values for cascaded counters to obtain desired frequency,
+ * and loads counters with calculated values
+ */
+int das1800_set_frequency(unsigned int period, comedi_device *dev)
+{
+       int err = 0;
+
+       das1800_find_divisors(period, dev);
+       if(das1800_load_counter(1, devpriv->divisor1, dev)) err++;
+       if(das1800_load_counter(2, devpriv->divisor2, dev)) err++;
+       if(err)
+               return -1;
+
+       return 0;
+}
+
+int das1800_load_counter(unsigned int counterNumber, int counterValue, comedi_device *dev)
+{
+       unsigned char byte;
+
+       if(counterNumber > 2) return -1;
+       if(counterValue < 2 || counterValue > 0xffff) return -1;
+
+  byte = counterNumber << 6;
+       byte = byte | 0x30;     // load low then high byte
+       byte = byte | 0x4;      // set counter mode 2
+       outb(byte, dev->iobase + DAS1800_COUNTER_CONTROL);
+       byte = counterValue & 0xff;     // lsb of counter value
+       outb(byte, dev->iobase + DAS1800_COUNTER(counterNumber));
+       byte = counterValue >> 8;       // msb of counter value
+       outb(byte, dev->iobase + DAS1800_COUNTER(counterNumber));
+       return 0;
+}
+
+unsigned int das1800_find_divisors(unsigned int period, comedi_device *dev)
+{
+       int clock = 200;        // 5 MHz master clock
+       int temp, close;
+       unsigned short i, j;
+       unsigned int max, min;
+
+       if((devpriv->divisor1 * devpriv->divisor2 * clock) == period) return period;
+
+       max = (period / (2 * clock));
+       if(max > 0xffff)
+               max = 0xffff;
+       min = 2;
+       close = period;
+       for(i = min; i <= max; i++)
+       {
+               for(j = (period / (i * clock)); j <= (period / (i * clock)) + 1; j++)
+               {
+                       temp = period - clock * i * j;
+                       if(temp < 0) temp = -temp;
+                       if(temp < close && j >= min)
+                       {
+                               close = temp;
+                               devpriv->divisor1 = i; devpriv->divisor2 = j;
+                               if(close == 0) return period;
+                       }
+               }
+       }
+       return devpriv->divisor1 * devpriv->divisor2 * clock;
+}
index 04055cbea8e367d6c892ee164c835dbac1212fcc..6ff5bcc9ffa39cab36a95594e6be1fa8a00e35fc 100644 (file)
@@ -41,6 +41,13 @@ cmd triggers supported:
 The number of channels scanned is determined from chanlist_len.  The
 starting channel to scan and the gain is determined from chanlist[0].
 
+TODO:
+       Add support for cards' digital i/o
+
+NOTES:
+       I've never tested the gain setting stuff since I only have a
+       DAS-800 board with fixed gain.
+
 */
 
 #include <linux/kernel.h>
@@ -94,15 +101,20 @@ das800_board das800_boards[] =
 {
        {
                name:   "DAS-800",
-               ai_speed:       25000,  /* 40kHz maximum conversion rate (25 microseconds) */
+       /* 50kHz maximum conversion rate (20 microseconds.) Warning:
+        * Only the computerboards versions can do 50kHz.  Keithley
+        * boards can only do 40kHz.
+        */
+               ai_speed:       20000,
        },
        {
                name:           "DAS-801",
-               ai_speed:       25000,
+       /* Warning: Keithley DAS-801 can only sample at 25kHz when gain is 500 */
+               ai_speed:       20000,
        },
        {
                name:           "DAS-802",
-               ai_speed:       25000,
+               ai_speed:       20000,
        },
 };
 /*
@@ -113,6 +125,8 @@ das800_board das800_boards[] =
 typedef struct{
        unsigned long count;  /* number of data points left to be taken */
        int forever;  /* flag indicating whether we should take data forever */
+       unsigned short divisor1;        /* value to load into board's counter 1 for timed conversions */
+       unsigned short divisor2;        /* value to load into board's counter 2 for timed conversions */
 }das800_private;
 
 #define devpriv ((das800_private *)dev->private)
@@ -160,12 +174,6 @@ static comedi_lrange *das800_range_lkup[] = {
        &range_das802_ai,
 };
 
-/*
- * The comedi_driver structure tells the Comedi core module
- * which functions to call to configure/deconfigure (attach/detach)
- * the board, and also about the kernel module that contains
- * the device code.
- */
 static int das800_attach(comedi_device *dev,comedi_devconfig *it);
 static int das800_detach(comedi_device *dev);
 static int das800_cancel(comedi_device *dev, comedi_subdevice *s);
@@ -186,12 +194,14 @@ static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn
 int das800_probe(comedi_device *dev);
 int das800_set_frequency( unsigned int period, comedi_device *dev);
 int das800_load_counter(unsigned int counterNumber, int counterValue, comedi_device *dev);
+unsigned int das800_find_divisors(unsigned int period, comedi_device *dev);
 
+/* probes das-800 series board type */
 int das800_probe(comedi_device *dev)
 {
        int id;
        outb(ID, dev->iobase + DAS800_GAIN);    /* select base address + 7 to be ID register */
-       id = inb(dev->iobase + DAS800_ID) & 0x3;
+       id = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */
        switch(id)
        {
                case 0x0:
@@ -208,7 +218,7 @@ int das800_probe(comedi_device *dev)
                        break;
                default :
                        printk(" Board model: probe returned 0x%x (unknown)\n", id);
-                       return 0;
+                       return -1;
                        break;
        }
        return -1;
@@ -236,20 +246,19 @@ static void das800_interrupt(int irq, void *d, struct pt_regs *regs)
                if( dataPoint & FIFO_OVF )
                {
                        comedi_error(dev, "DAS800 FIFO overflow");
+                       das800_cancel(dev, dev->subdevices + 0);
                        comedi_error_done(dev, s);
-                       devpriv->forever = 0;
-                       devpriv->count = 0;
-                       disableDAS800(dev);
                        return;
                }
                dataPoint = (dataPoint >> 4) & 0xfff;           /* strip off extraneous bits */
+               /* if there are more data points to collect */
                if(devpriv->count > 0 || devpriv->forever == 1)
                {
                        /* write data point to buffer */
-                       if(s->buf_int_ptr >= s->cur_trig.data_len )
+                       if(s->buf_int_ptr + sizeof(sampl_t) > s->cur_trig.data_len )
                        {
-                               s->buf_int_ptr=0;
-                               comedi_eobuf(dev,s);
+                               s->buf_int_ptr = 0;
+                               comedi_eobuf(dev, s);
                        }
                        *((sampl_t *)((void *)s->cur_trig.data + s->buf_int_ptr)) = dataPoint;
                        s->cur_chan++;
@@ -262,26 +271,23 @@ static void das800_interrupt(int irq, void *d, struct pt_regs *regs)
                        s->buf_int_ptr += sizeof(sampl_t);
                        if(devpriv->count > 0) devpriv->count--;
                }
-         /* read 16 bits from dev->iobase and dev->iobase + 1 */
-         dataPoint = inb(dev->iobase + DAS800_LSB);
-         dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
+               /* read 16 bits from dev->iobase and dev->iobase + 1 */
+               dataPoint = inb(dev->iobase + DAS800_LSB);
+               dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
        }
        comedi_bufcheck(dev,s);
        if(devpriv->count > 0 || devpriv->forever == 1)
-               outb(CONRTOL1_INTE, dev->iobase + DAS800_CONTROL1);     /* re-enable card's interrupt */
+               /* Re-enable card's interrupt */
+               outb(CONRTOL1_INTE, dev->iobase + DAS800_CONTROL1);
        /* otherwise, stop taking data */
        else
        {
                disableDAS800(dev);             /* diable hardware triggered conversions */
                comedi_done(dev, s);
        }
-};
+       return;
+}
 
-/*
- * Attach is called by the Comedi core to configure the driver
- * for a particular board.  _recognize() has already been called,
- * and dev->board contains whatever _recognize returned.
- */
 static int das800_attach(comedi_device *dev, comedi_devconfig *it)
 {
        comedi_subdevice *s;
@@ -291,7 +297,7 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        printk("comedi%d: das800: io 0x%x, irq %i\n",dev->minor, iobase, irq);
 
        /* check if io addresses are available */
-       if (check_region(iobase, DAS800_SIZE) < 0)
+       if(check_region(iobase, DAS800_SIZE) < 0)
        {
                printk("I/O port conflict\n");
                return -EIO;
@@ -301,43 +307,39 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        dev->iosize = DAS800_SIZE;
 
        /* grab our IRQ */
-       if(irq < 2 || irq > 7)
+       if(irq == 1 || irq > 7 || irq < 0)
        {
                printk("irq out of range\n");
                return -EINVAL;
        }
-       if( comedi_request_irq( irq, das800_interrupt, 0, "das800", dev ))
+       if(irq)
        {
-               printk( "unable to allocate irq %d\n", irq);
-               return -EINVAL;
+               if(comedi_request_irq( irq, das800_interrupt, 0, "das800", dev ))
+               {
+                       printk( "unable to allocate irq %d\n", irq);
+                       return -EINVAL;
+               }
        }
        dev->irq = irq;
 
        dev->board = das800_probe(dev);
+       if(dev->board < 0)
+       {
+               printk("unable to determine board type\n");
+               return -ENODEV;
+       }
 
-/*
- * Initialize dev->board_ptr.  This can point to an element in the
- * das800_boards array, for quick access to board-specific information.
- */
        dev->board_ptr = das800_boards + dev->board;
-
-/*
- * Initialize dev->board_name.  Note that we can use the "thisboard"
- * macro now, since we just initialized it in the last line.
- */
        dev->board_name = thisboard->name;
 
-/*
- * Allocate the private structure area.
- */
-       if(alloc_private(dev,sizeof(das800_private))<0)
+       /* allocate and initialize dev->private */
+       if(alloc_private(dev, sizeof(das800_private)) < 0)
                return -ENOMEM;
+       devpriv->count = 0;
+       devpriv->forever = 0;
 
-/*
- * Allocate the subdevice structures.
- */
        dev->n_subdevices = 1;
-       if(alloc_subdevices(dev)<0)
+       if(alloc_subdevices(dev) < 0)
                return -ENOMEM;
 
        /* analog input subdevice */
@@ -346,25 +348,31 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        s->subdev_flags = SDF_READABLE;
        s->n_chan = 8;
        s->len_chanlist = 8;
-       s->maxdata=(1 << 12) - 1;
+       s->maxdata = 0xfff;
        s->range_table = das800_range_lkup[dev->board];
        s->do_cmd = das800_ai_do_cmd;
        s->do_cmdtest = das800_ai_do_cmdtest;
        s->insn_read = das800_ai_rinsn;
        s->cancel = das800_cancel;
+
        return 0;
 };
 
 static int das800_detach(comedi_device *dev)
 {
-
        printk("comedi%d: das800: remove\n", dev->minor);
 
        /* only free stuff if it has been allocated by _attach */
        if(dev->iobase)
+       {
                release_region(dev->iobase, DAS800_SIZE);
+               dev->iobase = 0;
+       }
        if(dev->irq)
-               comedi_free_irq(dev->irq,dev);
+       {
+               comedi_free_irq(dev->irq, dev);
+               dev->irq = 0;
+       }
        return 0;
 };
 
@@ -395,7 +403,7 @@ void disableDAS800(comedi_device *dev)
 static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
 {
        int err = 0;
-       int tmp, chan, range;
+       int tmp;
 
        /* step 1: make sure trigger sources are trivially valid */
 
@@ -423,15 +431,19 @@ static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cm
 
        /* step 2: make sure trigger sources are unique and mutually compatible */
 
+       if(cmd->start_src != TRIG_NOW) err++;
        if(cmd->scan_begin_src != TRIG_FOLLOW) err++;
        if(cmd->convert_src != TRIG_TIMER &&
           cmd->convert_src != TRIG_EXT) err++;
+       if(cmd->scan_end_src != TRIG_COUNT) err++;
+       if(cmd->stop_src != TRIG_COUNT &&
+               cmd->stop_src != TRIG_NONE) err++;
 
        if(err)return 2;
 
        /* step 3: make sure arguments are trivially compatible */
 
-       if(cmd->start_arg!=0)
+       if(cmd->start_arg != 0)
        {
                cmd->start_arg = 0;
                err++;
@@ -446,12 +458,12 @@ static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cm
        }
        if(!cmd->chanlist_len)
        {
-               cmd->chanlist_len=1;
+               cmd->chanlist_len = 1;
                err++;
        }
-       if(cmd->chanlist_len > 8)
+       if(cmd->chanlist_len > s->len_chanlist)
        {
-               cmd->chanlist_len = 8;
+               cmd->chanlist_len = s->len_chanlist;
                err++;
        }
        if(cmd->scan_end_arg != cmd->chanlist_len)
@@ -479,21 +491,11 @@ static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cm
 
        /* step 4: fix up any arguments */
 
-       chan = CR_CHAN(cmd->chanlist[0]);
-       range = CR_RANGE(cmd->chanlist[0]);
-       if(chan >= 8)
-       {
-               cmd->chanlist[0] = CR_PACK(chan % 8, range, 0);
-               err++;
-       }
        if(cmd->convert_src == TRIG_TIMER)
        {
                tmp = cmd->convert_arg;
-/* todo: add code to figure out what actual convert_arg card will use
-               and store it in tmp
-
+               cmd->convert_arg = das800_find_divisors(cmd->convert_arg, dev);
                if(tmp != cmd->convert_arg) err++;
-*/
        }
 
        if(err)return 4;
@@ -505,6 +507,12 @@ static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s)
 {
        int startChan, endChan, scan, gain;
 
+       if(!dev->irq)
+       {
+               comedi_error(dev, "no irq assigned for das-800, cannot do hardware conversions");
+               return -1;
+       }
+
        disableDAS800(dev);
 
        /* set channel scan limits */
@@ -542,9 +550,9 @@ static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s)
        {
                case TRIG_TIMER:
                        outb(CONV_CONTROL, dev->iobase + DAS800_GAIN);  /* select dev->iobase + 2 to be conversion control register */
-                       outb(EACS | IEOC | CASC| ITE, dev->iobase + DAS800_CONV_CONTROL);
+                       outb(EACS | IEOC | CASC | ITE, dev->iobase + DAS800_CONV_CONTROL);
                        /* set conversion frequency */
-                       if( das800_set_frequency( s->cmd.convert_arg, dev) < 0)
+                       if(das800_set_frequency(s->cmd.convert_arg, dev) < 0)
                        {
                                comedi_error(dev, "Error setting up counters");
                                return -1;
@@ -564,12 +572,13 @@ static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s)
 
 static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
 {
-       int i,n;
+       int i, n;
        int chan;
        int range;
-       int lsb,msb;
+       int lsb, msb;
+       int timeout = 1000;
 
-       disableDAS800(dev);
+       disableDAS800(dev);     /* disable hardware conversions (enables software conversions) */
 
        /* set multiplexer */
        chan = CR_CHAN(insn->chanspec);
@@ -588,15 +597,15 @@ static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn
        for(n = 0; n < insn->n; n++)
        {
                /* trigger conversion */
-               outb(0, dev->iobase);
-               udelay(25);
-#define TIMEOUT 1000
-               for(i = 0; i < TIMEOUT; i++)
+               outb(0, dev->iobase + DAS800_MSB);
+               udelay(10);
+
+               for(i = 0; i < timeout; i++)
                {
-                       if(!(inb(DAS800_STATUS) & BUSY))
+                       if(!(inb(dev->iobase + DAS800_STATUS) & BUSY))
                                break;
                }
-               if(i == TIMEOUT)
+               if(i == timeout)
                {
                        comedi_error(dev, "timeout");
                        return -ETIME;
@@ -613,39 +622,17 @@ static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn
 /* finds best values for cascaded counters to obtain desired frequency,
  * and loads counters with calculated values
  */
-int das800_set_frequency( unsigned int period, comedi_device *dev)
+int das800_set_frequency(unsigned int period, comedi_device *dev)
 {
-       int clock = 1000;
-       int countA = 0;
-       int countB = 0;
-       int temp, close;
-       unsigned int i, j, max, min;
-
-       max = (unsigned int) (period / (2 * clock));
-       if(max > (1 << 16))
-               max = (1 << 16);
-       min = 2;
-       close = period;
-       for(i = min; i <= max; i++)
-       {
-               for(j = (period / (i * clock)); j <= (period / (i * clock)) + 1; j++)
-               {
-                       temp = period - clock * i * j;
-                       if(temp < 0) temp = -temp;
-                       if(temp < close)
-                       {
-                               close = temp;
-                               countA = i; countB = j;
-                       }
-               }
-       }
+       int err = 0;
 
-       countA = das800_load_counter(1, countA, dev);
-       countB = das800_load_counter(2, countB, dev);
-       if(countA < 0 || countB < 0)
+       das800_find_divisors(period, dev);
+       if(das800_load_counter(1, devpriv->divisor1, dev)) err++;
+       if(das800_load_counter(2, devpriv->divisor2, dev)) err++;
+       if(err)
                return -1;
 
-       return clock * countA * countB;
+       return 0;
 }
 
 int das800_load_counter(unsigned int counterNumber, int counterValue, comedi_device *dev)
@@ -657,11 +644,42 @@ int das800_load_counter(unsigned int counterNumber, int counterValue, comedi_dev
 
   byte = counterNumber << 6;
        byte = byte | 0x30;     // load low then high byte
-       byte = byte | 0x4;      // set counter mode
+       byte = byte | 0x4;      // set counter mode 2
        outb(byte, dev->iobase + 0x7);
        byte = counterValue & 0xff;     // lsb of counter value
        outb(byte, dev->iobase + 0x4 + counterNumber);
        byte = counterValue >> 8;       // msb of counter value
        outb(byte, dev->iobase + 0x4 + counterNumber);
-       return counterValue;
+       return 0;
+}
+
+unsigned int das800_find_divisors(unsigned int period, comedi_device *dev)
+{
+       int clock = 1000;
+       int temp, close;
+       unsigned short i, j;
+       unsigned int max, min;
+
+       if((devpriv->divisor1 * devpriv->divisor2 * clock) == period) return period;
+
+       max = (period / (2 * clock));
+       if(max > 0xffff)
+               max = 0xffff;
+       min = 2;
+       close = period;
+       for(i = min; i <= max; i++)
+       {
+               for(j = (period / (i * clock)); j <= (period / (i * clock)) + 1; j++)
+               {
+                       temp = period - clock * i * j;
+                       if(temp < 0) temp = -temp;
+                       if(temp < close && j >= min)
+                       {
+                               close = temp;
+                               devpriv->divisor1 = i; devpriv->divisor2 = j;
+                               if(close == 0) return period;
+                       }
+               }
+       }
+       return devpriv->divisor1 * devpriv->divisor2 * clock;
 }