]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - drivers/staging/comedi/drivers/comedi_test.c
Fix common misspellings
[mirror_ubuntu-bionic-kernel.git] / drivers / staging / comedi / drivers / comedi_test.c
1 /*
2 comedi/drivers/comedi_test.c
3
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.
8
9 Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
10 Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
11
12 COMEDI - Linux Control and Measurement Device Interface
13 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
14
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.
19
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.
24
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.
28
29 ************************************************************************/
30 /*
31 Driver: comedi_test
32 Description: generates fake waveforms
33 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
34 <fmhess@users.sourceforge.net>, ds
35 Devices:
36 Status: works
37 Updated: Sat, 16 Mar 2002 17:34:48 -0800
38
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
41 hardware.
42
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)
46
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
49 zero volts).
50
51 */
52
53 #include "../comedidev.h"
54
55 #include <asm/div64.h>
56
57 #include "comedi_fc.h"
58 #include <linux/timer.h>
59
60 /* Board descriptions */
61 struct waveform_board {
62 const char *name;
63 int ai_chans;
64 int ai_bits;
65 int have_dio;
66 };
67
68 #define N_CHANS 8
69
70 static const struct waveform_board waveform_boards[] = {
71 {
72 .name = "comedi_test",
73 .ai_chans = N_CHANS,
74 .ai_bits = 16,
75 .have_dio = 0,
76 },
77 };
78
79 #define thisboard ((const struct waveform_board *)dev->board_ptr)
80
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];
94 };
95 #define devpriv ((struct waveform_private *)dev->private)
96
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),
108 };
109
110 static int __init driver_waveform_init_module(void)
111 {
112 return comedi_driver_register(&driver_waveform);
113 }
114
115 static void __exit driver_waveform_cleanup_module(void)
116 {
117 comedi_driver_unregister(&driver_waveform);
118 }
119
120 module_init(driver_waveform_init_module);
121 module_exit(driver_waveform_cleanup_module);
122
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);
144
145 /* 1000 nanosec in a microsec */
146 static const int nano_per_micro = 1000;
147
148 /* fake analog input ranges */
149 static const struct comedi_lrange waveform_ai_ranges = {
150 2,
151 {
152 BIP_RANGE(10),
153 BIP_RANGE(5),
154 }
155 };
156
157 /*
158 This is the background routine used to generate arbitrary data.
159 It should run in the background; therefore it is scheduled by
160 a timer mechanism.
161 */
162 static void waveform_ai_interrupt(unsigned long arg)
163 {
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;
167 unsigned int i, j;
168 /* all times in microsec */
169 unsigned long elapsed_time;
170 unsigned int num_scans;
171 struct timeval now;
172
173 do_gettimeofday(&now);
174
175 elapsed_time =
176 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
177 devpriv->last.tv_usec;
178 devpriv->last = now;
179 num_scans =
180 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
181 devpriv->usec_remainder =
182 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
183 async->events = 0;
184
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,
188 fake_waveform(dev,
189 CR_CHAN(cmd->
190 chanlist[j]),
191 CR_RANGE(cmd->
192 chanlist[j]),
193 devpriv->
194 usec_current +
195 i *
196 devpriv->scan_period +
197 j *
198 devpriv->
199 convert_period));
200 }
201 devpriv->ai_count++;
202 if (cmd->stop_src == TRIG_COUNT
203 && devpriv->ai_count >= cmd->stop_arg) {
204 async->events |= COMEDI_CB_EOA;
205 break;
206 }
207 }
208
209 devpriv->usec_current += elapsed_time;
210 devpriv->usec_current %= devpriv->usec_period;
211
212 if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
213 mod_timer(&devpriv->timer, jiffies + 1);
214 else
215 del_timer(&devpriv->timer);
216
217 comedi_event(dev, dev->read_subdev);
218 }
219
220 static int waveform_attach(struct comedi_device *dev,
221 struct comedi_devconfig *it)
222 {
223 struct comedi_subdevice *s;
224 int amplitude = it->options[0];
225 int period = it->options[1];
226 int i;
227
228 dev->board_name = thisboard->name;
229
230 if (alloc_private(dev, sizeof(struct waveform_private)) < 0)
231 return -ENOMEM;
232
233 /* set default amplitude and period */
234 if (amplitude <= 0)
235 amplitude = 1000000; /* 1 volt */
236 if (period <= 0)
237 period = 100000; /* 0.1 sec */
238
239 devpriv->uvolt_amplitude = amplitude;
240 devpriv->usec_period = period;
241
242 dev->n_subdevices = 2;
243 if (alloc_subdevices(dev, dev->n_subdevices) < 0)
244 return -ENOMEM;
245
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;
259
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;
270 s->do_cmd = NULL;
271 s->do_cmdtest = NULL;
272 s->cancel = NULL;
273
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;
277
278 init_timer(&(devpriv->timer));
279 devpriv->timer.function = waveform_ai_interrupt;
280 devpriv->timer.data = (unsigned long)dev;
281
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);
285 return 1;
286 }
287
288 static int waveform_detach(struct comedi_device *dev)
289 {
290 printk("comedi%d: comedi_test: remove\n", dev->minor);
291
292 if (dev->private)
293 waveform_ai_cancel(dev, dev->read_subdev);
294
295 return 0;
296 }
297
298 static int waveform_ai_cmdtest(struct comedi_device *dev,
299 struct comedi_subdevice *s,
300 struct comedi_cmd *cmd)
301 {
302 int err = 0;
303 int tmp;
304
305 /* step 1: make sure trigger sources are trivially valid */
306
307 tmp = cmd->start_src;
308 cmd->start_src &= TRIG_NOW;
309 if (!cmd->start_src || tmp != cmd->start_src)
310 err++;
311
312 tmp = cmd->scan_begin_src;
313 cmd->scan_begin_src &= TRIG_TIMER;
314 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
315 err++;
316
317 tmp = cmd->convert_src;
318 cmd->convert_src &= TRIG_NOW | TRIG_TIMER;
319 if (!cmd->convert_src || tmp != cmd->convert_src)
320 err++;
321
322 tmp = cmd->scan_end_src;
323 cmd->scan_end_src &= TRIG_COUNT;
324 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
325 err++;
326
327 tmp = cmd->stop_src;
328 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
329 if (!cmd->stop_src || tmp != cmd->stop_src)
330 err++;
331
332 if (err)
333 return 1;
334
335 /*
336 * step 2: make sure trigger sources are unique and mutually compatible
337 */
338
339 if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER)
340 err++;
341 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
342 err++;
343
344 if (err)
345 return 2;
346
347 /* step 3: make sure arguments are trivially compatible */
348
349 if (cmd->start_arg != 0) {
350 cmd->start_arg = 0;
351 err++;
352 }
353 if (cmd->convert_src == TRIG_NOW) {
354 if (cmd->convert_arg != 0) {
355 cmd->convert_arg = 0;
356 err++;
357 }
358 }
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;
362 err++;
363 }
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;
369 err++;
370 }
371 }
372 /*
373 * XXX these checks are generic and should go in core if not there
374 * already
375 */
376 if (!cmd->chanlist_len) {
377 cmd->chanlist_len = 1;
378 err++;
379 }
380 if (cmd->scan_end_arg != cmd->chanlist_len) {
381 cmd->scan_end_arg = cmd->chanlist_len;
382 err++;
383 }
384
385 if (cmd->stop_src == TRIG_COUNT) {
386 if (!cmd->stop_arg) {
387 cmd->stop_arg = 1;
388 err++;
389 }
390 } else { /* TRIG_NONE */
391 if (cmd->stop_arg != 0) {
392 cmd->stop_arg = 0;
393 err++;
394 }
395 }
396
397 if (err)
398 return 3;
399
400 /* step 4: fix up any arguments */
401
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)
409 err++;
410 }
411 if (cmd->convert_src == TRIG_TIMER) {
412 tmp = cmd->convert_arg;
413 /* round to nearest microsec */
414 cmd->convert_arg =
415 nano_per_micro * ((tmp +
416 (nano_per_micro / 2)) / nano_per_micro);
417 if (tmp != cmd->convert_arg)
418 err++;
419 }
420
421 if (err)
422 return 4;
423
424 return 0;
425 }
426
427 static int waveform_ai_cmd(struct comedi_device *dev,
428 struct comedi_subdevice *s)
429 {
430 struct comedi_cmd *cmd = &s->async->cmd;
431
432 if (cmd->flags & TRIG_RT) {
433 comedi_error(dev,
434 "commands at RT priority not supported in this driver");
435 return -1;
436 }
437
438 devpriv->timer_running = 1;
439 devpriv->ai_count = 0;
440 devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
441
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;
446 else {
447 comedi_error(dev, "bug setting conversion period");
448 return -1;
449 }
450
451 do_gettimeofday(&devpriv->last);
452 devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
453 devpriv->usec_remainder = 0;
454
455 devpriv->timer.expires = jiffies + 1;
456 add_timer(&devpriv->timer);
457 return 0;
458 }
459
460 static int waveform_ai_cancel(struct comedi_device *dev,
461 struct comedi_subdevice *s)
462 {
463 devpriv->timer_running = 0;
464 del_timer(&devpriv->timer);
465 return 0;
466 }
467
468 static short fake_sawtooth(struct comedi_device *dev, unsigned int range_index,
469 unsigned long current_time)
470 {
471 struct comedi_subdevice *s = dev->read_subdev;
472 unsigned int offset = s->maxdata / 2;
473 u64 value;
474 const struct comedi_krange *krange =
475 &s->range_table->range[range_index];
476 u64 binary_amplitude;
477
478 binary_amplitude = s->maxdata;
479 binary_amplitude *= devpriv->uvolt_amplitude;
480 do_div(binary_amplitude, krange->max - krange->min);
481
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 */
487
488 return offset + value;
489 }
490
491 static short fake_squarewave(struct comedi_device *dev,
492 unsigned int range_index,
493 unsigned long current_time)
494 {
495 struct comedi_subdevice *s = dev->read_subdev;
496 unsigned int offset = s->maxdata / 2;
497 u64 value;
498 const struct comedi_krange *krange =
499 &s->range_table->range[range_index];
500 current_time %= devpriv->usec_period;
501
502 value = s->maxdata;
503 value *= devpriv->uvolt_amplitude;
504 do_div(value, krange->max - krange->min);
505
506 if (current_time < devpriv->usec_period / 2)
507 value *= -1;
508
509 return offset + value;
510 }
511
512 static short fake_flatline(struct comedi_device *dev, unsigned int range_index,
513 unsigned long current_time)
514 {
515 return dev->read_subdev->maxdata / 2;
516 }
517
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)
521 {
522 enum {
523 SAWTOOTH_CHAN,
524 SQUARE_CHAN,
525 };
526 switch (channel) {
527 case SAWTOOTH_CHAN:
528 return fake_sawtooth(dev, range, current_time);
529 break;
530 case SQUARE_CHAN:
531 return fake_squarewave(dev, range, current_time);
532 break;
533 default:
534 break;
535 }
536
537 return fake_flatline(dev, range, current_time);
538 }
539
540 static int waveform_ai_insn_read(struct comedi_device *dev,
541 struct comedi_subdevice *s,
542 struct comedi_insn *insn, unsigned int *data)
543 {
544 int i, chan = CR_CHAN(insn->chanspec);
545
546 for (i = 0; i < insn->n; i++)
547 data[i] = devpriv->ao_loopbacks[chan];
548
549 return insn->n;
550 }
551
552 static int waveform_ao_insn_write(struct comedi_device *dev,
553 struct comedi_subdevice *s,
554 struct comedi_insn *insn, unsigned int *data)
555 {
556 int i, chan = CR_CHAN(insn->chanspec);
557
558 for (i = 0; i < insn->n; i++)
559 devpriv->ao_loopbacks[chan] = data[i];
560
561 return insn->n;
562 }
563
564 MODULE_AUTHOR("Comedi http://www.comedi.org");
565 MODULE_DESCRIPTION("Comedi low-level driver");
566 MODULE_LICENSE("GPL");