// SPDX-License-Identifier: GPL-2.0 /* * Synaptics TouchCom touchscreen driver * * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. * * 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. * * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. * DOLLARS. */ /** * @file syna_tcm2.c * * This file implements the Synaptics device driver running under Linux kernel * input device subsystem, and also communicate with Synaptics touch controller * through TouchComm command-response protocol. */ #include "syna_tcm2.h" #include "syna_tcm2_platform.h" #include "synaptics_touchcom_core_dev.h" #include "synaptics_touchcom_func_base.h" #include "synaptics_touchcom_func_touch.h" #ifdef STARTUP_REFLASH #include "synaptics_touchcom_func_reflash.h" #endif #ifdef MULTICHIP_DUT_REFLASH #include "synaptics_touchcom_func_romboot.h" #endif /** * @section: USE_CUSTOM_TOUCH_REPORT_CONFIG * Open if willing to set up the format of touch report. * The custom_touch_format[] array can be used to describe the * customized report format. */ #ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG static unsigned char custom_touch_format[] = { /* entity code */ /* bits */ #ifdef ENABLE_WAKEUP_GESTURE TOUCH_REPORT_GESTURE_ID, 8, #endif TOUCH_REPORT_NUM_OF_ACTIVE_OBJECTS, 8, TOUCH_REPORT_FOREACH_ACTIVE_OBJECT, TOUCH_REPORT_OBJECT_N_INDEX, 8, TOUCH_REPORT_OBJECT_N_CLASSIFICATION, 8, TOUCH_REPORT_OBJECT_N_X_POSITION, 16, TOUCH_REPORT_OBJECT_N_Y_POSITION, 16, TOUCH_REPORT_FOREACH_END, TOUCH_REPORT_END }; #endif /** * @section: STARTUP_REFLASH_DELAY_TIME_MS * The delayed time to start fw update during the startup time. * This configuration depends on STARTUP_REFLASH. */ #ifdef STARTUP_REFLASH #define STARTUP_REFLASH_DELAY_TIME_MS (200) #define FW_IMAGE_NAME "synaptics/firmware.img" #endif /** * @section: RESET_ON_RESUME_DELAY_MS * The delayed time to issue a reset on resume state. * This configuration depends on RESET_ON_RESUME. */ #ifdef RESET_ON_RESUME #define RESET_ON_RESUME_DELAY_MS (100) #endif /** * @section: VERIFY_SEC_REPORTS * indicate the code segmant to verify the SEC reports */ #define VERIFY_SEC_REPORTS /** * @section: POWER_ALIVE_AT_SUSPEND * indicate that the power is still alive even at * system suspend. */ /* #define POWER_ALIVE_AT_SUSPEND */ /** * @section: global variables for an active drm panel * in order to register display notifier */ #ifdef USE_DRM_PANEL_NOTIFIER struct drm_panel *active_panel; #endif /** * syna_dev_read_from_sponge() * * Helper to retrieve raw data from sponge public area * * @param * [ in] tcm: tcm driver handle * [ in] rd_data: buffer to store the data read in * [ in] size_buf: size of given buffer * [ in] offset: offset to read * [ in] rd_len: size to read * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_read_from_sponge(struct syna_tcm *tcm, unsigned char *rd_buf, int size_buf, unsigned short offset, unsigned int rd_len) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; unsigned char payload[4] = {0}; unsigned char resp_code; struct tcm_buffer resp; if (tcm_dev->dev_mode != MODE_APPLICATION_FIRMWARE) { LOGN("Application firmware not running, current mode: %02x\n", tcm_dev->dev_mode); return -EINVAL; } if ((!rd_buf) || (size_buf == 0) || (size_buf < rd_len)) { LOGE("Invalid buffer size\n"); return -EINVAL; } if (rd_len == 0) return 0; syna_tcm_buf_init(&resp); payload[0] = (unsigned char)(offset & 0xFF); payload[1] = (unsigned char)((offset >> 8) & 0xFF); payload[2] = (unsigned char)(rd_len & 0xFF); payload[3] = (unsigned char)((rd_len >> 8) & 0xFF); retval = syna_tcm_send_command(tcm_dev, CMD_READ_SEC_SPONGE_REG, payload, sizeof(payload), &resp_code, &resp, 0); if (retval < 0) { LOGE("Fail to read from sponge, status:%x (offset:%x len:%d)\n", resp_code, offset, rd_len); goto exit; } if (resp.data_length != rd_len) { LOGE("Invalid data length\n"); retval = -EINVAL; goto exit; } retval = syna_pal_mem_cpy(rd_buf, size_buf, resp.buf, resp.buf_size, rd_len); if (retval < 0) { LOGE("Fail to copy sponge data to caller\n"); goto exit; } exit: syna_tcm_buf_release(&resp); return retval; } /** * syna_dev_write_to_sponge() * * Helper to Write raw data to sponge public area * * @param * [ in] tcm: tcm driver handle * [ in] wr_buf: buffer to write * [ in] size_buf: size of given buffer * [ in] offset: offset to write * [ in] wr_len: size to write * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_write_to_sponge(struct syna_tcm *tcm, unsigned char *wr_buf, int size_buf, unsigned short offset, unsigned int wr_len) { int retval = 0; unsigned char *payload = NULL; unsigned int payload_size; struct tcm_dev *tcm_dev = tcm->tcm_dev; unsigned char resp_code; struct tcm_buffer resp; if (tcm_dev->dev_mode != MODE_APPLICATION_FIRMWARE) { LOGN("Application firmware not running, current mode: %02x\n", tcm_dev->dev_mode); return -EINVAL; } if ((!wr_buf) || (size_buf == 0) || (size_buf < wr_len)) { LOGE("Invalid buffer size\n"); return -EINVAL; } if (wr_len == 0) return 0; syna_tcm_buf_init(&resp); payload_size = wr_len + 2; payload = syna_pal_mem_alloc(payload_size, sizeof(unsigned char)); if (!payload) { LOGE("Fail to allocate payload buffer\n"); goto exit; } payload[0] = (unsigned char)(offset & 0xFF); payload[1] = (unsigned char)((offset >> 8) & 0xFF); retval = syna_pal_mem_cpy(&payload[2], payload_size - 2, wr_buf, size_buf, wr_len); if (retval < 0) { LOGE("Fail to copy sponge data to wr buffer\n"); goto exit; } retval = syna_tcm_send_command(tcm_dev, CMD_WRITE_SEC_SPONGE_REG, payload, sizeof(payload), &resp_code, &resp, 0); if (retval < 0) { LOGE("Fail to write to sponge, status:%x (offset:%x len:%d)\n", resp_code, offset, wr_len); goto exit; } exit: if (payload) syna_pal_mem_free(payload); syna_tcm_buf_release(&resp); return retval; } #ifdef SEC_NATIVE_EAR_DETECTION /** * syna_dev_detect_proximity() * * Enable or disable the ear/proximity detection * * Enable - 0x00 0x00 * Disable - 0x01 0x00 * * @param * [ in] tcm: tcm driver handle * [ in] en: flag to enable or disable * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_detect_proximity(struct syna_tcm *tcm, bool en) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; unsigned short value = (en) ? 0 : 1; if (tcm_dev->dev_mode != MODE_APPLICATION_FIRMWARE) { LOGN("Application firmware not running, current mode: %02x\n", tcm_dev->dev_mode); return -EINVAL; } /* set up ear detection mode */ retval = syna_tcm_set_dynamic_config(tcm_dev, DC_DISABLE_PROXIMITY, value); if (retval < 0) { LOGE("Fail to set %d with dynamic command 0x%x\n", DC_DISABLE_PROXIMITY, value); return retval; } tcm->ed_enable = (int)en; LOGI("Proximity detection %s\n", (tcm->ed_enable)?"enabled":"disabled"); return 0; } #endif /** * syna_dev_enable_lowpwr_gesture() * * Enable or disable the low power gesture mode. * Furthermore, set up the wake-up irq. * * @param * [ in] tcm: tcm driver handle * [ in] en: '1' to enable low power gesture mode; '0' to disable * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enable_lowpwr_gesture(struct syna_tcm *tcm, bool en) { int retval = 0; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; if (!tcm->lpwg_enabled) return 0; if (attn->irq_id == 0) return 0; if (en) { if (!tcm->irq_wake) { enable_irq_wake(attn->irq_id); tcm->irq_wake = true; } /* enable wakeup gesture mode */ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_WAKEUP_GESTURE_MODE, 1); if (retval < 0) { LOGE("Fail to enable wakeup gesture via DC command\n"); return retval; } } else { if (tcm->irq_wake) { disable_irq_wake(attn->irq_id); tcm->irq_wake = false; } /* disable wakeup gesture mode */ retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_WAKEUP_GESTURE_MODE, 0); if (retval < 0) { LOGE("Fail to disable wakeup gesture via DC command\n"); return retval; } } return retval; } #ifdef ENABLE_CUSTOM_TOUCH_ENTITY /** * @section: Callback function used for custom entity parsing in touch report * * Allow to parse the custom "newly" entity in the touch report. * Please note that this function will be invoked in ISR * * @param * [ in] code: the code of current touch entity * [ in] config: the report configuration stored * [in/out] config_offset: offset of current position in report config, * and then return the updated position. * [ in] report: touch report given * [in/out] report_offset: offset of current position in touch report, * the updated position should be returned. * [ in] report_size: size of given touch report * [ in] callback_data: pointer to caller data passed to callback function * * @return * on success, 0 or positive value; otherwise, negative value on error. */ static int syna_dev_parse_custom_touch_report(const unsigned char code, const unsigned char *config, unsigned int *config_offset, const unsigned char *report, unsigned int *report_offset, unsigned int report_size, void *callback_data) { int retval; unsigned int data; unsigned int bits; struct syna_tcm *tcm = (struct syna_tcm *)callback_data; tcm->display_deep_sleep_state = SLEEP_NO_CHANGE; switch (code) { case TOUCH_REPORT_DISPLAY_DEEP_SLEEP_STATE: bits = config[(*config_offset)++]; retval = syna_tcm_get_touch_data(report, report_size, *report_offset, bits, &data); if (retval < 0) { LOGE("Fail to get display deep sleep state\n"); return retval; } if (tcm->display_deep_sleep_state != data) LOGI("display_deep_sleep_state %d\n", data); tcm->display_deep_sleep_state = data; *report_offset += bits; break; default: LOGW("Unknown touch config code (idx:%d 0x%02x)\n", *config_offset, code); retval = -1; break; } return retval; } #endif /** * syna_tcm_free_input_events() * * Clear all relevant touched events. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_free_input_events(struct syna_tcm *tcm) { struct input_dev *input_dev = tcm->input_dev; #ifdef TYPE_B_PROTOCOL unsigned int idx; #endif if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); if (tcm->prox_power_off) { LOGI("cancel proximity\n"); input_report_key(tcm->input_dev_proximity, KEY_INT_CANCEL, 1); input_sync(tcm->input_dev_proximity); input_report_key(tcm->input_dev_proximity, KEY_INT_CANCEL, 0); input_sync(input_dev); } #ifdef TYPE_B_PROTOCOL for (idx = 0; idx < MAX_NUM_OBJECTS; idx++) { input_mt_slot(input_dev, idx); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); } #endif input_report_key(input_dev, BTN_TOUCH, 0); input_report_key(input_dev, BTN_TOOL_FINGER, 0); #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif input_sync(input_dev); syna_pal_mutex_unlock(&tcm->tp_event_mutex); } /** * syna_dev_handle_sec_gesture_events() * * Parse a SEC gesture event . * * Retrieve data from custom gesture report generated by the device and * send input events to the input subsystem. * * The contents of event are defined by SEC, and the implementation is * also reference to SEC's open source. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_handle_sec_gesture_events(struct syna_tcm *tcm) { unsigned int report_size; struct sec_gesture_event_data *p_event; struct input_dev *input_dev = tcm->input_dev; if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); if (tcm->lp_state == PWR_OFF) goto exit; report_size = tcm->event_data.data_length; if (report_size > sizeof(struct sec_gesture_event_data)) { LOGE("Invalid report size %d (expected %d)\n", report_size, (int)sizeof(struct sec_gesture_event_data)); goto exit; } if (tcm->lp_state == LP_MODE) pm_wakeup_event(tcm->pdev->dev.parent, 1000); p_event = (struct sec_gesture_event_data *)tcm->event_data.buf; switch (p_event->type) { case SEC_TS_GESTURE_CODE_SWIPE: tcm->scrub_id = SPONGE_EVENT_TYPE_SPAY; LOGD("SEC_TS_GESTURE_CODE_SWIPE\n"); LOGD("SPAY: id:%d\n", tcm->scrub_id); input_report_key(input_dev, KEY_BLACK_UI_GESTURE, 1); break; case SEC_TS_GESTURE_CODE_DOUBLE_TAP: if (p_event->gesture_id == SEC_TS_GESTURE_ID_AOD) { tcm->scrub_id = SPONGE_EVENT_TYPE_AOD_DOUBLETAB; tcm->scrub_x = (p_event->gesture_data_1 << 4)| (p_event->gesture_data_3 >> 4); tcm->scrub_y = (p_event->gesture_data_2 << 4) | (p_event->gesture_data_3 & 0x0F); LOGD("SEC_TS_GESTURE_CODE_DOUBLE_TAP, id: AOD\n"); LOGD("AOD: id:%d, x:%d, y:%d\n", tcm->scrub_id, tcm->scrub_x, tcm->scrub_y); input_report_key(input_dev, KEY_BLACK_UI_GESTURE, 1); } else if (p_event->gesture_id == SEC_TS_GESTURE_ID_DOUBLETAP_TO_WAKEUP) { LOGD("SEC_TS_GESTURE_CODE_DOUBLE_TAP, id: TO_WAKEUP\n"); LOGD("AOT\n"); if ((tcm->lp_state == LP_MODE) && tcm->irq_wake) { input_report_key(input_dev, KEY_WAKEUP, 1); input_sync(input_dev); input_report_key(input_dev, KEY_WAKEUP, 0); } } break; case SEC_TS_GESTURE_CODE_SINGLE_TAP: tcm->scrub_id = SPONGE_EVENT_TYPE_SINGLE_TAP; tcm->scrub_x = (p_event->gesture_data_1 << 4) | (p_event->gesture_data_3 >> 4); tcm->scrub_y = (p_event->gesture_data_2 << 4) | (p_event->gesture_data_3 & 0x0F); LOGD("SEC_TS_GESTURE_CODE_SINGLE_TAP\n"); LOGD("SINGLE TAP: id:%d, x:%d, y:%d\n", tcm->scrub_id, tcm->scrub_x, tcm->scrub_y); input_report_key(input_dev, KEY_BLACK_UI_GESTURE, 1); break; case SEC_TS_GESTURE_CODE_PRESS: LOGD("SEC_TS_GESTURE_CODE_PRESS\n"); LOGD("FOD: %sPRESS\n", p_event->gesture_id ? "" : "LONG"); break; default: LOGE("Invalid gesture type, 0x%x\n", p_event->type); goto exit; } input_sync(input_dev); input_report_key(input_dev, KEY_BLACK_UI_GESTURE, 0); exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); } /** * syna_dev_handle_sec_touch_events() * * Parse a SEC coordinate event and report coordinate data to input * device subsystem. * * After syna_tcm_get_event_data() returns and original report data was * read in from firmware, parse and retrieve the essential data from the * given report data. After that, report to input device subsystm through * input events. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_handle_sec_touch_events(struct syna_tcm *tcm) { unsigned int report_size; struct sec_touch_event_data *p_event; struct input_dev *input_dev = tcm->input_dev; struct sec_ts_coordinate *ts = tcm->ts_coord; unsigned int ts_objs = 0; unsigned int idx; unsigned char t_id; unsigned char max_energy_flag = 0; if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); if (tcm->lp_state == PWR_OFF) goto exit; syna_pal_mem_set(tcm->ts_coord, 0x00, sizeof(tcm->ts_coord)); report_size = tcm->event_data.data_length; if (report_size % sizeof(struct sec_touch_event_data) != 0) { LOGE("Invalid report size %d (expected %d)\n", report_size, (int)sizeof(struct sec_touch_event_data)); goto exit; } /* parse the received data and * place into the correspendings position at ts_coord buffer */ ts_objs = report_size/sizeof(struct sec_touch_event_data); p_event = (struct sec_touch_event_data *)tcm->event_data.buf; for (idx = 0; idx < ts_objs; idx++) { if (p_event[idx].tid >= MAX_NUM_OBJECTS) { LOGW("Incorrect tID %d at %d obj\n", p_event[idx].tid, idx); goto exit; } t_id = p_event[idx].tid; ts[t_id].id = t_id; ts[t_id].action = p_event[idx].tchsta; ts[t_id].x = (p_event[idx].x_11_4 << 4) | (p_event[idx].x_3_0); ts[t_id].y = (p_event[idx].y_11_4 << 4) | (p_event[idx].y_3_0); ts[t_id].z = p_event[idx].z; ts[t_id].ttype = p_event[idx].ttype_3_2 << 2 | p_event[idx].ttype_1_0 << 0; ts[t_id].major = p_event[idx].major; ts[t_id].minor = p_event[idx].minor; #if defined(SEC_EVENT_16_BITS) max_energy_flag = p_event[t_id].max_energy_flag; #endif if (!ts[t_id].palm && (ts[t_id].ttype == SEC_TS_TOUCHTYPE_PALM)) ts[t_id].palm_count++; ts[t_id].palm = (ts[t_id].ttype == SEC_TS_TOUCHTYPE_PALM); ts[t_id].left_event = p_event[idx].left_event; #if defined(SEC_EVENT_16_BITS) ts[t_id].noise_level = MAX(ts[t_id].noise_level, (unsigned char)p_event[idx].noise_level); ts[t_id].max_strength = MAX(ts[t_id].max_strength, (unsigned char)p_event[idx].max_sensitivity); ts[t_id].hover_id_num = MAX(ts[t_id].hover_id_num, (unsigned char)p_event[idx].hover_id_num); #endif LOGD("[tID:%d] [action:%d] type:%d x:%d y:%d z:%d major:%d minor:%d\n", t_id, ts[t_id].action, ts[t_id].ttype, ts[t_id].x, ts[t_id].y, ts[t_id].z, ts[t_id].major, ts[t_id].minor); LOGD("[tID:%d] palm:%d noise:%x noiselvl:%d maxsen:%d hoverid:%d\n", t_id, ts[t_id].palm_count, ts[t_id].noise_status, ts[t_id].noise_level, ts[t_id].max_strength, ts[t_id].hover_id_num); if (ts[t_id].z <= 0) ts[t_id].z = 1; #ifdef REPORT_SWAP_XY ts[t_id].x = ts[t_id].x ^ ts[t_id].y; ts[t_id].y = ts[t_id].x ^ ts[t_id].y; ts[t_id].x = ts[t_id].x ^ ts[t_id].y; #endif #ifdef REPORT_FLIP_X ts[t_id].x = tcm->input_dev_params.max_x - ts[t_id].x; #endif #ifdef REPORT_FLIP_Y ts[t_id].y = tcm->input_dev_params.max_y - ts[t_id].y; #endif } /* report to input device subsystem */ tcm->touch_count = 0; for (idx = 0; idx < MAX_NUM_OBJECTS; idx++) { if ((ts[idx].action == SEC_TS_COORDINATE_ACTION_NONE) && (tcm->pre_action[idx] == SEC_TS_COORDINATE_ACTION_NONE)) { continue; } if ((ts[idx].action == SEC_TS_COORDINATE_ACTION_NONE) && ((tcm->pre_action[idx] == SEC_TS_COORDINATE_ACTION_MOVE) || (tcm->pre_action[idx] == SEC_TS_COORDINATE_ACTION_PRESS))) { LOGD("[idx:%d][tID:%d] jumping:%x->%x\n", idx, ts[idx].id, tcm->pre_action[idx], ts[t_id].action); ts[idx].action = SEC_TS_COORDINATE_ACTION_RELEASE; } switch (ts[idx].action) { case SEC_TS_COORDINATE_ACTION_RELEASE: input_mt_slot(input_dev, idx); input_report_abs(input_dev, ABS_MT_CUSTOM, 0); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); LOGD("[idx:%d][tID:%d] finger UP\n", idx, ts[idx].id); ts[idx].action = SEC_TS_COORDINATE_ACTION_NONE; ts[idx].mcount = 0; ts[idx].palm_count = 0; ts[idx].noise_level = 0; ts[idx].max_strength = 0; ts[idx].hover_id_num = 0; break; case SEC_TS_COORDINATE_ACTION_PRESS: ts[idx].max_energy_x = 0; ts[idx].max_energy_y = 0; ts[idx].p_x = ts[idx].x; ts[idx].p_y = ts[idx].y; if (max_energy_flag) { ts[idx].max_energy_x = ts[idx].x; ts[idx].max_energy_y = ts[idx].y; } LOGD("[idx:%d][tID:%d] finger DOWN\n", idx, ts[idx].id); case SEC_TS_COORDINATE_ACTION_MOVE: #ifdef TYPE_B_PROTOCOL input_mt_slot(input_dev, idx); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 1); #endif input_report_key(input_dev, BTN_TOUCH, 1); input_report_key(input_dev, BTN_TOOL_FINGER, 1); input_report_abs(input_dev, ABS_MT_POSITION_X, ts[idx].x); input_report_abs(input_dev, ABS_MT_POSITION_Y, ts[idx].y); #ifdef REPORT_TOUCH_WIDTH input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, ts[idx].major); input_report_abs(input_dev, ABS_MT_TOUCH_MINOR, ts[idx].minor); #endif input_report_abs(input_dev, ABS_MT_CUSTOM, ((max_energy_flag << 16) | (1 << 8) | (BRUSH_Z_DATA << 1) | ts[idx].palm)); #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif tcm->touch_count++; break; default: break; } tcm->pre_action[idx] = ts[idx].action; tcm->pre_ttype[idx] = ts[idx].ttype; } if (tcm->touch_count == 0) { input_report_key(input_dev, BTN_TOUCH, 0); input_report_key(input_dev, BTN_TOOL_FINGER, 0); #ifndef TYPE_B_PROTOCOL input_mt_sync(input_dev); #endif } input_sync(input_dev); exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); } /** * syna_dev_handle_sec_status_events() * * Handle a SEC status report. * * After syna_tcm_get_event_data() returns and original report data was * read in from firmware, parse and retrieve the essential data from the * given report data. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_handle_sec_status_events(struct syna_tcm *tcm) { unsigned int report_size; struct sec_status_event_data *p_event; struct input_dev *input_dev = tcm->input_dev; if (input_dev == NULL) return; syna_pal_mutex_lock(&tcm->tp_event_mutex); if (tcm->lp_state == PWR_OFF) goto exit; report_size = tcm->event_data.data_length; if (report_size > sizeof(struct sec_status_event_data)) { LOGE("Invalid report size %d (expected %d)\n", report_size, (int)sizeof(struct sec_status_event_data)); goto exit; } p_event = (struct sec_status_event_data *)tcm->event_data.buf; LOGD("type:%d id:%d\n",p_event->stype, p_event->status_id); if ((p_event->stype == TYPE_STATUS_EVENT_INFO) && (p_event->status_id == SEC_TS_ACK_WET_MODE)) { tcm->wet_count = p_event->status_data_1; } exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); } #if defined(SEC_NATIVE_EAR_DETECTION) /** * syna_dev_create_input_proximity_device() * * Allocate an input device and set up relevant parameters to the * input subsystem. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_create_input_proximity_device(struct syna_tcm *tcm) { int retval = 0; struct input_dev *input_dev = NULL; static char mms_phys[64] = { 0 }; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return -EINVAL; } input_dev = devm_input_allocate_device(dev); #else /* Legacy API */ input_dev = input_allocate_device(); #endif if (input_dev == NULL) { LOGE("Fail to allocate sec_touchproximity\n"); return -ENODEV; } input_dev->name = "sec_touchproximity"; snprintf(mms_phys, sizeof(mms_phys), "%s1", TOUCH_INPUT_PHYS_PATH); input_dev->phys = mms_phys; input_dev->dev.parent = tcm->pdev->dev.parent; input_set_drvdata(input_dev, tcm); set_bit(EV_SYN, input_dev->evbit); set_bit(EV_SW, input_dev->evbit); set_bit(INPUT_PROP_DIRECT, input_dev->propbit); input_set_abs_params(input_dev, ABS_MT_CUSTOM, 0, 0xFFFFFFFF, 0, 0); retval = input_register_device(input_dev); if (retval < 0) { LOGE("Fail to register input device, sec_touchproximity\n"); input_free_device(input_dev); input_dev = NULL; return retval; } tcm->input_dev_proximity = input_dev; return 0; } /** * syna_dev_release_input_proximity_device() * * Release an input device allocated previously. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_release_input_proximity_device(struct syna_tcm *tcm) { if (!tcm->input_dev_proximity) return; input_unregister_device(tcm->input_dev_proximity); input_free_device(tcm->input_dev_proximity); tcm->input_dev_proximity = NULL; } #endif /* end of defined(SEC_NATIVE_EAR_DETECTION) */ /** * syna_dev_open_input_device() * * By request, configure the device into normal power mode. * Thus, call dev_resume_pwr() function directly * * @param * [ in] dev: an instance of input device * * @return * on success, 0; otherwise, negative value on error. */ int syna_dev_open_input_device(struct input_dev *dev) { int retval; struct syna_tcm *tcm = input_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool support_ear_detect = hw_if->bdata.support_ear_detect; if (!tcm->is_connected) return 0; LOGD("lpwg_enabled:%d, ear_detection:%d\n", tcm->lpwg_enabled, support_ear_detect); if (tcm->lpwg_enabled || support_ear_detect) { retval = tcm->dev_set_normal_sensing(tcm); if (retval < 0) { LOGE("Fail to resume to normal power mode\n"); return retval; } } return 0; } /** * syna_dev_close_input_device() * * By request, configure the device into low-power mode. * Thus, call dev_suspend_pwr() function directly * * @param * [ in] dev: an instance of input device * * @return * none. */ void syna_dev_close_input_device(struct input_dev *dev) { int retval; struct syna_tcm *tcm = input_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool support_ear_detect = hw_if->bdata.support_ear_detect; LOGD("lpwg_enabled:%d, ear_detection:%d\n", tcm->lpwg_enabled, support_ear_detect); if (tcm->lpwg_enabled || support_ear_detect) { retval = tcm->dev_set_lowpwr_sensing(tcm); if (retval < 0) { LOGE("Fail to enter to low power mode\n"); return; } } } /** * syna_dev_create_input_device() * * Allocate an input device and set up relevant parameters to the * input subsystem. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_create_input_device(struct syna_tcm *tcm) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; struct input_dev *input_dev = NULL; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return -EINVAL; } input_dev = devm_input_allocate_device(dev); #else /* Legacy API */ input_dev = input_allocate_device(); #endif if (input_dev == NULL) { LOGE("Fail to allocate input device\n"); return -ENODEV; } input_dev->name = TOUCH_INPUT_NAME; input_dev->phys = TOUCH_INPUT_PHYS_PATH; input_dev->id.product = SYNAPTICS_TCM_DRIVER_ID; input_dev->id.version = SYNAPTICS_TCM_DRIVER_VERSION; input_dev->dev.parent = tcm->pdev->dev.parent; input_dev->open = syna_dev_open_input_device; input_dev->close = syna_dev_close_input_device; input_set_drvdata(input_dev, tcm); set_bit(EV_SYN, input_dev->evbit); set_bit(EV_KEY, input_dev->evbit); set_bit(EV_ABS, input_dev->evbit); set_bit(BTN_TOUCH, input_dev->keybit); set_bit(BTN_TOOL_FINGER, input_dev->keybit); #ifdef INPUT_PROP_DIRECT set_bit(INPUT_PROP_DIRECT, input_dev->propbit); #endif #ifdef ENABLE_WAKEUP_GESTURE set_bit(KEY_WAKEUP, input_dev->keybit); input_set_capability(input_dev, EV_KEY, KEY_WAKEUP); #endif input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, tcm_dev->max_x, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, tcm_dev->max_y, 0, 0); input_mt_init_slots(input_dev, tcm_dev->max_objects, INPUT_MT_DIRECT); #ifdef REPORT_TOUCH_WIDTH input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); #endif tcm->input_dev_params.max_x = tcm_dev->max_x; tcm->input_dev_params.max_y = tcm_dev->max_y; tcm->input_dev_params.max_objects = tcm_dev->max_objects; retval = input_register_device(input_dev); if (retval < 0) { LOGE("Fail to register input device\n"); input_free_device(input_dev); input_dev = NULL; return retval; } tcm->input_dev = input_dev; return 0; } /** * syna_dev_release_input_device() * * Release an input device allocated previously. * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_release_input_device(struct syna_tcm *tcm) { if (!tcm->input_dev) return; input_unregister_device(tcm->input_dev); input_free_device(tcm->input_dev); tcm->input_dev = NULL; } /** * syna_dev_check_input_params() * * Check if any of the input parameters registered to the input subsystem * has changed. * * @param * [ in] tcm: the driver handle * * @return * return 1, parameters are changed; otherwise, return 0. */ static int syna_dev_check_input_params(struct syna_tcm *tcm) { struct tcm_dev *tcm_dev = tcm->tcm_dev; if (tcm_dev->max_x == 0 && tcm_dev->max_y == 0) return 0; if (tcm->input_dev_params.max_x != tcm_dev->max_x) return 1; if (tcm->input_dev_params.max_y != tcm_dev->max_y) return 1; if (tcm->input_dev_params.max_objects != tcm_dev->max_objects) return 1; if (tcm_dev->max_objects > MAX_NUM_OBJECTS) { LOGW("Out of max num objects defined, in app_info: %d\n", tcm_dev->max_objects); return 0; } LOGN("Input parameters unchanged\n"); return 0; } /** * syna_dev_set_up_input_device() * * Set up input device to the input subsystem by confirming the supported * parameters and creating the device. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_set_up_input_device(struct syna_tcm *tcm) { int retval = 0; if (IS_NOT_APP_FW_MODE(tcm->tcm_dev->dev_mode)) { LOGN("Application firmware not running, current mode: %02x\n", tcm->tcm_dev->dev_mode); return 0; } syna_dev_free_input_events(tcm); syna_pal_mutex_lock(&tcm->tp_event_mutex); retval = syna_dev_check_input_params(tcm); if (retval == 0) goto exit; if (tcm->input_dev != NULL) syna_dev_release_input_device(tcm); retval = syna_dev_create_input_device(tcm); if (retval < 0) { LOGE("Fail to create input device\n"); goto exit; } #if defined(SEC_NATIVE_EAR_DETECTION) if (tcm->hw_if->bdata.support_ear_detect) { retval = syna_dev_create_input_proximity_device(tcm); if (retval < 0) { LOGE("Fail to create input proximity device\n"); syna_dev_release_input_device(tcm); goto exit; } } #endif syna_pal_mem_set(tcm->pre_action, 0x00, sizeof(tcm->pre_action)); syna_pal_mem_set(tcm->pre_ttype, 0x00, sizeof(tcm->pre_ttype)); exit: syna_pal_mutex_unlock(&tcm->tp_event_mutex); return retval; } /** * syna_dev_isr() * * This is the function to be called when the interrupt is asserted. * The purposes of this handler is to read events generated by device and * retrieve all enqueued messages until ATTN is no longer asserted. * * @param * [ in] irq: interrupt line * [ in] data: private data being passed to the handler function * * @return * on success, 0; otherwise, negative value on error. */ static irqreturn_t syna_dev_isr(int irq, void *data) { int retval; unsigned char code = 0; struct syna_tcm *tcm = data; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; if (unlikely(gpio_get_value(attn->irq_gpio) != attn->irq_on_state)) goto exit; tcm->isr_pid = current->pid; #ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS if (tcm->is_attn_redirecting) { syna_cdev_redirect_attn(tcm); goto exit; } #endif /* retrieve the original report date generated by firmware */ retval = syna_tcm_get_event_data(tcm->tcm_dev, &code, &tcm->event_data, &tcm->tp_data); if (retval < 0) { LOGE("Fail to get event data\n"); goto exit; } #ifdef ENABLE_EXTERNAL_FRAME_PROCESS if (tcm->report_to_queue[code] == EFP_ENABLE) { syna_cdev_update_report_queue(tcm, code, &tcm->tcm_dev->external_buf); #ifndef REPORT_CONCURRENTLY goto exit; #endif } #endif /* process SEC reports */ if (code == REPORT_SEC_COORDINATE_EVENT) syna_dev_handle_sec_touch_events(tcm); else if (code == REPORT_SEC_GESTURE_EVENT) syna_dev_handle_sec_gesture_events(tcm); else if (code == REPORT_SEC_STATUS_EVENT) syna_dev_handle_sec_status_events(tcm); exit: return IRQ_HANDLED; } /** * syna_dev_request_irq() * * Allocate an interrupt line and register the ISR handler * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_request_irq(struct syna_tcm *tcm) { int retval; struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); retval = -EINVAL; goto exit; } #endif if (attn->irq_gpio < 0) { LOGE("Invalid IRQ GPIO\n"); retval = -EINVAL; goto exit; } attn->irq_id = gpio_to_irq(attn->irq_gpio); #ifdef DEV_MANAGED_API retval = devm_request_threaded_irq(dev, attn->irq_id, NULL, syna_dev_isr, attn->irq_flags, PLATFORM_DRIVER_NAME, tcm); #else /* Legacy API */ retval = request_threaded_irq(attn->irq_id, NULL, syna_dev_isr, attn->irq_flags, PLATFORM_DRIVER_NAME, tcm); #endif if (retval < 0) { LOGE("Fail to request threaded irq\n"); goto exit; } attn->irq_enabled = true; LOGI("Interrupt handler registered\n"); exit: return retval; } /** * syna_dev_release_irq() * * Release an interrupt line allocated previously * * @param * [ in] tcm: the driver handle * * @return * none. */ static void syna_dev_release_irq(struct syna_tcm *tcm) { struct syna_hw_attn_data *attn = &tcm->hw_if->bdata_attn; #ifdef DEV_MANAGED_API struct device *dev = syna_request_managed_device(); if (!dev) { LOGE("Invalid managed device\n"); return; } #endif if (attn->irq_id <= 0) return; if (tcm->hw_if->ops_enable_irq) tcm->hw_if->ops_enable_irq(tcm->hw_if, false); #ifdef DEV_MANAGED_API devm_free_irq(dev, attn->irq_id, tcm); #else free_irq(attn->irq_id, tcm); #endif attn->irq_id = 0; attn->irq_enabled = false; LOGI("Interrupt handler released\n"); } /** * syna_dev_set_up_app_fw() * * Implement the essential steps for the initialization including the * preparation of app info and the configuration of touch report. * * This function should be called whenever the device initially powers * up, resets, or firmware update. * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_set_up_app_fw(struct syna_tcm *tcm) { int retval = 0; struct tcm_dev *tcm_dev = tcm->tcm_dev; if (IS_NOT_APP_FW_MODE(tcm_dev->dev_mode)) { LOGN("Application firmware not running, current mode: %02x\n", tcm_dev->dev_mode); return -EINVAL; } /* collect app info containing most of sensor information */ retval = syna_tcm_get_app_info(tcm_dev, &tcm_dev->app_info); if (retval < 0) { LOGE("Fail to get application info\n"); return -EIO; } /* VERIFY_SEC_REPORTS code segmant is used to verify the * SEC reports in the prior stage, it should be disabled * at the final release and configured in default fw. */ #ifdef VERIFY_SEC_REPORTS /* disable the generic touchcomm touch report */ retval = syna_tcm_enable_report(tcm_dev, REPORT_TOUCH, false); if (retval < 0) { LOGE("Fail to disable touch report\n"); return -EIO; } /* enable the sec reports */ retval = syna_tcm_enable_report(tcm_dev, REPORT_SEC_GESTURE_EVENT, true); if (retval < 0) { LOGE("Fail to enable sec gesture report\n"); return -EIO; } retval = syna_tcm_enable_report(tcm_dev, REPORT_SEC_COORDINATE_EVENT, true); if (retval < 0) { LOGE("Fail to enable sec coordinate report\n"); return -EIO; } retval = syna_tcm_enable_report(tcm_dev, REPORT_SEC_STATUS_EVENT, true); if (retval < 0) { LOGE("Fail to enable sec status report\n"); return -EIO; } return retval; #endif /* set up the format of touch report */ #ifdef USE_CUSTOM_TOUCH_REPORT_CONFIG retval = syna_tcm_set_touch_report_config(tcm_dev, custom_touch_format, (unsigned int)sizeof(custom_touch_format)); if (retval < 0) { LOGE("Fail to setup the custom touch report format\n"); return -EIO; } #endif /* preserve the format of touch report */ retval = syna_tcm_preserve_touch_report_config(tcm_dev); if (retval < 0) { LOGE("Fail to preserve touch report config\n"); return -EIO; } #ifdef ENABLE_CUSTOM_TOUCH_ENTITY /* set up custom touch data parsing method */ retval = syna_tcm_set_custom_touch_data_parsing_callback(tcm_dev, syna_dev_parse_custom_touch_report, (void *)tcm); if (retval < 0) { LOGE("Fail to set up custom touch data parsing method\n"); return -EIO; } #endif return retval; } /** * syna_dev_reflash_startup_work() * * Perform firmware update during system startup. * Function is available when the 'STARTUP_REFLASH' configuration * is enabled. * * @param * [ in] work: handle of work structure * * @return * none. */ #ifdef STARTUP_REFLASH static void syna_dev_reflash_startup_work(struct work_struct *work) { int retval; struct delayed_work *delayed_work; struct syna_tcm *tcm; struct tcm_dev *tcm_dev; const struct firmware *fw_entry; const unsigned char *fw_image = NULL; unsigned int fw_image_size; delayed_work = container_of(work, struct delayed_work, work); tcm = container_of(delayed_work, struct syna_tcm, reflash_work); tcm_dev = tcm->tcm_dev; /* get firmware image */ retval = request_firmware(&fw_entry, FW_IMAGE_NAME, tcm->pdev->dev.parent); if (retval < 0) { LOGE("Fail to request %s\n", FW_IMAGE_NAME); return; } fw_image = fw_entry->data; fw_image_size = fw_entry->size; LOGD("Firmware image size = %d\n", fw_image_size); pm_stay_awake(&tcm->pdev->dev); /* perform fw update */ #ifdef MULTICHIP_DUT_REFLASH retval = syna_tcm_romboot_do_multichip_reflash(tcm_dev, fw_image, fw_image_size, (REFLASH_ERASE_DELAY << 16) | REFLASH_WRITE_DELAY, false); #else retval = syna_tcm_do_fw_update(tcm_dev, fw_image, fw_image_size, (REFLASH_ERASE_DELAY << 16) | REFLASH_WRITE_DELAY, false); #endif if (retval < 0) { LOGE("Fail to do reflash\n"); goto exit; } /* re-initialize the app fw */ retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app fw after fw update\n"); goto exit; } /* allocate the input device if not registered yet */ if (tcm->input_dev == NULL) { retval = syna_dev_set_up_input_device(tcm); if (retval < 0) { LOGE("Fail to register input device\n"); goto exit; } } exit: pm_relax(&tcm->pdev->dev); } #endif /** * syna_dev_enter_normal_sensing() * * Helper to enter normal seneing mode * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enter_normal_sensing(struct syna_tcm *tcm) { int retval = 0; if (!tcm) return -EINVAL; /* bring out of sleep mode. */ retval = syna_tcm_sleep(tcm->tcm_dev, false); if (retval < 0) { LOGE("Fail to exit deep sleep\n"); return retval; } /* disable low power gesture mode, if needed */ if (tcm->lpwg_enabled) { retval = syna_dev_enable_lowpwr_gesture(tcm, false); if (retval < 0) { LOGE("Fail to disable low power gesture mode\n"); return retval; } } #if defined(SEC_NATIVE_EAR_DETECTION) if (tcm->ed_enable) { retval = syna_dev_detect_proximity(tcm, false); if (retval < 0) { LOGE("Fail to enable ear_detect mode\n"); return retval; } } #endif tcm->prox_power_off = 0; tcm->lp_state = PWR_ON; LOGD("power state:%d\n", tcm->lp_state); return 0; } /** * syna_dev_enter_lowpwr_sensing() * * Helper to enter power-saved sensing mode, that * may be the lower power gesture mode or deep sleep mode. * * @param * [ in] tcm: tcm driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_enter_lowpwr_sensing(struct syna_tcm *tcm) { int retval = 0; if (!tcm) return -EINVAL; /* enable low power gesture mode, if needed */ if (tcm->lpwg_enabled) { retval = syna_dev_enable_lowpwr_gesture(tcm, true); if (retval < 0) { LOGE("Fail to disable low power gesture mode\n"); return retval; } } else { /* enter sleep mode for non-LPWG cases */ if (!tcm->slept_in_early_suspend) { retval = syna_tcm_sleep(tcm->tcm_dev, true); if (retval < 0) { LOGE("Fail to enter deep sleep\n"); return retval; } } } if (tcm->lpwg_enabled || tcm->ed_enable) tcm->lp_state = LP_MODE; else tcm->lp_state = PWR_OFF; LOGD("power state:%d\n", tcm->lp_state); return 0; } /** * syna_dev_resume() * * Resume from the suspend state. * If RESET_ON_RESUME is defined, a reset is issued to the touch controller. * Otherwise, the touch controller is brought out of sleep mode. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_resume(struct device *dev) { int retval; struct syna_tcm *tcm = dev_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool irq_enabled = true; /* exit directly already in power on state */ if (tcm->lp_state == PWR_ON) return 0; #ifdef RESET_ON_RESUME syna_pal_sleep_ms(RESET_ON_RESUME_DELAY_MS); if (hw_if->ops_hw_reset) { hw_if->ops_hw_reset(hw_if); } else { retval = syna_tcm_reset(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to do reset\n"); goto exit; } } #else /* enter normal power mode */ retval = syna_dev_enter_normal_sensing(tcm); if (retval < 0) { LOGE("Fail to enter normal power mode\n"); goto exit; } retval = syna_tcm_rezero(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to rezero\n"); goto exit; } #endif /* end of RESET_ON_RESUME */ LOGI("Prepare to set up application firmware\n"); /* set up app firmware */ retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app firmware on resume\n"); goto exit; } retval = 0; LOGI("Device resumed\n"); exit: /* once lpwg is enabled, irq should be enabled already. * otherwise, set irq back to active mode. */ irq_enabled = (!tcm->lpwg_enabled) && (!tcm->ed_enable); /* enable irq */ if (irq_enabled && (hw_if->ops_enable_irq)) hw_if->ops_enable_irq(hw_if, true); tcm->slept_in_early_suspend = false; complete_all(&tcm->resume_done); return retval; } /** * syna_dev_suspend() * * Put device into suspend state. * Enter either the lower power gesture mode or sleep mode. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_suspend(struct device *dev) { struct syna_tcm *tcm = dev_get_drvdata(dev); struct syna_hw_interface *hw_if = tcm->hw_if; bool irq_disabled = true; /* exit directly once in power off state */ if (tcm->lp_state == PWR_OFF) return 0; /* clear all input events */ syna_dev_free_input_events(tcm); /* clear the event buffer */ syna_pal_mem_set(tcm->event_data.buf, 0x00, tcm->event_data.buf_size); syna_pal_mem_set(tcm->ts_coord, 0x00, sizeof(tcm->ts_coord)); #ifdef POWER_ALIVE_AT_SUSPEND /* enter power saved mode if power is not off */ if (syna_dev_suspend_pwr(tcm) < 0) { LOGE("Fail to enter suspended power mode\n"); } #endif /* once lpwg is enabled, irq should be alive. * otherwise, disable irq in suspend. */ irq_disabled = (!tcm->lpwg_enabled) && (!tcm->ed_enable); /* disable irq */ if (irq_disabled && (hw_if->ops_enable_irq)) hw_if->ops_enable_irq(hw_if, false); reinit_completion(&tcm->resume_done); LOGI("Device suspended\n"); return 0; } #if defined(ENABLE_DISP_NOTIFIER) /** * syna_dev_early_suspend() * * If having early suspend support, enter the sleep mode for * non-lpwg cases. * * @param * [ in] dev: an instance of device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_early_suspend(struct device *dev) { int retval; struct syna_tcm *tcm = dev_get_drvdata(dev); /* exit directly once in power off state */ if (tcm->lp_state == PWR_OFF) return 0; if (!tcm->lpwg_enabled) { retval = syna_tcm_sleep(tcm->tcm_dev, true); if (retval < 0) { LOGE("Fail to enter deep sleep\n"); return retval; } } tcm->slept_in_early_suspend = true; return 0; } /** * syna_dev_fb_notifier_cb() * * Listen the display screen on/off event and perform the corresponding * actions. * * @param * [ in] nb: instance of notifier_block * [ in] action: fb action * [ in] data: fb event data * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_fb_notifier_cb(struct notifier_block *nb, unsigned long action, void *data) { int retval; int transition; #if defined(USE_DRM_PANEL_NOTIFIER) struct drm_panel_notifier *evdata = data; #else struct fb_event *evdata = data; #endif struct syna_tcm *tcm = container_of(nb, struct syna_tcm, fb_notifier); int time = 0; int disp_blank_powerdown; int disp_early_event_blank; int disp_blank; int disp_blank_unblank; if (!evdata || !evdata->data || !tcm) return 0; retval = 0; #if defined(USE_DRM_PANEL_NOTIFIER) disp_blank_powerdown = DRM_PANEL_BLANK_POWERDOWN; disp_early_event_blank = DRM_PANEL_EARLY_EVENT_BLANK; disp_blank = DRM_PANEL_EVENT_BLANK; disp_blank_unblank = DRM_PANEL_BLANK_UNBLANK; #else disp_blank_powerdown = FB_BLANK_POWERDOWN; disp_early_event_blank = FB_EARLY_EVENT_BLANK; disp_blank = FB_EVENT_BLANK; disp_blank_unblank = FB_BLANK_UNBLANK; #endif transition = *(int *)evdata->data; /* confirm the firmware flashing is completed before screen off */ if (transition == disp_blank_powerdown) { while (ATOMIC_GET(tcm->tcm_dev->firmware_flashing)) { syna_pal_sleep_ms(500); time += 500; if (time >= 5000) { LOGE("Timed out waiting for reflashing\n"); ATOMIC_SET(tcm->tcm_dev->firmware_flashing, 0); return -EIO; } } } if (action == disp_early_event_blank && transition == disp_blank_powerdown) { retval = syna_dev_early_suspend(&tcm->pdev->dev); } else if (action == disp_blank) { if (transition == disp_blank_powerdown) { retval = syna_dev_suspend(&tcm->pdev->dev); tcm->fb_ready = 0; } else if (transition == disp_blank_unblank) { #ifndef RESUME_EARLY_UNBLANK retval = syna_dev_resume(&tcm->pdev->dev); tcm->fb_ready++; #endif } else if (action == disp_early_event_blank && transition == disp_blank_unblank) { #ifdef RESUME_EARLY_UNBLANK retval = syna_dev_resume(&tcm->pdev->dev); tcm->fb_ready++; #endif } } return 0; } #endif /** * syna_dev_disconnect() * * This function will power off the connected device. * Then, all the allocated resource will be released. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_disconnect(struct syna_tcm *tcm) { struct syna_hw_interface *hw_if = tcm->hw_if; struct tcm_dev *tcm_dev = tcm->tcm_dev; if (!tcm_dev) { LOGE("Invalid tcm_dev\n"); return -EINVAL; } if (tcm->is_connected == false) { LOGI("%s already disconnected\n", PLATFORM_DRIVER_NAME); return 0; } #ifdef STARTUP_REFLASH cancel_delayed_work_sync(&tcm->reflash_work); flush_workqueue(tcm->reflash_workqueue); destroy_workqueue(tcm->reflash_workqueue); #endif /* free interrupt line */ if (hw_if->bdata_attn.irq_id) syna_dev_release_irq(tcm); #if 0 /* remove SEC custom functions */ syna_sec_fn_remove(tcm); #endif /* unregister input device */ syna_dev_release_input_device(tcm); #if defined(SEC_NATIVE_EAR_DETECTION) syna_dev_release_input_proximity_device(tcm); #endif tcm->input_dev_params.max_x = 0; tcm->input_dev_params.max_y = 0; tcm->input_dev_params.max_objects = 0; /* power off */ if (hw_if->ops_power_on) hw_if->ops_power_on(hw_if, false); tcm->is_connected = false; LOGI("%s device disconnected\n", PLATFORM_DRIVER_NAME); return 0; } /** * syna_dev_connect() * * This function will power on and identify the connected device. * At the end of function, the ISR will be registered as well. * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_connect(struct syna_tcm *tcm) { int retval; struct syna_hw_interface *hw_if = tcm->hw_if; struct tcm_dev *tcm_dev = tcm->tcm_dev; if (!tcm_dev) { LOGE("Invalid tcm_dev\n"); return -EINVAL; } if (tcm->is_connected) { LOGI("%s already connected\n", PLATFORM_DRIVER_NAME); return 0; } /* power on the connected device */ if (hw_if->ops_power_on) { retval = hw_if->ops_power_on(hw_if, true); if (retval < 0) return -ENODEV; } /* perform a hardware reset */ if (hw_if->ops_hw_reset) hw_if->ops_hw_reset(hw_if); /* detect which modes of touch controller is running * * the signal of the Touch IC ready is called as "identify" * report and generated by firmware */ retval = syna_tcm_detect_device(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to detect the device\n"); goto err_detect_dev; } switch (retval) { case MODE_APPLICATION_FIRMWARE: retval = syna_dev_set_up_app_fw(tcm); if (retval < 0) { #ifdef FORCE_CONNECTION LOGW("App firmware is not available yet\n"); LOGW("However, connect and skip initialization\n"); goto end; #else LOGE("Fail to set up application firmware\n"); goto err_setup_app_fw; #endif } /* allocate and register to input device subsystem */ retval = syna_dev_set_up_input_device(tcm); if (retval < 0) { LOGE("Fail to set up input device\n"); goto err_setup_input_dev; } break; default: LOGN("Application firmware not running, current mode: %02x\n", retval); break; } #if 0 /* SEC custom functions */ retval = syna_sec_fn_init(tcm); if (retval < 0) { LOGE("Fail to do sec_fn_init\n"); goto err_request_irq; } #endif init_completion(&tcm->resume_done); complete_all(&tcm->resume_done); /* register the interrupt handler */ retval = syna_dev_request_irq(tcm); if (retval < 0) { LOGE("Fail to request the interrupt line\n"); goto err_request_irq; } /* for the reference, * create a delayed work to perform fw update during the startup time */ #ifdef STARTUP_REFLASH tcm->reflash_workqueue = create_singlethread_workqueue("syna_reflash"); INIT_DELAYED_WORK(&tcm->reflash_work, syna_dev_reflash_startup_work); queue_delayed_work(tcm->reflash_workqueue, &tcm->reflash_work, msecs_to_jiffies(STARTUP_REFLASH_DELAY_TIME_MS)); #endif #ifdef FORCE_CONNECTION end: #endif tcm->is_connected = true; tcm->lp_state = PWR_ON; LOGI("TCM packrat: %d\n", tcm->tcm_dev->packrat_number); LOGI("Features: name(%s), lpwg(%s), hw_rst(%s), irq_ctrl(%s)\n", PLATFORM_DRIVER_NAME, (tcm->lpwg_enabled) ? "yes" : "no", (hw_if->ops_hw_reset) ? "yes" : "no", (hw_if->ops_enable_irq) ? "yes" : "no"); LOGI("Features: name(%s), ear_detect(%s)\n", PLATFORM_DRIVER_NAME, (hw_if->bdata.support_ear_detect) ? "yes" : "no"); return 0; err_request_irq: /* unregister input device */ syna_dev_release_input_device(tcm); #if defined(SEC_NATIVE_EAR_DETECTION) syna_dev_release_input_proximity_device(tcm); #endif err_setup_input_dev: #ifdef FORCE_CONNECTION #else err_setup_app_fw: #endif err_detect_dev: if (hw_if->ops_power_on) hw_if->ops_power_on(hw_if, false); return retval; } #ifdef USE_DRM_PANEL_NOTIFIER static struct drm_panel *syna_dev_get_panel(struct device_node *np) { int i; int count; struct device_node *node; struct drm_panel *panel; count = of_count_phandle_with_args(np, "panel", NULL); if (count <= 0) return NULL; for (i = 0; i < count; i++) { node = of_parse_phandle(np, "panel", i); panel = of_drm_find_panel(node); of_node_put(node); if (!IS_ERR(panel)) { LOGI("Find available panel\n"); return panel; } } return NULL; } #endif /** * syna_dev_probe() * * Install the TouchComm device driver * * @param * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_probe(struct platform_device *pdev) { int retval; struct syna_tcm *tcm = NULL; struct tcm_dev *tcm_dev = NULL; struct syna_hw_interface *hw_if = NULL; #if defined(USE_DRM_PANEL_NOTIFIER) struct device *dev; #endif hw_if = pdev->dev.platform_data; if (!hw_if) { LOGE("Fail to find hardware configuration\n"); return -EINVAL; } tcm = syna_pal_mem_alloc(1, sizeof(struct syna_tcm)); if (!tcm) { LOGE("Fail to create the instance of syna_tcm\n"); return -ENOMEM; } /* allocate the TouchCom device handle */ retval = syna_tcm_allocate_device(&tcm_dev, hw_if); if ((retval < 0) || (!tcm_dev)) { LOGE("Fail to allocate TouchCom device handle\n"); goto err_allocate_cdev; } tcm->tcm_dev = tcm_dev; tcm->pdev = pdev; tcm->hw_if = hw_if; syna_tcm_buf_init(&tcm->event_data); syna_pal_mutex_alloc(&tcm->tp_event_mutex); #ifdef ENABLE_WAKEUP_GESTURE tcm->lpwg_enabled = true; #else tcm->lpwg_enabled = false; #endif tcm->irq_wake = false; tcm->is_connected = false; tcm->dev_connect = syna_dev_connect; tcm->dev_disconnect = syna_dev_disconnect; tcm->dev_set_up_app_fw = syna_dev_set_up_app_fw; tcm->dev_set_normal_sensing = syna_dev_enter_normal_sensing; tcm->dev_set_lowpwr_sensing = syna_dev_enter_lowpwr_sensing; #if defined(SEC_NATIVE_EAR_DETECTION) tcm->dev_detect_prox = syna_dev_detect_proximity; #endif tcm->dev_write_sponge = syna_dev_write_to_sponge; tcm->dev_read_sponge = syna_dev_read_from_sponge; platform_set_drvdata(pdev, tcm); device_init_wakeup(&pdev->dev, 1); #if defined(TCM_CONNECT_IN_PROBE) /* connect to target device */ retval = tcm->dev_connect(tcm); if (retval < 0) { LOGE("Fail to connect to the device\n"); syna_pal_mutex_free(&tcm->tp_event_mutex); goto err_connect; } #endif #ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS /* create the device file and register to char device classes */ retval = syna_cdev_create_sysfs(tcm, pdev); if (retval < 0) { LOGE("Fail to create the device sysfs\n"); syna_pal_mutex_free(&tcm->tp_event_mutex); goto err_create_cdev; } #endif #if defined(ENABLE_DISP_NOTIFIER) #if defined(USE_DRM_PANEL_NOTIFIER) dev = syna_request_managed_device(); active_panel = syna_dev_get_panel(dev->of_node); if (active_panel) { tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; retval = drm_panel_notifier_register(active_panel, &tcm->fb_notifier); if (retval < 0) { LOGE("Fail to register FB notifier client\n"); goto err_create_cdev; } } else { LOGE("No available drm panel\n"); } #else tcm->fb_notifier.notifier_call = syna_dev_fb_notifier_cb; retval = fb_register_client(&tcm->fb_notifier); if (retval < 0) { LOGE("Fail to register FB notifier client\n"); goto err_create_cdev; } #endif #endif LOGI("%s TouchComm driver v%d.%d.%d installed\n", PLATFORM_DRIVER_NAME, (unsigned char)(SYNAPTICS_TCM_DRIVER_VERSION >> 8), (unsigned char)SYNAPTICS_TCM_DRIVER_VERSION, (unsigned char)SYNAPTICS_TCM_DRIVER_SUBVER); return 0; #ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS err_create_cdev: syna_tcm_remove_device(tcm->tcm_dev); #endif #if defined(TCM_CONNECT_IN_PROBE) tcm->dev_disconnect(tcm); err_connect: #endif syna_tcm_buf_release(&tcm->event_data); syna_pal_mutex_free(&tcm->tp_event_mutex); err_allocate_cdev: syna_pal_mem_free((void *)tcm); return retval; } /** * syna_dev_remove() * * Release all allocated resources and remove the TouchCom device handle * * @param * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ static int syna_dev_remove(struct platform_device *pdev) { struct syna_tcm *tcm = platform_get_drvdata(pdev); if (!tcm) { LOGW("Invalid handle to remove\n"); return 0; } #if defined(ENABLE_DISP_NOTIFIER) #if defined(USE_DRM_PANEL_NOTIFIER) if (active_panel) drm_panel_notifier_unregister(active_panel, &tcm->fb_notifier); #else fb_unregister_client(&tcm->fb_notifier); #endif #endif #ifdef CONFIG_TOUCHSCREEN_SYNA_TCM2_SYSFS /* remove the cdev and sysfs nodes */ syna_cdev_remove_sysfs(tcm); #endif #if defined(TCM_CONNECT_IN_PROBE) /* do disconnection */ if (tcm->dev_disconnect(tcm) < 0) LOGE("Fail to do device disconnection\n"); #endif syna_tcm_buf_release(&tcm->event_data); syna_pal_mutex_free(&tcm->tp_event_mutex); /* remove the allocated tcm device */ syna_tcm_remove_device(tcm->tcm_dev); /* release the device context */ syna_pal_mem_free((void *)tcm); return 0; } /** * syna_dev_shutdown() * * Call syna_dev_remove() to release all resources * * @param * [in] pdev: an instance of platform device * * @return * none. */ static void syna_dev_shutdown(struct platform_device *pdev) { syna_dev_remove(pdev); } /** * Declare a TouchComm platform device */ #ifdef CONFIG_PM static const struct dev_pm_ops syna_dev_pm_ops = { #if !defined(ENABLE_DISP_NOTIFIER) .suspend = syna_dev_suspend, .resume = syna_dev_resume, #endif }; #endif static struct platform_driver syna_dev_driver = { .driver = { .name = PLATFORM_DRIVER_NAME, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &syna_dev_pm_ops, #endif }, .probe = syna_dev_probe, .remove = syna_dev_remove, .shutdown = syna_dev_shutdown, }; /** * syna_dev_module_init() * * The entry function of the reference driver, which initialize the * lower-level bus and register a platform driver. * * @param * void. * * @return * 0 if the driver registered and bound to a device, * else returns a negative error code and with the driver not registered. */ static int __init syna_dev_module_init(void) { int retval; retval = syna_hw_interface_init(); if (retval < 0) return retval; return platform_driver_register(&syna_dev_driver); } /** * syna_dev_module_exit() * * Function is called when un-installing the driver. * Remove the registered platform driver and the associated bus driver. * * @param * void. * * @return * none. */ static void __exit syna_dev_module_exit(void) { platform_driver_unregister(&syna_dev_driver); syna_hw_interface_exit(); } module_init(syna_dev_module_init); module_exit(syna_dev_module_exit); MODULE_AUTHOR("Synaptics, Inc."); MODULE_DESCRIPTION("Synaptics TCM Touch Driver"); MODULE_LICENSE("GPL v2");