blob: 22cfa4e458e815d41463662252c42bc0d410d7bd [file] [log] [blame]
/*
$License:
Copyright (C) 2011 InvenSense Corporation, 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 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, see <http://www.gnu.org/licenses/>.
$
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/stat.h>
#include <linux/irq.h>
#include <linux/signal.h>
#include <linux/miscdevice.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/poll.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/mpu.h>
#include "slaveirq.h"
#include "mldl_cfg.h"
/* function which gets slave data and sends it to SLAVE */
struct slaveirq_dev_data {
struct miscdevice dev;
struct i2c_client *slave_client;
struct mpuirq_data data;
wait_queue_head_t slaveirq_wait;
int irq;
int pid;
int data_ready;
int timeout;
};
/* The following depends on patch fa1f68db6ca7ebb6fc4487ac215bffba06c01c28
* drivers: misc: pass miscdevice pointer via file private data
*/
static int slaveirq_open(struct inode *inode, struct file *file)
{
/* Device node is availabe in the file->private_data, this is
* exactly what we want so we leave it there */
struct slaveirq_dev_data *data =
container_of(file->private_data, struct slaveirq_dev_data, dev);
dev_dbg(data->dev.this_device,
"%s current->pid %d\n", __func__, current->pid);
data->pid = current->pid;
return 0;
}
static int slaveirq_release(struct inode *inode, struct file *file)
{
struct slaveirq_dev_data *data =
container_of(file->private_data, struct slaveirq_dev_data, dev);
dev_dbg(data->dev.this_device, "slaveirq_release\n");
return 0;
}
/* read function called when from /dev/slaveirq is read */
static ssize_t slaveirq_read(struct file *file,
char *buf, size_t count, loff_t *ppos)
{
int len, err;
struct slaveirq_dev_data *data =
container_of(file->private_data, struct slaveirq_dev_data, dev);
if (!data->data_ready && data->timeout &&
!(file->f_flags & O_NONBLOCK)) {
wait_event_interruptible_timeout(data->slaveirq_wait,
data->data_ready,
data->timeout);
}
if (data->data_ready && NULL != buf && count >= sizeof(data->data)) {
err = copy_to_user(buf, &data->data, sizeof(data->data));
data->data.data_type = 0;
} else {
return 0;
}
if (err != 0) {
dev_err(data->dev.this_device,
"Copy to user returned %d\n", err);
return -EFAULT;
}
data->data_ready = 0;
len = sizeof(data->data);
return len;
}
static unsigned int slaveirq_poll(struct file *file,
struct poll_table_struct *poll)
{
int mask = 0;
struct slaveirq_dev_data *data =
container_of(file->private_data, struct slaveirq_dev_data, dev);
poll_wait(file, &data->slaveirq_wait, poll);
if (data->data_ready)
mask |= POLLIN | POLLRDNORM;
return mask;
}
/* ioctl - I/O control */
static long slaveirq_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int retval = 0;
int tmp;
struct slaveirq_dev_data *data =
container_of(file->private_data, struct slaveirq_dev_data, dev);
switch (cmd) {
case SLAVEIRQ_SET_TIMEOUT:
data->timeout = arg;
break;
case SLAVEIRQ_GET_INTERRUPT_CNT:
tmp = data->data.interruptcount - 1;
if (data->data.interruptcount > 1)
data->data.interruptcount = 1;
if (copy_to_user((int *)arg, &tmp, sizeof(int)))
return -EFAULT;
break;
case SLAVEIRQ_GET_IRQ_TIME:
if (copy_to_user((int *)arg, &data->data.irqtime,
sizeof(data->data.irqtime)))
return -EFAULT;
data->data.irqtime = 0;
break;
default:
retval = -EINVAL;
}
return retval;
}
static irqreturn_t slaveirq_handler(int irq, void *dev_id)
{
struct slaveirq_dev_data *data = (struct slaveirq_dev_data *)dev_id;
static int mycount;
struct timeval irqtime;
mycount++;
data->data.interruptcount++;
/* wake up (unblock) for reading data from userspace */
data->data_ready = 1;
do_gettimeofday(&irqtime);
data->data.irqtime = (((long long)irqtime.tv_sec) << 32);
data->data.irqtime += irqtime.tv_usec;
data->data.data_type |= 1;
wake_up_interruptible(&data->slaveirq_wait);
return IRQ_HANDLED;
}
/* define which file operations are supported */
static const struct file_operations slaveirq_fops = {
.owner = THIS_MODULE,
.read = slaveirq_read,
.poll = slaveirq_poll,
#if HAVE_COMPAT_IOCTL
.compat_ioctl = slaveirq_ioctl,
#endif
#if HAVE_UNLOCKED_IOCTL
.unlocked_ioctl = slaveirq_ioctl,
#endif
.open = slaveirq_open,
.release = slaveirq_release,
};
int slaveirq_init(struct i2c_adapter *slave_adapter,
struct ext_slave_platform_data *pdata, char *name)
{
int res;
struct slaveirq_dev_data *data;
if (!pdata->irq)
return -EINVAL;
pdata->irq_data = kzalloc(sizeof(*data), GFP_KERNEL);
data = (struct slaveirq_dev_data *)pdata->irq_data;
if (!data)
return -ENOMEM;
data->dev.minor = MISC_DYNAMIC_MINOR;
data->dev.name = name;
data->dev.fops = &slaveirq_fops;
data->irq = pdata->irq;
data->pid = 0;
data->data_ready = 0;
data->timeout = 0;
init_waitqueue_head(&data->slaveirq_wait);
res = request_irq(data->irq, slaveirq_handler,
IRQF_TRIGGER_RISING | IRQF_SHARED,
data->dev.name, data);
if (res) {
dev_err(&slave_adapter->dev,
"myirqtest: cannot register IRQ %d\n", data->irq);
goto out_request_irq;
}
res = misc_register(&data->dev);
if (res < 0) {
dev_err(&slave_adapter->dev,
"misc_register returned %d\n", res);
goto out_misc_register;
}
return res;
out_misc_register:
free_irq(data->irq, data);
out_request_irq:
kfree(pdata->irq_data);
pdata->irq_data = NULL;
return res;
}
EXPORT_SYMBOL(slaveirq_init);
void slaveirq_exit(struct ext_slave_platform_data *pdata)
{
struct slaveirq_dev_data *data = pdata->irq_data;
if (!pdata->irq_data || data->irq <= 0)
return;
dev_info(data->dev.this_device, "Unregistering %s\n", data->dev.name);
free_irq(data->irq, data);
misc_deregister(&data->dev);
kfree(pdata->irq_data);
pdata->irq_data = NULL;
}
EXPORT_SYMBOL(slaveirq_exit);