comedi_test.c revision 90f703d30dd3e0c16ff80f35e34e511385a05ad5
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 occured */ 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 110COMEDI_INITCLEANUP(driver_waveform); 111 112static int waveform_ai_cmdtest(struct comedi_device *dev, 113 struct comedi_subdevice *s, 114 struct comedi_cmd *cmd); 115static int waveform_ai_cmd(struct comedi_device *dev, 116 struct comedi_subdevice *s); 117static int waveform_ai_cancel(struct comedi_device *dev, 118 struct comedi_subdevice *s); 119static int waveform_ai_insn_read(struct comedi_device *dev, 120 struct comedi_subdevice *s, 121 struct comedi_insn *insn, unsigned int *data); 122static int waveform_ao_insn_write(struct comedi_device *dev, 123 struct comedi_subdevice *s, 124 struct comedi_insn *insn, unsigned int *data); 125static short fake_sawtooth(struct comedi_device *dev, unsigned int range, 126 unsigned long current_time); 127static short fake_squarewave(struct comedi_device *dev, unsigned int range, 128 unsigned long current_time); 129static short fake_flatline(struct comedi_device *dev, unsigned int range, 130 unsigned long current_time); 131static short fake_waveform(struct comedi_device *dev, unsigned int channel, 132 unsigned int range, unsigned long current_time); 133 134/* 1000 nanosec in a microsec */ 135static const int nano_per_micro = 1000; 136 137/* fake analog input ranges */ 138static const struct comedi_lrange waveform_ai_ranges = { 139 2, 140 { 141 BIP_RANGE(10), 142 BIP_RANGE(5), 143 } 144}; 145 146/* 147 This is the background routine used to generate arbitrary data. 148 It should run in the background; therefore it is scheduled by 149 a timer mechanism. 150*/ 151static void waveform_ai_interrupt(unsigned long arg) 152{ 153 struct comedi_device *dev = (struct comedi_device *)arg; 154 struct comedi_async *async = dev->read_subdev->async; 155 struct comedi_cmd *cmd = &async->cmd; 156 unsigned int i, j; 157 /* all times in microsec */ 158 unsigned long elapsed_time; 159 unsigned int num_scans; 160 struct timeval now; 161 162 do_gettimeofday(&now); 163 164 elapsed_time = 165 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec - 166 devpriv->last.tv_usec; 167 devpriv->last = now; 168 num_scans = 169 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period; 170 devpriv->usec_remainder = 171 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period; 172 async->events = 0; 173 174 for (i = 0; i < num_scans; i++) { 175 for (j = 0; j < cmd->chanlist_len; j++) { 176 cfc_write_to_buffer(dev->read_subdev, 177 fake_waveform(dev, 178 CR_CHAN(cmd-> 179 chanlist[j]), 180 CR_RANGE(cmd-> 181 chanlist[j]), 182 devpriv-> 183 usec_current + 184 i * 185 devpriv->scan_period + 186 j * 187 devpriv-> 188 convert_period)); 189 } 190 devpriv->ai_count++; 191 if (cmd->stop_src == TRIG_COUNT 192 && devpriv->ai_count >= cmd->stop_arg) { 193 async->events |= COMEDI_CB_EOA; 194 break; 195 } 196 } 197 198 devpriv->usec_current += elapsed_time; 199 devpriv->usec_current %= devpriv->usec_period; 200 201 if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running) 202 mod_timer(&devpriv->timer, jiffies + 1); 203 else 204 del_timer(&devpriv->timer); 205 206 comedi_event(dev, dev->read_subdev); 207} 208 209static int waveform_attach(struct comedi_device *dev, 210 struct comedi_devconfig *it) 211{ 212 struct comedi_subdevice *s; 213 int amplitude = it->options[0]; 214 int period = it->options[1]; 215 int i; 216 217 dev->board_name = thisboard->name; 218 219 if (alloc_private(dev, sizeof(struct waveform_private)) < 0) 220 return -ENOMEM; 221 222 /* set default amplitude and period */ 223 if (amplitude <= 0) 224 amplitude = 1000000; /* 1 volt */ 225 if (period <= 0) 226 period = 100000; /* 0.1 sec */ 227 228 devpriv->uvolt_amplitude = amplitude; 229 devpriv->usec_period = period; 230 231 dev->n_subdevices = 2; 232 if (alloc_subdevices(dev, dev->n_subdevices) < 0) 233 return -ENOMEM; 234 235 s = dev->subdevices + 0; 236 dev->read_subdev = s; 237 /* analog input subdevice */ 238 s->type = COMEDI_SUBD_AI; 239 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; 240 s->n_chan = thisboard->ai_chans; 241 s->maxdata = (1 << thisboard->ai_bits) - 1; 242 s->range_table = &waveform_ai_ranges; 243 s->len_chanlist = s->n_chan * 2; 244 s->insn_read = waveform_ai_insn_read; 245 s->do_cmd = waveform_ai_cmd; 246 s->do_cmdtest = waveform_ai_cmdtest; 247 s->cancel = waveform_ai_cancel; 248 249 s = dev->subdevices + 1; 250 dev->write_subdev = s; 251 /* analog output subdevice (loopback) */ 252 s->type = COMEDI_SUBD_AO; 253 s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; 254 s->n_chan = thisboard->ai_chans; 255 s->maxdata = (1 << thisboard->ai_bits) - 1; 256 s->range_table = &waveform_ai_ranges; 257 s->len_chanlist = s->n_chan * 2; 258 s->insn_write = waveform_ao_insn_write; 259 s->do_cmd = NULL; 260 s->do_cmdtest = NULL; 261 s->cancel = NULL; 262 263 /* Our default loopback value is just a 0V flatline */ 264 for (i = 0; i < s->n_chan; i++) 265 devpriv->ao_loopbacks[i] = s->maxdata / 2; 266 267 init_timer(&(devpriv->timer)); 268 devpriv->timer.function = waveform_ai_interrupt; 269 devpriv->timer.data = (unsigned long)dev; 270 271 printk(KERN_INFO "comedi%d: comedi_test: " 272 "%i microvolt, %li microsecond waveform attached\n", dev->minor, 273 devpriv->uvolt_amplitude, devpriv->usec_period); 274 return 1; 275} 276 277static int waveform_detach(struct comedi_device *dev) 278{ 279 printk("comedi%d: comedi_test: remove\n", dev->minor); 280 281 if (dev->private) 282 waveform_ai_cancel(dev, dev->read_subdev); 283 284 return 0; 285} 286 287static int waveform_ai_cmdtest(struct comedi_device *dev, 288 struct comedi_subdevice *s, 289 struct comedi_cmd *cmd) 290{ 291 int err = 0; 292 int tmp; 293 294 /* step 1: make sure trigger sources are trivially valid */ 295 296 tmp = cmd->start_src; 297 cmd->start_src &= TRIG_NOW; 298 if (!cmd->start_src || tmp != cmd->start_src) 299 err++; 300 301 tmp = cmd->scan_begin_src; 302 cmd->scan_begin_src &= TRIG_TIMER; 303 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 304 err++; 305 306 tmp = cmd->convert_src; 307 cmd->convert_src &= TRIG_NOW | TRIG_TIMER; 308 if (!cmd->convert_src || tmp != cmd->convert_src) 309 err++; 310 311 tmp = cmd->scan_end_src; 312 cmd->scan_end_src &= TRIG_COUNT; 313 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 314 err++; 315 316 tmp = cmd->stop_src; 317 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 318 if (!cmd->stop_src || tmp != cmd->stop_src) 319 err++; 320 321 if (err) 322 return 1; 323 324 /* 325 * step 2: make sure trigger sources are unique and mutually compatible 326 */ 327 328 if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER) 329 err++; 330 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) 331 err++; 332 333 if (err) 334 return 2; 335 336 /* step 3: make sure arguments are trivially compatible */ 337 338 if (cmd->start_arg != 0) { 339 cmd->start_arg = 0; 340 err++; 341 } 342 if (cmd->convert_src == TRIG_NOW) { 343 if (cmd->convert_arg != 0) { 344 cmd->convert_arg = 0; 345 err++; 346 } 347 } 348 if (cmd->scan_begin_src == TRIG_TIMER) { 349 if (cmd->scan_begin_arg < nano_per_micro) { 350 cmd->scan_begin_arg = nano_per_micro; 351 err++; 352 } 353 if (cmd->convert_src == TRIG_TIMER && 354 cmd->scan_begin_arg < 355 cmd->convert_arg * cmd->chanlist_len) { 356 cmd->scan_begin_arg = 357 cmd->convert_arg * cmd->chanlist_len; 358 err++; 359 } 360 } 361 /* 362 * XXX these checks are generic and should go in core if not there 363 * already 364 */ 365 if (!cmd->chanlist_len) { 366 cmd->chanlist_len = 1; 367 err++; 368 } 369 if (cmd->scan_end_arg != cmd->chanlist_len) { 370 cmd->scan_end_arg = cmd->chanlist_len; 371 err++; 372 } 373 374 if (cmd->stop_src == TRIG_COUNT) { 375 if (!cmd->stop_arg) { 376 cmd->stop_arg = 1; 377 err++; 378 } 379 } else { /* TRIG_NONE */ 380 if (cmd->stop_arg != 0) { 381 cmd->stop_arg = 0; 382 err++; 383 } 384 } 385 386 if (err) 387 return 3; 388 389 /* step 4: fix up any arguments */ 390 391 if (cmd->scan_begin_src == TRIG_TIMER) { 392 tmp = cmd->scan_begin_arg; 393 /* round to nearest microsec */ 394 cmd->scan_begin_arg = 395 nano_per_micro * ((tmp + 396 (nano_per_micro / 2)) / nano_per_micro); 397 if (tmp != cmd->scan_begin_arg) 398 err++; 399 } 400 if (cmd->convert_src == TRIG_TIMER) { 401 tmp = cmd->convert_arg; 402 /* round to nearest microsec */ 403 cmd->convert_arg = 404 nano_per_micro * ((tmp + 405 (nano_per_micro / 2)) / nano_per_micro); 406 if (tmp != cmd->convert_arg) 407 err++; 408 } 409 410 if (err) 411 return 4; 412 413 return 0; 414} 415 416static int waveform_ai_cmd(struct comedi_device *dev, 417 struct comedi_subdevice *s) 418{ 419 struct comedi_cmd *cmd = &s->async->cmd; 420 421 if (cmd->flags & TRIG_RT) { 422 comedi_error(dev, 423 "commands at RT priority not supported in this driver"); 424 return -1; 425 } 426 427 devpriv->timer_running = 1; 428 devpriv->ai_count = 0; 429 devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro; 430 431 if (cmd->convert_src == TRIG_NOW) 432 devpriv->convert_period = 0; 433 else if (cmd->convert_src == TRIG_TIMER) 434 devpriv->convert_period = cmd->convert_arg / nano_per_micro; 435 else { 436 comedi_error(dev, "bug setting conversion period"); 437 return -1; 438 } 439 440 do_gettimeofday(&devpriv->last); 441 devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period; 442 devpriv->usec_remainder = 0; 443 444 devpriv->timer.expires = jiffies + 1; 445 add_timer(&devpriv->timer); 446 return 0; 447} 448 449static int waveform_ai_cancel(struct comedi_device *dev, 450 struct comedi_subdevice *s) 451{ 452 devpriv->timer_running = 0; 453 del_timer(&devpriv->timer); 454 return 0; 455} 456 457static short fake_sawtooth(struct comedi_device *dev, unsigned int range_index, 458 unsigned long current_time) 459{ 460 struct comedi_subdevice *s = dev->read_subdev; 461 unsigned int offset = s->maxdata / 2; 462 u64 value; 463 const struct comedi_krange *krange = 464 &s->range_table->range[range_index]; 465 u64 binary_amplitude; 466 467 binary_amplitude = s->maxdata; 468 binary_amplitude *= devpriv->uvolt_amplitude; 469 do_div(binary_amplitude, krange->max - krange->min); 470 471 current_time %= devpriv->usec_period; 472 value = current_time; 473 value *= binary_amplitude * 2; 474 do_div(value, devpriv->usec_period); 475 value -= binary_amplitude; /* get rid of sawtooth's dc offset */ 476 477 return offset + value; 478} 479 480static short fake_squarewave(struct comedi_device *dev, 481 unsigned int range_index, 482 unsigned long current_time) 483{ 484 struct comedi_subdevice *s = dev->read_subdev; 485 unsigned int offset = s->maxdata / 2; 486 u64 value; 487 const struct comedi_krange *krange = 488 &s->range_table->range[range_index]; 489 current_time %= devpriv->usec_period; 490 491 value = s->maxdata; 492 value *= devpriv->uvolt_amplitude; 493 do_div(value, krange->max - krange->min); 494 495 if (current_time < devpriv->usec_period / 2) 496 value *= -1; 497 498 return offset + value; 499} 500 501static short fake_flatline(struct comedi_device *dev, unsigned int range_index, 502 unsigned long current_time) 503{ 504 return dev->read_subdev->maxdata / 2; 505} 506 507/* generates a different waveform depending on what channel is read */ 508static short fake_waveform(struct comedi_device *dev, unsigned int channel, 509 unsigned int range, unsigned long current_time) 510{ 511 enum { 512 SAWTOOTH_CHAN, 513 SQUARE_CHAN, 514 }; 515 switch (channel) { 516 case SAWTOOTH_CHAN: 517 return fake_sawtooth(dev, range, current_time); 518 break; 519 case SQUARE_CHAN: 520 return fake_squarewave(dev, range, current_time); 521 break; 522 default: 523 break; 524 } 525 526 return fake_flatline(dev, range, current_time); 527} 528 529static int waveform_ai_insn_read(struct comedi_device *dev, 530 struct comedi_subdevice *s, 531 struct comedi_insn *insn, unsigned int *data) 532{ 533 int i, chan = CR_CHAN(insn->chanspec); 534 535 for (i = 0; i < insn->n; i++) 536 data[i] = devpriv->ao_loopbacks[chan]; 537 538 return insn->n; 539} 540 541static int waveform_ao_insn_write(struct comedi_device *dev, 542 struct comedi_subdevice *s, 543 struct comedi_insn *insn, unsigned int *data) 544{ 545 int i, chan = CR_CHAN(insn->chanspec); 546 547 for (i = 0; i < insn->n; i++) 548 devpriv->ao_loopbacks[chan] = data[i]; 549 550 return insn->n; 551} 552 553MODULE_AUTHOR("Comedi http://www.comedi.org"); 554MODULE_DESCRIPTION("Comedi low-level driver"); 555MODULE_LICENSE("GPL"); 556