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. */
154 struct pc236_private {
155 #ifdef CONFIG_COMEDI_PCI
157 struct pci_dev *pci_dev;
158 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
163 #define devpriv ((struct pc236_private *)dev->private)
166 * The struct comedi_driver structure tells the Comedi core module
167 * which functions to call to configure/deconfigure (attach/detach)
168 * the board, and also about the kernel module that contains
171 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
172 static int pc236_detach(struct comedi_device *dev);
173 static struct comedi_driver driver_amplc_pc236 = {
174 .driver_name = PC236_DRIVER_NAME,
175 .module = THIS_MODULE,
176 .attach = pc236_attach,
177 .detach = pc236_detach,
178 .board_name = &pc236_boards[0].name,
179 .offset = sizeof(struct pc236_board),
180 .num_names = ARRAY_SIZE(pc236_boards),
183 #ifdef CONFIG_COMEDI_PCI
184 COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table);
186 COMEDI_INITCLEANUP(driver_amplc_pc236);
189 static int pc236_request_region(unsigned minor, unsigned long from,
190 unsigned long extent);
191 static void pc236_intr_disable(struct comedi_device *dev);
192 static void pc236_intr_enable(struct comedi_device *dev);
193 static int pc236_intr_check(struct comedi_device *dev);
194 static int pc236_intr_insn(struct comedi_device *dev,
195 struct comedi_subdevice *s, struct comedi_insn *insn,
197 static int pc236_intr_cmdtest(struct comedi_device *dev,
198 struct comedi_subdevice *s,
199 struct comedi_cmd *cmd);
200 static int pc236_intr_cmd(struct comedi_device *dev,
201 struct comedi_subdevice *s);
202 static int pc236_intr_cancel(struct comedi_device *dev,
203 struct comedi_subdevice *s);
204 static irqreturn_t pc236_interrupt(int irq, void *d);
207 * This function looks for a PCI device matching the requested board name,
210 #ifdef CONFIG_COMEDI_PCI
212 pc236_find_pci(struct comedi_device *dev, int bus, int slot,
213 struct pci_dev **pci_dev_p)
215 struct pci_dev *pci_dev = NULL;
219 /* Look for matching PCI device. */
220 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
222 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
223 PCI_ANY_ID, pci_dev)) {
224 /* If bus/slot specified, check them. */
226 if (bus != pci_dev->bus->number
227 || slot != PCI_SLOT(pci_dev->devfn))
230 if (thisboard->model == anypci_model) {
231 /* Match any supported model. */
234 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
235 if (pc236_boards[i].bustype != pci_bustype)
237 if (pci_dev->device == pc236_boards[i].devid) {
238 /* Change board_ptr to matched board. */
239 dev->board_ptr = &pc236_boards[i];
243 if (i == ARRAY_SIZE(pc236_boards))
246 /* Match specific model name. */
247 if (pci_dev->device != thisboard->devid)
252 *pci_dev_p = pci_dev;
255 /* No match found. */
258 "comedi%d: error! no %s found at pci %02x:%02x!\n",
259 dev->minor, thisboard->name, bus, slot);
261 printk(KERN_ERR "comedi%d: error! no %s found!\n",
262 dev->minor, thisboard->name);
269 * Attach is called by the Comedi core to configure the driver
270 * for a particular board. If you specified a board_name array
271 * in the driver structure, dev->board_ptr contains that
274 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
276 struct comedi_subdevice *s;
277 unsigned long iobase = 0;
278 unsigned int irq = 0;
279 #ifdef CONFIG_COMEDI_PCI
280 struct pci_dev *pci_dev = NULL;
281 int bus = 0, slot = 0;
286 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
289 * Allocate the private structure area. alloc_private() is a
290 * convenient macro defined in comedidev.h.
292 ret = alloc_private(dev, sizeof(struct pc236_private));
294 printk(KERN_ERR "comedi%d: error! out of memory!\n",
298 /* Process options. */
299 switch (thisboard->bustype) {
301 iobase = it->options[0];
302 irq = it->options[1];
305 #ifdef CONFIG_COMEDI_PCI
307 bus = it->options[0];
308 slot = it->options[1];
311 ret = pc236_find_pci(dev, bus, slot, &pci_dev);
314 devpriv->pci_dev = pci_dev;
316 #endif /* CONFIG_COMEDI_PCI */
319 "comedi%d: %s: BUG! cannot determine board type!\n",
320 dev->minor, PC236_DRIVER_NAME);
326 * Initialize dev->board_name.
328 dev->board_name = thisboard->name;
330 /* Enable device and reserve I/O spaces. */
331 #ifdef CONFIG_COMEDI_PCI
334 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
337 "comedi%d: error! cannot enable PCI device and request regions!\n",
341 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
342 iobase = pci_resource_start(pci_dev, 2);
347 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
352 dev->iobase = iobase;
355 * Allocate the subdevice structures. alloc_subdevice() is a
356 * convenient macro defined in comedidev.h.
358 ret = alloc_subdevices(dev, 2);
360 printk(KERN_ERR "comedi%d: error! out of memory!\n",
365 s = dev->subdevices + 0;
366 /* digital i/o subdevice (8255) */
367 ret = subdev_8255_init(dev, s, NULL, iobase);
369 printk(KERN_ERR "comedi%d: error! out of memory!\n",
373 s = dev->subdevices + 1;
374 dev->read_subdev = s;
375 s->type = COMEDI_SUBD_UNUSED;
376 pc236_intr_disable(dev);
378 unsigned long flags = share_irq ? IRQF_SHARED : 0;
380 if (request_irq(irq, pc236_interrupt, flags,
381 PC236_DRIVER_NAME, dev) >= 0) {
383 s->type = COMEDI_SUBD_DI;
384 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
387 s->range_table = &range_digital;
388 s->insn_bits = pc236_intr_insn;
389 s->do_cmdtest = pc236_intr_cmdtest;
390 s->do_cmd = pc236_intr_cmd;
391 s->cancel = pc236_intr_cancel;
394 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
395 if (thisboard->bustype == isa_bustype) {
396 printk("(base %#lx) ", iobase);
398 #ifdef CONFIG_COMEDI_PCI
399 printk("(pci %s) ", pci_name(pci_dev));
403 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
408 printk("attached\n");
414 * _detach is called to deconfigure a device. It should deallocate
416 * This function is also called when _attach() fails, so it should be
417 * careful not to release resources that were not necessarily
418 * allocated by _attach(). dev->private and dev->subdevices are
419 * deallocated automatically by the core.
421 static int pc236_detach(struct comedi_device *dev)
423 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
426 pc236_intr_disable(dev);
429 free_irq(dev->irq, dev);
430 if (dev->subdevices) {
431 subdev_8255_cleanup(dev, dev->subdevices + 0);
434 #ifdef CONFIG_COMEDI_PCI
435 if (devpriv->pci_dev) {
437 comedi_pci_disable(devpriv->pci_dev);
439 pci_dev_put(devpriv->pci_dev);
444 release_region(dev->iobase, PC236_IO_SIZE);
448 if (dev->board_name) {
449 printk(KERN_INFO "comedi%d: %s removed\n",
450 dev->minor, dev->board_name);
456 * This function checks and requests an I/O region, reporting an error
457 * if there is a conflict.
459 static int pc236_request_region(unsigned minor, unsigned long from,
460 unsigned long extent)
462 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
463 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
464 minor, from, extent);
471 * This function is called to mark the interrupt as disabled (no command
472 * configured on subdevice 1) and to physically disable the interrupt
473 * (not possible on the PC36AT, except by removing the IRQ jumper!).
475 static void pc236_intr_disable(struct comedi_device *dev)
479 spin_lock_irqsave(&dev->spinlock, flags);
480 devpriv->enable_irq = 0;
481 #ifdef CONFIG_COMEDI_PCI
482 if (devpriv->lcr_iobase)
483 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
485 spin_unlock_irqrestore(&dev->spinlock, flags);
489 * This function is called to mark the interrupt as enabled (a command
490 * configured on subdevice 1) and to physically enable the interrupt
491 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
493 static void pc236_intr_enable(struct comedi_device *dev)
497 spin_lock_irqsave(&dev->spinlock, flags);
498 devpriv->enable_irq = 1;
499 #ifdef CONFIG_COMEDI_PCI
500 if (devpriv->lcr_iobase)
501 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
503 spin_unlock_irqrestore(&dev->spinlock, flags);
507 * This function is called when an interrupt occurs to check whether
508 * the interrupt has been marked as enabled and was generated by the
509 * board. If so, the function prepares the hardware for the next
511 * Returns 0 if the interrupt should be ignored.
513 static int pc236_intr_check(struct comedi_device *dev)
518 spin_lock_irqsave(&dev->spinlock, flags);
519 if (devpriv->enable_irq) {
521 #ifdef CONFIG_COMEDI_PCI
522 if (devpriv->lcr_iobase) {
523 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
524 & PLX9052_INTCSR_LI1STAT_MASK)
525 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
528 /* Clear interrupt and keep it enabled. */
529 outl(PCI236_INTR_ENABLE,
530 devpriv->lcr_iobase + PLX9052_INTCSR);
535 spin_unlock_irqrestore(&dev->spinlock, flags);
541 * Input from subdevice 1.
542 * Copied from the comedi_parport driver.
544 static int pc236_intr_insn(struct comedi_device *dev,
545 struct comedi_subdevice *s, struct comedi_insn *insn,
553 * Subdevice 1 command test.
554 * Copied from the comedi_parport driver.
556 static int pc236_intr_cmdtest(struct comedi_device *dev,
557 struct comedi_subdevice *s,
558 struct comedi_cmd *cmd)
565 tmp = cmd->start_src;
566 cmd->start_src &= TRIG_NOW;
567 if (!cmd->start_src || tmp != cmd->start_src)
570 tmp = cmd->scan_begin_src;
571 cmd->scan_begin_src &= TRIG_EXT;
572 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
575 tmp = cmd->convert_src;
576 cmd->convert_src &= TRIG_FOLLOW;
577 if (!cmd->convert_src || tmp != cmd->convert_src)
580 tmp = cmd->scan_end_src;
581 cmd->scan_end_src &= TRIG_COUNT;
582 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
586 cmd->stop_src &= TRIG_NONE;
587 if (!cmd->stop_src || tmp != cmd->stop_src)
593 /* step 2: ignored */
600 if (cmd->start_arg != 0) {
604 if (cmd->scan_begin_arg != 0) {
605 cmd->scan_begin_arg = 0;
608 if (cmd->convert_arg != 0) {
609 cmd->convert_arg = 0;
612 if (cmd->scan_end_arg != 1) {
613 cmd->scan_end_arg = 1;
616 if (cmd->stop_arg != 0) {
624 /* step 4: ignored */
633 * Subdevice 1 command.
635 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
637 pc236_intr_enable(dev);
643 * Subdevice 1 cancel command.
645 static int pc236_intr_cancel(struct comedi_device *dev,
646 struct comedi_subdevice *s)
648 pc236_intr_disable(dev);
654 * Interrupt service routine.
655 * Based on the comedi_parport driver.
657 static irqreturn_t pc236_interrupt(int irq, void *d)
659 struct comedi_device *dev = d;
660 struct comedi_subdevice *s = dev->subdevices + 1;
663 handled = pc236_intr_check(dev);
664 if (dev->attached && handled) {
665 comedi_buf_put(s->async, 0);
666 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
667 comedi_event(dev, s);
669 return IRQ_RETVAL(handled);