amplc_pc236.c revision e7637a912911ae4346e19e954d9481f7a5b0e48b
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) 25 * Updated: Thu, 24 Jul 2014 14:25:26 +0000 26 * Status: works 27 * 28 * Configuration options - PC36AT: 29 * [0] - I/O port base address 30 * [1] - IRQ (optional) 31 * 32 * Manual configuration of PCI board (PCI236) is not supported; it is 33 * configured automatically. 34 * 35 * The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing 36 * as subdevice 0. 37 * 38 * Subdevice 1 pretends to be a digital input device, but it always returns 39 * 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT, 40 * a rising edge on port C bit 3 acts as an external trigger, which can be 41 * used to wake up tasks. This is like the comedi_parport device, but the 42 * only way to physically disable the interrupt on the PC36AT is to remove 43 * the IRQ jumper. If no interrupt is connected, then subdevice 1 is 44 * unused. 45 */ 46 47#include <linux/module.h> 48#include <linux/pci.h> 49#include <linux/interrupt.h> 50 51#include "../comedidev.h" 52 53#include "comedi_fc.h" 54#include "8255.h" 55#include "plx9052.h" 56 57#define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) 58#define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) 59 60/* PC36AT / PCI236 registers */ 61 62/* Disable, and clear, interrupts */ 63#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \ 64 PLX9052_INTCSR_LI2POL | \ 65 PLX9052_INTCSR_LI1SEL | \ 66 PLX9052_INTCSR_LI1CLRINT) 67 68/* Enable, and clear, interrupts */ 69#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \ 70 PLX9052_INTCSR_LI1POL | \ 71 PLX9052_INTCSR_LI2POL | \ 72 PLX9052_INTCSR_PCIENAB | \ 73 PLX9052_INTCSR_LI1SEL | \ 74 PLX9052_INTCSR_LI1CLRINT) 75 76/* 77 * Board descriptions for Amplicon PC36AT and PCI236. 78 */ 79 80enum pc236_bustype { isa_bustype, pci_bustype }; 81 82struct pc236_board { 83 const char *name; 84 enum pc236_bustype bustype; 85}; 86 87struct pc236_private { 88 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ 89 int enable_irq; 90}; 91 92/* test if ISA supported and this is an ISA board */ 93static inline bool is_isa_board(const struct pc236_board *board) 94{ 95 return DO_ISA && board->bustype == isa_bustype; 96} 97 98/* test if PCI supported and this is a PCI board */ 99static inline bool is_pci_board(const struct pc236_board *board) 100{ 101 return DO_PCI && board->bustype == pci_bustype; 102} 103 104/* 105 * This function is called to mark the interrupt as disabled (no command 106 * configured on subdevice 1) and to physically disable the interrupt 107 * (not possible on the PC36AT, except by removing the IRQ jumper!). 108 */ 109static void pc236_intr_disable(struct comedi_device *dev) 110{ 111 const struct pc236_board *thisboard = comedi_board(dev); 112 struct pc236_private *devpriv = dev->private; 113 unsigned long flags; 114 115 spin_lock_irqsave(&dev->spinlock, flags); 116 devpriv->enable_irq = 0; 117 if (is_pci_board(thisboard)) 118 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 119 spin_unlock_irqrestore(&dev->spinlock, flags); 120} 121 122/* 123 * This function is called to mark the interrupt as enabled (a command 124 * configured on subdevice 1) and to physically enable the interrupt 125 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). 126 */ 127static void pc236_intr_enable(struct comedi_device *dev) 128{ 129 const struct pc236_board *thisboard = comedi_board(dev); 130 struct pc236_private *devpriv = dev->private; 131 unsigned long flags; 132 133 spin_lock_irqsave(&dev->spinlock, flags); 134 devpriv->enable_irq = 1; 135 if (is_pci_board(thisboard)) 136 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); 137 spin_unlock_irqrestore(&dev->spinlock, flags); 138} 139 140/* 141 * This function is called when an interrupt occurs to check whether 142 * the interrupt has been marked as enabled and was generated by the 143 * board. If so, the function prepares the hardware for the next 144 * interrupt. 145 * Returns 0 if the interrupt should be ignored. 146 */ 147static int pc236_intr_check(struct comedi_device *dev) 148{ 149 const struct pc236_board *thisboard = comedi_board(dev); 150 struct pc236_private *devpriv = dev->private; 151 int retval = 0; 152 unsigned long flags; 153 unsigned int intcsr; 154 155 spin_lock_irqsave(&dev->spinlock, flags); 156 if (devpriv->enable_irq) { 157 retval = 1; 158 if (is_pci_board(thisboard)) { 159 intcsr = inl(devpriv->lcr_iobase + PLX9052_INTCSR); 160 if (!(intcsr & PLX9052_INTCSR_LI1STAT)) { 161 retval = 0; 162 } else { 163 /* Clear interrupt and keep it enabled. */ 164 outl(PCI236_INTR_ENABLE, 165 devpriv->lcr_iobase + PLX9052_INTCSR); 166 } 167 } 168 } 169 spin_unlock_irqrestore(&dev->spinlock, flags); 170 171 return retval; 172} 173 174/* 175 * Input from subdevice 1. 176 * Copied from the comedi_parport driver. 177 */ 178static int pc236_intr_insn(struct comedi_device *dev, 179 struct comedi_subdevice *s, struct comedi_insn *insn, 180 unsigned int *data) 181{ 182 data[1] = 0; 183 return insn->n; 184} 185 186/* 187 * Subdevice 1 command test. 188 * Copied from the comedi_parport driver. 189 */ 190static int pc236_intr_cmdtest(struct comedi_device *dev, 191 struct comedi_subdevice *s, 192 struct comedi_cmd *cmd) 193{ 194 int err = 0; 195 196 /* Step 1 : check if triggers are trivially valid */ 197 198 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); 199 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); 200 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); 201 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 202 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); 203 204 if (err) 205 return 1; 206 207 /* Step 2a : make sure trigger sources are unique */ 208 /* Step 2b : and mutually compatible */ 209 210 if (err) 211 return 2; 212 213 /* Step 3: check it arguments are trivially valid */ 214 215 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); 216 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 217 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); 218 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); 219 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); 220 221 if (err) 222 return 3; 223 224 /* step 4: ignored */ 225 226 if (err) 227 return 4; 228 229 return 0; 230} 231 232/* 233 * Subdevice 1 command. 234 */ 235static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 236{ 237 pc236_intr_enable(dev); 238 239 return 0; 240} 241 242/* 243 * Subdevice 1 cancel command. 244 */ 245static int pc236_intr_cancel(struct comedi_device *dev, 246 struct comedi_subdevice *s) 247{ 248 pc236_intr_disable(dev); 249 250 return 0; 251} 252 253/* 254 * Interrupt service routine. 255 * Based on the comedi_parport driver. 256 */ 257static irqreturn_t pc236_interrupt(int irq, void *d) 258{ 259 struct comedi_device *dev = d; 260 struct comedi_subdevice *s = dev->read_subdev; 261 int handled; 262 263 handled = pc236_intr_check(dev); 264 if (dev->attached && handled) { 265 comedi_buf_put(s, 0); 266 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; 267 comedi_event(dev, s); 268 } 269 return IRQ_RETVAL(handled); 270} 271 272static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase, 273 unsigned int irq, unsigned long req_irq_flags) 274{ 275 struct comedi_subdevice *s; 276 int ret; 277 278 dev->iobase = iobase; 279 280 ret = comedi_alloc_subdevices(dev, 2); 281 if (ret) 282 return ret; 283 284 s = &dev->subdevices[0]; 285 /* digital i/o subdevice (8255) */ 286 ret = subdev_8255_init(dev, s, NULL, iobase); 287 if (ret) 288 return ret; 289 290 s = &dev->subdevices[1]; 291 dev->read_subdev = s; 292 s->type = COMEDI_SUBD_UNUSED; 293 pc236_intr_disable(dev); 294 if (irq) { 295 if (request_irq(irq, pc236_interrupt, req_irq_flags, 296 dev->board_name, dev) >= 0) { 297 dev->irq = irq; 298 s->type = COMEDI_SUBD_DI; 299 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 300 s->n_chan = 1; 301 s->maxdata = 1; 302 s->range_table = &range_digital; 303 s->insn_bits = pc236_intr_insn; 304 s->len_chanlist = 1; 305 s->do_cmdtest = pc236_intr_cmdtest; 306 s->do_cmd = pc236_intr_cmd; 307 s->cancel = pc236_intr_cancel; 308 } 309 } 310 311 return 0; 312} 313 314static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) 315{ 316 struct pc236_private *devpriv; 317 int ret; 318 319 if (!DO_ISA) 320 return -EINVAL; 321 322 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 323 if (!devpriv) 324 return -ENOMEM; 325 326 ret = comedi_request_region(dev, it->options[0], 0x4); 327 if (ret) 328 return ret; 329 330 return pc236_common_attach(dev, dev->iobase, it->options[1], 0); 331} 332 333static const struct pc236_board pc236_pci_board = { 334 .name = "pci236", 335 .bustype = pci_bustype, 336}; 337 338static int pc236_auto_attach(struct comedi_device *dev, 339 unsigned long context_unused) 340{ 341 struct pci_dev *pci_dev = comedi_to_pci_dev(dev); 342 struct pc236_private *devpriv; 343 unsigned long iobase; 344 int ret; 345 346 if (!DO_PCI) 347 return -EINVAL; 348 349 dev_info(dev->class_dev, "attach pci %s\n", pci_name(pci_dev)); 350 351 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 352 if (!devpriv) 353 return -ENOMEM; 354 355 dev->board_ptr = &pc236_pci_board; 356 dev->board_name = pc236_pci_board.name; 357 ret = comedi_pci_enable(dev); 358 if (ret) 359 return ret; 360 361 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); 362 iobase = pci_resource_start(pci_dev, 2); 363 return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED); 364} 365 366static void pc236_detach(struct comedi_device *dev) 367{ 368 const struct pc236_board *thisboard = comedi_board(dev); 369 370 if (!thisboard) 371 return; 372 if (is_isa_board(thisboard)) { 373 comedi_legacy_detach(dev); 374 } else if (is_pci_board(thisboard)) { 375 if (dev->irq) 376 free_irq(dev->irq, dev); 377 comedi_pci_disable(dev); 378 } 379} 380 381static const struct pc236_board pc236_isa_boards[] = { 382 { 383 .name = "pc36at", 384 .bustype = isa_bustype, 385 }, 386}; 387 388static struct comedi_driver amplc_pc236_driver = { 389 .driver_name = "amplc_pc236", 390 .module = THIS_MODULE, 391 .attach = pc236_attach, 392 .auto_attach = pc236_auto_attach, 393 .detach = pc236_detach, 394#if DO_ISA 395 .board_name = &pc236_isa_boards[0].name, 396 .offset = sizeof(struct pc236_board), 397 .num_names = ARRAY_SIZE(pc236_isa_boards), 398#endif 399}; 400 401#if DO_PCI 402static const struct pci_device_id pc236_pci_table[] = { 403 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) }, 404 { 0 } 405}; 406 407MODULE_DEVICE_TABLE(pci, pc236_pci_table); 408 409static int amplc_pc236_pci_probe(struct pci_dev *dev, 410 const struct pci_device_id *id) 411{ 412 return comedi_pci_auto_config(dev, &lc_pc236_driver, 413 id->driver_data); 414} 415 416static struct pci_driver amplc_pc236_pci_driver = { 417 .name = "amplc_pc236", 418 .id_table = pc236_pci_table, 419 .probe = &lc_pc236_pci_probe, 420 .remove = comedi_pci_auto_unconfig, 421}; 422 423module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver); 424#else 425module_comedi_driver(amplc_pc236_driver); 426#endif 427 428MODULE_AUTHOR("Comedi http://www.comedi.org"); 429MODULE_DESCRIPTION("Comedi low-level driver"); 430MODULE_LICENSE("GPL"); 431