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>
58 enum waveform_state_bits
{
59 WAVEFORM_AI_RUNNING
= 0
62 /* Data unique to this driver */
63 struct waveform_private
{
64 struct timer_list timer
;
65 u64 ai_last_scan_time
; /* time of last AI scan in usec */
66 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
67 unsigned int usec_period
; /* waveform period in microseconds */
68 unsigned int usec_current
; /* current time (mod waveform period) */
69 unsigned long state_bits
;
70 unsigned int scan_period
; /* scan period in usec */
71 unsigned int convert_period
; /* conversion period in usec */
72 unsigned int ao_loopbacks
[N_CHANS
];
75 /* fake analog input ranges */
76 static const struct comedi_lrange waveform_ai_ranges
= {
83 static unsigned short fake_sawtooth(struct comedi_device
*dev
,
84 unsigned int range_index
,
85 unsigned int current_time
)
87 struct waveform_private
*devpriv
= dev
->private;
88 struct comedi_subdevice
*s
= dev
->read_subdev
;
89 unsigned int offset
= s
->maxdata
/ 2;
91 const struct comedi_krange
*krange
=
92 &s
->range_table
->range
[range_index
];
95 binary_amplitude
= s
->maxdata
;
96 binary_amplitude
*= devpriv
->uvolt_amplitude
;
97 do_div(binary_amplitude
, krange
->max
- krange
->min
);
100 value
*= binary_amplitude
* 2;
101 do_div(value
, devpriv
->usec_period
);
103 /* get rid of sawtooth's dc offset and clamp value */
104 if (value
< binary_amplitude
) {
105 value
= 0; /* negative saturation */
107 value
-= binary_amplitude
;
108 if (value
> s
->maxdata
)
109 value
= s
->maxdata
; /* positive saturation */
115 static unsigned short fake_squarewave(struct comedi_device
*dev
,
116 unsigned int range_index
,
117 unsigned int current_time
)
119 struct waveform_private
*devpriv
= dev
->private;
120 struct comedi_subdevice
*s
= dev
->read_subdev
;
121 unsigned int offset
= s
->maxdata
/ 2;
123 const struct comedi_krange
*krange
=
124 &s
->range_table
->range
[range_index
];
127 value
*= devpriv
->uvolt_amplitude
;
128 do_div(value
, krange
->max
- krange
->min
);
130 /* get one of two values for square-wave and clamp */
131 if (current_time
< devpriv
->usec_period
/ 2) {
133 value
= 0; /* negative saturation */
135 value
= offset
- value
;
138 if (value
> s
->maxdata
)
139 value
= s
->maxdata
; /* positive saturation */
145 static unsigned short fake_flatline(struct comedi_device
*dev
,
146 unsigned int range_index
,
147 unsigned int current_time
)
149 return dev
->read_subdev
->maxdata
/ 2;
152 /* generates a different waveform depending on what channel is read */
153 static unsigned short fake_waveform(struct comedi_device
*dev
,
154 unsigned int channel
, unsigned int range
,
155 unsigned int current_time
)
163 return fake_sawtooth(dev
, range
, current_time
);
165 return fake_squarewave(dev
, range
, current_time
);
170 return fake_flatline(dev
, range
, current_time
);
174 * This is the background routine used to generate arbitrary data.
175 * It should run in the background; therefore it is scheduled by
178 static void waveform_ai_interrupt(unsigned long arg
)
180 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
181 struct waveform_private
*devpriv
= dev
->private;
182 struct comedi_subdevice
*s
= dev
->read_subdev
;
183 struct comedi_async
*async
= s
->async
;
184 struct comedi_cmd
*cmd
= &async
->cmd
;
186 unsigned long elapsed_time
;
187 unsigned int num_scans
;
189 /* check command is still active */
190 if (!test_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
))
193 elapsed_time
= ktime_to_us(ktime_get()) - devpriv
->ai_last_scan_time
;
194 num_scans
= elapsed_time
/ devpriv
->scan_period
;
196 num_scans
= comedi_nscans_left(s
, num_scans
);
197 for (i
= 0; i
< num_scans
; i
++) {
198 unsigned int scan_remain_period
= devpriv
->scan_period
;
200 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
201 unsigned short sample
;
203 if (devpriv
->usec_current
>= devpriv
->usec_period
)
204 devpriv
->usec_current
%= devpriv
->usec_period
;
205 sample
= fake_waveform(dev
, CR_CHAN(cmd
->chanlist
[j
]),
206 CR_RANGE(cmd
->chanlist
[j
]),
207 devpriv
->usec_current
);
208 comedi_buf_write_samples(s
, &sample
, 1);
209 devpriv
->usec_current
+= devpriv
->convert_period
;
210 scan_remain_period
-= devpriv
->convert_period
;
212 devpriv
->usec_current
+= scan_remain_period
;
213 devpriv
->ai_last_scan_time
+= devpriv
->scan_period
;
215 if (devpriv
->usec_current
>= devpriv
->usec_period
)
216 devpriv
->usec_current
%= devpriv
->usec_period
;
218 if (cmd
->stop_src
== TRIG_COUNT
&& async
->scans_done
>= cmd
->stop_arg
)
219 async
->events
|= COMEDI_CB_EOA
;
221 mod_timer(&devpriv
->timer
, jiffies
+ 1);
223 comedi_handle_events(dev
, s
);
226 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
227 struct comedi_subdevice
*s
,
228 struct comedi_cmd
*cmd
)
231 unsigned int arg
, limit
;
233 /* Step 1 : check if triggers are trivially valid */
235 err
|= comedi_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
236 err
|= comedi_check_trigger_src(&cmd
->scan_begin_src
,
237 TRIG_FOLLOW
| TRIG_TIMER
);
238 err
|= comedi_check_trigger_src(&cmd
->convert_src
,
239 TRIG_NOW
| TRIG_TIMER
);
240 err
|= comedi_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
241 err
|= comedi_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
246 /* Step 2a : make sure trigger sources are unique */
248 err
|= comedi_check_trigger_is_unique(cmd
->convert_src
);
249 err
|= comedi_check_trigger_is_unique(cmd
->stop_src
);
251 /* Step 2b : and mutually compatible */
253 if (cmd
->scan_begin_src
== TRIG_FOLLOW
&& cmd
->convert_src
== TRIG_NOW
)
254 err
|= -EINVAL
; /* scan period would be 0 */
259 /* Step 3: check if arguments are trivially valid */
261 err
|= comedi_check_trigger_arg_is(&cmd
->start_arg
, 0);
263 if (cmd
->convert_src
== TRIG_NOW
) {
264 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, 0);
265 } else { /* cmd->convert_src == TRIG_TIMER */
266 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
267 err
|= comedi_check_trigger_arg_min(&cmd
->convert_arg
,
272 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
273 err
|= comedi_check_trigger_arg_is(&cmd
->scan_begin_arg
, 0);
274 } else { /* cmd->scan_begin_src == TRIG_TIMER */
275 err
|= comedi_check_trigger_arg_min(&cmd
->scan_begin_arg
,
279 err
|= comedi_check_trigger_arg_min(&cmd
->chanlist_len
, 1);
280 err
|= comedi_check_trigger_arg_is(&cmd
->scan_end_arg
,
283 if (cmd
->stop_src
== TRIG_COUNT
)
284 err
|= comedi_check_trigger_arg_min(&cmd
->stop_arg
, 1);
285 else /* cmd->stop_src == TRIG_NONE */
286 err
|= comedi_check_trigger_arg_is(&cmd
->stop_arg
, 0);
291 /* step 4: fix up any arguments */
293 if (cmd
->convert_src
== TRIG_TIMER
) {
294 /* round convert_arg to nearest microsecond */
295 arg
= cmd
->convert_arg
;
297 rounddown(UINT_MAX
, (unsigned int)NSEC_PER_USEC
));
298 arg
= NSEC_PER_USEC
* DIV_ROUND_CLOSEST(arg
, NSEC_PER_USEC
);
299 if (cmd
->scan_begin_arg
== TRIG_TIMER
) {
300 /* limit convert_arg to keep scan_begin_arg in range */
301 limit
= UINT_MAX
/ cmd
->scan_end_arg
;
302 limit
= rounddown(limit
, (unsigned int)NSEC_PER_SEC
);
303 arg
= min(arg
, limit
);
305 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, arg
);
308 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
309 /* round scan_begin_arg to nearest microsecond */
310 arg
= cmd
->scan_begin_arg
;
312 rounddown(UINT_MAX
, (unsigned int)NSEC_PER_USEC
));
313 arg
= NSEC_PER_USEC
* DIV_ROUND_CLOSEST(arg
, NSEC_PER_USEC
);
314 if (cmd
->convert_src
== TRIG_TIMER
) {
315 /* but ensure scan_begin_arg is large enough */
316 arg
= max(arg
, cmd
->convert_arg
* cmd
->scan_end_arg
);
318 err
|= comedi_check_trigger_arg_is(&cmd
->scan_begin_arg
, arg
);
327 static int waveform_ai_cmd(struct comedi_device
*dev
,
328 struct comedi_subdevice
*s
)
330 struct waveform_private
*devpriv
= dev
->private;
331 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
334 if (cmd
->flags
& CMDF_PRIORITY
) {
335 dev_err(dev
->class_dev
,
336 "commands at RT priority not supported in this driver\n");
340 if (cmd
->convert_src
== TRIG_NOW
)
341 devpriv
->convert_period
= 0;
342 else /* cmd->convert_src == TRIG_TIMER */
343 devpriv
->convert_period
= cmd
->convert_arg
/ NSEC_PER_USEC
;
345 if (cmd
->scan_begin_src
== TRIG_FOLLOW
) {
346 devpriv
->scan_period
= devpriv
->convert_period
*
348 } else { /* cmd->scan_begin_src == TRIG_TIMER */
349 devpriv
->scan_period
= cmd
->scan_begin_arg
/ NSEC_PER_USEC
;
352 devpriv
->ai_last_scan_time
= ktime_to_us(ktime_get());
353 /* Determine time within waveform period. */
354 usec_current
= devpriv
->ai_last_scan_time
;
355 devpriv
->usec_current
= do_div(usec_current
, devpriv
->usec_period
);
357 devpriv
->timer
.expires
= jiffies
+ 1;
358 /* mark command as active */
359 smp_mb__before_atomic();
360 set_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
361 smp_mb__after_atomic();
362 add_timer(&devpriv
->timer
);
366 static int waveform_ai_cancel(struct comedi_device
*dev
,
367 struct comedi_subdevice
*s
)
369 struct waveform_private
*devpriv
= dev
->private;
371 /* mark command as no longer active */
372 clear_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
373 smp_mb__after_atomic();
374 /* cannot call del_timer_sync() as may be called from timer routine */
375 del_timer(&devpriv
->timer
);
379 static int waveform_ai_insn_read(struct comedi_device
*dev
,
380 struct comedi_subdevice
*s
,
381 struct comedi_insn
*insn
, unsigned int *data
)
383 struct waveform_private
*devpriv
= dev
->private;
384 int i
, chan
= CR_CHAN(insn
->chanspec
);
386 for (i
= 0; i
< insn
->n
; i
++)
387 data
[i
] = devpriv
->ao_loopbacks
[chan
];
392 static int waveform_ao_insn_write(struct comedi_device
*dev
,
393 struct comedi_subdevice
*s
,
394 struct comedi_insn
*insn
, unsigned int *data
)
396 struct waveform_private
*devpriv
= dev
->private;
397 int i
, chan
= CR_CHAN(insn
->chanspec
);
399 for (i
= 0; i
< insn
->n
; i
++)
400 devpriv
->ao_loopbacks
[chan
] = data
[i
];
405 static int waveform_attach(struct comedi_device
*dev
,
406 struct comedi_devconfig
*it
)
408 struct waveform_private
*devpriv
;
409 struct comedi_subdevice
*s
;
410 int amplitude
= it
->options
[0];
411 int period
= it
->options
[1];
415 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
419 /* set default amplitude and period */
421 amplitude
= 1000000; /* 1 volt */
423 period
= 100000; /* 0.1 sec */
425 devpriv
->uvolt_amplitude
= amplitude
;
426 devpriv
->usec_period
= period
;
428 ret
= comedi_alloc_subdevices(dev
, 2);
432 s
= &dev
->subdevices
[0];
433 dev
->read_subdev
= s
;
434 /* analog input subdevice */
435 s
->type
= COMEDI_SUBD_AI
;
436 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
439 s
->range_table
= &waveform_ai_ranges
;
440 s
->len_chanlist
= s
->n_chan
* 2;
441 s
->insn_read
= waveform_ai_insn_read
;
442 s
->do_cmd
= waveform_ai_cmd
;
443 s
->do_cmdtest
= waveform_ai_cmdtest
;
444 s
->cancel
= waveform_ai_cancel
;
446 s
= &dev
->subdevices
[1];
447 dev
->write_subdev
= s
;
448 /* analog output subdevice (loopback) */
449 s
->type
= COMEDI_SUBD_AO
;
450 s
->subdev_flags
= SDF_WRITABLE
| SDF_GROUND
;
453 s
->range_table
= &waveform_ai_ranges
;
454 s
->insn_write
= waveform_ao_insn_write
;
456 /* Our default loopback value is just a 0V flatline */
457 for (i
= 0; i
< s
->n_chan
; i
++)
458 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
460 setup_timer(&devpriv
->timer
, waveform_ai_interrupt
,
463 dev_info(dev
->class_dev
,
464 "%s: %u microvolt, %u microsecond waveform attached\n",
466 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
471 static void waveform_detach(struct comedi_device
*dev
)
473 struct waveform_private
*devpriv
= dev
->private;
476 del_timer_sync(&devpriv
->timer
);
479 static struct comedi_driver waveform_driver
= {
480 .driver_name
= "comedi_test",
481 .module
= THIS_MODULE
,
482 .attach
= waveform_attach
,
483 .detach
= waveform_detach
,
485 module_comedi_driver(waveform_driver
);
487 MODULE_AUTHOR("Comedi http://www.comedi.org");
488 MODULE_DESCRIPTION("Comedi low-level driver");
489 MODULE_LICENSE("GPL");