diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-11 13:56:38 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-11 13:56:38 -0800 |
commit | 6a5971d8fea1f4a8c33dfe0cec6a1c490f0c9cde (patch) | |
tree | 982911522177da03dd839d816a6a93cc210e4657 /drivers/uio | |
parent | cff2f741b8ee8a70b208830e330de053efd4fc45 (diff) | |
parent | 70e78c40ed6c25bb34d642848e485d79ffc55c26 (diff) | |
download | lwn-6a5971d8fea1f4a8c33dfe0cec6a1c490f0c9cde.tar.gz lwn-6a5971d8fea1f4a8c33dfe0cec6a1c490f0c9cde.zip |
Merge tag 'char-misc-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull Char/Misc driver merge from Greg Kroah-Hartman:
"Here is the "big" char/misc driver patches for 3.8-rc1. I'm starting
to put random driver subsystems that I had previously sent you through
the driver-core tree in this tree, as it makes more sense to do so.
Nothing major here, the various __dev* removals, some mei driver
updates, and other random driver-specific things from the different
maintainers and developers.
Note, some MFD drivers got added through this tree, and they are also
coming in through the "real" MFD tree as well, due to some major
mis-communication between me and the different developers. If you
have any merge conflicts, take the ones from the MFD tree, not these
ones, sorry about that.
All of this has been in linux-next for a while.
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>"
Fix up trivial conflict in drivers/mmc/host/Kconfig due to new drivers
having been added (both at the end, as usual..)
* tag 'char-misc-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (84 commits)
MAINTAINERS: remove drivers/staging/hv/
misc/st_kim: Free resources in the error path of probe()
drivers/char: for hpet, add count checking, and ~0UL instead of -1
w1-gpio: Simplify & get rid of defines
w1-gpio: Pinctrl-fy
extcon: remove use of __devexit_p
extcon: remove use of __devinit
extcon: remove use of __devexit
drivers: uio: Only allocate new private data when probing device tree node
drivers: uio_dmem_genirq: Allow partial success when opening device
drivers: uio_dmem_genirq: Don't use DMA_ERROR_CODE to indicate unmapped regions
drivers: uio_dmem_genirq: Don't mix address spaces for dynamic region vaddr
uio: remove use of __devexit
uio: remove use of __devinitdata
uio: remove use of __devinit
uio: remove use of __devexit_p
char: remove use of __devexit
char: remove use of __devinitconst
char: remove use of __devinitdata
char: remove use of __devinit
...
Diffstat (limited to 'drivers/uio')
-rw-r--r-- | drivers/uio/Kconfig | 16 | ||||
-rw-r--r-- | drivers/uio/Makefile | 1 | ||||
-rw-r--r-- | drivers/uio/uio_aec.c | 2 | ||||
-rw-r--r-- | drivers/uio/uio_cif.c | 4 | ||||
-rw-r--r-- | drivers/uio/uio_dmem_genirq.c | 359 | ||||
-rw-r--r-- | drivers/uio/uio_netx.c | 2 | ||||
-rw-r--r-- | drivers/uio/uio_pci_generic.c | 2 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv.c | 1 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv_genirq.c | 3 | ||||
-rw-r--r-- | drivers/uio/uio_pruss.c | 6 | ||||
-rw-r--r-- | drivers/uio/uio_sercos3.c | 4 |
11 files changed, 389 insertions, 11 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index 6f3ea9bbc818..82e2b89d4480 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -44,6 +44,22 @@ 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" + 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 diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index d4dd9a5552f8..b354c539507a 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -2,6 +2,7 @@ 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 diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c index 72b22d44e8b9..1548982db58b 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; diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c index a84a451159ed..7dd6fc60539d 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; @@ -112,7 +112,7 @@ static void hilscher_pci_remove(struct pci_dev *dev) 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, diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c new file mode 100644 index 000000000000..252434c9ea9d --- /dev/null +++ b/drivers/uio/uio_dmem_genirq.c @@ -0,0 +1,359 @@ +/* + * 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 = pdev->dev.platform_data; + 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 bad0; + } + 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"); + goto bad1; + } + + platform_set_drvdata(pdev, priv); + return 0; + bad1: + kfree(priv); + pm_runtime_disable(&pdev->dev); + 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); +#else +# define uio_of_genirq_match NULL +#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 = 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_netx.c b/drivers/uio/uio_netx.c index a879fd5741f8..6a4ba5e83e37 100644 --- a/drivers/uio/uio_netx.c +++ b/drivers/uio/uio_netx.c @@ -48,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; diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c index 0bd08ef2b394..14aa10c1f6de 100644 --- a/drivers/uio/uio_pci_generic.c +++ b/drivers/uio/uio_pci_generic.c @@ -53,7 +53,7 @@ static irqreturn_t irqhandler(int irq, struct uio_info *info) 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; diff --git a/drivers/uio/uio_pdrv.c b/drivers/uio/uio_pdrv.c index 72d3646c7365..39be9e061700 100644 --- a/drivers/uio/uio_pdrv.c +++ b/drivers/uio/uio_pdrv.c @@ -60,6 +60,7 @@ static int uio_pdrv_probe(struct platform_device *pdev) uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; uiomem->size = resource_size(r); + uiomem->name = r->name; ++uiomem; } diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 42202cd83158..c122bca669b6 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -102,7 +102,7 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) int ret = -EINVAL; int i; - if (!uioinfo) { + if (pdev->dev.of_node) { int irq; /* alloc uioinfo for one device */ @@ -172,6 +172,7 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; uiomem->size = resource_size(r); + uiomem->name = r->name; ++uiomem; } diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c index 33a7a273b453..cce0f78341cc 100644 --- a/drivers/uio/uio_pruss.c +++ b/drivers/uio/uio_pruss.c @@ -112,7 +112,7 @@ static void pruss_cleanup(struct platform_device *dev, kfree(gdev); } -static int __devinit pruss_probe(struct platform_device *dev) +static int pruss_probe(struct platform_device *dev) { struct uio_info *p; struct uio_pruss_dev *gdev; @@ -209,7 +209,7 @@ out_free: return ret; } -static int __devexit pruss_remove(struct platform_device *dev) +static int pruss_remove(struct platform_device *dev) { struct uio_pruss_dev *gdev = platform_get_drvdata(dev); @@ -220,7 +220,7 @@ static int __devexit pruss_remove(struct platform_device *dev) static struct platform_driver pruss_driver = { .probe = pruss_probe, - .remove = __devexit_p(pruss_remove), + .remove = pruss_remove, .driver = { .name = DRV_NAME, .owner = THIS_MODULE, diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index a187fa14c5c0..81a10a563120 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; @@ -197,7 +197,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, |