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