1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/amplc_pci236.c |
4 | * Driver for Amplicon PCI236 DIO boards. |
5 | * |
6 | * Copyright (C) 2002-2014 MEV Ltd. <https://www.mev.co.uk/> |
7 | * |
8 | * COMEDI - Linux Control and Measurement Device Interface |
9 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
10 | */ |
11 | /* |
12 | * Driver: amplc_pci236 |
13 | * Description: Amplicon PCI236 |
14 | * Author: Ian Abbott <abbotti@mev.co.uk> |
15 | * Devices: [Amplicon] PCI236 (amplc_pci236) |
16 | * Updated: Fri, 25 Jul 2014 15:32:40 +0000 |
17 | * Status: works |
18 | * |
19 | * Configuration options: |
20 | * none |
21 | * |
22 | * Manual configuration of PCI board (PCI236) is not supported; it is |
23 | * configured automatically. |
24 | * |
25 | * The PCI236 board has a single 8255 appearing as subdevice 0. |
26 | * |
27 | * Subdevice 1 pretends to be a digital input device, but it always |
28 | * returns 0 when read. However, if you run a command with |
29 | * scan_begin_src=TRIG_EXT, a rising edge on port C bit 3 acts as an |
30 | * external trigger, which can be used to wake up tasks. This is like |
31 | * the comedi_parport device. If no interrupt is connected, then |
32 | * subdevice 1 is unused. |
33 | */ |
34 | |
35 | #include <linux/module.h> |
36 | #include <linux/interrupt.h> |
37 | #include <linux/comedi/comedi_pci.h> |
38 | |
39 | #include "amplc_pc236.h" |
40 | #include "plx9052.h" |
41 | |
42 | /* Disable, and clear, interrupts */ |
43 | #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \ |
44 | PLX9052_INTCSR_LI2POL | \ |
45 | PLX9052_INTCSR_LI1SEL | \ |
46 | PLX9052_INTCSR_LI1CLRINT) |
47 | |
48 | /* Enable, and clear, interrupts */ |
49 | #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \ |
50 | PLX9052_INTCSR_LI1POL | \ |
51 | PLX9052_INTCSR_LI2POL | \ |
52 | PLX9052_INTCSR_PCIENAB | \ |
53 | PLX9052_INTCSR_LI1SEL | \ |
54 | PLX9052_INTCSR_LI1CLRINT) |
55 | |
56 | static void pci236_intr_update_cb(struct comedi_device *dev, bool enable) |
57 | { |
58 | struct pc236_private *devpriv = dev->private; |
59 | |
60 | /* this will also clear the "local interrupt 1" latch */ |
61 | outl(value: enable ? PCI236_INTR_ENABLE : PCI236_INTR_DISABLE, |
62 | port: devpriv->lcr_iobase + PLX9052_INTCSR); |
63 | } |
64 | |
65 | static bool pci236_intr_chk_clr_cb(struct comedi_device *dev) |
66 | { |
67 | struct pc236_private *devpriv = dev->private; |
68 | |
69 | /* check if interrupt occurred */ |
70 | if (!(inl(port: devpriv->lcr_iobase + PLX9052_INTCSR) & |
71 | PLX9052_INTCSR_LI1STAT)) |
72 | return false; |
73 | /* clear the interrupt */ |
74 | pci236_intr_update_cb(dev, enable: devpriv->enable_irq); |
75 | return true; |
76 | } |
77 | |
78 | static const struct pc236_board pc236_pci_board = { |
79 | .name = "pci236" , |
80 | .intr_update_cb = pci236_intr_update_cb, |
81 | .intr_chk_clr_cb = pci236_intr_chk_clr_cb, |
82 | }; |
83 | |
84 | static int pci236_auto_attach(struct comedi_device *dev, |
85 | unsigned long context_unused) |
86 | { |
87 | struct pci_dev *pci_dev = comedi_to_pci_dev(dev); |
88 | struct pc236_private *devpriv; |
89 | unsigned long iobase; |
90 | int ret; |
91 | |
92 | dev_info(dev->class_dev, "amplc_pci236: attach pci %s\n" , |
93 | pci_name(pci_dev)); |
94 | |
95 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
96 | if (!devpriv) |
97 | return -ENOMEM; |
98 | |
99 | dev->board_ptr = &pc236_pci_board; |
100 | dev->board_name = pc236_pci_board.name; |
101 | ret = comedi_pci_enable(dev); |
102 | if (ret) |
103 | return ret; |
104 | |
105 | devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); |
106 | iobase = pci_resource_start(pci_dev, 2); |
107 | return amplc_pc236_common_attach(dev, iobase, irq: pci_dev->irq, |
108 | IRQF_SHARED); |
109 | } |
110 | |
111 | static struct comedi_driver amplc_pci236_driver = { |
112 | .driver_name = "amplc_pci236" , |
113 | .module = THIS_MODULE, |
114 | .auto_attach = pci236_auto_attach, |
115 | .detach = comedi_pci_detach, |
116 | }; |
117 | |
118 | static const struct pci_device_id pci236_pci_table[] = { |
119 | { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) }, |
120 | { 0 } |
121 | }; |
122 | |
123 | MODULE_DEVICE_TABLE(pci, pci236_pci_table); |
124 | |
125 | static int amplc_pci236_pci_probe(struct pci_dev *dev, |
126 | const struct pci_device_id *id) |
127 | { |
128 | return comedi_pci_auto_config(pcidev: dev, driver: &lc_pci236_driver, |
129 | context: id->driver_data); |
130 | } |
131 | |
132 | static struct pci_driver amplc_pci236_pci_driver = { |
133 | .name = "amplc_pci236" , |
134 | .id_table = pci236_pci_table, |
135 | .probe = &lc_pci236_pci_probe, |
136 | .remove = comedi_pci_auto_unconfig, |
137 | }; |
138 | |
139 | module_comedi_pci_driver(amplc_pci236_driver, amplc_pci236_pci_driver); |
140 | |
141 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
142 | MODULE_DESCRIPTION("Comedi driver for Amplicon PCI236 DIO boards" ); |
143 | MODULE_LICENSE("GPL" ); |
144 | |