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