]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blob - drivers/staging/comedi/drivers/serial2002.c
Staging: comedi: remove comedi-specific wrappers
[mirror_ubuntu-hirsute-kernel.git] / drivers / staging / comedi / drivers / serial2002.c
1 /*
2 comedi/drivers/serial2002.c
3 Skeleton code for a Comedi driver
4
5 COMEDI - Linux Control and Measurement Device Interface
6 Copyright (C) 2002 Anders Blomdell <anders.blomdell@control.lth.se>
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 */
23
24 /*
25 Driver: serial2002
26 Description: Driver for serial connected hardware
27 Devices:
28 Author: Anders Blomdell
29 Updated: Fri, 7 Jun 2002 12:56:45 -0700
30 Status: in development
31
32 */
33
34 #include "../comedidev.h"
35
36 #include <linux/delay.h>
37 #include <linux/ioport.h>
38
39 #include <asm/termios.h>
40 #include <asm/ioctls.h>
41 #include <linux/serial.h>
42 #include <linux/poll.h>
43
44 /*
45 * Board descriptions for two imaginary boards. Describing the
46 * boards in this way is optional, and completely driver-dependent.
47 * Some drivers use arrays such as this, other do not.
48 */
49 struct serial2002_board {
50 const char *name;
51 };
52
53 static const struct serial2002_board serial2002_boards[] = {
54 {
55 .name = "serial2002"}
56 };
57
58 /*
59 * Useful for shorthand access to the particular board structure
60 */
61 #define thisboard ((const struct serial2002_board *)dev->board_ptr)
62
63 struct serial2002_range_table_t {
64
65 /* HACK... */
66 int length;
67 struct comedi_krange range;
68 };
69
70
71 struct serial2002_private {
72
73 int port; /* /dev/ttyS<port> */
74 int speed; /* baudrate */
75 struct file *tty;
76 unsigned int ao_readback[32];
77 unsigned char digital_in_mapping[32];
78 unsigned char digital_out_mapping[32];
79 unsigned char analog_in_mapping[32];
80 unsigned char analog_out_mapping[32];
81 unsigned char encoder_in_mapping[32];
82 struct serial2002_range_table_t in_range[32], out_range[32];
83 };
84
85
86 /*
87 * most drivers define the following macro to make it easy to
88 * access the private structure.
89 */
90 #define devpriv ((struct serial2002_private *)dev->private)
91
92 static int serial2002_attach(struct comedi_device *dev, struct comedi_devconfig *it);
93 static int serial2002_detach(struct comedi_device *dev);
94 struct comedi_driver driver_serial2002 = {
95 .driver_name = "serial2002",
96 .module = THIS_MODULE,
97 .attach = serial2002_attach,
98 .detach = serial2002_detach,
99 .board_name = &serial2002_boards[0].name,
100 .offset = sizeof(struct serial2002_board),
101 .num_names = ARRAY_SIZE(serial2002_boards),
102 };
103
104 static int serial2002_di_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
105 struct comedi_insn *insn, unsigned int *data);
106 static int serial2002_do_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
107 struct comedi_insn *insn, unsigned int *data);
108 static int serial2002_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
109 struct comedi_insn *insn, unsigned int *data);
110 static int serial2002_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
111 struct comedi_insn *insn, unsigned int *data);
112 static int serial2002_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
113 struct comedi_insn *insn, unsigned int *data);
114
115 struct serial_data {
116 enum { is_invalid, is_digital, is_channel } kind;
117 int index;
118 unsigned long value;
119 };
120
121 static long tty_ioctl(struct file *f, unsigned op, unsigned long param)
122 {
123 #ifdef HAVE_UNLOCKED_IOCTL
124 if (f->f_op->unlocked_ioctl) {
125 return f->f_op->unlocked_ioctl(f, op, param);
126 }
127 #endif
128 if (f->f_op->ioctl) {
129 return f->f_op->ioctl(f->f_dentry->d_inode, f, op, param);
130 }
131 return -ENOSYS;
132 }
133
134 static int tty_write(struct file *f, unsigned char *buf, int count)
135 {
136 int result;
137 mm_segment_t oldfs;
138
139 oldfs = get_fs();
140 set_fs(KERNEL_DS);
141 f->f_pos = 0;
142 result = f->f_op->write(f, buf, count, &f->f_pos);
143 set_fs(oldfs);
144 return result;
145 }
146
147 #if 0
148 /*
149 * On 2.6.26.3 this occaisonally gave me page faults, worked around by
150 * settings.c_cc[VMIN] = 0; settings.c_cc[VTIME] = 0
151 */
152 static int tty_available(struct file *f)
153 {
154 long result = 0;
155 mm_segment_t oldfs;
156
157 oldfs = get_fs();
158 set_fs(KERNEL_DS);
159 tty_ioctl(f, FIONREAD, (unsigned long)&result);
160 set_fs(oldfs);
161 return result;
162 }
163 #endif
164
165 static int tty_read(struct file *f, int timeout)
166 {
167 int result;
168
169 result = -1;
170 if (!IS_ERR(f)) {
171 mm_segment_t oldfs;
172
173 oldfs = get_fs();
174 set_fs(KERNEL_DS);
175 if (f->f_op->poll) {
176 struct poll_wqueues table;
177 struct timeval start, now;
178
179 do_gettimeofday(&start);
180 poll_initwait(&table);
181 while (1) {
182 long elapsed;
183 int mask;
184
185 mask = f->f_op->poll(f, &table.pt);
186 if (mask & (POLLRDNORM | POLLRDBAND | POLLIN |
187 POLLHUP | POLLERR)) {
188 break;
189 }
190 do_gettimeofday(&now);
191 elapsed =
192 (1000000 * (now.tv_sec - start.tv_sec) +
193 now.tv_usec - start.tv_usec);
194 if (elapsed > timeout) {
195 break;
196 }
197 set_current_state(TASK_INTERRUPTIBLE);
198 schedule_timeout(((timeout -
199 elapsed) * HZ) / 10000);
200 }
201 poll_freewait(&table);
202 {
203 unsigned char ch;
204
205 f->f_pos = 0;
206 if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) {
207 result = ch;
208 }
209 }
210 } else {
211 /* Device does not support poll, busy wait */
212 int retries = 0;
213 while (1) {
214 unsigned char ch;
215
216 retries++;
217 if (retries >= timeout) {
218 break;
219 }
220
221 f->f_pos = 0;
222 if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) {
223 result = ch;
224 break;
225 }
226 udelay(100);
227 }
228 }
229 set_fs(oldfs);
230 }
231 return result;
232 }
233
234 static void tty_setspeed(struct file *f, int speed)
235 {
236 mm_segment_t oldfs;
237
238 oldfs = get_fs();
239 set_fs(KERNEL_DS);
240 {
241 /* Set speed */
242 struct termios settings;
243
244 tty_ioctl(f, TCGETS, (unsigned long)&settings);
245 /* printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); */
246 settings.c_iflag = 0;
247 settings.c_oflag = 0;
248 settings.c_lflag = 0;
249 settings.c_cflag = CLOCAL | CS8 | CREAD;
250 settings.c_cc[VMIN] = 0;
251 settings.c_cc[VTIME] = 0;
252 switch (speed) {
253 case 2400:{
254 settings.c_cflag |= B2400;
255 }
256 break;
257 case 4800:{
258 settings.c_cflag |= B4800;
259 }
260 break;
261 case 9600:{
262 settings.c_cflag |= B9600;
263 }
264 break;
265 case 19200:{
266 settings.c_cflag |= B19200;
267 }
268 break;
269 case 38400:{
270 settings.c_cflag |= B38400;
271 }
272 break;
273 case 57600:{
274 settings.c_cflag |= B57600;
275 }
276 break;
277 case 115200:{
278 settings.c_cflag |= B115200;
279 }
280 break;
281 default:{
282 settings.c_cflag |= B9600;
283 }
284 break;
285 }
286 tty_ioctl(f, TCSETS, (unsigned long)&settings);
287 /* printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); */
288 }
289 {
290 /* Set low latency */
291 struct serial_struct settings;
292
293 tty_ioctl(f, TIOCGSERIAL, (unsigned long)&settings);
294 settings.flags |= ASYNC_LOW_LATENCY;
295 tty_ioctl(f, TIOCSSERIAL, (unsigned long)&settings);
296 }
297
298 set_fs(oldfs);
299 }
300
301 static void poll_digital(struct file *f, int channel)
302 {
303 char cmd;
304
305 cmd = 0x40 | (channel & 0x1f);
306 tty_write(f, &cmd, 1);
307 }
308
309 static void poll_channel(struct file *f, int channel)
310 {
311 char cmd;
312
313 cmd = 0x60 | (channel & 0x1f);
314 tty_write(f, &cmd, 1);
315 }
316
317 static struct serial_data serial_read(struct file *f, int timeout)
318 {
319 struct serial_data result;
320 int length;
321
322 result.kind = is_invalid;
323 result.index = 0;
324 result.value = 0;
325 length = 0;
326 while (1) {
327 int data = tty_read(f, timeout);
328
329 length++;
330 if (data < 0) {
331 printk("serial2002 error\n");
332 break;
333 } else if (data & 0x80) {
334 result.value = (result.value << 7) | (data & 0x7f);
335 } else {
336 if (length == 1) {
337 switch ((data >> 5) & 0x03) {
338 case 0:{
339 result.value = 0;
340 result.kind = is_digital;
341 }
342 break;
343 case 1:{
344 result.value = 1;
345 result.kind = is_digital;
346 }
347 break;
348 }
349 } else {
350 result.value =
351 (result.
352 value << 2) | ((data & 0x60) >> 5);
353 result.kind = is_channel;
354 }
355 result.index = data & 0x1f;
356 break;
357 }
358 }
359 return result;
360
361 }
362
363 static void serial_write(struct file *f, struct serial_data data)
364 {
365 if (data.kind == is_digital) {
366 unsigned char ch =
367 ((data.value << 5) & 0x20) | (data.index & 0x1f);
368 tty_write(f, &ch, 1);
369 } else {
370 unsigned char ch[6];
371 int i = 0;
372 if (data.value >= (1L << 30)) {
373 ch[i] = 0x80 | ((data.value >> 30) & 0x03);
374 i++;
375 }
376 if (data.value >= (1L << 23)) {
377 ch[i] = 0x80 | ((data.value >> 23) & 0x7f);
378 i++;
379 }
380 if (data.value >= (1L << 16)) {
381 ch[i] = 0x80 | ((data.value >> 16) & 0x7f);
382 i++;
383 }
384 if (data.value >= (1L << 9)) {
385 ch[i] = 0x80 | ((data.value >> 9) & 0x7f);
386 i++;
387 }
388 ch[i] = 0x80 | ((data.value >> 2) & 0x7f);
389 i++;
390 ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
391 i++;
392 tty_write(f, ch, i);
393 }
394 }
395
396 static void serial_2002_open(struct comedi_device *dev)
397 {
398 char port[20];
399
400 sprintf(port, "/dev/ttyS%d", devpriv->port);
401 devpriv->tty = filp_open(port, 0, O_RDWR);
402 if (IS_ERR(devpriv->tty)) {
403 printk("serial_2002: file open error = %ld\n",
404 PTR_ERR(devpriv->tty));
405 } else {
406 struct config_t {
407
408 int kind;
409 int bits;
410 int min;
411 int max;
412 };
413
414 struct config_t dig_in_config[32];
415 struct config_t dig_out_config[32];
416 struct config_t chan_in_config[32];
417 struct config_t chan_out_config[32];
418 int i;
419
420 for (i = 0; i < 32; i++) {
421 dig_in_config[i].kind = 0;
422 dig_in_config[i].bits = 0;
423 dig_in_config[i].min = 0;
424 dig_in_config[i].max = 0;
425 dig_out_config[i].kind = 0;
426 dig_out_config[i].bits = 0;
427 dig_out_config[i].min = 0;
428 dig_out_config[i].max = 0;
429 chan_in_config[i].kind = 0;
430 chan_in_config[i].bits = 0;
431 chan_in_config[i].min = 0;
432 chan_in_config[i].max = 0;
433 chan_out_config[i].kind = 0;
434 chan_out_config[i].bits = 0;
435 chan_out_config[i].min = 0;
436 chan_out_config[i].max = 0;
437 }
438
439 tty_setspeed(devpriv->tty, devpriv->speed);
440 poll_channel(devpriv->tty, 31); /* Start reading configuration */
441 while (1) {
442 struct serial_data data;
443
444 data = serial_read(devpriv->tty, 1000);
445 if (data.kind != is_channel || data.index != 31
446 || !(data.value & 0xe0)) {
447 break;
448 } else {
449 int command, channel, kind;
450 struct config_t *cur_config = 0;
451
452 channel = data.value & 0x1f;
453 kind = (data.value >> 5) & 0x7;
454 command = (data.value >> 8) & 0x3;
455 switch (kind) {
456 case 1:{
457 cur_config = dig_in_config;
458 }
459 break;
460 case 2:{
461 cur_config = dig_out_config;
462 }
463 break;
464 case 3:{
465 cur_config = chan_in_config;
466 }
467 break;
468 case 4:{
469 cur_config = chan_out_config;
470 }
471 break;
472 case 5:{
473 cur_config = chan_in_config;
474 }
475 break;
476 }
477
478 if (cur_config) {
479 cur_config[channel].kind = kind;
480 switch (command) {
481 case 0:{
482 cur_config[channel].
483 bits =
484 (data.
485 value >> 10) &
486 0x3f;
487 }
488 break;
489 case 1:{
490 int unit, sign, min;
491 unit = (data.
492 value >> 10) &
493 0x7;
494 sign = (data.
495 value >> 13) &
496 0x1;
497 min = (data.
498 value >> 14) &
499 0xfffff;
500
501 switch (unit) {
502 case 0:{
503 min = min * 1000000;
504 }
505 break;
506 case 1:{
507 min = min * 1000;
508 }
509 break;
510 case 2:{
511 min = min * 1;
512 }
513 break;
514 }
515 if (sign) {
516 min = -min;
517 }
518 cur_config[channel].
519 min = min;
520 }
521 break;
522 case 2:{
523 int unit, sign, max;
524 unit = (data.
525 value >> 10) &
526 0x7;
527 sign = (data.
528 value >> 13) &
529 0x1;
530 max = (data.
531 value >> 14) &
532 0xfffff;
533
534 switch (unit) {
535 case 0:{
536 max = max * 1000000;
537 }
538 break;
539 case 1:{
540 max = max * 1000;
541 }
542 break;
543 case 2:{
544 max = max * 1;
545 }
546 break;
547 }
548 if (sign) {
549 max = -max;
550 }
551 cur_config[channel].
552 max = max;
553 }
554 break;
555 }
556 }
557 }
558 }
559 for (i = 0; i <= 4; i++) {
560 /* Fill in subdev data */
561 struct config_t *c;
562 unsigned char *mapping = 0;
563 struct serial2002_range_table_t *range = 0;
564 int kind = 0;
565
566 switch (i) {
567 case 0:{
568 c = dig_in_config;
569 mapping = devpriv->digital_in_mapping;
570 kind = 1;
571 }
572 break;
573 case 1:{
574 c = dig_out_config;
575 mapping = devpriv->digital_out_mapping;
576 kind = 2;
577 }
578 break;
579 case 2:{
580 c = chan_in_config;
581 mapping = devpriv->analog_in_mapping;
582 range = devpriv->in_range;
583 kind = 3;
584 }
585 break;
586 case 3:{
587 c = chan_out_config;
588 mapping = devpriv->analog_out_mapping;
589 range = devpriv->out_range;
590 kind = 4;
591 }
592 break;
593 case 4:{
594 c = chan_in_config;
595 mapping = devpriv->encoder_in_mapping;
596 range = devpriv->in_range;
597 kind = 5;
598 }
599 break;
600 default:{
601 c = 0;
602 }
603 break;
604 }
605 if (c) {
606 struct comedi_subdevice *s;
607 const struct comedi_lrange **range_table_list = NULL;
608 unsigned int *maxdata_list;
609 int j, chan;
610
611 for (chan = 0, j = 0; j < 32; j++) {
612 if (c[j].kind == kind) {
613 chan++;
614 }
615 }
616 s = &dev->subdevices[i];
617 s->n_chan = chan;
618 s->maxdata = 0;
619 if (s->maxdata_list) {
620 kfree(s->maxdata_list);
621 }
622 s->maxdata_list = maxdata_list =
623 kmalloc(sizeof(unsigned int) * s->n_chan,
624 GFP_KERNEL);
625 if (s->range_table_list) {
626 kfree(s->range_table_list);
627 }
628 if (range) {
629 s->range_table = 0;
630 s->range_table_list = range_table_list =
631 kmalloc(sizeof
632 (struct serial2002_range_table_t) *
633 s->n_chan, GFP_KERNEL);
634 }
635 for (chan = 0, j = 0; j < 32; j++) {
636 if (c[j].kind == kind) {
637 if (mapping) {
638 mapping[chan] = j;
639 }
640 if (range) {
641 range[j].length = 1;
642 range[j].range.min =
643 c[j].min;
644 range[j].range.max =
645 c[j].max;
646 range_table_list[chan] =
647 (const struct
648 comedi_lrange *)
649 &range[j];
650 }
651 maxdata_list[chan] =
652 ((long long)1 << c[j].
653 bits) - 1;
654 chan++;
655 }
656 }
657 }
658 }
659 }
660 }
661
662 static void serial_2002_close(struct comedi_device *dev)
663 {
664 if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) {
665 filp_close(devpriv->tty, 0);
666 }
667 }
668
669 static int serial2002_di_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
670 struct comedi_insn *insn, unsigned int *data)
671 {
672 int n;
673 int chan;
674
675 chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
676 for (n = 0; n < insn->n; n++) {
677 struct serial_data read;
678
679 poll_digital(devpriv->tty, chan);
680 while (1) {
681 read = serial_read(devpriv->tty, 1000);
682 if (read.kind != is_digital || read.index == chan) {
683 break;
684 }
685 }
686 data[n] = read.value;
687 }
688 return n;
689 }
690
691 static int serial2002_do_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
692 struct comedi_insn *insn, unsigned int *data)
693 {
694 int n;
695 int chan;
696
697 chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)];
698 for (n = 0; n < insn->n; n++) {
699 struct serial_data write;
700
701 write.kind = is_digital;
702 write.index = chan;
703 write.value = data[n];
704 serial_write(devpriv->tty, write);
705 }
706 return n;
707 }
708
709 static int serial2002_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
710 struct comedi_insn *insn, unsigned int *data)
711 {
712 int n;
713 int chan;
714
715 chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
716 for (n = 0; n < insn->n; n++) {
717 struct serial_data read;
718
719 poll_channel(devpriv->tty, chan);
720 while (1) {
721 read = serial_read(devpriv->tty, 1000);
722 if (read.kind != is_channel || read.index == chan) {
723 break;
724 }
725 }
726 data[n] = read.value;
727 }
728 return n;
729 }
730
731 static int serial2002_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
732 struct comedi_insn *insn, unsigned int *data)
733 {
734 int n;
735 int chan;
736
737 chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)];
738 for (n = 0; n < insn->n; n++) {
739 struct serial_data write;
740
741 write.kind = is_channel;
742 write.index = chan;
743 write.value = data[n];
744 serial_write(devpriv->tty, write);
745 devpriv->ao_readback[chan] = data[n];
746 }
747 return n;
748 }
749
750 static int serial2002_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
751 struct comedi_insn *insn, unsigned int *data)
752 {
753 int n;
754 int chan = CR_CHAN(insn->chanspec);
755
756 for (n = 0; n < insn->n; n++) {
757 data[n] = devpriv->ao_readback[chan];
758 }
759
760 return n;
761 }
762
763 static int serial2002_ei_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
764 struct comedi_insn *insn, unsigned int *data)
765 {
766 int n;
767 int chan;
768
769 chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
770 for (n = 0; n < insn->n; n++) {
771 struct serial_data read;
772
773 poll_channel(devpriv->tty, chan);
774 while (1) {
775 read = serial_read(devpriv->tty, 1000);
776 if (read.kind != is_channel || read.index == chan) {
777 break;
778 }
779 }
780 data[n] = read.value;
781 }
782 return n;
783 }
784
785 static int serial2002_attach(struct comedi_device *dev, struct comedi_devconfig *it)
786 {
787 struct comedi_subdevice *s;
788
789 printk("comedi%d: serial2002: ", dev->minor);
790 dev->board_name = thisboard->name;
791 if (alloc_private(dev, sizeof(struct serial2002_private)) < 0) {
792 return -ENOMEM;
793 }
794 dev->open = serial_2002_open;
795 dev->close = serial_2002_close;
796 devpriv->port = it->options[0];
797 devpriv->speed = it->options[1];
798 printk("/dev/ttyS%d @ %d\n", devpriv->port, devpriv->speed);
799
800 if (alloc_subdevices(dev, 5) < 0)
801 return -ENOMEM;
802
803 /* digital input subdevice */
804 s = dev->subdevices + 0;
805 s->type = COMEDI_SUBD_DI;
806 s->subdev_flags = SDF_READABLE;
807 s->n_chan = 0;
808 s->maxdata = 1;
809 s->range_table = &range_digital;
810 s->insn_read = &serial2002_di_rinsn;
811
812 /* digital output subdevice */
813 s = dev->subdevices + 1;
814 s->type = COMEDI_SUBD_DO;
815 s->subdev_flags = SDF_WRITEABLE;
816 s->n_chan = 0;
817 s->maxdata = 1;
818 s->range_table = &range_digital;
819 s->insn_write = &serial2002_do_winsn;
820
821 /* analog input subdevice */
822 s = dev->subdevices + 2;
823 s->type = COMEDI_SUBD_AI;
824 s->subdev_flags = SDF_READABLE | SDF_GROUND;
825 s->n_chan = 0;
826 s->maxdata = 1;
827 s->range_table = 0;
828 s->insn_read = &serial2002_ai_rinsn;
829
830 /* analog output subdevice */
831 s = dev->subdevices + 3;
832 s->type = COMEDI_SUBD_AO;
833 s->subdev_flags = SDF_WRITEABLE;
834 s->n_chan = 0;
835 s->maxdata = 1;
836 s->range_table = 0;
837 s->insn_write = &serial2002_ao_winsn;
838 s->insn_read = &serial2002_ao_rinsn;
839
840 /* encoder input subdevice */
841 s = dev->subdevices + 4;
842 s->type = COMEDI_SUBD_COUNTER;
843 s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
844 s->n_chan = 0;
845 s->maxdata = 1;
846 s->range_table = 0;
847 s->insn_read = &serial2002_ei_rinsn;
848
849 return 1;
850 }
851
852 static int serial2002_detach(struct comedi_device *dev)
853 {
854 struct comedi_subdevice *s;
855 int i;
856
857 printk("comedi%d: serial2002: remove\n", dev->minor);
858 for (i = 0; i < 4; i++) {
859 s = &dev->subdevices[i];
860 if (s->maxdata_list) {
861 kfree(s->maxdata_list);
862 }
863 if (s->range_table_list) {
864 kfree(s->range_table_list);
865 }
866 }
867 return 0;
868 }
869
870 COMEDI_INITCLEANUP(driver_serial2002);