/* Source for: * Cypress CY8CTMA300 Prototype touchscreen driver. * drivers/input/touchscreen/cy8c_ts.c * * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. * Copyright (c) 2010-2012 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. * * 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); __set_bit(INPUT_PROP_DIRECT, input_device->propbit); 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");