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