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