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