1/*
2   comedi/drivers/dt2815.c
3   Hardware driver for Data Translation DT2815
4
5   COMEDI - Linux Control and Measurement Device Interface
6   Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
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/*
19Driver: dt2815
20Description: Data Translation DT2815
21Author: ds
22Status: mostly complete, untested
23Devices: [Data Translation] DT2815 (dt2815)
24
25I'm not sure anyone has ever tested this board.  If you have information
26contrary, please update.
27
28Configuration options:
29  [0] - I/O port base base address
30  [1] - IRQ (unused)
31  [2] - Voltage unipolar/bipolar configuration
32	0 == unipolar 5V  (0V -- +5V)
33	1 == bipolar 5V  (-5V -- +5V)
34  [3] - Current offset configuration
35	0 == disabled  (0mA -- +32mAV)
36	1 == enabled  (+4mA -- +20mAV)
37  [4] - Firmware program configuration
38	0 == program 1 (see manual table 5-4)
39	1 == program 2 (see manual table 5-4)
40	2 == program 3 (see manual table 5-4)
41	3 == program 4 (see manual table 5-4)
42  [5] - Analog output 0 range configuration
43	0 == voltage
44	1 == current
45  [6] - Analog output 1 range configuration (same options)
46  [7] - Analog output 2 range configuration (same options)
47  [8] - Analog output 3 range configuration (same options)
48  [9] - Analog output 4 range configuration (same options)
49  [10] - Analog output 5 range configuration (same options)
50  [11] - Analog output 6 range configuration (same options)
51  [12] - Analog output 7 range configuration (same options)
52*/
53
54#include <linux/module.h>
55#include "../comedidev.h"
56
57#include <linux/delay.h>
58
59#define DT2815_DATA 0
60#define DT2815_STATUS 1
61
62struct dt2815_private {
63
64	const struct comedi_lrange *range_type_list[8];
65	unsigned int ao_readback[8];
66};
67
68static int dt2815_ao_status(struct comedi_device *dev,
69			    struct comedi_subdevice *s,
70			    struct comedi_insn *insn,
71			    unsigned long context)
72{
73	unsigned int status;
74
75	status = inb(dev->iobase + DT2815_STATUS);
76	if (status == context)
77		return 0;
78	return -EBUSY;
79}
80
81static int dt2815_ao_insn_read(struct comedi_device *dev,
82			       struct comedi_subdevice *s,
83			       struct comedi_insn *insn, unsigned int *data)
84{
85	struct dt2815_private *devpriv = dev->private;
86	int i;
87	int chan = CR_CHAN(insn->chanspec);
88
89	for (i = 0; i < insn->n; i++)
90		data[i] = devpriv->ao_readback[chan];
91
92	return i;
93}
94
95static int dt2815_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
96			  struct comedi_insn *insn, unsigned int *data)
97{
98	struct dt2815_private *devpriv = dev->private;
99	int i;
100	int chan = CR_CHAN(insn->chanspec);
101	unsigned int lo, hi;
102	int ret;
103
104	for (i = 0; i < insn->n; i++) {
105		lo = ((data[i] & 0x0f) << 4) | (chan << 1) | 0x01;
106		hi = (data[i] & 0xff0) >> 4;
107
108		ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x00);
109		if (ret)
110			return ret;
111
112		outb(lo, dev->iobase + DT2815_DATA);
113
114		ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x10);
115		if (ret)
116			return ret;
117
118		devpriv->ao_readback[chan] = data[i];
119	}
120	return i;
121}
122
123/*
124  options[0]   Board base address
125  options[1]   IRQ (not applicable)
126  options[2]   Voltage unipolar/bipolar configuration
127		0 == unipolar 5V  (0V -- +5V)
128		1 == bipolar 5V  (-5V -- +5V)
129  options[3]   Current offset configuration
130		0 == disabled  (0mA -- +32mAV)
131		1 == enabled  (+4mA -- +20mAV)
132  options[4]   Firmware program configuration
133		0 == program 1 (see manual table 5-4)
134		1 == program 2 (see manual table 5-4)
135		2 == program 3 (see manual table 5-4)
136		3 == program 4 (see manual table 5-4)
137  options[5]   Analog output 0 range configuration
138		0 == voltage
139		1 == current
140  options[6]   Analog output 1 range configuration
141  ...
142  options[12]   Analog output 7 range configuration
143		0 == voltage
144		1 == current
145 */
146
147static int dt2815_attach(struct comedi_device *dev, struct comedi_devconfig *it)
148{
149	struct dt2815_private *devpriv;
150	struct comedi_subdevice *s;
151	int i;
152	const struct comedi_lrange *current_range_type, *voltage_range_type;
153	int ret;
154
155	ret = comedi_request_region(dev, it->options[0], 0x2);
156	if (ret)
157		return ret;
158
159	ret = comedi_alloc_subdevices(dev, 1);
160	if (ret)
161		return ret;
162
163	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
164	if (!devpriv)
165		return -ENOMEM;
166
167	s = &dev->subdevices[0];
168	/* ao subdevice */
169	s->type = COMEDI_SUBD_AO;
170	s->subdev_flags = SDF_WRITABLE;
171	s->maxdata = 0xfff;
172	s->n_chan = 8;
173	s->insn_write = dt2815_ao_insn;
174	s->insn_read = dt2815_ao_insn_read;
175	s->range_table_list = devpriv->range_type_list;
176
177	current_range_type = (it->options[3])
178	    ? &range_4_20mA : &range_0_32mA;
179	voltage_range_type = (it->options[2])
180	    ? &range_bipolar5 : &range_unipolar5;
181	for (i = 0; i < 8; i++) {
182		devpriv->range_type_list[i] = (it->options[5 + i])
183		    ? current_range_type : voltage_range_type;
184	}
185
186	/* Init the 2815 */
187	outb(0x00, dev->iobase + DT2815_STATUS);
188	for (i = 0; i < 100; i++) {
189		/* This is incredibly slow (approx 20 ms) */
190		unsigned int status;
191
192		udelay(1000);
193		status = inb(dev->iobase + DT2815_STATUS);
194		if (status == 4) {
195			unsigned int program;
196
197			program = (it->options[4] & 0x3) << 3 | 0x7;
198			outb(program, dev->iobase + DT2815_DATA);
199			dev_dbg(dev->class_dev, "program: 0x%x (@t=%d)\n",
200				program, i);
201			break;
202		} else if (status != 0x00) {
203			dev_dbg(dev->class_dev,
204				"unexpected status 0x%x (@t=%d)\n",
205				status, i);
206			if (status & 0x60)
207				outb(0x00, dev->iobase + DT2815_STATUS);
208		}
209	}
210
211	return 0;
212}
213
214static struct comedi_driver dt2815_driver = {
215	.driver_name	= "dt2815",
216	.module		= THIS_MODULE,
217	.attach		= dt2815_attach,
218	.detach		= comedi_legacy_detach,
219};
220module_comedi_driver(dt2815_driver);
221
222MODULE_AUTHOR("Comedi http://www.comedi.org");
223MODULE_DESCRIPTION("Comedi low-level driver");
224MODULE_LICENSE("GPL");
225