diff options
author | John Ogness <john.ogness@linutronix.de> | 2008-09-18 05:57:15 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-10-16 12:24:53 -0400 |
commit | a6030fcc608bd333c80eab3bfc72f63906476c61 (patch) | |
tree | 28057d28d93ab033a0fe0ee87326986bddba94c8 /drivers/uio/uio_sercos3.c | |
parent | a6fcc3a196d34f6619173ff83c33f8a42074bb76 (diff) |
UIO: add automata sercos3 pci card support
Here is a new version of the patch to support the Automata Sercos III
PCI card driver. I now check that the IRQ is enabled before accepting
the interrupt.
I still use a logical OR to store the enabled interrupts and I've
added a second use of a logical OR when restoring the enabled
interrupts. I added an explanation of why I do this in comments at the
top of the source file.
Since I use a logical OR, I also removed the extra checks if the
Interrupt Enable Register and ier0_cache are 0.
Signed-off-by: John Ogness <john.ogness@linutronix.de>
Signed-off-by: Hans J. Koch <hjk@linutronix.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/uio/uio_sercos3.c')
-rw-r--r-- | drivers/uio/uio_sercos3.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c new file mode 100644 index 000000000000..a6d1b2bc47f3 --- /dev/null +++ b/drivers/uio/uio_sercos3.c | |||
@@ -0,0 +1,243 @@ | |||
1 | /* sercos3: UIO driver for the Automata Sercos III PCI card | ||
2 | |||
3 | Copyright (C) 2008 Linutronix GmbH | ||
4 | Author: John Ogness <john.ogness@linutronix.de> | ||
5 | |||
6 | This is a straight-forward UIO driver, where interrupts are disabled | ||
7 | by the interrupt handler and re-enabled via a write to the UIO device | ||
8 | by the userspace-part. | ||
9 | |||
10 | The only part that may seem odd is the use of a logical OR when | ||
11 | storing and restoring enabled interrupts. This is done because the | ||
12 | userspace-part could directly modify the Interrupt Enable Register | ||
13 | at any time. To reduce possible conflicts, the kernel driver uses | ||
14 | a logical OR to make more controlled changes (rather than blindly | ||
15 | overwriting previous values). | ||
16 | |||
17 | Race conditions exist if the userspace-part directly modifies the | ||
18 | Interrupt Enable Register while in operation. The consequences are | ||
19 | that certain interrupts would fail to be enabled or disabled. For | ||
20 | this reason, the userspace-part should only directly modify the | ||
21 | Interrupt Enable Register at the beginning (to get things going). | ||
22 | The userspace-part can safely disable interrupts at any time using | ||
23 | a write to the UIO device. | ||
24 | */ | ||
25 | |||
26 | #include <linux/device.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/pci.h> | ||
29 | #include <linux/uio_driver.h> | ||
30 | #include <linux/io.h> | ||
31 | |||
32 | /* ID's for SERCOS III PCI card (PLX 9030) */ | ||
33 | #define SERCOS_SUB_VENDOR_ID 0x1971 | ||
34 | #define SERCOS_SUB_SYSID_3530 0x3530 | ||
35 | #define SERCOS_SUB_SYSID_3535 0x3535 | ||
36 | #define SERCOS_SUB_SYSID_3780 0x3780 | ||
37 | |||
38 | /* Interrupt Enable Register */ | ||
39 | #define IER0_OFFSET 0x08 | ||
40 | |||
41 | /* Interrupt Status Register */ | ||
42 | #define ISR0_OFFSET 0x18 | ||
43 | |||
44 | struct sercos3_priv { | ||
45 | u32 ier0_cache; | ||
46 | spinlock_t ier0_cache_lock; | ||
47 | }; | ||
48 | |||
49 | /* this function assumes ier0_cache_lock is locked! */ | ||
50 | static void sercos3_disable_interrupts(struct uio_info *info, | ||
51 | struct sercos3_priv *priv) | ||
52 | { | ||
53 | void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; | ||
54 | |||
55 | /* add enabled interrupts to cache */ | ||
56 | priv->ier0_cache |= ioread32(ier0); | ||
57 | |||
58 | /* disable interrupts */ | ||
59 | iowrite32(0, ier0); | ||
60 | } | ||
61 | |||
62 | /* this function assumes ier0_cache_lock is locked! */ | ||
63 | static void sercos3_enable_interrupts(struct uio_info *info, | ||
64 | struct sercos3_priv *priv) | ||
65 | { | ||
66 | void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; | ||
67 | |||
68 | /* restore previously enabled interrupts */ | ||
69 | iowrite32(ioread32(ier0) | priv->ier0_cache, ier0); | ||
70 | priv->ier0_cache = 0; | ||
71 | } | ||
72 | |||
73 | static irqreturn_t sercos3_handler(int irq, struct uio_info *info) | ||
74 | { | ||
75 | struct sercos3_priv *priv = info->priv; | ||
76 | void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET; | ||
77 | void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; | ||
78 | |||
79 | if (!(ioread32(isr0) & ioread32(ier0))) | ||
80 | return IRQ_NONE; | ||
81 | |||
82 | spin_lock(&priv->ier0_cache_lock); | ||
83 | sercos3_disable_interrupts(info, priv); | ||
84 | spin_unlock(&priv->ier0_cache_lock); | ||
85 | |||
86 | return IRQ_HANDLED; | ||
87 | } | ||
88 | |||
89 | static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on) | ||
90 | { | ||
91 | struct sercos3_priv *priv = info->priv; | ||
92 | |||
93 | spin_lock_irq(&priv->ier0_cache_lock); | ||
94 | if (irq_on) | ||
95 | sercos3_enable_interrupts(info, priv); | ||
96 | else | ||
97 | sercos3_disable_interrupts(info, priv); | ||
98 | spin_unlock_irq(&priv->ier0_cache_lock); | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | |||
103 | static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info, | ||
104 | int n, int pci_bar) | ||
105 | { | ||
106 | info->mem[n].addr = pci_resource_start(dev, pci_bar); | ||
107 | if (!info->mem[n].addr) | ||
108 | return -1; | ||
109 | info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar), | ||
110 | pci_resource_len(dev, pci_bar)); | ||
111 | if (!info->mem[n].internal_addr) | ||
112 | return -1; | ||
113 | info->mem[n].size = pci_resource_len(dev, pci_bar); | ||
114 | info->mem[n].memtype = UIO_MEM_PHYS; | ||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static int __devinit sercos3_pci_probe(struct pci_dev *dev, | ||
119 | const struct pci_device_id *id) | ||
120 | { | ||
121 | struct uio_info *info; | ||
122 | struct sercos3_priv *priv; | ||
123 | int i; | ||
124 | |||
125 | info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); | ||
126 | if (!info) | ||
127 | return -ENOMEM; | ||
128 | |||
129 | priv = kzalloc(sizeof(struct sercos3_priv), GFP_KERNEL); | ||
130 | if (!priv) | ||
131 | goto out_free; | ||
132 | |||
133 | if (pci_enable_device(dev)) | ||
134 | goto out_free_priv; | ||
135 | |||
136 | if (pci_request_regions(dev, "sercos3")) | ||
137 | goto out_disable; | ||
138 | |||
139 | /* we only need PCI BAR's 0, 2, 3, 4, 5 */ | ||
140 | if (sercos3_setup_iomem(dev, info, 0, 0)) | ||
141 | goto out_unmap; | ||
142 | if (sercos3_setup_iomem(dev, info, 1, 2)) | ||
143 | goto out_unmap; | ||
144 | if (sercos3_setup_iomem(dev, info, 2, 3)) | ||
145 | goto out_unmap; | ||
146 | if (sercos3_setup_iomem(dev, info, 3, 4)) | ||
147 | goto out_unmap; | ||
148 | if (sercos3_setup_iomem(dev, info, 4, 5)) | ||
149 | goto out_unmap; | ||
150 | |||
151 | spin_lock_init(&priv->ier0_cache_lock); | ||
152 | info->priv = priv; | ||
153 | info->name = "Sercos_III_PCI"; | ||
154 | info->version = "0.0.1"; | ||
155 | info->irq = dev->irq; | ||
156 | info->irq_flags = IRQF_DISABLED | IRQF_SHARED; | ||
157 | info->handler = sercos3_handler; | ||
158 | info->irqcontrol = sercos3_irqcontrol; | ||
159 | |||
160 | pci_set_drvdata(dev, info); | ||
161 | |||
162 | if (uio_register_device(&dev->dev, info)) | ||
163 | goto out_unmap; | ||
164 | |||
165 | return 0; | ||
166 | |||
167 | out_unmap: | ||
168 | for (i = 0; i < 5; i++) { | ||
169 | if (info->mem[i].internal_addr) | ||
170 | iounmap(info->mem[i].internal_addr); | ||
171 | } | ||
172 | pci_release_regions(dev); | ||
173 | out_disable: | ||
174 | pci_disable_device(dev); | ||
175 | out_free_priv: | ||
176 | kfree(priv); | ||
177 | out_free: | ||
178 | kfree(info); | ||
179 | return -ENODEV; | ||
180 | } | ||
181 | |||
182 | static void sercos3_pci_remove(struct pci_dev *dev) | ||
183 | { | ||
184 | struct uio_info *info = pci_get_drvdata(dev); | ||
185 | int i; | ||
186 | |||
187 | uio_unregister_device(info); | ||
188 | pci_release_regions(dev); | ||
189 | pci_disable_device(dev); | ||
190 | pci_set_drvdata(dev, NULL); | ||
191 | for (i = 0; i < 5; i++) { | ||
192 | if (info->mem[i].internal_addr) | ||
193 | iounmap(info->mem[i].internal_addr); | ||
194 | } | ||
195 | kfree(info->priv); | ||
196 | kfree(info); | ||
197 | } | ||
198 | |||
199 | static struct pci_device_id sercos3_pci_ids[] __devinitdata = { | ||
200 | { | ||
201 | .vendor = PCI_VENDOR_ID_PLX, | ||
202 | .device = PCI_DEVICE_ID_PLX_9030, | ||
203 | .subvendor = SERCOS_SUB_VENDOR_ID, | ||
204 | .subdevice = SERCOS_SUB_SYSID_3530, | ||
205 | }, | ||
206 | { | ||
207 | .vendor = PCI_VENDOR_ID_PLX, | ||
208 | .device = PCI_DEVICE_ID_PLX_9030, | ||
209 | .subvendor = SERCOS_SUB_VENDOR_ID, | ||
210 | .subdevice = SERCOS_SUB_SYSID_3535, | ||
211 | }, | ||
212 | { | ||
213 | .vendor = PCI_VENDOR_ID_PLX, | ||
214 | .device = PCI_DEVICE_ID_PLX_9030, | ||
215 | .subvendor = SERCOS_SUB_VENDOR_ID, | ||
216 | .subdevice = SERCOS_SUB_SYSID_3780, | ||
217 | }, | ||
218 | { 0, } | ||
219 | }; | ||
220 | |||
221 | static struct pci_driver sercos3_pci_driver = { | ||
222 | .name = "sercos3", | ||
223 | .id_table = sercos3_pci_ids, | ||
224 | .probe = sercos3_pci_probe, | ||
225 | .remove = sercos3_pci_remove, | ||
226 | }; | ||
227 | |||
228 | static int __init sercos3_init_module(void) | ||
229 | { | ||
230 | return pci_register_driver(&sercos3_pci_driver); | ||
231 | } | ||
232 | |||
233 | static void __exit sercos3_exit_module(void) | ||
234 | { | ||
235 | pci_unregister_driver(&sercos3_pci_driver); | ||
236 | } | ||
237 | |||
238 | module_init(sercos3_init_module); | ||
239 | module_exit(sercos3_exit_module); | ||
240 | |||
241 | MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card"); | ||
242 | MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); | ||
243 | MODULE_LICENSE("GPL v2"); | ||