From: H Hartley Sweeten <hsweeten@visionengravers.com>
Date: Wed, 12 Jun 2013 23:22:53 +0000 (-0700)
Subject: staging: comedi: addi_apci_3xxx: fix the analog input subdevice
X-Git-Tag: MMI-PSA29.97-13-9~14152^2~108
X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=f32376e993a7657198974e713577152ac67833db;p=GitHub%2FMotorolaMobilityLLC%2Fkernel-slsi.git

staging: comedi: addi_apci_3xxx: fix the analog input subdevice

The analog input subdevice support functions in hwdrv_apci3xxx.c
do not follow the comedi API.

The (*insn_config) function overrides the INSN_CONFIG_DIO_INPUT
instruction as an internal APCI3XXX_CONFIGURATION instruction. The
APCI3XXX_CONFIGURATION instruction requires 4 data parameters but
the comedi core sanity checks the INSN_CONFIG_DIO_INPUT instruction
which only has 1 data parameter. Because of this the (*insn_config)
function can never be called.

The (*insn_read) function is supposed to do "one-shot" or "software-
triggered" reads and return the data. The function in hwdrv_apci3xxx.c
does do this but it also is used to optionally start a "hardware-
triggered" conversion. Hardware-triggered conversions should be done
with the comedi_async command functions.

Delete the hwrdv_apci3xxx.c file and fix the analog input subdevice
in the driver by:

1) add a new (*insn_read) function for "one-shot" reads
2) implement the (*do_cmdtest) and (*do_cmd) functions for
   "hardware-triggered" asyncronous reads
3) fix the interrupt handler to return the read data via the
   comedi_async buffer

Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com>
Reviewed-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---

diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c
deleted file mode 100644
index bea57235b287..000000000000
--- a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c
+++ /dev/null
@@ -1,178 +0,0 @@
-#define APCI3XXX_SINGLE				0
-#define APCI3XXX_DIFF				1
-#define APCI3XXX_CONFIGURATION			0
-
-/*
-+----------------------------------------------------------------------------+
-|                         ANALOG INPUT FUNCTIONS                             |
-+----------------------------------------------------------------------------+
-*/
-
-/*
-+----------------------------------------------------------------------------+
-| Function Name     : int   i_APCI3XXX_TestConversionStarted                 |
-|                          (struct comedi_device    *dev)                           |
-+----------------------------------------------------------------------------+
-| Task                Test if any conversion started                         |
-+----------------------------------------------------------------------------+
-| Input Parameters  : -                                                      |
-+----------------------------------------------------------------------------+
-| Output Parameters : -                                                      |
-+----------------------------------------------------------------------------+
-| Return Value      : 0 : Conversion not started                             |
-|                     1 : Conversion started                                 |
-+----------------------------------------------------------------------------+
-*/
-static int i_APCI3XXX_TestConversionStarted(struct comedi_device *dev)
-{
-	struct apci3xxx_private *devpriv = dev->private;
-
-	if ((readl(devpriv->mmio + 8) & 0x80000) == 0x80000)
-		return 1;
-	else
-		return 0;
-
-}
-
-static int apci3xxx_ai_configure(struct comedi_device *dev,
-				 struct comedi_subdevice *s,
-				 struct comedi_insn *insn,
-				 unsigned int *data)
-{
-	const struct apci3xxx_boardinfo *board = comedi_board(dev);
-	struct apci3xxx_private *devpriv = dev->private;
-	unsigned int time_base = data[2];
-	unsigned int reload_time = data[3];
-	unsigned int acq_ns;
-
-	if (time_base > 2)
-		return -EINVAL;
-
-	if (reload_time > 0xffff)
-		return -EINVAL;
-
-	time_base = 1 << time_base;
-	if (!(board->ai_conv_units & time_base))
-		return -EINVAL;
-
-	switch (time_base) {
-	case CONV_UNIT_NS:
-		acq_ns = reload_time;
-	case CONV_UNIT_US:
-		acq_ns = reload_time * 1000;
-	case CONV_UNIT_MS:
-		acq_ns = reload_time * 1000000;
-	default:
-		return -EINVAL;
-	}
-
-	/* Test the convert time value */
-	if (acq_ns < board->ai_min_acq_ns)
-		return -EINVAL;
-
-	/* Test if conversion not started */
-	if (i_APCI3XXX_TestConversionStarted(dev))
-		return -EBUSY;
-
-	devpriv->ui_EocEosConversionTime = reload_time;
-	devpriv->b_EocEosConversionTimeBase = time_base;
-
-	/* Set the convert timing unit */
-	writel(time_base, devpriv->mmio + 36);
-
-	/* Set the convert timing */
-	writel(reload_time, devpriv->mmio + 32);
-
-	return insn->n;
-}
-
-static int apci3xxx_ai_insn_config(struct comedi_device *dev,
-				   struct comedi_subdevice *s,
-				   struct comedi_insn *insn,
-				   unsigned int *data)
-{
-	switch (data[0]) {
-	case APCI3XXX_CONFIGURATION:
-		if (insn->n == 4)
-			return apci3xxx_ai_configure(dev, s, insn, data);
-		else
-			return -EINVAL;
-		break;
-	default:
-		return -EINVAL;
-	}
-}
-
-static int apci3xxx_ai_insn_read(struct comedi_device *dev,
-				 struct comedi_subdevice *s,
-				 struct comedi_insn *insn,
-				 unsigned int *data)
-{
-	struct apci3xxx_private *devpriv = dev->private;
-	unsigned int chan = CR_CHAN(insn->chanspec);
-	unsigned int range = CR_RANGE(insn->chanspec);
-	unsigned int aref = CR_AREF(insn->chanspec);
-	unsigned char use_interrupt = 0;	/* FIXME: use interrupts */
-	unsigned int delay_mode;
-	unsigned int val;
-	int i;
-
-	if (!use_interrupt && insn->n == 0)
-		return insn->n;
-
-	if (i_APCI3XXX_TestConversionStarted(dev))
-		return -EBUSY;
-
-	/* Clear the FIFO */
-	writel(0x10000, devpriv->mmio + 12);
-
-	/* Get and save the delay mode */
-	delay_mode = readl(devpriv->mmio + 4);
-	delay_mode &= 0xfffffef0;
-
-	/* Channel configuration selection */
-	writel(delay_mode, devpriv->mmio + 4);
-
-	/* Make the configuration */
-	val = (range & 3) | ((range >> 2) << 6) |
-	      ((aref == AREF_DIFF) << 7);
-	writel(val, devpriv->mmio + 0);
-
-	/* Channel selection */
-	writel(delay_mode | 0x100, devpriv->mmio + 4);
-	writel(chan, devpriv->mmio + 0);
-
-	/* Restore delay mode */
-	writel(delay_mode, devpriv->mmio + 4);
-
-	/* Set the number of sequence to 1 */
-	writel(1, devpriv->mmio + 48);
-
-	/* Save the interrupt flag */
-	devpriv->b_EocEosInterrupt = use_interrupt;
-
-	/* Save the number of channels */
-	devpriv->ui_AiNbrofChannels = 1;
-
-	/* Test if interrupt not used */
-	if (!use_interrupt) {
-		for (i = 0; i < insn->n; i++) {
-			/* Start the conversion */
-			writel(0x80000, devpriv->mmio + 8);
-
-			/* Wait the EOS */
-			do {
-				val = readl(devpriv->mmio + 20);
-				val &= 0x1;
-			} while (!val);
-
-			/* Read the analog value */
-			data[i] = readl(devpriv->mmio + 28);
-		}
-	} else {
-		/* Start the conversion */
-		writel(0x180000, devpriv->mmio + 8);
-	}
-
-	return insn->n;
-}
diff --git a/drivers/staging/comedi/drivers/addi_apci_3xxx.c b/drivers/staging/comedi/drivers/addi_apci_3xxx.c
index 77f0c455c19d..64471a1fba29 100644
--- a/drivers/staging/comedi/drivers/addi_apci_3xxx.c
+++ b/drivers/staging/comedi/drivers/addi_apci_3xxx.c
@@ -27,6 +27,8 @@
 
 #include "../comedidev.h"
 
+#include "comedi_fc.h"
+
 #define CONV_UNIT_NS		(1 << 0)
 #define CONV_UNIT_US		(1 << 1)
 #define CONV_UNIT_MS		(1 << 2)
@@ -351,21 +353,17 @@ static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
 
 struct apci3xxx_private {
 	void __iomem *mmio;
-	unsigned int ui_AiNbrofChannels;	/*  how many channels is measured */
-	unsigned int ui_AiReadData[32];
-	unsigned char b_EocEosInterrupt;
-	unsigned int ui_EocEosConversionTime;
-	unsigned char b_EocEosConversionTimeBase;
+	unsigned int ai_timer;
+	unsigned char ai_time_base;
 };
 
-#include "addi-data/hwdrv_apci3xxx.c"
-
 static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
 {
 	struct comedi_device *dev = d;
 	struct apci3xxx_private *devpriv = dev->private;
+	struct comedi_subdevice *s = dev->read_subdev;
 	unsigned int status;
-	int i;
+	unsigned int val;
 
 	/* Test if interrupt occur */
 	status = readl(devpriv->mmio + 16);
@@ -373,35 +371,247 @@ static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
 		/* Reset the interrupt */
 		writel(status, devpriv->mmio + 16);
 
-		/* Test if interrupt enabled */
-		if (devpriv->b_EocEosInterrupt == 1) {
-			/* Read all analog inputs value */
-			for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) {
-				unsigned int val;
+		val = readl(devpriv->mmio + 28);
+		comedi_buf_put(s->async, val);
+
+		s->async->events |= COMEDI_CB_EOA;
+		comedi_event(dev, s);
+
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+static int apci3xxx_ai_started(struct comedi_device *dev)
+{
+	struct apci3xxx_private *devpriv = dev->private;
+
+	if ((readl(devpriv->mmio + 8) & 0x80000) == 0x80000)
+		return 1;
+	else
+		return 0;
+
+}
+
+static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec)
+{
+	struct apci3xxx_private *devpriv = dev->private;
+	unsigned int chan = CR_CHAN(chanspec);
+	unsigned int range = CR_RANGE(chanspec);
+	unsigned int aref = CR_AREF(chanspec);
+	unsigned int delay_mode;
+	unsigned int val;
+
+	if (apci3xxx_ai_started(dev))
+		return -EBUSY;
+
+	/* Clear the FIFO */
+	writel(0x10000, devpriv->mmio + 12);
+
+	/* Get and save the delay mode */
+	delay_mode = readl(devpriv->mmio + 4);
+	delay_mode &= 0xfffffef0;
+
+	/* Channel configuration selection */
+	writel(delay_mode, devpriv->mmio + 4);
+
+	/* Make the configuration */
+	val = (range & 3) | ((range >> 2) << 6) |
+	      ((aref == AREF_DIFF) << 7);
+	writel(val, devpriv->mmio + 0);
+
+	/* Channel selection */
+	writel(delay_mode | 0x100, devpriv->mmio + 4);
+	writel(chan, devpriv->mmio + 0);
+
+	/* Restore delay mode */
+	writel(delay_mode, devpriv->mmio + 4);
+
+	/* Set the number of sequence to 1 */
+	writel(1, devpriv->mmio + 48);
+
+	return 0;
+}
+
+static int apci3xxx_ai_insn_read(struct comedi_device *dev,
+				 struct comedi_subdevice *s,
+				 struct comedi_insn *insn,
+				 unsigned int *data)
+{
+	struct apci3xxx_private *devpriv = dev->private;
+	unsigned int val;
+	int ret;
+	int i;
+
+	ret = apci3xxx_ai_setup(dev, insn->chanspec);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < insn->n; i++) {
+		/* Start the conversion */
+		writel(0x80000, devpriv->mmio + 8);
+
+		/* Wait the EOS */
+		do {
+			val = readl(devpriv->mmio + 20);
+			val &= 0x1;
+		} while (!val);
+
+		/* Read the analog value */
+		data[i] = readl(devpriv->mmio + 28);
+	}
 
-				val = readl(devpriv->mmio + 28);
-				devpriv->ui_AiReadData[i] = val;
-			}
+	return insn->n;
+}
+
+static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev,
+				   unsigned int *ns, int round_mode)
+{
+	const struct apci3xxx_boardinfo *board = comedi_board(dev);
+	struct apci3xxx_private *devpriv = dev->private;
+	unsigned int base;
+	unsigned int timer;
+	int time_base;
+
+	/* time_base: 0 = ns, 1 = us, 2 = ms */
+	for (time_base = 0; time_base < 3; time_base++) {
+		/* skip unsupported time bases */
+		if (!(board->ai_conv_units & (1 << time_base)))
+			continue;
+
+		switch (time_base) {
+		case 0:
+			base = 1;
+			break;
+		case 1:
+			base = 1000;
+			break;
+		case 2:
+			base = 1000000;
+			break;
+		}
 
-			/* Set the interrupt flag */
-			devpriv->b_EocEosInterrupt = 2;
+		switch (round_mode) {
+		case TRIG_ROUND_NEAREST:
+		default:
+			timer = (*ns + base / 2) / base;
+			break;
+		case TRIG_ROUND_DOWN:
+			timer = *ns / base;
+			break;
+		case TRIG_ROUND_UP:
+			timer = (*ns + base - 1) / base;
+			break;
+		}
 
-			/* FIXME: comedi_event() */
+		if (timer < 0x10000) {
+			devpriv->ai_time_base = time_base;
+			devpriv->ai_timer = timer;
+			*ns = timer * time_base;
+			return 0;
 		}
 	}
-	return IRQ_RETVAL(1);
+	return -EINVAL;
 }
 
 static int apci3xxx_ai_cmdtest(struct comedi_device *dev,
 			       struct comedi_subdevice *s,
 			       struct comedi_cmd *cmd)
 {
+	const struct apci3xxx_boardinfo *board = comedi_board(dev);
+	int err = 0;
+	unsigned int tmp;
+
+	/* 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);
+	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->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_arg, 0);
+	err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+					 board->ai_min_acq_ns);
+	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 */
+
+	/*
+	 * FIXME: The hardware supports multiple scan modes but the original
+	 * addi-data driver only supported reading a single channel with
+	 * interrupts. Need a proper datasheet to fix this.
+	 *
+	 * The following scan modes are supported by the hardware:
+	 * 1) Single software scan
+	 * 2) Single hardware triggered scan
+	 * 3) Continuous software scan
+	 * 4) Continuous software scan with timer delay
+	 * 5) Continuous hardware triggered scan
+	 * 6) Continuous hardware triggered scan with timer delay
+	 *
+	 * For now, limit the chanlist to a single channel.
+	 */
+	if (cmd->chanlist_len > 1) {
+		cmd->chanlist_len = 1;
+		err |= -EINVAL;
+	}
+
+	tmp = cmd->convert_arg;
+	err |= apci3xxx_ai_ns_to_timer(dev, &cmd->convert_arg,
+				       cmd->flags & TRIG_ROUND_MASK);
+	if (tmp != cmd->convert_arg)
+		err |= -EINVAL;
+
+	if (err)
+		return 4;
+
 	return 0;
 }
 
 static int apci3xxx_ai_cmd(struct comedi_device *dev,
 			   struct comedi_subdevice *s)
 {
+	struct apci3xxx_private *devpriv = dev->private;
+	struct comedi_cmd *cmd = &s->async->cmd;
+	int ret;
+
+	ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]);
+	if (ret)
+		return ret;
+
+	/* Set the convert timing unit */
+	writel(devpriv->ai_time_base, devpriv->mmio + 36);
+
+	/* Set the convert timing */
+	writel(devpriv->ai_timer, devpriv->mmio + 32);
+
+	/* Start the conversion */
+	writel(0x180000, devpriv->mmio + 8);
+
 	return 0;
 }
 
@@ -553,9 +763,6 @@ static int apci3xxx_reset(struct comedi_device *dev)
 	/* Disable the interrupt */
 	disable_irq(dev->irq);
 
-	/* Reset the interrupt flag */
-	devpriv->b_EocEosInterrupt = 0;
-
 	/* Clear the start command */
 	writel(0, devpriv->mmio + 8);
 
@@ -625,7 +832,6 @@ static int apci3xxx_auto_attach(struct comedi_device *dev,
 		s->maxdata	= board->ai_maxdata;
 		s->len_chanlist	= s->n_chan;
 		s->range_table	= &apci3xxx_ai_range;
-		s->insn_config	= apci3xxx_ai_insn_config;
 		s->insn_read	= apci3xxx_ai_insn_read;
 		if (dev->irq) {
 			dev->read_subdev = s;