kernel_samsung_a34x-permissive/drivers/input/touchscreen/synaptics_tcm2/syna_tcm2.c
2024-04-28 15:51:13 +02:00

2451 lines
58 KiB
C

// 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");