linux/drivers/staging/comedi/drivers/comedi_test.c
<<
>>
Prefs
   1/*
   2 * comedi/drivers/comedi_test.c
   3 *
   4 * Generates fake waveform signals that can be read through
   5 * the command interface.  It does _not_ read from any board;
   6 * it just generates deterministic waveforms.
   7 * Useful for various testing purposes.
   8 *
   9 * Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
  10 * Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
  11 *
  12 * COMEDI - Linux Control and Measurement Device Interface
  13 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
  14 *
  15 * This program is free software; you can redistribute it and/or modify
  16 * it under the terms of the GNU General Public License as published by
  17 * the Free Software Foundation; either version 2 of the License, or
  18 * (at your option) any later version.
  19 *
  20 * This program is distributed in the hope that it will be useful,
  21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23 * GNU General Public License for more details.
  24 */
  25
  26/*
  27 * Driver: comedi_test
  28 * Description: generates fake waveforms
  29 * Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
  30 *   <fmhess@users.sourceforge.net>, ds
  31 * Devices:
  32 * Status: works
  33 * Updated: Sat, 16 Mar 2002 17:34:48 -0800
  34 *
  35 * This driver is mainly for testing purposes, but can also be used to
  36 * generate sample waveforms on systems that don't have data acquisition
  37 * hardware.
  38 *
  39 * Configuration options:
  40 *   [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
  41 *   [1] - Period in microseconds for fake waveforms (default 0.1 sec)
  42 *
  43 * Generates a sawtooth wave on channel 0, square wave on channel 1, additional
  44 * waveforms could be added to other channels (currently they return flatline
  45 * zero volts).
  46 */
  47
  48#include <linux/module.h>
  49#include "../comedidev.h"
  50
  51#include <asm/div64.h>
  52
  53#include <linux/timer.h>
  54#include <linux/ktime.h>
  55#include <linux/jiffies.h>
  56
  57#define N_CHANS 8
  58
  59enum waveform_state_bits {
  60        WAVEFORM_AI_RUNNING,
  61        WAVEFORM_AO_RUNNING
  62};
  63
  64/* Data unique to this driver */
  65struct waveform_private {
  66        struct timer_list ai_timer;     /* timer for AI commands */
  67        u64 ai_convert_time;            /* time of next AI conversion in usec */
  68        unsigned int wf_amplitude;      /* waveform amplitude in microvolts */
  69        unsigned int wf_period;         /* waveform period in microseconds */
  70        unsigned int wf_current;        /* current time in waveform period */
  71        unsigned long state_bits;
  72        unsigned int ai_scan_period;    /* AI scan 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 */
  77        unsigned short ao_loopbacks[N_CHANS];
  78};
  79
  80/* fake analog input ranges */
  81static const struct comedi_lrange waveform_ai_ranges = {
  82        2, {
  83                BIP_RANGE(10),
  84                BIP_RANGE(5)
  85        }
  86};
  87
  88static unsigned short fake_sawtooth(struct comedi_device *dev,
  89                                    unsigned int range_index,
  90                                    unsigned int current_time)
  91{
  92        struct waveform_private *devpriv = dev->private;
  93        struct comedi_subdevice *s = dev->read_subdev;
  94        unsigned int offset = s->maxdata / 2;
  95        u64 value;
  96        const struct comedi_krange *krange =
  97            &s->range_table->range[range_index];
  98        u64 binary_amplitude;
  99
 100        binary_amplitude = s->maxdata;
 101        binary_amplitude *= devpriv->wf_amplitude;
 102        do_div(binary_amplitude, krange->max - krange->min);
 103
 104        value = current_time;
 105        value *= binary_amplitude * 2;
 106        do_div(value, devpriv->wf_period);
 107        value += offset;
 108        /* get rid of sawtooth's dc offset and clamp value */
 109        if (value < binary_amplitude) {
 110                value = 0;                      /* negative saturation */
 111        } else {
 112                value -= binary_amplitude;
 113                if (value > s->maxdata)
 114                        value = s->maxdata;     /* positive saturation */
 115        }
 116
 117        return value;
 118}
 119
 120static unsigned short fake_squarewave(struct comedi_device *dev,
 121                                      unsigned int range_index,
 122                                      unsigned int current_time)
 123{
 124        struct waveform_private *devpriv = dev->private;
 125        struct comedi_subdevice *s = dev->read_subdev;
 126        unsigned int offset = s->maxdata / 2;
 127        u64 value;
 128        const struct comedi_krange *krange =
 129            &s->range_table->range[range_index];
 130
 131        value = s->maxdata;
 132        value *= devpriv->wf_amplitude;
 133        do_div(value, krange->max - krange->min);
 134
 135        /* get one of two values for square-wave and clamp */
 136        if (current_time < devpriv->wf_period / 2) {
 137                if (offset < value)
 138                        value = 0;              /* negative saturation */
 139                else
 140                        value = offset - value;
 141        } else {
 142                value += offset;
 143                if (value > s->maxdata)
 144                        value = s->maxdata;     /* positive saturation */
 145        }
 146
 147        return value;
 148}
 149
 150static unsigned short fake_flatline(struct comedi_device *dev,
 151                                    unsigned int range_index,
 152                                    unsigned int current_time)
 153{
 154        return dev->read_subdev->maxdata / 2;
 155}
 156
 157/* generates a different waveform depending on what channel is read */
 158static unsigned short fake_waveform(struct comedi_device *dev,
 159                                    unsigned int channel, unsigned int range,
 160                                    unsigned int current_time)
 161{
 162        enum {
 163                SAWTOOTH_CHAN,
 164                SQUARE_CHAN,
 165        };
 166        switch (channel) {
 167        case SAWTOOTH_CHAN:
 168                return fake_sawtooth(dev, range, current_time);
 169        case SQUARE_CHAN:
 170                return fake_squarewave(dev, range, current_time);
 171        default:
 172                break;
 173        }
 174
 175        return fake_flatline(dev, range, current_time);
 176}
 177
 178/*
 179 * This is the background routine used to generate arbitrary data.
 180 * It should run in the background; therefore it is scheduled by
 181 * a timer mechanism.
 182 */
 183static void waveform_ai_timer(unsigned long arg)
 184{
 185        struct comedi_device *dev = (struct comedi_device *)arg;
 186        struct waveform_private *devpriv = dev->private;
 187        struct comedi_subdevice *s = dev->read_subdev;
 188        struct comedi_async *async = s->async;
 189        struct comedi_cmd *cmd = &async->cmd;
 190        u64 now;
 191        unsigned int nsamples;
 192        unsigned int time_increment;
 193
 194        /* check command is still active */
 195        if (!test_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits))
 196                return;
 197
 198        now = ktime_to_us(ktime_get());
 199        nsamples = comedi_nsamples_left(s, UINT_MAX);
 200
 201        while (nsamples && devpriv->ai_convert_time < now) {
 202                unsigned int chanspec = cmd->chanlist[async->cur_chan];
 203                unsigned short sample;
 204
 205                sample = fake_waveform(dev, CR_CHAN(chanspec),
 206                                       CR_RANGE(chanspec), devpriv->wf_current);
 207                if (comedi_buf_write_samples(s, &sample, 1) == 0)
 208                        goto overrun;
 209                time_increment = devpriv->ai_convert_period;
 210                if (async->scan_progress == 0) {
 211                        /* done last conversion in scan, so add dead time */
 212                        time_increment += devpriv->ai_scan_period -
 213                                          devpriv->ai_convert_period *
 214                                          cmd->scan_end_arg;
 215                }
 216                devpriv->wf_current += time_increment;
 217                if (devpriv->wf_current >= devpriv->wf_period)
 218                        devpriv->wf_current %= devpriv->wf_period;
 219                devpriv->ai_convert_time += time_increment;
 220                nsamples--;
 221        }
 222
 223        if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) {
 224                async->events |= COMEDI_CB_EOA;
 225        } else {
 226                if (devpriv->ai_convert_time > now)
 227                        time_increment = devpriv->ai_convert_time - now;
 228                else
 229                        time_increment = 1;
 230                mod_timer(&devpriv->ai_timer,
 231                          jiffies + usecs_to_jiffies(time_increment));
 232        }
 233
 234overrun:
 235        comedi_handle_events(dev, s);
 236}
 237
 238static int waveform_ai_cmdtest(struct comedi_device *dev,
 239                               struct comedi_subdevice *s,
 240                               struct comedi_cmd *cmd)
 241{
 242        int err = 0;
 243        unsigned int arg, limit;
 244
 245        /* Step 1 : check if triggers are trivially valid */
 246
 247        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 248        err |= comedi_check_trigger_src(&cmd->scan_begin_src,
 249                                        TRIG_FOLLOW | TRIG_TIMER);
 250        err |= comedi_check_trigger_src(&cmd->convert_src,
 251                                        TRIG_NOW | TRIG_TIMER);
 252        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 253        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 254
 255        if (err)
 256                return 1;
 257
 258        /* Step 2a : make sure trigger sources are unique */
 259
 260        err |= comedi_check_trigger_is_unique(cmd->convert_src);
 261        err |= comedi_check_trigger_is_unique(cmd->stop_src);
 262
 263        /* Step 2b : and mutually compatible */
 264
 265        if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
 266                err |= -EINVAL;         /* scan period would be 0 */
 267
 268        if (err)
 269                return 2;
 270
 271        /* Step 3: check if arguments are trivially valid */
 272
 273        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 274
 275        if (cmd->convert_src == TRIG_NOW) {
 276                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
 277        } else {        /* cmd->convert_src == TRIG_TIMER */
 278                if (cmd->scan_begin_src == TRIG_FOLLOW) {
 279                        err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
 280                                                            NSEC_PER_USEC);
 281                }
 282        }
 283
 284        if (cmd->scan_begin_src == TRIG_FOLLOW) {
 285                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 286        } else {        /* cmd->scan_begin_src == TRIG_TIMER */
 287                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
 288                                                    NSEC_PER_USEC);
 289        }
 290
 291        err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
 292        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 293                                           cmd->chanlist_len);
 294
 295        if (cmd->stop_src == TRIG_COUNT)
 296                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
 297        else    /* cmd->stop_src == TRIG_NONE */
 298                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 299
 300        if (err)
 301                return 3;
 302
 303        /* step 4: fix up any arguments */
 304
 305        if (cmd->convert_src == TRIG_TIMER) {
 306                /* round convert_arg to nearest microsecond */
 307                arg = cmd->convert_arg;
 308                arg = min(arg,
 309                          rounddown(UINT_MAX, (unsigned int)NSEC_PER_USEC));
 310                arg = NSEC_PER_USEC * DIV_ROUND_CLOSEST(arg, NSEC_PER_USEC);
 311                if (cmd->scan_begin_arg == TRIG_TIMER) {
 312                        /* limit convert_arg to keep scan_begin_arg in range */
 313                        limit = UINT_MAX / cmd->scan_end_arg;
 314                        limit = rounddown(limit, (unsigned int)NSEC_PER_SEC);
 315                        arg = min(arg, limit);
 316                }
 317                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
 318        }
 319
 320        if (cmd->scan_begin_src == TRIG_TIMER) {
 321                /* round scan_begin_arg to nearest microsecond */
 322                arg = cmd->scan_begin_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->convert_src == TRIG_TIMER) {
 327                        /* but ensure scan_begin_arg is large enough */
 328                        arg = max(arg, cmd->convert_arg * cmd->scan_end_arg);
 329                }
 330                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
 331        }
 332
 333        if (err)
 334                return 4;
 335
 336        return 0;
 337}
 338
 339static int waveform_ai_cmd(struct comedi_device *dev,
 340                           struct comedi_subdevice *s)
 341{
 342        struct waveform_private *devpriv = dev->private;
 343        struct comedi_cmd *cmd = &s->async->cmd;
 344        unsigned int first_convert_time;
 345        u64 wf_current;
 346
 347        if (cmd->flags & CMDF_PRIORITY) {
 348                dev_err(dev->class_dev,
 349                        "commands at RT priority not supported in this driver\n");
 350                return -1;
 351        }
 352
 353        if (cmd->convert_src == TRIG_NOW)
 354                devpriv->ai_convert_period = 0;
 355        else            /* cmd->convert_src == TRIG_TIMER */
 356                devpriv->ai_convert_period = cmd->convert_arg / NSEC_PER_USEC;
 357
 358        if (cmd->scan_begin_src == TRIG_FOLLOW) {
 359                devpriv->ai_scan_period = devpriv->ai_convert_period *
 360                                          cmd->scan_end_arg;
 361        } else {        /* cmd->scan_begin_src == TRIG_TIMER */
 362                devpriv->ai_scan_period = cmd->scan_begin_arg / NSEC_PER_USEC;
 363        }
 364
 365        /*
 366         * Simulate first conversion to occur at convert period after
 367         * conversion timer starts.  If scan_begin_src is TRIG_FOLLOW, assume
 368         * the conversion timer starts immediately.  If scan_begin_src is
 369         * TRIG_TIMER, assume the conversion timer starts after the scan
 370         * period.
 371         */
 372        first_convert_time = devpriv->ai_convert_period;
 373        if (cmd->scan_begin_src == TRIG_TIMER)
 374                first_convert_time += devpriv->ai_scan_period;
 375        devpriv->ai_convert_time = ktime_to_us(ktime_get()) +
 376                                   first_convert_time;
 377
 378        /* Determine time within waveform period at time of conversion. */
 379        wf_current = devpriv->ai_convert_time;
 380        devpriv->wf_current = do_div(wf_current, devpriv->wf_period);
 381
 382        /*
 383         * Schedule timer to expire just after first conversion time.
 384         * Seem to need an extra jiffy here, otherwise timer expires slightly
 385         * early!
 386         */
 387        devpriv->ai_timer.expires =
 388                jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1;
 389
 390        /* mark command as active */
 391        smp_mb__before_atomic();
 392        set_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
 393        smp_mb__after_atomic();
 394        add_timer(&devpriv->ai_timer);
 395        return 0;
 396}
 397
 398static int waveform_ai_cancel(struct comedi_device *dev,
 399                              struct comedi_subdevice *s)
 400{
 401        struct waveform_private *devpriv = dev->private;
 402
 403        /* mark command as no longer active */
 404        clear_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
 405        smp_mb__after_atomic();
 406        /* cannot call del_timer_sync() as may be called from timer routine */
 407        del_timer(&devpriv->ai_timer);
 408        return 0;
 409}
 410
 411static int waveform_ai_insn_read(struct comedi_device *dev,
 412                                 struct comedi_subdevice *s,
 413                                 struct comedi_insn *insn, unsigned int *data)
 414{
 415        struct waveform_private *devpriv = dev->private;
 416        int i, chan = CR_CHAN(insn->chanspec);
 417
 418        for (i = 0; i < insn->n; i++)
 419                data[i] = devpriv->ao_loopbacks[chan];
 420
 421        return insn->n;
 422}
 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
 619static int waveform_ao_insn_write(struct comedi_device *dev,
 620                                  struct comedi_subdevice *s,
 621                                  struct comedi_insn *insn, unsigned int *data)
 622{
 623        struct waveform_private *devpriv = dev->private;
 624        int i, chan = CR_CHAN(insn->chanspec);
 625
 626        for (i = 0; i < insn->n; i++)
 627                devpriv->ao_loopbacks[chan] = data[i];
 628
 629        return insn->n;
 630}
 631
 632static int waveform_attach(struct comedi_device *dev,
 633                           struct comedi_devconfig *it)
 634{
 635        struct waveform_private *devpriv;
 636        struct comedi_subdevice *s;
 637        int amplitude = it->options[0];
 638        int period = it->options[1];
 639        int i;
 640        int ret;
 641
 642        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 643        if (!devpriv)
 644                return -ENOMEM;
 645
 646        /* set default amplitude and period */
 647        if (amplitude <= 0)
 648                amplitude = 1000000;    /* 1 volt */
 649        if (period <= 0)
 650                period = 100000;        /* 0.1 sec */
 651
 652        devpriv->wf_amplitude = amplitude;
 653        devpriv->wf_period = period;
 654
 655        ret = comedi_alloc_subdevices(dev, 2);
 656        if (ret)
 657                return ret;
 658
 659        s = &dev->subdevices[0];
 660        dev->read_subdev = s;
 661        /* analog input subdevice */
 662        s->type = COMEDI_SUBD_AI;
 663        s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
 664        s->n_chan = N_CHANS;
 665        s->maxdata = 0xffff;
 666        s->range_table = &waveform_ai_ranges;
 667        s->len_chanlist = s->n_chan * 2;
 668        s->insn_read = waveform_ai_insn_read;
 669        s->do_cmd = waveform_ai_cmd;
 670        s->do_cmdtest = waveform_ai_cmdtest;
 671        s->cancel = waveform_ai_cancel;
 672
 673        s = &dev->subdevices[1];
 674        dev->write_subdev = s;
 675        /* analog output subdevice (loopback) */
 676        s->type = COMEDI_SUBD_AO;
 677        s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
 678        s->n_chan = N_CHANS;
 679        s->maxdata = 0xffff;
 680        s->range_table = &waveform_ai_ranges;
 681        s->len_chanlist = s->n_chan;
 682        s->insn_write = waveform_ao_insn_write;
 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;
 687
 688        /* Our default loopback value is just a 0V flatline */
 689        for (i = 0; i < s->n_chan; i++)
 690                devpriv->ao_loopbacks[i] = s->maxdata / 2;
 691
 692        setup_timer(&devpriv->ai_timer, waveform_ai_timer, (unsigned long)dev);
 693        setup_timer(&devpriv->ao_timer, waveform_ao_timer, (unsigned long)dev);
 694
 695        dev_info(dev->class_dev,
 696                 "%s: %u microvolt, %u microsecond waveform attached\n",
 697                 dev->board_name,
 698                 devpriv->wf_amplitude, devpriv->wf_period);
 699
 700        return 0;
 701}
 702
 703static void waveform_detach(struct comedi_device *dev)
 704{
 705        struct waveform_private *devpriv = dev->private;
 706
 707        if (devpriv) {
 708                del_timer_sync(&devpriv->ai_timer);
 709                del_timer_sync(&devpriv->ao_timer);
 710        }
 711}
 712
 713static struct comedi_driver waveform_driver = {
 714        .driver_name    = "comedi_test",
 715        .module         = THIS_MODULE,
 716        .attach         = waveform_attach,
 717        .detach         = waveform_detach,
 718};
 719module_comedi_driver(waveform_driver);
 720
 721MODULE_AUTHOR("Comedi http://www.comedi.org");
 722MODULE_DESCRIPTION("Comedi low-level driver");
 723MODULE_LICENSE("GPL");
 724