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