1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * pcl816.c 4 * Comedi driver for Advantech PCL-816 cards 5 * 6 * Author: Juan Grigera <juan@grigera.com.ar> 7 * based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 8 */ 9 10 /* 11 * Driver: pcl816 12 * Description: Advantech PCL-816 cards, PCL-814 13 * Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) 14 * Author: Juan Grigera <juan@grigera.com.ar> 15 * Status: works 16 * Updated: Tue, 2 Apr 2002 23:15:21 -0800 17 * 18 * PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. 19 * Differences are at resolution (16 vs 12 bits). 20 * 21 * The driver support AI command mode, other subdevices not written. 22 * 23 * Analog output and digital input and output are not supported. 24 * 25 * Configuration Options: 26 * [0] - IO Base 27 * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) 28 * [2] - DMA (0=disable, 1, 3) 29 * [3] - 0, 10=10MHz clock for 8254 30 * 1= 1MHz clock for 8254 31 */ 32 33 #include <linux/module.h> 34 #include <linux/gfp.h> 35 #include <linux/delay.h> 36 #include <linux/io.h> 37 #include <linux/interrupt.h> 38 #include <linux/comedi/comedidev.h> 39 #include <linux/comedi/comedi_8254.h> 40 #include <linux/comedi/comedi_isadma.h> 41 42 /* 43 * Register I/O map 44 */ 45 #define PCL816_DO_DI_LSB_REG 0x00 46 #define PCL816_DO_DI_MSB_REG 0x01 47 #define PCL816_TIMER_BASE 0x04 48 #define PCL816_AI_LSB_REG 0x08 49 #define PCL816_AI_MSB_REG 0x09 50 #define PCL816_RANGE_REG 0x09 51 #define PCL816_CLRINT_REG 0x0a 52 #define PCL816_MUX_REG 0x0b 53 #define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first)) 54 #define PCL816_CTRL_REG 0x0c 55 #define PCL816_CTRL_SOFT_TRIG BIT(0) 56 #define PCL816_CTRL_PACER_TRIG BIT(1) 57 #define PCL816_CTRL_EXT_TRIG BIT(2) 58 #define PCL816_CTRL_POE BIT(3) 59 #define PCL816_CTRL_DMAEN BIT(4) 60 #define PCL816_CTRL_INTEN BIT(5) 61 #define PCL816_CTRL_DMASRC_SLOT(x) (((x) & 0x3) << 6) 62 #define PCL816_STATUS_REG 0x0d 63 #define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0) 64 #define PCL816_STATUS_INTSRC_SLOT(x) (((x) & 0x3) << 4) 65 #define PCL816_STATUS_INTSRC_DMA PCL816_STATUS_INTSRC_SLOT(3) 66 #define PCL816_STATUS_INTSRC_MASK PCL816_STATUS_INTSRC_SLOT(3) 67 #define PCL816_STATUS_INTACT BIT(6) 68 #define PCL816_STATUS_DRDY BIT(7) 69 70 #define MAGIC_DMA_WORD 0x5a5a 71 72 static const struct comedi_lrange range_pcl816 = { 73 8, { 74 BIP_RANGE(10), 75 BIP_RANGE(5), 76 BIP_RANGE(2.5), 77 BIP_RANGE(1.25), 78 UNI_RANGE(10), 79 UNI_RANGE(5), 80 UNI_RANGE(2.5), 81 UNI_RANGE(1.25) 82 } 83 }; 84 85 struct pcl816_board { 86 const char *name; 87 int ai_maxdata; 88 int ai_chanlist; 89 }; 90 91 static const struct pcl816_board boardtypes[] = { 92 { 93 .name = "pcl816", 94 .ai_maxdata = 0xffff, 95 .ai_chanlist = 1024, 96 }, { 97 .name = "pcl814b", 98 .ai_maxdata = 0x3fff, 99 .ai_chanlist = 1024, 100 }, 101 }; 102 103 struct pcl816_private { 104 struct comedi_isadma *dma; 105 unsigned int ai_poll_ptr; /* how many sampes transfer poll */ 106 unsigned int ai_cmd_running:1; 107 unsigned int ai_cmd_canceled:1; 108 }; 109 110 static void pcl816_ai_setup_dma(struct comedi_device *dev, 111 struct comedi_subdevice *s, 112 unsigned int unread_samples) 113 { 114 struct pcl816_private *devpriv = dev->private; 115 struct comedi_isadma *dma = devpriv->dma; 116 struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; 117 unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize); 118 unsigned int nsamples; 119 120 comedi_isadma_disable(dma->chan); 121 122 /* 123 * Determine dma size based on the buffer maxsize plus the number of 124 * unread samples and the number of samples remaining in the command. 125 */ 126 nsamples = comedi_nsamples_left(s, max_samples + unread_samples); 127 if (nsamples > unread_samples) { 128 nsamples -= unread_samples; 129 desc->size = comedi_samples_to_bytes(s, nsamples); 130 comedi_isadma_program(desc); 131 } 132 } 133 134 static void pcl816_ai_set_chan_range(struct comedi_device *dev, 135 unsigned int chan, 136 unsigned int range) 137 { 138 outb(chan, dev->iobase + PCL816_MUX_REG); 139 outb(range, dev->iobase + PCL816_RANGE_REG); 140 } 141 142 static void pcl816_ai_set_chan_scan(struct comedi_device *dev, 143 unsigned int first_chan, 144 unsigned int last_chan) 145 { 146 outb(PCL816_MUX_SCAN(first_chan, last_chan), 147 dev->iobase + PCL816_MUX_REG); 148 } 149 150 static void pcl816_ai_setup_chanlist(struct comedi_device *dev, 151 unsigned int *chanlist, 152 unsigned int seglen) 153 { 154 unsigned int first_chan = CR_CHAN(chanlist[0]); 155 unsigned int last_chan; 156 unsigned int range; 157 unsigned int i; 158 159 /* store range list to card */ 160 for (i = 0; i < seglen; i++) { 161 last_chan = CR_CHAN(chanlist[i]); 162 range = CR_RANGE(chanlist[i]); 163 164 pcl816_ai_set_chan_range(dev, last_chan, range); 165 } 166 167 udelay(1); 168 169 pcl816_ai_set_chan_scan(dev, first_chan, last_chan); 170 } 171 172 static void pcl816_ai_clear_eoc(struct comedi_device *dev) 173 { 174 /* writing any value clears the interrupt request */ 175 outb(0, dev->iobase + PCL816_CLRINT_REG); 176 } 177 178 static void pcl816_ai_soft_trig(struct comedi_device *dev) 179 { 180 /* writing any value triggers a software conversion */ 181 outb(0, dev->iobase + PCL816_AI_LSB_REG); 182 } 183 184 static unsigned int pcl816_ai_get_sample(struct comedi_device *dev, 185 struct comedi_subdevice *s) 186 { 187 unsigned int val; 188 189 val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8; 190 val |= inb(dev->iobase + PCL816_AI_LSB_REG); 191 192 return val & s->maxdata; 193 } 194 195 static int pcl816_ai_eoc(struct comedi_device *dev, 196 struct comedi_subdevice *s, 197 struct comedi_insn *insn, 198 unsigned long context) 199 { 200 unsigned int status; 201 202 status = inb(dev->iobase + PCL816_STATUS_REG); 203 if ((status & PCL816_STATUS_DRDY) == 0) 204 return 0; 205 return -EBUSY; 206 } 207 208 static bool pcl816_ai_next_chan(struct comedi_device *dev, 209 struct comedi_subdevice *s) 210 { 211 struct comedi_cmd *cmd = &s->async->cmd; 212 213 if (cmd->stop_src == TRIG_COUNT && 214 s->async->scans_done >= cmd->stop_arg) { 215 s->async->events |= COMEDI_CB_EOA; 216 return false; 217 } 218 219 return true; 220 } 221 222 static void transfer_from_dma_buf(struct comedi_device *dev, 223 struct comedi_subdevice *s, 224 unsigned short *ptr, 225 unsigned int bufptr, unsigned int len) 226 { 227 unsigned short val; 228 int i; 229 230 for (i = 0; i < len; i++) { 231 val = ptr[bufptr++]; 232 comedi_buf_write_samples(s, &val, 1); 233 234 if (!pcl816_ai_next_chan(dev, s)) 235 return; 236 } 237 } 238 239 static irqreturn_t pcl816_interrupt(int irq, void *d) 240 { 241 struct comedi_device *dev = d; 242 struct comedi_subdevice *s = dev->read_subdev; 243 struct pcl816_private *devpriv = dev->private; 244 struct comedi_isadma *dma = devpriv->dma; 245 struct comedi_isadma_desc *desc; 246 unsigned int nsamples; 247 unsigned int bufptr; 248 249 if (!dev->attached || !devpriv->ai_cmd_running) { 250 pcl816_ai_clear_eoc(dev); 251 return IRQ_HANDLED; 252 } 253 254 if (devpriv->ai_cmd_canceled) { 255 devpriv->ai_cmd_canceled = 0; 256 pcl816_ai_clear_eoc(dev); 257 return IRQ_HANDLED; 258 } 259 260 desc = &dma->desc[dma->cur_dma]; 261 nsamples = comedi_bytes_to_samples(s, desc->size) - 262 devpriv->ai_poll_ptr; 263 bufptr = devpriv->ai_poll_ptr; 264 devpriv->ai_poll_ptr = 0; 265 266 /* restart dma with the next buffer */ 267 dma->cur_dma = 1 - dma->cur_dma; 268 pcl816_ai_setup_dma(dev, s, nsamples); 269 270 transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples); 271 272 pcl816_ai_clear_eoc(dev); 273 274 comedi_handle_events(dev, s); 275 return IRQ_HANDLED; 276 } 277 278 static int check_channel_list(struct comedi_device *dev, 279 struct comedi_subdevice *s, 280 unsigned int *chanlist, 281 unsigned int chanlen) 282 { 283 unsigned int chansegment[16]; 284 unsigned int i, nowmustbechan, seglen; 285 286 /* correct channel and range number check itself comedi/range.c */ 287 if (chanlen < 1) { 288 dev_err(dev->class_dev, "range/channel list is empty!\n"); 289 return 0; 290 } 291 292 if (chanlen > 1) { 293 /* first channel is every time ok */ 294 chansegment[0] = chanlist[0]; 295 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { 296 /* we detect loop, this must by finish */ 297 if (chanlist[0] == chanlist[i]) 298 break; 299 nowmustbechan = 300 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; 301 if (nowmustbechan != CR_CHAN(chanlist[i])) { 302 /* channel list isn't continuous :-( */ 303 dev_dbg(dev->class_dev, 304 "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", 305 i, CR_CHAN(chanlist[i]), nowmustbechan, 306 CR_CHAN(chanlist[0])); 307 return 0; 308 } 309 /* well, this is next correct channel in list */ 310 chansegment[i] = chanlist[i]; 311 } 312 313 /* check whole chanlist */ 314 for (i = 0; i < chanlen; i++) { 315 if (chanlist[i] != chansegment[i % seglen]) { 316 dev_dbg(dev->class_dev, 317 "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", 318 i, CR_CHAN(chansegment[i]), 319 CR_RANGE(chansegment[i]), 320 CR_AREF(chansegment[i]), 321 CR_CHAN(chanlist[i % seglen]), 322 CR_RANGE(chanlist[i % seglen]), 323 CR_AREF(chansegment[i % seglen])); 324 return 0; /* chan/gain list is strange */ 325 } 326 } 327 } else { 328 seglen = 1; 329 } 330 331 return seglen; /* we can serve this with MUX logic */ 332 } 333 334 static int pcl816_ai_cmdtest(struct comedi_device *dev, 335 struct comedi_subdevice *s, struct comedi_cmd *cmd) 336 { 337 int err = 0; 338 339 /* Step 1 : check if triggers are trivially valid */ 340 341 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 342 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); 343 err |= comedi_check_trigger_src(&cmd->convert_src, 344 TRIG_EXT | TRIG_TIMER); 345 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 346 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 347 348 if (err) 349 return 1; 350 351 /* Step 2a : make sure trigger sources are unique */ 352 353 err |= comedi_check_trigger_is_unique(cmd->convert_src); 354 err |= comedi_check_trigger_is_unique(cmd->stop_src); 355 356 /* Step 2b : and mutually compatible */ 357 358 if (err) 359 return 2; 360 361 /* Step 3: check if arguments are trivially valid */ 362 363 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 364 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 365 366 if (cmd->convert_src == TRIG_TIMER) 367 err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); 368 else /* TRIG_EXT */ 369 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 370 371 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 372 cmd->chanlist_len); 373 374 if (cmd->stop_src == TRIG_COUNT) 375 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 376 else /* TRIG_NONE */ 377 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 378 379 if (err) 380 return 3; 381 382 /* step 4: fix up any arguments */ 383 if (cmd->convert_src == TRIG_TIMER) { 384 unsigned int arg = cmd->convert_arg; 385 386 comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); 387 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); 388 } 389 390 if (err) 391 return 4; 392 393 /* step 5: complain about special chanlist considerations */ 394 395 if (cmd->chanlist) { 396 if (!check_channel_list(dev, s, cmd->chanlist, 397 cmd->chanlist_len)) 398 return 5; /* incorrect channels list */ 399 } 400 401 return 0; 402 } 403 404 static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 405 { 406 struct pcl816_private *devpriv = dev->private; 407 struct comedi_isadma *dma = devpriv->dma; 408 struct comedi_cmd *cmd = &s->async->cmd; 409 unsigned int ctrl; 410 unsigned int seglen; 411 412 if (devpriv->ai_cmd_running) 413 return -EBUSY; 414 415 seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); 416 if (seglen < 1) 417 return -EINVAL; 418 pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen); 419 udelay(1); 420 421 devpriv->ai_cmd_running = 1; 422 devpriv->ai_poll_ptr = 0; 423 devpriv->ai_cmd_canceled = 0; 424 425 /* setup and enable dma for the first buffer */ 426 dma->cur_dma = 0; 427 pcl816_ai_setup_dma(dev, s, 0); 428 429 comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY); 430 comedi_8254_write(dev->pacer, 0, 0x0ff); 431 udelay(1); 432 comedi_8254_update_divisors(dev->pacer); 433 comedi_8254_pacer_enable(dev->pacer, 1, 2, true); 434 435 ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | 436 PCL816_CTRL_DMASRC_SLOT(0); 437 if (cmd->convert_src == TRIG_TIMER) 438 ctrl |= PCL816_CTRL_PACER_TRIG; 439 else /* TRIG_EXT */ 440 ctrl |= PCL816_CTRL_EXT_TRIG; 441 442 outb(ctrl, dev->iobase + PCL816_CTRL_REG); 443 outb((dma->chan << 4) | dev->irq, 444 dev->iobase + PCL816_STATUS_REG); 445 446 return 0; 447 } 448 449 static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) 450 { 451 struct pcl816_private *devpriv = dev->private; 452 struct comedi_isadma *dma = devpriv->dma; 453 struct comedi_isadma_desc *desc; 454 unsigned long flags; 455 unsigned int poll; 456 int ret; 457 458 spin_lock_irqsave(&dev->spinlock, flags); 459 460 poll = comedi_isadma_poll(dma); 461 poll = comedi_bytes_to_samples(s, poll); 462 if (poll > devpriv->ai_poll_ptr) { 463 desc = &dma->desc[dma->cur_dma]; 464 transfer_from_dma_buf(dev, s, desc->virt_addr, 465 devpriv->ai_poll_ptr, 466 poll - devpriv->ai_poll_ptr); 467 /* new buffer position */ 468 devpriv->ai_poll_ptr = poll; 469 470 comedi_handle_events(dev, s); 471 472 ret = comedi_buf_n_bytes_ready(s); 473 } else { 474 /* no new samples */ 475 ret = 0; 476 } 477 spin_unlock_irqrestore(&dev->spinlock, flags); 478 479 return ret; 480 } 481 482 static int pcl816_ai_cancel(struct comedi_device *dev, 483 struct comedi_subdevice *s) 484 { 485 struct pcl816_private *devpriv = dev->private; 486 487 if (!devpriv->ai_cmd_running) 488 return 0; 489 490 outb(0, dev->iobase + PCL816_CTRL_REG); 491 pcl816_ai_clear_eoc(dev); 492 493 comedi_8254_pacer_enable(dev->pacer, 1, 2, false); 494 495 devpriv->ai_cmd_running = 0; 496 devpriv->ai_cmd_canceled = 1; 497 498 return 0; 499 } 500 501 static int pcl816_ai_insn_read(struct comedi_device *dev, 502 struct comedi_subdevice *s, 503 struct comedi_insn *insn, 504 unsigned int *data) 505 { 506 unsigned int chan = CR_CHAN(insn->chanspec); 507 unsigned int range = CR_RANGE(insn->chanspec); 508 int ret = 0; 509 int i; 510 511 outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG); 512 513 pcl816_ai_set_chan_range(dev, chan, range); 514 pcl816_ai_set_chan_scan(dev, chan, chan); 515 516 for (i = 0; i < insn->n; i++) { 517 pcl816_ai_clear_eoc(dev); 518 pcl816_ai_soft_trig(dev); 519 520 ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0); 521 if (ret) 522 break; 523 524 data[i] = pcl816_ai_get_sample(dev, s); 525 } 526 outb(0, dev->iobase + PCL816_CTRL_REG); 527 pcl816_ai_clear_eoc(dev); 528 529 return ret ? ret : insn->n; 530 } 531 532 static int pcl816_di_insn_bits(struct comedi_device *dev, 533 struct comedi_subdevice *s, 534 struct comedi_insn *insn, 535 unsigned int *data) 536 { 537 data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) | 538 (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8); 539 540 return insn->n; 541 } 542 543 static int pcl816_do_insn_bits(struct comedi_device *dev, 544 struct comedi_subdevice *s, 545 struct comedi_insn *insn, 546 unsigned int *data) 547 { 548 if (comedi_dio_update_state(s, data)) { 549 outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG); 550 outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG); 551 } 552 553 data[1] = s->state; 554 555 return insn->n; 556 } 557 558 static void pcl816_reset(struct comedi_device *dev) 559 { 560 outb(0, dev->iobase + PCL816_CTRL_REG); 561 pcl816_ai_set_chan_range(dev, 0, 0); 562 pcl816_ai_clear_eoc(dev); 563 564 /* set all digital outputs low */ 565 outb(0, dev->iobase + PCL816_DO_DI_LSB_REG); 566 outb(0, dev->iobase + PCL816_DO_DI_MSB_REG); 567 } 568 569 static void pcl816_alloc_irq_and_dma(struct comedi_device *dev, 570 struct comedi_devconfig *it) 571 { 572 struct pcl816_private *devpriv = dev->private; 573 unsigned int irq_num = it->options[1]; 574 unsigned int dma_chan = it->options[2]; 575 576 /* only IRQs 2-7 and DMA channels 3 and 1 are valid */ 577 if (!(irq_num >= 2 && irq_num <= 7) || 578 !(dma_chan == 3 || dma_chan == 1)) 579 return; 580 581 if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev)) 582 return; 583 584 /* DMA uses two 16K buffers */ 585 devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, 586 PAGE_SIZE * 4, COMEDI_ISADMA_READ); 587 if (!devpriv->dma) 588 free_irq(irq_num, dev); 589 else 590 dev->irq = irq_num; 591 } 592 593 static void pcl816_free_dma(struct comedi_device *dev) 594 { 595 struct pcl816_private *devpriv = dev->private; 596 597 if (devpriv) 598 comedi_isadma_free(devpriv->dma); 599 } 600 601 static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) 602 { 603 const struct pcl816_board *board = dev->board_ptr; 604 struct pcl816_private *devpriv; 605 struct comedi_subdevice *s; 606 int ret; 607 608 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 609 if (!devpriv) 610 return -ENOMEM; 611 612 ret = comedi_check_request_region(dev, it->options[0], 0x10, 613 0, 0x3ff, 16); 614 if (ret) 615 return ret; 616 617 /* an IRQ and DMA are required to support async commands */ 618 pcl816_alloc_irq_and_dma(dev, it); 619 620 dev->pacer = comedi_8254_io_alloc(dev->iobase + PCL816_TIMER_BASE, 621 I8254_OSC_BASE_10MHZ, I8254_IO8, 0); 622 if (IS_ERR(dev->pacer)) 623 return PTR_ERR(dev->pacer); 624 625 ret = comedi_alloc_subdevices(dev, 4); 626 if (ret) 627 return ret; 628 629 s = &dev->subdevices[0]; 630 s->type = COMEDI_SUBD_AI; 631 s->subdev_flags = SDF_CMD_READ | SDF_DIFF; 632 s->n_chan = 16; 633 s->maxdata = board->ai_maxdata; 634 s->range_table = &range_pcl816; 635 s->insn_read = pcl816_ai_insn_read; 636 if (dev->irq) { 637 dev->read_subdev = s; 638 s->subdev_flags |= SDF_CMD_READ; 639 s->len_chanlist = board->ai_chanlist; 640 s->do_cmdtest = pcl816_ai_cmdtest; 641 s->do_cmd = pcl816_ai_cmd; 642 s->poll = pcl816_ai_poll; 643 s->cancel = pcl816_ai_cancel; 644 } 645 646 /* Piggyback Slot1 subdevice */ 647 s = &dev->subdevices[1]; 648 s->type = COMEDI_SUBD_UNUSED; 649 650 /* Digital Input subdevice */ 651 s = &dev->subdevices[2]; 652 s->type = COMEDI_SUBD_DI; 653 s->subdev_flags = SDF_READABLE; 654 s->n_chan = 16; 655 s->maxdata = 1; 656 s->range_table = &range_digital; 657 s->insn_bits = pcl816_di_insn_bits; 658 659 /* Digital Output subdevice */ 660 s = &dev->subdevices[3]; 661 s->type = COMEDI_SUBD_DO; 662 s->subdev_flags = SDF_WRITABLE; 663 s->n_chan = 16; 664 s->maxdata = 1; 665 s->range_table = &range_digital; 666 s->insn_bits = pcl816_do_insn_bits; 667 668 pcl816_reset(dev); 669 670 return 0; 671 } 672 673 static void pcl816_detach(struct comedi_device *dev) 674 { 675 if (dev->private) { 676 pcl816_ai_cancel(dev, dev->read_subdev); 677 pcl816_reset(dev); 678 } 679 pcl816_free_dma(dev); 680 comedi_legacy_detach(dev); 681 } 682 683 static struct comedi_driver pcl816_driver = { 684 .driver_name = "pcl816", 685 .module = THIS_MODULE, 686 .attach = pcl816_attach, 687 .detach = pcl816_detach, 688 .board_name = &boardtypes[0].name, 689 .num_names = ARRAY_SIZE(boardtypes), 690 .offset = sizeof(struct pcl816_board), 691 }; 692 module_comedi_driver(pcl816_driver); 693 694 MODULE_AUTHOR("Comedi https://www.comedi.org"); 695 MODULE_DESCRIPTION("Comedi low-level driver"); 696 MODULE_LICENSE("GPL"); 697