comedi_test.c revision 25985edcedea6396277003854657b5f3cb31a628
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/* 31Driver: comedi_test 32Description: generates fake waveforms 33Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess 34 <fmhess@users.sourceforge.net>, ds 35Devices: 36Status: works 37Updated: Sat, 16 Mar 2002 17:34:48 -0800 38 39This driver is mainly for testing purposes, but can also be used to 40generate sample waveforms on systems that don't have data acquisition 41hardware. 42 43Configuration 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 47Generates a sawtooth wave on channel 0, square wave on channel 1, additional 48waveforms could be added to other channels (currently they return flatline 49zero 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 */ 61struct 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 70static 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 */ 82struct 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 97static int waveform_attach(struct comedi_device *dev, 98 struct comedi_devconfig *it); 99static int waveform_detach(struct comedi_device *dev); 100static 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 110static int __init driver_waveform_init_module(void) 111{ 112 return comedi_driver_register(&driver_waveform); 113} 114 115static void __exit driver_waveform_cleanup_module(void) 116{ 117 comedi_driver_unregister(&driver_waveform); 118} 119 120module_init(driver_waveform_init_module); 121module_exit(driver_waveform_cleanup_module); 122 123static int waveform_ai_cmdtest(struct comedi_device *dev, 124 struct comedi_subdevice *s, 125 struct comedi_cmd *cmd); 126static int waveform_ai_cmd(struct comedi_device *dev, 127 struct comedi_subdevice *s); 128static int waveform_ai_cancel(struct comedi_device *dev, 129 struct comedi_subdevice *s); 130static int waveform_ai_insn_read(struct comedi_device *dev, 131 struct comedi_subdevice *s, 132 struct comedi_insn *insn, unsigned int *data); 133static int waveform_ao_insn_write(struct comedi_device *dev, 134 struct comedi_subdevice *s, 135 struct comedi_insn *insn, unsigned int *data); 136static short fake_sawtooth(struct comedi_device *dev, unsigned int range, 137 unsigned long current_time); 138static short fake_squarewave(struct comedi_device *dev, unsigned int range, 139 unsigned long current_time); 140static short fake_flatline(struct comedi_device *dev, unsigned int range, 141 unsigned long current_time); 142static 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 */ 146static const int nano_per_micro = 1000; 147 148/* fake analog input ranges */ 149static 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*/ 162static 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 220static 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 288static 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 298static 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 427static 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 460static 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 468static 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 491static 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 512static 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 */ 519static 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 540static 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 552static 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 564MODULE_AUTHOR("Comedi http://www.comedi.org"); 565MODULE_DESCRIPTION("Comedi low-level driver"); 566MODULE_LICENSE("GPL"); 567