dt2814.c revision 90f703d30dd3e0c16ff80f35e34e511385a05ad5
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(KERN_INFO "dt2814: status: %02x\n", status);
103			udelay(10);
104			if (status & DT2814_FINISH)
105				break;
106		}
107		if (i >= DT2814_TIMEOUT) {
108			printk(KERN_INFO "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
177	 * unique and mutually compatible */
178
179	/* note that mutual compatibility is not an issue here */
180	if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT)
181		err++;
182
183	if (err)
184		return 2;
185
186	/* step 3: make sure arguments are trivially compatible */
187
188	if (cmd->start_arg != 0) {
189		cmd->start_arg = 0;
190		err++;
191	}
192	if (cmd->scan_begin_arg > 1000000000) {
193		cmd->scan_begin_arg = 1000000000;
194		err++;
195	}
196	if (cmd->scan_begin_arg < DT2814_MAX_SPEED) {
197		cmd->scan_begin_arg = DT2814_MAX_SPEED;
198		err++;
199	}
200	if (cmd->scan_end_arg != cmd->chanlist_len) {
201		cmd->scan_end_arg = cmd->chanlist_len;
202		err++;
203	}
204	if (cmd->stop_src == TRIG_COUNT) {
205		if (cmd->stop_arg < 2) {
206			cmd->stop_arg = 2;
207			err++;
208		}
209	} else {
210		/* TRIG_NONE */
211		if (cmd->stop_arg != 0) {
212			cmd->stop_arg = 0;
213			err++;
214		}
215	}
216
217	if (err)
218		return 3;
219
220	/* step 4: fix up any arguments */
221
222	tmp = cmd->scan_begin_arg;
223	dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
224	if (tmp != cmd->scan_begin_arg)
225		err++;
226
227	if (err)
228		return 4;
229
230	return 0;
231}
232
233static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
234{
235	struct comedi_cmd *cmd = &s->async->cmd;
236	int chan;
237	int trigvar;
238
239	trigvar =
240	    dt2814_ns_to_timer(&cmd->scan_begin_arg,
241			       cmd->flags & TRIG_ROUND_MASK);
242
243	chan = CR_CHAN(cmd->chanlist[0]);
244
245	devpriv->ntrig = cmd->stop_arg;
246	outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
247
248	return 0;
249
250}
251
252static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
253{
254	int i, irq;
255	int ret;
256	struct comedi_subdevice *s;
257	unsigned long iobase;
258
259	iobase = it->options[0];
260	printk(KERN_INFO "comedi%d: dt2814: 0x%04lx ", dev->minor, iobase);
261	if (!request_region(iobase, DT2814_SIZE, "dt2814")) {
262		printk(KERN_ERR "I/O port conflict\n");
263		return -EIO;
264	}
265	dev->iobase = iobase;
266	dev->board_name = "dt2814";
267
268	outb(0, dev->iobase + DT2814_CSR);
269	udelay(100);
270	if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
271		printk(KERN_ERR "reset error (fatal)\n");
272		return -EIO;
273	}
274	i = inb(dev->iobase + DT2814_DATA);
275	i = inb(dev->iobase + DT2814_DATA);
276
277	irq = it->options[1];
278#if 0
279	if (irq < 0) {
280		save_flags(flags);
281		sti();
282		irqs = probe_irq_on();
283
284		outb(0, dev->iobase + DT2814_CSR);
285
286		udelay(100);
287
288		irq = probe_irq_off(irqs);
289		restore_flags(flags);
290		if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR)
291			printk(KERN_DEBUG "error probing irq (bad)\n");
292
293
294		i = inb(dev->iobase + DT2814_DATA);
295		i = inb(dev->iobase + DT2814_DATA);
296	}
297#endif
298	dev->irq = 0;
299	if (irq > 0) {
300		if (request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) {
301			printk(KERN_WARNING "(irq %d unavailable)\n", irq);
302		} else {
303			printk(KERN_INFO "( irq = %d )\n", irq);
304			dev->irq = irq;
305		}
306	} else if (irq == 0) {
307		printk(KERN_WARNING "(no irq)\n");
308	} else {
309#if 0
310		printk(KERN_DEBUG "(probe returned multiple irqs--bad)\n");
311#else
312		printk(KERN_WARNING "(irq probe not implemented)\n");
313#endif
314	}
315
316	ret = alloc_subdevices(dev, 1);
317	if (ret < 0)
318		return ret;
319
320	ret = alloc_private(dev, sizeof(struct dt2814_private));
321	if (ret < 0)
322		return ret;
323
324	s = dev->subdevices + 0;
325	dev->read_subdev = s;
326	s->type = COMEDI_SUBD_AI;
327	s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
328	s->n_chan = 16;		/* XXX */
329	s->len_chanlist = 1;
330	s->insn_read = dt2814_ai_insn_read;
331	s->do_cmd = dt2814_ai_cmd;
332	s->do_cmdtest = dt2814_ai_cmdtest;
333	s->maxdata = 0xfff;
334	s->range_table = &range_unknown;	/* XXX */
335
336	return 0;
337}
338
339static int dt2814_detach(struct comedi_device *dev)
340{
341	printk(KERN_INFO "comedi%d: dt2814: remove\n", dev->minor);
342
343	if (dev->irq)
344		free_irq(dev->irq, dev);
345
346	if (dev->iobase)
347		release_region(dev->iobase, DT2814_SIZE);
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
391MODULE_AUTHOR("Comedi http://www.comedi.org");
392MODULE_DESCRIPTION("Comedi low-level driver");
393MODULE_LICENSE("GPL");
394