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