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