/* * Copyright (C) 2010 - 2017 Novatek, Inc. * * $Revision: 31202 $ * $Date: 2018-07-23 10:56:09 +0800 (週一, 23 七月 2018) $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * */ #include #include #include #include #include #if defined(CONFIG_DRM_MSM) /* drm structure is not use fb */ #include #endif #if defined(CONFIG_INPUT_SEC_SECURE_TOUCH) #include #include #include #include #include #define SECURE_TOUCH_ENABLED 1 #define SECURE_TOUCH_DISABLED 0 #endif #include "nt36xxx.h" #define TOUCH_MAX_AREA_NUM 255 #define TOUCH_MAX_PRESSURE_NUM 1000 #define FINGER_ENTER 0x01 #define FINGER_MOVING 0x02 #define GLOVE_TOUCH 0x06 #define NVT_TS_I2C_RETRY_CNT 5 #define NVT_TS_LOCATION_DETECT_SIZE 6 #define TOUCH_PRINT_INFO_DWORK_TIME 30000 /* 30s */ int nvt_ts_sec_fn_init(struct nvt_ts_data *ts); void nvt_ts_sec_fn_remove(struct nvt_ts_data *ts); int nvt_ts_firmware_update_on_probe(struct nvt_ts_data *ts, bool bforce); int nvt_ts_mode_restore(struct nvt_ts_data *ts); int nvt_ts_mode_read(struct nvt_ts_data *ts); #ifdef CONFIG_DISPLAY_SAMSUNG extern int get_lcd_attached(char *mode); #endif #ifdef CONFIG_SAMSUNG_TUI struct nvt_ts_data *tsp_info; #endif #ifdef CONFIG_INPUT_SEC_SECURE_TOUCH static irqreturn_t secure_filter_interrupt(struct nvt_ts_data *ts) { if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLED) { if (atomic_cmpxchg(&ts->secure_pending_irqs, 0, 1) == 0) { sysfs_notify(&ts->input_dev->dev.kobj, NULL, "secure_touch"); } else { input_err(true, &ts->client->dev, "%s: pending irq:%d\n", __func__, (int)atomic_read(&ts->secure_pending_irqs)); } return IRQ_HANDLED; } return IRQ_NONE; } /** * Sysfs attr group for secure touch & interrupt handler for Secure world. * @atomic : syncronization for secure_enabled * @pm_runtime : set rpm_resume or rpm_ilde */ static ssize_t secure_touch_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct nvt_ts_data *ts = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d", atomic_read(&ts->secure_enabled)); } static ssize_t secure_touch_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct nvt_ts_data *ts = dev_get_drvdata(dev); int ret; unsigned long data; if (count > 2) { input_err(true, &ts->client->dev, "%s: cmd length is over (%s,%d)!!\n", __func__, buf, (int)strlen(buf)); return -EINVAL; } ret = kstrtoul(buf, 10, &data); if (ret != 0) { input_err(true, &ts->client->dev, "%s: failed to read:%d\n", __func__, ret); return -EINVAL; } if (data == 1) { /* Enable Secure World */ if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLED) { input_err(true, &ts->client->dev, "%s: already enabled\n", __func__); return -EBUSY; } disable_irq(ts->client->irq); if (pm_runtime_get_sync(ts->client->adapter->dev.parent) < 0) { enable_irq(ts->client->irq); input_err(true, &ts->client->dev, "%s: failed to get pm_runtime\n", __func__); return -EIO; } reinit_completion(&ts->secure_powerdown); reinit_completion(&ts->secure_interrupt); msleep(10); atomic_set(&ts->secure_enabled, 1); atomic_set(&ts->secure_pending_irqs, 0); enable_irq(ts->client->irq); input_info(true, &ts->client->dev, "%s: secure touch enable\n", __func__); } else if (!data) { /* Disable Secure World */ if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_DISABLED) { input_err(true, &ts->client->dev, "%s: already disabled\n", __func__); return count; } pm_runtime_put_sync(ts->client->adapter->dev.parent); atomic_set(&ts->secure_enabled, 0); sysfs_notify(&ts->input_dev->dev.kobj, NULL, "secure_touch"); msleep(10); complete(&ts->secure_powerdown); complete(&ts->secure_interrupt); input_info(true, &ts->client->dev, "%s: secure touch disable\n", __func__); } else { input_err(true, &ts->client->dev, "%s: unsupported value %ld\n", __func__, data); return -EINVAL; } return count; } static ssize_t secure_touch_show(struct device *dev, struct device_attribute *attr, char *buf) { struct nvt_ts_data *ts = dev_get_drvdata(dev); int val = 0; if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_DISABLED) { input_err(true, &ts->client->dev, "%s: disabled\n", __func__); return -EBADF; } if (atomic_cmpxchg(&ts->secure_pending_irqs, -1, 0) == -1) { input_err(true, &ts->client->dev, "%s: pending irq -1\n", __func__); return -EINVAL; } if (atomic_cmpxchg(&ts->secure_pending_irqs, 1, 0) == 1) val = 1; input_info(true, &ts->client->dev, "%s: pending irq is %d\n", __func__, atomic_read(&ts->secure_pending_irqs)); complete(&ts->secure_interrupt); return snprintf(buf, PAGE_SIZE, "%u", val); } static DEVICE_ATTR(secure_touch_enable, (S_IRUGO | S_IWUSR | S_IWGRP), secure_touch_enable_show, secure_touch_enable_store); static DEVICE_ATTR(secure_touch, S_IRUGO, secure_touch_show, NULL); static struct attribute *secure_attr[] = { &dev_attr_secure_touch_enable.attr, &dev_attr_secure_touch.attr, NULL, }; static struct attribute_group secure_attr_group = { .attrs = secure_attr, }; static void secure_touch_init(struct nvt_ts_data *ts) { init_completion(&ts->secure_powerdown); init_completion(&ts->secure_interrupt); } static void secure_touch_stop(struct nvt_ts_data *ts, bool stop) { if (atomic_read(&ts->secure_enabled)) { atomic_set(&ts->secure_pending_irqs, -1); sysfs_notify(&ts->input_dev->dev.kobj, NULL, "secure_touch"); if (stop) wait_for_completion_interruptible(&ts->secure_powerdown); input_info(true, &ts->client->dev, "%s: %d\n", __func__, stop); } } #endif int nvt_ts_i2c_read(struct nvt_ts_data *ts, u16 address, u8 *buf, u16 len) { struct i2c_msg msgs[2]; int ret = -1; int retries = 0; memset(&buf[1], 0x00, len - 1); #ifdef CONFIG_INPUT_SEC_SECURE_TOUCH if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLED) { input_err(true, &ts->client->dev, "%s: TSP no accessible from Linux, TUI is enabled!\n", __func__); return -EBUSY; } #endif #ifdef CONFIG_SAMSUNG_TUI if (STUI_MODE_TOUCH_SEC & stui_get_mode()) return -EBUSY; #endif if (ts->power_status == POWER_OFF_STATUS) { input_err(true, &ts->client->dev, "%s: POWER_STATUS : OFF!\n", __func__); return -EIO; } msgs[0].flags = !I2C_M_RD; msgs[0].addr = address; msgs[0].len = 1; msgs[0].buf = &buf[0]; msgs[1].flags = I2C_M_RD; msgs[1].addr = address; msgs[1].len = len - 1; msgs[1].buf = &buf[1]; mutex_lock(&ts->i2c_mutex); while (retries < NVT_TS_I2C_RETRY_CNT) { ret = i2c_transfer(ts->client->adapter, msgs, 2); if (ret == 2) break; if (ts->power_status == POWER_OFF_STATUS) { input_err(true, &ts->client->dev, "%s: POWER_STATUS : OFF!\n", __func__); mutex_unlock(&ts->i2c_mutex); ret = -EIO; goto err; } if (retries >= (NVT_TS_I2C_RETRY_CNT - 1)) { input_err(true, &ts->client->dev, "%s: I2C retry %d, ret:%d\n", __func__, retries + 1, ret); ts->comm_err_count++; } retries++; } mutex_unlock(&ts->i2c_mutex); if (unlikely(retries == NVT_TS_I2C_RETRY_CNT)) input_err(true, &ts->client->dev, "%s: I2C read over retry limit\n", __func__); err: return ret; } int nvt_ts_i2c_write(struct nvt_ts_data *ts, u16 address, u8 *buf, u16 len) { struct i2c_msg msg; int ret = -1; int retries = 0; #ifdef CONFIG_INPUT_SEC_SECURE_TOUCH if (atomic_read(&ts->secure_enabled) == SECURE_TOUCH_ENABLED) { input_err(true, &ts->client->dev, "%s: TSP no accessible from Linux, TUI is enabled!\n", __func__); return -EBUSY; } #endif #ifdef CONFIG_SAMSUNG_TUI if (STUI_MODE_TOUCH_SEC & stui_get_mode()) return -EBUSY; #endif if (ts->power_status == POWER_OFF_STATUS) { input_err(true, &ts->client->dev, "%s: POWER_STATUS : OFF!\n", __func__); return -EIO; } msg.flags = !I2C_M_RD; msg.addr = address; msg.len = len; msg.buf = buf; mutex_lock(&ts->i2c_mutex); while (retries < NVT_TS_I2C_RETRY_CNT) { ret = i2c_transfer(ts->client->adapter, &msg, 1); if (ret == 1) break; if (ts->power_status == POWER_OFF_STATUS) { input_err(true, &ts->client->dev, "%s: POWER_STATUS : OFF!\n", __func__); mutex_unlock(&ts->i2c_mutex); ret = -EIO; goto err; } if (retries >= (NVT_TS_I2C_RETRY_CNT - 1)) { input_err(true, &ts->client->dev, "%s: I2C retry %d, ret:%d\n", __func__, retries + 1, ret); ts->comm_err_count++; } retries++; } mutex_unlock(&ts->i2c_mutex); if (unlikely(retries == NVT_TS_I2C_RETRY_CNT)) input_err(true, &ts->client->dev, "%s: I2C write over retry limit\n", __func__); err: return ret; } void nvt_ts_sw_reset_idle(struct nvt_ts_data *ts) { u8 buf[4] = { 0 }; buf[0]=0x00; buf[1]=0xA5; nvt_ts_i2c_write(ts, I2C_HW_Address, buf, 2); msleep(15); } void nvt_ts_bootloader_reset(struct nvt_ts_data *ts) { u8 buf[4] = { 0 }; buf[0] = 0x00; buf[1] = 0x69; nvt_ts_i2c_write(ts, I2C_HW_Address, buf, 2); msleep(35); } int nvt_ts_check_fw_reset_state(struct nvt_ts_data *ts, RST_COMPLETE_STATE reset_state) { u8 buf[8] = { 0 }; int retry = 0; int ret; while (retry <= 100) { msleep(10); //---read reset state--- buf[0] = EVENT_MAP_RESET_COMPLETE; nvt_ts_i2c_read(ts, I2C_FW_Address, buf, 6); if ((buf[1] >= reset_state) && (buf[1] <= RESET_STATE_MAX)) { ret = 0; break; } retry++; } if (unlikely(retry > 100)) { input_err(true, &ts->client->dev, "%s: Time over\n", __func__); ret = -EPERM; } input_info(true, &ts->client->dev, "%s: %02X %02X %02X %02X %02X\n", __func__, buf[1], buf[2], buf[3], buf[4], buf[5]); return ret; } int nvt_ts_get_fw_info(struct nvt_ts_data *ts) { struct nvt_ts_platdata *platdata = ts->platdata; u8 buf[20] = { 0 }; u8 fw_ver, x_num, y_num; u16 abs_x_max, abs_y_max; int i; int ret = -EPERM; for (i = 0; i < 3; i++) { //---set xdata index to EVENT BUF ADDR--- buf[0] = 0xFF; buf[1] = (ts->mmap->EVENT_BUF_ADDR >> 16) & 0xFF; buf[2] = (ts->mmap->EVENT_BUF_ADDR >> 8) & 0xFF; nvt_ts_i2c_write(ts, I2C_FW_Address, buf, 3); //---read fw info--- buf[0] = EVENT_MAP_FWINFO; buf[1] = 0x00; buf[2] = 0x00; nvt_ts_i2c_read(ts, I2C_FW_Address, buf, 20); fw_ver = buf[1]; x_num = buf[3]; y_num = buf[4]; abs_x_max = (u16)((buf[5] << 8) | buf[6]); abs_y_max = (u16)((buf[7] << 8) | buf[8]); //---clear x_num, y_num if fw info is broken--- if ((buf[1] + buf[2]) == 0xFF) { ret = 0; break; } input_err(true, &ts->client->dev, "FW info is broken! fw_ver = 0x%02X, ~fw_ver = 0x%02X\n", buf[1], buf[2]); fw_ver = 0; x_num = platdata->x_num; y_num = platdata->y_num; abs_x_max = platdata->abs_x_max; abs_y_max = platdata->abs_y_max; } platdata->x_num = x_num; platdata->y_num = y_num; platdata->abs_x_max = abs_x_max; platdata->abs_y_max = abs_y_max; input_info(true, &ts->client->dev, "%s: fw_ver: %d, channel: (%d, %d), resolution: (%d,%d)\n", __func__, fw_ver, x_num, y_num, abs_x_max, abs_y_max); /* ic_name, project_id, panel, fw info */ // Customized version string start //---Get IC name--- if ((trim_id_table[ts->ic_idx].id[5] == 0x03) && (trim_id_table[ts->ic_idx].id[4] == 0x66) && (trim_id_table[ts->ic_idx].id[3] == 0x72) && (trim_id_table[ts->ic_idx].id[0] == 0x0A)) { ts->fw_ver_ic[0] = (((trim_id_table[ts->ic_idx].id[5] & 0x0F) << 4) | ((trim_id_table[ts->ic_idx].id[4] & 0xF0) >> 4)); } else { ts->fw_ver_ic[0] = buf[15]; } //---Get Novatek PID--- buf[0] = 0xFF; buf[1] = (ts->mmap->EVENT_BUF_ADDR >> 16) & 0xFF; buf[2] = (ts->mmap->EVENT_BUF_ADDR >> 8) & 0xFF; ret = nvt_ts_i2c_write(ts, I2C_FW_Address, buf, 3); if (ret < 0) { input_err(true, &ts->client->dev, "%s: nvt_ts_i2c_write error(%d)\n",__func__, ret); return ret; } buf[0] = EVENT_MAP_PROJECTID; buf[1] = 0x00; ret = nvt_ts_i2c_read(ts, I2C_FW_Address, buf, 2); if (ret < 0) { input_err(true, &ts->client->dev, "%s: nvt_ts_i2c_read error(%d)\n",__func__, ret); return ret; } ts->fw_ver_ic[1] = buf[1]; //---get panel id--- buf[0] = EVENT_MAP_PANEL; buf[1] = 0x00; ret = nvt_ts_i2c_read(ts, I2C_FW_Address, buf, 2); if (ret < 0) { input_err(true, &ts->client->dev, "%s: nvt_ts_i2c_read error(%d)\n",__func__, ret); return ret; } ts->fw_ver_ic[2] = buf[1]; //---get firmware version--- buf[0] = EVENT_MAP_FWINFO; buf[1] = 0x00; ret = nvt_ts_i2c_read(ts, I2C_FW_Address, buf, 2); if (ret < 0) { input_err(true, &ts->client->dev, "%s: nvt_ts_i2c_read error(%d)\n",__func__, ret); return ret; } ts->fw_ver_ic[3] = buf[1]; input_info(true, &ts->client->dev, "%s: fw_ver_ic = %02X%02X%02X%02X\n", __func__, ts->fw_ver_ic[0], ts->fw_ver_ic[1], ts->fw_ver_ic[2], ts->fw_ver_ic[3]); input_info(true, &ts->client->dev, "%s: trim_ver_ic = %02X%02X%02X%02X\n", __func__, trim_id_table[ts->ic_idx].id[5], trim_id_table[ts->ic_idx].id[4], trim_id_table[ts->ic_idx].id[3], trim_id_table[ts->ic_idx].id[0]); return ret; } /************************************************************ * 720 * 1480 : <48 96 60> indicator: 24dp navigator:48dp edge:60px dpi=320 * 1080 * 2220 : 4096 * 4096 : <133 266 341> (approximately value) ************************************************************/ static void location_detect(struct nvt_ts_data *ts, char *loc, int x, int y) { int i; for (i = 0; i < NVT_TS_LOCATION_DETECT_SIZE; ++i) loc[i] = 0; if (x < ts->platdata->area_edge) strcat(loc, "E."); else if (x < (ts->platdata->abs_x_max - ts->platdata->area_edge)) strcat(loc, "C."); else strcat(loc, "e."); if (y < ts->platdata->area_indicator) strcat(loc, "S"); else if (y < (ts->platdata->abs_y_max - ts->platdata->area_navigation)) strcat(loc, "C"); else strcat(loc, "N"); } static void nvt_ts_print_info(struct nvt_ts_data *ts) { if (!ts->print_info_cnt_open && !ts->print_info_cnt_release) { msleep(200); ts->sec_function = nvt_ts_mode_read(ts); } ts->print_info_cnt_open++; if (ts->print_info_cnt_open > 0xfff0) ts->print_info_cnt_open = 0; if (ts->touch_count == 0) ts->print_info_cnt_release++; input_info(true, &ts->client->dev, "tc:%d v:%02X%02X%02X mode:%04X // #%d %d\n", ts->touch_count, ts->fw_ver_ic[1], ts->fw_ver_ic[2], ts->fw_ver_ic[3], ts->sec_function, ts->print_info_cnt_open, ts->print_info_cnt_release); } static void nvt_ts_release_all_finger(struct nvt_ts_data *ts) { char location[NVT_TS_LOCATION_DETECT_SIZE] = { 0, }; int i = 0; for (i = 0; i < ts->platdata->max_touch_num; i++) { location_detect(ts, location, ts->coords[i].x, ts->coords[i].y); input_mt_slot(ts->input_dev, i); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); if (ts->coords[i].p_press) { ts->touch_count--; #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) input_info(true, &ts->client->dev, "[RA] tId:%d loc:%s dd:%d,%d mc:%d tc:%d lx:%d ly:%d\n", i, location, ts->coords[i].p_x - ts->coords[i].x, ts->coords[i].p_y - ts->coords[i].y, ts->coords[i].move_count, ts->touch_count, ts->coords[i].x, ts->coords[i].y); #else input_info(true, &ts->client->dev, "[RA] tId:%d loc:%s dd:%d,%d mc:%d tc:%d\n", i, location, ts->coords[i].p_x - ts->coords[i].x, ts->coords[i].p_y - ts->coords[i].y, ts->coords[i].move_count, ts->touch_count); #endif ts->coords[i].p_press = false; ts->coords[i].press = false; } } input_mt_slot(ts->input_dev, 0); input_report_key(ts->input_dev, BTN_TOUCH, false); input_report_key(ts->input_dev, BTN_TOOL_FINGER, false); input_sync(ts->input_dev); ts->check_multi = false; ts->touch_count = 0; } static void nvt_ts_print_coord(struct nvt_ts_data *ts) { int i; char location[NVT_TS_LOCATION_DETECT_SIZE] = { 0, }; for (i = 0; i < TOUCH_MAX_FINGER_NUM; i++) { location_detect(ts, location, ts->coords[i].x, ts->coords[i].y); if (ts->coords[i].press && !ts->coords[i].p_press) { ts->touch_count++; ts->all_finger_count++; #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) input_info(true, &ts->client->dev, "[P] tId:%d.%d x:%d y:%d z:%d loc:%s tc:%d type:%X\n", i, (ts->input_dev->mt->trkid - 1) & TRKID_MAX, ts->coords[i].x, ts->coords[i].y, ts->coords[i].area, location, ts->touch_count, ts->coords[i].status); #else input_info(true, &ts->client->dev, "[P] tId:%d.%d z:%d loc:%s tc:%d type:%X\n", i, (ts->input_dev->mt->trkid - 1) & TRKID_MAX, ts->coords[i].area, location, ts->touch_count, ts->coords[i].status); #endif ts->coords[i].p_press = ts->coords[i].press; ts->coords[i].p_x = ts->coords[i].x; ts->coords[i].p_y = ts->coords[i].y; ts->coords[i].p_status = 0; ts->coords[i].move_count = 0; } else if (!ts->coords[i].press && ts->coords[i].p_press) { ts->touch_count--; if (!ts->touch_count) ts->print_info_cnt_release = 0; #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) input_info(true, &ts->client->dev, "[R] tId:%d loc:%s dd:%d,%d mc:%d tc:%d lx:%d ly:%d\n", i, location, ts->coords[i].p_x - ts->coords[i].x, ts->coords[i].p_y - ts->coords[i].y, ts->coords[i].move_count, ts->touch_count, ts->coords[i].x, ts->coords[i].y); #else input_info(true, &ts->client->dev, "[R] tId:%d loc:%s dd:%d,%d mc:%d tc:%d\n", i, location, ts->coords[i].p_x - ts->coords[i].x, ts->coords[i].p_y - ts->coords[i].y, ts->coords[i].move_count, ts->touch_count); #endif ts->coords[i].p_press = false; } else if (ts->coords[i].press) { if (ts->coords[i].p_status && (ts->coords[i].status != ts->coords[i].p_status)) { #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) input_info(true, &ts->client->dev, "[C] tId:%d x:%d y:%d z:%d tc:%d type:%X\n", i, ts->coords[i].x, ts->coords[i].y, ts->coords[i].area, ts->touch_count, ts->coords[i].status); #else input_info(true, &ts->client->dev, "[C] tId:%d z:%d tc:%d type:%X\n", i, ts->coords[i].area, ts->touch_count, ts->coords[i].status); #endif } ts->coords[i].p_status = ts->coords[i].status; ts->coords[i].move_count++; } } } #define POINT_DATA_LEN 65 static void nvt_ts_event_handler(struct nvt_ts_data *ts) { struct nvt_ts_platdata *platdata = ts->platdata; struct nvt_ts_event_coord *p_event_coord; u8 point_data[POINT_DATA_LEN + 1] = { 0 }; bool press_id[TOUCH_MAX_FINGER_NUM] = { 0 }; u16 x, y, w, p; u8 id, status; int finger_cnt; int i; int ret; ret = nvt_ts_i2c_read(ts, I2C_FW_Address, point_data, POINT_DATA_LEN + 1); if (ret < 0) { input_err(true, &ts->client->dev, "nvt_ts_i2c_read failed.(%d)\n", ret); return; } /* //--- dump I2C buf --- for (i = 0; i < 10; i++) { printk("[sec_input] %02X %02X %02X %02X %02X %02X ", point_data[1+i*6], point_data[2+i*6], point_data[3+i*6], point_data[4+i*6], point_data[5+i*6], point_data[6+i*6]); } printk("\n"); */ for (i = 0, finger_cnt = 0; i < platdata->max_touch_num; i++) { p_event_coord = (struct nvt_ts_event_coord *)&point_data[1 + 6 * i]; id = p_event_coord->id; if (!id || (id > platdata->max_touch_num)) continue; id = id - 1; status = p_event_coord->status; if ((status == FINGER_ENTER) || (status == FINGER_MOVING) || (status == GLOVE_TOUCH)) { ts->coords[id].status = status; press_id[id] = ts->coords[id].press = true; x = ts->coords[id].x = (u32)(p_event_coord->x_11_4 << 4) + (u32)(p_event_coord->x_3_0); y = ts->coords[id].y = (u32)(p_event_coord->y_11_4 << 4) + (u32)(p_event_coord->y_3_0); if ((x < 0) || (y < 0) || (x > platdata->abs_x_max) || (y > platdata->abs_y_max)) { input_err(true, &ts->client->dev, "%s: invaild coordinate (%d, %d)\n", __func__, x, y); continue; } w = ts->coords[id].area = (u32)(p_event_coord->area); if (!w) w = 1; if (i < 2) { p = (u32)(p_event_coord->pressure_7_0) + (u32)(point_data[i + 62] << 8); if (p > TOUCH_MAX_PRESSURE_NUM) p = TOUCH_MAX_PRESSURE_NUM; } else { p = (u32)(p_event_coord->pressure_7_0); } if (!p) p = 1; ts->coords[id].p = p; input_mt_slot(ts->input_dev, id); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w); //input_report_abs(ts->input_dev, ABS_MT_PRESSURE, p); finger_cnt++; } } for (i = 0; i < platdata->max_touch_num; i++) { if (!press_id[i] && ts->coords[i].p_press) { ts->coords[i].press = false; input_mt_slot(ts->input_dev, i); input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); //input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); } } input_report_key(ts->input_dev, BTN_TOUCH, (finger_cnt > 0)); input_report_key(ts->input_dev, BTN_TOOL_FINGER, (finger_cnt > 0)); input_sync(ts->input_dev); if (!finger_cnt) { ts->check_multi = false; } else if ((finger_cnt > 4) && !ts->check_multi) { ts->check_multi = true; ts->multi_count++; } nvt_ts_print_coord(ts); } static irqreturn_t nvt_ts_irq_thread(int32_t irq, void *dev_id) { struct nvt_ts_data *ts = (struct nvt_ts_data *)dev_id; #ifdef CONFIG_INPUT_SEC_SECURE_TOUCH if (IRQ_HANDLED == secure_filter_interrupt(ts)) { wait_for_completion_interruptible_timeout(&ts->secure_interrupt, msecs_to_jiffies(5 * MSEC_PER_SEC)); input_info(true, &ts->client->dev, "%s: secure interrupt handled\n", __func__); return IRQ_HANDLED; } #endif nvt_ts_event_handler(ts); return IRQ_HANDLED; } void nvt_ts_stop_crc_reboot(struct nvt_ts_data *ts) { u8 buf[8] = { 0 }; int retry = 0; //read dummy buffer to check CRC fail reboot is happening or not //---change I2C index to prevent geting 0xFF, but not 0xFC--- buf[0] = 0xFF; buf[1] = (CHIP_VER_TRIM_ADDR >> 16) & 0xFF; buf[2] = (CHIP_VER_TRIM_ADDR >> 8) & 0xFF; nvt_ts_i2c_write(ts, I2C_BLDR_Address, buf, 3); //---read to check if buf is 0xFC which means IC is in CRC reboot --- buf[0] = CHIP_VER_TRIM_ADDR & 0xFF; buf[1] = 0x00; buf[2] = 0x00; buf[3] = 0x00; nvt_ts_i2c_read(ts, I2C_BLDR_Address, buf, 4); if ((buf[1] == 0xFC) || ((buf[1] == 0xFF) && (buf[2] == 0xFF) && (buf[3] == 0xFF))) { //IC is in CRC fail reboot loop, needs to be stopped! for (retry = 5; retry > 0; retry--) { //---write i2c cmds to reset idle : 1st--- buf[0]=0x00; buf[1]=0xA5; nvt_ts_i2c_write(ts, I2C_HW_Address, buf, 2); //---write i2c cmds to reset idle : 2rd--- buf[0]=0x00; buf[1]=0xA5; nvt_ts_i2c_write(ts, I2C_HW_Address, buf, 2); msleep(1); //---clear CRC_ERR_FLAG--- buf[0] = 0xFF; buf[1] = 0x03; buf[2] = 0xF1; nvt_ts_i2c_write(ts, I2C_BLDR_Address, buf, 3); buf[0] = 0x35; buf[1] = 0xA5; nvt_ts_i2c_write(ts, I2C_BLDR_Address, buf, 2); //---check CRC_ERR_FLAG--- buf[0] = 0xFF; buf[1] = 0x03; buf[2] = 0xF1; nvt_ts_i2c_write(ts, I2C_BLDR_Address, buf, 3); buf[0] = 0x35; buf[1] = 0x00; nvt_ts_i2c_read(ts, I2C_BLDR_Address, buf, 2); if (buf[1] == 0xA5) break; } if (retry == 0) input_err(true, &ts->client->dev, "failed to stop CRC auto reboot [0x%02X]\n", buf[1]); } return; } /******************************************************* Description: Novatek touchscreen check chip version trim function. return: Executive outcomes. 0---NVT IC. -1---not NVT IC. *******************************************************/ static int8_t nvt_ts_check_chip_ver_trim(struct nvt_ts_data *ts, uint32_t chip_ver_trim_addr) { uint8_t buf[8] = {0}; int32_t retry = 0; int32_t list = 0; int32_t i = 0; nvt_ts_bootloader_reset(ts); // NOT in retry loop //---Check for 5 times--- for (retry = 5; retry > 0; retry--) { nvt_ts_sw_reset_idle(ts); buf[0] = 0x00; buf[1] = 0x35; nvt_ts_i2c_write(ts, I2C_HW_Address, buf, 2); msleep(10); buf[0] = 0xFF; buf[1] = (chip_ver_trim_addr >> 16) & 0xFF; buf[2] = (chip_ver_trim_addr >> 8) & 0xFF; nvt_ts_i2c_write(ts, I2C_BLDR_Address, buf, 3); buf[0] = chip_ver_trim_addr & 0xFF; buf[1] = 0x00; buf[2] = 0x00; buf[3] = 0x00; buf[4] = 0x00; buf[5] = 0x00; buf[6] = 0x00; nvt_ts_i2c_read(ts, I2C_BLDR_Address, buf, 7); input_info(true, &ts->client->dev, "IC version: %02X %02X %02X %02X %02X %02X\n", buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); //---Stop CRC check to prevent IC auto reboot--- if ((buf[1] == 0xFC) || ((buf[1] == 0xFF) && (buf[2] == 0xFF) && (buf[3] == 0xFF))) { nvt_ts_stop_crc_reboot(ts); continue; } // compare read chip id on supported list for (list = 0; list < (sizeof(trim_id_table) / sizeof(struct nvt_ts_trim_id_table)); list++) { // compare each byte for (i = 0; i < NVT_ID_BYTE_MAX; i++) { if (trim_id_table[list].mask[i]) { if (buf[i + 1] != trim_id_table[list].id[i]) break; } } if (i == NVT_ID_BYTE_MAX) { input_info(true, &ts->client->dev, "found match ic version\n"); ts->mmap = trim_id_table[list].mmap; ts->carrier_system = trim_id_table[list].hwinfo->carrier_system; ts->ic_idx = list; return 0; } else { ts->mmap = NULL; } } msleep(10); } return -ENODEV; } static int nvt_ts_open(struct nvt_ts_data *ts) { input_info(true, &ts->client->dev, "%s\n", __func__); if (ts->power_status == POWER_ON_STATUS) { input_err(true, &ts->client->dev, "%s: already power on\n", __func__); return 0; } #if defined(CONFIG_INPUT_SEC_SECURE_TOUCH) secure_touch_stop(ts, 0); #endif mutex_lock(&ts->lock); ts->power_status = POWER_ON_STATUS; nvt_ts_bootloader_reset(ts); nvt_ts_check_fw_reset_state(ts, RESET_STATE_REK); nvt_ts_mode_restore(ts); enable_irq(ts->client->irq); mutex_unlock(&ts->lock); cancel_delayed_work(&ts->work_print_info); ts->print_info_cnt_open = 0; ts->print_info_cnt_release = 0; schedule_work(&ts->work_print_info.work); return 0; } static void nvt_ts_close(struct nvt_ts_data *ts) { u16 mode; char buf[2]; if (ts->power_status == POWER_OFF_STATUS) { input_err(true, &ts->client->dev, "%s: already power off\n", __func__); return; } cancel_delayed_work(&ts->work_print_info); #if defined(CONFIG_INPUT_SEC_SECURE_TOUCH) secure_touch_stop(ts, 1); #endif #ifdef CONFIG_SAMSUNG_TUI stui_cancel_session(); #endif mutex_lock(&ts->lock); disable_irq(ts->client->irq); nvt_ts_print_info(ts); mode = nvt_ts_mode_read(ts); if (ts->sec_function != mode) { input_info(true, &ts->client->dev, "%s: func mode 0x%04X -> 0x%04X", __func__, ts->sec_function, mode); ts->sec_function = mode; } //---write i2c command to enter "deep sleep mode"--- buf[0] = EVENT_MAP_HOST_CMD; buf[1] = 0x11; nvt_ts_i2c_write(ts, I2C_FW_Address, buf, 2); ts->power_status = POWER_OFF_STATUS; ts->untouchable_area = false; msleep(50); nvt_ts_release_all_finger(ts); mutex_unlock(&ts->lock); input_info(true, &ts->client->dev, "%s", __func__); } static void nvt_ts_open_work(struct kthread_work *work) { struct nvt_ts_data *ts = container_of(work, struct nvt_ts_data, kwork); nvt_ts_open(ts); } static void nvt_ts_set_input_value(struct nvt_ts_data *ts, struct input_dev *input_dev) { input_dev->name = "sec_touchscreen"; input_dev->id.bustype = BUS_I2C; input_dev->dev.parent = &ts->client->dev; input_mt_init_slots(input_dev, TOUCH_MAX_FINGER_NUM, INPUT_MT_DIRECT); input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->platdata->abs_x_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->platdata->abs_y_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, TOUCH_MAX_AREA_NUM, 0, 0); //input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, TOUCH_MAX_PRESSURE_NUM, 0, 0); input_set_capability(input_dev, EV_KEY, BTN_TOUCH); input_set_capability(input_dev, EV_KEY, BTN_TOOL_FINGER); ts->input_dev = input_dev; input_set_drvdata(input_dev, ts); } #if defined(CONFIG_DRM_MSM) static int drm_notifier_callback(struct notifier_block *self, unsigned long event, void *data) { struct msm_drm_notifier *evdata = data; int *blank; struct nvt_ts_data *ts = container_of(self, struct nvt_ts_data, drm_notif); if (!evdata) return 0; if (!evdata->data) return 0; /* tsp driver do not need callback about sub display */ if (evdata->id) return 0; blank = evdata->data; if (event == MSM_DRM_EARLY_EVENT_BLANK) { if (*blank == MSM_DRM_BLANK_POWERDOWN) nvt_ts_close(ts); } else if (event == MSM_DRM_EVENT_BLANK) { if (*blank == MSM_DRM_BLANK_UNBLANK) nvt_ts_open(ts); } return 0; } #endif #if defined(CONFIG_FB) static int fb_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct nvt_ts_data *ts = container_of(nb, struct nvt_ts_data, fb_nb); struct fb_event *evdata = data; int *blank; if (!evdata || !evdata->data) return 0; blank = evdata->data; if (event == FB_EARLY_EVENT_BLANK) { if (*blank == FB_BLANK_POWERDOWN) nvt_ts_close(ts); } else if (event == FB_EVENT_BLANK) { if (*blank == FB_BLANK_UNBLANK) kthread_queue_work(&ts->kworker, &ts->kwork); } return 0; } #endif static void nvt_ts_print_info_work(struct work_struct *work) { struct nvt_ts_data *ts = container_of(work, struct nvt_ts_data, work_print_info.work); mutex_lock(&ts->lock); nvt_ts_print_info(ts); mutex_unlock(&ts->lock); schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(TOUCH_PRINT_INFO_DWORK_TIME)); } static int nvt_ts_gpio_config(struct device *dev, struct nvt_ts_platdata *platdata) { int ret; ret = devm_gpio_request(dev, platdata->irq_gpio, "nvt_irq"); if (ret) { input_err(true, dev, "failed to request nvt_irq [%d]\n", ret); return ret; } return 0; } #ifdef CONFIG_OF static struct nvt_ts_platdata *nvt_ts_parse_dt(struct device *dev) { struct nvt_ts_platdata *platdata; struct device_node *np = dev->of_node; int tmp[3]; int ret; #if !defined(CONFIG_EXYNOS_DPU20) int lcdtype = 0; #endif #if defined(CONFIG_EXYNOS_DECON_FB) int connected; #endif if (!np) return ERR_PTR(-ENODEV); #if defined(CONFIG_DISPLAY_SAMSUNG) lcdtype = get_lcd_attached("GET"); if (lcdtype == 0xFFFFFF) { input_err(true, dev, "lcd is not attached\n"); return ERR_PTR(-ENODEV); } #endif #if defined(CONFIG_EXYNOS_DECON_FB) connected = get_lcd_info("connected"); if (connected < 0) { input_err(true, dev, "failed to get lcd connected info\n"); return ERR_PTR(-EINVAL); } if (!connected) { input_err(true, dev, "lcd is not attached\n"); return ERR_PTR(-ENODEV); } lcdtype = get_lcd_info("id"); if (lcdtype < 0) { input_err(true, dev, "failed to get lcd id info\n"); return ERR_PTR(-EINVAL); } #endif #if defined(CONFIG_EXYNOS_DPU20) if (lcdtype == 0) { input_err(true, dev, "lcd is not attached\n"); return ERR_PTR(-ENODEV); } #endif input_info(true, dev, "lcdtype 0x%08X\n", lcdtype); platdata = devm_kzalloc(dev, sizeof(*platdata), GFP_KERNEL); if (!platdata) return ERR_PTR(-ENOMEM); platdata->irq_gpio = of_get_named_gpio_flags(np, "novatek,irq-gpio", 0, &platdata->irq_flags); if (!gpio_is_valid(platdata->irq_gpio)) { input_err(true, dev, "failed to get irq-gpio(%d)\n", platdata->irq_gpio); return ERR_PTR(platdata->irq_gpio); } else if (!platdata->irq_flags) { platdata->irq_flags = IRQF_TRIGGER_LOW | IRQF_ONESHOT; } platdata->support_dual_fw = of_property_read_bool(np, "support_dual_fw"); ret = of_property_read_string(np, "novatek,firmware_name", &platdata->firmware_name); /* LCD type old NT36672A:12F240 LCD type new NT36672C:4BF245 */ if(platdata->support_dual_fw && lcdtype == 0x4BF245) { ret = of_property_read_string(np, "novatek,firmware_name_new_ic", &platdata->firmware_name); } if (ret) platdata->firmware_name = NULL; ret = of_property_read_u32_array(np, "novatek,resolution", tmp, 2); if (!ret) { platdata->abs_x_max = tmp[0]; platdata->abs_y_max = tmp[1]; } else { platdata->abs_x_max = DEFAULT_MAX_WIDTH; platdata->abs_x_max = DEFAULT_MAX_HEIGHT; } ret = of_property_read_u32_array(np, "novatek,max_touch_num", tmp, 1); if (!ret) platdata->max_touch_num = tmp[0]; else platdata->max_touch_num = 10; if (of_property_read_u32_array(np, "novatek,area-size", tmp, 3)) { platdata->area_indicator = 133; platdata->area_navigation = 266; platdata->area_edge = 341; } else { platdata->area_indicator = tmp[0]; platdata->area_navigation = tmp[1]; platdata->area_edge = tmp[2]; } if (of_property_read_u32(np, "novatek,bringup", &platdata->bringup) < 0) platdata->bringup = 0; platdata->check_fw_project_id = of_property_read_bool(np, "check_fw_project_id"); input_info(true, dev, "zone's size - indicator:%d, navigation:%d, edge:%d\n", platdata->area_indicator, platdata->area_navigation, platdata->area_edge); input_info(true, dev, "bringup:%d, resolution: (%d, %d), firware_name: %s\n", platdata->bringup, platdata->abs_x_max, platdata->abs_y_max, platdata->firmware_name); return platdata; } #else static struct nvt_ts_platdata *nvt_ts_parse_dt(struct device *dev) { input_err(true, &dev, "no platform data specified\n"); return ERR_PTR(-EINVAL); } #endif static int nvt_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct nvt_ts_data *ts; struct nvt_ts_platdata *platdata = dev_get_platdata(&client->dev); struct input_dev *input_dev; struct task_struct *kworker_task; int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { input_err(true, &client->dev, "I2C functionality not supported\n"); return -EIO; } ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; if (!platdata) { platdata = nvt_ts_parse_dt(&client->dev); if (IS_ERR(platdata)) return PTR_ERR(platdata); } input_dev = devm_input_allocate_device(&client->dev); if (!input_dev) return -ENOMEM; ret = nvt_ts_gpio_config(&client->dev, platdata); if (ret) return ret; ts->client = client; ts->platdata = platdata; i2c_set_clientdata(client, ts); mutex_init(&ts->lock); mutex_init(&ts->i2c_mutex); INIT_DELAYED_WORK(&ts->work_print_info, nvt_ts_print_info_work); kthread_init_worker(&ts->kworker); kworker_task = kthread_run(kthread_worker_fn, &ts->kworker, "sec_touchscreen"); if (IS_ERR(kworker_task)) { input_err(true, &client->dev, "Failed to create resume thread\n"); goto err_kthread; } kthread_init_work(&ts->kwork, nvt_ts_open_work); ts->power_status = POWER_ON_STATUS; /* need 10ms delay after POR(power on reset) */ msleep(10); /* check chip version trim */ ret = nvt_ts_check_chip_ver_trim(ts, CHIP_VER_TRIM_ADDR); if (ret) { input_err(true, &client->dev, "try to check from old chip ver trim address\n"); ret = nvt_ts_check_chip_ver_trim(ts, CHIP_VER_TRIM_OLD_ADDR); if (ret) { input_err(true, &client->dev, "chip is not identified\n"); ret = -EINVAL; goto err_check_trim; } } nvt_ts_bootloader_reset(ts); nvt_ts_check_fw_reset_state(ts, RESET_STATE_INIT); ret = nvt_ts_firmware_update_on_probe(ts, false); if (ret) { input_err(true, &client->dev, "failed to firmware update\n"); goto err_fw_update; } nvt_ts_get_fw_info(ts); nvt_ts_set_input_value(ts, input_dev); ret = input_register_device(input_dev); if (ret) { input_err(true, &client->dev, "failed to register input device\n"); goto err_input_regi_dev; } ts->sec_function = nvt_ts_mode_read(ts); input_info(true, &ts->client->dev, "%s: default func mode 0x%04X\n", __func__, ts->sec_function); client->irq = gpio_to_irq(platdata->irq_gpio); ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, nvt_ts_irq_thread, platdata->irq_flags, client->name, ts); if (ret < 0) { input_err(true, &client->dev, "failed to request irq\n"); goto err_regi_irq; } #if defined(CONFIG_DRM_MSM) ts->drm_notif.notifier_call = drm_notifier_callback; ret = msm_drm_register_client(&ts->drm_notif); if (ret) { input_err(true, &client->dev, "failed to register msm drm notifier ret=%d\n", ret); goto err_regi_drm_notif; } #endif #if defined(CONFIG_FB) ts->fb_nb.notifier_call = fb_notifier_cb; ret = fb_register_client(&ts->fb_nb); if (ret) { input_err(true, &client->dev, "failed to register fb notifier ret=%d\n", ret); goto err_regi_fb_notif; } #endif ret = nvt_ts_sec_fn_init(ts); if (ret) { input_err(true, &client->dev, "failed to init for factory function\n"); goto err_init_sec_fn; } #ifdef CONFIG_INPUT_SEC_SECURE_TOUCH ts->ss_touch_num = 1; if (sysfs_create_group(&ts->input_dev->dev.kobj, &secure_attr_group) >= 0) sec_secure_touch_register(ts, ts->ss_touch_num, &ts->input_dev->dev.kobj); else input_err(true, &client->dev, "failed create sysfs group\n"); secure_touch_init(ts); #endif #ifdef CONFIG_SAMSUNG_TUI tsp_info = ts; #endif #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) nvt_ts_flash_proc_init(ts); #endif schedule_delayed_work(&ts->work_print_info, msecs_to_jiffies(50)); input_err(true, &client->dev, "initialization is done\n"); input_log_fix(); return 0; err_init_sec_fn: #if defined(CONFIG_FB) fb_unregister_client(&ts->fb_nb); err_regi_fb_notif: #endif #if defined(CONFIG_DRM_MSM) msm_drm_unregister_client(&ts->drm_notif); err_regi_drm_notif: #endif err_regi_irq: err_input_regi_dev: err_fw_update: err_check_trim: err_kthread: mutex_destroy(&ts->lock); mutex_destroy(&ts->i2c_mutex); input_err(true, &client->dev, "failed to initialization\n"); input_log_fix(); return ret; } #ifdef CONFIG_PM static int nvt_ts_suspend(struct device *dev) { return 0; } static int nvt_ts_resume(struct device *dev) { return 0; } static SIMPLE_DEV_PM_OPS(nvt_ts_dev_pm_ops, nvt_ts_suspend, nvt_ts_resume); #endif static void nvt_ts_shutdown(struct i2c_client *client) { struct nvt_ts_data *ts = i2c_get_clientdata(client); free_irq(client->irq, ts); input_info(true, &ts->client->dev, "%s\n", __func__); } static int nvt_ts_remove(struct i2c_client *client) { struct nvt_ts_data *ts = i2c_get_clientdata(client); #if defined(CONFIG_FB) fb_unregister_client(&ts->fb_nb); #endif #if defined(CONFIG_DRM_MSM) msm_drm_unregister_client(&ts->drm_notif); #endif mutex_destroy(&ts->lock); mutex_destroy(&ts->i2c_mutex); input_info(true, &ts->client->dev, "%s\n", __func__); return 0; } #ifdef CONFIG_SAMSUNG_TUI extern int stui_i2c_lock(struct i2c_adapter *adap); extern int stui_i2c_unlock(struct i2c_adapter *adap); int stui_tsp_enter(void) { int ret = 0; if (!tsp_info) return -EINVAL; disable_irq(tsp_info->client->irq); ret = stui_i2c_lock(tsp_info->client->adapter); if (ret) { pr_err("[STUI] stui_i2c_lock failed : %d\n", ret); enable_irq(tsp_info->client->irq); return -1; } return 0; } int stui_tsp_exit(void) { int ret = 0; if (!tsp_info) return -EINVAL; ret = stui_i2c_unlock(tsp_info->client->adapter); if (ret) pr_err("[STUI] stui_i2c_unlock failed : %d\n", ret); enable_irq(tsp_info->client->irq); return ret; } #endif static const struct i2c_device_id nvt_ts_id[] = { { NVT_I2C_NAME, 0 }, { } }; #ifdef CONFIG_OF static struct of_device_id nvt_match_table[] = { { .compatible = "novatek,nvt-ts",}, { }, }; #endif static struct i2c_driver nvt_i2c_driver = { .driver = { .name = NVT_I2C_NAME, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &nvt_ts_dev_pm_ops, #endif .of_match_table = of_match_ptr(nvt_match_table), }, .probe = nvt_ts_probe, .remove = nvt_ts_remove, .shutdown = nvt_ts_shutdown, .id_table = nvt_ts_id, }; #ifdef CONFIG_BATTERY_SAMSUNG extern unsigned int lpcharge; #endif static int __init nvt_ts_driver_init(void) { int ret = 0; #ifdef CONFIG_BATTERY_SAMSUNG if (lpcharge) { pr_info("%s: %s: Do not load driver due to lpm %d\n", SECLOG, __func__, lpcharge); return ret; } #endif ret = i2c_add_driver(&nvt_i2c_driver); if (ret) pr_err("%s: %s: failed to add i2c driver\n", SECLOG, __func__); return ret; } static void __exit nvt_ts_driver_exit(void) { i2c_del_driver(&nvt_i2c_driver); } late_initcall_sync(nvt_ts_driver_init); module_exit(nvt_ts_driver_exit); MODULE_DESCRIPTION("Novatek Touchscreen Driver"); MODULE_LICENSE("GPL");