1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/comedi_test.c |
4 | * |
5 | * Generates fake waveform signals that can be read through |
6 | * the command interface. It does _not_ read from any board; |
7 | * it just generates deterministic waveforms. |
8 | * Useful for various testing purposes. |
9 | * |
10 | * Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de> |
11 | * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> |
12 | * |
13 | * COMEDI - Linux Control and Measurement Device Interface |
14 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
15 | */ |
16 | |
17 | /* |
18 | * Driver: comedi_test |
19 | * Description: generates fake waveforms |
20 | * Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess |
21 | * <fmhess@users.sourceforge.net>, ds |
22 | * Devices: |
23 | * Status: works |
24 | * Updated: Sat, 16 Mar 2002 17:34:48 -0800 |
25 | * |
26 | * This driver is mainly for testing purposes, but can also be used to |
27 | * generate sample waveforms on systems that don't have data acquisition |
28 | * hardware. |
29 | * |
30 | * Auto-configuration is the default mode if no parameter is supplied during |
31 | * module loading. Manual configuration requires COMEDI userspace tool. |
32 | * To disable auto-configuration mode, pass "noauto=1" parameter for module |
33 | * loading. Refer modinfo or MODULE_PARM_DESC description below for details. |
34 | * |
35 | * Auto-configuration options: |
36 | * Refer modinfo or MODULE_PARM_DESC description below for details. |
37 | * |
38 | * Manual configuration options: |
39 | * [0] - Amplitude in microvolts for fake waveforms (default 1 volt) |
40 | * [1] - Period in microseconds for fake waveforms (default 0.1 sec) |
41 | * |
42 | * Generates a sawtooth wave on channel 0, square wave on channel 1, additional |
43 | * waveforms could be added to other channels (currently they return flatline |
44 | * zero volts). |
45 | */ |
46 | |
47 | #include <linux/module.h> |
48 | #include <linux/comedi/comedidev.h> |
49 | #include <asm/div64.h> |
50 | #include <linux/timer.h> |
51 | #include <linux/ktime.h> |
52 | #include <linux/jiffies.h> |
53 | #include <linux/device.h> |
54 | #include <linux/kdev_t.h> |
55 | |
56 | #define N_CHANS 8 |
57 | #define DEV_NAME "comedi_testd" |
58 | #define CLASS_NAME "comedi_test" |
59 | |
60 | static bool config_mode; |
61 | static unsigned int set_amplitude; |
62 | static unsigned int set_period; |
63 | static const struct class ctcls = { |
64 | .name = CLASS_NAME, |
65 | }; |
66 | static struct device *ctdev; |
67 | |
68 | module_param_named(noauto, config_mode, bool, 0444); |
69 | MODULE_PARM_DESC(noauto, "Disable auto-configuration: (1=disable [defaults to enable])" ); |
70 | |
71 | module_param_named(amplitude, set_amplitude, uint, 0444); |
72 | MODULE_PARM_DESC(amplitude, "Set auto mode wave amplitude in microvolts: (defaults to 1 volt)" ); |
73 | |
74 | module_param_named(period, set_period, uint, 0444); |
75 | MODULE_PARM_DESC(period, "Set auto mode wave period in microseconds: (defaults to 0.1 sec)" ); |
76 | |
77 | /* Data unique to this driver */ |
78 | struct waveform_private { |
79 | struct timer_list ai_timer; /* timer for AI commands */ |
80 | u64 ai_convert_time; /* time of next AI conversion in usec */ |
81 | unsigned int wf_amplitude; /* waveform amplitude in microvolts */ |
82 | unsigned int wf_period; /* waveform period in microseconds */ |
83 | unsigned int wf_current; /* current time in waveform period */ |
84 | unsigned int ai_scan_period; /* AI scan period in usec */ |
85 | unsigned int ai_convert_period; /* AI conversion period in usec */ |
86 | struct timer_list ao_timer; /* timer for AO commands */ |
87 | struct comedi_device *dev; /* parent comedi device */ |
88 | u64 ao_last_scan_time; /* time of previous AO scan in usec */ |
89 | unsigned int ao_scan_period; /* AO scan period in usec */ |
90 | bool ai_timer_enable:1; /* should AI timer be running? */ |
91 | bool ao_timer_enable:1; /* should AO timer be running? */ |
92 | unsigned short ao_loopbacks[N_CHANS]; |
93 | }; |
94 | |
95 | /* fake analog input ranges */ |
96 | static const struct comedi_lrange waveform_ai_ranges = { |
97 | 2, { |
98 | BIP_RANGE(10), |
99 | BIP_RANGE(5) |
100 | } |
101 | }; |
102 | |
103 | static unsigned short fake_sawtooth(struct comedi_device *dev, |
104 | unsigned int range_index, |
105 | unsigned int current_time) |
106 | { |
107 | struct waveform_private *devpriv = dev->private; |
108 | struct comedi_subdevice *s = dev->read_subdev; |
109 | unsigned int offset = s->maxdata / 2; |
110 | u64 value; |
111 | const struct comedi_krange *krange = |
112 | &s->range_table->range[range_index]; |
113 | u64 binary_amplitude; |
114 | |
115 | binary_amplitude = s->maxdata; |
116 | binary_amplitude *= devpriv->wf_amplitude; |
117 | do_div(binary_amplitude, krange->max - krange->min); |
118 | |
119 | value = current_time; |
120 | value *= binary_amplitude * 2; |
121 | do_div(value, devpriv->wf_period); |
122 | value += offset; |
123 | /* get rid of sawtooth's dc offset and clamp value */ |
124 | if (value < binary_amplitude) { |
125 | value = 0; /* negative saturation */ |
126 | } else { |
127 | value -= binary_amplitude; |
128 | if (value > s->maxdata) |
129 | value = s->maxdata; /* positive saturation */ |
130 | } |
131 | |
132 | return value; |
133 | } |
134 | |
135 | static unsigned short fake_squarewave(struct comedi_device *dev, |
136 | unsigned int range_index, |
137 | unsigned int current_time) |
138 | { |
139 | struct waveform_private *devpriv = dev->private; |
140 | struct comedi_subdevice *s = dev->read_subdev; |
141 | unsigned int offset = s->maxdata / 2; |
142 | u64 value; |
143 | const struct comedi_krange *krange = |
144 | &s->range_table->range[range_index]; |
145 | |
146 | value = s->maxdata; |
147 | value *= devpriv->wf_amplitude; |
148 | do_div(value, krange->max - krange->min); |
149 | |
150 | /* get one of two values for square-wave and clamp */ |
151 | if (current_time < devpriv->wf_period / 2) { |
152 | if (offset < value) |
153 | value = 0; /* negative saturation */ |
154 | else |
155 | value = offset - value; |
156 | } else { |
157 | value += offset; |
158 | if (value > s->maxdata) |
159 | value = s->maxdata; /* positive saturation */ |
160 | } |
161 | |
162 | return value; |
163 | } |
164 | |
165 | static unsigned short fake_flatline(struct comedi_device *dev, |
166 | unsigned int range_index, |
167 | unsigned int current_time) |
168 | { |
169 | return dev->read_subdev->maxdata / 2; |
170 | } |
171 | |
172 | /* generates a different waveform depending on what channel is read */ |
173 | static unsigned short fake_waveform(struct comedi_device *dev, |
174 | unsigned int channel, unsigned int range, |
175 | unsigned int current_time) |
176 | { |
177 | enum { |
178 | SAWTOOTH_CHAN, |
179 | SQUARE_CHAN, |
180 | }; |
181 | switch (channel) { |
182 | case SAWTOOTH_CHAN: |
183 | return fake_sawtooth(dev, range_index: range, current_time); |
184 | case SQUARE_CHAN: |
185 | return fake_squarewave(dev, range_index: range, current_time); |
186 | default: |
187 | break; |
188 | } |
189 | |
190 | return fake_flatline(dev, range_index: range, current_time); |
191 | } |
192 | |
193 | /* |
194 | * This is the background routine used to generate arbitrary data. |
195 | * It should run in the background; therefore it is scheduled by |
196 | * a timer mechanism. |
197 | */ |
198 | static void waveform_ai_timer(struct timer_list *t) |
199 | { |
200 | struct waveform_private *devpriv = from_timer(devpriv, t, ai_timer); |
201 | struct comedi_device *dev = devpriv->dev; |
202 | struct comedi_subdevice *s = dev->read_subdev; |
203 | struct comedi_async *async = s->async; |
204 | struct comedi_cmd *cmd = &async->cmd; |
205 | u64 now; |
206 | unsigned int nsamples; |
207 | unsigned int time_increment; |
208 | |
209 | now = ktime_to_us(kt: ktime_get()); |
210 | nsamples = comedi_nsamples_left(s, UINT_MAX); |
211 | |
212 | while (nsamples && devpriv->ai_convert_time < now) { |
213 | unsigned int chanspec = cmd->chanlist[async->cur_chan]; |
214 | unsigned short sample; |
215 | |
216 | sample = fake_waveform(dev, CR_CHAN(chanspec), |
217 | CR_RANGE(chanspec), current_time: devpriv->wf_current); |
218 | if (comedi_buf_write_samples(s, data: &sample, nsamples: 1) == 0) |
219 | goto overrun; |
220 | time_increment = devpriv->ai_convert_period; |
221 | if (async->scan_progress == 0) { |
222 | /* done last conversion in scan, so add dead time */ |
223 | time_increment += devpriv->ai_scan_period - |
224 | devpriv->ai_convert_period * |
225 | cmd->scan_end_arg; |
226 | } |
227 | devpriv->wf_current += time_increment; |
228 | if (devpriv->wf_current >= devpriv->wf_period) |
229 | devpriv->wf_current %= devpriv->wf_period; |
230 | devpriv->ai_convert_time += time_increment; |
231 | nsamples--; |
232 | } |
233 | |
234 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { |
235 | async->events |= COMEDI_CB_EOA; |
236 | } else { |
237 | if (devpriv->ai_convert_time > now) |
238 | time_increment = devpriv->ai_convert_time - now; |
239 | else |
240 | time_increment = 1; |
241 | spin_lock(lock: &dev->spinlock); |
242 | if (devpriv->ai_timer_enable) { |
243 | mod_timer(timer: &devpriv->ai_timer, |
244 | expires: jiffies + usecs_to_jiffies(u: time_increment)); |
245 | } |
246 | spin_unlock(lock: &dev->spinlock); |
247 | } |
248 | |
249 | overrun: |
250 | comedi_handle_events(dev, s); |
251 | } |
252 | |
253 | static int waveform_ai_cmdtest(struct comedi_device *dev, |
254 | struct comedi_subdevice *s, |
255 | struct comedi_cmd *cmd) |
256 | { |
257 | int err = 0; |
258 | unsigned int arg, limit; |
259 | |
260 | /* Step 1 : check if triggers are trivially valid */ |
261 | |
262 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
263 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, |
264 | TRIG_FOLLOW | TRIG_TIMER); |
265 | err |= comedi_check_trigger_src(src: &cmd->convert_src, |
266 | TRIG_NOW | TRIG_TIMER); |
267 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
268 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
269 | |
270 | if (err) |
271 | return 1; |
272 | |
273 | /* Step 2a : make sure trigger sources are unique */ |
274 | |
275 | err |= comedi_check_trigger_is_unique(src: cmd->convert_src); |
276 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
277 | |
278 | /* Step 2b : and mutually compatible */ |
279 | |
280 | if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) |
281 | err |= -EINVAL; /* scan period would be 0 */ |
282 | |
283 | if (err) |
284 | return 2; |
285 | |
286 | /* Step 3: check if arguments are trivially valid */ |
287 | |
288 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
289 | |
290 | if (cmd->convert_src == TRIG_NOW) { |
291 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
292 | } else { /* cmd->convert_src == TRIG_TIMER */ |
293 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
294 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, |
295 | NSEC_PER_USEC); |
296 | } |
297 | } |
298 | |
299 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
300 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
301 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ |
302 | err |= comedi_check_trigger_arg_min(arg: &cmd->scan_begin_arg, |
303 | NSEC_PER_USEC); |
304 | } |
305 | |
306 | err |= comedi_check_trigger_arg_min(arg: &cmd->chanlist_len, val: 1); |
307 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
308 | val: cmd->chanlist_len); |
309 | |
310 | if (cmd->stop_src == TRIG_COUNT) |
311 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
312 | else /* cmd->stop_src == TRIG_NONE */ |
313 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
314 | |
315 | if (err) |
316 | return 3; |
317 | |
318 | /* step 4: fix up any arguments */ |
319 | |
320 | if (cmd->convert_src == TRIG_TIMER) { |
321 | /* round convert_arg to nearest microsecond */ |
322 | arg = cmd->convert_arg; |
323 | arg = min(arg, |
324 | rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); |
325 | arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); |
326 | if (cmd->scan_begin_arg == TRIG_TIMER) { |
327 | /* limit convert_arg to keep scan_begin_arg in range */ |
328 | limit = UINT_MAX / cmd->scan_end_arg; |
329 | limit = rounddown(limit, (unsigned int)NSEC_PER_SEC); |
330 | arg = min(arg, limit); |
331 | } |
332 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
333 | } |
334 | |
335 | if (cmd->scan_begin_src == TRIG_TIMER) { |
336 | /* round scan_begin_arg to nearest microsecond */ |
337 | arg = cmd->scan_begin_arg; |
338 | arg = min(arg, |
339 | rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); |
340 | arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); |
341 | if (cmd->convert_src == TRIG_TIMER) { |
342 | /* but ensure scan_begin_arg is large enough */ |
343 | arg = max(arg, cmd->convert_arg * cmd->scan_end_arg); |
344 | } |
345 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: arg); |
346 | } |
347 | |
348 | if (err) |
349 | return 4; |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | static int waveform_ai_cmd(struct comedi_device *dev, |
355 | struct comedi_subdevice *s) |
356 | { |
357 | struct waveform_private *devpriv = dev->private; |
358 | struct comedi_cmd *cmd = &s->async->cmd; |
359 | unsigned int first_convert_time; |
360 | u64 wf_current; |
361 | |
362 | if (cmd->flags & CMDF_PRIORITY) { |
363 | dev_err(dev->class_dev, |
364 | "commands at RT priority not supported in this driver\n" ); |
365 | return -1; |
366 | } |
367 | |
368 | if (cmd->convert_src == TRIG_NOW) |
369 | devpriv->ai_convert_period = 0; |
370 | else /* cmd->convert_src == TRIG_TIMER */ |
371 | devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC; |
372 | |
373 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
374 | devpriv->ai_scan_period = devpriv->ai_convert_period * |
375 | cmd->scan_end_arg; |
376 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ |
377 | devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; |
378 | } |
379 | |
380 | /* |
381 | * Simulate first conversion to occur at convert period after |
382 | * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume |
383 | * the conversion timer starts immediately. If scan_begin_src is |
384 | * TRIG_TIMER, assume the conversion timer starts after the scan |
385 | * period. |
386 | */ |
387 | first_convert_time = devpriv->ai_convert_period; |
388 | if (cmd->scan_begin_src == TRIG_TIMER) |
389 | first_convert_time += devpriv->ai_scan_period; |
390 | devpriv->ai_convert_time = ktime_to_us(kt: ktime_get()) + |
391 | first_convert_time; |
392 | |
393 | /* Determine time within waveform period at time of conversion. */ |
394 | wf_current = devpriv->ai_convert_time; |
395 | devpriv->wf_current = do_div(wf_current, devpriv->wf_period); |
396 | |
397 | /* |
398 | * Schedule timer to expire just after first conversion time. |
399 | * Seem to need an extra jiffy here, otherwise timer expires slightly |
400 | * early! |
401 | */ |
402 | spin_lock_bh(lock: &dev->spinlock); |
403 | devpriv->ai_timer_enable = true; |
404 | devpriv->ai_timer.expires = |
405 | jiffies + usecs_to_jiffies(u: devpriv->ai_convert_period) + 1; |
406 | add_timer(timer: &devpriv->ai_timer); |
407 | spin_unlock_bh(lock: &dev->spinlock); |
408 | return 0; |
409 | } |
410 | |
411 | static int waveform_ai_cancel(struct comedi_device *dev, |
412 | struct comedi_subdevice *s) |
413 | { |
414 | struct waveform_private *devpriv = dev->private; |
415 | |
416 | spin_lock_bh(lock: &dev->spinlock); |
417 | devpriv->ai_timer_enable = false; |
418 | spin_unlock_bh(lock: &dev->spinlock); |
419 | if (in_softirq()) { |
420 | /* Assume we were called from the timer routine itself. */ |
421 | del_timer(timer: &devpriv->ai_timer); |
422 | } else { |
423 | del_timer_sync(timer: &devpriv->ai_timer); |
424 | } |
425 | return 0; |
426 | } |
427 | |
428 | static int waveform_ai_insn_read(struct comedi_device *dev, |
429 | struct comedi_subdevice *s, |
430 | struct comedi_insn *insn, unsigned int *data) |
431 | { |
432 | struct waveform_private *devpriv = dev->private; |
433 | int i, chan = CR_CHAN(insn->chanspec); |
434 | |
435 | for (i = 0; i < insn->n; i++) |
436 | data[i] = devpriv->ao_loopbacks[chan]; |
437 | |
438 | return insn->n; |
439 | } |
440 | |
441 | /* |
442 | * This is the background routine to handle AO commands, scheduled by |
443 | * a timer mechanism. |
444 | */ |
445 | static void waveform_ao_timer(struct timer_list *t) |
446 | { |
447 | struct waveform_private *devpriv = from_timer(devpriv, t, ao_timer); |
448 | struct comedi_device *dev = devpriv->dev; |
449 | struct comedi_subdevice *s = dev->write_subdev; |
450 | struct comedi_async *async = s->async; |
451 | struct comedi_cmd *cmd = &async->cmd; |
452 | u64 now; |
453 | u64 scans_since; |
454 | unsigned int scans_avail = 0; |
455 | |
456 | /* determine number of scan periods since last time */ |
457 | now = ktime_to_us(kt: ktime_get()); |
458 | scans_since = now - devpriv->ao_last_scan_time; |
459 | do_div(scans_since, devpriv->ao_scan_period); |
460 | if (scans_since) { |
461 | unsigned int i; |
462 | |
463 | /* determine scans in buffer, limit to scans to do this time */ |
464 | scans_avail = comedi_nscans_left(s, nscans: 0); |
465 | if (scans_avail > scans_since) |
466 | scans_avail = scans_since; |
467 | if (scans_avail) { |
468 | /* skip all but the last scan to save processing time */ |
469 | if (scans_avail > 1) { |
470 | unsigned int skip_bytes, nbytes; |
471 | |
472 | skip_bytes = |
473 | comedi_samples_to_bytes(s, nsamples: cmd->scan_end_arg * |
474 | (scans_avail - 1)); |
475 | nbytes = comedi_buf_read_alloc(s, n: skip_bytes); |
476 | comedi_buf_read_free(s, n: nbytes); |
477 | comedi_inc_scan_progress(s, num_bytes: nbytes); |
478 | if (nbytes < skip_bytes) { |
479 | /* unexpected underrun! (cancelled?) */ |
480 | async->events |= COMEDI_CB_OVERFLOW; |
481 | goto underrun; |
482 | } |
483 | } |
484 | /* output the last scan */ |
485 | for (i = 0; i < cmd->scan_end_arg; i++) { |
486 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
487 | unsigned short *pd; |
488 | |
489 | pd = &devpriv->ao_loopbacks[chan]; |
490 | |
491 | if (!comedi_buf_read_samples(s, data: pd, nsamples: 1)) { |
492 | /* unexpected underrun! (cancelled?) */ |
493 | async->events |= COMEDI_CB_OVERFLOW; |
494 | goto underrun; |
495 | } |
496 | } |
497 | /* advance time of last scan */ |
498 | devpriv->ao_last_scan_time += |
499 | (u64)scans_avail * devpriv->ao_scan_period; |
500 | } |
501 | } |
502 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { |
503 | async->events |= COMEDI_CB_EOA; |
504 | } else if (scans_avail < scans_since) { |
505 | async->events |= COMEDI_CB_OVERFLOW; |
506 | } else { |
507 | unsigned int time_inc = devpriv->ao_last_scan_time + |
508 | devpriv->ao_scan_period - now; |
509 | |
510 | spin_lock(lock: &dev->spinlock); |
511 | if (devpriv->ao_timer_enable) { |
512 | mod_timer(timer: &devpriv->ao_timer, |
513 | expires: jiffies + usecs_to_jiffies(u: time_inc)); |
514 | } |
515 | spin_unlock(lock: &dev->spinlock); |
516 | } |
517 | |
518 | underrun: |
519 | comedi_handle_events(dev, s); |
520 | } |
521 | |
522 | static int waveform_ao_inttrig_start(struct comedi_device *dev, |
523 | struct comedi_subdevice *s, |
524 | unsigned int trig_num) |
525 | { |
526 | struct waveform_private *devpriv = dev->private; |
527 | struct comedi_async *async = s->async; |
528 | struct comedi_cmd *cmd = &async->cmd; |
529 | |
530 | if (trig_num != cmd->start_arg) |
531 | return -EINVAL; |
532 | |
533 | async->inttrig = NULL; |
534 | |
535 | devpriv->ao_last_scan_time = ktime_to_us(kt: ktime_get()); |
536 | spin_lock_bh(lock: &dev->spinlock); |
537 | devpriv->ao_timer_enable = true; |
538 | devpriv->ao_timer.expires = |
539 | jiffies + usecs_to_jiffies(u: devpriv->ao_scan_period); |
540 | add_timer(timer: &devpriv->ao_timer); |
541 | spin_unlock_bh(lock: &dev->spinlock); |
542 | |
543 | return 1; |
544 | } |
545 | |
546 | static int waveform_ao_cmdtest(struct comedi_device *dev, |
547 | struct comedi_subdevice *s, |
548 | struct comedi_cmd *cmd) |
549 | { |
550 | int err = 0; |
551 | unsigned int arg; |
552 | |
553 | /* Step 1 : check if triggers are trivially valid */ |
554 | |
555 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_INT); |
556 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_TIMER); |
557 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_NOW); |
558 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
559 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
560 | |
561 | if (err) |
562 | return 1; |
563 | |
564 | /* Step 2a : make sure trigger sources are unique */ |
565 | |
566 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
567 | |
568 | /* Step 2b : and mutually compatible */ |
569 | |
570 | if (err) |
571 | return 2; |
572 | |
573 | /* Step 3: check if arguments are trivially valid */ |
574 | |
575 | err |= comedi_check_trigger_arg_min(arg: &cmd->scan_begin_arg, |
576 | NSEC_PER_USEC); |
577 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
578 | err |= comedi_check_trigger_arg_min(arg: &cmd->chanlist_len, val: 1); |
579 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
580 | val: cmd->chanlist_len); |
581 | if (cmd->stop_src == TRIG_COUNT) |
582 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
583 | else /* cmd->stop_src == TRIG_NONE */ |
584 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
585 | |
586 | if (err) |
587 | return 3; |
588 | |
589 | /* step 4: fix up any arguments */ |
590 | |
591 | /* round scan_begin_arg to nearest microsecond */ |
592 | arg = cmd->scan_begin_arg; |
593 | arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); |
594 | arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); |
595 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: arg); |
596 | |
597 | if (err) |
598 | return 4; |
599 | |
600 | return 0; |
601 | } |
602 | |
603 | static int waveform_ao_cmd(struct comedi_device *dev, |
604 | struct comedi_subdevice *s) |
605 | { |
606 | struct waveform_private *devpriv = dev->private; |
607 | struct comedi_cmd *cmd = &s->async->cmd; |
608 | |
609 | if (cmd->flags & CMDF_PRIORITY) { |
610 | dev_err(dev->class_dev, |
611 | "commands at RT priority not supported in this driver\n" ); |
612 | return -1; |
613 | } |
614 | |
615 | devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; |
616 | s->async->inttrig = waveform_ao_inttrig_start; |
617 | return 0; |
618 | } |
619 | |
620 | static int waveform_ao_cancel(struct comedi_device *dev, |
621 | struct comedi_subdevice *s) |
622 | { |
623 | struct waveform_private *devpriv = dev->private; |
624 | |
625 | s->async->inttrig = NULL; |
626 | spin_lock_bh(lock: &dev->spinlock); |
627 | devpriv->ao_timer_enable = false; |
628 | spin_unlock_bh(lock: &dev->spinlock); |
629 | if (in_softirq()) { |
630 | /* Assume we were called from the timer routine itself. */ |
631 | del_timer(timer: &devpriv->ao_timer); |
632 | } else { |
633 | del_timer_sync(timer: &devpriv->ao_timer); |
634 | } |
635 | return 0; |
636 | } |
637 | |
638 | static int waveform_ao_insn_write(struct comedi_device *dev, |
639 | struct comedi_subdevice *s, |
640 | struct comedi_insn *insn, unsigned int *data) |
641 | { |
642 | struct waveform_private *devpriv = dev->private; |
643 | int i, chan = CR_CHAN(insn->chanspec); |
644 | |
645 | for (i = 0; i < insn->n; i++) |
646 | devpriv->ao_loopbacks[chan] = data[i]; |
647 | |
648 | return insn->n; |
649 | } |
650 | |
651 | static int waveform_ai_insn_config(struct comedi_device *dev, |
652 | struct comedi_subdevice *s, |
653 | struct comedi_insn *insn, |
654 | unsigned int *data) |
655 | { |
656 | if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { |
657 | /* |
658 | * input: data[1], data[2] : scan_begin_src, convert_src |
659 | * output: data[1], data[2] : scan_begin_min, convert_min |
660 | */ |
661 | if (data[1] == TRIG_FOLLOW) { |
662 | /* exactly TRIG_FOLLOW case */ |
663 | data[1] = 0; |
664 | data[2] = NSEC_PER_USEC; |
665 | } else { |
666 | data[1] = NSEC_PER_USEC; |
667 | if (data[2] & TRIG_TIMER) |
668 | data[2] = NSEC_PER_USEC; |
669 | else |
670 | data[2] = 0; |
671 | } |
672 | return 0; |
673 | } |
674 | |
675 | return -EINVAL; |
676 | } |
677 | |
678 | static int waveform_ao_insn_config(struct comedi_device *dev, |
679 | struct comedi_subdevice *s, |
680 | struct comedi_insn *insn, |
681 | unsigned int *data) |
682 | { |
683 | if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { |
684 | /* we don't care about actual channels */ |
685 | data[1] = NSEC_PER_USEC; /* scan_begin_min */ |
686 | data[2] = 0; /* convert_min */ |
687 | return 0; |
688 | } |
689 | |
690 | return -EINVAL; |
691 | } |
692 | |
693 | static int waveform_common_attach(struct comedi_device *dev, |
694 | int amplitude, int period) |
695 | { |
696 | struct waveform_private *devpriv; |
697 | struct comedi_subdevice *s; |
698 | int i; |
699 | int ret; |
700 | |
701 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
702 | if (!devpriv) |
703 | return -ENOMEM; |
704 | |
705 | devpriv->wf_amplitude = amplitude; |
706 | devpriv->wf_period = period; |
707 | |
708 | ret = comedi_alloc_subdevices(dev, num_subdevices: 2); |
709 | if (ret) |
710 | return ret; |
711 | |
712 | s = &dev->subdevices[0]; |
713 | dev->read_subdev = s; |
714 | /* analog input subdevice */ |
715 | s->type = COMEDI_SUBD_AI; |
716 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; |
717 | s->n_chan = N_CHANS; |
718 | s->maxdata = 0xffff; |
719 | s->range_table = &waveform_ai_ranges; |
720 | s->len_chanlist = s->n_chan * 2; |
721 | s->insn_read = waveform_ai_insn_read; |
722 | s->do_cmd = waveform_ai_cmd; |
723 | s->do_cmdtest = waveform_ai_cmdtest; |
724 | s->cancel = waveform_ai_cancel; |
725 | s->insn_config = waveform_ai_insn_config; |
726 | |
727 | s = &dev->subdevices[1]; |
728 | dev->write_subdev = s; |
729 | /* analog output subdevice (loopback) */ |
730 | s->type = COMEDI_SUBD_AO; |
731 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; |
732 | s->n_chan = N_CHANS; |
733 | s->maxdata = 0xffff; |
734 | s->range_table = &waveform_ai_ranges; |
735 | s->len_chanlist = s->n_chan; |
736 | s->insn_write = waveform_ao_insn_write; |
737 | s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ |
738 | s->do_cmd = waveform_ao_cmd; |
739 | s->do_cmdtest = waveform_ao_cmdtest; |
740 | s->cancel = waveform_ao_cancel; |
741 | s->insn_config = waveform_ao_insn_config; |
742 | |
743 | /* Our default loopback value is just a 0V flatline */ |
744 | for (i = 0; i < s->n_chan; i++) |
745 | devpriv->ao_loopbacks[i] = s->maxdata / 2; |
746 | |
747 | devpriv->dev = dev; |
748 | timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); |
749 | timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); |
750 | |
751 | dev_info(dev->class_dev, |
752 | "%s: %u microvolt, %u microsecond waveform attached\n" , |
753 | dev->board_name, |
754 | devpriv->wf_amplitude, devpriv->wf_period); |
755 | |
756 | return 0; |
757 | } |
758 | |
759 | static int waveform_attach(struct comedi_device *dev, |
760 | struct comedi_devconfig *it) |
761 | { |
762 | int amplitude = it->options[0]; |
763 | int period = it->options[1]; |
764 | |
765 | /* set default amplitude and period */ |
766 | if (amplitude <= 0) |
767 | amplitude = 1000000; /* 1 volt */ |
768 | if (period <= 0) |
769 | period = 100000; /* 0.1 sec */ |
770 | |
771 | return waveform_common_attach(dev, amplitude, period); |
772 | } |
773 | |
774 | static int waveform_auto_attach(struct comedi_device *dev, |
775 | unsigned long context_unused) |
776 | { |
777 | int amplitude = set_amplitude; |
778 | int period = set_period; |
779 | |
780 | /* set default amplitude and period */ |
781 | if (!amplitude) |
782 | amplitude = 1000000; /* 1 volt */ |
783 | if (!period) |
784 | period = 100000; /* 0.1 sec */ |
785 | |
786 | return waveform_common_attach(dev, amplitude, period); |
787 | } |
788 | |
789 | static void waveform_detach(struct comedi_device *dev) |
790 | { |
791 | struct waveform_private *devpriv = dev->private; |
792 | |
793 | if (devpriv) { |
794 | del_timer_sync(timer: &devpriv->ai_timer); |
795 | del_timer_sync(timer: &devpriv->ao_timer); |
796 | } |
797 | } |
798 | |
799 | static struct comedi_driver waveform_driver = { |
800 | .driver_name = "comedi_test" , |
801 | .module = THIS_MODULE, |
802 | .attach = waveform_attach, |
803 | .auto_attach = waveform_auto_attach, |
804 | .detach = waveform_detach, |
805 | }; |
806 | |
807 | /* |
808 | * For auto-configuration, a device is created to stand in for a |
809 | * real hardware device. |
810 | */ |
811 | static int __init comedi_test_init(void) |
812 | { |
813 | int ret; |
814 | |
815 | ret = comedi_driver_register(driver: &waveform_driver); |
816 | if (ret) { |
817 | pr_err("comedi_test: unable to register driver\n" ); |
818 | return ret; |
819 | } |
820 | |
821 | if (!config_mode) { |
822 | ret = class_register(class: &ctcls); |
823 | if (ret) { |
824 | pr_warn("comedi_test: unable to create class\n" ); |
825 | goto clean3; |
826 | } |
827 | |
828 | ctdev = device_create(cls: &ctcls, NULL, MKDEV(0, 0), NULL, DEV_NAME); |
829 | if (IS_ERR(ptr: ctdev)) { |
830 | pr_warn("comedi_test: unable to create device\n" ); |
831 | goto clean2; |
832 | } |
833 | |
834 | ret = comedi_auto_config(hardware_device: ctdev, driver: &waveform_driver, context: 0); |
835 | if (ret) { |
836 | pr_warn("comedi_test: unable to auto-configure device\n" ); |
837 | goto clean; |
838 | } |
839 | } |
840 | |
841 | return 0; |
842 | |
843 | clean: |
844 | device_destroy(cls: &ctcls, MKDEV(0, 0)); |
845 | clean2: |
846 | class_unregister(class: &ctcls); |
847 | clean3: |
848 | return 0; |
849 | } |
850 | module_init(comedi_test_init); |
851 | |
852 | static void __exit comedi_test_exit(void) |
853 | { |
854 | if (ctdev) |
855 | comedi_auto_unconfig(hardware_device: ctdev); |
856 | |
857 | if (class_is_registered(class: &ctcls)) { |
858 | device_destroy(cls: &ctcls, MKDEV(0, 0)); |
859 | class_unregister(class: &ctcls); |
860 | } |
861 | |
862 | comedi_driver_unregister(driver: &waveform_driver); |
863 | } |
864 | module_exit(comedi_test_exit); |
865 | |
866 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
867 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
868 | MODULE_LICENSE("GPL" ); |
869 | |