File Index
Files and directories
Sign in
FILE

multiq3.c

File preview with syntax highlighting.

linux-6.19.14/drivers/comedi/drivers/multiq3.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * multiq3.c
  4 * Hardware driver for Quanser Consulting MultiQ-3 board
  5 *
  6 * COMEDI - Linux Control and Measurement Device Interface
  7 * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
  8 */
  9
 10/*
 11 * Driver: multiq3
 12 * Description: Quanser Consulting MultiQ-3
 13 * Devices: [Quanser Consulting] MultiQ-3 (multiq3)
 14 * Author: Anders Blomdell <anders.blomdell@control.lth.se>
 15 * Status: works
 16 *
 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
 21 *	  0 = none
 22 *	  1 = 2 inputs (Model -2E)
 23 *	  2 = 4 inputs (Model -4E)
 24 *	  3 = 6 inputs (Model -6E)
 25 *	  4 = 8 inputs (Model -8E)
 26 */
 27
 28#include <linux/module.h>
 29#include <linux/comedi/comedidev.h>
 30
 31/*
 32 * Register map
 33 */
 34#define MULTIQ3_DI_REG			0x00
 35#define MULTIQ3_DO_REG			0x00
 36#define MULTIQ3_AO_REG			0x02
 37#define MULTIQ3_AI_REG			0x04
 38#define MULTIQ3_AI_CONV_REG		0x04
 39#define MULTIQ3_STATUS_REG		0x06
 40#define MULTIQ3_STATUS_EOC		BIT(3)
 41#define MULTIQ3_STATUS_EOC_I		BIT(4)
 42#define MULTIQ3_CTRL_REG		0x06
 43#define MULTIQ3_CTRL_AO_CHAN(x)		(((x) & 0x7) << 0)
 44#define MULTIQ3_CTRL_RC(x)		(((x) & 0x3) << 0)
 45#define MULTIQ3_CTRL_AI_CHAN(x)		(((x) & 0x7) << 3)
 46#define MULTIQ3_CTRL_E_CHAN(x)		(((x) & 0x7) << 3)
 47#define MULTIQ3_CTRL_EN			BIT(6)
 48#define MULTIQ3_CTRL_AZ			BIT(7)
 49#define MULTIQ3_CTRL_CAL		BIT(8)
 50#define MULTIQ3_CTRL_SH			BIT(9)
 51#define MULTIQ3_CTRL_CLK		BIT(10)
 52#define MULTIQ3_CTRL_LD			(3 << 11)
 53#define MULTIQ3_CLK_REG			0x08
 54#define MULTIQ3_ENC_DATA_REG		0x0c
 55#define MULTIQ3_ENC_CTRL_REG		0x0e
 56
 57/*
 58 * Encoder chip commands (from the programming manual)
 59 */
 60#define MULTIQ3_CLOCK_DATA		0x00	/* FCK frequency divider */
 61#define MULTIQ3_CLOCK_SETUP		0x18	/* xfer PR0 to PSC */
 62#define MULTIQ3_INPUT_SETUP		0x41	/* enable inputs A and B */
 63#define MULTIQ3_QUAD_X4			0x38	/* quadrature */
 64#define MULTIQ3_BP_RESET		0x01	/* reset byte pointer */
 65#define MULTIQ3_CNTR_RESET		0x02	/* reset counter */
 66#define MULTIQ3_TRSFRPR_CTR		0x08	/* xfre preset reg to counter */
 67#define MULTIQ3_TRSFRCNTR_OL		0x10	/* xfer CNTR to OL (x and y) */
 68#define MULTIQ3_EFLAG_RESET		0x06	/* reset E bit of flag reg */
 69
 70/*
 71 * Limit on the number of optional encoder channels
 72 */
 73#define MULTIQ3_MAX_ENC_CHANS		8
 74
 75static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits)
 76{
 77	/*
 78	 * According to the programming manual, the SH and CLK bits should
 79	 * be kept high at all times.
 80	 */
 81	outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits,
 82	     dev->iobase + MULTIQ3_CTRL_REG);
 83}
 84
 85static int multiq3_ai_status(struct comedi_device *dev,
 86			     struct comedi_subdevice *s,
 87			     struct comedi_insn *insn,
 88			     unsigned long context)
 89{
 90	unsigned int status;
 91
 92	status = inw(dev->iobase + MULTIQ3_STATUS_REG);
 93	if (status & context)
 94		return 0;
 95	return -EBUSY;
 96}
 97
 98static int multiq3_ai_insn_read(struct comedi_device *dev,
 99				struct comedi_subdevice *s,
100				struct comedi_insn *insn,
101				unsigned int *data)
102{
103	unsigned int chan = CR_CHAN(insn->chanspec);
104	unsigned int val;
105	int ret;
106	int i;
107
108	multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan));
109
110	ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
111			     MULTIQ3_STATUS_EOC);
112	if (ret)
113		return ret;
114
115	for (i = 0; i < insn->n; i++) {
116		outw(0, dev->iobase + MULTIQ3_AI_CONV_REG);
117
118		ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
119				     MULTIQ3_STATUS_EOC_I);
120		if (ret)
121			return ret;
122
123		/* get a 16-bit sample; mask it to the subdevice resolution */
124		val = inb(dev->iobase + MULTIQ3_AI_REG) << 8;
125		val |= inb(dev->iobase + MULTIQ3_AI_REG);
126		val &= s->maxdata;
127
128		/* munge the 2's complement value to offset binary */
129		data[i] = comedi_offset_munge(s, val);
130	}
131
132	return insn->n;
133}
134
135static int multiq3_ao_insn_write(struct comedi_device *dev,
136				 struct comedi_subdevice *s,
137				 struct comedi_insn *insn,
138				 unsigned int *data)
139{
140	unsigned int chan = CR_CHAN(insn->chanspec);
141	unsigned int val = s->readback[chan];
142	int i;
143
144	for (i = 0; i < insn->n; i++) {
145		val = data[i];
146		multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD |
147				      MULTIQ3_CTRL_AO_CHAN(chan));
148		outw(val, dev->iobase + MULTIQ3_AO_REG);
149		multiq3_set_ctrl(dev, 0);
150	}
151	s->readback[chan] = val;
152
153	return insn->n;
154}
155
156static int multiq3_di_insn_bits(struct comedi_device *dev,
157				struct comedi_subdevice *s,
158				struct comedi_insn *insn, unsigned int *data)
159{
160	data[1] = inw(dev->iobase + MULTIQ3_DI_REG);
161
162	return insn->n;
163}
164
165static int multiq3_do_insn_bits(struct comedi_device *dev,
166				struct comedi_subdevice *s,
167				struct comedi_insn *insn,
168				unsigned int *data)
169{
170	if (comedi_dio_update_state(s, data))
171		outw(s->state, dev->iobase + MULTIQ3_DO_REG);
172
173	data[1] = s->state;
174
175	return insn->n;
176}
177
178static int multiq3_encoder_insn_read(struct comedi_device *dev,
179				     struct comedi_subdevice *s,
180				     struct comedi_insn *insn,
181				     unsigned int *data)
182{
183	unsigned int chan = CR_CHAN(insn->chanspec);
184	unsigned int val;
185	int i;
186
187	for (i = 0; i < insn->n; i++) {
188		/* select encoder channel */
189		multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN |
190				      MULTIQ3_CTRL_E_CHAN(chan));
191
192		/* reset the byte pointer */
193		outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
194
195		/* latch the data */
196		outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG);
197
198		/* read the 24-bit encoder data (lsb/mid/msb) */
199		val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG);
200		val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8);
201		val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16);
202
203		/*
204		 * Munge the data so that the reset value is in the middle
205		 * of the maxdata range, i.e.:
206		 *
207		 * real value	comedi value
208		 * 0xffffff	0x7fffff	1 negative count
209		 * 0x000000	0x800000	reset value
210		 * 0x000001	0x800001	1 positive count
211		 *
212		 * It's possible for the 24-bit counter to overflow but it
213		 * would normally take _quite_ a few turns. A 2000 line
214		 * encoder in quadrature results in 8000 counts/rev. So about
215		 * 1048 turns in either direction can be measured without
216		 * an overflow.
217		 */
218		data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata;
219	}
220
221	return insn->n;
222}
223
224static void multiq3_encoder_reset(struct comedi_device *dev,
225				  unsigned int chan)
226{
227	multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan));
228	outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
229	outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
230	outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG);
231	outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
232	outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
233	outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG);
234	outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
235}
236
237static int multiq3_encoder_insn_config(struct comedi_device *dev,
238				       struct comedi_subdevice *s,
239				       struct comedi_insn *insn,
240				       unsigned int *data)
241{
242	unsigned int chan = CR_CHAN(insn->chanspec);
243
244	switch (data[0]) {
245	case INSN_CONFIG_RESET:
246		multiq3_encoder_reset(dev, chan);
247		break;
248	default:
249		return -EINVAL;
250	}
251
252	return insn->n;
253}
254
255static int multiq3_attach(struct comedi_device *dev,
256			  struct comedi_devconfig *it)
257{
258	struct comedi_subdevice *s;
259	int ret;
260	int i;
261
262	ret = comedi_request_region(dev, it->options[0], 0x10);
263	if (ret)
264		return ret;
265
266	ret = comedi_alloc_subdevices(dev, 5);
267	if (ret)
268		return ret;
269
270	/* Analog Input subdevice */
271	s = &dev->subdevices[0];
272	s->type		= COMEDI_SUBD_AI;
273	s->subdev_flags	= SDF_READABLE | SDF_GROUND;
274	s->n_chan	= 8;
275	s->maxdata	= 0x1fff;
276	s->range_table	= &range_bipolar5;
277	s->insn_read	= multiq3_ai_insn_read;
278
279	/* Analog Output subdevice */
280	s = &dev->subdevices[1];
281	s->type		= COMEDI_SUBD_AO;
282	s->subdev_flags	= SDF_WRITABLE;
283	s->n_chan	= 8;
284	s->maxdata	= 0x0fff;
285	s->range_table	= &range_bipolar5;
286	s->insn_write	= multiq3_ao_insn_write;
287
288	ret = comedi_alloc_subdev_readback(s);
289	if (ret)
290		return ret;
291
292	/* Digital Input subdevice */
293	s = &dev->subdevices[2];
294	s->type		= COMEDI_SUBD_DI;
295	s->subdev_flags	= SDF_READABLE;
296	s->n_chan	= 16;
297	s->maxdata	= 1;
298	s->range_table	= &range_digital;
299	s->insn_bits	= multiq3_di_insn_bits;
300
301	/* Digital Output subdevice */
302	s = &dev->subdevices[3];
303	s->type		= COMEDI_SUBD_DO;
304	s->subdev_flags	= SDF_WRITABLE;
305	s->n_chan	= 16;
306	s->maxdata	= 1;
307	s->range_table	= &range_digital;
308	s->insn_bits	= multiq3_do_insn_bits;
309
310	/* Encoder (Counter) subdevice */
311	s = &dev->subdevices[4];
312	s->type		= COMEDI_SUBD_COUNTER;
313	s->subdev_flags	= SDF_READABLE | SDF_LSAMPL;
314	s->n_chan	= it->options[2] * 2;
315	s->maxdata	= 0x00ffffff;
316	s->range_table	= &range_unknown;
317	s->insn_read	= multiq3_encoder_insn_read;
318	s->insn_config	= multiq3_encoder_insn_config;
319
320	/* sanity check for number of encoder channels */
321	if (s->n_chan > MULTIQ3_MAX_ENC_CHANS)
322		s->n_chan = MULTIQ3_MAX_ENC_CHANS;
323
324	for (i = 0; i < s->n_chan; i++)
325		multiq3_encoder_reset(dev, i);
326
327	return 0;
328}
329
330static struct comedi_driver multiq3_driver = {
331	.driver_name	= "multiq3",
332	.module		= THIS_MODULE,
333	.attach		= multiq3_attach,
334	.detach		= comedi_legacy_detach,
335};
336module_comedi_driver(multiq3_driver);
337
338MODULE_AUTHOR("Comedi https://www.comedi.org");
339MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board");
340MODULE_LICENSE("GPL");
~/ Read-only file preview.