1/*
2   comedi/drivers/multiq3.c
3   Hardware driver for Quanser Consulting MultiQ-3 board
4
5   COMEDI - Linux Control and Measurement Device Interface
6   Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17 */
18/*
19Driver: multiq3
20Description: Quanser Consulting MultiQ-3
21Author: Anders Blomdell <anders.blomdell@control.lth.se>
22Status: works
23Devices: [Quanser Consulting] MultiQ-3 (multiq3)
24
25*/
26
27#include <linux/module.h>
28#include <linux/interrupt.h>
29#include "../comedidev.h"
30
31/*
32 * MULTIQ-3 port offsets
33 */
34#define MULTIQ3_DIGIN_PORT 0
35#define MULTIQ3_DIGOUT_PORT 0
36#define MULTIQ3_DAC_DATA 2
37#define MULTIQ3_AD_DATA 4
38#define MULTIQ3_AD_CS 4
39#define MULTIQ3_STATUS 6
40#define MULTIQ3_CONTROL 6
41#define MULTIQ3_CLK_DATA 8
42#define MULTIQ3_ENC_DATA 12
43#define MULTIQ3_ENC_CONTROL 14
44
45/*
46 * flags for CONTROL register
47 */
48#define MULTIQ3_AD_MUX_EN      0x0040
49#define MULTIQ3_AD_AUTOZ       0x0080
50#define MULTIQ3_AD_AUTOCAL     0x0100
51#define MULTIQ3_AD_SH          0x0200
52#define MULTIQ3_AD_CLOCK_4M    0x0400
53#define MULTIQ3_DA_LOAD                0x1800
54
55#define MULTIQ3_CONTROL_MUST    0x0600
56
57/*
58 * flags for STATUS register
59 */
60#define MULTIQ3_STATUS_EOC      0x008
61#define MULTIQ3_STATUS_EOC_I    0x010
62
63/*
64 * flags for encoder control
65 */
66#define MULTIQ3_CLOCK_DATA      0x00
67#define MULTIQ3_CLOCK_SETUP     0x18
68#define MULTIQ3_INPUT_SETUP     0x41
69#define MULTIQ3_QUAD_X4         0x38
70#define MULTIQ3_BP_RESET        0x01
71#define MULTIQ3_CNTR_RESET      0x02
72#define MULTIQ3_TRSFRPR_CTR     0x08
73#define MULTIQ3_TRSFRCNTR_OL    0x10
74#define MULTIQ3_EFLAG_RESET     0x06
75
76#define MULTIQ3_TIMEOUT 30
77
78static int multiq3_ai_status(struct comedi_device *dev,
79			     struct comedi_subdevice *s,
80			     struct comedi_insn *insn,
81			     unsigned long context)
82{
83	unsigned int status;
84
85	status = inw(dev->iobase + MULTIQ3_STATUS);
86	if (status & context)
87		return 0;
88	return -EBUSY;
89}
90
91static int multiq3_ai_insn_read(struct comedi_device *dev,
92				struct comedi_subdevice *s,
93				struct comedi_insn *insn, unsigned int *data)
94{
95	int n;
96	int chan;
97	unsigned int hi, lo;
98	int ret;
99
100	chan = CR_CHAN(insn->chanspec);
101	outw(MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3),
102	     dev->iobase + MULTIQ3_CONTROL);
103
104	ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
105			     MULTIQ3_STATUS_EOC);
106	if (ret)
107		return ret;
108
109	for (n = 0; n < insn->n; n++) {
110		outw(0, dev->iobase + MULTIQ3_AD_CS);
111
112		ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
113				     MULTIQ3_STATUS_EOC_I);
114		if (ret)
115			return ret;
116
117		hi = inb(dev->iobase + MULTIQ3_AD_CS);
118		lo = inb(dev->iobase + MULTIQ3_AD_CS);
119		data[n] = (((hi << 8) | lo) + 0x1000) & 0x1fff;
120	}
121
122	return n;
123}
124
125static int multiq3_ao_insn_write(struct comedi_device *dev,
126				 struct comedi_subdevice *s,
127				 struct comedi_insn *insn,
128				 unsigned int *data)
129{
130	unsigned int chan = CR_CHAN(insn->chanspec);
131	unsigned int val = s->readback[chan];
132	int i;
133
134	for (i = 0; i < insn->n; i++) {
135		val = data[i];
136		outw(MULTIQ3_CONTROL_MUST | MULTIQ3_DA_LOAD | chan,
137		     dev->iobase + MULTIQ3_CONTROL);
138		outw(val, dev->iobase + MULTIQ3_DAC_DATA);
139		outw(MULTIQ3_CONTROL_MUST, dev->iobase + MULTIQ3_CONTROL);
140	}
141	s->readback[chan] = val;
142
143	return insn->n;
144}
145
146static int multiq3_di_insn_bits(struct comedi_device *dev,
147				struct comedi_subdevice *s,
148				struct comedi_insn *insn, unsigned int *data)
149{
150	data[1] = inw(dev->iobase + MULTIQ3_DIGIN_PORT);
151
152	return insn->n;
153}
154
155static int multiq3_do_insn_bits(struct comedi_device *dev,
156				struct comedi_subdevice *s,
157				struct comedi_insn *insn,
158				unsigned int *data)
159{
160	if (comedi_dio_update_state(s, data))
161		outw(s->state, dev->iobase + MULTIQ3_DIGOUT_PORT);
162
163	data[1] = s->state;
164
165	return insn->n;
166}
167
168static int multiq3_encoder_insn_read(struct comedi_device *dev,
169				     struct comedi_subdevice *s,
170				     struct comedi_insn *insn,
171				     unsigned int *data)
172{
173	int chan = CR_CHAN(insn->chanspec);
174	int control = MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3);
175	int value;
176	int n;
177
178	for (n = 0; n < insn->n; n++) {
179		outw(control, dev->iobase + MULTIQ3_CONTROL);
180		outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
181		outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CONTROL);
182		value = inb(dev->iobase + MULTIQ3_ENC_DATA);
183		value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 8);
184		value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 16);
185		data[n] = (value + 0x800000) & 0xffffff;
186	}
187
188	return n;
189}
190
191static void encoder_reset(struct comedi_device *dev)
192{
193	struct comedi_subdevice *s = &dev->subdevices[4];
194	int chan;
195
196	for (chan = 0; chan < s->n_chan; chan++) {
197		int control =
198		    MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3);
199		outw(control, dev->iobase + MULTIQ3_CONTROL);
200		outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
201		outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
202		outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA);
203		outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL);
204		outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL);
205		outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CONTROL);
206		outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CONTROL);
207	}
208}
209
210static int multiq3_attach(struct comedi_device *dev,
211			  struct comedi_devconfig *it)
212{
213	struct comedi_subdevice *s;
214	int ret;
215
216	ret = comedi_request_region(dev, it->options[0], 0x10);
217	if (ret)
218		return ret;
219
220	ret = comedi_alloc_subdevices(dev, 5);
221	if (ret)
222		return ret;
223
224	s = &dev->subdevices[0];
225	/* ai subdevice */
226	s->type = COMEDI_SUBD_AI;
227	s->subdev_flags = SDF_READABLE | SDF_GROUND;
228	s->n_chan = 8;
229	s->insn_read = multiq3_ai_insn_read;
230	s->maxdata = 0x1fff;
231	s->range_table = &range_bipolar5;
232
233	s = &dev->subdevices[1];
234	/* ao subdevice */
235	s->type = COMEDI_SUBD_AO;
236	s->subdev_flags = SDF_WRITABLE;
237	s->n_chan = 8;
238	s->maxdata = 0xfff;
239	s->range_table = &range_bipolar5;
240	s->insn_write = multiq3_ao_insn_write;
241	s->insn_read = comedi_readback_insn_read;
242
243	ret = comedi_alloc_subdev_readback(s);
244	if (ret)
245		return ret;
246
247	s = &dev->subdevices[2];
248	/* di subdevice */
249	s->type = COMEDI_SUBD_DI;
250	s->subdev_flags = SDF_READABLE;
251	s->n_chan = 16;
252	s->insn_bits = multiq3_di_insn_bits;
253	s->maxdata = 1;
254	s->range_table = &range_digital;
255
256	s = &dev->subdevices[3];
257	/* do subdevice */
258	s->type = COMEDI_SUBD_DO;
259	s->subdev_flags = SDF_WRITABLE;
260	s->n_chan = 16;
261	s->insn_bits = multiq3_do_insn_bits;
262	s->maxdata = 1;
263	s->range_table = &range_digital;
264	s->state = 0;
265
266	s = &dev->subdevices[4];
267	/* encoder (counter) subdevice */
268	s->type = COMEDI_SUBD_COUNTER;
269	s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
270	s->n_chan = it->options[2] * 2;
271	s->insn_read = multiq3_encoder_insn_read;
272	s->maxdata = 0xffffff;
273	s->range_table = &range_unknown;
274
275	encoder_reset(dev);
276
277	return 0;
278}
279
280static struct comedi_driver multiq3_driver = {
281	.driver_name	= "multiq3",
282	.module		= THIS_MODULE,
283	.attach		= multiq3_attach,
284	.detach		= comedi_legacy_detach,
285};
286module_comedi_driver(multiq3_driver);
287
288MODULE_AUTHOR("Comedi http://www.comedi.org");
289MODULE_DESCRIPTION("Comedi low-level driver");
290MODULE_LICENSE("GPL");
291