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/*
23Driver: dt2811
24Description: Data Translation DT2811
25Author: ds
26Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh)
27Status: works
28
29Configuration options:
30  [0] - I/O port base address
31  [1] - IRQ, although this is currently unused
32  [2] - A/D reference
33	  0 = signle-ended
34	  1 = differential
35	  2 = pseudo-differential (common reference)
36  [3] - A/D range
37	  0 = [-5, 5]
38	  1 = [-2.5, 2.5]
39	  2 = [0, 5]
40  [4] - D/A 0 range (same choices)
41  [4] - D/A 1 range (same choices)
42*/
43
44#include <linux/module.h>
45#include "../comedidev.h"
46
47static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = {
48	4, {
49		UNI_RANGE(5),
50		UNI_RANGE(2.5),
51		UNI_RANGE(1.25),
52		UNI_RANGE(0.625)
53	}
54};
55
56static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = {
57	4, {
58		BIP_RANGE(2.5),
59		BIP_RANGE(1.25),
60		BIP_RANGE(0.625),
61		BIP_RANGE(0.3125)
62	}
63};
64
65static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = {
66	4, {
67		BIP_RANGE(5),
68		BIP_RANGE(2.5),
69		BIP_RANGE(1.25),
70		BIP_RANGE(0.625)
71	}
72};
73
74static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = {
75	4, {
76		UNI_RANGE(5),
77		UNI_RANGE(0.5),
78		UNI_RANGE(0.05),
79		UNI_RANGE(0.01)
80	}
81};
82
83static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = {
84	4, {
85		BIP_RANGE(2.5),
86		BIP_RANGE(0.25),
87		BIP_RANGE(0.025),
88		BIP_RANGE(0.005)
89	}
90};
91
92static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = {
93	4, {
94		BIP_RANGE(5),
95		BIP_RANGE(0.5),
96		BIP_RANGE(0.05),
97		BIP_RANGE(0.01)
98	}
99};
100
101/*
102
103   0x00    ADCSR R/W  A/D Control/Status Register
104   bit 7 - (R) 1 indicates A/D conversion done
105   reading ADDAT clears bit
106   (W) ignored
107   bit 6 - (R) 1 indicates A/D error
108   (W) ignored
109   bit 5 - (R) 1 indicates A/D busy, cleared at end
110   of conversion
111   (W) ignored
112   bit 4 - (R) 0
113   (W)
114   bit 3 - (R) 0
115   bit 2 - (R/W) 1 indicates interrupts enabled
116   bits 1,0 - (R/W) mode bits
117   00  single conversion on ADGCR load
118   01  continuous conversion, internal clock,
119   (clock enabled on ADGCR load)
120   10  continuous conversion, internal clock,
121   external trigger
122   11  continuous conversion, external clock,
123   external trigger
124
125   0x01    ADGCR R/W A/D Gain/Channel Register
126   bit 6,7 - (R/W) gain select
127   00  gain=1, both PGH, PGL models
128   01  gain=2 PGH, 10 PGL
129   10  gain=4 PGH, 100 PGL
130   11  gain=8 PGH, 500 PGL
131   bit 4,5 - reserved
132   bit 3-0 - (R/W) channel select
133   channel number from 0-15
134
135   0x02,0x03 (R) ADDAT A/D Data Register
136   (W) DADAT0 D/A Data Register 0
137   0x02 low byte
138   0x03 high byte
139
140   0x04,0x05 (W) DADAT0 D/A Data Register 1
141
142   0x06 (R) DIO0 Digital Input Port 0
143   (W) DIO1 Digital Output Port 1
144
145   0x07 TMRCTR (R/W) Timer/Counter Register
146   bits 6,7 - reserved
147   bits 5-3 - Timer frequency control (mantissa)
148   543  divisor  freqency (kHz)
149   000  1        600
150   001  10       60
151   010  2        300
152   011  3        200
153   100  4        150
154   101  5        120
155   110  6        100
156   111  12       50
157   bits 2-0 - Timer frequency control (exponent)
158   210  multiply divisor/divide frequency by
159   000  1
160   001  10
161   010  100
162   011  1000
163   100  10000
164   101  100000
165   110  1000000
166   111  10000000
167
168 */
169
170#define TIMEOUT 10000
171
172#define DT2811_ADCSR 0
173#define DT2811_ADGCR 1
174#define DT2811_ADDATLO 2
175#define DT2811_ADDATHI 3
176#define DT2811_DADAT0LO 2
177#define DT2811_DADAT0HI 3
178#define DT2811_DADAT1LO 4
179#define DT2811_DADAT1HI 5
180#define DT2811_DIO 6
181#define DT2811_TMRCTR 7
182
183/*
184 * flags
185 */
186
187/* ADCSR */
188
189#define DT2811_ADDONE   0x80
190#define DT2811_ADERROR  0x40
191#define DT2811_ADBUSY   0x20
192#define DT2811_CLRERROR 0x10
193#define DT2811_INTENB   0x04
194#define DT2811_ADMODE   0x03
195
196struct dt2811_board {
197
198	const char *name;
199	const struct comedi_lrange *bip_5;
200	const struct comedi_lrange *bip_2_5;
201	const struct comedi_lrange *unip_5;
202};
203
204enum { card_2811_pgh, card_2811_pgl };
205
206struct dt2811_private {
207	int ntrig;
208	int curadchan;
209	enum {
210		adc_singleended, adc_diff, adc_pseudo_diff
211	} adc_mux;
212	enum {
213		dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5
214	} dac_range[2];
215	const struct comedi_lrange *range_type_list[2];
216};
217
218static const struct comedi_lrange *dac_range_types[] = {
219	&range_bipolar5,
220	&range_bipolar2_5,
221	&range_unipolar5
222};
223
224static int dt2811_ai_eoc(struct comedi_device *dev,
225			 struct comedi_subdevice *s,
226			 struct comedi_insn *insn,
227			 unsigned long context)
228{
229	unsigned int status;
230
231	status = inb(dev->iobase + DT2811_ADCSR);
232	if ((status & DT2811_ADBUSY) == 0)
233		return 0;
234	return -EBUSY;
235}
236
237static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
238			  struct comedi_insn *insn, unsigned int *data)
239{
240	int chan = CR_CHAN(insn->chanspec);
241	int ret;
242	int i;
243
244	for (i = 0; i < insn->n; i++) {
245		outb(chan, dev->iobase + DT2811_ADGCR);
246
247		ret = comedi_timeout(dev, s, insn, dt2811_ai_eoc, 0);
248		if (ret)
249			return ret;
250
251		data[i] = inb(dev->iobase + DT2811_ADDATLO);
252		data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8;
253		data[i] &= 0xfff;
254	}
255
256	return i;
257}
258
259static int dt2811_ao_insn_write(struct comedi_device *dev,
260				struct comedi_subdevice *s,
261				struct comedi_insn *insn,
262				unsigned int *data)
263{
264	unsigned int chan = CR_CHAN(insn->chanspec);
265	unsigned int val = s->readback[chan];
266	int i;
267
268	for (i = 0; i < insn->n; i++) {
269		val = data[i];
270		outb(val & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan);
271		outb((val >> 8) & 0xff,
272		     dev->iobase + DT2811_DADAT0HI + 2 * chan);
273	}
274	s->readback[chan] = val;
275
276	return insn->n;
277}
278
279static int dt2811_di_insn_bits(struct comedi_device *dev,
280			       struct comedi_subdevice *s,
281			       struct comedi_insn *insn, unsigned int *data)
282{
283	data[1] = inb(dev->iobase + DT2811_DIO);
284
285	return insn->n;
286}
287
288static int dt2811_do_insn_bits(struct comedi_device *dev,
289			       struct comedi_subdevice *s,
290			       struct comedi_insn *insn,
291			       unsigned int *data)
292{
293	if (comedi_dio_update_state(s, data))
294		outb(s->state, dev->iobase + DT2811_DIO);
295
296	data[1] = s->state;
297
298	return insn->n;
299}
300
301/*
302  options[0]   Board base address
303  options[1]   IRQ
304  options[2]   Input configuration
305		 0 == single-ended
306		 1 == differential
307		 2 == pseudo-differential
308  options[3]   Analog input range configuration
309		 0 == bipolar 5  (-5V -- +5V)
310		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
311		 2 == unipolar 5V  (0V -- +5V)
312  options[4]   Analog output 0 range configuration
313		 0 == bipolar 5  (-5V -- +5V)
314		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
315		 2 == unipolar 5V  (0V -- +5V)
316  options[5]   Analog output 1 range configuration
317		 0 == bipolar 5  (-5V -- +5V)
318		 1 == bipolar 2.5V  (-2.5V -- +2.5V)
319		 2 == unipolar 5V  (0V -- +5V)
320*/
321static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it)
322{
323	/* int i; */
324	const struct dt2811_board *board = dev->board_ptr;
325	struct dt2811_private *devpriv;
326	int ret;
327	struct comedi_subdevice *s;
328
329	ret = comedi_request_region(dev, it->options[0], 0x8);
330	if (ret)
331		return ret;
332
333#if 0
334	outb(0, dev->iobase + DT2811_ADCSR);
335	udelay(100);
336	i = inb(dev->iobase + DT2811_ADDATLO);
337	i = inb(dev->iobase + DT2811_ADDATHI);
338#endif
339
340	ret = comedi_alloc_subdevices(dev, 4);
341	if (ret)
342		return ret;
343
344	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
345	if (!devpriv)
346		return -ENOMEM;
347
348	switch (it->options[2]) {
349	case 0:
350		devpriv->adc_mux = adc_singleended;
351		break;
352	case 1:
353		devpriv->adc_mux = adc_diff;
354		break;
355	case 2:
356		devpriv->adc_mux = adc_pseudo_diff;
357		break;
358	default:
359		devpriv->adc_mux = adc_singleended;
360		break;
361	}
362	switch (it->options[4]) {
363	case 0:
364		devpriv->dac_range[0] = dac_bipolar_5;
365		break;
366	case 1:
367		devpriv->dac_range[0] = dac_bipolar_2_5;
368		break;
369	case 2:
370		devpriv->dac_range[0] = dac_unipolar_5;
371		break;
372	default:
373		devpriv->dac_range[0] = dac_bipolar_5;
374		break;
375	}
376	switch (it->options[5]) {
377	case 0:
378		devpriv->dac_range[1] = dac_bipolar_5;
379		break;
380	case 1:
381		devpriv->dac_range[1] = dac_bipolar_2_5;
382		break;
383	case 2:
384		devpriv->dac_range[1] = dac_unipolar_5;
385		break;
386	default:
387		devpriv->dac_range[1] = dac_bipolar_5;
388		break;
389	}
390
391	s = &dev->subdevices[0];
392	/* initialize the ADC subdevice */
393	s->type = COMEDI_SUBD_AI;
394	s->subdev_flags = SDF_READABLE | SDF_GROUND;
395	s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16;
396	s->insn_read = dt2811_ai_insn;
397	s->maxdata = 0xfff;
398	switch (it->options[3]) {
399	case 0:
400	default:
401		s->range_table = board->bip_5;
402		break;
403	case 1:
404		s->range_table = board->bip_2_5;
405		break;
406	case 2:
407		s->range_table = board->unip_5;
408		break;
409	}
410
411	s = &dev->subdevices[1];
412	/* ao subdevice */
413	s->type = COMEDI_SUBD_AO;
414	s->subdev_flags = SDF_WRITABLE;
415	s->n_chan = 2;
416	s->maxdata = 0xfff;
417	s->range_table_list = devpriv->range_type_list;
418	devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]];
419	devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]];
420	s->insn_write = dt2811_ao_insn_write;
421	s->insn_read = comedi_readback_insn_read;
422
423	ret = comedi_alloc_subdev_readback(s);
424	if (ret)
425		return ret;
426
427	s = &dev->subdevices[2];
428	/* di subdevice */
429	s->type = COMEDI_SUBD_DI;
430	s->subdev_flags = SDF_READABLE;
431	s->n_chan = 8;
432	s->insn_bits = dt2811_di_insn_bits;
433	s->maxdata = 1;
434	s->range_table = &range_digital;
435
436	s = &dev->subdevices[3];
437	/* do subdevice */
438	s->type = COMEDI_SUBD_DO;
439	s->subdev_flags = SDF_WRITABLE;
440	s->n_chan = 8;
441	s->insn_bits = dt2811_do_insn_bits;
442	s->maxdata = 1;
443	s->state = 0;
444	s->range_table = &range_digital;
445
446	return 0;
447}
448
449static const struct dt2811_board boardtypes[] = {
450	{
451		.name		= "dt2811-pgh",
452		.bip_5		= &range_dt2811_pgh_ai_5_bipolar,
453		.bip_2_5	= &range_dt2811_pgh_ai_2_5_bipolar,
454		.unip_5		= &range_dt2811_pgh_ai_5_unipolar,
455	}, {
456		.name		= "dt2811-pgl",
457		.bip_5		= &range_dt2811_pgl_ai_5_bipolar,
458		.bip_2_5	= &range_dt2811_pgl_ai_2_5_bipolar,
459		.unip_5		= &range_dt2811_pgl_ai_5_unipolar,
460	},
461};
462
463static struct comedi_driver dt2811_driver = {
464	.driver_name	= "dt2811",
465	.module		= THIS_MODULE,
466	.attach		= dt2811_attach,
467	.detach		= comedi_legacy_detach,
468	.board_name	= &boardtypes[0].name,
469	.num_names	= ARRAY_SIZE(boardtypes),
470	.offset		= sizeof(struct dt2811_board),
471};
472module_comedi_driver(dt2811_driver);
473
474MODULE_AUTHOR("Comedi http://www.comedi.org");
475MODULE_DESCRIPTION("Comedi low-level driver");
476MODULE_LICENSE("GPL");
477