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> |
df0e68c1 | 48 | #include <linux/comedi/comedidev.h> |
498460eb | 49 | #include <asm/div64.h> |
de4545cd | 50 | #include <linux/timer.h> |
dd28153b | 51 | #include <linux/ktime.h> |
4e5ffbf2 | 52 | #include <linux/jiffies.h> |
9ff7400b CKC |
53 | #include <linux/device.h> |
54 | #include <linux/kdev_t.h> | |
498460eb | 55 | |
498460eb | 56 | #define N_CHANS 8 |
9ff7400b CKC |
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; | |
3b7a628d IO |
63 | static const struct class ctcls = { |
64 | .name = CLASS_NAME, | |
65 | }; | |
9ff7400b CKC |
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 */ | |
f53641a6 IA |
90 | bool ai_timer_enable:1; /* should AI timer be running? */ |
91 | bool ao_timer_enable:1; /* should AO timer be running? */ | |
3b2468fe | 92 | unsigned short ao_loopbacks[N_CHANS]; |
8c49292f | 93 | }; |
498460eb | 94 | |
1be0e3ed | 95 | /* fake analog input ranges */ |
9ced1de6 | 96 | static const struct comedi_lrange waveform_ai_ranges = { |
c7b51165 HS |
97 | 2, { |
98 | BIP_RANGE(10), | |
99 | BIP_RANGE(5) | |
100 | } | |
498460eb JW |
101 | }; |
102 | ||
8bd48c9e IA |
103 | static unsigned short fake_sawtooth(struct comedi_device *dev, |
104 | unsigned int range_index, | |
21ec1bf7 | 105 | unsigned int current_time) |
0eb0f278 | 106 | { |
f1e5aa75 | 107 | struct waveform_private *devpriv = dev->private; |
0eb0f278 HS |
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; | |
f3f24dff | 116 | binary_amplitude *= devpriv->wf_amplitude; |
0eb0f278 HS |
117 | do_div(binary_amplitude, krange->max - krange->min); |
118 | ||
0eb0f278 HS |
119 | value = current_time; |
120 | value *= binary_amplitude * 2; | |
f3f24dff | 121 | do_div(value, devpriv->wf_period); |
19e86985 IA |
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 | } | |
0eb0f278 | 131 | |
19e86985 | 132 | return value; |
0eb0f278 HS |
133 | } |
134 | ||
8bd48c9e IA |
135 | static unsigned short fake_squarewave(struct comedi_device *dev, |
136 | unsigned int range_index, | |
21ec1bf7 | 137 | unsigned int current_time) |
0eb0f278 | 138 | { |
f1e5aa75 | 139 | struct waveform_private *devpriv = dev->private; |
0eb0f278 HS |
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]; | |
0eb0f278 HS |
145 | |
146 | value = s->maxdata; | |
f3f24dff | 147 | value *= devpriv->wf_amplitude; |
0eb0f278 HS |
148 | do_div(value, krange->max - krange->min); |
149 | ||
19e86985 | 150 | /* get one of two values for square-wave and clamp */ |
f3f24dff | 151 | if (current_time < devpriv->wf_period / 2) { |
19e86985 IA |
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 | } | |
0eb0f278 | 161 | |
19e86985 | 162 | return value; |
0eb0f278 HS |
163 | } |
164 | ||
8bd48c9e IA |
165 | static unsigned short fake_flatline(struct comedi_device *dev, |
166 | unsigned int range_index, | |
21ec1bf7 | 167 | unsigned int current_time) |
0eb0f278 HS |
168 | { |
169 | return dev->read_subdev->maxdata / 2; | |
170 | } | |
171 | ||
172 | /* generates a different waveform depending on what channel is read */ | |
8bd48c9e IA |
173 | static unsigned short fake_waveform(struct comedi_device *dev, |
174 | unsigned int channel, unsigned int range, | |
21ec1bf7 | 175 | unsigned int current_time) |
0eb0f278 HS |
176 | { |
177 | enum { | |
178 | SAWTOOTH_CHAN, | |
179 | SQUARE_CHAN, | |
180 | }; | |
181 | switch (channel) { | |
182 | case SAWTOOTH_CHAN: | |
183 | return fake_sawtooth(dev, range, current_time); | |
0eb0f278 HS |
184 | case SQUARE_CHAN: |
185 | return fake_squarewave(dev, range, current_time); | |
0eb0f278 HS |
186 | default: |
187 | break; | |
188 | } | |
189 | ||
190 | return fake_flatline(dev, range, current_time); | |
191 | } | |
192 | ||
498460eb | 193 | /* |
cbad8cf4 IA |
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 | */ | |
e44d4907 | 198 | static void waveform_ai_timer(struct timer_list *t) |
498460eb | 199 | { |
e44d4907 KC |
200 | struct waveform_private *devpriv = from_timer(devpriv, t, ai_timer); |
201 | struct comedi_device *dev = devpriv->dev; | |
24051247 HS |
202 | struct comedi_subdevice *s = dev->read_subdev; |
203 | struct comedi_async *async = s->async; | |
ea6d0d4c | 204 | struct comedi_cmd *cmd = &async->cmd; |
1eb85ae8 IA |
205 | u64 now; |
206 | unsigned int nsamples; | |
207 | unsigned int time_increment; | |
498460eb | 208 | |
1eb85ae8 IA |
209 | now = ktime_to_us(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), devpriv->wf_current); | |
218 | if (comedi_buf_write_samples(s, &sample, 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; | |
498460eb | 226 | } |
1eb85ae8 IA |
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--; | |
498460eb | 232 | } |
498460eb | 233 | |
4e5ffbf2 | 234 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { |
ea4f72b2 | 235 | async->events |= COMEDI_CB_EOA; |
4e5ffbf2 | 236 | } else { |
1eb85ae8 IA |
237 | if (devpriv->ai_convert_time > now) |
238 | time_increment = devpriv->ai_convert_time - now; | |
239 | else | |
240 | time_increment = 1; | |
f53641a6 IA |
241 | spin_lock(&dev->spinlock); |
242 | if (devpriv->ai_timer_enable) { | |
243 | mod_timer(&devpriv->ai_timer, | |
244 | jiffies + usecs_to_jiffies(time_increment)); | |
245 | } | |
246 | spin_unlock(&dev->spinlock); | |
4e5ffbf2 | 247 | } |
498460eb | 248 | |
1eb85ae8 | 249 | overrun: |
24051247 | 250 | comedi_handle_events(dev, s); |
498460eb JW |
251 | } |
252 | ||
0a85b6f0 MT |
253 | static int waveform_ai_cmdtest(struct comedi_device *dev, |
254 | struct comedi_subdevice *s, | |
ea6d0d4c | 255 | struct comedi_cmd *cmd) |
498460eb JW |
256 | { |
257 | int err = 0; | |
5afdcad2 | 258 | unsigned int arg, limit; |
498460eb | 259 | |
27020ffe | 260 | /* Step 1 : check if triggers are trivially valid */ |
498460eb | 261 | |
61f76970 | 262 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); |
783ddaeb IA |
263 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, |
264 | TRIG_FOLLOW | TRIG_TIMER); | |
61f76970 IA |
265 | err |= comedi_check_trigger_src(&cmd->convert_src, |
266 | TRIG_NOW | TRIG_TIMER); | |
267 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
268 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
498460eb JW |
269 | |
270 | if (err) | |
271 | return 1; | |
272 | ||
27020ffe | 273 | /* Step 2a : make sure trigger sources are unique */ |
498460eb | 274 | |
61f76970 IA |
275 | err |= comedi_check_trigger_is_unique(cmd->convert_src); |
276 | err |= comedi_check_trigger_is_unique(cmd->stop_src); | |
27020ffe HS |
277 | |
278 | /* Step 2b : and mutually compatible */ | |
498460eb | 279 | |
783ddaeb IA |
280 | if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) |
281 | err |= -EINVAL; /* scan period would be 0 */ | |
282 | ||
498460eb JW |
283 | if (err) |
284 | return 2; | |
285 | ||
df5daff8 HS |
286 | /* Step 3: check if arguments are trivially valid */ |
287 | ||
61f76970 | 288 | err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
df5daff8 | 289 | |
783ddaeb | 290 | if (cmd->convert_src == TRIG_NOW) { |
61f76970 | 291 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); |
783ddaeb IA |
292 | } else { /* cmd->convert_src == TRIG_TIMER */ |
293 | if (cmd->scan_begin_src == TRIG_FOLLOW) { | |
294 | err |= comedi_check_trigger_arg_min(&cmd->convert_arg, | |
295 | NSEC_PER_USEC); | |
296 | } | |
297 | } | |
498460eb | 298 | |
783ddaeb IA |
299 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
300 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
301 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ | |
302 | err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, | |
303 | NSEC_PER_USEC); | |
304 | } | |
498460eb | 305 | |
61f76970 IA |
306 | err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); |
307 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
308 | cmd->chanlist_len); | |
df5daff8 HS |
309 | |
310 | if (cmd->stop_src == TRIG_COUNT) | |
61f76970 | 311 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); |
783ddaeb | 312 | else /* cmd->stop_src == TRIG_NONE */ |
61f76970 | 313 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); |
498460eb JW |
314 | |
315 | if (err) | |
316 | return 3; | |
317 | ||
318 | /* step 4: fix up any arguments */ | |
319 | ||
498460eb | 320 | if (cmd->convert_src == TRIG_TIMER) { |
5afdcad2 | 321 | /* round convert_arg to nearest microsecond */ |
b2ef4813 | 322 | arg = cmd->convert_arg; |
5afdcad2 IA |
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); | |
783ddaeb IA |
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 | } | |
61f76970 | 332 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); |
498460eb JW |
333 | } |
334 | ||
783ddaeb IA |
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(&cmd->scan_begin_arg, arg); | |
5afdcad2 | 346 | } |
5afdcad2 | 347 | |
498460eb JW |
348 | if (err) |
349 | return 4; | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
0a85b6f0 MT |
354 | static int waveform_ai_cmd(struct comedi_device *dev, |
355 | struct comedi_subdevice *s) | |
498460eb | 356 | { |
f1e5aa75 | 357 | struct waveform_private *devpriv = dev->private; |
ea6d0d4c | 358 | struct comedi_cmd *cmd = &s->async->cmd; |
1eb85ae8 | 359 | unsigned int first_convert_time; |
f3f24dff | 360 | u64 wf_current; |
498460eb | 361 | |
51d66b29 | 362 | if (cmd->flags & CMDF_PRIORITY) { |
b8de3cc4 HS |
363 | dev_err(dev->class_dev, |
364 | "commands at RT priority not supported in this driver\n"); | |
498460eb JW |
365 | return -1; |
366 | } | |
367 | ||
498460eb | 368 | if (cmd->convert_src == TRIG_NOW) |
807060a3 | 369 | devpriv->ai_convert_period = 0; |
783ddaeb | 370 | else /* cmd->convert_src == TRIG_TIMER */ |
807060a3 | 371 | devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC; |
498460eb | 372 | |
783ddaeb | 373 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
807060a3 IA |
374 | devpriv->ai_scan_period = devpriv->ai_convert_period * |
375 | cmd->scan_end_arg; | |
783ddaeb | 376 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ |
807060a3 | 377 | devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; |
783ddaeb IA |
378 | } |
379 | ||
1eb85ae8 IA |
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(ktime_get()) + | |
391 | first_convert_time; | |
392 | ||
393 | /* Determine time within waveform period at time of conversion. */ | |
394 | wf_current = devpriv->ai_convert_time; | |
f3f24dff | 395 | devpriv->wf_current = do_div(wf_current, devpriv->wf_period); |
498460eb | 396 | |
1eb85ae8 IA |
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 | */ | |
f53641a6 IA |
402 | spin_lock_bh(&dev->spinlock); |
403 | devpriv->ai_timer_enable = true; | |
4e5ffbf2 | 404 | devpriv->ai_timer.expires = |
1eb85ae8 | 405 | jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1; |
807060a3 | 406 | add_timer(&devpriv->ai_timer); |
f53641a6 | 407 | spin_unlock_bh(&dev->spinlock); |
498460eb JW |
408 | return 0; |
409 | } | |
410 | ||
0a85b6f0 MT |
411 | static int waveform_ai_cancel(struct comedi_device *dev, |
412 | struct comedi_subdevice *s) | |
498460eb | 413 | { |
f1e5aa75 HS |
414 | struct waveform_private *devpriv = dev->private; |
415 | ||
f53641a6 IA |
416 | spin_lock_bh(&dev->spinlock); |
417 | devpriv->ai_timer_enable = false; | |
418 | spin_unlock_bh(&dev->spinlock); | |
403fe7f3 IA |
419 | if (in_softirq()) { |
420 | /* Assume we were called from the timer routine itself. */ | |
421 | del_timer(&devpriv->ai_timer); | |
422 | } else { | |
423 | del_timer_sync(&devpriv->ai_timer); | |
424 | } | |
498460eb JW |
425 | return 0; |
426 | } | |
427 | ||
0a85b6f0 MT |
428 | static int waveform_ai_insn_read(struct comedi_device *dev, |
429 | struct comedi_subdevice *s, | |
90035c08 | 430 | struct comedi_insn *insn, unsigned int *data) |
498460eb | 431 | { |
f1e5aa75 | 432 | struct waveform_private *devpriv = dev->private; |
498460eb JW |
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 | ||
0cf55bbe IA |
441 | /* |
442 | * This is the background routine to handle AO commands, scheduled by | |
443 | * a timer mechanism. | |
444 | */ | |
e44d4907 | 445 | static void waveform_ao_timer(struct timer_list *t) |
0cf55bbe | 446 | { |
e44d4907 KC |
447 | struct waveform_private *devpriv = from_timer(devpriv, t, ao_timer); |
448 | struct comedi_device *dev = devpriv->dev; | |
0cf55bbe IA |
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 | ||
0cf55bbe IA |
456 | /* determine number of scan periods since last time */ |
457 | now = ktime_to_us(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, 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, cmd->scan_end_arg * | |
474 | (scans_avail - 1)); | |
475 | nbytes = comedi_buf_read_alloc(s, skip_bytes); | |
476 | comedi_buf_read_free(s, nbytes); | |
477 | comedi_inc_scan_progress(s, 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]); | |
607b6cd3 | 487 | unsigned short *pd; |
0cf55bbe | 488 | |
607b6cd3 CKC |
489 | pd = &devpriv->ao_loopbacks[chan]; |
490 | ||
491 | if (!comedi_buf_read_samples(s, pd, 1)) { | |
0cf55bbe IA |
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 | ||
f53641a6 IA |
510 | spin_lock(&dev->spinlock); |
511 | if (devpriv->ao_timer_enable) { | |
512 | mod_timer(&devpriv->ao_timer, | |
513 | jiffies + usecs_to_jiffies(time_inc)); | |
514 | } | |
515 | spin_unlock(&dev->spinlock); | |
0cf55bbe IA |
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(ktime_get()); | |
f53641a6 IA |
536 | spin_lock_bh(&dev->spinlock); |
537 | devpriv->ao_timer_enable = true; | |
0cf55bbe IA |
538 | devpriv->ao_timer.expires = |
539 | jiffies + usecs_to_jiffies(devpriv->ao_scan_period); | |
0cf55bbe | 540 | add_timer(&devpriv->ao_timer); |
f53641a6 | 541 | spin_unlock_bh(&dev->spinlock); |
0cf55bbe IA |
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(&cmd->start_src, TRIG_INT); | |
556 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); | |
557 | err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); | |
558 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
559 | err |= comedi_check_trigger_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(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(&cmd->scan_begin_arg, | |
576 | NSEC_PER_USEC); | |
577 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); | |
578 | err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); | |
579 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
580 | cmd->chanlist_len); | |
581 | if (cmd->stop_src == TRIG_COUNT) | |
582 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); | |
583 | else /* cmd->stop_src == TRIG_NONE */ | |
584 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 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(&cmd->scan_begin_arg, 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; | |
f53641a6 IA |
626 | spin_lock_bh(&dev->spinlock); |
627 | devpriv->ao_timer_enable = false; | |
628 | spin_unlock_bh(&dev->spinlock); | |
403fe7f3 IA |
629 | if (in_softirq()) { |
630 | /* Assume we were called from the timer routine itself. */ | |
631 | del_timer(&devpriv->ao_timer); | |
632 | } else { | |
633 | del_timer_sync(&devpriv->ao_timer); | |
634 | } | |
0cf55bbe IA |
635 | return 0; |
636 | } | |
637 | ||
0a85b6f0 MT |
638 | static int waveform_ao_insn_write(struct comedi_device *dev, |
639 | struct comedi_subdevice *s, | |
90035c08 | 640 | struct comedi_insn *insn, unsigned int *data) |
498460eb | 641 | { |
f1e5aa75 | 642 | struct waveform_private *devpriv = dev->private; |
498460eb JW |
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 | } | |
90f703d3 | 650 | |
e0b2ca89 SO |
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 | ||
9ff7400b CKC |
693 | static int waveform_common_attach(struct comedi_device *dev, |
694 | int amplitude, int period) | |
0eb0f278 | 695 | { |
f1e5aa75 | 696 | struct waveform_private *devpriv; |
0eb0f278 | 697 | struct comedi_subdevice *s; |
0eb0f278 | 698 | int i; |
8b6c5694 | 699 | int ret; |
0eb0f278 | 700 | |
0bdab509 | 701 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
702 | if (!devpriv) |
703 | return -ENOMEM; | |
0eb0f278 | 704 | |
f3f24dff IA |
705 | devpriv->wf_amplitude = amplitude; |
706 | devpriv->wf_period = period; | |
0eb0f278 | 707 | |
8b6c5694 HS |
708 | ret = comedi_alloc_subdevices(dev, 2); |
709 | if (ret) | |
710 | return ret; | |
0eb0f278 | 711 | |
713e5c35 | 712 | s = &dev->subdevices[0]; |
0eb0f278 HS |
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; | |
b1da4943 HS |
717 | s->n_chan = N_CHANS; |
718 | s->maxdata = 0xffff; | |
0eb0f278 HS |
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; | |
e0b2ca89 | 725 | s->insn_config = waveform_ai_insn_config; |
0eb0f278 | 726 | |
713e5c35 | 727 | s = &dev->subdevices[1]; |
0eb0f278 HS |
728 | dev->write_subdev = s; |
729 | /* analog output subdevice (loopback) */ | |
730 | s->type = COMEDI_SUBD_AO; | |
0cf55bbe | 731 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; |
b1da4943 HS |
732 | s->n_chan = N_CHANS; |
733 | s->maxdata = 0xffff; | |
0eb0f278 | 734 | s->range_table = &waveform_ai_ranges; |
0cf55bbe | 735 | s->len_chanlist = s->n_chan; |
0eb0f278 | 736 | s->insn_write = waveform_ao_insn_write; |
e0c6fe12 | 737 | s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ |
0cf55bbe IA |
738 | s->do_cmd = waveform_ao_cmd; |
739 | s->do_cmdtest = waveform_ao_cmdtest; | |
740 | s->cancel = waveform_ao_cancel; | |
e0b2ca89 | 741 | s->insn_config = waveform_ao_insn_config; |
0eb0f278 HS |
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 | ||
e44d4907 KC |
747 | devpriv->dev = dev; |
748 | timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); | |
749 | timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); | |
0eb0f278 | 750 | |
9ac6eb40 | 751 | dev_info(dev->class_dev, |
21ec1bf7 | 752 | "%s: %u microvolt, %u microsecond waveform attached\n", |
9254c841 | 753 | dev->board_name, |
f3f24dff | 754 | devpriv->wf_amplitude, devpriv->wf_period); |
9ac6eb40 HS |
755 | |
756 | return 0; | |
0eb0f278 HS |
757 | } |
758 | ||
9ff7400b CKC |
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 | ||
484ecc95 | 789 | static void waveform_detach(struct comedi_device *dev) |
0eb0f278 | 790 | { |
f1e5aa75 HS |
791 | struct waveform_private *devpriv = dev->private; |
792 | ||
0cf55bbe | 793 | if (devpriv) { |
807060a3 | 794 | del_timer_sync(&devpriv->ai_timer); |
0cf55bbe IA |
795 | del_timer_sync(&devpriv->ao_timer); |
796 | } | |
0eb0f278 HS |
797 | } |
798 | ||
0eb0f278 HS |
799 | static struct comedi_driver waveform_driver = { |
800 | .driver_name = "comedi_test", | |
801 | .module = THIS_MODULE, | |
802 | .attach = waveform_attach, | |
9ff7400b | 803 | .auto_attach = waveform_auto_attach, |
0eb0f278 | 804 | .detach = waveform_detach, |
0eb0f278 | 805 | }; |
9ff7400b CKC |
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(&waveform_driver); | |
816 | if (ret) { | |
817 | pr_err("comedi_test: unable to register driver\n"); | |
818 | return ret; | |
819 | } | |
820 | ||
821 | if (!config_mode) { | |
3b7a628d IO |
822 | ret = class_register(&ctcls); |
823 | if (ret) { | |
9ff7400b CKC |
824 | pr_warn("comedi_test: unable to create class\n"); |
825 | goto clean3; | |
826 | } | |
827 | ||
3b7a628d | 828 | ctdev = device_create(&ctcls, NULL, MKDEV(0, 0), NULL, DEV_NAME); |
9ff7400b CKC |
829 | if (IS_ERR(ctdev)) { |
830 | pr_warn("comedi_test: unable to create device\n"); | |
831 | goto clean2; | |
832 | } | |
833 | ||
834 | ret = comedi_auto_config(ctdev, &waveform_driver, 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: | |
3b7a628d | 844 | device_destroy(&ctcls, MKDEV(0, 0)); |
9ff7400b | 845 | clean2: |
3b7a628d | 846 | class_unregister(&ctcls); |
9ff7400b | 847 | clean3: |
9ff7400b CKC |
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(ctdev); | |
856 | ||
3b7a628d IO |
857 | if (class_is_registered(&ctcls)) { |
858 | device_destroy(&ctcls, MKDEV(0, 0)); | |
859 | class_unregister(&ctcls); | |
9ff7400b CKC |
860 | } |
861 | ||
862 | comedi_driver_unregister(&waveform_driver); | |
863 | } | |
864 | module_exit(comedi_test_exit); | |
0eb0f278 | 865 | |
13d8b1f3 | 866 | MODULE_AUTHOR("Comedi https://www.comedi.org"); |
90f703d3 AT |
867 | MODULE_DESCRIPTION("Comedi low-level driver"); |
868 | MODULE_LICENSE("GPL"); |