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"
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 };
101 unsigned short devid;
102 enum pc236_bustype bustype;
103 enum pc236_model model;
105 static const struct pc236_board pc236_boards[] = {
106 #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
109 .bustype = isa_bustype,
110 .model = pc36at_model,
113 #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
116 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
117 .bustype = pci_bustype,
118 .model = pci236_model,
121 .name = PC236_DRIVER_NAME,
122 .devid = PCI_DEVICE_ID_INVALID,
123 .bustype = pci_bustype,
124 .model = anypci_model, /* wildcard */
129 /* this structure is for data unique to this hardware driver. If
130 several hardware drivers keep similar information in this structure,
131 feel free to suggest moving the variable to the struct comedi_device struct.
133 struct pc236_private {
134 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
139 * This function looks for a board matching the supplied PCI device.
141 static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
145 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
146 if (pc236_boards[i].bustype == pci_bustype &&
147 pci_dev->device == pc236_boards[i].devid)
148 return &pc236_boards[i];
153 * This function looks for a PCI device matching the requested board name,
156 static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
157 struct comedi_devconfig *it)
159 const struct pc236_board *thisboard = comedi_board(dev);
160 struct pci_dev *pci_dev = NULL;
161 int bus = it->options[0];
162 int slot = it->options[1];
164 for_each_pci_dev(pci_dev) {
166 if (bus != pci_dev->bus->number ||
167 slot != PCI_SLOT(pci_dev->devfn))
170 if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
173 if (thisboard->model == anypci_model) {
174 /* Wildcard board matches any supported PCI board. */
175 const struct pc236_board *foundboard;
177 foundboard = pc236_find_pci_board(pci_dev);
178 if (foundboard == NULL)
180 /* Replace wildcard board_ptr. */
181 dev->board_ptr = foundboard;
183 /* Match specific model name. */
184 if (pci_dev->device != thisboard->devid)
189 dev_err(dev->class_dev,
190 "No supported board found! (req. bus %d, slot %d)\n",
196 * This function checks and requests an I/O region, reporting an error
197 * if there is a conflict.
199 static int pc236_request_region(struct comedi_device *dev, unsigned long from,
200 unsigned long extent)
202 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
203 dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n",
211 * This function is called to mark the interrupt as disabled (no command
212 * configured on subdevice 1) and to physically disable the interrupt
213 * (not possible on the PC36AT, except by removing the IRQ jumper!).
215 static void pc236_intr_disable(struct comedi_device *dev)
217 struct pc236_private *devpriv = dev->private;
220 spin_lock_irqsave(&dev->spinlock, flags);
221 devpriv->enable_irq = 0;
222 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) && devpriv->lcr_iobase)
223 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
224 spin_unlock_irqrestore(&dev->spinlock, flags);
228 * This function is called to mark the interrupt as enabled (a command
229 * configured on subdevice 1) and to physically enable the interrupt
230 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
232 static void pc236_intr_enable(struct comedi_device *dev)
234 struct pc236_private *devpriv = dev->private;
237 spin_lock_irqsave(&dev->spinlock, flags);
238 devpriv->enable_irq = 1;
239 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) && devpriv->lcr_iobase)
240 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
241 spin_unlock_irqrestore(&dev->spinlock, flags);
245 * This function is called when an interrupt occurs to check whether
246 * the interrupt has been marked as enabled and was generated by the
247 * board. If so, the function prepares the hardware for the next
249 * Returns 0 if the interrupt should be ignored.
251 static int pc236_intr_check(struct comedi_device *dev)
253 struct pc236_private *devpriv = dev->private;
257 spin_lock_irqsave(&dev->spinlock, flags);
258 if (devpriv->enable_irq) {
260 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) &&
261 devpriv->lcr_iobase) {
262 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
263 & PLX9052_INTCSR_LI1STAT_MASK)
264 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
267 /* Clear interrupt and keep it enabled. */
268 outl(PCI236_INTR_ENABLE,
269 devpriv->lcr_iobase + PLX9052_INTCSR);
273 spin_unlock_irqrestore(&dev->spinlock, flags);
279 * Input from subdevice 1.
280 * Copied from the comedi_parport driver.
282 static int pc236_intr_insn(struct comedi_device *dev,
283 struct comedi_subdevice *s, struct comedi_insn *insn,
291 * Subdevice 1 command test.
292 * Copied from the comedi_parport driver.
294 static int pc236_intr_cmdtest(struct comedi_device *dev,
295 struct comedi_subdevice *s,
296 struct comedi_cmd *cmd)
303 tmp = cmd->start_src;
304 cmd->start_src &= TRIG_NOW;
305 if (!cmd->start_src || tmp != cmd->start_src)
308 tmp = cmd->scan_begin_src;
309 cmd->scan_begin_src &= TRIG_EXT;
310 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
313 tmp = cmd->convert_src;
314 cmd->convert_src &= TRIG_FOLLOW;
315 if (!cmd->convert_src || tmp != cmd->convert_src)
318 tmp = cmd->scan_end_src;
319 cmd->scan_end_src &= TRIG_COUNT;
320 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
324 cmd->stop_src &= TRIG_NONE;
325 if (!cmd->stop_src || tmp != cmd->stop_src)
331 /* step 2: ignored */
338 if (cmd->start_arg != 0) {
342 if (cmd->scan_begin_arg != 0) {
343 cmd->scan_begin_arg = 0;
346 if (cmd->convert_arg != 0) {
347 cmd->convert_arg = 0;
350 if (cmd->scan_end_arg != 1) {
351 cmd->scan_end_arg = 1;
354 if (cmd->stop_arg != 0) {
362 /* step 4: ignored */
371 * Subdevice 1 command.
373 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
375 pc236_intr_enable(dev);
381 * Subdevice 1 cancel command.
383 static int pc236_intr_cancel(struct comedi_device *dev,
384 struct comedi_subdevice *s)
386 pc236_intr_disable(dev);
392 * Interrupt service routine.
393 * Based on the comedi_parport driver.
395 static irqreturn_t pc236_interrupt(int irq, void *d)
397 struct comedi_device *dev = d;
398 struct comedi_subdevice *s = dev->subdevices + 1;
401 handled = pc236_intr_check(dev);
402 if (dev->attached && handled) {
403 comedi_buf_put(s->async, 0);
404 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
405 comedi_event(dev, s);
407 return IRQ_RETVAL(handled);
410 static void pc236_report_attach(struct comedi_device *dev, unsigned int irq)
412 const struct pc236_board *thisboard = comedi_board(dev);
413 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
417 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) &&
418 thisboard->bustype == isa_bustype)
419 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
420 "(base %#lx) ", dev->iobase);
421 else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) &&
422 thisboard->bustype == pci_bustype) {
423 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
424 "(pci %s) ", pci_name(pcidev));
428 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
430 (dev->irq ? "" : " UNAVAILABLE"));
432 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
434 dev_info(dev->class_dev, "%s %sattached\n",
435 dev->board_name, tmpbuf);
438 static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
439 unsigned int irq, unsigned long req_irq_flags)
441 const struct pc236_board *thisboard = comedi_board(dev);
442 struct comedi_subdevice *s;
445 dev->board_name = thisboard->name;
446 dev->iobase = iobase;
448 ret = comedi_alloc_subdevices(dev, 2);
452 s = dev->subdevices + 0;
453 /* digital i/o subdevice (8255) */
454 ret = subdev_8255_init(dev, s, NULL, iobase);
456 dev_err(dev->class_dev, "error! out of memory!\n");
459 s = dev->subdevices + 1;
460 dev->read_subdev = s;
461 s->type = COMEDI_SUBD_UNUSED;
462 pc236_intr_disable(dev);
464 if (request_irq(irq, pc236_interrupt, req_irq_flags,
465 PC236_DRIVER_NAME, dev) >= 0) {
467 s->type = COMEDI_SUBD_DI;
468 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
471 s->range_table = &range_digital;
472 s->insn_bits = pc236_intr_insn;
473 s->do_cmdtest = pc236_intr_cmdtest;
474 s->do_cmd = pc236_intr_cmd;
475 s->cancel = pc236_intr_cancel;
478 pc236_report_attach(dev, irq);
482 static int pc236_pci_common_attach(struct comedi_device *dev,
483 struct pci_dev *pci_dev)
485 struct pc236_private *devpriv = dev->private;
486 unsigned long iobase;
489 comedi_set_hw_dev(dev, &pci_dev->dev);
491 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
493 dev_err(dev->class_dev,
494 "error! cannot enable PCI device and request regions!\n");
497 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
498 iobase = pci_resource_start(pci_dev, 2);
499 return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
503 * Attach is called by the Comedi core to configure the driver
504 * for a particular board. If you specified a board_name array
505 * in the driver structure, dev->board_ptr contains that
508 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
510 const struct pc236_board *thisboard = comedi_board(dev);
513 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach\n");
514 ret = alloc_private(dev, sizeof(struct pc236_private));
516 dev_err(dev->class_dev, "error! out of memory!\n");
519 /* Process options according to bus type. */
520 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) &&
521 thisboard->bustype == isa_bustype) {
522 unsigned long iobase = it->options[0];
523 unsigned int irq = it->options[1];
524 ret = pc236_request_region(dev, iobase, PC236_IO_SIZE);
527 return pc236_common_attach(dev, iobase, irq, 0);
528 } else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) &&
529 thisboard->bustype == pci_bustype) {
530 struct pci_dev *pci_dev;
532 pci_dev = pc236_find_pci_dev(dev, it);
535 return pc236_pci_common_attach(dev, pci_dev);
537 dev_err(dev->class_dev, PC236_DRIVER_NAME
538 ": BUG! cannot determine board type!\n");
544 * The attach_pci hook (if non-NULL) is called at PCI probe time in preference
545 * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should
546 * be a board entry matching the supplied PCI device.
548 static int __devinit pc236_attach_pci(struct comedi_device *dev,
549 struct pci_dev *pci_dev)
553 if (!IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI))
556 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
558 ret = alloc_private(dev, sizeof(struct pc236_private));
560 dev_err(dev->class_dev, "error! out of memory!\n");
563 dev->board_ptr = pc236_find_pci_board(pci_dev);
564 if (dev->board_ptr == NULL) {
565 dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
568 return pc236_pci_common_attach(dev, pci_dev);
571 static void pc236_detach(struct comedi_device *dev)
573 struct pc236_private *devpriv = dev->private;
574 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
577 pc236_intr_disable(dev);
579 free_irq(dev->irq, dev);
581 subdev_8255_cleanup(dev, dev->subdevices + 0);
584 comedi_pci_disable(pcidev);
588 release_region(dev->iobase, PC236_IO_SIZE);
593 * The struct comedi_driver structure tells the Comedi core module
594 * which functions to call to configure/deconfigure (attach/detach)
595 * the board, and also about the kernel module that contains
598 static struct comedi_driver amplc_pc236_driver = {
599 .driver_name = PC236_DRIVER_NAME,
600 .module = THIS_MODULE,
601 .attach = pc236_attach,
602 .attach_pci = pc236_attach_pci,
603 .detach = pc236_detach,
604 .board_name = &pc236_boards[0].name,
605 .offset = sizeof(struct pc236_board),
606 .num_names = ARRAY_SIZE(pc236_boards),
609 #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
610 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
611 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
615 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
617 static int __devinit amplc_pc236_pci_probe(struct pci_dev *dev,
618 const struct pci_device_id *ent)
620 return comedi_pci_auto_config(dev, &lc_pc236_driver);
623 static void __devexit amplc_pc236_pci_remove(struct pci_dev *dev)
625 comedi_pci_auto_unconfig(dev);
628 static struct pci_driver amplc_pc236_pci_driver = {
629 .name = PC236_DRIVER_NAME,
630 .id_table = pc236_pci_table,
631 .probe = &lc_pc236_pci_probe,
632 .remove = __devexit_p(&lc_pc236_pci_remove)
635 module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
637 module_comedi_driver(amplc_pc236_driver);
640 MODULE_AUTHOR("Comedi http://www.comedi.org");
641 MODULE_DESCRIPTION("Comedi low-level driver");
642 MODULE_LICENSE("GPL");