| /* |
| * |
| * FocalTech TouchScreen driver. |
| * |
| * Copyright (c) 2012-2018, FocalTech Systems, Ltd., 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. |
| * |
| */ |
| /***************************************************************************** |
| * |
| * File Name: focaltech_core.c |
| * |
| * Author: Focaltech Driver Team |
| * |
| * Created: 2016-08-08 |
| * |
| * Abstract: entrance for focaltech ts driver |
| * |
| * Version: V1.0 |
| * |
| *****************************************************************************/ |
| |
| /***************************************************************************** |
| * Included header files |
| *****************************************************************************/ |
| #include "focaltech_core.h" |
| #if defined(CONFIG_FB) |
| #include <linux/notifier.h> |
| #include <linux/fb.h> |
| #elif defined(CONFIG_HAS_EARLYSUSPEND) |
| #include <linux/earlysuspend.h> |
| #define FTS_SUSPEND_LEVEL 1 /* Early-suspend level */ |
| #endif |
| |
| /***************************************************************************** |
| * Private constant and macro definitions using #define |
| *****************************************************************************/ |
| #define FTS_DRIVER_NAME "fts_ts" |
| #define INTERVAL_READ_REG 100 /* unit:ms */ |
| #define TIMEOUT_READ_REG 1000 /* unit:ms */ |
| #if FTS_POWER_SOURCE_CUST_EN |
| #define FTS_VTG_MIN_UV 2600000 |
| #define FTS_VTG_MAX_UV 3300000 |
| #define FTS_I2C_VTG_MIN_UV 1800000 |
| #define FTS_I2C_VTG_MAX_UV 1800000 |
| #endif |
| |
| /***************************************************************************** |
| * Global variable or extern global variabls/functions |
| *****************************************************************************/ |
| struct fts_ts_data *fts_data; |
| |
| /***************************************************************************** |
| * Static function prototypes |
| *****************************************************************************/ |
| static void fts_release_all_finger(void); |
| static int fts_ts_suspend(struct device *dev); |
| static int fts_ts_resume(struct device *dev); |
| |
| |
| /***************************************************************************** |
| * Name: fts_wait_tp_to_valid |
| * Brief: Read chip id until TP FW become valid(Timeout: TIMEOUT_READ_REG), |
| * need call when reset/power on/resume... |
| * Input: |
| * Output: |
| * Return: return 0 if tp valid, otherwise return error code |
| *****************************************************************************/ |
| int fts_wait_tp_to_valid(struct i2c_client *client) |
| { |
| int ret = 0; |
| int cnt = 0; |
| u8 reg_value = 0; |
| u8 chip_id = fts_data->ic_info.ids.chip_idh; |
| |
| do { |
| ret = fts_i2c_read_reg(client, FTS_REG_CHIP_ID, ®_value); |
| if ((ret < 0) || (reg_value != chip_id)) { |
| FTS_INFO("TP Not Ready, ReadData = 0x%x", reg_value); |
| } else if (reg_value == chip_id) { |
| FTS_INFO("TP Ready, Device ID = 0x%x", reg_value); |
| return 0; |
| } |
| cnt++; |
| msleep(INTERVAL_READ_REG); |
| } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG); |
| |
| return -EIO; |
| } |
| |
| /************************************************************************ |
| * Name: fts_get_chip_types |
| * Brief: verity chip id and get chip type data |
| * Input: |
| * Output: |
| * Return: return 0 if success, otherwise return error code |
| ***********************************************************************/ |
| static int fts_get_chip_types( |
| struct fts_ts_data *ts_data, |
| u8 id_h, u8 id_l, bool fw_valid) |
| { |
| int i = 0; |
| struct ft_chip_t ctype[] = FTS_CHIP_TYPE_MAPPING; |
| u32 ctype_entries = sizeof(ctype) / sizeof(struct ft_chip_t); |
| |
| if ((0x0 == id_h) || (0x0 == id_l)) { |
| FTS_ERROR("id_h/id_l is 0"); |
| return -EINVAL; |
| } |
| |
| FTS_INFO("verify id:0x%02x%02x", id_h, id_l); |
| for (i = 0; i < ctype_entries; i++) { |
| if (VALID == fw_valid) { |
| if ((id_h == ctype[i].chip_idh) && (id_l == ctype[i].chip_idl)) |
| break; |
| } else { |
| if (((id_h == ctype[i].rom_idh) && (id_l == ctype[i].rom_idl)) |
| || ((id_h == ctype[i].pb_idh) && (id_l == ctype[i].pb_idl)) |
| || ((id_h == ctype[i].bl_idh) && (id_l == ctype[i].bl_idl))) |
| break; |
| } |
| } |
| |
| if (i >= ctype_entries) { |
| return -ENODATA; |
| } |
| |
| ts_data->ic_info.ids = ctype[i]; |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_get_ic_information |
| * Brief: |
| * Input: |
| * Output: |
| * Return: return 0 if success, otherwise return error code |
| *****************************************************************************/ |
| static int fts_get_ic_information(struct fts_ts_data *ts_data) |
| { |
| int ret = 0; |
| int cnt = 0; |
| u8 chip_id[2] = { 0 }; |
| u8 id_cmd[4] = { 0 }; |
| u32 id_cmd_len = 0; |
| struct i2c_client *client = ts_data->client; |
| |
| ts_data->ic_info.is_incell = FTS_CHIP_IDC; |
| ts_data->ic_info.hid_supported = FTS_HID_SUPPORTTED; |
| do { |
| ret = fts_i2c_read_reg(client, FTS_REG_CHIP_ID, &chip_id[0]); |
| ret = fts_i2c_read_reg(client, FTS_REG_CHIP_ID2, &chip_id[1]); |
| if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) { |
| FTS_INFO("i2c read invalid, read:0x%02x%02x", chip_id[0], chip_id[1]); |
| } else { |
| ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID); |
| if (!ret) |
| break; |
| else |
| FTS_INFO("TP not ready, read:0x%02x%02x", chip_id[0], chip_id[1]); |
| } |
| |
| cnt++; |
| msleep(INTERVAL_READ_REG); |
| } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG); |
| |
| if ((cnt * INTERVAL_READ_REG) >= TIMEOUT_READ_REG) { |
| FTS_INFO("fw is invalid, need read boot id"); |
| if (ts_data->ic_info.hid_supported) { |
| fts_i2c_hid2std(client); |
| } |
| |
| id_cmd[0] = FTS_CMD_START1; |
| id_cmd[1] = FTS_CMD_START2; |
| ret = fts_i2c_write(client, id_cmd, 2); |
| if (ret < 0) { |
| FTS_ERROR("start cmd write fail"); |
| return ret; |
| } |
| |
| msleep(FTS_CMD_START_DELAY); |
| id_cmd[0] = FTS_CMD_READ_ID; |
| id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00; |
| if (ts_data->ic_info.is_incell) |
| id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL; |
| else |
| id_cmd_len = FTS_CMD_READ_ID_LEN; |
| ret = fts_i2c_read(client, id_cmd, id_cmd_len, chip_id, 2); |
| if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) { |
| FTS_ERROR("read boot id fail"); |
| return -EIO; |
| } |
| ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID); |
| if (ret < 0) { |
| FTS_ERROR("can't get ic informaton"); |
| return ret; |
| } |
| } |
| |
| FTS_INFO("get ic information, chip id = 0x%02x%02x", |
| ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl); |
| |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_tp_state_recovery |
| * Brief: Need execute this function when reset |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| void fts_tp_state_recovery(struct i2c_client *client) |
| { |
| FTS_FUNC_ENTER(); |
| /* wait tp stable */ |
| fts_wait_tp_to_valid(client); |
| /* recover TP charger state 0x8B */ |
| /* recover TP glove state 0xC0 */ |
| /* recover TP cover state 0xC1 */ |
| fts_ex_mode_recovery(client); |
| /* recover TP gesture state 0xD0 */ |
| #if FTS_GESTURE_EN |
| fts_gesture_recovery(client); |
| #endif |
| FTS_FUNC_EXIT(); |
| } |
| |
| /***************************************************************************** |
| * Name: fts_reset_proc |
| * Brief: Execute reset operation |
| * Input: hdelayms - delay time unit:ms |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| int fts_reset_proc(int hdelayms) |
| { |
| FTS_FUNC_ENTER(); |
| gpio_direction_output(fts_data->pdata->reset_gpio, 0); |
| msleep(20); |
| gpio_direction_output(fts_data->pdata->reset_gpio, 1); |
| if (hdelayms) { |
| msleep(hdelayms); |
| } |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_irq_disable |
| * Brief: disable irq |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| void fts_irq_disable(void) |
| { |
| unsigned long irqflags; |
| |
| FTS_FUNC_ENTER(); |
| spin_lock_irqsave(&fts_data->irq_lock, irqflags); |
| |
| if (!fts_data->irq_disabled) { |
| disable_irq_nosync(fts_data->irq); |
| fts_data->irq_disabled = true; |
| } |
| |
| spin_unlock_irqrestore(&fts_data->irq_lock, irqflags); |
| FTS_FUNC_EXIT(); |
| } |
| |
| /***************************************************************************** |
| * Name: fts_irq_enable |
| * Brief: enable irq |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| void fts_irq_enable(void) |
| { |
| unsigned long irqflags = 0; |
| |
| FTS_FUNC_ENTER(); |
| spin_lock_irqsave(&fts_data->irq_lock, irqflags); |
| |
| if (fts_data->irq_disabled) { |
| enable_irq(fts_data->irq); |
| fts_data->irq_disabled = false; |
| } |
| |
| spin_unlock_irqrestore(&fts_data->irq_lock, irqflags); |
| FTS_FUNC_EXIT(); |
| } |
| |
| #if FTS_POWER_SOURCE_CUST_EN |
| /***************************************************************************** |
| * Power Control |
| *****************************************************************************/ |
| static int fts_power_source_init(struct fts_ts_data *data) |
| { |
| int ret = 0; |
| |
| FTS_FUNC_ENTER(); |
| |
| data->vdd = regulator_get(&data->client->dev, "vdd"); |
| if (IS_ERR(data->vdd)) { |
| ret = PTR_ERR(data->vdd); |
| FTS_ERROR("get vdd regulator failed,ret=%d", ret); |
| return ret; |
| } |
| |
| if (regulator_count_voltages(data->vdd) > 0) { |
| ret = regulator_set_voltage(data->vdd, FTS_VTG_MIN_UV, FTS_VTG_MAX_UV); |
| if (ret) { |
| FTS_ERROR("vdd regulator set_vtg failed ret=%d", ret); |
| goto err_set_vtg_vdd; |
| } |
| } |
| |
| data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c"); |
| if (IS_ERR(data->vcc_i2c)) { |
| ret = PTR_ERR(data->vcc_i2c); |
| FTS_ERROR("ret vcc_i2c regulator failed,ret=%d", ret); |
| goto err_get_vcc; |
| } |
| |
| if (regulator_count_voltages(data->vcc_i2c) > 0) { |
| ret = regulator_set_voltage(data->vcc_i2c, FTS_I2C_VTG_MIN_UV, FTS_I2C_VTG_MAX_UV); |
| if (ret) { |
| FTS_ERROR("vcc_i2c regulator set_vtg failed ret=%d", ret); |
| goto err_set_vtg_vcc; |
| } |
| } |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| |
| err_set_vtg_vcc: |
| regulator_put(data->vcc_i2c); |
| err_get_vcc: |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, FTS_VTG_MAX_UV); |
| err_set_vtg_vdd: |
| regulator_put(data->vdd); |
| |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| static int fts_power_source_release(struct fts_ts_data *data) |
| { |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, FTS_VTG_MAX_UV); |
| regulator_put(data->vdd); |
| |
| if (regulator_count_voltages(data->vcc_i2c) > 0) |
| regulator_set_voltage(data->vcc_i2c, 0, FTS_I2C_VTG_MAX_UV); |
| regulator_put(data->vcc_i2c); |
| |
| return 0; |
| } |
| |
| static int fts_power_source_ctrl(struct fts_ts_data *data, int enable) |
| { |
| int ret = 0; |
| |
| FTS_FUNC_ENTER(); |
| if (enable) { |
| if (data->power_disabled) { |
| FTS_INFO("regulator enabled!"); |
| ret = regulator_enable(data->vdd); |
| if (ret) { |
| FTS_ERROR("enable vdd regulator failed,ret=%d", ret); |
| } |
| |
| ret = regulator_enable(data->vcc_i2c); |
| if (ret) { |
| FTS_ERROR("enable vcc_i2c regulator failed,ret=%d", ret); |
| } |
| data->power_disabled = false; |
| } |
| } else { |
| if (!data->power_disabled) { |
| FTS_INFO("regulator disabled!"); |
| ret = regulator_disable(data->vdd); |
| if (ret) { |
| FTS_ERROR("disable vdd regulator failed,ret=%d", ret); |
| } |
| ret = regulator_disable(data->vcc_i2c); |
| if (ret) { |
| FTS_ERROR("disable vcc_i2c regulator failed,ret=%d", ret); |
| } |
| data->power_disabled = true; |
| } |
| } |
| |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| #if FTS_PINCTRL_EN |
| /***************************************************************************** |
| * Name: fts_pinctrl_init |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_pinctrl_init(struct fts_ts_data *ts) |
| { |
| int ret = 0; |
| struct i2c_client *client = ts->client; |
| |
| ts->pinctrl = devm_pinctrl_get(&client->dev); |
| if (IS_ERR_OR_NULL(ts->pinctrl)) { |
| FTS_ERROR("Failed to get pinctrl, please check dts"); |
| ret = PTR_ERR(ts->pinctrl); |
| goto err_pinctrl_get; |
| } |
| |
| ts->pins_active = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_active"); |
| if (IS_ERR_OR_NULL(ts->pins_active)) { |
| FTS_ERROR("Pin state[active] not found"); |
| ret = PTR_ERR(ts->pins_active); |
| goto err_pinctrl_lookup; |
| } |
| |
| ts->pins_suspend = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_suspend"); |
| if (IS_ERR_OR_NULL(ts->pins_suspend)) { |
| FTS_ERROR("Pin state[suspend] not found"); |
| ret = PTR_ERR(ts->pins_suspend); |
| goto err_pinctrl_lookup; |
| } |
| |
| ts->pins_release = pinctrl_lookup_state(ts->pinctrl, "pmx_ts_release"); |
| if (IS_ERR_OR_NULL(ts->pins_release)) { |
| FTS_ERROR("Pin state[release] not found"); |
| ret = PTR_ERR(ts->pins_release); |
| } |
| |
| return 0; |
| err_pinctrl_lookup: |
| if (ts->pinctrl) { |
| devm_pinctrl_put(ts->pinctrl); |
| } |
| err_pinctrl_get: |
| ts->pinctrl = NULL; |
| ts->pins_release = NULL; |
| ts->pins_suspend = NULL; |
| ts->pins_active = NULL; |
| return ret; |
| } |
| |
| static int fts_pinctrl_select_normal(struct fts_ts_data *ts) |
| { |
| int ret = 0; |
| |
| if (ts->pinctrl && ts->pins_active) { |
| ret = pinctrl_select_state(ts->pinctrl, ts->pins_active); |
| if (ret < 0) { |
| FTS_ERROR("Set normal pin state error:%d", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int fts_pinctrl_select_suspend(struct fts_ts_data *ts) |
| { |
| int ret = 0; |
| |
| if (ts->pinctrl && ts->pins_suspend) { |
| ret = pinctrl_select_state(ts->pinctrl, ts->pins_suspend); |
| if (ret < 0) { |
| FTS_ERROR("Set suspend pin state error:%d", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int fts_pinctrl_select_release(struct fts_ts_data *ts) |
| { |
| int ret = 0; |
| |
| if (ts->pinctrl) { |
| if (IS_ERR_OR_NULL(ts->pins_release)) { |
| devm_pinctrl_put(ts->pinctrl); |
| ts->pinctrl = NULL; |
| } else { |
| ret = pinctrl_select_state(ts->pinctrl, ts->pins_release); |
| if (ret < 0) |
| FTS_ERROR("Set gesture pin state error:%d", ret); |
| } |
| } |
| |
| return ret; |
| } |
| #endif /* FTS_PINCTRL_EN */ |
| |
| #endif /* FTS_POWER_SOURCE_CUST_EN */ |
| |
| /***************************************************************************** |
| * Reprot related |
| *****************************************************************************/ |
| #if (FTS_DEBUG_EN && (FTS_DEBUG_LEVEL == 2)) |
| char g_sz_debug[1024] = {0}; |
| static void fts_show_touch_buffer(u8 *buf, int point_num) |
| { |
| int len = point_num * FTS_ONE_TCH_LEN; |
| int count = 0; |
| int i; |
| |
| memset(g_sz_debug, 0, 1024); |
| if (len > (fts_data->pnt_buf_size - 3)) { |
| len = fts_data->pnt_buf_size - 3; |
| } else if (len == 0) { |
| len += FTS_ONE_TCH_LEN; |
| } |
| count += snprintf(g_sz_debug, PAGE_SIZE, "%02X,%02X,%02X", buf[0], buf[1], buf[2]); |
| for (i = 0; i < len; i++) { |
| count += snprintf(g_sz_debug + count, PAGE_SIZE, ",%02X", buf[i + 3]); |
| } |
| FTS_DEBUG("buffer: %s", g_sz_debug); |
| } |
| #endif |
| |
| /***************************************************************************** |
| * Name: fts_release_all_finger |
| * Brief: report all points' up events, release touch |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static void fts_release_all_finger(void) |
| { |
| struct input_dev *input_dev = fts_data->input_dev; |
| #if FTS_MT_PROTOCOL_B_EN |
| u32 finger_count = 0; |
| #endif |
| |
| FTS_FUNC_ENTER(); |
| mutex_lock(&fts_data->report_mutex); |
| #if FTS_MT_PROTOCOL_B_EN |
| for (finger_count = 0; finger_count < fts_data->pdata->max_touch_number; finger_count++) { |
| input_mt_slot(input_dev, finger_count); |
| input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); |
| } |
| #else |
| input_mt_sync(input_dev); |
| #endif |
| input_report_key(input_dev, BTN_TOUCH, 0); |
| input_sync(input_dev); |
| |
| mutex_unlock(&fts_data->report_mutex); |
| FTS_FUNC_EXIT(); |
| } |
| |
| /************************************************************************ |
| * Name: fts_input_report_key |
| * Brief: report key event |
| * Input: events info |
| * Output: |
| * Return: return 0 if success |
| ***********************************************************************/ |
| static int fts_input_report_key(struct fts_ts_data *data, int index) |
| { |
| u32 ik; |
| int id = data->events[index].id; |
| int x = data->events[index].x; |
| int y = data->events[index].y; |
| int flag = data->events[index].flag; |
| u32 key_num = data->pdata->key_number; |
| |
| if (!KEY_EN(data)) { |
| return -EINVAL; |
| } |
| for (ik = 0; ik < key_num; ik++) { |
| if (TOUCH_IN_KEY(x, data->pdata->key_x_coords[ik])) { |
| if (EVENT_DOWN(flag)) { |
| data->key_down = true; |
| input_report_key(data->input_dev, data->pdata->keys[ik], 1); |
| FTS_DEBUG("Key%d(%d, %d) DOWN!", ik, x, y); |
| } else { |
| data->key_down = false; |
| input_report_key(data->input_dev, data->pdata->keys[ik], 0); |
| FTS_DEBUG("Key%d(%d, %d) Up!", ik, x, y); |
| } |
| return 0; |
| } |
| } |
| |
| FTS_ERROR("invalid touch for key, [%d](%d, %d)", id, x, y); |
| return -EINVAL; |
| } |
| |
| #if FTS_MT_PROTOCOL_B_EN |
| static int fts_input_report_b(struct fts_ts_data *data) |
| { |
| int i = 0; |
| int uppoint = 0; |
| int touchs = 0; |
| bool va_reported = false; |
| u32 max_touch_num = data->pdata->max_touch_number; |
| u32 key_y_coor = data->pdata->key_y_coord; |
| struct ts_event *events = data->events; |
| |
| for (i = 0; i < data->touch_point; i++) { |
| if (KEY_EN(data) && TOUCH_IS_KEY(events[i].y, key_y_coor)) { |
| fts_input_report_key(data, i); |
| continue; |
| } |
| |
| if (events[i].id >= max_touch_num) |
| break; |
| |
| va_reported = true; |
| input_mt_slot(data->input_dev, events[i].id); |
| |
| if (EVENT_DOWN(events[i].flag)) { |
| input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true); |
| |
| #if FTS_REPORT_PRESSURE_EN |
| if (events[i].p <= 0) { |
| events[i].p = 0x3f; |
| } |
| input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p); |
| #endif |
| if (events[i].area <= 0) { |
| events[i].area = 0x09; |
| } |
| input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].area); |
| input_report_abs(data->input_dev, ABS_X, events[i].x); |
| input_report_abs(data->input_dev, ABS_Y, events[i].y); |
| input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x); |
| input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y); |
| |
| touchs |= BIT(events[i].id); |
| data->touchs |= BIT(events[i].id); |
| |
| FTS_DEBUG("[B]P%d(%d, %d)[p:%d,tm:%d] DOWN!", events[i].id, events[i].x, |
| events[i].y, events[i].p, events[i].area); |
| } else { |
| uppoint++; |
| input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false); |
| data->touchs &= ~BIT(events[i].id); |
| FTS_DEBUG("[B]P%d UP!", events[i].id); |
| } |
| } |
| |
| if (unlikely(data->touchs ^ touchs)) { |
| for (i = 0; i < max_touch_num; i++) { |
| if (BIT(i) & (data->touchs ^ touchs)) { |
| FTS_DEBUG("[B]P%d UP!", i); |
| va_reported = true; |
| input_mt_slot(data->input_dev, i); |
| input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false); |
| } |
| } |
| } |
| data->touchs = touchs; |
| |
| if (va_reported) { |
| /* touchs==0, there's no point but key */ |
| if (EVENT_NO_DOWN(data) || (!touchs)) { |
| FTS_DEBUG("[B]Points All Up!"); |
| input_report_key(data->input_dev, BTN_TOUCH, 0); |
| } else { |
| input_report_key(data->input_dev, BTN_TOUCH, 1); |
| } |
| } |
| |
| input_sync(data->input_dev); |
| return 0; |
| } |
| |
| #else |
| static int fts_input_report_a(struct fts_ts_data *data) |
| { |
| int i = 0; |
| int touchs = 0; |
| bool va_reported = false; |
| u32 key_y_coor = data->pdata->key_y_coord; |
| struct ts_event *events = data->events; |
| |
| for (i = 0; i < data->touch_point; i++) { |
| if (KEY_EN(data) && TOUCH_IS_KEY(events[i].y, key_y_coor)) { |
| fts_input_report_key(data, i); |
| continue; |
| } |
| |
| va_reported = true; |
| if (EVENT_DOWN(events[i].flag)) { |
| input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, events[i].id); |
| #if FTS_REPORT_PRESSURE_EN |
| if (events[i].p <= 0) { |
| events[i].p = 0x3f; |
| } |
| input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p); |
| #endif |
| if (events[i].area <= 0) { |
| events[i].area = 0x09; |
| } |
| input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].area); |
| |
| input_report_abs(data->input_dev, ABS_X, events[i].x); |
| input_report_abs(data->input_dev, ABS_Y, events[i].y); |
| input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x); |
| input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y); |
| |
| input_mt_sync(data->input_dev); |
| |
| FTS_DEBUG("[A]P%d(%d, %d)[p:%d,tm:%d] DOWN!", events[i].id, events[i].x, |
| events[i].y, events[i].p, events[i].area); |
| touchs++; |
| } |
| } |
| |
| /* last point down, current no point but key */ |
| if (data->touchs && !touchs) { |
| va_reported = true; |
| } |
| data->touchs = touchs; |
| |
| if (va_reported) { |
| if (EVENT_NO_DOWN(data)) { |
| FTS_DEBUG("[A]Points All Up!"); |
| input_report_key(data->input_dev, BTN_TOUCH, 0); |
| input_mt_sync(data->input_dev); |
| } else { |
| input_report_key(data->input_dev, BTN_TOUCH, 1); |
| } |
| } |
| |
| input_sync(data->input_dev); |
| return 0; |
| } |
| #endif |
| |
| /***************************************************************************** |
| * Name: fts_read_touchdata |
| * Brief: |
| * Input: |
| * Output: |
| * Return: return 0 if succuss |
| *****************************************************************************/ |
| static int fts_read_touchdata(struct fts_ts_data *data) |
| { |
| int ret = 0; |
| int i = 0; |
| u8 pointid; |
| int base; |
| struct ts_event *events = data->events; |
| int max_touch_num = data->pdata->max_touch_number; |
| u8 *buf = data->point_buf; |
| struct i2c_client *client = data->client; |
| |
| #if FTS_GESTURE_EN |
| if (0 == fts_gesture_readdata(data)) { |
| FTS_INFO("succuss to get gesture data in irq handler"); |
| return 1; |
| } |
| #endif |
| |
| #if FTS_POINT_REPORT_CHECK_EN |
| fts_prc_queue_work(data); |
| #endif |
| |
| data->point_num = 0; |
| data->touch_point = 0; |
| |
| memset(buf, 0xFF, data->pnt_buf_size); |
| buf[0] = 0x00; |
| |
| ret = fts_i2c_read(data->client, buf, 1, buf, data->pnt_buf_size); |
| if (ret < 0) { |
| FTS_ERROR("read touchdata failed, ret:%d", ret); |
| return ret; |
| } |
| data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F; |
| |
| if (data->ic_info.is_incell) { |
| if ((data->point_num == 0x0F) && (buf[1] == 0xFF) && (buf[2] == 0xFF) |
| && (buf[3] == 0xFF) && (buf[4] == 0xFF) && (buf[5] == 0xFF) && (buf[6] == 0xFF)) { |
| FTS_INFO("touch buff is 0xff, need recovery state"); |
| fts_tp_state_recovery(client); |
| return -EIO; |
| } |
| } |
| |
| if (data->point_num > max_touch_num) { |
| FTS_INFO("invalid point_num(%d)", data->point_num); |
| return -EIO; |
| } |
| |
| #if (FTS_DEBUG_EN && (FTS_DEBUG_LEVEL == 2)) |
| fts_show_touch_buffer(buf, data->point_num); |
| #endif |
| |
| for (i = 0; i < max_touch_num; i++) { |
| base = FTS_ONE_TCH_LEN * i; |
| |
| pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4; |
| if (pointid >= FTS_MAX_ID) |
| break; |
| else if (pointid >= max_touch_num) { |
| FTS_ERROR("ID(%d) beyond max_touch_number", pointid); |
| return -EINVAL; |
| } |
| |
| data->touch_point++; |
| |
| events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) + |
| (buf[FTS_TOUCH_X_L_POS + base] & 0xFF); |
| events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) + |
| (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF); |
| events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6; |
| events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4; |
| events[i].area = buf[FTS_TOUCH_AREA_POS + base] >> 4; |
| events[i].p = buf[FTS_TOUCH_PRE_POS + base]; |
| |
| if (EVENT_DOWN(events[i].flag) && (data->point_num == 0)) { |
| FTS_INFO("abnormal touch data from fw"); |
| return -EIO; |
| } |
| } |
| if (data->touch_point == 0) { |
| FTS_INFO("no touch point information"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_report_event |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static void fts_report_event(struct fts_ts_data *data) |
| { |
| #if FTS_MT_PROTOCOL_B_EN |
| fts_input_report_b(data); |
| #else |
| fts_input_report_a(data); |
| #endif |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_interrupt |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static irqreturn_t fts_ts_interrupt(int irq, void *data) |
| { |
| int ret = 0; |
| struct fts_ts_data *ts_data = (struct fts_ts_data *)data; |
| |
| if (!ts_data) { |
| FTS_ERROR("[INTR]: Invalid fts_ts_data"); |
| return IRQ_HANDLED; |
| } |
| |
| #if FTS_ESDCHECK_EN |
| fts_esdcheck_set_intr(1); |
| #endif |
| |
| ret = fts_read_touchdata(ts_data); |
| if (ret == 0) { |
| mutex_lock(&ts_data->report_mutex); |
| fts_report_event(ts_data); |
| mutex_unlock(&ts_data->report_mutex); |
| } |
| |
| #if FTS_ESDCHECK_EN |
| fts_esdcheck_set_intr(0); |
| #endif |
| |
| return IRQ_HANDLED; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_irq_registration |
| * Brief: |
| * Input: |
| * Output: |
| * Return: return 0 if succuss, otherwise return error code |
| *****************************************************************************/ |
| static int fts_irq_registration(struct fts_ts_data *ts_data) |
| { |
| int ret = 0; |
| struct fts_ts_platform_data *pdata = ts_data->pdata; |
| |
| ts_data->irq = gpio_to_irq(pdata->irq_gpio); |
| FTS_INFO("irq in ts_data:%d irq in client:%d", ts_data->irq, ts_data->client->irq); |
| if (ts_data->irq != ts_data->client->irq) |
| FTS_ERROR("IRQs are inconsistent, please check <interrupts> & <focaltech,irq-gpio> in DTS"); |
| |
| if (0 == pdata->irq_gpio_flags) |
| pdata->irq_gpio_flags = IRQF_TRIGGER_FALLING; |
| FTS_INFO("irq flag:%x", pdata->irq_gpio_flags); |
| ret = request_threaded_irq(ts_data->irq, NULL, fts_ts_interrupt, |
| pdata->irq_gpio_flags | IRQF_ONESHOT, |
| ts_data->client->name, ts_data); |
| |
| return ret; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_input_init |
| * Brief: input device init |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_input_init(struct fts_ts_data *ts_data) |
| { |
| int ret = 0; |
| int key_num = 0; |
| struct fts_ts_platform_data *pdata = ts_data->pdata; |
| struct input_dev *input_dev; |
| int point_num; |
| |
| FTS_FUNC_ENTER(); |
| |
| input_dev = input_allocate_device(); |
| if (!input_dev) { |
| FTS_ERROR("Failed to allocate memory for input device"); |
| return -ENOMEM; |
| } |
| |
| /* Init and register Input device */ |
| input_dev->name = FTS_DRIVER_NAME; |
| input_dev->id.bustype = BUS_I2C; |
| input_dev->dev.parent = &ts_data->client->dev; |
| |
| input_set_drvdata(input_dev, ts_data); |
| |
| __set_bit(EV_SYN, input_dev->evbit); |
| __set_bit(EV_ABS, input_dev->evbit); |
| __set_bit(EV_KEY, input_dev->evbit); |
| __set_bit(BTN_TOUCH, input_dev->keybit); |
| __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); |
| |
| if (pdata->have_key) { |
| FTS_INFO("set key capabilities"); |
| for (key_num = 0; key_num < pdata->key_number; key_num++) |
| input_set_capability(input_dev, EV_KEY, pdata->keys[key_num]); |
| } |
| |
| #if FTS_MT_PROTOCOL_B_EN |
| input_mt_init_slots(input_dev, pdata->max_touch_number, INPUT_MT_DIRECT); |
| #else |
| input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 0x0f, 0, 0); |
| #endif |
| input_set_abs_params(input_dev, ABS_X, pdata->x_min, pdata->x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_Y, pdata->y_min, pdata->y_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0); |
| input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0xFF, 0, 0); |
| #if FTS_REPORT_PRESSURE_EN |
| input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0); |
| #endif |
| point_num = pdata->max_touch_number; |
| ts_data->pnt_buf_size = point_num * FTS_ONE_TCH_LEN + 3; |
| ts_data->point_buf = (u8 *)kzalloc(ts_data->pnt_buf_size, GFP_KERNEL); |
| if (!ts_data->point_buf) { |
| FTS_ERROR("failed to alloc memory for point buf!"); |
| ret = -ENOMEM; |
| goto err_point_buf; |
| } |
| |
| ts_data->events = (struct ts_event *)kzalloc(point_num * sizeof(struct ts_event), GFP_KERNEL); |
| if (!ts_data->events) { |
| |
| FTS_ERROR("failed to alloc memory for point events!"); |
| ret = -ENOMEM; |
| goto err_event_buf; |
| } |
| |
| |
| ret = input_register_device(input_dev); |
| if (ret) { |
| FTS_ERROR("Input device registration failed"); |
| goto err_input_reg; |
| } |
| |
| ts_data->input_dev = input_dev; |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| |
| err_input_reg: |
| kfree_safe(ts_data->events); |
| |
| err_event_buf: |
| kfree_safe(ts_data->point_buf); |
| |
| err_point_buf: |
| input_set_drvdata(input_dev, NULL); |
| input_free_device(input_dev); |
| input_dev = NULL; |
| |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| |
| /***************************************************************************** |
| * Name: fts_gpio_configure |
| * Brief: Configure IRQ&RESET GPIO |
| * Input: |
| * Output: |
| * Return: return 0 if succuss |
| *****************************************************************************/ |
| static int fts_gpio_configure(struct fts_ts_data *data) |
| { |
| int ret = 0; |
| |
| FTS_FUNC_ENTER(); |
| /* request irq gpio */ |
| if (gpio_is_valid(data->pdata->irq_gpio)) { |
| ret = gpio_request(data->pdata->irq_gpio, "fts_irq_gpio"); |
| if (ret) { |
| FTS_ERROR("[GPIO]irq gpio request failed"); |
| goto err_irq_gpio_req; |
| } |
| |
| ret = gpio_direction_input(data->pdata->irq_gpio); |
| if (ret) { |
| FTS_ERROR("[GPIO]set_direction for irq gpio failed"); |
| goto err_irq_gpio_dir; |
| } |
| } |
| |
| /* request reset gpio */ |
| if (gpio_is_valid(data->pdata->reset_gpio)) { |
| ret = gpio_request(data->pdata->reset_gpio, "fts_reset_gpio"); |
| if (ret) { |
| FTS_ERROR("[GPIO]reset gpio request failed"); |
| goto err_irq_gpio_dir; |
| } |
| |
| ret = gpio_direction_output(data->pdata->reset_gpio, 1); |
| if (ret) { |
| FTS_ERROR("[GPIO]set_direction for reset gpio failed"); |
| goto err_reset_gpio_dir; |
| } |
| } |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| |
| err_reset_gpio_dir: |
| if (gpio_is_valid(data->pdata->reset_gpio)) |
| gpio_free(data->pdata->reset_gpio); |
| err_irq_gpio_dir: |
| if (gpio_is_valid(data->pdata->irq_gpio)) |
| gpio_free(data->pdata->irq_gpio); |
| err_irq_gpio_req: |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_get_dt_coords |
| * Brief: |
| * Input: |
| * Output: |
| * Return: return 0 if succuss, otherwise return error code |
| *****************************************************************************/ |
| static int fts_get_dt_coords(struct device *dev, char *name, |
| struct fts_ts_platform_data *pdata) |
| { |
| int ret = 0; |
| u32 coords[FTS_COORDS_ARR_SIZE] = { 0 }; |
| struct property *prop; |
| struct device_node *np = dev->of_node; |
| int coords_size; |
| |
| prop = of_find_property(np, name, NULL); |
| if (!prop) |
| return -EINVAL; |
| if (!prop->value) |
| return -ENODATA; |
| |
| coords_size = prop->length / sizeof(u32); |
| if (coords_size != FTS_COORDS_ARR_SIZE) { |
| FTS_ERROR("invalid:%s, size:%d", name, coords_size); |
| return -EINVAL; |
| } |
| |
| ret = of_property_read_u32_array(np, name, coords, coords_size); |
| if (ret && (ret != -EINVAL)) { |
| FTS_ERROR("Unable to read %s", name); |
| return -ENODATA; |
| } |
| |
| if (!strcmp(name, "focaltech,display-coords")) { |
| pdata->x_min = coords[0]; |
| pdata->y_min = coords[1]; |
| pdata->x_max = coords[2]; |
| pdata->y_max = coords[3]; |
| } else { |
| FTS_ERROR("unsupported property %s", name); |
| return -EINVAL; |
| } |
| |
| FTS_INFO("display x(%d %d) y(%d %d)", pdata->x_min, pdata->x_max, |
| pdata->y_min, pdata->y_max); |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_parse_dt |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_parse_dt(struct device *dev, struct fts_ts_platform_data *pdata) |
| { |
| int ret = 0; |
| struct device_node *np = dev->of_node; |
| u32 temp_val; |
| |
| FTS_FUNC_ENTER(); |
| |
| ret = fts_get_dt_coords(dev, "focaltech,display-coords", pdata); |
| if (ret < 0) |
| FTS_ERROR("Unable to get display-coords"); |
| |
| /* key */ |
| pdata->have_key = of_property_read_bool(np, "focaltech,have-key"); |
| if (pdata->have_key) { |
| ret = of_property_read_u32(np, "focaltech,key-number", &pdata->key_number); |
| if (ret) |
| FTS_ERROR("Key number undefined!"); |
| |
| ret = of_property_read_u32_array(np, "focaltech,keys", |
| pdata->keys, pdata->key_number); |
| if (ret) |
| FTS_ERROR("Keys undefined!"); |
| else if (pdata->key_number > FTS_MAX_KEYS) |
| pdata->key_number = FTS_MAX_KEYS; |
| |
| ret = of_property_read_u32(np, "focaltech,key-y-coord", &pdata->key_y_coord); |
| if (ret) |
| FTS_ERROR("Key Y Coord undefined!"); |
| |
| ret = of_property_read_u32_array(np, "focaltech,key-x-coords", |
| pdata->key_x_coords, pdata->key_number); |
| if (ret) |
| FTS_ERROR("Key X Coords undefined!"); |
| |
| FTS_INFO("VK(%d): (%d, %d, %d), [%d, %d, %d][%d]", |
| pdata->key_number, pdata->keys[0], pdata->keys[1], pdata->keys[2], |
| pdata->key_x_coords[0], pdata->key_x_coords[1], pdata->key_x_coords[2], |
| pdata->key_y_coord); |
| } |
| |
| /* reset, irq gpio info */ |
| pdata->reset_gpio = of_get_named_gpio_flags(np, "focaltech,reset-gpio", 0, &pdata->reset_gpio_flags); |
| if (pdata->reset_gpio < 0) |
| FTS_ERROR("Unable to get reset_gpio"); |
| |
| pdata->irq_gpio = of_get_named_gpio_flags(np, "focaltech,irq-gpio", 0, &pdata->irq_gpio_flags); |
| if (pdata->irq_gpio < 0) |
| FTS_ERROR("Unable to get irq_gpio"); |
| |
| ret = of_property_read_u32(np, "focaltech,max-touch-number", &temp_val); |
| if (0 == ret) { |
| if (temp_val < 2) |
| pdata->max_touch_number = 2; |
| else if (temp_val > FTS_MAX_POINTS_SUPPORT) |
| pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT; |
| else |
| pdata->max_touch_number = temp_val; |
| } else { |
| FTS_ERROR("Unable to get max-touch-number"); |
| pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT; |
| } |
| |
| FTS_INFO("max touch number:%d, irq gpio:%d, reset gpio:%d", |
| pdata->max_touch_number, pdata->irq_gpio, pdata->reset_gpio); |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| } |
| |
| #if defined(CONFIG_FB) |
| /***************************************************************************** |
| * Name: fb_notifier_callback |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fb_event *evdata = data; |
| int *blank; |
| struct fts_ts_data *fts_data = |
| container_of(self, struct fts_ts_data, fb_notif); |
| |
| if (evdata && evdata->data && event == FB_EVENT_BLANK && |
| fts_data && fts_data->client) { |
| blank = evdata->data; |
| if (*blank == FB_BLANK_UNBLANK) |
| fts_ts_resume(&fts_data->client->dev); |
| else if (*blank == FB_BLANK_POWERDOWN) |
| fts_ts_suspend(&fts_data->client->dev); |
| } |
| |
| return 0; |
| } |
| #elif defined(CONFIG_HAS_EARLYSUSPEND) |
| /***************************************************************************** |
| * Name: fts_ts_early_suspend |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static void fts_ts_early_suspend(struct early_suspend *handler) |
| { |
| struct fts_ts_data *data = container_of(handler, |
| struct fts_ts_data, |
| early_suspend); |
| |
| fts_ts_suspend(&data->client->dev); |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_late_resume |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static void fts_ts_late_resume(struct early_suspend *handler) |
| { |
| struct fts_ts_data *data = container_of(handler, |
| struct fts_ts_data, |
| early_suspend); |
| |
| fts_ts_resume(&data->client->dev); |
| } |
| #endif |
| |
| /***************************************************************************** |
| * Name: fts_ts_probe |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| int ret = 0; |
| struct fts_ts_platform_data *pdata; |
| struct fts_ts_data *ts_data; |
| |
| FTS_FUNC_ENTER(); |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| FTS_ERROR("I2C not supported"); |
| return -ENODEV; |
| } |
| |
| if (client->dev.of_node) { |
| pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) { |
| FTS_ERROR("Failed to allocate memory for platform data"); |
| return -ENOMEM; |
| } |
| ret = fts_parse_dt(&client->dev, pdata); |
| if (ret) |
| FTS_ERROR("[DTS]DT parsing failed"); |
| } else { |
| pdata = client->dev.platform_data; |
| } |
| |
| if (!pdata) { |
| FTS_ERROR("no ts platform data found"); |
| return -EINVAL; |
| } |
| |
| ts_data = devm_kzalloc(&client->dev, sizeof(*ts_data), GFP_KERNEL); |
| if (!ts_data) { |
| FTS_ERROR("Failed to allocate memory for fts_data"); |
| return -ENOMEM; |
| } |
| |
| fts_data = ts_data; |
| ts_data->client = client; |
| ts_data->pdata = pdata; |
| i2c_set_clientdata(client, ts_data); |
| |
| ts_data->ts_workqueue = create_singlethread_workqueue("fts_wq"); |
| if (NULL == ts_data->ts_workqueue) { |
| FTS_ERROR("failed to create fts workqueue"); |
| } |
| |
| spin_lock_init(&ts_data->irq_lock); |
| mutex_init(&ts_data->report_mutex); |
| |
| ret = fts_input_init(ts_data); |
| if (ret) { |
| FTS_ERROR("fts input initialize fail"); |
| goto err_input_init; |
| } |
| |
| #if FTS_POWER_SOURCE_CUST_EN |
| ret = fts_power_source_init(ts_data); |
| if (ret) { |
| FTS_ERROR("fail to get vdd/vcc_i2c regulator"); |
| goto err_power_init; |
| } |
| |
| ts_data->power_disabled = true; |
| ret = fts_power_source_ctrl(ts_data, ENABLE); |
| if (ret) { |
| FTS_ERROR("fail to enable vdd/vcc_i2c regulator"); |
| goto err_power_ctrl; |
| } |
| |
| #if FTS_PINCTRL_EN |
| ret = fts_pinctrl_init(ts_data); |
| if (0 == ret) { |
| fts_pinctrl_select_normal(ts_data); |
| } |
| #endif |
| #endif |
| |
| ret = fts_gpio_configure(ts_data); |
| if (ret) { |
| FTS_ERROR("[GPIO]Failed to configure the gpios"); |
| goto err_gpio_config; |
| } |
| |
| #if (!FTS_CHIP_IDC) |
| fts_reset_proc(200); |
| #endif |
| |
| ret = fts_get_ic_information(ts_data); |
| if (ret) { |
| FTS_ERROR("not focal IC, unregister driver"); |
| goto err_irq_req; |
| } |
| |
| #if FTS_APK_NODE_EN |
| ret = fts_create_apk_debug_channel(ts_data); |
| if (ret) { |
| FTS_ERROR("create apk debug node fail"); |
| } |
| #endif |
| |
| #if FTS_SYSFS_NODE_EN |
| ret = fts_create_sysfs(client); |
| if (ret) { |
| FTS_ERROR("create sysfs node fail"); |
| } |
| #endif |
| |
| #if FTS_POINT_REPORT_CHECK_EN |
| ret = fts_point_report_check_init(ts_data); |
| if (ret) { |
| FTS_ERROR("init point report check fail"); |
| } |
| #endif |
| |
| ret = fts_ex_mode_init(client); |
| if (ret) { |
| FTS_ERROR("init glove/cover/charger fail"); |
| } |
| |
| #if FTS_GESTURE_EN |
| ret = fts_gesture_init(ts_data); |
| if (ret) { |
| FTS_ERROR("init gesture fail"); |
| } |
| #endif |
| |
| #if FTS_TEST_EN |
| ret = fts_test_init(client); |
| if (ret) { |
| FTS_ERROR("init production test fail"); |
| } |
| #endif |
| |
| #if FTS_ESDCHECK_EN |
| ret = fts_esdcheck_init(ts_data); |
| if (ret) { |
| FTS_ERROR("init esd check fail"); |
| } |
| #endif |
| |
| ret = fts_irq_registration(ts_data); |
| if (ret) { |
| FTS_ERROR("request irq failed"); |
| goto err_irq_req; |
| } |
| |
| #if FTS_AUTO_UPGRADE_EN |
| ret = fts_fwupg_init(ts_data); |
| if (ret) { |
| FTS_ERROR("init fw upgrade fail"); |
| } |
| #endif |
| |
| #if defined(CONFIG_FB) |
| ts_data->fb_notif.notifier_call = fb_notifier_callback; |
| ret = fb_register_client(&ts_data->fb_notif); |
| if (ret) { |
| FTS_ERROR("[FB]Unable to register fb_notifier: %d", ret); |
| } |
| #elif defined(CONFIG_HAS_EARLYSUSPEND) |
| ts_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + FTS_SUSPEND_LEVEL; |
| ts_data->early_suspend.suspend = fts_ts_early_suspend; |
| ts_data->early_suspend.resume = fts_ts_late_resume; |
| register_early_suspend(&ts_data->early_suspend); |
| #endif |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| |
| err_irq_req: |
| if (gpio_is_valid(pdata->reset_gpio)) |
| gpio_free(pdata->reset_gpio); |
| if (gpio_is_valid(pdata->irq_gpio)) |
| gpio_free(pdata->irq_gpio); |
| err_gpio_config: |
| #if FTS_POWER_SOURCE_CUST_EN |
| #if FTS_PINCTRL_EN |
| fts_pinctrl_select_release(ts_data); |
| #endif |
| fts_power_source_ctrl(ts_data, DISABLE); |
| err_power_ctrl: |
| fts_power_source_release(ts_data); |
| err_power_init: |
| #endif |
| kfree_safe(ts_data->point_buf); |
| kfree_safe(ts_data->events); |
| input_unregister_device(ts_data->input_dev); |
| err_input_init: |
| if (ts_data->ts_workqueue) |
| destroy_workqueue(ts_data->ts_workqueue); |
| devm_kfree(&client->dev, ts_data); |
| |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_remove |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_ts_remove(struct i2c_client *client) |
| { |
| struct fts_ts_data *ts_data = i2c_get_clientdata(client); |
| |
| FTS_FUNC_ENTER(); |
| |
| #if FTS_POINT_REPORT_CHECK_EN |
| fts_point_report_check_exit(ts_data); |
| #endif |
| |
| #if FTS_APK_NODE_EN |
| fts_release_apk_debug_channel(ts_data); |
| #endif |
| |
| #if FTS_SYSFS_NODE_EN |
| fts_remove_sysfs(client); |
| #endif |
| |
| fts_ex_mode_exit(client); |
| |
| #if FTS_AUTO_UPGRADE_EN |
| fts_fwupg_exit(ts_data); |
| #endif |
| |
| #if FTS_TEST_EN |
| fts_test_exit(client); |
| #endif |
| |
| #if FTS_ESDCHECK_EN |
| fts_esdcheck_exit(ts_data); |
| #endif |
| |
| #if FTS_GESTURE_EN |
| fts_gesture_exit(client); |
| #endif |
| |
| #if defined(CONFIG_FB) |
| if (fb_unregister_client(&ts_data->fb_notif)) |
| FTS_ERROR("Error occurred while unregistering fb_notifier."); |
| #elif defined(CONFIG_HAS_EARLYSUSPEND) |
| unregister_early_suspend(&ts_data->early_suspend); |
| #endif |
| |
| free_irq(ts_data->irq, ts_data); |
| input_unregister_device(ts_data->input_dev); |
| |
| if (gpio_is_valid(ts_data->pdata->reset_gpio)) |
| gpio_free(ts_data->pdata->reset_gpio); |
| |
| if (gpio_is_valid(ts_data->pdata->irq_gpio)) |
| gpio_free(ts_data->pdata->irq_gpio); |
| |
| if (ts_data->ts_workqueue) |
| destroy_workqueue(ts_data->ts_workqueue); |
| |
| #if FTS_POWER_SOURCE_CUST_EN |
| #if FTS_PINCTRL_EN |
| fts_pinctrl_select_release(ts_data); |
| #endif |
| fts_power_source_ctrl(ts_data, DISABLE); |
| fts_power_source_release(ts_data); |
| #endif |
| |
| kfree_safe(ts_data->point_buf); |
| kfree_safe(ts_data->events); |
| |
| devm_kfree(&client->dev, ts_data); |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_suspend |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_ts_suspend(struct device *dev) |
| { |
| int ret = 0; |
| struct fts_ts_data *ts_data = dev_get_drvdata(dev); |
| |
| FTS_FUNC_ENTER(); |
| if (ts_data->suspended) { |
| FTS_INFO("Already in suspend state"); |
| return 0; |
| } |
| |
| if (ts_data->fw_loading) { |
| FTS_INFO("fw upgrade in process, can't suspend"); |
| return 0; |
| } |
| |
| #if FTS_ESDCHECK_EN |
| fts_esdcheck_suspend(); |
| #endif |
| |
| #if FTS_GESTURE_EN |
| if (fts_gesture_suspend(ts_data->client) == 0) { |
| ts_data->suspended = true; |
| return 0; |
| } |
| #endif |
| |
| fts_irq_disable(); |
| |
| #if FTS_POWER_SOURCE_CUST_EN |
| ret = fts_power_source_ctrl(ts_data, DISABLE); |
| if (ret < 0) { |
| FTS_ERROR("power off fail, ret=%d", ret); |
| } |
| #if FTS_PINCTRL_EN |
| fts_pinctrl_select_suspend(ts_data); |
| #endif |
| #else |
| /* TP enter sleep mode */ |
| ret = fts_i2c_write_reg(ts_data->client, FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP_VALUE); |
| if (ret < 0) |
| FTS_ERROR("set TP to sleep mode fail, ret=%d", ret); |
| #endif |
| |
| ts_data->suspended = true; |
| FTS_FUNC_EXIT(); |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_resume |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int fts_ts_resume(struct device *dev) |
| { |
| struct fts_ts_data *ts_data = dev_get_drvdata(dev); |
| |
| FTS_FUNC_ENTER(); |
| if (!ts_data->suspended) { |
| FTS_DEBUG("Already in awake state"); |
| return 0; |
| } |
| |
| fts_release_all_finger(); |
| |
| #if FTS_POWER_SOURCE_CUST_EN |
| fts_power_source_ctrl(ts_data, ENABLE); |
| #if FTS_PINCTRL_EN |
| fts_pinctrl_select_normal(ts_data); |
| #endif |
| #endif |
| |
| if (!ts_data->ic_info.is_incell) { |
| fts_reset_proc(200); |
| } |
| |
| fts_tp_state_recovery(ts_data->client); |
| |
| #if FTS_ESDCHECK_EN |
| fts_esdcheck_resume(); |
| #endif |
| |
| #if FTS_GESTURE_EN |
| if (fts_gesture_resume(ts_data->client) == 0) { |
| ts_data->suspended = false; |
| return 0; |
| } |
| #endif |
| |
| ts_data->suspended = false; |
| fts_irq_enable(); |
| |
| FTS_FUNC_EXIT(); |
| return 0; |
| } |
| |
| /***************************************************************************** |
| * I2C Driver |
| *****************************************************************************/ |
| static const struct i2c_device_id fts_ts_id[] = { |
| {FTS_DRIVER_NAME, 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, fts_ts_id); |
| |
| static struct of_device_id fts_match_table[] = { |
| { .compatible = "focaltech,fts", }, |
| { }, |
| }; |
| |
| static struct i2c_driver fts_ts_driver = { |
| .probe = fts_ts_probe, |
| .remove = fts_ts_remove, |
| .driver = { |
| .name = FTS_DRIVER_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = fts_match_table, |
| }, |
| .id_table = fts_ts_id, |
| }; |
| |
| /***************************************************************************** |
| * Name: fts_ts_init |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static int __init fts_ts_init(void) |
| { |
| int ret = 0; |
| |
| FTS_FUNC_ENTER(); |
| ret = i2c_add_driver(&fts_ts_driver); |
| if ( ret != 0 ) { |
| FTS_ERROR("Focaltech touch screen driver init failed!"); |
| } |
| FTS_FUNC_EXIT(); |
| return ret; |
| } |
| |
| /***************************************************************************** |
| * Name: fts_ts_exit |
| * Brief: |
| * Input: |
| * Output: |
| * Return: |
| *****************************************************************************/ |
| static void __exit fts_ts_exit(void) |
| { |
| i2c_del_driver(&fts_ts_driver); |
| } |
| |
| module_init(fts_ts_init); |
| module_exit(fts_ts_exit); |
| |
| MODULE_AUTHOR("FocalTech Driver Team"); |
| MODULE_DESCRIPTION("FocalTech Touchscreen Driver"); |
| MODULE_LICENSE("GPL v2"); |