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