1
0
Fork 0
alistair23-linux/drivers/staging/fsl_qbman/fsl_usdpaa_irq.c

290 lines
8.2 KiB
C

/* 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_debug("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, &current->cpus_mask);
if (ret)
pr_err("USDPAA irq_set_affinity() failed, ret= %d\n", ret);
ret = irq_set_affinity_hint(ctx->irq_num, &current->cpus_mask);
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");