1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * pcmuio.c 4 * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards. 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org> 8 */ 9 10 /* 11 * Driver: pcmuio 12 * Description: Winsystems PC-104 based 48/96-channel DIO boards. 13 * Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96) 14 * Author: Calin Culianu <calin@ajvar.org> 15 * Updated: Fri, 13 Jan 2006 12:01:01 -0500 16 * Status: works 17 * 18 * A driver for the relatively straightforward-to-program PCM-UIO48A and 19 * PCM-UIO96A boards from Winsystems. These boards use either one or two 20 * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This 21 * chip is interesting in that each I/O line is individually programmable 22 * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel 23 * basis). Also, each chip supports edge-triggered interrupts for the first 24 * 24 I/O lines. Of course, since the 96-channel version of the board has 25 * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since 26 * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection 27 * are done through jumpers on the board. You need to pass that information 28 * to this driver as the first and second comedi_config option, respectively. 29 * Note that the 48-channel version uses 16 bytes of IO memory and the 96- 30 * channel version uses 32-bytes (in case you are worried about conflicts). 31 * The 48-channel board is split into two 24-channel comedi subdevices. The 32 * 96-channel board is split into 4 24-channel DIO subdevices. 33 * 34 * Note that IRQ support has been added, but it is untested. 35 * 36 * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the 37 * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use 38 * comedi_commands with TRIG_NOW. Your callback will be called each time an 39 * edge is triggered, and the data values will be two sample_t's, which 40 * should be concatenated to form one 32-bit unsigned int. This value is 41 * the mask of channels that had edges detected from your channel list. Note 42 * that the bits positions in the mask correspond to positions in your 43 * chanlist when you specified the command and *not* channel id's! 44 * 45 * To set the polarity of the edge-detection interrupts pass a nonzero value 46 * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for 47 * both CR_RANGE and CR_AREF if you want edge-down polarity. 48 * 49 * In the 48-channel version: 50 * 51 * On subdev 0, the first 24 channels are edge-detect channels. 52 * 53 * In the 96-channel board you have the following channels that can do edge 54 * detection: 55 * 56 * subdev 0, channels 0-24 (first 24 channels of 1st ASIC) 57 * subdev 2, channels 0-24 (first 24 channels of 2nd ASIC) 58 * 59 * Configuration Options: 60 * [0] - I/O port base address 61 * [1] - IRQ (for first ASIC, or first 24 channels) 62 * [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72 63 * can be the same as first irq!) 64 */ 65 66 #include <linux/module.h> 67 #include <linux/interrupt.h> 68 #include <linux/comedi/comedidev.h> 69 70 /* 71 * Register I/O map 72 * 73 * Offset Page 0 Page 1 Page 2 Page 3 74 * ------ ----------- ----------- ----------- ----------- 75 * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O 76 * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O 77 * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O 78 * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O 79 * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O 80 * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O 81 * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING 82 * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock 83 * 0x08 N/A POL_0 ENAB_0 INT_ID0 84 * 0x09 N/A POL_1 ENAB_1 INT_ID1 85 * 0x0a N/A POL_2 ENAB_2 INT_ID2 86 */ 87 #define PCMUIO_PORT_REG(x) (0x00 + (x)) 88 #define PCMUIO_INT_PENDING_REG 0x06 89 #define PCMUIO_PAGE_LOCK_REG 0x07 90 #define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f) 91 #define PCMUIO_PAGE(x) (((x) & 0x3) << 6) 92 #define PCMUIO_PAGE_MASK PCMUIO_PAGE(3) 93 #define PCMUIO_PAGE_POL 1 94 #define PCMUIO_PAGE_ENAB 2 95 #define PCMUIO_PAGE_INT_ID 3 96 #define PCMUIO_PAGE_REG(x) (0x08 + (x)) 97 98 #define PCMUIO_ASIC_IOSIZE 0x10 99 #define PCMUIO_MAX_ASICS 2 100 101 struct pcmuio_board { 102 const char *name; 103 const int num_asics; 104 }; 105 106 static const struct pcmuio_board pcmuio_boards[] = { 107 { 108 .name = "pcmuio48", 109 .num_asics = 1, 110 }, { 111 .name = "pcmuio96", 112 .num_asics = 2, 113 }, 114 }; 115 116 struct pcmuio_asic { 117 spinlock_t pagelock; /* protects the page registers */ 118 spinlock_t spinlock; /* protects member variables */ 119 unsigned int enabled_mask; 120 unsigned int active:1; 121 }; 122 123 struct pcmuio_private { 124 struct pcmuio_asic asics[PCMUIO_MAX_ASICS]; 125 unsigned int irq2; 126 }; 127 128 static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev, 129 int asic) 130 { 131 return dev->iobase + (asic * PCMUIO_ASIC_IOSIZE); 132 } 133 134 static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s) 135 { 136 /* 137 * subdevice 0 and 1 are handled by the first asic 138 * subdevice 2 and 3 are handled by the second asic 139 */ 140 return s->index / 2; 141 } 142 143 static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s) 144 { 145 /* 146 * subdevice 0 and 2 use port registers 0-2 147 * subdevice 1 and 3 use port registers 3-5 148 */ 149 return (s->index % 2) ? 3 : 0; 150 } 151 152 static void pcmuio_write(struct comedi_device *dev, unsigned int val, 153 int asic, int page, int port) 154 { 155 struct pcmuio_private *devpriv = dev->private; 156 struct pcmuio_asic *chip = &devpriv->asics[asic]; 157 unsigned long iobase = pcmuio_asic_iobase(dev, asic); 158 unsigned long flags; 159 160 spin_lock_irqsave(&chip->pagelock, flags); 161 if (page == 0) { 162 /* Port registers are valid for any page */ 163 outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0)); 164 outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1)); 165 outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2)); 166 } else { 167 outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG); 168 outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0)); 169 outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1)); 170 outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2)); 171 } 172 spin_unlock_irqrestore(&chip->pagelock, flags); 173 } 174 175 static unsigned int pcmuio_read(struct comedi_device *dev, 176 int asic, int page, int port) 177 { 178 struct pcmuio_private *devpriv = dev->private; 179 struct pcmuio_asic *chip = &devpriv->asics[asic]; 180 unsigned long iobase = pcmuio_asic_iobase(dev, asic); 181 unsigned long flags; 182 unsigned int val; 183 184 spin_lock_irqsave(&chip->pagelock, flags); 185 if (page == 0) { 186 /* Port registers are valid for any page */ 187 val = inb(iobase + PCMUIO_PORT_REG(port + 0)); 188 val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8); 189 val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16); 190 } else { 191 outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG); 192 val = inb(iobase + PCMUIO_PAGE_REG(0)); 193 val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8); 194 val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16); 195 } 196 spin_unlock_irqrestore(&chip->pagelock, flags); 197 198 return val; 199 } 200 201 /* 202 * Each channel can be individually programmed for input or output. 203 * Writing a '0' to a channel causes the corresponding output pin 204 * to go to a high-z state (pulled high by an external 10K resistor). 205 * This allows it to be used as an input. When used in the input mode, 206 * a read reflects the inverted state of the I/O pin, such that a 207 * high on the pin will read as a '0' in the register. Writing a '1' 208 * to a bit position causes the pin to sink current (up to 12mA), 209 * effectively pulling it low. 210 */ 211 static int pcmuio_dio_insn_bits(struct comedi_device *dev, 212 struct comedi_subdevice *s, 213 struct comedi_insn *insn, 214 unsigned int *data) 215 { 216 int asic = pcmuio_subdevice_to_asic(s); 217 int port = pcmuio_subdevice_to_port(s); 218 unsigned int chanmask = (1 << s->n_chan) - 1; 219 unsigned int mask; 220 unsigned int val; 221 222 mask = comedi_dio_update_state(s, data); 223 if (mask) { 224 /* 225 * Outputs are inverted, invert the state and 226 * update the channels. 227 * 228 * The s->io_bits mask makes sure the input channels 229 * are '0' so that the outputs pins stay in a high 230 * z-state. 231 */ 232 val = ~s->state & chanmask; 233 val &= s->io_bits; 234 pcmuio_write(dev, val, asic, 0, port); 235 } 236 237 /* get inverted state of the channels from the port */ 238 val = pcmuio_read(dev, asic, 0, port); 239 240 /* return the true state of the channels */ 241 data[1] = ~val & chanmask; 242 243 return insn->n; 244 } 245 246 static int pcmuio_dio_insn_config(struct comedi_device *dev, 247 struct comedi_subdevice *s, 248 struct comedi_insn *insn, 249 unsigned int *data) 250 { 251 int asic = pcmuio_subdevice_to_asic(s); 252 int port = pcmuio_subdevice_to_port(s); 253 int ret; 254 255 ret = comedi_dio_insn_config(dev, s, insn, data, 0); 256 if (ret) 257 return ret; 258 259 if (data[0] == INSN_CONFIG_DIO_INPUT) 260 pcmuio_write(dev, s->io_bits, asic, 0, port); 261 262 return insn->n; 263 } 264 265 static void pcmuio_reset(struct comedi_device *dev) 266 { 267 const struct pcmuio_board *board = dev->board_ptr; 268 int asic; 269 270 for (asic = 0; asic < board->num_asics; ++asic) { 271 /* first, clear all the DIO port bits */ 272 pcmuio_write(dev, 0, asic, 0, 0); 273 pcmuio_write(dev, 0, asic, 0, 3); 274 275 /* Next, clear all the paged registers for each page */ 276 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0); 277 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0); 278 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0); 279 } 280 } 281 282 /* chip->spinlock is already locked */ 283 static void pcmuio_stop_intr(struct comedi_device *dev, 284 struct comedi_subdevice *s) 285 { 286 struct pcmuio_private *devpriv = dev->private; 287 int asic = pcmuio_subdevice_to_asic(s); 288 struct pcmuio_asic *chip = &devpriv->asics[asic]; 289 290 chip->enabled_mask = 0; 291 chip->active = 0; 292 s->async->inttrig = NULL; 293 294 /* disable all intrs for this subdev.. */ 295 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0); 296 } 297 298 static void pcmuio_handle_intr_subdev(struct comedi_device *dev, 299 struct comedi_subdevice *s, 300 unsigned int triggered) 301 { 302 struct pcmuio_private *devpriv = dev->private; 303 int asic = pcmuio_subdevice_to_asic(s); 304 struct pcmuio_asic *chip = &devpriv->asics[asic]; 305 struct comedi_cmd *cmd = &s->async->cmd; 306 unsigned int val = 0; 307 unsigned long flags; 308 unsigned int i; 309 310 spin_lock_irqsave(&chip->spinlock, flags); 311 312 if (!chip->active) 313 goto done; 314 315 if (!(triggered & chip->enabled_mask)) 316 goto done; 317 318 for (i = 0; i < cmd->chanlist_len; i++) { 319 unsigned int chan = CR_CHAN(cmd->chanlist[i]); 320 321 if (triggered & (1 << chan)) 322 val |= (1 << i); 323 } 324 325 comedi_buf_write_samples(s, &val, 1); 326 327 if (cmd->stop_src == TRIG_COUNT && 328 s->async->scans_done >= cmd->stop_arg) 329 s->async->events |= COMEDI_CB_EOA; 330 331 done: 332 spin_unlock_irqrestore(&chip->spinlock, flags); 333 334 comedi_handle_events(dev, s); 335 } 336 337 static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic) 338 { 339 /* there are could be two asics so we can't use dev->read_subdev */ 340 struct comedi_subdevice *s = &dev->subdevices[asic * 2]; 341 unsigned long iobase = pcmuio_asic_iobase(dev, asic); 342 unsigned int val; 343 344 /* are there any interrupts pending */ 345 val = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07; 346 if (!val) 347 return 0; 348 349 /* get, and clear, the pending interrupts */ 350 val = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0); 351 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0); 352 353 /* handle the pending interrupts */ 354 pcmuio_handle_intr_subdev(dev, s, val); 355 356 return 1; 357 } 358 359 static irqreturn_t pcmuio_interrupt(int irq, void *d) 360 { 361 struct comedi_device *dev = d; 362 struct pcmuio_private *devpriv = dev->private; 363 int handled = 0; 364 365 if (irq == dev->irq) 366 handled += pcmuio_handle_asic_interrupt(dev, 0); 367 if (irq == devpriv->irq2) 368 handled += pcmuio_handle_asic_interrupt(dev, 1); 369 370 return handled ? IRQ_HANDLED : IRQ_NONE; 371 } 372 373 /* chip->spinlock is already locked */ 374 static void pcmuio_start_intr(struct comedi_device *dev, 375 struct comedi_subdevice *s) 376 { 377 struct pcmuio_private *devpriv = dev->private; 378 int asic = pcmuio_subdevice_to_asic(s); 379 struct pcmuio_asic *chip = &devpriv->asics[asic]; 380 struct comedi_cmd *cmd = &s->async->cmd; 381 unsigned int bits = 0; 382 unsigned int pol_bits = 0; 383 int i; 384 385 chip->enabled_mask = 0; 386 chip->active = 1; 387 if (cmd->chanlist) { 388 for (i = 0; i < cmd->chanlist_len; i++) { 389 unsigned int chanspec = cmd->chanlist[i]; 390 unsigned int chan = CR_CHAN(chanspec); 391 unsigned int range = CR_RANGE(chanspec); 392 unsigned int aref = CR_AREF(chanspec); 393 394 bits |= (1 << chan); 395 pol_bits |= ((aref || range) ? 1 : 0) << chan; 396 } 397 } 398 bits &= ((1 << s->n_chan) - 1); 399 chip->enabled_mask = bits; 400 401 /* set pol and enab intrs for this subdev.. */ 402 pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0); 403 pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0); 404 } 405 406 static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) 407 { 408 struct pcmuio_private *devpriv = dev->private; 409 int asic = pcmuio_subdevice_to_asic(s); 410 struct pcmuio_asic *chip = &devpriv->asics[asic]; 411 unsigned long flags; 412 413 spin_lock_irqsave(&chip->spinlock, flags); 414 if (chip->active) 415 pcmuio_stop_intr(dev, s); 416 spin_unlock_irqrestore(&chip->spinlock, flags); 417 418 return 0; 419 } 420 421 static int pcmuio_inttrig_start_intr(struct comedi_device *dev, 422 struct comedi_subdevice *s, 423 unsigned int trig_num) 424 { 425 struct pcmuio_private *devpriv = dev->private; 426 struct comedi_cmd *cmd = &s->async->cmd; 427 int asic = pcmuio_subdevice_to_asic(s); 428 struct pcmuio_asic *chip = &devpriv->asics[asic]; 429 unsigned long flags; 430 431 if (trig_num != cmd->start_arg) 432 return -EINVAL; 433 434 spin_lock_irqsave(&chip->spinlock, flags); 435 s->async->inttrig = NULL; 436 if (chip->active) 437 pcmuio_start_intr(dev, s); 438 439 spin_unlock_irqrestore(&chip->spinlock, flags); 440 441 return 1; 442 } 443 444 /* 445 * 'do_cmd' function for an 'INTERRUPT' subdevice. 446 */ 447 static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 448 { 449 struct pcmuio_private *devpriv = dev->private; 450 struct comedi_cmd *cmd = &s->async->cmd; 451 int asic = pcmuio_subdevice_to_asic(s); 452 struct pcmuio_asic *chip = &devpriv->asics[asic]; 453 unsigned long flags; 454 455 spin_lock_irqsave(&chip->spinlock, flags); 456 chip->active = 1; 457 458 /* Set up start of acquisition. */ 459 if (cmd->start_src == TRIG_INT) 460 s->async->inttrig = pcmuio_inttrig_start_intr; 461 else /* TRIG_NOW */ 462 pcmuio_start_intr(dev, s); 463 464 spin_unlock_irqrestore(&chip->spinlock, flags); 465 466 return 0; 467 } 468 469 static int pcmuio_cmdtest(struct comedi_device *dev, 470 struct comedi_subdevice *s, 471 struct comedi_cmd *cmd) 472 { 473 int err = 0; 474 475 /* Step 1 : check if triggers are trivially valid */ 476 477 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); 478 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); 479 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); 480 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 481 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 482 483 if (err) 484 return 1; 485 486 /* Step 2a : make sure trigger sources are unique */ 487 488 err |= comedi_check_trigger_is_unique(cmd->start_src); 489 err |= comedi_check_trigger_is_unique(cmd->stop_src); 490 491 /* Step 2b : and mutually compatible */ 492 493 if (err) 494 return 2; 495 496 /* Step 3: check if arguments are trivially valid */ 497 498 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 499 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 500 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 501 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 502 cmd->chanlist_len); 503 504 if (cmd->stop_src == TRIG_COUNT) 505 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); 506 else /* TRIG_NONE */ 507 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 508 509 if (err) 510 return 3; 511 512 /* step 4: fix up any arguments */ 513 514 /* if (err) return 4; */ 515 516 return 0; 517 } 518 519 static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) 520 { 521 const struct pcmuio_board *board = dev->board_ptr; 522 struct comedi_subdevice *s; 523 struct pcmuio_private *devpriv; 524 int ret; 525 int i; 526 527 ret = comedi_request_region(dev, it->options[0], 528 board->num_asics * PCMUIO_ASIC_IOSIZE); 529 if (ret) 530 return ret; 531 532 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 533 if (!devpriv) 534 return -ENOMEM; 535 536 for (i = 0; i < PCMUIO_MAX_ASICS; ++i) { 537 struct pcmuio_asic *chip = &devpriv->asics[i]; 538 539 spin_lock_init(&chip->pagelock); 540 spin_lock_init(&chip->spinlock); 541 } 542 543 pcmuio_reset(dev); 544 545 if (it->options[1]) { 546 /* request the irq for the 1st asic */ 547 ret = request_irq(it->options[1], pcmuio_interrupt, 0, 548 dev->board_name, dev); 549 if (ret == 0) 550 dev->irq = it->options[1]; 551 } 552 553 if (board->num_asics == 2) { 554 if (it->options[2] == dev->irq) { 555 /* the same irq (or none) is used by both asics */ 556 devpriv->irq2 = it->options[2]; 557 } else if (it->options[2]) { 558 /* request the irq for the 2nd asic */ 559 ret = request_irq(it->options[2], pcmuio_interrupt, 0, 560 dev->board_name, dev); 561 if (ret == 0) 562 devpriv->irq2 = it->options[2]; 563 } 564 } 565 566 ret = comedi_alloc_subdevices(dev, board->num_asics * 2); 567 if (ret) 568 return ret; 569 570 for (i = 0; i < dev->n_subdevices; ++i) { 571 s = &dev->subdevices[i]; 572 s->type = COMEDI_SUBD_DIO; 573 s->subdev_flags = SDF_READABLE | SDF_WRITABLE; 574 s->n_chan = 24; 575 s->maxdata = 1; 576 s->range_table = &range_digital; 577 s->insn_bits = pcmuio_dio_insn_bits; 578 s->insn_config = pcmuio_dio_insn_config; 579 580 /* subdevices 0 and 2 can support interrupts */ 581 if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) { 582 /* setup the interrupt subdevice */ 583 dev->read_subdev = s; 584 s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | 585 SDF_PACKED; 586 s->len_chanlist = s->n_chan; 587 s->cancel = pcmuio_cancel; 588 s->do_cmd = pcmuio_cmd; 589 s->do_cmdtest = pcmuio_cmdtest; 590 } 591 } 592 593 return 0; 594 } 595 596 static void pcmuio_detach(struct comedi_device *dev) 597 { 598 struct pcmuio_private *devpriv = dev->private; 599 600 if (devpriv) { 601 pcmuio_reset(dev); 602 603 /* free the 2nd irq if used, the core will free the 1st one */ 604 if (devpriv->irq2 && devpriv->irq2 != dev->irq) 605 free_irq(devpriv->irq2, dev); 606 } 607 comedi_legacy_detach(dev); 608 } 609 610 static struct comedi_driver pcmuio_driver = { 611 .driver_name = "pcmuio", 612 .module = THIS_MODULE, 613 .attach = pcmuio_attach, 614 .detach = pcmuio_detach, 615 .board_name = &pcmuio_boards[0].name, 616 .offset = sizeof(struct pcmuio_board), 617 .num_names = ARRAY_SIZE(pcmuio_boards), 618 }; 619 module_comedi_driver(pcmuio_driver); 620 621 MODULE_AUTHOR("Comedi https://www.comedi.org"); 622 MODULE_DESCRIPTION("Comedi low-level driver"); 623 MODULE_LICENSE("GPL"); 624