serial2002.c revision ca9ed0f295a5e85861c63713406d86643e570580
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 70 71typedef struct { 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} serial2002_private; 83 84/* 85 * most drivers define the following macro to make it easy to 86 * access the private structure. 87 */ 88#define devpriv ((serial2002_private *)dev->private) 89 90static int serial2002_attach(struct comedi_device * dev, struct comedi_devconfig * it); 91static int serial2002_detach(struct comedi_device * dev); 92struct comedi_driver driver_serial2002 = { 93 driver_name:"serial2002", 94 module:THIS_MODULE, 95 attach:serial2002_attach, 96 detach:serial2002_detach, 97 board_name:&serial2002_boards[0].name, 98 offset:sizeof(struct serial2002_board), 99 num_names:sizeof(serial2002_boards) / sizeof(struct serial2002_board), 100}; 101 102static int serial2002_di_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 103 struct comedi_insn * insn, unsigned int * data); 104static int serial2002_do_winsn(struct comedi_device * dev, struct comedi_subdevice * s, 105 struct comedi_insn * insn, unsigned int * data); 106static int serial2002_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 107 struct comedi_insn * insn, unsigned int * data); 108static int serial2002_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, 109 struct comedi_insn * insn, unsigned int * data); 110static int serial2002_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 111 struct comedi_insn * insn, unsigned int * data); 112 113struct serial_data { 114 enum { is_invalid, is_digital, is_channel } kind; 115 int index; 116 unsigned long value; 117}; 118 119static long tty_ioctl(struct file *f, unsigned op, unsigned long param) 120{ 121#ifdef HAVE_UNLOCKED_IOCTL 122 if (f->f_op->unlocked_ioctl) { 123 return f->f_op->unlocked_ioctl(f, op, param); 124 } 125#endif 126 if (f->f_op->ioctl) { 127 return f->f_op->ioctl(f->f_dentry->d_inode, f, op, param); 128 } 129 return -ENOSYS; 130} 131 132static int tty_write(struct file *f, unsigned char *buf, int count) 133{ 134 int result; 135 mm_segment_t oldfs; 136 137 oldfs = get_fs(); 138 set_fs(KERNEL_DS); 139 f->f_pos = 0; 140 result = f->f_op->write(f, buf, count, &f->f_pos); 141 set_fs(oldfs); 142 return result; 143} 144 145#if 0 146/* 147 * On 2.6.26.3 this occaisonally gave me page faults, worked around by 148 * settings.c_cc[VMIN] = 0; settings.c_cc[VTIME] = 0 149 */ 150static int tty_available(struct file *f) 151{ 152 long result = 0; 153 mm_segment_t oldfs; 154 155 oldfs = get_fs(); 156 set_fs(KERNEL_DS); 157 tty_ioctl(f, FIONREAD, (unsigned long)&result); 158 set_fs(oldfs); 159 return result; 160} 161#endif 162 163static int tty_read(struct file *f, int timeout) 164{ 165 int result; 166 167 result = -1; 168 if (!IS_ERR(f)) { 169 mm_segment_t oldfs; 170 171 oldfs = get_fs(); 172 set_fs(KERNEL_DS); 173 if (f->f_op->poll) { 174 struct poll_wqueues table; 175 struct timeval start, now; 176 177 do_gettimeofday(&start); 178 poll_initwait(&table); 179 while (1) { 180 long elapsed; 181 int mask; 182 183 mask = f->f_op->poll(f, &table.pt); 184 if (mask & (POLLRDNORM | POLLRDBAND | POLLIN | 185 POLLHUP | POLLERR)) { 186 break; 187 } 188 do_gettimeofday(&now); 189 elapsed = 190 (1000000 * (now.tv_sec - start.tv_sec) + 191 now.tv_usec - start.tv_usec); 192 if (elapsed > timeout) { 193 break; 194 } 195 set_current_state(TASK_INTERRUPTIBLE); 196 schedule_timeout(((timeout - 197 elapsed) * HZ) / 10000); 198 } 199 poll_freewait(&table); 200 { 201 unsigned char ch; 202 203 f->f_pos = 0; 204 if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) { 205 result = ch; 206 } 207 } 208 } else { 209 /* Device does not support poll, busy wait */ 210 int retries = 0; 211 while (1) { 212 unsigned char ch; 213 214 retries++; 215 if (retries >= timeout) { 216 break; 217 } 218 219 f->f_pos = 0; 220 if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) { 221 result = ch; 222 break; 223 } 224 comedi_udelay(100); 225 } 226 } 227 set_fs(oldfs); 228 } 229 return result; 230} 231 232static void tty_setspeed(struct file *f, int speed) 233{ 234 mm_segment_t oldfs; 235 236 oldfs = get_fs(); 237 set_fs(KERNEL_DS); 238 { 239 // Set speed 240 struct termios settings; 241 242 tty_ioctl(f, TCGETS, (unsigned long)&settings); 243// printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); 244 settings.c_iflag = 0; 245 settings.c_oflag = 0; 246 settings.c_lflag = 0; 247 settings.c_cflag = CLOCAL | CS8 | CREAD; 248 settings.c_cc[VMIN] = 0; 249 settings.c_cc[VTIME] = 0; 250 switch (speed) { 251 case 2400:{ 252 settings.c_cflag |= B2400; 253 } 254 break; 255 case 4800:{ 256 settings.c_cflag |= B4800; 257 } 258 break; 259 case 9600:{ 260 settings.c_cflag |= B9600; 261 } 262 break; 263 case 19200:{ 264 settings.c_cflag |= B19200; 265 } 266 break; 267 case 38400:{ 268 settings.c_cflag |= B38400; 269 } 270 break; 271 case 57600:{ 272 settings.c_cflag |= B57600; 273 } 274 break; 275 case 115200:{ 276 settings.c_cflag |= B115200; 277 } 278 break; 279 default:{ 280 settings.c_cflag |= B9600; 281 } 282 break; 283 } 284 tty_ioctl(f, TCSETS, (unsigned long)&settings); 285// printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); 286 } 287 { 288 // Set low latency 289 struct serial_struct settings; 290 291 tty_ioctl(f, TIOCGSERIAL, (unsigned long)&settings); 292 settings.flags |= ASYNC_LOW_LATENCY; 293 tty_ioctl(f, TIOCSSERIAL, (unsigned long)&settings); 294 } 295 296 set_fs(oldfs); 297} 298 299static void poll_digital(struct file *f, int channel) 300{ 301 char cmd; 302 303 cmd = 0x40 | (channel & 0x1f); 304 tty_write(f, &cmd, 1); 305} 306 307static void poll_channel(struct file *f, int channel) 308{ 309 char cmd; 310 311 cmd = 0x60 | (channel & 0x1f); 312 tty_write(f, &cmd, 1); 313} 314 315static struct serial_data serial_read(struct file *f, int timeout) 316{ 317 struct serial_data result; 318 int length; 319 320 result.kind = is_invalid; 321 result.index = 0; 322 result.value = 0; 323 length = 0; 324 while (1) { 325 int data = tty_read(f, timeout); 326 327 length++; 328 if (data < 0) { 329 printk("serial2002 error\n"); 330 break; 331 } else if (data & 0x80) { 332 result.value = (result.value << 7) | (data & 0x7f); 333 } else { 334 if (length == 1) { 335 switch ((data >> 5) & 0x03) { 336 case 0:{ 337 result.value = 0; 338 result.kind = is_digital; 339 } 340 break; 341 case 1:{ 342 result.value = 1; 343 result.kind = is_digital; 344 } 345 break; 346 } 347 } else { 348 result.value = 349 (result. 350 value << 2) | ((data & 0x60) >> 5); 351 result.kind = is_channel; 352 } 353 result.index = data & 0x1f; 354 break; 355 } 356 } 357 return result; 358 359} 360 361static void serial_write(struct file *f, struct serial_data data) 362{ 363 if (data.kind == is_digital) { 364 unsigned char ch = 365 ((data.value << 5) & 0x20) | (data.index & 0x1f); 366 tty_write(f, &ch, 1); 367 } else { 368 unsigned char ch[6]; 369 int i = 0; 370 if (data.value >= (1L << 30)) { 371 ch[i] = 0x80 | ((data.value >> 30) & 0x03); 372 i++; 373 } 374 if (data.value >= (1L << 23)) { 375 ch[i] = 0x80 | ((data.value >> 23) & 0x7f); 376 i++; 377 } 378 if (data.value >= (1L << 16)) { 379 ch[i] = 0x80 | ((data.value >> 16) & 0x7f); 380 i++; 381 } 382 if (data.value >= (1L << 9)) { 383 ch[i] = 0x80 | ((data.value >> 9) & 0x7f); 384 i++; 385 } 386 ch[i] = 0x80 | ((data.value >> 2) & 0x7f); 387 i++; 388 ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f); 389 i++; 390 tty_write(f, ch, i); 391 } 392} 393 394static void serial_2002_open(struct comedi_device * dev) 395{ 396 char port[20]; 397 398 sprintf(port, "/dev/ttyS%d", devpriv->port); 399 devpriv->tty = filp_open(port, 0, O_RDWR); 400 if (IS_ERR(devpriv->tty)) { 401 printk("serial_2002: file open error = %ld\n", 402 PTR_ERR(devpriv->tty)); 403 } else { 404 struct config_t { 405 406 int kind; 407 int bits; 408 int min; 409 int max; 410 }; 411 412 struct config_t dig_in_config[32]; 413 struct config_t dig_out_config[32]; 414 struct config_t chan_in_config[32]; 415 struct config_t chan_out_config[32]; 416 int i; 417 418 for (i = 0; i < 32; i++) { 419 dig_in_config[i].kind = 0; 420 dig_in_config[i].bits = 0; 421 dig_in_config[i].min = 0; 422 dig_in_config[i].max = 0; 423 dig_out_config[i].kind = 0; 424 dig_out_config[i].bits = 0; 425 dig_out_config[i].min = 0; 426 dig_out_config[i].max = 0; 427 chan_in_config[i].kind = 0; 428 chan_in_config[i].bits = 0; 429 chan_in_config[i].min = 0; 430 chan_in_config[i].max = 0; 431 chan_out_config[i].kind = 0; 432 chan_out_config[i].bits = 0; 433 chan_out_config[i].min = 0; 434 chan_out_config[i].max = 0; 435 } 436 437 tty_setspeed(devpriv->tty, devpriv->speed); 438 poll_channel(devpriv->tty, 31); // Start reading configuration 439 while (1) { 440 struct serial_data data; 441 442 data = serial_read(devpriv->tty, 1000); 443 if (data.kind != is_channel || data.index != 31 444 || !(data.value & 0xe0)) { 445 break; 446 } else { 447 int command, channel, kind; 448 struct config_t *cur_config = 0; 449 450 channel = data.value & 0x1f; 451 kind = (data.value >> 5) & 0x7; 452 command = (data.value >> 8) & 0x3; 453 switch (kind) { 454 case 1:{ 455 cur_config = dig_in_config; 456 } 457 break; 458 case 2:{ 459 cur_config = dig_out_config; 460 } 461 break; 462 case 3:{ 463 cur_config = chan_in_config; 464 } 465 break; 466 case 4:{ 467 cur_config = chan_out_config; 468 } 469 break; 470 case 5:{ 471 cur_config = chan_in_config; 472 } 473 break; 474 } 475 476 if (cur_config) { 477 cur_config[channel].kind = kind; 478 switch (command) { 479 case 0:{ 480 cur_config[channel]. 481 bits = 482 (data. 483 value >> 10) & 484 0x3f; 485 } 486 break; 487 case 1:{ 488 int unit, sign, min; 489 unit = (data. 490 value >> 10) & 491 0x7; 492 sign = (data. 493 value >> 13) & 494 0x1; 495 min = (data. 496 value >> 14) & 497 0xfffff; 498 499 switch (unit) { 500 case 0:{ 501 min = min * 1000000; 502 } 503 break; 504 case 1:{ 505 min = min * 1000; 506 } 507 break; 508 case 2:{ 509 min = min * 1; 510 } 511 break; 512 } 513 if (sign) { 514 min = -min; 515 } 516 cur_config[channel]. 517 min = min; 518 } 519 break; 520 case 2:{ 521 int unit, sign, max; 522 unit = (data. 523 value >> 10) & 524 0x7; 525 sign = (data. 526 value >> 13) & 527 0x1; 528 max = (data. 529 value >> 14) & 530 0xfffff; 531 532 switch (unit) { 533 case 0:{ 534 max = max * 1000000; 535 } 536 break; 537 case 1:{ 538 max = max * 1000; 539 } 540 break; 541 case 2:{ 542 max = max * 1; 543 } 544 break; 545 } 546 if (sign) { 547 max = -max; 548 } 549 cur_config[channel]. 550 max = max; 551 } 552 break; 553 } 554 } 555 } 556 } 557 for (i = 0; i <= 4; i++) { 558 // Fill in subdev data 559 struct config_t *c; 560 unsigned char *mapping = 0; 561 struct serial2002_range_table_t *range = 0; 562 int kind = 0; 563 564 switch (i) { 565 case 0:{ 566 c = dig_in_config; 567 mapping = devpriv->digital_in_mapping; 568 kind = 1; 569 } 570 break; 571 case 1:{ 572 c = dig_out_config; 573 mapping = devpriv->digital_out_mapping; 574 kind = 2; 575 } 576 break; 577 case 2:{ 578 c = chan_in_config; 579 mapping = devpriv->analog_in_mapping; 580 range = devpriv->in_range; 581 kind = 3; 582 } 583 break; 584 case 3:{ 585 c = chan_out_config; 586 mapping = devpriv->analog_out_mapping; 587 range = devpriv->out_range; 588 kind = 4; 589 } 590 break; 591 case 4:{ 592 c = chan_in_config; 593 mapping = devpriv->encoder_in_mapping; 594 range = devpriv->in_range; 595 kind = 5; 596 } 597 break; 598 default:{ 599 c = 0; 600 } 601 break; 602 } 603 if (c) { 604 struct comedi_subdevice *s; 605 const struct comedi_lrange **range_table_list = NULL; 606 unsigned int *maxdata_list; 607 int j, chan; 608 609 for (chan = 0, j = 0; j < 32; j++) { 610 if (c[j].kind == kind) { 611 chan++; 612 } 613 } 614 s = &dev->subdevices[i]; 615 s->n_chan = chan; 616 s->maxdata = 0; 617 if (s->maxdata_list) { 618 kfree(s->maxdata_list); 619 } 620 s->maxdata_list = maxdata_list = 621 kmalloc(sizeof(unsigned int) * s->n_chan, 622 GFP_KERNEL); 623 if (s->range_table_list) { 624 kfree(s->range_table_list); 625 } 626 if (range) { 627 s->range_table = 0; 628 s->range_table_list = range_table_list = 629 kmalloc(sizeof 630 (struct serial2002_range_table_t) * 631 s->n_chan, GFP_KERNEL); 632 } 633 for (chan = 0, j = 0; j < 32; j++) { 634 if (c[j].kind == kind) { 635 if (mapping) { 636 mapping[chan] = j; 637 } 638 if (range) { 639 range[j].length = 1; 640 range[j].range.min = 641 c[j].min; 642 range[j].range.max = 643 c[j].max; 644 range_table_list[chan] = 645 (const struct 646 comedi_lrange *) 647 &range[j]; 648 } 649 maxdata_list[chan] = 650 ((long long)1 << c[j]. 651 bits) - 1; 652 chan++; 653 } 654 } 655 } 656 } 657 } 658} 659 660static void serial_2002_close(struct comedi_device * dev) 661{ 662 if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) { 663 filp_close(devpriv->tty, 0); 664 } 665} 666 667static int serial2002_di_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 668 struct comedi_insn * insn, unsigned int * data) 669{ 670 int n; 671 int chan; 672 673 chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)]; 674 for (n = 0; n < insn->n; n++) { 675 struct serial_data read; 676 677 poll_digital(devpriv->tty, chan); 678 while (1) { 679 read = serial_read(devpriv->tty, 1000); 680 if (read.kind != is_digital || read.index == chan) { 681 break; 682 } 683 } 684 data[n] = read.value; 685 } 686 return n; 687} 688 689static int serial2002_do_winsn(struct comedi_device * dev, struct comedi_subdevice * s, 690 struct comedi_insn * insn, unsigned int * data) 691{ 692 int n; 693 int chan; 694 695 chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)]; 696 for (n = 0; n < insn->n; n++) { 697 struct serial_data write; 698 699 write.kind = is_digital; 700 write.index = chan; 701 write.value = data[n]; 702 serial_write(devpriv->tty, write); 703 } 704 return n; 705} 706 707static int serial2002_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 708 struct comedi_insn * insn, unsigned int * data) 709{ 710 int n; 711 int chan; 712 713 chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)]; 714 for (n = 0; n < insn->n; n++) { 715 struct serial_data read; 716 717 poll_channel(devpriv->tty, chan); 718 while (1) { 719 read = serial_read(devpriv->tty, 1000); 720 if (read.kind != is_channel || read.index == chan) { 721 break; 722 } 723 } 724 data[n] = read.value; 725 } 726 return n; 727} 728 729static int serial2002_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, 730 struct comedi_insn * insn, unsigned int * data) 731{ 732 int n; 733 int chan; 734 735 chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)]; 736 for (n = 0; n < insn->n; n++) { 737 struct serial_data write; 738 739 write.kind = is_channel; 740 write.index = chan; 741 write.value = data[n]; 742 serial_write(devpriv->tty, write); 743 devpriv->ao_readback[chan] = data[n]; 744 } 745 return n; 746} 747 748static int serial2002_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 749 struct comedi_insn * insn, unsigned int * data) 750{ 751 int n; 752 int chan = CR_CHAN(insn->chanspec); 753 754 for (n = 0; n < insn->n; n++) { 755 data[n] = devpriv->ao_readback[chan]; 756 } 757 758 return n; 759} 760 761static int serial2002_ei_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, 762 struct comedi_insn * insn, unsigned int * data) 763{ 764 int n; 765 int chan; 766 767 chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)]; 768 for (n = 0; n < insn->n; n++) { 769 struct serial_data read; 770 771 poll_channel(devpriv->tty, chan); 772 while (1) { 773 read = serial_read(devpriv->tty, 1000); 774 if (read.kind != is_channel || read.index == chan) { 775 break; 776 } 777 } 778 data[n] = read.value; 779 } 780 return n; 781} 782 783static int serial2002_attach(struct comedi_device * dev, struct comedi_devconfig * it) 784{ 785 struct comedi_subdevice *s; 786 787 printk("comedi%d: serial2002: ", dev->minor); 788 dev->board_name = thisboard->name; 789 if (alloc_private(dev, sizeof(serial2002_private)) < 0) { 790 return -ENOMEM; 791 } 792 dev->open = serial_2002_open; 793 dev->close = serial_2002_close; 794 devpriv->port = it->options[0]; 795 devpriv->speed = it->options[1]; 796 printk("/dev/ttyS%d @ %d\n", devpriv->port, devpriv->speed); 797 798 if (alloc_subdevices(dev, 5) < 0) 799 return -ENOMEM; 800 801 /* digital input subdevice */ 802 s = dev->subdevices + 0; 803 s->type = COMEDI_SUBD_DI; 804 s->subdev_flags = SDF_READABLE; 805 s->n_chan = 0; 806 s->maxdata = 1; 807 s->range_table = &range_digital; 808 s->insn_read = &serial2002_di_rinsn; 809 810 /* digital output subdevice */ 811 s = dev->subdevices + 1; 812 s->type = COMEDI_SUBD_DO; 813 s->subdev_flags = SDF_WRITEABLE; 814 s->n_chan = 0; 815 s->maxdata = 1; 816 s->range_table = &range_digital; 817 s->insn_write = &serial2002_do_winsn; 818 819 /* analog input subdevice */ 820 s = dev->subdevices + 2; 821 s->type = COMEDI_SUBD_AI; 822 s->subdev_flags = SDF_READABLE | SDF_GROUND; 823 s->n_chan = 0; 824 s->maxdata = 1; 825 s->range_table = 0; 826 s->insn_read = &serial2002_ai_rinsn; 827 828 /* analog output subdevice */ 829 s = dev->subdevices + 3; 830 s->type = COMEDI_SUBD_AO; 831 s->subdev_flags = SDF_WRITEABLE; 832 s->n_chan = 0; 833 s->maxdata = 1; 834 s->range_table = 0; 835 s->insn_write = &serial2002_ao_winsn; 836 s->insn_read = &serial2002_ao_rinsn; 837 838 /* encoder input subdevice */ 839 s = dev->subdevices + 4; 840 s->type = COMEDI_SUBD_COUNTER; 841 s->subdev_flags = SDF_READABLE | SDF_LSAMPL; 842 s->n_chan = 0; 843 s->maxdata = 1; 844 s->range_table = 0; 845 s->insn_read = &serial2002_ei_rinsn; 846 847 return 1; 848} 849 850static int serial2002_detach(struct comedi_device * dev) 851{ 852 struct comedi_subdevice *s; 853 int i; 854 855 printk("comedi%d: serial2002: remove\n", dev->minor); 856 for (i = 0; i < 4; i++) { 857 s = &dev->subdevices[i]; 858 if (s->maxdata_list) { 859 kfree(s->maxdata_list); 860 } 861 if (s->range_table_list) { 862 kfree(s->range_table_list); 863 } 864 } 865 return 0; 866} 867 868COMEDI_INITCLEANUP(driver_serial2002); 869