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 = &dma->desc[dma->cur_dma]; 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 nsamples = comedi_bytes_to_samples(s, desc->size) - 261 devpriv->ai_poll_ptr; 262 bufptr = devpriv->ai_poll_ptr; 263 devpriv->ai_poll_ptr = 0; 264 265 /* restart dma with the next buffer */ 266 dma->cur_dma = 1 - dma->cur_dma; 267 pcl816_ai_setup_dma(dev, s, nsamples); 268 269 transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples); 270 271 pcl816_ai_clear_eoc(dev); 272 273 comedi_handle_events(dev, s); 274 return IRQ_HANDLED; 275 } 276 277 static int check_channel_list(struct comedi_device *dev, 278 struct comedi_subdevice *s, 279 unsigned int *chanlist, 280 unsigned int chanlen) 281 { 282 unsigned int chansegment[16]; 283 unsigned int i, nowmustbechan, seglen; 284 285 /* correct channel and range number check itself comedi/range.c */ 286 if (chanlen < 1) { 287 dev_err(dev->class_dev, "range/channel list is empty!\n"); 288 return 0; 289 } 290 291 if (chanlen > 1) { 292 /* first channel is every time ok */ 293 chansegment[0] = chanlist[0]; 294 for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { 295 /* we detect loop, this must by finish */ 296 if (chanlist[0] == chanlist[i]) 297 break; 298 nowmustbechan = 299 (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; 300 if (nowmustbechan != CR_CHAN(chanlist[i])) { 301 /* channel list isn't continuous :-( */ 302 dev_dbg(dev->class_dev, 303 "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", 304 i, CR_CHAN(chanlist[i]), nowmustbechan, 305 CR_CHAN(chanlist[0])); 306 return 0; 307 } 308 /* well, this is next correct channel in list */ 309 chansegment[i] = chanlist[i]; 310 } 311 312 /* check whole chanlist */ 313 for (i = 0; i < chanlen; i++) { 314 if (chanlist[i] != chansegment[i % seglen]) { 315 dev_dbg(dev->class_dev, 316 "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", 317 i, CR_CHAN(chansegment[i]), 318 CR_RANGE(chansegment[i]), 319 CR_AREF(chansegment[i]), 320 CR_CHAN(chanlist[i % seglen]), 321 CR_RANGE(chanlist[i % seglen]), 322 CR_AREF(chansegment[i % seglen])); 323 return 0; /* chan/gain list is strange */ 324 } 325 } 326 } else { 327 seglen = 1; 328 } 329 330 return seglen; /* we can serve this with MUX logic */ 331 } 332 333 static int pcl816_ai_cmdtest(struct comedi_device *dev, 334 struct comedi_subdevice *s, struct comedi_cmd *cmd) 335 { 336 int err = 0; 337 338 /* Step 1 : check if triggers are trivially valid */ 339 340 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 341 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); 342 err |= comedi_check_trigger_src(&cmd->convert_src, 343 TRIG_EXT | TRIG_TIMER); 344 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 345 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 346 347 if (err) 348 return 1; 349 350 /* Step 2a : make sure trigger sources are unique */ 351 352 err |= comedi_check_trigger_is_unique(cmd->convert_src); 353 err |= comedi_check_trigger_is_unique(cmd->stop_src); 354 355 /* Step 2b : and mutually compatible */ 356 357 if (err) 358 return 2; 359 360 /* Step 3: check if arguments are trivially valid */ 361 362 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 363 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 364 365 if (cmd->convert_src == TRIG_TIMER) 366 err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); 367 else /* TRIG_EXT */ 368 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 369 370 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 371 cmd->chanlist_len); 372 373 if (cmd->stop_src == TRIG_COUNT) 374 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 375 else /* TRIG_NONE */ 376 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 377 378 if (err) 379 return 3; 380 381 /* step 4: fix up any arguments */ 382 if (cmd->convert_src == TRIG_TIMER) { 383 unsigned int arg = cmd->convert_arg; 384 385 comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); 386 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); 387 } 388 389 if (err) 390 return 4; 391 392 /* step 5: complain about special chanlist considerations */ 393 394 if (cmd->chanlist) { 395 if (!check_channel_list(dev, s, cmd->chanlist, 396 cmd->chanlist_len)) 397 return 5; /* incorrect channels list */ 398 } 399 400 return 0; 401 } 402 403 static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 404 { 405 struct pcl816_private *devpriv = dev->private; 406 struct comedi_isadma *dma = devpriv->dma; 407 struct comedi_cmd *cmd = &s->async->cmd; 408 unsigned int ctrl; 409 unsigned int seglen; 410 411 if (devpriv->ai_cmd_running) 412 return -EBUSY; 413 414 seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); 415 if (seglen < 1) 416 return -EINVAL; 417 pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen); 418 udelay(1); 419 420 devpriv->ai_cmd_running = 1; 421 devpriv->ai_poll_ptr = 0; 422 devpriv->ai_cmd_canceled = 0; 423 424 /* setup and enable dma for the first buffer */ 425 dma->cur_dma = 0; 426 pcl816_ai_setup_dma(dev, s, 0); 427 428 comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY); 429 comedi_8254_write(dev->pacer, 0, 0x0ff); 430 udelay(1); 431 comedi_8254_update_divisors(dev->pacer); 432 comedi_8254_pacer_enable(dev->pacer, 1, 2, true); 433 434 ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | 435 PCL816_CTRL_DMASRC_SLOT(0); 436 if (cmd->convert_src == TRIG_TIMER) 437 ctrl |= PCL816_CTRL_PACER_TRIG; 438 else /* TRIG_EXT */ 439 ctrl |= PCL816_CTRL_EXT_TRIG; 440 441 outb(ctrl, dev->iobase + PCL816_CTRL_REG); 442 outb((dma->chan << 4) | dev->irq, 443 dev->iobase + PCL816_STATUS_REG); 444 445 return 0; 446 } 447 448 static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) 449 { 450 struct pcl816_private *devpriv = dev->private; 451 struct comedi_isadma *dma = devpriv->dma; 452 struct comedi_isadma_desc *desc; 453 unsigned long flags; 454 unsigned int poll; 455 int ret; 456 457 spin_lock_irqsave(&dev->spinlock, flags); 458 459 poll = comedi_isadma_poll(dma); 460 poll = comedi_bytes_to_samples(s, poll); 461 if (poll > devpriv->ai_poll_ptr) { 462 desc = &dma->desc[dma->cur_dma]; 463 transfer_from_dma_buf(dev, s, desc->virt_addr, 464 devpriv->ai_poll_ptr, 465 poll - devpriv->ai_poll_ptr); 466 /* new buffer position */ 467 devpriv->ai_poll_ptr = poll; 468 469 comedi_handle_events(dev, s); 470 471 ret = comedi_buf_n_bytes_ready(s); 472 } else { 473 /* no new samples */ 474 ret = 0; 475 } 476 spin_unlock_irqrestore(&dev->spinlock, flags); 477 478 return ret; 479 } 480 481 static int pcl816_ai_cancel(struct comedi_device *dev, 482 struct comedi_subdevice *s) 483 { 484 struct pcl816_private *devpriv = dev->private; 485 486 if (!devpriv->ai_cmd_running) 487 return 0; 488 489 outb(0, dev->iobase + PCL816_CTRL_REG); 490 pcl816_ai_clear_eoc(dev); 491 492 comedi_8254_pacer_enable(dev->pacer, 1, 2, false); 493 494 devpriv->ai_cmd_running = 0; 495 devpriv->ai_cmd_canceled = 1; 496 497 return 0; 498 } 499 500 static int pcl816_ai_insn_read(struct comedi_device *dev, 501 struct comedi_subdevice *s, 502 struct comedi_insn *insn, 503 unsigned int *data) 504 { 505 unsigned int chan = CR_CHAN(insn->chanspec); 506 unsigned int range = CR_RANGE(insn->chanspec); 507 int ret = 0; 508 int i; 509 510 outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG); 511 512 pcl816_ai_set_chan_range(dev, chan, range); 513 pcl816_ai_set_chan_scan(dev, chan, chan); 514 515 for (i = 0; i < insn->n; i++) { 516 pcl816_ai_clear_eoc(dev); 517 pcl816_ai_soft_trig(dev); 518 519 ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0); 520 if (ret) 521 break; 522 523 data[i] = pcl816_ai_get_sample(dev, s); 524 } 525 outb(0, dev->iobase + PCL816_CTRL_REG); 526 pcl816_ai_clear_eoc(dev); 527 528 return ret ? ret : insn->n; 529 } 530 531 static int pcl816_di_insn_bits(struct comedi_device *dev, 532 struct comedi_subdevice *s, 533 struct comedi_insn *insn, 534 unsigned int *data) 535 { 536 data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) | 537 (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8); 538 539 return insn->n; 540 } 541 542 static int pcl816_do_insn_bits(struct comedi_device *dev, 543 struct comedi_subdevice *s, 544 struct comedi_insn *insn, 545 unsigned int *data) 546 { 547 if (comedi_dio_update_state(s, data)) { 548 outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG); 549 outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG); 550 } 551 552 data[1] = s->state; 553 554 return insn->n; 555 } 556 557 static void pcl816_reset(struct comedi_device *dev) 558 { 559 outb(0, dev->iobase + PCL816_CTRL_REG); 560 pcl816_ai_set_chan_range(dev, 0, 0); 561 pcl816_ai_clear_eoc(dev); 562 563 /* set all digital outputs low */ 564 outb(0, dev->iobase + PCL816_DO_DI_LSB_REG); 565 outb(0, dev->iobase + PCL816_DO_DI_MSB_REG); 566 } 567 568 static void pcl816_alloc_irq_and_dma(struct comedi_device *dev, 569 struct comedi_devconfig *it) 570 { 571 struct pcl816_private *devpriv = dev->private; 572 unsigned int irq_num = it->options[1]; 573 unsigned int dma_chan = it->options[2]; 574 575 /* only IRQs 2-7 and DMA channels 3 and 1 are valid */ 576 if (!(irq_num >= 2 && irq_num <= 7) || 577 !(dma_chan == 3 || dma_chan == 1)) 578 return; 579 580 if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev)) 581 return; 582 583 /* DMA uses two 16K buffers */ 584 devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, 585 PAGE_SIZE * 4, COMEDI_ISADMA_READ); 586 if (!devpriv->dma) 587 free_irq(irq_num, dev); 588 else 589 dev->irq = irq_num; 590 } 591 592 static void pcl816_free_dma(struct comedi_device *dev) 593 { 594 struct pcl816_private *devpriv = dev->private; 595 596 if (devpriv) 597 comedi_isadma_free(devpriv->dma); 598 } 599 600 static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) 601 { 602 const struct pcl816_board *board = dev->board_ptr; 603 struct pcl816_private *devpriv; 604 struct comedi_subdevice *s; 605 int ret; 606 607 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 608 if (!devpriv) 609 return -ENOMEM; 610 611 ret = comedi_request_region(dev, it->options[0], 0x10); 612 if (ret) 613 return ret; 614 615 /* an IRQ and DMA are required to support async commands */ 616 pcl816_alloc_irq_and_dma(dev, it); 617 618 dev->pacer = comedi_8254_init(dev->iobase + PCL816_TIMER_BASE, 619 I8254_OSC_BASE_10MHZ, I8254_IO8, 0); 620 if (!dev->pacer) 621 return -ENOMEM; 622 623 ret = comedi_alloc_subdevices(dev, 4); 624 if (ret) 625 return ret; 626 627 s = &dev->subdevices[0]; 628 s->type = COMEDI_SUBD_AI; 629 s->subdev_flags = SDF_CMD_READ | SDF_DIFF; 630 s->n_chan = 16; 631 s->maxdata = board->ai_maxdata; 632 s->range_table = &range_pcl816; 633 s->insn_read = pcl816_ai_insn_read; 634 if (dev->irq) { 635 dev->read_subdev = s; 636 s->subdev_flags |= SDF_CMD_READ; 637 s->len_chanlist = board->ai_chanlist; 638 s->do_cmdtest = pcl816_ai_cmdtest; 639 s->do_cmd = pcl816_ai_cmd; 640 s->poll = pcl816_ai_poll; 641 s->cancel = pcl816_ai_cancel; 642 } 643 644 /* Piggyback Slot1 subdevice */ 645 s = &dev->subdevices[1]; 646 s->type = COMEDI_SUBD_UNUSED; 647 648 /* Digital Input subdevice */ 649 s = &dev->subdevices[2]; 650 s->type = COMEDI_SUBD_DI; 651 s->subdev_flags = SDF_READABLE; 652 s->n_chan = 16; 653 s->maxdata = 1; 654 s->range_table = &range_digital; 655 s->insn_bits = pcl816_di_insn_bits; 656 657 /* Digital Output subdevice */ 658 s = &dev->subdevices[3]; 659 s->type = COMEDI_SUBD_DO; 660 s->subdev_flags = SDF_WRITABLE; 661 s->n_chan = 16; 662 s->maxdata = 1; 663 s->range_table = &range_digital; 664 s->insn_bits = pcl816_do_insn_bits; 665 666 pcl816_reset(dev); 667 668 return 0; 669 } 670 671 static void pcl816_detach(struct comedi_device *dev) 672 { 673 if (dev->private) { 674 pcl816_ai_cancel(dev, dev->read_subdev); 675 pcl816_reset(dev); 676 } 677 pcl816_free_dma(dev); 678 comedi_legacy_detach(dev); 679 } 680 681 static struct comedi_driver pcl816_driver = { 682 .driver_name = "pcl816", 683 .module = THIS_MODULE, 684 .attach = pcl816_attach, 685 .detach = pcl816_detach, 686 .board_name = &boardtypes[0].name, 687 .num_names = ARRAY_SIZE(boardtypes), 688 .offset = sizeof(struct pcl816_board), 689 }; 690 module_comedi_driver(pcl816_driver); 691 692 MODULE_AUTHOR("Comedi https://www.comedi.org"); 693 MODULE_DESCRIPTION("Comedi low-level driver"); 694 MODULE_LICENSE("GPL"); 695