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