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 ktime_t last
; /* time last timer interrupt occurred */
66 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
67 unsigned long usec_period
; /* waveform period in microseconds */
68 unsigned long usec_current
; /* current time (mod waveform period) */
69 unsigned long usec_remainder
; /* usec since last scan */
70 unsigned long state_bits
;
71 unsigned int scan_period
; /* scan period in usec */
72 unsigned int convert_period
; /* conversion period in usec */
73 unsigned int 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 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
);
105 /* get rid of sawtooth's dc offset and clamp value */
106 if (value
< binary_amplitude
) {
107 value
= 0; /* negative saturation */
109 value
-= binary_amplitude
;
110 if (value
> s
->maxdata
)
111 value
= s
->maxdata
; /* positive saturation */
117 static unsigned short fake_squarewave(struct comedi_device
*dev
,
118 unsigned int range_index
,
119 unsigned long current_time
)
121 struct waveform_private
*devpriv
= dev
->private;
122 struct comedi_subdevice
*s
= dev
->read_subdev
;
123 unsigned int offset
= s
->maxdata
/ 2;
125 const struct comedi_krange
*krange
=
126 &s
->range_table
->range
[range_index
];
128 current_time
%= devpriv
->usec_period
;
130 value
*= devpriv
->uvolt_amplitude
;
131 do_div(value
, krange
->max
- krange
->min
);
133 /* get one of two values for square-wave and clamp */
134 if (current_time
< devpriv
->usec_period
/ 2) {
136 value
= 0; /* negative saturation */
138 value
= offset
- value
;
141 if (value
> s
->maxdata
)
142 value
= s
->maxdata
; /* positive saturation */
148 static unsigned short fake_flatline(struct comedi_device
*dev
,
149 unsigned int range_index
,
150 unsigned long current_time
)
152 return dev
->read_subdev
->maxdata
/ 2;
155 /* generates a different waveform depending on what channel is read */
156 static unsigned short fake_waveform(struct comedi_device
*dev
,
157 unsigned int channel
, unsigned int range
,
158 unsigned long current_time
)
166 return fake_sawtooth(dev
, range
, current_time
);
168 return fake_squarewave(dev
, range
, current_time
);
173 return fake_flatline(dev
, range
, current_time
);
177 * This is the background routine used to generate arbitrary data.
178 * It should run in the background; therefore it is scheduled by
181 static void waveform_ai_interrupt(unsigned long arg
)
183 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
184 struct waveform_private
*devpriv
= dev
->private;
185 struct comedi_subdevice
*s
= dev
->read_subdev
;
186 struct comedi_async
*async
= s
->async
;
187 struct comedi_cmd
*cmd
= &async
->cmd
;
189 /* all times in microsec */
190 unsigned long elapsed_time
;
191 unsigned int num_scans
;
194 /* check command is still active */
195 if (!test_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
))
200 elapsed_time
= ktime_to_us(ktime_sub(now
, devpriv
->last
));
203 (devpriv
->usec_remainder
+ elapsed_time
) / devpriv
->scan_period
;
204 devpriv
->usec_remainder
=
205 (devpriv
->usec_remainder
+ elapsed_time
) % devpriv
->scan_period
;
207 num_scans
= comedi_nscans_left(s
, num_scans
);
208 for (i
= 0; i
< num_scans
; i
++) {
209 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
210 unsigned short sample
;
212 sample
= fake_waveform(dev
, CR_CHAN(cmd
->chanlist
[j
]),
213 CR_RANGE(cmd
->chanlist
[j
]),
214 devpriv
->usec_current
+
215 i
* devpriv
->scan_period
+
216 j
* devpriv
->convert_period
);
217 comedi_buf_write_samples(s
, &sample
, 1);
221 devpriv
->usec_current
+= elapsed_time
;
222 devpriv
->usec_current
%= devpriv
->usec_period
;
224 if (cmd
->stop_src
== TRIG_COUNT
&& async
->scans_done
>= cmd
->stop_arg
)
225 async
->events
|= COMEDI_CB_EOA
;
227 mod_timer(&devpriv
->timer
, jiffies
+ 1);
229 comedi_handle_events(dev
, s
);
232 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
233 struct comedi_subdevice
*s
,
234 struct comedi_cmd
*cmd
)
239 /* Step 1 : check if triggers are trivially valid */
241 err
|= comedi_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
242 err
|= comedi_check_trigger_src(&cmd
->scan_begin_src
, TRIG_TIMER
);
243 err
|= comedi_check_trigger_src(&cmd
->convert_src
,
244 TRIG_NOW
| TRIG_TIMER
);
245 err
|= comedi_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
246 err
|= comedi_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
251 /* Step 2a : make sure trigger sources are unique */
253 err
|= comedi_check_trigger_is_unique(cmd
->convert_src
);
254 err
|= comedi_check_trigger_is_unique(cmd
->stop_src
);
256 /* Step 2b : and mutually compatible */
261 /* Step 3: check if arguments are trivially valid */
263 err
|= comedi_check_trigger_arg_is(&cmd
->start_arg
, 0);
265 if (cmd
->convert_src
== TRIG_NOW
)
266 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, 0);
268 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
269 err
|= comedi_check_trigger_arg_min(&cmd
->scan_begin_arg
,
271 if (cmd
->convert_src
== TRIG_TIMER
) {
272 err
|= comedi_check_trigger_arg_min(&cmd
->
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);
286 err
|= comedi_check_trigger_arg_is(&cmd
->stop_arg
, 0);
291 /* step 4: fix up any arguments */
293 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
294 arg
= cmd
->scan_begin_arg
;
295 /* round to nearest microsec */
296 arg
= NSEC_PER_USEC
*
297 ((arg
+ (NSEC_PER_USEC
/ 2)) / NSEC_PER_USEC
);
298 err
|= comedi_check_trigger_arg_is(&cmd
->scan_begin_arg
, arg
);
300 if (cmd
->convert_src
== TRIG_TIMER
) {
301 arg
= cmd
->convert_arg
;
302 /* round to nearest microsec */
303 arg
= NSEC_PER_USEC
*
304 ((arg
+ (NSEC_PER_USEC
/ 2)) / NSEC_PER_USEC
);
305 err
|= comedi_check_trigger_arg_is(&cmd
->convert_arg
, arg
);
314 static int waveform_ai_cmd(struct comedi_device
*dev
,
315 struct comedi_subdevice
*s
)
317 struct waveform_private
*devpriv
= dev
->private;
318 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
320 if (cmd
->flags
& CMDF_PRIORITY
) {
321 dev_err(dev
->class_dev
,
322 "commands at RT priority not supported in this driver\n");
326 devpriv
->scan_period
= cmd
->scan_begin_arg
/ NSEC_PER_USEC
;
328 if (cmd
->convert_src
== TRIG_NOW
)
329 devpriv
->convert_period
= 0;
330 else /* TRIG_TIMER */
331 devpriv
->convert_period
= cmd
->convert_arg
/ NSEC_PER_USEC
;
333 devpriv
->last
= ktime_get();
334 devpriv
->usec_current
=
335 ((u32
)ktime_to_us(devpriv
->last
)) % devpriv
->usec_period
;
336 devpriv
->usec_remainder
= 0;
338 devpriv
->timer
.expires
= jiffies
+ 1;
339 /* mark command as active */
340 smp_mb__before_atomic();
341 set_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
342 smp_mb__after_atomic();
343 add_timer(&devpriv
->timer
);
347 static int waveform_ai_cancel(struct comedi_device
*dev
,
348 struct comedi_subdevice
*s
)
350 struct waveform_private
*devpriv
= dev
->private;
352 /* mark command as no longer active */
353 clear_bit(WAVEFORM_AI_RUNNING
, &devpriv
->state_bits
);
354 smp_mb__after_atomic();
355 /* cannot call del_timer_sync() as may be called from timer routine */
356 del_timer(&devpriv
->timer
);
360 static int waveform_ai_insn_read(struct comedi_device
*dev
,
361 struct comedi_subdevice
*s
,
362 struct comedi_insn
*insn
, unsigned int *data
)
364 struct waveform_private
*devpriv
= dev
->private;
365 int i
, chan
= CR_CHAN(insn
->chanspec
);
367 for (i
= 0; i
< insn
->n
; i
++)
368 data
[i
] = devpriv
->ao_loopbacks
[chan
];
373 static int waveform_ao_insn_write(struct comedi_device
*dev
,
374 struct comedi_subdevice
*s
,
375 struct comedi_insn
*insn
, unsigned int *data
)
377 struct waveform_private
*devpriv
= dev
->private;
378 int i
, chan
= CR_CHAN(insn
->chanspec
);
380 for (i
= 0; i
< insn
->n
; i
++)
381 devpriv
->ao_loopbacks
[chan
] = data
[i
];
386 static int waveform_attach(struct comedi_device
*dev
,
387 struct comedi_devconfig
*it
)
389 struct waveform_private
*devpriv
;
390 struct comedi_subdevice
*s
;
391 int amplitude
= it
->options
[0];
392 int period
= it
->options
[1];
396 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
400 /* set default amplitude and period */
402 amplitude
= 1000000; /* 1 volt */
404 period
= 100000; /* 0.1 sec */
406 devpriv
->uvolt_amplitude
= amplitude
;
407 devpriv
->usec_period
= period
;
409 ret
= comedi_alloc_subdevices(dev
, 2);
413 s
= &dev
->subdevices
[0];
414 dev
->read_subdev
= s
;
415 /* analog input subdevice */
416 s
->type
= COMEDI_SUBD_AI
;
417 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
420 s
->range_table
= &waveform_ai_ranges
;
421 s
->len_chanlist
= s
->n_chan
* 2;
422 s
->insn_read
= waveform_ai_insn_read
;
423 s
->do_cmd
= waveform_ai_cmd
;
424 s
->do_cmdtest
= waveform_ai_cmdtest
;
425 s
->cancel
= waveform_ai_cancel
;
427 s
= &dev
->subdevices
[1];
428 dev
->write_subdev
= s
;
429 /* analog output subdevice (loopback) */
430 s
->type
= COMEDI_SUBD_AO
;
431 s
->subdev_flags
= SDF_WRITABLE
| SDF_GROUND
;
434 s
->range_table
= &waveform_ai_ranges
;
435 s
->insn_write
= waveform_ao_insn_write
;
437 /* Our default loopback value is just a 0V flatline */
438 for (i
= 0; i
< s
->n_chan
; i
++)
439 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
441 setup_timer(&devpriv
->timer
, waveform_ai_interrupt
,
444 dev_info(dev
->class_dev
,
445 "%s: %i microvolt, %li microsecond waveform attached\n",
447 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
452 static void waveform_detach(struct comedi_device
*dev
)
454 struct waveform_private
*devpriv
= dev
->private;
457 del_timer_sync(&devpriv
->timer
);
460 static struct comedi_driver waveform_driver
= {
461 .driver_name
= "comedi_test",
462 .module
= THIS_MODULE
,
463 .attach
= waveform_attach
,
464 .detach
= waveform_detach
,
466 module_comedi_driver(waveform_driver
);
468 MODULE_AUTHOR("Comedi http://www.comedi.org");
469 MODULE_DESCRIPTION("Comedi low-level driver");
470 MODULE_LICENSE("GPL");