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