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