18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* sercos3: UIO driver for the Automata Sercos III PCI card
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci   Copyright (C) 2008 Linutronix GmbH
58c2ecf20Sopenharmony_ci     Author: John Ogness <john.ogness@linutronix.de>
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci   This is a straight-forward UIO driver, where interrupts are disabled
88c2ecf20Sopenharmony_ci   by the interrupt handler and re-enabled via a write to the UIO device
98c2ecf20Sopenharmony_ci   by the userspace-part.
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci   The only part that may seem odd is the use of a logical OR when
128c2ecf20Sopenharmony_ci   storing and restoring enabled interrupts. This is done because the
138c2ecf20Sopenharmony_ci   userspace-part could directly modify the Interrupt Enable Register
148c2ecf20Sopenharmony_ci   at any time. To reduce possible conflicts, the kernel driver uses
158c2ecf20Sopenharmony_ci   a logical OR to make more controlled changes (rather than blindly
168c2ecf20Sopenharmony_ci   overwriting previous values).
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci   Race conditions exist if the userspace-part directly modifies the
198c2ecf20Sopenharmony_ci   Interrupt Enable Register while in operation. The consequences are
208c2ecf20Sopenharmony_ci   that certain interrupts would fail to be enabled or disabled. For
218c2ecf20Sopenharmony_ci   this reason, the userspace-part should only directly modify the
228c2ecf20Sopenharmony_ci   Interrupt Enable Register at the beginning (to get things going).
238c2ecf20Sopenharmony_ci   The userspace-part can safely disable interrupts at any time using
248c2ecf20Sopenharmony_ci   a write to the UIO device.
258c2ecf20Sopenharmony_ci*/
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <linux/device.h>
288c2ecf20Sopenharmony_ci#include <linux/module.h>
298c2ecf20Sopenharmony_ci#include <linux/pci.h>
308c2ecf20Sopenharmony_ci#include <linux/uio_driver.h>
318c2ecf20Sopenharmony_ci#include <linux/io.h>
328c2ecf20Sopenharmony_ci#include <linux/slab.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/* ID's for SERCOS III PCI card (PLX 9030) */
358c2ecf20Sopenharmony_ci#define SERCOS_SUB_VENDOR_ID  0x1971
368c2ecf20Sopenharmony_ci#define SERCOS_SUB_SYSID_3530 0x3530
378c2ecf20Sopenharmony_ci#define SERCOS_SUB_SYSID_3535 0x3535
388c2ecf20Sopenharmony_ci#define SERCOS_SUB_SYSID_3780 0x3780
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci/* Interrupt Enable Register */
418c2ecf20Sopenharmony_ci#define IER0_OFFSET 0x08
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci/* Interrupt Status Register */
448c2ecf20Sopenharmony_ci#define ISR0_OFFSET 0x18
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistruct sercos3_priv {
478c2ecf20Sopenharmony_ci	u32 ier0_cache;
488c2ecf20Sopenharmony_ci	spinlock_t ier0_cache_lock;
498c2ecf20Sopenharmony_ci};
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/* this function assumes ier0_cache_lock is locked! */
528c2ecf20Sopenharmony_cistatic void sercos3_disable_interrupts(struct uio_info *info,
538c2ecf20Sopenharmony_ci				       struct sercos3_priv *priv)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* add enabled interrupts to cache */
588c2ecf20Sopenharmony_ci	priv->ier0_cache |= ioread32(ier0);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/* disable interrupts */
618c2ecf20Sopenharmony_ci	iowrite32(0, ier0);
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci/* this function assumes ier0_cache_lock is locked! */
658c2ecf20Sopenharmony_cistatic void sercos3_enable_interrupts(struct uio_info *info,
668c2ecf20Sopenharmony_ci				      struct sercos3_priv *priv)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	/* restore previously enabled interrupts */
718c2ecf20Sopenharmony_ci	iowrite32(ioread32(ier0) | priv->ier0_cache, ier0);
728c2ecf20Sopenharmony_ci	priv->ier0_cache = 0;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic irqreturn_t sercos3_handler(int irq, struct uio_info *info)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct sercos3_priv *priv = info->priv;
788c2ecf20Sopenharmony_ci	void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET;
798c2ecf20Sopenharmony_ci	void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (!(ioread32(isr0) & ioread32(ier0)))
828c2ecf20Sopenharmony_ci		return IRQ_NONE;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	spin_lock(&priv->ier0_cache_lock);
858c2ecf20Sopenharmony_ci	sercos3_disable_interrupts(info, priv);
868c2ecf20Sopenharmony_ci	spin_unlock(&priv->ier0_cache_lock);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic int sercos3_irqcontrol(struct uio_info *info, s32 irq_on)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct sercos3_priv *priv = info->priv;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	spin_lock_irq(&priv->ier0_cache_lock);
968c2ecf20Sopenharmony_ci	if (irq_on)
978c2ecf20Sopenharmony_ci		sercos3_enable_interrupts(info, priv);
988c2ecf20Sopenharmony_ci	else
998c2ecf20Sopenharmony_ci		sercos3_disable_interrupts(info, priv);
1008c2ecf20Sopenharmony_ci	spin_unlock_irq(&priv->ier0_cache_lock);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	return 0;
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info,
1068c2ecf20Sopenharmony_ci			       int n, int pci_bar)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	info->mem[n].addr = pci_resource_start(dev, pci_bar);
1098c2ecf20Sopenharmony_ci	if (!info->mem[n].addr)
1108c2ecf20Sopenharmony_ci		return -1;
1118c2ecf20Sopenharmony_ci	info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar),
1128c2ecf20Sopenharmony_ci					     pci_resource_len(dev, pci_bar));
1138c2ecf20Sopenharmony_ci	if (!info->mem[n].internal_addr)
1148c2ecf20Sopenharmony_ci		return -1;
1158c2ecf20Sopenharmony_ci	info->mem[n].size = pci_resource_len(dev, pci_bar);
1168c2ecf20Sopenharmony_ci	info->mem[n].memtype = UIO_MEM_PHYS;
1178c2ecf20Sopenharmony_ci	return 0;
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int sercos3_pci_probe(struct pci_dev *dev,
1218c2ecf20Sopenharmony_ci				       const struct pci_device_id *id)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct uio_info *info;
1248c2ecf20Sopenharmony_ci	struct sercos3_priv *priv;
1258c2ecf20Sopenharmony_ci	int i;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
1288c2ecf20Sopenharmony_ci	if (!info)
1298c2ecf20Sopenharmony_ci		return -ENOMEM;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	priv = kzalloc(sizeof(struct sercos3_priv), GFP_KERNEL);
1328c2ecf20Sopenharmony_ci	if (!priv)
1338c2ecf20Sopenharmony_ci		goto out_free;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (pci_enable_device(dev))
1368c2ecf20Sopenharmony_ci		goto out_free_priv;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	if (pci_request_regions(dev, "sercos3"))
1398c2ecf20Sopenharmony_ci		goto out_disable;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	/* we only need PCI BAR's 0, 2, 3, 4, 5 */
1428c2ecf20Sopenharmony_ci	if (sercos3_setup_iomem(dev, info, 0, 0))
1438c2ecf20Sopenharmony_ci		goto out_unmap;
1448c2ecf20Sopenharmony_ci	if (sercos3_setup_iomem(dev, info, 1, 2))
1458c2ecf20Sopenharmony_ci		goto out_unmap;
1468c2ecf20Sopenharmony_ci	if (sercos3_setup_iomem(dev, info, 2, 3))
1478c2ecf20Sopenharmony_ci		goto out_unmap;
1488c2ecf20Sopenharmony_ci	if (sercos3_setup_iomem(dev, info, 3, 4))
1498c2ecf20Sopenharmony_ci		goto out_unmap;
1508c2ecf20Sopenharmony_ci	if (sercos3_setup_iomem(dev, info, 4, 5))
1518c2ecf20Sopenharmony_ci		goto out_unmap;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	spin_lock_init(&priv->ier0_cache_lock);
1548c2ecf20Sopenharmony_ci	info->priv = priv;
1558c2ecf20Sopenharmony_ci	info->name = "Sercos_III_PCI";
1568c2ecf20Sopenharmony_ci	info->version = "0.0.1";
1578c2ecf20Sopenharmony_ci	info->irq = dev->irq;
1588c2ecf20Sopenharmony_ci	info->irq_flags = IRQF_SHARED;
1598c2ecf20Sopenharmony_ci	info->handler = sercos3_handler;
1608c2ecf20Sopenharmony_ci	info->irqcontrol = sercos3_irqcontrol;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	pci_set_drvdata(dev, info);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	if (uio_register_device(&dev->dev, info))
1658c2ecf20Sopenharmony_ci		goto out_unmap;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return 0;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ciout_unmap:
1708c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++) {
1718c2ecf20Sopenharmony_ci		if (info->mem[i].internal_addr)
1728c2ecf20Sopenharmony_ci			iounmap(info->mem[i].internal_addr);
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci	pci_release_regions(dev);
1758c2ecf20Sopenharmony_ciout_disable:
1768c2ecf20Sopenharmony_ci	pci_disable_device(dev);
1778c2ecf20Sopenharmony_ciout_free_priv:
1788c2ecf20Sopenharmony_ci	kfree(priv);
1798c2ecf20Sopenharmony_ciout_free:
1808c2ecf20Sopenharmony_ci	kfree(info);
1818c2ecf20Sopenharmony_ci	return -ENODEV;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic void sercos3_pci_remove(struct pci_dev *dev)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct uio_info *info = pci_get_drvdata(dev);
1878c2ecf20Sopenharmony_ci	int i;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	uio_unregister_device(info);
1908c2ecf20Sopenharmony_ci	pci_release_regions(dev);
1918c2ecf20Sopenharmony_ci	pci_disable_device(dev);
1928c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++) {
1938c2ecf20Sopenharmony_ci		if (info->mem[i].internal_addr)
1948c2ecf20Sopenharmony_ci			iounmap(info->mem[i].internal_addr);
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci	kfree(info->priv);
1978c2ecf20Sopenharmony_ci	kfree(info);
1988c2ecf20Sopenharmony_ci}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_cistatic struct pci_device_id sercos3_pci_ids[] = {
2018c2ecf20Sopenharmony_ci	{
2028c2ecf20Sopenharmony_ci		.vendor =       PCI_VENDOR_ID_PLX,
2038c2ecf20Sopenharmony_ci		.device =       PCI_DEVICE_ID_PLX_9030,
2048c2ecf20Sopenharmony_ci		.subvendor =    SERCOS_SUB_VENDOR_ID,
2058c2ecf20Sopenharmony_ci		.subdevice =    SERCOS_SUB_SYSID_3530,
2068c2ecf20Sopenharmony_ci	},
2078c2ecf20Sopenharmony_ci	{
2088c2ecf20Sopenharmony_ci		.vendor =       PCI_VENDOR_ID_PLX,
2098c2ecf20Sopenharmony_ci		.device =       PCI_DEVICE_ID_PLX_9030,
2108c2ecf20Sopenharmony_ci		.subvendor =    SERCOS_SUB_VENDOR_ID,
2118c2ecf20Sopenharmony_ci		.subdevice =    SERCOS_SUB_SYSID_3535,
2128c2ecf20Sopenharmony_ci	},
2138c2ecf20Sopenharmony_ci	{
2148c2ecf20Sopenharmony_ci		.vendor =       PCI_VENDOR_ID_PLX,
2158c2ecf20Sopenharmony_ci		.device =       PCI_DEVICE_ID_PLX_9030,
2168c2ecf20Sopenharmony_ci		.subvendor =    SERCOS_SUB_VENDOR_ID,
2178c2ecf20Sopenharmony_ci		.subdevice =    SERCOS_SUB_SYSID_3780,
2188c2ecf20Sopenharmony_ci	},
2198c2ecf20Sopenharmony_ci	{ 0, }
2208c2ecf20Sopenharmony_ci};
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic struct pci_driver sercos3_pci_driver = {
2238c2ecf20Sopenharmony_ci	.name = "sercos3",
2248c2ecf20Sopenharmony_ci	.id_table = sercos3_pci_ids,
2258c2ecf20Sopenharmony_ci	.probe = sercos3_pci_probe,
2268c2ecf20Sopenharmony_ci	.remove = sercos3_pci_remove,
2278c2ecf20Sopenharmony_ci};
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cimodule_pci_driver(sercos3_pci_driver);
2308c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card");
2318c2ecf20Sopenharmony_ciMODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
2328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
233