diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index 56eb471b557..aaee4481197 100644 --- a/drivers/input/joystick/Kconfig +++ b/drivers/input/joystick/Kconfig @@ -329,4 +329,15 @@ config JOYSTICK_MAPLE To compile this as a module choose M here: the module will be called maplecontrol. +config TOUCHDISC_VTD518_SHINETSU + tristate "ShinEtsu VTD518 TouchDisc" + depends on I2C + default n + help + Say Y here if you have the ShinEtsu VTD518 Touchdisc connected. It + provides the detection of absolute and relative motions and dpad + like buttons. + + To compile this as a module choose M here: the module will be called + tdisc_vtd518_shinetsu. endif diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile index 92dc0de9dfe..7009c38e79f 100644 --- a/drivers/input/joystick/Makefile +++ b/drivers/input/joystick/Makefile @@ -32,4 +32,4 @@ obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o - +obj-$(CONFIG_TOUCHDISC_VTD518_SHINETSU) += tdisc_vtd518_shinetsu.o \ No newline at end of file diff --git a/drivers/input/joystick/tdisc_vtd518_shinetsu.c b/drivers/input/joystick/tdisc_vtd518_shinetsu.c new file mode 100644 index 00000000000..efbe97474b8 --- /dev/null +++ b/drivers/input/joystick/tdisc_vtd518_shinetsu.c @@ -0,0 +1,528 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +/* 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); diff --git a/include/linux/input/tdisc_shinetsu.h b/include/linux/input/tdisc_shinetsu.h new file mode 100644 index 00000000000..88f84f2a848 --- /dev/null +++ b/include/linux/input/tdisc_shinetsu.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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. + * + */ + +#ifndef _TDISC_SHINETSU_H_ +#define _TDISC_SHINETSU_H_ + +struct tdisc_abs_values { + int x_max; + int y_max; + int x_min; + int y_min; + int pressure_max; + int pressure_min; +}; + +struct tdisc_platform_data { + int (*tdisc_setup) (void); + void (*tdisc_release) (void); + int (*tdisc_enable) (void); + int (*tdisc_disable)(void); + int tdisc_wakeup; + int tdisc_gpio; + bool tdisc_report_keys; + bool tdisc_report_relative; + bool tdisc_report_absolute; + bool tdisc_report_wheel; + bool tdisc_reverse_x; + bool tdisc_reverse_y; + struct tdisc_abs_values *tdisc_abs; +}; + +#endif /* _TDISC_SHINETSU_H_ */