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