update from frank
authorDavid Schleef <ds@schleef.org>
Wed, 29 Nov 2000 23:18:10 +0000 (23:18 +0000)
committerDavid Schleef <ds@schleef.org>
Wed, 29 Nov 2000 23:18:10 +0000 (23:18 +0000)
comedi/drivers/das800.c

index ad1c4e7f020c8ce757649865d9677515efa1e3c2..a06925ee00723e891ae7f31e62885ee03755d96f 100644 (file)
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
+************************************************************************
+
+This driver supports the Keithley DAS-800, DAS-801, and DAS-802
+boards.  It will also work with Measurement Computing (Computer Boards)
+boards CIO-DAS800, CIO-DAS801, and CIO-DAS802 boards although the
+CIO-DAS801 has a slightly different gain list than the DAS-801.
+
+Options:
+       [0] - base io address
+       [1] - irq
+
+cmd triggers supported:
+       start_src: TRIG_NOW
+       scan_begin_src: TRIG_FOLLOW
+       scan_end_src:   TRIG_COUNT
+       convert_src:    TRIG_TIMER | TRIG_EXT
+       scan_end_src:   TRIG_COUNT
+       stop_src:       TRIG_END | TRIG_COUNT
+
+The number of channels scanned is determined from chanlist_len.  The
+starting channel to scan and the gain is determined from chanlist[0].
+
 */
 
 #include <linux/kernel.h>
 #include <linux/timex.h>
 #include <linux/timer.h>
 #include <asm/io.h>
-#include <linux/comedidev.h>
+#include <comedi_module.h>
 
 #define DAS800_SIZE           8
-#define TIMER_BASE            1000
 /* Registers for the das800 */
 
-#define DAS800_AI_LSB_READ    0
+#define DAS800_LSB    0
 #define   FIFO_EMPTY            0x1
 #define   FIFO_OVF              0x2
-#define DAS800_AI_MSB_READ    1
+#define DAS800_MSB    1
 #define DAS800_CONTROL1       2
 #define   CONRTOL1_INTE         0x8
 #define DAS800_CONV_CONTROL   2
@@ -52,6 +73,8 @@
 #define   EACS                  0x10
 #define   CONV_HCEN             0x80
 #define DAS800_SCAN_LIMITS    2
+#define DAS800_STATUS         2
+#define   BUSY                  0x80
 #define DAS800_GAIN           3
 #define   CONTROL1              0x80
 #define   CONV_CONTROL          0xa0
 #define   STATUS2_INTE          0X20
 #define DAS800_ID             7
 
-/*
- * Board descriptions for two imaginary boards.  Describing the
- * boards in this way is optional, and completely driver-dependent.
- * Some drivers use arrays such as this, other do not.
- */
 typedef struct das800_board_struct{
        char *name;
        int ai_speed;
@@ -92,17 +110,11 @@ das800_board das800_boards[] =
  */
 #define thisboard ((das800_board *)dev->board_ptr)
 
-/* this structure is for data unique to this hardware driver.  If
-   several hardware drivers keep similar information in this structure,
-   feel free to suggest moving the variable to the comedi_device struct.  */
 typedef struct{
        unsigned long count;  /* number of data points left to be taken */
        int forever;  /* flag indicating whether we should take data forever */
 }das800_private;
-/*
- * most drivers define the following macro to make it easy to
- * access the private structure.
- */
+
 #define devpriv ((das800_private *)dev->private)
 
 static comedi_lrange range_das800_ai = {
@@ -156,6 +168,7 @@ static comedi_lrange *das800_range_lkup[] = {
  */
 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);
 
 comedi_driver driver_das800={
        driver_name:    "das800",
@@ -164,24 +177,21 @@ comedi_driver driver_das800={
        detach:         das800_detach,
 };
 
-/* static int das800_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
- * static int das800_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
- */
-
 static void das800_interrupt(int irq, void *d, struct pt_regs *regs);
 void enableDAS800(comedi_device *dev);
 void disableDAS800(comedi_device *dev);
-static int das800_do_cmd(comedi_device *dev, comedi_subdevice *s);
+static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd);
+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 das800_probe(comedi_device *dev);
-int das800SetFrequency( unsigned int period, comedi_device *dev);
-int das800LoadCounter(unsigned int counterNumber, int counterValue, 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);
 
 int das800_probe(comedi_device *dev)
 {
        int id;
        outb(ID, dev->iobase + DAS800_GAIN);    /* select base address + 7 to be ID register */
-       id = inb(DAS800_ID);
-       outb(CONTROL1, dev->iobase + DAS800_GAIN);      /* select base address +7 to be status register 2 */
+       id = inb(dev->iobase + DAS800_ID) & 0x3;
        switch(id)
        {
                case 0x0:
@@ -197,8 +207,8 @@ int das800_probe(comedi_device *dev)
                        return 2;
                        break;
                default :
-                       printk(" Board model: probe failed\n");
-                       return dev->board;
+                       printk(" Board model: probe returned 0x%x (unknown)\n", id);
+                       return 0;
                        break;
        }
        return -1;
@@ -213,33 +223,35 @@ COMEDI_INITCLEANUP(driver_das800);
 static void das800_interrupt(int irq, void *d, struct pt_regs *regs)
 {
        short i;                /* loop index */
-  sampl_t dataPoint;
+       sampl_t dataPoint;
        comedi_device *dev = d;
        comedi_subdevice *s = dev->subdevices + 0;      /* analog input subdevice */
 
   /* read 16 bits from dev->iobase and dev->iobase + 1 */
-  dataPoint = inb(dev->iobase + DAS800_AI_LSB_READ);
-  dataPoint += inb(dev->iobase + DAS800_AI_MSB_READ) << 8;
-       /* loop while card's fifo is not empty (and make sure loop terminates by limiting to 10 iterations) */
-       for(i = 0; (dataPoint & FIFO_EMPTY) == 0 && i < 10; i++)
+       dataPoint = inb(dev->iobase + DAS800_LSB);
+       dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
+       /* loop while card's fifo is not empty (and make sure loop terminates by limiting to 256 iterations) */
+       for(i = 0; (dataPoint & FIFO_EMPTY) == 0 && i < 256; i++)
        {
                if( dataPoint & FIFO_OVF )
                {
-      comedi_error(dev, "DAS800 FIFO overflow");
+                       comedi_error(dev, "DAS800 FIFO overflow");
                        comedi_error_done(dev, s);
+                       devpriv->forever = 0;
+                       devpriv->count = 0;
+                       disableDAS800(dev);
                        return;
                }
                dataPoint = (dataPoint >> 4) & 0xfff;           /* strip off extraneous bits */
                if(devpriv->count > 0 || devpriv->forever == 1)
                {
                        /* write data point to buffer */
-                       if(s->buf_int_ptr + sizeof(sampl_t) >= s->cur_trig.data_len)
+                       if(s->buf_int_ptr >= s->cur_trig.data_len )
                        {
                                s->buf_int_ptr=0;
                                comedi_eobuf(dev,s);
-                               break;
                        }
-                       *(s->cur_trig.data + s->buf_int_ptr) = dataPoint;
+                       *((sampl_t *)((void *)s->cur_trig.data + s->buf_int_ptr)) = dataPoint;
                        s->cur_chan++;
                        if( s->cur_chan >= s->cur_chanlist_len )
                        {
@@ -251,8 +263,8 @@ static void das800_interrupt(int irq, void *d, struct pt_regs *regs)
                        if(devpriv->count > 0) devpriv->count--;
                }
          /* read 16 bits from dev->iobase and dev->iobase + 1 */
-         dataPoint = inb(dev->iobase + DAS800_AI_LSB_READ);
-         dataPoint += inb(dev->iobase + DAS800_AI_MSB_READ) << 8;
+         dataPoint = inb(dev->iobase + DAS800_LSB);
+         dataPoint += inb(dev->iobase + DAS800_MSB) << 8;
        }
        comedi_bufcheck(dev,s);
        if(devpriv->count > 0 || devpriv->forever == 1)
@@ -276,7 +288,7 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        int iobase = it->options[0];
        int irq = it->options[1];
 
-       printk("comedi%d: das800: io 0x%x, irq %i",dev->minor, iobase, irq);
+       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)
@@ -284,6 +296,7 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
                printk("I/O port conflict\n");
                return -EIO;
        }
+       request_region(iobase, DAS800_SIZE, "das800");
        dev->iobase = iobase;
        dev->iosize = DAS800_SIZE;
 
@@ -300,14 +313,6 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        }
        dev->irq = irq;
 
-       /* wait until after irq check before allocating io addresses */
-       request_region(dev->iobase, DAS800_SIZE, "das800");
-
-/*
- * If you can probe the device to determine what device in a series
- * it is, this is the place to do it.  Otherwise, you should use the
- * _recognize method, and use the value in dev->board.
- */
        dev->board = das800_probe(dev);
 
 /*
@@ -340,32 +345,37 @@ static int das800_attach(comedi_device *dev, comedi_devconfig *it)
        s->type = COMEDI_SUBD_AI;
        s->subdev_flags = SDF_READABLE;
        s->n_chan = 8;
+       s->len_chanlist = 8;
        s->maxdata=(1 << 12) - 1;
        s->range_table = das800_range_lkup[dev->board];
-       s->do_cmd = &das800_do_cmd;
-       s->do_cmdtest = &das800_do_cmdtest;
-
-       return 1;
+       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;
 };
 
-/*
- * _detach is called to deconfigure a device.  It should deallocate
- * resources.
- * This function is also called when _attach() fails, so it should be
- * careful not to release resources that were not necessarily
- * allocated by _attach().  dev->private and dev->subdevices are
- * deallocated automatically by the core.
- */
 static int das800_detach(comedi_device *dev)
 {
 
        printk("comedi%d: das800: remove\n", dev->minor);
 
-       release_region(dev->iobase, DAS800_SIZE);               /* free general status and control registers' i/o ports */
-       comedi_free_irq(dev->irq,dev);          /* free irq */
+       /* only free stuff if it has been allocated by _attach */
+       if(dev->iobase)
+               release_region(dev->iobase, DAS800_SIZE);
+       if(dev->irq)
+               comedi_free_irq(dev->irq,dev);
        return 0;
 };
 
+static int das800_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+       devpriv->forever = 0;
+       devpriv->count = 0;
+       disableDAS800(dev);
+       return 0;
+}
+
 /* enableDAS800 makes the card start taking hardware triggered conversions */
 void enableDAS800(comedi_device *dev)
 {
@@ -378,23 +388,54 @@ void enableDAS800(comedi_device *dev)
 /* disableDAS800 stops hardware triggered conversions */
 void disableDAS800(comedi_device *dev)
 {
-       outb(0x0, dev->iobase + DAS800_CONTROL1);       /* disable card's interrupt */
        outb(CONV_CONTROL, dev->iobase + DAS800_GAIN);  /* select dev->iobase + 2 to be conversion control register */
        outb(0x0, dev->iobase + DAS800_CONV_CONTROL);   /* disable hardware triggering of conversions */
-       outb(CONTROL1, dev->iobase + DAS800_GAIN);      /* select dev->iobase + 2 to be control register 1 */
 }
 
-static int das800_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
+static int das800_ai_do_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
 {
        int err = 0;
+       int tmp, chan, range;
+
+       /* 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 | TRIG_EXT;
+       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++;
 
-       /* check for supported triggers */
-       if(cmd->start_src != TRIG_NOW) err++;
-       if(cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) err++;
-       if(cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) err++;
        if(err) return 1;
 
-       /* check arguments */
+       /* step 2: make sure trigger sources are unique and mutually compatible */
+
+       if(cmd->scan_begin_src != TRIG_FOLLOW) err++;
+       if(cmd->convert_src != TRIG_TIMER &&
+          cmd->convert_src != TRIG_EXT) 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)
@@ -403,43 +444,82 @@ static int das800_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *
                        err++;
                }
        }
+       if(!cmd->chanlist_len)
+       {
+               cmd->chanlist_len=1;
+               err++;
+       }
+       if(cmd->chanlist_len > 8)
+       {
+               cmd->chanlist_len = 8;
+               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 > 0x00ffffff)
+               if(!cmd->stop_arg)
                {
-                       cmd->stop_arg = 0x00ffffff;
+                       cmd->stop_arg = 1;
+                       err++;
+               }
+       } else
+       { /* TRIG_NONE */
+               if(cmd->stop_arg != 0)
+               {
+                       cmd->stop_arg = 0;
                        err++;
                }
        }
-       if(cmd->chanlist_len <= 0 || cmd->chanlist_len >8)
+
+       if(err)return 3;
+
+       /* step 4: fix up any arguments */
+
+       chan = CR_CHAN(cmd->chanlist[0]);
+       range = CR_RANGE(cmd->chanlist[0]);
+       if(chan >= 8)
        {
-               cmd->chanlist_len = 1;
+               cmd->chanlist[0] = CR_PACK(chan % 8, range, 0);
                err++;
        }
-       if(err)return 3;
+       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
+
+               if(tmp != cmd->convert_arg) err++;
+*/
+       }
+
+       if(err)return 4;
 
        return 0;
 }
 
-static int das800_do_cmd(comedi_device *dev, comedi_subdevice *s)
+static int das800_ai_do_cmd(comedi_device *dev, comedi_subdevice *s)
 {
-       int gain;
-       unsigned int numChannels = s->cmd.chanlist_len;
+       int startChan, endChan, scan, gain;
 
        disableDAS800(dev);
 
        /* set channel scan limits */
        outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN);   /* select base address + 2 to be scan limits register */
-       outb((numChannels - 1) << 3, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
-       outb(CONTROL1, dev->iobase + DAS800_GAIN);      /* select dev->iobase + 2 to be control register 1 */
+       startChan = CR_CHAN(s->cmd.chanlist[0]);
+       endChan = (startChan + s->cmd.chanlist_len - 1) % 8;
+       scan = (endChan << 3) | startChan;
+       outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */
 
        /* set gain */
-       gain = s->cmd.chanlist[0];
+       gain = CR_RANGE(s->cmd.chanlist[0]);
        if( gain > 0)
                gain += 0x7;
-       gain = gain & 0xf;
-       outb(gain, DAS800_GAIN);
-
+       gain &= 0xf;
+       outb(gain, dev->iobase + DAS800_GAIN);
 
        switch(s->cmd.stop_src)
        {
@@ -461,15 +541,17 @@ static int das800_do_cmd(comedi_device *dev, comedi_subdevice *s)
        switch(s->cmd.convert_src)
        {
                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);
                        /* set conversion frequency */
-                       if( das800SetFrequency( 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;
                        }
                        break;
                case TRIG_EXT:
+                       outb(CONV_CONTROL, dev->iobase + DAS800_GAIN);  /* select dev->iobase + 2 to be conversion control register */
                        outb(EACS | IEOC, dev->iobase + DAS800_CONV_CONTROL);
                        break;
                default:
@@ -480,21 +562,71 @@ static int das800_do_cmd(comedi_device *dev, comedi_subdevice *s)
        return 0;
 }
 
+static int das800_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+       int i,n;
+       int chan;
+       int range;
+       int lsb,msb;
+
+       disableDAS800(dev);
+
+       /* set multiplexer */
+       chan = CR_CHAN(insn->chanspec);
+       outb(CONTROL1, dev->iobase + DAS800_GAIN);      /* select dev->iobase + 2 to be control register 1 */
+       outb(chan & 0x7, dev->iobase + DAS800_CONTROL1);
+
+       /* set gain / range */
+       range = CR_RANGE(insn->chanspec);
+       if(range)
+               range += 0x7;
+       range &= 0xf;
+       outb(range, dev->iobase + DAS800_GAIN);
+
+       udelay(5);
+
+       for(n = 0; n < insn->n; n++)
+       {
+               /* trigger conversion */
+               outb(0, dev->iobase);
+               udelay(25);
+#define TIMEOUT 1000
+               for(i = 0; i < TIMEOUT; i++)
+               {
+                       if(!(inb(DAS800_STATUS) & BUSY))
+                               break;
+               }
+               if(i == TIMEOUT)
+               {
+                       comedi_error(dev, "timeout");
+                       return -ETIME;
+               }
+               lsb = inb(dev->iobase + DAS800_LSB);
+               msb = inb(dev->iobase + DAS800_MSB);
+               data[n] = (lsb >> 4) | (msb << 4);
+       }
+
+       return n;
+}
+
+
 /* finds best values for cascaded counters to obtain desired frequency,
  * and loads counters with calculated values
  */
-int das800SetFrequency( 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, start;
+       unsigned int i, j, max, min;
 
-       max = (unsigned int) (period / (2 * clock)) + 1;
-       start  = 1;
+       max = (unsigned int) (period / (2 * clock));
+       if(max > (1 << 16))
+               max = (1 << 16);
+       min = 2;
        close = period;
-       for(i = start; i <= max; i++)
+       for(i = min; i <= max; i++)
        {
                for(j = (period / (i * clock)); j <= (period / (i * clock)) + 1; j++)
                {
@@ -508,20 +640,20 @@ int das800SetFrequency( unsigned int period, comedi_device *dev)
                }
        }
 
-       countA = das800LoadCounter(1, countA, dev);
-       countB = das800LoadCounter(2, countB, dev);
+       countA = das800_load_counter(1, countA, dev);
+       countB = das800_load_counter(2, countB, dev);
        if(countA < 0 || countB < 0)
                return -1;
 
        return clock * countA * countB;
 }
 
-int das800LoadCounter(unsigned int counterNumber, int counterValue, comedi_device *dev)
+int das800_load_counter(unsigned int counterNumber, int counterValue, comedi_device *dev)
 {
        unsigned char byte;
 
        if(counterNumber > 2) return -1;
-       if(counterValue < 1 || counterValue > 0xffff) return -1;
+       if(counterValue < 2 || counterValue > 0xffff) return -1;
 
   byte = counterNumber << 6;
        byte = byte | 0x30;     // load low then high byte
@@ -531,6 +663,5 @@ int das800LoadCounter(unsigned int counterNumber, int counterValue, comedi_devic
        outb(byte, dev->iobase + 0x4 + counterNumber);
        byte = counterValue >> 8;       // msb of counter value
        outb(byte, dev->iobase + 0x4 + counterNumber);
-
        return counterValue;
 }