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.
27 Description: generates fake waveforms
28 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
29 <fmhess@users.sourceforge.net>, ds
32 Updated: Sat, 16 Mar 2002 17:34:48 -0800
34 This driver is mainly for testing purposes, but can also be used to
35 generate sample waveforms on systems that don't have data acquisition
38 Configuration options:
39 [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
40 [1] - Period in microseconds for fake waveforms (default 0.1 sec)
42 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
43 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 "comedi_fc.h"
54 #include <linux/timer.h>
55 #include <linux/ktime.h>
59 /* Data unique to this driver */
60 struct waveform_private
{
61 struct timer_list timer
;
62 ktime_t last
; /* time last timer interrupt occurred */
63 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
64 unsigned long usec_period
; /* waveform period in microseconds */
65 unsigned long usec_current
; /* current time (mod waveform period) */
66 unsigned long usec_remainder
; /* usec since last scan */
67 unsigned long ai_count
; /* number of conversions remaining */
68 unsigned int scan_period
; /* scan period in usec */
69 unsigned int convert_period
; /* conversion period in usec */
70 unsigned int ao_loopbacks
[N_CHANS
];
73 /* 1000 nanosec in a microsec */
74 static const int nano_per_micro
= 1000;
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 long 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
->uvolt_amplitude
;
98 do_div(binary_amplitude
, krange
->max
- krange
->min
);
100 current_time
%= devpriv
->usec_period
;
101 value
= current_time
;
102 value
*= binary_amplitude
* 2;
103 do_div(value
, devpriv
->usec_period
);
104 value
-= binary_amplitude
; /* get rid of sawtooth's dc offset */
106 return offset
+ value
;
109 static unsigned short fake_squarewave(struct comedi_device
*dev
,
110 unsigned int range_index
,
111 unsigned long current_time
)
113 struct waveform_private
*devpriv
= dev
->private;
114 struct comedi_subdevice
*s
= dev
->read_subdev
;
115 unsigned int offset
= s
->maxdata
/ 2;
117 const struct comedi_krange
*krange
=
118 &s
->range_table
->range
[range_index
];
119 current_time
%= devpriv
->usec_period
;
122 value
*= devpriv
->uvolt_amplitude
;
123 do_div(value
, krange
->max
- krange
->min
);
125 if (current_time
< devpriv
->usec_period
/ 2)
128 return offset
+ value
;
131 static unsigned short fake_flatline(struct comedi_device
*dev
,
132 unsigned int range_index
,
133 unsigned long current_time
)
135 return dev
->read_subdev
->maxdata
/ 2;
138 /* generates a different waveform depending on what channel is read */
139 static unsigned short fake_waveform(struct comedi_device
*dev
,
140 unsigned int channel
, unsigned int range
,
141 unsigned long current_time
)
149 return fake_sawtooth(dev
, range
, current_time
);
151 return fake_squarewave(dev
, range
, current_time
);
156 return fake_flatline(dev
, range
, current_time
);
160 This is the background routine used to generate arbitrary data.
161 It should run in the background; therefore it is scheduled by
164 static void waveform_ai_interrupt(unsigned long arg
)
166 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
167 struct waveform_private
*devpriv
= dev
->private;
168 struct comedi_subdevice
*s
= dev
->read_subdev
;
169 struct comedi_async
*async
= s
->async
;
170 struct comedi_cmd
*cmd
= &async
->cmd
;
172 /* all times in microsec */
173 unsigned long elapsed_time
;
174 unsigned int num_scans
;
176 bool stopping
= false;
180 elapsed_time
= ktime_to_us(ktime_sub(now
, devpriv
->last
));
183 (devpriv
->usec_remainder
+ elapsed_time
) / devpriv
->scan_period
;
184 devpriv
->usec_remainder
=
185 (devpriv
->usec_remainder
+ elapsed_time
) % devpriv
->scan_period
;
187 if (cmd
->stop_src
== TRIG_COUNT
) {
188 unsigned int remaining
= cmd
->stop_arg
- devpriv
->ai_count
;
190 if (num_scans
>= remaining
) {
191 /* about to finish */
192 num_scans
= remaining
;
197 for (i
= 0; i
< num_scans
; i
++) {
198 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
199 unsigned short sample
;
201 sample
= fake_waveform(dev
, CR_CHAN(cmd
->chanlist
[j
]),
202 CR_RANGE(cmd
->chanlist
[j
]),
203 devpriv
->usec_current
+
204 i
* devpriv
->scan_period
+
205 j
* devpriv
->convert_period
);
206 cfc_write_to_buffer(s
, sample
);
210 devpriv
->ai_count
+= i
;
211 devpriv
->usec_current
+= elapsed_time
;
212 devpriv
->usec_current
%= devpriv
->usec_period
;
215 async
->events
|= COMEDI_CB_EOA
;
217 mod_timer(&devpriv
->timer
, jiffies
+ 1);
219 comedi_handle_events(dev
, s
);
222 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
223 struct comedi_subdevice
*s
,
224 struct comedi_cmd
*cmd
)
229 /* Step 1 : check if triggers are trivially valid */
231 err
|= cfc_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
232 err
|= cfc_check_trigger_src(&cmd
->scan_begin_src
, TRIG_TIMER
);
233 err
|= cfc_check_trigger_src(&cmd
->convert_src
, TRIG_NOW
| TRIG_TIMER
);
234 err
|= cfc_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
235 err
|= cfc_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
240 /* Step 2a : make sure trigger sources are unique */
242 err
|= cfc_check_trigger_is_unique(cmd
->convert_src
);
243 err
|= cfc_check_trigger_is_unique(cmd
->stop_src
);
245 /* Step 2b : and mutually compatible */
250 /* Step 3: check if arguments are trivially valid */
252 err
|= cfc_check_trigger_arg_is(&cmd
->start_arg
, 0);
254 if (cmd
->convert_src
== TRIG_NOW
)
255 err
|= cfc_check_trigger_arg_is(&cmd
->convert_arg
, 0);
257 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
258 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
260 if (cmd
->convert_src
== TRIG_TIMER
)
261 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
262 cmd
->convert_arg
* cmd
->chanlist_len
);
265 err
|= cfc_check_trigger_arg_min(&cmd
->chanlist_len
, 1);
266 err
|= cfc_check_trigger_arg_is(&cmd
->scan_end_arg
, cmd
->chanlist_len
);
268 if (cmd
->stop_src
== TRIG_COUNT
)
269 err
|= cfc_check_trigger_arg_min(&cmd
->stop_arg
, 1);
271 err
|= cfc_check_trigger_arg_is(&cmd
->stop_arg
, 0);
276 /* step 4: fix up any arguments */
278 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
279 arg
= cmd
->scan_begin_arg
;
280 /* round to nearest microsec */
281 arg
= nano_per_micro
*
282 ((arg
+ (nano_per_micro
/ 2)) / nano_per_micro
);
283 err
|= cfc_check_trigger_arg_is(&cmd
->scan_begin_arg
, arg
);
285 if (cmd
->convert_src
== TRIG_TIMER
) {
286 arg
= cmd
->convert_arg
;
287 /* round to nearest microsec */
288 arg
= nano_per_micro
*
289 ((arg
+ (nano_per_micro
/ 2)) / nano_per_micro
);
290 err
|= cfc_check_trigger_arg_is(&cmd
->convert_arg
, arg
);
299 static int waveform_ai_cmd(struct comedi_device
*dev
,
300 struct comedi_subdevice
*s
)
302 struct waveform_private
*devpriv
= dev
->private;
303 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
305 if (cmd
->flags
& CMDF_PRIORITY
) {
306 dev_err(dev
->class_dev
,
307 "commands at RT priority not supported in this driver\n");
311 devpriv
->ai_count
= 0;
312 devpriv
->scan_period
= cmd
->scan_begin_arg
/ nano_per_micro
;
314 if (cmd
->convert_src
== TRIG_NOW
)
315 devpriv
->convert_period
= 0;
316 else /* TRIG_TIMER */
317 devpriv
->convert_period
= cmd
->convert_arg
/ nano_per_micro
;
319 devpriv
->last
= ktime_get();
320 devpriv
->usec_current
=
321 ((u32
)ktime_to_us(devpriv
->last
)) % devpriv
->usec_period
;
322 devpriv
->usec_remainder
= 0;
324 devpriv
->timer
.expires
= jiffies
+ 1;
325 add_timer(&devpriv
->timer
);
329 static int waveform_ai_cancel(struct comedi_device
*dev
,
330 struct comedi_subdevice
*s
)
332 struct waveform_private
*devpriv
= dev
->private;
334 del_timer_sync(&devpriv
->timer
);
338 static int waveform_ai_insn_read(struct comedi_device
*dev
,
339 struct comedi_subdevice
*s
,
340 struct comedi_insn
*insn
, unsigned int *data
)
342 struct waveform_private
*devpriv
= dev
->private;
343 int i
, chan
= CR_CHAN(insn
->chanspec
);
345 for (i
= 0; i
< insn
->n
; i
++)
346 data
[i
] = devpriv
->ao_loopbacks
[chan
];
351 static int waveform_ao_insn_write(struct comedi_device
*dev
,
352 struct comedi_subdevice
*s
,
353 struct comedi_insn
*insn
, unsigned int *data
)
355 struct waveform_private
*devpriv
= dev
->private;
356 int i
, chan
= CR_CHAN(insn
->chanspec
);
358 for (i
= 0; i
< insn
->n
; i
++)
359 devpriv
->ao_loopbacks
[chan
] = data
[i
];
364 static int waveform_attach(struct comedi_device
*dev
,
365 struct comedi_devconfig
*it
)
367 struct waveform_private
*devpriv
;
368 struct comedi_subdevice
*s
;
369 int amplitude
= it
->options
[0];
370 int period
= it
->options
[1];
374 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
378 /* set default amplitude and period */
380 amplitude
= 1000000; /* 1 volt */
382 period
= 100000; /* 0.1 sec */
384 devpriv
->uvolt_amplitude
= amplitude
;
385 devpriv
->usec_period
= period
;
387 ret
= comedi_alloc_subdevices(dev
, 2);
391 s
= &dev
->subdevices
[0];
392 dev
->read_subdev
= s
;
393 /* analog input subdevice */
394 s
->type
= COMEDI_SUBD_AI
;
395 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
398 s
->range_table
= &waveform_ai_ranges
;
399 s
->len_chanlist
= s
->n_chan
* 2;
400 s
->insn_read
= waveform_ai_insn_read
;
401 s
->do_cmd
= waveform_ai_cmd
;
402 s
->do_cmdtest
= waveform_ai_cmdtest
;
403 s
->cancel
= waveform_ai_cancel
;
405 s
= &dev
->subdevices
[1];
406 dev
->write_subdev
= s
;
407 /* analog output subdevice (loopback) */
408 s
->type
= COMEDI_SUBD_AO
;
409 s
->subdev_flags
= SDF_WRITEABLE
| SDF_GROUND
;
412 s
->range_table
= &waveform_ai_ranges
;
413 s
->insn_write
= waveform_ao_insn_write
;
415 /* Our default loopback value is just a 0V flatline */
416 for (i
= 0; i
< s
->n_chan
; i
++)
417 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
419 init_timer(&devpriv
->timer
);
420 devpriv
->timer
.function
= waveform_ai_interrupt
;
421 devpriv
->timer
.data
= (unsigned long)dev
;
423 dev_info(dev
->class_dev
,
424 "%s: %i microvolt, %li microsecond waveform attached\n",
426 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
431 static void waveform_detach(struct comedi_device
*dev
)
433 struct waveform_private
*devpriv
= dev
->private;
436 waveform_ai_cancel(dev
, dev
->read_subdev
);
439 static struct comedi_driver waveform_driver
= {
440 .driver_name
= "comedi_test",
441 .module
= THIS_MODULE
,
442 .attach
= waveform_attach
,
443 .detach
= waveform_detach
,
445 module_comedi_driver(waveform_driver
);
447 MODULE_AUTHOR("Comedi http://www.comedi.org");
448 MODULE_DESCRIPTION("Comedi low-level driver");
449 MODULE_LICENSE("GPL");