| /* |
| * |
| * FocalTech ft5x06 TouchScreen driver. |
| * |
| * Copyright (c) 2010 Focal tech Ltd. |
| * Copyright (c) 2012, Code Aurora Forum. All rights reserved. |
| * |
| * 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/i2c.h> |
| #include <linux/input.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/gpio.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/input/ft5x06_ts.h> |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| #include <linux/earlysuspend.h> |
| /* Early-suspend level */ |
| #define FT5X06_SUSPEND_LEVEL 1 |
| #endif |
| |
| #define CFG_MAX_TOUCH_POINTS 5 |
| |
| #define FT_STARTUP_DLY 150 |
| #define FT_RESET_DLY 20 |
| |
| #define FT_PRESS 0x7F |
| #define FT_MAX_ID 0x0F |
| #define FT_TOUCH_STEP 6 |
| #define FT_TOUCH_X_H_POS 3 |
| #define FT_TOUCH_X_L_POS 4 |
| #define FT_TOUCH_Y_H_POS 5 |
| #define FT_TOUCH_Y_L_POS 6 |
| #define FT_TOUCH_EVENT_POS 3 |
| #define FT_TOUCH_ID_POS 5 |
| |
| #define POINT_READ_BUF (3 + FT_TOUCH_STEP * CFG_MAX_TOUCH_POINTS) |
| |
| /*register address*/ |
| #define FT5X06_REG_PMODE 0xA5 |
| #define FT5X06_REG_FW_VER 0xA6 |
| #define FT5X06_REG_POINT_RATE 0x88 |
| #define FT5X06_REG_THGROUP 0x80 |
| |
| /* power register bits*/ |
| #define FT5X06_PMODE_ACTIVE 0x00 |
| #define FT5X06_PMODE_MONITOR 0x01 |
| #define FT5X06_PMODE_STANDBY 0x02 |
| #define FT5X06_PMODE_HIBERNATE 0x03 |
| |
| #define FT5X06_VTG_MIN_UV 2600000 |
| #define FT5X06_VTG_MAX_UV 3300000 |
| #define FT5X06_I2C_VTG_MIN_UV 1800000 |
| #define FT5X06_I2C_VTG_MAX_UV 1800000 |
| |
| struct ts_event { |
| u16 x[CFG_MAX_TOUCH_POINTS]; /*x coordinate */ |
| u16 y[CFG_MAX_TOUCH_POINTS]; /*y coordinate */ |
| /* touch event: 0 -- down; 1-- contact; 2 -- contact */ |
| u8 touch_event[CFG_MAX_TOUCH_POINTS]; |
| u8 finger_id[CFG_MAX_TOUCH_POINTS]; /*touch ID */ |
| u16 pressure; |
| u8 touch_point; |
| }; |
| |
| struct ft5x06_ts_data { |
| struct i2c_client *client; |
| struct input_dev *input_dev; |
| struct ts_event event; |
| const struct ft5x06_ts_platform_data *pdata; |
| struct regulator *vdd; |
| struct regulator *vcc_i2c; |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| struct early_suspend early_suspend; |
| #endif |
| }; |
| |
| static int ft5x06_i2c_read(struct i2c_client *client, char *writebuf, |
| int writelen, char *readbuf, int readlen) |
| { |
| int ret; |
| |
| if (writelen > 0) { |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = 0, |
| .len = writelen, |
| .buf = writebuf, |
| }, |
| { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| .len = readlen, |
| .buf = readbuf, |
| }, |
| }; |
| ret = i2c_transfer(client->adapter, msgs, 2); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: i2c read error.\n", |
| __func__); |
| } else { |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = I2C_M_RD, |
| .len = readlen, |
| .buf = readbuf, |
| }, |
| }; |
| ret = i2c_transfer(client->adapter, msgs, 1); |
| if (ret < 0) |
| dev_err(&client->dev, "%s:i2c read error.\n", __func__); |
| } |
| return ret; |
| } |
| |
| static int ft5x06_i2c_write(struct i2c_client *client, char *writebuf, |
| int writelen) |
| { |
| int ret; |
| |
| struct i2c_msg msgs[] = { |
| { |
| .addr = client->addr, |
| .flags = 0, |
| .len = writelen, |
| .buf = writebuf, |
| }, |
| }; |
| ret = i2c_transfer(client->adapter, msgs, 1); |
| if (ret < 0) |
| dev_err(&client->dev, "%s: i2c write error.\n", __func__); |
| |
| return ret; |
| } |
| |
| static void ft5x06_report_value(struct ft5x06_ts_data *data) |
| { |
| struct ts_event *event = &data->event; |
| int i; |
| int fingerdown = 0; |
| |
| for (i = 0; i < event->touch_point; i++) { |
| if (event->touch_event[i] == 0 || event->touch_event[i] == 2) { |
| event->pressure = FT_PRESS; |
| fingerdown++; |
| } else { |
| event->pressure = 0; |
| } |
| |
| input_report_abs(data->input_dev, ABS_MT_POSITION_X, |
| event->x[i]); |
| input_report_abs(data->input_dev, ABS_MT_POSITION_Y, |
| event->y[i]); |
| input_report_abs(data->input_dev, ABS_MT_PRESSURE, |
| event->pressure); |
| input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, |
| event->finger_id[i]); |
| input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, |
| event->pressure); |
| input_mt_sync(data->input_dev); |
| } |
| |
| input_report_key(data->input_dev, BTN_TOUCH, !!fingerdown); |
| input_sync(data->input_dev); |
| } |
| |
| static int ft5x06_handle_touchdata(struct ft5x06_ts_data *data) |
| { |
| struct ts_event *event = &data->event; |
| int ret, i; |
| u8 buf[POINT_READ_BUF] = { 0 }; |
| u8 pointid = FT_MAX_ID; |
| |
| ret = ft5x06_i2c_read(data->client, buf, 1, buf, POINT_READ_BUF); |
| if (ret < 0) { |
| dev_err(&data->client->dev, "%s read touchdata failed.\n", |
| __func__); |
| return ret; |
| } |
| memset(event, 0, sizeof(struct ts_event)); |
| |
| event->touch_point = 0; |
| for (i = 0; i < CFG_MAX_TOUCH_POINTS; i++) { |
| pointid = (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4; |
| if (pointid >= FT_MAX_ID) |
| break; |
| else |
| event->touch_point++; |
| event->x[i] = |
| (s16) (buf[FT_TOUCH_X_H_POS + FT_TOUCH_STEP * i] & 0x0F) << |
| 8 | (s16) buf[FT_TOUCH_X_L_POS + FT_TOUCH_STEP * i]; |
| event->y[i] = |
| (s16) (buf[FT_TOUCH_Y_H_POS + FT_TOUCH_STEP * i] & 0x0F) << |
| 8 | (s16) buf[FT_TOUCH_Y_L_POS + FT_TOUCH_STEP * i]; |
| event->touch_event[i] = |
| buf[FT_TOUCH_EVENT_POS + FT_TOUCH_STEP * i] >> 6; |
| event->finger_id[i] = |
| (buf[FT_TOUCH_ID_POS + FT_TOUCH_STEP * i]) >> 4; |
| } |
| |
| ft5x06_report_value(data); |
| |
| return 0; |
| } |
| |
| static irqreturn_t ft5x06_ts_interrupt(int irq, void *dev_id) |
| { |
| struct ft5x06_ts_data *data = dev_id; |
| int rc; |
| |
| rc = ft5x06_handle_touchdata(data); |
| if (rc) |
| pr_err("%s: handling touchdata failed\n", __func__); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int ft5x06_power_on(struct ft5x06_ts_data *data, bool on) |
| { |
| int rc; |
| |
| if (!on) |
| goto power_off; |
| |
| rc = regulator_enable(data->vdd); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vdd enable failed rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = regulator_enable(data->vcc_i2c); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vcc_i2c enable failed rc=%d\n", rc); |
| regulator_disable(data->vdd); |
| } |
| |
| return rc; |
| |
| power_off: |
| rc = regulator_disable(data->vdd); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vdd disable failed rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = regulator_disable(data->vcc_i2c); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vcc_i2c disable failed rc=%d\n", rc); |
| regulator_enable(data->vdd); |
| } |
| |
| return rc; |
| } |
| |
| static int ft5x06_power_init(struct ft5x06_ts_data *data, bool on) |
| { |
| int rc; |
| |
| if (!on) |
| goto pwr_deinit; |
| |
| data->vdd = regulator_get(&data->client->dev, "vdd"); |
| if (IS_ERR(data->vdd)) { |
| rc = PTR_ERR(data->vdd); |
| dev_err(&data->client->dev, |
| "Regulator get failed vdd rc=%d\n", rc); |
| return rc; |
| } |
| |
| if (regulator_count_voltages(data->vdd) > 0) { |
| rc = regulator_set_voltage(data->vdd, FT5X06_VTG_MIN_UV, |
| FT5X06_VTG_MAX_UV); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator set_vtg failed vdd rc=%d\n", rc); |
| goto reg_vdd_put; |
| } |
| } |
| |
| data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c"); |
| if (IS_ERR(data->vcc_i2c)) { |
| rc = PTR_ERR(data->vcc_i2c); |
| dev_err(&data->client->dev, |
| "Regulator get failed vcc_i2c rc=%d\n", rc); |
| goto reg_vdd_set_vtg; |
| } |
| |
| if (regulator_count_voltages(data->vcc_i2c) > 0) { |
| rc = regulator_set_voltage(data->vcc_i2c, FT5X06_I2C_VTG_MIN_UV, |
| FT5X06_I2C_VTG_MAX_UV); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); |
| goto reg_vcc_i2c_put; |
| } |
| } |
| |
| return 0; |
| |
| reg_vcc_i2c_put: |
| regulator_put(data->vcc_i2c); |
| reg_vdd_set_vtg: |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV); |
| reg_vdd_put: |
| regulator_put(data->vdd); |
| return rc; |
| |
| pwr_deinit: |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, FT5X06_VTG_MAX_UV); |
| |
| regulator_put(data->vdd); |
| |
| if (regulator_count_voltages(data->vcc_i2c) > 0) |
| regulator_set_voltage(data->vcc_i2c, 0, FT5X06_I2C_VTG_MAX_UV); |
| |
| regulator_put(data->vcc_i2c); |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int ft5x06_ts_suspend(struct device *dev) |
| { |
| struct ft5x06_ts_data *data = dev_get_drvdata(dev); |
| char txbuf[2]; |
| |
| disable_irq(data->client->irq); |
| |
| if (gpio_is_valid(data->pdata->reset_gpio)) { |
| txbuf[0] = FT5X06_REG_PMODE; |
| txbuf[1] = FT5X06_PMODE_HIBERNATE; |
| ft5x06_i2c_write(data->client, txbuf, sizeof(txbuf)); |
| } |
| |
| return 0; |
| } |
| |
| static int ft5x06_ts_resume(struct device *dev) |
| { |
| struct ft5x06_ts_data *data = dev_get_drvdata(dev); |
| |
| if (gpio_is_valid(data->pdata->reset_gpio)) { |
| gpio_set_value_cansleep(data->pdata->reset_gpio, 0); |
| msleep(FT_RESET_DLY); |
| gpio_set_value_cansleep(data->pdata->reset_gpio, 1); |
| } |
| enable_irq(data->client->irq); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| static void ft5x06_ts_early_suspend(struct early_suspend *handler) |
| { |
| struct ft5x06_ts_data *data = container_of(handler, |
| struct ft5x06_ts_data, |
| early_suspend); |
| |
| ft5x06_ts_suspend(&data->client->dev); |
| } |
| |
| static void ft5x06_ts_late_resume(struct early_suspend *handler) |
| { |
| struct ft5x06_ts_data *data = container_of(handler, |
| struct ft5x06_ts_data, |
| early_suspend); |
| |
| ft5x06_ts_resume(&data->client->dev); |
| } |
| #endif |
| |
| static const struct dev_pm_ops ft5x06_ts_pm_ops = { |
| #ifndef CONFIG_HAS_EARLYSUSPEND |
| .suspend = ft5x06_ts_suspend, |
| .resume = ft5x06_ts_resume, |
| #endif |
| }; |
| #endif |
| |
| static int ft5x06_ts_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| const struct ft5x06_ts_platform_data *pdata = client->dev.platform_data; |
| struct ft5x06_ts_data *data; |
| struct input_dev *input_dev; |
| u8 reg_value; |
| u8 reg_addr; |
| int err; |
| |
| if (!pdata) { |
| dev_err(&client->dev, "Invalid pdata\n"); |
| return -EINVAL; |
| } |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "I2C not supported\n"); |
| return -ENODEV; |
| } |
| |
| data = kzalloc(sizeof(struct ft5x06_ts_data), GFP_KERNEL); |
| if (!data) { |
| dev_err(&client->dev, "Not enough memory\n"); |
| return -ENOMEM; |
| } |
| |
| input_dev = input_allocate_device(); |
| if (!input_dev) { |
| err = -ENOMEM; |
| dev_err(&client->dev, "failed to allocate input device\n"); |
| goto free_mem; |
| } |
| |
| data->input_dev = input_dev; |
| data->client = client; |
| data->pdata = pdata; |
| |
| input_dev->name = "ft5x06_ts"; |
| input_dev->id.bustype = BUS_I2C; |
| input_dev->dev.parent = &client->dev; |
| |
| input_set_drvdata(input_dev, data); |
| i2c_set_clientdata(client, data); |
| |
| __set_bit(EV_KEY, input_dev->evbit); |
| __set_bit(EV_ABS, input_dev->evbit); |
| __set_bit(BTN_TOUCH, input_dev->keybit); |
| __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); |
| |
| input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, |
| pdata->x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, |
| pdata->y_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, |
| CFG_MAX_TOUCH_POINTS, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, FT_PRESS, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, FT_PRESS, 0, 0); |
| |
| err = input_register_device(input_dev); |
| if (err) { |
| dev_err(&client->dev, "Input device registration failed\n"); |
| goto free_inputdev; |
| } |
| |
| if (pdata->power_init) { |
| err = pdata->power_init(true); |
| if (err) { |
| dev_err(&client->dev, "power init failed"); |
| goto unreg_inputdev; |
| } |
| } else { |
| err = ft5x06_power_init(data, true); |
| if (err) { |
| dev_err(&client->dev, "power init failed"); |
| goto unreg_inputdev; |
| } |
| } |
| |
| if (pdata->power_on) { |
| err = pdata->power_on(true); |
| if (err) { |
| dev_err(&client->dev, "power on failed"); |
| goto pwr_deinit; |
| } |
| } else { |
| err = ft5x06_power_on(data, true); |
| if (err) { |
| dev_err(&client->dev, "power on failed"); |
| goto pwr_deinit; |
| } |
| } |
| |
| if (gpio_is_valid(pdata->irq_gpio)) { |
| err = gpio_request(pdata->irq_gpio, "ft5x06_irq_gpio"); |
| if (err) { |
| dev_err(&client->dev, "irq gpio request failed"); |
| goto pwr_off; |
| } |
| err = gpio_direction_input(pdata->irq_gpio); |
| if (err) { |
| dev_err(&client->dev, |
| "set_direction for irq gpio failed\n"); |
| goto free_irq_gpio; |
| } |
| } |
| |
| if (gpio_is_valid(pdata->reset_gpio)) { |
| err = gpio_request(pdata->reset_gpio, "ft5x06_reset_gpio"); |
| if (err) { |
| dev_err(&client->dev, "reset gpio request failed"); |
| goto free_irq_gpio; |
| } |
| |
| err = gpio_direction_output(pdata->reset_gpio, 0); |
| if (err) { |
| dev_err(&client->dev, |
| "set_direction for reset gpio failed\n"); |
| goto free_reset_gpio; |
| } |
| msleep(FT_RESET_DLY); |
| gpio_set_value_cansleep(data->pdata->reset_gpio, 1); |
| } |
| |
| /* make sure CTP already finish startup process */ |
| msleep(FT_STARTUP_DLY); |
| |
| /*get some register information */ |
| reg_addr = FT5X06_REG_FW_VER; |
| err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); |
| if (err) |
| dev_err(&client->dev, "version read failed"); |
| |
| dev_info(&client->dev, "[FTS] Firmware version = 0x%x\n", reg_value); |
| |
| reg_addr = FT5X06_REG_POINT_RATE; |
| ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); |
| if (err) |
| dev_err(&client->dev, "report rate read failed"); |
| dev_info(&client->dev, "[FTS] report rate is %dHz.\n", reg_value * 10); |
| |
| reg_addr = FT5X06_REG_THGROUP; |
| err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); |
| if (err) |
| dev_err(&client->dev, "threshold read failed"); |
| dev_dbg(&client->dev, "[FTS] touch threshold is %d.\n", reg_value * 4); |
| |
| err = request_threaded_irq(client->irq, NULL, |
| ft5x06_ts_interrupt, pdata->irqflags, |
| client->dev.driver->name, data); |
| if (err) { |
| dev_err(&client->dev, "request irq failed\n"); |
| goto free_reset_gpio; |
| } |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + |
| FT5X06_SUSPEND_LEVEL; |
| data->early_suspend.suspend = ft5x06_ts_early_suspend; |
| data->early_suspend.resume = ft5x06_ts_late_resume; |
| register_early_suspend(&data->early_suspend); |
| #endif |
| |
| return 0; |
| |
| free_reset_gpio: |
| if (gpio_is_valid(pdata->reset_gpio)) |
| gpio_free(pdata->reset_gpio); |
| free_irq_gpio: |
| if (gpio_is_valid(pdata->irq_gpio)) |
| gpio_free(pdata->reset_gpio); |
| pwr_off: |
| if (pdata->power_on) |
| pdata->power_on(false); |
| else |
| ft5x06_power_on(data, false); |
| pwr_deinit: |
| if (pdata->power_init) |
| pdata->power_init(false); |
| else |
| ft5x06_power_init(data, false); |
| unreg_inputdev: |
| input_unregister_device(input_dev); |
| input_dev = NULL; |
| free_inputdev: |
| input_free_device(input_dev); |
| free_mem: |
| kfree(data); |
| return err; |
| } |
| |
| static int __devexit ft5x06_ts_remove(struct i2c_client *client) |
| { |
| struct ft5x06_ts_data *data = i2c_get_clientdata(client); |
| |
| #ifdef CONFIG_HAS_EARLYSUSPEND |
| unregister_early_suspend(&data->early_suspend); |
| #endif |
| free_irq(client->irq, data); |
| |
| if (gpio_is_valid(data->pdata->reset_gpio)) |
| gpio_free(data->pdata->reset_gpio); |
| |
| if (gpio_is_valid(data->pdata->irq_gpio)) |
| gpio_free(data->pdata->reset_gpio); |
| |
| if (data->pdata->power_on) |
| data->pdata->power_on(false); |
| else |
| ft5x06_power_on(data, false); |
| |
| if (data->pdata->power_init) |
| data->pdata->power_init(false); |
| else |
| ft5x06_power_init(data, false); |
| |
| input_unregister_device(data->input_dev); |
| kfree(data); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id ft5x06_ts_id[] = { |
| {"ft5x06_ts", 0}, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id); |
| |
| static struct i2c_driver ft5x06_ts_driver = { |
| .probe = ft5x06_ts_probe, |
| .remove = __devexit_p(ft5x06_ts_remove), |
| .driver = { |
| .name = "ft5x06_ts", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &ft5x06_ts_pm_ops, |
| #endif |
| }, |
| .id_table = ft5x06_ts_id, |
| }; |
| |
| static int __init ft5x06_ts_init(void) |
| { |
| return i2c_add_driver(&ft5x06_ts_driver); |
| } |
| module_init(ft5x06_ts_init); |
| |
| static void __exit ft5x06_ts_exit(void) |
| { |
| i2c_del_driver(&ft5x06_ts_driver); |
| } |
| module_exit(ft5x06_ts_exit); |
| |
| MODULE_DESCRIPTION("FocalTech ft5x06 TouchScreen driver"); |
| MODULE_LICENSE("GPL v2"); |