dt2811.c revision 3b9fdcd5e85104e622c0ec5f626c81b831ddfae2
1/*
2   comedi/drivers/dt2811.c
3   Hardware driver for Data Translation DT2811
4
5   COMEDI - Linux Control and Measurement Device Interface
6   History:
7   Base Version  - David A. Schleef <ds@schleef.org>
8   December 1998 - Updated to work.  David does not have a DT2811
9   board any longer so this was suffering from bitrot.
10   Updated performed by ...
11
12   This program is free software; you can redistribute it and/or modify
13   it under the terms of the GNU General Public License as published by
14   the Free Software Foundation; either version 2 of the License, or
15   (at your option) any later version.
16
17   This program is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20   GNU General Public License for more details.
21
22   You should have received a copy of the GNU General Public License
23   along with this program; if not, write to the Free Software
24   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 */
26/*
27Driver: dt2811
28Description: Data Translation DT2811
29Author: ds
30Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh)
31Status: works
32
33Configuration options:
34  [0] - I/O port base address
35  [1] - IRQ, although this is currently unused
36  [2] - A/D reference
37	  0 = signle-ended
38	  1 = differential
39	  2 = pseudo-differential (common reference)
40  [3] - A/D range
41	  0 = [-5, 5]
42	  1 = [-2.5, 2.5]
43	  2 = [0, 5]
44  [4] - D/A 0 range (same choices)
45  [4] - D/A 1 range (same choices)
46*/
47
48#include <linux/interrupt.h>
49#include "../comedidev.h"
50
51#include <linux/ioport.h>
52
53static const char *driver_name = "dt2811";
54
55static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = {
56	4, {
57		RANGE(0, 5),
58		RANGE(0, 2.5),
59		RANGE(0, 1.25),
60		RANGE(0, 0.625)
61	}
62};
63
64static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = {
65	4, {
66		RANGE(-2.5, 2.5),
67		RANGE(-1.25, 1.25),
68		RANGE(-0.625, 0.625),
69		RANGE(-0.3125, 0.3125)
70	}
71};
72
73static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = {
74	4, {
75		RANGE(-5, 5),
76		RANGE(-2.5, 2.5),
77		RANGE(-1.25, 1.25),
78		RANGE(-0.625, 0.625)
79	}
80};
81
82static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = {
83	4, {
84		RANGE(0, 5),
85		RANGE(0, 0.5),
86		RANGE(0, 0.05),
87		RANGE(0, 0.01)
88	}
89};
90
91static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = {
92	4, {
93		RANGE(-2.5, 2.5),
94		RANGE(-0.25, 0.25),
95		RANGE(-0.025, 0.025),
96		RANGE(-0.005, 0.005)
97	}
98};
99
100static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = {
101	4, {
102		RANGE(-5, 5),
103		RANGE(-0.5, 0.5),
104		RANGE(-0.05, 0.05),
105		RANGE(-0.01, 0.01)
106	}
107};
108
109/*
110
111   0x00    ADCSR R/W  A/D Control/Status Register
112   bit 7 - (R) 1 indicates A/D conversion done
113   reading ADDAT clears bit
114   (W) ignored
115   bit 6 - (R) 1 indicates A/D error
116   (W) ignored
117   bit 5 - (R) 1 indicates A/D busy, cleared at end
118   of conversion
119   (W) ignored
120   bit 4 - (R) 0
121   (W)
122   bit 3 - (R) 0
123   bit 2 - (R/W) 1 indicates interrupts enabled
124   bits 1,0 - (R/W) mode bits
125   00  single conversion on ADGCR load
126   01  continuous conversion, internal clock,
127   (clock enabled on ADGCR load)
128   10  continuous conversion, internal clock,
129   external trigger
130   11  continuous conversion, external clock,
131   external trigger
132
133   0x01    ADGCR R/W A/D Gain/Channel Register
134   bit 6,7 - (R/W) gain select
135   00  gain=1, both PGH, PGL models
136   01  gain=2 PGH, 10 PGL
137   10  gain=4 PGH, 100 PGL
138   11  gain=8 PGH, 500 PGL
139   bit 4,5 - reserved
140   bit 3-0 - (R/W) channel select
141   channel number from 0-15
142
143   0x02,0x03 (R) ADDAT A/D Data Register
144   (W) DADAT0 D/A Data Register 0
145   0x02 low byte
146   0x03 high byte
147
148   0x04,0x05 (W) DADAT0 D/A Data Register 1
149
150   0x06 (R) DIO0 Digital Input Port 0
151   (W) DIO1 Digital Output Port 1
152
153   0x07 TMRCTR (R/W) Timer/Counter Register
154   bits 6,7 - reserved
155   bits 5-3 - Timer frequency control (mantissa)
156   543  divisor  freqency (kHz)
157   000  1        600
158   001  10       60
159   010  2        300
160   011  3        200
161   100  4        150
162   101  5        120
163   110  6        100
164   111  12       50
165   bits 2-0 - Timer frequency control (exponent)
166   210  multiply divisor/divide frequency by
167   000  1
168   001  10
169   010  100
170   011  1000
171   100  10000
172   101  100000
173   110  1000000
174   111  10000000
175
176 */
177
178#define TIMEOUT 10000
179
180#define DT2811_SIZE 8
181
182#define DT2811_ADCSR 0
183#define DT2811_ADGCR 1
184#define DT2811_ADDATLO 2
185#define DT2811_ADDATHI 3
186#define DT2811_DADAT0LO 2
187#define DT2811_DADAT0HI 3
188#define DT2811_DADAT1LO 4
189#define DT2811_DADAT1HI 5
190#define DT2811_DIO 6
191#define DT2811_TMRCTR 7
192
193/*
194 * flags
195 */
196
197/* ADCSR */
198
199#define DT2811_ADDONE   0x80
200#define DT2811_ADERROR  0x40
201#define DT2811_ADBUSY   0x20
202#define DT2811_CLRERROR 0x10
203#define DT2811_INTENB   0x04
204#define DT2811_ADMODE   0x03
205
206struct dt2811_board {
207
208	const char *name;
209	const struct comedi_lrange *bip_5;
210	const struct comedi_lrange *bip_2_5;
211	const struct comedi_lrange *unip_5;
212};
213
214static const struct dt2811_board boardtypes[] = {
215	{"dt2811-pgh",
216	 &range_dt2811_pgh_ai_5_bipolar,
217	 &range_dt2811_pgh_ai_2_5_bipolar,
218	 &range_dt2811_pgh_ai_5_unipolar,
219	 },
220	{"dt2811-pgl",
221	 &range_dt2811_pgl_ai_5_bipolar,
222	 &range_dt2811_pgl_ai_2_5_bipolar,
223	 &range_dt2811_pgl_ai_5_unipolar,
224	 },
225};
226
227#define this_board ((const struct dt2811_board *)dev->board_ptr)
228
229static int dt2811_attach(struct comedi_device *dev,
230			 struct comedi_devconfig *it);
231static int dt2811_detach(struct comedi_device *dev);
232static struct comedi_driver driver_dt2811 = {
233	.driver_name = "dt2811",
234	.module = THIS_MODULE,
235	.attach = dt2811_attach,
236	.detach = dt2811_detach,
237	.board_name = &boardtypes[0].name,
238	.num_names = ARRAY_SIZE(boardtypes),
239	.offset = sizeof(struct dt2811_board),
240};
241
242COMEDI_INITCLEANUP(driver_dt2811);
243
244static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
245			  struct comedi_insn *insn, unsigned int *data);
246static int dt2811_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
247			  struct comedi_insn *insn, unsigned int *data);
248static int dt2811_ao_insn_read(struct comedi_device *dev,
249			       struct comedi_subdevice *s,
250			       struct comedi_insn *insn, unsigned int *data);
251static int dt2811_di_insn_bits(struct comedi_device *dev,
252			       struct comedi_subdevice *s,
253			       struct comedi_insn *insn, unsigned int *data);
254static int dt2811_do_insn_bits(struct comedi_device *dev,
255			       struct comedi_subdevice *s,
256			       struct comedi_insn *insn, unsigned int *data);
257
258enum { card_2811_pgh, card_2811_pgl };
259
260struct dt2811_private {
261	int ntrig;
262	int curadchan;
263	enum {
264		adc_singleended, adc_diff, adc_pseudo_diff
265	} adc_mux;
266	enum {
267		dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5
268	} dac_range[2];
269	const struct comedi_lrange *range_type_list[2];
270	unsigned int ao_readback[2];
271};
272
273#define devpriv ((struct dt2811_private *)dev->private)
274
275static const struct comedi_lrange *dac_range_types[] = {
276	&range_bipolar5,
277	&range_bipolar2_5,
278	&range_unipolar5
279};
280
281#define DT2811_TIMEOUT 5
282
283#if 0
284static irqreturn_t dt2811_interrupt(int irq, void *d)
285{
286	int lo, hi;
287	int data;
288	struct comedi_device *dev = d;
289
290	if (!dev->attached) {
291		comedi_error(dev, "spurious interrupt");
292		return IRQ_HANDLED;
293	}
294
295	lo = inb(dev->iobase + DT2811_ADDATLO);
296	hi = inb(dev->iobase + DT2811_ADDATHI);
297
298	data = lo + (hi << 8);
299
300	if (!(--devpriv->ntrig)) {
301		/* how to turn off acquisition */
302		s->async->events |= COMEDI_SB_EOA;
303	}
304	comedi_event(dev, s);
305	return IRQ_HANDLED;
306}
307#endif
308
309/*
310  options[0]   Board base address
311  options[1]   IRQ
312  options[2]   Input configuration
313		 0 == single-ended
314		 1 == differential
315		 2 == pseudo-differential
316  options[3]   Analog input range configuration
317		 0 == bipolar 5  (-5V -- +5V)
318		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
319		 2 == unipolar 5V  (0V -- +5V)
320  options[4]   Analog output 0 range configuration
321		 0 == bipolar 5  (-5V -- +5V)
322		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
323		 2 == unipolar 5V  (0V -- +5V)
324  options[5]   Analog output 1 range configuration
325		 0 == bipolar 5  (-5V -- +5V)
326		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
327		 2 == unipolar 5V  (0V -- +5V)
328*/
329
330static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it)
331{
332	/* int i, irq; */
333	/* unsigned long irqs; */
334	/* long flags; */
335
336	int ret;
337	struct comedi_subdevice *s;
338	unsigned long iobase;
339
340	iobase = it->options[0];
341
342	printk(KERN_INFO "comedi%d: dt2811:base=0x%04lx\n", dev->minor, iobase);
343
344	if (!request_region(iobase, DT2811_SIZE, driver_name)) {
345		printk(KERN_ERR "I/O port conflict\n");
346		return -EIO;
347	}
348
349	dev->iobase = iobase;
350	dev->board_name = this_board->name;
351
352#if 0
353	outb(0, dev->iobase + DT2811_ADCSR);
354	udelay(100);
355	i = inb(dev->iobase + DT2811_ADDATLO);
356	i = inb(dev->iobase + DT2811_ADDATHI);
357#endif
358
359#if 0
360	irq = it->options[1];
361	if (irq < 0) {
362		save_flags(flags);
363		sti();
364		irqs = probe_irq_on();
365
366		outb(DT2811_CLRERROR | DT2811_INTENB,
367		     dev->iobase + DT2811_ADCSR);
368		outb(0, dev->iobase + DT2811_ADGCR);
369
370		udelay(100);
371
372		irq = probe_irq_off(irqs);
373		restore_flags(flags);
374
375		/*outb(DT2811_CLRERROR|DT2811_INTENB,
376			dev->iobase+DT2811_ADCSR);*/
377
378		if (inb(dev->iobase + DT2811_ADCSR) & DT2811_ADERROR)
379			printk(KERN_ERR "error probing irq (bad)\n");
380		dev->irq = 0;
381		if (irq > 0) {
382			i = inb(dev->iobase + DT2811_ADDATLO);
383			i = inb(dev->iobase + DT2811_ADDATHI);
384			printk(KERN_INFO "(irq = %d)\n", irq);
385			ret = request_irq(irq, dt2811_interrupt, 0,
386					  driver_name, dev);
387			if (ret < 0)
388				return -EIO;
389			dev->irq = irq;
390		} else if (irq == 0) {
391			printk(KERN_INFO "(no irq)\n");
392		} else {
393			printk(KERN_ERR "( multiple irq's -- this is bad! )\n");
394		}
395	}
396#endif
397
398	ret = alloc_subdevices(dev, 4);
399	if (ret < 0)
400		return ret;
401
402	ret = alloc_private(dev, sizeof(struct dt2811_private));
403	if (ret < 0)
404		return ret;
405
406	switch (it->options[2]) {
407	case 0:
408		devpriv->adc_mux = adc_singleended;
409		break;
410	case 1:
411		devpriv->adc_mux = adc_diff;
412		break;
413	case 2:
414		devpriv->adc_mux = adc_pseudo_diff;
415		break;
416	default:
417		devpriv->adc_mux = adc_singleended;
418		break;
419	}
420	switch (it->options[4]) {
421	case 0:
422		devpriv->dac_range[0] = dac_bipolar_5;
423		break;
424	case 1:
425		devpriv->dac_range[0] = dac_bipolar_2_5;
426		break;
427	case 2:
428		devpriv->dac_range[0] = dac_unipolar_5;
429		break;
430	default:
431		devpriv->dac_range[0] = dac_bipolar_5;
432		break;
433	}
434	switch (it->options[5]) {
435	case 0:
436		devpriv->dac_range[1] = dac_bipolar_5;
437		break;
438	case 1:
439		devpriv->dac_range[1] = dac_bipolar_2_5;
440		break;
441	case 2:
442		devpriv->dac_range[1] = dac_unipolar_5;
443		break;
444	default:
445		devpriv->dac_range[1] = dac_bipolar_5;
446		break;
447	}
448
449	s = dev->subdevices + 0;
450	/* initialize the ADC subdevice */
451	s->type = COMEDI_SUBD_AI;
452	s->subdev_flags = SDF_READABLE | SDF_GROUND;
453	s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16;
454	s->insn_read = dt2811_ai_insn;
455	s->maxdata = 0xfff;
456	switch (it->options[3]) {
457	case 0:
458	default:
459		s->range_table = this_board->bip_5;
460		break;
461	case 1:
462		s->range_table = this_board->bip_2_5;
463		break;
464	case 2:
465		s->range_table = this_board->unip_5;
466		break;
467	}
468
469	s = dev->subdevices + 1;
470	/* ao subdevice */
471	s->type = COMEDI_SUBD_AO;
472	s->subdev_flags = SDF_WRITABLE;
473	s->n_chan = 2;
474	s->insn_write = dt2811_ao_insn;
475	s->insn_read = dt2811_ao_insn_read;
476	s->maxdata = 0xfff;
477	s->range_table_list = devpriv->range_type_list;
478	devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]];
479	devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]];
480
481	s = dev->subdevices + 2;
482	/* di subdevice */
483	s->type = COMEDI_SUBD_DI;
484	s->subdev_flags = SDF_READABLE;
485	s->n_chan = 8;
486	s->insn_bits = dt2811_di_insn_bits;
487	s->maxdata = 1;
488	s->range_table = &range_digital;
489
490	s = dev->subdevices + 3;
491	/* do subdevice */
492	s->type = COMEDI_SUBD_DO;
493	s->subdev_flags = SDF_WRITABLE;
494	s->n_chan = 8;
495	s->insn_bits = dt2811_do_insn_bits;
496	s->maxdata = 1;
497	s->state = 0;
498	s->range_table = &range_digital;
499
500	return 0;
501}
502
503static int dt2811_detach(struct comedi_device *dev)
504{
505	printk(KERN_INFO "comedi%d: dt2811: remove\n", dev->minor);
506
507	if (dev->irq)
508		free_irq(dev->irq, dev);
509	if (dev->iobase)
510		release_region(dev->iobase, DT2811_SIZE);
511
512	return 0;
513}
514
515static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
516			  struct comedi_insn *insn, unsigned int *data)
517{
518	int chan = CR_CHAN(insn->chanspec);
519	int timeout = DT2811_TIMEOUT;
520	int i;
521
522	for (i = 0; i < insn->n; i++) {
523		outb(chan, dev->iobase + DT2811_ADGCR);
524
525		while (timeout
526		       && inb(dev->iobase + DT2811_ADCSR) & DT2811_ADBUSY)
527			timeout--;
528		if (!timeout)
529			return -ETIME;
530
531		data[i] = inb(dev->iobase + DT2811_ADDATLO);
532		data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8;
533		data[i] &= 0xfff;
534	}
535
536	return i;
537}
538
539#if 0
540/* Wow.  This is code from the Comedi stone age.  But it hasn't been
541 * replaced, so I'll let it stay. */
542int dt2811_adtrig(kdev_t minor, comedi_adtrig *adtrig)
543{
544	struct comedi_device *dev = comedi_devices + minor;
545
546	if (adtrig->n < 1)
547		return 0;
548	dev->curadchan = adtrig->chan;
549	switch (dev->i_admode) {
550	case COMEDI_MDEMAND:
551		dev->ntrig = adtrig->n - 1;
552		/* not neccessary */
553		/*printk("dt2811: AD soft trigger\n"); */
554		/*outb(DT2811_CLRERROR|DT2811_INTENB,
555			dev->iobase+DT2811_ADCSR); */
556		outb(dev->curadchan, dev->iobase + DT2811_ADGCR);
557		do_gettimeofday(&trigtime);
558		break;
559	case COMEDI_MCONTS:
560		dev->ntrig = adtrig->n;
561		break;
562	}
563
564	return 0;
565}
566#endif
567
568static int dt2811_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
569			  struct comedi_insn *insn, unsigned int *data)
570{
571	int i;
572	int chan;
573
574	chan = CR_CHAN(insn->chanspec);
575
576	for (i = 0; i < insn->n; i++) {
577		outb(data[i] & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan);
578		outb((data[i] >> 8) & 0xff,
579		     dev->iobase + DT2811_DADAT0HI + 2 * chan);
580		devpriv->ao_readback[chan] = data[i];
581	}
582
583	return i;
584}
585
586static int dt2811_ao_insn_read(struct comedi_device *dev,
587			       struct comedi_subdevice *s,
588			       struct comedi_insn *insn, unsigned int *data)
589{
590	int i;
591	int chan;
592
593	chan = CR_CHAN(insn->chanspec);
594
595	for (i = 0; i < insn->n; i++)
596		data[i] = devpriv->ao_readback[chan];
597
598	return i;
599}
600
601static int dt2811_di_insn_bits(struct comedi_device *dev,
602			       struct comedi_subdevice *s,
603			       struct comedi_insn *insn, unsigned int *data)
604{
605	if (insn->n != 2)
606		return -EINVAL;
607
608	data[1] = inb(dev->iobase + DT2811_DIO);
609
610	return 2;
611}
612
613static int dt2811_do_insn_bits(struct comedi_device *dev,
614			       struct comedi_subdevice *s,
615			       struct comedi_insn *insn, unsigned int *data)
616{
617	if (insn->n != 2)
618		return -EINVAL;
619
620	s->state &= ~data[0];
621	s->state |= data[0] & data[1];
622	outb(s->state, dev->iobase + DT2811_DIO);
623
624	data[1] = s->state;
625
626	return 2;
627}
628