From d00232f331b00b340117d3e6959507ab183d0db3 Mon Sep 17 00:00:00 2001 From: Anirudh Ghayal Date: Wed, 16 Jun 2010 14:21:22 +0530 Subject: [PATCH] input: touchscreen: Add support for TMG200 touch controller The TMG200 touch controller supports upto two touches. This driver supports both TMG200 and TMA300 controllers. CRs-Fixed: 226967 Change-Id: I32871e76d3dd77fec0a723e1c306492bda2de897 Signed-off-by: Anirudh Ghayal --- drivers/input/touchscreen/Kconfig | 14 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/cy8c_ts.c | 824 ++++++++++++++++++++++++++++ include/linux/input/cy8c_ts.h | 65 +++ 4 files changed, 904 insertions(+) create mode 100644 drivers/input/touchscreen/cy8c_ts.c create mode 100644 include/linux/input/cy8c_ts.h diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index caf6547e5f7..fb02ec90a7b 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -875,6 +875,20 @@ config TOUCHSCREEN_TPS6507X To compile this driver as a module, choose M here: the module will be called tps6507x_ts. +config TOUCHSCREEN_CY8C_TS + tristate "Cypress TMA300-TMG200 based touchscreens" + depends on I2C + default n + help + Say Y here if you have a Cypress TMA300/TMG200 based touchscreen. + TMA300 is a multi-touch screen which can report upto 10 + touches at a time. TMG200 supports 2 touches. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cy8c_ts. + config TOUCHSCREEN_CYTTSP_I2C_QC tristate "Cypress TTSP based touchscreens" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 51340aaa3b7..87ac5b91ed0 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -73,4 +73,5 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o +obj-$(CONFIG_TOUCHSCREEN_CY8C_TS) += cy8c_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C_QC) += cyttsp-i2c-qc.o diff --git a/drivers/input/touchscreen/cy8c_ts.c b/drivers/input/touchscreen/cy8c_ts.c new file mode 100644 index 00000000000..f70858251c8 --- /dev/null +++ b/drivers/input/touchscreen/cy8c_ts.c @@ -0,0 +1,824 @@ +/* Source for: + * Cypress CY8CTMA300 Prototype touchscreen driver. + * drivers/input/touchscreen/cy8c_ts.c + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * Copyright (c) 2010, 2011 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + * History: + * (C) 2010 Cypress - Update for GPL distribution + * (C) 2009 Cypress - Assume maintenance ownership + * (C) 2009 Enea - Original prototype + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include + +/* Early-suspend level */ +#define CY8C_TS_SUSPEND_LEVEL 1 +#endif + +#define CY8CTMA300 0x0 +#define CY8CTMG200 0x1 +#define CY8CTMA340 0x2 + +#define INVALID_DATA 0xff + +#define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(10)) +#define INITIAL_DELAY (msecs_to_jiffies(25000)) + +struct cy8c_ts_data { + u8 x_index; + u8 y_index; + u8 z_index; + u8 id_index; + u8 touch_index; + u8 data_reg; + u8 status_reg; + u8 data_size; + u8 touch_bytes; + u8 update_data; + u8 touch_meta_data; + u8 finger_size; +}; + +static struct cy8c_ts_data devices[] = { + [0] = { + .x_index = 6, + .y_index = 4, + .z_index = 3, + .id_index = 0, + .data_reg = 0x3, + .status_reg = 0x1, + .update_data = 0x4, + .touch_bytes = 8, + .touch_meta_data = 3, + .finger_size = 70, + }, + [1] = { + .x_index = 2, + .y_index = 4, + .id_index = 6, + .data_reg = 0x6, + .status_reg = 0x5, + .update_data = 0x1, + .touch_bytes = 12, + .finger_size = 70, + }, + [2] = { + .x_index = 1, + .y_index = 3, + .z_index = 5, + .id_index = 6, + .data_reg = 0x2, + .status_reg = 0, + .update_data = 0x4, + .touch_bytes = 6, + .touch_meta_data = 3, + .finger_size = 70, + }, +}; + +struct cy8c_ts { + struct i2c_client *client; + struct input_dev *input; + struct delayed_work work; + struct workqueue_struct *wq; + struct cy8c_ts_platform_data *pdata; + struct cy8c_ts_data *dd; + u8 *touch_data; + u8 device_id; + u8 prev_touches; + bool is_suspended; + bool int_pending; + struct mutex sus_lock; + u32 pen_irq; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif +}; + +static inline u16 join_bytes(u8 a, u8 b) +{ + u16 ab = 0; + ab = ab | a; + ab = ab << 8 | b; + return ab; +} + +static s32 cy8c_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val) +{ + s32 data; + + data = i2c_smbus_write_byte_data(client, reg, val); + if (data < 0) + dev_err(&client->dev, "error %d in writing reg 0x%x\n", + data, reg); + + return data; +} + +static s32 cy8c_ts_read_reg_u8(struct i2c_client *client, u8 reg) +{ + s32 data; + + data = i2c_smbus_read_byte_data(client, reg); + if (data < 0) + dev_err(&client->dev, "error %d in reading reg 0x%x\n", + data, reg); + + return data; +} + +static int cy8c_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num) +{ + struct i2c_msg xfer_msg[2]; + + xfer_msg[0].addr = client->addr; + xfer_msg[0].len = 1; + xfer_msg[0].flags = 0; + xfer_msg[0].buf = ® + + xfer_msg[1].addr = client->addr; + xfer_msg[1].len = num; + xfer_msg[1].flags = I2C_M_RD; + xfer_msg[1].buf = buf; + + return i2c_transfer(client->adapter, xfer_msg, 2); +} + +static void report_data(struct cy8c_ts *ts, u16 x, u16 y, u8 pressure, u8 id) +{ + if (ts->pdata->swap_xy) + swap(x, y); + + /* handle inverting coordinates */ + if (ts->pdata->invert_x) + x = ts->pdata->res_x - x; + if (ts->pdata->invert_y) + y = ts->pdata->res_y - y; + + input_report_abs(ts->input, ABS_MT_TRACKING_ID, id); + input_report_abs(ts->input, ABS_MT_POSITION_X, x); + input_report_abs(ts->input, ABS_MT_POSITION_Y, y); + input_report_abs(ts->input, ABS_MT_PRESSURE, pressure); + input_mt_sync(ts->input); +} + +static void process_tma300_data(struct cy8c_ts *ts) +{ + u8 id, pressure, touches, i; + u16 x, y; + + touches = ts->touch_data[ts->dd->touch_index]; + + for (i = 0; i < touches; i++) { + id = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->id_index]; + pressure = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->z_index]; + x = join_bytes(ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->x_index], + ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->x_index + 1]); + y = join_bytes(ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->y_index], + ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->y_index + 1]); + + report_data(ts, x, y, pressure, id); + } + + for (i = 0; i < ts->prev_touches - touches; i++) { + input_report_abs(ts->input, ABS_MT_PRESSURE, 0); + input_mt_sync(ts->input); + } + + ts->prev_touches = touches; + input_sync(ts->input); +} + +static void process_tmg200_data(struct cy8c_ts *ts) +{ + u8 id, touches, i; + u16 x, y; + + touches = ts->touch_data[ts->dd->touch_index]; + + if (touches > 0) { + x = join_bytes(ts->touch_data[ts->dd->x_index], + ts->touch_data[ts->dd->x_index+1]); + y = join_bytes(ts->touch_data[ts->dd->y_index], + ts->touch_data[ts->dd->y_index+1]); + id = ts->touch_data[ts->dd->id_index]; + + report_data(ts, x, y, 255, id - 1); + + if (touches == 2) { + x = join_bytes(ts->touch_data[ts->dd->x_index+5], + ts->touch_data[ts->dd->x_index+6]); + y = join_bytes(ts->touch_data[ts->dd->y_index+5], + ts->touch_data[ts->dd->y_index+6]); + id = ts->touch_data[ts->dd->id_index+5]; + + report_data(ts, x, y, 255, id - 1); + } + } else { + for (i = 0; i < ts->prev_touches; i++) { + input_report_abs(ts->input, ABS_MT_PRESSURE, 0); + input_mt_sync(ts->input); + } + } + + input_sync(ts->input); + ts->prev_touches = touches; +} + +static void cy8c_ts_xy_worker(struct work_struct *work) +{ + int rc; + struct cy8c_ts *ts = container_of(work, struct cy8c_ts, + work.work); + + mutex_lock(&ts->sus_lock); + if (ts->is_suspended == true) { + dev_dbg(&ts->client->dev, "TS is supended\n"); + ts->int_pending = true; + mutex_unlock(&ts->sus_lock); + return; + } + mutex_unlock(&ts->sus_lock); + + /* read data from DATA_REG */ + rc = cy8c_ts_read(ts->client, ts->dd->data_reg, ts->touch_data, + ts->dd->data_size); + if (rc < 0) { + dev_err(&ts->client->dev, "read failed\n"); + goto schedule; + } + + if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA) + goto schedule; + + if ((ts->device_id == CY8CTMA300) || (ts->device_id == CY8CTMA340)) + process_tma300_data(ts); + else + process_tmg200_data(ts); + +schedule: + enable_irq(ts->pen_irq); + + /* write to STATUS_REG to update coordinates*/ + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, "write failed, try once more\n"); + + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, "write failed, exiting\n"); + } +} + +static irqreturn_t cy8c_ts_irq(int irq, void *dev_id) +{ + struct cy8c_ts *ts = dev_id; + + disable_irq_nosync(irq); + + queue_delayed_work(ts->wq, &ts->work, 0); + + return IRQ_HANDLED; +} + +static int cy8c_ts_init_ts(struct i2c_client *client, struct cy8c_ts *ts) +{ + struct input_dev *input_device; + int rc = 0; + + ts->dd = &devices[ts->device_id]; + + if (!ts->pdata->nfingers) { + dev_err(&client->dev, "Touches information not specified\n"); + return -EINVAL; + } + + if (ts->device_id == CY8CTMA300) { + if (ts->pdata->nfingers > 10) { + dev_err(&client->dev, "Touches >=1 & <= 10\n"); + return -EINVAL; + } + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + + ts->dd->touch_meta_data; + ts->dd->touch_index = ts->pdata->nfingers * + ts->dd->touch_bytes; + } else if (ts->device_id == CY8CTMG200) { + if (ts->pdata->nfingers > 2) { + dev_err(&client->dev, "Touches >=1 & <= 2\n"); + return -EINVAL; + } + ts->dd->data_size = ts->dd->touch_bytes; + ts->dd->touch_index = 0x0; + } else if (ts->device_id == CY8CTMA340) { + if (ts->pdata->nfingers > 10) { + dev_err(&client->dev, "Touches >=1 & <= 10\n"); + return -EINVAL; + } + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + + ts->dd->touch_meta_data; + ts->dd->touch_index = 0x0; + } + + ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL); + if (!ts->touch_data) { + pr_err("%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + + ts->prev_touches = 0; + + input_device = input_allocate_device(); + if (!input_device) { + rc = -ENOMEM; + goto error_alloc_dev; + } + + ts->input = input_device; + input_device->name = ts->pdata->ts_name; + input_device->id.bustype = BUS_I2C; + input_device->dev.parent = &client->dev; + input_set_drvdata(input_device, ts); + + __set_bit(EV_ABS, input_device->evbit); + + if (ts->device_id == CY8CTMA340) { + /* set up virtual key */ + __set_bit(EV_KEY, input_device->evbit); + /* set dummy key to make driver work with virtual keys */ + input_set_capability(input_device, EV_KEY, KEY_PROG1); + } + + input_set_abs_params(input_device, ABS_MT_POSITION_X, + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); + input_set_abs_params(input_device, ABS_MT_POSITION_Y, + ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0); + input_set_abs_params(input_device, ABS_MT_PRESSURE, + ts->pdata->min_touch, ts->pdata->max_touch, 0, 0); + input_set_abs_params(input_device, ABS_MT_TRACKING_ID, + ts->pdata->min_tid, ts->pdata->max_tid, 0, 0); + + ts->wq = create_singlethread_workqueue("kworkqueue_ts"); + if (!ts->wq) { + dev_err(&client->dev, "Could not create workqueue\n"); + goto error_wq_create; + } + + INIT_DELAYED_WORK(&ts->work, cy8c_ts_xy_worker); + + rc = input_register_device(input_device); + if (rc) + goto error_unreg_device; + + return 0; + +error_unreg_device: + destroy_workqueue(ts->wq); +error_wq_create: + input_free_device(input_device); +error_alloc_dev: + kfree(ts->touch_data); + return rc; +} + +#ifdef CONFIG_PM +static int cy8c_ts_suspend(struct device *dev) +{ + struct cy8c_ts *ts = dev_get_drvdata(dev); + int rc = 0; + + if (device_may_wakeup(dev)) { + /* mark suspend flag */ + mutex_lock(&ts->sus_lock); + ts->is_suspended = true; + mutex_unlock(&ts->sus_lock); + + enable_irq_wake(ts->pen_irq); + } else { + disable_irq_nosync(ts->pen_irq); + + rc = cancel_delayed_work_sync(&ts->work); + + if (rc) { + /* missed the worker, write to STATUS_REG to + acknowledge interrupt */ + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, + "write failed, try once more\n"); + + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, + "write failed, exiting\n"); + } + + enable_irq(ts->pen_irq); + } + + gpio_free(ts->pdata->irq_gpio); + + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(0); + if (rc) { + dev_err(dev, "unable to goto suspend\n"); + return rc; + } + } + } + return 0; +} + +static int cy8c_ts_resume(struct device *dev) +{ + struct cy8c_ts *ts = dev_get_drvdata(dev); + int rc = 0; + + if (device_may_wakeup(dev)) { + disable_irq_wake(ts->pen_irq); + + mutex_lock(&ts->sus_lock); + ts->is_suspended = false; + + if (ts->int_pending == true) { + ts->int_pending = false; + + /* start a delayed work */ + queue_delayed_work(ts->wq, &ts->work, 0); + } + mutex_unlock(&ts->sus_lock); + + } else { + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(1); + if (rc) { + dev_err(dev, "unable to resume\n"); + return rc; + } + } + + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ts->pdata->irq_gpio, "cy8c_irq_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto err_power_off; + } + + rc = gpio_direction_input(ts->pdata->irq_gpio); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto err_gpio_free; + } + + enable_irq(ts->pen_irq); + + /* Clear the status register of the TS controller */ + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, + "write failed, try once more\n"); + + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, + "write failed, exiting\n"); + } + } + return 0; +err_gpio_free: + gpio_free(ts->pdata->irq_gpio); +err_power_off: + if (ts->pdata->power_on) + rc = ts->pdata->power_on(0); + return rc; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cy8c_ts_early_suspend(struct early_suspend *h) +{ + struct cy8c_ts *ts = container_of(h, struct cy8c_ts, early_suspend); + + cy8c_ts_suspend(&ts->client->dev); +} + +static void cy8c_ts_late_resume(struct early_suspend *h) +{ + struct cy8c_ts *ts = container_of(h, struct cy8c_ts, early_suspend); + + cy8c_ts_resume(&ts->client->dev); +} +#endif + +static struct dev_pm_ops cy8c_ts_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = cy8c_ts_suspend, + .resume = cy8c_ts_resume, +#endif +}; +#endif + +static int __devinit cy8c_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cy8c_ts *ts; + struct cy8c_ts_platform_data *pdata = client->dev.platform_data; + int rc, temp_reg; + + if (!pdata) { + dev_err(&client->dev, "platform data is required!\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + dev_err(&client->dev, "I2C functionality not supported\n"); + return -EIO; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + /* 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); + + ts->client = client; + ts->pdata = pdata; + i2c_set_clientdata(client, ts); + ts->device_id = id->driver_data; + + if (ts->pdata->dev_setup) { + rc = ts->pdata->dev_setup(1); + if (rc < 0) { + dev_err(&client->dev, "dev setup failed\n"); + goto error_touch_data_alloc; + } + } + + /* power on the device */ + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(1); + if (rc) { + pr_err("%s: Unable to power on the device\n", __func__); + goto error_dev_setup; + } + } + + /* read one byte to make sure i2c device exists */ + if (id->driver_data == CY8CTMA300) + temp_reg = 0x01; + else if (id->driver_data == CY8CTMA340) + temp_reg = 0x00; + else + temp_reg = 0x05; + + rc = cy8c_ts_read_reg_u8(client, temp_reg); + if (rc < 0) { + dev_err(&client->dev, "i2c sanity check failed\n"); + goto error_power_on; + } + + ts->is_suspended = false; + ts->int_pending = false; + mutex_init(&ts->sus_lock); + + rc = cy8c_ts_init_ts(client, ts); + if (rc < 0) { + dev_err(&client->dev, "CY8CTMG200-TMA300 init failed\n"); + goto error_mutex_destroy; + } + + if (ts->pdata->resout_gpio < 0) + goto config_irq_gpio; + + /* configure touchscreen reset out gpio */ + rc = gpio_request(ts->pdata->resout_gpio, "cy8c_resout_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->resout_gpio); + goto error_uninit_ts; + } + + rc = gpio_direction_output(ts->pdata->resout_gpio, 0); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->resout_gpio); + goto error_resout_gpio_dir; + } + /* reset gpio stabilization time */ + msleep(20); + +config_irq_gpio: + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ts->pdata->irq_gpio, "cy8c_irq_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto error_irq_gpio_req; + } + + rc = gpio_direction_input(ts->pdata->irq_gpio); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto error_irq_gpio_dir; + } + + ts->pen_irq = gpio_to_irq(ts->pdata->irq_gpio); + rc = request_irq(ts->pen_irq, cy8c_ts_irq, + IRQF_TRIGGER_FALLING, + ts->client->dev.driver->name, ts); + if (rc) { + dev_err(&ts->client->dev, "could not request irq\n"); + goto error_req_irq_fail; + } + + /* Clear the status register of the TS controller */ + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) { + /* Do multiple writes in case of failure */ + dev_err(&ts->client->dev, "%s: write failed %d" + "trying again\n", __func__, rc); + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, "%s: write failed" + "second time(%d)\n", __func__, rc); + } + } + + device_init_wakeup(&client->dev, ts->pdata->wakeup); + +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + CY8C_TS_SUSPEND_LEVEL; + ts->early_suspend.suspend = cy8c_ts_early_suspend; + ts->early_suspend.resume = cy8c_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + return 0; +error_req_irq_fail: +error_irq_gpio_dir: + gpio_free(ts->pdata->irq_gpio); +error_irq_gpio_req: +error_resout_gpio_dir: + if (ts->pdata->resout_gpio >= 0) + gpio_free(ts->pdata->resout_gpio); +error_uninit_ts: + destroy_workqueue(ts->wq); + input_unregister_device(ts->input); + kfree(ts->touch_data); +error_mutex_destroy: + mutex_destroy(&ts->sus_lock); +error_power_on: + if (ts->pdata->power_on) + ts->pdata->power_on(0); +error_dev_setup: + if (ts->pdata->dev_setup) + ts->pdata->dev_setup(0); +error_touch_data_alloc: + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + kfree(ts); + return rc; +} + +static int __devexit cy8c_ts_remove(struct i2c_client *client) +{ + struct cy8c_ts *ts = i2c_get_clientdata(client); + +#if defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&ts->early_suspend); +#endif + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + + device_init_wakeup(&client->dev, 0); + + cancel_delayed_work_sync(&ts->work); + + free_irq(ts->pen_irq, ts); + + gpio_free(ts->pdata->irq_gpio); + + if (ts->pdata->resout_gpio >= 0) + gpio_free(ts->pdata->resout_gpio); + + destroy_workqueue(ts->wq); + + input_unregister_device(ts->input); + + mutex_destroy(&ts->sus_lock); + + if (ts->pdata->power_on) + ts->pdata->power_on(0); + + if (ts->pdata->dev_setup) + ts->pdata->dev_setup(0); + + kfree(ts->touch_data); + kfree(ts); + + return 0; +} + +static const struct i2c_device_id cy8c_ts_id[] = { + {"cy8ctma300", CY8CTMA300}, + {"cy8ctmg200", CY8CTMG200}, + {"cy8ctma340", CY8CTMA340}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cy8c_ts_id); + + +static struct i2c_driver cy8c_ts_driver = { + .driver = { + .name = "cy8c_ts", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &cy8c_ts_pm_ops, +#endif + }, + .probe = cy8c_ts_probe, + .remove = __devexit_p(cy8c_ts_remove), + .id_table = cy8c_ts_id, +}; + +static int __init cy8c_ts_init(void) +{ + return i2c_add_driver(&cy8c_ts_driver); +} +/* Making this as late init to avoid power fluctuations + * during LCD initialization. + */ +late_initcall(cy8c_ts_init); + +static void __exit cy8c_ts_exit(void) +{ + return i2c_del_driver(&cy8c_ts_driver); +} +module_exit(cy8c_ts_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CY8CTMA340-CY8CTMG200 touchscreen controller driver"); +MODULE_AUTHOR("Cypress"); +MODULE_ALIAS("platform:cy8c_ts"); diff --git a/include/linux/input/cy8c_ts.h b/include/linux/input/cy8c_ts.h new file mode 100644 index 00000000000..d25f31d66da --- /dev/null +++ b/include/linux/input/cy8c_ts.h @@ -0,0 +1,65 @@ +/* Header file for: + * Cypress CY8CTMA300 Prototype touchscreen driver. + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + * History: + * (C) 2010 Cypress - Update for GPL distribution + * (C) 2009 Cypress - Assume maintenance ownership + * (C) 2009 Enea - Original prototype + * + */ +#ifndef __CY8C8CTS_H__ +#define __CY8C8CTS_H__ + + +/* CY8CTMA300-TMG200 platform data + */ +struct cy8c_ts_platform_data { + int (*power_on)(int on); + int (*dev_setup)(bool on); + const char *ts_name; + u32 dis_min_x; /* display resoltion */ + u32 dis_max_x; + u32 dis_min_y; + u32 dis_max_y; + u32 min_touch; /* no.of touches supported */ + u32 max_touch; + u32 min_tid; /* track id */ + u32 max_tid; + u32 min_width;/* size of the finger */ + u32 max_width; + u32 res_x; /* TS resolution */ + u32 res_y; + u32 swap_xy; + u32 flags; + u16 invert_x; + u16 invert_y; + u8 nfingers; + u32 irq_gpio; + int resout_gpio; + bool wakeup; +}; + +#endif