4d4358ce171078c70234b7229cdbeae84fae6ebd
[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 Driver: comedi_test
27 Description: generates fake waveforms
28 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
29 <fmhess@users.sourceforge.net>, ds
30 Devices:
31 Status: works
32 Updated: Sat, 16 Mar 2002 17:34:48 -0800
33
34 This driver is mainly for testing purposes, but can also be used to
35 generate sample waveforms on systems that don't have data acquisition
36 hardware.
37
38 Configuration 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
42 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
43 waveforms could be added to other channels (currently they return flatline
44 zero 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 #include <linux/ktime.h>
56
57 #define N_CHANS 8
58
59 /* Data unique to this driver */
60 struct waveform_private {
61 struct timer_list timer;
62 ktime_t last; /* time last timer interrupt occurred */
63 unsigned int uvolt_amplitude; /* waveform amplitude in microvolts */
64 unsigned long usec_period; /* waveform period in microseconds */
65 unsigned long usec_current; /* current time (mod waveform period) */
66 unsigned long usec_remainder; /* usec since last scan */
67 unsigned long ai_count; /* number of conversions remaining */
68 unsigned int scan_period; /* scan period in usec */
69 unsigned int convert_period; /* conversion period in usec */
70 unsigned int ao_loopbacks[N_CHANS];
71 };
72
73 /* 1000 nanosec in a microsec */
74 static const int nano_per_micro = 1000;
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 -= binary_amplitude; /* get rid of sawtooth's dc offset */
105
106 return offset + value;
107 }
108
109 static 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
131 static 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 */
139 static 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 case SQUARE_CHAN:
151 return fake_squarewave(dev, range, current_time);
152 default:
153 break;
154 }
155
156 return fake_flatline(dev, range, current_time);
157 }
158
159 /*
160 This is the background routine used to generate arbitrary data.
161 It should run in the background; therefore it is scheduled by
162 a timer mechanism.
163 */
164 static void waveform_ai_interrupt(unsigned long arg)
165 {
166 struct comedi_device *dev = (struct comedi_device *)arg;
167 struct waveform_private *devpriv = dev->private;
168 struct comedi_subdevice *s = dev->read_subdev;
169 struct comedi_async *async = s->async;
170 struct comedi_cmd *cmd = &async->cmd;
171 unsigned int i, j;
172 /* all times in microsec */
173 unsigned long elapsed_time;
174 unsigned int num_scans;
175 ktime_t now;
176 bool stopping = false;
177
178 now = ktime_get();
179
180 elapsed_time = ktime_to_us(ktime_sub(now, devpriv->last));
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(s, 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_handle_events(dev, s);
220 }
221
222 static 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
299 static 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 devpriv->last = ktime_get();
320 devpriv->usec_current =
321 ((u32)ktime_to_us(devpriv->last)) % devpriv->usec_period;
322 devpriv->usec_remainder = 0;
323
324 devpriv->timer.expires = jiffies + 1;
325 add_timer(&devpriv->timer);
326 return 0;
327 }
328
329 static int waveform_ai_cancel(struct comedi_device *dev,
330 struct comedi_subdevice *s)
331 {
332 struct waveform_private *devpriv = dev->private;
333
334 del_timer_sync(&devpriv->timer);
335 return 0;
336 }
337
338 static int waveform_ai_insn_read(struct comedi_device *dev,
339 struct comedi_subdevice *s,
340 struct comedi_insn *insn, unsigned int *data)
341 {
342 struct waveform_private *devpriv = dev->private;
343 int i, chan = CR_CHAN(insn->chanspec);
344
345 for (i = 0; i < insn->n; i++)
346 data[i] = devpriv->ao_loopbacks[chan];
347
348 return insn->n;
349 }
350
351 static int waveform_ao_insn_write(struct comedi_device *dev,
352 struct comedi_subdevice *s,
353 struct comedi_insn *insn, unsigned int *data)
354 {
355 struct waveform_private *devpriv = dev->private;
356 int i, chan = CR_CHAN(insn->chanspec);
357
358 for (i = 0; i < insn->n; i++)
359 devpriv->ao_loopbacks[chan] = data[i];
360
361 return insn->n;
362 }
363
364 static int waveform_attach(struct comedi_device *dev,
365 struct comedi_devconfig *it)
366 {
367 struct waveform_private *devpriv;
368 struct comedi_subdevice *s;
369 int amplitude = it->options[0];
370 int period = it->options[1];
371 int i;
372 int ret;
373
374 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
375 if (!devpriv)
376 return -ENOMEM;
377
378 /* set default amplitude and period */
379 if (amplitude <= 0)
380 amplitude = 1000000; /* 1 volt */
381 if (period <= 0)
382 period = 100000; /* 0.1 sec */
383
384 devpriv->uvolt_amplitude = amplitude;
385 devpriv->usec_period = period;
386
387 ret = comedi_alloc_subdevices(dev, 2);
388 if (ret)
389 return ret;
390
391 s = &dev->subdevices[0];
392 dev->read_subdev = s;
393 /* analog input subdevice */
394 s->type = COMEDI_SUBD_AI;
395 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
396 s->n_chan = N_CHANS;
397 s->maxdata = 0xffff;
398 s->range_table = &waveform_ai_ranges;
399 s->len_chanlist = s->n_chan * 2;
400 s->insn_read = waveform_ai_insn_read;
401 s->do_cmd = waveform_ai_cmd;
402 s->do_cmdtest = waveform_ai_cmdtest;
403 s->cancel = waveform_ai_cancel;
404
405 s = &dev->subdevices[1];
406 dev->write_subdev = s;
407 /* analog output subdevice (loopback) */
408 s->type = COMEDI_SUBD_AO;
409 s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
410 s->n_chan = N_CHANS;
411 s->maxdata = 0xffff;
412 s->range_table = &waveform_ai_ranges;
413 s->insn_write = waveform_ao_insn_write;
414
415 /* Our default loopback value is just a 0V flatline */
416 for (i = 0; i < s->n_chan; i++)
417 devpriv->ao_loopbacks[i] = s->maxdata / 2;
418
419 init_timer(&devpriv->timer);
420 devpriv->timer.function = waveform_ai_interrupt;
421 devpriv->timer.data = (unsigned long)dev;
422
423 dev_info(dev->class_dev,
424 "%s: %i microvolt, %li microsecond waveform attached\n",
425 dev->board_name,
426 devpriv->uvolt_amplitude, devpriv->usec_period);
427
428 return 0;
429 }
430
431 static void waveform_detach(struct comedi_device *dev)
432 {
433 struct waveform_private *devpriv = dev->private;
434
435 if (devpriv)
436 waveform_ai_cancel(dev, dev->read_subdev);
437 }
438
439 static struct comedi_driver waveform_driver = {
440 .driver_name = "comedi_test",
441 .module = THIS_MODULE,
442 .attach = waveform_attach,
443 .detach = waveform_detach,
444 };
445 module_comedi_driver(waveform_driver);
446
447 MODULE_AUTHOR("Comedi http://www.comedi.org");
448 MODULE_DESCRIPTION("Comedi low-level driver");
449 MODULE_LICENSE("GPL");
This page took 0.069366 seconds and 5 git commands to generate.