[go: nahoru, domu]

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