Re: [PATCH v2 13/14] staging: comedi: multiq3: add 8254 counter/timer subdevice support

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

 



On 05/10/15 23:33, H Hartley Sweeten wrote:
The board has an 8254 timer/counter. Add support for this subdevice.

Signed-off-by: H Hartley Sweeten <hsweeten@xxxxxxxxxxxxxxxxxxx>
Cc: Ian Abbott <abbotti@xxxxxxxxx>
Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
  drivers/staging/comedi/drivers/multiq3.c | 147 ++++++++++++++++++++++++++++++-
  1 file changed, 146 insertions(+), 1 deletion(-)

diff --git a/drivers/staging/comedi/drivers/multiq3.c b/drivers/staging/comedi/drivers/multiq3.c
index fc743df..50efc7c 100644
--- a/drivers/staging/comedi/drivers/multiq3.c
+++ b/drivers/staging/comedi/drivers/multiq3.c
@@ -38,6 +38,8 @@

  #include "../comedidev.h"

+#include "comedi_8254.h"	/* just for the register map */
+
  /*
   * Register map
   */
@@ -47,6 +49,8 @@
  #define MULTIQ3_AI_REG			0x04
  #define MULTIQ3_AI_CONV_REG		0x04
  #define MULTIQ3_STATUS_REG		0x06
+#define MULTIQ3_STATUS_CT(x)		(((x) == 0) ? BIT(0) :	\
+					 ((x) == 1) ? BIT(2) : BIT(1))

Which manual are you looking at, Hartley? The first copy I found online was this one:

http://engineering.nyu.edu/mechatronics/Control_Lab/mq3_manual.pdf

I suspect the strange ordering of the CTx bits in the diagram in section 3.2.4.2 is probably a typo. Underneath the diagram it says, "Bits 0-2 (CT0,CT1,CT2): Counter timeout states for the three timers." There's a discrepancy between that and the diagram, so I think I'd stick with the one that is less stupid!

These CTx status bits are more than likely connected to the OUT0, OUT1 and OUT2 pins of the 82C54 and will behave according to the mode of the respective counter channels.

  #define MULTIQ3_STATUS_EOC		BIT(3)
  #define MULTIQ3_STATUS_EOC_I		BIT(4)
  #define MULTIQ3_CTRL_REG		0x06
@@ -257,6 +261,126 @@ static int multiq3_encoder_insn_config(struct comedi_device *dev,
  	return insn->n;
  }

+static unsigned int multiq3_i8254_read(struct comedi_device *dev,
+				       unsigned int reg)
+{
+	/* select the 8254 register then read the value */
+	multiq3_set_ctrl(dev, MULTIQ3_CTRL_RC(reg));
+	return inb(dev->iobase + MULTIQ3_CLK_REG);
+}

According to the manual I linked to, sections 3.2, 3.2.5.1 and 3.2.5.2 indicate that CLK_DATA (your MULTIQ3_CLK_REG) is write-only. This means the INSN_CONFIG_8254_READ_STATUS config instruction and the insn_read handler won't work, although INSN_CONFIG_8254_READ_STATUS could be partially emulated (see below).

+
+static void multiq3_i8254_write(struct comedi_device *dev,
+				unsigned int val, unsigned int reg)
+{
+	/* select the 8254 register then write the value */
+	multiq3_set_ctrl(dev, MULTIQ3_CTRL_RC(reg));
+	outb(val, dev->iobase + MULTIQ3_CLK_REG);
+}
+
+static int multiq3_8254_set_mode(struct comedi_device *dev,
+				 unsigned int chan, unsigned int mode)
+{
+	unsigned int byte;
+
+	if (mode > (I8254_MODE5 | I8254_BCD))
+		return -EINVAL;
+
+	byte = I8254_CTRL_SEL_CTR(chan) |	/* select counter */
+	       I8254_CTRL_LSB_MSB |		/* load LSB then MSB */
+	       mode;				/* mode and BCD|binary */
+	multiq3_i8254_write(dev, byte, I8254_CTRL_REG);
+
+	return 0;
+}
+
+static int multiq3_8254_insn_read(struct comedi_device *dev,
+				  struct comedi_subdevice *s,
+				  struct comedi_insn *insn,
+				  unsigned int *data)
+{
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	unsigned int ctrl = I8254_CTRL_LATCH | I8254_CTRL_SEL_CTR(chan);
+	unsigned int val;
+	int i;
+
+	for (i = 0; i < insn->n; i++) {
+		/* latch counter */
+		multiq3_i8254_write(dev, ctrl, I8254_CTRL_REG);
+
+		/* read LSB then MSB */
+		val = multiq3_i8254_read(dev, chan);
+		val |= (multiq3_i8254_read(dev, chan) << 8);
+
+		data[i] = val;
+	}
+
+	return insn->n;
+}

As mentioned above, the insn_read handler this won't work, unfortunately!

+
+static int multiq3_8254_insn_write(struct comedi_device *dev,
+				   struct comedi_subdevice *s,
+				   struct comedi_insn *insn,
+				   unsigned int *data)
+{
+	unsigned int chan = CR_CHAN(insn->chanspec);
+
+	if (insn->n) {
+		unsigned int val = data[insn->n - 1];
+
+		/* load LSB then MSB */
+		multiq3_i8254_write(dev, val & 0xff, chan);
+		multiq3_i8254_write(dev, (val >> 8) & 0xff, chan);
+	}
+
+	return insn->n;
+}
+
+static int multiq3_8254_insn_config(struct comedi_device *dev,
+				    struct comedi_subdevice *s,
+				    struct comedi_insn *insn,
+				    unsigned int *data)
+{
+	unsigned int chan = CR_CHAN(insn->chanspec);
+	unsigned int status;
+	int ret;
+
+	switch (data[0]) {
+	case INSN_CONFIG_RESET:
+		ret = multiq3_8254_set_mode(dev, chan,
+					    I8254_MODE0 | I8254_BINARY);
+		if (ret)
+			return ret;
+		break;
+	case INSN_CONFIG_SET_COUNTER_MODE:
+		ret = multiq3_8254_set_mode(dev, chan, data[1]);
+		if (ret)
+			return ret;
+		break;
+	case INSN_CONFIG_GET_COUNTER_STATUS:
+		data[1] = 0;
+		status = inw(dev->iobase + MULTIQ3_STATUS_REG);
+		if (status & MULTIQ3_STATUS_CT(chan))
+			data[1] |= COMEDI_COUNTER_TERMINAL_COUNT;
+		data[2] = COMEDI_COUNTER_TERMINAL_COUNT;
+		break;

I think this would only indicate a terminal count if the counter channel is in mode 0.

+	case INSN_CONFIG_8254_READ_STATUS:
+		multiq3_i8254_write(dev, I8254_CTRL_READBACK_STATUS |
+					 I8254_CTRL_READBACK_SEL_CTR(chan),
+				    I8254_CTRL_REG);
+
+		data[1] = multiq3_i8254_read(dev, chan);
+		break;

As mentioned above, this won't work, unfortunately, due to the register being write-only. Most of the function can be emulated by remembering the mode the channel is set to, and by reading the output status of the channel from MULTIQ3_STATUS_REG. The format of the 8254 status is:

bit 7: OUT pin
bit 6: Null Count
bit 5: RW1
bit 4: RW0
bit 3: M2
bit 2: M1
bit 1: M0
bit 0: BCD

The OUT pin status can be read from MULTIQ3_STATUS_REG.

The Null Count status is partially unknown - it is set to 1 whenever the channel mode is written, and whenever the channel's counter is written. It is reset to 0 when the written counter value is clocked in to the channel's counting element, but exactly when that happens depends on the mode. The nearest thing that could be done is to set it to 1 when setting the mode and set it to 0 when writing the counter.

The RW1 and RW0 bits will both be 1 (indicating 2 x 8-bit counter access).

The M2, M1, M0 and BCD bits are as set by the mode.

Implementing this would also make INSN_CONFIG_GET_COUNTER_STATUS redundant.

+	case INSN_CONFIG_GET_CLOCK_SRC:
+		data[1] = 0;
+		data[2] = I8254_OSC_BASE_2MHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return insn->n;
+}
+
  static int multiq3_attach(struct comedi_device *dev,
  			  struct comedi_devconfig *it)
  {
@@ -268,7 +392,7 @@ static int multiq3_attach(struct comedi_device *dev,
  	if (ret)
  		return ret;

-	ret = comedi_alloc_subdevices(dev, 5);
+	ret = comedi_alloc_subdevices(dev, 6);
  	if (ret)
  		return ret;

@@ -325,6 +449,27 @@ static int multiq3_attach(struct comedi_device *dev,
  	for (i = 0; i < s->n_chan; i++)
  		multiq3_encoder_reset(dev, i);

+	/*
+	 * 8254 Counter/Timer subdevice
+	 *
+	 * This board uses the control register to address the four
+	 * registers in the 8254 timer. Because of this, the comedi_8254
+	 * driver cannot be used to provide support for the timer.
+	 */
+	s = &dev->subdevices[5];
+	s->type		= COMEDI_SUBD_COUNTER;
+	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
+	s->n_chan	= 3;
+	s->maxdata	= 0xffff;
+	s->range_table	= &range_unknown;
+	s->insn_read	= multiq3_8254_insn_read;
+	s->insn_write	= multiq3_8254_insn_write;
+	s->insn_config	= multiq3_8254_insn_config;
+
+	/* reset all the counters by setting them to I8254_MODE0 */
+	for (i = 0; i < 3; i++)
+		multiq3_8254_set_mode(dev, i, I8254_MODE0 | I8254_BINARY);
+
  	return 0;
  }




--
-=( Ian Abbott @ MEV Ltd.    E-mail: <abbotti@xxxxxxxxx> )=-
-=(                          Web: http://www.mev.co.uk/  )=-
_______________________________________________
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