]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/blob - drivers/staging/comedi/drivers/serial2002.c
Merge tag 'tegra-for-4.3-cleanup' of git://git.kernel.org/pub/scm/linux/kernel/git...
[mirror_ubuntu-eoan-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
19 /*
20 Driver: serial2002
21 Description: Driver for serial connected hardware
22 Devices:
23 Author: Anders Blomdell
24 Updated: Fri, 7 Jun 2002 12:56:45 -0700
25 Status: in development
26
27 */
28
29 #include <linux/module.h>
30 #include "../comedidev.h"
31
32 #include <linux/delay.h>
33 #include <linux/sched.h>
34 #include <linux/slab.h>
35
36 #include <linux/termios.h>
37 #include <asm/ioctls.h>
38 #include <linux/serial.h>
39 #include <linux/poll.h>
40
41 struct serial2002_range_table_t {
42 /* HACK... */
43 int length;
44 struct comedi_krange range;
45 };
46
47 struct serial2002_private {
48 int port; /* /dev/ttyS<port> */
49 int speed; /* baudrate */
50 struct file *tty;
51 unsigned int ao_readback[32];
52 unsigned char digital_in_mapping[32];
53 unsigned char digital_out_mapping[32];
54 unsigned char analog_in_mapping[32];
55 unsigned char analog_out_mapping[32];
56 unsigned char encoder_in_mapping[32];
57 struct serial2002_range_table_t in_range[32], out_range[32];
58 };
59
60 struct serial_data {
61 enum { is_invalid, is_digital, is_channel } kind;
62 int index;
63 unsigned long value;
64 };
65
66 /*
67 * The configuration serial_data.value read from the device is
68 * a bitmask that defines specific options of a channel:
69 *
70 * 4:0 - the channel to configure
71 * 7:5 - the kind of channel
72 * 9:8 - the command used to configure the channel
73 *
74 * The remaining bits vary in use depending on the command:
75 *
76 * BITS 15:10 - the channel bits (maxdata)
77 * MIN/MAX 12:10 - the units multiplier for the scale
78 * 13 - the sign of the scale
79 * 33:14 - the base value for the range
80 */
81 #define S2002_CFG_CHAN(x) ((x) & 0x1f)
82 #define S2002_CFG_KIND(x) (((x) >> 5) & 0x7)
83 #define S2002_CFG_KIND_INVALID 0
84 #define S2002_CFG_KIND_DIGITAL_IN 1
85 #define S2002_CFG_KIND_DIGITAL_OUT 2
86 #define S2002_CFG_KIND_ANALOG_IN 3
87 #define S2002_CFG_KIND_ANALOG_OUT 4
88 #define S2002_CFG_KIND_ENCODER_IN 5
89 #define S2002_CFG_CMD(x) (((x) >> 8) & 0x3)
90 #define S2002_CFG_CMD_BITS 0
91 #define S2002_CFG_CMD_MIN 1
92 #define S2002_CFG_CMD_MAX 2
93 #define S2002_CFG_BITS(x) (((x) >> 10) & 0x3f)
94 #define S2002_CFG_UNITS(x) (((x) >> 10) & 0x7)
95 #define S2002_CFG_SIGN(x) (((x) >> 13) & 0x1)
96 #define S2002_CFG_BASE(x) (((x) >> 14) & 0xfffff)
97
98 static long serial2002_tty_ioctl(struct file *f, unsigned op,
99 unsigned long param)
100 {
101 if (f->f_op->unlocked_ioctl)
102 return f->f_op->unlocked_ioctl(f, op, param);
103
104 return -ENOSYS;
105 }
106
107 static int serial2002_tty_write(struct file *f, unsigned char *buf, int count)
108 {
109 const char __user *p = (__force const char __user *)buf;
110 int result;
111 loff_t offset = 0;
112 mm_segment_t oldfs;
113
114 oldfs = get_fs();
115 set_fs(KERNEL_DS);
116 result = __vfs_write(f, p, count, &offset);
117 set_fs(oldfs);
118 return result;
119 }
120
121 static void serial2002_tty_read_poll_wait(struct file *f, int timeout)
122 {
123 struct poll_wqueues table;
124 struct timeval start, now;
125
126 do_gettimeofday(&start);
127 poll_initwait(&table);
128 while (1) {
129 long elapsed;
130 int mask;
131
132 mask = f->f_op->poll(f, &table.pt);
133 if (mask & (POLLRDNORM | POLLRDBAND | POLLIN |
134 POLLHUP | POLLERR)) {
135 break;
136 }
137 do_gettimeofday(&now);
138 elapsed = 1000000 * (now.tv_sec - start.tv_sec) +
139 now.tv_usec - start.tv_usec;
140 if (elapsed > timeout)
141 break;
142 set_current_state(TASK_INTERRUPTIBLE);
143 schedule_timeout(((timeout - elapsed) * HZ) / 10000);
144 }
145 poll_freewait(&table);
146 }
147
148 static int serial2002_tty_read(struct file *f, int timeout)
149 {
150 unsigned char ch;
151 int result;
152
153 result = -1;
154 if (!IS_ERR(f)) {
155 mm_segment_t oldfs;
156 char __user *p = (__force char __user *)&ch;
157 loff_t offset = 0;
158
159 oldfs = get_fs();
160 set_fs(KERNEL_DS);
161 if (f->f_op->poll) {
162 serial2002_tty_read_poll_wait(f, timeout);
163
164 if (__vfs_read(f, p, 1, &offset) == 1)
165 result = ch;
166 } else {
167 /* Device does not support poll, busy wait */
168 int retries = 0;
169
170 while (1) {
171 retries++;
172 if (retries >= timeout)
173 break;
174
175 if (__vfs_read(f, p, 1, &offset) == 1) {
176 result = ch;
177 break;
178 }
179 udelay(100);
180 }
181 }
182 set_fs(oldfs);
183 }
184 return result;
185 }
186
187 static void serial2002_tty_setspeed(struct file *f, int speed)
188 {
189 struct termios termios;
190 struct serial_struct serial;
191 mm_segment_t oldfs;
192
193 oldfs = get_fs();
194 set_fs(KERNEL_DS);
195
196 /* Set speed */
197 serial2002_tty_ioctl(f, TCGETS, (unsigned long)&termios);
198 termios.c_iflag = 0;
199 termios.c_oflag = 0;
200 termios.c_lflag = 0;
201 termios.c_cflag = CLOCAL | CS8 | CREAD;
202 termios.c_cc[VMIN] = 0;
203 termios.c_cc[VTIME] = 0;
204 switch (speed) {
205 case 2400:
206 termios.c_cflag |= B2400;
207 break;
208 case 4800:
209 termios.c_cflag |= B4800;
210 break;
211 case 9600:
212 termios.c_cflag |= B9600;
213 break;
214 case 19200:
215 termios.c_cflag |= B19200;
216 break;
217 case 38400:
218 termios.c_cflag |= B38400;
219 break;
220 case 57600:
221 termios.c_cflag |= B57600;
222 break;
223 case 115200:
224 termios.c_cflag |= B115200;
225 break;
226 default:
227 termios.c_cflag |= B9600;
228 break;
229 }
230 serial2002_tty_ioctl(f, TCSETS, (unsigned long)&termios);
231
232 /* Set low latency */
233 serial2002_tty_ioctl(f, TIOCGSERIAL, (unsigned long)&serial);
234 serial.flags |= ASYNC_LOW_LATENCY;
235 serial2002_tty_ioctl(f, TIOCSSERIAL, (unsigned long)&serial);
236
237 set_fs(oldfs);
238 }
239
240 static void serial2002_poll_digital(struct file *f, int channel)
241 {
242 char cmd;
243
244 cmd = 0x40 | (channel & 0x1f);
245 serial2002_tty_write(f, &cmd, 1);
246 }
247
248 static void serial2002_poll_channel(struct file *f, int channel)
249 {
250 char cmd;
251
252 cmd = 0x60 | (channel & 0x1f);
253 serial2002_tty_write(f, &cmd, 1);
254 }
255
256 static struct serial_data serial2002_read(struct file *f, int timeout)
257 {
258 struct serial_data result;
259 int length;
260
261 result.kind = is_invalid;
262 result.index = 0;
263 result.value = 0;
264 length = 0;
265 while (1) {
266 int data = serial2002_tty_read(f, timeout);
267
268 length++;
269 if (data < 0) {
270 break;
271 } else if (data & 0x80) {
272 result.value = (result.value << 7) | (data & 0x7f);
273 } else {
274 if (length == 1) {
275 switch ((data >> 5) & 0x03) {
276 case 0:
277 result.value = 0;
278 result.kind = is_digital;
279 break;
280 case 1:
281 result.value = 1;
282 result.kind = is_digital;
283 break;
284 }
285 } else {
286 result.value =
287 (result.value << 2) | ((data & 0x60) >> 5);
288 result.kind = is_channel;
289 }
290 result.index = data & 0x1f;
291 break;
292 }
293 }
294 return result;
295 }
296
297 static void serial2002_write(struct file *f, struct serial_data data)
298 {
299 if (data.kind == is_digital) {
300 unsigned char ch =
301 ((data.value << 5) & 0x20) | (data.index & 0x1f);
302 serial2002_tty_write(f, &ch, 1);
303 } else {
304 unsigned char ch[6];
305 int i = 0;
306
307 if (data.value >= (1L << 30)) {
308 ch[i] = 0x80 | ((data.value >> 30) & 0x03);
309 i++;
310 }
311 if (data.value >= (1L << 23)) {
312 ch[i] = 0x80 | ((data.value >> 23) & 0x7f);
313 i++;
314 }
315 if (data.value >= (1L << 16)) {
316 ch[i] = 0x80 | ((data.value >> 16) & 0x7f);
317 i++;
318 }
319 if (data.value >= (1L << 9)) {
320 ch[i] = 0x80 | ((data.value >> 9) & 0x7f);
321 i++;
322 }
323 ch[i] = 0x80 | ((data.value >> 2) & 0x7f);
324 i++;
325 ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
326 i++;
327 serial2002_tty_write(f, ch, i);
328 }
329 }
330
331 struct config_t {
332 short int kind;
333 short int bits;
334 int min;
335 int max;
336 };
337
338 static int serial2002_setup_subdevice(struct comedi_subdevice *s,
339 struct config_t *cfg,
340 struct serial2002_range_table_t *range,
341 unsigned char *mapping,
342 int kind)
343 {
344 const struct comedi_lrange **range_table_list = NULL;
345 unsigned int *maxdata_list;
346 int j, chan;
347
348 for (chan = 0, j = 0; j < 32; j++) {
349 if (cfg[j].kind == kind)
350 chan++;
351 }
352 s->n_chan = chan;
353 s->maxdata = 0;
354 kfree(s->maxdata_list);
355 maxdata_list = kmalloc_array(s->n_chan, sizeof(unsigned int),
356 GFP_KERNEL);
357 if (!maxdata_list)
358 return -ENOMEM;
359 s->maxdata_list = maxdata_list;
360 kfree(s->range_table_list);
361 s->range_table = NULL;
362 s->range_table_list = NULL;
363 if (kind == 1 || kind == 2) {
364 s->range_table = &range_digital;
365 } else if (range) {
366 range_table_list = kmalloc_array(s->n_chan, sizeof(*range),
367 GFP_KERNEL);
368 if (!range_table_list)
369 return -ENOMEM;
370 s->range_table_list = range_table_list;
371 }
372 for (chan = 0, j = 0; j < 32; j++) {
373 if (cfg[j].kind == kind) {
374 if (mapping)
375 mapping[chan] = j;
376 if (range && range_table_list) {
377 range[j].length = 1;
378 range[j].range.min = cfg[j].min;
379 range[j].range.max = cfg[j].max;
380 range_table_list[chan] =
381 (const struct comedi_lrange *)&range[j];
382 }
383 maxdata_list[chan] = ((long long)1 << cfg[j].bits) - 1;
384 chan++;
385 }
386 }
387 return 0;
388 }
389
390 static int serial2002_setup_subdevs(struct comedi_device *dev)
391 {
392 struct serial2002_private *devpriv = dev->private;
393 struct config_t *di_cfg;
394 struct config_t *do_cfg;
395 struct config_t *ai_cfg;
396 struct config_t *ao_cfg;
397 struct config_t *cfg;
398 struct comedi_subdevice *s;
399 int result = 0;
400 int i;
401
402 /* Allocate the temporary structs to hold the configuration data */
403 di_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
404 do_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
405 ai_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
406 ao_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
407 if (!di_cfg || !do_cfg || !ai_cfg || !ao_cfg) {
408 result = -ENOMEM;
409 goto err_alloc_configs;
410 }
411
412 /* Read the configuration from the connected device */
413 serial2002_tty_setspeed(devpriv->tty, devpriv->speed);
414 serial2002_poll_channel(devpriv->tty, 31);
415 while (1) {
416 struct serial_data data = serial2002_read(devpriv->tty, 1000);
417 int kind = S2002_CFG_KIND(data.value);
418 int channel = S2002_CFG_CHAN(data.value);
419 int range = S2002_CFG_BASE(data.value);
420 int cmd = S2002_CFG_CMD(data.value);
421
422 if (data.kind != is_channel || data.index != 31 ||
423 kind == S2002_CFG_KIND_INVALID)
424 break;
425
426 switch (kind) {
427 case S2002_CFG_KIND_DIGITAL_IN:
428 cfg = di_cfg;
429 break;
430 case S2002_CFG_KIND_DIGITAL_OUT:
431 cfg = do_cfg;
432 break;
433 case S2002_CFG_KIND_ANALOG_IN:
434 cfg = ai_cfg;
435 break;
436 case S2002_CFG_KIND_ANALOG_OUT:
437 cfg = ao_cfg;
438 break;
439 case S2002_CFG_KIND_ENCODER_IN:
440 cfg = ai_cfg;
441 break;
442 default:
443 cfg = NULL;
444 break;
445 }
446 if (!cfg)
447 continue; /* unknown kind, skip it */
448
449 cfg[channel].kind = kind;
450
451 switch (cmd) {
452 case S2002_CFG_CMD_BITS:
453 cfg[channel].bits = S2002_CFG_BITS(data.value);
454 break;
455 case S2002_CFG_CMD_MIN:
456 case S2002_CFG_CMD_MAX:
457 switch (S2002_CFG_UNITS(data.value)) {
458 case 0:
459 range *= 1000000;
460 break;
461 case 1:
462 range *= 1000;
463 break;
464 case 2:
465 range *= 1;
466 break;
467 }
468 if (S2002_CFG_SIGN(data.value))
469 range = -range;
470 if (cmd == S2002_CFG_CMD_MIN)
471 cfg[channel].min = range;
472 else
473 cfg[channel].max = range;
474 break;
475 }
476 }
477
478 /* Fill in subdevice data */
479 for (i = 0; i <= 4; i++) {
480 unsigned char *mapping = NULL;
481 struct serial2002_range_table_t *range = NULL;
482 int kind = 0;
483
484 s = &dev->subdevices[i];
485
486 switch (i) {
487 case 0:
488 cfg = di_cfg;
489 mapping = devpriv->digital_in_mapping;
490 kind = S2002_CFG_KIND_DIGITAL_IN;
491 break;
492 case 1:
493 cfg = do_cfg;
494 mapping = devpriv->digital_out_mapping;
495 kind = S2002_CFG_KIND_DIGITAL_OUT;
496 break;
497 case 2:
498 cfg = ai_cfg;
499 mapping = devpriv->analog_in_mapping;
500 range = devpriv->in_range;
501 kind = S2002_CFG_KIND_ANALOG_IN;
502 break;
503 case 3:
504 cfg = ao_cfg;
505 mapping = devpriv->analog_out_mapping;
506 range = devpriv->out_range;
507 kind = S2002_CFG_KIND_ANALOG_OUT;
508 break;
509 case 4:
510 cfg = ai_cfg;
511 mapping = devpriv->encoder_in_mapping;
512 range = devpriv->in_range;
513 kind = S2002_CFG_KIND_ENCODER_IN;
514 break;
515 }
516
517 if (serial2002_setup_subdevice(s, cfg, range, mapping, kind))
518 break; /* err handled below */
519 }
520 if (i <= 4) {
521 /*
522 * Failed to allocate maxdata_list or range_table_list
523 * for a subdevice that needed it.
524 */
525 result = -ENOMEM;
526 for (i = 0; i <= 4; i++) {
527 s = &dev->subdevices[i];
528 kfree(s->maxdata_list);
529 s->maxdata_list = NULL;
530 kfree(s->range_table_list);
531 s->range_table_list = NULL;
532 }
533 }
534
535 err_alloc_configs:
536 kfree(di_cfg);
537 kfree(do_cfg);
538 kfree(ai_cfg);
539 kfree(ao_cfg);
540
541 if (result) {
542 if (devpriv->tty) {
543 filp_close(devpriv->tty, NULL);
544 devpriv->tty = NULL;
545 }
546 }
547
548 return result;
549 }
550
551 static int serial2002_open(struct comedi_device *dev)
552 {
553 struct serial2002_private *devpriv = dev->private;
554 int result;
555 char port[20];
556
557 sprintf(port, "/dev/ttyS%d", devpriv->port);
558 devpriv->tty = filp_open(port, O_RDWR, 0);
559 if (IS_ERR(devpriv->tty)) {
560 result = (int)PTR_ERR(devpriv->tty);
561 dev_err(dev->class_dev, "file open error = %d\n", result);
562 } else {
563 result = serial2002_setup_subdevs(dev);
564 }
565 return result;
566 }
567
568 static void serial2002_close(struct comedi_device *dev)
569 {
570 struct serial2002_private *devpriv = dev->private;
571
572 if (!IS_ERR(devpriv->tty) && devpriv->tty)
573 filp_close(devpriv->tty, NULL);
574 }
575
576 static int serial2002_di_insn_read(struct comedi_device *dev,
577 struct comedi_subdevice *s,
578 struct comedi_insn *insn,
579 unsigned int *data)
580 {
581 struct serial2002_private *devpriv = dev->private;
582 int n;
583 int chan;
584
585 chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
586 for (n = 0; n < insn->n; n++) {
587 struct serial_data read;
588
589 serial2002_poll_digital(devpriv->tty, chan);
590 while (1) {
591 read = serial2002_read(devpriv->tty, 1000);
592 if (read.kind != is_digital || read.index == chan)
593 break;
594 }
595 data[n] = read.value;
596 }
597 return n;
598 }
599
600 static int serial2002_do_insn_write(struct comedi_device *dev,
601 struct comedi_subdevice *s,
602 struct comedi_insn *insn,
603 unsigned int *data)
604 {
605 struct serial2002_private *devpriv = dev->private;
606 int n;
607 int chan;
608
609 chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)];
610 for (n = 0; n < insn->n; n++) {
611 struct serial_data write;
612
613 write.kind = is_digital;
614 write.index = chan;
615 write.value = data[n];
616 serial2002_write(devpriv->tty, write);
617 }
618 return n;
619 }
620
621 static int serial2002_ai_insn_read(struct comedi_device *dev,
622 struct comedi_subdevice *s,
623 struct comedi_insn *insn,
624 unsigned int *data)
625 {
626 struct serial2002_private *devpriv = dev->private;
627 int n;
628 int chan;
629
630 chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
631 for (n = 0; n < insn->n; n++) {
632 struct serial_data read;
633
634 serial2002_poll_channel(devpriv->tty, chan);
635 while (1) {
636 read = serial2002_read(devpriv->tty, 1000);
637 if (read.kind != is_channel || read.index == chan)
638 break;
639 }
640 data[n] = read.value;
641 }
642 return n;
643 }
644
645 static int serial2002_ao_insn_write(struct comedi_device *dev,
646 struct comedi_subdevice *s,
647 struct comedi_insn *insn,
648 unsigned int *data)
649 {
650 struct serial2002_private *devpriv = dev->private;
651 int n;
652 int chan;
653
654 chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)];
655 for (n = 0; n < insn->n; n++) {
656 struct serial_data write;
657
658 write.kind = is_channel;
659 write.index = chan;
660 write.value = data[n];
661 serial2002_write(devpriv->tty, write);
662 devpriv->ao_readback[chan] = data[n];
663 }
664 return n;
665 }
666
667 static int serial2002_ao_insn_read(struct comedi_device *dev,
668 struct comedi_subdevice *s,
669 struct comedi_insn *insn,
670 unsigned int *data)
671 {
672 struct serial2002_private *devpriv = dev->private;
673 int n;
674 int chan = CR_CHAN(insn->chanspec);
675
676 for (n = 0; n < insn->n; n++)
677 data[n] = devpriv->ao_readback[chan];
678
679 return n;
680 }
681
682 static int serial2002_encoder_insn_read(struct comedi_device *dev,
683 struct comedi_subdevice *s,
684 struct comedi_insn *insn,
685 unsigned int *data)
686 {
687 struct serial2002_private *devpriv = dev->private;
688 int n;
689 int chan;
690
691 chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
692 for (n = 0; n < insn->n; n++) {
693 struct serial_data read;
694
695 serial2002_poll_channel(devpriv->tty, chan);
696 while (1) {
697 read = serial2002_read(devpriv->tty, 1000);
698 if (read.kind != is_channel || read.index == chan)
699 break;
700 }
701 data[n] = read.value;
702 }
703 return n;
704 }
705
706 static int serial2002_attach(struct comedi_device *dev,
707 struct comedi_devconfig *it)
708 {
709 struct serial2002_private *devpriv;
710 struct comedi_subdevice *s;
711 int ret;
712
713 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
714 if (!devpriv)
715 return -ENOMEM;
716
717 devpriv->port = it->options[0];
718 devpriv->speed = it->options[1];
719
720 ret = comedi_alloc_subdevices(dev, 5);
721 if (ret)
722 return ret;
723
724 /* digital input subdevice */
725 s = &dev->subdevices[0];
726 s->type = COMEDI_SUBD_DI;
727 s->subdev_flags = SDF_READABLE;
728 s->n_chan = 0;
729 s->maxdata = 1;
730 s->range_table = &range_digital;
731 s->insn_read = serial2002_di_insn_read;
732
733 /* digital output subdevice */
734 s = &dev->subdevices[1];
735 s->type = COMEDI_SUBD_DO;
736 s->subdev_flags = SDF_WRITABLE;
737 s->n_chan = 0;
738 s->maxdata = 1;
739 s->range_table = &range_digital;
740 s->insn_write = serial2002_do_insn_write;
741
742 /* analog input subdevice */
743 s = &dev->subdevices[2];
744 s->type = COMEDI_SUBD_AI;
745 s->subdev_flags = SDF_READABLE | SDF_GROUND;
746 s->n_chan = 0;
747 s->maxdata = 1;
748 s->range_table = NULL;
749 s->insn_read = serial2002_ai_insn_read;
750
751 /* analog output subdevice */
752 s = &dev->subdevices[3];
753 s->type = COMEDI_SUBD_AO;
754 s->subdev_flags = SDF_WRITABLE;
755 s->n_chan = 0;
756 s->maxdata = 1;
757 s->range_table = NULL;
758 s->insn_write = serial2002_ao_insn_write;
759 s->insn_read = serial2002_ao_insn_read;
760
761 /* encoder input subdevice */
762 s = &dev->subdevices[4];
763 s->type = COMEDI_SUBD_COUNTER;
764 s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
765 s->n_chan = 0;
766 s->maxdata = 1;
767 s->range_table = NULL;
768 s->insn_read = serial2002_encoder_insn_read;
769
770 dev->open = serial2002_open;
771 dev->close = serial2002_close;
772
773 return 0;
774 }
775
776 static void serial2002_detach(struct comedi_device *dev)
777 {
778 struct comedi_subdevice *s;
779 int i;
780
781 for (i = 0; i < dev->n_subdevices; i++) {
782 s = &dev->subdevices[i];
783 kfree(s->maxdata_list);
784 kfree(s->range_table_list);
785 }
786 }
787
788 static struct comedi_driver serial2002_driver = {
789 .driver_name = "serial2002",
790 .module = THIS_MODULE,
791 .attach = serial2002_attach,
792 .detach = serial2002_detach,
793 };
794 module_comedi_driver(serial2002_driver);
795
796 MODULE_AUTHOR("Comedi http://www.comedi.org");
797 MODULE_DESCRIPTION("Comedi low-level driver");
798 MODULE_LICENSE("GPL");