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