| /* 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 <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/input.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/gpio.h> |
| #include <linux/workqueue.h> |
| #include <linux/mutex.h> |
| #include <linux/delay.h> |
| #include <linux/input/cy8c_ts.h> |
| #include <linux/pm.h> |
| #include <linux/pm_runtime.h> |
| |
| #if defined(CONFIG_HAS_EARLYSUSPEND) |
| #include <linux/earlysuspend.h> |
| |
| /* 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_TOUCH_MAJOR, pressure); |
| input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, ts->dd->finger_size); |
| 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_TOUCH_MAJOR, 0); |
| input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 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_TOUCH_MAJOR, 0); |
| input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 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_TOUCH_MAJOR, |
| ts->pdata->min_touch, ts->pdata->max_touch, 0, 0); |
| input_set_abs_params(input_device, ABS_MT_WIDTH_MAJOR, |
| ts->pdata->min_width, ts->pdata->max_width, 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"); |