dt2814.c revision 0a85b6f0ab0d2edb0d41b32697111ce0e4f43496
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    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22*/
23/*
24Driver: dt2814
25Description: Data Translation DT2814
26Author: ds
27Status: complete
28Devices: [Data Translation] DT2814 (dt2814)
29
30Configuration options:
31  [0] - I/O port base address
32  [1] - IRQ
33
34This card has 16 analog inputs multiplexed onto a 12 bit ADC.  There
35is a minimally useful onboard clock.  The base frequency for the
36clock is selected by jumpers, and the clock divider can be selected
37via programmed I/O.  Unfortunately, the clock divider can only be
38a power of 10, from 1 to 10^7, of which only 3 or 4 are useful.  In
39addition, the clock does not seem to be very accurate.
40*/
41
42#include <linux/interrupt.h>
43#include "../comedidev.h"
44
45#include <linux/ioport.h>
46#include <linux/delay.h>
47
48#define DT2814_SIZE 2
49
50#define DT2814_CSR 0
51#define DT2814_DATA 1
52
53/*
54 * flags
55 */
56
57#define DT2814_FINISH 0x80
58#define DT2814_ERR 0x40
59#define DT2814_BUSY 0x20
60#define DT2814_ENB 0x10
61#define DT2814_CHANMASK 0x0f
62
63static int dt2814_attach(struct comedi_device *dev,
64			 struct comedi_devconfig *it);
65static int dt2814_detach(struct comedi_device *dev);
66static struct comedi_driver driver_dt2814 = {
67	.driver_name = "dt2814",
68	.module = THIS_MODULE,
69	.attach = dt2814_attach,
70	.detach = dt2814_detach,
71};
72
73COMEDI_INITCLEANUP(driver_dt2814);
74
75static irqreturn_t dt2814_interrupt(int irq, void *dev);
76
77struct dt2814_private {
78
79	int ntrig;
80	int curadchan;
81};
82
83#define devpriv ((struct dt2814_private *)dev->private)
84
85#define DT2814_TIMEOUT 10
86#define DT2814_MAX_SPEED 100000	/* Arbitrary 10 khz limit */
87
88static int dt2814_ai_insn_read(struct comedi_device *dev,
89			       struct comedi_subdevice *s,
90			       struct comedi_insn *insn, unsigned int *data)
91{
92	int n, i, hi, lo;
93	int chan;
94	int status = 0;
95
96	for (n = 0; n < insn->n; n++) {
97		chan = CR_CHAN(insn->chanspec);
98
99		outb(chan, dev->iobase + DT2814_CSR);
100		for (i = 0; i < DT2814_TIMEOUT; i++) {
101			status = inb(dev->iobase + DT2814_CSR);
102			printk("dt2814: status: %02x\n", status);
103			udelay(10);
104			if (status & DT2814_FINISH)
105				break;
106		}
107		if (i >= DT2814_TIMEOUT) {
108			printk("dt2814: status: %02x\n", status);
109			return -ETIMEDOUT;
110		}
111
112		hi = inb(dev->iobase + DT2814_DATA);
113		lo = inb(dev->iobase + DT2814_DATA);
114
115		data[n] = (hi << 4) | (lo >> 4);
116	}
117
118	return n;
119}
120
121static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
122{
123	int i;
124	unsigned int f;
125
126	/* XXX ignores flags */
127
128	f = 10000;		/* ns */
129	for (i = 0; i < 8; i++) {
130		if ((2 * (*ns)) < (f * 11))
131			break;
132		f *= 10;
133	}
134
135	*ns = f;
136
137	return i;
138}
139
140static int dt2814_ai_cmdtest(struct comedi_device *dev,
141			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
142{
143	int err = 0;
144	int tmp;
145
146	/* step 1: make sure trigger sources are trivially valid */
147
148	tmp = cmd->start_src;
149	cmd->start_src &= TRIG_NOW;
150	if (!cmd->start_src || tmp != cmd->start_src)
151		err++;
152
153	tmp = cmd->scan_begin_src;
154	cmd->scan_begin_src &= TRIG_TIMER;
155	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
156		err++;
157
158	tmp = cmd->convert_src;
159	cmd->convert_src &= TRIG_NOW;
160	if (!cmd->convert_src || tmp != cmd->convert_src)
161		err++;
162
163	tmp = cmd->scan_end_src;
164	cmd->scan_end_src &= TRIG_COUNT;
165	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
166		err++;
167
168	tmp = cmd->stop_src;
169	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
170	if (!cmd->stop_src || tmp != cmd->stop_src)
171		err++;
172
173	if (err)
174		return 1;
175
176	/* step 2: make sure trigger sources are unique and mutually compatible */
177
178	/* note that mutual compatiblity is not an issue here */
179	if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT)
180		err++;
181
182	if (err)
183		return 2;
184
185	/* step 3: make sure arguments are trivially compatible */
186
187	if (cmd->start_arg != 0) {
188		cmd->start_arg = 0;
189		err++;
190	}
191	if (cmd->scan_begin_arg > 1000000000) {
192		cmd->scan_begin_arg = 1000000000;
193		err++;
194	}
195	if (cmd->scan_begin_arg < DT2814_MAX_SPEED) {
196		cmd->scan_begin_arg = DT2814_MAX_SPEED;
197		err++;
198	}
199	if (cmd->scan_end_arg != cmd->chanlist_len) {
200		cmd->scan_end_arg = cmd->chanlist_len;
201		err++;
202	}
203	if (cmd->stop_src == TRIG_COUNT) {
204		if (cmd->stop_arg < 2) {
205			cmd->stop_arg = 2;
206			err++;
207		}
208	} else {
209		/* TRIG_NONE */
210		if (cmd->stop_arg != 0) {
211			cmd->stop_arg = 0;
212			err++;
213		}
214	}
215
216	if (err)
217		return 3;
218
219	/* step 4: fix up any arguments */
220
221	tmp = cmd->scan_begin_arg;
222	dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
223	if (tmp != cmd->scan_begin_arg)
224		err++;
225
226	if (err)
227		return 4;
228
229	return 0;
230}
231
232static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
233{
234	struct comedi_cmd *cmd = &s->async->cmd;
235	int chan;
236	int trigvar;
237
238	trigvar =
239	    dt2814_ns_to_timer(&cmd->scan_begin_arg,
240			       cmd->flags & TRIG_ROUND_MASK);
241
242	chan = CR_CHAN(cmd->chanlist[0]);
243
244	devpriv->ntrig = cmd->stop_arg;
245	outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
246
247	return 0;
248
249}
250
251static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
252{
253	int i, irq;
254	int ret;
255	struct comedi_subdevice *s;
256	unsigned long iobase;
257
258	iobase = it->options[0];
259	printk("comedi%d: dt2814: 0x%04lx ", dev->minor, iobase);
260	if (!request_region(iobase, DT2814_SIZE, "dt2814")) {
261		printk("I/O port conflict\n");
262		return -EIO;
263	}
264	dev->iobase = iobase;
265	dev->board_name = "dt2814";
266
267	outb(0, dev->iobase + DT2814_CSR);
268	udelay(100);
269	if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
270		printk("reset error (fatal)\n");
271		return -EIO;
272	}
273	i = inb(dev->iobase + DT2814_DATA);
274	i = inb(dev->iobase + DT2814_DATA);
275
276	irq = it->options[1];
277#if 0
278	if (irq < 0) {
279		save_flags(flags);
280		sti();
281		irqs = probe_irq_on();
282
283		outb(0, dev->iobase + DT2814_CSR);
284
285		udelay(100);
286
287		irq = probe_irq_off(irqs);
288		restore_flags(flags);
289		if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
290			printk("error probing irq (bad) \n");
291		}
292
293		i = inb(dev->iobase + DT2814_DATA);
294		i = inb(dev->iobase + DT2814_DATA);
295	}
296#endif
297	dev->irq = 0;
298	if (irq > 0) {
299		if (request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) {
300			printk("(irq %d unavailable)\n", irq);
301		} else {
302			printk("( irq = %d )\n", irq);
303			dev->irq = irq;
304		}
305	} else if (irq == 0) {
306		printk("(no irq)\n");
307	} else {
308#if 0
309		printk("(probe returned multiple irqs--bad)\n");
310#else
311		printk("(irq probe not implemented)\n");
312#endif
313	}
314
315	ret = alloc_subdevices(dev, 1);
316	if (ret < 0)
317		return ret;
318
319	ret = alloc_private(dev, sizeof(struct dt2814_private));
320	if (ret < 0)
321		return ret;
322
323	s = dev->subdevices + 0;
324	dev->read_subdev = s;
325	s->type = COMEDI_SUBD_AI;
326	s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
327	s->n_chan = 16;		/* XXX */
328	s->len_chanlist = 1;
329	s->insn_read = dt2814_ai_insn_read;
330	s->do_cmd = dt2814_ai_cmd;
331	s->do_cmdtest = dt2814_ai_cmdtest;
332	s->maxdata = 0xfff;
333	s->range_table = &range_unknown;	/* XXX */
334
335	return 0;
336}
337
338static int dt2814_detach(struct comedi_device *dev)
339{
340	printk("comedi%d: dt2814: remove\n", dev->minor);
341
342	if (dev->irq) {
343		free_irq(dev->irq, dev);
344	}
345	if (dev->iobase) {
346		release_region(dev->iobase, DT2814_SIZE);
347	}
348
349	return 0;
350}
351
352static irqreturn_t dt2814_interrupt(int irq, void *d)
353{
354	int lo, hi;
355	struct comedi_device *dev = d;
356	struct comedi_subdevice *s;
357	int data;
358
359	if (!dev->attached) {
360		comedi_error(dev, "spurious interrupt");
361		return IRQ_HANDLED;
362	}
363
364	s = dev->subdevices + 0;
365
366	hi = inb(dev->iobase + DT2814_DATA);
367	lo = inb(dev->iobase + DT2814_DATA);
368
369	data = (hi << 4) | (lo >> 4);
370
371	if (!(--devpriv->ntrig)) {
372		int i;
373
374		outb(0, dev->iobase + DT2814_CSR);
375		/* note: turning off timed mode triggers another
376		   sample. */
377
378		for (i = 0; i < DT2814_TIMEOUT; i++) {
379			if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH)
380				break;
381		}
382		inb(dev->iobase + DT2814_DATA);
383		inb(dev->iobase + DT2814_DATA);
384
385		s->async->events |= COMEDI_CB_EOA;
386	}
387	comedi_event(dev, s);
388	return IRQ_HANDLED;
389}
390