Add support for async commands with the Analog Input subdevice.
Signed-off-by: H Hartley Sweeten <[email protected]>
Cc: Ian Abbott <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
---
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
[email protected]
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel