| /******************** (C) COPYRIGHT 2012 STMicroelectronics ******************** |
| * |
| * File Name : fts.c |
| * Authors : AMS(Analog Mems Sensor) Team |
| * Description : FTS Capacitive touch screen controller (FingerTipS) |
| * |
| ******************************************************************************** |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES |
| * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE |
| * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. |
| * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, |
| * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE |
| * CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING |
| * INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. |
| * |
| * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. |
| ******************************************************************************** |
| * REVISON HISTORY |
| * DATE | DESCRIPTION |
| * 03/09/2012| First Release |
| * 08/11/2012| Code migration |
| * 09/04/2013| Support Blob Information |
| *******************************************************************************/ |
| |
| #include <linux/init.h> |
| #include <linux/errno.h> |
| #include <linux/platform_device.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/serio.h> |
| #include <linux/init.h> |
| #include <linux/pm.h> |
| #include <linux/delay.h> |
| #include <linux/ctype.h> |
| #include <linux/gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c-dev.h> |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/power_supply.h> |
| #include <linux/firmware.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| #include <linux/trustedui.h> |
| #endif |
| |
| #ifdef CONFIG_OF |
| #ifndef USE_OPEN_CLOSE |
| #define USE_OPEN_CLOSE |
| #undef CONFIG_PM |
| #endif |
| #endif |
| |
| #include <linux/input/mt.h> |
| #include "ftm4_ts.h" |
| |
| #define FTS_REGISTER_PSY_MS 200 |
| |
| static struct i2c_driver fts_i2c_driver; |
| |
| extern int msm_gpio_install_direct_irq(unsigned int gpio, |
| unsigned int irq, |
| unsigned int input_polarity); |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| extern int tui_force_close(uint32_t arg); |
| struct fts_ts_info *tui_tsp_info; |
| #endif |
| |
| #ifdef USE_OPEN_CLOSE |
| static int fts_input_open(struct input_dev *dev); |
| static void fts_input_close(struct input_dev *dev); |
| #ifdef USE_OPEN_DWORK |
| static void fts_open_work(struct work_struct *work); |
| #endif |
| #endif |
| |
| static int fts_stop_device(struct fts_ts_info *info); |
| static int fts_start_device(struct fts_ts_info *info); |
| static void fts_irq_enable(struct fts_ts_info *info, bool enable); |
| static void fts_reset_work(struct work_struct *work); |
| void fts_recovery_cx(struct fts_ts_info *info); |
| void fts_release_all_finger(struct fts_ts_info *info); |
| static int fts_suspend(struct i2c_client *client, pm_message_t mesg); |
| static int fts_resume(struct i2c_client *client); |
| |
| #if defined(CONFIG_FB) |
| static int touch_fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data); |
| #endif |
| |
| static int fts_ts_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| return -EINVAL; |
| } |
| |
| static enum power_supply_property fts_ts_props[] = { |
| POWER_SUPPLY_PROP_TYPE, |
| }; |
| |
| static void fts_control_ta_detect_pin(struct fts_ts_info *info) |
| { |
| int ta_pin = 0; |
| |
| if (!gpio_is_valid(info->board->ta_detect_pin)) |
| return; |
| |
| gpio_set_value(info->board->ta_detect_pin, info->charger_connected); |
| |
| ta_pin = gpio_get_value(info->board->ta_detect_pin); |
| |
| tsp_debug_dbg(&info->client->dev, |
| "%s: ta_detect_pin status = %d.\n", |
| __func__, ta_pin); |
| } |
| |
| static void fts_external_power_changed(struct power_supply *psy) |
| { |
| struct fts_ts_info *info = power_supply_get_drvdata(psy); |
| union power_supply_propval prop = {0}; |
| |
| if (!info->usb_psy) |
| info->usb_psy = power_supply_get_by_name("usb"); |
| if (!info->usb_psy) |
| return; |
| if (info->usb_psy) { |
| power_supply_get_property(info->usb_psy, |
| POWER_SUPPLY_PROP_ONLINE, &prop); |
| if (info->charger_connected != prop.intval) { |
| tsp_debug_dbg(&info->client->dev, |
| "%s: charger_connected transition: %d => %d.\n", |
| __func__, info->charger_connected, prop.intval); |
| info->charger_connected = prop.intval; |
| fts_control_ta_detect_pin(info); |
| } |
| } |
| } |
| |
| static const struct power_supply_desc fts_ts_desc = { |
| .name = "touch", |
| .type = POWER_SUPPLY_TYPE_UNKNOWN, |
| .properties = fts_ts_props, |
| .num_properties = ARRAY_SIZE(fts_ts_props), |
| .get_property = fts_ts_get_property, |
| .external_power_changed = fts_external_power_changed, |
| }; |
| |
| static void fts_psy_work(struct work_struct *work) |
| { |
| struct fts_ts_info *info = container_of(work, struct fts_ts_info, |
| psy_work.work); |
| struct power_supply_config psy_cfg = {}; |
| |
| psy_cfg.of_node = info->dev->of_node; |
| psy_cfg.drv_data = info; |
| info->ts_psy = devm_power_supply_register(info->dev, &fts_ts_desc, |
| &psy_cfg); |
| if (!IS_ERR_OR_NULL(info->ts_psy)) { |
| fts_external_power_changed(info->ts_psy); |
| } else if (PTR_ERR(info->ts_psy) == -EPROBE_DEFER) { |
| schedule_delayed_work(&info->psy_work, |
| msecs_to_jiffies(FTS_REGISTER_PSY_MS)); |
| } else { |
| tsp_debug_err(info->dev, |
| "%s: Failed to register power supply\n", |
| __func__); |
| } |
| } |
| |
| int fts_write_reg(struct fts_ts_info *info, |
| unsigned char *reg, unsigned short num_com) |
| { |
| struct i2c_msg xfer_msg[2]; |
| int ret = 0; |
| |
| if (info->touch_stopped) { |
| tsp_debug_err(&info->client->dev, |
| "%s: Sensor stopped\n", __func__); |
| goto exit; |
| } |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TSP no accessible from Linux," |
| "TUI is enabled!\n", __func__); |
| return -EIO; |
| } |
| #endif |
| |
| mutex_lock(&info->i2c_mutex); |
| |
| xfer_msg[0].addr = info->client->addr; |
| xfer_msg[0].len = num_com; |
| xfer_msg[0].flags = 0; |
| xfer_msg[0].buf = reg; |
| |
| ret = i2c_transfer(info->client->adapter, xfer_msg, 1); |
| |
| mutex_unlock(&info->i2c_mutex); |
| return ret; |
| |
| exit: |
| return -EIO; |
| } |
| |
| int fts_read_reg(struct fts_ts_info *info, unsigned char *reg, int cnum, |
| unsigned char *buf, int num) |
| { |
| struct i2c_msg xfer_msg[2]; |
| int ret = 0; |
| |
| if (info->touch_stopped) { |
| tsp_debug_err(&info->client->dev, |
| "%s: Sensor stopped\n", __func__); |
| goto exit; |
| } |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| if (TRUSTEDUI_MODE_INPUT_SECURED & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TSP no accessible from Linux," |
| " TUI is enabled!\n", __func__); |
| return -EIO; |
| } |
| #endif |
| |
| mutex_lock(&info->i2c_mutex); |
| |
| xfer_msg[0].addr = info->client->addr; |
| xfer_msg[0].len = cnum; |
| xfer_msg[0].flags = 0; |
| xfer_msg[0].buf = reg; |
| |
| xfer_msg[1].addr = info->client->addr; |
| xfer_msg[1].len = num; |
| xfer_msg[1].flags = I2C_M_RD; |
| xfer_msg[1].buf = buf; |
| |
| ret = i2c_transfer(info->client->adapter, xfer_msg, 2); |
| |
| mutex_unlock(&info->i2c_mutex); |
| |
| return ret; |
| |
| exit: |
| return -EIO; |
| } |
| |
| void fts_delay(unsigned int ms) |
| { |
| if (ms < 20) |
| usleep_range(ms * 1000, ms * 1000); |
| else |
| msleep(ms); |
| } |
| |
| void fts_command(struct fts_ts_info *info, unsigned char cmd) |
| { |
| unsigned char regAdd = 0; |
| int ret = 0; |
| |
| regAdd = cmd; |
| ret = fts_write_reg(info, ®Add, 1); |
| tsp_debug_dbg(&info->client->dev, |
| "FTS Command (%02X) , " |
| "ret = %d\n", cmd, ret); |
| } |
| |
| void fts_change_scan_rate(struct fts_ts_info *info, unsigned char cmd) |
| { |
| unsigned char regAdd[2] = {0xC3, 0x00}; |
| int ret = 0; |
| |
| regAdd[1] = cmd; |
| ret = fts_write_reg(info, ®Add[0], 2); |
| |
| tsp_debug_dbg(&info->client->dev, |
| "FTS %s Scan Rate (%02X %02X) , ret = %d\n", |
| (cmd == FTS_CMD_FAST_SCAN) ? "90Hz" : |
| (cmd == FTS_CMD_SLOW_SCAN) ? "60Hz" : "30Hz", |
| regAdd[0], regAdd[1], ret); |
| } |
| |
| int fts_systemreset(struct fts_ts_info *info) |
| { |
| int ret = 0; |
| unsigned char addr[4] = {0xB6, 0x00, 0x28, 0x80}; |
| unsigned char addr_wbcrc[4] = {0xB6, 0x00, 0x1E, 0x20}; |
| |
| tsp_debug_info(&info->client->dev, "FTS Enable WBCRC\n"); |
| ret = fts_write_reg(info, &addr_wbcrc[0], 4); |
| fts_delay(10); |
| |
| tsp_debug_dbg(&info->client->dev, "FTS SystemReset\n"); |
| ret = fts_write_reg(info, &addr[0], 4); |
| fts_delay(10); |
| |
| return ret; |
| } |
| |
| static void fts_interrupt_set(struct fts_ts_info *info, int enable) |
| { |
| unsigned char regAdd[4] = {0xB6, 0x00, 0x2C, INT_ENABLE}; |
| |
| if (enable== INT_ENABLE) { |
| tsp_debug_dbg(&info->client->dev, "FTS INT Enable\n"); |
| } else { |
| regAdd[3] = INT_DISABLE; |
| tsp_debug_dbg(&info->client->dev, "FTS INT Disable\n"); |
| } |
| |
| fts_write_reg(info, ®Add[0], 4); |
| } |
| |
| void fts_get_afe_info(struct fts_ts_info *info) |
| { |
| int rc = 0; |
| unsigned char regAdd[3]; |
| unsigned char data[FTS_EVENT_SIZE]; |
| |
| /* Reading the final AFE version */ |
| regAdd[0] = 0xd0; |
| regAdd[1] = 0x00; |
| regAdd[2] = 0x52; |
| |
| rc = fts_read_reg(info, regAdd, 3, (unsigned char *)data, 3); |
| if (rc < 0) { |
| info->afe_ver = 0; |
| tsp_debug_err(info->dev, |
| "%s: Read Fail - Final AFE [Data : " |
| "%2X] AFE Ver [Data : %2X] \n", |
| __func__, |
| data[1], |
| data[2]); |
| } else |
| info->afe_ver = data[2]; |
| } |
| |
| static int fts_product_info_read(struct fts_ts_info *info) |
| { |
| /* This function must be called with interrupts/irqs disabled */ |
| unsigned char data[FTS_EVENT_SIZE] = {0,}; |
| unsigned char prd_info[FTS_LOCKDOWNCODE_SIZE] ={0x0,}; |
| static u8 addr[2] = {READ_ONE_EVENT, 0}; |
| int retry = (FTS_RETRY_COUNT * 5); |
| int total_length = 0; |
| int offset = 0; |
| int ret = 0; |
| int i = 0; |
| |
| memset(&info->prd_info, 0, sizeof(struct fts_prd_info)); |
| |
| info->fts_command(info, SENSEOFF); |
| |
| fts_command(info,LOCKDOWN_READ); |
| |
| while (retry--) { |
| fts_delay(5); |
| |
| ret = fts_read_reg(info, &addr[0], 1, &data[0], FTS_EVENT_SIZE); |
| if (ret < 0) { |
| tsp_debug_err(info->dev, "ftm4_reg_read fail\n"); |
| goto error; |
| } |
| |
| if (data[0] == EVENTID_LOCKDOWN_CODE) { |
| total_length = data[1]; |
| offset = data[2]; |
| |
| tsp_debug_dbg(info->dev, "Total length : %d | offset : %d\n", total_length, offset); |
| |
| if (total_length == FTS_LOCKDOWNCODE_SIZE) { |
| for (i = 0; i < 4; i++) { |
| if (offset+i >= FTS_LOCKDOWNCODE_SIZE) { |
| memcpy(&info->prd_info.product_id[0], &prd_info[0], 3); |
| info->prd_info.chip_rev = (prd_info[3] >> 4) & 0xF; |
| info->prd_info.fpc_rev = prd_info[3] & 0xF; |
| info->prd_info.t_sensor_rev = prd_info[4]; |
| info->prd_info.site = prd_info[5]; |
| info->prd_info.inspector_no = prd_info[6]; |
| memcpy(&info->prd_info.date[0], &prd_info[7], 6); |
| |
| info->fts_command(info, SENSEON); |
| return 0; |
| } |
| prd_info[offset+i] = data[i+3]; |
| tsp_debug_dbg(info->dev, "[fts_lockdown_read] code [0x%02X]\n", prd_info[offset+i]); |
| } |
| } |
| } else if ((data[0] == EVENTID_ERROR) && (data[1] == EVENTID_ERROR_LOCKDOWN)) { |
| switch (data[2] & 0x0F) { |
| case 0x01: |
| tsp_debug_err(info->dev, "[fts_lockdown_read] Error - no lockdown code"); |
| goto error; |
| case 0x02: |
| tsp_debug_err(info->dev, "[fts_lockdown_read] Error - Data Corrupted"); |
| goto error; |
| case 0x03: |
| tsp_debug_err(info->dev, "[fts_lockdown_read] Error - Command format invalid"); |
| goto error; |
| } |
| } |
| } |
| |
| tsp_debug_err(info->dev, "[fts_lockdown_read] Error - Time over, retry =%d", retry); |
| error: |
| info->fts_command(info, SENSEON); |
| |
| return -EINVAL; |
| } |
| |
| int fts_get_version_info(struct fts_ts_info *info) |
| { |
| /* This function must be called with interrupts/irqs disabled */ |
| int rc = 0; |
| unsigned char addr[3] = {0xD0, 0x00, 0x56}; |
| unsigned char buff[7] = {0}; |
| char str[16] = {0}; |
| int str_ret = 0; |
| |
| rc = fts_read_reg(info, &addr[0], 3, &buff[0], 7); |
| if (rc < 0) { |
| tsp_debug_err(&info->client->dev, "FTS get version info fail!\n"); |
| goto error; |
| } |
| |
| info->fw_version_of_ic = buff[1] + (buff[2] << 8); |
| info->config_version_of_ic = buff[3] + (buff[4] << 8); |
| info->fw_main_version_of_ic = buff[6] + (buff[5] << 8); |
| info->ic_fw_ver.build = ((buff[5] >> 4) & 0x0F); |
| info->ic_fw_ver.major = (buff[5] & 0x0F); |
| info->ic_fw_ver.minor = buff[6]; |
| |
| str_ret += snprintf(str + str_ret, sizeof(str) - str_ret, |
| "v%d.%02d", |
| info->ic_fw_ver.major, |
| info->ic_fw_ver.minor); |
| |
| if (info->ic_fw_ver.build) { |
| str_ret += snprintf(str + str_ret, sizeof(str) - str_ret, |
| ".%d", |
| info->ic_fw_ver.build); |
| } |
| |
| fts_get_afe_info(info); |
| fts_product_info_read(info); |
| get_pure_autotune_status(info); |
| |
| tsp_debug_info(&info->client->dev, |
| "IC Firmware Version: 0x%04X [%s] " |
| "IC Config Version: 0x%04X " |
| "IC Main Version: 0x%04X " |
| "AFE Version: 0x%02X\n", |
| info->fw_version_of_ic, str, |
| info->config_version_of_ic, |
| info->fw_main_version_of_ic, |
| info->afe_ver); |
| tsp_debug_info(&info->client->dev, |
| "product id: [%02x %02x %02x]\n", |
| info->prd_info.product_id[0], |
| info->prd_info.product_id[1], |
| info->prd_info.product_id[2]); |
| tsp_debug_info(&info->client->dev, |
| "Chip revision: %d, fpc: %d, t_sensor: %d, site: %d, " |
| "inspector_no: %d\n", |
| info->prd_info.chip_rev, info->prd_info.fpc_rev, |
| info->prd_info.t_sensor_rev, info->prd_info.site, |
| info->prd_info.inspector_no); |
| tsp_debug_info(&info->client->dev, |
| "date : %02d.%02d.%02d %02d:%02d:%02d\n", |
| info->prd_info.date[0], info->prd_info.date[1], |
| info->prd_info.date[2], info->prd_info.date[3], |
| info->prd_info.date[4], info->prd_info.date[5]); |
| |
| error: |
| return rc; |
| } |
| |
| int fts_read_chip_id(struct fts_ts_info *info) |
| { |
| unsigned char regAdd[3] = {0xB6, 0x00, 0x04}; |
| unsigned char val[7] = {0}; |
| int ret = 0; |
| |
| ret = fts_read_reg(info, regAdd, 3, (unsigned char *)val, 7); |
| if (ret < 0) { |
| tsp_debug_err(&info->client->dev, "%s failed. ret: %d\n", |
| __func__, ret); |
| return ret; |
| } |
| |
| tsp_debug_dbg(&info->client->dev, |
| "FTS %02X%02X%02X = %02X %02X %02X %02X %02X %02X\n", |
| regAdd[0], regAdd[1], regAdd[2], |
| val[1], val[2], val[3], val[4], |
| val[5], val[6]); |
| |
| if ((val[1] == FTS_ID0) && (val[2] == FTS_ID1)) { |
| if ((val[5] == 0x00) && (val[6] == 0x00)) { |
| tsp_debug_err(&info->client->dev, |
| "\n\r[fts_read_chip_id] Error - No FW : %02X %02X", |
| val[5], val[6]); |
| info->flash_corruption_info.fw_broken = true; |
| } else { |
| tsp_debug_info(&info->client->dev, |
| "FTS Chip ID : %02X %02X\n", |
| val[1], val[2]); |
| info->flash_corruption_info.fw_broken = false; |
| } |
| } else |
| return -FTS_ERROR_INVALID_CHIP_ID; |
| |
| return ret; |
| } |
| |
| int fts_wait_for_ready(struct fts_ts_info *info) |
| { |
| int rc = 0; |
| unsigned char addr; |
| unsigned char data[FTS_EVENT_SIZE]; |
| int retry = 0; |
| int err_cnt = 0; |
| |
| memset(data, 0x0, FTS_EVENT_SIZE); |
| |
| addr = READ_ONE_EVENT; |
| |
| while (fts_read_reg(info, &addr, 1, |
| (unsigned char *)data, FTS_EVENT_SIZE)) { |
| if (data[0] == EVENTID_CONTROLLER_READY) { |
| info->flash_corruption_info.cfg_broken = false; |
| info->flash_corruption_info.cx_broken = false; |
| rc = 0; |
| break; |
| } |
| |
| if (data[0] == EVENTID_ERROR) { |
| if (data[1] == EVENTID_ERROR_FLASH_CORRUPTION) { |
| rc = -FTS_ERROR_EVENT_ID; |
| |
| tsp_debug_err(&info->client->dev, |
| "%s: flash corruption:%02X,%02X,%02X\n", |
| __func__, data[0], |
| data[1], data[2]); |
| |
| switch (data[2]) { |
| case EVENTID_ERROR_CONFIG_FLASH_CORRUPTION_1: |
| info->flash_corruption_info.cfg_broken = true; |
| break; |
| case EVENTID_ERROR_CONFIG_FLASH_CORRUPTION_2: |
| info->flash_corruption_info.cfg_broken = true; |
| break; |
| case EVENTID_ERROR_CX_FLASH_CORRUPTION: |
| info->flash_corruption_info.cx_broken = true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (err_cnt++ > 32) { |
| rc = -FTS_ERROR_EVENT_ID; |
| break; |
| } |
| continue; |
| } |
| |
| if (retry++ > FTS_RETRY_COUNT) { |
| rc = -FTS_ERROR_TIMEOUT; |
| tsp_debug_err(&info->client->dev, "%s: Time Over\n", |
| __func__); |
| |
| if (info->lowpower_mode) { |
| schedule_delayed_work(&info->reset_work, |
| msecs_to_jiffies(10)); |
| } |
| break; |
| } |
| fts_delay(20); |
| } |
| |
| tsp_debug_dbg(&info->client->dev, |
| "%s: %02X, %02X, %02X, %02X, %02X, %02X, %02X, %02X\n", |
| __func__, data[0], data[1], data[2], data[3], |
| data[4], data[5], data[6], data[7]); |
| |
| return rc; |
| } |
| |
| int fts_get_channel_info(struct fts_ts_info *info) |
| { |
| int rc = 0; |
| unsigned char cmd[4] = { 0xB2, 0x00, 0x14, 0x02 }; |
| unsigned char data[FTS_EVENT_SIZE]; |
| int retry = 0; |
| |
| memset(data, 0x0, FTS_EVENT_SIZE); |
| |
| fts_write_reg(info, &cmd[0], 4); |
| cmd[0] = READ_ONE_EVENT; |
| while (fts_read_reg |
| (info, &cmd[0], 1, (unsigned char *)data, FTS_EVENT_SIZE)) { |
| if (data[0] == EVENTID_RESULT_READ_REGISTER) { |
| if ((data[1] == cmd[1]) && (data[2] == cmd[2])) { |
| info->SenseChannelLength = data[3]; |
| info->ForceChannelLength = data[4]; |
| rc = 0; |
| break; |
| } |
| } |
| if (retry++ > 30) { |
| rc = -1; |
| tsp_debug_err(&info->client->dev, |
| "Time over - wait for channel info\n"); |
| break; |
| } |
| fts_delay(5); |
| } |
| return rc; |
| } |
| |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| int fts_get_noise_param_address(struct fts_ts_info *info) |
| { |
| int rc = 0; |
| unsigned char regAdd[3]; |
| unsigned char rData[3]; |
| struct fts_noise_param *noise_param; |
| int i; |
| |
| noise_param = (struct fts_noise_param *)&info->noise_param; |
| |
| regAdd[0] = 0xd0; |
| regAdd[1] = 0x00; |
| regAdd[2] = 32 * 2; |
| |
| if (info->digital_rev == FTS_DIGITAL_REV_1) |
| rc = fts_read_reg(info, regAdd, 3, |
| (unsigned char *)noise_param->pAddr, 2); |
| else { |
| rc = fts_read_reg(info, regAdd, 3, (unsigned char *)rData, 3); |
| noise_param->pAddr[0] = rData[1] + (rData[2]<<8); |
| } |
| |
| for (i = 1; i < MAX_NOISE_PARAM; i++) |
| noise_param->pAddr[i] = noise_param->pAddr[0] + i * 2; |
| |
| for (i = 0; i < MAX_NOISE_PARAM; i++) |
| tsp_debug_dbg(&info->client->dev, |
| "Get Noise Param%d Address = 0x%4x\n", |
| i, noise_param->pAddr[i]); |
| |
| return rc; |
| } |
| |
| static int fts_get_noise_param(struct fts_ts_info *info) |
| { |
| int rc = 0; |
| unsigned char regAdd[3]; |
| unsigned char data[MAX_NOISE_PARAM * 2]; |
| struct fts_noise_param *noise_param; |
| int i; |
| unsigned char buf[3]; |
| |
| noise_param = (struct fts_noise_param *)&info->noise_param; |
| memset(data, 0x0, MAX_NOISE_PARAM * 2); |
| |
| for (i = 0; i < MAX_NOISE_PARAM; i++) { |
| regAdd[0] = 0xb3; |
| regAdd[1] = 0x00; |
| regAdd[2] = 0x10; |
| fts_write_reg(info, regAdd, 3); |
| |
| regAdd[0] = 0xb1; |
| regAdd[1] = (noise_param->pAddr[i] >> 8) & 0xff; |
| regAdd[2] = noise_param->pAddr[i] & 0xff; |
| rc = fts_read_reg(info, regAdd, 3, &buf[0], 3); |
| |
| noise_param->pData[i] = buf[1]+(buf[2]<<8); |
| } |
| |
| for (i = 0; i < MAX_NOISE_PARAM; i++) { |
| tsp_debug_dbg(&info->client->dev, |
| "Get Noise Param%d Address " |
| "[ 0x%04x ] = 0x%04x\n", i, |
| noise_param->pAddr[i], |
| noise_param->pData[i]); |
| } |
| |
| return rc; |
| } |
| |
| static int fts_set_noise_param(struct fts_ts_info *info) |
| { |
| int i; |
| unsigned char regAdd[5]; |
| struct fts_noise_param *noise_param; |
| |
| noise_param = (struct fts_noise_param *)&info->noise_param; |
| |
| for (i = 0; i < MAX_NOISE_PARAM; i++) { |
| regAdd[0] = 0xb3; |
| regAdd[1] = 0x00; |
| regAdd[2] = 0x10; |
| fts_write_reg(info, regAdd, 3); |
| |
| regAdd[0] = 0xb1; |
| regAdd[1] = (noise_param->pAddr[i] >> 8) & 0xff; |
| regAdd[2] = noise_param->pAddr[i] & 0xff; |
| regAdd[3] = noise_param->pData[i] & 0xff; |
| regAdd[4] = (noise_param->pData[i] >> 8) & 0xff; |
| fts_write_reg(info, regAdd, 5); |
| } |
| |
| for (i = 0; i < MAX_NOISE_PARAM; i++) { |
| tsp_debug_dbg(&info->client->dev, |
| "Set Noise Param%d Address " |
| "[ 0x%04x ] = 0x%04x\n", |
| i, |
| noise_param->pAddr[i], |
| noise_param->pData[i]); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| int fts_cmd_completion_check(struct fts_ts_info *info, uint8_t event1, uint8_t event2, uint8_t event3) |
| { |
| unsigned char val[8]; |
| unsigned char reg[2] = {0x85, 0}; |
| int retry = 100; |
| int rc = 0; |
| |
| while (retry--) { |
| fts_delay(10); |
| info->fts_read_reg(info, ®[0], 1, &val[0], FTS_EVENT_SIZE); |
| if ((val[0] == event1) && (val[1] == event2) && (val[2] == event3)) { |
| tsp_debug_info(&info->client->dev, |
| "\n\r[fts_cmd_completion_check] OK [%02x][%02x][%02x]", val[0], val[1], val[2]); |
| return rc; |
| } else if (val[0] == 0x0F) { |
| tsp_debug_err(&info->client->dev, |
| "\n\r[fts_cmd_completion_check] Error - [%02x][%02x][%02x]", val[0], val[1], val[2]); |
| } |
| } |
| |
| rc = -1; |
| if (retry <= 0) |
| tsp_debug_err(&info->client->dev, |
| "\n\r[fts_cmd_completion_check] Error - Time Over [%02x][%02x][%02x]", event1, event2, event3); |
| return rc; |
| } |
| |
| static int fts_init(struct fts_ts_info *info) |
| { |
| unsigned char val[16]; |
| unsigned char regAdd[8]; |
| int rc = 0; |
| |
| fts_interrupt_set(info, INT_DISABLE); |
| fts_irq_enable(info, false); |
| |
| rc = fts_systemreset(info); |
| if (rc < 0) { |
| tsp_debug_err(&info->client->dev, "%s: Failed to system reset(rc = %d)\n", |
| __func__, rc); |
| return rc; |
| } |
| |
| rc = fts_wait_for_ready(info); |
| if (rc == -FTS_ERROR_EVENT_ID) { |
| info->fw_version_of_ic = 0; |
| info->config_version_of_ic = 0; |
| info->fw_main_version_of_ic = 0; |
| } else { |
| fts_get_version_info(info); |
| } |
| |
| rc = fts_read_chip_id(info); |
| if (rc < 0) { |
| tsp_debug_err(&info->client->dev, "%s: Failed to fts_read_chip_id\n", |
| __func__); |
| return rc; |
| } |
| /* |
| rc = fts_fw_update(info); |
| if (rc < 0) |
| tsp_debug_err(&info->client->dev, "%s: Failed to firmware update\n", |
| __func__); |
| */ |
| |
| #ifdef FEATURE_FTS_PRODUCTION_CODE |
| info->digital_rev = FTS_DIGITAL_REV_2; |
| rc = info->fts_get_channel_info(info); |
| if (rc == 0) { |
| tsp_debug_dbg(&info->client->dev, |
| "FTS Sense(%02d) Force(%02d)\n", |
| info->SenseChannelLength, |
| info->ForceChannelLength); |
| } else { |
| tsp_debug_err(&info->client->dev, |
| "FTS read failed rc = %d\n", rc); |
| tsp_debug_err(&info->client->dev, |
| "FTS Initialise Failed\n"); |
| return rc; |
| } |
| info->pFrame = |
| kzalloc(info->SenseChannelLength * info->ForceChannelLength * 2, |
| GFP_KERNEL); |
| if (info->pFrame == NULL) { |
| tsp_debug_err(&info->client->dev, |
| "FTS pFrame kzalloc Failed\n"); |
| return -ENOMEM; |
| } |
| info->cx_data = kzalloc(info->SenseChannelLength * |
| info->ForceChannelLength, GFP_KERNEL); |
| if (!info->cx_data) |
| tsp_debug_err(&info->client->dev, |
| "%s: cx_data kzalloc Failed\n", __func__); |
| #endif |
| fts_command(info, SENSEON); |
| |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| fts_get_noise_param_address(info); |
| #endif |
| /* fts driver set functional feature */ |
| info->touch_count = 0; |
| info->palm_pressed = false; |
| info->flip_enable = false; |
| info->mainscr_disable = false; |
| |
| info->deepsleep_mode = false; |
| info->wirelesscharger_mode = false; |
| info->lowpower_mode = false; |
| info->lowpower_flag = 0x00; |
| info->fts_power_state = FTS_POWER_STATE_ACTIVE; |
| |
| fts_command(info, FORCECALIBRATION); |
| |
| fts_irq_enable(info, true); |
| fts_interrupt_set(info, INT_ENABLE); |
| |
| memset(val, 0x0, 4); |
| regAdd[0] = READ_STATUS; |
| fts_read_reg(info, regAdd, 1, (unsigned char *)val, 4); |
| tsp_debug_dbg(&info->client->dev, |
| "FTS ReadStatus(0x84) : " |
| "%02X %02X %02X %02X\n", |
| val[0], val[1], val[2], val[3]); |
| |
| tsp_debug_info(&info->client->dev, |
| "FTS Initialized\n"); |
| |
| return 0; |
| } |
| |
| static void fts_debug_msg_event_handler(struct fts_ts_info *info, |
| unsigned char data[]) |
| { |
| tsp_debug_info(&info->client->dev, |
| "%s: %02X %02X %02X %02X " |
| "%02X %02X %02X %02X\n", __func__, |
| data[0], data[1], data[2], data[3], |
| data[4], data[5], data[6], data[7]); |
| } |
| |
| static void fts_error_event_handler(struct fts_ts_info *info, |
| unsigned char data[]) |
| { |
| unsigned char error_type = data[1]; |
| |
| if (error_type == EVENTID_ERROR_M3) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_M3"); |
| else if (error_type == EVENTID_ERROR_AFE) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_AFE"); |
| else if (error_type == EVENTID_ERROR_FLASH_CORRUPTION) |
| tsp_debug_info(&info->client->dev, |
| "EVENTID_ERROR_FLASH_CORRUPTION"); |
| else if (error_type == EVENTID_ERROR_ITO) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_ITO"); |
| else if (error_type == EVENTID_ERROR_OSC_TRIM) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_OSC_TRIM"); |
| else if (error_type == EVENTID_ERROR_RTOS) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_RTOS"); |
| else if (error_type == EVENTID_ERROR_CX_TUNE) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_CX_TUNE"); |
| else if (error_type == EVENTID_ERROR_LIB) |
| tsp_debug_info(&info->client->dev, "EVENTID_ERROR_LIB"); |
| else |
| fts_debug_msg_event_handler(info, data); |
| } |
| |
| static void fts_status_event_handler(struct fts_ts_info *info, |
| unsigned char status, |
| unsigned char data[]) |
| { |
| if (status == STATUS_EVENT_MUTUAL_AUTOTUNE_DONE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Mutual Autotune Done Event\n"); |
| } else if (status == STATUS_EVENT_SELF_AUTOTUNE_DONE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Self Autotune Done Event\n"); |
| } else if (status == STATUS_EVENT_FLASH_WRITE_CONFIG) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Flash Write Config Event\n"); |
| } else if (status == STATUS_EVENT_FLASH_WRITE_CXTUNE_VALUE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Flash Write CX Tune Event\n"); |
| } else if (status == STATUS_EVENT_FORCE_CALIBRATION) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Force Cal Event [ %x ]\n", |
| data[4]); |
| } else if (status == STATUS_EVENT_FORCE_CAL_DONE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Force Cal Done Event\n"); |
| } else if (status == STATUS_EVENT_RESERVED) { |
| tsp_debug_dbg(&info->client->dev, |
| "[FTS] Received Reserved Event\n"); |
| } else if (status == STATUS_EVENT_LOCKDOWN_FOR_LGD) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Lockdown Event\n"); |
| } else if (status == STATUS_EVENT_FRAME_DROP) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Frame Drop Event\n"); |
| } else if (status == STATUS_EVENT_WATER_MODE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Water Mode Event [ %s ]\n", |
| (data[2] == 0) ? "OFF" : "ON"); |
| } else if (status == STATUS_EVENT_PURE_AUTOTUNE_FLAG_WRITE_FINISH) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Pure Autotune Write Finish Event\n"); |
| } else if (status == STATUS_EVENT_PURE_AUTOTUNE_FLAG_CLEAR_FINISH) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Pure Autotune Clear Finish Event\n"); |
| } else if (status == STATUS_EVENT_BASIC_AUTOTUNE_PROTECTION) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Basic Autotune Protection Event [ %x ]\n", |
| data[2]); |
| } else if (status == STATUS_EVENT_FLASH_WRITE_AUTOTUNE_VALUE) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Flash Write Autotune Value Event\n"); |
| } else if (status == STATUS_EVENT_F_CAL_AFTER_AUTOTUNE_PROTECTION) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received F Cal After Autotune Event\n"); |
| } else if (status == STATUS_EVENT_CHARGER_CONNECTED) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Charger Connected Event\n"); |
| } else if (status == STATUS_EVENT_CHARGER_DISCONNECTED) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Charger Disconnected Event\n"); |
| } else if (status == STATUS_EVENT_WIRELESS_CHARGER_ON) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Wireless Charger On Event\n"); |
| } else if (status == STATUS_EVENT_WIRELESS_CHARGER_OFF) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received Wireless Charger Off Event\n"); |
| } else if (status == STATUS_EVENT_REBOOT_BY_ESD) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received ESD detected Event need to Reset\n"); |
| schedule_delayed_work(&info->reset_work, msecs_to_jiffies(10)); |
| } else if (status == STATUS_EVENT_VR_MODE_ENABLED) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received VR Mode Enabled Event\n"); |
| info->vr_mode = 1; |
| } else if (status == STATUS_EVENT_VR_MODE_DISABLED) { |
| tsp_debug_info(&info->client->dev, |
| "[FTS] Received VR Mode Disabled Event\n"); |
| info->vr_mode = 0; |
| } else |
| fts_debug_msg_event_handler(info, |
| data); |
| } |
| |
| static unsigned char fts_event_handler_type_b(struct fts_ts_info *info, |
| unsigned char data[], |
| unsigned char LeftEvent) |
| { |
| unsigned char EventNum = 0; |
| unsigned char NumTouches = 0; |
| unsigned char TouchID = 0, EventID = 0, status = 0; |
| unsigned char LastLeftEvent = 0; |
| unsigned int slotsReported = 0; |
| int x = 0, y = 0, z = 0; |
| int bw = 0, bh = 0, palm = 0; |
| int orient = 0; |
| |
| #if defined(CONFIG_INPUT_BOOSTER) |
| bool booster_restart = false; |
| #endif |
| |
| for (EventNum = 0; EventNum < LeftEvent; EventNum++) { |
| #ifdef DEBUG |
| tsp_debug_dbg(&info->client->dev, |
| "%d %2x %2x %2x %2x %2x %2x %2x %2x\n", |
| EventNum, |
| data[EventNum * FTS_EVENT_SIZE], |
| data[EventNum * FTS_EVENT_SIZE+1], |
| data[EventNum * FTS_EVENT_SIZE+2], |
| data[EventNum * FTS_EVENT_SIZE+3], |
| data[EventNum * FTS_EVENT_SIZE+4], |
| data[EventNum * FTS_EVENT_SIZE+5], |
| data[EventNum * FTS_EVENT_SIZE+6], |
| data[EventNum * FTS_EVENT_SIZE+7]); |
| tsp_debug_dbg(&info->client->dev, |
| "fts_power_state (%d)\n", |
| info->fts_power_state ); |
| #endif |
| |
| if (info->fts_power_state == FTS_POWER_STATE_LOWPOWER) |
| EventID = data[EventNum * FTS_EVENT_SIZE] & 0xFF; |
| else |
| EventID = data[EventNum * FTS_EVENT_SIZE] & 0x0F; |
| |
| if ((EventID >= 3) && (EventID <= 5)) { |
| LastLeftEvent = 0; |
| NumTouches = 1; |
| TouchID = (data[EventNum * FTS_EVENT_SIZE] >> 4) & 0x0F; |
| } else { |
| LastLeftEvent = |
| data[7 + EventNum * FTS_EVENT_SIZE] & 0x0F; |
| NumTouches = |
| (data[1 + EventNum * FTS_EVENT_SIZE] & 0xF0) >> 4; |
| TouchID = data[1 + EventNum * FTS_EVENT_SIZE] & 0x0F; |
| EventID = data[EventNum * FTS_EVENT_SIZE] & 0xFF; |
| status = data[1 + EventNum * FTS_EVENT_SIZE] & 0xFF; |
| } |
| |
| /* If a slot is reported more than once within an irq, sync |
| * between the events. This should be an uncommon occurrence. |
| */ |
| if (slotsReported & (1 << TouchID)) { |
| input_sync(info->input_dev); |
| slotsReported = 0; |
| tsp_debug_err(&info->client->dev, |
| "%s: Syncing between duplicate slots.\n", |
| __func__); |
| } |
| slotsReported |= (1 << TouchID); |
| |
| switch (EventID) { |
| case EVENTID_NO_EVENT: |
| break; |
| |
| case EVENTID_ERROR: |
| fts_error_event_handler(info, |
| &data[EventNum * FTS_EVENT_SIZE]); |
| break; |
| |
| case EVENTID_HOVER_ENTER_POINTER: |
| case EVENTID_HOVER_MOTION_POINTER: |
| x = ((data[4 + EventNum * FTS_EVENT_SIZE] & 0xF0) >> 4) |
| | ((data[2 + EventNum * FTS_EVENT_SIZE]) << 4); |
| y = ((data[4 + EventNum * FTS_EVENT_SIZE] & 0x0F) | |
| ((data[3 + EventNum * FTS_EVENT_SIZE]) << 4)); |
| |
| z = data[5 + EventNum * FTS_EVENT_SIZE]; |
| |
| input_mt_slot(info->input_dev, 0); |
| input_mt_report_slot_state(info->input_dev, |
| MT_TOOL_FINGER, 1); |
| |
| input_report_key(info->input_dev, BTN_TOUCH, 0); |
| input_report_key(info->input_dev, BTN_TOOL_FINGER, 1); |
| |
| input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); |
| input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); |
| input_report_abs(info->input_dev, ABS_MT_DISTANCE, 255 - z); |
| break; |
| |
| case EVENTID_HOVER_LEAVE_POINTER: |
| input_mt_slot(info->input_dev, 0); |
| input_mt_report_slot_state(info->input_dev, |
| MT_TOOL_FINGER, 0); |
| break; |
| |
| case EVENTID_ENTER_POINTER: |
| if (info->fts_power_state == FTS_POWER_STATE_LOWPOWER) |
| break; |
| |
| info->touch_count++; |
| #if defined(CONFIG_INPUT_BOOSTER) |
| booster_restart = true; |
| #endif |
| case EVENTID_MOTION_POINTER: |
| if (info->fts_power_state == FTS_POWER_STATE_LOWPOWER) { |
| tsp_debug_info(&info->client->dev, |
| "%s: low power mode\n", __func__); |
| fts_release_all_finger(info); |
| break; |
| } |
| |
| if (info->touch_count == 0) { |
| tsp_debug_info(&info->client->dev, |
| "%s: count 0\n", __func__); |
| fts_release_all_finger(info); |
| break; |
| } |
| |
| if ((EventID == EVENTID_MOTION_POINTER) && |
| (info->finger[TouchID].state == |
| EVENTID_LEAVE_POINTER)) { |
| tsp_debug_info(&info->client->dev, |
| "%s: state leave but point is moved.\n", __func__); |
| break; |
| } |
| |
| if (info->fts_power_state == FTS_POWER_STATE_LOWPOWER) |
| break; |
| |
| x = ((data[1 + EventNum * FTS_EVENT_SIZE] |
| & 0xFF) << 4) + |
| ((data[3 + EventNum * FTS_EVENT_SIZE] |
| & 0xF0) >> 4); |
| y = ((data[2 + EventNum * FTS_EVENT_SIZE] |
| & 0xFF) << 4) + |
| (data[3 + EventNum * FTS_EVENT_SIZE] |
| & 0xF); |
| |
| z = data[4 + EventNum * FTS_EVENT_SIZE]; |
| |
| bw = (data[6 + EventNum * FTS_EVENT_SIZE] << 2) |
| | ((data[7 + EventNum * FTS_EVENT_SIZE] >> 6) |
| & 0x03); |
| |
| bh = (data[7 + EventNum * FTS_EVENT_SIZE] & 0x3F) |
| * bw / 63; |
| |
| orient = (s8)data[5 + EventNum * FTS_EVENT_SIZE]; |
| |
| if (z == 255) { |
| tsp_debug_info(&info->client->dev, |
| "%s: Palm Detected\n", __func__); |
| tsp_debug_event(&info->client->dev, "%s: " |
| "[ID:%2d X:%4d Y:%4d Z:%4d " |
| " WM:%4d Wm:%4d Orient:%2d " |
| "tc:%2d]\n", __func__, |
| TouchID, x, y, z, |
| max(bw, bh), min(bw, bh), |
| orient, info->touch_count); |
| info->palm_pressed = true; |
| fts_release_all_finger(info); |
| return 0; |
| } |
| |
| input_mt_slot(info->input_dev, TouchID); |
| input_mt_report_slot_state(info->input_dev, |
| MT_TOOL_FINGER, |
| 1 + (palm << 1)); |
| |
| input_report_key(info->input_dev, BTN_TOUCH, 1); |
| input_report_key(info->input_dev, |
| BTN_TOOL_FINGER, 1); |
| input_report_abs(info->input_dev, |
| ABS_MT_POSITION_X, x); |
| input_report_abs(info->input_dev, |
| ABS_MT_POSITION_Y, y); |
| input_report_abs(info->input_dev, |
| ABS_MT_TOUCH_MAJOR, bw); |
| input_report_abs(info->input_dev, |
| ABS_MT_TOUCH_MINOR, bh); |
| input_report_abs(info->input_dev, |
| ABS_MT_PRESSURE, z); |
| input_report_abs(info->input_dev, |
| ABS_MT_ORIENTATION, orient); |
| info->finger[TouchID].lx = x; |
| info->finger[TouchID].ly = y; |
| |
| break; |
| |
| case EVENTID_LEAVE_POINTER: |
| if (info->fts_power_state == FTS_POWER_STATE_LOWPOWER) |
| break; |
| |
| if (info->palm_pressed) { |
| tsp_debug_event(&info->client->dev, |
| "%s: Palm Released\n", |
| __func__); |
| info->palm_pressed = false; |
| return 0; |
| } |
| |
| if (info->touch_count <= 0) { |
| tsp_debug_info(&info->client->dev, |
| "%s: count 0\n", __func__); |
| fts_release_all_finger(info); |
| break; |
| } |
| |
| info->touch_count--; |
| |
| input_mt_slot(info->input_dev, TouchID); |
| |
| input_mt_report_slot_state(info->input_dev, |
| MT_TOOL_FINGER, 0); |
| |
| if (info->touch_count == 0) { |
| /* Clear BTN_TOUCH when All touch are released */ |
| input_report_key(info->input_dev, BTN_TOUCH, 0); |
| input_report_key(info->input_dev, BTN_TOOL_FINGER, 0); |
| |
| } |
| break; |
| case EVENTID_STATUS_EVENT: |
| fts_status_event_handler(info, |
| status, |
| &data[EventNum * |
| FTS_EVENT_SIZE]); |
| break; |
| |
| #ifdef FEATURE_FTS_PRODUCTION_CODE |
| case EVENTID_RESULT_READ_REGISTER: |
| procedure_cmd_event(info, &data[EventNum * FTS_EVENT_SIZE]); |
| break; |
| #endif /* FEATURE_FTS_PRODUCTION_CODE */ |
| |
| default: |
| fts_debug_msg_event_handler(info, |
| &data[EventNum * |
| FTS_EVENT_SIZE]); |
| continue; |
| } |
| |
| if (EventID == EVENTID_ENTER_POINTER) |
| tsp_debug_event(&info->client->dev, |
| "[P] tID:%d x:%d y:%d w:%d " |
| "h:%d z:%d p:%d tc:%d tm:%d\n", |
| TouchID, x, y, bw, |
| bh, z, palm, info->touch_count, info->touch_mode); |
| else if (EventID == EVENTID_HOVER_ENTER_POINTER) |
| tsp_debug_event(&info->client->dev, |
| "[HP] tID:%d x:%d y:%d z:%d\n", |
| TouchID, x, y, z); |
| |
| else if (EventID == EVENTID_LEAVE_POINTER) { |
| tsp_debug_event(&info->client->dev, |
| "[R] tID:%d mc: %d tc:%d lx: %d ly: %d " |
| "Ver[%02X%04X%01X%01X]\n", |
| TouchID, info->finger[TouchID].mcount, info->touch_count, |
| info->finger[TouchID].lx, |
| info->finger[TouchID].ly, |
| info->panel_revision, |
| info->fw_main_version_of_ic, |
| info->flip_enable, |
| info->mainscr_disable); |
| |
| info->finger[TouchID].mcount = 0; |
| } else if (EventID == EVENTID_HOVER_LEAVE_POINTER) { |
| tsp_debug_event(&info->client->dev, |
| "[HR] tID:%d Ver[%02X%04X%01X]\n", |
| TouchID, |
| info->panel_revision, |
| info->fw_main_version_of_ic, |
| info->flip_enable); |
| info->finger[TouchID].mcount = 0; |
| } else if (EventID == EVENTID_MOTION_POINTER) |
| info->finger[TouchID].mcount++; |
| |
| if ((EventID == EVENTID_ENTER_POINTER) || |
| (EventID == EVENTID_MOTION_POINTER) || |
| (EventID == EVENTID_LEAVE_POINTER)) |
| info->finger[TouchID].state = EventID; |
| } |
| |
| input_sync(info->input_dev); |
| |
| #if defined(CONFIG_INPUT_BOOSTER) |
| if ((EventID == EVENTID_ENTER_POINTER) |
| || (EventID == EVENTID_LEAVE_POINTER)) { |
| if (booster_restart) |
| input_booster_send_event(BOOSTER_DEVICE_TOUCH, BOOSTER_MODE_ON); |
| if (!info->touch_count) |
| input_booster_send_event(BOOSTER_DEVICE_TOUCH, BOOSTER_MODE_OFF); |
| } |
| #endif |
| |
| return LastLeftEvent; |
| } |
| |
| #ifdef FTS_SUPPORT_TA_MODE |
| static void fts_ta_cb(struct fts_callbacks *cb, int ta_status) |
| { |
| struct fts_ts_info *info = |
| container_of(cb, struct fts_ts_info, callbacks); |
| |
| if (ta_status == 0x01 || ta_status == 0x03) { |
| fts_command(info, FTS_CMD_CHARGER_PLUGGED); |
| info->TA_Pluged = true; |
| tsp_debug_dbg(&info->client->dev, |
| "%s: device_control : CHARGER CONNECTED, ta_status : %x\n", |
| __func__, ta_status); |
| } else { |
| fts_command(info, FTS_CMD_CHARGER_UNPLUGGED); |
| info->TA_Pluged = false; |
| tsp_debug_dbg(&info->client->dev, |
| "%s: device_control : CHARGER DISCONNECTED, ta_status : %x\n", |
| __func__, ta_status); |
| } |
| } |
| #endif |
| |
| /** |
| * fts_hard_interrupt_handler() |
| * Called by the kernel when the touch interrupt occurs. |
| * This represents the top half of the interrupt. |
| * |
| * Set the input event timestamp here to ensure that we have an accurate |
| * estimate of when the touch event actually occurred. |
| */ |
| static irqreturn_t fts_hard_interrupt_handler(int irq, void *handle) |
| { |
| struct fts_ts_info *info = handle; |
| input_set_timestamp(info->input_dev, ktime_get()); |
| return IRQ_WAKE_THREAD; |
| } |
| |
| /** |
| * fts_interrupt_handler() |
| * |
| * Called by the kernel when an interrupt occurs (when the sensor |
| * asserts the attention irq). |
| * |
| * This function is the ISR thread and handles the acquisition |
| * and the reporting of finger data when the presence of fingers |
| * is detected. |
| */ |
| static irqreturn_t fts_interrupt_handler(int irq, void *handle) |
| { |
| struct fts_ts_info *info = handle; |
| unsigned char regAdd[4] = {0xB6, 0x00, 0x23, READ_ALL_EVENT}; |
| unsigned short evtcount = 0; |
| |
| /* prevent CPU from entering deep sleep */ |
| pm_qos_update_request(&info->pm_qos_req, 100); |
| evtcount = 0; |
| |
| fts_read_reg(info, ®Add[0], 3, (unsigned char *)&evtcount, 2); |
| evtcount = evtcount >> 8; |
| evtcount = evtcount / 2; |
| |
| if (evtcount > FTS_FIFO_MAX) |
| evtcount = FTS_FIFO_MAX; |
| |
| if (evtcount > 0) { |
| memset(info->data, 0x0, FTS_EVENT_SIZE * evtcount); |
| fts_read_reg(info, ®Add[3], 1, (unsigned char *)info->data, |
| FTS_EVENT_SIZE * evtcount); |
| fts_event_handler_type_b(info, info->data, evtcount); |
| } |
| pm_qos_update_request(&info->pm_qos_req, PM_QOS_DEFAULT_VALUE); |
| return IRQ_HANDLED; |
| } |
| |
| static void fts_irq_enable(struct fts_ts_info *info, |
| bool enable) |
| { |
| spin_lock(&info->lock); |
| |
| if (enable) { |
| if (atomic_cmpxchg(&info->irq_enabled, 0, 1) == 0) { |
| tsp_debug_dbg(info->dev, "enable_irq\n"); |
| enable_irq(info->irq); |
| |
| if (device_may_wakeup(&info->client->dev)) |
| enable_irq_wake(info->irq); |
| } |
| } else { |
| if (atomic_cmpxchg(&info->irq_enabled, 1, 0) == 1) { |
| tsp_debug_dbg(info->dev, "disable_irq\n"); |
| if (device_may_wakeup(&info->client->dev)) |
| disable_irq_wake(info->irq); |
| |
| disable_irq_nosync(info->irq); |
| } |
| } |
| |
| spin_unlock(&info->lock); |
| } |
| |
| #ifdef CONFIG_OF |
| #ifdef FTS_SUPPORT_TA_MODE |
| struct fts_callbacks *fts_charger_callbacks; |
| void tsp_charger_infom(bool en) |
| { |
| pr_err("[TSP]%s: ta:%d\n", __func__, en); |
| |
| if (fts_charger_callbacks && fts_charger_callbacks->inform_charger) |
| fts_charger_callbacks->inform_charger(fts_charger_callbacks, en); |
| } |
| static void fts_tsp_register_callback(void *cb) |
| { |
| fts_charger_callbacks = cb; |
| } |
| #endif |
| static int fts_power_ctrl(void *data, bool on) |
| { |
| struct fts_ts_info *info = (struct fts_ts_info *)data; |
| const struct fts_i2c_platform_data *pdata = info->board; |
| struct device *dev = &info->client->dev; |
| struct regulator *regulator_dvdd = NULL; |
| struct regulator *regulator_avdd = NULL; |
| static bool enabled; |
| int retval = 0; |
| |
| if (enabled == on) |
| return retval; |
| |
| /* touch power init */ |
| if (gpio_is_valid(pdata->vdd_gpio)) { |
| gpio_request(pdata->vdd_gpio, "touch-vdd"); |
| } else { |
| regulator_avdd = regulator_get(NULL, pdata->regulator_avdd); |
| if (IS_ERR_OR_NULL(regulator_avdd)) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to get %s regulator.\n", |
| __func__, pdata->regulator_avdd); |
| goto out; |
| } |
| } |
| if (gpio_is_valid(pdata->vio_gpio)) { |
| gpio_request(pdata->vio_gpio, "touch-vio"); |
| } else { |
| regulator_dvdd = regulator_get(NULL, pdata->regulator_dvdd); |
| if (IS_ERR_OR_NULL(regulator_dvdd)) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to get %s regulator.\n", |
| __func__, pdata->regulator_dvdd); |
| goto out; |
| } |
| } |
| |
| tsp_debug_info(dev, "%s: %s\n", __func__, on ? "on" : "off"); |
| |
| if (on) { |
| if (gpio_is_valid(pdata->vdd_gpio)) { |
| retval = gpio_direction_output(pdata->vdd_gpio, 1); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable vdd: %d\n", |
| __func__, retval); |
| } |
| } else if (!IS_ERR_OR_NULL(regulator_avdd)) { |
| retval = regulator_enable(regulator_avdd); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable avdd: %d\n", |
| __func__, retval); |
| goto out; |
| } |
| } |
| |
| if (gpio_is_valid(pdata->vio_gpio)) { |
| retval = gpio_direction_output(pdata->vio_gpio, 1); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable vio: %d\n", |
| __func__, retval); |
| } |
| } else if (!IS_ERR_OR_NULL(regulator_dvdd)) { |
| retval = regulator_enable(regulator_dvdd); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable dvdd: %d\n", |
| __func__, retval); |
| goto out; |
| } |
| } |
| |
| retval = pinctrl_select_state(pdata->pinctrl, pdata->pins_default); |
| if (retval < 0) |
| tsp_debug_err(dev, "%s: " |
| "Failed to configure tsp_attn pin\n", |
| __func__); |
| |
| fts_delay(5); |
| } else { |
| if (gpio_is_valid(pdata->vio_gpio)) { |
| retval = gpio_direction_output(pdata->vio_gpio, 0); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable vio: %d\n", |
| __func__, retval); |
| } |
| } else if (!IS_ERR_OR_NULL(regulator_dvdd)) { |
| retval = regulator_disable(regulator_dvdd); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable dvdd: %d\n", |
| __func__, retval); |
| goto out; |
| } |
| } |
| |
| if (gpio_is_valid(pdata->vdd_gpio)) { |
| retval = gpio_direction_output(pdata->vdd_gpio, 0); |
| if (retval) |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable vdd: %d\n", |
| __func__, retval); |
| } else if (!IS_ERR_OR_NULL(regulator_avdd)) { |
| retval = regulator_disable(regulator_avdd); |
| if (retval) { |
| tsp_debug_err(dev, "%s: " |
| "Failed to enable avdd: %d\n", |
| __func__, retval); |
| goto out; |
| } |
| } |
| |
| retval = pinctrl_select_state(pdata->pinctrl, pdata->pins_sleep); |
| if (retval < 0) |
| tsp_debug_err(dev, "%s: Failed to " |
| "configure tsp_attn pin\n", __func__); |
| } |
| |
| enabled = on; |
| |
| out: |
| regulator_put(regulator_dvdd); |
| regulator_put(regulator_avdd); |
| |
| return retval; |
| } |
| |
| static int fts_parse_dt(struct i2c_client *client) |
| { |
| struct device *dev = &client->dev; |
| struct fts_i2c_platform_data *pdata = dev->platform_data; |
| struct device_node *np = dev->of_node; |
| u32 coords[2], lines[2]; |
| int retval = 0; |
| |
| pdata->tspid = of_get_named_gpio(np, "stm,tspid_gpio", 0); |
| if (gpio_is_valid(pdata->tspid)) |
| tsp_debug_dbg(dev, |
| "TSP_ID : %d\n", |
| gpio_get_value(pdata->tspid)); |
| else |
| tsp_debug_err(dev, |
| "Failed to get tspid gpio\n"); |
| |
| pdata->tspid2 = of_get_named_gpio(np, "stm,tspid2_gpio", 0); |
| if (gpio_is_valid(pdata->tspid2)) |
| tsp_debug_dbg(dev, |
| "TSP_ID2 : %d\n", |
| gpio_get_value(pdata->tspid2)); |
| else |
| tsp_debug_err(dev, |
| "Failed to get tspid2 gpio\n"); |
| |
| pdata->gpio = of_get_named_gpio(np, "stm,irq_gpio", 0); |
| if (gpio_is_valid(pdata->gpio)) { |
| retval = gpio_request_one(pdata->gpio, |
| GPIOF_DIR_IN, "stm,tsp_int"); |
| if (retval) { |
| tsp_debug_err(dev, |
| "Unable to request tsp_int [%d]\n", |
| pdata->gpio); |
| return -EINVAL; |
| } |
| } else { |
| tsp_debug_err(dev, |
| "Failed to get irq gpio\n"); |
| return -EINVAL; |
| } |
| |
| tsp_debug_info(dev, "irq_gpio = %d\n", pdata->gpio); |
| client->irq = of_irq_get_byname(np, "tp_direct_interrupt"); |
| tsp_debug_info(dev, "client->irq = %d\n", client->irq); |
| |
| if (of_property_read_u32(np, "stm,irq_type", &pdata->irq_type)) { |
| tsp_debug_err(dev, "Failed to get irq_type property\n"); |
| return -EINVAL; |
| } |
| |
| if (of_property_read_u32(np, "stm,grip_area", &pdata->grip_area)) |
| tsp_debug_err(dev, "Failed to get grip_area property\n"); |
| |
| if (of_property_read_u32_array(np, "stm,max_coords", coords, 2)) { |
| tsp_debug_err(dev, "Failed to get max_coords property\n"); |
| return -EINVAL; |
| } |
| pdata->max_x = coords[0]; |
| pdata->max_y = coords[1]; |
| |
| if (of_property_read_u32_array(np, "stm,num_lines", lines, 2)) |
| tsp_debug_dbg(dev, "skipped to get num_lines property\n"); |
| else { |
| pdata->SenseChannelLength = lines[0]; |
| pdata->ForceChannelLength = lines[1]; |
| tsp_debug_dbg(dev, "num_of[rx,tx]: [%d,%d]\n", |
| pdata->SenseChannelLength, pdata->ForceChannelLength); |
| } |
| |
| if (of_property_read_string(np, "stm,regulator_dvdd", &pdata->regulator_dvdd)) { |
| tsp_debug_err(dev, |
| "Failed to get regulator_dvdd name property\n"); |
| } |
| if (of_property_read_string(np, "stm,regulator_avdd", &pdata->regulator_avdd)) { |
| tsp_debug_err(dev, |
| "Failed to get regulator_avdd name property\n"); |
| } |
| |
| pdata->vdd_gpio = of_get_named_gpio(np, "stm,vdd-gpio", 0); |
| if (gpio_is_valid(pdata->vdd_gpio)) |
| tsp_debug_dbg(dev, "vdd_gpio : %d\n", |
| gpio_get_value(pdata->vdd_gpio)); |
| else |
| tsp_debug_err(dev, "Failed to get vdd_gpio gpio\n"); |
| |
| pdata->vio_gpio = of_get_named_gpio(np, "stm,vio-gpio", 0); |
| if (gpio_is_valid(pdata->vio_gpio)) |
| tsp_debug_dbg(dev, "vio_gpio :" |
| "%d\n", gpio_get_value(pdata->vio_gpio)); |
| else |
| tsp_debug_err(dev, "Failed to get vio_gpio gpio\n"); |
| |
| pdata->power = fts_power_ctrl; |
| |
| pdata->reset_pin = of_get_named_gpio(np, "stm,reset-gpio", 0); |
| if (gpio_is_valid(pdata->reset_pin)) { |
| if (devm_gpio_request_one(&client->dev, pdata->reset_pin, |
| GPIOF_OUT_INIT_LOW, "reset_pin")) { |
| tsp_debug_err(dev, "Failed to request gpio reset_pin\n"); |
| pdata->reset_pin = -1; |
| } else { |
| tsp_debug_dbg(dev, "reset_pin : %d\n", |
| gpio_get_value(pdata->reset_pin)); |
| } |
| } else { |
| tsp_debug_err(dev, "Failed to get reset_pin gpio\n"); |
| } |
| |
| pdata->ta_detect_pin = of_get_named_gpio(np, "stm,ta_detect_gpio", 0); |
| if (gpio_is_valid(pdata->ta_detect_pin)) { |
| if (devm_gpio_request_one(&client->dev, pdata->ta_detect_pin, |
| GPIOF_OUT_INIT_LOW, "ta_detect_pin")) { |
| tsp_debug_err(dev, "Failed to request gpio ta_detect_pin\n"); |
| pdata->ta_detect_pin = -1; |
| } else { |
| tsp_debug_dbg(dev, "ta_detect_pin : %d\n", |
| gpio_get_value(pdata->ta_detect_pin)); |
| } |
| } else { |
| tsp_debug_err(dev, "Failed to get ta_detect_pin gpio\n"); |
| } |
| |
| /* Optional parmeters(those values are not mandatory) |
| * do not return error value even if fail to get the value |
| */ |
| of_property_read_string(np, "stm,firmware_name", &pdata->firmware_name); |
| |
| if (of_property_read_string_index(np, "stm,project_name", 0, &pdata->project_name)) |
| tsp_debug_dbg(dev, |
| "skipped to get project_name property\n"); |
| if (of_property_read_string_index(np, "stm,project_name", 1, &pdata->model_name)) |
| tsp_debug_dbg(dev, |
| "skipped to get model_name property\n"); |
| |
| pdata->max_width = 28; |
| pdata->support_hover = true; |
| pdata->support_mshover = true; |
| #ifdef FTS_SUPPORT_TA_MODE |
| pdata->register_cb = fts_tsp_register_callback; |
| #endif |
| |
| return retval; |
| } |
| #endif |
| |
| static int fts_setup_drv_data(struct i2c_client *client) |
| { |
| int retval = 0; |
| struct fts_i2c_platform_data *pdata; |
| struct fts_ts_info *info; |
| |
| /* parse dt */ |
| if (client->dev.of_node) { |
| pdata = devm_kzalloc(&client->dev, |
| sizeof(struct fts_i2c_platform_data), GFP_KERNEL); |
| |
| if (!pdata) { |
| tsp_debug_err(&client->dev, "Failed to allocate platform data\n"); |
| return -ENOMEM; |
| } |
| |
| client->dev.platform_data = pdata; |
| retval = fts_parse_dt(client); |
| if (retval) { |
| tsp_debug_err(&client->dev, "Failed to parse dt\n"); |
| return retval; |
| } |
| } else { |
| pdata = client->dev.platform_data; |
| } |
| |
| if (!pdata) { |
| tsp_debug_err(&client->dev, "No platform data found\n"); |
| return -EINVAL; |
| } |
| if (!pdata->power) { |
| tsp_debug_err(&client->dev, "No power contorl found\n"); |
| return -EINVAL; |
| } |
| |
| pdata->pinctrl = devm_pinctrl_get(&client->dev); |
| if (IS_ERR(pdata->pinctrl)) { |
| tsp_debug_err(&client->dev, "could not get pinctrl\n"); |
| return PTR_ERR(pdata->pinctrl); |
| } |
| |
| pdata->pins_default = pinctrl_lookup_state(pdata->pinctrl, "on_state"); |
| if (IS_ERR(pdata->pins_default)) |
| tsp_debug_err(&client->dev, "could not get default pinstate\n"); |
| |
| pdata->pins_sleep = pinctrl_lookup_state(pdata->pinctrl, "off_state"); |
| if (IS_ERR(pdata->pins_sleep)) |
| tsp_debug_err(&client->dev, "could not get sleep pinstate\n"); |
| |
| info = kzalloc(sizeof(struct fts_ts_info), GFP_KERNEL); |
| if (!info) { |
| tsp_debug_err(&client->dev, |
| "%s: Failed to alloc mem for info\n", |
| __func__); |
| return -ENOMEM; |
| } |
| |
| info->client = client; |
| info->board = pdata; |
| info->irq = client->irq; |
| info->irq_type = info->board->irq_type; |
| atomic_set(&info->irq_enabled, 0); |
| info->charger_connected = 0; |
| info->touch_stopped = false; |
| info->panel_revision = info->board->panel_revision; |
| info->stop_device = fts_stop_device; |
| info->start_device = fts_start_device; |
| info->fts_command = fts_command; |
| info->fts_read_reg = fts_read_reg; |
| info->fts_write_reg = fts_write_reg; |
| info->fts_systemreset = fts_systemreset; |
| info->fts_get_version_info = fts_get_version_info; |
| info->fts_wait_for_ready = fts_wait_for_ready; |
| #ifdef FEATURE_FTS_PRODUCTION_CODE |
| info->fts_get_channel_info = fts_get_channel_info; |
| info->fts_interrupt_set = fts_interrupt_set; |
| info->fts_irq_enable = fts_irq_enable; |
| info->fts_release_all_finger = fts_release_all_finger; |
| #endif |
| |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| info->fts_get_noise_param_address = fts_get_noise_param_address; |
| #endif |
| |
| #ifdef USE_OPEN_DWORK |
| INIT_DELAYED_WORK(&info->open_work, fts_open_work); |
| #endif |
| info->delay_time = 300; |
| INIT_DELAYED_WORK(&info->reset_work, fts_reset_work); |
| |
| if (info->board->support_hover) |
| tsp_debug_info(&info->client->dev, "FTS Support Hover Event\n"); |
| else |
| tsp_debug_info(&info->client->dev, "FTS Not support Hover Event\n"); |
| |
| i2c_set_clientdata(client, info); |
| |
| if (pdata->get_ddi_type) { |
| info->ddi_type = pdata->get_ddi_type(); |
| tsp_debug_info(&client->dev, |
| "%s: DDI Type is %s[%d]\n", |
| __func__, info->ddi_type ? |
| "MAGNA" : "SDC", info->ddi_type); |
| } |
| |
| info->switch_gpio = of_get_named_gpio(client->dev.of_node, |
| "stm,switch_gpio", 0); |
| tsp_debug_info(&client->dev, "switch_gpio = %d\n", info->switch_gpio); |
| |
| if (!gpio_is_valid(info->switch_gpio)) { |
| tsp_debug_err(&client->dev, "Failed to get switch gpio\n"); |
| return -EINVAL; |
| } |
| |
| retval = gpio_request_one(info->switch_gpio, |
| GPIOF_OUT_INIT_LOW, |
| "stm,tsp_i2c_switch"); |
| if (retval) { |
| tsp_debug_err(&client->dev, |
| "Unable to request tsp_i2c_switch [%d]\n", |
| info->switch_gpio); |
| return -EINVAL; |
| } |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| tui_tsp_info = info; |
| #endif |
| return retval; |
| } |
| |
| static int fts_probe(struct i2c_client *client, const struct i2c_device_id *idp) |
| { |
| int retval = 0; |
| struct fts_ts_info *info = NULL; |
| static char fts_ts_phys[64] = { 0 }; |
| struct power_supply_config psy_cfg = {}; |
| int i = 0; |
| |
| /* |
| tsp_debug_info(&client->dev, "FTS Driver [12%s] %s %s\n", |
| FTS_TS_DRV_VERSION, __DATE__, __TIME__); |
| */ |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| tsp_debug_err(&client->dev, "FTS err = EIO!\n"); |
| return -EIO; |
| } |
| |
| /* Build up driver data */ |
| retval = fts_setup_drv_data(client); |
| if (retval < 0) { |
| tsp_debug_err(&client->dev, |
| "%s: Failed to set up driver data\n", |
| __func__); |
| goto err_setup_drv_data; |
| } |
| |
| info = (struct fts_ts_info *)i2c_get_clientdata(client); |
| if (!info) { |
| tsp_debug_err(&client->dev, |
| "%s: Failed to get driver data\n", __func__); |
| retval = -ENODEV; |
| goto err_get_drv_data; |
| } |
| |
| if (info->board->power) { |
| info->board->power(info, true); |
| fts_delay(15); |
| |
| if (gpio_is_valid(info->board->reset_pin)) |
| gpio_set_value(info->board->reset_pin, 1); |
| } |
| |
| info->dev = &info->client->dev; |
| info->input_dev = input_allocate_device(); |
| if (!info->input_dev) { |
| tsp_debug_err(&info->client->dev, "FTS err = ENOMEM!\n"); |
| retval = -ENOMEM; |
| goto err_input_allocate_device; |
| } |
| |
| info->input_dev->dev.parent = &client->dev; |
| info->input_dev->name = "touchscreen"; |
| snprintf(fts_ts_phys, sizeof(fts_ts_phys), "%s/input1", |
| info->input_dev->name); |
| info->input_dev->phys = fts_ts_phys; |
| info->input_dev->id.bustype = BUS_I2C; |
| |
| #ifdef USE_OPEN_CLOSE |
| info->input_dev->open = fts_input_open; |
| info->input_dev->close = fts_input_close; |
| #endif |
| |
| set_bit(EV_SYN, info->input_dev->evbit); |
| set_bit(EV_KEY, info->input_dev->evbit); |
| set_bit(EV_ABS, info->input_dev->evbit); |
| #ifdef INPUT_PROP_DIRECT |
| set_bit(INPUT_PROP_DIRECT, info->input_dev->propbit); |
| #endif |
| set_bit(BTN_TOUCH, info->input_dev->keybit); |
| set_bit(BTN_TOOL_FINGER, info->input_dev->keybit); |
| |
| input_mt_init_slots(info->input_dev, FINGER_MAX, INPUT_MT_DIRECT); |
| input_set_abs_params(info->input_dev, ABS_MT_POSITION_X, |
| 0, info->board->max_x, 0, 0); |
| input_set_abs_params(info->input_dev, ABS_MT_POSITION_Y, |
| 0, info->board->max_y, 0, 0); |
| input_set_abs_params(info->input_dev, ABS_MT_PRESSURE, |
| 0, 255, 0, 0); |
| |
| mutex_init(&info->device_mutex); |
| mutex_init(&info->i2c_mutex); |
| spin_lock_init(&info->lock); |
| |
| info->enabled = false; |
| mutex_lock(&info->device_mutex); |
| retval = fts_init(info); |
| info->reinit_done = true; |
| mutex_unlock(&info->device_mutex); |
| if (info->flash_corruption_info.fw_broken || |
| info->flash_corruption_info.cfg_broken || |
| info->flash_corruption_info.cx_broken) { |
| tsp_debug_err(&info->client->dev, |
| "Attempt to recover corrupt/missing firmware."); |
| } else if (retval < 0) { |
| tsp_debug_err(&info->client->dev, "FTS fts_init fail!\n"); |
| goto err_fts_init; |
| } |
| |
| input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MAJOR, |
| 0, 1024, 0, 0); |
| input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MINOR, |
| 0, 1024, 0, 0); |
| input_set_abs_params(info->input_dev, ABS_MT_DISTANCE, |
| 0, 255, 0, 0); |
| input_set_abs_params(info->input_dev, ABS_MT_ORIENTATION, |
| -128, 127, 0, 0); |
| |
| input_set_drvdata(info->input_dev, info); |
| i2c_set_clientdata(client, info); |
| |
| retval = input_register_device(info->input_dev); |
| if (retval) { |
| tsp_debug_err(&info->client->dev, "FTS input_register_device fail!\n"); |
| goto err_register_input; |
| } |
| |
| for (i = 0; i < FINGER_MAX; i++) { |
| info->finger[i].state = EVENTID_LEAVE_POINTER; |
| info->finger[i].mcount = 0; |
| } |
| |
| info->enabled = true; |
| |
| tsp_debug_info(&info->client->dev, |
| "installing direct irq on GPIO %d\n", |
| info->board->gpio); |
| retval = msm_gpio_install_direct_irq(info->board->gpio, 0, 0); |
| if (retval) { |
| tsp_debug_info(&info->client->dev, |
| "%s: Failed to install direct irq, ret = %d\n", |
| __func__, retval); |
| goto err_enable_irq; |
| } |
| |
| retval = request_threaded_irq(info->irq, fts_hard_interrupt_handler, |
| fts_interrupt_handler, info->board->irq_type, |
| FTS_TS_DRV_NAME, info); |
| if (retval < 0) { |
| tsp_debug_err(&info->client->dev, |
| "%s: Failed to enable attention interrupt\n", |
| __func__); |
| goto err_enable_irq; |
| } |
| atomic_set(&info->irq_enabled, 1); |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| trustedui_set_tsp_irq(info->irq); |
| tsp_debug_info(&client->dev, "%s[%d] called!\n", |
| __func__, info->irq); |
| #endif |
| |
| #ifdef CONFIG_FB |
| info->fb_notif.notifier_call = touch_fb_notifier_callback; |
| retval = fb_register_client(&info->fb_notif); |
| if (retval < 0) { |
| tsp_debug_err(&client->dev, "%s: Failed to register fb client\n", |
| __func__); |
| goto err_fb_client; |
| } |
| #endif |
| |
| |
| #ifdef FTS_SUPPORT_TA_MODE |
| info->register_cb = info->board->register_cb; |
| |
| info->callbacks.inform_charger = fts_ta_cb; |
| if (info->register_cb) |
| info->register_cb(&info->callbacks); |
| #endif |
| |
| INIT_DELAYED_WORK(&info->psy_work, fts_psy_work); |
| psy_cfg.of_node = info->dev->of_node; |
| psy_cfg.drv_data = info; |
| info->ts_psy = devm_power_supply_register(info->dev, |
| &fts_ts_desc, &psy_cfg); |
| if (!IS_ERR_OR_NULL(info->ts_psy)) { |
| fts_external_power_changed(info->ts_psy); |
| } else if (PTR_ERR(info->ts_psy) == -EPROBE_DEFER) { |
| schedule_delayed_work(&info->psy_work, |
| msecs_to_jiffies(FTS_REGISTER_PSY_MS)); |
| } else { |
| tsp_debug_err(&client->dev, |
| "%s: Failed to register power supply\n", __func__); |
| retval = PTR_ERR(info->ts_psy); |
| goto err_power_supply; |
| } |
| |
| #ifdef FEATURE_FTS_PRODUCTION_CODE |
| fts_production_init(info); |
| #endif /* FEATURE_FTS_PRODUCTION_CODE */ |
| device_init_wakeup(&client->dev, false); |
| if (device_may_wakeup(&info->client->dev)) |
| enable_irq_wake(info->irq); |
| info->lowpower_mode = true; |
| |
| return 0; |
| |
| err_power_supply: |
| #ifdef CONFIG_FB |
| fb_unregister_client(&info->fb_notif); |
| |
| err_fb_client: |
| #endif |
| fts_irq_enable(info, false); |
| free_irq(info->irq, info); |
| |
| err_enable_irq: |
| input_unregister_device(info->input_dev); |
| info->input_dev = NULL; |
| |
| err_register_input: |
| if (info->input_dev) |
| input_free_device(info->input_dev); |
| |
| err_fts_init: |
| mutex_destroy(&info->device_mutex); |
| mutex_destroy(&info->i2c_mutex); |
| err_input_allocate_device: |
| info->board->power(info, false); |
| kfree(info); |
| err_get_drv_data: |
| err_setup_drv_data: |
| return retval; |
| } |
| |
| static int fts_remove(struct i2c_client *client) |
| { |
| struct fts_ts_info *info = i2c_get_clientdata(client); |
| |
| tsp_debug_info(&info->client->dev, "FTS removed\n"); |
| |
| #if defined(CONFIG_FB) |
| if (fb_unregister_client(&info->fb_notif)) |
| tsp_debug_err(&info->client->dev, |
| "%s: Error occured while unregistering fb_notifier.\n", __func__); |
| #endif |
| |
| fts_interrupt_set(info, INT_DISABLE); |
| fts_command(info, FLUSHBUFFER); |
| |
| fts_irq_enable(info, false); |
| free_irq(info->irq, info); |
| |
| input_mt_destroy_slots(info->input_dev); |
| |
| input_unregister_device(info->input_dev); |
| info->input_dev = NULL; |
| |
| info->board->power(info, false); |
| |
| kfree(info); |
| |
| return 0; |
| } |
| |
| #ifdef USE_OPEN_CLOSE |
| #ifdef USE_OPEN_DWORK |
| static void fts_open_work(struct work_struct *work) |
| { |
| int retval = 0; |
| struct fts_ts_info *info = container_of(work, struct fts_ts_info, |
| open_work.work); |
| |
| tsp_debug_info(&info->client->dev, "%s\n", __func__); |
| |
| retval = fts_start_device(info); |
| if (retval < 0) |
| tsp_debug_err(&info->client->dev, |
| "%s: Failed to start device\n", __func__); |
| } |
| #endif |
| static int fts_input_open(struct input_dev *dev) |
| { |
| struct fts_ts_info *info = input_get_drvdata(dev); |
| unsigned char regAdd[4] = {0xB0, 0x01, 0x29, 0x41}; |
| int retval = 0; |
| |
| tsp_debug_info(&info->client->dev, "%s\n", __func__); |
| |
| #ifdef USE_OPEN_DWORK |
| schedule_delayed_work(&info->open_work, |
| msecs_to_jiffies(TOUCH_OPEN_DWORK_TIME)); |
| #else |
| retval = fts_start_device(info); |
| if (retval < 0) { |
| tsp_debug_err(&info->client->dev, |
| "%s: Failed to start device\n", __func__); |
| goto out; |
| } |
| #endif |
| |
| tsp_debug_info(&info->client->dev, |
| "FTS cmd after wakeup : h%d\n", |
| info->retry_hover_enable_after_wakeup); |
| |
| if (info->retry_hover_enable_after_wakeup == 1) { |
| fts_write_reg(info, ®Add[0], 4); |
| fts_command(info, FTS_CMD_HOVER_ON); |
| } |
| |
| pm_qos_add_request(&info->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, |
| PM_QOS_DEFAULT_VALUE); |
| |
| out: |
| return 0; |
| } |
| |
| static void fts_input_close(struct input_dev *dev) |
| { |
| struct fts_ts_info *info = input_get_drvdata(dev); |
| |
| tsp_debug_info(&info->client->dev, "%s\n", __func__); |
| |
| pm_qos_remove_request(&info->pm_qos_req); |
| |
| #ifdef USE_OPEN_DWORK |
| cancel_delayed_work(&info->open_work); |
| #endif |
| |
| fts_stop_device(info); |
| |
| info->retry_hover_enable_after_wakeup = 0; |
| } |
| #endif |
| |
| static void fts_reinit(struct fts_ts_info *info) |
| { |
| fts_interrupt_set(info, INT_DISABLE); |
| fts_irq_enable(info, false); |
| |
| fts_systemreset(info); |
| |
| fts_wait_for_ready(info); |
| |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| fts_set_noise_param(info); |
| #endif |
| |
| fts_command(info, SENSEON); |
| fts_delay(50); |
| |
| #ifdef FTS_SUPPORT_TA_MODE |
| if (info->TA_Pluged) |
| fts_command(info, FTS_CMD_CHARGER_PLUGGED); |
| #endif |
| |
| info->touch_count = 0; |
| info->palm_pressed = false; |
| |
| fts_command(info, FLUSHBUFFER); |
| |
| fts_irq_enable(info, true); |
| fts_interrupt_set(info, INT_ENABLE); |
| } |
| |
| void fts_release_all_finger(struct fts_ts_info *info) |
| { |
| int i; |
| |
| for (i = 0; i < FINGER_MAX; i++) { |
| input_mt_slot(info->input_dev, i); |
| input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, 0); |
| |
| if ((info->finger[i].state == EVENTID_ENTER_POINTER) || |
| (info->finger[i].state == EVENTID_MOTION_POINTER)) { |
| info->touch_count--; |
| if (info->touch_count < 0) |
| info->touch_count = 0; |
| |
| tsp_debug_event(&info->client->dev, |
| "[RA] tID:%d mc: %d tc:%d Ver[%02X%04X%01X%01X]\n", |
| i, info->finger[i].mcount, info->touch_count, |
| info->panel_revision, info->fw_main_version_of_ic, |
| info->flip_enable, info->mainscr_disable); |
| } |
| |
| info->finger[i].state = EVENTID_LEAVE_POINTER; |
| info->finger[i].mcount = 0; |
| } |
| |
| input_report_key(info->input_dev, BTN_TOUCH, 0); |
| input_report_key(info->input_dev, BTN_TOOL_FINGER, 0); |
| |
| #ifdef CONFIG_INPUT_BOOSTER |
| input_booster_send_event(BOOSTER_DEVICE_TOUCH, BOOSTER_MODE_FORCE_OFF); |
| #endif |
| |
| input_sync(info->input_dev); |
| } |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| void trustedui_mode_on(void) |
| { |
| tsp_debug_info(&tui_tsp_info->client->dev, |
| "%s, release all finger..", __func__); |
| fts_release_all_finger(tui_tsp_info); |
| } |
| #endif |
| |
| static void fts_reset_work(struct work_struct *work) |
| { |
| struct fts_ts_info *info = container_of(work, struct fts_ts_info, |
| reset_work.work); |
| bool temp_lpm; |
| |
| temp_lpm = info->lowpower_mode; |
| /* Reset-routine must go to power off state */ |
| info->lowpower_mode = 0; |
| |
| tsp_debug_info(&info->client->dev, "%s, Call Power-Off to recover IC, lpm:%d\n", __func__, temp_lpm); |
| fts_stop_device(info); |
| |
| fts_delay(100); /* Delay to discharge the IC from ESD or On-state.*/ |
| if (fts_start_device(info) < 0) |
| tsp_debug_err(&info->client->dev, "%s: Failed to start device\n", __func__); |
| |
| info->lowpower_mode = temp_lpm; |
| } |
| |
| static int fts_stop_device(struct fts_ts_info *info) |
| { |
| tsp_debug_dbg(&info->client->dev, "%s\n", __func__); |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TUI cancel event call!\n", __func__); |
| fts_delay(100); |
| tui_force_close(1); |
| fts_delay(200); |
| if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TUI flag force clear!\n", __func__); |
| trustedui_clear_mask(TRUSTEDUI_MODE_VIDEO_SECURED| |
| TRUSTEDUI_MODE_INPUT_SECURED); |
| trustedui_set_mode(TRUSTEDUI_MODE_OFF); |
| } |
| } |
| #endif |
| |
| mutex_lock(&info->device_mutex); |
| |
| if (info->touch_stopped) { |
| tsp_debug_err(&info->client->dev, |
| "%s already power off\n", __func__); |
| goto out; |
| } |
| |
| if (info->lowpower_mode) { |
| tsp_debug_info(&info->client->dev, |
| "%s lowpower flag:%d\n", |
| __func__, info->lowpower_flag); |
| |
| info->fts_power_state = FTS_POWER_STATE_LOWPOWER; |
| |
| fts_interrupt_set(info, INT_DISABLE); |
| fts_irq_enable(info, false); |
| synchronize_irq(info->irq); |
| |
| fts_command(info, FLUSHBUFFER); |
| fts_command(info, SENSEOFF); |
| fts_command(info, FLUSHBUFFER); |
| |
| fts_release_all_finger(info); |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| fts_get_noise_param(info); |
| #endif |
| |
| } else { |
| fts_interrupt_set(info, INT_DISABLE); |
| fts_irq_enable(info, false); |
| synchronize_irq(info->irq); |
| |
| fts_command(info, FLUSHBUFFER); |
| fts_release_all_finger(info); |
| #ifdef FTS_SUPPORT_NOISE_PARAM |
| fts_get_noise_param(info); |
| #endif |
| info->touch_stopped = true; |
| |
| if (info->board->power) |
| info->board->power(info, false); |
| |
| info->fts_power_state = FTS_POWER_STATE_POWERDOWN; |
| } |
| out: |
| mutex_unlock(&info->device_mutex); |
| return 0; |
| } |
| |
| static int fts_start_device(struct fts_ts_info *info) |
| { |
| tsp_debug_dbg(&info->client->dev, "%s %s\n", |
| __func__, |
| info->lowpower_mode ? |
| "exit low power mode" : ""); |
| |
| #ifdef CONFIG_TRUSTONIC_TRUSTED_UI |
| if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TUI cancel event call!\n", __func__); |
| fts_delay(100); |
| tui_force_close(1); |
| fts_delay(200); |
| if (TRUSTEDUI_MODE_TUI_SESSION & trustedui_get_current_mode()) { |
| tsp_debug_err(&info->client->dev, |
| "%s TUI flag force clear!\n", __func__); |
| trustedui_clear_mask(TRUSTEDUI_MODE_VIDEO_SECURED| |
| TRUSTEDUI_MODE_INPUT_SECURED); |
| trustedui_set_mode(TRUSTEDUI_MODE_OFF); |
| } |
| } |
| #endif |
| |
| mutex_lock(&info->device_mutex); |
| |
| if (!info->touch_stopped && !info->lowpower_mode) { |
| tsp_debug_err(&info->client->dev, |
| "%s already power on\n", __func__); |
| goto out; |
| } |
| |
| fts_release_all_finger(info); |
| if (info->lowpower_mode) { |
| /* low power mode command is sent after LCD OFF. */ |
| /* turn on touch power @ LCD ON */ |
| if (info->touch_stopped) |
| goto tsp_power_on; |
| |
| info->reinit_done = false; |
| fts_reinit(info); |
| info->reinit_done = true; |
| } else { |
| tsp_power_on: |
| if (info->board->power) |
| info->board->power(info, true); |
| info->touch_stopped = false; |
| |
| info->reinit_done = false; |
| fts_reinit(info); |
| info->reinit_done = true; |
| } |
| |
| out: |
| mutex_unlock(&info->device_mutex); |
| |
| info->fts_power_state = FTS_POWER_STATE_ACTIVE; |
| |
| return 0; |
| } |
| |
| static void fts_shutdown(struct i2c_client *client) |
| { |
| struct fts_ts_info *info = i2c_get_clientdata(client); |
| |
| tsp_debug_info(&info->client->dev, "FTS %s called!\n", __func__); |
| |
| if (info->lowpower_mode) { |
| info->lowpower_mode = 0; |
| tsp_debug_info(&info->client->dev, "FTS lowpower_mode off!\n"); |
| } |
| |
| fts_stop_device(info); |
| } |
| |
| void fts_recovery_cx(struct fts_ts_info *info) |
| { |
| unsigned char regAdd[4] = {0}; |
| unsigned char buf[8] = {0}; |
| unsigned char cnt = 100; |
| int ret = 0; |
| |
| regAdd[0] = 0xB6; |
| regAdd[1] = 0x00; |
| regAdd[2] = 0x1E; |
| regAdd[3] = 0x08; |
| fts_write_reg(info, ®Add[0], 4); /* Loading FW to PRAM without CRC Check */ |
| fts_delay(30); |
| |
| |
| fts_command(info, CX_TUNNING); |
| fts_delay(300); |
| |
| fts_command(info, FTS_CMD_SAVE_CX_TUNING); |
| fts_delay(200); |
| |
| do { |
| regAdd[0] = READ_ONE_EVENT; |
| ret = fts_read_reg(info, regAdd, 1, &buf[0], FTS_EVENT_SIZE); |
| |
| fts_delay(10); |
| |
| if (cnt-- == 0) |
| break; |
| } while (buf[0] != 0x16 || buf[1] != 0x04); |
| |
| fts_command(info, SENSEON); |
| fts_delay(50); |
| |
| fts_command(info, FLUSHBUFFER); |
| } |
| |
| #ifdef CONFIG_PM |
| static int fts_pm_suspend(struct device *dev) |
| { |
| struct fts_ts_info *info = dev_get_drvdata(dev); |
| |
| tsp_debug_info(&info->client->dev, "%s\n", __func__); |
| |
| mutex_lock(&info->input_dev->mutex); |
| |
| if (info->input_dev->users) |
| fts_stop_device(info); |
| |
| mutex_unlock(&info->input_dev->mutex); |
| |
| return 0; |
| } |
| |
| static int fts_pm_resume(struct device *dev) |
| { |
| struct fts_ts_info *info = dev_get_drvdata(dev); |
| |
| tsp_debug_info(&info->client->dev, "%s\n", __func__); |
| |
| mutex_lock(&info->input_dev->mutex); |
| |
| if (info->input_dev->users) |
| fts_start_device(info); |
| |
| mutex_unlock(&info->input_dev->mutex); |
| |
| return 0; |
| } |
| #endif |
| |
| static int fts_suspend(struct i2c_client *client, pm_message_t mesg) |
| { |
| struct fts_ts_info *info = i2c_get_clientdata(client); |
| |
| tsp_debug_info(&info->client->dev, "%s power state : %d\n", |
| __func__, info->fts_power_state); |
| /* if suspend is called from non-active state, the i2c bus is not |
| * switched to AP, skipping suspend routine */ |
| if (info->fts_power_state != FTS_POWER_STATE_ACTIVE) { |
| tsp_debug_info(&info->client->dev, |
| "%s: calling suspend from non-active state, " |
| "skipping\n", __func__); |
| return 0; |
| } |
| |
| fts_stop_device(info); |
| |
| gpio_set_value(info->switch_gpio, 1); |
| tsp_debug_info(&info->client->dev, |
| "%s: switch i2c to SLPI (set to %d)\n", |
| __func__, |
| gpio_get_value(info->switch_gpio)); |
| |
| return 0; |
| } |
| |
| static int fts_resume(struct i2c_client *client) |
| { |
| struct fts_ts_info *info = i2c_get_clientdata(client); |
| |
| tsp_debug_info(&info->client->dev, "%s power state : %d\n", |
| __func__, info->fts_power_state); |
| /* if resume is called from active state, the i2c bus is not |
| * switched to AP, skipping resume routine */ |
| if (info->fts_power_state == FTS_POWER_STATE_ACTIVE) { |
| tsp_debug_info(&info->client->dev, |
| "%s: calling resume from active state, " |
| "skipping\n", __func__); |
| return 0; |
| } |
| |
| gpio_set_value(info->switch_gpio, 0); |
| tsp_debug_info(&info->client->dev, |
| "%s: switch i2c to AP (set to %d)\n", |
| __func__, |
| gpio_get_value(info->switch_gpio)); |
| |
| fts_start_device(info); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_FB) |
| static int touch_fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fts_ts_info *info = |
| container_of(self, struct fts_ts_info, fb_notif); |
| struct fb_event *ev = (struct fb_event *)data; |
| |
| if (ev && ev->data) { |
| int *blank = (int *)ev->data; |
| if (event == FB_EARLY_EVENT_BLANK && *blank != FB_BLANK_UNBLANK) |
| fts_suspend(info->client, PMSG_SUSPEND); |
| else if (event == FB_EVENT_BLANK && *blank == FB_BLANK_UNBLANK) |
| fts_resume(info->client); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static const struct i2c_device_id fts_device_id[] = { |
| {FTS_TS_DRV_NAME, 0}, |
| {} |
| }; |
| |
| #ifdef CONFIG_PM |
| static const struct dev_pm_ops fts_dev_pm_ops = { |
| .suspend = fts_pm_suspend, |
| .resume = fts_pm_resume, |
| }; |
| #endif |
| |
| #ifdef CONFIG_OF |
| static struct of_device_id fts_match_table[] = { |
| { .compatible = "stm,ftm4_fts",}, |
| { }, |
| }; |
| #else |
| #define fts_match_table NULL |
| #endif |
| |
| static struct i2c_driver fts_i2c_driver = { |
| .driver = { |
| .name = FTS_TS_DRV_NAME, |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = fts_match_table, |
| #endif |
| #ifdef CONFIG_PM |
| .pm = &fts_dev_pm_ops, |
| #endif |
| }, |
| .probe = fts_probe, |
| .remove = fts_remove, |
| .shutdown = fts_shutdown, |
| #if (!defined(CONFIG_FB)) |
| .suspend = fts_suspend, |
| .resume = fts_resume, |
| #endif |
| .id_table = fts_device_id, |
| }; |
| |
| static int __init fts_driver_init(void) |
| { |
| return i2c_add_driver(&fts_i2c_driver); |
| } |
| |
| static void __exit fts_driver_exit(void) |
| { |
| i2c_del_driver(&fts_i2c_driver); |
| } |
| |
| MODULE_DESCRIPTION("STMicroelectronics MultiTouch IC Driver"); |
| MODULE_AUTHOR("STMicroelectronics, Inc."); |
| MODULE_LICENSE("GPL v2"); |
| |
| module_init(fts_driver_init); |
| module_exit(fts_driver_exit); |