1/* 2 * comedi/drivers/amplc_pci236.c 3 * Driver for Amplicon PCI236 DIO boards. 4 * 5 * Copyright (C) 2002-2014 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_pci236 22 * Description: Amplicon PCI236 23 * Author: Ian Abbott <abbotti@mev.co.uk> 24 * Devices: [Amplicon] PCI236 (amplc_pci236) 25 * Updated: Fri, 25 Jul 2014 15:32:40 +0000 26 * Status: works 27 * 28 * Configuration options: 29 * none 30 * 31 * Manual configuration of PCI board (PCI236) is not supported; it is 32 * configured automatically. 33 * 34 * The PCI236 board has a single 8255 appearing as subdevice 0. 35 * 36 * Subdevice 1 pretends to be a digital input device, but it always 37 * returns 0 when read. However, if you run a command with 38 * scan_begin_src=TRIG_EXT, a rising edge on port C bit 3 acts as an 39 * external trigger, which can be used to wake up tasks. This is like 40 * the comedi_parport device. If no interrupt is connected, then 41 * subdevice 1 is unused. 42 */ 43 44#include <linux/module.h> 45#include <linux/pci.h> 46#include <linux/interrupt.h> 47 48#include "../comedidev.h" 49 50#include "amplc_pc236.h" 51#include "plx9052.h" 52 53/* Disable, and clear, interrupts */ 54#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \ 55 PLX9052_INTCSR_LI2POL | \ 56 PLX9052_INTCSR_LI1SEL | \ 57 PLX9052_INTCSR_LI1CLRINT) 58 59/* Enable, and clear, interrupts */ 60#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \ 61 PLX9052_INTCSR_LI1POL | \ 62 PLX9052_INTCSR_LI2POL | \ 63 PLX9052_INTCSR_PCIENAB | \ 64 PLX9052_INTCSR_LI1SEL | \ 65 PLX9052_INTCSR_LI1CLRINT) 66 67static void pci236_intr_update_cb(struct comedi_device *dev, bool enable) 68{ 69 struct pc236_private *devpriv = dev->private; 70 71 /* this will also clear the "local interrupt 1" latch */ 72 outl(enable ? PCI236_INTR_ENABLE : PCI236_INTR_DISABLE, 73 devpriv->lcr_iobase + PLX9052_INTCSR); 74} 75 76static bool pci236_intr_chk_clr_cb(struct comedi_device *dev) 77{ 78 struct pc236_private *devpriv = dev->private; 79 80 /* check if interrupt occurred */ 81 if (!(inl(devpriv->lcr_iobase + PLX9052_INTCSR) & 82 PLX9052_INTCSR_LI1STAT)) 83 return false; 84 /* clear the interrupt */ 85 pci236_intr_update_cb(dev, devpriv->enable_irq); 86 return true; 87} 88 89static const struct pc236_board pc236_pci_board = { 90 .name = "pci236", 91 .intr_update_cb = pci236_intr_update_cb, 92 .intr_chk_clr_cb = pci236_intr_chk_clr_cb, 93}; 94 95static int pci236_auto_attach(struct comedi_device *dev, 96 unsigned long context_unused) 97{ 98 struct pci_dev *pci_dev = comedi_to_pci_dev(dev); 99 struct pc236_private *devpriv; 100 unsigned long iobase; 101 int ret; 102 103 dev_info(dev->class_dev, "amplc_pci236: attach pci %s\n", 104 pci_name(pci_dev)); 105 106 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 107 if (!devpriv) 108 return -ENOMEM; 109 110 dev->board_ptr = &pc236_pci_board; 111 dev->board_name = pc236_pci_board.name; 112 ret = comedi_pci_enable(dev); 113 if (ret) 114 return ret; 115 116 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); 117 iobase = pci_resource_start(pci_dev, 2); 118 return amplc_pc236_common_attach(dev, iobase, pci_dev->irq, 119 IRQF_SHARED); 120} 121 122static struct comedi_driver amplc_pci236_driver = { 123 .driver_name = "amplc_pci236", 124 .module = THIS_MODULE, 125 .auto_attach = pci236_auto_attach, 126 .detach = comedi_pci_detach, 127}; 128 129static const struct pci_device_id pci236_pci_table[] = { 130 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) }, 131 { 0 } 132}; 133 134MODULE_DEVICE_TABLE(pci, pci236_pci_table); 135 136static int amplc_pci236_pci_probe(struct pci_dev *dev, 137 const struct pci_device_id *id) 138{ 139 return comedi_pci_auto_config(dev, &lc_pci236_driver, 140 id->driver_data); 141} 142 143static struct pci_driver amplc_pci236_pci_driver = { 144 .name = "amplc_pci236", 145 .id_table = pci236_pci_table, 146 .probe = &lc_pci236_pci_probe, 147 .remove = comedi_pci_auto_unconfig, 148}; 149 150module_comedi_pci_driver(amplc_pci236_driver, amplc_pci236_pci_driver); 151 152MODULE_AUTHOR("Comedi http://www.comedi.org"); 153MODULE_DESCRIPTION("Comedi driver for Amplicon PCI236 DIO boards"); 154MODULE_LICENSE("GPL"); 155