529 lines
14 KiB
C
529 lines
14 KiB
C
/* Copyright (c) 2010, The Linux Foundation. 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 version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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/init.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/input.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/input/tdisc_shinetsu.h>
|
|
|
|
#if defined(CONFIG_HAS_EARLYSUSPEND)
|
|
#include <linux/earlysuspend.h>
|
|
/* Early-suspend level */
|
|
#define TDISC_SUSPEND_LEVEL 1
|
|
#endif
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION("0.1");
|
|
MODULE_DESCRIPTION("Shinetsu Touchdisc driver");
|
|
MODULE_ALIAS("platform:tdisc-shinetsu");
|
|
|
|
#define TDSIC_BLK_READ_CMD 0x00
|
|
#define TDISC_READ_DELAY msecs_to_jiffies(25)
|
|
#define X_MAX (32)
|
|
#define X_MIN (-32)
|
|
#define Y_MAX (32)
|
|
#define Y_MIN (-32)
|
|
#define PRESSURE_MAX (32)
|
|
#define PRESSURE_MIN (0)
|
|
#define TDISC_USER_ACTIVE_MASK 0x40
|
|
#define TDISC_NORTH_SWITCH_MASK 0x20
|
|
#define TDISC_SOUTH_SWITCH_MASK 0x10
|
|
#define TDISC_EAST_SWITCH_MASK 0x08
|
|
#define TDISC_WEST_SWITCH_MASK 0x04
|
|
#define TDISC_CENTER_SWITCH 0x01
|
|
#define TDISC_BUTTON_PRESS_MASK 0x3F
|
|
|
|
#define DRIVER_NAME "tdisc-shinetsu"
|
|
#define DEVICE_NAME "vtd518"
|
|
#define TDISC_NAME "tdisc_shinetsu"
|
|
#define TDISC_INT "tdisc_interrupt"
|
|
|
|
struct tdisc_data {
|
|
struct input_dev *tdisc_device;
|
|
struct i2c_client *clientp;
|
|
struct tdisc_platform_data *pdata;
|
|
struct delayed_work tdisc_work;
|
|
#if defined(CONFIG_HAS_EARLYSUSPEND)
|
|
struct early_suspend tdisc_early_suspend;
|
|
#endif
|
|
};
|
|
|
|
static void process_tdisc_data(struct tdisc_data *dd, u8 *data)
|
|
{
|
|
int i;
|
|
static bool button_press;
|
|
s8 x, y;
|
|
|
|
/* Check if the user is actively navigating */
|
|
if (!(data[7] & TDISC_USER_ACTIVE_MASK)) {
|
|
pr_debug(" TDISC ! No Data to report ! False positive \n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 8 ; i++)
|
|
pr_debug(" Data[%d] = %x\n", i, data[i]);
|
|
|
|
/* Check if there is a button press */
|
|
if (dd->pdata->tdisc_report_keys)
|
|
if (data[7] & TDISC_BUTTON_PRESS_MASK || button_press == true) {
|
|
input_report_key(dd->tdisc_device, KEY_UP,
|
|
(data[7] & TDISC_NORTH_SWITCH_MASK));
|
|
|
|
input_report_key(dd->tdisc_device, KEY_DOWN,
|
|
(data[7] & TDISC_SOUTH_SWITCH_MASK));
|
|
|
|
input_report_key(dd->tdisc_device, KEY_RIGHT,
|
|
(data[7] & TDISC_EAST_SWITCH_MASK));
|
|
|
|
input_report_key(dd->tdisc_device, KEY_LEFT,
|
|
(data[7] & TDISC_WEST_SWITCH_MASK));
|
|
|
|
input_report_key(dd->tdisc_device, KEY_ENTER,
|
|
(data[7] & TDISC_CENTER_SWITCH));
|
|
|
|
if (data[7] & TDISC_BUTTON_PRESS_MASK)
|
|
button_press = true;
|
|
else
|
|
button_press = false;
|
|
}
|
|
|
|
if (dd->pdata->tdisc_report_relative) {
|
|
/* Report relative motion values */
|
|
x = (s8) data[0];
|
|
y = (s8) data[1];
|
|
|
|
if (dd->pdata->tdisc_reverse_x)
|
|
x *= -1;
|
|
if (dd->pdata->tdisc_reverse_y)
|
|
y *= -1;
|
|
|
|
input_report_rel(dd->tdisc_device, REL_X, x);
|
|
input_report_rel(dd->tdisc_device, REL_Y, y);
|
|
}
|
|
|
|
if (dd->pdata->tdisc_report_absolute) {
|
|
input_report_abs(dd->tdisc_device, ABS_X, data[2]);
|
|
input_report_abs(dd->tdisc_device, ABS_Y, data[3]);
|
|
input_report_abs(dd->tdisc_device, ABS_PRESSURE, data[4]);
|
|
}
|
|
|
|
if (dd->pdata->tdisc_report_wheel)
|
|
input_report_rel(dd->tdisc_device, REL_WHEEL, (s8) data[6]);
|
|
|
|
input_sync(dd->tdisc_device);
|
|
}
|
|
|
|
static void tdisc_work_f(struct work_struct *work)
|
|
{
|
|
int rc;
|
|
u8 data[8];
|
|
struct tdisc_data *dd =
|
|
container_of(work, struct tdisc_data, tdisc_work.work);
|
|
|
|
/*
|
|
* Read the value of the interrupt pin. If low, perform
|
|
* an I2C read of 8 bytes to get the touch values and then
|
|
* reschedule the work after 25ms. If pin is high, exit
|
|
* and wait for next interrupt.
|
|
*/
|
|
rc = gpio_get_value_cansleep(dd->pdata->tdisc_gpio);
|
|
if (rc < 0) {
|
|
rc = pm_runtime_put_sync(&dd->clientp->dev);
|
|
if (rc < 0)
|
|
dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync"
|
|
" failed\n", __func__);
|
|
enable_irq(dd->clientp->irq);
|
|
return;
|
|
}
|
|
|
|
pr_debug("%s: TDISC gpio_get_value = %d\n", __func__, rc);
|
|
if (rc == 0) {
|
|
/* We have data to read */
|
|
rc = i2c_smbus_read_i2c_block_data(dd->clientp,
|
|
TDSIC_BLK_READ_CMD, 8, data);
|
|
if (rc < 0) {
|
|
pr_debug("%s:I2C read failed,trying again\n", __func__);
|
|
rc = i2c_smbus_read_i2c_block_data(dd->clientp,
|
|
TDSIC_BLK_READ_CMD, 8, data);
|
|
if (rc < 0) {
|
|
pr_err("%s:I2C read failed again, exiting\n",
|
|
__func__);
|
|
goto fail_i2c_read;
|
|
}
|
|
}
|
|
pr_debug("%s: TDISC: I2C read success\n", __func__);
|
|
process_tdisc_data(dd, data);
|
|
} else {
|
|
/*
|
|
* We have no data to read.
|
|
* Enable the IRQ to receive further interrupts.
|
|
*/
|
|
enable_irq(dd->clientp->irq);
|
|
|
|
rc = pm_runtime_put_sync(&dd->clientp->dev);
|
|
if (rc < 0)
|
|
dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync"
|
|
" failed\n", __func__);
|
|
return;
|
|
}
|
|
|
|
fail_i2c_read:
|
|
schedule_delayed_work(&dd->tdisc_work, TDISC_READ_DELAY);
|
|
}
|
|
|
|
static irqreturn_t tdisc_interrupt(int irq, void *dev_id)
|
|
{
|
|
/*
|
|
* The touch disc intially generates an interrupt on any
|
|
* touch. The interrupt line is pulled low and remains low
|
|
* untill there are touch operations being performed. In case
|
|
* there are no further touch operations, the line goes high. The
|
|
* same process repeats again the next time,when the disc is touched.
|
|
*
|
|
* We do the following operations once we receive an interrupt.
|
|
* 1. Disable the IRQ for any further interrutps.
|
|
* 2. Schedule work every 25ms if the GPIO is still low.
|
|
* 3. In the work queue do a I2C read to get the touch data.
|
|
* 4. If the GPIO is pulled high, enable the IRQ and cancel the work.
|
|
*/
|
|
struct tdisc_data *dd = dev_id;
|
|
int rc;
|
|
|
|
rc = pm_runtime_get(&dd->clientp->dev);
|
|
if (rc < 0)
|
|
dev_dbg(&dd->clientp->dev, "%s: pm_runtime_get"
|
|
" failed\n", __func__);
|
|
pr_debug("%s: TDISC IRQ ! :-)\n", __func__);
|
|
|
|
/* Schedule the work immediately */
|
|
disable_irq_nosync(dd->clientp->irq);
|
|
schedule_delayed_work(&dd->tdisc_work, 0);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int tdisc_open(struct input_dev *dev)
|
|
{
|
|
int rc;
|
|
struct tdisc_data *dd = input_get_drvdata(dev);
|
|
|
|
if (!dd->clientp) {
|
|
/* Check if a valid i2c client is present */
|
|
pr_err("%s: no i2c adapter present \n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Enable the device */
|
|
if (dd->pdata->tdisc_enable != NULL) {
|
|
rc = dd->pdata->tdisc_enable();
|
|
if (rc)
|
|
goto fail_open;
|
|
}
|
|
rc = request_any_context_irq(dd->clientp->irq, tdisc_interrupt,
|
|
IRQF_TRIGGER_FALLING, TDISC_INT, dd);
|
|
if (rc < 0) {
|
|
pr_err("%s: request IRQ failed\n", __func__);
|
|
goto fail_irq_open;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail_irq_open:
|
|
if (dd->pdata->tdisc_disable != NULL)
|
|
dd->pdata->tdisc_disable();
|
|
fail_open:
|
|
return rc;
|
|
}
|
|
|
|
static void tdisc_close(struct input_dev *dev)
|
|
{
|
|
struct tdisc_data *dd = input_get_drvdata(dev);
|
|
|
|
free_irq(dd->clientp->irq, dd);
|
|
cancel_delayed_work_sync(&dd->tdisc_work);
|
|
if (dd->pdata->tdisc_disable != NULL)
|
|
dd->pdata->tdisc_disable();
|
|
}
|
|
|
|
static int __devexit tdisc_remove(struct i2c_client *client)
|
|
{
|
|
struct tdisc_data *dd;
|
|
|
|
pm_runtime_disable(&client->dev);
|
|
dd = i2c_get_clientdata(client);
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
unregister_early_suspend(&dd->tdisc_early_suspend);
|
|
#endif
|
|
input_unregister_device(dd->tdisc_device);
|
|
if (dd->pdata->tdisc_release != NULL)
|
|
dd->pdata->tdisc_release();
|
|
i2c_set_clientdata(client, NULL);
|
|
kfree(dd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int tdisc_suspend(struct device *dev)
|
|
{
|
|
int rc;
|
|
struct tdisc_data *dd;
|
|
|
|
dd = dev_get_drvdata(dev);
|
|
if (device_may_wakeup(&dd->clientp->dev))
|
|
enable_irq_wake(dd->clientp->irq);
|
|
else {
|
|
disable_irq(dd->clientp->irq);
|
|
|
|
if (cancel_delayed_work_sync(&dd->tdisc_work))
|
|
enable_irq(dd->clientp->irq);
|
|
|
|
if (dd->pdata->tdisc_disable) {
|
|
rc = dd->pdata->tdisc_disable();
|
|
if (rc) {
|
|
pr_err("%s: Suspend failed\n", __func__);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tdisc_resume(struct device *dev)
|
|
{
|
|
int rc;
|
|
struct tdisc_data *dd;
|
|
|
|
dd = dev_get_drvdata(dev);
|
|
if (device_may_wakeup(&dd->clientp->dev))
|
|
disable_irq_wake(dd->clientp->irq);
|
|
else {
|
|
if (dd->pdata->tdisc_enable) {
|
|
rc = dd->pdata->tdisc_enable();
|
|
if (rc) {
|
|
pr_err("%s: Resume failed\n", __func__);
|
|
return rc;
|
|
}
|
|
}
|
|
enable_irq(dd->clientp->irq);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
static void tdisc_early_suspend(struct early_suspend *h)
|
|
{
|
|
struct tdisc_data *dd = container_of(h, struct tdisc_data,
|
|
tdisc_early_suspend);
|
|
|
|
tdisc_suspend(&dd->clientp->dev);
|
|
}
|
|
|
|
static void tdisc_late_resume(struct early_suspend *h)
|
|
{
|
|
struct tdisc_data *dd = container_of(h, struct tdisc_data,
|
|
tdisc_early_suspend);
|
|
|
|
tdisc_resume(&dd->clientp->dev);
|
|
}
|
|
#endif
|
|
|
|
static struct dev_pm_ops tdisc_pm_ops = {
|
|
#ifndef CONFIG_HAS_EARLYSUSPEND
|
|
.suspend = tdisc_suspend,
|
|
.resume = tdisc_resume,
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
static const struct i2c_device_id tdisc_id[] = {
|
|
{ DEVICE_NAME, 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, tdisc_id);
|
|
|
|
static int __devinit tdisc_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int rc = -1;
|
|
int x_max, x_min, y_max, y_min, pressure_min, pressure_max;
|
|
struct tdisc_platform_data *pd;
|
|
struct tdisc_data *dd;
|
|
|
|
/* Check if the I2C adapter supports the BLOCK READ functionality */
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_READ_I2C_BLOCK))
|
|
return -ENODEV;
|
|
|
|
/* Enable runtime PM ops, start in ACTIVE mode */
|
|
rc = pm_runtime_set_active(&client->dev);
|
|
if (rc < 0)
|
|
dev_dbg(&client->dev, "unable to set runtime pm state\n");
|
|
pm_runtime_enable(&client->dev);
|
|
|
|
dd = kzalloc(sizeof *dd, GFP_KERNEL);
|
|
if (!dd) {
|
|
rc = -ENOMEM;
|
|
goto probe_exit;
|
|
}
|
|
|
|
i2c_set_clientdata(client, dd);
|
|
dd->clientp = client;
|
|
pd = client->dev.platform_data;
|
|
if (!pd) {
|
|
pr_err("%s: platform data not set \n", __func__);
|
|
rc = -EFAULT;
|
|
goto probe_free_exit;
|
|
}
|
|
|
|
dd->pdata = pd;
|
|
|
|
dd->tdisc_device = input_allocate_device();
|
|
if (!dd->tdisc_device) {
|
|
rc = -ENOMEM;
|
|
goto probe_free_exit;
|
|
}
|
|
|
|
input_set_drvdata(dd->tdisc_device, dd);
|
|
dd->tdisc_device->open = tdisc_open;
|
|
dd->tdisc_device->close = tdisc_close;
|
|
dd->tdisc_device->name = TDISC_NAME;
|
|
dd->tdisc_device->id.bustype = BUS_I2C;
|
|
dd->tdisc_device->id.product = 1;
|
|
dd->tdisc_device->id.version = 1;
|
|
|
|
if (pd->tdisc_abs) {
|
|
x_max = pd->tdisc_abs->x_max;
|
|
x_min = pd->tdisc_abs->x_min;
|
|
y_max = pd->tdisc_abs->y_max;
|
|
y_min = pd->tdisc_abs->y_min;
|
|
pressure_max = pd->tdisc_abs->pressure_max;
|
|
pressure_min = pd->tdisc_abs->pressure_min;
|
|
} else {
|
|
x_max = X_MAX;
|
|
x_min = X_MIN;
|
|
y_max = Y_MAX;
|
|
y_min = Y_MIN;
|
|
pressure_max = PRESSURE_MAX;
|
|
pressure_min = PRESSURE_MIN;
|
|
}
|
|
|
|
/* Device capablities for relative motion */
|
|
input_set_capability(dd->tdisc_device, EV_REL, REL_X);
|
|
input_set_capability(dd->tdisc_device, EV_REL, REL_Y);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, BTN_MOUSE);
|
|
|
|
/* Device capablities for absolute motion */
|
|
input_set_capability(dd->tdisc_device, EV_ABS, ABS_X);
|
|
input_set_capability(dd->tdisc_device, EV_ABS, ABS_Y);
|
|
input_set_capability(dd->tdisc_device, EV_ABS, ABS_PRESSURE);
|
|
|
|
input_set_abs_params(dd->tdisc_device, ABS_X, x_min, x_max, 0, 0);
|
|
input_set_abs_params(dd->tdisc_device, ABS_Y, y_min, y_max, 0, 0);
|
|
input_set_abs_params(dd->tdisc_device, ABS_PRESSURE, pressure_min,
|
|
pressure_max, 0, 0);
|
|
|
|
/* Device capabilities for scroll and buttons */
|
|
input_set_capability(dd->tdisc_device, EV_REL, REL_WHEEL);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, KEY_LEFT);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, KEY_RIGHT);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, KEY_UP);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, KEY_DOWN);
|
|
input_set_capability(dd->tdisc_device, EV_KEY, KEY_ENTER);
|
|
|
|
/* Setup the device for operation */
|
|
if (dd->pdata->tdisc_setup != NULL) {
|
|
rc = dd->pdata->tdisc_setup();
|
|
if (rc) {
|
|
pr_err("%s: Setup failed \n", __func__);
|
|
goto probe_unreg_free_exit;
|
|
}
|
|
}
|
|
|
|
/* Setup wakeup capability */
|
|
device_init_wakeup(&dd->clientp->dev, dd->pdata->tdisc_wakeup);
|
|
|
|
INIT_DELAYED_WORK(&dd->tdisc_work, tdisc_work_f);
|
|
|
|
rc = input_register_device(dd->tdisc_device);
|
|
if (rc) {
|
|
pr_err("%s: input register device failed \n", __func__);
|
|
rc = -EINVAL;
|
|
goto probe_register_fail;
|
|
}
|
|
|
|
pm_runtime_set_suspended(&client->dev);
|
|
|
|
#ifdef CONFIG_HAS_EARLYSUSPEND
|
|
dd->tdisc_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
|
|
TDISC_SUSPEND_LEVEL;
|
|
dd->tdisc_early_suspend.suspend = tdisc_early_suspend;
|
|
dd->tdisc_early_suspend.resume = tdisc_late_resume;
|
|
register_early_suspend(&dd->tdisc_early_suspend);
|
|
#endif
|
|
return 0;
|
|
|
|
probe_register_fail:
|
|
if (dd->pdata->tdisc_release != NULL)
|
|
dd->pdata->tdisc_release();
|
|
probe_unreg_free_exit:
|
|
input_free_device(dd->tdisc_device);
|
|
probe_free_exit:
|
|
i2c_set_clientdata(client, NULL);
|
|
kfree(dd);
|
|
probe_exit:
|
|
pm_runtime_set_suspended(&client->dev);
|
|
pm_runtime_disable(&client->dev);
|
|
return rc;
|
|
}
|
|
|
|
static struct i2c_driver tdisc_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_PM
|
|
.pm = &tdisc_pm_ops,
|
|
#endif
|
|
},
|
|
.probe = tdisc_probe,
|
|
.remove = __devexit_p(tdisc_remove),
|
|
.id_table = tdisc_id,
|
|
};
|
|
|
|
static int __init tdisc_init(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = i2c_add_driver(&tdisc_driver);
|
|
if (rc)
|
|
pr_err("%s: i2c add driver failed \n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
static void __exit tdisc_exit(void)
|
|
{
|
|
i2c_del_driver(&tdisc_driver);
|
|
}
|
|
|
|
module_init(tdisc_init);
|
|
module_exit(tdisc_exit);
|