xref: /linux/drivers/uio/uio_sercos3.c (revision cdd38c5f1ce4398ec58fec95904b75824daab7b5)
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