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) = {
137 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
141 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
142 #endif /* CONFIG_COMEDI_PCI */
145 * Useful for shorthand access to the particular board structure
147 #define thisboard ((const struct pc236_board *)dev->board_ptr)
149 /* this structure is for data unique to this hardware driver. If
150 several hardware drivers keep similar information in this structure,
151 feel free to suggest moving the variable to the struct comedi_device struct.
153 struct pc236_private {
154 #ifdef CONFIG_COMEDI_PCI
156 struct pci_dev *pci_dev;
157 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
162 #define devpriv ((struct pc236_private *)dev->private)
165 * The struct comedi_driver structure tells the Comedi core module
166 * which functions to call to configure/deconfigure (attach/detach)
167 * the board, and also about the kernel module that contains
170 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
171 static int pc236_detach(struct comedi_device *dev);
172 static struct comedi_driver driver_amplc_pc236 = {
173 .driver_name = PC236_DRIVER_NAME,
174 .module = THIS_MODULE,
175 .attach = pc236_attach,
176 .detach = pc236_detach,
177 .board_name = &pc236_boards[0].name,
178 .offset = sizeof(struct pc236_board),
179 .num_names = ARRAY_SIZE(pc236_boards),
182 #ifdef CONFIG_COMEDI_PCI
183 static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev,
184 const struct pci_device_id
187 return comedi_pci_auto_config(dev, driver_amplc_pc236.driver_name);
190 static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
192 comedi_pci_auto_unconfig(dev);
195 static struct pci_driver driver_amplc_pc236_pci_driver = {
196 .id_table = pc236_pci_table,
197 .probe = &driver_amplc_pc236_pci_probe,
198 .remove = __devexit_p(&driver_amplc_pc236_pci_remove)
201 static int __init driver_amplc_pc236_init_module(void)
205 retval = comedi_driver_register(&driver_amplc_pc236);
209 driver_amplc_pc236_pci_driver.name =
210 (char *)driver_amplc_pc236.driver_name;
211 return pci_register_driver(&driver_amplc_pc236_pci_driver);
214 static void __exit driver_amplc_pc236_cleanup_module(void)
216 pci_unregister_driver(&driver_amplc_pc236_pci_driver);
217 comedi_driver_unregister(&driver_amplc_pc236);
220 module_init(driver_amplc_pc236_init_module);
221 module_exit(driver_amplc_pc236_cleanup_module);
223 static int __init driver_amplc_pc236_init_module(void)
225 return comedi_driver_register(&driver_amplc_pc236);
228 static void __exit driver_amplc_pc236_cleanup_module(void)
230 comedi_driver_unregister(&driver_amplc_pc236);
233 module_init(driver_amplc_pc236_init_module);
234 module_exit(driver_amplc_pc236_cleanup_module);
237 static int pc236_request_region(unsigned minor, unsigned long from,
238 unsigned long extent);
239 static void pc236_intr_disable(struct comedi_device *dev);
240 static void pc236_intr_enable(struct comedi_device *dev);
241 static int pc236_intr_check(struct comedi_device *dev);
242 static int pc236_intr_insn(struct comedi_device *dev,
243 struct comedi_subdevice *s, struct comedi_insn *insn,
245 static int pc236_intr_cmdtest(struct comedi_device *dev,
246 struct comedi_subdevice *s,
247 struct comedi_cmd *cmd);
248 static int pc236_intr_cmd(struct comedi_device *dev,
249 struct comedi_subdevice *s);
250 static int pc236_intr_cancel(struct comedi_device *dev,
251 struct comedi_subdevice *s);
252 static irqreturn_t pc236_interrupt(int irq, void *d);
255 * This function looks for a PCI device matching the requested board name,
258 #ifdef CONFIG_COMEDI_PCI
260 pc236_find_pci(struct comedi_device *dev, int bus, int slot,
261 struct pci_dev **pci_dev_p)
263 struct pci_dev *pci_dev = NULL;
267 /* Look for matching PCI device. */
268 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
270 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
271 PCI_ANY_ID, pci_dev)) {
272 /* If bus/slot specified, check them. */
274 if (bus != pci_dev->bus->number
275 || slot != PCI_SLOT(pci_dev->devfn))
278 if (thisboard->model == anypci_model) {
279 /* Match any supported model. */
282 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
283 if (pc236_boards[i].bustype != pci_bustype)
285 if (pci_dev->device == pc236_boards[i].devid) {
286 /* Change board_ptr to matched board. */
287 dev->board_ptr = &pc236_boards[i];
291 if (i == ARRAY_SIZE(pc236_boards))
294 /* Match specific model name. */
295 if (pci_dev->device != thisboard->devid)
300 *pci_dev_p = pci_dev;
303 /* No match found. */
306 "comedi%d: error! no %s found at pci %02x:%02x!\n",
307 dev->minor, thisboard->name, bus, slot);
309 printk(KERN_ERR "comedi%d: error! no %s found!\n",
310 dev->minor, thisboard->name);
317 * Attach is called by the Comedi core to configure the driver
318 * for a particular board. If you specified a board_name array
319 * in the driver structure, dev->board_ptr contains that
322 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
324 struct comedi_subdevice *s;
325 unsigned long iobase = 0;
326 unsigned int irq = 0;
327 #ifdef CONFIG_COMEDI_PCI
328 struct pci_dev *pci_dev = NULL;
329 int bus = 0, slot = 0;
334 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
337 * Allocate the private structure area. alloc_private() is a
338 * convenient macro defined in comedidev.h.
340 ret = alloc_private(dev, sizeof(struct pc236_private));
342 printk(KERN_ERR "comedi%d: error! out of memory!\n",
346 /* Process options. */
347 switch (thisboard->bustype) {
349 iobase = it->options[0];
350 irq = it->options[1];
353 #ifdef CONFIG_COMEDI_PCI
355 bus = it->options[0];
356 slot = it->options[1];
359 ret = pc236_find_pci(dev, bus, slot, &pci_dev);
362 devpriv->pci_dev = pci_dev;
364 #endif /* CONFIG_COMEDI_PCI */
367 "comedi%d: %s: BUG! cannot determine board type!\n",
368 dev->minor, PC236_DRIVER_NAME);
374 * Initialize dev->board_name.
376 dev->board_name = thisboard->name;
378 /* Enable device and reserve I/O spaces. */
379 #ifdef CONFIG_COMEDI_PCI
382 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
385 "comedi%d: error! cannot enable PCI device and request regions!\n",
389 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
390 iobase = pci_resource_start(pci_dev, 2);
395 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
399 dev->iobase = iobase;
402 * Allocate the subdevice structures. alloc_subdevice() is a
403 * convenient macro defined in comedidev.h.
405 ret = alloc_subdevices(dev, 2);
407 printk(KERN_ERR "comedi%d: error! out of memory!\n",
412 s = dev->subdevices + 0;
413 /* digital i/o subdevice (8255) */
414 ret = subdev_8255_init(dev, s, NULL, iobase);
416 printk(KERN_ERR "comedi%d: error! out of memory!\n",
420 s = dev->subdevices + 1;
421 dev->read_subdev = s;
422 s->type = COMEDI_SUBD_UNUSED;
423 pc236_intr_disable(dev);
425 unsigned long flags = share_irq ? IRQF_SHARED : 0;
427 if (request_irq(irq, pc236_interrupt, flags,
428 PC236_DRIVER_NAME, dev) >= 0) {
430 s->type = COMEDI_SUBD_DI;
431 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
434 s->range_table = &range_digital;
435 s->insn_bits = pc236_intr_insn;
436 s->do_cmdtest = pc236_intr_cmdtest;
437 s->do_cmd = pc236_intr_cmd;
438 s->cancel = pc236_intr_cancel;
441 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
442 if (thisboard->bustype == isa_bustype) {
443 printk("(base %#lx) ", iobase);
445 #ifdef CONFIG_COMEDI_PCI
446 printk("(pci %s) ", pci_name(pci_dev));
450 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
454 printk("attached\n");
460 * _detach is called to deconfigure a device. It should deallocate
462 * This function is also called when _attach() fails, so it should be
463 * careful not to release resources that were not necessarily
464 * allocated by _attach(). dev->private and dev->subdevices are
465 * deallocated automatically by the core.
467 static int pc236_detach(struct comedi_device *dev)
469 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
472 pc236_intr_disable(dev);
475 free_irq(dev->irq, dev);
477 subdev_8255_cleanup(dev, dev->subdevices + 0);
479 #ifdef CONFIG_COMEDI_PCI
480 if (devpriv->pci_dev) {
482 comedi_pci_disable(devpriv->pci_dev);
483 pci_dev_put(devpriv->pci_dev);
488 release_region(dev->iobase, PC236_IO_SIZE);
491 if (dev->board_name) {
492 printk(KERN_INFO "comedi%d: %s removed\n",
493 dev->minor, dev->board_name);
499 * This function checks and requests an I/O region, reporting an error
500 * if there is a conflict.
502 static int pc236_request_region(unsigned minor, unsigned long from,
503 unsigned long extent)
505 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
506 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
507 minor, from, extent);
514 * This function is called to mark the interrupt as disabled (no command
515 * configured on subdevice 1) and to physically disable the interrupt
516 * (not possible on the PC36AT, except by removing the IRQ jumper!).
518 static void pc236_intr_disable(struct comedi_device *dev)
522 spin_lock_irqsave(&dev->spinlock, flags);
523 devpriv->enable_irq = 0;
524 #ifdef CONFIG_COMEDI_PCI
525 if (devpriv->lcr_iobase)
526 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
528 spin_unlock_irqrestore(&dev->spinlock, flags);
532 * This function is called to mark the interrupt as enabled (a command
533 * configured on subdevice 1) and to physically enable the interrupt
534 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
536 static void pc236_intr_enable(struct comedi_device *dev)
540 spin_lock_irqsave(&dev->spinlock, flags);
541 devpriv->enable_irq = 1;
542 #ifdef CONFIG_COMEDI_PCI
543 if (devpriv->lcr_iobase)
544 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
546 spin_unlock_irqrestore(&dev->spinlock, flags);
550 * This function is called when an interrupt occurs to check whether
551 * the interrupt has been marked as enabled and was generated by the
552 * board. If so, the function prepares the hardware for the next
554 * Returns 0 if the interrupt should be ignored.
556 static int pc236_intr_check(struct comedi_device *dev)
561 spin_lock_irqsave(&dev->spinlock, flags);
562 if (devpriv->enable_irq) {
564 #ifdef CONFIG_COMEDI_PCI
565 if (devpriv->lcr_iobase) {
566 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
567 & PLX9052_INTCSR_LI1STAT_MASK)
568 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
571 /* Clear interrupt and keep it enabled. */
572 outl(PCI236_INTR_ENABLE,
573 devpriv->lcr_iobase + PLX9052_INTCSR);
578 spin_unlock_irqrestore(&dev->spinlock, flags);
584 * Input from subdevice 1.
585 * Copied from the comedi_parport driver.
587 static int pc236_intr_insn(struct comedi_device *dev,
588 struct comedi_subdevice *s, struct comedi_insn *insn,
596 * Subdevice 1 command test.
597 * Copied from the comedi_parport driver.
599 static int pc236_intr_cmdtest(struct comedi_device *dev,
600 struct comedi_subdevice *s,
601 struct comedi_cmd *cmd)
608 tmp = cmd->start_src;
609 cmd->start_src &= TRIG_NOW;
610 if (!cmd->start_src || tmp != cmd->start_src)
613 tmp = cmd->scan_begin_src;
614 cmd->scan_begin_src &= TRIG_EXT;
615 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
618 tmp = cmd->convert_src;
619 cmd->convert_src &= TRIG_FOLLOW;
620 if (!cmd->convert_src || tmp != cmd->convert_src)
623 tmp = cmd->scan_end_src;
624 cmd->scan_end_src &= TRIG_COUNT;
625 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
629 cmd->stop_src &= TRIG_NONE;
630 if (!cmd->stop_src || tmp != cmd->stop_src)
636 /* step 2: ignored */
643 if (cmd->start_arg != 0) {
647 if (cmd->scan_begin_arg != 0) {
648 cmd->scan_begin_arg = 0;
651 if (cmd->convert_arg != 0) {
652 cmd->convert_arg = 0;
655 if (cmd->scan_end_arg != 1) {
656 cmd->scan_end_arg = 1;
659 if (cmd->stop_arg != 0) {
667 /* step 4: ignored */
676 * Subdevice 1 command.
678 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
680 pc236_intr_enable(dev);
686 * Subdevice 1 cancel command.
688 static int pc236_intr_cancel(struct comedi_device *dev,
689 struct comedi_subdevice *s)
691 pc236_intr_disable(dev);
697 * Interrupt service routine.
698 * Based on the comedi_parport driver.
700 static irqreturn_t pc236_interrupt(int irq, void *d)
702 struct comedi_device *dev = d;
703 struct comedi_subdevice *s = dev->subdevices + 1;
706 handled = pc236_intr_check(dev);
707 if (dev->attached && handled) {
708 comedi_buf_put(s->async, 0);
709 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
710 comedi_event(dev, s);
712 return IRQ_RETVAL(handled);
715 MODULE_AUTHOR("Comedi http://www.comedi.org");
716 MODULE_DESCRIPTION("Comedi low-level driver");
717 MODULE_LICENSE("GPL");