diff options
Diffstat (limited to 'drivers/uio')
| -rw-r--r-- | drivers/uio/Kconfig | 57 | ||||
| -rw-r--r-- | drivers/uio/Makefile | 4 | ||||
| -rw-r--r-- | drivers/uio/uio.c | 157 | ||||
| -rw-r--r-- | drivers/uio/uio_aec.c | 17 | ||||
| -rw-r--r-- | drivers/uio/uio_cif.c | 19 | ||||
| -rw-r--r-- | drivers/uio/uio_dmem_genirq.c | 357 | ||||
| -rw-r--r-- | drivers/uio/uio_mf624.c | 246 | ||||
| -rw-r--r-- | drivers/uio/uio_netx.c | 36 | ||||
| -rw-r--r-- | drivers/uio/uio_pci_generic.c | 102 | ||||
| -rw-r--r-- | drivers/uio/uio_pdrv.c | 121 | ||||
| -rw-r--r-- | drivers/uio/uio_pdrv_genirq.c | 98 | ||||
| -rw-r--r-- | drivers/uio/uio_pruss.c | 242 | ||||
| -rw-r--r-- | drivers/uio/uio_sercos3.c | 19 |
13 files changed, 1081 insertions, 394 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index bb440792a1b..5a90914d856 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -1,6 +1,6 @@ menuconfig UIO tristate "Userspace I/O drivers" - depends on !S390 + depends on MMU help Enable this to allow the userspace driver core code to be built. This code allows userspace programs easy access to @@ -24,13 +24,6 @@ config UIO_CIF To compile this driver as a module, choose M here: the module will be called uio_cif. -config UIO_PDRV - tristate "Userspace I/O platform driver" - help - Generic platform driver for Userspace I/O devices. - - If you don't know what to do here, say N. - config UIO_PDRV_GENIRQ tristate "Userspace I/O platform driver with generic IRQ handling" help @@ -44,6 +37,23 @@ config UIO_PDRV_GENIRQ If you don't know what to do here, say N. +config UIO_DMEM_GENIRQ + tristate "Userspace platform driver with generic irq and dynamic memory" + depends on HAS_DMA + help + Platform driver for Userspace I/O devices, including generic + interrupt handling code. Shared interrupts are not supported. + + Memory regions can be specified with the same platform device + resources as the UIO_PDRV drivers, but dynamic regions can also + be specified. + The number and size of these regions is static, + but the memory allocation is not performed until + the associated device file is opened. The + memory is freed once the uio device is closed. + + If you don't know what to do here, say N. + config UIO_AEC tristate "AEC video timestamp device" depends on PCI @@ -94,4 +104,35 @@ config UIO_NETX To compile this driver as a module, choose M here; the module will be called uio_netx. +config UIO_PRUSS + tristate "Texas Instruments PRUSS driver" + depends on ARCH_DAVINCI_DA850 + select GENERIC_ALLOCATOR + help + PRUSS driver for OMAPL138/DA850/AM18XX devices + PRUSS driver requires user space components, examples and user space + driver is available from below SVN repo - you may use anonymous login + + https://gforge.ti.com/gf/project/pru_sw/ + + More info on API is available at below wiki + + http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader + + To compile this driver as a module, choose M here: the module + will be called uio_pruss. + +config UIO_MF624 + tristate "Humusoft MF624 DAQ PCI card driver" + depends on PCI + help + Userspace I/O interface for the Humusoft MF624 PCI card. + A sample userspace application using this driver is available + (among other MF624 related information and software components) + for download in a git repository: + + git clone git://rtime.felk.cvut.cz/mf6xx.git + + If you compile this as a module, it will be called uio_mf624. + endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index 18fd818c5b9..d3218bde3ae 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -1,8 +1,10 @@ obj-$(CONFIG_UIO) += uio.o obj-$(CONFIG_UIO_CIF) += uio_cif.o -obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o +obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o obj-$(CONFIG_UIO_AEC) += uio_aec.o obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o obj-$(CONFIG_UIO_NETX) += uio_netx.o +obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o +obj-$(CONFIG_UIO_MF624) += uio_mf624.o diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 51fe1795d5a..a673e5b6a2e 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -35,7 +35,6 @@ struct uio_device { atomic_t event; struct fasync_struct *async_queue; wait_queue_head_t wait; - int vma_count; struct uio_info *info; struct kobject *map_dir; struct kobject *portio_dir; @@ -69,7 +68,7 @@ static ssize_t map_name_show(struct uio_mem *mem, char *buf) static ssize_t map_addr_show(struct uio_mem *mem, char *buf) { - return sprintf(buf, "0x%lx\n", mem->addr); + return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr); } static ssize_t map_size_show(struct uio_mem *mem, char *buf) @@ -79,7 +78,7 @@ static ssize_t map_size_show(struct uio_mem *mem, char *buf) static ssize_t map_offset_show(struct uio_mem *mem, char *buf) { - return sprintf(buf, "0x%lx\n", mem->addr & ~PAGE_MASK); + return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr & ~PAGE_MASK); } struct map_sysfs_entry { @@ -224,38 +223,42 @@ static struct kobj_type portio_attr_type = { .default_attrs = portio_attrs, }; -static ssize_t show_name(struct device *dev, +static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uio_device *idev = dev_get_drvdata(dev); return sprintf(buf, "%s\n", idev->info->name); } +static DEVICE_ATTR_RO(name); -static ssize_t show_version(struct device *dev, +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uio_device *idev = dev_get_drvdata(dev); return sprintf(buf, "%s\n", idev->info->version); } +static DEVICE_ATTR_RO(version); -static ssize_t show_event(struct device *dev, +static ssize_t event_show(struct device *dev, struct device_attribute *attr, char *buf) { struct uio_device *idev = dev_get_drvdata(dev); return sprintf(buf, "%u\n", (unsigned int)atomic_read(&idev->event)); } +static DEVICE_ATTR_RO(event); -static struct device_attribute uio_class_attributes[] = { - __ATTR(name, S_IRUGO, show_name, NULL), - __ATTR(version, S_IRUGO, show_version, NULL), - __ATTR(event, S_IRUGO, show_event, NULL), - {} +static struct attribute *uio_attrs[] = { + &dev_attr_name.attr, + &dev_attr_version.attr, + &dev_attr_event.attr, + NULL, }; +ATTRIBUTE_GROUPS(uio); /* UIO class infrastructure */ static struct class uio_class = { .name = "uio", - .dev_attrs = uio_class_attributes, + .dev_groups = uio_groups, }; /* @@ -285,13 +288,13 @@ static int uio_dev_add_attributes(struct uio_device *idev) } map = kzalloc(sizeof(*map), GFP_KERNEL); if (!map) - goto err_map; + goto err_map_kobj; kobject_init(&map->kobj, &map_attr_type); map->mem = mem; mem->map = map; ret = kobject_add(&map->kobj, idev->map_dir, "map%d", mi); if (ret) - goto err_map; + goto err_map_kobj; ret = kobject_uevent(&map->kobj, KOBJ_ADD); if (ret) goto err_map; @@ -310,14 +313,14 @@ static int uio_dev_add_attributes(struct uio_device *idev) } portio = kzalloc(sizeof(*portio), GFP_KERNEL); if (!portio) - goto err_portio; + goto err_portio_kobj; kobject_init(&portio->kobj, &portio_attr_type); portio->port = port; port->portio = portio; ret = kobject_add(&portio->kobj, idev->portio_dir, "port%d", pi); if (ret) - goto err_portio; + goto err_portio_kobj; ret = kobject_uevent(&portio->kobj, KOBJ_ADD); if (ret) goto err_portio; @@ -326,14 +329,18 @@ static int uio_dev_add_attributes(struct uio_device *idev) return 0; err_portio: - for (pi--; pi >= 0; pi--) { + pi--; +err_portio_kobj: + for (; pi >= 0; pi--) { port = &idev->info->port[pi]; portio = port->portio; kobject_put(&portio->kobj); } kobject_put(idev->portio_dir); err_map: - for (mi--; mi>=0; mi--) { + mi--; +err_map_kobj: + for (; mi >= 0; mi--) { mem = &idev->info->mem[mi]; map = mem->map; kobject_put(&map->kobj); @@ -369,20 +376,16 @@ static void uio_dev_del_attributes(struct uio_device *idev) static int uio_get_minor(struct uio_device *idev) { int retval = -ENOMEM; - int id; mutex_lock(&minor_lock); - if (idr_pre_get(&uio_idr, GFP_KERNEL) == 0) - goto exit; - - retval = idr_get_new(&uio_idr, idev, &id); - if (retval < 0) { - if (retval == -EAGAIN) - retval = -ENOMEM; - goto exit; + retval = idr_alloc(&uio_idr, idev, 0, UIO_MAX_DEVICES, GFP_KERNEL); + if (retval >= 0) { + idev->minor = retval; + retval = 0; + } else if (retval == -ENOSPC) { + dev_err(idev->dev, "too many uio devices\n"); + retval = -EINVAL; } - idev->minor = id & MAX_ID_MASK; -exit: mutex_unlock(&minor_lock); return retval; } @@ -587,35 +590,22 @@ static ssize_t uio_write(struct file *filep, const char __user *buf, static int uio_find_mem_index(struct vm_area_struct *vma) { - int mi; struct uio_device *idev = vma->vm_private_data; - for (mi = 0; mi < MAX_UIO_MAPS; mi++) { - if (idev->info->mem[mi].size == 0) + if (vma->vm_pgoff < MAX_UIO_MAPS) { + if (idev->info->mem[vma->vm_pgoff].size == 0) return -1; - if (vma->vm_pgoff == mi) - return mi; + return (int)vma->vm_pgoff; } return -1; } -static void uio_vma_open(struct vm_area_struct *vma) -{ - struct uio_device *idev = vma->vm_private_data; - idev->vma_count++; -} - -static void uio_vma_close(struct vm_area_struct *vma) -{ - struct uio_device *idev = vma->vm_private_data; - idev->vma_count--; -} - static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct uio_device *idev = vma->vm_private_data; struct page *page; unsigned long offset; + void *addr; int mi = uio_find_mem_index(vma); if (mi < 0) @@ -627,48 +617,66 @@ static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) */ offset = (vmf->pgoff - mi) << PAGE_SHIFT; + addr = (void *)(unsigned long)idev->info->mem[mi].addr + offset; if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL) - page = virt_to_page(idev->info->mem[mi].addr + offset); + page = virt_to_page(addr); else - page = vmalloc_to_page((void *)idev->info->mem[mi].addr - + offset); + page = vmalloc_to_page(addr); get_page(page); vmf->page = page; return 0; } -static const struct vm_operations_struct uio_vm_ops = { - .open = uio_vma_open, - .close = uio_vma_close, +static const struct vm_operations_struct uio_logical_vm_ops = { .fault = uio_vma_fault, }; +static int uio_mmap_logical(struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = &uio_logical_vm_ops; + return 0; +} + +static const struct vm_operations_struct uio_physical_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys, +#endif +}; + static int uio_mmap_physical(struct vm_area_struct *vma) { struct uio_device *idev = vma->vm_private_data; int mi = uio_find_mem_index(vma); + struct uio_mem *mem; if (mi < 0) return -EINVAL; + mem = idev->info->mem + mi; - vma->vm_flags |= VM_IO | VM_RESERVED; + if (mem->addr & ~PAGE_MASK) + return -ENODEV; + if (vma->vm_end - vma->vm_start > mem->size) + return -EINVAL; + vma->vm_ops = &uio_physical_vm_ops; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + /* + * We cannot use the vm_iomap_memory() helper here, + * because vma->vm_pgoff is the map index we looked + * up above in uio_find_mem_index(), rather than an + * actual page offset into the mmap. + * + * So we just do the physical mmap without a page + * offset. + */ return remap_pfn_range(vma, vma->vm_start, - idev->info->mem[mi].addr >> PAGE_SHIFT, + mem->addr >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); } -static int uio_mmap_logical(struct vm_area_struct *vma) -{ - vma->vm_flags |= VM_RESERVED; - vma->vm_ops = &uio_vm_ops; - uio_vma_open(vma); - return 0; -} - static int uio_mmap(struct file *filep, struct vm_area_struct *vma) { struct uio_listener *listener = filep->private_data; @@ -686,7 +694,7 @@ static int uio_mmap(struct file *filep, struct vm_area_struct *vma) if (mi < 0) return -EINVAL; - requested_pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; + requested_pages = vma_pages(vma); actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK) + idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT; if (requested_pages > actual_pages) @@ -746,14 +754,13 @@ static int uio_major_init(void) uio_major = MAJOR(uio_dev); uio_cdev = cdev; - result = 0; -out: - return result; + return 0; out_put: kobject_put(&cdev->kobj); out_unregister: unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES); - goto out; +out: + return result; } static void uio_major_cleanup(void) @@ -810,10 +817,9 @@ int __uio_register_device(struct module *owner, info->uio_dev = NULL; - idev = kzalloc(sizeof(*idev), GFP_KERNEL); + idev = devm_kzalloc(parent, sizeof(*idev), GFP_KERNEL); if (!idev) { - ret = -ENOMEM; - goto err_kzalloc; + return -ENOMEM; } idev->owner = owner; @@ -823,7 +829,7 @@ int __uio_register_device(struct module *owner, ret = uio_get_minor(idev); if (ret) - goto err_get_minor; + return ret; idev->dev = device_create(&uio_class, parent, MKDEV(uio_major, idev->minor), idev, @@ -841,7 +847,7 @@ int __uio_register_device(struct module *owner, info->uio_dev = idev; if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) { - ret = request_irq(info->irq, uio_interrupt, + ret = devm_request_irq(idev->dev, info->irq, uio_interrupt, info->irq_flags, info->name, idev); if (ret) goto err_request_irq; @@ -855,9 +861,6 @@ err_uio_dev_add_attributes: device_destroy(&uio_class, MKDEV(uio_major, idev->minor)); err_device_create: uio_free_minor(idev); -err_get_minor: - kfree(idev); -err_kzalloc: return ret; } EXPORT_SYMBOL_GPL(__uio_register_device); @@ -878,13 +881,9 @@ void uio_unregister_device(struct uio_info *info) uio_free_minor(idev); - if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) - free_irq(info->irq, idev); - uio_dev_del_attributes(idev); device_destroy(&uio_class, MKDEV(uio_major, idev->minor)); - kfree(idev); return; } diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c index 72b22d44e8b..1549fab633c 100644 --- a/drivers/uio/uio_aec.c +++ b/drivers/uio/uio_aec.c @@ -78,7 +78,7 @@ static void print_board_data(struct pci_dev *pdev, struct uio_info *i) ioread8(i->priv + 0x07)); } -static int __devinit probe(struct pci_dev *pdev, const struct pci_device_id *id) +static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct uio_info *info; int ret; @@ -147,7 +147,6 @@ static void remove(struct pci_dev *pdev) uio_unregister_device(info); pci_release_regions(pdev); pci_disable_device(pdev); - pci_set_drvdata(pdev, NULL); iounmap(info->priv); kfree(info); @@ -160,17 +159,5 @@ static struct pci_driver pci_driver = { .remove = remove, }; -static int __init aectc_init(void) -{ - return pci_register_driver(&pci_driver); -} - -static void __exit aectc_exit(void) -{ - pci_unregister_driver(&pci_driver); -} - +module_pci_driver(pci_driver); MODULE_LICENSE("GPL"); - -module_init(aectc_init); -module_exit(aectc_exit); diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c index a84a451159e..30f533ce375 100644 --- a/drivers/uio/uio_cif.c +++ b/drivers/uio/uio_cif.c @@ -40,7 +40,7 @@ static irqreturn_t hilscher_handler(int irq, struct uio_info *dev_info) return IRQ_HANDLED; } -static int __devinit hilscher_pci_probe(struct pci_dev *dev, +static int hilscher_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; @@ -106,13 +106,12 @@ static void hilscher_pci_remove(struct pci_dev *dev) uio_unregister_device(info); pci_release_regions(dev); pci_disable_device(dev); - pci_set_drvdata(dev, NULL); iounmap(info->mem[0].internal_addr); kfree (info); } -static struct pci_device_id hilscher_pci_ids[] __devinitdata = { +static struct pci_device_id hilscher_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, @@ -135,19 +134,7 @@ static struct pci_driver hilscher_pci_driver = { .remove = hilscher_pci_remove, }; -static int __init hilscher_init_module(void) -{ - return pci_register_driver(&hilscher_pci_driver); -} - -static void __exit hilscher_exit_module(void) -{ - pci_unregister_driver(&hilscher_pci_driver); -} - -module_init(hilscher_init_module); -module_exit(hilscher_exit_module); - +module_pci_driver(hilscher_pci_driver); MODULE_DEVICE_TABLE(pci, hilscher_pci_ids); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Hans J. Koch, Benedikt Spranger"); diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c new file mode 100644 index 00000000000..8d0bba46956 --- /dev/null +++ b/drivers/uio/uio_dmem_genirq.c @@ -0,0 +1,357 @@ +/* + * drivers/uio/uio_dmem_genirq.c + * + * Userspace I/O platform driver with generic IRQ handling code. + * + * Copyright (C) 2012 Damian Hobson-Garcia + * + * Based on uio_pdrv_genirq.c by Magnus Damm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_data/uio_dmem_genirq.h> +#include <linux/stringify.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> + +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> + +#define DRIVER_NAME "uio_dmem_genirq" +#define DMEM_MAP_ERROR (~0) + +struct uio_dmem_genirq_platdata { + struct uio_info *uioinfo; + spinlock_t lock; + unsigned long flags; + struct platform_device *pdev; + unsigned int dmem_region_start; + unsigned int num_dmem_regions; + void *dmem_region_vaddr[MAX_UIO_MAPS]; + struct mutex alloc_lock; + unsigned int refcnt; +}; + +static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + int ret = 0; + int dmem_region = priv->dmem_region_start; + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + void *addr; + if (!uiomem->size) + break; + + addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size, + (dma_addr_t *)&uiomem->addr, GFP_KERNEL); + if (!addr) { + uiomem->addr = DMEM_MAP_ERROR; + } + priv->dmem_region_vaddr[dmem_region++] = addr; + ++uiomem; + } + priv->refcnt++; + + mutex_unlock(&priv->alloc_lock); + /* Wait until the Runtime PM code has woken up the device */ + pm_runtime_get_sync(&priv->pdev->dev); + return ret; +} + +static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + int dmem_region = priv->dmem_region_start; + + /* Tell the Runtime PM code that the device has become idle */ + pm_runtime_put_sync(&priv->pdev->dev); + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + + priv->refcnt--; + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + if (!uiomem->size) + break; + if (priv->dmem_region_vaddr[dmem_region]) { + dma_free_coherent(&priv->pdev->dev, uiomem->size, + priv->dmem_region_vaddr[dmem_region], + uiomem->addr); + } + uiomem->addr = DMEM_MAP_ERROR; + ++dmem_region; + ++uiomem; + } + + mutex_unlock(&priv->alloc_lock); + return 0; +} + +static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + + /* Just disable the interrupt in the interrupt controller, and + * remember the state so we can allow user space to enable it later. + */ + + if (!test_and_set_bit(0, &priv->flags)) + disable_irq_nosync(irq); + + return IRQ_HANDLED; +} + +static int uio_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + unsigned long flags; + + /* Allow user space to enable and disable the interrupt + * in the interrupt controller, but keep track of the + * state to prevent per-irq depth damage. + * + * Serialize this operation to support multiple tasks. + */ + + spin_lock_irqsave(&priv->lock, flags); + if (irq_on) { + if (test_and_clear_bit(0, &priv->flags)) + enable_irq(dev_info->irq); + } else { + if (!test_and_set_bit(0, &priv->flags)) + disable_irq(dev_info->irq); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int uio_dmem_genirq_probe(struct platform_device *pdev) +{ + struct uio_dmem_genirq_pdata *pdata = dev_get_platdata(&pdev->dev); + struct uio_info *uioinfo = &pdata->uioinfo; + struct uio_dmem_genirq_platdata *priv; + struct uio_mem *uiomem; + int ret = -EINVAL; + int i; + + if (pdev->dev.of_node) { + int irq; + + /* alloc uioinfo for one device */ + uioinfo = kzalloc(sizeof(*uioinfo), GFP_KERNEL); + if (!uioinfo) { + ret = -ENOMEM; + dev_err(&pdev->dev, "unable to kmalloc\n"); + goto bad2; + } + uioinfo->name = pdev->dev.of_node->name; + uioinfo->version = "devicetree"; + + /* Multiple IRQs are not supported */ + irq = platform_get_irq(pdev, 0); + if (irq == -ENXIO) + uioinfo->irq = UIO_IRQ_NONE; + else + uioinfo->irq = irq; + } + + if (!uioinfo || !uioinfo->name || !uioinfo->version) { + dev_err(&pdev->dev, "missing platform_data\n"); + goto bad0; + } + + if (uioinfo->handler || uioinfo->irqcontrol || + uioinfo->irq_flags & IRQF_SHARED) { + dev_err(&pdev->dev, "interrupt configuration error\n"); + goto bad0; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&pdev->dev, "unable to kmalloc\n"); + goto bad0; + } + + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + priv->uioinfo = uioinfo; + spin_lock_init(&priv->lock); + priv->flags = 0; /* interrupt is enabled to begin with */ + priv->pdev = pdev; + mutex_init(&priv->alloc_lock); + + if (!uioinfo->irq) { + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + goto bad1; + } + uioinfo->irq = ret; + } + uiomem = &uioinfo->mem[0]; + + for (i = 0; i < pdev->num_resources; ++i) { + struct resource *r = &pdev->resource[i]; + + if (r->flags != IORESOURCE_MEM) + continue; + + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " I/O memory resources.\n"); + break; + } + + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start; + uiomem->size = resource_size(r); + ++uiomem; + } + + priv->dmem_region_start = i; + priv->num_dmem_regions = pdata->num_dynamic_regions; + + for (i = 0; i < pdata->num_dynamic_regions; ++i) { + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " dynamic and fixed memory regions.\n"); + break; + } + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = DMEM_MAP_ERROR; + uiomem->size = pdata->dynamic_region_sizes[i]; + ++uiomem; + } + + while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + /* This driver requires no hardware specific kernel code to handle + * interrupts. Instead, the interrupt handler simply disables the + * interrupt in the interrupt controller. User space is responsible + * for performing hardware specific acknowledge and re-enabling of + * the interrupt in the interrupt controller. + * + * Interrupt sharing is not supported. + */ + + uioinfo->handler = uio_dmem_genirq_handler; + uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol; + uioinfo->open = uio_dmem_genirq_open; + uioinfo->release = uio_dmem_genirq_release; + uioinfo->priv = priv; + + /* Enable Runtime PM for this device: + * The device starts in suspended state to allow the hardware to be + * turned off by default. The Runtime PM bus code should power on the + * hardware and enable clocks at open(). + */ + pm_runtime_enable(&pdev->dev); + + ret = uio_register_device(&pdev->dev, priv->uioinfo); + if (ret) { + dev_err(&pdev->dev, "unable to register uio device\n"); + pm_runtime_disable(&pdev->dev); + goto bad1; + } + + platform_set_drvdata(pdev, priv); + return 0; + bad1: + kfree(priv); + bad0: + /* kfree uioinfo for OF */ + if (pdev->dev.of_node) + kfree(uioinfo); + bad2: + return ret; +} + +static int uio_dmem_genirq_remove(struct platform_device *pdev) +{ + struct uio_dmem_genirq_platdata *priv = platform_get_drvdata(pdev); + + uio_unregister_device(priv->uioinfo); + pm_runtime_disable(&pdev->dev); + + priv->uioinfo->handler = NULL; + priv->uioinfo->irqcontrol = NULL; + + /* kfree uioinfo for OF */ + if (pdev->dev.of_node) + kfree(priv->uioinfo); + + kfree(priv); + return 0; +} + +static int uio_dmem_genirq_runtime_nop(struct device *dev) +{ + /* Runtime PM callback shared between ->runtime_suspend() + * and ->runtime_resume(). Simply returns success. + * + * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() + * are used at open() and release() time. This allows the + * Runtime PM code to turn off power to the device while the + * device is unused, ie before open() and after release(). + * + * This Runtime PM callback does not need to save or restore + * any registers since user space is responsbile for hardware + * register reinitialization after open(). + */ + return 0; +} + +static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = { + .runtime_suspend = uio_dmem_genirq_runtime_nop, + .runtime_resume = uio_dmem_genirq_runtime_nop, +}; + +#ifdef CONFIG_OF +static const struct of_device_id uio_of_genirq_match[] = { + { /* empty for now */ }, +}; +MODULE_DEVICE_TABLE(of, uio_of_genirq_match); +#endif + +static struct platform_driver uio_dmem_genirq = { + .probe = uio_dmem_genirq_probe, + .remove = uio_dmem_genirq_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &uio_dmem_genirq_dev_pm_ops, + .of_match_table = of_match_ptr(uio_of_genirq_match), + }, +}; + +module_platform_driver(uio_dmem_genirq); + +MODULE_AUTHOR("Damian Hobson-Garcia"); +MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory."); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c new file mode 100644 index 00000000000..d1f95a1567b --- /dev/null +++ b/drivers/uio/uio_mf624.c @@ -0,0 +1,246 @@ +/* + * UIO driver fo Humusoft MF624 DAQ card. + * Copyright (C) 2011 Rostislav Lisovy <lisovy@gmail.com>, + * Czech Technical University in Prague + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/uio_driver.h> + +#define PCI_VENDOR_ID_HUMUSOFT 0x186c +#define PCI_DEVICE_ID_MF624 0x0624 +#define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c +#define PCI_SUBDEVICE_DEVICE 0x0624 + +/* BAR0 Interrupt control/status register */ +#define INTCSR 0x4C +#define INTCSR_ADINT_ENABLE (1 << 0) +#define INTCSR_CTR4INT_ENABLE (1 << 3) +#define INTCSR_PCIINT_ENABLE (1 << 6) +#define INTCSR_ADINT_STATUS (1 << 2) +#define INTCSR_CTR4INT_STATUS (1 << 5) + +enum mf624_interrupt_source {ADC, CTR4, ALL}; + +static void mf624_disable_interrupt(enum mf624_interrupt_source source, + struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + switch (source) { + case ADC: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + + case CTR4: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + + case ALL: + default: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE + | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + } +} + +static void mf624_enable_interrupt(enum mf624_interrupt_source source, + struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + switch (source) { + case ADC: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + + case CTR4: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + + case ALL: + default: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE + | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + } +} + +static irqreturn_t mf624_irq_handler(int irq, struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + if ((ioread32(INTCSR_reg) & INTCSR_ADINT_ENABLE) + && (ioread32(INTCSR_reg) & INTCSR_ADINT_STATUS)) { + mf624_disable_interrupt(ADC, info); + return IRQ_HANDLED; + } + + if ((ioread32(INTCSR_reg) & INTCSR_CTR4INT_ENABLE) + && (ioread32(INTCSR_reg) & INTCSR_CTR4INT_STATUS)) { + mf624_disable_interrupt(CTR4, info); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) +{ + if (irq_on == 0) + mf624_disable_interrupt(ALL, info); + else if (irq_on == 1) + mf624_enable_interrupt(ALL, info); + + return 0; +} + +static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct uio_info *info; + + 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, "mf624")) + goto out_disable; + + info->name = "mf624"; + info->version = "0.0.1"; + + /* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */ + + /* BAR0 */ + info->mem[0].name = "PCI chipset, interrupts, status " + "bits, special functions"; + info->mem[0].addr = pci_resource_start(dev, 0); + if (!info->mem[0].addr) + goto out_release; + info->mem[0].size = pci_resource_len(dev, 0); + info->mem[0].memtype = UIO_MEM_PHYS; + info->mem[0].internal_addr = pci_ioremap_bar(dev, 0); + if (!info->mem[0].internal_addr) + goto out_release; + + /* BAR2 */ + info->mem[1].name = "ADC, DAC, DIO"; + info->mem[1].addr = pci_resource_start(dev, 2); + if (!info->mem[1].addr) + goto out_unmap0; + info->mem[1].size = pci_resource_len(dev, 2); + info->mem[1].memtype = UIO_MEM_PHYS; + info->mem[1].internal_addr = pci_ioremap_bar(dev, 2); + if (!info->mem[1].internal_addr) + goto out_unmap0; + + /* BAR4 */ + info->mem[2].name = "Counter/timer chip"; + info->mem[2].addr = pci_resource_start(dev, 4); + if (!info->mem[2].addr) + goto out_unmap1; + info->mem[2].size = pci_resource_len(dev, 4); + info->mem[2].memtype = UIO_MEM_PHYS; + info->mem[2].internal_addr = pci_ioremap_bar(dev, 4); + if (!info->mem[2].internal_addr) + goto out_unmap1; + + info->irq = dev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = mf624_irq_handler; + + info->irqcontrol = mf624_irqcontrol; + + if (uio_register_device(&dev->dev, info)) + goto out_unmap2; + + pci_set_drvdata(dev, info); + + return 0; + +out_unmap2: + iounmap(info->mem[2].internal_addr); +out_unmap1: + iounmap(info->mem[1].internal_addr); +out_unmap0: + 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 mf624_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + + mf624_disable_interrupt(ALL, info); + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + + iounmap(info->mem[0].internal_addr); + iounmap(info->mem[1].internal_addr); + iounmap(info->mem[2].internal_addr); + + kfree(info); +} + +static const struct pci_device_id mf624_pci_id[] = { + { PCI_DEVICE(PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF624) }, + { 0, } +}; + +static struct pci_driver mf624_pci_driver = { + .name = "mf624", + .id_table = mf624_pci_id, + .probe = mf624_pci_probe, + .remove = mf624_pci_remove, +}; +MODULE_DEVICE_TABLE(pci, mf624_pci_id); + +module_pci_driver(mf624_pci_driver); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>"); diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c index 5ffdb483b01..4c345db8b01 100644 --- a/drivers/uio/uio_netx.c +++ b/drivers/uio/uio_netx.c @@ -18,6 +18,9 @@ #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 @@ -45,7 +48,7 @@ static irqreturn_t netx_handler(int irq, struct uio_info *dev_info) return IRQ_HANDLED; } -static int __devinit netx_pci_probe(struct pci_dev *dev, +static int netx_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; @@ -66,6 +69,10 @@ static int __devinit netx_pci_probe(struct pci_dev *dev, 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"; @@ -120,7 +127,6 @@ static void netx_pci_remove(struct pci_dev *dev) uio_unregister_device(info); pci_release_regions(dev); pci_disable_device(dev); - pci_set_drvdata(dev, NULL); iounmap(info->mem[0].internal_addr); kfree(info); @@ -134,6 +140,18 @@ static struct pci_device_id netx_pci_ids[] = { .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, @@ -155,19 +173,7 @@ static struct pci_driver netx_pci_driver = { .remove = netx_pci_remove, }; -static int __init netx_init_module(void) -{ - return pci_register_driver(&netx_pci_driver); -} - -static void __exit netx_exit_module(void) -{ - pci_unregister_driver(&netx_pci_driver); -} - -module_init(netx_init_module); -module_exit(netx_exit_module); - +module_pci_driver(netx_pci_driver); MODULE_DEVICE_TABLE(pci, netx_pci_ids); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Hans J. Koch, Manuel Traut"); diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c index fc22e1e6f21..077ae12269c 100644 --- a/drivers/uio/uio_pci_generic.c +++ b/drivers/uio/uio_pci_generic.c @@ -24,7 +24,6 @@ #include <linux/pci.h> #include <linux/slab.h> #include <linux/uio_driver.h> -#include <linux/spinlock.h> #define DRIVER_VERSION "0.01.0" #define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>" @@ -33,7 +32,6 @@ struct uio_pci_generic_dev { struct uio_info info; struct pci_dev *pdev; - spinlock_t lock; /* guards command register accesses */ }; static inline struct uio_pci_generic_dev * @@ -47,82 +45,15 @@ to_uio_pci_generic_dev(struct uio_info *info) static irqreturn_t irqhandler(int irq, struct uio_info *info) { struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info); - struct pci_dev *pdev = gdev->pdev; - irqreturn_t ret = IRQ_NONE; - u32 cmd_status_dword; - u16 origcmd, newcmd, status; - - /* We do a single dword read to retrieve both command and status. - * Document assumptions that make this possible. */ - BUILD_BUG_ON(PCI_COMMAND % 4); - BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS); - - spin_lock_irq(&gdev->lock); - pci_block_user_cfg_access(pdev); - - /* Read both command and status registers in a single 32-bit operation. - * Note: we could cache the value for command and move the status read - * out of the lock if there was a way to get notified of user changes - * to command register through sysfs. Should be good for shared irqs. */ - pci_read_config_dword(pdev, PCI_COMMAND, &cmd_status_dword); - origcmd = cmd_status_dword; - status = cmd_status_dword >> 16; - - /* Check interrupt status register to see whether our device - * triggered the interrupt. */ - if (!(status & PCI_STATUS_INTERRUPT)) - goto done; - - /* We triggered the interrupt, disable it. */ - newcmd = origcmd | PCI_COMMAND_INTX_DISABLE; - if (newcmd != origcmd) - pci_write_config_word(pdev, PCI_COMMAND, newcmd); - /* UIO core will signal the user process. */ - ret = IRQ_HANDLED; -done: - - pci_unblock_user_cfg_access(pdev); - spin_unlock_irq(&gdev->lock); - return ret; -} + if (!pci_check_and_mask_intx(gdev->pdev)) + return IRQ_NONE; -/* Verify that the device supports Interrupt Disable bit in command register, - * per PCI 2.3, by flipping this bit and reading it back: this bit was readonly - * in PCI 2.2. */ -static int __devinit verify_pci_2_3(struct pci_dev *pdev) -{ - u16 orig, new; - int err = 0; - - pci_block_user_cfg_access(pdev); - pci_read_config_word(pdev, PCI_COMMAND, &orig); - pci_write_config_word(pdev, PCI_COMMAND, - orig ^ PCI_COMMAND_INTX_DISABLE); - pci_read_config_word(pdev, PCI_COMMAND, &new); - /* There's no way to protect against - * hardware bugs or detect them reliably, but as long as we know - * what the value should be, let's go ahead and check it. */ - if ((new ^ orig) & ~PCI_COMMAND_INTX_DISABLE) { - err = -EBUSY; - dev_err(&pdev->dev, "Command changed from 0x%x to 0x%x: " - "driver or HW bug?\n", orig, new); - goto err; - } - if (!((new ^ orig) & PCI_COMMAND_INTX_DISABLE)) { - dev_warn(&pdev->dev, "Device does not support " - "disabling interrupts: unable to bind.\n"); - err = -ENODEV; - goto err; - } - /* Now restore the original value. */ - pci_write_config_word(pdev, PCI_COMMAND, orig); -err: - pci_unblock_user_cfg_access(pdev); - return err; + /* UIO core will signal the user process. */ + return IRQ_HANDLED; } -static int __devinit probe(struct pci_dev *pdev, +static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct uio_pci_generic_dev *gdev; @@ -142,9 +73,10 @@ static int __devinit probe(struct pci_dev *pdev, return -ENODEV; } - err = verify_pci_2_3(pdev); - if (err) + if (!pci_intx_mask_supported(pdev)) { + err = -ENODEV; goto err_verify; + } gdev = kzalloc(sizeof(struct uio_pci_generic_dev), GFP_KERNEL); if (!gdev) { @@ -158,7 +90,6 @@ static int __devinit probe(struct pci_dev *pdev, gdev->info.irq_flags = IRQF_SHARED; gdev->info.handler = irqhandler; gdev->pdev = pdev; - spin_lock_init(&gdev->lock); if (uio_register_device(&pdev->dev, &gdev->info)) goto err_register; @@ -182,27 +113,14 @@ static void remove(struct pci_dev *pdev) kfree(gdev); } -static struct pci_driver driver = { +static struct pci_driver uio_pci_driver = { .name = "uio_pci_generic", .id_table = NULL, /* only dynamic id's */ .probe = probe, .remove = remove, }; -static int __init init(void) -{ - pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); - return pci_register_driver(&driver); -} - -static void __exit cleanup(void) -{ - pci_unregister_driver(&driver); -} - -module_init(init); -module_exit(cleanup); - +module_pci_driver(uio_pci_driver); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR(DRIVER_AUTHOR); diff --git a/drivers/uio/uio_pdrv.c b/drivers/uio/uio_pdrv.c deleted file mode 100644 index 7d3e469b990..00000000000 --- a/drivers/uio/uio_pdrv.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * drivers/uio/uio_pdrv.c - * - * Copyright (C) 2008 by Digi International Inc. - * All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - */ -#include <linux/platform_device.h> -#include <linux/uio_driver.h> -#include <linux/stringify.h> -#include <linux/slab.h> - -#define DRIVER_NAME "uio_pdrv" - -struct uio_platdata { - struct uio_info *uioinfo; -}; - -static int uio_pdrv_probe(struct platform_device *pdev) -{ - struct uio_info *uioinfo = pdev->dev.platform_data; - struct uio_platdata *pdata; - struct uio_mem *uiomem; - int ret = -ENODEV; - int i; - - if (!uioinfo || !uioinfo->name || !uioinfo->version) { - dev_dbg(&pdev->dev, "%s: err_uioinfo\n", __func__); - goto err_uioinfo; - } - - pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); - if (!pdata) { - ret = -ENOMEM; - dev_dbg(&pdev->dev, "%s: err_alloc_pdata\n", __func__); - goto err_alloc_pdata; - } - - pdata->uioinfo = uioinfo; - - uiomem = &uioinfo->mem[0]; - - for (i = 0; i < pdev->num_resources; ++i) { - struct resource *r = &pdev->resource[i]; - - if (r->flags != IORESOURCE_MEM) - continue; - - if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { - dev_warn(&pdev->dev, "device has more than " - __stringify(MAX_UIO_MAPS) - " I/O memory resources.\n"); - break; - } - - uiomem->memtype = UIO_MEM_PHYS; - uiomem->addr = r->start; - uiomem->size = r->end - r->start + 1; - ++uiomem; - } - - while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { - uiomem->size = 0; - ++uiomem; - } - - pdata->uioinfo->priv = pdata; - - ret = uio_register_device(&pdev->dev, pdata->uioinfo); - - if (ret) { - kfree(pdata); -err_alloc_pdata: -err_uioinfo: - return ret; - } - - platform_set_drvdata(pdev, pdata); - - return 0; -} - -static int uio_pdrv_remove(struct platform_device *pdev) -{ - struct uio_platdata *pdata = platform_get_drvdata(pdev); - - uio_unregister_device(pdata->uioinfo); - - kfree(pdata); - - return 0; -} - -static struct platform_driver uio_pdrv = { - .probe = uio_pdrv_probe, - .remove = uio_pdrv_remove, - .driver = { - .name = DRIVER_NAME, - .owner = THIS_MODULE, - }, -}; - -static int __init uio_pdrv_init(void) -{ - return platform_driver_register(&uio_pdrv); -} - -static void __exit uio_pdrv_exit(void) -{ - platform_driver_unregister(&uio_pdrv); -} -module_init(uio_pdrv_init); -module_exit(uio_pdrv_exit); - -MODULE_AUTHOR("Uwe Kleine-Koenig"); -MODULE_DESCRIPTION("Userspace I/O platform driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 7174d518b8a..76669313e9a 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -18,11 +18,16 @@ #include <linux/uio_driver.h> #include <linux/spinlock.h> #include <linux/bitops.h> +#include <linux/module.h> #include <linux/interrupt.h> #include <linux/stringify.h> #include <linux/pm_runtime.h> #include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> + #define DRIVER_NAME "uio_pdrv_genirq" struct uio_pdrv_genirq_platdata { @@ -32,6 +37,11 @@ struct uio_pdrv_genirq_platdata { struct platform_device *pdev; }; +/* Bits in uio_pdrv_genirq_platdata.flags */ +enum { + UIO_IRQ_DISABLED = 0, +}; + static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode) { struct uio_pdrv_genirq_platdata *priv = info->priv; @@ -58,8 +68,10 @@ static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info) * remember the state so we can allow user space to enable it later. */ - if (!test_and_set_bit(0, &priv->flags)) + spin_lock(&priv->lock); + if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) disable_irq_nosync(irq); + spin_unlock(&priv->lock); return IRQ_HANDLED; } @@ -73,16 +85,17 @@ static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) * in the interrupt controller, but keep track of the * state to prevent per-irq depth damage. * - * Serialize this operation to support multiple tasks. + * Serialize this operation to support multiple tasks and concurrency + * with irq handler on SMP systems. */ spin_lock_irqsave(&priv->lock, flags); if (irq_on) { - if (test_and_clear_bit(0, &priv->flags)) + if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags)) enable_irq(dev_info->irq); } else { - if (!test_and_set_bit(0, &priv->flags)) - disable_irq(dev_info->irq); + if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) + disable_irq_nosync(dev_info->irq); } spin_unlock_irqrestore(&priv->lock, flags); @@ -91,28 +104,40 @@ static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) static int uio_pdrv_genirq_probe(struct platform_device *pdev) { - struct uio_info *uioinfo = pdev->dev.platform_data; + struct uio_info *uioinfo = dev_get_platdata(&pdev->dev); struct uio_pdrv_genirq_platdata *priv; struct uio_mem *uiomem; int ret = -EINVAL; int i; + if (pdev->dev.of_node) { + /* alloc uioinfo for one device */ + uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), + GFP_KERNEL); + if (!uioinfo) { + dev_err(&pdev->dev, "unable to kmalloc\n"); + return -ENOMEM; + } + uioinfo->name = pdev->dev.of_node->name; + uioinfo->version = "devicetree"; + /* Multiple IRQs are not supported */ + } + if (!uioinfo || !uioinfo->name || !uioinfo->version) { dev_err(&pdev->dev, "missing platform_data\n"); - goto bad0; + return ret; } if (uioinfo->handler || uioinfo->irqcontrol || uioinfo->irq_flags & IRQF_SHARED) { dev_err(&pdev->dev, "interrupt configuration error\n"); - goto bad0; + return ret; } - priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) { - ret = -ENOMEM; dev_err(&pdev->dev, "unable to kmalloc\n"); - goto bad0; + return -ENOMEM; } priv->uioinfo = uioinfo; @@ -120,6 +145,17 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) priv->flags = 0; /* interrupt is enabled to begin with */ priv->pdev = pdev; + if (!uioinfo->irq) { + ret = platform_get_irq(pdev, 0); + uioinfo->irq = ret; + if (ret == -ENXIO && pdev->dev.of_node) + uioinfo->irq = UIO_IRQ_NONE; + else if (ret < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return ret; + } + } + uiomem = &uioinfo->mem[0]; for (i = 0; i < pdev->num_resources; ++i) { @@ -137,7 +173,8 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; - uiomem->size = r->end - r->start + 1; + uiomem->size = resource_size(r); + uiomem->name = r->name; ++uiomem; } @@ -171,16 +208,12 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) ret = uio_register_device(&pdev->dev, priv->uioinfo); if (ret) { dev_err(&pdev->dev, "unable to register uio device\n"); - goto bad1; + pm_runtime_disable(&pdev->dev); + return ret; } platform_set_drvdata(pdev, priv); return 0; - bad1: - kfree(priv); - pm_runtime_disable(&pdev->dev); - bad0: - return ret; } static int uio_pdrv_genirq_remove(struct platform_device *pdev) @@ -189,7 +222,10 @@ static int uio_pdrv_genirq_remove(struct platform_device *pdev) uio_unregister_device(priv->uioinfo); pm_runtime_disable(&pdev->dev); - kfree(priv); + + priv->uioinfo->handler = NULL; + priv->uioinfo->irqcontrol = NULL; + return 0; } @@ -215,6 +251,16 @@ static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { .runtime_resume = uio_pdrv_genirq_runtime_nop, }; +#ifdef CONFIG_OF +static struct of_device_id uio_of_genirq_match[] = { + { /* This is filled with module_parm */ }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, uio_of_genirq_match); +module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0); +MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio"); +#endif + static struct platform_driver uio_pdrv_genirq = { .probe = uio_pdrv_genirq_probe, .remove = uio_pdrv_genirq_remove, @@ -222,21 +268,11 @@ static struct platform_driver uio_pdrv_genirq = { .name = DRIVER_NAME, .owner = THIS_MODULE, .pm = &uio_pdrv_genirq_dev_pm_ops, + .of_match_table = of_match_ptr(uio_of_genirq_match), }, }; -static int __init uio_pdrv_genirq_init(void) -{ - return platform_driver_register(&uio_pdrv_genirq); -} - -static void __exit uio_pdrv_genirq_exit(void) -{ - platform_driver_unregister(&uio_pdrv_genirq); -} - -module_init(uio_pdrv_genirq_init); -module_exit(uio_pdrv_genirq_exit); +module_platform_driver(uio_pdrv_genirq); MODULE_AUTHOR("Magnus Damm"); MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling"); diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c new file mode 100644 index 00000000000..96c4a19b191 --- /dev/null +++ b/drivers/uio/uio_pruss.c @@ -0,0 +1,242 @@ +/* + * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss) + * + * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM, + * and DDR RAM to user space for applications interacting with PRUSS firmware + * + * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/platform_data/uio_pruss.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/genalloc.h> + +#define DRV_NAME "pruss_uio" +#define DRV_VERSION "1.0" + +static int sram_pool_sz = SZ_16K; +module_param(sram_pool_sz, int, 0); +MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate "); + +static int extram_pool_sz = SZ_256K; +module_param(extram_pool_sz, int, 0); +MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate"); + +/* + * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt + * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS + * firmware and user space application, async notification from PRU firmware + * to user space application + * 3 PRU_EVTOUT0 + * 4 PRU_EVTOUT1 + * 5 PRU_EVTOUT2 + * 6 PRU_EVTOUT3 + * 7 PRU_EVTOUT4 + * 8 PRU_EVTOUT5 + * 9 PRU_EVTOUT6 + * 10 PRU_EVTOUT7 +*/ +#define MAX_PRUSS_EVT 8 + +#define PINTC_HIDISR 0x0038 +#define PINTC_HIPIR 0x0900 +#define HIPIR_NOPEND 0x80000000 +#define PINTC_HIER 0x1500 + +struct uio_pruss_dev { + struct uio_info *info; + struct clk *pruss_clk; + dma_addr_t sram_paddr; + dma_addr_t ddr_paddr; + void __iomem *prussio_vaddr; + unsigned long sram_vaddr; + void *ddr_vaddr; + unsigned int hostirq_start; + unsigned int pintc_base; + struct gen_pool *sram_pool; +}; + +static irqreturn_t pruss_handler(int irq, struct uio_info *info) +{ + struct uio_pruss_dev *gdev = info->priv; + int intr_bit = (irq - gdev->hostirq_start + 2); + int val, intr_mask = (1 << intr_bit); + void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base; + void __iomem *intren_reg = base + PINTC_HIER; + void __iomem *intrdis_reg = base + PINTC_HIDISR; + void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2); + + val = ioread32(intren_reg); + /* Is interrupt enabled and active ? */ + if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND)) + return IRQ_NONE; + /* Disable interrupt */ + iowrite32(intr_bit, intrdis_reg); + return IRQ_HANDLED; +} + +static void pruss_cleanup(struct platform_device *dev, + struct uio_pruss_dev *gdev) +{ + int cnt; + struct uio_info *p = gdev->info; + + for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) { + uio_unregister_device(p); + kfree(p->name); + } + iounmap(gdev->prussio_vaddr); + if (gdev->ddr_vaddr) { + dma_free_coherent(&dev->dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); + } + if (gdev->sram_vaddr) + gen_pool_free(gdev->sram_pool, + gdev->sram_vaddr, + sram_pool_sz); + kfree(gdev->info); + clk_put(gdev->pruss_clk); + kfree(gdev); +} + +static int pruss_probe(struct platform_device *dev) +{ + struct uio_info *p; + struct uio_pruss_dev *gdev; + struct resource *regs_prussio; + int ret = -ENODEV, cnt = 0, len; + struct uio_pruss_pdata *pdata = dev_get_platdata(&dev->dev); + + gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + gdev->info = kzalloc(sizeof(*p) * MAX_PRUSS_EVT, GFP_KERNEL); + if (!gdev->info) { + kfree(gdev); + return -ENOMEM; + } + /* Power on PRU in case its not done as part of boot-loader */ + gdev->pruss_clk = clk_get(&dev->dev, "pruss"); + if (IS_ERR(gdev->pruss_clk)) { + dev_err(&dev->dev, "Failed to get clock\n"); + ret = PTR_ERR(gdev->pruss_clk); + kfree(gdev->info); + kfree(gdev); + return ret; + } else { + clk_enable(gdev->pruss_clk); + } + + regs_prussio = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!regs_prussio) { + dev_err(&dev->dev, "No PRUSS I/O resource specified\n"); + goto out_free; + } + + if (!regs_prussio->start) { + dev_err(&dev->dev, "Invalid memory resource\n"); + goto out_free; + } + + if (pdata->sram_pool) { + gdev->sram_pool = pdata->sram_pool; + gdev->sram_vaddr = + (unsigned long)gen_pool_dma_alloc(gdev->sram_pool, + sram_pool_sz, &gdev->sram_paddr); + if (!gdev->sram_vaddr) { + dev_err(&dev->dev, "Could not allocate SRAM pool\n"); + goto out_free; + } + } + + gdev->ddr_vaddr = dma_alloc_coherent(&dev->dev, extram_pool_sz, + &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); + if (!gdev->ddr_vaddr) { + dev_err(&dev->dev, "Could not allocate external memory\n"); + goto out_free; + } + + len = resource_size(regs_prussio); + gdev->prussio_vaddr = ioremap(regs_prussio->start, len); + if (!gdev->prussio_vaddr) { + dev_err(&dev->dev, "Can't remap PRUSS I/O address range\n"); + goto out_free; + } + + gdev->pintc_base = pdata->pintc_base; + gdev->hostirq_start = platform_get_irq(dev, 0); + + for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { + p->mem[0].addr = regs_prussio->start; + p->mem[0].size = resource_size(regs_prussio); + p->mem[0].memtype = UIO_MEM_PHYS; + + p->mem[1].addr = gdev->sram_paddr; + p->mem[1].size = sram_pool_sz; + p->mem[1].memtype = UIO_MEM_PHYS; + + p->mem[2].addr = gdev->ddr_paddr; + p->mem[2].size = extram_pool_sz; + p->mem[2].memtype = UIO_MEM_PHYS; + + p->name = kasprintf(GFP_KERNEL, "pruss_evt%d", cnt); + p->version = DRV_VERSION; + + /* Register PRUSS IRQ lines */ + p->irq = gdev->hostirq_start + cnt; + p->handler = pruss_handler; + p->priv = gdev; + + ret = uio_register_device(&dev->dev, p); + if (ret < 0) + goto out_free; + } + + platform_set_drvdata(dev, gdev); + return 0; + +out_free: + pruss_cleanup(dev, gdev); + return ret; +} + +static int pruss_remove(struct platform_device *dev) +{ + struct uio_pruss_dev *gdev = platform_get_drvdata(dev); + + pruss_cleanup(dev, gdev); + return 0; +} + +static struct platform_driver pruss_driver = { + .probe = pruss_probe, + .remove = pruss_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(pruss_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>"); +MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>"); diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index a187fa14c5c..9cfdfcafa26 100644 --- a/drivers/uio/uio_sercos3.c +++ b/drivers/uio/uio_sercos3.c @@ -116,7 +116,7 @@ static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info, return 0; } -static int __devinit sercos3_pci_probe(struct pci_dev *dev, +static int sercos3_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { struct uio_info *info; @@ -188,7 +188,6 @@ static void sercos3_pci_remove(struct pci_dev *dev) uio_unregister_device(info); pci_release_regions(dev); pci_disable_device(dev); - pci_set_drvdata(dev, NULL); for (i = 0; i < 5; i++) { if (info->mem[i].internal_addr) iounmap(info->mem[i].internal_addr); @@ -197,7 +196,7 @@ static void sercos3_pci_remove(struct pci_dev *dev) kfree(info); } -static struct pci_device_id sercos3_pci_ids[] __devinitdata = { +static struct pci_device_id sercos3_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, @@ -226,19 +225,7 @@ static struct pci_driver sercos3_pci_driver = { .remove = sercos3_pci_remove, }; -static int __init sercos3_init_module(void) -{ - return pci_register_driver(&sercos3_pci_driver); -} - -static void __exit sercos3_exit_module(void) -{ - pci_unregister_driver(&sercos3_pci_driver); -} - -module_init(sercos3_init_module); -module_exit(sercos3_exit_module); - +module_pci_driver(sercos3_pci_driver); MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card"); MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); MODULE_LICENSE("GPL v2"); |
