aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIan Abbott <abbotti@mev.co.uk>2015-10-27 12:59:25 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2015-10-28 19:58:36 -0400
commit0cf55bbef2f9f5a51d947c430e207d2360e89e4c (patch)
treea49c28caf87e3c92ce216409c69dad0c2ad0c3fb
parent9406a3140a76772cc6bbf8704ecebbd249e9ca9b (diff)
staging: comedi: comedi_test: implement commands on AO subdevice
Implement COMEDI asynchronous commands on the fake analog output subdevice. This is useful for testing asynchronous commands in the "write" direction when no real hardware is available. A normal kernel timer is used to drive the command. The new timer expiry function `waveform_ao_timer()` handles whole "scans" at a time according to the number of scan period that have elapsed since the last scan. Data for each channel in the scan is written to the internal loopback array `devpriv->ao_loopbacks[]` and can be read back on the analog input channels. However, if several scan periods are outstanding in the timer expiry function, only the latest available scan data is written to the loopback array in order to save processing time. The expiry function also checks for underrun conditions, and checks for normal termination of the asynchronous command when a "stop" scan count is reached. After the command is tested by `waveform_ao_cmdtest()` and set up by `waveform_ao_cmd()`, it is not started until an internal trigger function `waveform_ao_inttrig_start()` is called as a result of the user performing an `INSN_INTTRIG` instruction on the subdevice. The command is stopped when the "cancel" handler `waveform_ao_cancel()` is called. This may be due to the command terminating due to completion or an error, or as a result of the user cancelling the command. Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Reviewed-by: H Hartley Sweeten <hsweeten@visionengravers.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/staging/comedi/drivers/comedi_test.c212
1 files changed, 209 insertions, 3 deletions
diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c
index 14a0b6247922..4ab186669f0c 100644
--- a/drivers/staging/comedi/drivers/comedi_test.c
+++ b/drivers/staging/comedi/drivers/comedi_test.c
@@ -57,7 +57,8 @@
57#define N_CHANS 8 57#define N_CHANS 8
58 58
59enum waveform_state_bits { 59enum waveform_state_bits {
60 WAVEFORM_AI_RUNNING = 0 60 WAVEFORM_AI_RUNNING,
61 WAVEFORM_AO_RUNNING
61}; 62};
62 63
63/* Data unique to this driver */ 64/* Data unique to this driver */
@@ -70,6 +71,9 @@ struct waveform_private {
70 unsigned long state_bits; 71 unsigned long state_bits;
71 unsigned int ai_scan_period; /* AI scan period in usec */ 72 unsigned int ai_scan_period; /* AI scan period in usec */
72 unsigned int ai_convert_period; /* AI conversion period in usec */ 73 unsigned int ai_convert_period; /* AI conversion period in usec */
74 struct timer_list ao_timer; /* timer for AO commands */
75 u64 ao_last_scan_time; /* time of previous AO scan in usec */
76 unsigned int ao_scan_period; /* AO scan period in usec */
73 unsigned short ao_loopbacks[N_CHANS]; 77 unsigned short ao_loopbacks[N_CHANS];
74}; 78};
75 79
@@ -417,6 +421,201 @@ static int waveform_ai_insn_read(struct comedi_device *dev,
417 return insn->n; 421 return insn->n;
418} 422}
419 423
424/*
425 * This is the background routine to handle AO commands, scheduled by
426 * a timer mechanism.
427 */
428static void waveform_ao_timer(unsigned long arg)
429{
430 struct comedi_device *dev = (struct comedi_device *)arg;
431 struct waveform_private *devpriv = dev->private;
432 struct comedi_subdevice *s = dev->write_subdev;
433 struct comedi_async *async = s->async;
434 struct comedi_cmd *cmd = &async->cmd;
435 u64 now;
436 u64 scans_since;
437 unsigned int scans_avail = 0;
438
439 /* check command is still active */
440 if (!test_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits))
441 return;
442
443 /* determine number of scan periods since last time */
444 now = ktime_to_us(ktime_get());
445 scans_since = now - devpriv->ao_last_scan_time;
446 do_div(scans_since, devpriv->ao_scan_period);
447 if (scans_since) {
448 unsigned int i;
449
450 /* determine scans in buffer, limit to scans to do this time */
451 scans_avail = comedi_nscans_left(s, 0);
452 if (scans_avail > scans_since)
453 scans_avail = scans_since;
454 if (scans_avail) {
455 /* skip all but the last scan to save processing time */
456 if (scans_avail > 1) {
457 unsigned int skip_bytes, nbytes;
458
459 skip_bytes =
460 comedi_samples_to_bytes(s, cmd->scan_end_arg *
461 (scans_avail - 1));
462 nbytes = comedi_buf_read_alloc(s, skip_bytes);
463 comedi_buf_read_free(s, nbytes);
464 comedi_inc_scan_progress(s, nbytes);
465 if (nbytes < skip_bytes) {
466 /* unexpected underrun! (cancelled?) */
467 async->events |= COMEDI_CB_OVERFLOW;
468 goto underrun;
469 }
470 }
471 /* output the last scan */
472 for (i = 0; i < cmd->scan_end_arg; i++) {
473 unsigned int chan = CR_CHAN(cmd->chanlist[i]);
474
475 if (comedi_buf_read_samples(s,
476 &devpriv->
477 ao_loopbacks[chan],
478 1) == 0) {
479 /* unexpected underrun! (cancelled?) */
480 async->events |= COMEDI_CB_OVERFLOW;
481 goto underrun;
482 }
483 }
484 /* advance time of last scan */
485 devpriv->ao_last_scan_time +=
486 (u64)scans_avail * devpriv->ao_scan_period;
487 }
488 }
489 if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) {
490 async->events |= COMEDI_CB_EOA;
491 } else if (scans_avail < scans_since) {
492 async->events |= COMEDI_CB_OVERFLOW;
493 } else {
494 unsigned int time_inc = devpriv->ao_last_scan_time +
495 devpriv->ao_scan_period - now;
496
497 mod_timer(&devpriv->ao_timer,
498 jiffies + usecs_to_jiffies(time_inc));
499 }
500
501underrun:
502 comedi_handle_events(dev, s);
503}
504
505static int waveform_ao_inttrig_start(struct comedi_device *dev,
506 struct comedi_subdevice *s,
507 unsigned int trig_num)
508{
509 struct waveform_private *devpriv = dev->private;
510 struct comedi_async *async = s->async;
511 struct comedi_cmd *cmd = &async->cmd;
512
513 if (trig_num != cmd->start_arg)
514 return -EINVAL;
515
516 async->inttrig = NULL;
517
518 devpriv->ao_last_scan_time = ktime_to_us(ktime_get());
519 devpriv->ao_timer.expires =
520 jiffies + usecs_to_jiffies(devpriv->ao_scan_period);
521
522 /* mark command as active */
523 smp_mb__before_atomic();
524 set_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits);
525 smp_mb__after_atomic();
526 add_timer(&devpriv->ao_timer);
527
528 return 1;
529}
530
531static int waveform_ao_cmdtest(struct comedi_device *dev,
532 struct comedi_subdevice *s,
533 struct comedi_cmd *cmd)
534{
535 int err = 0;
536 unsigned int arg;
537
538 /* Step 1 : check if triggers are trivially valid */
539
540 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
541 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
542 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
543 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
544 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
545
546 if (err)
547 return 1;
548
549 /* Step 2a : make sure trigger sources are unique */
550
551 err |= comedi_check_trigger_is_unique(cmd->stop_src);
552
553 /* Step 2b : and mutually compatible */
554
555 if (err)
556 return 2;
557
558 /* Step 3: check if arguments are trivially valid */
559
560 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
561 NSEC_PER_USEC);
562 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
563 err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
564 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
565 cmd->chanlist_len);
566 if (cmd->stop_src == TRIG_COUNT)
567 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
568 else /* cmd->stop_src == TRIG_NONE */
569 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
570
571 if (err)
572 return 3;
573
574 /* step 4: fix up any arguments */
575
576 /* round scan_begin_arg to nearest microsecond */
577 arg = cmd->scan_begin_arg;
578 arg = min(arg, rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC));
579 arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC);
580 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
581
582 if (err)
583 return 4;
584
585 return 0;
586}
587
588static int waveform_ao_cmd(struct comedi_device *dev,
589 struct comedi_subdevice *s)
590{
591 struct waveform_private *devpriv = dev->private;
592 struct comedi_cmd *cmd = &s->async->cmd;
593
594 if (cmd->flags & CMDF_PRIORITY) {
595 dev_err(dev->class_dev,
596 "commands at RT priority not supported in this driver\n");
597 return -1;
598 }
599
600 devpriv->ao_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC;
601 s->async->inttrig = waveform_ao_inttrig_start;
602 return 0;
603}
604
605static int waveform_ao_cancel(struct comedi_device *dev,
606 struct comedi_subdevice *s)
607{
608 struct waveform_private *devpriv = dev->private;
609
610 s->async->inttrig = NULL;
611 /* mark command as no longer active */
612 clear_bit(WAVEFORM_AO_RUNNING, &devpriv->state_bits);
613 smp_mb__after_atomic();
614 /* cannot call del_timer_sync() as may be called from timer routine */
615 del_timer(&devpriv->ao_timer);
616 return 0;
617}
618
420static int waveform_ao_insn_write(struct comedi_device *dev, 619static int waveform_ao_insn_write(struct comedi_device *dev,
421 struct comedi_subdevice *s, 620 struct comedi_subdevice *s,
422 struct comedi_insn *insn, unsigned int *data) 621 struct comedi_insn *insn, unsigned int *data)
@@ -475,18 +674,23 @@ static int waveform_attach(struct comedi_device *dev,
475 dev->write_subdev = s; 674 dev->write_subdev = s;
476 /* analog output subdevice (loopback) */ 675 /* analog output subdevice (loopback) */
477 s->type = COMEDI_SUBD_AO; 676 s->type = COMEDI_SUBD_AO;
478 s->subdev_flags = SDF_WRITABLE | SDF_GROUND; 677 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
479 s->n_chan = N_CHANS; 678 s->n_chan = N_CHANS;
480 s->maxdata = 0xffff; 679 s->maxdata = 0xffff;
481 s->range_table = &waveform_ai_ranges; 680 s->range_table = &waveform_ai_ranges;
681 s->len_chanlist = s->n_chan;
482 s->insn_write = waveform_ao_insn_write; 682 s->insn_write = waveform_ao_insn_write;
483 s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */ 683 s->insn_read = waveform_ai_insn_read; /* do same as AI insn_read */
684 s->do_cmd = waveform_ao_cmd;
685 s->do_cmdtest = waveform_ao_cmdtest;
686 s->cancel = waveform_ao_cancel;
484 687
485 /* Our default loopback value is just a 0V flatline */ 688 /* Our default loopback value is just a 0V flatline */
486 for (i = 0; i < s->n_chan; i++) 689 for (i = 0; i < s->n_chan; i++)
487 devpriv->ao_loopbacks[i] = s->maxdata / 2; 690 devpriv->ao_loopbacks[i] = s->maxdata / 2;
488 691
489 setup_timer(&devpriv->ai_timer, waveform_ai_timer, (unsigned long)dev); 692 setup_timer(&devpriv->ai_timer, waveform_ai_timer, (unsigned long)dev);
693 setup_timer(&devpriv->ao_timer, waveform_ao_timer, (unsigned long)dev);
490 694
491 dev_info(dev->class_dev, 695 dev_info(dev->class_dev,
492 "%s: %u microvolt, %u microsecond waveform attached\n", 696 "%s: %u microvolt, %u microsecond waveform attached\n",
@@ -500,8 +704,10 @@ static void waveform_detach(struct comedi_device *dev)
500{ 704{
501 struct waveform_private *devpriv = dev->private; 705 struct waveform_private *devpriv = dev->private;
502 706
503 if (devpriv) 707 if (devpriv) {
504 del_timer_sync(&devpriv->ai_timer); 708 del_timer_sync(&devpriv->ai_timer);
709 del_timer_sync(&devpriv->ao_timer);
710 }
505} 711}
506 712
507static struct comedi_driver waveform_driver = { 713static struct comedi_driver waveform_driver = {