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, 22 Oct 2008 13:40:03 +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 7 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 "../comedidev.h"
57 #include "comedi_pci.h"
62 #define PC236_DRIVER_NAME "amplc_pc236"
64 /* PCI236 PCI configuration register information */
65 #define PCI_VENDOR_ID_AMPLICON 0x14dc
66 #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
67 #define PCI_DEVICE_ID_INVALID 0xffff
69 /* PC36AT / PCI236 registers */
71 #define PC236_IO_SIZE 4
72 #define PC236_LCR_IO_SIZE 128
75 * INTCSR values for PCI236.
77 /* Disable interrupt, also clear any interrupt there */
78 #define PCI236_INTR_DISABLE ( PLX9052_INTCSR_LI1ENAB_DISABLED \
79 | PLX9052_INTCSR_LI1POL_HIGH \
80 | PLX9052_INTCSR_LI2POL_HIGH \
81 | PLX9052_INTCSR_PCIENAB_DISABLED \
82 | PLX9052_INTCSR_LI1SEL_EDGE \
83 | PLX9052_INTCSR_LI1CLRINT_ASSERTED )
84 /* Enable interrupt, also clear any interrupt there. */
85 #define PCI236_INTR_ENABLE ( PLX9052_INTCSR_LI1ENAB_ENABLED \
86 | PLX9052_INTCSR_LI1POL_HIGH \
87 | PLX9052_INTCSR_LI2POL_HIGH \
88 | PLX9052_INTCSR_PCIENAB_ENABLED \
89 | PLX9052_INTCSR_LI1SEL_EDGE \
90 | PLX9052_INTCSR_LI1CLRINT_ASSERTED )
93 * Board descriptions for Amplicon PC36AT and PCI236.
96 enum pc236_bustype { isa_bustype, pci_bustype };
97 enum pc236_model { pc36at_model, pci236_model, anypci_model };
99 typedef struct pc236_board_struct {
101 const char *fancy_name;
102 unsigned short devid;
103 enum pc236_bustype bustype;
104 enum pc236_model model;
106 static const pc236_board pc236_boards[] = {
110 bustype: isa_bustype,
113 #ifdef CONFIG_COMEDI_PCI
117 devid: PCI_DEVICE_ID_AMPLICON_PCI236,
118 bustype: pci_bustype,
122 #ifdef CONFIG_COMEDI_PCI
124 name: PC236_DRIVER_NAME,
125 fancy_name:PC236_DRIVER_NAME,
126 devid: PCI_DEVICE_ID_INVALID,
127 bustype: pci_bustype,
128 model: anypci_model, /* wildcard */
133 #ifdef CONFIG_COMEDI_PCI
134 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
135 {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236, PCI_ANY_ID,
136 PCI_ANY_ID, 0, 0, 0},
140 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
141 #endif /* CONFIG_COMEDI_PCI */
144 * Useful for shorthand access to the particular board structure
146 #define thisboard ((const pc236_board *)dev->board_ptr)
148 /* this structure is for data unique to this hardware driver. If
149 several hardware drivers keep similar information in this structure,
150 feel free to suggest moving the variable to the comedi_device struct. */
152 #ifdef CONFIG_COMEDI_PCI
154 struct pci_dev *pci_dev;
155 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
160 #define devpriv ((pc236_private *)dev->private)
163 * The comedi_driver structure tells the Comedi core module
164 * which functions to call to configure/deconfigure (attach/detach)
165 * the board, and also about the kernel module that contains
168 static int pc236_attach(comedi_device * dev, comedi_devconfig * it);
169 static int pc236_detach(comedi_device * dev);
170 static comedi_driver driver_amplc_pc236 = {
171 driver_name:PC236_DRIVER_NAME,
175 board_name:&pc236_boards[0].name,
176 offset:sizeof(pc236_board),
177 num_names:sizeof(pc236_boards) / sizeof(pc236_board),
180 #ifdef CONFIG_COMEDI_PCI
181 COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table);
183 COMEDI_INITCLEANUP(driver_amplc_pc236);
186 static int pc236_request_region(unsigned minor, unsigned long from,
187 unsigned long extent);
188 static void pc236_intr_disable(comedi_device * dev);
189 static void pc236_intr_enable(comedi_device * dev);
190 static int pc236_intr_check(comedi_device * dev);
191 static int pc236_intr_insn(comedi_device * dev, comedi_subdevice * s,
192 comedi_insn * insn, unsigned int * data);
193 static int pc236_intr_cmdtest(comedi_device * dev, comedi_subdevice * s,
195 static int pc236_intr_cmd(comedi_device * dev, comedi_subdevice * s);
196 static int pc236_intr_cancel(comedi_device * dev, comedi_subdevice * s);
197 static irqreturn_t pc236_interrupt(int irq, void *d PT_REGS_ARG);
200 * This function looks for a PCI device matching the requested board name,
203 #ifdef CONFIG_COMEDI_PCI
205 pc236_find_pci(comedi_device * dev, int bus, int slot,
206 struct pci_dev **pci_dev_p)
208 struct pci_dev *pci_dev = NULL;
212 /* Look for matching PCI device. */
213 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
215 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
216 PCI_ANY_ID, pci_dev)) {
217 /* If bus/slot specified, check them. */
219 if (bus != pci_dev->bus->number
220 || slot != PCI_SLOT(pci_dev->devfn))
223 if (thisboard->model == anypci_model) {
224 /* Match any supported model. */
227 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
228 if (pc236_boards[i].bustype != pci_bustype)
230 if (pci_dev->device == pc236_boards[i].devid) {
231 /* Change board_ptr to matched board. */
232 dev->board_ptr = &pc236_boards[i];
236 if (i == ARRAY_SIZE(pc236_boards))
239 /* Match specific model name. */
240 if (pci_dev->device != thisboard->devid)
245 *pci_dev_p = pci_dev;
248 /* No match found. */
251 "comedi%d: error! no %s found at pci %02x:%02x!\n",
252 dev->minor, thisboard->name, bus, slot);
254 printk(KERN_ERR "comedi%d: error! no %s found!\n",
255 dev->minor, thisboard->name);
262 * Attach is called by the Comedi core to configure the driver
263 * for a particular board. If you specified a board_name array
264 * in the driver structure, dev->board_ptr contains that
267 static int pc236_attach(comedi_device * dev, comedi_devconfig * it)
270 unsigned long iobase = 0;
271 unsigned int irq = 0;
272 #ifdef CONFIG_COMEDI_PCI
273 struct pci_dev *pci_dev = NULL;
274 int bus = 0, slot = 0;
279 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
282 * Allocate the private structure area. alloc_private() is a
283 * convenient macro defined in comedidev.h.
285 if ((ret = alloc_private(dev, sizeof(pc236_private))) < 0) {
286 printk(KERN_ERR "comedi%d: error! out of memory!\n",
290 /* Process options. */
291 switch (thisboard->bustype) {
293 iobase = it->options[0];
294 irq = it->options[1];
297 #ifdef CONFIG_COMEDI_PCI
299 bus = it->options[0];
300 slot = it->options[1];
303 if ((ret = pc236_find_pci(dev, bus, slot, &pci_dev)) < 0)
305 devpriv->pci_dev = pci_dev;
307 #endif /* CONFIG_COMEDI_PCI */
310 "comedi%d: %s: BUG! cannot determine board type!\n",
311 dev->minor, PC236_DRIVER_NAME);
317 * Initialize dev->board_name.
319 dev->board_name = thisboard->name;
321 /* Enable device and reserve I/O spaces. */
322 #ifdef CONFIG_COMEDI_PCI
324 if ((ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME)) < 0) {
326 "comedi%d: error! cannot enable PCI device and request regions!\n",
330 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
331 iobase = pci_resource_start(pci_dev, 2);
336 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
341 dev->iobase = iobase;
344 * Allocate the subdevice structures. alloc_subdevice() is a
345 * convenient macro defined in comedidev.h.
347 if ((ret = alloc_subdevices(dev, 2)) < 0) {
348 printk(KERN_ERR "comedi%d: error! out of memory!\n",
353 s = dev->subdevices + 0;
354 /* digital i/o subdevice (8255) */
355 if ((ret = subdev_8255_init(dev, s, NULL, iobase)) < 0) {
356 printk(KERN_ERR "comedi%d: error! out of memory!\n",
360 s = dev->subdevices + 1;
361 dev->read_subdev = s;
362 s->type = COMEDI_SUBD_UNUSED;
363 pc236_intr_disable(dev);
365 unsigned long flags = share_irq ? IRQF_SHARED : 0;
367 if (comedi_request_irq(irq, pc236_interrupt, flags,
368 PC236_DRIVER_NAME, dev) >= 0) {
370 s->type = COMEDI_SUBD_DI;
371 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
374 s->range_table = &range_digital;
375 s->insn_bits = pc236_intr_insn;
376 s->do_cmdtest = pc236_intr_cmdtest;
377 s->do_cmd = pc236_intr_cmd;
378 s->cancel = pc236_intr_cancel;
381 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
382 if (thisboard->bustype == isa_bustype) {
383 printk("(base %#lx) ", iobase);
385 #ifdef CONFIG_COMEDI_PCI
386 printk("(pci %s) ", pci_name(pci_dev));
390 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
395 printk("attached\n");
401 * _detach is called to deconfigure a device. It should deallocate
403 * This function is also called when _attach() fails, so it should be
404 * careful not to release resources that were not necessarily
405 * allocated by _attach(). dev->private and dev->subdevices are
406 * deallocated automatically by the core.
408 static int pc236_detach(comedi_device * dev)
410 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
413 pc236_intr_disable(dev);
416 comedi_free_irq(dev->irq, dev);
417 if (dev->subdevices) {
418 subdev_8255_cleanup(dev, dev->subdevices + 0);
421 #ifdef CONFIG_COMEDI_PCI
422 if (devpriv->pci_dev) {
424 comedi_pci_disable(devpriv->pci_dev);
426 pci_dev_put(devpriv->pci_dev);
431 release_region(dev->iobase, PC236_IO_SIZE);
435 if (dev->board_name) {
436 printk(KERN_INFO "comedi%d: %s removed\n",
437 dev->minor, dev->board_name);
443 * This function checks and requests an I/O region, reporting an error
444 * if there is a conflict.
446 static int pc236_request_region(unsigned minor, unsigned long from,
447 unsigned long extent)
449 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
450 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
451 minor, from, extent);
458 * This function is called to mark the interrupt as disabled (no command
459 * configured on subdevice 1) and to physically disable the interrupt
460 * (not possible on the PC36AT, except by removing the IRQ jumper!).
462 static void pc236_intr_disable(comedi_device * dev)
466 comedi_spin_lock_irqsave(&dev->spinlock, flags);
467 devpriv->enable_irq = 0;
468 #ifdef CONFIG_COMEDI_PCI
469 if (devpriv->lcr_iobase)
470 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
472 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
476 * This function is called to mark the interrupt as enabled (a command
477 * configured on subdevice 1) and to physically enable the interrupt
478 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
480 static void pc236_intr_enable(comedi_device * dev)
484 comedi_spin_lock_irqsave(&dev->spinlock, flags);
485 devpriv->enable_irq = 1;
486 #ifdef CONFIG_COMEDI_PCI
487 if (devpriv->lcr_iobase)
488 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
490 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
494 * This function is called when an interrupt occurs to check whether
495 * the interrupt has been marked as enabled and was generated by the
496 * board. If so, the function prepares the hardware for the next
498 * Returns 0 if the interrupt should be ignored.
500 static int pc236_intr_check(comedi_device * dev)
505 comedi_spin_lock_irqsave(&dev->spinlock, flags);
506 if (devpriv->enable_irq) {
508 #ifdef CONFIG_COMEDI_PCI
509 if (devpriv->lcr_iobase) {
510 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
511 & PLX9052_INTCSR_LI1STAT_MASK)
512 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
515 /* Clear interrupt and keep it enabled. */
516 outl(PCI236_INTR_ENABLE,
517 devpriv->lcr_iobase + PLX9052_INTCSR);
522 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
528 * Input from subdevice 1.
529 * Copied from the comedi_parport driver.
531 static int pc236_intr_insn(comedi_device * dev, comedi_subdevice * s,
532 comedi_insn * insn, unsigned int * data)
539 * Subdevice 1 command test.
540 * Copied from the comedi_parport driver.
542 static int pc236_intr_cmdtest(comedi_device * dev, comedi_subdevice * s,
550 tmp = cmd->start_src;
551 cmd->start_src &= TRIG_NOW;
552 if (!cmd->start_src || tmp != cmd->start_src)
555 tmp = cmd->scan_begin_src;
556 cmd->scan_begin_src &= TRIG_EXT;
557 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
560 tmp = cmd->convert_src;
561 cmd->convert_src &= TRIG_FOLLOW;
562 if (!cmd->convert_src || tmp != cmd->convert_src)
565 tmp = cmd->scan_end_src;
566 cmd->scan_end_src &= TRIG_COUNT;
567 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
571 cmd->stop_src &= TRIG_NONE;
572 if (!cmd->stop_src || tmp != cmd->stop_src)
578 /* step 2: ignored */
585 if (cmd->start_arg != 0) {
589 if (cmd->scan_begin_arg != 0) {
590 cmd->scan_begin_arg = 0;
593 if (cmd->convert_arg != 0) {
594 cmd->convert_arg = 0;
597 if (cmd->scan_end_arg != 1) {
598 cmd->scan_end_arg = 1;
601 if (cmd->stop_arg != 0) {
609 /* step 4: ignored */
618 * Subdevice 1 command.
620 static int pc236_intr_cmd(comedi_device * dev, comedi_subdevice * s)
622 pc236_intr_enable(dev);
628 * Subdevice 1 cancel command.
630 static int pc236_intr_cancel(comedi_device * dev, comedi_subdevice * s)
632 pc236_intr_disable(dev);
638 * Interrupt service routine.
639 * Based on the comedi_parport driver.
641 static irqreturn_t pc236_interrupt(int irq, void *d PT_REGS_ARG)
643 comedi_device *dev = d;
644 comedi_subdevice *s = dev->subdevices + 1;
647 handled = pc236_intr_check(dev);
648 if (dev->attached && handled) {
649 comedi_buf_put(s->async, 0);
650 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
651 comedi_event(dev, s);
653 return IRQ_RETVAL(handled);