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