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