amplc_pc236.c revision c25386f5db48ee5d94b6ff3b1dc43ea6c87c9560
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, 01 Apr 2009 15:41:25 +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 3 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 <linux/interrupt.h> 56 57#include "../comedidev.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#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 108 { 109 .name = "pc36at", 110 .fancy_name = "PC36AT", 111 .bustype = isa_bustype, 112 .model = pc36at_model, 113 }, 114#endif 115#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 116 { 117 .name = "pci236", 118 .fancy_name = "PCI236", 119 .devid = PCI_DEVICE_ID_AMPLICON_PCI236, 120 .bustype = pci_bustype, 121 .model = pci236_model, 122 }, 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#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 134static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = { 135 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) }, 136 {0} 137}; 138 139MODULE_DEVICE_TABLE(pci, pc236_pci_table); 140#endif /* CONFIG_COMEDI_AMPLC_PC236_PCI */ 141 142/* 143 * Useful for shorthand access to the particular board structure 144 */ 145#define thisboard ((const struct pc236_board *)dev->board_ptr) 146 147/* this structure is for data unique to this hardware driver. If 148 several hardware drivers keep similar information in this structure, 149 feel free to suggest moving the variable to the struct comedi_device struct. 150 */ 151struct pc236_private { 152#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_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 void pc236_detach(struct comedi_device *dev); 170static struct comedi_driver amplc_pc236_driver = { 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 = ARRAY_SIZE(pc236_boards), 178}; 179 180#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 181static int __devinit amplc_pc236_pci_probe(struct pci_dev *dev, 182 const struct pci_device_id 183 *ent) 184{ 185 return comedi_pci_auto_config(dev, &lc_pc236_driver); 186} 187 188static void __devexit amplc_pc236_pci_remove(struct pci_dev *dev) 189{ 190 comedi_pci_auto_unconfig(dev); 191} 192 193static struct pci_driver amplc_pc236_pci_driver = { 194 .name = PC236_DRIVER_NAME, 195 .id_table = pc236_pci_table, 196 .probe = &lc_pc236_pci_probe, 197 .remove = __devexit_p(&lc_pc236_pci_remove) 198}; 199 200module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver); 201#else 202module_comedi_driver(amplc_pc236_driver); 203#endif 204 205#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 206static int pc236_request_region(unsigned minor, unsigned long from, 207 unsigned long extent); 208#endif 209static void pc236_intr_disable(struct comedi_device *dev); 210static void pc236_intr_enable(struct comedi_device *dev); 211static int pc236_intr_check(struct comedi_device *dev); 212static int pc236_intr_insn(struct comedi_device *dev, 213 struct comedi_subdevice *s, struct comedi_insn *insn, 214 unsigned int *data); 215static int pc236_intr_cmdtest(struct comedi_device *dev, 216 struct comedi_subdevice *s, 217 struct comedi_cmd *cmd); 218static int pc236_intr_cmd(struct comedi_device *dev, 219 struct comedi_subdevice *s); 220static int pc236_intr_cancel(struct comedi_device *dev, 221 struct comedi_subdevice *s); 222static irqreturn_t pc236_interrupt(int irq, void *d); 223 224/* 225 * This function looks for a PCI device matching the requested board name, 226 * bus and slot. 227 */ 228#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 229static int 230pc236_find_pci(struct comedi_device *dev, int bus, int slot, 231 struct pci_dev **pci_dev_p) 232{ 233 struct pci_dev *pci_dev = NULL; 234 235 *pci_dev_p = NULL; 236 237 /* Look for matching PCI device. */ 238 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); 239 pci_dev != NULL; 240 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, 241 PCI_ANY_ID, pci_dev)) { 242 /* If bus/slot specified, check them. */ 243 if (bus || slot) { 244 if (bus != pci_dev->bus->number 245 || slot != PCI_SLOT(pci_dev->devfn)) 246 continue; 247 } 248 if (thisboard->model == anypci_model) { 249 /* Match any supported model. */ 250 int i; 251 252 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) { 253 if (pc236_boards[i].bustype != pci_bustype) 254 continue; 255 if (pci_dev->device == pc236_boards[i].devid) { 256 /* Change board_ptr to matched board. */ 257 dev->board_ptr = &pc236_boards[i]; 258 break; 259 } 260 } 261 if (i == ARRAY_SIZE(pc236_boards)) 262 continue; 263 } else { 264 /* Match specific model name. */ 265 if (pci_dev->device != thisboard->devid) 266 continue; 267 } 268 269 /* Found a match. */ 270 *pci_dev_p = pci_dev; 271 return 0; 272 } 273 /* No match found. */ 274 if (bus || slot) { 275 printk(KERN_ERR 276 "comedi%d: error! no %s found at pci %02x:%02x!\n", 277 dev->minor, thisboard->name, bus, slot); 278 } else { 279 printk(KERN_ERR "comedi%d: error! no %s found!\n", 280 dev->minor, thisboard->name); 281 } 282 return -EIO; 283} 284#endif 285 286/* 287 * Attach is called by the Comedi core to configure the driver 288 * for a particular board. If you specified a board_name array 289 * in the driver structure, dev->board_ptr contains that 290 * address. 291 */ 292static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) 293{ 294 struct comedi_subdevice *s; 295 unsigned long iobase = 0; 296 unsigned int irq = 0; 297#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 298 struct pci_dev *pci_dev = NULL; 299 int bus = 0, slot = 0; 300#endif 301 int share_irq = 0; 302 int ret; 303 304 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, 305 PC236_DRIVER_NAME); 306/* 307 * Allocate the private structure area. alloc_private() is a 308 * convenient macro defined in comedidev.h. 309 */ 310 ret = alloc_private(dev, sizeof(struct pc236_private)); 311 if (ret < 0) { 312 printk(KERN_ERR "comedi%d: error! out of memory!\n", 313 dev->minor); 314 return ret; 315 } 316 /* Process options. */ 317 switch (thisboard->bustype) { 318#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 319 case isa_bustype: 320 iobase = it->options[0]; 321 irq = it->options[1]; 322 share_irq = 0; 323 break; 324#endif 325#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 326 case pci_bustype: 327 bus = it->options[0]; 328 slot = it->options[1]; 329 share_irq = 1; 330 331 ret = pc236_find_pci(dev, bus, slot, &pci_dev); 332 if (ret < 0) 333 return ret; 334 devpriv->pci_dev = pci_dev; 335 break; 336#endif 337 default: 338 printk(KERN_ERR 339 "comedi%d: %s: BUG! cannot determine board type!\n", 340 dev->minor, PC236_DRIVER_NAME); 341 return -EINVAL; 342 break; 343 } 344 345/* 346 * Initialize dev->board_name. 347 */ 348 dev->board_name = thisboard->name; 349 350 /* Enable device and reserve I/O spaces. */ 351#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 352 if (pci_dev) { 353 354 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME); 355 if (ret < 0) { 356 printk(KERN_ERR 357 "comedi%d: error! cannot enable PCI device and request regions!\n", 358 dev->minor); 359 return ret; 360 } 361 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); 362 iobase = pci_resource_start(pci_dev, 2); 363 irq = pci_dev->irq; 364 } else 365#endif 366 { 367#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 368 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE); 369 if (ret < 0) 370 return ret; 371#endif 372 } 373 dev->iobase = iobase; 374 375/* 376 * Allocate the subdevice structures. alloc_subdevice() is a 377 * convenient macro defined in comedidev.h. 378 */ 379 ret = alloc_subdevices(dev, 2); 380 if (ret < 0) { 381 printk(KERN_ERR "comedi%d: error! out of memory!\n", 382 dev->minor); 383 return ret; 384 } 385 386 s = dev->subdevices + 0; 387 /* digital i/o subdevice (8255) */ 388 ret = subdev_8255_init(dev, s, NULL, iobase); 389 if (ret < 0) { 390 printk(KERN_ERR "comedi%d: error! out of memory!\n", 391 dev->minor); 392 return ret; 393 } 394 s = dev->subdevices + 1; 395 dev->read_subdev = s; 396 s->type = COMEDI_SUBD_UNUSED; 397 pc236_intr_disable(dev); 398 if (irq) { 399 unsigned long flags = share_irq ? IRQF_SHARED : 0; 400 401 if (request_irq(irq, pc236_interrupt, flags, 402 PC236_DRIVER_NAME, dev) >= 0) { 403 dev->irq = irq; 404 s->type = COMEDI_SUBD_DI; 405 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 406 s->n_chan = 1; 407 s->maxdata = 1; 408 s->range_table = &range_digital; 409 s->insn_bits = pc236_intr_insn; 410 s->do_cmdtest = pc236_intr_cmdtest; 411 s->do_cmd = pc236_intr_cmd; 412 s->cancel = pc236_intr_cancel; 413 } 414 } 415 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); 416 switch (thisboard->bustype) { 417#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 418 case isa_bustype: 419 printk("(base %#lx) ", iobase); 420 break; 421#endif 422#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 423 case pci_bustype: 424 printk("(pci %s) ", pci_name(pci_dev)); 425 break; 426#endif 427 default: 428 break; 429 } 430 if (irq) 431 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE")); 432 else 433 printk("(no irq) "); 434 435 printk("attached\n"); 436 437 return 1; 438} 439 440static void pc236_detach(struct comedi_device *dev) 441{ 442 if (devpriv) 443 pc236_intr_disable(dev); 444 if (dev->irq) 445 free_irq(dev->irq, dev); 446 if (dev->subdevices) 447 subdev_8255_cleanup(dev, dev->subdevices + 0); 448 if (devpriv) { 449#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 450 if (devpriv->pci_dev) { 451 if (dev->iobase) 452 comedi_pci_disable(devpriv->pci_dev); 453 pci_dev_put(devpriv->pci_dev); 454 } else 455#endif 456 { 457#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 458 if (dev->iobase) 459 release_region(dev->iobase, PC236_IO_SIZE); 460#endif 461 } 462 } 463} 464 465/* 466 * This function checks and requests an I/O region, reporting an error 467 * if there is a conflict. 468 */ 469#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 470static int pc236_request_region(unsigned minor, unsigned long from, 471 unsigned long extent) 472{ 473 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) { 474 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n", 475 minor, from, extent); 476 return -EIO; 477 } 478 return 0; 479} 480#endif 481 482/* 483 * This function is called to mark the interrupt as disabled (no command 484 * configured on subdevice 1) and to physically disable the interrupt 485 * (not possible on the PC36AT, except by removing the IRQ jumper!). 486 */ 487static void pc236_intr_disable(struct comedi_device *dev) 488{ 489 unsigned long flags; 490 491 spin_lock_irqsave(&dev->spinlock, flags); 492 devpriv->enable_irq = 0; 493#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 494 if (devpriv->lcr_iobase) 495 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 496#endif 497 spin_unlock_irqrestore(&dev->spinlock, flags); 498} 499 500/* 501 * This function is called to mark the interrupt as enabled (a command 502 * configured on subdevice 1) and to physically enable the interrupt 503 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). 504 */ 505static void pc236_intr_enable(struct comedi_device *dev) 506{ 507 unsigned long flags; 508 509 spin_lock_irqsave(&dev->spinlock, flags); 510 devpriv->enable_irq = 1; 511#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 512 if (devpriv->lcr_iobase) 513 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 514#endif 515 spin_unlock_irqrestore(&dev->spinlock, flags); 516} 517 518/* 519 * This function is called when an interrupt occurs to check whether 520 * the interrupt has been marked as enabled and was generated by the 521 * board. If so, the function prepares the hardware for the next 522 * interrupt. 523 * Returns 0 if the interrupt should be ignored. 524 */ 525static int pc236_intr_check(struct comedi_device *dev) 526{ 527 int retval = 0; 528 unsigned long flags; 529 530 spin_lock_irqsave(&dev->spinlock, flags); 531 if (devpriv->enable_irq) { 532 retval = 1; 533#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 534 if (devpriv->lcr_iobase) { 535 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR) 536 & PLX9052_INTCSR_LI1STAT_MASK) 537 == PLX9052_INTCSR_LI1STAT_INACTIVE) { 538 retval = 0; 539 } else { 540 /* Clear interrupt and keep it enabled. */ 541 outl(PCI236_INTR_ENABLE, 542 devpriv->lcr_iobase + PLX9052_INTCSR); 543 } 544 } 545#endif 546 } 547 spin_unlock_irqrestore(&dev->spinlock, flags); 548 549 return retval; 550} 551 552/* 553 * Input from subdevice 1. 554 * Copied from the comedi_parport driver. 555 */ 556static int pc236_intr_insn(struct comedi_device *dev, 557 struct comedi_subdevice *s, struct comedi_insn *insn, 558 unsigned int *data) 559{ 560 data[1] = 0; 561 return 2; 562} 563 564/* 565 * Subdevice 1 command test. 566 * Copied from the comedi_parport driver. 567 */ 568static int pc236_intr_cmdtest(struct comedi_device *dev, 569 struct comedi_subdevice *s, 570 struct comedi_cmd *cmd) 571{ 572 int err = 0; 573 int tmp; 574 575 /* step 1 */ 576 577 tmp = cmd->start_src; 578 cmd->start_src &= TRIG_NOW; 579 if (!cmd->start_src || tmp != cmd->start_src) 580 err++; 581 582 tmp = cmd->scan_begin_src; 583 cmd->scan_begin_src &= TRIG_EXT; 584 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 585 err++; 586 587 tmp = cmd->convert_src; 588 cmd->convert_src &= TRIG_FOLLOW; 589 if (!cmd->convert_src || tmp != cmd->convert_src) 590 err++; 591 592 tmp = cmd->scan_end_src; 593 cmd->scan_end_src &= TRIG_COUNT; 594 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 595 err++; 596 597 tmp = cmd->stop_src; 598 cmd->stop_src &= TRIG_NONE; 599 if (!cmd->stop_src || tmp != cmd->stop_src) 600 err++; 601 602 if (err) 603 return 1; 604 605 /* step 2: ignored */ 606 607 if (err) 608 return 2; 609 610 /* step 3: */ 611 612 if (cmd->start_arg != 0) { 613 cmd->start_arg = 0; 614 err++; 615 } 616 if (cmd->scan_begin_arg != 0) { 617 cmd->scan_begin_arg = 0; 618 err++; 619 } 620 if (cmd->convert_arg != 0) { 621 cmd->convert_arg = 0; 622 err++; 623 } 624 if (cmd->scan_end_arg != 1) { 625 cmd->scan_end_arg = 1; 626 err++; 627 } 628 if (cmd->stop_arg != 0) { 629 cmd->stop_arg = 0; 630 err++; 631 } 632 633 if (err) 634 return 3; 635 636 /* step 4: ignored */ 637 638 if (err) 639 return 4; 640 641 return 0; 642} 643 644/* 645 * Subdevice 1 command. 646 */ 647static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 648{ 649 pc236_intr_enable(dev); 650 651 return 0; 652} 653 654/* 655 * Subdevice 1 cancel command. 656 */ 657static int pc236_intr_cancel(struct comedi_device *dev, 658 struct comedi_subdevice *s) 659{ 660 pc236_intr_disable(dev); 661 662 return 0; 663} 664 665/* 666 * Interrupt service routine. 667 * Based on the comedi_parport driver. 668 */ 669static irqreturn_t pc236_interrupt(int irq, void *d) 670{ 671 struct comedi_device *dev = d; 672 struct comedi_subdevice *s = dev->subdevices + 1; 673 int handled; 674 675 handled = pc236_intr_check(dev); 676 if (dev->attached && handled) { 677 comedi_buf_put(s->async, 0); 678 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; 679 comedi_event(dev, s); 680 } 681 return IRQ_RETVAL(handled); 682} 683 684MODULE_AUTHOR("Comedi http://www.comedi.org"); 685MODULE_DESCRIPTION("Comedi low-level driver"); 686MODULE_LICENSE("GPL"); 687