| /* drivers/input/touchscreen/cy8c_tmg_ts.c |
| * |
| * Copyright (C) 2007-2008 HTC Corporation. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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/cy8c_tmg_ts.h> |
| #include <linux/delay.h> |
| #include <linux/earlysuspend.h> |
| #include <linux/hrtimer.h> |
| #include <linux/i2c.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| |
| #define CY8C_REG_START_NEW_SCAN 0x0F |
| #define CY8C_REG_INTR_STATUS 0x3C |
| #define CY8C_REG_VERSION 0x3E |
| |
| struct cy8c_ts_data { |
| struct i2c_client *client; |
| struct input_dev *input_dev; |
| int use_irq; |
| struct hrtimer timer; |
| struct work_struct work; |
| uint16_t version; |
| int (*power) (int on); |
| struct early_suspend early_suspend; |
| }; |
| |
| struct workqueue_struct *cypress_touch_wq; |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void cy8c_ts_early_suspend(struct early_suspend *h); |
| static void cy8c_ts_late_resume(struct early_suspend *h); |
| #endif |
| |
| uint16_t sample_count, X_mean, Y_mean, first_touch; |
| |
| static s32 cy8c_read_word_data(struct i2c_client *client, |
| u8 command, uint16_t * data) |
| { |
| s32 ret = i2c_smbus_read_word_data(client, command); |
| if (ret != -1) { |
| *data = (u16) ((ret << 8) | (ret >> 8)); |
| } |
| return ret; |
| } |
| |
| static int cy8c_init_panel(struct cy8c_ts_data *ts) |
| { |
| int ret; |
| sample_count = X_mean = Y_mean = first_touch = 0; |
| |
| /* clean intr busy */ |
| ret = i2c_smbus_write_byte_data(ts->client, CY8C_REG_INTR_STATUS, |
| 0x00); |
| if (ret < 0) { |
| dev_err(&ts->client->dev, |
| "cy8c_init_panel failed for clean intr busy\n"); |
| goto exit; |
| } |
| |
| /* start new scan */ |
| ret = i2c_smbus_write_byte_data(ts->client, CY8C_REG_START_NEW_SCAN, |
| 0x01); |
| if (ret < 0) { |
| dev_err(&ts->client->dev, |
| "cy8c_init_panel failed for start new scan\n"); |
| goto exit; |
| } |
| |
| exit: |
| return ret; |
| } |
| |
| static void cy8c_ts_reset(struct i2c_client *client) |
| { |
| struct cy8c_ts_data *ts = i2c_get_clientdata(client); |
| |
| if (ts->power) { |
| ts->power(0); |
| msleep(10); |
| ts->power(1); |
| msleep(10); |
| } |
| |
| cy8c_init_panel(ts); |
| } |
| |
| static void cy8c_ts_work_func(struct work_struct *work) |
| { |
| struct cy8c_ts_data *ts = container_of(work, struct cy8c_ts_data, work); |
| uint16_t x1, y1, x2, y2; |
| uint8_t is_touch, start_reg, force, area, finger2_pressed; |
| uint8_t buf[11]; |
| struct i2c_msg msg[2]; |
| int ret = 0; |
| |
| x2 = y2 = 0; |
| |
| /*printk("%s: enter\n",__func__);*/ |
| is_touch = i2c_smbus_read_byte_data(ts->client, 0x20); |
| dev_dbg(&ts->client->dev, "fIsTouch %d,\n", is_touch); |
| if (is_touch < 0 || is_touch > 3) { |
| pr_err("%s: invalid is_touch = %d\n", __func__, is_touch); |
| cy8c_ts_reset(ts->client); |
| msleep(10); |
| goto done; |
| } |
| |
| msg[0].addr = ts->client->addr; |
| msg[0].flags = 0; |
| msg[0].len = 1; |
| start_reg = 0x16; |
| msg[0].buf = &start_reg; |
| |
| msg[1].addr = ts->client->addr; |
| msg[1].flags = I2C_M_RD; |
| msg[1].len = sizeof(buf); |
| msg[1].buf = buf; |
| |
| ret = i2c_transfer(ts->client->adapter, msg, 2); |
| if (ret < 0) |
| goto done; |
| |
| /* parse data */ |
| force = buf[0]; |
| area = buf[1]; |
| x1 = (buf[2] << 8) | buf[3]; |
| y1 = (buf[6] << 8) | buf[7]; |
| is_touch = buf[10]; |
| |
| if (is_touch == 2) { |
| x2 = (buf[4] << 8) | buf[5]; |
| y2 = (buf[8] << 8) | buf[9]; |
| finger2_pressed = 1; |
| } |
| |
| dev_dbg(&ts->client->dev, |
| "bFingerForce %d, bFingerArea %d \n", force, area); |
| dev_dbg(&ts->client->dev, "x1: %d, y1: %d \n", x1, y1); |
| if (finger2_pressed) |
| dev_dbg(&ts->client->dev, "x2: %d, y2: %d \n", x2, y2); |
| |
| /* drop the first one? */ |
| if ((is_touch == 1) && (first_touch == 0)) { |
| first_touch = 1; |
| goto done; |
| } |
| |
| if (!first_touch) |
| goto done; |
| |
| if (is_touch == 2) |
| finger2_pressed = 1; |
| |
| input_report_abs(ts->input_dev, ABS_X, x1); |
| input_report_abs(ts->input_dev, ABS_Y, y1); |
| input_report_abs(ts->input_dev, ABS_PRESSURE, force); |
| input_report_abs(ts->input_dev, ABS_TOOL_WIDTH, area); |
| input_report_key(ts->input_dev, BTN_TOUCH, is_touch); |
| input_report_key(ts->input_dev, BTN_2, finger2_pressed); |
| |
| if (finger2_pressed) { |
| input_report_abs(ts->input_dev, ABS_HAT0X, x2); |
| input_report_abs(ts->input_dev, ABS_HAT0Y, y2); |
| } |
| input_sync(ts->input_dev); |
| |
| done: |
| if (is_touch == 0) |
| first_touch = sample_count = 0; |
| |
| /* prepare for next intr */ |
| i2c_smbus_write_byte_data(ts->client, CY8C_REG_INTR_STATUS, 0x00); |
| if (!ts->use_irq) |
| hrtimer_start(&ts->timer, ktime_set(0, 12500000), HRTIMER_MODE_REL); |
| else |
| enable_irq(ts->client->irq); |
| } |
| |
| static enum hrtimer_restart cy8c_ts_timer_func(struct hrtimer *timer) |
| { |
| struct cy8c_ts_data *ts; |
| |
| ts = container_of(timer, struct cy8c_ts_data, timer); |
| queue_work(cypress_touch_wq, &ts->work); |
| return HRTIMER_NORESTART; |
| } |
| |
| static irqreturn_t cy8c_ts_irq_handler(int irq, void *dev_id) |
| { |
| struct cy8c_ts_data *ts = dev_id; |
| |
| disable_irq_nosync(ts->client->irq); |
| queue_work(cypress_touch_wq, &ts->work); |
| return IRQ_HANDLED; |
| } |
| |
| static int cy8c_ts_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct cy8c_ts_data *ts; |
| struct cy8c_i2c_platform_data *pdata; |
| uint16_t panel_version; |
| int ret = 0; |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "need I2C_FUNC_I2C\n"); |
| ret = -ENODEV; |
| goto err_check_functionality_failed; |
| } |
| |
| ts = kzalloc(sizeof(struct cy8c_ts_data), GFP_KERNEL); |
| if (ts == NULL) { |
| dev_err(&client->dev, "allocate cy8c_ts_data failed\n"); |
| ret = -ENOMEM; |
| goto err_alloc_data_failed; |
| } |
| |
| INIT_WORK(&ts->work, cy8c_ts_work_func); |
| ts->client = client; |
| i2c_set_clientdata(client, ts); |
| |
| pdata = client->dev.platform_data; |
| if (pdata) { |
| ts->version = pdata->version; |
| ts->power = pdata->power; |
| } |
| |
| if (ts->power) { |
| ret = ts->power(1); |
| msleep(10); |
| if (ret < 0) { |
| dev_err(&client->dev, "power on failed\n"); |
| goto err_power_failed; |
| } |
| } |
| |
| ret = cy8c_read_word_data(ts->client, CY8C_REG_VERSION, &panel_version); |
| if (ret < 0) { |
| dev_err(&client->dev, "init panel failed\n"); |
| goto err_detect_failed; |
| } |
| dev_info(&client->dev, "Panel Version %04X\n", panel_version); |
| if (pdata) { |
| while (pdata->version > panel_version) { |
| dev_info(&client->dev, "old tp detected, " |
| "panel version = %x\n", panel_version); |
| pdata++; |
| } |
| } |
| |
| ret = cy8c_init_panel(ts); |
| if (ret < 0) { |
| dev_err(&client->dev, "init panel failed\n"); |
| goto err_detect_failed; |
| } |
| |
| ts->input_dev = input_allocate_device(); |
| if (ts->input_dev == NULL) { |
| ret = -ENOMEM; |
| dev_err(&client->dev, "Failed to allocate input device\n"); |
| goto err_input_dev_alloc_failed; |
| } |
| ts->input_dev->name = "cy8c-touchscreen"; |
| |
| set_bit(EV_SYN, ts->input_dev->evbit); |
| set_bit(EV_ABS, ts->input_dev->evbit); |
| set_bit(EV_KEY, ts->input_dev->evbit); |
| input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); |
| input_set_capability(ts->input_dev, EV_KEY, BTN_2); |
| |
| input_set_abs_params(ts->input_dev, ABS_X, |
| pdata->abs_x_min, pdata->abs_x_max, 5, 0); |
| input_set_abs_params(ts->input_dev, ABS_Y, |
| pdata->abs_y_min, pdata->abs_y_max, 5, 0); |
| input_set_abs_params(ts->input_dev, ABS_HAT0X, |
| pdata->abs_x_min, pdata->abs_x_max, 0, 0); |
| input_set_abs_params(ts->input_dev, ABS_HAT0Y, |
| pdata->abs_y_min, pdata->abs_y_max, 0, 0); |
| input_set_abs_params(ts->input_dev, ABS_PRESSURE, |
| pdata->abs_pressure_min, pdata->abs_pressure_max, |
| 0, 0); |
| input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, |
| pdata->abs_width_min, pdata->abs_width_max, 0, 0); |
| |
| ret = input_register_device(ts->input_dev); |
| if (ret) { |
| dev_err(&client->dev, |
| "cy8c_ts_probe: Unable to register %s input device\n", |
| ts->input_dev->name); |
| goto err_input_register_device_failed; |
| } |
| |
| if (client->irq) { |
| ret = request_irq(client->irq, cy8c_ts_irq_handler, |
| IRQF_TRIGGER_LOW, CYPRESS_TMG_NAME, ts); |
| if (ret == 0) |
| ts->use_irq = 1; |
| else |
| dev_err(&client->dev, "request_irq failed\n"); |
| } |
| |
| if (!ts->use_irq) { |
| hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); |
| ts->timer.function = cy8c_ts_timer_func; |
| hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; |
| ts->early_suspend.suspend = cy8c_ts_early_suspend; |
| ts->early_suspend.resume = cy8c_ts_late_resume; |
| register_early_suspend(&ts->early_suspend); |
| #endif |
| |
| dev_info(&client->dev, "Start touchscreen %s in %s mode\n", |
| ts->input_dev->name, (ts->use_irq ? "interrupt" : "polling")); |
| |
| return 0; |
| |
| err_input_register_device_failed: |
| input_free_device(ts->input_dev); |
| |
| err_input_dev_alloc_failed: |
| if (ts->power) |
| ts->power(0); |
| |
| err_detect_failed: |
| err_power_failed: |
| kfree(ts); |
| |
| err_alloc_data_failed: |
| err_check_functionality_failed: |
| return ret; |
| } |
| |
| static int cy8c_ts_remove(struct i2c_client *client) |
| { |
| struct cy8c_ts_data *ts = i2c_get_clientdata(client); |
| |
| unregister_early_suspend(&ts->early_suspend); |
| |
| if (ts->use_irq) |
| free_irq(client->irq, ts); |
| else |
| hrtimer_cancel(&ts->timer); |
| |
| input_unregister_device(ts->input_dev); |
| kfree(ts); |
| |
| return 0; |
| } |
| |
| static int cy8c_ts_suspend(struct i2c_client *client, pm_message_t mesg) |
| { |
| struct cy8c_ts_data *ts = i2c_get_clientdata(client); |
| int ret; |
| |
| if (ts->use_irq) |
| disable_irq_nosync(client->irq); |
| else |
| hrtimer_cancel(&ts->timer); |
| |
| ret = cancel_work_sync(&ts->work); |
| if (ret && ts->use_irq) |
| enable_irq(client->irq); |
| |
| if (ts->power) |
| ts->power(0); |
| |
| return 0; |
| } |
| |
| static int cy8c_ts_resume(struct i2c_client *client) |
| { |
| int ret; |
| struct cy8c_ts_data *ts = i2c_get_clientdata(client); |
| |
| if (ts->power) { |
| ret = ts->power(1); |
| if (ret < 0) |
| dev_err(&client->dev, |
| "cy8c_ts_resume power on failed\n"); |
| msleep(10); |
| |
| cy8c_init_panel(ts); |
| } |
| |
| if (ts->use_irq) |
| enable_irq(client->irq); |
| else |
| hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void cy8c_ts_early_suspend(struct early_suspend *h) |
| { |
| struct cy8c_ts_data *ts; |
| ts = container_of(h, struct cy8c_ts_data, early_suspend); |
| cy8c_ts_suspend(ts->client, PMSG_SUSPEND); |
| } |
| |
| static void cy8c_ts_late_resume(struct early_suspend *h) |
| { |
| struct cy8c_ts_data *ts; |
| ts = container_of(h, struct cy8c_ts_data, early_suspend); |
| cy8c_ts_resume(ts->client); |
| } |
| #endif |
| |
| static const struct i2c_device_id cy8c_ts_i2c_id[] = { |
| {CYPRESS_TMG_NAME, 0}, |
| {} |
| }; |
| |
| static struct i2c_driver cy8c_ts_driver = { |
| .id_table = cy8c_ts_i2c_id, |
| .probe = cy8c_ts_probe, |
| .remove = cy8c_ts_remove, |
| #ifndef CONFIG_HAS_EARLYSUSPEND |
| .suspend = cy8c_ts_suspend, |
| .resume = cy8c_ts_resume, |
| #endif |
| .driver = { |
| .name = CYPRESS_TMG_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __devinit cy8c_ts_init(void) |
| { |
| cypress_touch_wq = create_singlethread_workqueue("cypress_touch_wq"); |
| if (!cypress_touch_wq) |
| return -ENOMEM; |
| |
| return i2c_add_driver(&cy8c_ts_driver); |
| } |
| |
| static void __exit cy8c_ts_exit(void) |
| { |
| if (cypress_touch_wq) |
| destroy_workqueue(cypress_touch_wq); |
| |
| i2c_del_driver(&cy8c_ts_driver); |
| } |
| |
| module_init(cy8c_ts_init); |
| module_exit(cy8c_ts_exit); |
| |
| MODULE_DESCRIPTION("Cypress TMG Touchscreen Driver"); |
| MODULE_LICENSE("GPL"); |
| |