Merge tag 'locking-urgent-2021-05-23' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.git] / drivers / uio / uio_mf624.c
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(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(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(ALL, info);
111         else if (irq_on == 1)
112                 mf624_enable_interrupt(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(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, 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, 0, &info->mem[0], "PCI chipset, interrupts, status "
156                             "bits, special functions"))
157                 goto out_release;
158         /* BAR2 */
159         if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO"))
160                 goto out_unmap0;
161
162         /* BAR4 */
163         if (mf624_setup_mem(dev, 4, &info->mem[2], "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(dev, info);
176
177         return 0;
178
179 out_unmap2:
180         iounmap(info->mem[2].internal_addr);
181 out_unmap1:
182         iounmap(info->mem[1].internal_addr);
183 out_unmap0:
184         iounmap(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(dev);
198
199         mf624_disable_interrupt(ALL, info);
200
201         uio_unregister_device(info);
202         pci_release_regions(dev);
203         pci_disable_device(dev);
204
205         iounmap(info->mem[0].internal_addr);
206         iounmap(info->mem[1].internal_addr);
207         iounmap(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>");