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