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