serial2002.c revision 67a6efb1f86eb51e4b702c4e8e735ca24e3427d8
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	if (f->f_op->unlocked_ioctl)
129		return f->f_op->unlocked_ioctl(f, op, param);
130
131	return -ENOSYS;
132}
133
134static 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 */
152static 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
165static 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
234static 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
301static 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
309static 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
317static 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.value << 2) | ((data & 0x60) >> 5);
352				result.kind = is_channel;
353			}
354			result.index = data & 0x1f;
355			break;
356		}
357	}
358	return result;
359
360}
361
362static void serial_write(struct file *f, struct serial_data data)
363{
364	if (data.kind == is_digital) {
365		unsigned char ch =
366		    ((data.value << 5) & 0x20) | (data.index & 0x1f);
367		tty_write(f, &ch, 1);
368	} else {
369		unsigned char ch[6];
370		int i = 0;
371		if (data.value >= (1L << 30)) {
372			ch[i] = 0x80 | ((data.value >> 30) & 0x03);
373			i++;
374		}
375		if (data.value >= (1L << 23)) {
376			ch[i] = 0x80 | ((data.value >> 23) & 0x7f);
377			i++;
378		}
379		if (data.value >= (1L << 16)) {
380			ch[i] = 0x80 | ((data.value >> 16) & 0x7f);
381			i++;
382		}
383		if (data.value >= (1L << 9)) {
384			ch[i] = 0x80 | ((data.value >> 9) & 0x7f);
385			i++;
386		}
387		ch[i] = 0x80 | ((data.value >> 2) & 0x7f);
388		i++;
389		ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
390		i++;
391		tty_write(f, ch, i);
392	}
393}
394
395static void serial_2002_open(struct comedi_device *dev)
396{
397	char port[20];
398
399	sprintf(port, "/dev/ttyS%d", devpriv->port);
400	devpriv->tty = filp_open(port, 0, O_RDWR);
401	if (IS_ERR(devpriv->tty)) {
402		printk("serial_2002: file open error = %ld\n",
403		       PTR_ERR(devpriv->tty));
404	} else {
405		struct config_t {
406
407			short int kind;
408			short int bits;
409			int min;
410			int max;
411		};
412
413		struct config_t dig_in_config[32];
414		struct config_t dig_out_config[32];
415		struct config_t chan_in_config[32];
416		struct config_t chan_out_config[32];
417		int i;
418
419		for (i = 0; i < 32; i++) {
420			dig_in_config[i].kind = 0;
421			dig_in_config[i].bits = 0;
422			dig_in_config[i].min = 0;
423			dig_in_config[i].max = 0;
424			dig_out_config[i].kind = 0;
425			dig_out_config[i].bits = 0;
426			dig_out_config[i].min = 0;
427			dig_out_config[i].max = 0;
428			chan_in_config[i].kind = 0;
429			chan_in_config[i].bits = 0;
430			chan_in_config[i].min = 0;
431			chan_in_config[i].max = 0;
432			chan_out_config[i].kind = 0;
433			chan_out_config[i].bits = 0;
434			chan_out_config[i].min = 0;
435			chan_out_config[i].max = 0;
436		}
437
438		tty_setspeed(devpriv->tty, devpriv->speed);
439		poll_channel(devpriv->tty, 31);	/*  Start reading configuration */
440		while (1) {
441			struct serial_data data;
442
443			data = serial_read(devpriv->tty, 1000);
444			if (data.kind != is_channel || data.index != 31
445			    || !(data.value & 0xe0)) {
446				break;
447			} else {
448				int command, channel, kind;
449				struct config_t *cur_config = 0;
450
451				channel = data.value & 0x1f;
452				kind = (data.value >> 5) & 0x7;
453				command = (data.value >> 8) & 0x3;
454				switch (kind) {
455				case 1:{
456						cur_config = dig_in_config;
457					}
458					break;
459				case 2:{
460						cur_config = dig_out_config;
461					}
462					break;
463				case 3:{
464						cur_config = chan_in_config;
465					}
466					break;
467				case 4:{
468						cur_config = chan_out_config;
469					}
470					break;
471				case 5:{
472						cur_config = chan_in_config;
473					}
474					break;
475				}
476
477				if (cur_config) {
478					cur_config[channel].kind = kind;
479					switch (command) {
480					case 0:{
481							cur_config[channel].bits
482							    =
483							    (data.value >> 10) &
484							    0x3f;
485						}
486						break;
487					case 1:{
488							int unit, sign, min;
489							unit =
490							    (data.value >> 10) &
491							    0x7;
492							sign =
493							    (data.value >> 13) &
494							    0x1;
495							min =
496							    (data.value >> 14) &
497							    0xfffff;
498
499							switch (unit) {
500							case 0:{
501									min =
502									    min
503									    *
504									    1000000;
505								}
506								break;
507							case 1:{
508									min =
509									    min
510									    *
511									    1000;
512								}
513								break;
514							case 2:{
515									min =
516									    min
517									    * 1;
518								}
519								break;
520							}
521							if (sign) {
522								min = -min;
523							}
524							cur_config[channel].min
525							    = min;
526						}
527						break;
528					case 2:{
529							int unit, sign, max;
530							unit =
531							    (data.value >> 10) &
532							    0x7;
533							sign =
534							    (data.value >> 13) &
535							    0x1;
536							max =
537							    (data.value >> 14) &
538							    0xfffff;
539
540							switch (unit) {
541							case 0:{
542									max =
543									    max
544									    *
545									    1000000;
546								}
547								break;
548							case 1:{
549									max =
550									    max
551									    *
552									    1000;
553								}
554								break;
555							case 2:{
556									max =
557									    max
558									    * 1;
559								}
560								break;
561							}
562							if (sign) {
563								max = -max;
564							}
565							cur_config[channel].max
566							    = max;
567						}
568						break;
569					}
570				}
571			}
572		}
573		for (i = 0; i <= 4; i++) {
574			/*  Fill in subdev data */
575			struct config_t *c;
576			unsigned char *mapping = 0;
577			struct serial2002_range_table_t *range = 0;
578			int kind = 0;
579
580			switch (i) {
581			case 0:{
582					c = dig_in_config;
583					mapping = devpriv->digital_in_mapping;
584					kind = 1;
585				}
586				break;
587			case 1:{
588					c = dig_out_config;
589					mapping = devpriv->digital_out_mapping;
590					kind = 2;
591				}
592				break;
593			case 2:{
594					c = chan_in_config;
595					mapping = devpriv->analog_in_mapping;
596					range = devpriv->in_range;
597					kind = 3;
598				}
599				break;
600			case 3:{
601					c = chan_out_config;
602					mapping = devpriv->analog_out_mapping;
603					range = devpriv->out_range;
604					kind = 4;
605				}
606				break;
607			case 4:{
608					c = chan_in_config;
609					mapping = devpriv->encoder_in_mapping;
610					range = devpriv->in_range;
611					kind = 5;
612				}
613				break;
614			default:{
615					c = 0;
616				}
617				break;
618			}
619			if (c) {
620				struct comedi_subdevice *s;
621				const struct comedi_lrange **range_table_list =
622				    NULL;
623				unsigned int *maxdata_list;
624				int j, chan;
625
626				for (chan = 0, j = 0; j < 32; j++) {
627					if (c[j].kind == kind) {
628						chan++;
629					}
630				}
631				s = &dev->subdevices[i];
632				s->n_chan = chan;
633				s->maxdata = 0;
634				if (s->maxdata_list) {
635					kfree(s->maxdata_list);
636				}
637				s->maxdata_list = maxdata_list =
638				    kmalloc(sizeof(unsigned int) * s->n_chan,
639					    GFP_KERNEL);
640				if (s->range_table_list) {
641					kfree(s->range_table_list);
642				}
643				if (range) {
644					s->range_table = 0;
645					s->range_table_list = range_table_list =
646					    kmalloc(sizeof
647						    (struct
648						     serial2002_range_table_t) *
649						    s->n_chan, GFP_KERNEL);
650				}
651				for (chan = 0, j = 0; j < 32; j++) {
652					if (c[j].kind == kind) {
653						if (mapping) {
654							mapping[chan] = j;
655						}
656						if (range) {
657							range[j].length = 1;
658							range[j].range.min =
659							    c[j].min;
660							range[j].range.max =
661							    c[j].max;
662							range_table_list[chan] =
663							    (const struct
664							     comedi_lrange *)
665							    &range[j];
666						}
667						maxdata_list[chan] =
668						    ((long long)1 << c[j].bits)
669						    - 1;
670						chan++;
671					}
672				}
673			}
674		}
675	}
676}
677
678static void serial_2002_close(struct comedi_device *dev)
679{
680	if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) {
681		filp_close(devpriv->tty, 0);
682	}
683}
684
685static int serial2002_di_rinsn(struct comedi_device *dev,
686			       struct comedi_subdevice *s,
687			       struct comedi_insn *insn, unsigned int *data)
688{
689	int n;
690	int chan;
691
692	chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
693	for (n = 0; n < insn->n; n++) {
694		struct serial_data read;
695
696		poll_digital(devpriv->tty, chan);
697		while (1) {
698			read = serial_read(devpriv->tty, 1000);
699			if (read.kind != is_digital || read.index == chan) {
700				break;
701			}
702		}
703		data[n] = read.value;
704	}
705	return n;
706}
707
708static int serial2002_do_winsn(struct comedi_device *dev,
709			       struct comedi_subdevice *s,
710			       struct comedi_insn *insn, unsigned int *data)
711{
712	int n;
713	int chan;
714
715	chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)];
716	for (n = 0; n < insn->n; n++) {
717		struct serial_data write;
718
719		write.kind = is_digital;
720		write.index = chan;
721		write.value = data[n];
722		serial_write(devpriv->tty, write);
723	}
724	return n;
725}
726
727static int serial2002_ai_rinsn(struct comedi_device *dev,
728			       struct comedi_subdevice *s,
729			       struct comedi_insn *insn, unsigned int *data)
730{
731	int n;
732	int chan;
733
734	chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
735	for (n = 0; n < insn->n; n++) {
736		struct serial_data read;
737
738		poll_channel(devpriv->tty, chan);
739		while (1) {
740			read = serial_read(devpriv->tty, 1000);
741			if (read.kind != is_channel || read.index == chan) {
742				break;
743			}
744		}
745		data[n] = read.value;
746	}
747	return n;
748}
749
750static int serial2002_ao_winsn(struct comedi_device *dev,
751			       struct comedi_subdevice *s,
752			       struct comedi_insn *insn, unsigned int *data)
753{
754	int n;
755	int chan;
756
757	chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)];
758	for (n = 0; n < insn->n; n++) {
759		struct serial_data write;
760
761		write.kind = is_channel;
762		write.index = chan;
763		write.value = data[n];
764		serial_write(devpriv->tty, write);
765		devpriv->ao_readback[chan] = data[n];
766	}
767	return n;
768}
769
770static int serial2002_ao_rinsn(struct comedi_device *dev,
771			       struct comedi_subdevice *s,
772			       struct comedi_insn *insn, unsigned int *data)
773{
774	int n;
775	int chan = CR_CHAN(insn->chanspec);
776
777	for (n = 0; n < insn->n; n++) {
778		data[n] = devpriv->ao_readback[chan];
779	}
780
781	return n;
782}
783
784static int serial2002_ei_rinsn(struct comedi_device *dev,
785			       struct comedi_subdevice *s,
786			       struct comedi_insn *insn, unsigned int *data)
787{
788	int n;
789	int chan;
790
791	chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
792	for (n = 0; n < insn->n; n++) {
793		struct serial_data read;
794
795		poll_channel(devpriv->tty, chan);
796		while (1) {
797			read = serial_read(devpriv->tty, 1000);
798			if (read.kind != is_channel || read.index == chan) {
799				break;
800			}
801		}
802		data[n] = read.value;
803	}
804	return n;
805}
806
807static int serial2002_attach(struct comedi_device *dev,
808			     struct comedi_devconfig *it)
809{
810	struct comedi_subdevice *s;
811
812	printk("comedi%d: serial2002: ", dev->minor);
813	dev->board_name = thisboard->name;
814	if (alloc_private(dev, sizeof(struct serial2002_private)) < 0) {
815		return -ENOMEM;
816	}
817	dev->open = serial_2002_open;
818	dev->close = serial_2002_close;
819	devpriv->port = it->options[0];
820	devpriv->speed = it->options[1];
821	printk("/dev/ttyS%d @ %d\n", devpriv->port, devpriv->speed);
822
823	if (alloc_subdevices(dev, 5) < 0)
824		return -ENOMEM;
825
826	/* digital input subdevice */
827	s = dev->subdevices + 0;
828	s->type = COMEDI_SUBD_DI;
829	s->subdev_flags = SDF_READABLE;
830	s->n_chan = 0;
831	s->maxdata = 1;
832	s->range_table = &range_digital;
833	s->insn_read = &serial2002_di_rinsn;
834
835	/* digital output subdevice */
836	s = dev->subdevices + 1;
837	s->type = COMEDI_SUBD_DO;
838	s->subdev_flags = SDF_WRITEABLE;
839	s->n_chan = 0;
840	s->maxdata = 1;
841	s->range_table = &range_digital;
842	s->insn_write = &serial2002_do_winsn;
843
844	/* analog input subdevice */
845	s = dev->subdevices + 2;
846	s->type = COMEDI_SUBD_AI;
847	s->subdev_flags = SDF_READABLE | SDF_GROUND;
848	s->n_chan = 0;
849	s->maxdata = 1;
850	s->range_table = 0;
851	s->insn_read = &serial2002_ai_rinsn;
852
853	/* analog output subdevice */
854	s = dev->subdevices + 3;
855	s->type = COMEDI_SUBD_AO;
856	s->subdev_flags = SDF_WRITEABLE;
857	s->n_chan = 0;
858	s->maxdata = 1;
859	s->range_table = 0;
860	s->insn_write = &serial2002_ao_winsn;
861	s->insn_read = &serial2002_ao_rinsn;
862
863	/* encoder input subdevice */
864	s = dev->subdevices + 4;
865	s->type = COMEDI_SUBD_COUNTER;
866	s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
867	s->n_chan = 0;
868	s->maxdata = 1;
869	s->range_table = 0;
870	s->insn_read = &serial2002_ei_rinsn;
871
872	return 1;
873}
874
875static int serial2002_detach(struct comedi_device *dev)
876{
877	struct comedi_subdevice *s;
878	int i;
879
880	printk("comedi%d: serial2002: remove\n", dev->minor);
881	for (i = 0; i < 4; i++) {
882		s = &dev->subdevices[i];
883		if (s->maxdata_list) {
884			kfree(s->maxdata_list);
885		}
886		if (s->range_table_list) {
887			kfree(s->range_table_list);
888		}
889	}
890	return 0;
891}
892
893COMEDI_INITCLEANUP(driver_serial2002);
894