serial2002.c revision 2021937c6d8ec1212b8f4fa01e86f852c9b96368
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, O_RDWR, 0); 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