1/*
2    comedi/drivers/dt2814.c
3    Hardware driver for Data Translation DT2814
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 1998 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: dt2814
20Description: Data Translation DT2814
21Author: ds
22Status: complete
23Devices: [Data Translation] DT2814 (dt2814)
24
25Configuration options:
26  [0] - I/O port base address
27  [1] - IRQ
28
29This card has 16 analog inputs multiplexed onto a 12 bit ADC.  There
30is a minimally useful onboard clock.  The base frequency for the
31clock is selected by jumpers, and the clock divider can be selected
32via programmed I/O.  Unfortunately, the clock divider can only be
33a power of 10, from 1 to 10^7, of which only 3 or 4 are useful.  In
34addition, the clock does not seem to be very accurate.
35*/
36
37#include <linux/module.h>
38#include <linux/interrupt.h>
39#include "../comedidev.h"
40
41#include <linux/delay.h>
42
43#define DT2814_CSR 0
44#define DT2814_DATA 1
45
46/*
47 * flags
48 */
49
50#define DT2814_FINISH 0x80
51#define DT2814_ERR 0x40
52#define DT2814_BUSY 0x20
53#define DT2814_ENB 0x10
54#define DT2814_CHANMASK 0x0f
55
56struct dt2814_private {
57	int ntrig;
58	int curadchan;
59};
60
61#define DT2814_TIMEOUT 10
62#define DT2814_MAX_SPEED 100000	/* Arbitrary 10 khz limit */
63
64static int dt2814_ai_eoc(struct comedi_device *dev,
65			 struct comedi_subdevice *s,
66			 struct comedi_insn *insn,
67			 unsigned long context)
68{
69	unsigned int status;
70
71	status = inb(dev->iobase + DT2814_CSR);
72	if (status & DT2814_FINISH)
73		return 0;
74	return -EBUSY;
75}
76
77static int dt2814_ai_insn_read(struct comedi_device *dev,
78			       struct comedi_subdevice *s,
79			       struct comedi_insn *insn, unsigned int *data)
80{
81	int n, hi, lo;
82	int chan;
83	int ret;
84
85	for (n = 0; n < insn->n; n++) {
86		chan = CR_CHAN(insn->chanspec);
87
88		outb(chan, dev->iobase + DT2814_CSR);
89
90		ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
91		if (ret)
92			return ret;
93
94		hi = inb(dev->iobase + DT2814_DATA);
95		lo = inb(dev->iobase + DT2814_DATA);
96
97		data[n] = (hi << 4) | (lo >> 4);
98	}
99
100	return n;
101}
102
103static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
104{
105	int i;
106	unsigned int f;
107
108	/* XXX ignores flags */
109
110	f = 10000;		/* ns */
111	for (i = 0; i < 8; i++) {
112		if ((2 * (*ns)) < (f * 11))
113			break;
114		f *= 10;
115	}
116
117	*ns = f;
118
119	return i;
120}
121
122static int dt2814_ai_cmdtest(struct comedi_device *dev,
123			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
124{
125	int err = 0;
126	unsigned int arg;
127
128	/* Step 1 : check if triggers are trivially valid */
129
130	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
131	err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
132	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
133	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
134	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
135
136	if (err)
137		return 1;
138
139	/* Step 2a : make sure trigger sources are unique */
140
141	err |= comedi_check_trigger_is_unique(cmd->stop_src);
142
143	/* Step 2b : and mutually compatible */
144
145	if (err)
146		return 2;
147
148	/* Step 3: check if arguments are trivially valid */
149
150	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
151
152	err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
153	err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
154					    DT2814_MAX_SPEED);
155
156	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
157					   cmd->chanlist_len);
158
159	if (cmd->stop_src == TRIG_COUNT)
160		err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
161	else	/* TRIG_NONE */
162		err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
163
164	if (err)
165		return 3;
166
167	/* step 4: fix up any arguments */
168
169	arg = cmd->scan_begin_arg;
170	dt2814_ns_to_timer(&arg, cmd->flags);
171	err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
172
173	if (err)
174		return 4;
175
176	return 0;
177}
178
179static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
180{
181	struct dt2814_private *devpriv = dev->private;
182	struct comedi_cmd *cmd = &s->async->cmd;
183	int chan;
184	int trigvar;
185
186	trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
187
188	chan = CR_CHAN(cmd->chanlist[0]);
189
190	devpriv->ntrig = cmd->stop_arg;
191	outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
192
193	return 0;
194}
195
196static irqreturn_t dt2814_interrupt(int irq, void *d)
197{
198	int lo, hi;
199	struct comedi_device *dev = d;
200	struct dt2814_private *devpriv = dev->private;
201	struct comedi_subdevice *s = dev->read_subdev;
202	int data;
203
204	if (!dev->attached) {
205		dev_err(dev->class_dev, "spurious interrupt\n");
206		return IRQ_HANDLED;
207	}
208
209	hi = inb(dev->iobase + DT2814_DATA);
210	lo = inb(dev->iobase + DT2814_DATA);
211
212	data = (hi << 4) | (lo >> 4);
213
214	if (!(--devpriv->ntrig)) {
215		int i;
216
217		outb(0, dev->iobase + DT2814_CSR);
218		/* note: turning off timed mode triggers another
219		   sample. */
220
221		for (i = 0; i < DT2814_TIMEOUT; i++) {
222			if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH)
223				break;
224		}
225		inb(dev->iobase + DT2814_DATA);
226		inb(dev->iobase + DT2814_DATA);
227
228		s->async->events |= COMEDI_CB_EOA;
229	}
230	comedi_handle_events(dev, s);
231	return IRQ_HANDLED;
232}
233
234static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
235{
236	struct dt2814_private *devpriv;
237	struct comedi_subdevice *s;
238	int ret;
239	int i;
240
241	ret = comedi_request_region(dev, it->options[0], 0x2);
242	if (ret)
243		return ret;
244
245	outb(0, dev->iobase + DT2814_CSR);
246	udelay(100);
247	if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
248		dev_err(dev->class_dev, "reset error (fatal)\n");
249		return -EIO;
250	}
251	i = inb(dev->iobase + DT2814_DATA);
252	i = inb(dev->iobase + DT2814_DATA);
253
254	if (it->options[1]) {
255		ret = request_irq(it->options[1], dt2814_interrupt, 0,
256				  dev->board_name, dev);
257		if (ret == 0)
258			dev->irq = it->options[1];
259	}
260
261	ret = comedi_alloc_subdevices(dev, 1);
262	if (ret)
263		return ret;
264
265	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
266	if (!devpriv)
267		return -ENOMEM;
268
269	s = &dev->subdevices[0];
270	s->type = COMEDI_SUBD_AI;
271	s->subdev_flags = SDF_READABLE | SDF_GROUND;
272	s->n_chan = 16;		/* XXX */
273	s->insn_read = dt2814_ai_insn_read;
274	s->maxdata = 0xfff;
275	s->range_table = &range_unknown;	/* XXX */
276	if (dev->irq) {
277		dev->read_subdev = s;
278		s->subdev_flags |= SDF_CMD_READ;
279		s->len_chanlist = 1;
280		s->do_cmd = dt2814_ai_cmd;
281		s->do_cmdtest = dt2814_ai_cmdtest;
282	}
283
284	return 0;
285}
286
287static struct comedi_driver dt2814_driver = {
288	.driver_name	= "dt2814",
289	.module		= THIS_MODULE,
290	.attach		= dt2814_attach,
291	.detach		= comedi_legacy_detach,
292};
293module_comedi_driver(dt2814_driver);
294
295MODULE_AUTHOR("Comedi http://www.comedi.org");
296MODULE_DESCRIPTION("Comedi low-level driver");
297MODULE_LICENSE("GPL");
298