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 */
138 /* test if ISA supported and this is an ISA board */
139 static inline bool is_isa_board(const struct pc236_board
*board
)
141 return IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA
)
142 && board
->bustype
== isa_bustype
;
145 /* test if PCI supported and this is a PCI board */
146 static inline bool is_pci_board(const struct pc236_board
*board
)
148 return IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
)
149 && board
->bustype
== pci_bustype
;
153 * This function looks for a board matching the supplied PCI device.
155 static const struct pc236_board
*pc236_find_pci_board(struct pci_dev
*pci_dev
)
159 for (i
= 0; i
< ARRAY_SIZE(pc236_boards
); i
++)
160 if (is_pci_board(&pc236_boards
[i
]) &&
161 pci_dev
->device
== pc236_boards
[i
].devid
)
162 return &pc236_boards
[i
];
167 * This function looks for a PCI device matching the requested board name,
170 static struct pci_dev
*pc236_find_pci_dev(struct comedi_device
*dev
,
171 struct comedi_devconfig
*it
)
173 const struct pc236_board
*thisboard
= comedi_board(dev
);
174 struct pci_dev
*pci_dev
= NULL
;
175 int bus
= it
->options
[0];
176 int slot
= it
->options
[1];
178 for_each_pci_dev(pci_dev
) {
180 if (bus
!= pci_dev
->bus
->number
||
181 slot
!= PCI_SLOT(pci_dev
->devfn
))
184 if (pci_dev
->vendor
!= PCI_VENDOR_ID_AMPLICON
)
187 if (thisboard
->model
== anypci_model
) {
188 /* Wildcard board matches any supported PCI board. */
189 const struct pc236_board
*foundboard
;
191 foundboard
= pc236_find_pci_board(pci_dev
);
192 if (foundboard
== NULL
)
194 /* Replace wildcard board_ptr. */
195 dev
->board_ptr
= foundboard
;
197 /* Match specific model name. */
198 if (pci_dev
->device
!= thisboard
->devid
)
203 dev_err(dev
->class_dev
,
204 "No supported board found! (req. bus %d, slot %d)\n",
210 * This function checks and requests an I/O region, reporting an error
211 * if there is a conflict.
213 static int pc236_request_region(struct comedi_device
*dev
, unsigned long from
,
214 unsigned long extent
)
216 if (!from
|| !request_region(from
, extent
, PC236_DRIVER_NAME
)) {
217 dev_err(dev
->class_dev
, "I/O port conflict (%#lx,%lu)!\n",
225 * This function is called to mark the interrupt as disabled (no command
226 * configured on subdevice 1) and to physically disable the interrupt
227 * (not possible on the PC36AT, except by removing the IRQ jumper!).
229 static void pc236_intr_disable(struct comedi_device
*dev
)
231 struct pc236_private
*devpriv
= dev
->private;
234 spin_lock_irqsave(&dev
->spinlock
, flags
);
235 devpriv
->enable_irq
= 0;
236 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) && devpriv
->lcr_iobase
)
237 outl(PCI236_INTR_DISABLE
, devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
238 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
242 * This function is called to mark the interrupt as enabled (a command
243 * configured on subdevice 1) and to physically enable the interrupt
244 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
246 static void pc236_intr_enable(struct comedi_device
*dev
)
248 struct pc236_private
*devpriv
= dev
->private;
251 spin_lock_irqsave(&dev
->spinlock
, flags
);
252 devpriv
->enable_irq
= 1;
253 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) && devpriv
->lcr_iobase
)
254 outl(PCI236_INTR_ENABLE
, devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
255 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
259 * This function is called when an interrupt occurs to check whether
260 * the interrupt has been marked as enabled and was generated by the
261 * board. If so, the function prepares the hardware for the next
263 * Returns 0 if the interrupt should be ignored.
265 static int pc236_intr_check(struct comedi_device
*dev
)
267 struct pc236_private
*devpriv
= dev
->private;
271 spin_lock_irqsave(&dev
->spinlock
, flags
);
272 if (devpriv
->enable_irq
) {
274 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
) &&
275 devpriv
->lcr_iobase
) {
276 if ((inl(devpriv
->lcr_iobase
+ PLX9052_INTCSR
)
277 & PLX9052_INTCSR_LI1STAT_MASK
)
278 == PLX9052_INTCSR_LI1STAT_INACTIVE
) {
281 /* Clear interrupt and keep it enabled. */
282 outl(PCI236_INTR_ENABLE
,
283 devpriv
->lcr_iobase
+ PLX9052_INTCSR
);
287 spin_unlock_irqrestore(&dev
->spinlock
, flags
);
293 * Input from subdevice 1.
294 * Copied from the comedi_parport driver.
296 static int pc236_intr_insn(struct comedi_device
*dev
,
297 struct comedi_subdevice
*s
, struct comedi_insn
*insn
,
305 * Subdevice 1 command test.
306 * Copied from the comedi_parport driver.
308 static int pc236_intr_cmdtest(struct comedi_device
*dev
,
309 struct comedi_subdevice
*s
,
310 struct comedi_cmd
*cmd
)
317 tmp
= cmd
->start_src
;
318 cmd
->start_src
&= TRIG_NOW
;
319 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
322 tmp
= cmd
->scan_begin_src
;
323 cmd
->scan_begin_src
&= TRIG_EXT
;
324 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
327 tmp
= cmd
->convert_src
;
328 cmd
->convert_src
&= TRIG_FOLLOW
;
329 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
332 tmp
= cmd
->scan_end_src
;
333 cmd
->scan_end_src
&= TRIG_COUNT
;
334 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
338 cmd
->stop_src
&= TRIG_NONE
;
339 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
345 /* step 2: ignored */
352 if (cmd
->start_arg
!= 0) {
356 if (cmd
->scan_begin_arg
!= 0) {
357 cmd
->scan_begin_arg
= 0;
360 if (cmd
->convert_arg
!= 0) {
361 cmd
->convert_arg
= 0;
364 if (cmd
->scan_end_arg
!= 1) {
365 cmd
->scan_end_arg
= 1;
368 if (cmd
->stop_arg
!= 0) {
376 /* step 4: ignored */
385 * Subdevice 1 command.
387 static int pc236_intr_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
389 pc236_intr_enable(dev
);
395 * Subdevice 1 cancel command.
397 static int pc236_intr_cancel(struct comedi_device
*dev
,
398 struct comedi_subdevice
*s
)
400 pc236_intr_disable(dev
);
406 * Interrupt service routine.
407 * Based on the comedi_parport driver.
409 static irqreturn_t
pc236_interrupt(int irq
, void *d
)
411 struct comedi_device
*dev
= d
;
412 struct comedi_subdevice
*s
= dev
->subdevices
+ 1;
415 handled
= pc236_intr_check(dev
);
416 if (dev
->attached
&& handled
) {
417 comedi_buf_put(s
->async
, 0);
418 s
->async
->events
|= COMEDI_CB_BLOCK
| COMEDI_CB_EOS
;
419 comedi_event(dev
, s
);
421 return IRQ_RETVAL(handled
);
424 static void pc236_report_attach(struct comedi_device
*dev
, unsigned int irq
)
426 const struct pc236_board
*thisboard
= comedi_board(dev
);
427 struct pci_dev
*pcidev
= comedi_to_pci_dev(dev
);
431 if (is_isa_board(thisboard
))
432 tmplen
= scnprintf(tmpbuf
, sizeof(tmpbuf
),
433 "(base %#lx) ", dev
->iobase
);
434 else if (is_pci_board(thisboard
))
435 tmplen
= scnprintf(tmpbuf
, sizeof(tmpbuf
),
436 "(pci %s) ", pci_name(pcidev
));
440 tmplen
+= scnprintf(&tmpbuf
[tmplen
], sizeof(tmpbuf
) - tmplen
,
442 (dev
->irq
? "" : " UNAVAILABLE"));
444 tmplen
+= scnprintf(&tmpbuf
[tmplen
], sizeof(tmpbuf
) - tmplen
,
446 dev_info(dev
->class_dev
, "%s %sattached\n",
447 dev
->board_name
, tmpbuf
);
450 static int pc236_common_attach(struct comedi_device
*dev
, unsigned long iobase
,
451 unsigned int irq
, unsigned long req_irq_flags
)
453 const struct pc236_board
*thisboard
= comedi_board(dev
);
454 struct comedi_subdevice
*s
;
457 dev
->board_name
= thisboard
->name
;
458 dev
->iobase
= iobase
;
460 ret
= comedi_alloc_subdevices(dev
, 2);
464 s
= dev
->subdevices
+ 0;
465 /* digital i/o subdevice (8255) */
466 ret
= subdev_8255_init(dev
, s
, NULL
, iobase
);
468 dev_err(dev
->class_dev
, "error! out of memory!\n");
471 s
= dev
->subdevices
+ 1;
472 dev
->read_subdev
= s
;
473 s
->type
= COMEDI_SUBD_UNUSED
;
474 pc236_intr_disable(dev
);
476 if (request_irq(irq
, pc236_interrupt
, req_irq_flags
,
477 PC236_DRIVER_NAME
, dev
) >= 0) {
479 s
->type
= COMEDI_SUBD_DI
;
480 s
->subdev_flags
= SDF_READABLE
| SDF_CMD_READ
;
483 s
->range_table
= &range_digital
;
484 s
->insn_bits
= pc236_intr_insn
;
485 s
->do_cmdtest
= pc236_intr_cmdtest
;
486 s
->do_cmd
= pc236_intr_cmd
;
487 s
->cancel
= pc236_intr_cancel
;
490 pc236_report_attach(dev
, irq
);
494 static int pc236_pci_common_attach(struct comedi_device
*dev
,
495 struct pci_dev
*pci_dev
)
497 struct pc236_private
*devpriv
= dev
->private;
498 unsigned long iobase
;
501 comedi_set_hw_dev(dev
, &pci_dev
->dev
);
503 ret
= comedi_pci_enable(pci_dev
, PC236_DRIVER_NAME
);
505 dev_err(dev
->class_dev
,
506 "error! cannot enable PCI device and request regions!\n");
509 devpriv
->lcr_iobase
= pci_resource_start(pci_dev
, 1);
510 iobase
= pci_resource_start(pci_dev
, 2);
511 return pc236_common_attach(dev
, iobase
, pci_dev
->irq
, IRQF_SHARED
);
515 * Attach is called by the Comedi core to configure the driver
516 * for a particular board. If you specified a board_name array
517 * in the driver structure, dev->board_ptr contains that
520 static int pc236_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
522 const struct pc236_board
*thisboard
= comedi_board(dev
);
525 dev_info(dev
->class_dev
, PC236_DRIVER_NAME
": attach\n");
526 ret
= alloc_private(dev
, sizeof(struct pc236_private
));
528 dev_err(dev
->class_dev
, "error! out of memory!\n");
531 /* Process options according to bus type. */
532 if (is_isa_board(thisboard
)) {
533 unsigned long iobase
= it
->options
[0];
534 unsigned int irq
= it
->options
[1];
535 ret
= pc236_request_region(dev
, iobase
, PC236_IO_SIZE
);
538 return pc236_common_attach(dev
, iobase
, irq
, 0);
539 } else if (is_pci_board(thisboard
)) {
540 struct pci_dev
*pci_dev
;
542 pci_dev
= pc236_find_pci_dev(dev
, it
);
545 return pc236_pci_common_attach(dev
, pci_dev
);
547 dev_err(dev
->class_dev
, PC236_DRIVER_NAME
548 ": BUG! cannot determine board type!\n");
554 * The attach_pci hook (if non-NULL) is called at PCI probe time in preference
555 * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should
556 * be a board entry matching the supplied PCI device.
558 static int __devinit
pc236_attach_pci(struct comedi_device
*dev
,
559 struct pci_dev
*pci_dev
)
563 if (!IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI
))
566 dev_info(dev
->class_dev
, PC236_DRIVER_NAME
": attach pci %s\n",
568 ret
= alloc_private(dev
, sizeof(struct pc236_private
));
570 dev_err(dev
->class_dev
, "error! out of memory!\n");
573 dev
->board_ptr
= pc236_find_pci_board(pci_dev
);
574 if (dev
->board_ptr
== NULL
) {
575 dev_err(dev
->class_dev
, "BUG! cannot determine board type!\n");
578 return pc236_pci_common_attach(dev
, pci_dev
);
581 static void pc236_detach(struct comedi_device
*dev
)
583 struct pc236_private
*devpriv
= dev
->private;
584 struct pci_dev
*pcidev
= comedi_to_pci_dev(dev
);
587 pc236_intr_disable(dev
);
589 free_irq(dev
->irq
, dev
);
591 subdev_8255_cleanup(dev
, dev
->subdevices
+ 0);
594 comedi_pci_disable(pcidev
);
598 release_region(dev
->iobase
, PC236_IO_SIZE
);
603 * The struct comedi_driver structure tells the Comedi core module
604 * which functions to call to configure/deconfigure (attach/detach)
605 * the board, and also about the kernel module that contains
608 static struct comedi_driver amplc_pc236_driver
= {
609 .driver_name
= PC236_DRIVER_NAME
,
610 .module
= THIS_MODULE
,
611 .attach
= pc236_attach
,
612 .attach_pci
= pc236_attach_pci
,
613 .detach
= pc236_detach
,
614 .board_name
= &pc236_boards
[0].name
,
615 .offset
= sizeof(struct pc236_board
),
616 .num_names
= ARRAY_SIZE(pc236_boards
),
619 #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
620 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table
) = {
621 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON
, PCI_DEVICE_ID_AMPLICON_PCI236
) },
625 MODULE_DEVICE_TABLE(pci
, pc236_pci_table
);
627 static int __devinit
amplc_pc236_pci_probe(struct pci_dev
*dev
,
628 const struct pci_device_id
*ent
)
630 return comedi_pci_auto_config(dev
, &lc_pc236_driver
);
633 static void __devexit
amplc_pc236_pci_remove(struct pci_dev
*dev
)
635 comedi_pci_auto_unconfig(dev
);
638 static struct pci_driver amplc_pc236_pci_driver
= {
639 .name
= PC236_DRIVER_NAME
,
640 .id_table
= pc236_pci_table
,
641 .probe
= &lc_pc236_pci_probe
,
642 .remove
= __devexit_p(&lc_pc236_pci_remove
)
645 module_comedi_pci_driver(amplc_pc236_driver
, amplc_pc236_pci_driver
);
647 module_comedi_driver(amplc_pc236_driver
);
650 MODULE_AUTHOR("Comedi http://www.comedi.org");
651 MODULE_DESCRIPTION("Comedi low-level driver");
652 MODULE_LICENSE("GPL");