1bce5c2eaSStephen Hemminger // SPDX-License-Identifier: GPL-2.0
2a6030fccSJohn Ogness /* sercos3: UIO driver for the Automata Sercos III PCI card
3a6030fccSJohn Ogness
4a6030fccSJohn Ogness Copyright (C) 2008 Linutronix GmbH
5a6030fccSJohn Ogness Author: John Ogness <john.ogness@linutronix.de>
6a6030fccSJohn Ogness
7a6030fccSJohn Ogness This is a straight-forward UIO driver, where interrupts are disabled
8a6030fccSJohn Ogness by the interrupt handler and re-enabled via a write to the UIO device
9a6030fccSJohn Ogness by the userspace-part.
10a6030fccSJohn Ogness
11a6030fccSJohn Ogness The only part that may seem odd is the use of a logical OR when
12a6030fccSJohn Ogness storing and restoring enabled interrupts. This is done because the
13a6030fccSJohn Ogness userspace-part could directly modify the Interrupt Enable Register
14a6030fccSJohn Ogness at any time. To reduce possible conflicts, the kernel driver uses
15a6030fccSJohn Ogness a logical OR to make more controlled changes (rather than blindly
16a6030fccSJohn Ogness overwriting previous values).
17a6030fccSJohn Ogness
18a6030fccSJohn Ogness Race conditions exist if the userspace-part directly modifies the
19a6030fccSJohn Ogness Interrupt Enable Register while in operation. The consequences are
20a6030fccSJohn Ogness that certain interrupts would fail to be enabled or disabled. For
21a6030fccSJohn Ogness this reason, the userspace-part should only directly modify the
22a6030fccSJohn Ogness Interrupt Enable Register at the beginning (to get things going).
23a6030fccSJohn Ogness The userspace-part can safely disable interrupts at any time using
24a6030fccSJohn Ogness a write to the UIO device.
25a6030fccSJohn Ogness */
26a6030fccSJohn Ogness
27a6030fccSJohn Ogness #include <linux/device.h>
28a6030fccSJohn Ogness #include <linux/module.h>
29a6030fccSJohn Ogness #include <linux/pci.h>
30a6030fccSJohn Ogness #include <linux/uio_driver.h>
31a6030fccSJohn Ogness #include <linux/io.h>
325a0e3ad6STejun Heo #include <linux/slab.h>
33a6030fccSJohn Ogness
34a6030fccSJohn Ogness /* ID's for SERCOS III PCI card (PLX 9030) */
35a6030fccSJohn Ogness #define SERCOS_SUB_VENDOR_ID 0x1971
36a6030fccSJohn Ogness #define SERCOS_SUB_SYSID_3530 0x3530
37a6030fccSJohn Ogness #define SERCOS_SUB_SYSID_3535 0x3535
38a6030fccSJohn Ogness #define SERCOS_SUB_SYSID_3780 0x3780
39a6030fccSJohn Ogness
40a6030fccSJohn Ogness /* Interrupt Enable Register */
41a6030fccSJohn Ogness #define IER0_OFFSET 0x08
42a6030fccSJohn Ogness
43a6030fccSJohn Ogness /* Interrupt Status Register */
44a6030fccSJohn Ogness #define ISR0_OFFSET 0x18
45a6030fccSJohn Ogness
46a6030fccSJohn Ogness struct sercos3_priv {
47a6030fccSJohn Ogness u32 ier0_cache;
48a6030fccSJohn Ogness spinlock_t ier0_cache_lock;
49a6030fccSJohn Ogness };
50a6030fccSJohn Ogness
51a6030fccSJohn Ogness /* this function assumes ier0_cache_lock is locked! */
sercos3_disable_interrupts(struct uio_info * info,struct sercos3_priv * priv)52a6030fccSJohn Ogness static void sercos3_disable_interrupts(struct uio_info *info,
53a6030fccSJohn Ogness struct sercos3_priv *priv)
54a6030fccSJohn Ogness {
55a6030fccSJohn Ogness void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
56a6030fccSJohn Ogness
57a6030fccSJohn Ogness /* add enabled interrupts to cache */
58a6030fccSJohn Ogness priv->ier0_cache |= ioread32(ier0);
59a6030fccSJohn Ogness
60a6030fccSJohn Ogness /* disable interrupts */
61a6030fccSJohn Ogness iowrite32(0, ier0);
62a6030fccSJohn Ogness }
63a6030fccSJohn Ogness
64a6030fccSJohn Ogness /* this function assumes ier0_cache_lock is locked! */
sercos3_enable_interrupts(struct uio_info * info,struct sercos3_priv * priv)65a6030fccSJohn Ogness static void sercos3_enable_interrupts(struct uio_info *info,
66a6030fccSJohn Ogness struct sercos3_priv *priv)
67a6030fccSJohn Ogness {
68a6030fccSJohn Ogness void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
69a6030fccSJohn Ogness
70a6030fccSJohn Ogness /* restore previously enabled interrupts */
71a6030fccSJohn Ogness iowrite32(ioread32(ier0) | priv->ier0_cache, ier0);
72a6030fccSJohn Ogness priv->ier0_cache = 0;
73a6030fccSJohn Ogness }
74a6030fccSJohn Ogness
sercos3_handler(int irq,struct uio_info * info)75a6030fccSJohn Ogness static irqreturn_t sercos3_handler(int irq, struct uio_info *info)
76a6030fccSJohn Ogness {
77a6030fccSJohn Ogness struct sercos3_priv *priv = info->priv;
78a6030fccSJohn Ogness void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET;
79a6030fccSJohn Ogness void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
80a6030fccSJohn Ogness
81a6030fccSJohn Ogness if (!(ioread32(isr0) & ioread32(ier0)))
82a6030fccSJohn Ogness return IRQ_NONE;
83a6030fccSJohn Ogness
84a6030fccSJohn Ogness spin_lock(&priv->ier0_cache_lock);
85a6030fccSJohn Ogness sercos3_disable_interrupts(info, priv);
86a6030fccSJohn Ogness spin_unlock(&priv->ier0_cache_lock);
87a6030fccSJohn Ogness
88a6030fccSJohn Ogness return IRQ_HANDLED;
89a6030fccSJohn Ogness }
90a6030fccSJohn Ogness
sercos3_irqcontrol(struct uio_info * info,s32 irq_on)91a6030fccSJohn Ogness static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on)
92a6030fccSJohn Ogness {
93a6030fccSJohn Ogness struct sercos3_priv *priv = info->priv;
94a6030fccSJohn Ogness
95a6030fccSJohn Ogness spin_lock_irq(&priv->ier0_cache_lock);
96a6030fccSJohn Ogness if (irq_on)
97a6030fccSJohn Ogness sercos3_enable_interrupts(info, priv);
98a6030fccSJohn Ogness else
99a6030fccSJohn Ogness sercos3_disable_interrupts(info, priv);
100a6030fccSJohn Ogness spin_unlock_irq(&priv->ier0_cache_lock);
101a6030fccSJohn Ogness
102a6030fccSJohn Ogness return 0;
103a6030fccSJohn Ogness }
104a6030fccSJohn Ogness
sercos3_setup_iomem(struct pci_dev * dev,struct uio_info * info,int n,int pci_bar)105a6030fccSJohn Ogness static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info,
106a6030fccSJohn Ogness int n, int pci_bar)
107a6030fccSJohn Ogness {
108a6030fccSJohn Ogness info->mem[n].addr = pci_resource_start(dev, pci_bar);
109a6030fccSJohn Ogness if (!info->mem[n].addr)
110a6030fccSJohn Ogness return -1;
111a6030fccSJohn Ogness info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar),
112a6030fccSJohn Ogness pci_resource_len(dev, pci_bar));
113a6030fccSJohn Ogness if (!info->mem[n].internal_addr)
114a6030fccSJohn Ogness return -1;
115a6030fccSJohn Ogness info->mem[n].size = pci_resource_len(dev, pci_bar);
116a6030fccSJohn Ogness info->mem[n].memtype = UIO_MEM_PHYS;
117a6030fccSJohn Ogness return 0;
118a6030fccSJohn Ogness }
119a6030fccSJohn Ogness
sercos3_pci_probe(struct pci_dev * dev,const struct pci_device_id * id)120b17b75bbSBill Pemberton static int sercos3_pci_probe(struct pci_dev *dev,
121a6030fccSJohn Ogness const struct pci_device_id *id)
122a6030fccSJohn Ogness {
123a6030fccSJohn Ogness struct uio_info *info;
124a6030fccSJohn Ogness struct sercos3_priv *priv;
125a6030fccSJohn Ogness int i;
126a6030fccSJohn Ogness
127*023c9c6dSAlexandru Ardelean info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL);
128a6030fccSJohn Ogness if (!info)
129a6030fccSJohn Ogness return -ENOMEM;
130a6030fccSJohn Ogness
131*023c9c6dSAlexandru Ardelean priv = devm_kzalloc(&dev->dev, sizeof(struct sercos3_priv), GFP_KERNEL);
132a6030fccSJohn Ogness if (!priv)
133*023c9c6dSAlexandru Ardelean return -ENOMEM;
134a6030fccSJohn Ogness
135a6030fccSJohn Ogness if (pci_enable_device(dev))
136*023c9c6dSAlexandru Ardelean return -ENODEV;
137a6030fccSJohn Ogness
138a6030fccSJohn Ogness if (pci_request_regions(dev, "sercos3"))
139a6030fccSJohn Ogness goto out_disable;
140a6030fccSJohn Ogness
141a6030fccSJohn Ogness /* we only need PCI BAR's 0, 2, 3, 4, 5 */
142a6030fccSJohn Ogness if (sercos3_setup_iomem(dev, info, 0, 0))
143a6030fccSJohn Ogness goto out_unmap;
144a6030fccSJohn Ogness if (sercos3_setup_iomem(dev, info, 1, 2))
145a6030fccSJohn Ogness goto out_unmap;
146a6030fccSJohn Ogness if (sercos3_setup_iomem(dev, info, 2, 3))
147a6030fccSJohn Ogness goto out_unmap;
148a6030fccSJohn Ogness if (sercos3_setup_iomem(dev, info, 3, 4))
149a6030fccSJohn Ogness goto out_unmap;
150a6030fccSJohn Ogness if (sercos3_setup_iomem(dev, info, 4, 5))
151a6030fccSJohn Ogness goto out_unmap;
152a6030fccSJohn Ogness
153a6030fccSJohn Ogness spin_lock_init(&priv->ier0_cache_lock);
154a6030fccSJohn Ogness info->priv = priv;
155a6030fccSJohn Ogness info->name = "Sercos_III_PCI";
156a6030fccSJohn Ogness info->version = "0.0.1";
157a6030fccSJohn Ogness info->irq = dev->irq;
15862c86779SHans J. Koch info->irq_flags = IRQF_SHARED;
159a6030fccSJohn Ogness info->handler = sercos3_handler;
160a6030fccSJohn Ogness info->irqcontrol = sercos3_irqcontrol;
161a6030fccSJohn Ogness
162a6030fccSJohn Ogness pci_set_drvdata(dev, info);
163a6030fccSJohn Ogness
164a6030fccSJohn Ogness if (uio_register_device(&dev->dev, info))
165a6030fccSJohn Ogness goto out_unmap;
166a6030fccSJohn Ogness
167a6030fccSJohn Ogness return 0;
168a6030fccSJohn Ogness
169a6030fccSJohn Ogness out_unmap:
170a6030fccSJohn Ogness for (i = 0; i < 5; i++) {
171a6030fccSJohn Ogness if (info->mem[i].internal_addr)
172a6030fccSJohn Ogness iounmap(info->mem[i].internal_addr);
173a6030fccSJohn Ogness }
174a6030fccSJohn Ogness pci_release_regions(dev);
175a6030fccSJohn Ogness out_disable:
176a6030fccSJohn Ogness pci_disable_device(dev);
177a6030fccSJohn Ogness return -ENODEV;
178a6030fccSJohn Ogness }
179a6030fccSJohn Ogness
sercos3_pci_remove(struct pci_dev * dev)180a6030fccSJohn Ogness static void sercos3_pci_remove(struct pci_dev *dev)
181a6030fccSJohn Ogness {
182a6030fccSJohn Ogness struct uio_info *info = pci_get_drvdata(dev);
183a6030fccSJohn Ogness int i;
184a6030fccSJohn Ogness
185a6030fccSJohn Ogness uio_unregister_device(info);
186a6030fccSJohn Ogness pci_release_regions(dev);
187a6030fccSJohn Ogness pci_disable_device(dev);
188a6030fccSJohn Ogness for (i = 0; i < 5; i++) {
189a6030fccSJohn Ogness if (info->mem[i].internal_addr)
190a6030fccSJohn Ogness iounmap(info->mem[i].internal_addr);
191a6030fccSJohn Ogness }
192a6030fccSJohn Ogness }
193a6030fccSJohn Ogness
194d46f7438SBill Pemberton static struct pci_device_id sercos3_pci_ids[] = {
195a6030fccSJohn Ogness {
196a6030fccSJohn Ogness .vendor = PCI_VENDOR_ID_PLX,
197a6030fccSJohn Ogness .device = PCI_DEVICE_ID_PLX_9030,
198a6030fccSJohn Ogness .subvendor = SERCOS_SUB_VENDOR_ID,
199a6030fccSJohn Ogness .subdevice = SERCOS_SUB_SYSID_3530,
200a6030fccSJohn Ogness },
201a6030fccSJohn Ogness {
202a6030fccSJohn Ogness .vendor = PCI_VENDOR_ID_PLX,
203a6030fccSJohn Ogness .device = PCI_DEVICE_ID_PLX_9030,
204a6030fccSJohn Ogness .subvendor = SERCOS_SUB_VENDOR_ID,
205a6030fccSJohn Ogness .subdevice = SERCOS_SUB_SYSID_3535,
206a6030fccSJohn Ogness },
207a6030fccSJohn Ogness {
208a6030fccSJohn Ogness .vendor = PCI_VENDOR_ID_PLX,
209a6030fccSJohn Ogness .device = PCI_DEVICE_ID_PLX_9030,
210a6030fccSJohn Ogness .subvendor = SERCOS_SUB_VENDOR_ID,
211a6030fccSJohn Ogness .subdevice = SERCOS_SUB_SYSID_3780,
212a6030fccSJohn Ogness },
213a6030fccSJohn Ogness { 0, }
214a6030fccSJohn Ogness };
215a6030fccSJohn Ogness
216a6030fccSJohn Ogness static struct pci_driver sercos3_pci_driver = {
217a6030fccSJohn Ogness .name = "sercos3",
218a6030fccSJohn Ogness .id_table = sercos3_pci_ids,
219a6030fccSJohn Ogness .probe = sercos3_pci_probe,
220a6030fccSJohn Ogness .remove = sercos3_pci_remove,
221a6030fccSJohn Ogness };
222a6030fccSJohn Ogness
2238bcaec4eSPeter Huewe module_pci_driver(sercos3_pci_driver);
224a6030fccSJohn Ogness MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card");
225a6030fccSJohn Ogness MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
226a6030fccSJohn Ogness MODULE_LICENSE("GPL v2");
227