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
{
135 struct pci_dev
*pci_dev
;
136 unsigned long lcr_iobase
; /* PLX PCI9052 config registers in PCIBAR1 */
141 * This function looks for a board matching the supplied PCI device.
143 static const struct pc236_board
*pc236_find_pci_board(struct pci_dev
*pci_dev
)
147 for (i
= 0; i
< ARRAY_SIZE(pc236_boards
); i
++)
148 if (pc236_boards
[i
].bustype
== pci_bustype
&&
149 pci_dev
->device
== pc236_boards
[i
].devid
)
150 return &pc236_boards
[i
];
155 * This function looks for a PCI device matching the requested board name,
158 static struct pci_dev
*
159 pc236_find_pci(struct comedi_device
*dev
, int bus
, int slot
)
161 const struct pc236_board
*thisboard
= comedi_board(dev
);
162 struct pci_dev
*pci_dev
= NULL
;
164 /* Look for matching PCI device. */
165 for (pci_dev
= pci_get_device(PCI_VENDOR_ID_AMPLICON
, PCI_ANY_ID
, NULL
);
167 pci_dev
= pci_get_device(PCI_VENDOR_ID_AMPLICON
,
168 PCI_ANY_ID
, pci_dev
)) {
169 /* If bus/slot specified, check them. */
171 if (bus
!= pci_dev
->bus
->number
172 || slot
!= PCI_SLOT(pci_dev
->devfn
))
175 if (thisboard
->model
== anypci_model
) {
176 /* Wildcard board matches any supported PCI board. */
177 const struct pc236_board
*foundboard
;
178 foundboard
= pc236_find_pci_board(pci_dev
);
179 if (foundboard
== NULL
)
181 /* Replace wildcard board_ptr. */
182 dev
->board_ptr
= thisboard
= foundboard
;
184 /* Match specific model name. */
185 if (pci_dev
->device
!= thisboard
->devid
)
192 /* No match found. */
194 dev_err(dev
->class_dev
,
195 "error! no %s found at pci %02x:%02x!\n",
196 thisboard
->name
, bus
, slot
);
198 dev_err(dev
->class_dev
, "error! no %s found!\n",
205 * This function checks and requests an I/O region, reporting an error
206 * if there is a conflict.
208 static int pc236_request_region(struct comedi_device
*dev
, unsigned long from
,
209 unsigned long extent
)
211 if (!from
|| !request_region(from
, extent
, PC236_DRIVER_NAME
)) {
212 dev_err(dev
->class_dev
, "I/O port conflict (%#lx,%lu)!\n",
220 * This function is called to mark the interrupt as disabled (no command
221 * configured on subdevice 1) and to physically disable the interrupt
222 * (not possible on the PC36AT, except by removing the IRQ jumper!).
224 static void pc236_intr_disable(struct comedi_device
*dev
)
226 struct pc236_private
*devpriv
= dev
->private;
229 spin_lock_irqsave(&dev
->spinlock
, flags
);
230 devpriv
->enable_irq
= 0;
231 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) && devpriv
->lcr_iobase
)
232 outl(PCI236_INTR_DISABLE
, devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
233 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
237 * This function is called to mark the interrupt as enabled (a command
238 * configured on subdevice 1) and to physically enable the interrupt
239 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
241 static void pc236_intr_enable(struct comedi_device
*dev
)
243 struct pc236_private
*devpriv
= dev
->private;
246 spin_lock_irqsave(&dev
->spinlock
, flags
);
247 devpriv
->enable_irq
= 1;
248 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) && devpriv
->lcr_iobase
)
249 outl(PCI236_INTR_ENABLE
, devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
250 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
254 * This function is called when an interrupt occurs to check whether
255 * the interrupt has been marked as enabled and was generated by the
256 * board. If so, the function prepares the hardware for the next
258 * Returns 0 if the interrupt should be ignored.
260 static int pc236_intr_check(struct comedi_device
*dev
)
262 struct pc236_private
*devpriv
= dev
->private;
266 spin_lock_irqsave(&dev
->spinlock
, flags
);
267 if (devpriv
->enable_irq
) {
269 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) &&
270 devpriv
->lcr_iobase
) {
271 if ((inl(devpriv
->lcr_iobase
+ PLX9052_INTCSR
)
272 & PLX9052_INTCSR_LI1STAT_MASK
)
273 == PLX9052_INTCSR_LI1STAT_INACTIVE
) {
276 /* Clear interrupt and keep it enabled. */
277 outl(PCI236_INTR_ENABLE
,
278 devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
282 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
288 * Input from subdevice 1.
289 * Copied from the comedi_parport driver.
291 static int pc236_intr_insn(struct comedi_device
*dev
,
292 struct comedi_subdevice
*s
, struct comedi_insn
*insn
,
300 * Subdevice 1 command test.
301 * Copied from the comedi_parport driver.
303 static int pc236_intr_cmdtest(struct comedi_device
*dev
,
304 struct comedi_subdevice
*s
,
305 struct comedi_cmd
*cmd
)
312 tmp
= cmd
->start_src
;
313 cmd
->start_src
&= TRIG_NOW
;
314 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
317 tmp
= cmd
->scan_begin_src
;
318 cmd
->scan_begin_src
&= TRIG_EXT
;
319 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
322 tmp
= cmd
->convert_src
;
323 cmd
->convert_src
&= TRIG_FOLLOW
;
324 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
327 tmp
= cmd
->scan_end_src
;
328 cmd
->scan_end_src
&= TRIG_COUNT
;
329 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
333 cmd
->stop_src
&= TRIG_NONE
;
334 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
340 /* step 2: ignored */
347 if (cmd
->start_arg
!= 0) {
351 if (cmd
->scan_begin_arg
!= 0) {
352 cmd
->scan_begin_arg
= 0;
355 if (cmd
->convert_arg
!= 0) {
356 cmd
->convert_arg
= 0;
359 if (cmd
->scan_end_arg
!= 1) {
360 cmd
->scan_end_arg
= 1;
363 if (cmd
->stop_arg
!= 0) {
371 /* step 4: ignored */
380 * Subdevice 1 command.
382 static int pc236_intr_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
384 pc236_intr_enable(dev
);
390 * Subdevice 1 cancel command.
392 static int pc236_intr_cancel(struct comedi_device
*dev
,
393 struct comedi_subdevice
*s
)
395 pc236_intr_disable(dev
);
401 * Interrupt service routine.
402 * Based on the comedi_parport driver.
404 static irqreturn_t
pc236_interrupt(int irq
, void *d
)
406 struct comedi_device
*dev
= d
;
407 struct comedi_subdevice
*s
= dev
->subdevices
+ 1;
410 handled
= pc236_intr_check(dev
);
411 if (dev
->attached
&& handled
) {
412 comedi_buf_put(s
->async
, 0);
413 s
->async
->events
|= COMEDI_CB_BLOCK
| COMEDI_CB_EOS
;
414 comedi_event(dev
, s
);
416 return IRQ_RETVAL(handled
);
419 static void pc236_report_attach(struct comedi_device
*dev
, unsigned int irq
)
421 const struct pc236_board
*thisboard
= comedi_board(dev
);
425 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA
) &&
426 thisboard
->bustype
== isa_bustype
)
427 tmplen
= scnprintf(tmpbuf
, sizeof(tmpbuf
),
428 "(base %#lx) ", dev
->iobase
);
429 else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) &&
430 thisboard
->bustype
== pci_bustype
) {
431 struct pc236_private
*devpriv
= dev
->private;
432 struct pci_dev
*pci_dev
= devpriv
->pci_dev
;
433 tmplen
= scnprintf(tmpbuf
, sizeof(tmpbuf
),
434 "(pci %s) ", pci_name(pci_dev
));
438 tmplen
+= scnprintf(&tmpbuf
[tmplen
], sizeof(tmpbuf
) - tmplen
,
440 (dev
->irq
? "" : " UNAVAILABLE"));
442 tmplen
+= scnprintf(&tmpbuf
[tmplen
], sizeof(tmpbuf
) - tmplen
,
444 dev_info(dev
->class_dev
, "%s %sattached\n",
445 dev
->board_name
, tmpbuf
);
448 static int pc236_common_attach(struct comedi_device
*dev
, unsigned long iobase
,
449 unsigned int irq
, unsigned long req_irq_flags
)
451 const struct pc236_board
*thisboard
= comedi_board(dev
);
452 struct comedi_subdevice
*s
;
455 dev
->board_name
= thisboard
->name
;
456 dev
->iobase
= iobase
;
458 ret
= comedi_alloc_subdevices(dev
, 2);
462 s
= dev
->subdevices
+ 0;
463 /* digital i/o subdevice (8255) */
464 ret
= subdev_8255_init(dev
, s
, NULL
, iobase
);
466 dev_err(dev
->class_dev
, "error! out of memory!\n");
469 s
= dev
->subdevices
+ 1;
470 dev
->read_subdev
= s
;
471 s
->type
= COMEDI_SUBD_UNUSED
;
472 pc236_intr_disable(dev
);
474 if (request_irq(irq
, pc236_interrupt
, req_irq_flags
,
475 PC236_DRIVER_NAME
, dev
) >= 0) {
477 s
->type
= COMEDI_SUBD_DI
;
478 s
->subdev_flags
= SDF_READABLE
| SDF_CMD_READ
;
481 s
->range_table
= &range_digital
;
482 s
->insn_bits
= pc236_intr_insn
;
483 s
->do_cmdtest
= pc236_intr_cmdtest
;
484 s
->do_cmd
= pc236_intr_cmd
;
485 s
->cancel
= pc236_intr_cancel
;
488 pc236_report_attach(dev
, irq
);
492 static int pc236_pci_common_attach(struct comedi_device
*dev
,
493 struct pci_dev
*pci_dev
)
495 struct pc236_private
*devpriv
= dev
->private;
496 unsigned long iobase
;
499 devpriv
->pci_dev
= pci_dev
;
500 ret
= comedi_pci_enable(pci_dev
, PC236_DRIVER_NAME
);
502 dev_err(dev
->class_dev
,
503 "error! cannot enable PCI device and request regions!\n");
506 devpriv
->lcr_iobase
= pci_resource_start(pci_dev
, 1);
507 iobase
= pci_resource_start(pci_dev
, 2);
508 return pc236_common_attach(dev
, iobase
, pci_dev
->irq
, IRQF_SHARED
);
512 * Attach is called by the Comedi core to configure the driver
513 * for a particular board. If you specified a board_name array
514 * in the driver structure, dev->board_ptr contains that
517 static int pc236_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
519 const struct pc236_board
*thisboard
= comedi_board(dev
);
522 dev_info(dev
->class_dev
, PC236_DRIVER_NAME
": attach\n");
523 ret
= alloc_private(dev
, sizeof(struct pc236_private
));
525 dev_err(dev
->class_dev
, "error! out of memory!\n");
528 /* Process options according to bus type. */
529 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA
) &&
530 thisboard
->bustype
== isa_bustype
) {
531 unsigned long iobase
= it
->options
[0];
532 unsigned int irq
= it
->options
[1];
533 ret
= pc236_request_region(dev
, iobase
, PC236_IO_SIZE
);
536 return pc236_common_attach(dev
, iobase
, irq
, 0);
537 } else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) &&
538 thisboard
->bustype
== pci_bustype
) {
539 int bus
= it
->options
[0];
540 int slot
= it
->options
[1];
541 struct pci_dev
*pci_dev
;
543 pci_dev
= pc236_find_pci(dev
, bus
, slot
);
546 return pc236_pci_common_attach(dev
, pci_dev
);
548 dev_err(dev
->class_dev
, PC236_DRIVER_NAME
549 ": BUG! cannot determine board type!\n");
555 * The attach_pci hook (if non-NULL) is called at PCI probe time in preference
556 * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should
557 * be a board entry matching the supplied PCI device.
559 static int __devinit
pc236_attach_pci(struct comedi_device
*dev
,
560 struct pci_dev
*pci_dev
)
564 if (!IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
))
567 dev_info(dev
->class_dev
, PC236_DRIVER_NAME
": attach pci %s\n",
569 ret
= alloc_private(dev
, sizeof(struct pc236_private
));
571 dev_err(dev
->class_dev
, "error! out of memory!\n");
574 dev
->board_ptr
= pc236_find_pci_board(pci_dev
);
575 if (dev
->board_ptr
== NULL
) {
576 dev_err(dev
->class_dev
, "BUG! cannot determine board type!\n");
579 return pc236_pci_common_attach(dev
, pci_dev
);
582 static void pc236_detach(struct comedi_device
*dev
)
584 struct pc236_private
*devpriv
= dev
->private;
587 pc236_intr_disable(dev
);
589 free_irq(dev
->irq
, dev
);
591 subdev_8255_cleanup(dev
, dev
->subdevices
+ 0);
593 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) &&
596 comedi_pci_disable(devpriv
->pci_dev
);
597 pci_dev_put(devpriv
->pci_dev
);
598 } else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA
)) {
600 release_region(dev
->iobase
, PC236_IO_SIZE
);
606 * The struct comedi_driver structure tells the Comedi core module
607 * which functions to call to configure/deconfigure (attach/detach)
608 * the board, and also about the kernel module that contains
611 static struct comedi_driver amplc_pc236_driver
= {
612 .driver_name
= PC236_DRIVER_NAME
,
613 .module
= THIS_MODULE
,
614 .attach
= pc236_attach
,
615 .attach_pci
= pc236_attach_pci
,
616 .detach
= pc236_detach
,
617 .board_name
= &pc236_boards
[0].name
,
618 .offset
= sizeof(struct pc236_board
),
619 .num_names
= ARRAY_SIZE(pc236_boards
),
622 #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
623 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table
) = {
624 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON
, PCI_DEVICE_ID_AMPLICON_PCI236
) },
628 MODULE_DEVICE_TABLE(pci
, pc236_pci_table
);
630 static int __devinit
amplc_pc236_pci_probe(struct pci_dev
*dev
,
631 const struct pci_device_id
*ent
)
633 return comedi_pci_auto_config(dev
, &lc_pc236_driver
);
636 static void __devexit
amplc_pc236_pci_remove(struct pci_dev
*dev
)
638 comedi_pci_auto_unconfig(dev
);
641 static struct pci_driver amplc_pc236_pci_driver
= {
642 .name
= PC236_DRIVER_NAME
,
643 .id_table
= pc236_pci_table
,
644 .probe
= &lc_pc236_pci_probe
,
645 .remove
= __devexit_p(&lc_pc236_pci_remove
)
648 module_comedi_pci_driver(amplc_pc236_driver
, amplc_pc236_pci_driver
);
650 module_comedi_driver(amplc_pc236_driver
);
653 MODULE_AUTHOR("Comedi http://www.comedi.org");
654 MODULE_DESCRIPTION("Comedi low-level driver");
655 MODULE_LICENSE("GPL");