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