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_fc.h"
63 #define PC236_DRIVER_NAME "amplc_pc236"
65 #define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
66 #define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
68 /* PCI236 PCI configuration register information */
69 #define PCI_VENDOR_ID_AMPLICON 0x14dc
70 #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
71 #define PCI_DEVICE_ID_INVALID 0xffff
73 /* PC36AT / PCI236 registers */
75 #define PC236_IO_SIZE 4
76 #define PC236_LCR_IO_SIZE 128
79 * INTCSR values for PCI236.
81 /* Disable interrupt, also clear any interrupt there */
82 #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
83 | PLX9052_INTCSR_LI1POL_HIGH \
84 | PLX9052_INTCSR_LI2POL_HIGH \
85 | PLX9052_INTCSR_PCIENAB_DISABLED \
86 | PLX9052_INTCSR_LI1SEL_EDGE \
87 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
88 /* Enable interrupt, also clear any interrupt there. */
89 #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
90 | PLX9052_INTCSR_LI1POL_HIGH \
91 | PLX9052_INTCSR_LI2POL_HIGH \
92 | PLX9052_INTCSR_PCIENAB_ENABLED \
93 | PLX9052_INTCSR_LI1SEL_EDGE \
94 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
97 * Board descriptions for Amplicon PC36AT and PCI236.
100 enum pc236_bustype { isa_bustype, pci_bustype };
101 enum pc236_model { pc36at_model, pci236_model, anypci_model };
105 unsigned short devid;
106 enum pc236_bustype bustype;
107 enum pc236_model model;
109 static const struct pc236_board pc236_boards[] = {
113 .bustype = isa_bustype,
114 .model = pc36at_model,
120 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
121 .bustype = pci_bustype,
122 .model = pci236_model,
125 .name = PC236_DRIVER_NAME,
126 .devid = PCI_DEVICE_ID_INVALID,
127 .bustype = pci_bustype,
128 .model = anypci_model, /* wildcard */
133 /* this structure is for data unique to this hardware driver. If
134 several hardware drivers keep similar information in this structure,
135 feel free to suggest moving the variable to the struct comedi_device struct.
137 struct pc236_private {
138 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
142 /* test if ISA supported and this is an ISA board */
143 static inline bool is_isa_board(const struct pc236_board *board)
145 return DO_ISA && board->bustype == isa_bustype;
148 /* test if PCI supported and this is a PCI board */
149 static inline bool is_pci_board(const struct pc236_board *board)
151 return DO_PCI && board->bustype == pci_bustype;
155 * This function looks for a board matching the supplied PCI device.
157 static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
161 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
162 if (is_pci_board(&pc236_boards[i]) &&
163 pci_dev->device == pc236_boards[i].devid)
164 return &pc236_boards[i];
169 * This function looks for a PCI device matching the requested board name,
172 static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
173 struct comedi_devconfig *it)
175 const struct pc236_board *thisboard = comedi_board(dev);
176 struct pci_dev *pci_dev = NULL;
177 int bus = it->options[0];
178 int slot = it->options[1];
180 for_each_pci_dev(pci_dev) {
182 if (bus != pci_dev->bus->number ||
183 slot != PCI_SLOT(pci_dev->devfn))
186 if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
189 if (thisboard->model == anypci_model) {
190 /* Wildcard board matches any supported PCI board. */
191 const struct pc236_board *foundboard;
193 foundboard = pc236_find_pci_board(pci_dev);
194 if (foundboard == NULL)
196 /* Replace wildcard board_ptr. */
197 dev->board_ptr = foundboard;
199 /* Match specific model name. */
200 if (pci_dev->device != thisboard->devid)
205 dev_err(dev->class_dev,
206 "No supported board found! (req. bus %d, slot %d)\n",
212 * This function checks and requests an I/O region, reporting an error
213 * if there is a conflict.
215 static int pc236_request_region(struct comedi_device *dev, unsigned long from,
216 unsigned long extent)
218 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
219 dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n",
227 * This function is called to mark the interrupt as disabled (no command
228 * configured on subdevice 1) and to physically disable the interrupt
229 * (not possible on the PC36AT, except by removing the IRQ jumper!).
231 static void pc236_intr_disable(struct comedi_device *dev)
233 const struct pc236_board *thisboard = comedi_board(dev);
234 struct pc236_private *devpriv = dev->private;
237 spin_lock_irqsave(&dev->spinlock, flags);
238 devpriv->enable_irq = 0;
239 if (is_pci_board(thisboard))
240 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
241 spin_unlock_irqrestore(&dev->spinlock, flags);
245 * This function is called to mark the interrupt as enabled (a command
246 * configured on subdevice 1) and to physically enable the interrupt
247 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
249 static void pc236_intr_enable(struct comedi_device *dev)
251 const struct pc236_board *thisboard = comedi_board(dev);
252 struct pc236_private *devpriv = dev->private;
255 spin_lock_irqsave(&dev->spinlock, flags);
256 devpriv->enable_irq = 1;
257 if (is_pci_board(thisboard))
258 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
259 spin_unlock_irqrestore(&dev->spinlock, flags);
263 * This function is called when an interrupt occurs to check whether
264 * the interrupt has been marked as enabled and was generated by the
265 * board. If so, the function prepares the hardware for the next
267 * Returns 0 if the interrupt should be ignored.
269 static int pc236_intr_check(struct comedi_device *dev)
271 const struct pc236_board *thisboard = comedi_board(dev);
272 struct pc236_private *devpriv = dev->private;
276 spin_lock_irqsave(&dev->spinlock, flags);
277 if (devpriv->enable_irq) {
279 if (is_pci_board(thisboard)) {
280 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
281 & PLX9052_INTCSR_LI1STAT_MASK)
282 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
285 /* Clear interrupt and keep it enabled. */
286 outl(PCI236_INTR_ENABLE,
287 devpriv->lcr_iobase + PLX9052_INTCSR);
291 spin_unlock_irqrestore(&dev->spinlock, flags);
297 * Input from subdevice 1.
298 * Copied from the comedi_parport driver.
300 static int pc236_intr_insn(struct comedi_device *dev,
301 struct comedi_subdevice *s, struct comedi_insn *insn,
309 * Subdevice 1 command test.
310 * Copied from the comedi_parport driver.
312 static int pc236_intr_cmdtest(struct comedi_device *dev,
313 struct comedi_subdevice *s,
314 struct comedi_cmd *cmd)
318 /* Step 1 : check if triggers are trivially valid */
320 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
321 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
322 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
323 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
324 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
329 /* Step 2a : make sure trigger sources are unique */
330 /* Step 2b : and mutually compatible */
337 if (cmd->start_arg != 0) {
341 if (cmd->scan_begin_arg != 0) {
342 cmd->scan_begin_arg = 0;
345 if (cmd->convert_arg != 0) {
346 cmd->convert_arg = 0;
349 if (cmd->scan_end_arg != 1) {
350 cmd->scan_end_arg = 1;
353 if (cmd->stop_arg != 0) {
361 /* step 4: ignored */
370 * Subdevice 1 command.
372 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
374 pc236_intr_enable(dev);
380 * Subdevice 1 cancel command.
382 static int pc236_intr_cancel(struct comedi_device *dev,
383 struct comedi_subdevice *s)
385 pc236_intr_disable(dev);
391 * Interrupt service routine.
392 * Based on the comedi_parport driver.
394 static irqreturn_t pc236_interrupt(int irq, void *d)
396 struct comedi_device *dev = d;
397 struct comedi_subdevice *s = &dev->subdevices[1];
400 handled = pc236_intr_check(dev);
401 if (dev->attached && handled) {
402 comedi_buf_put(s->async, 0);
403 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
404 comedi_event(dev, s);
406 return IRQ_RETVAL(handled);
409 static void pc236_report_attach(struct comedi_device *dev, unsigned int irq)
411 const struct pc236_board *thisboard = comedi_board(dev);
412 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
416 if (is_isa_board(thisboard))
417 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
418 "(base %#lx) ", dev->iobase);
419 else if (is_pci_board(thisboard))
420 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
421 "(pci %s) ", pci_name(pcidev));
425 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
427 (dev->irq ? "" : " UNAVAILABLE"));
429 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
431 dev_info(dev->class_dev, "%s %sattached\n",
432 dev->board_name, tmpbuf);
435 static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
436 unsigned int irq, unsigned long req_irq_flags)
438 const struct pc236_board *thisboard = comedi_board(dev);
439 struct comedi_subdevice *s;
442 dev->board_name = thisboard->name;
443 dev->iobase = iobase;
445 ret = comedi_alloc_subdevices(dev, 2);
449 s = &dev->subdevices[0];
450 /* digital i/o subdevice (8255) */
451 ret = subdev_8255_init(dev, s, NULL, iobase);
453 dev_err(dev->class_dev, "error! out of memory!\n");
456 s = &dev->subdevices[1];
457 dev->read_subdev = s;
458 s->type = COMEDI_SUBD_UNUSED;
459 pc236_intr_disable(dev);
461 if (request_irq(irq, pc236_interrupt, req_irq_flags,
462 PC236_DRIVER_NAME, dev) >= 0) {
464 s->type = COMEDI_SUBD_DI;
465 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
468 s->range_table = &range_digital;
469 s->insn_bits = pc236_intr_insn;
470 s->do_cmdtest = pc236_intr_cmdtest;
471 s->do_cmd = pc236_intr_cmd;
472 s->cancel = pc236_intr_cancel;
475 pc236_report_attach(dev, irq);
479 static int pc236_pci_common_attach(struct comedi_device *dev,
480 struct pci_dev *pci_dev)
482 struct pc236_private *devpriv = dev->private;
483 unsigned long iobase;
486 comedi_set_hw_dev(dev, &pci_dev->dev);
488 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
490 dev_err(dev->class_dev,
491 "error! cannot enable PCI device and request regions!\n");
494 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
495 iobase = pci_resource_start(pci_dev, 2);
496 return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
500 * Attach is called by the Comedi core to configure the driver
501 * for a particular board. If you specified a board_name array
502 * in the driver structure, dev->board_ptr contains that
505 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
507 const struct pc236_board *thisboard = comedi_board(dev);
510 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach\n");
511 ret = alloc_private(dev, sizeof(struct pc236_private));
513 dev_err(dev->class_dev, "error! out of memory!\n");
516 /* Process options according to bus type. */
517 if (is_isa_board(thisboard)) {
518 unsigned long iobase = it->options[0];
519 unsigned int irq = it->options[1];
520 ret = pc236_request_region(dev, iobase, PC236_IO_SIZE);
523 return pc236_common_attach(dev, iobase, irq, 0);
524 } else if (is_pci_board(thisboard)) {
525 struct pci_dev *pci_dev;
527 pci_dev = pc236_find_pci_dev(dev, it);
530 return pc236_pci_common_attach(dev, pci_dev);
532 dev_err(dev->class_dev, PC236_DRIVER_NAME
533 ": BUG! cannot determine board type!\n");
539 * The attach_pci hook (if non-NULL) is called at PCI probe time in preference
540 * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should
541 * be a board entry matching the supplied PCI device.
543 static int __devinit pc236_attach_pci(struct comedi_device *dev,
544 struct pci_dev *pci_dev)
551 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
553 ret = alloc_private(dev, sizeof(struct pc236_private));
555 dev_err(dev->class_dev, "error! out of memory!\n");
558 dev->board_ptr = pc236_find_pci_board(pci_dev);
559 if (dev->board_ptr == NULL) {
560 dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
564 * Need to 'get' the PCI device to match the 'put' in pc236_detach().
565 * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
566 * support for manual attachment of PCI devices via pc236_attach()
569 pci_dev_get(pci_dev);
570 return pc236_pci_common_attach(dev, pci_dev);
573 static void pc236_detach(struct comedi_device *dev)
575 const struct pc236_board *thisboard = comedi_board(dev);
576 struct pc236_private *devpriv = dev->private;
579 pc236_intr_disable(dev);
581 free_irq(dev->irq, dev);
583 subdev_8255_cleanup(dev, &dev->subdevices[0]);
584 if (is_isa_board(thisboard)) {
586 release_region(dev->iobase, PC236_IO_SIZE);
587 } else if (is_pci_board(thisboard)) {
588 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
591 comedi_pci_disable(pcidev);
598 * The struct comedi_driver structure tells the Comedi core module
599 * which functions to call to configure/deconfigure (attach/detach)
600 * the board, and also about the kernel module that contains
603 static struct comedi_driver amplc_pc236_driver = {
604 .driver_name = PC236_DRIVER_NAME,
605 .module = THIS_MODULE,
606 .attach = pc236_attach,
607 .attach_pci = pc236_attach_pci,
608 .detach = pc236_detach,
609 .board_name = &pc236_boards[0].name,
610 .offset = sizeof(struct pc236_board),
611 .num_names = ARRAY_SIZE(pc236_boards),
615 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
616 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
620 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
622 static int __devinit amplc_pc236_pci_probe(struct pci_dev *dev,
623 const struct pci_device_id *ent)
625 return comedi_pci_auto_config(dev, &lc_pc236_driver);
628 static void __devexit amplc_pc236_pci_remove(struct pci_dev *dev)
630 comedi_pci_auto_unconfig(dev);
633 static struct pci_driver amplc_pc236_pci_driver = {
634 .name = PC236_DRIVER_NAME,
635 .id_table = pc236_pci_table,
636 .probe = &lc_pc236_pci_probe,
637 .remove = __devexit_p(&lc_pc236_pci_remove)
640 module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
642 module_comedi_driver(amplc_pc236_driver);
645 MODULE_AUTHOR("Comedi http://www.comedi.org");
646 MODULE_DESCRIPTION("Comedi low-level driver");
647 MODULE_LICENSE("GPL");