| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * UIO driver for Hilscher NetX based fieldbus cards (cifX, comX). | 
 |  * See http://www.hilscher.com for details. | 
 |  * | 
 |  * (C) 2007 Hans J. Koch <hjk@hansjkoch.de> | 
 |  * (C) 2008 Manuel Traut <manut@linutronix.de> | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/io.h> | 
 | #include <linux/module.h> | 
 | #include <linux/pci.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/uio_driver.h> | 
 |  | 
 | #define PCI_VENDOR_ID_HILSCHER		0x15CF | 
 | #define PCI_DEVICE_ID_HILSCHER_NETX	0x0000 | 
 | #define PCI_DEVICE_ID_HILSCHER_NETPLC	0x0010 | 
 | #define PCI_SUBDEVICE_ID_NETPLC_RAM	0x0000 | 
 | #define PCI_SUBDEVICE_ID_NETPLC_FLASH	0x0001 | 
 | #define PCI_SUBDEVICE_ID_NXSB_PCA	0x3235 | 
 | #define PCI_SUBDEVICE_ID_NXPCA		0x3335 | 
 |  | 
 | #define DPM_HOST_INT_EN0	0xfff0 | 
 | #define DPM_HOST_INT_STAT0	0xffe0 | 
 |  | 
 | #define DPM_HOST_INT_MASK	0xe600ffff | 
 | #define DPM_HOST_INT_GLOBAL_EN	0x80000000 | 
 |  | 
 | static irqreturn_t netx_handler(int irq, struct uio_info *dev_info) | 
 | { | 
 | 	void __iomem *int_enable_reg = dev_info->mem[0].internal_addr | 
 | 					+ DPM_HOST_INT_EN0; | 
 | 	void __iomem *int_status_reg = dev_info->mem[0].internal_addr | 
 | 					+ DPM_HOST_INT_STAT0; | 
 |  | 
 | 	/* Is one of our interrupts enabled and active ? */ | 
 | 	if (!(ioread32(int_enable_reg) & ioread32(int_status_reg) | 
 | 		& DPM_HOST_INT_MASK)) | 
 | 		return IRQ_NONE; | 
 |  | 
 | 	/* Disable interrupt */ | 
 | 	iowrite32(ioread32(int_enable_reg) & ~DPM_HOST_INT_GLOBAL_EN, | 
 | 		int_enable_reg); | 
 | 	return IRQ_HANDLED; | 
 | } | 
 |  | 
 | static int netx_pci_probe(struct pci_dev *dev, | 
 | 					const struct pci_device_id *id) | 
 | { | 
 | 	struct uio_info *info; | 
 | 	int bar; | 
 |  | 
 | 	info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); | 
 | 	if (!info) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	if (pci_enable_device(dev)) | 
 | 		goto out_free; | 
 |  | 
 | 	if (pci_request_regions(dev, "netx")) | 
 | 		goto out_disable; | 
 |  | 
 | 	switch (id->device) { | 
 | 	case PCI_DEVICE_ID_HILSCHER_NETX: | 
 | 		bar = 0; | 
 | 		info->name = "netx"; | 
 | 		break; | 
 | 	case PCI_DEVICE_ID_HILSCHER_NETPLC: | 
 | 		bar = 0; | 
 | 		info->name = "netplc"; | 
 | 		break; | 
 | 	default: | 
 | 		bar = 2; | 
 | 		info->name = "netx_plx"; | 
 | 	} | 
 |  | 
 | 	/* BAR0 or 2 points to the card's dual port memory */ | 
 | 	info->mem[0].addr = pci_resource_start(dev, bar); | 
 | 	if (!info->mem[0].addr) | 
 | 		goto out_release; | 
 | 	info->mem[0].internal_addr = ioremap(pci_resource_start(dev, bar), | 
 | 						pci_resource_len(dev, bar)); | 
 |  | 
 | 	if (!info->mem[0].internal_addr) | 
 | 			goto out_release; | 
 |  | 
 | 	info->mem[0].size = pci_resource_len(dev, bar); | 
 | 	info->mem[0].memtype = UIO_MEM_PHYS; | 
 | 	info->irq = dev->irq; | 
 | 	info->irq_flags = IRQF_SHARED; | 
 | 	info->handler = netx_handler; | 
 | 	info->version = "0.0.1"; | 
 |  | 
 | 	/* Make sure all interrupts are disabled */ | 
 | 	iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0); | 
 |  | 
 | 	if (uio_register_device(&dev->dev, info)) | 
 | 		goto out_unmap; | 
 |  | 
 | 	pci_set_drvdata(dev, info); | 
 | 	dev_info(&dev->dev, "Found %s card, registered UIO device.\n", | 
 | 				info->name); | 
 |  | 
 | 	return 0; | 
 |  | 
 | out_unmap: | 
 | 	iounmap(info->mem[0].internal_addr); | 
 | out_release: | 
 | 	pci_release_regions(dev); | 
 | out_disable: | 
 | 	pci_disable_device(dev); | 
 | out_free: | 
 | 	kfree(info); | 
 | 	return -ENODEV; | 
 | } | 
 |  | 
 | static void netx_pci_remove(struct pci_dev *dev) | 
 | { | 
 | 	struct uio_info *info = pci_get_drvdata(dev); | 
 |  | 
 | 	/* Disable all interrupts */ | 
 | 	iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0); | 
 | 	uio_unregister_device(info); | 
 | 	pci_release_regions(dev); | 
 | 	pci_disable_device(dev); | 
 | 	iounmap(info->mem[0].internal_addr); | 
 |  | 
 | 	kfree(info); | 
 | } | 
 |  | 
 | static struct pci_device_id netx_pci_ids[] = { | 
 | 	{ | 
 | 		.vendor =	PCI_VENDOR_ID_HILSCHER, | 
 | 		.device =	PCI_DEVICE_ID_HILSCHER_NETX, | 
 | 		.subvendor =	0, | 
 | 		.subdevice =	0, | 
 | 	}, | 
 | 	{ | 
 | 		.vendor =       PCI_VENDOR_ID_HILSCHER, | 
 | 		.device =       PCI_DEVICE_ID_HILSCHER_NETPLC, | 
 | 		.subvendor =    PCI_VENDOR_ID_HILSCHER, | 
 | 		.subdevice =    PCI_SUBDEVICE_ID_NETPLC_RAM, | 
 | 	}, | 
 | 	{ | 
 | 		.vendor =       PCI_VENDOR_ID_HILSCHER, | 
 | 		.device =       PCI_DEVICE_ID_HILSCHER_NETPLC, | 
 | 		.subvendor =    PCI_VENDOR_ID_HILSCHER, | 
 | 		.subdevice =    PCI_SUBDEVICE_ID_NETPLC_FLASH, | 
 | 	}, | 
 | 	{ | 
 | 		.vendor =	PCI_VENDOR_ID_PLX, | 
 | 		.device =	PCI_DEVICE_ID_PLX_9030, | 
 | 		.subvendor =	PCI_VENDOR_ID_PLX, | 
 | 		.subdevice =	PCI_SUBDEVICE_ID_NXSB_PCA, | 
 | 	}, | 
 | 	{ | 
 | 		.vendor =	PCI_VENDOR_ID_PLX, | 
 | 		.device =	PCI_DEVICE_ID_PLX_9030, | 
 | 		.subvendor =	PCI_VENDOR_ID_PLX, | 
 | 		.subdevice =	PCI_SUBDEVICE_ID_NXPCA, | 
 | 	}, | 
 | 	{ 0, } | 
 | }; | 
 |  | 
 | static struct pci_driver netx_pci_driver = { | 
 | 	.name = "netx", | 
 | 	.id_table = netx_pci_ids, | 
 | 	.probe = netx_pci_probe, | 
 | 	.remove = netx_pci_remove, | 
 | }; | 
 |  | 
 | module_pci_driver(netx_pci_driver); | 
 | MODULE_DEVICE_TABLE(pci, netx_pci_ids); | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_AUTHOR("Hans J. Koch, Manuel Traut"); |