1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * UIO driver fo Humusoft MF624 DAQ card. |
4 | * Copyright (C) 2011 Rostislav Lisovy <lisovy@gmail.com>, |
5 | * Czech Technical University in Prague |
6 | */ |
7 | |
8 | #include <linux/init.h> |
9 | #include <linux/module.h> |
10 | #include <linux/device.h> |
11 | #include <linux/pci.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/io.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/uio_driver.h> |
16 | |
17 | #define PCI_VENDOR_ID_HUMUSOFT 0x186c |
18 | #define PCI_DEVICE_ID_MF624 0x0624 |
19 | #define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c |
20 | #define PCI_SUBDEVICE_DEVICE 0x0624 |
21 | |
22 | /* BAR0 Interrupt control/status register */ |
23 | #define INTCSR 0x4C |
24 | #define INTCSR_ADINT_ENABLE (1 << 0) |
25 | #define INTCSR_CTR4INT_ENABLE (1 << 3) |
26 | #define INTCSR_PCIINT_ENABLE (1 << 6) |
27 | #define INTCSR_ADINT_STATUS (1 << 2) |
28 | #define INTCSR_CTR4INT_STATUS (1 << 5) |
29 | |
30 | enum mf624_interrupt_source {ADC, CTR4, ALL}; |
31 | |
32 | static void mf624_disable_interrupt(enum mf624_interrupt_source source, |
33 | struct uio_info *info) |
34 | { |
35 | void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; |
36 | |
37 | switch (source) { |
38 | case ADC: |
39 | iowrite32(ioread32(INTCSR_reg) |
40 | & ~(INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE), |
41 | INTCSR_reg); |
42 | break; |
43 | |
44 | case CTR4: |
45 | iowrite32(ioread32(INTCSR_reg) |
46 | & ~(INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE), |
47 | INTCSR_reg); |
48 | break; |
49 | |
50 | case ALL: |
51 | default: |
52 | iowrite32(ioread32(INTCSR_reg) |
53 | & ~(INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE |
54 | | INTCSR_PCIINT_ENABLE), |
55 | INTCSR_reg); |
56 | break; |
57 | } |
58 | } |
59 | |
60 | static void mf624_enable_interrupt(enum mf624_interrupt_source source, |
61 | struct uio_info *info) |
62 | { |
63 | void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; |
64 | |
65 | switch (source) { |
66 | case ADC: |
67 | iowrite32(ioread32(INTCSR_reg) |
68 | | INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE, |
69 | INTCSR_reg); |
70 | break; |
71 | |
72 | case CTR4: |
73 | iowrite32(ioread32(INTCSR_reg) |
74 | | INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE, |
75 | INTCSR_reg); |
76 | break; |
77 | |
78 | case ALL: |
79 | default: |
80 | iowrite32(ioread32(INTCSR_reg) |
81 | | INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE |
82 | | INTCSR_PCIINT_ENABLE, |
83 | INTCSR_reg); |
84 | break; |
85 | } |
86 | } |
87 | |
88 | static irqreturn_t mf624_irq_handler(int irq, struct uio_info *info) |
89 | { |
90 | void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; |
91 | |
92 | if ((ioread32(INTCSR_reg) & INTCSR_ADINT_ENABLE) |
93 | && (ioread32(INTCSR_reg) & INTCSR_ADINT_STATUS)) { |
94 | mf624_disable_interrupt(source: ADC, info); |
95 | return IRQ_HANDLED; |
96 | } |
97 | |
98 | if ((ioread32(INTCSR_reg) & INTCSR_CTR4INT_ENABLE) |
99 | && (ioread32(INTCSR_reg) & INTCSR_CTR4INT_STATUS)) { |
100 | mf624_disable_interrupt(source: CTR4, info); |
101 | return IRQ_HANDLED; |
102 | } |
103 | |
104 | return IRQ_NONE; |
105 | } |
106 | |
107 | static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) |
108 | { |
109 | if (irq_on == 0) |
110 | mf624_disable_interrupt(source: ALL, info); |
111 | else if (irq_on == 1) |
112 | mf624_enable_interrupt(source: ALL, info); |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name) |
118 | { |
119 | resource_size_t start = pci_resource_start(dev, bar); |
120 | resource_size_t len = pci_resource_len(dev, bar); |
121 | |
122 | mem->name = name; |
123 | mem->addr = start & PAGE_MASK; |
124 | mem->offs = start & ~PAGE_MASK; |
125 | if (!mem->addr) |
126 | return -ENODEV; |
127 | mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK; |
128 | mem->memtype = UIO_MEM_PHYS; |
129 | mem->internal_addr = pci_ioremap_bar(pdev: dev, bar); |
130 | if (!mem->internal_addr) |
131 | return -ENODEV; |
132 | return 0; |
133 | } |
134 | |
135 | static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) |
136 | { |
137 | struct uio_info *info; |
138 | |
139 | info = devm_kzalloc(dev: &dev->dev, size: sizeof(struct uio_info), GFP_KERNEL); |
140 | if (!info) |
141 | return -ENOMEM; |
142 | |
143 | if (pci_enable_device(dev)) |
144 | return -ENODEV; |
145 | |
146 | if (pci_request_regions(dev, "mf624" )) |
147 | goto out_disable; |
148 | |
149 | info->name = "mf624" ; |
150 | info->version = "0.0.1" ; |
151 | |
152 | /* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */ |
153 | |
154 | /* BAR0 */ |
155 | if (mf624_setup_mem(dev, bar: 0, mem: &info->mem[0], name: "PCI chipset, interrupts, status " |
156 | "bits, special functions" )) |
157 | goto out_release; |
158 | /* BAR2 */ |
159 | if (mf624_setup_mem(dev, bar: 2, mem: &info->mem[1], name: "ADC, DAC, DIO" )) |
160 | goto out_unmap0; |
161 | |
162 | /* BAR4 */ |
163 | if (mf624_setup_mem(dev, bar: 4, mem: &info->mem[2], name: "Counter/timer chip" )) |
164 | goto out_unmap1; |
165 | |
166 | info->irq = dev->irq; |
167 | info->irq_flags = IRQF_SHARED; |
168 | info->handler = mf624_irq_handler; |
169 | |
170 | info->irqcontrol = mf624_irqcontrol; |
171 | |
172 | if (uio_register_device(&dev->dev, info)) |
173 | goto out_unmap2; |
174 | |
175 | pci_set_drvdata(pdev: dev, data: info); |
176 | |
177 | return 0; |
178 | |
179 | out_unmap2: |
180 | iounmap(addr: info->mem[2].internal_addr); |
181 | out_unmap1: |
182 | iounmap(addr: info->mem[1].internal_addr); |
183 | out_unmap0: |
184 | iounmap(addr: info->mem[0].internal_addr); |
185 | |
186 | out_release: |
187 | pci_release_regions(dev); |
188 | |
189 | out_disable: |
190 | pci_disable_device(dev); |
191 | |
192 | return -ENODEV; |
193 | } |
194 | |
195 | static void mf624_pci_remove(struct pci_dev *dev) |
196 | { |
197 | struct uio_info *info = pci_get_drvdata(pdev: dev); |
198 | |
199 | mf624_disable_interrupt(source: ALL, info); |
200 | |
201 | uio_unregister_device(info); |
202 | pci_release_regions(dev); |
203 | pci_disable_device(dev); |
204 | |
205 | iounmap(addr: info->mem[0].internal_addr); |
206 | iounmap(addr: info->mem[1].internal_addr); |
207 | iounmap(addr: info->mem[2].internal_addr); |
208 | } |
209 | |
210 | static const struct pci_device_id mf624_pci_id[] = { |
211 | { PCI_DEVICE(PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF624) }, |
212 | { 0, } |
213 | }; |
214 | |
215 | static struct pci_driver mf624_pci_driver = { |
216 | .name = "mf624" , |
217 | .id_table = mf624_pci_id, |
218 | .probe = mf624_pci_probe, |
219 | .remove = mf624_pci_remove, |
220 | }; |
221 | MODULE_DEVICE_TABLE(pci, mf624_pci_id); |
222 | |
223 | module_pci_driver(mf624_pci_driver); |
224 | MODULE_LICENSE("GPL v2" ); |
225 | MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>" ); |
226 | |