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