1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * comedi/drivers/comedi_test.c 4 * 5 * Generates fake waveform signals that can be read through 6 * the command interface. It does _not_ read from any board; 7 * it just generates deterministic waveforms. 8 * Useful for various testing purposes. 9 * 10 * Copyright (C) 2002 Joachim Wuttke <[email protected]> 11 * Copyright (C) 2002 Frank Mori Hess <[email protected]> 12 * 13 * COMEDI - Linux Control and Measurement Device Interface 14 * Copyright (C) 2000 David A. Schleef <[email protected]> 15 */ 16 17 /* 18 * Driver: comedi_test 19 * Description: generates fake waveforms 20 * Author: Joachim Wuttke <[email protected]>, Frank Mori Hess 21 * <[email protected]>, ds 22 * Devices: 23 * Status: works 24 * Updated: Sat, 16 Mar 2002 17:34:48 -0800 25 * 26 * This driver is mainly for testing purposes, but can also be used to 27 * generate sample waveforms on systems that don't have data acquisition 28 * hardware. 29 * 30 * Auto-configuration is the default mode if no parameter is supplied during 31 * module loading. Manual configuration requires COMEDI userspace tool. 32 * To disable auto-configuration mode, pass "noauto=1" parameter for module 33 * loading. Refer modinfo or MODULE_PARM_DESC description below for details. 34 * 35 * Auto-configuration options: 36 * Refer modinfo or MODULE_PARM_DESC description below for details. 37 * 38 * Manual configuration options: 39 * [0] - Amplitude in microvolts for fake waveforms (default 1 volt) 40 * [1] - Period in microseconds for fake waveforms (default 0.1 sec) 41 * 42 * Generates a sawtooth wave on channel 0, square wave on channel 1, additional 43 * waveforms could be added to other channels (currently they return flatline 44 * zero volts). 45 */ 46 47 #include <linux/module.h> 48 #include <linux/comedi/comedidev.h> 49 #include <asm/div64.h> 50 #include <linux/timer.h> 51 #include <linux/ktime.h> 52 #include <linux/jiffies.h> 53 #include <linux/device.h> 54 #include <linux/kdev_t.h> 55 56 #define N_CHANS 8 57 #define DEV_NAME "comedi_testd" 58 #define CLASS_NAME "comedi_test" 59 60 static bool config_mode; 61 static unsigned int set_amplitude; 62 static unsigned int set_period; 63 static struct class *ctcls; 64 static struct device *ctdev; 65 66 module_param_named(noauto, config_mode, bool, 0444); 67 MODULE_PARM_DESC(noauto, "Disable auto-configuration: (1=disable [defaults to enable])"); 68 69 module_param_named(amplitude, set_amplitude, uint, 0444); 70 MODULE_PARM_DESC(amplitude, "Set auto mode wave amplitude in microvolts: (defaults to 1 volt)"); 71 72 module_param_named(period, set_period, uint, 0444); 73 MODULE_PARM_DESC(period, "Set auto mode wave period in microseconds: (defaults to 0.1 sec)"); 74 75 /* Data unique to this driver */ 76 struct waveform_private { 77 struct timer_list ai_timer; /* timer for AI commands */ 78 u64 ai_convert_time; /* time of next AI conversion in usec */ 79 unsigned int wf_amplitude; /* waveform amplitude in microvolts */ 80 unsigned int wf_period; /* waveform period in microseconds */ 81 unsigned int wf_current; /* current time in waveform period */ 82 unsigned int ai_scan_period; /* AI scan period in usec */ 83 unsigned int ai_convert_period; /* AI conversion period in usec */ 84 struct timer_list ao_timer; /* timer for AO commands */ 85 struct comedi_device *dev; /* parent comedi device */ 86 u64 ao_last_scan_time; /* time of previous AO scan in usec */ 87 unsigned int ao_scan_period; /* AO scan period in usec */ 88 unsigned short ao_loopbacks[N_CHANS]; 89 }; 90 91 /* fake analog input ranges */ 92 static const struct comedi_lrange waveform_ai_ranges = { 93 2, { 94 BIP_RANGE(10), 95 BIP_RANGE(5) 96 } 97 }; 98 99 static unsigned short fake_sawtooth(struct comedi_device *dev, 100 unsigned int range_index, 101 unsigned int current_time) 102 { 103 struct waveform_private *devpriv = dev->private; 104 struct comedi_subdevice *s = dev->read_subdev; 105 unsigned int offset = s->maxdata / 2; 106 u64 value; 107 const struct comedi_krange *krange = 108 &s->range_table->range[range_index]; 109 u64 binary_amplitude; 110 111 binary_amplitude = s->maxdata; 112 binary_amplitude *= devpriv->wf_amplitude; 113 do_div(binary_amplitude, krange->max - krange->min); 114 115 value = current_time; 116 value *= binary_amplitude * 2; 117 do_div(value, devpriv->wf_period); 118 value += offset; 119 /* get rid of sawtooth's dc offset and clamp value */ 120 if (value < binary_amplitude) { 121 value = 0; /* negative saturation */ 122 } else { 123 value -= binary_amplitude; 124 if (value > s->maxdata) 125 value = s->maxdata; /* positive saturation */ 126 } 127 128 return value; 129 } 130 131 static unsigned short fake_squarewave(struct comedi_device *dev, 132 unsigned int range_index, 133 unsigned int current_time) 134 { 135 struct waveform_private *devpriv = dev->private; 136 struct comedi_subdevice *s = dev->read_subdev; 137 unsigned int offset = s->maxdata / 2; 138 u64 value; 139 const struct comedi_krange *krange = 140 &s->range_table->range[range_index]; 141 142 value = s->maxdata; 143 value *= devpriv->wf_amplitude; 144 do_div(value, krange->max - krange->min); 145 146 /* get one of two values for square-wave and clamp */ 147 if (current_time < devpriv->wf_period / 2) { 148 if (offset < value) 149 value = 0; /* negative saturation */ 150 else 151 value = offset - value; 152 } else { 153 value += offset; 154 if (value > s->maxdata) 155 value = s->maxdata; /* positive saturation */ 156 } 157 158 return value; 159 } 160 161 static unsigned short fake_flatline(struct comedi_device *dev, 162 unsigned int range_index, 163 unsigned int current_time) 164 { 165 return dev->read_subdev->maxdata / 2; 166 } 167 168 /* generates a different waveform depending on what channel is read */ 169 static unsigned short fake_waveform(struct comedi_device *dev, 170 unsigned int channel, unsigned int range, 171 unsigned int current_time) 172 { 173 enum { 174 SAWTOOTH_CHAN, 175 SQUARE_CHAN, 176 }; 177 switch (channel) { 178 case SAWTOOTH_CHAN: 179 return fake_sawtooth(dev, range, current_time); 180 case SQUARE_CHAN: 181 return fake_squarewave(dev, range, current_time); 182 default: 183 break; 184 } 185 186 return fake_flatline(dev, range, current_time); 187 } 188 189 /* 190 * This is the background routine used to generate arbitrary data. 191 * It should run in the background; therefore it is scheduled by 192 * a timer mechanism. 193 */ 194 static void waveform_ai_timer(struct timer_list *t) 195 { 196 struct waveform_private *devpriv = from_timer(devpriv, t, ai_timer); 197 struct comedi_device *dev = devpriv->dev; 198 struct comedi_subdevice *s = dev->read_subdev; 199 struct comedi_async *async = s->async; 200 struct comedi_cmd *cmd = &async->cmd; 201 u64 now; 202 unsigned int nsamples; 203 unsigned int time_increment; 204 205 now = ktime_to_us(ktime_get()); 206 nsamples = comedi_nsamples_left(s, UINT_MAX); 207 208 while (nsamples && devpriv->ai_convert_time < now) { 209 unsigned int chanspec = cmd->chanlist[async->cur_chan]; 210 unsigned short sample; 211 212 sample = fake_waveform(dev, CR_CHAN(chanspec), 213 CR_RANGE(chanspec), devpriv->wf_current); 214 if (comedi_buf_write_samples(s, &sample, 1) == 0) 215 goto overrun; 216 time_increment = devpriv->ai_convert_period; 217 if (async->scan_progress == 0) { 218 /* done last conversion in scan, so add dead time */ 219 time_increment += devpriv->ai_scan_period - 220 devpriv->ai_convert_period * 221 cmd->scan_end_arg; 222 } 223 devpriv->wf_current += time_increment; 224 if (devpriv->wf_current >= devpriv->wf_period) 225 devpriv->wf_current %= devpriv->wf_period; 226 devpriv->ai_convert_time += time_increment; 227 nsamples--; 228 } 229 230 if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { 231 async->events |= COMEDI_CB_EOA; 232 } else { 233 if (devpriv->ai_convert_time > now) 234 time_increment = devpriv->ai_convert_time - now; 235 else 236 time_increment = 1; 237 mod_timer(&devpriv->ai_timer, 238 jiffies + usecs_to_jiffies(time_increment)); 239 } 240 241 overrun: 242 comedi_handle_events(dev, s); 243 } 244 245 static int waveform_ai_cmdtest(struct comedi_device *dev, 246 struct comedi_subdevice *s, 247 struct comedi_cmd *cmd) 248 { 249 int err = 0; 250 unsigned int arg, limit; 251 252 /* Step 1 : check if triggers are trivially valid */ 253 254 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 255 err |= comedi_check_trigger_src(&cmd->scan_begin_src, 256 TRIG_FOLLOW | TRIG_TIMER); 257 err |= comedi_check_trigger_src(&cmd->convert_src, 258 TRIG_NOW | TRIG_TIMER); 259 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 260 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 261 262 if (err) 263 return 1; 264 265 /* Step 2a : make sure trigger sources are unique */ 266 267 err |= comedi_check_trigger_is_unique(cmd->convert_src); 268 err |= comedi_check_trigger_is_unique(cmd->stop_src); 269 270 /* Step 2b : and mutually compatible */ 271 272 if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) 273 err |= -EINVAL; /* scan period would be 0 */ 274 275 if (err) 276 return 2; 277 278 /* Step 3: check if arguments are trivially valid */ 279 280 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 281 282 if (cmd->convert_src == TRIG_NOW) { 283 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 284 } else { /* cmd->convert_src == TRIG_TIMER */ 285 if (cmd->scan_begin_src == TRIG_FOLLOW) { 286 err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 287 NSEC_PER_USEC); 288 } 289 } 290 291 if (cmd->scan_begin_src == TRIG_FOLLOW) { 292 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 293 } else { /* cmd->scan_begin_src == TRIG_TIMER */ 294 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 295 NSEC_PER_USEC); 296 } 297 298 err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); 299 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 300 cmd->chanlist_len); 301 302 if (cmd->stop_src == TRIG_COUNT) 303 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 304 else /* cmd->stop_src == TRIG_NONE */ 305 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 306 307 if (err) 308 return 3; 309 310 /* step 4: fix up any arguments */ 311 312 if (cmd->convert_src == TRIG_TIMER) { 313 /* round convert_arg to nearest microsecond */ 314 arg = cmd->convert_arg; 315 arg = min(arg, 316 rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); 317 arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); 318 if (cmd->scan_begin_arg == TRIG_TIMER) { 319 /* limit convert_arg to keep scan_begin_arg in range */ 320 limit = UINT_MAX / cmd->scan_end_arg; 321 limit = rounddown(limit, (unsigned int)NSEC_PER_SEC); 322 arg = min(arg, limit); 323 } 324 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); 325 } 326 327 if (cmd->scan_begin_src == TRIG_TIMER) { 328 /* round scan_begin_arg to nearest microsecond */ 329 arg = cmd->scan_begin_arg; 330 arg = min(arg, 331 rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); 332 arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); 333 if (cmd->convert_src == TRIG_TIMER) { 334 /* but ensure scan_begin_arg is large enough */ 335 arg = max(arg, cmd->convert_arg * cmd->scan_end_arg); 336 } 337 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 338 } 339 340 if (err) 341 return 4; 342 343 return 0; 344 } 345 346 static int waveform_ai_cmd(struct comedi_device *dev, 347 struct comedi_subdevice *s) 348 { 349 struct waveform_private *devpriv = dev->private; 350 struct comedi_cmd *cmd = &s->async->cmd; 351 unsigned int first_convert_time; 352 u64 wf_current; 353 354 if (cmd->flags & CMDF_PRIORITY) { 355 dev_err(dev->class_dev, 356 "commands at RT priority not supported in this driver\n"); 357 return -1; 358 } 359 360 if (cmd->convert_src == TRIG_NOW) 361 devpriv->ai_convert_period = 0; 362 else /* cmd->convert_src == TRIG_TIMER */ 363 devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC; 364 365 if (cmd->scan_begin_src == TRIG_FOLLOW) { 366 devpriv->ai_scan_period = devpriv->ai_convert_period * 367 cmd->scan_end_arg; 368 } else { /* cmd->scan_begin_src == TRIG_TIMER */ 369 devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; 370 } 371 372 /* 373 * Simulate first conversion to occur at convert period after 374 * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume 375 * the conversion timer starts immediately. If scan_begin_src is 376 * TRIG_TIMER, assume the conversion timer starts after the scan 377 * period. 378 */ 379 first_convert_time = devpriv->ai_convert_period; 380 if (cmd->scan_begin_src == TRIG_TIMER) 381 first_convert_time += devpriv->ai_scan_period; 382 devpriv->ai_convert_time = ktime_to_us(ktime_get()) + 383 first_convert_time; 384 385 /* Determine time within waveform period at time of conversion. */ 386 wf_current = devpriv->ai_convert_time; 387 devpriv->wf_current = do_div(wf_current, devpriv->wf_period); 388 389 /* 390 * Schedule timer to expire just after first conversion time. 391 * Seem to need an extra jiffy here, otherwise timer expires slightly 392 * early! 393 */ 394 devpriv->ai_timer.expires = 395 jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1; 396 add_timer(&devpriv->ai_timer); 397 return 0; 398 } 399 400 static int waveform_ai_cancel(struct comedi_device *dev, 401 struct comedi_subdevice *s) 402 { 403 struct waveform_private *devpriv = dev->private; 404 405 if (in_softirq()) { 406 /* Assume we were called from the timer routine itself. */ 407 del_timer(&devpriv->ai_timer); 408 } else { 409 del_timer_sync(&devpriv->ai_timer); 410 } 411 return 0; 412 } 413 414 static int waveform_ai_insn_read(struct comedi_device *dev, 415 struct comedi_subdevice *s, 416 struct comedi_insn *insn, unsigned int *data) 417 { 418 struct waveform_private *devpriv = dev->private; 419 int i, chan = CR_CHAN(insn->chanspec); 420 421 for (i = 0; i < insn->n; i++) 422 data[i] = devpriv->ao_loopbacks[chan]; 423 424 return insn->n; 425 } 426 427 /* 428 * This is the background routine to handle AO commands, scheduled by 429 * a timer mechanism. 430 */ 431 static void waveform_ao_timer(struct timer_list *t) 432 { 433 struct waveform_private *devpriv = from_timer(devpriv, t, ao_timer); 434 struct comedi_device *dev = devpriv->dev; 435 struct comedi_subdevice *s = dev->write_subdev; 436 struct comedi_async *async = s->async; 437 struct comedi_cmd *cmd = &async->cmd; 438 u64 now; 439 u64 scans_since; 440 unsigned int scans_avail = 0; 441 442 /* determine number of scan periods since last time */ 443 now = ktime_to_us(ktime_get()); 444 scans_since = now - devpriv->ao_last_scan_time; 445 do_div(scans_since, devpriv->ao_scan_period); 446 if (scans_since) { 447 unsigned int i; 448 449 /* determine scans in buffer, limit to scans to do this time */ 450 scans_avail = comedi_nscans_left(s, 0); 451 if (scans_avail > scans_since) 452 scans_avail = scans_since; 453 if (scans_avail) { 454 /* skip all but the last scan to save processing time */ 455 if (scans_avail > 1) { 456 unsigned int skip_bytes, nbytes; 457 458 skip_bytes = 459 comedi_samples_to_bytes(s, cmd->scan_end_arg * 460 (scans_avail - 1)); 461 nbytes = comedi_buf_read_alloc(s, skip_bytes); 462 comedi_buf_read_free(s, nbytes); 463 comedi_inc_scan_progress(s, nbytes); 464 if (nbytes < skip_bytes) { 465 /* unexpected underrun! (cancelled?) */ 466 async->events |= COMEDI_CB_OVERFLOW; 467 goto underrun; 468 } 469 } 470 /* output the last scan */ 471 for (i = 0; i < cmd->scan_end_arg; i++) { 472 unsigned int chan = CR_CHAN(cmd->chanlist[i]); 473 unsigned short *pd; 474 475 pd = &devpriv->ao_loopbacks[chan]; 476 477 if (!comedi_buf_read_samples(s, pd, 1)) { 478 /* unexpected underrun! (cancelled?) */ 479 async->events |= COMEDI_CB_OVERFLOW; 480 goto underrun; 481 } 482 } 483 /* advance time of last scan */ 484 devpriv->ao_last_scan_time += 485 (u64)scans_avail * devpriv->ao_scan_period; 486 } 487 } 488 if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) { 489 async->events |= COMEDI_CB_EOA; 490 } else if (scans_avail < scans_since) { 491 async->events |= COMEDI_CB_OVERFLOW; 492 } else { 493 unsigned int time_inc = devpriv->ao_last_scan_time + 494 devpriv->ao_scan_period - now; 495 496 mod_timer(&devpriv->ao_timer, 497 jiffies + usecs_to_jiffies(time_inc)); 498 } 499 500 underrun: 501 comedi_handle_events(dev, s); 502 } 503 504 static int waveform_ao_inttrig_start(struct comedi_device *dev, 505 struct comedi_subdevice *s, 506 unsigned int trig_num) 507 { 508 struct waveform_private *devpriv = dev->private; 509 struct comedi_async *async = s->async; 510 struct comedi_cmd *cmd = &async->cmd; 511 512 if (trig_num != cmd->start_arg) 513 return -EINVAL; 514 515 async->inttrig = NULL; 516 517 devpriv->ao_last_scan_time = ktime_to_us(ktime_get()); 518 devpriv->ao_timer.expires = 519 jiffies + usecs_to_jiffies(devpriv->ao_scan_period); 520 add_timer(&devpriv->ao_timer); 521 522 return 1; 523 } 524 525 static int waveform_ao_cmdtest(struct comedi_device *dev, 526 struct comedi_subdevice *s, 527 struct comedi_cmd *cmd) 528 { 529 int err = 0; 530 unsigned int arg; 531 532 /* Step 1 : check if triggers are trivially valid */ 533 534 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT); 535 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); 536 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); 537 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 538 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 539 540 if (err) 541 return 1; 542 543 /* Step 2a : make sure trigger sources are unique */ 544 545 err |= comedi_check_trigger_is_unique(cmd->stop_src); 546 547 /* Step 2b : and mutually compatible */ 548 549 if (err) 550 return 2; 551 552 /* Step 3: check if arguments are trivially valid */ 553 554 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 555 NSEC_PER_USEC); 556 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 557 err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1); 558 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 559 cmd->chanlist_len); 560 if (cmd->stop_src == TRIG_COUNT) 561 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 562 else /* cmd->stop_src == TRIG_NONE */ 563 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 564 565 if (err) 566 return 3; 567 568 /* step 4: fix up any arguments */ 569 570 /* round scan_begin_arg to nearest microsecond */ 571 arg = cmd->scan_begin_arg; 572 arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC)); 573 arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC); 574 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 575 576 if (err) 577 return 4; 578 579 return 0; 580 } 581 582 static int waveform_ao_cmd(struct comedi_device *dev, 583 struct comedi_subdevice *s) 584 { 585 struct waveform_private *devpriv = dev->private; 586 struct comedi_cmd *cmd = &s->async->cmd; 587 588 if (cmd->flags & CMDF_PRIORITY) { 589 dev_err(dev->class_dev, 590 "commands at RT priority not supported in this driver\n"); 591 return -1; 592 } 593 594 devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC; 595 s->async->inttrig = waveform_ao_inttrig_start; 596 return 0; 597 } 598 599 static int waveform_ao_cancel(struct comedi_device *dev, 600 struct comedi_subdevice *s) 601 { 602 struct waveform_private *devpriv = dev->private; 603 604 s->async->inttrig = NULL; 605 if (in_softirq()) { 606 /* Assume we were called from the timer routine itself. */ 607 del_timer(&devpriv->ao_timer); 608 } else { 609 del_timer_sync(&devpriv->ao_timer); 610 } 611 return 0; 612 } 613 614 static int waveform_ao_insn_write(struct comedi_device *dev, 615 struct comedi_subdevice *s, 616 struct comedi_insn *insn, unsigned int *data) 617 { 618 struct waveform_private *devpriv = dev->private; 619 int i, chan = CR_CHAN(insn->chanspec); 620 621 for (i = 0; i < insn->n; i++) 622 devpriv->ao_loopbacks[chan] = data[i]; 623 624 return insn->n; 625 } 626 627 static int waveform_ai_insn_config(struct comedi_device *dev, 628 struct comedi_subdevice *s, 629 struct comedi_insn *insn, 630 unsigned int *data) 631 { 632 if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { 633 /* 634 * input: data[1], data[2] : scan_begin_src, convert_src 635 * output: data[1], data[2] : scan_begin_min, convert_min 636 */ 637 if (data[1] == TRIG_FOLLOW) { 638 /* exactly TRIG_FOLLOW case */ 639 data[1] = 0; 640 data[2] = NSEC_PER_USEC; 641 } else { 642 data[1] = NSEC_PER_USEC; 643 if (data[2] & TRIG_TIMER) 644 data[2] = NSEC_PER_USEC; 645 else 646 data[2] = 0; 647 } 648 return 0; 649 } 650 651 return -EINVAL; 652 } 653 654 static int waveform_ao_insn_config(struct comedi_device *dev, 655 struct comedi_subdevice *s, 656 struct comedi_insn *insn, 657 unsigned int *data) 658 { 659 if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { 660 /* we don't care about actual channels */ 661 data[1] = NSEC_PER_USEC; /* scan_begin_min */ 662 data[2] = 0; /* convert_min */ 663 return 0; 664 } 665 666 return -EINVAL; 667 } 668 669 static int waveform_common_attach(struct comedi_device *dev, 670 int amplitude, int period) 671 { 672 struct waveform_private *devpriv; 673 struct comedi_subdevice *s; 674 int i; 675 int ret; 676 677 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 678 if (!devpriv) 679 return -ENOMEM; 680 681 devpriv->wf_amplitude = amplitude; 682 devpriv->wf_period = period; 683 684 ret = comedi_alloc_subdevices(dev, 2); 685 if (ret) 686 return ret; 687 688 s = &dev->subdevices[0]; 689 dev->read_subdev = s; 690 /* analog input subdevice */ 691 s->type = COMEDI_SUBD_AI; 692 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; 693 s->n_chan = N_CHANS; 694 s->maxdata = 0xffff; 695 s->range_table = &waveform_ai_ranges; 696 s->len_chanlist = s->n_chan * 2; 697 s->insn_read = waveform_ai_insn_read; 698 s->do_cmd = waveform_ai_cmd; 699 s->do_cmdtest = waveform_ai_cmdtest; 700 s->cancel = waveform_ai_cancel; 701 s->insn_config = waveform_ai_insn_config; 702 703 s = &dev->subdevices[1]; 704 dev->write_subdev = s; 705 /* analog output subdevice (loopback) */ 706 s->type = COMEDI_SUBD_AO; 707 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; 708 s->n_chan = N_CHANS; 709 s->maxdata = 0xffff; 710 s->range_table = &waveform_ai_ranges; 711 s->len_chanlist = s->n_chan; 712 s->insn_write = waveform_ao_insn_write; 713 s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ 714 s->do_cmd = waveform_ao_cmd; 715 s->do_cmdtest = waveform_ao_cmdtest; 716 s->cancel = waveform_ao_cancel; 717 s->insn_config = waveform_ao_insn_config; 718 719 /* Our default loopback value is just a 0V flatline */ 720 for (i = 0; i < s->n_chan; i++) 721 devpriv->ao_loopbacks[i] = s->maxdata / 2; 722 723 devpriv->dev = dev; 724 timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); 725 timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); 726 727 dev_info(dev->class_dev, 728 "%s: %u microvolt, %u microsecond waveform attached\n", 729 dev->board_name, 730 devpriv->wf_amplitude, devpriv->wf_period); 731 732 return 0; 733 } 734 735 static int waveform_attach(struct comedi_device *dev, 736 struct comedi_devconfig *it) 737 { 738 int amplitude = it->options[0]; 739 int period = it->options[1]; 740 741 /* set default amplitude and period */ 742 if (amplitude <= 0) 743 amplitude = 1000000; /* 1 volt */ 744 if (period <= 0) 745 period = 100000; /* 0.1 sec */ 746 747 return waveform_common_attach(dev, amplitude, period); 748 } 749 750 static int waveform_auto_attach(struct comedi_device *dev, 751 unsigned long context_unused) 752 { 753 int amplitude = set_amplitude; 754 int period = set_period; 755 756 /* set default amplitude and period */ 757 if (!amplitude) 758 amplitude = 1000000; /* 1 volt */ 759 if (!period) 760 period = 100000; /* 0.1 sec */ 761 762 return waveform_common_attach(dev, amplitude, period); 763 } 764 765 static void waveform_detach(struct comedi_device *dev) 766 { 767 struct waveform_private *devpriv = dev->private; 768 769 if (devpriv) { 770 del_timer_sync(&devpriv->ai_timer); 771 del_timer_sync(&devpriv->ao_timer); 772 } 773 } 774 775 static struct comedi_driver waveform_driver = { 776 .driver_name = "comedi_test", 777 .module = THIS_MODULE, 778 .attach = waveform_attach, 779 .auto_attach = waveform_auto_attach, 780 .detach = waveform_detach, 781 }; 782 783 /* 784 * For auto-configuration, a device is created to stand in for a 785 * real hardware device. 786 */ 787 static int __init comedi_test_init(void) 788 { 789 int ret; 790 791 ret = comedi_driver_register(&waveform_driver); 792 if (ret) { 793 pr_err("comedi_test: unable to register driver\n"); 794 return ret; 795 } 796 797 if (!config_mode) { 798 ctcls = class_create(CLASS_NAME); 799 if (IS_ERR(ctcls)) { 800 pr_warn("comedi_test: unable to create class\n"); 801 goto clean3; 802 } 803 804 ctdev = device_create(ctcls, NULL, MKDEV(0, 0), NULL, DEV_NAME); 805 if (IS_ERR(ctdev)) { 806 pr_warn("comedi_test: unable to create device\n"); 807 goto clean2; 808 } 809 810 ret = comedi_auto_config(ctdev, &waveform_driver, 0); 811 if (ret) { 812 pr_warn("comedi_test: unable to auto-configure device\n"); 813 goto clean; 814 } 815 } 816 817 return 0; 818 819 clean: 820 device_destroy(ctcls, MKDEV(0, 0)); 821 clean2: 822 class_destroy(ctcls); 823 ctdev = NULL; 824 clean3: 825 ctcls = NULL; 826 827 return 0; 828 } 829 module_init(comedi_test_init); 830 831 static void __exit comedi_test_exit(void) 832 { 833 if (ctdev) 834 comedi_auto_unconfig(ctdev); 835 836 if (ctcls) { 837 device_destroy(ctcls, MKDEV(0, 0)); 838 class_destroy(ctcls); 839 } 840 841 comedi_driver_unregister(&waveform_driver); 842 } 843 module_exit(comedi_test_exit); 844 845 MODULE_AUTHOR("Comedi https://www.comedi.org"); 846 MODULE_DESCRIPTION("Comedi low-level driver"); 847 MODULE_LICENSE("GPL"); 848