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