| /* 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); |