1/*
2    comedi/drivers/adq12b.c
3    driver for MicroAxial ADQ12-B data acquisition and control card
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
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: adq12b
20Description: driver for MicroAxial ADQ12-B data acquisition and control card
21Devices: [MicroAxial] ADQ12-B (adq12b)
22Author: jeremy theler <thelerg@ib.cnea.gov.ar>
23Updated: Thu, 21 Feb 2008 02:56:27 -0300
24Status: works
25
26Driver for the acquisition card ADQ12-B (without any add-on).
27
28 - Analog input is subdevice 0 (16 channels single-ended or 8 differential)
29 - Digital input is subdevice 1 (5 channels)
30 - Digital output is subdevice 1 (8 channels)
31 - The PACER is not supported in this version
32
33If you do not specify any options, they will default to
34
35  # comedi_config /dev/comedi0 adq12b 0x300,0,0
36
37  option 1: I/O base address. The following table is provided as a help
38   of the hardware jumpers.
39
40	 address            jumper JADR
41	  0x300                 1 (factory default)
42	  0x320                 2
43	  0x340                 3
44	  0x360                 4
45	  0x380                 5
46	  0x3A0                 6
47
48  option 2: unipolar/bipolar ADC selection: 0 -> bipolar, 1 -> unipolar
49
50	selection         comedi_config option            JUB
51	 bipolar                0                         2-3 (factory default)
52	 unipolar               1                         1-2
53
54  option 3: single-ended/differential AI selection: 0 -> SE, 1 -> differential
55
56	selection         comedi_config option     JCHA    JCHB
57       single-ended             0                  1-2     1-2 (factory default)
58       differential             1                  2-3     2-3
59
60   written by jeremy theler <thelerg@ib.cnea.gov.ar>
61
62   instituto balseiro
63   commission nacional de energia atomica
64   universidad nacional de cuyo
65   argentina
66
67   21-feb-2008
68     + changed supported devices string (missused the [] and ())
69
70   13-oct-2007
71     + first try
72
73
74*/
75
76#include <linux/module.h>
77#include <linux/delay.h>
78
79#include "../comedidev.h"
80
81/* address scheme (page 2.17 of the manual) */
82#define ADQ12B_CTREG		0x00
83#define ADQ12B_CTREG_MSKP	(1 << 7)	/* enable pacer interrupt */
84#define ADQ12B_CTREG_GTP	(1 << 6)	/* enable pacer */
85#define ADQ12B_CTREG_RANGE(x)	((x) << 4)
86#define ADQ12B_CTREG_CHAN(x)	((x) << 0)
87#define ADQ12B_STINR		0x00
88#define ADQ12B_STINR_OUT2	(1 << 7)	/* timer 2 output state */
89#define ADQ12B_STINR_OUTP	(1 << 6)	/* pacer output state */
90#define ADQ12B_STINR_EOC	(1 << 5)	/* A/D end-of-conversion */
91#define ADQ12B_STINR_IN_MASK	(0x1f << 0)
92#define ADQ12B_OUTBR		0x04
93#define ADQ12B_ADLOW		0x08
94#define ADQ12B_ADHIG		0x09
95#define ADQ12B_TIMER_BASE	0x0c
96
97/* available ranges through the PGA gains */
98static const struct comedi_lrange range_adq12b_ai_bipolar = {
99	4, {
100		BIP_RANGE(5),
101		BIP_RANGE(2),
102		BIP_RANGE(1),
103		BIP_RANGE(0.5)
104	}
105};
106
107static const struct comedi_lrange range_adq12b_ai_unipolar = {
108	4, {
109		UNI_RANGE(5),
110		UNI_RANGE(2),
111		UNI_RANGE(1),
112		UNI_RANGE(0.5)
113	}
114};
115
116struct adq12b_private {
117	unsigned int last_ctreg;
118};
119
120static int adq12b_ai_eoc(struct comedi_device *dev,
121			 struct comedi_subdevice *s,
122			 struct comedi_insn *insn,
123			 unsigned long context)
124{
125	unsigned char status;
126
127	status = inb(dev->iobase + ADQ12B_STINR);
128	if (status & ADQ12B_STINR_EOC)
129		return 0;
130	return -EBUSY;
131}
132
133static int adq12b_ai_insn_read(struct comedi_device *dev,
134			       struct comedi_subdevice *s,
135			       struct comedi_insn *insn,
136			       unsigned int *data)
137{
138	struct adq12b_private *devpriv = dev->private;
139	unsigned int chan = CR_CHAN(insn->chanspec);
140	unsigned int range = CR_RANGE(insn->chanspec);
141	unsigned int val;
142	int ret;
143	int i;
144
145	/* change channel and range only if it is different from the previous */
146	val = ADQ12B_CTREG_RANGE(range) | ADQ12B_CTREG_CHAN(chan);
147	if (val != devpriv->last_ctreg) {
148		outb(val, dev->iobase + ADQ12B_CTREG);
149		devpriv->last_ctreg = val;
150		udelay(50);	/* wait for the mux to settle */
151	}
152
153	val = inb(dev->iobase + ADQ12B_ADLOW);	/* trigger A/D */
154
155	for (i = 0; i < insn->n; i++) {
156		ret = comedi_timeout(dev, s, insn, adq12b_ai_eoc, 0);
157		if (ret)
158			return ret;
159
160		val = inb(dev->iobase + ADQ12B_ADHIG) << 8;
161		val |= inb(dev->iobase + ADQ12B_ADLOW);	/* retriggers A/D */
162
163		data[i] = val;
164	}
165
166	return insn->n;
167}
168
169static int adq12b_di_insn_bits(struct comedi_device *dev,
170			       struct comedi_subdevice *s,
171			       struct comedi_insn *insn, unsigned int *data)
172{
173
174	/* only bits 0-4 have information about digital inputs */
175	data[1] = (inb(dev->iobase + ADQ12B_STINR) & ADQ12B_STINR_IN_MASK);
176
177	return insn->n;
178}
179
180static int adq12b_do_insn_bits(struct comedi_device *dev,
181			       struct comedi_subdevice *s,
182			       struct comedi_insn *insn,
183			       unsigned int *data)
184{
185	unsigned int mask;
186	unsigned int chan;
187	unsigned int val;
188
189	mask = comedi_dio_update_state(s, data);
190	if (mask) {
191		for (chan = 0; chan < 8; chan++) {
192			if ((mask >> chan) & 0x01) {
193				val = (s->state >> chan) & 0x01;
194				outb((val << 3) | chan,
195				     dev->iobase + ADQ12B_OUTBR);
196			}
197		}
198	}
199
200	data[1] = s->state;
201
202	return insn->n;
203}
204
205static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it)
206{
207	struct adq12b_private *devpriv;
208	struct comedi_subdevice *s;
209	int ret;
210
211	ret = comedi_request_region(dev, it->options[0], 0x10);
212	if (ret)
213		return ret;
214
215	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
216	if (!devpriv)
217		return -ENOMEM;
218
219	devpriv->last_ctreg = -1;	/* force ctreg update */
220
221	ret = comedi_alloc_subdevices(dev, 3);
222	if (ret)
223		return ret;
224
225	/* Analog Input subdevice */
226	s = &dev->subdevices[0];
227	s->type		= COMEDI_SUBD_AI;
228	if (it->options[2]) {
229		s->subdev_flags	= SDF_READABLE | SDF_DIFF;
230		s->n_chan	= 8;
231	} else {
232		s->subdev_flags	= SDF_READABLE | SDF_GROUND;
233		s->n_chan	= 16;
234	}
235	s->maxdata	= 0xfff;
236	s->range_table	= it->options[1] ? &range_adq12b_ai_unipolar
237					 : &range_adq12b_ai_bipolar;
238	s->insn_read	= adq12b_ai_insn_read;
239
240	/* Digital Input subdevice */
241	s = &dev->subdevices[1];
242	s->type		= COMEDI_SUBD_DI;
243	s->subdev_flags	= SDF_READABLE;
244	s->n_chan	= 5;
245	s->maxdata	= 1;
246	s->range_table	= &range_digital;
247	s->insn_bits	= adq12b_di_insn_bits;
248
249	/* Digital Output subdevice */
250	s = &dev->subdevices[2];
251	s->type		= COMEDI_SUBD_DO;
252	s->subdev_flags	= SDF_WRITABLE;
253	s->n_chan	= 8;
254	s->maxdata	= 1;
255	s->range_table	= &range_digital;
256	s->insn_bits	= adq12b_do_insn_bits;
257
258	return 0;
259}
260
261static struct comedi_driver adq12b_driver = {
262	.driver_name	= "adq12b",
263	.module		= THIS_MODULE,
264	.attach		= adq12b_attach,
265	.detach		= comedi_legacy_detach,
266};
267module_comedi_driver(adq12b_driver);
268
269MODULE_AUTHOR("Comedi http://www.comedi.org");
270MODULE_DESCRIPTION("Comedi low-level driver");
271MODULE_LICENSE("GPL");
272