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