[PATCH 13/15] staging: comedi: ni_daq_700: add ai async command support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add support for async commands with the Analog Input subdevice.

Signed-off-by: H Hartley Sweeten <hsweeten@xxxxxxxxxxxxxxxxxxx>
Cc: Ian Abbott <abbotti@xxxxxxxxx>
Cc: Greg Kroah-Hartman <gregk@xxxxxxxxxxxxxxxxxxx>
---
 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
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel




[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux