2 comedi/drivers/amplc_pc236.c
3 Driver for Amplicon PC36AT and PCI236 DIO boards.
5 Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
7 COMEDI - Linux Control and Measurement Device Interface
8 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
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.
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.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 Description: Amplicon PC36AT, PCI236
28 Author: Ian Abbott <abbotti@mev.co.uk>
29 Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236)
30 Updated: Wed, 01 Apr 2009 15:41:25 +0100
33 Configuration options - PC36AT:
34 [0] - I/O port base address
37 Configuration options - PCI236:
38 [0] - PCI bus of device (optional)
39 [1] - PCI slot of device (optional)
40 If bus/slot is not specified, the first available PCI device will be
43 The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
46 Subdevice 1 pretends to be a digital input device, but it always returns
47 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
48 a rising edge on port C bit 3 acts as an external trigger, which can be
49 used to wake up tasks. This is like the comedi_parport device, but the
50 only way to physically disable the interrupt on the PC36AT is to remove
51 the IRQ jumper. If no interrupt is connected, then subdevice 1 is
55 #include <linux/interrupt.h>
57 #include "../comedidev.h"
59 #include "comedi_pci.h"
64 #define PC236_DRIVER_NAME "amplc_pc236"
66 /* PCI236 PCI configuration register information */
67 #define PCI_VENDOR_ID_AMPLICON 0x14dc
68 #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
69 #define PCI_DEVICE_ID_INVALID 0xffff
71 /* PC36AT / PCI236 registers */
73 #define PC236_IO_SIZE 4
74 #define PC236_LCR_IO_SIZE 128
77 * INTCSR values for PCI236.
79 /* Disable interrupt, also clear any interrupt there */
80 #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
81 | PLX9052_INTCSR_LI1POL_HIGH \
82 | PLX9052_INTCSR_LI2POL_HIGH \
83 | PLX9052_INTCSR_PCIENAB_DISABLED \
84 | PLX9052_INTCSR_LI1SEL_EDGE \
85 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
86 /* Enable interrupt, also clear any interrupt there. */
87 #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
88 | PLX9052_INTCSR_LI1POL_HIGH \
89 | PLX9052_INTCSR_LI2POL_HIGH \
90 | PLX9052_INTCSR_PCIENAB_ENABLED \
91 | PLX9052_INTCSR_LI1SEL_EDGE \
92 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
95 * Board descriptions for Amplicon PC36AT and PCI236.
98 enum pc236_bustype { isa_bustype, pci_bustype };
99 enum pc236_model { pc36at_model, pci236_model, anypci_model };
103 const char *fancy_name;
104 unsigned short devid;
105 enum pc236_bustype bustype;
106 enum pc236_model model;
108 static const struct pc236_board pc236_boards[] = {
111 .fancy_name = "PC36AT",
112 .bustype = isa_bustype,
113 .model = pc36at_model,
115 #ifdef CONFIG_COMEDI_PCI
118 .fancy_name = "PCI236",
119 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
120 .bustype = pci_bustype,
121 .model = pci236_model,
124 #ifdef CONFIG_COMEDI_PCI
126 .name = PC236_DRIVER_NAME,
127 .fancy_name = PC236_DRIVER_NAME,
128 .devid = PCI_DEVICE_ID_INVALID,
129 .bustype = pci_bustype,
130 .model = anypci_model, /* wildcard */
135 #ifdef CONFIG_COMEDI_PCI
136 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
138 PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236,
139 PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
143 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
144 #endif /* CONFIG_COMEDI_PCI */
147 * Useful for shorthand access to the particular board structure
149 #define thisboard ((const struct pc236_board *)dev->board_ptr)
151 /* this structure is for data unique to this hardware driver. If
152 several hardware drivers keep similar information in this structure,
153 feel free to suggest moving the variable to the struct comedi_device struct.
155 struct pc236_private {
156 #ifdef CONFIG_COMEDI_PCI
158 struct pci_dev *pci_dev;
159 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
164 #define devpriv ((struct pc236_private *)dev->private)
167 * The struct comedi_driver structure tells the Comedi core module
168 * which functions to call to configure/deconfigure (attach/detach)
169 * the board, and also about the kernel module that contains
172 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
173 static int pc236_detach(struct comedi_device *dev);
174 static struct comedi_driver driver_amplc_pc236 = {
175 .driver_name = PC236_DRIVER_NAME,
176 .module = THIS_MODULE,
177 .attach = pc236_attach,
178 .detach = pc236_detach,
179 .board_name = &pc236_boards[0].name,
180 .offset = sizeof(struct pc236_board),
181 .num_names = ARRAY_SIZE(pc236_boards),
184 #ifdef CONFIG_COMEDI_PCI
185 static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev,
186 const struct pci_device_id
189 return comedi_pci_auto_config(dev, driver_amplc_pc236.driver_name);
192 static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
194 comedi_pci_auto_unconfig(dev);
197 static struct pci_driver driver_amplc_pc236_pci_driver = {
198 .id_table = pc236_pci_table,
199 .probe = &driver_amplc_pc236_pci_probe,
200 .remove = __devexit_p(&driver_amplc_pc236_pci_remove)
203 static int __init driver_amplc_pc236_init_module(void)
207 retval = comedi_driver_register(&driver_amplc_pc236);
211 driver_amplc_pc236_pci_driver.name =
212 (char *)driver_amplc_pc236.driver_name;
213 return pci_register_driver(&driver_amplc_pc236_pci_driver);
216 static void __exit driver_amplc_pc236_cleanup_module(void)
218 pci_unregister_driver(&driver_amplc_pc236_pci_driver);
219 comedi_driver_unregister(&driver_amplc_pc236);
222 module_init(driver_amplc_pc236_init_module);
223 module_exit(driver_amplc_pc236_cleanup_module);
225 static int __init driver_amplc_pc236_init_module(void)
227 return comedi_driver_register(&driver_amplc_pc236);
230 static void __exit driver_amplc_pc236_cleanup_module(void)
232 comedi_driver_unregister(&driver_amplc_pc236);
235 module_init(driver_amplc_pc236_init_module);
236 module_exit(driver_amplc_pc236_cleanup_module);
239 static int pc236_request_region(unsigned minor, unsigned long from,
240 unsigned long extent);
241 static void pc236_intr_disable(struct comedi_device *dev);
242 static void pc236_intr_enable(struct comedi_device *dev);
243 static int pc236_intr_check(struct comedi_device *dev);
244 static int pc236_intr_insn(struct comedi_device *dev,
245 struct comedi_subdevice *s, struct comedi_insn *insn,
247 static int pc236_intr_cmdtest(struct comedi_device *dev,
248 struct comedi_subdevice *s,
249 struct comedi_cmd *cmd);
250 static int pc236_intr_cmd(struct comedi_device *dev,
251 struct comedi_subdevice *s);
252 static int pc236_intr_cancel(struct comedi_device *dev,
253 struct comedi_subdevice *s);
254 static irqreturn_t pc236_interrupt(int irq, void *d);
257 * This function looks for a PCI device matching the requested board name,
260 #ifdef CONFIG_COMEDI_PCI
262 pc236_find_pci(struct comedi_device *dev, int bus, int slot,
263 struct pci_dev **pci_dev_p)
265 struct pci_dev *pci_dev = NULL;
269 /* Look for matching PCI device. */
270 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
272 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
273 PCI_ANY_ID, pci_dev)) {
274 /* If bus/slot specified, check them. */
276 if (bus != pci_dev->bus->number
277 || slot != PCI_SLOT(pci_dev->devfn))
280 if (thisboard->model == anypci_model) {
281 /* Match any supported model. */
284 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
285 if (pc236_boards[i].bustype != pci_bustype)
287 if (pci_dev->device == pc236_boards[i].devid) {
288 /* Change board_ptr to matched board. */
289 dev->board_ptr = &pc236_boards[i];
293 if (i == ARRAY_SIZE(pc236_boards))
296 /* Match specific model name. */
297 if (pci_dev->device != thisboard->devid)
302 *pci_dev_p = pci_dev;
305 /* No match found. */
308 "comedi%d: error! no %s found at pci %02x:%02x!\n",
309 dev->minor, thisboard->name, bus, slot);
311 printk(KERN_ERR "comedi%d: error! no %s found!\n",
312 dev->minor, thisboard->name);
319 * Attach is called by the Comedi core to configure the driver
320 * for a particular board. If you specified a board_name array
321 * in the driver structure, dev->board_ptr contains that
324 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
326 struct comedi_subdevice *s;
327 unsigned long iobase = 0;
328 unsigned int irq = 0;
329 #ifdef CONFIG_COMEDI_PCI
330 struct pci_dev *pci_dev = NULL;
331 int bus = 0, slot = 0;
336 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
339 * Allocate the private structure area. alloc_private() is a
340 * convenient macro defined in comedidev.h.
342 ret = alloc_private(dev, sizeof(struct pc236_private));
344 printk(KERN_ERR "comedi%d: error! out of memory!\n",
348 /* Process options. */
349 switch (thisboard->bustype) {
351 iobase = it->options[0];
352 irq = it->options[1];
355 #ifdef CONFIG_COMEDI_PCI
357 bus = it->options[0];
358 slot = it->options[1];
361 ret = pc236_find_pci(dev, bus, slot, &pci_dev);
364 devpriv->pci_dev = pci_dev;
366 #endif /* CONFIG_COMEDI_PCI */
369 "comedi%d: %s: BUG! cannot determine board type!\n",
370 dev->minor, PC236_DRIVER_NAME);
376 * Initialize dev->board_name.
378 dev->board_name = thisboard->name;
380 /* Enable device and reserve I/O spaces. */
381 #ifdef CONFIG_COMEDI_PCI
384 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
387 "comedi%d: error! cannot enable PCI device and request regions!\n",
391 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
392 iobase = pci_resource_start(pci_dev, 2);
397 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
401 dev->iobase = iobase;
404 * Allocate the subdevice structures. alloc_subdevice() is a
405 * convenient macro defined in comedidev.h.
407 ret = alloc_subdevices(dev, 2);
409 printk(KERN_ERR "comedi%d: error! out of memory!\n",
414 s = dev->subdevices + 0;
415 /* digital i/o subdevice (8255) */
416 ret = subdev_8255_init(dev, s, NULL, iobase);
418 printk(KERN_ERR "comedi%d: error! out of memory!\n",
422 s = dev->subdevices + 1;
423 dev->read_subdev = s;
424 s->type = COMEDI_SUBD_UNUSED;
425 pc236_intr_disable(dev);
427 unsigned long flags = share_irq ? IRQF_SHARED : 0;
429 if (request_irq(irq, pc236_interrupt, flags,
430 PC236_DRIVER_NAME, dev) >= 0) {
432 s->type = COMEDI_SUBD_DI;
433 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
436 s->range_table = &range_digital;
437 s->insn_bits = pc236_intr_insn;
438 s->do_cmdtest = pc236_intr_cmdtest;
439 s->do_cmd = pc236_intr_cmd;
440 s->cancel = pc236_intr_cancel;
443 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
444 if (thisboard->bustype == isa_bustype) {
445 printk("(base %#lx) ", iobase);
447 #ifdef CONFIG_COMEDI_PCI
448 printk("(pci %s) ", pci_name(pci_dev));
452 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
456 printk("attached\n");
462 * _detach is called to deconfigure a device. It should deallocate
464 * This function is also called when _attach() fails, so it should be
465 * careful not to release resources that were not necessarily
466 * allocated by _attach(). dev->private and dev->subdevices are
467 * deallocated automatically by the core.
469 static int pc236_detach(struct comedi_device *dev)
471 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
474 pc236_intr_disable(dev);
477 free_irq(dev->irq, dev);
479 subdev_8255_cleanup(dev, dev->subdevices + 0);
481 #ifdef CONFIG_COMEDI_PCI
482 if (devpriv->pci_dev) {
484 comedi_pci_disable(devpriv->pci_dev);
485 pci_dev_put(devpriv->pci_dev);
490 release_region(dev->iobase, PC236_IO_SIZE);
493 if (dev->board_name) {
494 printk(KERN_INFO "comedi%d: %s removed\n",
495 dev->minor, dev->board_name);
501 * This function checks and requests an I/O region, reporting an error
502 * if there is a conflict.
504 static int pc236_request_region(unsigned minor, unsigned long from,
505 unsigned long extent)
507 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
508 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
509 minor, from, extent);
516 * This function is called to mark the interrupt as disabled (no command
517 * configured on subdevice 1) and to physically disable the interrupt
518 * (not possible on the PC36AT, except by removing the IRQ jumper!).
520 static void pc236_intr_disable(struct comedi_device *dev)
524 spin_lock_irqsave(&dev->spinlock, flags);
525 devpriv->enable_irq = 0;
526 #ifdef CONFIG_COMEDI_PCI
527 if (devpriv->lcr_iobase)
528 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
530 spin_unlock_irqrestore(&dev->spinlock, flags);
534 * This function is called to mark the interrupt as enabled (a command
535 * configured on subdevice 1) and to physically enable the interrupt
536 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
538 static void pc236_intr_enable(struct comedi_device *dev)
542 spin_lock_irqsave(&dev->spinlock, flags);
543 devpriv->enable_irq = 1;
544 #ifdef CONFIG_COMEDI_PCI
545 if (devpriv->lcr_iobase)
546 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
548 spin_unlock_irqrestore(&dev->spinlock, flags);
552 * This function is called when an interrupt occurs to check whether
553 * the interrupt has been marked as enabled and was generated by the
554 * board. If so, the function prepares the hardware for the next
556 * Returns 0 if the interrupt should be ignored.
558 static int pc236_intr_check(struct comedi_device *dev)
563 spin_lock_irqsave(&dev->spinlock, flags);
564 if (devpriv->enable_irq) {
566 #ifdef CONFIG_COMEDI_PCI
567 if (devpriv->lcr_iobase) {
568 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
569 & PLX9052_INTCSR_LI1STAT_MASK)
570 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
573 /* Clear interrupt and keep it enabled. */
574 outl(PCI236_INTR_ENABLE,
575 devpriv->lcr_iobase + PLX9052_INTCSR);
580 spin_unlock_irqrestore(&dev->spinlock, flags);
586 * Input from subdevice 1.
587 * Copied from the comedi_parport driver.
589 static int pc236_intr_insn(struct comedi_device *dev,
590 struct comedi_subdevice *s, struct comedi_insn *insn,
598 * Subdevice 1 command test.
599 * Copied from the comedi_parport driver.
601 static int pc236_intr_cmdtest(struct comedi_device *dev,
602 struct comedi_subdevice *s,
603 struct comedi_cmd *cmd)
610 tmp = cmd->start_src;
611 cmd->start_src &= TRIG_NOW;
612 if (!cmd->start_src || tmp != cmd->start_src)
615 tmp = cmd->scan_begin_src;
616 cmd->scan_begin_src &= TRIG_EXT;
617 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
620 tmp = cmd->convert_src;
621 cmd->convert_src &= TRIG_FOLLOW;
622 if (!cmd->convert_src || tmp != cmd->convert_src)
625 tmp = cmd->scan_end_src;
626 cmd->scan_end_src &= TRIG_COUNT;
627 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
631 cmd->stop_src &= TRIG_NONE;
632 if (!cmd->stop_src || tmp != cmd->stop_src)
638 /* step 2: ignored */
645 if (cmd->start_arg != 0) {
649 if (cmd->scan_begin_arg != 0) {
650 cmd->scan_begin_arg = 0;
653 if (cmd->convert_arg != 0) {
654 cmd->convert_arg = 0;
657 if (cmd->scan_end_arg != 1) {
658 cmd->scan_end_arg = 1;
661 if (cmd->stop_arg != 0) {
669 /* step 4: ignored */
678 * Subdevice 1 command.
680 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
682 pc236_intr_enable(dev);
688 * Subdevice 1 cancel command.
690 static int pc236_intr_cancel(struct comedi_device *dev,
691 struct comedi_subdevice *s)
693 pc236_intr_disable(dev);
699 * Interrupt service routine.
700 * Based on the comedi_parport driver.
702 static irqreturn_t pc236_interrupt(int irq, void *d)
704 struct comedi_device *dev = d;
705 struct comedi_subdevice *s = dev->subdevices + 1;
708 handled = pc236_intr_check(dev);
709 if (dev->attached && handled) {
710 comedi_buf_put(s->async, 0);
711 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
712 comedi_event(dev, s);
714 return IRQ_RETVAL(handled);
717 MODULE_AUTHOR("Comedi http://www.comedi.org");
718 MODULE_DESCRIPTION("Comedi low-level driver");
719 MODULE_LICENSE("GPL");