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