]> git.ipfire.org Git - thirdparty/kernel/linux.git/blame - drivers/staging/comedi/drivers/dt2814.c
staging: comedi: dt2814: Fix asynchronous command interrupt handling
[thirdparty/kernel/linux.git] / drivers / staging / comedi / drivers / dt2814.c
CommitLineData
e184e2be 1// SPDX-License-Identifier: GPL-2.0+
a211ea97 2/*
6adb21c2
RKM
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>
6adb21c2 8 */
a211ea97 9/*
6adb21c2
RKM
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 */
a211ea97 27
ce157f80 28#include <linux/module.h>
25436dc9 29#include <linux/interrupt.h>
a211ea97
DS
30#include "../comedidev.h"
31
a211ea97
DS
32#include <linux/delay.h>
33
a211ea97
DS
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
ca6f10ca 47struct dt2814_private {
a211ea97
DS
48 int ntrig;
49 int curadchan;
ca6f10ca
BP
50};
51
a211ea97
DS
52#define DT2814_TIMEOUT 10
53#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
54
7a3f3a70
IA
55static 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
70static 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
82c7e864
HS
92static 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
0a85b6f0
MT
105static int dt2814_ai_insn_read(struct comedi_device *dev,
106 struct comedi_subdevice *s,
107 struct comedi_insn *insn, unsigned int *data)
a211ea97 108{
82c7e864 109 int n, hi, lo;
a211ea97 110 int chan;
82c7e864 111 int ret;
a211ea97 112
7a3f3a70 113 dt2814_ai_clear(dev); /* clear stale data or error */
a211ea97
DS
114 for (n = 0; n < insn->n; n++) {
115 chan = CR_CHAN(insn->chanspec);
116
117 outb(chan, dev->iobase + DT2814_CSR);
82c7e864
HS
118
119 ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
120 if (ret)
121 return ret;
a211ea97
DS
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
132static 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
0a85b6f0
MT
151static int dt2814_ai_cmdtest(struct comedi_device *dev,
152 struct comedi_subdevice *s, struct comedi_cmd *cmd)
a211ea97
DS
153{
154 int err = 0;
704b0050 155 unsigned int arg;
a211ea97 156
27020ffe 157 /* Step 1 : check if triggers are trivially valid */
a211ea97 158
71bb49d0
IA
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);
a211ea97
DS
164
165 if (err)
166 return 1;
167
27020ffe 168 /* Step 2a : make sure trigger sources are unique */
a211ea97 169
71bb49d0 170 err |= comedi_check_trigger_is_unique(cmd->stop_src);
27020ffe
HS
171
172 /* Step 2b : and mutually compatible */
a211ea97
DS
173
174 if (err)
175 return 2;
176
2345dc8b 177 /* Step 3: check if arguments are trivially valid */
a211ea97 178
71bb49d0 179 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
2345dc8b 180
71bb49d0
IA
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);
2345dc8b 184
71bb49d0
IA
185 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
186 cmd->chanlist_len);
2345dc8b
HS
187
188 if (cmd->stop_src == TRIG_COUNT)
71bb49d0 189 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
2345dc8b 190 else /* TRIG_NONE */
71bb49d0 191 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
a211ea97
DS
192
193 if (err)
194 return 3;
195
196 /* step 4: fix up any arguments */
197
704b0050 198 arg = cmd->scan_begin_arg;
a207c12f 199 dt2814_ns_to_timer(&arg, cmd->flags);
71bb49d0 200 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
a211ea97
DS
201
202 if (err)
203 return 4;
204
205 return 0;
206}
207
da91b269 208static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
a211ea97 209{
9a1a6cf8 210 struct dt2814_private *devpriv = dev->private;
ea6d0d4c 211 struct comedi_cmd *cmd = &s->async->cmd;
a211ea97
DS
212 int chan;
213 int trigvar;
214
7a3f3a70 215 dt2814_ai_clear(dev); /* clear stale data or error */
a207c12f 216 trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
a211ea97
DS
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;
a211ea97
DS
224}
225
3d7b3101
IA
226static int dt2814_ai_cancel(struct comedi_device *dev,
227 struct comedi_subdevice *s)
228{
229 unsigned int status;
230 unsigned long flags;
231
232 spin_lock_irqsave(&dev->spinlock, flags);
233 status = inb(dev->iobase + DT2814_CSR);
234 if (status & DT2814_ENB) {
235 /*
236 * Clear the timed trigger enable bit.
237 *
238 * Note: turning off timed mode triggers another
239 * sample. This will be mopped up by the calls to
240 * dt2814_ai_clear().
241 */
242 outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR);
243 }
244 spin_unlock_irqrestore(&dev->spinlock, flags);
245 return 0;
246}
247
7806012e
HS
248static irqreturn_t dt2814_interrupt(int irq, void *d)
249{
7806012e 250 struct comedi_device *dev = d;
89d48385 251 struct comedi_subdevice *s = dev->read_subdev;
3d7b3101
IA
252 struct comedi_async *async;
253 unsigned int lo, hi;
254 unsigned short data;
255 unsigned int status;
7806012e
HS
256
257 if (!dev->attached) {
dffe87cd 258 dev_err(dev->class_dev, "spurious interrupt\n");
7806012e
HS
259 return IRQ_HANDLED;
260 }
261
3d7b3101 262 async = s->async;
7806012e 263
3d7b3101
IA
264 spin_lock(&dev->spinlock);
265
266 status = inb(dev->iobase + DT2814_CSR);
267 if (!(status & DT2814_ENB)) {
268 /* Timed acquisition not enabled. Nothing to do. */
269 spin_unlock(&dev->spinlock);
270 return IRQ_HANDLED;
271 }
272
273 if (!(status & (DT2814_FINISH | DT2814_ERR))) {
274 /* Spurious interrupt? */
275 spin_unlock(&dev->spinlock);
276 return IRQ_HANDLED;
277 }
278
279 /* Read data or clear error. */
280 hi = inb(dev->iobase + DT2814_DATA);
281 lo = inb(dev->iobase + DT2814_DATA);
282
283 data = (hi << 4) | (lo >> 4);
284
285 if (status & DT2814_ERR) {
286 async->events |= COMEDI_CB_ERROR;
287 } else {
288 comedi_buf_write_samples(s, &data, 1);
289 if (async->cmd.stop_src == TRIG_COUNT &&
290 async->scans_done >= async->cmd.stop_arg) {
291 async->events |= COMEDI_CB_EOA;
292 }
293 }
294 if (async->events & COMEDI_CB_CANCEL_MASK) {
6adb21c2 295 /*
3d7b3101
IA
296 * Disable timed mode.
297 *
46ffba06
IA
298 * Note: turning off timed mode triggers another
299 * sample. This will be mopped up by the calls to
300 * dt2814_ai_clear().
6adb21c2 301 */
3d7b3101 302 outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR);
7806012e 303 }
3d7b3101
IA
304
305 spin_unlock(&dev->spinlock);
306
001cce48 307 comedi_handle_events(dev, s);
7806012e
HS
308 return IRQ_HANDLED;
309}
310
da91b269 311static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
a211ea97 312{
9a1a6cf8 313 struct dt2814_private *devpriv;
34c43922 314 struct comedi_subdevice *s;
c5a9595b 315 int ret;
a211ea97 316
862755ec 317 ret = comedi_request_region(dev, it->options[0], 0x2);
c5c18cd3
HS
318 if (ret)
319 return ret;
a211ea97
DS
320
321 outb(0, dev->iobase + DT2814_CSR);
5fc336c6 322 if (dt2814_ai_clear(dev)) {
323cd355 323 dev_err(dev->class_dev, "reset error (fatal)\n");
a211ea97
DS
324 return -EIO;
325 }
a211ea97 326
c5a9595b
HS
327 if (it->options[1]) {
328 ret = request_irq(it->options[1], dt2814_interrupt, 0,
329 dev->board_name, dev);
330 if (ret == 0)
331 dev->irq = it->options[1];
a211ea97
DS
332 }
333
2f0b9d08 334 ret = comedi_alloc_subdevices(dev, 1);
8b6c5694 335 if (ret)
a211ea97 336 return ret;
c3744138 337
0bdab509 338 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
c34fa261
HS
339 if (!devpriv)
340 return -ENOMEM;
a211ea97 341
92af10e1 342 s = &dev->subdevices[0];
a211ea97 343 s->type = COMEDI_SUBD_AI;
c5a9595b 344 s->subdev_flags = SDF_READABLE | SDF_GROUND;
a211ea97 345 s->n_chan = 16; /* XXX */
a211ea97 346 s->insn_read = dt2814_ai_insn_read;
a211ea97
DS
347 s->maxdata = 0xfff;
348 s->range_table = &range_unknown; /* XXX */
c5a9595b
HS
349 if (dev->irq) {
350 dev->read_subdev = s;
351 s->subdev_flags |= SDF_CMD_READ;
352 s->len_chanlist = 1;
353 s->do_cmd = dt2814_ai_cmd;
354 s->do_cmdtest = dt2814_ai_cmdtest;
3d7b3101 355 s->cancel = dt2814_ai_cancel;
c5a9595b 356 }
a211ea97
DS
357
358 return 0;
359}
360
7806012e
HS
361static struct comedi_driver dt2814_driver = {
362 .driver_name = "dt2814",
363 .module = THIS_MODULE,
364 .attach = dt2814_attach,
3d1fe3f7 365 .detach = comedi_legacy_detach,
7806012e
HS
366};
367module_comedi_driver(dt2814_driver);
90f703d3 368
f8cdbd4f 369MODULE_AUTHOR("Comedi https://www.comedi.org");
90f703d3
AT
370MODULE_DESCRIPTION("Comedi low-level driver");
371MODULE_LICENSE("GPL");