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