]> git.ipfire.org Git - thirdparty/kernel/linux.git/blob - drivers/staging/comedi/drivers/dt2814.c
da4dc4df3a9508eae23d9e4bde3069b874b20d2f
[thirdparty/kernel/linux.git] / drivers / staging / comedi / drivers / dt2814.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * comedi/drivers/dt2814.c
4 * Hardware driver for Data Translation DT2814
5 *
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
8 */
9 /*
10 * Driver: dt2814
11 * Description: Data Translation DT2814
12 * Author: ds
13 * Status: complete
14 * Devices: [Data Translation] DT2814 (dt2814)
15 *
16 * Configuration options:
17 * [0] - I/O port base address
18 * [1] - IRQ
19 *
20 * This card has 16 analog inputs multiplexed onto a 12 bit ADC. There
21 * is a minimally useful onboard clock. The base frequency for the
22 * clock is selected by jumpers, and the clock divider can be selected
23 * via programmed I/O. Unfortunately, the clock divider can only be
24 * a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In
25 * addition, the clock does not seem to be very accurate.
26 */
27
28 #include <linux/module.h>
29 #include <linux/interrupt.h>
30 #include "../comedidev.h"
31
32 #include <linux/delay.h>
33
34 #define DT2814_CSR 0
35 #define DT2814_DATA 1
36
37 /*
38 * flags
39 */
40
41 #define DT2814_FINISH 0x80
42 #define DT2814_ERR 0x40
43 #define DT2814_BUSY 0x20
44 #define DT2814_ENB 0x10
45 #define DT2814_CHANMASK 0x0f
46
47 struct dt2814_private {
48 int ntrig;
49 int curadchan;
50 };
51
52 #define DT2814_TIMEOUT 10
53 #define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
54
55 static int dt2814_ai_notbusy(struct comedi_device *dev,
56 struct comedi_subdevice *s,
57 struct comedi_insn *insn,
58 unsigned long context)
59 {
60 unsigned int status;
61
62 status = inb(dev->iobase + DT2814_CSR);
63 if (context)
64 *(unsigned int *)context = status;
65 if (status & DT2814_BUSY)
66 return -EBUSY;
67 return 0;
68 }
69
70 static int dt2814_ai_clear(struct comedi_device *dev)
71 {
72 unsigned int status = 0;
73 int ret;
74
75 /* Wait until not busy and get status register value. */
76 ret = comedi_timeout(dev, NULL, NULL, dt2814_ai_notbusy,
77 (unsigned long)&status);
78 if (ret)
79 return ret;
80
81 if (status & (DT2814_FINISH | DT2814_ERR)) {
82 /*
83 * There unread data, or the error flag is set.
84 * Read the data register twice to clear the condition.
85 */
86 inb(dev->iobase + DT2814_DATA);
87 inb(dev->iobase + DT2814_DATA);
88 }
89 return 0;
90 }
91
92 static int dt2814_ai_eoc(struct comedi_device *dev,
93 struct comedi_subdevice *s,
94 struct comedi_insn *insn,
95 unsigned long context)
96 {
97 unsigned int status;
98
99 status = inb(dev->iobase + DT2814_CSR);
100 if (status & DT2814_FINISH)
101 return 0;
102 return -EBUSY;
103 }
104
105 static int dt2814_ai_insn_read(struct comedi_device *dev,
106 struct comedi_subdevice *s,
107 struct comedi_insn *insn, unsigned int *data)
108 {
109 int n, hi, lo;
110 int chan;
111 int ret;
112
113 dt2814_ai_clear(dev); /* clear stale data or error */
114 for (n = 0; n < insn->n; n++) {
115 chan = CR_CHAN(insn->chanspec);
116
117 outb(chan, dev->iobase + DT2814_CSR);
118
119 ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
120 if (ret)
121 return ret;
122
123 hi = inb(dev->iobase + DT2814_DATA);
124 lo = inb(dev->iobase + DT2814_DATA);
125
126 data[n] = (hi << 4) | (lo >> 4);
127 }
128
129 return n;
130 }
131
132 static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
133 {
134 int i;
135 unsigned int f;
136
137 /* XXX ignores flags */
138
139 f = 10000; /* ns */
140 for (i = 0; i < 8; i++) {
141 if ((2 * (*ns)) < (f * 11))
142 break;
143 f *= 10;
144 }
145
146 *ns = f;
147
148 return i;
149 }
150
151 static int dt2814_ai_cmdtest(struct comedi_device *dev,
152 struct comedi_subdevice *s, struct comedi_cmd *cmd)
153 {
154 int err = 0;
155 unsigned int arg;
156
157 /* Step 1 : check if triggers are trivially valid */
158
159 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
160 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
161 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
162 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
163 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
164
165 if (err)
166 return 1;
167
168 /* Step 2a : make sure trigger sources are unique */
169
170 err |= comedi_check_trigger_is_unique(cmd->stop_src);
171
172 /* Step 2b : and mutually compatible */
173
174 if (err)
175 return 2;
176
177 /* Step 3: check if arguments are trivially valid */
178
179 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
180
181 err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
182 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
183 DT2814_MAX_SPEED);
184
185 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
186 cmd->chanlist_len);
187
188 if (cmd->stop_src == TRIG_COUNT)
189 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
190 else /* TRIG_NONE */
191 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
192
193 if (err)
194 return 3;
195
196 /* step 4: fix up any arguments */
197
198 arg = cmd->scan_begin_arg;
199 dt2814_ns_to_timer(&arg, cmd->flags);
200 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
201
202 if (err)
203 return 4;
204
205 return 0;
206 }
207
208 static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
209 {
210 struct dt2814_private *devpriv = dev->private;
211 struct comedi_cmd *cmd = &s->async->cmd;
212 int chan;
213 int trigvar;
214
215 dt2814_ai_clear(dev); /* clear stale data or error */
216 trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
217
218 chan = CR_CHAN(cmd->chanlist[0]);
219
220 devpriv->ntrig = cmd->stop_arg;
221 outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
222
223 return 0;
224 }
225
226 static irqreturn_t dt2814_interrupt(int irq, void *d)
227 {
228 struct comedi_device *dev = d;
229 struct dt2814_private *devpriv = dev->private;
230 struct comedi_subdevice *s = dev->read_subdev;
231
232 if (!dev->attached) {
233 dev_err(dev->class_dev, "spurious interrupt\n");
234 return IRQ_HANDLED;
235 }
236
237 inb(dev->iobase + DT2814_DATA);
238 inb(dev->iobase + DT2814_DATA);
239
240 if (!(--devpriv->ntrig)) {
241 outb(0, dev->iobase + DT2814_CSR);
242 /*
243 * Note: turning off timed mode triggers another
244 * sample. This will be mopped up by the calls to
245 * dt2814_ai_clear().
246 */
247
248 s->async->events |= COMEDI_CB_EOA;
249 }
250 comedi_handle_events(dev, s);
251 return IRQ_HANDLED;
252 }
253
254 static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
255 {
256 struct dt2814_private *devpriv;
257 struct comedi_subdevice *s;
258 int ret;
259
260 ret = comedi_request_region(dev, it->options[0], 0x2);
261 if (ret)
262 return ret;
263
264 outb(0, dev->iobase + DT2814_CSR);
265 if (dt2814_ai_clear(dev)) {
266 dev_err(dev->class_dev, "reset error (fatal)\n");
267 return -EIO;
268 }
269
270 if (it->options[1]) {
271 ret = request_irq(it->options[1], dt2814_interrupt, 0,
272 dev->board_name, dev);
273 if (ret == 0)
274 dev->irq = it->options[1];
275 }
276
277 ret = comedi_alloc_subdevices(dev, 1);
278 if (ret)
279 return ret;
280
281 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
282 if (!devpriv)
283 return -ENOMEM;
284
285 s = &dev->subdevices[0];
286 s->type = COMEDI_SUBD_AI;
287 s->subdev_flags = SDF_READABLE | SDF_GROUND;
288 s->n_chan = 16; /* XXX */
289 s->insn_read = dt2814_ai_insn_read;
290 s->maxdata = 0xfff;
291 s->range_table = &range_unknown; /* XXX */
292 if (dev->irq) {
293 dev->read_subdev = s;
294 s->subdev_flags |= SDF_CMD_READ;
295 s->len_chanlist = 1;
296 s->do_cmd = dt2814_ai_cmd;
297 s->do_cmdtest = dt2814_ai_cmdtest;
298 }
299
300 return 0;
301 }
302
303 static struct comedi_driver dt2814_driver = {
304 .driver_name = "dt2814",
305 .module = THIS_MODULE,
306 .attach = dt2814_attach,
307 .detach = comedi_legacy_detach,
308 };
309 module_comedi_driver(dt2814_driver);
310
311 MODULE_AUTHOR("Comedi https://www.comedi.org");
312 MODULE_DESCRIPTION("Comedi low-level driver");
313 MODULE_LICENSE("GPL");