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/*
  26Driver: comedi_test
  27Description: generates fake waveforms
  28Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
  29  <fmhess@users.sourceforge.net>, ds
  30Devices:
  31Status: works
  32Updated: Sat, 16 Mar 2002 17:34:48 -0800
  33
  34This driver is mainly for testing purposes, but can also be used to
  35generate sample waveforms on systems that don't have data acquisition
  36hardware.
  37
  38Configuration 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
  42Generates a sawtooth wave on channel 0, square wave on channel 1, additional
  43waveforms could be added to other channels (currently they return flatline
  44zero volts).
  45
  46*/
  47
  48#include <linux/module.h>
  49#include "../comedidev.h"
  50
  51#include <asm/div64.h>
  52
  53#include "comedi_fc.h"
  54#include <linux/timer.h>
  55
  56#define N_CHANS 8
  57
  58/* Data unique to this driver */
  59struct waveform_private {
  60        struct timer_list timer;
  61        struct timeval last;            /* time last timer interrupt occurred */
  62        unsigned int uvolt_amplitude;   /* waveform amplitude in microvolts */
  63        unsigned long usec_period;      /* waveform period in microseconds */
  64        unsigned long usec_current;     /* current time (mod waveform period) */
  65        unsigned long usec_remainder;   /* usec since last scan */
  66        unsigned long ai_count;         /* number of conversions remaining */
  67        unsigned int scan_period;       /* scan period in usec */
  68        unsigned int convert_period;    /* conversion period in usec */
  69        unsigned int ao_loopbacks[N_CHANS];
  70};
  71
  72/* 1000 nanosec in a microsec */
  73static const int nano_per_micro = 1000;
  74
  75/* fake analog input ranges */
  76static const struct comedi_lrange waveform_ai_ranges = {
  77        2,
  78        {
  79         BIP_RANGE(10),
  80         BIP_RANGE(5),
  81         }
  82};
  83
  84static unsigned short fake_sawtooth(struct comedi_device *dev,
  85                                    unsigned int range_index,
  86                                    unsigned long current_time)
  87{
  88        struct waveform_private *devpriv = dev->private;
  89        struct comedi_subdevice *s = dev->read_subdev;
  90        unsigned int offset = s->maxdata / 2;
  91        u64 value;
  92        const struct comedi_krange *krange =
  93            &s->range_table->range[range_index];
  94        u64 binary_amplitude;
  95
  96        binary_amplitude = s->maxdata;
  97        binary_amplitude *= devpriv->uvolt_amplitude;
  98        do_div(binary_amplitude, krange->max - krange->min);
  99
 100        current_time %= devpriv->usec_period;
 101        value = current_time;
 102        value *= binary_amplitude * 2;
 103        do_div(value, devpriv->usec_period);
 104        value -= binary_amplitude;      /* get rid of sawtooth's dc offset */
 105
 106        return offset + value;
 107}
 108
 109static unsigned short fake_squarewave(struct comedi_device *dev,
 110                                      unsigned int range_index,
 111                                      unsigned long current_time)
 112{
 113        struct waveform_private *devpriv = dev->private;
 114        struct comedi_subdevice *s = dev->read_subdev;
 115        unsigned int offset = s->maxdata / 2;
 116        u64 value;
 117        const struct comedi_krange *krange =
 118            &s->range_table->range[range_index];
 119        current_time %= devpriv->usec_period;
 120
 121        value = s->maxdata;
 122        value *= devpriv->uvolt_amplitude;
 123        do_div(value, krange->max - krange->min);
 124
 125        if (current_time < devpriv->usec_period / 2)
 126                value *= -1;
 127
 128        return offset + value;
 129}
 130
 131static unsigned short fake_flatline(struct comedi_device *dev,
 132                                    unsigned int range_index,
 133                                    unsigned long current_time)
 134{
 135        return dev->read_subdev->maxdata / 2;
 136}
 137
 138/* generates a different waveform depending on what channel is read */
 139static unsigned short fake_waveform(struct comedi_device *dev,
 140                                    unsigned int channel, unsigned int range,
 141                                    unsigned long current_time)
 142{
 143        enum {
 144                SAWTOOTH_CHAN,
 145                SQUARE_CHAN,
 146        };
 147        switch (channel) {
 148        case SAWTOOTH_CHAN:
 149                return fake_sawtooth(dev, range, current_time);
 150                break;
 151        case SQUARE_CHAN:
 152                return fake_squarewave(dev, range, current_time);
 153                break;
 154        default:
 155                break;
 156        }
 157
 158        return fake_flatline(dev, range, current_time);
 159}
 160
 161/*
 162   This is the background routine used to generate arbitrary data.
 163   It should run in the background; therefore it is scheduled by
 164   a timer mechanism.
 165*/
 166static void waveform_ai_interrupt(unsigned long arg)
 167{
 168        struct comedi_device *dev = (struct comedi_device *)arg;
 169        struct waveform_private *devpriv = dev->private;
 170        struct comedi_async *async = dev->read_subdev->async;
 171        struct comedi_cmd *cmd = &async->cmd;
 172        unsigned int i, j;
 173        /* all times in microsec */
 174        unsigned long elapsed_time;
 175        unsigned int num_scans;
 176        struct timeval now;
 177        bool stopping = false;
 178
 179        do_gettimeofday(&now);
 180
 181        elapsed_time =
 182            1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
 183            devpriv->last.tv_usec;
 184        devpriv->last = now;
 185        num_scans =
 186            (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
 187        devpriv->usec_remainder =
 188            (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
 189        async->events = 0;
 190
 191        if (cmd->stop_src == TRIG_COUNT) {
 192                unsigned int remaining = cmd->stop_arg - devpriv->ai_count;
 193                if (num_scans >= remaining) {
 194                        /* about to finish */
 195                        num_scans = remaining;
 196                        stopping = true;
 197                }
 198        }
 199
 200        for (i = 0; i < num_scans; i++) {
 201                for (j = 0; j < cmd->chanlist_len; j++) {
 202                        unsigned short sample;
 203                        sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
 204                                               CR_RANGE(cmd->chanlist[j]),
 205                                               devpriv->usec_current +
 206                                                   i * devpriv->scan_period +
 207                                                   j * devpriv->convert_period);
 208                        cfc_write_to_buffer(dev->read_subdev, sample);
 209                }
 210        }
 211
 212        devpriv->ai_count += i;
 213        devpriv->usec_current += elapsed_time;
 214        devpriv->usec_current %= devpriv->usec_period;
 215
 216        if (stopping)
 217                async->events |= COMEDI_CB_EOA;
 218        else
 219                mod_timer(&devpriv->timer, jiffies + 1);
 220
 221        comedi_event(dev, dev->read_subdev);
 222}
 223
 224static int waveform_ai_cmdtest(struct comedi_device *dev,
 225                               struct comedi_subdevice *s,
 226                               struct comedi_cmd *cmd)
 227{
 228        int err = 0;
 229        int tmp;
 230
 231        /* Step 1 : check if triggers are trivially valid */
 232
 233        err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
 234        err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
 235        err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER);
 236        err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 237        err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 238
 239        if (err)
 240                return 1;
 241
 242        /* Step 2a : make sure trigger sources are unique */
 243
 244        err |= cfc_check_trigger_is_unique(cmd->convert_src);
 245        err |= cfc_check_trigger_is_unique(cmd->stop_src);
 246
 247        /* Step 2b : and mutually compatible */
 248
 249        if (err)
 250                return 2;
 251
 252        /* Step 3: check if arguments are trivially valid */
 253
 254        err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
 255
 256        if (cmd->convert_src == TRIG_NOW)
 257                err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
 258
 259        if (cmd->scan_begin_src == TRIG_TIMER) {
 260                err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
 261                                                 nano_per_micro);
 262                if (cmd->convert_src == TRIG_TIMER)
 263                        err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
 264                                        cmd->convert_arg * cmd->chanlist_len);
 265        }
 266
 267        err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
 268        err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
 269
 270        if (cmd->stop_src == TRIG_COUNT)
 271                err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
 272        else    /* TRIG_NONE */
 273                err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
 274
 275        if (err)
 276                return 3;
 277
 278        /* step 4: fix up any arguments */
 279
 280        if (cmd->scan_begin_src == TRIG_TIMER) {
 281                tmp = cmd->scan_begin_arg;
 282                /* round to nearest microsec */
 283                cmd->scan_begin_arg =
 284                    nano_per_micro * ((tmp +
 285                                       (nano_per_micro / 2)) / nano_per_micro);
 286                if (tmp != cmd->scan_begin_arg)
 287                        err++;
 288        }
 289        if (cmd->convert_src == TRIG_TIMER) {
 290                tmp = cmd->convert_arg;
 291                /* round to nearest microsec */
 292                cmd->convert_arg =
 293                    nano_per_micro * ((tmp +
 294                                       (nano_per_micro / 2)) / nano_per_micro);
 295                if (tmp != cmd->convert_arg)
 296                        err++;
 297        }
 298
 299        if (err)
 300                return 4;
 301
 302        return 0;
 303}
 304
 305static int waveform_ai_cmd(struct comedi_device *dev,
 306                           struct comedi_subdevice *s)
 307{
 308        struct waveform_private *devpriv = dev->private;
 309        struct comedi_cmd *cmd = &s->async->cmd;
 310
 311        if (cmd->flags & TRIG_RT) {
 312                comedi_error(dev,
 313                             "commands at RT priority not supported in this driver");
 314                return -1;
 315        }
 316
 317        devpriv->ai_count = 0;
 318        devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
 319
 320        if (cmd->convert_src == TRIG_NOW)
 321                devpriv->convert_period = 0;
 322        else if (cmd->convert_src == TRIG_TIMER)
 323                devpriv->convert_period = cmd->convert_arg / nano_per_micro;
 324        else {
 325                comedi_error(dev, "bug setting conversion period");
 326                return -1;
 327        }
 328
 329        do_gettimeofday(&devpriv->last);
 330        devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
 331        devpriv->usec_remainder = 0;
 332
 333        devpriv->timer.expires = jiffies + 1;
 334        add_timer(&devpriv->timer);
 335        return 0;
 336}
 337
 338static int waveform_ai_cancel(struct comedi_device *dev,
 339                              struct comedi_subdevice *s)
 340{
 341        struct waveform_private *devpriv = dev->private;
 342
 343        del_timer_sync(&devpriv->timer);
 344        return 0;
 345}
 346
 347static int waveform_ai_insn_read(struct comedi_device *dev,
 348                                 struct comedi_subdevice *s,
 349                                 struct comedi_insn *insn, unsigned int *data)
 350{
 351        struct waveform_private *devpriv = dev->private;
 352        int i, chan = CR_CHAN(insn->chanspec);
 353
 354        for (i = 0; i < insn->n; i++)
 355                data[i] = devpriv->ao_loopbacks[chan];
 356
 357        return insn->n;
 358}
 359
 360static int waveform_ao_insn_write(struct comedi_device *dev,
 361                                  struct comedi_subdevice *s,
 362                                  struct comedi_insn *insn, unsigned int *data)
 363{
 364        struct waveform_private *devpriv = dev->private;
 365        int i, chan = CR_CHAN(insn->chanspec);
 366
 367        for (i = 0; i < insn->n; i++)
 368                devpriv->ao_loopbacks[chan] = data[i];
 369
 370        return insn->n;
 371}
 372
 373static int waveform_attach(struct comedi_device *dev,
 374                           struct comedi_devconfig *it)
 375{
 376        struct waveform_private *devpriv;
 377        struct comedi_subdevice *s;
 378        int amplitude = it->options[0];
 379        int period = it->options[1];
 380        int i;
 381        int ret;
 382
 383        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 384        if (!devpriv)
 385                return -ENOMEM;
 386
 387        /* set default amplitude and period */
 388        if (amplitude <= 0)
 389                amplitude = 1000000;    /* 1 volt */
 390        if (period <= 0)
 391                period = 100000;        /* 0.1 sec */
 392
 393        devpriv->uvolt_amplitude = amplitude;
 394        devpriv->usec_period = period;
 395
 396        ret = comedi_alloc_subdevices(dev, 2);
 397        if (ret)
 398                return ret;
 399
 400        s = &dev->subdevices[0];
 401        dev->read_subdev = s;
 402        /* analog input subdevice */
 403        s->type = COMEDI_SUBD_AI;
 404        s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
 405        s->n_chan = N_CHANS;
 406        s->maxdata = 0xffff;
 407        s->range_table = &waveform_ai_ranges;
 408        s->len_chanlist = s->n_chan * 2;
 409        s->insn_read = waveform_ai_insn_read;
 410        s->do_cmd = waveform_ai_cmd;
 411        s->do_cmdtest = waveform_ai_cmdtest;
 412        s->cancel = waveform_ai_cancel;
 413
 414        s = &dev->subdevices[1];
 415        dev->write_subdev = s;
 416        /* analog output subdevice (loopback) */
 417        s->type = COMEDI_SUBD_AO;
 418        s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
 419        s->n_chan = N_CHANS;
 420        s->maxdata = 0xffff;
 421        s->range_table = &waveform_ai_ranges;
 422        s->len_chanlist = s->n_chan * 2;
 423        s->insn_write = waveform_ao_insn_write;
 424        s->do_cmd = NULL;
 425        s->do_cmdtest = NULL;
 426        s->cancel = NULL;
 427
 428        /* Our default loopback value is just a 0V flatline */
 429        for (i = 0; i < s->n_chan; i++)
 430                devpriv->ao_loopbacks[i] = s->maxdata / 2;
 431
 432        init_timer(&(devpriv->timer));
 433        devpriv->timer.function = waveform_ai_interrupt;
 434        devpriv->timer.data = (unsigned long)dev;
 435
 436        dev_info(dev->class_dev,
 437                "%s: %i microvolt, %li microsecond waveform attached\n",
 438                dev->board_name,
 439                devpriv->uvolt_amplitude, devpriv->usec_period);
 440
 441        return 0;
 442}
 443
 444static void waveform_detach(struct comedi_device *dev)
 445{
 446        struct waveform_private *devpriv = dev->private;
 447
 448        if (devpriv)
 449                waveform_ai_cancel(dev, dev->read_subdev);
 450}
 451
 452static struct comedi_driver waveform_driver = {
 453        .driver_name    = "comedi_test",
 454        .module         = THIS_MODULE,
 455        .attach         = waveform_attach,
 456        .detach         = waveform_detach,
 457};
 458module_comedi_driver(waveform_driver);
 459
 460MODULE_AUTHOR("Comedi http://www.comedi.org");
 461MODULE_DESCRIPTION("Comedi low-level driver");
 462MODULE_LICENSE("GPL");
 463