d9810caae4759ea7759177b192d5855fdb218d31
[deliverable/linux.git] / drivers / staging / comedi / drivers / comedi_test.c
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
56 #define N_CHANS 8
57
58 enum waveform_state_bits {
59 WAVEFORM_AI_RUNNING = 0
60 };
61
62 /* Data unique to this driver */
63 struct waveform_private {
64 struct timer_list timer;
65 ktime_t last; /* time last timer interrupt occurred */
66 unsigned int uvolt_amplitude; /* waveform amplitude in microvolts */
67 unsigned long usec_period; /* waveform period in microseconds */
68 unsigned long usec_current; /* current time (mod waveform period) */
69 unsigned long usec_remainder; /* usec since last scan */
70 unsigned long state_bits;
71 unsigned int scan_period; /* scan period in usec */
72 unsigned int convert_period; /* conversion period in usec */
73 unsigned int ao_loopbacks[N_CHANS];
74 };
75
76 /* fake analog input ranges */
77 static const struct comedi_lrange waveform_ai_ranges = {
78 2, {
79 BIP_RANGE(10),
80 BIP_RANGE(5)
81 }
82 };
83
84 static 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 += offset;
105 /* get rid of sawtooth's dc offset and clamp value */
106 if (value < binary_amplitude) {
107 value = 0; /* negative saturation */
108 } else {
109 value -= binary_amplitude;
110 if (value > s->maxdata)
111 value = s->maxdata; /* positive saturation */
112 }
113
114 return value;
115 }
116
117 static unsigned short fake_squarewave(struct comedi_device *dev,
118 unsigned int range_index,
119 unsigned long current_time)
120 {
121 struct waveform_private *devpriv = dev->private;
122 struct comedi_subdevice *s = dev->read_subdev;
123 unsigned int offset = s->maxdata / 2;
124 u64 value;
125 const struct comedi_krange *krange =
126 &s->range_table->range[range_index];
127
128 current_time %= devpriv->usec_period;
129 value = s->maxdata;
130 value *= devpriv->uvolt_amplitude;
131 do_div(value, krange->max - krange->min);
132
133 /* get one of two values for square-wave and clamp */
134 if (current_time < devpriv->usec_period / 2) {
135 if (offset < value)
136 value = 0; /* negative saturation */
137 else
138 value = offset - value;
139 } else {
140 value += offset;
141 if (value > s->maxdata)
142 value = s->maxdata; /* positive saturation */
143 }
144
145 return value;
146 }
147
148 static unsigned short fake_flatline(struct comedi_device *dev,
149 unsigned int range_index,
150 unsigned long current_time)
151 {
152 return dev->read_subdev->maxdata / 2;
153 }
154
155 /* generates a different waveform depending on what channel is read */
156 static unsigned short fake_waveform(struct comedi_device *dev,
157 unsigned int channel, unsigned int range,
158 unsigned long current_time)
159 {
160 enum {
161 SAWTOOTH_CHAN,
162 SQUARE_CHAN,
163 };
164 switch (channel) {
165 case SAWTOOTH_CHAN:
166 return fake_sawtooth(dev, range, current_time);
167 case SQUARE_CHAN:
168 return fake_squarewave(dev, range, current_time);
169 default:
170 break;
171 }
172
173 return fake_flatline(dev, range, current_time);
174 }
175
176 /*
177 * This is the background routine used to generate arbitrary data.
178 * It should run in the background; therefore it is scheduled by
179 * a timer mechanism.
180 */
181 static void waveform_ai_interrupt(unsigned long arg)
182 {
183 struct comedi_device *dev = (struct comedi_device *)arg;
184 struct waveform_private *devpriv = dev->private;
185 struct comedi_subdevice *s = dev->read_subdev;
186 struct comedi_async *async = s->async;
187 struct comedi_cmd *cmd = &async->cmd;
188 unsigned int i, j;
189 /* all times in microsec */
190 unsigned long elapsed_time;
191 unsigned int num_scans;
192 ktime_t now;
193
194 /* check command is still active */
195 if (!test_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits))
196 return;
197
198 now = ktime_get();
199
200 elapsed_time = ktime_to_us(ktime_sub(now, devpriv->last));
201 devpriv->last = now;
202 num_scans =
203 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
204 devpriv->usec_remainder =
205 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
206
207 num_scans = comedi_nscans_left(s, num_scans);
208 for (i = 0; i < num_scans; i++) {
209 for (j = 0; j < cmd->chanlist_len; j++) {
210 unsigned short sample;
211
212 sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
213 CR_RANGE(cmd->chanlist[j]),
214 devpriv->usec_current +
215 i * devpriv->scan_period +
216 j * devpriv->convert_period);
217 comedi_buf_write_samples(s, &sample, 1);
218 }
219 }
220
221 devpriv->usec_current += elapsed_time;
222 devpriv->usec_current %= devpriv->usec_period;
223
224 if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
225 async->events |= COMEDI_CB_EOA;
226 else
227 mod_timer(&devpriv->timer, jiffies + 1);
228
229 comedi_handle_events(dev, s);
230 }
231
232 static int waveform_ai_cmdtest(struct comedi_device *dev,
233 struct comedi_subdevice *s,
234 struct comedi_cmd *cmd)
235 {
236 int err = 0;
237 unsigned int arg;
238
239 /* Step 1 : check if triggers are trivially valid */
240
241 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
242 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
243 err |= comedi_check_trigger_src(&cmd->convert_src,
244 TRIG_NOW | TRIG_TIMER);
245 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
246 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
247
248 if (err)
249 return 1;
250
251 /* Step 2a : make sure trigger sources are unique */
252
253 err |= comedi_check_trigger_is_unique(cmd->convert_src);
254 err |= comedi_check_trigger_is_unique(cmd->stop_src);
255
256 /* Step 2b : and mutually compatible */
257
258 if (err)
259 return 2;
260
261 /* Step 3: check if arguments are trivially valid */
262
263 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
264
265 if (cmd->convert_src == TRIG_NOW)
266 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
267
268 if (cmd->scan_begin_src == TRIG_TIMER) {
269 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
270 NSEC_PER_USEC);
271 if (cmd->convert_src == TRIG_TIMER) {
272 err |= comedi_check_trigger_arg_min(&cmd->
273 scan_begin_arg,
274 cmd->convert_arg *
275 cmd->chanlist_len);
276 }
277 }
278
279 err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
280 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
281 cmd->chanlist_len);
282
283 if (cmd->stop_src == TRIG_COUNT)
284 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
285 else /* TRIG_NONE */
286 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
287
288 if (err)
289 return 3;
290
291 /* step 4: fix up any arguments */
292
293 if (cmd->scan_begin_src == TRIG_TIMER) {
294 arg = cmd->scan_begin_arg;
295 /* round to nearest microsec */
296 arg = NSEC_PER_USEC *
297 ((arg + (NSEC_PER_USEC / 2)) / NSEC_PER_USEC);
298 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
299 }
300 if (cmd->convert_src == TRIG_TIMER) {
301 arg = cmd->convert_arg;
302 /* round to nearest microsec */
303 arg = NSEC_PER_USEC *
304 ((arg + (NSEC_PER_USEC / 2)) / NSEC_PER_USEC);
305 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
306 }
307
308 if (err)
309 return 4;
310
311 return 0;
312 }
313
314 static int waveform_ai_cmd(struct comedi_device *dev,
315 struct comedi_subdevice *s)
316 {
317 struct waveform_private *devpriv = dev->private;
318 struct comedi_cmd *cmd = &s->async->cmd;
319
320 if (cmd->flags & CMDF_PRIORITY) {
321 dev_err(dev->class_dev,
322 "commands at RT priority not supported in this driver\n");
323 return -1;
324 }
325
326 devpriv->scan_period = cmd->scan_begin_arg / NSEC_PER_USEC;
327
328 if (cmd->convert_src == TRIG_NOW)
329 devpriv->convert_period = 0;
330 else /* TRIG_TIMER */
331 devpriv->convert_period = cmd->convert_arg / NSEC_PER_USEC;
332
333 devpriv->last = ktime_get();
334 devpriv->usec_current =
335 ((u32)ktime_to_us(devpriv->last)) % devpriv->usec_period;
336 devpriv->usec_remainder = 0;
337
338 devpriv->timer.expires = jiffies + 1;
339 /* mark command as active */
340 smp_mb__before_atomic();
341 set_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
342 smp_mb__after_atomic();
343 add_timer(&devpriv->timer);
344 return 0;
345 }
346
347 static int waveform_ai_cancel(struct comedi_device *dev,
348 struct comedi_subdevice *s)
349 {
350 struct waveform_private *devpriv = dev->private;
351
352 /* mark command as no longer active */
353 clear_bit(WAVEFORM_AI_RUNNING, &devpriv->state_bits);
354 smp_mb__after_atomic();
355 /* cannot call del_timer_sync() as may be called from timer routine */
356 del_timer(&devpriv->timer);
357 return 0;
358 }
359
360 static int waveform_ai_insn_read(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 data[i] = devpriv->ao_loopbacks[chan];
369
370 return insn->n;
371 }
372
373 static int waveform_ao_insn_write(struct comedi_device *dev,
374 struct comedi_subdevice *s,
375 struct comedi_insn *insn, unsigned int *data)
376 {
377 struct waveform_private *devpriv = dev->private;
378 int i, chan = CR_CHAN(insn->chanspec);
379
380 for (i = 0; i < insn->n; i++)
381 devpriv->ao_loopbacks[chan] = data[i];
382
383 return insn->n;
384 }
385
386 static int waveform_attach(struct comedi_device *dev,
387 struct comedi_devconfig *it)
388 {
389 struct waveform_private *devpriv;
390 struct comedi_subdevice *s;
391 int amplitude = it->options[0];
392 int period = it->options[1];
393 int i;
394 int ret;
395
396 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
397 if (!devpriv)
398 return -ENOMEM;
399
400 /* set default amplitude and period */
401 if (amplitude <= 0)
402 amplitude = 1000000; /* 1 volt */
403 if (period <= 0)
404 period = 100000; /* 0.1 sec */
405
406 devpriv->uvolt_amplitude = amplitude;
407 devpriv->usec_period = period;
408
409 ret = comedi_alloc_subdevices(dev, 2);
410 if (ret)
411 return ret;
412
413 s = &dev->subdevices[0];
414 dev->read_subdev = s;
415 /* analog input subdevice */
416 s->type = COMEDI_SUBD_AI;
417 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
418 s->n_chan = N_CHANS;
419 s->maxdata = 0xffff;
420 s->range_table = &waveform_ai_ranges;
421 s->len_chanlist = s->n_chan * 2;
422 s->insn_read = waveform_ai_insn_read;
423 s->do_cmd = waveform_ai_cmd;
424 s->do_cmdtest = waveform_ai_cmdtest;
425 s->cancel = waveform_ai_cancel;
426
427 s = &dev->subdevices[1];
428 dev->write_subdev = s;
429 /* analog output subdevice (loopback) */
430 s->type = COMEDI_SUBD_AO;
431 s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
432 s->n_chan = N_CHANS;
433 s->maxdata = 0xffff;
434 s->range_table = &waveform_ai_ranges;
435 s->insn_write = waveform_ao_insn_write;
436
437 /* Our default loopback value is just a 0V flatline */
438 for (i = 0; i < s->n_chan; i++)
439 devpriv->ao_loopbacks[i] = s->maxdata / 2;
440
441 setup_timer(&devpriv->timer, waveform_ai_interrupt,
442 (unsigned long)dev);
443
444 dev_info(dev->class_dev,
445 "%s: %i microvolt, %li microsecond waveform attached\n",
446 dev->board_name,
447 devpriv->uvolt_amplitude, devpriv->usec_period);
448
449 return 0;
450 }
451
452 static void waveform_detach(struct comedi_device *dev)
453 {
454 struct waveform_private *devpriv = dev->private;
455
456 if (devpriv)
457 del_timer_sync(&devpriv->timer);
458 }
459
460 static struct comedi_driver waveform_driver = {
461 .driver_name = "comedi_test",
462 .module = THIS_MODULE,
463 .attach = waveform_attach,
464 .detach = waveform_detach,
465 };
466 module_comedi_driver(waveform_driver);
467
468 MODULE_AUTHOR("Comedi http://www.comedi.org");
469 MODULE_DESCRIPTION("Comedi low-level driver");
470 MODULE_LICENSE("GPL");
This page took 0.040625 seconds and 4 git commands to generate.