Add support for async commands with the Analog Input subdevice.

Signed-off-by: H Hartley Sweeten <hswee...@visionengravers.com>
Cc: Ian Abbott <abbo...@mev.co.uk>
Cc: Greg Kroah-Hartman <gr...@linuxfoundation.org>
---
 drivers/staging/comedi/Kconfig              |   1 +
 drivers/staging/comedi/drivers/ni_daq_700.c | 401 ++++++++++++++++++++++++++--
 2 files changed, 385 insertions(+), 17 deletions(-)

diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
index 341fc07..5523d14 100644
--- a/drivers/staging/comedi/Kconfig
+++ b/drivers/staging/comedi/Kconfig
@@ -1144,6 +1144,7 @@ config COMEDI_DAS08_CS
 
 config COMEDI_NI_DAQ_700_CS
        tristate "NI DAQCard-700 PCMCIA support"
+       select COMEDI_FC
        ---help---
          Enable support for the National Instruments PCMCIA DAQCard-700 DIO
 
diff --git a/drivers/staging/comedi/drivers/ni_daq_700.c 
b/drivers/staging/comedi/drivers/ni_daq_700.c
index 2a474b0..e1e7ba7 100644
--- a/drivers/staging/comedi/drivers/ni_daq_700.c
+++ b/drivers/staging/comedi/drivers/ni_daq_700.c
@@ -56,6 +56,7 @@
 
 #include "../comedidev.h"
 
+#include "comedi_fc.h"
 #include "8253.h"
 
 /*
@@ -95,6 +96,8 @@
 #define DAQ700_CMD2_DISABDAQ           (1 << 1)
 #define DAQ700_TIMER_BASE              0x08
 
+#define DAQ700_FIFO_SIZE               512     /* samples */
+
 static const struct comedi_lrange range_daq700_ai = {
        3,
        {
@@ -104,6 +107,14 @@ static const struct comedi_lrange range_daq700_ai = {
        }
 };
 
+struct daq700_private {
+       unsigned int divisor;
+       unsigned int scans_done;
+       unsigned int samples_left;
+       unsigned char cmd1;
+       unsigned char cmd3;
+};
+
 static int daq700_dio_insn_bits(struct comedi_device *dev,
                                struct comedi_subdevice *s,
                                struct comedi_insn *insn,
@@ -151,8 +162,10 @@ static int daq700_dio_insn_config(struct comedi_device 
*dev,
 }
 
 static void daq700_ai_set_chanspec(struct comedi_device *dev,
-                                  unsigned int chanspec)
+                                  unsigned int chanspec,
+                                  bool scan)
 {
+       struct daq700_private *devpriv = dev->private;
        unsigned int chan = CR_CHAN(chanspec);
        unsigned int range = CR_RANGE(chanspec);
        unsigned int aref = CR_AREF(chanspec);
@@ -164,13 +177,25 @@ static void daq700_ai_set_chanspec(struct comedi_device 
*dev,
        val = DAQ700_CMD3_ARNG(range);
        if (aref == AREF_DIFF)
                val |= DAQ700_CMD3_DIFF;
-       outb(val, dev->iobase + DAQ700_CMD3_REG);
+       if (val != devpriv->cmd3) {
+               outb(val, dev->iobase + DAQ700_CMD3_REG);
+               devpriv->cmd3 = val;
+       }
 
        /* set multiplexer for single-channel scan */
-       outb(DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(chan),
-            dev->iobase + DAQ700_CMD1_REG);
-       /* mux needs 2us to really settle [Fred Brooks]. */
-       udelay(2);
+       val = DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(chan);
+       if (val != devpriv->cmd1) {
+               outb(val, dev->iobase + DAQ700_CMD1_REG);
+               devpriv->cmd1 = val;
+               /* mux needs 2us to really settle [Fred Brooks] */
+               udelay(2);
+       }
+
+       /* enable multi-channel scanning */
+       if (scan) {
+               devpriv->cmd1 &= ~DAQ700_CMD1_SCANDISAB;
+               outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG);
+       }
 }
 
 static void daq700_ai_start_conv(struct comedi_device *dev)
@@ -185,6 +210,15 @@ static void daq700_ai_start_conv(struct comedi_device *dev)
                       0, I8254_MODE2 | I8254_BINARY);  /* OUT0 high */
 }
 
+static void daq700_ai_stop_conv(struct comedi_device *dev)
+{
+       /*
+        * Stop A/D conversions by forcing OUT0 high.
+        */
+       i8254_set_mode(dev->iobase + DAQ700_TIMER_BASE, 0,
+                      0, I8254_MODE2 | I8254_BINARY);  /* OUT0 high */
+}
+
 static void daq700_ai_flush_fifo(struct comedi_device *dev)
 {
        outb(DAQ700_AI_CLR_FIFO, dev->iobase + DAQ700_AI_CLR_REG);
@@ -222,7 +256,7 @@ static int daq700_ai_insn_read(struct comedi_device *dev,
        int ret;
        int i;
 
-       daq700_ai_set_chanspec(dev, insn->chanspec);
+       daq700_ai_set_chanspec(dev, insn->chanspec, false);
 
        for (i = 0; i < insn->n; i++) {
                daq700_ai_start_conv(dev);
@@ -241,6 +275,320 @@ static int daq700_ai_insn_read(struct comedi_device *dev,
        return insn->n;
 }
 
+static bool daq700_interrupts_enabled(struct comedi_device *dev)
+{
+       struct daq700_private *devpriv = dev->private;
+
+       if (devpriv->cmd1 & (DAQ700_CMD1_CNTINTEN |
+                            DAQ700_CMD1_EXTINTEN |
+                            DAQ700_CMD1_FIFOINTEN))
+               return true;
+       else
+               return false;
+}
+
+static void daq700_ai_read_fifo(struct comedi_device *dev,
+                               struct comedi_subdevice *s)
+{
+       struct daq700_private *devpriv = dev->private;
+       struct comedi_async *async = s->async;
+       struct comedi_cmd *cmd = &async->cmd;
+       unsigned int val;
+       unsigned char status;
+
+       do {
+               status = inb(dev->iobase + DAQ700_STATUS1_REG);
+
+               if (status & DAQ700_STATUS1_DATAERR) {
+                       async->events |= COMEDI_CB_ERROR;
+                       break;
+               }
+
+               if (status & DAQ700_STATUS1_DAVAIL) {
+                       val = inw(dev->iobase + DAQ700_AI_FIFO_REG);
+                       cfc_write_to_buffer(s, comedi_offset_munge(s, val));
+
+                       if (cmd->stop_src == TRIG_COUNT) {
+                               devpriv->samples_left--;
+                               if (devpriv->samples_left == 0)
+                                       break;
+                       }
+               }
+       } while (status & DAQ700_STATUS1_DAVAIL);
+}
+
+static irqreturn_t daq700_interrupt(int irq, void *d)
+{
+       struct comedi_device *dev = d;
+       struct daq700_private *devpriv = dev->private;
+       struct comedi_subdevice *s = dev->read_subdev;
+       struct comedi_async *async = s->async;
+       struct comedi_cmd *cmd = &async->cmd;
+       unsigned char status;
+
+       if (!dev->attached || !daq700_interrupts_enabled(dev))
+               return IRQ_NONE;
+
+       status = inb(dev->iobase + DAQ700_STATUS1_REG);
+       if (status & DAQ700_STATUS1_CNTINT) {
+               /*
+                * Interrupt from counter 2 output
+                */
+               outb(DAQ700_TIC_CLR_INT, dev->iobase + DAQ700_TIC_REG);
+       }
+       if ((status & DAQ700_STATUS1_EXTINT) == 0) {
+               /*
+                * Interrupt caused by the EXTINT* signal on the
+                * I/O connector. Not sure how to handle this...
+                */
+               dev_dbg(dev->class_dev, "EXTINT detected\n");
+       }
+       if (devpriv->cmd1 & DAQ700_CMD1_FIFOINTEN) {
+               daq700_ai_read_fifo(dev, s);
+
+               if (async->events & COMEDI_CB_EOS)
+                       devpriv->scans_done++;
+
+               if (cmd->stop_src == TRIG_COUNT) {
+                       if (devpriv->scans_done >= cmd->stop_arg)
+                               async->events |= COMEDI_CB_EOA;
+
+                       if (devpriv->samples_left < (DAQ700_FIFO_SIZE / 2)) {
+                               if (devpriv->cmd3 & DAQ700_CMD3_FIFOHFINT) {
+                                       devpriv->cmd3 &= ~DAQ700_CMD3_FIFOHFINT;
+                                       outb(devpriv->cmd3,
+                                            dev->iobase + DAQ700_CMD3_REG);
+                               }
+                       }
+               }
+       }
+
+       cfc_handle_events(dev, s);
+
+       return IRQ_HANDLED;
+}
+
+static int daq700_ai_cancel(struct comedi_device *dev,
+                           struct comedi_subdevice *s)
+{
+       struct daq700_private *devpriv = dev->private;
+
+       outb(DAQ700_CMD2_DISABDAQ, dev->iobase + DAQ700_CMD2_REG);
+
+       devpriv->cmd1 &= ~DAQ700_CMD1_FIFOINTEN;
+       outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG);
+       devpriv->cmd3 &= ~DAQ700_CMD3_FIFOHFINT;
+       outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG);
+
+       daq700_ai_stop_conv(dev);
+       daq700_ai_flush_fifo(dev);
+
+       outb(DAQ700_CMD2_ENADAQ, dev->iobase + DAQ700_CMD2_REG);
+
+       return 0;
+}
+
+static int daq700_ai_cmd(struct comedi_device *dev,
+                        struct comedi_subdevice *s)
+{
+       struct daq700_private *devpriv = dev->private;
+       struct comedi_cmd *cmd = &s->async->cmd;
+       bool scan = false;
+
+       devpriv->scans_done = 0;
+       if (cmd->stop_src == TRIG_COUNT)
+               devpriv->samples_left = cmd->scan_end_arg * cmd->stop_arg;
+       else    /* TRIG_NONE */
+               devpriv->samples_left = DAQ700_FIFO_SIZE;
+
+       if (cmd->chanlist_len > 1) {
+               unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+               unsigned int chan1 = CR_CHAN(cmd->chanlist[1]);
+
+               if (chan0 != chan1)
+                       scan = true;
+       }
+       daq700_ai_set_chanspec(dev, cmd->chanlist[0], scan);
+
+       daq700_ai_flush_fifo(dev);
+
+       if (cmd->convert_src == TRIG_TIMER)
+               i8254_load(dev->iobase + DAQ700_TIMER_BASE, 0,
+                          0, devpriv->divisor, I8254_MODE2 | I8254_BINARY);
+
+       /*
+        * If TRIG_WAKE_EOS is not set we use the FIFO half-full interrupt
+        * to read as many samples as possible on each interrupt.
+        */
+       if ((cmd->flags & TRIG_WAKE_EOS) == 0) {
+               if (devpriv->samples_left > (DAQ700_FIFO_SIZE / 2)) {
+                       devpriv->cmd3 |= DAQ700_CMD3_FIFOHFINT;
+                       outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG);
+               }
+       }
+
+       /* enable interrupts */
+       devpriv->cmd1 |= DAQ700_CMD1_FIFOINTEN;
+       outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG);
+
+       return 0;
+}
+
+static void daq700_ns_to_timer(unsigned int osc_base,
+                              unsigned int *divisor,
+                              unsigned int *ns,
+                              unsigned int flags)
+{
+       unsigned int div;
+
+       switch (flags & TRIG_ROUND_MASK) {
+       case TRIG_ROUND_NEAREST:
+       default:
+               div = (*ns + osc_base / 2) / osc_base;
+               break;
+       case TRIG_ROUND_UP:
+               div = *ns / osc_base;
+               break;
+       case TRIG_ROUND_DOWN:
+               div = (*ns + osc_base - 1) / osc_base;
+               break;
+       }
+
+       if (div > 0xffff)
+               div = 0xffff;
+       *divisor = div;
+       *ns = div * osc_base;
+}
+
+static int daq700_check_chanlist(struct comedi_device *dev,
+                                struct comedi_subdevice *s,
+                                struct comedi_cmd *cmd)
+{
+       unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+       unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+       unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+       int i;
+
+       /*
+        * All channels in the scan list must have the same range and
+        * aref. The channels must also be sequential and count down
+        * to 0.
+        */
+       for (i = 1; i < cmd->chanlist_len; i++) {
+               unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+               unsigned int range = CR_RANGE(cmd->chanlist[i]);
+               unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+               if (chan != (chan0 - i)) {
+                       dev_dbg(dev->class_dev,
+                               "chanlist must use consecutive channels (down 
to 0)\n");
+                       return -EINVAL;
+               }
+               if (range != range0) {
+                       dev_dbg(dev->class_dev,
+                               "chanlist must use the same range\n");
+                       return -EINVAL;
+               }
+               if (aref != aref0) {
+                       dev_dbg(dev->class_dev,
+                               "chanlist must use the same aref\n");
+                       return -EINVAL;
+               }
+       }
+       if (cmd->chanlist_len > 1) {
+               unsigned int last_chan;
+
+               last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
+               if (last_chan != 0) {
+                       dev_dbg(dev->class_dev,
+                               "last channel in chanlist must be 0\n");
+                       return -EINVAL;
+               }
+       }
+       return 0;
+}
+
+static int daq700_ai_cmdtest(struct comedi_device *dev,
+                            struct comedi_subdevice *s,
+                            struct comedi_cmd *cmd)
+{
+       struct daq700_private *devpriv = dev->private;
+       int err = 0;
+       unsigned int arg;
+
+       /* Step 1 : check if triggers are trivially valid */
+
+       err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+       err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+       err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+       err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+       err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+       if (err)
+               return 1;
+
+       /* Step 2a : make sure trigger sources are unique */
+
+       err |= cfc_check_trigger_is_unique(cmd->convert_src);
+       err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+       /* Step 2b : and mutually compatible */
+
+       if (err)
+               return 2;
+
+       /* Step 3: check if arguments are trivially valid */
+
+       err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+       err |= cfc_check_trigger_arg_is(&cmd->scan_begin_src, 0);
+
+       if (cmd->convert_src == TRIG_TIMER) {
+               /* sample interval can be between 10 and 65535 ns */
+               err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+                                                10 * 1000);
+               err |= cfc_check_trigger_arg_max(&cmd->convert_arg,
+                                                65535 * 1000);
+       } else {        /* TRIG_EXT */
+               /*
+                * A low-to-high transition of EXTCONV starts an
+                * A/D conversion.
+                */
+               err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+       }
+
+       err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+       if (cmd->stop_src == TRIG_COUNT)
+               err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+       else    /* TRIG_NONE */
+               err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+       if (err)
+               return 3;
+
+       /* Step 4: fix up any arguments */
+
+       if (cmd->convert_src == TRIG_TIMER) {
+               arg = cmd->convert_arg;
+               daq700_ns_to_timer(I8254_OSC_BASE_1MHZ,
+                                  &devpriv->divisor, &arg, cmd->flags);
+               err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+       }
+
+       if (err)
+               return 4;
+
+       /* Step 5: check channel list if it exists */
+
+       if (cmd->chanlist && cmd->chanlist_len > 0)
+               err |= daq700_check_chanlist(dev, s, cmd);
+
+       if (err)
+               return 5;
+
+       return 0;
+}
+
 /*
  * Data acquisition is enabled.
  * The counter 0 output is high.
@@ -254,15 +602,16 @@ static int daq700_ai_insn_read(struct comedi_device *dev,
  */
 static void daq700_initialize(struct comedi_device *dev)
 {
-       unsigned long iobase = dev->iobase;
+       struct daq700_private *devpriv = dev->private;
 
-       outb(DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(0),
-            iobase + DAQ700_CMD1_REG);
-       outb(DAQ700_CMD2_ENADAQ, iobase + DAQ700_CMD2_REG);
-       outb(DAQ700_CMD3_ARNG(0), iobase + DAQ700_CMD3_REG);
-       i8254_set_mode(iobase + DAQ700_TIMER_BASE, 0,
-                      0, I8254_MODE2 | I8254_BINARY);  /* OUT0 high */
-       outb(DAQ700_TIC_CLR_INT, iobase + DAQ700_TIC_REG);
+       devpriv->cmd1 = DAQ700_CMD1_SCANDISAB | DAQ700_CMD1_MA(0);
+       devpriv->cmd3 = DAQ700_CMD3_ARNG(0);
+
+       outb(devpriv->cmd1, dev->iobase + DAQ700_CMD1_REG);
+       outb(DAQ700_CMD2_ENADAQ, dev->iobase + DAQ700_CMD2_REG);
+       outb(devpriv->cmd3, dev->iobase + DAQ700_CMD3_REG);
+       daq700_ai_stop_conv(dev);
+       outb(DAQ700_TIC_CLR_INT, dev->iobase + DAQ700_TIC_REG);
        daq700_ai_flush_fifo(dev);
 }
 
@@ -270,15 +619,27 @@ static int daq700_auto_attach(struct comedi_device *dev,
                              unsigned long context)
 {
        struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+       struct daq700_private *devpriv;
        struct comedi_subdevice *s;
        int ret;
 
+       devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+       if (!devpriv)
+               return -ENOMEM;
+
        link->config_flags |= CONF_AUTO_SET_IO;
        ret = comedi_pcmcia_enable(dev, NULL);
        if (ret)
                return ret;
        dev->iobase = link->resource[0]->start;
 
+       daq700_initialize(dev);
+
+       link->priv = dev;
+       ret = pcmcia_request_irq(link, daq700_interrupt);
+       if (ret == 0)
+               dev->irq = link->irq;
+
        ret = comedi_alloc_subdevices(dev, 2);
        if (ret)
                return ret;
@@ -302,8 +663,14 @@ static int daq700_auto_attach(struct comedi_device *dev,
        s->maxdata      = 0x0fff;
        s->range_table  = &range_daq700_ai;
        s->insn_read    = daq700_ai_insn_read;
-
-       daq700_initialize(dev);
+       if (dev->irq) {
+               dev->read_subdev = s;
+               s->subdev_flags |= SDF_CMD_READ;
+               s->len_chanlist = s->n_chan;
+               s->do_cmdtest   = daq700_ai_cmdtest;
+               s->do_cmd       = daq700_ai_cmd;
+               s->cancel       = daq700_ai_cancel;
+       }
 
        return 0;
 }
-- 
1.9.3

_______________________________________________
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

Reply via email to