Commit | Line | Data |
---|---|---|
498460eb | 1 | /* |
cbad8cf4 IA |
2 | * comedi/drivers/comedi_test.c |
3 | * | |
4 | * Generates fake waveform signals that can be read through | |
5 | * the command interface. It does _not_ read from any board; | |
6 | * it just generates deterministic waveforms. | |
7 | * Useful for various testing purposes. | |
8 | * | |
9 | * Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de> | |
10 | * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> | |
11 | * | |
12 | * COMEDI - Linux Control and Measurement Device Interface | |
13 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
14 | * | |
15 | * This program is free software; you can redistribute it and/or modify | |
16 | * it under the terms of the GNU General Public License as published by | |
17 | * the Free Software Foundation; either version 2 of the License, or | |
18 | * (at your option) any later version. | |
19 | * | |
20 | * This program is distributed in the hope that it will be useful, | |
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
23 | * GNU General Public License for more details. | |
24 | */ | |
498460eb | 25 | |
498460eb | 26 | /* |
cbad8cf4 IA |
27 | * Driver: comedi_test |
28 | * Description: generates fake waveforms | |
29 | * Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess | |
30 | * <fmhess@users.sourceforge.net>, ds | |
31 | * Devices: | |
32 | * Status: works | |
33 | * Updated: Sat, 16 Mar 2002 17:34:48 -0800 | |
34 | * | |
35 | * This driver is mainly for testing purposes, but can also be used to | |
36 | * generate sample waveforms on systems that don't have data acquisition | |
37 | * hardware. | |
38 | * | |
39 | * Configuration options: | |
40 | * [0] - Amplitude in microvolts for fake waveforms (default 1 volt) | |
41 | * [1] - Period in microseconds for fake waveforms (default 0.1 sec) | |
42 | * | |
43 | * Generates a sawtooth wave on channel 0, square wave on channel 1, additional | |
44 | * waveforms could be added to other channels (currently they return flatline | |
45 | * zero volts). | |
46 | */ | |
498460eb | 47 | |
ce157f80 | 48 | #include <linux/module.h> |
498460eb JW |
49 | #include "../comedidev.h" |
50 | ||
51 | #include <asm/div64.h> | |
52 | ||
de4545cd | 53 | #include <linux/timer.h> |
dd28153b | 54 | #include <linux/ktime.h> |
4e5ffbf2 | 55 | #include <linux/jiffies.h> |
498460eb | 56 | |
498460eb JW |
57 | #define N_CHANS 8 |
58 | ||
73e0e4df | 59 | enum waveform_state_bits { |
0cf55bbe IA |
60 | WAVEFORM_AI_RUNNING, |
61 | WAVEFORM_AO_RUNNING | |
73e0e4df IA |
62 | }; |
63 | ||
498460eb | 64 | /* Data unique to this driver */ |
8c49292f | 65 | struct waveform_private { |
807060a3 | 66 | struct timer_list ai_timer; /* timer for AI commands */ |
1eb85ae8 | 67 | u64 ai_convert_time; /* time of next AI conversion in usec */ |
f3f24dff IA |
68 | unsigned int wf_amplitude; /* waveform amplitude in microvolts */ |
69 | unsigned int wf_period; /* waveform period in microseconds */ | |
70 | unsigned int wf_current; /* current time in waveform period */ | |
73e0e4df | 71 | unsigned long state_bits; |
807060a3 IA |
72 | unsigned int ai_scan_period; /* AI scan period in usec */ |
73 | unsigned int ai_convert_period; /* AI conversion period in usec */ | |
0cf55bbe IA |
74 | struct timer_list ao_timer; /* timer for AO commands */ |
75 | u64 ao_last_scan_time; /* time of previous AO scan in usec */ | |
76 | unsigned int ao_scan_period; /* AO scan period in usec */ | |
3b2468fe | 77 | unsigned short ao_loopbacks[N_CHANS]; |
8c49292f | 78 | }; |
498460eb | 79 | |
1be0e3ed | 80 | /* fake analog input ranges */ |
9ced1de6 | 81 | static const struct comedi_lrange waveform_ai_ranges = { |
c7b51165 HS |
82 | 2, { |
83 | BIP_RANGE(10), | |
84 | BIP_RANGE(5) | |
85 | } | |
498460eb JW |
86 | }; |
87 | ||
8bd48c9e IA |
88 | static unsigned short fake_sawtooth(struct comedi_device *dev, |
89 | unsigned int range_index, | |
21ec1bf7 | 90 | unsigned int current_time) |
0eb0f278 | 91 | { |
f1e5aa75 | 92 | struct waveform_private *devpriv = dev->private; |
0eb0f278 HS |
93 | struct comedi_subdevice *s = dev->read_subdev; |
94 | unsigned int offset = s->maxdata / 2; | |
95 | u64 value; | |
96 | const struct comedi_krange *krange = | |
97 | &s->range_table->range[range_index]; | |
98 | u64 binary_amplitude; | |
99 | ||
100 | binary_amplitude = s->maxdata; | |
f3f24dff | 101 | binary_amplitude *= devpriv->wf_amplitude; |
0eb0f278 HS |
102 | do_div(binary_amplitude, krange->max - krange->min); |
103 | ||
0eb0f278 HS |
104 | value = current_time; |
105 | value *= binary_amplitude * 2; | |
f3f24dff | 106 | do_div(value, devpriv->wf_period); |
19e86985 IA |
107 | value += offset; |
108 | /* get rid of sawtooth's dc offset and clamp value */ | |
109 | if (value < binary_amplitude) { | |
110 | value = 0; /* negative saturation */ | |
111 | } else { | |
112 | value -= binary_amplitude; | |
113 | if (value > s->maxdata) | |
114 | value = s->maxdata; /* positive saturation */ | |
115 | } | |
0eb0f278 | 116 | |
19e86985 | 117 | return value; |
0eb0f278 HS |
118 | } |
119 | ||
8bd48c9e IA |
120 | static unsigned short fake_squarewave(struct comedi_device *dev, |
121 | unsigned int range_index, | |
21ec1bf7 | 122 | unsigned int current_time) |
0eb0f278 | 123 | { |
f1e5aa75 | 124 | struct waveform_private *devpriv = dev->private; |
0eb0f278 HS |
125 | struct comedi_subdevice *s = dev->read_subdev; |
126 | unsigned int offset = s->maxdata / 2; | |
127 | u64 value; | |
128 | const struct comedi_krange *krange = | |
129 | &s->range_table->range[range_index]; | |
0eb0f278 HS |
130 | |
131 | value = s->maxdata; | |
f3f24dff | 132 | value *= devpriv->wf_amplitude; |
0eb0f278 HS |
133 | do_div(value, krange->max - krange->min); |
134 | ||
19e86985 | 135 | /* get one of two values for square-wave and clamp */ |
f3f24dff | 136 | if (current_time < devpriv->wf_period / 2) { |
19e86985 IA |
137 | if (offset < value) |
138 | value = 0; /* negative saturation */ | |
139 | else | |
140 | value = offset - value; | |
141 | } else { | |
142 | value += offset; | |
143 | if (value > s->maxdata) | |
144 | value = s->maxdata; /* positive saturation */ | |
145 | } | |
0eb0f278 | 146 | |
19e86985 | 147 | return value; |
0eb0f278 HS |
148 | } |
149 | ||
8bd48c9e IA |
150 | static unsigned short fake_flatline(struct comedi_device *dev, |
151 | unsigned int range_index, | |
21ec1bf7 | 152 | unsigned int current_time) |
0eb0f278 HS |
153 | { |
154 | return dev->read_subdev->maxdata / 2; | |
155 | } | |
156 | ||
157 | /* generates a different waveform depending on what channel is read */ | |
8bd48c9e IA |
158 | static unsigned short fake_waveform(struct comedi_device *dev, |
159 | unsigned int channel, unsigned int range, | |
21ec1bf7 | 160 | unsigned int current_time) |
0eb0f278 HS |
161 | { |
162 | enum { | |
163 | SAWTOOTH_CHAN, | |
164 | SQUARE_CHAN, | |
165 | }; | |
166 | switch (channel) { | |
167 | case SAWTOOTH_CHAN: | |
168 | return fake_sawtooth(dev, range, current_time); | |
0eb0f278 HS |
169 | case SQUARE_CHAN: |
170 | return fake_squarewave(dev, range, current_time); | |
0eb0f278 HS |
171 | default: |
172 | break; | |
173 | } | |
174 | ||
175 | return fake_flatline(dev, range, current_time); | |
176 | } | |
177 | ||
498460eb | 178 | /* |
cbad8cf4 IA |
179 | * This is the background routine used to generate arbitrary data. |
180 | * It should run in the background; therefore it is scheduled by | |
181 | * a timer mechanism. | |
182 | */ | |
9406a314 | 183 | static void waveform_ai_timer(unsigned long arg) |
498460eb | 184 | { |
0a85b6f0 | 185 | struct comedi_device *dev = (struct comedi_device *)arg; |
f1e5aa75 | 186 | struct waveform_private *devpriv = dev->private; |
24051247 HS |
187 | struct comedi_subdevice *s = dev->read_subdev; |
188 | struct comedi_async *async = s->async; | |
ea6d0d4c | 189 | struct comedi_cmd *cmd = &async->cmd; |
1eb85ae8 IA |
190 | u64 now; |
191 | unsigned int nsamples; | |
192 | unsigned int time_increment; | |
498460eb | 193 | |
73e0e4df IA |
194 | /* check command is still active */ |
195 | if (!test_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits)) | |
196 | return; | |
197 | ||
1eb85ae8 IA |
198 | now = ktime_to_us(ktime_get()); |
199 | nsamples = comedi_nsamples_left(s, UINT_MAX); | |
200 | ||
201 | while (nsamples && devpriv->ai_convert_time < now) { | |
202 | unsigned int chanspec = cmd->chanlist[async->cur_chan]; | |
203 | unsigned short sample; | |
204 | ||
205 | sample = fake_waveform(dev, CR_CHAN(chanspec), | |
206 | CR_RANGE(chanspec), devpriv->wf_current); | |
207 | if (comedi_buf_write_samples(s, &sample, 1) == 0) | |
208 | goto overrun; | |
209 | time_increment = devpriv->ai_convert_period; | |
210 | if (async->scan_progress == 0) { | |
211 | /* done last conversion in scan, so add dead time */ | |
212 | time_increment += devpriv->ai_scan_period - | |
213 | devpriv->ai_convert_period * | |
214 | cmd->scan_end_arg; | |
498460eb | 215 | } |
1eb85ae8 IA |
216 | devpriv->wf_current += time_increment; |
217 | if (devpriv->wf_current >= devpriv->wf_period) | |
218 | devpriv->wf_current %= devpriv->wf_period; | |
219 | devpriv->ai_convert_time += time_increment; | |
220 | nsamples--; | |
498460eb | 221 | } |
498460eb | 222 | |
4e5ffbf2 | 223 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { |
ea4f72b2 | 224 | async->events |= COMEDI_CB_EOA; |
4e5ffbf2 | 225 | } else { |
1eb85ae8 IA |
226 | if (devpriv->ai_convert_time > now) |
227 | time_increment = devpriv->ai_convert_time - now; | |
228 | else | |
229 | time_increment = 1; | |
4e5ffbf2 | 230 | mod_timer(&devpriv->ai_timer, |
1eb85ae8 | 231 | jiffies + usecs_to_jiffies(time_increment)); |
4e5ffbf2 | 232 | } |
498460eb | 233 | |
1eb85ae8 | 234 | overrun: |
24051247 | 235 | comedi_handle_events(dev, s); |
498460eb JW |
236 | } |
237 | ||
0a85b6f0 MT |
238 | static int waveform_ai_cmdtest(struct comedi_device *dev, |
239 | struct comedi_subdevice *s, | |
ea6d0d4c | 240 | struct comedi_cmd *cmd) |
498460eb JW |
241 | { |
242 | int err = 0; | |
5afdcad2 | 243 | unsigned int arg, limit; |
498460eb | 244 | |
27020ffe | 245 | /* Step 1 : check if triggers are trivially valid */ |
498460eb | 246 | |
61f76970 | 247 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); |
783ddaeb IA |
248 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, |
249 | TRIG_FOLLOW | TRIG_TIMER); | |
61f76970 IA |
250 | err |= comedi_check_trigger_src(&cmd->convert_src, |
251 | TRIG_NOW | TRIG_TIMER); | |
252 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
253 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
498460eb JW |
254 | |
255 | if (err) | |
256 | return 1; | |
257 | ||
27020ffe | 258 | /* Step 2a : make sure trigger sources are unique */ |
498460eb | 259 | |
61f76970 IA |
260 | err |= comedi_check_trigger_is_unique(cmd->convert_src); |
261 | err |= comedi_check_trigger_is_unique(cmd->stop_src); | |
27020ffe HS |
262 | |
263 | /* Step 2b : and mutually compatible */ | |
498460eb | 264 | |
783ddaeb IA |
265 | if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) |
266 | err |= -EINVAL; /* scan period would be 0 */ | |
267 | ||
498460eb JW |
268 | if (err) |
269 | return 2; | |
270 | ||
df5daff8 HS |
271 | /* Step 3: check if arguments are trivially valid */ |
272 | ||
61f76970 | 273 | err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
df5daff8 | 274 | |
783ddaeb | 275 | if (cmd->convert_src == TRIG_NOW) { |
61f76970 | 276 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); |
783ddaeb IA |
277 | } else { /* cmd->convert_src == TRIG_TIMER */ |
278 | if (cmd->scan_begin_src == TRIG_FOLLOW) { | |
279 | err |= comedi_check_trigger_arg_min(&cmd->convert_arg, | |
280 | NSEC_PER_USEC); | |
281 | } | |
282 | } | |
498460eb | 283 | |
783ddaeb IA |
284 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
285 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
286 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ | |
287 | err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, | |
288 | NSEC_PER_USEC); | |
289 | } | |
498460eb | 290 | |
61f76970 IA |
291 | err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); |
292 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
293 | cmd->chanlist_len); | |
df5daff8 HS |
294 | |
295 | if (cmd->stop_src == TRIG_COUNT) | |
61f76970 | 296 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); |
783ddaeb | 297 | else /* cmd->stop_src == TRIG_NONE */ |
61f76970 | 298 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); |
498460eb JW |
299 | |
300 | if (err) | |
301 | return 3; | |
302 | ||
303 | /* step 4: fix up any arguments */ | |
304 | ||
498460eb | 305 | if (cmd->convert_src == TRIG_TIMER) { |
5afdcad2 | 306 | /* round convert_arg to nearest microsecond */ |
b2ef4813 | 307 | arg = cmd->convert_arg; |
5afdcad2 IA |
308 | arg = min(arg, |
309 | rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); | |
310 | arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); | |
783ddaeb IA |
311 | if (cmd->scan_begin_arg == TRIG_TIMER) { |
312 | /* limit convert_arg to keep scan_begin_arg in range */ | |
313 | limit = UINT_MAX / cmd->scan_end_arg; | |
314 | limit = rounddown(limit, (unsigned int)NSEC_PER_SEC); | |
315 | arg = min(arg, limit); | |
316 | } | |
61f76970 | 317 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); |
498460eb JW |
318 | } |
319 | ||
783ddaeb IA |
320 | if (cmd->scan_begin_src == TRIG_TIMER) { |
321 | /* round scan_begin_arg to nearest microsecond */ | |
322 | arg = cmd->scan_begin_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->convert_src == TRIG_TIMER) { | |
327 | /* but ensure scan_begin_arg is large enough */ | |
328 | arg = max(arg, cmd->convert_arg * cmd->scan_end_arg); | |
329 | } | |
330 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); | |
5afdcad2 | 331 | } |
5afdcad2 | 332 | |
498460eb JW |
333 | if (err) |
334 | return 4; | |
335 | ||
336 | return 0; | |
337 | } | |
338 | ||
0a85b6f0 MT |
339 | static int waveform_ai_cmd(struct comedi_device *dev, |
340 | struct comedi_subdevice *s) | |
498460eb | 341 | { |
f1e5aa75 | 342 | struct waveform_private *devpriv = dev->private; |
ea6d0d4c | 343 | struct comedi_cmd *cmd = &s->async->cmd; |
1eb85ae8 | 344 | unsigned int first_convert_time; |
f3f24dff | 345 | u64 wf_current; |
498460eb | 346 | |
51d66b29 | 347 | if (cmd->flags & CMDF_PRIORITY) { |
b8de3cc4 HS |
348 | dev_err(dev->class_dev, |
349 | "commands at RT priority not supported in this driver\n"); | |
498460eb JW |
350 | return -1; |
351 | } | |
352 | ||
498460eb | 353 | if (cmd->convert_src == TRIG_NOW) |
807060a3 | 354 | devpriv->ai_convert_period = 0; |
783ddaeb | 355 | else /* cmd->convert_src == TRIG_TIMER */ |
807060a3 | 356 | devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC; |
498460eb | 357 | |
783ddaeb | 358 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
807060a3 IA |
359 | devpriv->ai_scan_period = devpriv->ai_convert_period * |
360 | cmd->scan_end_arg; | |
783ddaeb | 361 | } else { /* cmd->scan_begin_src == TRIG_TIMER */ |
807060a3 | 362 | devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; |
783ddaeb IA |
363 | } |
364 | ||
1eb85ae8 IA |
365 | /* |
366 | * Simulate first conversion to occur at convert period after | |
367 | * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume | |
368 | * the conversion timer starts immediately. If scan_begin_src is | |
369 | * TRIG_TIMER, assume the conversion timer starts after the scan | |
370 | * period. | |
371 | */ | |
372 | first_convert_time = devpriv->ai_convert_period; | |
373 | if (cmd->scan_begin_src == TRIG_TIMER) | |
374 | first_convert_time += devpriv->ai_scan_period; | |
375 | devpriv->ai_convert_time = ktime_to_us(ktime_get()) + | |
376 | first_convert_time; | |
377 | ||
378 | /* Determine time within waveform period at time of conversion. */ | |
379 | wf_current = devpriv->ai_convert_time; | |
f3f24dff | 380 | devpriv->wf_current = do_div(wf_current, devpriv->wf_period); |
498460eb | 381 | |
1eb85ae8 IA |
382 | /* |
383 | * Schedule timer to expire just after first conversion time. | |
384 | * Seem to need an extra jiffy here, otherwise timer expires slightly | |
385 | * early! | |
386 | */ | |
4e5ffbf2 | 387 | devpriv->ai_timer.expires = |
1eb85ae8 | 388 | jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1; |
4e5ffbf2 | 389 | |
73e0e4df IA |
390 | /* mark command as active */ |
391 | smp_mb__before_atomic(); | |
392 | set_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits); | |
393 | smp_mb__after_atomic(); | |
807060a3 | 394 | add_timer(&devpriv->ai_timer); |
498460eb JW |
395 | return 0; |
396 | } | |
397 | ||
0a85b6f0 MT |
398 | static int waveform_ai_cancel(struct comedi_device *dev, |
399 | struct comedi_subdevice *s) | |
498460eb | 400 | { |
f1e5aa75 HS |
401 | struct waveform_private *devpriv = dev->private; |
402 | ||
73e0e4df IA |
403 | /* mark command as no longer active */ |
404 | clear_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits); | |
405 | smp_mb__after_atomic(); | |
406 | /* cannot call del_timer_sync() as may be called from timer routine */ | |
807060a3 | 407 | del_timer(&devpriv->ai_timer); |
498460eb JW |
408 | return 0; |
409 | } | |
410 | ||
0a85b6f0 MT |
411 | static int waveform_ai_insn_read(struct comedi_device *dev, |
412 | struct comedi_subdevice *s, | |
90035c08 | 413 | struct comedi_insn *insn, unsigned int *data) |
498460eb | 414 | { |
f1e5aa75 | 415 | struct waveform_private *devpriv = dev->private; |
498460eb JW |
416 | int i, chan = CR_CHAN(insn->chanspec); |
417 | ||
418 | for (i = 0; i < insn->n; i++) | |
419 | data[i] = devpriv->ao_loopbacks[chan]; | |
420 | ||
421 | return insn->n; | |
422 | } | |
423 | ||
0cf55bbe IA |
424 | /* |
425 | * This is the background routine to handle AO commands, scheduled by | |
426 | * a timer mechanism. | |
427 | */ | |
428 | static void waveform_ao_timer(unsigned long arg) | |
429 | { | |
430 | struct comedi_device *dev = (struct comedi_device *)arg; | |
431 | struct waveform_private *devpriv = dev->private; | |
432 | struct comedi_subdevice *s = dev->write_subdev; | |
433 | struct comedi_async *async = s->async; | |
434 | struct comedi_cmd *cmd = &async->cmd; | |
435 | u64 now; | |
436 | u64 scans_since; | |
437 | unsigned int scans_avail = 0; | |
438 | ||
439 | /* check command is still active */ | |
440 | if (!test_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits)) | |
441 | return; | |
442 | ||
443 | /* determine number of scan periods since last time */ | |
444 | now = ktime_to_us(ktime_get()); | |
445 | scans_since = now - devpriv->ao_last_scan_time; | |
446 | do_div(scans_since, devpriv->ao_scan_period); | |
447 | if (scans_since) { | |
448 | unsigned int i; | |
449 | ||
450 | /* determine scans in buffer, limit to scans to do this time */ | |
451 | scans_avail = comedi_nscans_left(s, 0); | |
452 | if (scans_avail > scans_since) | |
453 | scans_avail = scans_since; | |
454 | if (scans_avail) { | |
455 | /* skip all but the last scan to save processing time */ | |
456 | if (scans_avail > 1) { | |
457 | unsigned int skip_bytes, nbytes; | |
458 | ||
459 | skip_bytes = | |
460 | comedi_samples_to_bytes(s, cmd->scan_end_arg * | |
461 | (scans_avail - 1)); | |
462 | nbytes = comedi_buf_read_alloc(s, skip_bytes); | |
463 | comedi_buf_read_free(s, nbytes); | |
464 | comedi_inc_scan_progress(s, nbytes); | |
465 | if (nbytes < skip_bytes) { | |
466 | /* unexpected underrun! (cancelled?) */ | |
467 | async->events |= COMEDI_CB_OVERFLOW; | |
468 | goto underrun; | |
469 | } | |
470 | } | |
471 | /* output the last scan */ | |
472 | for (i = 0; i < cmd->scan_end_arg; i++) { | |
473 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); | |
474 | ||
475 | if (comedi_buf_read_samples(s, | |
476 | &devpriv-> | |
477 | ao_loopbacks[chan], | |
478 | 1) == 0) { | |
479 | /* unexpected underrun! (cancelled?) */ | |
480 | async->events |= COMEDI_CB_OVERFLOW; | |
481 | goto underrun; | |
482 | } | |
483 | } | |
484 | /* advance time of last scan */ | |
485 | devpriv->ao_last_scan_time += | |
486 | (u64)scans_avail * devpriv->ao_scan_period; | |
487 | } | |
488 | } | |
489 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { | |
490 | async->events |= COMEDI_CB_EOA; | |
491 | } else if (scans_avail < scans_since) { | |
492 | async->events |= COMEDI_CB_OVERFLOW; | |
493 | } else { | |
494 | unsigned int time_inc = devpriv->ao_last_scan_time + | |
495 | devpriv->ao_scan_period - now; | |
496 | ||
497 | mod_timer(&devpriv->ao_timer, | |
498 | jiffies + usecs_to_jiffies(time_inc)); | |
499 | } | |
500 | ||
501 | underrun: | |
502 | comedi_handle_events(dev, s); | |
503 | } | |
504 | ||
505 | static int waveform_ao_inttrig_start(struct comedi_device *dev, | |
506 | struct comedi_subdevice *s, | |
507 | unsigned int trig_num) | |
508 | { | |
509 | struct waveform_private *devpriv = dev->private; | |
510 | struct comedi_async *async = s->async; | |
511 | struct comedi_cmd *cmd = &async->cmd; | |
512 | ||
513 | if (trig_num != cmd->start_arg) | |
514 | return -EINVAL; | |
515 | ||
516 | async->inttrig = NULL; | |
517 | ||
518 | devpriv->ao_last_scan_time = ktime_to_us(ktime_get()); | |
519 | devpriv->ao_timer.expires = | |
520 | jiffies + usecs_to_jiffies(devpriv->ao_scan_period); | |
521 | ||
522 | /* mark command as active */ | |
523 | smp_mb__before_atomic(); | |
524 | set_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits); | |
525 | smp_mb__after_atomic(); | |
526 | add_timer(&devpriv->ao_timer); | |
527 | ||
528 | return 1; | |
529 | } | |
530 | ||
531 | static int waveform_ao_cmdtest(struct comedi_device *dev, | |
532 | struct comedi_subdevice *s, | |
533 | struct comedi_cmd *cmd) | |
534 | { | |
535 | int err = 0; | |
536 | unsigned int arg; | |
537 | ||
538 | /* Step 1 : check if triggers are trivially valid */ | |
539 | ||
540 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); | |
541 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); | |
542 | err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); | |
543 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
544 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
545 | ||
546 | if (err) | |
547 | return 1; | |
548 | ||
549 | /* Step 2a : make sure trigger sources are unique */ | |
550 | ||
551 | err |= comedi_check_trigger_is_unique(cmd->stop_src); | |
552 | ||
553 | /* Step 2b : and mutually compatible */ | |
554 | ||
555 | if (err) | |
556 | return 2; | |
557 | ||
558 | /* Step 3: check if arguments are trivially valid */ | |
559 | ||
560 | err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, | |
561 | NSEC_PER_USEC); | |
562 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); | |
563 | err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); | |
564 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
565 | cmd->chanlist_len); | |
566 | if (cmd->stop_src == TRIG_COUNT) | |
567 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); | |
568 | else /* cmd->stop_src == TRIG_NONE */ | |
569 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); | |
570 | ||
571 | if (err) | |
572 | return 3; | |
573 | ||
574 | /* step 4: fix up any arguments */ | |
575 | ||
576 | /* round scan_begin_arg to nearest microsecond */ | |
577 | arg = cmd->scan_begin_arg; | |
578 | arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); | |
579 | arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); | |
580 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); | |
581 | ||
582 | if (err) | |
583 | return 4; | |
584 | ||
585 | return 0; | |
586 | } | |
587 | ||
588 | static int waveform_ao_cmd(struct comedi_device *dev, | |
589 | struct comedi_subdevice *s) | |
590 | { | |
591 | struct waveform_private *devpriv = dev->private; | |
592 | struct comedi_cmd *cmd = &s->async->cmd; | |
593 | ||
594 | if (cmd->flags & CMDF_PRIORITY) { | |
595 | dev_err(dev->class_dev, | |
596 | "commands at RT priority not supported in this driver\n"); | |
597 | return -1; | |
598 | } | |
599 | ||
600 | devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; | |
601 | s->async->inttrig = waveform_ao_inttrig_start; | |
602 | return 0; | |
603 | } | |
604 | ||
605 | static int waveform_ao_cancel(struct comedi_device *dev, | |
606 | struct comedi_subdevice *s) | |
607 | { | |
608 | struct waveform_private *devpriv = dev->private; | |
609 | ||
610 | s->async->inttrig = NULL; | |
611 | /* mark command as no longer active */ | |
612 | clear_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits); | |
613 | smp_mb__after_atomic(); | |
614 | /* cannot call del_timer_sync() as may be called from timer routine */ | |
615 | del_timer(&devpriv->ao_timer); | |
616 | return 0; | |
617 | } | |
618 | ||
0a85b6f0 MT |
619 | static int waveform_ao_insn_write(struct comedi_device *dev, |
620 | struct comedi_subdevice *s, | |
90035c08 | 621 | struct comedi_insn *insn, unsigned int *data) |
498460eb | 622 | { |
f1e5aa75 | 623 | struct waveform_private *devpriv = dev->private; |
498460eb JW |
624 | int i, chan = CR_CHAN(insn->chanspec); |
625 | ||
626 | for (i = 0; i < insn->n; i++) | |
627 | devpriv->ao_loopbacks[chan] = data[i]; | |
628 | ||
629 | return insn->n; | |
630 | } | |
90f703d3 | 631 | |
0eb0f278 HS |
632 | static int waveform_attach(struct comedi_device *dev, |
633 | struct comedi_devconfig *it) | |
634 | { | |
f1e5aa75 | 635 | struct waveform_private *devpriv; |
0eb0f278 HS |
636 | struct comedi_subdevice *s; |
637 | int amplitude = it->options[0]; | |
638 | int period = it->options[1]; | |
639 | int i; | |
8b6c5694 | 640 | int ret; |
0eb0f278 | 641 | |
0bdab509 | 642 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
643 | if (!devpriv) |
644 | return -ENOMEM; | |
0eb0f278 HS |
645 | |
646 | /* set default amplitude and period */ | |
647 | if (amplitude <= 0) | |
648 | amplitude = 1000000; /* 1 volt */ | |
649 | if (period <= 0) | |
650 | period = 100000; /* 0.1 sec */ | |
651 | ||
f3f24dff IA |
652 | devpriv->wf_amplitude = amplitude; |
653 | devpriv->wf_period = period; | |
0eb0f278 | 654 | |
8b6c5694 HS |
655 | ret = comedi_alloc_subdevices(dev, 2); |
656 | if (ret) | |
657 | return ret; | |
0eb0f278 | 658 | |
713e5c35 | 659 | s = &dev->subdevices[0]; |
0eb0f278 HS |
660 | dev->read_subdev = s; |
661 | /* analog input subdevice */ | |
662 | s->type = COMEDI_SUBD_AI; | |
663 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; | |
b1da4943 HS |
664 | s->n_chan = N_CHANS; |
665 | s->maxdata = 0xffff; | |
0eb0f278 HS |
666 | s->range_table = &waveform_ai_ranges; |
667 | s->len_chanlist = s->n_chan * 2; | |
668 | s->insn_read = waveform_ai_insn_read; | |
669 | s->do_cmd = waveform_ai_cmd; | |
670 | s->do_cmdtest = waveform_ai_cmdtest; | |
671 | s->cancel = waveform_ai_cancel; | |
672 | ||
713e5c35 | 673 | s = &dev->subdevices[1]; |
0eb0f278 HS |
674 | dev->write_subdev = s; |
675 | /* analog output subdevice (loopback) */ | |
676 | s->type = COMEDI_SUBD_AO; | |
0cf55bbe | 677 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; |
b1da4943 HS |
678 | s->n_chan = N_CHANS; |
679 | s->maxdata = 0xffff; | |
0eb0f278 | 680 | s->range_table = &waveform_ai_ranges; |
0cf55bbe | 681 | s->len_chanlist = s->n_chan; |
0eb0f278 | 682 | s->insn_write = waveform_ao_insn_write; |
e0c6fe12 | 683 | s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ |
0cf55bbe IA |
684 | s->do_cmd = waveform_ao_cmd; |
685 | s->do_cmdtest = waveform_ao_cmdtest; | |
686 | s->cancel = waveform_ao_cancel; | |
0eb0f278 HS |
687 | |
688 | /* Our default loopback value is just a 0V flatline */ | |
689 | for (i = 0; i < s->n_chan; i++) | |
690 | devpriv->ao_loopbacks[i] = s->maxdata / 2; | |
691 | ||
9406a314 | 692 | setup_timer(&devpriv->ai_timer, waveform_ai_timer, (unsigned long)dev); |
0cf55bbe | 693 | setup_timer(&devpriv->ao_timer, waveform_ao_timer, (unsigned long)dev); |
0eb0f278 | 694 | |
9ac6eb40 | 695 | dev_info(dev->class_dev, |
21ec1bf7 | 696 | "%s: %u microvolt, %u microsecond waveform attached\n", |
9254c841 | 697 | dev->board_name, |
f3f24dff | 698 | devpriv->wf_amplitude, devpriv->wf_period); |
9ac6eb40 HS |
699 | |
700 | return 0; | |
0eb0f278 HS |
701 | } |
702 | ||
484ecc95 | 703 | static void waveform_detach(struct comedi_device *dev) |
0eb0f278 | 704 | { |
f1e5aa75 HS |
705 | struct waveform_private *devpriv = dev->private; |
706 | ||
0cf55bbe | 707 | if (devpriv) { |
807060a3 | 708 | del_timer_sync(&devpriv->ai_timer); |
0cf55bbe IA |
709 | del_timer_sync(&devpriv->ao_timer); |
710 | } | |
0eb0f278 HS |
711 | } |
712 | ||
0eb0f278 HS |
713 | static struct comedi_driver waveform_driver = { |
714 | .driver_name = "comedi_test", | |
715 | .module = THIS_MODULE, | |
716 | .attach = waveform_attach, | |
717 | .detach = waveform_detach, | |
0eb0f278 HS |
718 | }; |
719 | module_comedi_driver(waveform_driver); | |
720 | ||
90f703d3 AT |
721 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
722 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
723 | MODULE_LICENSE("GPL"); |