1 // SPDX-License-Identifier: GPL-2.0+
4 * Hardware driver for Quanser Consulting MultiQ-3 board
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
12 * Description: Quanser Consulting MultiQ-3
13 * Devices: [Quanser Consulting] MultiQ-3 (multiq3)
14 * Author: Anders Blomdell <anders.blomdell@control.lth.se>
17 * Configuration Options:
18 * [0] - I/O port base address
19 * [1] - IRQ (not used)
20 * [2] - Number of optional encoder chips installed on board
22 * 1 = 2 inputs (Model -2E)
23 * 2 = 4 inputs (Model -4E)
24 * 3 = 6 inputs (Model -6E)
25 * 4 = 8 inputs (Model -8E)
28 #include <linux/module.h>
30 #include "../comedidev.h"
35 #define MULTIQ3_DI_REG 0x00
36 #define MULTIQ3_DO_REG 0x00
37 #define MULTIQ3_AO_REG 0x02
38 #define MULTIQ3_AI_REG 0x04
39 #define MULTIQ3_AI_CONV_REG 0x04
40 #define MULTIQ3_STATUS_REG 0x06
41 #define MULTIQ3_STATUS_EOC BIT(3)
42 #define MULTIQ3_STATUS_EOC_I BIT(4)
43 #define MULTIQ3_CTRL_REG 0x06
44 #define MULTIQ3_CTRL_AO_CHAN(x) (((x) & 0x7) << 0)
45 #define MULTIQ3_CTRL_RC(x) (((x) & 0x3) << 0)
46 #define MULTIQ3_CTRL_AI_CHAN(x) (((x) & 0x7) << 3)
47 #define MULTIQ3_CTRL_E_CHAN(x) (((x) & 0x7) << 3)
48 #define MULTIQ3_CTRL_EN BIT(6)
49 #define MULTIQ3_CTRL_AZ BIT(7)
50 #define MULTIQ3_CTRL_CAL BIT(8)
51 #define MULTIQ3_CTRL_SH BIT(9)
52 #define MULTIQ3_CTRL_CLK BIT(10)
53 #define MULTIQ3_CTRL_LD (3 << 11)
54 #define MULTIQ3_CLK_REG 0x08
55 #define MULTIQ3_ENC_DATA_REG 0x0c
56 #define MULTIQ3_ENC_CTRL_REG 0x0e
59 * Encoder chip commands (from the programming manual)
61 #define MULTIQ3_CLOCK_DATA 0x00 /* FCK frequency divider */
62 #define MULTIQ3_CLOCK_SETUP 0x18 /* xfer PR0 to PSC */
63 #define MULTIQ3_INPUT_SETUP 0x41 /* enable inputs A and B */
64 #define MULTIQ3_QUAD_X4 0x38 /* quadrature */
65 #define MULTIQ3_BP_RESET 0x01 /* reset byte pointer */
66 #define MULTIQ3_CNTR_RESET 0x02 /* reset counter */
67 #define MULTIQ3_TRSFRPR_CTR 0x08 /* xfre preset reg to counter */
68 #define MULTIQ3_TRSFRCNTR_OL 0x10 /* xfer CNTR to OL (x and y) */
69 #define MULTIQ3_EFLAG_RESET 0x06 /* reset E bit of flag reg */
71 static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits)
74 * According to the programming manual, the SH and CLK bits should
75 * be kept high at all times.
77 outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits,
78 dev->iobase + MULTIQ3_CTRL_REG);
81 static int multiq3_ai_status(struct comedi_device *dev,
82 struct comedi_subdevice *s,
83 struct comedi_insn *insn,
84 unsigned long context)
88 status = inw(dev->iobase + MULTIQ3_STATUS_REG);
94 static int multiq3_ai_insn_read(struct comedi_device *dev,
95 struct comedi_subdevice *s,
96 struct comedi_insn *insn,
99 unsigned int chan = CR_CHAN(insn->chanspec);
104 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan));
106 ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
111 for (i = 0; i < insn->n; i++) {
112 outw(0, dev->iobase + MULTIQ3_AI_CONV_REG);
114 ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
115 MULTIQ3_STATUS_EOC_I);
119 /* get a 16-bit sample; mask it to the subdevice resolution */
120 val = inb(dev->iobase + MULTIQ3_AI_REG) << 8;
121 val |= inb(dev->iobase + MULTIQ3_AI_REG);
124 /* munge the 2's complement value to offset binary */
125 data[i] = comedi_offset_munge(s, val);
131 static int multiq3_ao_insn_write(struct comedi_device *dev,
132 struct comedi_subdevice *s,
133 struct comedi_insn *insn,
136 unsigned int chan = CR_CHAN(insn->chanspec);
137 unsigned int val = s->readback[chan];
140 for (i = 0; i < insn->n; i++) {
142 multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD |
143 MULTIQ3_CTRL_AO_CHAN(chan));
144 outw(val, dev->iobase + MULTIQ3_AO_REG);
145 multiq3_set_ctrl(dev, 0);
147 s->readback[chan] = val;
152 static int multiq3_di_insn_bits(struct comedi_device *dev,
153 struct comedi_subdevice *s,
154 struct comedi_insn *insn, unsigned int *data)
156 data[1] = inw(dev->iobase + MULTIQ3_DI_REG);
161 static int multiq3_do_insn_bits(struct comedi_device *dev,
162 struct comedi_subdevice *s,
163 struct comedi_insn *insn,
166 if (comedi_dio_update_state(s, data))
167 outw(s->state, dev->iobase + MULTIQ3_DO_REG);
174 static int multiq3_encoder_insn_read(struct comedi_device *dev,
175 struct comedi_subdevice *s,
176 struct comedi_insn *insn,
179 unsigned int chan = CR_CHAN(insn->chanspec);
183 for (i = 0; i < insn->n; i++) {
184 /* select encoder channel */
185 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN |
186 MULTIQ3_CTRL_E_CHAN(chan));
188 /* reset the byte pointer */
189 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
192 outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG);
194 /* read the 24-bit encoder data (lsb/mid/msb) */
195 val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG);
196 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8);
197 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16);
200 * Munge the data so that the reset value is in the middle
201 * of the maxdata range, i.e.:
203 * real value comedi value
204 * 0xffffff 0x7fffff 1 negative count
205 * 0x000000 0x800000 reset value
206 * 0x000001 0x800001 1 positive count
208 * It's possible for the 24-bit counter to overflow but it
209 * would normally take _quite_ a few turns. A 2000 line
210 * encoder in quadrature results in 8000 counts/rev. So about
211 * 1048 turns in either direction can be measured without
214 data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata;
220 static void multiq3_encoder_reset(struct comedi_device *dev,
223 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan));
224 outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
225 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
226 outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG);
227 outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
228 outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
229 outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG);
230 outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
233 static int multiq3_encoder_insn_config(struct comedi_device *dev,
234 struct comedi_subdevice *s,
235 struct comedi_insn *insn,
238 unsigned int chan = CR_CHAN(insn->chanspec);
241 case INSN_CONFIG_RESET:
242 multiq3_encoder_reset(dev, chan);
251 static int multiq3_attach(struct comedi_device *dev,
252 struct comedi_devconfig *it)
254 struct comedi_subdevice *s;
258 ret = comedi_request_region(dev, it->options[0], 0x10);
262 ret = comedi_alloc_subdevices(dev, 5);
266 /* Analog Input subdevice */
267 s = &dev->subdevices[0];
268 s->type = COMEDI_SUBD_AI;
269 s->subdev_flags = SDF_READABLE | SDF_GROUND;
272 s->range_table = &range_bipolar5;
273 s->insn_read = multiq3_ai_insn_read;
275 /* Analog Output subdevice */
276 s = &dev->subdevices[1];
277 s->type = COMEDI_SUBD_AO;
278 s->subdev_flags = SDF_WRITABLE;
281 s->range_table = &range_bipolar5;
282 s->insn_write = multiq3_ao_insn_write;
284 ret = comedi_alloc_subdev_readback(s);
288 /* Digital Input subdevice */
289 s = &dev->subdevices[2];
290 s->type = COMEDI_SUBD_DI;
291 s->subdev_flags = SDF_READABLE;
294 s->range_table = &range_digital;
295 s->insn_bits = multiq3_di_insn_bits;
297 /* Digital Output subdevice */
298 s = &dev->subdevices[3];
299 s->type = COMEDI_SUBD_DO;
300 s->subdev_flags = SDF_WRITABLE;
303 s->range_table = &range_digital;
304 s->insn_bits = multiq3_do_insn_bits;
306 /* Encoder (Counter) subdevice */
307 s = &dev->subdevices[4];
308 s->type = COMEDI_SUBD_COUNTER;
309 s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
310 s->n_chan = it->options[2] * 2;
311 s->maxdata = 0x00ffffff;
312 s->range_table = &range_unknown;
313 s->insn_read = multiq3_encoder_insn_read;
314 s->insn_config = multiq3_encoder_insn_config;
316 for (i = 0; i < s->n_chan; i++)
317 multiq3_encoder_reset(dev, i);
322 static struct comedi_driver multiq3_driver = {
323 .driver_name = "multiq3",
324 .module = THIS_MODULE,
325 .attach = multiq3_attach,
326 .detach = comedi_legacy_detach,
328 module_comedi_driver(multiq3_driver);
330 MODULE_AUTHOR("Comedi https://www.comedi.org");
331 MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board");
332 MODULE_LICENSE("GPL");