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