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