diff options
Diffstat (limited to 'drivers/staging/fsl_qbman/fsl_usdpaa_irq.c')
-rw-r--r-- | drivers/staging/fsl_qbman/fsl_usdpaa_irq.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/drivers/staging/fsl_qbman/fsl_usdpaa_irq.c b/drivers/staging/fsl_qbman/fsl_usdpaa_irq.c new file mode 100644 index 0000000..914c7471 --- /dev/null +++ b/drivers/staging/fsl_qbman/fsl_usdpaa_irq.c @@ -0,0 +1,289 @@ +/* Copyright (c) 2013 Freescale Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* define a device that allows USPDAA processes to open a file + descriptor and specify which IRQ it wants to montior using an ioctl() + When an IRQ is received, the device becomes readable so that a process + can use read() or select() type calls to monitor for IRQs */ + +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/uaccess.h> +#include <linux/fsl_usdpaa.h> +#include <linux/module.h> +#include <linux/fdtable.h> +#include <linux/file.h> + +#include "qman_low.h" +#include "bman_low.h" + +struct usdpaa_irq_ctx { + int irq_set; /* Set to true once the irq is set via ioctl */ + unsigned int irq_num; + u32 last_irq_count; /* Last value returned from read */ + u32 irq_count; /* Number of irqs since last read */ + wait_queue_head_t wait_queue; /* Waiting processes */ + spinlock_t lock; + void *inhibit_addr; /* inhibit register address */ + struct file *usdpaa_filp; + char irq_name[128]; +}; + +static int usdpaa_irq_open(struct inode *inode, struct file *filp) +{ + struct usdpaa_irq_ctx *ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + ctx->irq_set = 0; + ctx->irq_count = 0; + ctx->last_irq_count = 0; + init_waitqueue_head(&ctx->wait_queue); + spin_lock_init(&ctx->lock); + filp->private_data = ctx; + return 0; +} + +static int usdpaa_irq_release(struct inode *inode, struct file *filp) +{ + struct usdpaa_irq_ctx *ctx = filp->private_data; + if (ctx->irq_set) { + /* Inhibit the IRQ */ + out_be32(ctx->inhibit_addr, 0x1); + irq_set_affinity_hint(ctx->irq_num, NULL); + free_irq(ctx->irq_num, ctx); + ctx->irq_set = 0; + fput(ctx->usdpaa_filp); + } + kfree(filp->private_data); + return 0; +} + +static irqreturn_t usdpaa_irq_handler(int irq, void *_ctx) +{ + unsigned long flags; + struct usdpaa_irq_ctx *ctx = _ctx; + spin_lock_irqsave(&ctx->lock, flags); + ++ctx->irq_count; + spin_unlock_irqrestore(&ctx->lock, flags); + wake_up_all(&ctx->wait_queue); + /* Set the inhibit register. This will be reenabled + once the USDPAA code handles the IRQ */ + out_be32(ctx->inhibit_addr, 0x1); + pr_info("Inhibit at %p count %d", ctx->inhibit_addr, ctx->irq_count); + return IRQ_HANDLED; +} + +static int map_irq(struct file *fp, struct usdpaa_ioctl_irq_map *irq_map) +{ + struct usdpaa_irq_ctx *ctx = fp->private_data; + int ret; + + if (ctx->irq_set) { + pr_debug("Setting USDPAA IRQ when it was already set!\n"); + return -EBUSY; + } + + ctx->usdpaa_filp = fget(irq_map->fd); + if (!ctx->usdpaa_filp) { + pr_debug("USDPAA fget(%d) returned NULL\n", irq_map->fd); + return -EINVAL; + } + + ret = usdpaa_get_portal_config(ctx->usdpaa_filp, irq_map->portal_cinh, + irq_map->type, &ctx->irq_num, + &ctx->inhibit_addr); + if (ret) { + pr_debug("USDPAA IRQ couldn't identify portal\n"); + fput(ctx->usdpaa_filp); + return ret; + } + + ctx->irq_set = 1; + + snprintf(ctx->irq_name, sizeof(ctx->irq_name), + "usdpaa_irq %d", ctx->irq_num); + + ret = request_irq(ctx->irq_num, usdpaa_irq_handler, 0, + ctx->irq_name, ctx); + if (ret) { + pr_err("USDPAA request_irq(%d) failed, ret= %d\n", + ctx->irq_num, ret); + ctx->irq_set = 0; + fput(ctx->usdpaa_filp); + return ret; + } + ret = irq_set_affinity(ctx->irq_num, ¤t->cpus_allowed); + if (ret) + pr_err("USDPAA irq_set_affinity() failed, ret= %d\n", ret); + + ret = irq_set_affinity_hint(ctx->irq_num, ¤t->cpus_allowed); + if (ret) + pr_err("USDPAA irq_set_affinity_hint() failed, ret= %d\n", ret); + + return 0; +} + +static long usdpaa_irq_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct usdpaa_ioctl_irq_map irq_map; + + if (cmd != USDPAA_IOCTL_PORTAL_IRQ_MAP) { + pr_debug("USDPAA IRQ unknown command 0x%x\n", cmd); + return -EINVAL; + } + + ret = copy_from_user(&irq_map, (void __user *)arg, + sizeof(irq_map)); + if (ret) + return ret; + return map_irq(fp, &irq_map); +} + +static ssize_t usdpaa_irq_read(struct file *filp, char __user *buff, + size_t count, loff_t *offp) +{ + struct usdpaa_irq_ctx *ctx = filp->private_data; + int ret; + + if (!ctx->irq_set) { + pr_debug("Reading USDPAA IRQ before it was set\n"); + return -EINVAL; + } + + if (count < sizeof(ctx->irq_count)) { + pr_debug("USDPAA IRQ Read too small\n"); + return -EINVAL; + } + if (ctx->irq_count == ctx->last_irq_count) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(ctx->wait_queue, + ctx->irq_count != ctx->last_irq_count); + if (ret == -ERESTARTSYS) + return ret; + } + + ctx->last_irq_count = ctx->irq_count; + + if (copy_to_user(buff, &ctx->last_irq_count, + sizeof(ctx->last_irq_count))) + return -EFAULT; + return sizeof(ctx->irq_count); +} + +static unsigned int usdpaa_irq_poll(struct file *filp, poll_table *wait) +{ + struct usdpaa_irq_ctx *ctx = filp->private_data; + unsigned int ret = 0; + unsigned long flags; + + if (!ctx->irq_set) + return POLLHUP; + + poll_wait(filp, &ctx->wait_queue, wait); + + spin_lock_irqsave(&ctx->lock, flags); + if (ctx->irq_count != ctx->last_irq_count) + ret |= POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&ctx->lock, flags); + return ret; +} + +static long usdpaa_irq_ioctl_compat(struct file *fp, unsigned int cmd, + unsigned long arg) +{ +#ifdef CONFIG_COMPAT + void __user *a = (void __user *)arg; +#endif + switch (cmd) { +#ifdef CONFIG_COMPAT + case USDPAA_IOCTL_PORTAL_IRQ_MAP_COMPAT: + { + struct compat_ioctl_irq_map input; + struct usdpaa_ioctl_irq_map converted; + if (copy_from_user(&input, a, sizeof(input))) + return -EFAULT; + converted.type = input.type; + converted.fd = input.fd; + converted.portal_cinh = compat_ptr(input.portal_cinh); + return map_irq(fp, &converted); + } +#endif + default: + return usdpaa_irq_ioctl(fp, cmd, arg); + } +} + +static const struct file_operations usdpaa_irq_fops = { + .open = usdpaa_irq_open, + .release = usdpaa_irq_release, + .unlocked_ioctl = usdpaa_irq_ioctl, + .compat_ioctl = usdpaa_irq_ioctl_compat, + .read = usdpaa_irq_read, + .poll = usdpaa_irq_poll +}; + +static struct miscdevice usdpaa_miscdev = { + .name = "fsl-usdpaa-irq", + .fops = &usdpaa_irq_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init usdpaa_irq_init(void) +{ + int ret; + + pr_info("Freescale USDPAA process IRQ driver\n"); + ret = misc_register(&usdpaa_miscdev); + if (ret) + pr_err("fsl-usdpaa-irq: failed to register misc device\n"); + return ret; +} + +static void __exit usdpaa_irq_exit(void) +{ + misc_deregister(&usdpaa_miscdev); +} + +module_init(usdpaa_irq_init); +module_exit(usdpaa_irq_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Freescale USDPAA process IRQ driver"); |