android_kernel_cmhtcleo/drivers/input/misc/capella_cm3602.c
2010-08-27 11:19:57 +02:00

272 lines
6.1 KiB
C

/* drivers/input/misc/capella_cm3602.c
*
* Copyright (C) 2009 Google, Inc.
* Author: Iliyan Malchev <malchev@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/capella_cm3602.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#define D(x...) pr_info(x)
static struct capella_cm3602_data {
struct input_dev *input_dev;
struct capella_cm3602_platform_data *pdata;
int enabled;
} the_data;
static int misc_opened;
static int capella_cm3602_report(struct capella_cm3602_data *data)
{
int val = gpio_get_value(data->pdata->p_out);
if (val < 0) {
pr_err("%s: gpio_get_value error %d\n", __func__, val);
return val;
}
D("proximity %d\n", val);
/* 0 is close, 1 is far */
input_report_abs(data->input_dev, ABS_DISTANCE, val);
input_sync(data->input_dev);
return val;
}
static irqreturn_t capella_cm3602_irq_handler(int irq, void *data)
{
struct capella_cm3602_data *ip = data;
int val = capella_cm3602_report(ip);
return IRQ_HANDLED;
}
static int capella_cm3602_enable(struct capella_cm3602_data *data)
{
int rc;
D("%s\n", __func__);
if (data->enabled) {
D("%s: already enabled\n", __func__);
return 0;
}
data->pdata->power(1);
data->enabled = !rc;
if (!rc)
capella_cm3602_report(data);
return rc;
}
static int capella_cm3602_disable(struct capella_cm3602_data *data)
{
int rc = -EIO;
D("%s\n", __func__);
if (!data->enabled) {
D("%s: already disabled\n", __func__);
return 0;
}
data->pdata->power(0);
data->enabled = 0;
return rc;
}
static int capella_cm3602_setup(struct capella_cm3602_data *ip)
{
int rc = -EIO;
struct capella_cm3602_platform_data *pdata = ip->pdata;
int irq = gpio_to_irq(pdata->p_out);
D("%s\n", __func__);
rc = gpio_request(pdata->p_out, "gpio_proximity_out");
if (rc < 0) {
pr_err("%s: gpio %d request failed (%d)\n",
__func__, pdata->p_out, rc);
goto done;
}
rc = gpio_direction_input(pdata->p_out);
if (rc < 0) {
pr_err("%s: failed to set gpio %d as input (%d)\n",
__func__, pdata->p_out, rc);
goto fail_free_p_out;
}
rc = request_irq(irq,
capella_cm3602_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"capella_cm3602",
ip);
if (rc < 0) {
pr_err("%s: request_irq(%d) failed for gpio %d (%d)\n",
__func__, irq,
pdata->p_out, rc);
goto fail_free_p_out;
}
rc = set_irq_wake(irq, 1);
if (rc < 0) {
pr_err("%s: failed to set irq %d as a wake interrupt\n",
__func__, irq);
goto fail_free_irq;
}
goto done;
fail_free_irq:
free_irq(irq, 0);
fail_free_p_out:
gpio_free(pdata->p_out);
done:
return rc;
}
static int capella_cm3602_open(struct inode *inode, struct file *file)
{
D("%s\n", __func__);
if (misc_opened)
return -EBUSY;
misc_opened = 1;
return 0;
}
static int capella_cm3602_release(struct inode *inode, struct file *file)
{
D("%s\n", __func__);
misc_opened = 0;
return capella_cm3602_disable(&the_data);
}
static long capella_cm3602_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int val;
D("%s cmd %d\n", __func__, _IOC_NR(cmd));
switch (cmd) {
case CAPELLA_CM3602_IOCTL_ENABLE:
if (get_user(val, (unsigned long __user *)arg))
return -EFAULT;
if (val)
return capella_cm3602_enable(&the_data);
else
return capella_cm3602_disable(&the_data);
break;
case CAPELLA_CM3602_IOCTL_GET_ENABLED:
return put_user(the_data.enabled, (unsigned long __user *)arg);
break;
default:
pr_err("%s: invalid cmd %d\n", __func__, _IOC_NR(cmd));
return -EINVAL;
}
}
static struct file_operations capella_cm3602_fops = {
.owner = THIS_MODULE,
.open = capella_cm3602_open,
.release = capella_cm3602_release,
.unlocked_ioctl = capella_cm3602_ioctl
};
struct miscdevice capella_cm3602_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "cm3602",
.fops = &capella_cm3602_fops
};
static int capella_cm3602_probe(struct platform_device *pdev)
{
int rc = -EIO;
struct input_dev *input_dev;
struct capella_cm3602_data *ip;
struct capella_cm3602_platform_data *pdata;
D("%s: probe\n", __func__);
pdata = pdev->dev.platform_data;
if (!pdata) {
pr_err("%s: missing pdata!\n", __func__);
goto done;
}
if (!pdata->power) {
pr_err("%s: incomplete pdata!\n", __func__);
goto done;
}
ip = &the_data;
platform_set_drvdata(pdev, ip);
D("%s: allocating input device\n", __func__);
input_dev = input_allocate_device();
if (!input_dev) {
pr_err("%s: could not allocate input device\n", __func__);
rc = -ENOMEM;
goto done;
}
ip->input_dev = input_dev;
ip->pdata = pdata;
input_set_drvdata(input_dev, ip);
input_dev->name = "proximity";
set_bit(EV_ABS, input_dev->evbit);
input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0);
D("%s: registering input device\n", __func__);
rc = input_register_device(input_dev);
if (rc < 0) {
pr_err("%s: could not register input device\n", __func__);
goto err_free_input_device;
}
D("%s: registering misc device\n", __func__);
rc = misc_register(&capella_cm3602_misc);
if (rc < 0) {
pr_err("%s: could not register misc device\n", __func__);
goto err_unregister_input_device;
}
rc = capella_cm3602_setup(ip);
if (!rc)
goto done;
misc_deregister(&capella_cm3602_misc);
err_unregister_input_device:
input_unregister_device(input_dev);
goto done;
err_free_input_device:
input_free_device(input_dev);
done:
return rc;
}
static struct platform_driver capella_cm3602_driver = {
.probe = capella_cm3602_probe,
.driver = {
.name = CAPELLA_CM3602,
.owner = THIS_MODULE
},
};
static int __init capella_cm3602_init(void)
{
return platform_driver_register(&capella_cm3602_driver);
}
device_initcall(capella_cm3602_init);