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