new driver, untested
authorFrank Mori Hess <fmhess@speakeasy.net>
Tue, 3 Jul 2001 21:11:18 +0000 (21:11 +0000)
committerFrank Mori Hess <fmhess@speakeasy.net>
Tue, 3 Jul 2001 21:11:18 +0000 (21:11 +0000)
comedi/Config.in
comedi/drivers/Makefile
comedi/drivers/ni_at_a2150.c [new file with mode: 0644]

index ae34ed365c15c16d1f11ffd66a2ee39340c7979d..dabe3a2fabbd03492d8527351c90d4bc01d370f7 100644 (file)
@@ -61,6 +61,7 @@ if [ "$CONFIG_COMEDI_NI" = "y" ];then
                fi
        fi
        dep_tristate '  AT-MIO-16D' CONFIG_COMEDI_NI_ATMIO16D $CONFIG_COMEDI
+       dep_tristate '  AT-A2150' CONFIG_COMEDI_NI_AT_A2150 $CONFIG_COMEDI
 fi
 
 dep_tristate 'DAS08 compatible driver' CONFIG_COMEDI_DAS08 $CONFIG_COMEDI
index e9b05e90640d869b706a77633c2ce149597b5b54..9009226b378fa353c55dd37b116298fb072af99d 100644 (file)
@@ -61,6 +61,7 @@ obj-$(CONFIG_COMEDI_NI_PCIMIO)                += ni_pcimio.o
 obj-$(CONFIG_COMEDI_NI_PCIDIO)         += ni_pcidio.o
 obj-$(CONFIG_COMEDI_MITE)              += mite.o
 obj-$(CONFIG_COMEDI_NI_ATMIO16D)       += ni_atmio16d.o
+obj-$(CONFIG_COMEDI_NI_AT_A2150)       += ni_at_a2150.o
 
 obj-$(CONFIG_COMEDI_PCM3730)           += pcm3730.o
 
diff --git a/comedi/drivers/ni_at_a2150.c b/comedi/drivers/ni_at_a2150.c
new file mode 100644 (file)
index 0000000..a291052
--- /dev/null
@@ -0,0 +1,760 @@
+/*
+    ni_at_a2150.c driver for National Instruments AT-A2150 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.
+
+************************************************************************
+
+Yet another driver for obsolete hardware brought to you by Frank Hess.
+
+This driver supports the boards:
+
+AT-A2150C
+AT-A2150S
+
+The only difference is their master clock frequencies.
+
+Options:
+       [0] - base io address
+       [1] - irq
+       [2] - dma channel
+
+*/
+
+#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 <asm/io.h>
+#include <asm/dma.h>
+#include <linux/comedidev.h>
+#include "8253.h"
+
+#define A2150_SIZE           28
+#define A2150_DMA_BUFFER_SIZE  0xff00  // size in bytes of dma buffer
+
+/* Registers and bits */
+#define CONFIG_REG             0x0
+#define   CHANNEL_BITS(x)              ((x) & 0x7)
+#define   CHANNEL_MASK         0x7
+#define   CLOCK_SELECT_BITS(x)         (((x) & 0x3) << 3)
+#define   CLOCK_DIVISOR_BITS(x)                (((x) & 0x3) << 5)
+#define   CLOCK_MASK           (0xf << 3)
+#define   AC0_BIT              0x200   // ac couple channels 0,1
+#define   AC1_BIT              0x400   // ac couple channels 2,3
+#define   APD_BIT              0x800   // analog power down
+#define   DPD_BIT              0x1000  // digital power down
+#define TRIGGER_REG            0x2     // trigger config register
+#define   DELAY_TRIGGER_BITS           0x3
+#define FIFO_START_REG         0x6     // software start aquistion trigger
+#define FIFO_RESET_REG         0x8     // clears fifo + fifo flags
+#define DMA_TC_CLEAR_REG               0xe     // clear dma terminal count interrupt
+#define STATUS_REG             0x12    // read only
+#define   FNE_BIT              0x1     // fifo not empty
+#define   OVFL_BIT             0x8     // fifo overflow
+#define   EDAQ_BIT             0x10    // end of aquisition interrupt
+#define   INTR_BIT             0x40    // interrupt has occured
+#define   DMA_TC_BIT           0x80    // dma terminal count interrupt has occured
+#define   ID_BITS(x)   (((x) >> 8) & 0x3)
+#define IRQ_DMA_CNTRL_REG              0x12    // write only
+#define   DMA_CHAN_BITS(x)             ((x) & 0x7)     // sets dma channel
+#define   DMA_EN_BIT           0x8     // enables dma
+#define   IRQ_LVL_BITS(x)              (((x) & 0xf) << 4)      // sets irq level
+#define   FIFO_INTR_EN_BIT             0x100   // enable fifo interrupts
+#define   FIFO_INTR_FHF_BIT            0x200   // interrupt fifo half full
+#define   DMA_INTR_EN_BIT              0x800   // enable interrupt on dma terminal count
+#define   DMA_DEM_EN_BIT       0x1000  // enables demand mode dma
+#define I8253_BASE_REG         0x14
+#define I8253_MODE_REG         0x17
+
+typedef struct a2150_board_struct{
+       char *name;
+       int clock[4];   // master clock periods, in nanoseconds
+       int num_clocks; // number of available master clock speeds
+       int ai_speed;   // maximum conversion rate in nanoseconds
+}a2150_board;
+
+//analog input range
+static comedi_lrange range_a2150 = {
+       1,
+       {
+               RANGE( -2.828, 2.828 ),
+       }
+};
+
+// enum must match board indices
+enum{a2150_c, a2150_s};
+a2150_board a2150_boards[] =
+{
+       {
+               name:   "at-a2150c",
+               clock:  {31250,  22676, 20833, 19531},
+               num_clocks:     4,
+               ai_speed:       19531,
+       },
+       {
+               name:   "at-a2150s",
+               clock:  {62500, 50000, 41667, 0},
+               num_clocks:     3,
+               ai_speed:       41667,
+       },
+};
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((a2150_board *)dev->board_ptr)
+
+typedef struct{
+       volatile unsigned int count;  /* number of data points left to be taken */
+       volatile int forever;  /* flag indicating whether we should take data forever */
+       unsigned int dma;       // dma channel
+       s16 *dma_buffer;        // dma buffer
+       unsigned int dma_transfer_size; // size in bytes of dma transfers
+       int irq_dma_bits;       // irq/dma register bits
+       int config_bits;        // config register bits
+}a2150_private;
+
+#define devpriv ((a2150_private *)dev->private)
+
+static int a2150_attach(comedi_device *dev,comedi_devconfig *it);
+static int a2150_detach(comedi_device *dev);
+static int a2150_cancel(comedi_device *dev, comedi_subdevice *s);
+
+comedi_driver driver_a2150={
+       driver_name:    "ni_at_a2150",
+       module:         THIS_MODULE,
+       attach:         a2150_attach,
+       detach:         a2150_detach,
+};
+
+static void a2150_interrupt(int irq, void *d, struct pt_regs *regs);
+static int a2150_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd);
+static int a2150_ai_cmd(comedi_device *dev, comedi_subdevice *s);
+static int a2150_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int a2150_get_timing(comedi_device *dev, unsigned int *period, int flags);
+static int a2150_probe(comedi_device *dev);
+static int a2150_set_chanlist(comedi_device *dev, unsigned int start_channel, unsigned int num_channels);
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_a2150);
+
+/* interrupt service routine */
+static void a2150_interrupt(int irq, void *d, struct pt_regs *regs)
+{
+       int i;
+       int status;
+       unsigned long flags;
+       comedi_device *dev = d;
+       comedi_subdevice *s = dev->read_subdev;
+       comedi_async *async;
+       comedi_cmd *cmd;
+       unsigned int max_points, num_points, residue, leftover;
+       sampl_t dpnt;
+       static const int sample_size = sizeof(devpriv->dma_buffer[0]);
+
+       if(dev->attached == 0)
+       {
+               comedi_error(dev, "premature interrupt");
+               return;
+       }
+       // initialize async here to make sure s is not NULL
+       async = s->async;
+       cmd = &async->cmd;
+
+       status = inw(dev->iobase + STATUS_REG);
+
+       if((status & INTR_BIT ) == 0)
+       {
+               comedi_error(dev, "spurious interrupt");
+               return;
+       }
+
+       if(status & OVFL_BIT)
+       {
+               comedi_error(dev, "fifo overflow");
+               a2150_cancel(dev, s);
+               async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+       }
+
+       if((status & DMA_TC_BIT) == 0)
+       {
+               comedi_error(dev, "caught non-dma interrupt?  Aborting.");
+               a2150_cancel(dev, s);
+               async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+               comedi_event(dev, s, async->events);
+               async->events = 0;
+               return;
+       }
+
+       flags = claim_dma_lock();
+       disable_dma(devpriv->dma);
+       /* clear flip-flop to make sure 2-byte registers for
+        * count and address get set correctly */
+       clear_dma_ff(devpriv->dma);
+
+       // figure out how many points to read
+       max_points = devpriv->dma_transfer_size  / sample_size;
+       /* residue is the number of points left to be done on the dma
+        * transfer.  It should always be zero at this point unless
+        * the stop_src is set to external triggering.
+        */
+       residue = get_dma_residue(devpriv->dma) / sample_size;
+       num_points = max_points - residue;
+       if(devpriv->count < num_points &&
+               cmd->stop_src == TRIG_COUNT)
+               num_points = devpriv->count;
+
+       // figure out how many points will be stored next time
+       leftover = 0;
+       if(cmd->stop_src == TRIG_NONE)
+       {
+               leftover = devpriv->dma_transfer_size / sample_size;
+       }else if(devpriv->count > max_points)
+       {
+               leftover = devpriv->count - max_points;
+               if(leftover > max_points)
+                       leftover = max_points;
+       }
+       /* there should only be a residue if collection was stopped by having
+        * the stop_src set to an external trigger, in which case there
+        * will be no more data
+        */
+       if(residue)
+               leftover = 0;
+
+       for(i = 0; i < num_points; i++)
+       {
+               /* write data point to comedi buffer */
+               dpnt = devpriv->dma_buffer[i];
+               // convert from 2's complement to unsigned coding
+               dpnt += 0x8000;
+               comedi_buf_put(async, dpnt);
+               if(devpriv->count > 0) devpriv->count--;
+       }
+       // re-enable  dma
+       set_dma_addr(devpriv->dma, (unsigned int) devpriv->dma_buffer);
+       set_dma_count(devpriv->dma, leftover * sample_size);
+       enable_dma(devpriv->dma);
+       release_dma_lock(flags);
+
+       async->events |= COMEDI_CB_BLOCK;
+
+       if(devpriv->count == 0)
+       {       /* end of acquisition */
+               a2150_cancel(dev, s);
+               async->events |= COMEDI_CB_EOA;
+       }
+
+       comedi_event(dev, s, async->events);
+       async->events = 0;
+
+       /* clear interrupt */
+       outb(0x00, dev->iobase + DMA_TC_CLEAR_REG);
+
+       return;
+}
+
+// probes board type, returns offset
+static int a2150_probe(comedi_device *dev)
+{
+       int status = inw(dev->iobase + STATUS_REG);
+       return ID_BITS(status);
+}
+
+static int a2150_attach(comedi_device *dev, comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       int iobase = it->options[0];
+       int irq = it->options[1];
+       int dma = it->options[2];
+
+       printk("comedi%d: %s: io 0x%x", dev->minor, driver_a2150.driver_name, iobase);
+       if(irq)
+       {
+               printk(", irq %i", irq);
+       }else
+       {
+               printk(", no irq");
+       }
+       if(dma)
+       {
+               printk(", dma %i", dma);
+       }else
+       {
+               printk(", no dma");
+       }
+       printk("\n");
+
+       /* allocate and initialize dev->private */
+       if(alloc_private(dev, sizeof(a2150_private)) < 0)
+               return -ENOMEM;
+
+       if(iobase == 0)
+       {
+               printk(" io base address required\n");
+               return -EINVAL;
+       }
+
+       /* check if io addresses are available */
+       if(check_region(iobase, A2150_SIZE) < 0)
+       {
+               printk(" I/O port conflict\n");
+               return -EIO;
+       }
+       request_region(iobase, A2150_SIZE, driver_a2150.driver_name);
+       dev->iobase = iobase;
+
+       /* grab our IRQ */
+       if(irq)
+       {
+               // check that irq is supported
+               if(irq < 3 || irq == 8 || irq == 13 || irq > 15)
+               {
+                       printk(" invalid irq line\n");
+                       return -EINVAL;
+               }
+               if(comedi_request_irq( irq, a2150_interrupt, 0, driver_a2150.driver_name, dev ))
+               {
+                       printk( "unable to allocate irq %d\n", irq);
+                       return -EINVAL;
+               }
+               devpriv->irq_dma_bits |= IRQ_LVL_BITS(irq);
+               dev->irq = irq;
+       }
+
+       // initialize dma
+       if(dma)
+       {
+               if(dma < 0 || dma == 4 || dma > 7)
+               {
+                       printk(" invalid dma channel %i\n", dma);
+                       return -EINVAL;
+               }
+               if(request_dma(dma, driver_a2150.driver_name))
+               {
+                       printk(" failed to allocate dma channel %i\n", dma);
+                       return -EINVAL;
+               }
+               devpriv->dma_buffer = kmalloc(A2150_DMA_BUFFER_SIZE, GFP_BUFFER | GFP_DMA);
+               if(devpriv->dma_buffer == NULL)
+                       return -ENOMEM;
+
+               disable_dma(dma);
+               set_dma_mode(dma, DMA_MODE_READ);
+
+               devpriv->dma = dma;
+               devpriv->irq_dma_bits |= DMA_DEM_EN_BIT | DMA_CHAN_BITS(dma);
+       }
+
+       dev->board_ptr = a2150_boards + a2150_probe(dev);
+       dev->board_name = thisboard->name;
+
+       dev->n_subdevices = 1;
+       if(alloc_subdevices(dev) < 0)
+               return -ENOMEM;
+
+       /* analog input subdevice */
+       s = dev->subdevices + 0;
+       dev->read_subdev = s;
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE;
+       s->n_chan = 4;
+       s->len_chanlist = 4;
+       s->maxdata = 0xffff;
+       s->range_table = &range_a2150;
+       s->do_cmd = a2150_ai_cmd;
+       s->do_cmdtest = a2150_ai_cmdtest;
+//     s->insn_read = a2150_ai_rinsn;  XXX
+       s->cancel = a2150_cancel;
+
+       // set card's irq and dma levels
+       outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+       // initialize configuration register
+       devpriv->config_bits = 0;
+       outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+
+       return 0;
+};
+
+static int a2150_detach(comedi_device *dev)
+{
+       printk("comedi%d: %s: remove\n", dev->minor, driver_a2150.driver_name);
+
+       // put board in power-down mode
+       outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG);
+
+       /* only free stuff if it has been allocated by _attach */
+       if(dev->iobase)
+               release_region(dev->iobase, A2150_SIZE);
+       if(dev->irq)
+               comedi_free_irq(dev->irq, dev);
+       if(devpriv)
+       {
+               if(devpriv->dma)
+                       free_dma(devpriv->dma);
+               if(devpriv->dma_buffer)
+                       kfree(devpriv->dma_buffer);
+       }
+
+       return 0;
+};
+
+static int a2150_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+       // disable dma on card
+       devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT;
+       outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+       // disable computer's dma
+       disable_dma(devpriv->dma);
+
+       // clear fifo and reset triggering circuitry
+       outw(0, dev->iobase + FIFO_RESET_REG);
+
+       return 0;
+}
+
+static int a2150_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,comedi_cmd *cmd)
+{
+       int err = 0;
+       int tmp;
+       int startChan;
+       int i;
+
+       /* step 1: make sure trigger sources are trivially valid */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW; // | TRIG_EXT;
+       if(!cmd->start_src || tmp != cmd->start_src) err++;
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_TIMER;
+       if(!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++;
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_NOW;
+       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 &&
+               cmd->start_src != TRIG_EXT) 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->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++;
+               }
+       }
+
+       // check channel/gain list against card's limitations
+       startChan = CR_CHAN(cmd->chanlist[0]);
+       for(i = 1; i < cmd->chanlist_len; i++)
+       {
+               if(CR_CHAN(cmd->chanlist[i]) != (startChan + i))
+               {
+                       comedi_error(dev, "entries in chanlist must be consecutive channels, counting upwards\n");
+                       err++;
+               }
+       }
+       if(cmd->chanlist_len == 2 && CR_CHAN(cmd->chanlist[0]) == 1)
+       {
+               comedi_error(dev, "length 2 chanlist must be channels 0,1 or channels 2,3");
+               err++;
+       }
+       if(cmd->chanlist_len == 3)
+       {
+               comedi_error(dev, "chanlist must have 1,2 or 4 channels");
+               err++;
+       }
+       if(CR_AREF(cmd->chanlist[0]) != CR_AREF(cmd->chanlist[1]) ||
+               CR_AREF(cmd->chanlist[2]) != CR_AREF(cmd->chanlist[3]))
+       {
+               comedi_error(dev, "channels 0/1 and 2/3 must have the same analog reference");
+               err++;
+       }
+
+       if(err)return 3;
+
+       /* step 4: fix up any arguments */
+
+       if(cmd->scan_begin_src == TRIG_TIMER)
+       {
+               tmp = cmd->scan_begin_arg;
+               a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags);
+               if(tmp != cmd->convert_arg) err++;
+       }
+
+       if(err)return 4;
+
+       return 0;
+}
+
+static int a2150_ai_cmd(comedi_device *dev, comedi_subdevice *s)
+{
+       comedi_async *async = s->async;
+       comedi_cmd *cmd = &async->cmd;
+       unsigned long lock_flags;
+
+       if(!dev->irq || !devpriv->dma)
+       {
+               comedi_error(dev, " irq and dma required, cannot do hardware conversions");
+               return -1;
+       }
+       if(cmd->flags & TRIG_RT)
+       {
+               comedi_error(dev, " dma incompatible with hard real-time interrupt (TRIG_RT), aborting");
+               return -1;
+       }
+
+       // clear fifo and reset triggering circuitry
+       outw(0, dev->iobase + FIFO_RESET_REG);
+
+       /* setup chanlist */
+       if(a2150_set_chanlist(dev, CR_CHAN(cmd->chanlist[0]), cmd->chanlist_len) < 0)
+               return -1;
+
+       // setup ac/dc coupling
+       if(CR_AREF(cmd->chanlist[0]) == AREF_DIFF)
+               devpriv->config_bits |= AC0_BIT;
+       else
+               devpriv->config_bits &= ~AC0_BIT;
+       if(CR_AREF(cmd->chanlist[2]) == AREF_DIFF)
+               devpriv->config_bits |= AC1_BIT;
+       else
+               devpriv->config_bits &= ~AC1_BIT;
+
+       // setup timing
+       a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags);
+
+       // send timing, channel, config bits
+       outw(devpriv->config_bits, dev->iobase + CONFIG_REG);
+
+       // initialize number of samples remaining
+       devpriv->count = cmd->stop_arg * cmd->chanlist_len;
+
+       // enable computer's dma
+       lock_flags = claim_dma_lock();
+       disable_dma(devpriv->dma);
+       /* clear flip-flop to make sure 2-byte registers for
+        * count and address get set correctly */
+       clear_dma_ff(devpriv->dma);
+       set_dma_addr(devpriv->dma, (unsigned int) devpriv->dma_buffer);
+       // set size of transfer to fill in 1/3 second
+       devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]) * cmd->chanlist_len *
+               (1000000000.0 / 3.0 ) / cmd->scan_begin_arg;
+       if(devpriv->dma_transfer_size > A2150_DMA_BUFFER_SIZE)
+               devpriv->dma_transfer_size = A2150_DMA_BUFFER_SIZE;
+       if(devpriv->dma_transfer_size < sizeof(devpriv->dma_buffer[0]))
+               devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]);
+       devpriv->dma_transfer_size -= devpriv->dma_transfer_size % sizeof(devpriv->dma_buffer[0]);
+       set_dma_count(devpriv->dma, devpriv->dma_transfer_size);
+       enable_dma(devpriv->dma);
+       release_dma_lock(lock_flags);
+
+       // enable dma on card
+       devpriv->irq_dma_bits |= DMA_INTR_EN_BIT | DMA_EN_BIT;
+       outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG);
+
+       // need to wait 72 sampling periods after changing timing
+       i8254_load(dev->iobase + I8253_BASE_REG, 2, 72, 0);
+       // set trigger source to delay trigger
+       outw(DELAY_TRIGGER_BITS, dev->iobase + TRIGGER_REG);
+
+       async->events = 0;
+
+       // start aquisition
+       outw(0, dev->iobase + FIFO_START_REG);
+
+       return 0;
+}
+
+static int a2150_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+// XXX
+       return 0;
+}
+
+/* sets bits in devpriv->clock_bits to nearest approximation of requested period,
+ * adjusts requested period to actual timing. */
+static int a2150_get_timing(comedi_device *dev, unsigned int *period, int flags)
+{
+       int lub, glb, temp;
+       int lub_divisor_shift, lub_index, glb_divisor_shift, glb_index;
+       int i, j;
+
+       // initialize greatest upper and least lower bounds
+       lub_divisor_shift = 3;
+       lub_index = 0;
+       lub = thisboard->clock[lub_index] * (1 << lub_divisor_shift);
+       glb_divisor_shift = 0;
+       glb_index = thisboard->num_clocks - 1;
+       glb = thisboard->clock[glb_index] * (1 << lub_divisor_shift);
+
+       // we can multiply period by 1, 2, 4, or 8, using (1 << i)
+       for(i = 0; i < 4; i = i++)
+       {
+               // there are a maximum of 4 master clocks
+               for(j = 0; j < 4; j++)
+               {
+                       if(thisboard->clock[j] == 0) break;
+                       // temp is the period in nanosec we are evaluating
+                       temp = thisboard->clock[j] * (1 << i);
+                       // if it is the best match yet
+                       if(temp < lub && temp >= *period)
+                       {
+                               lub_divisor_shift = i;
+                               lub_index = j;
+                               lub = temp;
+                       }
+                       if(temp > glb && temp <= *period)
+                       {
+                               glb_divisor_shift = i;
+                               glb_index = j;
+                               glb = temp;
+                       }
+               }
+       }
+       flags &= TRIG_ROUND_MASK;
+       switch (flags)
+       {
+               case TRIG_ROUND_NEAREST:
+               default:
+                       // if least upper bound is better approximation
+                       if(lub - *period < *period - glb)
+                       {
+                               *period = lub;
+                       }else
+                       {
+                               *period = glb;
+                       }
+                       break;
+               case TRIG_ROUND_UP:
+                               *period = lub;
+                       break;
+               case TRIG_ROUND_DOWN:
+                               *period = glb;
+                       break;
+       }
+
+       // set clock bits for config register appropriately
+       devpriv->config_bits &= ~CLOCK_MASK;
+       if(*period == lub)
+       {
+               devpriv->config_bits |= CLOCK_SELECT_BITS(lub_index) | CLOCK_DIVISOR_BITS(lub_divisor_shift);
+       }else
+       {
+               devpriv->config_bits |= CLOCK_SELECT_BITS(glb_index) | CLOCK_DIVISOR_BITS(glb_divisor_shift);
+       }
+
+       return 0;
+}
+
+static int a2150_set_chanlist(comedi_device *dev, unsigned int start_channel, unsigned int num_channels)
+{
+       if(start_channel + num_channels > 4)
+               return -1;
+
+       devpriv->config_bits &= ~CHANNEL_MASK;
+
+       switch(num_channels)
+       {
+               case 1:
+                       devpriv->config_bits |= CHANNEL_BITS(0x100 | start_channel);
+                       break;
+               case 2:
+                       if(start_channel == 0)
+                       {
+                               devpriv->config_bits |= CHANNEL_BITS(0x010);
+                       }else if(start_channel == 2)
+                       {
+                               devpriv->config_bits |= CHANNEL_BITS(0x011);
+                       }else
+                       {
+                               return -1;
+                       }
+                       break;
+               case 4:
+                       devpriv->config_bits |= CHANNEL_BITS(0x001);
+                       break;
+               default:
+                       return -1;
+                       break;
+       }
+
+       return 0;
+}
+