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_DEVICE_ID_AMPLICON_PCI236 0x0009
70 #define PCI_DEVICE_ID_INVALID 0xffff
72 /* PC36AT / PCI236 registers */
74 #define PC236_IO_SIZE 4
75 #define PC236_LCR_IO_SIZE 128
78 * INTCSR values for PCI236.
80 /* Disable interrupt, also clear any interrupt there */
81 #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
82 | PLX9052_INTCSR_LI1POL_HIGH \
83 | PLX9052_INTCSR_LI2POL_HIGH \
84 | PLX9052_INTCSR_PCIENAB_DISABLED \
85 | PLX9052_INTCSR_LI1SEL_EDGE \
86 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
87 /* Enable interrupt, also clear any interrupt there. */
88 #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
89 | PLX9052_INTCSR_LI1POL_HIGH \
90 | PLX9052_INTCSR_LI2POL_HIGH \
91 | PLX9052_INTCSR_PCIENAB_ENABLED \
92 | PLX9052_INTCSR_LI1SEL_EDGE \
93 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
96 * Board descriptions for Amplicon PC36AT and PCI236.
99 enum pc236_bustype { isa_bustype, pci_bustype };
100 enum pc236_model { pc36at_model, pci236_model, anypci_model };
104 unsigned short devid;
105 enum pc236_bustype bustype;
106 enum pc236_model model;
108 static const struct pc236_board pc236_boards[] = {
112 .bustype = isa_bustype,
113 .model = pc36at_model,
119 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
120 .bustype = pci_bustype,
121 .model = pci236_model,
124 .name = PC236_DRIVER_NAME,
125 .devid = PCI_DEVICE_ID_INVALID,
126 .bustype = pci_bustype,
127 .model = anypci_model, /* wildcard */
132 /* this structure is for data unique to this hardware driver. If
133 several hardware drivers keep similar information in this structure,
134 feel free to suggest moving the variable to the struct comedi_device struct.
136 struct pc236_private {
137 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
141 /* test if ISA supported and this is an ISA board */
142 static inline bool is_isa_board(const struct pc236_board *board)
144 return DO_ISA && board->bustype == isa_bustype;
147 /* test if PCI supported and this is a PCI board */
148 static inline bool is_pci_board(const struct pc236_board *board)
150 return DO_PCI && board->bustype == pci_bustype;
154 * This function looks for a board matching the supplied PCI device.
156 static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
160 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
161 if (is_pci_board(&pc236_boards[i]) &&
162 pci_dev->device == pc236_boards[i].devid)
163 return &pc236_boards[i];
168 * This function looks for a PCI device matching the requested board name,
171 static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
172 struct comedi_devconfig *it)
174 const struct pc236_board *thisboard = comedi_board(dev);
175 struct pci_dev *pci_dev = NULL;
176 int bus = it->options[0];
177 int slot = it->options[1];
179 for_each_pci_dev(pci_dev) {
181 if (bus != pci_dev->bus->number ||
182 slot != PCI_SLOT(pci_dev->devfn))
185 if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
188 if (thisboard->model == anypci_model) {
189 /* Wildcard board matches any supported PCI board. */
190 const struct pc236_board *foundboard;
192 foundboard = pc236_find_pci_board(pci_dev);
193 if (foundboard == NULL)
195 /* Replace wildcard board_ptr. */
196 dev->board_ptr = foundboard;
198 /* Match specific model name. */
199 if (pci_dev->device != thisboard->devid)
204 dev_err(dev->class_dev,
205 "No supported board found! (req. bus %d, slot %d)\n",
211 * This function checks and requests an I/O region, reporting an error
212 * if there is a conflict.
214 static int pc236_request_region(struct comedi_device *dev, unsigned long from,
215 unsigned long extent)
217 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
218 dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n",
226 * This function is called to mark the interrupt as disabled (no command
227 * configured on subdevice 1) and to physically disable the interrupt
228 * (not possible on the PC36AT, except by removing the IRQ jumper!).
230 static void pc236_intr_disable(struct comedi_device *dev)
232 const struct pc236_board *thisboard = comedi_board(dev);
233 struct pc236_private *devpriv = dev->private;
236 spin_lock_irqsave(&dev->spinlock, flags);
237 devpriv->enable_irq = 0;
238 if (is_pci_board(thisboard))
239 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
240 spin_unlock_irqrestore(&dev->spinlock, flags);
244 * This function is called to mark the interrupt as enabled (a command
245 * configured on subdevice 1) and to physically enable the interrupt
246 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
248 static void pc236_intr_enable(struct comedi_device *dev)
250 const struct pc236_board *thisboard = comedi_board(dev);
251 struct pc236_private *devpriv = dev->private;
254 spin_lock_irqsave(&dev->spinlock, flags);
255 devpriv->enable_irq = 1;
256 if (is_pci_board(thisboard))
257 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
258 spin_unlock_irqrestore(&dev->spinlock, flags);
262 * This function is called when an interrupt occurs to check whether
263 * the interrupt has been marked as enabled and was generated by the
264 * board. If so, the function prepares the hardware for the next
266 * Returns 0 if the interrupt should be ignored.
268 static int pc236_intr_check(struct comedi_device *dev)
270 const struct pc236_board *thisboard = comedi_board(dev);
271 struct pc236_private *devpriv = dev->private;
275 spin_lock_irqsave(&dev->spinlock, flags);
276 if (devpriv->enable_irq) {
278 if (is_pci_board(thisboard)) {
279 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
280 & PLX9052_INTCSR_LI1STAT_MASK)
281 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
284 /* Clear interrupt and keep it enabled. */
285 outl(PCI236_INTR_ENABLE,
286 devpriv->lcr_iobase + PLX9052_INTCSR);
290 spin_unlock_irqrestore(&dev->spinlock, flags);
296 * Input from subdevice 1.
297 * Copied from the comedi_parport driver.
299 static int pc236_intr_insn(struct comedi_device *dev,
300 struct comedi_subdevice *s, struct comedi_insn *insn,
308 * Subdevice 1 command test.
309 * Copied from the comedi_parport driver.
311 static int pc236_intr_cmdtest(struct comedi_device *dev,
312 struct comedi_subdevice *s,
313 struct comedi_cmd *cmd)
317 /* Step 1 : check if triggers are trivially valid */
319 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
320 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
321 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
322 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
323 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
328 /* Step 2a : make sure trigger sources are unique */
329 /* Step 2b : and mutually compatible */
334 /* Step 3: check it arguments are trivially valid */
336 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
337 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
338 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
339 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, 1);
340 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
345 /* step 4: ignored */
354 * Subdevice 1 command.
356 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
358 pc236_intr_enable(dev);
364 * Subdevice 1 cancel command.
366 static int pc236_intr_cancel(struct comedi_device *dev,
367 struct comedi_subdevice *s)
369 pc236_intr_disable(dev);
375 * Interrupt service routine.
376 * Based on the comedi_parport driver.
378 static irqreturn_t pc236_interrupt(int irq, void *d)
380 struct comedi_device *dev = d;
381 struct comedi_subdevice *s = &dev->subdevices[1];
384 handled = pc236_intr_check(dev);
385 if (dev->attached && handled) {
386 comedi_buf_put(s->async, 0);
387 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
388 comedi_event(dev, s);
390 return IRQ_RETVAL(handled);
393 static void pc236_report_attach(struct comedi_device *dev, unsigned int irq)
395 const struct pc236_board *thisboard = comedi_board(dev);
396 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
400 if (is_isa_board(thisboard))
401 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
402 "(base %#lx) ", dev->iobase);
403 else if (is_pci_board(thisboard))
404 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
405 "(pci %s) ", pci_name(pcidev));
409 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
411 (dev->irq ? "" : " UNAVAILABLE"));
413 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
415 dev_info(dev->class_dev, "%s %sattached\n",
416 dev->board_name, tmpbuf);
419 static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
420 unsigned int irq, unsigned long req_irq_flags)
422 const struct pc236_board *thisboard = comedi_board(dev);
423 struct comedi_subdevice *s;
426 dev->board_name = thisboard->name;
427 dev->iobase = iobase;
429 ret = comedi_alloc_subdevices(dev, 2);
433 s = &dev->subdevices[0];
434 /* digital i/o subdevice (8255) */
435 ret = subdev_8255_init(dev, s, NULL, iobase);
437 dev_err(dev->class_dev, "error! out of memory!\n");
440 s = &dev->subdevices[1];
441 dev->read_subdev = s;
442 s->type = COMEDI_SUBD_UNUSED;
443 pc236_intr_disable(dev);
445 if (request_irq(irq, pc236_interrupt, req_irq_flags,
446 PC236_DRIVER_NAME, dev) >= 0) {
448 s->type = COMEDI_SUBD_DI;
449 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
452 s->range_table = &range_digital;
453 s->insn_bits = pc236_intr_insn;
454 s->do_cmdtest = pc236_intr_cmdtest;
455 s->do_cmd = pc236_intr_cmd;
456 s->cancel = pc236_intr_cancel;
459 pc236_report_attach(dev, irq);
463 static int pc236_pci_common_attach(struct comedi_device *dev,
464 struct pci_dev *pci_dev)
466 struct pc236_private *devpriv = dev->private;
467 unsigned long iobase;
470 comedi_set_hw_dev(dev, &pci_dev->dev);
472 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
474 dev_err(dev->class_dev,
475 "error! cannot enable PCI device and request regions!\n");
478 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
479 iobase = pci_resource_start(pci_dev, 2);
480 return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
484 * Attach is called by the Comedi core to configure the driver
485 * for a particular board. If you specified a board_name array
486 * in the driver structure, dev->board_ptr contains that
489 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
491 const struct pc236_board *thisboard = comedi_board(dev);
492 struct pc236_private *devpriv;
495 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach\n");
497 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
500 dev->private = devpriv;
502 /* Process options according to bus type. */
503 if (is_isa_board(thisboard)) {
504 unsigned long iobase = it->options[0];
505 unsigned int irq = it->options[1];
506 ret = pc236_request_region(dev, iobase, PC236_IO_SIZE);
509 return pc236_common_attach(dev, iobase, irq, 0);
510 } else if (is_pci_board(thisboard)) {
511 struct pci_dev *pci_dev;
513 pci_dev = pc236_find_pci_dev(dev, it);
516 return pc236_pci_common_attach(dev, pci_dev);
518 dev_err(dev->class_dev, PC236_DRIVER_NAME
519 ": BUG! cannot determine board type!\n");
525 * The auto_attach hook is called at PCI probe time via
526 * comedi_pci_auto_config(). dev->board_ptr is NULL on entry.
527 * There should be a board entry matching the supplied PCI device.
529 static int pc236_auto_attach(struct comedi_device *dev,
530 unsigned long context_unused)
532 struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
533 struct pc236_private *devpriv;
538 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
541 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
544 dev->private = devpriv;
546 dev->board_ptr = pc236_find_pci_board(pci_dev);
547 if (dev->board_ptr == NULL) {
548 dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
552 * Need to 'get' the PCI device to match the 'put' in pc236_detach().
553 * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
554 * support for manual attachment of PCI devices via pc236_attach()
557 pci_dev_get(pci_dev);
558 return pc236_pci_common_attach(dev, pci_dev);
561 static void pc236_detach(struct comedi_device *dev)
563 const struct pc236_board *thisboard = comedi_board(dev);
568 pc236_intr_disable(dev);
570 free_irq(dev->irq, dev);
572 subdev_8255_cleanup(dev, &dev->subdevices[0]);
573 if (is_isa_board(thisboard)) {
575 release_region(dev->iobase, PC236_IO_SIZE);
576 } else if (is_pci_board(thisboard)) {
577 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
580 comedi_pci_disable(pcidev);
587 * The struct comedi_driver structure tells the Comedi core module
588 * which functions to call to configure/deconfigure (attach/detach)
589 * the board, and also about the kernel module that contains
592 static struct comedi_driver amplc_pc236_driver = {
593 .driver_name = PC236_DRIVER_NAME,
594 .module = THIS_MODULE,
595 .attach = pc236_attach,
596 .auto_attach = pc236_auto_attach,
597 .detach = pc236_detach,
598 .board_name = &pc236_boards[0].name,
599 .offset = sizeof(struct pc236_board),
600 .num_names = ARRAY_SIZE(pc236_boards),
604 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
605 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
609 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
611 static int amplc_pc236_pci_probe(struct pci_dev *dev,
612 const struct pci_device_id *ent)
614 return comedi_pci_auto_config(dev, &lc_pc236_driver);
617 static void amplc_pc236_pci_remove(struct pci_dev *dev)
619 comedi_pci_auto_unconfig(dev);
622 static struct pci_driver amplc_pc236_pci_driver = {
623 .name = PC236_DRIVER_NAME,
624 .id_table = pc236_pci_table,
625 .probe = &lc_pc236_pci_probe,
626 .remove = &lc_pc236_pci_remove
629 module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
631 module_comedi_driver(amplc_pc236_driver);
634 MODULE_AUTHOR("Comedi http://www.comedi.org");
635 MODULE_DESCRIPTION("Comedi low-level driver");
636 MODULE_LICENSE("GPL");