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.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 ************************************************************************/
32 Description: generates fake waveforms
33 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
34 <fmhess@users.sourceforge.net>, ds
37 Updated: Sat, 16 Mar 2002 17:34:48 -0800
39 This driver is mainly for testing purposes, but can also be used to
40 generate sample waveforms on systems that don't have data acquisition
43 Configuration options:
44 [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
45 [1] - Period in microseconds for fake waveforms (default 0.1 sec)
47 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
48 waveforms could be added to other channels (currently they return flatline
53 #include "../comedidev.h"
55 #include <asm/div64.h>
57 #include "comedi_fc.h"
58 #include <linux/timer.h>
60 /* Board descriptions */
61 struct waveform_board
{
70 static const struct waveform_board waveform_boards
[] = {
72 .name
= "comedi_test",
79 #define thisboard ((const struct waveform_board *)dev->board_ptr)
81 /* Data unique to this driver */
82 struct waveform_private
{
83 struct timer_list timer
;
84 struct timeval last
; /* time at which last timer interrupt occurred */
85 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
86 unsigned long usec_period
; /* waveform period in microseconds */
87 unsigned long usec_current
; /* current time (modulo waveform period) */
88 unsigned long usec_remainder
; /* usec since last scan; */
89 unsigned long ai_count
; /* number of conversions remaining */
90 unsigned int scan_period
; /* scan period in usec */
91 unsigned int convert_period
; /* conversion period in usec */
92 unsigned timer_running
:1;
93 unsigned int ao_loopbacks
[N_CHANS
];
95 #define devpriv ((struct waveform_private *)dev->private)
97 static int waveform_attach(struct comedi_device
*dev
,
98 struct comedi_devconfig
*it
);
99 static int waveform_detach(struct comedi_device
*dev
);
100 static struct comedi_driver driver_waveform
= {
101 .driver_name
= "comedi_test",
102 .module
= THIS_MODULE
,
103 .attach
= waveform_attach
,
104 .detach
= waveform_detach
,
105 .board_name
= &waveform_boards
[0].name
,
106 .offset
= sizeof(struct waveform_board
),
107 .num_names
= ARRAY_SIZE(waveform_boards
),
110 static int __init
driver_waveform_init_module(void)
112 return comedi_driver_register(&driver_waveform
);
115 static void __exit
driver_waveform_cleanup_module(void)
117 comedi_driver_unregister(&driver_waveform
);
120 module_init(driver_waveform_init_module
);
121 module_exit(driver_waveform_cleanup_module
);
123 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
124 struct comedi_subdevice
*s
,
125 struct comedi_cmd
*cmd
);
126 static int waveform_ai_cmd(struct comedi_device
*dev
,
127 struct comedi_subdevice
*s
);
128 static int waveform_ai_cancel(struct comedi_device
*dev
,
129 struct comedi_subdevice
*s
);
130 static int waveform_ai_insn_read(struct comedi_device
*dev
,
131 struct comedi_subdevice
*s
,
132 struct comedi_insn
*insn
, unsigned int *data
);
133 static int waveform_ao_insn_write(struct comedi_device
*dev
,
134 struct comedi_subdevice
*s
,
135 struct comedi_insn
*insn
, unsigned int *data
);
136 static short fake_sawtooth(struct comedi_device
*dev
, unsigned int range
,
137 unsigned long current_time
);
138 static short fake_squarewave(struct comedi_device
*dev
, unsigned int range
,
139 unsigned long current_time
);
140 static short fake_flatline(struct comedi_device
*dev
, unsigned int range
,
141 unsigned long current_time
);
142 static short fake_waveform(struct comedi_device
*dev
, unsigned int channel
,
143 unsigned int range
, unsigned long current_time
);
145 /* 1000 nanosec in a microsec */
146 static const int nano_per_micro
= 1000;
148 /* fake analog input ranges */
149 static const struct comedi_lrange waveform_ai_ranges
= {
158 This is the background routine used to generate arbitrary data.
159 It should run in the background; therefore it is scheduled by
162 static void waveform_ai_interrupt(unsigned long arg
)
164 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
165 struct comedi_async
*async
= dev
->read_subdev
->async
;
166 struct comedi_cmd
*cmd
= &async
->cmd
;
168 /* all times in microsec */
169 unsigned long elapsed_time
;
170 unsigned int num_scans
;
173 do_gettimeofday(&now
);
176 1000000 * (now
.tv_sec
- devpriv
->last
.tv_sec
) + now
.tv_usec
-
177 devpriv
->last
.tv_usec
;
180 (devpriv
->usec_remainder
+ elapsed_time
) / devpriv
->scan_period
;
181 devpriv
->usec_remainder
=
182 (devpriv
->usec_remainder
+ elapsed_time
) % devpriv
->scan_period
;
185 for (i
= 0; i
< num_scans
; i
++) {
186 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
187 cfc_write_to_buffer(dev
->read_subdev
,
196 devpriv
->scan_period
+
202 if (cmd
->stop_src
== TRIG_COUNT
203 && devpriv
->ai_count
>= cmd
->stop_arg
) {
204 async
->events
|= COMEDI_CB_EOA
;
209 devpriv
->usec_current
+= elapsed_time
;
210 devpriv
->usec_current
%= devpriv
->usec_period
;
212 if ((async
->events
& COMEDI_CB_EOA
) == 0 && devpriv
->timer_running
)
213 mod_timer(&devpriv
->timer
, jiffies
+ 1);
215 del_timer(&devpriv
->timer
);
217 comedi_event(dev
, dev
->read_subdev
);
220 static int waveform_attach(struct comedi_device
*dev
,
221 struct comedi_devconfig
*it
)
223 struct comedi_subdevice
*s
;
224 int amplitude
= it
->options
[0];
225 int period
= it
->options
[1];
228 dev
->board_name
= thisboard
->name
;
230 if (alloc_private(dev
, sizeof(struct waveform_private
)) < 0)
233 /* set default amplitude and period */
235 amplitude
= 1000000; /* 1 volt */
237 period
= 100000; /* 0.1 sec */
239 devpriv
->uvolt_amplitude
= amplitude
;
240 devpriv
->usec_period
= period
;
242 dev
->n_subdevices
= 2;
243 if (alloc_subdevices(dev
, dev
->n_subdevices
) < 0)
246 s
= dev
->subdevices
+ 0;
247 dev
->read_subdev
= s
;
248 /* analog input subdevice */
249 s
->type
= COMEDI_SUBD_AI
;
250 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
251 s
->n_chan
= thisboard
->ai_chans
;
252 s
->maxdata
= (1 << thisboard
->ai_bits
) - 1;
253 s
->range_table
= &waveform_ai_ranges
;
254 s
->len_chanlist
= s
->n_chan
* 2;
255 s
->insn_read
= waveform_ai_insn_read
;
256 s
->do_cmd
= waveform_ai_cmd
;
257 s
->do_cmdtest
= waveform_ai_cmdtest
;
258 s
->cancel
= waveform_ai_cancel
;
260 s
= dev
->subdevices
+ 1;
261 dev
->write_subdev
= s
;
262 /* analog output subdevice (loopback) */
263 s
->type
= COMEDI_SUBD_AO
;
264 s
->subdev_flags
= SDF_WRITEABLE
| SDF_GROUND
;
265 s
->n_chan
= thisboard
->ai_chans
;
266 s
->maxdata
= (1 << thisboard
->ai_bits
) - 1;
267 s
->range_table
= &waveform_ai_ranges
;
268 s
->len_chanlist
= s
->n_chan
* 2;
269 s
->insn_write
= waveform_ao_insn_write
;
271 s
->do_cmdtest
= NULL
;
274 /* Our default loopback value is just a 0V flatline */
275 for (i
= 0; i
< s
->n_chan
; i
++)
276 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
278 init_timer(&(devpriv
->timer
));
279 devpriv
->timer
.function
= waveform_ai_interrupt
;
280 devpriv
->timer
.data
= (unsigned long)dev
;
282 printk(KERN_INFO
"comedi%d: comedi_test: "
283 "%i microvolt, %li microsecond waveform attached\n", dev
->minor
,
284 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
288 static int waveform_detach(struct comedi_device
*dev
)
290 printk("comedi%d: comedi_test: remove\n", dev
->minor
);
293 waveform_ai_cancel(dev
, dev
->read_subdev
);
298 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
299 struct comedi_subdevice
*s
,
300 struct comedi_cmd
*cmd
)
305 /* step 1: make sure trigger sources are trivially valid */
307 tmp
= cmd
->start_src
;
308 cmd
->start_src
&= TRIG_NOW
;
309 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
312 tmp
= cmd
->scan_begin_src
;
313 cmd
->scan_begin_src
&= TRIG_TIMER
;
314 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
317 tmp
= cmd
->convert_src
;
318 cmd
->convert_src
&= TRIG_NOW
| TRIG_TIMER
;
319 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
322 tmp
= cmd
->scan_end_src
;
323 cmd
->scan_end_src
&= TRIG_COUNT
;
324 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
328 cmd
->stop_src
&= TRIG_COUNT
| TRIG_NONE
;
329 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
336 * step 2: make sure trigger sources are unique and mutually compatible
339 if (cmd
->convert_src
!= TRIG_NOW
&& cmd
->convert_src
!= TRIG_TIMER
)
341 if (cmd
->stop_src
!= TRIG_COUNT
&& cmd
->stop_src
!= TRIG_NONE
)
347 /* step 3: make sure arguments are trivially compatible */
349 if (cmd
->start_arg
!= 0) {
353 if (cmd
->convert_src
== TRIG_NOW
) {
354 if (cmd
->convert_arg
!= 0) {
355 cmd
->convert_arg
= 0;
359 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
360 if (cmd
->scan_begin_arg
< nano_per_micro
) {
361 cmd
->scan_begin_arg
= nano_per_micro
;
364 if (cmd
->convert_src
== TRIG_TIMER
&&
365 cmd
->scan_begin_arg
<
366 cmd
->convert_arg
* cmd
->chanlist_len
) {
367 cmd
->scan_begin_arg
=
368 cmd
->convert_arg
* cmd
->chanlist_len
;
373 * XXX these checks are generic and should go in core if not there
376 if (!cmd
->chanlist_len
) {
377 cmd
->chanlist_len
= 1;
380 if (cmd
->scan_end_arg
!= cmd
->chanlist_len
) {
381 cmd
->scan_end_arg
= cmd
->chanlist_len
;
385 if (cmd
->stop_src
== TRIG_COUNT
) {
386 if (!cmd
->stop_arg
) {
390 } else { /* TRIG_NONE */
391 if (cmd
->stop_arg
!= 0) {
400 /* step 4: fix up any arguments */
402 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
403 tmp
= cmd
->scan_begin_arg
;
404 /* round to nearest microsec */
405 cmd
->scan_begin_arg
=
406 nano_per_micro
* ((tmp
+
407 (nano_per_micro
/ 2)) / nano_per_micro
);
408 if (tmp
!= cmd
->scan_begin_arg
)
411 if (cmd
->convert_src
== TRIG_TIMER
) {
412 tmp
= cmd
->convert_arg
;
413 /* round to nearest microsec */
415 nano_per_micro
* ((tmp
+
416 (nano_per_micro
/ 2)) / nano_per_micro
);
417 if (tmp
!= cmd
->convert_arg
)
427 static int waveform_ai_cmd(struct comedi_device
*dev
,
428 struct comedi_subdevice
*s
)
430 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
432 if (cmd
->flags
& TRIG_RT
) {
434 "commands at RT priority not supported in this driver");
438 devpriv
->timer_running
= 1;
439 devpriv
->ai_count
= 0;
440 devpriv
->scan_period
= cmd
->scan_begin_arg
/ nano_per_micro
;
442 if (cmd
->convert_src
== TRIG_NOW
)
443 devpriv
->convert_period
= 0;
444 else if (cmd
->convert_src
== TRIG_TIMER
)
445 devpriv
->convert_period
= cmd
->convert_arg
/ nano_per_micro
;
447 comedi_error(dev
, "bug setting conversion period");
451 do_gettimeofday(&devpriv
->last
);
452 devpriv
->usec_current
= devpriv
->last
.tv_usec
% devpriv
->usec_period
;
453 devpriv
->usec_remainder
= 0;
455 devpriv
->timer
.expires
= jiffies
+ 1;
456 add_timer(&devpriv
->timer
);
460 static int waveform_ai_cancel(struct comedi_device
*dev
,
461 struct comedi_subdevice
*s
)
463 devpriv
->timer_running
= 0;
464 del_timer(&devpriv
->timer
);
468 static short fake_sawtooth(struct comedi_device
*dev
, unsigned int range_index
,
469 unsigned long current_time
)
471 struct comedi_subdevice
*s
= dev
->read_subdev
;
472 unsigned int offset
= s
->maxdata
/ 2;
474 const struct comedi_krange
*krange
=
475 &s
->range_table
->range
[range_index
];
476 u64 binary_amplitude
;
478 binary_amplitude
= s
->maxdata
;
479 binary_amplitude
*= devpriv
->uvolt_amplitude
;
480 do_div(binary_amplitude
, krange
->max
- krange
->min
);
482 current_time
%= devpriv
->usec_period
;
483 value
= current_time
;
484 value
*= binary_amplitude
* 2;
485 do_div(value
, devpriv
->usec_period
);
486 value
-= binary_amplitude
; /* get rid of sawtooth's dc offset */
488 return offset
+ value
;
491 static short fake_squarewave(struct comedi_device
*dev
,
492 unsigned int range_index
,
493 unsigned long current_time
)
495 struct comedi_subdevice
*s
= dev
->read_subdev
;
496 unsigned int offset
= s
->maxdata
/ 2;
498 const struct comedi_krange
*krange
=
499 &s
->range_table
->range
[range_index
];
500 current_time
%= devpriv
->usec_period
;
503 value
*= devpriv
->uvolt_amplitude
;
504 do_div(value
, krange
->max
- krange
->min
);
506 if (current_time
< devpriv
->usec_period
/ 2)
509 return offset
+ value
;
512 static short fake_flatline(struct comedi_device
*dev
, unsigned int range_index
,
513 unsigned long current_time
)
515 return dev
->read_subdev
->maxdata
/ 2;
518 /* generates a different waveform depending on what channel is read */
519 static short fake_waveform(struct comedi_device
*dev
, unsigned int channel
,
520 unsigned int range
, unsigned long current_time
)
528 return fake_sawtooth(dev
, range
, current_time
);
531 return fake_squarewave(dev
, range
, current_time
);
537 return fake_flatline(dev
, range
, current_time
);
540 static int waveform_ai_insn_read(struct comedi_device
*dev
,
541 struct comedi_subdevice
*s
,
542 struct comedi_insn
*insn
, unsigned int *data
)
544 int i
, chan
= CR_CHAN(insn
->chanspec
);
546 for (i
= 0; i
< insn
->n
; i
++)
547 data
[i
] = devpriv
->ao_loopbacks
[chan
];
552 static int waveform_ao_insn_write(struct comedi_device
*dev
,
553 struct comedi_subdevice
*s
,
554 struct comedi_insn
*insn
, unsigned int *data
)
556 int i
, chan
= CR_CHAN(insn
->chanspec
);
558 for (i
= 0; i
< insn
->n
; i
++)
559 devpriv
->ao_loopbacks
[chan
] = data
[i
];
564 MODULE_AUTHOR("Comedi http://www.comedi.org");
565 MODULE_DESCRIPTION("Comedi low-level driver");
566 MODULE_LICENSE("GPL");