amplc_pc236.c revision f1ee810a1383b623ec523edd521467a7b620a09a
1/* 2 comedi/drivers/amplc_pc236.c 3 Driver for Amplicon PC36AT and PCI236 DIO boards. 4 5 Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/> 6 7 COMEDI - Linux Control and Measurement Device Interface 8 Copyright (C) 2000 David A. Schleef <ds@schleef.org> 9 10 This program is free software; you can redistribute it and/or modify 11 it under the terms of the GNU General Public License as published by 12 the Free Software Foundation; either version 2 of the License, or 13 (at your option) any later version. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 23 24*/ 25/* 26Driver: amplc_pc236 27Description: Amplicon PC36AT, PCI236 28Author: Ian Abbott <abbotti@mev.co.uk> 29Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236) 30Updated: Wed, 22 Oct 2008 13:40:03 +0100 31Status: works 32 33Configuration options - PC36AT: 34 [0] - I/O port base address 35 [1] - IRQ (optional) 36 37Configuration options - PCI236: 38 [0] - PCI bus of device (optional) 39 [1] - PCI slot of device (optional) 40 If bus/slot is not specified, the first available PCI device will be 41 used. 42 43The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing 44as subdevice 0. 45 46Subdevice 1 pretends to be a digital input device, but it always returns 470 when read. However, if you run a command with scan_begin_src=TRIG_EXT, 48a rising edge on port C bit 7 acts as an external trigger, which can be 49used to wake up tasks. This is like the comedi_parport device, but the 50only way to physically disable the interrupt on the PC36AT is to remove 51the IRQ jumper. If no interrupt is connected, then subdevice 1 is 52unused. 53*/ 54 55#include "../comedidev.h" 56 57#include "comedi_pci.h" 58 59#include "8255.h" 60#include "plx9052.h" 61 62#define PC236_DRIVER_NAME "amplc_pc236" 63 64/* PCI236 PCI configuration register information */ 65#define PCI_VENDOR_ID_AMPLICON 0x14dc 66#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009 67#define PCI_DEVICE_ID_INVALID 0xffff 68 69/* PC36AT / PCI236 registers */ 70 71#define PC236_IO_SIZE 4 72#define PC236_LCR_IO_SIZE 128 73 74/* 75 * INTCSR values for PCI236. 76 */ 77/* Disable interrupt, also clear any interrupt there */ 78#define PCI236_INTR_DISABLE ( PLX9052_INTCSR_LI1ENAB_DISABLED \ 79 | PLX9052_INTCSR_LI1POL_HIGH \ 80 | PLX9052_INTCSR_LI2POL_HIGH \ 81 | PLX9052_INTCSR_PCIENAB_DISABLED \ 82 | PLX9052_INTCSR_LI1SEL_EDGE \ 83 | PLX9052_INTCSR_LI1CLRINT_ASSERTED ) 84/* Enable interrupt, also clear any interrupt there. */ 85#define PCI236_INTR_ENABLE ( PLX9052_INTCSR_LI1ENAB_ENABLED \ 86 | PLX9052_INTCSR_LI1POL_HIGH \ 87 | PLX9052_INTCSR_LI2POL_HIGH \ 88 | PLX9052_INTCSR_PCIENAB_ENABLED \ 89 | PLX9052_INTCSR_LI1SEL_EDGE \ 90 | PLX9052_INTCSR_LI1CLRINT_ASSERTED ) 91 92/* 93 * Board descriptions for Amplicon PC36AT and PCI236. 94 */ 95 96enum pc236_bustype { isa_bustype, pci_bustype }; 97enum pc236_model { pc36at_model, pci236_model, anypci_model }; 98 99struct pc236_board { 100 const char *name; 101 const char *fancy_name; 102 unsigned short devid; 103 enum pc236_bustype bustype; 104 enum pc236_model model; 105}; 106static const struct pc236_board pc236_boards[] = { 107 { 108 name: "pc36at", 109 fancy_name:"PC36AT", 110 bustype: isa_bustype, 111 model: pc36at_model, 112 }, 113#ifdef CONFIG_COMEDI_PCI 114 { 115 name: "pci236", 116 fancy_name:"PCI236", 117 devid: PCI_DEVICE_ID_AMPLICON_PCI236, 118 bustype: pci_bustype, 119 model: pci236_model, 120 }, 121#endif 122#ifdef CONFIG_COMEDI_PCI 123 { 124 name: PC236_DRIVER_NAME, 125 fancy_name:PC236_DRIVER_NAME, 126 devid: PCI_DEVICE_ID_INVALID, 127 bustype: pci_bustype, 128 model: anypci_model, /* wildcard */ 129 }, 130#endif 131}; 132 133#ifdef CONFIG_COMEDI_PCI 134static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = { 135 {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236, PCI_ANY_ID, 136 PCI_ANY_ID, 0, 0, 0}, 137 {0} 138}; 139 140MODULE_DEVICE_TABLE(pci, pc236_pci_table); 141#endif /* CONFIG_COMEDI_PCI */ 142 143/* 144 * Useful for shorthand access to the particular board structure 145 */ 146#define thisboard ((const struct pc236_board *)dev->board_ptr) 147 148/* this structure is for data unique to this hardware driver. If 149 several hardware drivers keep similar information in this structure, 150 feel free to suggest moving the variable to the struct comedi_device struct. */ 151struct pc236_private { 152#ifdef CONFIG_COMEDI_PCI 153 /* PCI device */ 154 struct pci_dev *pci_dev; 155 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ 156#endif 157 int enable_irq; 158}; 159 160#define devpriv ((struct pc236_private *)dev->private) 161 162/* 163 * The struct comedi_driver structure tells the Comedi core module 164 * which functions to call to configure/deconfigure (attach/detach) 165 * the board, and also about the kernel module that contains 166 * the device code. 167 */ 168static int pc236_attach(struct comedi_device * dev, struct comedi_devconfig * it); 169static int pc236_detach(struct comedi_device * dev); 170static struct comedi_driver driver_amplc_pc236 = { 171 driver_name:PC236_DRIVER_NAME, 172 module:THIS_MODULE, 173 attach:pc236_attach, 174 detach:pc236_detach, 175 board_name:&pc236_boards[0].name, 176 offset:sizeof(struct pc236_board), 177 num_names:sizeof(pc236_boards) / sizeof(struct pc236_board), 178}; 179 180#ifdef CONFIG_COMEDI_PCI 181COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table); 182#else 183COMEDI_INITCLEANUP(driver_amplc_pc236); 184#endif 185 186static int pc236_request_region(unsigned minor, unsigned long from, 187 unsigned long extent); 188static void pc236_intr_disable(struct comedi_device * dev); 189static void pc236_intr_enable(struct comedi_device * dev); 190static int pc236_intr_check(struct comedi_device * dev); 191static int pc236_intr_insn(struct comedi_device * dev, struct comedi_subdevice * s, 192 struct comedi_insn * insn, unsigned int * data); 193static int pc236_intr_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, 194 struct comedi_cmd * cmd); 195static int pc236_intr_cmd(struct comedi_device * dev, struct comedi_subdevice * s); 196static int pc236_intr_cancel(struct comedi_device * dev, struct comedi_subdevice * s); 197static irqreturn_t pc236_interrupt(int irq, void *d PT_REGS_ARG); 198 199/* 200 * This function looks for a PCI device matching the requested board name, 201 * bus and slot. 202 */ 203#ifdef CONFIG_COMEDI_PCI 204static int 205pc236_find_pci(struct comedi_device * dev, int bus, int slot, 206 struct pci_dev **pci_dev_p) 207{ 208 struct pci_dev *pci_dev = NULL; 209 210 *pci_dev_p = NULL; 211 212 /* Look for matching PCI device. */ 213 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); 214 pci_dev != NULL; 215 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, 216 PCI_ANY_ID, pci_dev)) { 217 /* If bus/slot specified, check them. */ 218 if (bus || slot) { 219 if (bus != pci_dev->bus->number 220 || slot != PCI_SLOT(pci_dev->devfn)) 221 continue; 222 } 223 if (thisboard->model == anypci_model) { 224 /* Match any supported model. */ 225 int i; 226 227 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) { 228 if (pc236_boards[i].bustype != pci_bustype) 229 continue; 230 if (pci_dev->device == pc236_boards[i].devid) { 231 /* Change board_ptr to matched board. */ 232 dev->board_ptr = &pc236_boards[i]; 233 break; 234 } 235 } 236 if (i == ARRAY_SIZE(pc236_boards)) 237 continue; 238 } else { 239 /* Match specific model name. */ 240 if (pci_dev->device != thisboard->devid) 241 continue; 242 } 243 244 /* Found a match. */ 245 *pci_dev_p = pci_dev; 246 return 0; 247 } 248 /* No match found. */ 249 if (bus || slot) { 250 printk(KERN_ERR 251 "comedi%d: error! no %s found at pci %02x:%02x!\n", 252 dev->minor, thisboard->name, bus, slot); 253 } else { 254 printk(KERN_ERR "comedi%d: error! no %s found!\n", 255 dev->minor, thisboard->name); 256 } 257 return -EIO; 258} 259#endif 260 261/* 262 * Attach is called by the Comedi core to configure the driver 263 * for a particular board. If you specified a board_name array 264 * in the driver structure, dev->board_ptr contains that 265 * address. 266 */ 267static int pc236_attach(struct comedi_device * dev, struct comedi_devconfig * it) 268{ 269 struct comedi_subdevice *s; 270 unsigned long iobase = 0; 271 unsigned int irq = 0; 272#ifdef CONFIG_COMEDI_PCI 273 struct pci_dev *pci_dev = NULL; 274 int bus = 0, slot = 0; 275#endif 276 int share_irq = 0; 277 int ret; 278 279 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, 280 PC236_DRIVER_NAME); 281/* 282 * Allocate the private structure area. alloc_private() is a 283 * convenient macro defined in comedidev.h. 284 */ 285 if ((ret = alloc_private(dev, sizeof(struct pc236_private))) < 0) { 286 printk(KERN_ERR "comedi%d: error! out of memory!\n", 287 dev->minor); 288 return ret; 289 } 290 /* Process options. */ 291 switch (thisboard->bustype) { 292 case isa_bustype: 293 iobase = it->options[0]; 294 irq = it->options[1]; 295 share_irq = 0; 296 break; 297#ifdef CONFIG_COMEDI_PCI 298 case pci_bustype: 299 bus = it->options[0]; 300 slot = it->options[1]; 301 share_irq = 1; 302 303 if ((ret = pc236_find_pci(dev, bus, slot, &pci_dev)) < 0) 304 return ret; 305 devpriv->pci_dev = pci_dev; 306 break; 307#endif /* CONFIG_COMEDI_PCI */ 308 default: 309 printk(KERN_ERR 310 "comedi%d: %s: BUG! cannot determine board type!\n", 311 dev->minor, PC236_DRIVER_NAME); 312 return -EINVAL; 313 break; 314 } 315 316/* 317 * Initialize dev->board_name. 318 */ 319 dev->board_name = thisboard->name; 320 321 /* Enable device and reserve I/O spaces. */ 322#ifdef CONFIG_COMEDI_PCI 323 if (pci_dev) { 324 if ((ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME)) < 0) { 325 printk(KERN_ERR 326 "comedi%d: error! cannot enable PCI device and request regions!\n", 327 dev->minor); 328 return ret; 329 } 330 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); 331 iobase = pci_resource_start(pci_dev, 2); 332 irq = pci_dev->irq; 333 } else 334#endif 335 { 336 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE); 337 if (ret < 0) { 338 return ret; 339 } 340 } 341 dev->iobase = iobase; 342 343/* 344 * Allocate the subdevice structures. alloc_subdevice() is a 345 * convenient macro defined in comedidev.h. 346 */ 347 if ((ret = alloc_subdevices(dev, 2)) < 0) { 348 printk(KERN_ERR "comedi%d: error! out of memory!\n", 349 dev->minor); 350 return ret; 351 } 352 353 s = dev->subdevices + 0; 354 /* digital i/o subdevice (8255) */ 355 if ((ret = subdev_8255_init(dev, s, NULL, iobase)) < 0) { 356 printk(KERN_ERR "comedi%d: error! out of memory!\n", 357 dev->minor); 358 return ret; 359 } 360 s = dev->subdevices + 1; 361 dev->read_subdev = s; 362 s->type = COMEDI_SUBD_UNUSED; 363 pc236_intr_disable(dev); 364 if (irq) { 365 unsigned long flags = share_irq ? IRQF_SHARED : 0; 366 367 if (comedi_request_irq(irq, pc236_interrupt, flags, 368 PC236_DRIVER_NAME, dev) >= 0) { 369 dev->irq = irq; 370 s->type = COMEDI_SUBD_DI; 371 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 372 s->n_chan = 1; 373 s->maxdata = 1; 374 s->range_table = &range_digital; 375 s->insn_bits = pc236_intr_insn; 376 s->do_cmdtest = pc236_intr_cmdtest; 377 s->do_cmd = pc236_intr_cmd; 378 s->cancel = pc236_intr_cancel; 379 } 380 } 381 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); 382 if (thisboard->bustype == isa_bustype) { 383 printk("(base %#lx) ", iobase); 384 } else { 385#ifdef CONFIG_COMEDI_PCI 386 printk("(pci %s) ", pci_name(pci_dev)); 387#endif 388 } 389 if (irq) { 390 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE")); 391 } else { 392 printk("(no irq) "); 393 } 394 395 printk("attached\n"); 396 397 return 1; 398} 399 400/* 401 * _detach is called to deconfigure a device. It should deallocate 402 * resources. 403 * This function is also called when _attach() fails, so it should be 404 * careful not to release resources that were not necessarily 405 * allocated by _attach(). dev->private and dev->subdevices are 406 * deallocated automatically by the core. 407 */ 408static int pc236_detach(struct comedi_device * dev) 409{ 410 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, 411 PC236_DRIVER_NAME); 412 if (devpriv) { 413 pc236_intr_disable(dev); 414 } 415 if (dev->irq) 416 comedi_free_irq(dev->irq, dev); 417 if (dev->subdevices) { 418 subdev_8255_cleanup(dev, dev->subdevices + 0); 419 } 420 if (devpriv) { 421#ifdef CONFIG_COMEDI_PCI 422 if (devpriv->pci_dev) { 423 if (dev->iobase) { 424 comedi_pci_disable(devpriv->pci_dev); 425 } 426 pci_dev_put(devpriv->pci_dev); 427 } else 428#endif 429 { 430 if (dev->iobase) { 431 release_region(dev->iobase, PC236_IO_SIZE); 432 } 433 } 434 } 435 if (dev->board_name) { 436 printk(KERN_INFO "comedi%d: %s removed\n", 437 dev->minor, dev->board_name); 438 } 439 return 0; 440} 441 442/* 443 * This function checks and requests an I/O region, reporting an error 444 * if there is a conflict. 445 */ 446static int pc236_request_region(unsigned minor, unsigned long from, 447 unsigned long extent) 448{ 449 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) { 450 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n", 451 minor, from, extent); 452 return -EIO; 453 } 454 return 0; 455} 456 457/* 458 * This function is called to mark the interrupt as disabled (no command 459 * configured on subdevice 1) and to physically disable the interrupt 460 * (not possible on the PC36AT, except by removing the IRQ jumper!). 461 */ 462static void pc236_intr_disable(struct comedi_device * dev) 463{ 464 unsigned long flags; 465 466 comedi_spin_lock_irqsave(&dev->spinlock, flags); 467 devpriv->enable_irq = 0; 468#ifdef CONFIG_COMEDI_PCI 469 if (devpriv->lcr_iobase) 470 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 471#endif 472 comedi_spin_unlock_irqrestore(&dev->spinlock, flags); 473} 474 475/* 476 * This function is called to mark the interrupt as enabled (a command 477 * configured on subdevice 1) and to physically enable the interrupt 478 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). 479 */ 480static void pc236_intr_enable(struct comedi_device * dev) 481{ 482 unsigned long flags; 483 484 comedi_spin_lock_irqsave(&dev->spinlock, flags); 485 devpriv->enable_irq = 1; 486#ifdef CONFIG_COMEDI_PCI 487 if (devpriv->lcr_iobase) 488 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 489#endif 490 comedi_spin_unlock_irqrestore(&dev->spinlock, flags); 491} 492 493/* 494 * This function is called when an interrupt occurs to check whether 495 * the interrupt has been marked as enabled and was generated by the 496 * board. If so, the function prepares the hardware for the next 497 * interrupt. 498 * Returns 0 if the interrupt should be ignored. 499 */ 500static int pc236_intr_check(struct comedi_device * dev) 501{ 502 int retval = 0; 503 unsigned long flags; 504 505 comedi_spin_lock_irqsave(&dev->spinlock, flags); 506 if (devpriv->enable_irq) { 507 retval = 1; 508#ifdef CONFIG_COMEDI_PCI 509 if (devpriv->lcr_iobase) { 510 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR) 511 & PLX9052_INTCSR_LI1STAT_MASK) 512 == PLX9052_INTCSR_LI1STAT_INACTIVE) { 513 retval = 0; 514 } else { 515 /* Clear interrupt and keep it enabled. */ 516 outl(PCI236_INTR_ENABLE, 517 devpriv->lcr_iobase + PLX9052_INTCSR); 518 } 519 } 520#endif 521 } 522 comedi_spin_unlock_irqrestore(&dev->spinlock, flags); 523 524 return retval; 525} 526 527/* 528 * Input from subdevice 1. 529 * Copied from the comedi_parport driver. 530 */ 531static int pc236_intr_insn(struct comedi_device * dev, struct comedi_subdevice * s, 532 struct comedi_insn * insn, unsigned int * data) 533{ 534 data[1] = 0; 535 return 2; 536} 537 538/* 539 * Subdevice 1 command test. 540 * Copied from the comedi_parport driver. 541 */ 542static int pc236_intr_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, 543 struct comedi_cmd * cmd) 544{ 545 int err = 0; 546 int tmp; 547 548 /* step 1 */ 549 550 tmp = cmd->start_src; 551 cmd->start_src &= TRIG_NOW; 552 if (!cmd->start_src || tmp != cmd->start_src) 553 err++; 554 555 tmp = cmd->scan_begin_src; 556 cmd->scan_begin_src &= TRIG_EXT; 557 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 558 err++; 559 560 tmp = cmd->convert_src; 561 cmd->convert_src &= TRIG_FOLLOW; 562 if (!cmd->convert_src || tmp != cmd->convert_src) 563 err++; 564 565 tmp = cmd->scan_end_src; 566 cmd->scan_end_src &= TRIG_COUNT; 567 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 568 err++; 569 570 tmp = cmd->stop_src; 571 cmd->stop_src &= TRIG_NONE; 572 if (!cmd->stop_src || tmp != cmd->stop_src) 573 err++; 574 575 if (err) 576 return 1; 577 578 /* step 2: ignored */ 579 580 if (err) 581 return 2; 582 583 /* step 3: */ 584 585 if (cmd->start_arg != 0) { 586 cmd->start_arg = 0; 587 err++; 588 } 589 if (cmd->scan_begin_arg != 0) { 590 cmd->scan_begin_arg = 0; 591 err++; 592 } 593 if (cmd->convert_arg != 0) { 594 cmd->convert_arg = 0; 595 err++; 596 } 597 if (cmd->scan_end_arg != 1) { 598 cmd->scan_end_arg = 1; 599 err++; 600 } 601 if (cmd->stop_arg != 0) { 602 cmd->stop_arg = 0; 603 err++; 604 } 605 606 if (err) 607 return 3; 608 609 /* step 4: ignored */ 610 611 if (err) 612 return 4; 613 614 return 0; 615} 616 617/* 618 * Subdevice 1 command. 619 */ 620static int pc236_intr_cmd(struct comedi_device * dev, struct comedi_subdevice * s) 621{ 622 pc236_intr_enable(dev); 623 624 return 0; 625} 626 627/* 628 * Subdevice 1 cancel command. 629 */ 630static int pc236_intr_cancel(struct comedi_device * dev, struct comedi_subdevice * s) 631{ 632 pc236_intr_disable(dev); 633 634 return 0; 635} 636 637/* 638 * Interrupt service routine. 639 * Based on the comedi_parport driver. 640 */ 641static irqreturn_t pc236_interrupt(int irq, void *d PT_REGS_ARG) 642{ 643 struct comedi_device *dev = d; 644 struct comedi_subdevice *s = dev->subdevices + 1; 645 int handled; 646 647 handled = pc236_intr_check(dev); 648 if (dev->attached && handled) { 649 comedi_buf_put(s->async, 0); 650 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; 651 comedi_event(dev, s); 652 } 653 return IRQ_RETVAL(handled); 654} 655