pcl816.c revision 13de4f000ec491b16a820e4ed59de2c98b7807be
1/* 2 comedi/drivers/pcl816.c 3 4 Author: Juan Grigera <juan@grigera.com.ar> 5 based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 6 7 hardware driver for Advantech cards: 8 card: PCL-816, PCL814B 9 driver: pcl816 10*/ 11/* 12Driver: pcl816 13Description: Advantech PCL-816 cards, PCL-814 14Author: Juan Grigera <juan@grigera.com.ar> 15Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) 16Status: works 17Updated: Tue, 2 Apr 2002 23:15:21 -0800 18 19PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. 20Differences are at resolution (16 vs 12 bits). 21 22The driver support AI command mode, other subdevices not written. 23 24Analog output and digital input and output are not supported. 25 26Configuration Options: 27 [0] - IO Base 28 [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) 29 [2] - DMA (0=disable, 1, 3) 30 [3] - 0, 10=10MHz clock for 8254 31 1= 1MHz clock for 8254 32 33*/ 34 35#include "../comedidev.h" 36 37#include <linux/ioport.h> 38#include <linux/mc146818rtc.h> 39#include <linux/delay.h> 40#include <asm/dma.h> 41 42#include "8253.h" 43 44#define DEBUG(x) x 45 46/* boards constants */ 47/* IO space len */ 48#define PCLx1x_RANGE 16 49 50/* #define outb(x,y) printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y) */ 51 52/* INTEL 8254 counters */ 53#define PCL816_CTR0 4 54#define PCL816_CTR1 5 55#define PCL816_CTR2 6 56/* R: counter read-back register W: counter control */ 57#define PCL816_CTRCTL 7 58 59/* R: A/D high byte W: A/D range control */ 60#define PCL816_RANGE 9 61/* W: clear INT request */ 62#define PCL816_CLRINT 10 63/* R: next mux scan channel W: mux scan channel & range control pointer */ 64#define PCL816_MUX 11 65/* R/W: operation control register */ 66#define PCL816_CONTROL 12 67 68/* R: return status byte W: set DMA/IRQ */ 69#define PCL816_STATUS 13 70#define PCL816_STATUS_DRDY_MASK 0x80 71 72/* R: low byte of A/D W: soft A/D trigger */ 73#define PCL816_AD_LO 8 74/* R: high byte of A/D W: A/D range control */ 75#define PCL816_AD_HI 9 76 77/* type of interrupt handler */ 78#define INT_TYPE_AI1_INT 1 79#define INT_TYPE_AI1_DMA 2 80#define INT_TYPE_AI3_INT 4 81#define INT_TYPE_AI3_DMA 5 82#ifdef unused 83#define INT_TYPE_AI1_DMA_RTC 9 84#define INT_TYPE_AI3_DMA_RTC 10 85 86/* RTC stuff... */ 87#define RTC_IRQ 8 88#define RTC_IO_EXTENT 0x10 89#endif 90 91#define MAGIC_DMA_WORD 0x5a5a 92 93static const struct comedi_lrange range_pcl816 = { 8, { 94 BIP_RANGE(10), 95 BIP_RANGE(5), 96 BIP_RANGE(2.5), 97 BIP_RANGE(1.25), 98 UNI_RANGE(10), 99 UNI_RANGE(5), 100 UNI_RANGE(2.5), 101 UNI_RANGE(1.25), 102 } 103}; 104 105struct pcl816_board { 106 107 const char *name; /* board name */ 108 int n_ranges; /* len of range list */ 109 int n_aichan; /* num of A/D chans in diferencial mode */ 110 unsigned int ai_ns_min; /* minimal alllowed delay between samples (in ns) */ 111 int n_aochan; /* num of D/A chans */ 112 int n_dichan; /* num of DI chans */ 113 int n_dochan; /* num of DO chans */ 114 const struct comedi_lrange *ai_range_type; /* default A/D rangelist */ 115 const struct comedi_lrange *ao_range_type; /* default D/A rangelist */ 116 unsigned int io_range; /* len of IO space */ 117 unsigned int IRQbits; /* allowed interrupts */ 118 unsigned int DMAbits; /* allowed DMA chans */ 119 int ai_maxdata; /* maxdata for A/D */ 120 int ao_maxdata; /* maxdata for D/A */ 121 int ai_chanlist; /* allowed len of channel list A/D */ 122 int ao_chanlist; /* allowed len of channel list D/A */ 123 int i8254_osc_base; /* 1/frequency of on board oscilator in ns */ 124}; 125 126static const struct pcl816_board boardtypes[] = { 127 {"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816, 128 &range_pcl816, PCLx1x_RANGE, 129 0x00fc, /* IRQ mask */ 130 0x0a, /* DMA mask */ 131 0xffff, /* 16-bit card */ 132 0xffff, /* D/A maxdata */ 133 1024, 134 1, /* ao chan list */ 135 100}, 136 {"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816, 137 &range_pcl816, PCLx1x_RANGE, 138 0x00fc, 139 0x0a, 140 0x3fff, /* 14 bit card */ 141 0x3fff, 142 1024, 143 1, 144 100}, 145}; 146 147#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl816_board)) 148#define devpriv ((struct pcl816_private *)dev->private) 149#define this_board ((const struct pcl816_board *)dev->board_ptr) 150 151static int pcl816_attach(struct comedi_device *dev, 152 struct comedi_devconfig *it); 153static int pcl816_detach(struct comedi_device *dev); 154 155#ifdef unused 156static int RTC_lock = 0; /* RTC lock */ 157static int RTC_timer_lock = 0; /* RTC int lock */ 158#endif 159 160static struct comedi_driver driver_pcl816 = { 161 .driver_name = "pcl816", 162 .module = THIS_MODULE, 163 .attach = pcl816_attach, 164 .detach = pcl816_detach, 165 .board_name = &boardtypes[0].name, 166 .num_names = n_boardtypes, 167 .offset = sizeof(struct pcl816_board), 168}; 169 170COMEDI_INITCLEANUP(driver_pcl816); 171 172struct pcl816_private { 173 174 unsigned int dma; /* used DMA, 0=don't use DMA */ 175 int dma_rtc; /* 1=RTC used with DMA, 0=no RTC alloc */ 176#ifdef unused 177 unsigned long rtc_iobase; /* RTC port region */ 178 unsigned int rtc_iosize; 179 unsigned int rtc_irq; 180#endif 181 unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ 182 unsigned int dmapages[2]; /* len of DMA buffers in PAGE_SIZEs */ 183 unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ 184 unsigned int hwdmasize[2]; /* len of DMA buffers in Bytes */ 185 unsigned int dmasamplsize; /* size in samples hwdmasize[0]/2 */ 186 unsigned int last_top_dma; /* DMA pointer in last RTC int */ 187 int next_dma_buf; /* which DMA buffer will be used next round */ 188 long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ 189 unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ 190 191 unsigned int ai_scans; /* len of scanlist */ 192 unsigned char ai_neverending; /* if=1, then we do neverending record (you must use cancel()) */ 193 int irq_free; /* 1=have allocated IRQ */ 194 int irq_blocked; /* 1=IRQ now uses any subdev */ 195#ifdef unused 196 int rtc_irq_blocked; /* 1=we now do AI with DMA&RTC */ 197#endif 198 int irq_was_now_closed; /* when IRQ finish, there's stored int816_mode for last interrupt */ 199 int int816_mode; /* who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */ 200 struct comedi_subdevice *last_int_sub; /* ptr to subdevice which now finish */ 201 int ai_act_scan; /* how many scans we finished */ 202 unsigned int ai_act_chanlist[16]; /* MUX setting for actual AI operations */ 203 unsigned int ai_act_chanlist_len; /* how long is actual MUX list */ 204 unsigned int ai_act_chanlist_pos; /* actual position in MUX list */ 205 unsigned int ai_n_chan; /* how many channels per scan */ 206 unsigned int ai_poll_ptr; /* how many sampes transfer poll */ 207 struct comedi_subdevice *sub_ai; /* ptr to AI subdevice */ 208#ifdef unused 209 struct timer_list rtc_irq_timer; /* timer for RTC sanity check */ 210 unsigned long rtc_freq; /* RTC int freq */ 211#endif 212}; 213 214/* 215============================================================================== 216*/ 217static int check_and_setup_channel_list(struct comedi_device *dev, 218 struct comedi_subdevice *s, 219 unsigned int *chanlist, int chanlen); 220static int pcl816_ai_cancel(struct comedi_device *dev, 221 struct comedi_subdevice *s); 222static void start_pacer(struct comedi_device *dev, int mode, 223 unsigned int divisor1, unsigned int divisor2); 224#ifdef unused 225static int set_rtc_irq_bit(unsigned char bit); 226#endif 227 228static int pcl816_ai_cmdtest(struct comedi_device *dev, 229 struct comedi_subdevice *s, 230 struct comedi_cmd *cmd); 231static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); 232 233/* 234============================================================================== 235 ANALOG INPUT MODE0, 816 cards, slow version 236*/ 237static int pcl816_ai_insn_read(struct comedi_device *dev, 238 struct comedi_subdevice *s, 239 struct comedi_insn *insn, unsigned int *data) 240{ 241 int n; 242 int timeout; 243 244 DPRINTK("mode 0 analog input\n"); 245 /* software trigger, DMA and INT off */ 246 outb(0, dev->iobase + PCL816_CONTROL); 247 /* clear INT (conversion end) flag */ 248 outb(0, dev->iobase + PCL816_CLRINT); 249 250 /* Set the input channel */ 251 outb(CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX); 252 outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); /* select gain */ 253 254 for (n = 0; n < insn->n; n++) { 255 256 outb(0, dev->iobase + PCL816_AD_LO); /* start conversion */ 257 258 timeout = 100; 259 while (timeout--) { 260 if (!(inb(dev->iobase + PCL816_STATUS) & 261 PCL816_STATUS_DRDY_MASK)) { 262 /* return read value */ 263 data[n] = 264 ((inb(dev->iobase + 265 PCL816_AD_HI) << 8) | 266 (inb(dev->iobase + PCL816_AD_LO))); 267 268 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ 269 break; 270 } 271 udelay(1); 272 } 273 /* Return timeout error */ 274 if (!timeout) { 275 comedi_error(dev, "A/D insn timeout\n"); 276 data[0] = 0; 277 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT (conversion end) flag */ 278 return -EIO; 279 } 280 281 } 282 return n; 283} 284 285/* 286============================================================================== 287 analog input interrupt mode 1 & 3, 818 cards 288 one sample per interrupt version 289*/ 290static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d) 291{ 292 struct comedi_device *dev = d; 293 struct comedi_subdevice *s = dev->subdevices + 0; 294 int low, hi; 295 int timeout = 50; /* wait max 50us */ 296 297 while (timeout--) { 298 if (!(inb(dev->iobase + PCL816_STATUS) & 299 PCL816_STATUS_DRDY_MASK)) 300 break; 301 udelay(1); 302 } 303 if (!timeout) { /* timeout, bail error */ 304 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ 305 comedi_error(dev, "A/D mode1/3 IRQ without DRDY!"); 306 pcl816_ai_cancel(dev, s); 307 s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; 308 comedi_event(dev, s); 309 return IRQ_HANDLED; 310 311 } 312 313 /* get the sample */ 314 low = inb(dev->iobase + PCL816_AD_LO); 315 hi = inb(dev->iobase + PCL816_AD_HI); 316 317 comedi_buf_put(s->async, (hi << 8) | low); 318 319 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ 320 321 if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) 322 devpriv->ai_act_chanlist_pos = 0; 323 324 s->async->cur_chan++; 325 if (s->async->cur_chan >= devpriv->ai_n_chan) { 326 s->async->cur_chan = 0; 327 devpriv->ai_act_scan++; 328 } 329 330 if (!devpriv->ai_neverending) 331 if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */ 332 /* all data sampled */ 333 pcl816_ai_cancel(dev, s); 334 s->async->events |= COMEDI_CB_EOA; 335 } 336 comedi_event(dev, s); 337 return IRQ_HANDLED; 338} 339 340/* 341============================================================================== 342 analog input dma mode 1 & 3, 816 cards 343*/ 344static void transfer_from_dma_buf(struct comedi_device *dev, 345 struct comedi_subdevice *s, short *ptr, 346 unsigned int bufptr, unsigned int len) 347{ 348 int i; 349 350 s->async->events = 0; 351 352 for (i = 0; i < len; i++) { 353 354 comedi_buf_put(s->async, ptr[bufptr++]); 355 356 if (++devpriv->ai_act_chanlist_pos >= 357 devpriv->ai_act_chanlist_len) { 358 devpriv->ai_act_chanlist_pos = 0; 359 } 360 361 s->async->cur_chan++; 362 if (s->async->cur_chan >= devpriv->ai_n_chan) { 363 s->async->cur_chan = 0; 364 devpriv->ai_act_scan++; 365 } 366 367 if (!devpriv->ai_neverending) 368 if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data sampled */ 369 pcl816_ai_cancel(dev, s); 370 s->async->events |= COMEDI_CB_EOA; 371 s->async->events |= COMEDI_CB_BLOCK; 372 break; 373 } 374 } 375 376 comedi_event(dev, s); 377} 378 379static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d) 380{ 381 struct comedi_device *dev = d; 382 struct comedi_subdevice *s = dev->subdevices + 0; 383 int len, bufptr, this_dma_buf; 384 unsigned long dma_flags; 385 short *ptr; 386 387 disable_dma(devpriv->dma); 388 this_dma_buf = devpriv->next_dma_buf; 389 390 if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) { /* switch dma bufs */ 391 392 devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; 393 set_dma_mode(devpriv->dma, DMA_MODE_READ); 394 dma_flags = claim_dma_lock(); 395/* clear_dma_ff (devpriv->dma); */ 396 set_dma_addr(devpriv->dma, 397 devpriv->hwdmaptr[devpriv->next_dma_buf]); 398 if (devpriv->dma_runs_to_end) { 399 set_dma_count(devpriv->dma, 400 devpriv->hwdmasize[devpriv-> 401 next_dma_buf]); 402 } else { 403 set_dma_count(devpriv->dma, devpriv->last_dma_run); 404 } 405 release_dma_lock(dma_flags); 406 enable_dma(devpriv->dma); 407 } 408 409 devpriv->dma_runs_to_end--; 410 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ 411 412 ptr = (short *)devpriv->dmabuf[this_dma_buf]; 413 414 len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr; 415 bufptr = devpriv->ai_poll_ptr; 416 devpriv->ai_poll_ptr = 0; 417 418 transfer_from_dma_buf(dev, s, ptr, bufptr, len); 419 return IRQ_HANDLED; 420} 421 422/* 423============================================================================== 424 INT procedure 425*/ 426static irqreturn_t interrupt_pcl816(int irq, void *d) 427{ 428 struct comedi_device *dev = d; 429 DPRINTK("<I>"); 430 431 if (!dev->attached) { 432 comedi_error(dev, "premature interrupt"); 433 return IRQ_HANDLED; 434 } 435 436 switch (devpriv->int816_mode) { 437 case INT_TYPE_AI1_DMA: 438 case INT_TYPE_AI3_DMA: 439 return interrupt_pcl816_ai_mode13_dma(irq, d); 440 case INT_TYPE_AI1_INT: 441 case INT_TYPE_AI3_INT: 442 return interrupt_pcl816_ai_mode13_int(irq, d); 443 } 444 445 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ 446 if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) | 447 (!devpriv->int816_mode)) { 448 if (devpriv->irq_was_now_closed) { 449 devpriv->irq_was_now_closed = 0; 450 /* comedi_error(dev,"last IRQ.."); */ 451 return IRQ_HANDLED; 452 } 453 comedi_error(dev, "bad IRQ!"); 454 return IRQ_NONE; 455 } 456 comedi_error(dev, "IRQ from unknown source!"); 457 return IRQ_NONE; 458} 459 460/* 461============================================================================== 462 COMMAND MODE 463*/ 464static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd) 465{ 466 printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, 467 cmd->start_src, cmd->scan_begin_src, cmd->convert_src); 468 printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e, 469 cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); 470 printk("pcl816 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src, 471 cmd->scan_end_src); 472 printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e, 473 cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len); 474} 475 476/* 477============================================================================== 478*/ 479static int pcl816_ai_cmdtest(struct comedi_device *dev, 480 struct comedi_subdevice *s, struct comedi_cmd *cmd) 481{ 482 int err = 0; 483 int tmp, divisor1 = 0, divisor2 = 0; 484 485 DEBUG(printk("pcl816 pcl812_ai_cmdtest\n"); pcl816_cmdtest_out(-1, cmd); 486 ); 487 488 /* step 1: make sure trigger sources are trivially valid */ 489 tmp = cmd->start_src; 490 cmd->start_src &= TRIG_NOW; 491 if (!cmd->start_src || tmp != cmd->start_src) 492 err++; 493 494 tmp = cmd->scan_begin_src; 495 cmd->scan_begin_src &= TRIG_FOLLOW; 496 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 497 err++; 498 499 tmp = cmd->convert_src; 500 cmd->convert_src &= TRIG_EXT | TRIG_TIMER; 501 if (!cmd->convert_src || tmp != cmd->convert_src) 502 err++; 503 504 tmp = cmd->scan_end_src; 505 cmd->scan_end_src &= TRIG_COUNT; 506 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 507 err++; 508 509 tmp = cmd->stop_src; 510 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 511 if (!cmd->stop_src || tmp != cmd->stop_src) 512 err++; 513 514 if (err) { 515 return 1; 516 } 517 518 /* step 2: make sure trigger sources are unique and mutually compatible */ 519 520 if (cmd->start_src != TRIG_NOW) { 521 cmd->start_src = TRIG_NOW; 522 err++; 523 } 524 525 if (cmd->scan_begin_src != TRIG_FOLLOW) { 526 cmd->scan_begin_src = TRIG_FOLLOW; 527 err++; 528 } 529 530 if (cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_TIMER) { 531 cmd->convert_src = TRIG_TIMER; 532 err++; 533 } 534 535 if (cmd->scan_end_src != TRIG_COUNT) { 536 cmd->scan_end_src = TRIG_COUNT; 537 err++; 538 } 539 540 if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) 541 err++; 542 543 if (err) { 544 return 2; 545 } 546 547 /* step 3: make sure arguments are trivially compatible */ 548 if (cmd->start_arg != 0) { 549 cmd->start_arg = 0; 550 err++; 551 } 552 553 if (cmd->scan_begin_arg != 0) { 554 cmd->scan_begin_arg = 0; 555 err++; 556 } 557 if (cmd->convert_src == TRIG_TIMER) { 558 if (cmd->convert_arg < this_board->ai_ns_min) { 559 cmd->convert_arg = this_board->ai_ns_min; 560 err++; 561 } 562 } else { /* TRIG_EXT */ 563 if (cmd->convert_arg != 0) { 564 cmd->convert_arg = 0; 565 err++; 566 } 567 } 568 569 if (!cmd->chanlist_len) { 570 cmd->chanlist_len = 1; 571 err++; 572 } 573 if (cmd->chanlist_len > this_board->n_aichan) { 574 cmd->chanlist_len = this_board->n_aichan; 575 err++; 576 } 577 if (cmd->scan_end_arg != cmd->chanlist_len) { 578 cmd->scan_end_arg = cmd->chanlist_len; 579 err++; 580 } 581 if (cmd->stop_src == TRIG_COUNT) { 582 if (!cmd->stop_arg) { 583 cmd->stop_arg = 1; 584 err++; 585 } 586 } else { /* TRIG_NONE */ 587 if (cmd->stop_arg != 0) { 588 cmd->stop_arg = 0; 589 err++; 590 } 591 } 592 593 if (err) { 594 return 3; 595 } 596 597 /* step 4: fix up any arguments */ 598 if (cmd->convert_src == TRIG_TIMER) { 599 tmp = cmd->convert_arg; 600 i8253_cascade_ns_to_timer(this_board->i8254_osc_base, 601 &divisor1, &divisor2, 602 &cmd->convert_arg, 603 cmd->flags & TRIG_ROUND_MASK); 604 if (cmd->convert_arg < this_board->ai_ns_min) 605 cmd->convert_arg = this_board->ai_ns_min; 606 if (tmp != cmd->convert_arg) 607 err++; 608 } 609 610 if (err) { 611 return 4; 612 } 613 614 return 0; 615} 616 617static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 618{ 619 unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq; 620 struct comedi_cmd *cmd = &s->async->cmd; 621 622 if (cmd->start_src != TRIG_NOW) 623 return -EINVAL; 624 if (cmd->scan_begin_src != TRIG_FOLLOW) 625 return -EINVAL; 626 if (cmd->scan_end_src != TRIG_COUNT) 627 return -EINVAL; 628 if (cmd->scan_end_arg != cmd->chanlist_len) 629 return -EINVAL; 630/* if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; */ 631 if (devpriv->irq_blocked) 632 return -EBUSY; 633 634 if (cmd->convert_src == TRIG_TIMER) { 635 if (cmd->convert_arg < this_board->ai_ns_min) 636 cmd->convert_arg = this_board->ai_ns_min; 637 638 i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1, 639 &divisor2, &cmd->convert_arg, 640 cmd->flags & TRIG_ROUND_MASK); 641 if (divisor1 == 1) { /* PCL816 crash if any divisor is set to 1 */ 642 divisor1 = 2; 643 divisor2 /= 2; 644 } 645 if (divisor2 == 1) { 646 divisor2 = 2; 647 divisor1 /= 2; 648 } 649 } 650 651 start_pacer(dev, -1, 0, 0); /* stop pacer */ 652 653 if (!check_and_setup_channel_list(dev, s, cmd->chanlist, 654 cmd->chanlist_len)) 655 return -EINVAL; 656 udelay(1); 657 658 devpriv->ai_n_chan = cmd->chanlist_len; 659 devpriv->ai_act_scan = 0; 660 s->async->cur_chan = 0; 661 devpriv->irq_blocked = 1; 662 devpriv->ai_poll_ptr = 0; 663 devpriv->irq_was_now_closed = 0; 664 665 if (cmd->stop_src == TRIG_COUNT) { 666 devpriv->ai_scans = cmd->stop_arg; 667 devpriv->ai_neverending = 0; 668 } else { 669 devpriv->ai_scans = 0; 670 devpriv->ai_neverending = 1; 671 } 672 673 if ((cmd->flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? */ 674 printk("pl816: You wankt WAKE_EOS but I dont want handle it"); 675 /* devpriv->ai_eos=1; */ 676 /* if (devpriv->ai_n_chan==1) */ 677 /* devpriv->dma=0; // DMA is useless for this situation */ 678 } 679 680 if (devpriv->dma) { 681 bytes = devpriv->hwdmasize[0]; 682 if (!devpriv->ai_neverending) { 683 bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof(short); /* how many */ 684 devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0]; /* how many DMA pages we must fill */ 685 devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; /* on last dma transfer must be moved */ 686 devpriv->dma_runs_to_end--; 687 if (devpriv->dma_runs_to_end >= 0) 688 bytes = devpriv->hwdmasize[0]; 689 } else 690 devpriv->dma_runs_to_end = -1; 691 692 devpriv->next_dma_buf = 0; 693 set_dma_mode(devpriv->dma, DMA_MODE_READ); 694 dma_flags = claim_dma_lock(); 695 clear_dma_ff(devpriv->dma); 696 set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); 697 set_dma_count(devpriv->dma, bytes); 698 release_dma_lock(dma_flags); 699 enable_dma(devpriv->dma); 700 } 701 702 start_pacer(dev, 1, divisor1, divisor2); 703 dmairq = ((devpriv->dma & 0x3) << 4) | (dev->irq & 0x7); 704 705 switch (cmd->convert_src) { 706 case TRIG_TIMER: 707 devpriv->int816_mode = INT_TYPE_AI1_DMA; 708 outb(0x32, dev->iobase + PCL816_CONTROL); /* Pacer+IRQ+DMA */ 709 outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq and DMA to card */ 710 break; 711 712 default: 713 devpriv->int816_mode = INT_TYPE_AI3_DMA; 714 outb(0x34, dev->iobase + PCL816_CONTROL); /* Ext trig+IRQ+DMA */ 715 outb(dmairq, dev->iobase + PCL816_STATUS); /* write irq to card */ 716 break; 717 } 718 719 DPRINTK("pcl816 END: pcl812_ai_cmd()\n"); 720 return 0; 721} 722 723static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) 724{ 725 unsigned long flags; 726 unsigned int top1, top2, i; 727 728 if (!devpriv->dma) 729 return 0; /* poll is valid only for DMA transfer */ 730 731 spin_lock_irqsave(&dev->spinlock, flags); 732 733 for (i = 0; i < 20; i++) { 734 top1 = get_dma_residue(devpriv->dma); /* where is now DMA */ 735 top2 = get_dma_residue(devpriv->dma); 736 if (top1 == top2) 737 break; 738 } 739 if (top1 != top2) { 740 spin_unlock_irqrestore(&dev->spinlock, flags); 741 return 0; 742 } 743 744 top1 = devpriv->hwdmasize[0] - top1; /* where is now DMA in buffer */ 745 top1 >>= 1; /* sample position */ 746 top2 = top1 - devpriv->ai_poll_ptr; 747 if (top2 < 1) { /* no new samples */ 748 spin_unlock_irqrestore(&dev->spinlock, flags); 749 return 0; 750 } 751 752 transfer_from_dma_buf(dev, s, 753 (short *)devpriv->dmabuf[devpriv->next_dma_buf], 754 devpriv->ai_poll_ptr, top2); 755 756 devpriv->ai_poll_ptr = top1; /* new buffer position */ 757 spin_unlock_irqrestore(&dev->spinlock, flags); 758 759 return s->async->buf_write_count - s->async->buf_read_count; 760} 761 762/* 763============================================================================== 764 cancel any mode 1-4 AI 765*/ 766static int pcl816_ai_cancel(struct comedi_device *dev, 767 struct comedi_subdevice *s) 768{ 769/* DEBUG(printk("pcl816_ai_cancel()\n");) */ 770 771 if (devpriv->irq_blocked > 0) { 772 switch (devpriv->int816_mode) { 773#ifdef unused 774 case INT_TYPE_AI1_DMA_RTC: 775 case INT_TYPE_AI3_DMA_RTC: 776 set_rtc_irq_bit(0); /* stop RTC */ 777 del_timer(&devpriv->rtc_irq_timer); 778#endif 779 case INT_TYPE_AI1_DMA: 780 case INT_TYPE_AI3_DMA: 781 disable_dma(devpriv->dma); 782 case INT_TYPE_AI1_INT: 783 case INT_TYPE_AI3_INT: 784 outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL); /* Stop A/D */ 785 udelay(1); 786 outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ 787 outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ 788 outb(0x70, dev->iobase + PCL816_CTRCTL); 789 outb(0, dev->iobase + PCL816_AD_LO); 790 inb(dev->iobase + PCL816_AD_LO); 791 inb(dev->iobase + PCL816_AD_HI); 792 outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ 793 outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ 794 devpriv->irq_blocked = 0; 795 devpriv->irq_was_now_closed = devpriv->int816_mode; 796 devpriv->int816_mode = 0; 797 devpriv->last_int_sub = s; 798/* s->busy = 0; */ 799 break; 800 } 801 } 802 803 DEBUG(printk("comedi: pcl816_ai_cancel() successful\n");) 804 return 0; 805} 806 807/* 808============================================================================== 809 chech for PCL816 810*/ 811static int pcl816_check(unsigned long iobase) 812{ 813 outb(0x00, iobase + PCL816_MUX); 814 udelay(1); 815 if (inb(iobase + PCL816_MUX) != 0x00) 816 return 1; /* there isn't card */ 817 outb(0x55, iobase + PCL816_MUX); 818 udelay(1); 819 if (inb(iobase + PCL816_MUX) != 0x55) 820 return 1; /* there isn't card */ 821 outb(0x00, iobase + PCL816_MUX); 822 udelay(1); 823 outb(0x18, iobase + PCL816_CONTROL); 824 udelay(1); 825 if (inb(iobase + PCL816_CONTROL) != 0x18) 826 return 1; /* there isn't card */ 827 return 0; /* ok, card exist */ 828} 829 830/* 831============================================================================== 832 reset whole PCL-816 cards 833*/ 834static void pcl816_reset(struct comedi_device *dev) 835{ 836/* outb (0, dev->iobase + PCL818_DA_LO); DAC=0V */ 837/* outb (0, dev->iobase + PCL818_DA_HI); */ 838/* udelay (1); */ 839/* outb (0, dev->iobase + PCL818_DO_HI); DO=$0000 */ 840/* outb (0, dev->iobase + PCL818_DO_LO); */ 841/* udelay (1); */ 842 outb(0, dev->iobase + PCL816_CONTROL); 843 outb(0, dev->iobase + PCL816_MUX); 844 outb(0, dev->iobase + PCL816_CLRINT); 845 outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ 846 outb(0x70, dev->iobase + PCL816_CTRCTL); 847 outb(0x30, dev->iobase + PCL816_CTRCTL); 848 outb(0, dev->iobase + PCL816_RANGE); 849} 850 851/* 852============================================================================== 853 Start/stop pacer onboard pacer 854*/ 855static void 856start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1, 857 unsigned int divisor2) 858{ 859 outb(0x32, dev->iobase + PCL816_CTRCTL); 860 outb(0xff, dev->iobase + PCL816_CTR0); 861 outb(0x00, dev->iobase + PCL816_CTR0); 862 udelay(1); 863 outb(0xb4, dev->iobase + PCL816_CTRCTL); /* set counter 2 as mode 3 */ 864 outb(0x74, dev->iobase + PCL816_CTRCTL); /* set counter 1 as mode 3 */ 865 udelay(1); 866 867 if (mode == 1) { 868 DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1, 869 divisor2); 870 outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2); 871 outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2); 872 outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1); 873 outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1); 874 } 875 876 /* clear pending interrupts (just in case) */ 877/* outb(0, dev->iobase + PCL816_CLRINT); */ 878} 879 880/* 881============================================================================== 882 Check if channel list from user is builded correctly 883 If it's ok, then program scan/gain logic 884*/ 885static int 886check_and_setup_channel_list(struct comedi_device *dev, 887 struct comedi_subdevice *s, unsigned int *chanlist, 888 int chanlen) 889{ 890 unsigned int chansegment[16]; 891 unsigned int i, nowmustbechan, seglen, segpos; 892 893 /* correct channel and range number check itself comedi/range.c */ 894 if (chanlen < 1) { 895 comedi_error(dev, "range/channel list is empty!"); 896 return 0; 897 } 898 899 if (chanlen > 1) { 900 chansegment[0] = chanlist[0]; /* first channel is everytime ok */ 901 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { 902 /* build part of chanlist */ 903 DEBUG(printk("%d. %d %d\n", i, CR_CHAN(chanlist[i]), 904 CR_RANGE(chanlist[i]));) 905 if (chanlist[0] == chanlist[i]) 906 break; /* we detect loop, this must by finish */ 907 nowmustbechan = 908 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; 909 if (nowmustbechan != CR_CHAN(chanlist[i])) { 910 /* channel list isn't continous :-( */ 911 printk 912 ("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n", 913 dev->minor, i, CR_CHAN(chanlist[i]), 914 nowmustbechan, CR_CHAN(chanlist[0])); 915 return 0; 916 } 917 chansegment[i] = chanlist[i]; /* well, this is next correct channel in list */ 918 } 919 920 for (i = 0, segpos = 0; i < chanlen; i++) { /* check whole chanlist */ 921 DEBUG(printk("%d %d=%d %d\n", 922 CR_CHAN(chansegment[i % seglen]), 923 CR_RANGE(chansegment[i % seglen]), 924 CR_CHAN(chanlist[i]), 925 CR_RANGE(chanlist[i]));) 926 if (chanlist[i] != chansegment[i % seglen]) { 927 printk 928 ("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", 929 dev->minor, i, CR_CHAN(chansegment[i]), 930 CR_RANGE(chansegment[i]), 931 CR_AREF(chansegment[i]), 932 CR_CHAN(chanlist[i % seglen]), 933 CR_RANGE(chanlist[i % seglen]), 934 CR_AREF(chansegment[i % seglen])); 935 return 0; /* chan/gain list is strange */ 936 } 937 } 938 } else { 939 seglen = 1; 940 } 941 942 devpriv->ai_act_chanlist_len = seglen; 943 devpriv->ai_act_chanlist_pos = 0; 944 945 for (i = 0; i < seglen; i++) { /* store range list to card */ 946 devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]); 947 outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX); 948 outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); /* select gain */ 949 } 950 951 udelay(1); 952 953 outb(devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), dev->iobase + PCL816_MUX); /* select channel interval to scan */ 954 955 return 1; /* we can serve this with MUX logic */ 956} 957 958#ifdef unused 959/* 960============================================================================== 961 Enable(1)/disable(0) periodic interrupts from RTC 962*/ 963static int set_rtc_irq_bit(unsigned char bit) 964{ 965 unsigned char val; 966 unsigned long flags; 967 968 if (bit == 1) { 969 RTC_timer_lock++; 970 if (RTC_timer_lock > 1) 971 return 0; 972 } else { 973 RTC_timer_lock--; 974 if (RTC_timer_lock < 0) 975 RTC_timer_lock = 0; 976 if (RTC_timer_lock > 0) 977 return 0; 978 } 979 980 save_flags(flags); 981 cli(); 982 val = CMOS_READ(RTC_CONTROL); 983 if (bit) { 984 val |= RTC_PIE; 985 } else { 986 val &= ~RTC_PIE; 987 } 988 CMOS_WRITE(val, RTC_CONTROL); 989 CMOS_READ(RTC_INTR_FLAGS); 990 restore_flags(flags); 991 return 0; 992} 993#endif 994 995/* 996============================================================================== 997 Free any resources that we have claimed 998*/ 999static void free_resources(struct comedi_device *dev) 1000{ 1001 /* printk("free_resource()\n"); */ 1002 if (dev->private) { 1003 pcl816_ai_cancel(dev, devpriv->sub_ai); 1004 pcl816_reset(dev); 1005 if (devpriv->dma) 1006 free_dma(devpriv->dma); 1007 if (devpriv->dmabuf[0]) 1008 free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]); 1009 if (devpriv->dmabuf[1]) 1010 free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]); 1011#ifdef unused 1012 if (devpriv->rtc_irq) 1013 free_irq(devpriv->rtc_irq, dev); 1014 if ((devpriv->dma_rtc) && (RTC_lock == 1)) { 1015 if (devpriv->rtc_iobase) 1016 release_region(devpriv->rtc_iobase, 1017 devpriv->rtc_iosize); 1018 } 1019#endif 1020 } 1021 1022 if (dev->irq) 1023 free_irq(dev->irq, dev); 1024 if (dev->iobase) 1025 release_region(dev->iobase, this_board->io_range); 1026 /* printk("free_resource() end\n"); */ 1027} 1028 1029/* 1030============================================================================== 1031 1032 Initialization 1033 1034*/ 1035static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) 1036{ 1037 int ret; 1038 unsigned long iobase; 1039 unsigned int irq, dma; 1040 unsigned long pages; 1041 /* int i; */ 1042 struct comedi_subdevice *s; 1043 1044 /* claim our I/O space */ 1045 iobase = it->options[0]; 1046 printk("comedi%d: pcl816: board=%s, ioport=0x%03lx", dev->minor, 1047 this_board->name, iobase); 1048 1049 if (!request_region(iobase, this_board->io_range, "pcl816")) { 1050 printk("I/O port conflict\n"); 1051 return -EIO; 1052 } 1053 1054 dev->iobase = iobase; 1055 1056 if (pcl816_check(iobase)) { 1057 printk(", I cann't detect board. FAIL!\n"); 1058 return -EIO; 1059 } 1060 1061 ret = alloc_private(dev, sizeof(struct pcl816_private)); 1062 if (ret < 0) 1063 return ret; /* Can't alloc mem */ 1064 1065 /* set up some name stuff */ 1066 dev->board_name = this_board->name; 1067 1068 /* grab our IRQ */ 1069 irq = 0; 1070 if (this_board->IRQbits != 0) { /* board support IRQ */ 1071 irq = it->options[1]; 1072 if (irq) { /* we want to use IRQ */ 1073 if (((1 << irq) & this_board->IRQbits) == 0) { 1074 printk 1075 (", IRQ %u is out of allowed range, DISABLING IT", 1076 irq); 1077 irq = 0; /* Bad IRQ */ 1078 } else { 1079 if (request_irq 1080 (irq, interrupt_pcl816, 0, "pcl816", dev)) { 1081 printk 1082 (", unable to allocate IRQ %u, DISABLING IT", 1083 irq); 1084 irq = 0; /* Can't use IRQ */ 1085 } else { 1086 printk(", irq=%u", irq); 1087 } 1088 } 1089 } 1090 } 1091 1092 dev->irq = irq; 1093 if (irq) { 1094 devpriv->irq_free = 1; 1095 } /* 1=we have allocated irq */ 1096 else { 1097 devpriv->irq_free = 0; 1098 } 1099 devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ 1100 devpriv->int816_mode = 0; /* mode of irq */ 1101 1102#ifdef unused 1103 /* grab RTC for DMA operations */ 1104 devpriv->dma_rtc = 0; 1105 if (it->options[2] > 0) { /* we want to use DMA */ 1106 if (RTC_lock == 0) { 1107 if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, 1108 "pcl816 (RTC)")) 1109 goto no_rtc; 1110 } 1111 devpriv->rtc_iobase = RTC_PORT(0); 1112 devpriv->rtc_iosize = RTC_IO_EXTENT; 1113 RTC_lock++; 1114#ifdef UNTESTED_CODE 1115 if (!request_irq(RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, 0, 1116 "pcl816 DMA (RTC)", dev)) { 1117 devpriv->dma_rtc = 1; 1118 devpriv->rtc_irq = RTC_IRQ; 1119 printk(", dma_irq=%u", devpriv->rtc_irq); 1120 } else { 1121 RTC_lock--; 1122 if (RTC_lock == 0) { 1123 if (devpriv->rtc_iobase) 1124 release_region(devpriv->rtc_iobase, 1125 devpriv->rtc_iosize); 1126 } 1127 devpriv->rtc_iobase = 0; 1128 devpriv->rtc_iosize = 0; 1129 } 1130#else 1131 printk("pcl816: RTC code missing"); 1132#endif 1133 1134 } 1135 1136no_rtc: 1137#endif 1138 /* grab our DMA */ 1139 dma = 0; 1140 devpriv->dma = dma; 1141 if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0)) 1142 goto no_dma; /* if we haven't IRQ, we can't use DMA */ 1143 1144 if (this_board->DMAbits != 0) { /* board support DMA */ 1145 dma = it->options[2]; 1146 if (dma < 1) 1147 goto no_dma; /* DMA disabled */ 1148 1149 if (((1 << dma) & this_board->DMAbits) == 0) { 1150 printk(", DMA is out of allowed range, FAIL!\n"); 1151 return -EINVAL; /* Bad DMA */ 1152 } 1153 ret = request_dma(dma, "pcl816"); 1154 if (ret) { 1155 printk(", unable to allocate DMA %u, FAIL!\n", dma); 1156 return -EBUSY; /* DMA isn't free */ 1157 } 1158 1159 devpriv->dma = dma; 1160 printk(", dma=%u", dma); 1161 pages = 2; /* we need 16KB */ 1162 devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); 1163 1164 if (!devpriv->dmabuf[0]) { 1165 printk(", unable to allocate DMA buffer, FAIL!\n"); 1166 /* maybe experiment with try_to_free_pages() will help .... */ 1167 return -EBUSY; /* no buffer :-( */ 1168 } 1169 devpriv->dmapages[0] = pages; 1170 devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]); 1171 devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE; 1172 /* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */ 1173 1174 if (devpriv->dma_rtc == 0) { /* we must do duble buff :-( */ 1175 devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); 1176 if (!devpriv->dmabuf[1]) { 1177 printk 1178 (", unable to allocate DMA buffer, FAIL!\n"); 1179 return -EBUSY; 1180 } 1181 devpriv->dmapages[1] = pages; 1182 devpriv->hwdmaptr[1] = 1183 virt_to_bus((void *)devpriv->dmabuf[1]); 1184 devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE; 1185 } 1186 } 1187 1188no_dma: 1189 1190/* if (this_board->n_aochan > 0) 1191 subdevs[1] = COMEDI_SUBD_AO; 1192 if (this_board->n_dichan > 0) 1193 subdevs[2] = COMEDI_SUBD_DI; 1194 if (this_board->n_dochan > 0) 1195 subdevs[3] = COMEDI_SUBD_DO; 1196*/ 1197 1198 ret = alloc_subdevices(dev, 1); 1199 if (ret < 0) 1200 return ret; 1201 1202 s = dev->subdevices + 0; 1203 if (this_board->n_aichan > 0) { 1204 s->type = COMEDI_SUBD_AI; 1205 devpriv->sub_ai = s; 1206 dev->read_subdev = s; 1207 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 1208 s->n_chan = this_board->n_aichan; 1209 s->subdev_flags |= SDF_DIFF; 1210 /* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */ 1211 s->maxdata = this_board->ai_maxdata; 1212 s->len_chanlist = this_board->ai_chanlist; 1213 s->range_table = this_board->ai_range_type; 1214 s->cancel = pcl816_ai_cancel; 1215 s->do_cmdtest = pcl816_ai_cmdtest; 1216 s->do_cmd = pcl816_ai_cmd; 1217 s->poll = pcl816_ai_poll; 1218 s->insn_read = pcl816_ai_insn_read; 1219 } else { 1220 s->type = COMEDI_SUBD_UNUSED; 1221 } 1222 1223#if 0 1224case COMEDI_SUBD_AO: 1225 s->subdev_flags = SDF_WRITABLE | SDF_GROUND; 1226 s->n_chan = this_board->n_aochan; 1227 s->maxdata = this_board->ao_maxdata; 1228 s->len_chanlist = this_board->ao_chanlist; 1229 s->range_table = this_board->ao_range_type; 1230 break; 1231 1232case COMEDI_SUBD_DI: 1233 s->subdev_flags = SDF_READABLE; 1234 s->n_chan = this_board->n_dichan; 1235 s->maxdata = 1; 1236 s->len_chanlist = this_board->n_dichan; 1237 s->range_table = &range_digital; 1238 break; 1239 1240case COMEDI_SUBD_DO: 1241 s->subdev_flags = SDF_WRITABLE; 1242 s->n_chan = this_board->n_dochan; 1243 s->maxdata = 1; 1244 s->len_chanlist = this_board->n_dochan; 1245 s->range_table = &range_digital; 1246 break; 1247#endif 1248 1249 pcl816_reset(dev); 1250 1251 printk("\n"); 1252 1253 return 0; 1254} 1255 1256/* 1257============================================================================== 1258 Removes device 1259 */ 1260static int pcl816_detach(struct comedi_device *dev) 1261{ 1262 DEBUG(printk("comedi%d: pcl816: remove\n", dev->minor);) 1263 free_resources(dev); 1264#ifdef unused 1265 if (devpriv->dma_rtc) 1266 RTC_lock--; 1267#endif 1268 return 0; 1269} 1270