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