]>
Commit | Line | Data |
---|---|---|
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 | 47 | struct 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 |
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 | ||
82c7e864 HS |
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 | ||
0a85b6f0 MT |
105 | static 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 | ||
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 | ||
0a85b6f0 MT |
151 | static 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 | 208 | static 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 |
226 | static 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 |
248 | static 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 | 311 | static 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 |
361 | static 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 | }; |
367 | module_comedi_driver(dt2814_driver); | |
90f703d3 | 368 | |
f8cdbd4f | 369 | MODULE_AUTHOR("Comedi https://www.comedi.org"); |
90f703d3 AT |
370 | MODULE_DESCRIPTION("Comedi low-level driver"); |
371 | MODULE_LICENSE("GPL"); |