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