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