2 * comedi/drivers/comedi_test.c
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.
9 * Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
10 * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
12 * COMEDI - Linux Control and Measurement Device Interface
13 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
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.
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.
28 * Description: generates fake waveforms
29 * Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
30 * <fmhess@users.sourceforge.net>, ds
33 * Updated: Sat, 16 Mar 2002 17:34:48 -0800
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
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)
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
48 #include <linux/module.h>
49 #include "../comedidev.h"
51 #include <asm/div64.h>
53 #include <linux/timer.h>
54 #include <linux/ktime.h>
55 #include <linux/jiffies.h>
59 enum waveform_state_bits
{
60 WAVEFORM_AI_RUNNING
= 0
63 /* Data unique to this driver */
64 struct waveform_private
{
65 struct timer_list ai_timer
; /* timer for AI commands */
66 u64 ai_last_scan_time
; /* time of last AI scan in usec */
67 unsigned int wf_amplitude
; /* waveform amplitude in microvolts */
68 unsigned int wf_period
; /* waveform period in microseconds */
69 unsigned int wf_current
; /* current time in waveform period */
70 unsigned long state_bits
;
71 unsigned int ai_scan_period
; /* AI scan period in usec */
72 unsigned int ai_convert_period
; /* AI conversion period in usec */
73 unsigned short ao_loopbacks
[N_CHANS
];
76 /* fake analog input ranges */
77 static const struct comedi_lrange waveform_ai_ranges
= {
84 static unsigned short fake_sawtooth(struct comedi_device
*dev
,
85 unsigned int range_index
,
86 unsigned int current_time
)
88 struct waveform_private
*devpriv
= dev
->private;
89 struct comedi_subdevice
*s
= dev
->read_subdev
;
90 unsigned int offset
= s
->maxdata
/ 2;
92 const struct comedi_krange
*krange
=
93 &s
->range_table
->range
[range_index
];
96 binary_amplitude
= s
->maxdata
;
97 binary_amplitude
*= devpriv
->wf_amplitude
;
98 do_div(binary_amplitude
, krange
->max
- krange
->min
);
100 value
= current_time
;
101 value
*= binary_amplitude
* 2;
102 do_div(value
, devpriv
->wf_period
);
104 /* get rid of sawtooth's dc offset and clamp value */
105 if (value
< binary_amplitude
) {
106 value
= 0; /* negative saturation */
108 value
-= binary_amplitude
;
109 if (value
> s
->maxdata
)
110 value
= s
->maxdata
; /* positive saturation */
116 static unsigned short fake_squarewave(struct comedi_device
*dev
,
117 unsigned int range_index
,
118 unsigned int current_time
)
120 struct waveform_private
*devpriv
= dev
->private;
121 struct comedi_subdevice
*s
= dev
->read_subdev
;
122 unsigned int offset
= s
->maxdata
/ 2;
124 const struct comedi_krange
*krange
=
125 &s
->range_table
->range
[range_index
];
128 value
*= devpriv
->wf_amplitude
;
129 do_div(value
, krange
->max
- krange
->min
);
131 /* get one of two values for square-wave and clamp */
132 if (current_time
< devpriv
->wf_period
/ 2) {
134 value
= 0; /* negative saturation */
136 value
= offset
- value
;
139 if (value
> s
->maxdata
)
140 value
= s
->maxdata
; /* positive saturation */
146 static unsigned short fake_flatline(struct comedi_device
*dev
,
147 unsigned int range_index
,
148 unsigned int current_time
)
150 return dev
->read_subdev
->maxdata
/ 2;
153 /* generates a different waveform depending on what channel is read */
154 static unsigned short fake_waveform(struct comedi_device
*dev
,
155 unsigned int channel
, unsigned int range
,
156 unsigned int current_time
)
164 return fake_sawtooth(dev
, range
, current_time
);
166 return fake_squarewave(dev
, range
, current_time
);
171 return fake_flatline(dev
, range
, current_time
);
175 * This is the background routine used to generate arbitrary data.
176 * It should run in the background; therefore it is scheduled by
179 static void waveform_ai_interrupt(unsigned long arg
)
181 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
182 struct waveform_private
*devpriv
= dev
->private;
183 struct comedi_subdevice
*s
= dev
->read_subdev
;
184 struct comedi_async
*async
= s
->async
;
185 struct comedi_cmd
*cmd
= &async
->cmd
;
187 unsigned long elapsed_time
;
188 unsigned int num_scans
;
190 /* check command is still active */
191 if (!test_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
))
194 elapsed_time
= ktime_to_us(ktime_get()) - devpriv
->ai_last_scan_time
;
195 num_scans
= elapsed_time
/ devpriv
->ai_scan_period
;
197 num_scans
= comedi_nscans_left(s
, num_scans
);
198 for (i
= 0; i
< num_scans
; i
++) {
199 unsigned int scan_remain_period
= devpriv
->ai_scan_period
;
201 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
202 unsigned short sample
;
204 if (devpriv
->wf_current
>= devpriv
->wf_period
)
205 devpriv
->wf_current
%= devpriv
->wf_period
;
206 sample
= fake_waveform(dev
, CR_CHAN(cmd
->chanlist
[j
]),
207 CR_RANGE(cmd
->chanlist
[j
]),
208 devpriv
->wf_current
);
209 comedi_buf_write_samples(s
, &sample
, 1);
210 devpriv
->wf_current
+= devpriv
->ai_convert_period
;
211 scan_remain_period
-= devpriv
->ai_convert_period
;
213 devpriv
->wf_current
+= scan_remain_period
;
214 devpriv
->ai_last_scan_time
+= devpriv
->ai_scan_period
;
216 if (devpriv
->wf_current
>= devpriv
->wf_period
)
217 devpriv
->wf_current
%= devpriv
->wf_period
;
219 if (cmd
->stop_src
== TRIG_COUNT
&& async
->scans_done
>= cmd
->stop_arg
) {
220 async
->events
|= COMEDI_CB_EOA
;
222 mod_timer(&devpriv
->ai_timer
,
223 jiffies
+ usecs_to_jiffies(devpriv
->ai_scan_period
));
226 comedi_handle_events(dev
, s
);
229 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
230 struct comedi_subdevice
*s
,
231 struct comedi_cmd
*cmd
)
234 unsigned int arg
, limit
;
236 /* Step 1 : check if triggers are trivially valid */
238 err
|= comedi_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
239 err
|= comedi_check_trigger_src(&cmd
->scan_begin_src
,
240 TRIG_FOLLOW
| TRIG_TIMER
);
241 err
|= comedi_check_trigger_src(&cmd
->convert_src
,
242 TRIG_NOW
| TRIG_TIMER
);
243 err
|= comedi_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
244 err
|= comedi_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
249 /* Step 2a : make sure trigger sources are unique */
251 err
|= comedi_check_trigger_is_unique(cmd
->convert_src
);
252 err
|= comedi_check_trigger_is_unique(cmd
->stop_src
);
254 /* Step 2b : and mutually compatible */
256 if (cmd
->scan_begin_src
== TRIG_FOLLOW
&& cmd
->convert_src
== TRIG_NOW
)
257 err
|= -EINVAL
; /* scan period would be 0 */
262 /* Step 3: check if arguments are trivially valid */
264 err
|= comedi_check_trigger_arg_is(&cmd
->start_arg
, 0);
266 if (cmd
->convert_src
== TRIG_NOW
) {
267 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, 0);
268 } else { /* cmd->convert_src == TRIG_TIMER */
269 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
270 err
|= comedi_check_trigger_arg_min(&cmd
->convert_arg
,
275 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
276 err
|= comedi_check_trigger_arg_is(&cmd
->scan_begin_arg
, 0);
277 } else { /* cmd->scan_begin_src == TRIG_TIMER */
278 err
|= comedi_check_trigger_arg_min(&cmd
->scan_begin_arg
,
282 err
|= comedi_check_trigger_arg_min(&cmd
->chanlist_len
, 1);
283 err
|= comedi_check_trigger_arg_is(&cmd
->scan_end_arg
,
286 if (cmd
->stop_src
== TRIG_COUNT
)
287 err
|= comedi_check_trigger_arg_min(&cmd
->stop_arg
, 1);
288 else /* cmd->stop_src == TRIG_NONE */
289 err
|= comedi_check_trigger_arg_is(&cmd
->stop_arg
, 0);
294 /* step 4: fix up any arguments */
296 if (cmd
->convert_src
== TRIG_TIMER
) {
297 /* round convert_arg to nearest microsecond */
298 arg
= cmd
->convert_arg
;
300 rounddown(UINT_MAX
, (unsigned int)NSEC_PER_USEC
));
301 arg
= NSEC_PER_USEC
* DIV_ROUND_CLOSEST(arg
, NSEC_PER_USEC
);
302 if (cmd
->scan_begin_arg
== TRIG_TIMER
) {
303 /* limit convert_arg to keep scan_begin_arg in range */
304 limit
= UINT_MAX
/ cmd
->scan_end_arg
;
305 limit
= rounddown(limit
, (unsigned int)NSEC_PER_SEC
);
306 arg
= min(arg
, limit
);
308 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, arg
);
311 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
312 /* round scan_begin_arg to nearest microsecond */
313 arg
= cmd
->scan_begin_arg
;
315 rounddown(UINT_MAX
, (unsigned int)NSEC_PER_USEC
));
316 arg
= NSEC_PER_USEC
* DIV_ROUND_CLOSEST(arg
, NSEC_PER_USEC
);
317 if (cmd
->convert_src
== TRIG_TIMER
) {
318 /* but ensure scan_begin_arg is large enough */
319 arg
= max(arg
, cmd
->convert_arg
* cmd
->scan_end_arg
);
321 err
|= comedi_check_trigger_arg_is(&cmd
->scan_begin_arg
, arg
);
330 static int waveform_ai_cmd(struct comedi_device
*dev
,
331 struct comedi_subdevice
*s
)
333 struct waveform_private
*devpriv
= dev
->private;
334 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
337 if (cmd
->flags
& CMDF_PRIORITY
) {
338 dev_err(dev
->class_dev
,
339 "commands at RT priority not supported in this driver\n");
343 if (cmd
->convert_src
== TRIG_NOW
)
344 devpriv
->ai_convert_period
= 0;
345 else /* cmd->convert_src == TRIG_TIMER */
346 devpriv
->ai_convert_period
= cmd
->convert_arg
/ NSEC_PER_USEC
;
348 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
349 devpriv
->ai_scan_period
= devpriv
->ai_convert_period
*
351 } else { /* cmd->scan_begin_src == TRIG_TIMER */
352 devpriv
->ai_scan_period
= cmd
->scan_begin_arg
/ NSEC_PER_USEC
;
355 devpriv
->ai_last_scan_time
= ktime_to_us(ktime_get());
356 /* Determine time within waveform period. */
357 wf_current
= devpriv
->ai_last_scan_time
;
358 devpriv
->wf_current
= do_div(wf_current
, devpriv
->wf_period
);
360 devpriv
->ai_timer
.expires
=
361 jiffies
+ usecs_to_jiffies(devpriv
->ai_scan_period
);
363 /* mark command as active */
364 smp_mb__before_atomic();
365 set_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
366 smp_mb__after_atomic();
367 add_timer(&devpriv
->ai_timer
);
371 static int waveform_ai_cancel(struct comedi_device
*dev
,
372 struct comedi_subdevice
*s
)
374 struct waveform_private
*devpriv
= dev
->private;
376 /* mark command as no longer active */
377 clear_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
378 smp_mb__after_atomic();
379 /* cannot call del_timer_sync() as may be called from timer routine */
380 del_timer(&devpriv
->ai_timer
);
384 static int waveform_ai_insn_read(struct comedi_device
*dev
,
385 struct comedi_subdevice
*s
,
386 struct comedi_insn
*insn
, unsigned int *data
)
388 struct waveform_private
*devpriv
= dev
->private;
389 int i
, chan
= CR_CHAN(insn
->chanspec
);
391 for (i
= 0; i
< insn
->n
; i
++)
392 data
[i
] = devpriv
->ao_loopbacks
[chan
];
397 static int waveform_ao_insn_write(struct comedi_device
*dev
,
398 struct comedi_subdevice
*s
,
399 struct comedi_insn
*insn
, unsigned int *data
)
401 struct waveform_private
*devpriv
= dev
->private;
402 int i
, chan
= CR_CHAN(insn
->chanspec
);
404 for (i
= 0; i
< insn
->n
; i
++)
405 devpriv
->ao_loopbacks
[chan
] = data
[i
];
410 static int waveform_attach(struct comedi_device
*dev
,
411 struct comedi_devconfig
*it
)
413 struct waveform_private
*devpriv
;
414 struct comedi_subdevice
*s
;
415 int amplitude
= it
->options
[0];
416 int period
= it
->options
[1];
420 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
424 /* set default amplitude and period */
426 amplitude
= 1000000; /* 1 volt */
428 period
= 100000; /* 0.1 sec */
430 devpriv
->wf_amplitude
= amplitude
;
431 devpriv
->wf_period
= period
;
433 ret
= comedi_alloc_subdevices(dev
, 2);
437 s
= &dev
->subdevices
[0];
438 dev
->read_subdev
= s
;
439 /* analog input subdevice */
440 s
->type
= COMEDI_SUBD_AI
;
441 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
444 s
->range_table
= &waveform_ai_ranges
;
445 s
->len_chanlist
= s
->n_chan
* 2;
446 s
->insn_read
= waveform_ai_insn_read
;
447 s
->do_cmd
= waveform_ai_cmd
;
448 s
->do_cmdtest
= waveform_ai_cmdtest
;
449 s
->cancel
= waveform_ai_cancel
;
451 s
= &dev
->subdevices
[1];
452 dev
->write_subdev
= s
;
453 /* analog output subdevice (loopback) */
454 s
->type
= COMEDI_SUBD_AO
;
455 s
->subdev_flags
= SDF_WRITABLE
| SDF_GROUND
;
458 s
->range_table
= &waveform_ai_ranges
;
459 s
->insn_write
= waveform_ao_insn_write
;
461 /* Our default loopback value is just a 0V flatline */
462 for (i
= 0; i
< s
->n_chan
; i
++)
463 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
465 setup_timer(&devpriv
->ai_timer
, waveform_ai_interrupt
,
468 dev_info(dev
->class_dev
,
469 "%s: %u microvolt, %u microsecond waveform attached\n",
471 devpriv
->wf_amplitude
, devpriv
->wf_period
);
476 static void waveform_detach(struct comedi_device
*dev
)
478 struct waveform_private
*devpriv
= dev
->private;
481 del_timer_sync(&devpriv
->ai_timer
);
484 static struct comedi_driver waveform_driver
= {
485 .driver_name
= "comedi_test",
486 .module
= THIS_MODULE
,
487 .attach
= waveform_attach
,
488 .detach
= waveform_detach
,
490 module_comedi_driver(waveform_driver
);
492 MODULE_AUTHOR("Comedi http://www.comedi.org");
493 MODULE_DESCRIPTION("Comedi low-level driver");
494 MODULE_LICENSE("GPL");