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