serial2002.c revision da91b2692e0939b307f9047192d2b9fe07793e7a
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 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/* 87 * most drivers define the following macro to make it easy to 88 * access the private structure. 89 */ 90#define devpriv ((struct serial2002_private *)dev->private) 91 92static int serial2002_attach(struct comedi_device *dev, 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:sizeof(serial2002_boards) / sizeof(struct serial2002_board), 102}; 103 104static int serial2002_di_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 105 struct comedi_insn *insn, unsigned int *data); 106static int serial2002_do_winsn(struct comedi_device *dev, struct comedi_subdevice *s, 107 struct comedi_insn *insn, unsigned int *data); 108static int serial2002_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 109 struct comedi_insn *insn, unsigned int *data); 110static int serial2002_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, 111 struct comedi_insn *insn, unsigned int *data); 112static int serial2002_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 113 struct comedi_insn *insn, unsigned int *data); 114 115struct serial_data { 116 enum { is_invalid, is_digital, is_channel } kind; 117 int index; 118 unsigned long value; 119}; 120 121static long tty_ioctl(struct file *f, unsigned op, unsigned long param) 122{ 123#ifdef HAVE_UNLOCKED_IOCTL 124 if (f->f_op->unlocked_ioctl) { 125 return f->f_op->unlocked_ioctl(f, op, param); 126 } 127#endif 128 if (f->f_op->ioctl) { 129 return f->f_op->ioctl(f->f_dentry->d_inode, 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 comedi_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. 352 value << 2) | ((data & 0x60) >> 5); 353 result.kind = is_channel; 354 } 355 result.index = data & 0x1f; 356 break; 357 } 358 } 359 return result; 360 361} 362 363static void serial_write(struct file *f, struct serial_data data) 364{ 365 if (data.kind == is_digital) { 366 unsigned char ch = 367 ((data.value << 5) & 0x20) | (data.index & 0x1f); 368 tty_write(f, &ch, 1); 369 } else { 370 unsigned char ch[6]; 371 int i = 0; 372 if (data.value >= (1L << 30)) { 373 ch[i] = 0x80 | ((data.value >> 30) & 0x03); 374 i++; 375 } 376 if (data.value >= (1L << 23)) { 377 ch[i] = 0x80 | ((data.value >> 23) & 0x7f); 378 i++; 379 } 380 if (data.value >= (1L << 16)) { 381 ch[i] = 0x80 | ((data.value >> 16) & 0x7f); 382 i++; 383 } 384 if (data.value >= (1L << 9)) { 385 ch[i] = 0x80 | ((data.value >> 9) & 0x7f); 386 i++; 387 } 388 ch[i] = 0x80 | ((data.value >> 2) & 0x7f); 389 i++; 390 ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f); 391 i++; 392 tty_write(f, ch, i); 393 } 394} 395 396static void serial_2002_open(struct comedi_device *dev) 397{ 398 char port[20]; 399 400 sprintf(port, "/dev/ttyS%d", devpriv->port); 401 devpriv->tty = filp_open(port, 0, O_RDWR); 402 if (IS_ERR(devpriv->tty)) { 403 printk("serial_2002: file open error = %ld\n", 404 PTR_ERR(devpriv->tty)); 405 } else { 406 struct config_t { 407 408 int kind; 409 int bits; 410 int min; 411 int max; 412 }; 413 414 struct config_t dig_in_config[32]; 415 struct config_t dig_out_config[32]; 416 struct config_t chan_in_config[32]; 417 struct config_t chan_out_config[32]; 418 int i; 419 420 for (i = 0; i < 32; i++) { 421 dig_in_config[i].kind = 0; 422 dig_in_config[i].bits = 0; 423 dig_in_config[i].min = 0; 424 dig_in_config[i].max = 0; 425 dig_out_config[i].kind = 0; 426 dig_out_config[i].bits = 0; 427 dig_out_config[i].min = 0; 428 dig_out_config[i].max = 0; 429 chan_in_config[i].kind = 0; 430 chan_in_config[i].bits = 0; 431 chan_in_config[i].min = 0; 432 chan_in_config[i].max = 0; 433 chan_out_config[i].kind = 0; 434 chan_out_config[i].bits = 0; 435 chan_out_config[i].min = 0; 436 chan_out_config[i].max = 0; 437 } 438 439 tty_setspeed(devpriv->tty, devpriv->speed); 440 poll_channel(devpriv->tty, 31); /* Start reading configuration */ 441 while (1) { 442 struct serial_data data; 443 444 data = serial_read(devpriv->tty, 1000); 445 if (data.kind != is_channel || data.index != 31 446 || !(data.value & 0xe0)) { 447 break; 448 } else { 449 int command, channel, kind; 450 struct config_t *cur_config = 0; 451 452 channel = data.value & 0x1f; 453 kind = (data.value >> 5) & 0x7; 454 command = (data.value >> 8) & 0x3; 455 switch (kind) { 456 case 1:{ 457 cur_config = dig_in_config; 458 } 459 break; 460 case 2:{ 461 cur_config = dig_out_config; 462 } 463 break; 464 case 3:{ 465 cur_config = chan_in_config; 466 } 467 break; 468 case 4:{ 469 cur_config = chan_out_config; 470 } 471 break; 472 case 5:{ 473 cur_config = chan_in_config; 474 } 475 break; 476 } 477 478 if (cur_config) { 479 cur_config[channel].kind = kind; 480 switch (command) { 481 case 0:{ 482 cur_config[channel]. 483 bits = 484 (data. 485 value >> 10) & 486 0x3f; 487 } 488 break; 489 case 1:{ 490 int unit, sign, min; 491 unit = (data. 492 value >> 10) & 493 0x7; 494 sign = (data. 495 value >> 13) & 496 0x1; 497 min = (data. 498 value >> 14) & 499 0xfffff; 500 501 switch (unit) { 502 case 0:{ 503 min = min * 1000000; 504 } 505 break; 506 case 1:{ 507 min = min * 1000; 508 } 509 break; 510 case 2:{ 511 min = min * 1; 512 } 513 break; 514 } 515 if (sign) { 516 min = -min; 517 } 518 cur_config[channel]. 519 min = min; 520 } 521 break; 522 case 2:{ 523 int unit, sign, max; 524 unit = (data. 525 value >> 10) & 526 0x7; 527 sign = (data. 528 value >> 13) & 529 0x1; 530 max = (data. 531 value >> 14) & 532 0xfffff; 533 534 switch (unit) { 535 case 0:{ 536 max = max * 1000000; 537 } 538 break; 539 case 1:{ 540 max = max * 1000; 541 } 542 break; 543 case 2:{ 544 max = max * 1; 545 } 546 break; 547 } 548 if (sign) { 549 max = -max; 550 } 551 cur_config[channel]. 552 max = max; 553 } 554 break; 555 } 556 } 557 } 558 } 559 for (i = 0; i <= 4; i++) { 560 /* Fill in subdev data */ 561 struct config_t *c; 562 unsigned char *mapping = 0; 563 struct serial2002_range_table_t *range = 0; 564 int kind = 0; 565 566 switch (i) { 567 case 0:{ 568 c = dig_in_config; 569 mapping = devpriv->digital_in_mapping; 570 kind = 1; 571 } 572 break; 573 case 1:{ 574 c = dig_out_config; 575 mapping = devpriv->digital_out_mapping; 576 kind = 2; 577 } 578 break; 579 case 2:{ 580 c = chan_in_config; 581 mapping = devpriv->analog_in_mapping; 582 range = devpriv->in_range; 583 kind = 3; 584 } 585 break; 586 case 3:{ 587 c = chan_out_config; 588 mapping = devpriv->analog_out_mapping; 589 range = devpriv->out_range; 590 kind = 4; 591 } 592 break; 593 case 4:{ 594 c = chan_in_config; 595 mapping = devpriv->encoder_in_mapping; 596 range = devpriv->in_range; 597 kind = 5; 598 } 599 break; 600 default:{ 601 c = 0; 602 } 603 break; 604 } 605 if (c) { 606 struct comedi_subdevice *s; 607 const struct comedi_lrange **range_table_list = NULL; 608 unsigned int *maxdata_list; 609 int j, chan; 610 611 for (chan = 0, j = 0; j < 32; j++) { 612 if (c[j].kind == kind) { 613 chan++; 614 } 615 } 616 s = &dev->subdevices[i]; 617 s->n_chan = chan; 618 s->maxdata = 0; 619 if (s->maxdata_list) { 620 kfree(s->maxdata_list); 621 } 622 s->maxdata_list = maxdata_list = 623 kmalloc(sizeof(unsigned int) * s->n_chan, 624 GFP_KERNEL); 625 if (s->range_table_list) { 626 kfree(s->range_table_list); 627 } 628 if (range) { 629 s->range_table = 0; 630 s->range_table_list = range_table_list = 631 kmalloc(sizeof 632 (struct serial2002_range_table_t) * 633 s->n_chan, GFP_KERNEL); 634 } 635 for (chan = 0, j = 0; j < 32; j++) { 636 if (c[j].kind == kind) { 637 if (mapping) { 638 mapping[chan] = j; 639 } 640 if (range) { 641 range[j].length = 1; 642 range[j].range.min = 643 c[j].min; 644 range[j].range.max = 645 c[j].max; 646 range_table_list[chan] = 647 (const struct 648 comedi_lrange *) 649 &range[j]; 650 } 651 maxdata_list[chan] = 652 ((long long)1 << c[j]. 653 bits) - 1; 654 chan++; 655 } 656 } 657 } 658 } 659 } 660} 661 662static void serial_2002_close(struct comedi_device *dev) 663{ 664 if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) { 665 filp_close(devpriv->tty, 0); 666 } 667} 668 669static int serial2002_di_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 670 struct comedi_insn *insn, unsigned int *data) 671{ 672 int n; 673 int chan; 674 675 chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)]; 676 for (n = 0; n < insn->n; n++) { 677 struct serial_data read; 678 679 poll_digital(devpriv->tty, chan); 680 while (1) { 681 read = serial_read(devpriv->tty, 1000); 682 if (read.kind != is_digital || read.index == chan) { 683 break; 684 } 685 } 686 data[n] = read.value; 687 } 688 return n; 689} 690 691static int serial2002_do_winsn(struct comedi_device *dev, struct comedi_subdevice *s, 692 struct comedi_insn *insn, unsigned int *data) 693{ 694 int n; 695 int chan; 696 697 chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)]; 698 for (n = 0; n < insn->n; n++) { 699 struct serial_data write; 700 701 write.kind = is_digital; 702 write.index = chan; 703 write.value = data[n]; 704 serial_write(devpriv->tty, write); 705 } 706 return n; 707} 708 709static int serial2002_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 710 struct comedi_insn *insn, unsigned int *data) 711{ 712 int n; 713 int chan; 714 715 chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)]; 716 for (n = 0; n < insn->n; n++) { 717 struct serial_data read; 718 719 poll_channel(devpriv->tty, chan); 720 while (1) { 721 read = serial_read(devpriv->tty, 1000); 722 if (read.kind != is_channel || read.index == chan) { 723 break; 724 } 725 } 726 data[n] = read.value; 727 } 728 return n; 729} 730 731static int serial2002_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, 732 struct comedi_insn *insn, unsigned int *data) 733{ 734 int n; 735 int chan; 736 737 chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)]; 738 for (n = 0; n < insn->n; n++) { 739 struct serial_data write; 740 741 write.kind = is_channel; 742 write.index = chan; 743 write.value = data[n]; 744 serial_write(devpriv->tty, write); 745 devpriv->ao_readback[chan] = data[n]; 746 } 747 return n; 748} 749 750static int serial2002_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 751 struct comedi_insn *insn, unsigned int *data) 752{ 753 int n; 754 int chan = CR_CHAN(insn->chanspec); 755 756 for (n = 0; n < insn->n; n++) { 757 data[n] = devpriv->ao_readback[chan]; 758 } 759 760 return n; 761} 762 763static int serial2002_ei_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 764 struct comedi_insn *insn, unsigned int *data) 765{ 766 int n; 767 int chan; 768 769 chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)]; 770 for (n = 0; n < insn->n; n++) { 771 struct serial_data read; 772 773 poll_channel(devpriv->tty, chan); 774 while (1) { 775 read = serial_read(devpriv->tty, 1000); 776 if (read.kind != is_channel || read.index == chan) { 777 break; 778 } 779 } 780 data[n] = read.value; 781 } 782 return n; 783} 784 785static int serial2002_attach(struct comedi_device *dev, struct comedi_devconfig *it) 786{ 787 struct comedi_subdevice *s; 788 789 printk("comedi%d: serial2002: ", dev->minor); 790 dev->board_name = thisboard->name; 791 if (alloc_private(dev, sizeof(struct serial2002_private)) < 0) { 792 return -ENOMEM; 793 } 794 dev->open = serial_2002_open; 795 dev->close = serial_2002_close; 796 devpriv->port = it->options[0]; 797 devpriv->speed = it->options[1]; 798 printk("/dev/ttyS%d @ %d\n", devpriv->port, devpriv->speed); 799 800 if (alloc_subdevices(dev, 5) < 0) 801 return -ENOMEM; 802 803 /* digital input subdevice */ 804 s = dev->subdevices + 0; 805 s->type = COMEDI_SUBD_DI; 806 s->subdev_flags = SDF_READABLE; 807 s->n_chan = 0; 808 s->maxdata = 1; 809 s->range_table = &range_digital; 810 s->insn_read = &serial2002_di_rinsn; 811 812 /* digital output subdevice */ 813 s = dev->subdevices + 1; 814 s->type = COMEDI_SUBD_DO; 815 s->subdev_flags = SDF_WRITEABLE; 816 s->n_chan = 0; 817 s->maxdata = 1; 818 s->range_table = &range_digital; 819 s->insn_write = &serial2002_do_winsn; 820 821 /* analog input subdevice */ 822 s = dev->subdevices + 2; 823 s->type = COMEDI_SUBD_AI; 824 s->subdev_flags = SDF_READABLE | SDF_GROUND; 825 s->n_chan = 0; 826 s->maxdata = 1; 827 s->range_table = 0; 828 s->insn_read = &serial2002_ai_rinsn; 829 830 /* analog output subdevice */ 831 s = dev->subdevices + 3; 832 s->type = COMEDI_SUBD_AO; 833 s->subdev_flags = SDF_WRITEABLE; 834 s->n_chan = 0; 835 s->maxdata = 1; 836 s->range_table = 0; 837 s->insn_write = &serial2002_ao_winsn; 838 s->insn_read = &serial2002_ao_rinsn; 839 840 /* encoder input subdevice */ 841 s = dev->subdevices + 4; 842 s->type = COMEDI_SUBD_COUNTER; 843 s->subdev_flags = SDF_READABLE | SDF_LSAMPL; 844 s->n_chan = 0; 845 s->maxdata = 1; 846 s->range_table = 0; 847 s->insn_read = &serial2002_ei_rinsn; 848 849 return 1; 850} 851 852static int serial2002_detach(struct comedi_device *dev) 853{ 854 struct comedi_subdevice *s; 855 int i; 856 857 printk("comedi%d: serial2002: remove\n", dev->minor); 858 for (i = 0; i < 4; i++) { 859 s = &dev->subdevices[i]; 860 if (s->maxdata_list) { 861 kfree(s->maxdata_list); 862 } 863 if (s->range_table_list) { 864 kfree(s->range_table_list); 865 } 866 } 867 return 0; 868} 869 870COMEDI_INITCLEANUP(driver_serial2002); 871