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