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