blob: aa2c3ca700f0114e7e2002af8641ba721bcf9948 [file] [log] [blame]
/*
*
* FocalTech TouchScreen driver.
*
* Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
/*****************************************************************************
*
* File Name: focaltech_core.c
*
* Author: Focaltech Driver Team
*
* Created: 2016-08-08
*
* Abstract: entrance for focaltech ts driver
*
* Version: V1.0
*
*****************************************************************************/
/*****************************************************************************
* Included header files
*****************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#if defined(CONFIG_FB)
#include <linux/notifier.h>
#include <linux/fb.h>
#elif defined(CONFIG_DRM)
#if defined(CONFIG_DRM_PANEL)
#include <drm/drm_panel.h>
#elif defined(CONFIG_ARCH_MSM)
#include <linux/msm_drm_notify.h>
#endif
#elif defined(CONFIG_HAS_EARLYSUSPEND)
#include <linux/earlysuspend.h>
#define FTS_SUSPEND_LEVEL 1 /* Early-suspend level */
#endif
#include <linux/types.h>
#include "focaltech_core.h"
/*****************************************************************************
* Private constant and macro definitions using #define
*****************************************************************************/
#define FTS_DRIVER_NAME "fts_ts"
#define FTS_DRIVER_PEN_NAME "fts_ts,pen"
#define INTERVAL_READ_REG 200 /* unit:ms */
#define TIMEOUT_READ_REG 1000 /* unit:ms */
/*****************************************************************************
* Global variable or extern global variabls/functions
*****************************************************************************/
struct fts_ts_data *fts_data;
/*****************************************************************************
* Static function prototypes
*****************************************************************************/
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
static int register_panel_bridge(struct fts_ts_data *ts);
static void unregister_panel_bridge(struct drm_bridge *bridge);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
static void fts_offload_set_running(struct fts_ts_data *ts_data, bool running);
static void fts_populate_frame(struct fts_ts_data *ts_data, int populate_channel_types);
static void fts_offload_push_coord_frame(struct fts_ts_data *ts);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
static int fts_get_heatmap(struct fts_ts_data *ts_data);
#endif
static int fts_ts_suspend(struct device *dev);
static int fts_ts_resume(struct device *dev);
static void fts_update_motion_filter(struct fts_ts_data *ts, u8 touches);
static char *status_list_str[STATUS_CNT_END] = {
"Baseline refreshed",
"Baseline refreshed",
"Palm",
"Water",
"Grip",
"Glove",
"Edge palm",
"RESET",
};
static char *feature_list_str[FW_CNT_END] = {
"FW_GLOVE",
"FW_GRIP",
"FW_PALM",
"FW_HEATMAP",
"FW_CONTINUOUS",
};
static char *status_baseline_refresh_str[4] = {
"Baseline refreshed: none",
"Baseline refreshed: removing touch",
"Baseline refreshed: removing water",
"Baseline refreshed: removing shell iron",
};
int fts_check_cid(struct fts_ts_data *ts_data, u8 id_h)
{
int i = 0;
struct ft_chip_id_t *cid = &ts_data->ic_info.cid;
u8 cid_h = 0x0;
if (cid->type == 0)
return -ENODATA;
for (i = 0; i < FTS_MAX_CHIP_IDS; i++) {
cid_h = ((cid->chip_ids[i] >> 8) & 0x00FF);
if (cid_h && (id_h == cid_h)) {
return 0;
}
}
return -ENODATA;
}
/*****************************************************************************
* Name: fts_wait_tp_to_valid
* Brief: Read chip id until TP FW become valid(Timeout: TIMEOUT_READ_REG),
* need call when reset/power on/resume...
* Input:
* Output:
* Return: return 0 if tp valid, otherwise return error code
*****************************************************************************/
int fts_wait_tp_to_valid(void)
{
int ret = 0;
int cnt = 0;
u8 idh = 0;
struct fts_ts_data *ts_data = fts_data;
u8 chip_idh = ts_data->ic_info.ids.chip_idh;
u16 retry_duration = 0;
do {
ret = fts_read_reg(FTS_REG_CHIP_ID, &idh);
if (ret == 0 && ((idh == chip_idh) || (fts_check_cid(ts_data, idh) == 0))) {
FTS_INFO("TP Ready,Device ID:0x%02x, retry:%d", idh, cnt);
return 0;
}
cnt++;
if (ret == -EIO) {
fts_reset_proc(FTS_RESET_INTERVAL);
retry_duration += FTS_RESET_INTERVAL;
} else {
msleep(INTERVAL_READ_REG);
retry_duration += INTERVAL_READ_REG;
}
} while (retry_duration < TIMEOUT_READ_REG);
FTS_ERROR("Wait tp timeout");
return -ETIMEDOUT;
}
/*****************************************************************************
* Name: fts_tp_state_recovery
* Brief: Need execute this function when reset
* Input:
* Output:
* Return:
*****************************************************************************/
void fts_tp_state_recovery(struct fts_ts_data *ts_data)
{
FTS_FUNC_ENTER();
/* wait tp stable */
fts_wait_tp_to_valid();
/* recover all firmware modes based on the settings of driver side. */
fts_ex_mode_recovery(ts_data);
/* recover TP gesture state 0xD0 */
fts_gesture_recovery(ts_data);
FTS_FUNC_EXIT();
}
int fts_reset_proc(int hdelayms)
{
FTS_DEBUG("tp reset");
gpio_direction_output(fts_data->pdata->reset_gpio, 0);
/* The minimum reset duration is 1 ms. */
msleep(1);
gpio_direction_output(fts_data->pdata->reset_gpio, 1);
if (hdelayms) {
msleep(hdelayms);
}
return 0;
}
void fts_irq_disable(void)
{
unsigned long irqflags;
FTS_FUNC_ENTER();
spin_lock_irqsave(&fts_data->irq_lock, irqflags);
if (!fts_data->irq_disabled) {
disable_irq_nosync(fts_data->irq);
fts_data->irq_disabled = true;
}
spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
FTS_FUNC_EXIT();
}
void fts_irq_enable(void)
{
unsigned long irqflags = 0;
FTS_FUNC_ENTER();
spin_lock_irqsave(&fts_data->irq_lock, irqflags);
if (fts_data->irq_disabled) {
enable_irq(fts_data->irq);
fts_data->irq_disabled = false;
}
spin_unlock_irqrestore(&fts_data->irq_lock, irqflags);
FTS_FUNC_EXIT();
}
void fts_hid2std(void)
{
int ret = 0;
u8 buf[3] = {0xEB, 0xAA, 0x09};
if (fts_data->bus_type != FTS_BUS_TYPE_I2C)
return;
ret = fts_write(buf, 3);
if (ret < 0) {
FTS_ERROR("hid2std cmd write fail");
} else {
msleep(10);
buf[0] = buf[1] = buf[2] = 0;
ret = fts_read(NULL, 0, buf, 3);
if (ret < 0) {
FTS_ERROR("hid2std cmd read fail");
} else if ((0xEB == buf[0]) && (0xAA == buf[1]) && (0x08 == buf[2])) {
FTS_DEBUG("hidi2c change to stdi2c successful");
} else {
FTS_DEBUG("hidi2c change to stdi2c not support or fail");
}
}
}
static int fts_match_cid(struct fts_ts_data *ts_data,
u16 type, u8 id_h, u8 id_l, bool force)
{
#ifdef FTS_CHIP_ID_MAPPING
u32 i = 0;
u32 j = 0;
struct ft_chip_id_t chip_id_list[] = FTS_CHIP_ID_MAPPING;
u32 cid_entries = sizeof(chip_id_list) / sizeof(struct ft_chip_id_t);
u16 id = (id_h << 8) + id_l;
memset(&ts_data->ic_info.cid, 0, sizeof(struct ft_chip_id_t));
for (i = 0; i < cid_entries; i++) {
if (!force && (type == chip_id_list[i].type)) {
break;
} else if (force && (type == chip_id_list[i].type)) {
FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
ts_data->ic_info.cid = chip_id_list[i];
return 0;
}
}
if (i >= cid_entries) {
return -ENODATA;
}
for (j = 0; j < FTS_MAX_CHIP_IDS; j++) {
if (id == chip_id_list[i].chip_ids[j]) {
FTS_DEBUG("cid:%x==%x", id, chip_id_list[i].chip_ids[j]);
FTS_INFO("match cid,type:0x%x", (int)chip_id_list[i].type);
ts_data->ic_info.cid = chip_id_list[i];
return 0;
}
}
return -ENODATA;
#else
return -EINVAL;
#endif
}
static int fts_get_chip_types(
struct fts_ts_data *ts_data,
u8 id_h, u8 id_l, bool fw_valid)
{
u32 i = 0;
struct ft_chip_t ctype[] = FTS_CHIP_TYPE_MAPPING;
u32 ctype_entries = sizeof(ctype) / sizeof(struct ft_chip_t);
if ((0x0 == id_h) || (0x0 == id_l)) {
FTS_ERROR("id_h/id_l is 0");
return -EINVAL;
}
FTS_DEBUG("verify id:0x%02x%02x", id_h, id_l);
for (i = 0; i < ctype_entries; i++) {
if (VALID == fw_valid) {
if (((id_h == ctype[i].chip_idh) && (id_l == ctype[i].chip_idl))
|| (!fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 0)))
break;
} else {
if (((id_h == ctype[i].rom_idh) && (id_l == ctype[i].rom_idl))
|| ((id_h == ctype[i].pb_idh) && (id_l == ctype[i].pb_idl))
|| ((id_h == ctype[i].bl_idh) && (id_l == ctype[i].bl_idl))) {
break;
}
}
}
if (i >= ctype_entries) {
return -ENODATA;
}
fts_match_cid(ts_data, ctype[i].type, id_h, id_l, 1);
ts_data->ic_info.ids = ctype[i];
return 0;
}
static int fts_read_bootid(struct fts_ts_data *ts_data, u8 *id)
{
int ret = 0;
u8 chip_id[2] = { 0 };
u8 id_cmd[4] = { 0 };
u32 id_cmd_len = 0;
id_cmd[0] = FTS_CMD_START1;
id_cmd[1] = FTS_CMD_START2;
ret = fts_write(id_cmd, 2);
if (ret < 0) {
FTS_ERROR("start cmd write fail");
return ret;
}
msleep(FTS_CMD_START_DELAY);
id_cmd[0] = FTS_CMD_READ_ID;
id_cmd[1] = id_cmd[2] = id_cmd[3] = 0x00;
if (ts_data->ic_info.is_incell)
id_cmd_len = FTS_CMD_READ_ID_LEN_INCELL;
else
id_cmd_len = FTS_CMD_READ_ID_LEN;
ret = fts_read(id_cmd, id_cmd_len, chip_id, 2);
if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
FTS_ERROR("read boot id fail,read:0x%02x%02x", chip_id[0], chip_id[1]);
return -EIO;
}
id[0] = chip_id[0];
id[1] = chip_id[1];
return 0;
}
/*****************************************************************************
* Name: fts_get_ic_information
* Brief: read chip id to get ic information, after run the function, driver w-
* ill know which IC is it.
* If cant get the ic information, maybe not focaltech's touch IC, need
* unregister the driver
* Input:
* Output:
* Return: return 0 if get correct ic information, otherwise return error code
*****************************************************************************/
static int fts_get_ic_information(struct fts_ts_data *ts_data)
{
int ret = 0;
int cnt = 0;
u8 chip_id[2] = { 0 };
ts_data->ic_info.is_incell = FTS_CHIP_IDC;
ts_data->ic_info.hid_supported = FTS_HID_SUPPORTTED;
do {
ret = fts_read_reg(FTS_REG_CHIP_ID, &chip_id[0]);
ret = fts_read_reg(FTS_REG_CHIP_ID2, &chip_id[1]);
if ((ret < 0) || (0x0 == chip_id[0]) || (0x0 == chip_id[1])) {
FTS_DEBUG("chip id read invalid, read:0x%02x%02x",
chip_id[0], chip_id[1]);
} else {
ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], VALID);
if (!ret)
break;
else
FTS_DEBUG("TP not ready, read:0x%02x%02x",
chip_id[0], chip_id[1]);
}
cnt++;
msleep(INTERVAL_READ_REG);
} while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG);
if ((cnt * INTERVAL_READ_REG) >= TIMEOUT_READ_REG) {
FTS_INFO("fw is invalid, need read boot id");
if (ts_data->ic_info.hid_supported) {
fts_hid2std();
}
ret = fts_read_bootid(ts_data, &chip_id[0]);
if (ret < 0) {
FTS_ERROR("read boot id fail");
return ret;
}
ret = fts_get_chip_types(ts_data, chip_id[0], chip_id[1], INVALID);
if (ret < 0) {
FTS_ERROR("can't get ic informaton");
return ret;
}
}
FTS_INFO("get ic information, chip id = 0x%02x%02x(cid type=0x%x)",
ts_data->ic_info.ids.chip_idh, ts_data->ic_info.ids.chip_idl,
ts_data->ic_info.cid.type);
return 0;
}
/*****************************************************************************
* Reprot related
*****************************************************************************/
static void fts_show_touch_buffer(u8 *data, int datalen)
{
int i = 0;
int count = 0;
char *tmpbuf = NULL;
tmpbuf = kzalloc(1024, GFP_KERNEL);
if (!tmpbuf) {
FTS_ERROR("tmpbuf zalloc fail");
return;
}
for (i = 0; i < datalen; i++) {
count += scnprintf(tmpbuf + count, 1024 - count, "%02X,", data[i]);
if (count >= 1024)
break;
}
FTS_DEBUG("point buffer:%s", tmpbuf);
if (tmpbuf) {
kfree(tmpbuf);
tmpbuf = NULL;
}
}
void fts_release_all_finger(void)
{
struct fts_ts_data *ts_data = fts_data;
struct input_dev *input_dev = ts_data->input_dev;
#if FTS_MT_PROTOCOL_B_EN
u32 finger_count = 0;
u32 max_touches = ts_data->pdata->max_touch_number;
#endif
mutex_lock(&ts_data->report_mutex);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
for (finger_count = 0; finger_count < max_touches; finger_count++) {
ts_data->offload.coords[finger_count].status = COORD_STATUS_INACTIVE;
ts_data->offload.coords[finger_count].major = 0;
ts_data->offload.coords[finger_count].minor = 0;
ts_data->offload.coords[finger_count].pressure = 0;
ts_data->offload.coords[finger_count].rotation = 0;
}
if (ts_data->touch_offload_active_coords && ts_data->offload.offload_running) {
fts_offload_push_coord_frame(ts_data);
} else {
#endif
#if FTS_MT_PROTOCOL_B_EN
for (finger_count = 0; finger_count < max_touches; finger_count++) {
input_mt_slot(input_dev, finger_count);
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false);
}
#else
input_mt_sync(input_dev);
#endif
input_report_key(input_dev, BTN_TOUCH, 0);
input_sync(input_dev);
#if FTS_PEN_EN
input_report_key(ts_data->pen_dev, BTN_TOOL_PEN, 0);
input_report_key(ts_data->pen_dev, BTN_TOUCH, 0);
input_sync(ts_data->pen_dev);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
}
#endif
ts_data->touchs = 0;
ts_data->key_state = 0;
mutex_unlock(&ts_data->report_mutex);
}
/*****************************************************************************
* Name: fts_input_report_key
* Brief: process key events,need report key-event if key enable.
* if point's coordinate is in (x_dim-50,y_dim-50) ~ (x_dim+50,y_dim+50),
* need report it to key event.
* x_dim: parse from dts, means key x_coordinate, dimension:+-50
* y_dim: parse from dts, means key y_coordinate, dimension:+-50
* Input:
* Output:
* Return: return 0 if it's key event, otherwise return error code
*****************************************************************************/
static int fts_input_report_key(struct fts_ts_data *data, int index)
{
int i = 0;
int x = data->events[index].x;
int y = data->events[index].y;
int *x_dim = &data->pdata->key_x_coords[0];
int *y_dim = &data->pdata->key_y_coords[0];
if (!data->pdata->have_key) {
return -EINVAL;
}
for (i = 0; i < data->pdata->key_number; i++) {
if ((x >= x_dim[i] - FTS_KEY_DIM) && (x <= x_dim[i] + FTS_KEY_DIM) &&
(y >= y_dim[i] - FTS_KEY_DIM) && (y <= y_dim[i] + FTS_KEY_DIM)) {
if (EVENT_DOWN(data->events[index].flag)
&& !(data->key_state & (1 << i))) {
input_report_key(data->input_dev, data->pdata->keys[i], 1);
data->key_state |= (1 << i);
FTS_DEBUG("Key%d(%d,%d) DOWN!", i, x, y);
} else if (EVENT_UP(data->events[index].flag)
&& (data->key_state & (1 << i))) {
input_report_key(data->input_dev, data->pdata->keys[i], 0);
data->key_state &= ~(1 << i);
FTS_DEBUG("Key%d(%d,%d) Up!", i, x, y);
}
return 0;
}
}
return -EINVAL;
}
#if FTS_MT_PROTOCOL_B_EN
static int fts_input_report_b(struct fts_ts_data *data)
{
int i = 0;
int touchs = 0;
bool va_reported = false;
u32 max_touch_num = data->pdata->max_touch_number;
struct ts_event *events = data->events;
for (i = 0; i < data->touch_point; i++) {
if (fts_input_report_key(data, i) == 0) {
continue;
}
va_reported = true;
if (EVENT_DOWN(events[i].flag)) {
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
data->offload.coords[events[i].id].status = COORD_STATUS_FINGER;
data->offload.coords[events[i].id].x = events[i].x;
data->offload.coords[events[i].id].y = events[i].y;
data->offload.coords[events[i].id].pressure = events[i].p;
data->offload.coords[events[i].id].major = events[i].major;
data->offload.coords[events[i].id].minor = events[i].minor;
/* Rotation is not supported by firmware */
data->offload.coords[events[i].id].rotation = 0;
if (!data->offload.offload_running) {
#endif
input_mt_slot(data->input_dev, events[i].id);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true);
#if FTS_REPORT_PRESSURE_EN
if (events[i].p <= 0) {
events[i].p = 0x00;
}
input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
#endif
if (events[i].area <= 0) {
events[i].area = 0x00;
}
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
}
#endif
touchs |= BIT(events[i].id);
data->touchs |= BIT(events[i].id);
if ((data->log_level >= 2) ||
((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
FTS_DEBUG("[B]P%d(%d, %d)[ma:%d,mi:%d,p:%d] DOWN!",
events[i].id,
events[i].x,
events[i].y,
events[i].major,
events[i].minor,
events[i].p);
}
} else { //EVENT_UP
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
data->offload.coords[events[i].id].status = COORD_STATUS_INACTIVE;
if (!data->offload.offload_running) {
#endif
input_mt_slot(data->input_dev, events[i].id);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
}
#endif
data->touchs &= ~BIT(events[i].id);
if (data->log_level >= 1) {
FTS_DEBUG("[B1]P%d UP!", events[i].id);
}
}
}
if (unlikely(data->touchs ^ touchs)) {
for (i = 0; i < max_touch_num; i++) {
if (BIT(i) & (data->touchs ^ touchs)) {
if (data->log_level >= 1) {
FTS_DEBUG("[B2]P%d UP!", i);
}
va_reported = true;
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
data->offload.coords[i].status = COORD_STATUS_INACTIVE;
if (!data->offload.offload_running) {
#endif
input_mt_slot(data->input_dev, i);
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, false);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
}
#endif
}
}
}
data->touchs = touchs;
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
if (!data->offload.offload_running) {
#endif
if (va_reported) {
/* touchs==0, there's no point but key */
if (EVENT_NO_DOWN(data) || (!touchs)) {
if (data->log_level >= 1) {
FTS_DEBUG("[B]Points All Up!");
}
input_report_key(data->input_dev, BTN_TOUCH, 0);
} else {
input_report_key(data->input_dev, BTN_TOUCH, 1);
}
}
input_set_timestamp(data->input_dev, data->coords_timestamp);
input_sync(data->input_dev);
fts_update_motion_filter(data, data->point_num);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
}
#endif
return 0;
}
#else
static int fts_input_report_a(struct fts_ts_data *data)
{
int i = 0;
int touchs = 0;
bool va_reported = false;
struct ts_event *events = data->events;
for (i = 0; i < data->touch_point; i++) {
if (fts_input_report_key(data, i) == 0) {
continue;
}
va_reported = true;
if (EVENT_DOWN(events[i].flag)) {
input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, events[i].id);
#if FTS_REPORT_PRESSURE_EN
if (events[i].p <= 0) {
events[i].p = 0x00;
}
input_report_abs(data->input_dev, ABS_MT_PRESSURE, events[i].p);
#endif
if (events[i].area <= 0) {
events[i].area = 0x00;
}
input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, events[i].major);
input_report_abs(data->input_dev, ABS_MT_TOUCH_MINOR, events[i].minor);
input_report_abs(data->input_dev, ABS_MT_POSITION_X, events[i].x);
input_report_abs(data->input_dev, ABS_MT_POSITION_Y, events[i].y);
input_mt_sync(data->input_dev);
if ((data->log_level >= 2) ||
((1 == data->log_level) && (FTS_TOUCH_DOWN == events[i].flag))) {
FTS_DEBUG("[A]P%d(%d, %d)[ma:%d,mi:%d,p:%d] DOWN!",
events[i].id,
events[i].x,
events[i].y,
events[i].major,
events[i].minor,
events[i].p);
}
touchs++;
}
}
/* last point down, current no point but key */
if (data->touchs && !touchs) {
va_reported = true;
}
data->touchs = touchs;
if (va_reported) {
if (EVENT_NO_DOWN(data)) {
if (data->log_level >= 1) {
FTS_DEBUG("[A]Points All Up!");
}
input_report_key(data->input_dev, BTN_TOUCH, 0);
input_mt_sync(data->input_dev);
} else {
input_report_key(data->input_dev, BTN_TOUCH, 1);
}
}
input_set_timestamp(data->input_dev, data->timestamp);
input_sync(data->input_dev);
return 0;
}
#endif
#if FTS_PEN_EN
static int fts_input_pen_report(struct fts_ts_data *data)
{
struct input_dev *pen_dev = data->pen_dev;
struct pen_event *pevt = &data->pevent;
u8 *buf = data->point_buf;
if (buf[3] & 0x08)
input_report_key(pen_dev, BTN_STYLUS, 1);
else
input_report_key(pen_dev, BTN_STYLUS, 0);
if (buf[3] & 0x02)
input_report_key(pen_dev, BTN_STYLUS2, 1);
else
input_report_key(pen_dev, BTN_STYLUS2, 0);
pevt->inrange = (buf[3] & 0x20) ? 1 : 0;
pevt->tip = (buf[3] & 0x01) ? 1 : 0;
pevt->x = ((buf[4] & 0x0F) << 8) + buf[5];
pevt->y = ((buf[6] & 0x0F) << 8) + buf[7];
pevt->p = ((buf[8] & 0x0F) << 8) + buf[9];
pevt->id = buf[6] >> 4;
pevt->flag = buf[4] >> 6;
pevt->tilt_x = (buf[10] << 8) + buf[11];
pevt->tilt_y = (buf[12] << 8) + buf[13];
pevt->tool_type = BTN_TOOL_PEN;
if (data->log_level >= 2 ||
((1 == data->log_level) && (FTS_TOUCH_DOWN == pevt->flag))) {
FTS_DEBUG("[PEN]x:%d,y:%d,p:%d,inrange:%d,tip:%d,flag:%d DOWN!",
pevt->x, pevt->y, pevt->p, pevt->inrange,
pevt->tip, pevt->flag);
}
if ( (data->log_level >= 1) && (!pevt->inrange)) {
FTS_DEBUG("[PEN]UP!");
}
input_report_abs(pen_dev, ABS_X, pevt->x);
input_report_abs(pen_dev, ABS_Y, pevt->y);
input_report_abs(pen_dev, ABS_PRESSURE, pevt->p);
/* check if the pen support tilt event */
if ((pevt->tilt_x != 0) || (pevt->tilt_y != 0)) {
input_report_abs(pen_dev, ABS_TILT_X, pevt->tilt_x);
input_report_abs(pen_dev, ABS_TILT_Y, pevt->tilt_y);
}
input_report_key(pen_dev, BTN_TOUCH, pevt->tip);
input_report_key(pen_dev, BTN_TOOL_PEN, pevt->inrange);
input_sync(pen_dev);
return 0;
}
#endif
static int fts_read_touchdata(struct fts_ts_data *data)
{
int ret = 0;
u8 *buf = data->point_buf;
u8 cmd[1] = { 0 };
#if IS_ENABLED(GOOGLE_REPORT_MODE)
u8 regB2_data[FTS_CUSTOMER_STATUS_LEN] = { 0 };
u8 check_regB2_status[2] = { 0 };
int i;
if (data->work_mode == FTS_REG_WORKMODE_WORK_VALUE) {
/* If fw_heatmap_mode is enableed compressed heatmap, to read register
* 0xB2 before fts_get_heatamp() to get the length of compressed
* heatmap first.
*/
if (data->fw_heatmap_mode == FW_HEATMAP_MODE_COMPRESSED) {
cmd[0] = FTS_REG_CUSTOMER_STATUS;
fts_read(cmd, 1, regB2_data, FTS_CUSTOMER_STATUS_LEN);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
data->compress_heatmap_wlen = (regB2_data[2] << 8) + regB2_data[3];
#endif
}
}
#endif
cmd[0] = FTS_CMD_READ_TOUCH_DATA;
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
ret = fts_get_heatmap(data);
if (ret < 0)
return ret;
memcpy(buf + 1, data->heatmap_raw, data->pnt_buf_size - 1);
#else
ret = fts_read(cmd, 1, buf + 1, data->pnt_buf_size - 1);
if (ret < 0) {
FTS_ERROR("touch data(%x) abnormal,ret:%d", buf[1], ret);
return -EIO;
}
#endif
if (data->gesture_mode) {
ret = fts_gesture_readdata(data, true);
if (ret == 0) {
FTS_INFO("succuss to get gesture data in irq handler");
return 1;
}
}
#if IS_ENABLED(GOOGLE_REPORT_MODE)
if (data->work_mode == FTS_REG_WORKMODE_WORK_VALUE) {
/* If fw_heatmap_mode is disabled heatmap or enableed uncompressed
* heatmap, to read register 0xB2 after fts_get_heatamp().
*/
if (data->fw_heatmap_mode != FW_HEATMAP_MODE_COMPRESSED) {
cmd[0] = FTS_REG_CUSTOMER_STATUS;
fts_read(cmd, 1, regB2_data, FTS_CUSTOMER_STATUS_LEN);
}
check_regB2_status[0] = regB2_data[0] ^ data->current_host_status[0] ;
if (check_regB2_status[0]) { // current_status is different with previous_status
for (i = STATUS_BASELINE_REFRESH_B1; i < STATUS_CNT_END; i++) {
if ((i == STATUS_BASELINE_REFRESH_B1) && (check_regB2_status[0] & 0x03)) {
FTS_INFO("-------%s\n",
status_baseline_refresh_str[regB2_data[0] & 0x03]);
} else {
bool status_changed = check_regB2_status[0] & (1 << i);
bool new_status = regB2_data[0] & (1 << i);
if (status_changed) {
FTS_INFO("-------%s %s\n", status_list_str[i],
new_status ? "enter" : "exit");
if (i == STATUS_RESET && new_status) {
/* Write 0x01 to register(0xEC) to clear the reset
* flag in bit 7 of register(0xB2).
*/
fts_write_reg(FTS_REG_CLR_RESET, 0x01);
}
}
}
}
data->current_host_status[0] = regB2_data[0];
}
check_regB2_status[1] =
(regB2_data[1] ^ data->current_host_status[1]) & FTS_CUSTOMER_STATUS1_MASK;
if (check_regB2_status[1]) {
bool feature_changed;
bool feature_enabled;
FTS_ERROR("FW settings dose not match host side, host: 0x%x, B2[1]:0x%x\n",
data->current_host_status[1], regB2_data[1]);
for (i = FW_GLOVE; i < FW_CNT_END; i++) {
feature_changed = check_regB2_status[1] & (1 << i);
feature_enabled = regB2_data[1] & (1 << i);
if (feature_changed) {
FTS_INFO("-------%s setting %s\n", feature_list_str[i],
feature_enabled ? "enable" : "disable");
}
}
/* The status in data->current_host_status[1] are updated in
* fts_update_host_feature_setting().
*/
/* recover touch firmware state. */
fts_tp_state_recovery(data);
}
}
#endif
if (data->log_level >= 3) {
fts_show_touch_buffer(buf, data->pnt_buf_size);
}
return ret;
}
static int fts_read_parse_touchdata(struct fts_ts_data *data)
{
int ret = 0;
int i = 0;
u8 pointid = 0;
int base = 0;
struct ts_event *events = data->events;
int max_touch_num = data->pdata->max_touch_number;
u8 *buf = data->point_buf;
ret = fts_read_touchdata(data);
if (ret) {
return ret;
}
#if FTS_PEN_EN
if ((buf[2] & 0xF0) == 0xB0) {
fts_input_pen_report(data);
return 2;
}
#endif
data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F;
data->touch_point = 0;
if (data->ic_info.is_incell) {
if ((data->point_num == 0x0F) && (buf[2] == 0xFF) && (buf[3] == 0xFF)
&& (buf[4] == 0xFF) && (buf[5] == 0xFF) && (buf[6] == 0xFF)) {
FTS_DEBUG("touch buff is 0xff, need recovery state");
fts_release_all_finger();
fts_tp_state_recovery(data);
data->point_num = 0;
return -EIO;
}
}
if (data->point_num > max_touch_num) {
FTS_DEBUG("invalid point_num(%d)", data->point_num);
data->point_num = 0;
return -EIO;
}
for (i = 0; i < max_touch_num; i++) {
base = FTS_ONE_TCH_LEN * i;
pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4;
if (pointid >= FTS_MAX_ID)
break;
else if (pointid >= max_touch_num) {
FTS_ERROR("ID(%d) beyond max_touch_number", pointid);
return -EINVAL;
}
data->touch_point++;
events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) +
(buf[FTS_TOUCH_X_L_POS + base] & 0xFF);
events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) +
(buf[FTS_TOUCH_Y_L_POS + base] & 0xFF);
events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6;
events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4;
events[i].p = (((buf[FTS_TOUCH_AREA_POS + base] << 1) & 0x02) +
(buf[FTS_TOUCH_PRE_POS + base] & 0x01)) *
FTS_PRESSURE_SCALE;
events[i].minor =
((buf[FTS_TOUCH_PRE_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
events[i].major =
((buf[FTS_TOUCH_AREA_POS + base] >> 1) & 0x7F) * data->pdata->mm2px;
if (EVENT_DOWN(events[i].flag) && (data->point_num == 0)) {
FTS_INFO("abnormal touch data from fw");
return -EIO;
}
}
if (data->touch_point == 0) {
FTS_INFO("no touch point information(%02x)", buf[2]);
return -EIO;
}
return 0;
}
static void fts_irq_read_report(void)
{
int ret = 0;
struct fts_ts_data *ts_data = fts_data;
#if FTS_ESDCHECK_EN
fts_esdcheck_set_intr(1);
#endif
#if FTS_POINT_REPORT_CHECK_EN
fts_prc_queue_work(ts_data);
#endif
ret = fts_read_parse_touchdata(ts_data);
if (ret == 0) {
mutex_lock(&ts_data->report_mutex);
#if FTS_MT_PROTOCOL_B_EN
fts_input_report_b(ts_data);
#else
fts_input_report_a(ts_data);
#endif
mutex_unlock(&ts_data->report_mutex);
}
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
ret = touch_offload_reserve_frame(&ts_data->offload,
&ts_data->reserved_frame);
if (ret != 0) {
PR_LOGD("Could not reserve a frame: error=%d.\n", ret);
/* Stop offload when there are no buffers available. */
fts_offload_set_running(ts_data, false);
} else {
fts_offload_set_running(ts_data, true);
PR_LOGD("reserve a frame ok");
fts_populate_frame(ts_data, 0xFFFFFFFF);
ret = touch_offload_queue_frame(&ts_data->offload,
ts_data->reserved_frame);
if (ret != 0) {
FTS_ERROR("Failed to queue reserved frame: error=%d.\n", ret);
}
}
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
heatmap_read(&ts_data->v4l2, ktime_to_ns(ts_data->coords_timestamp));
#endif
#if FTS_ESDCHECK_EN
fts_esdcheck_set_intr(0);
#endif
}
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
static void fts_ts_aggregate_bus_state(struct fts_ts_data *ts)
{
/* Complete or cancel any outstanding transitions */
cancel_work_sync(&ts->suspend_work);
cancel_work_sync(&ts->resume_work);
if ((ts->bus_refmask == 0 &&
ts->power_status == FTS_TS_STATE_SUSPEND) ||
(ts->bus_refmask != 0 &&
ts->power_status != FTS_TS_STATE_SUSPEND))
return;
if (ts->bus_refmask == 0)
queue_work(ts->ts_workqueue, &ts->suspend_work);
else
queue_work(ts->ts_workqueue, &ts->resume_work);
}
int fts_ts_set_bus_ref(struct fts_ts_data *ts, u16 ref, bool enable)
{
int result = 0;
mutex_lock(&ts->bus_mutex);
if ((enable && (ts->bus_refmask & ref)) ||
(!enable && !(ts->bus_refmask & ref))) {
mutex_unlock(&ts->bus_mutex);
return -EINVAL;
}
if (enable) {
/* IRQs can only keep the bus active. IRQs received while the
* bus is transferred to AOC should be ignored.
*/
if (ref == FTS_TS_BUS_REF_IRQ && ts->bus_refmask == 0)
result = -EAGAIN;
else
ts->bus_refmask |= ref;
} else
ts->bus_refmask &= ~ref;
fts_ts_aggregate_bus_state(ts);
mutex_unlock(&ts->bus_mutex);
/* When triggering a wake, wait up to one second to resume. SCREEN_ON
* and IRQ references do not need to wait.
*/
if (enable &&
ref != FTS_TS_BUS_REF_SCREEN_ON && ref != FTS_TS_BUS_REF_IRQ) {
wait_for_completion_timeout(&ts->bus_resumed, HZ);
if (ts->power_status != FTS_TS_STATE_POWER_ON) {
FTS_ERROR("Failed to wake the touch bus.\n");
result = -ETIMEDOUT;
}
}
return result;
}
struct drm_connector *get_bridge_connector(struct drm_bridge *bridge)
{
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;
drm_connector_list_iter_begin(bridge->dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->encoder == bridge->encoder)
break;
}
drm_connector_list_iter_end(&conn_iter);
return connector;
}
static bool bridge_is_lp_mode(struct drm_connector *connector)
{
if (connector && connector->state) {
struct exynos_drm_connector_state *s =
to_exynos_connector_state(connector->state);
return s->exynos_mode.is_lp_mode;
}
return false;
}
static void panel_bridge_enable(struct drm_bridge *bridge)
{
struct fts_ts_data *ts =
container_of(bridge, struct fts_ts_data, panel_bridge);
if (!ts->is_panel_lp_mode)
fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, true);
}
static void panel_bridge_disable(struct drm_bridge *bridge)
{
struct fts_ts_data *ts =
container_of(bridge, struct fts_ts_data, panel_bridge);
if (bridge->encoder && bridge->encoder->crtc) {
const struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state;
if (drm_atomic_crtc_effectively_active(crtc_state))
return;
}
fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, false);
}
static void panel_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct fts_ts_data *ts =
container_of(bridge, struct fts_ts_data, panel_bridge);
if (!ts->connector || !ts->connector->state)
ts->connector = get_bridge_connector(bridge);
ts->is_panel_lp_mode = bridge_is_lp_mode(ts->connector);
fts_ts_set_bus_ref(ts, FTS_TS_BUS_REF_SCREEN_ON, !ts->is_panel_lp_mode);
if (adjusted_mode) {
int vrefresh = drm_mode_vrefresh(adjusted_mode);
if (ts->display_refresh_rate != vrefresh) {
FTS_INFO("refresh rate(Hz) changed to %d from %d\n",
vrefresh, ts->display_refresh_rate);
ts->display_refresh_rate = vrefresh;
}
}
}
static const struct drm_bridge_funcs panel_bridge_funcs = {
.enable = panel_bridge_enable,
.disable = panel_bridge_disable,
.mode_set = panel_bridge_mode_set,
};
static int register_panel_bridge(struct fts_ts_data *ts)
{
FTS_FUNC_ENTER();
#ifdef CONFIG_OF
ts->panel_bridge.of_node = ts->spi->dev.of_node;
#endif
ts->panel_bridge.funcs = &panel_bridge_funcs;
drm_bridge_add(&ts->panel_bridge);
FTS_FUNC_EXIT();
return 0;
}
static void unregister_panel_bridge(struct drm_bridge *bridge)
{
struct drm_bridge *node;
FTS_FUNC_ENTER();
drm_bridge_remove(bridge);
if (!bridge->dev) /* not attached */
return;
drm_modeset_lock(&bridge->dev->mode_config.connection_mutex, NULL);
list_for_each_entry(node, &bridge->encoder->bridge_chain, chain_node)
if (node == bridge) {
if (bridge->funcs->detach)
bridge->funcs->detach(bridge);
list_del(&bridge->chain_node);
break;
}
drm_modeset_unlock(&bridge->dev->mode_config.connection_mutex);
bridge->dev = NULL;
FTS_FUNC_EXIT();
}
#endif
/* Update a state machine used to toggle control of the touch IC's motion
* filter.
*/
static void fts_update_motion_filter(struct fts_ts_data *ts, u8 touches)
{
/* Motion filter timeout, in milliseconds */
const u32 mf_timeout_ms = 500;
u8 next_state;
next_state = ts->mf_state;
if (ts->mf_mode == MF_OFF) {
next_state = MF_UNFILTERED;
} else if (ts->mf_mode == MF_DYNAMIC) {
/* Determine the next filter state. The motion filter is enabled by
* default and it is disabled while a single finger is touching the
* screen. If another finger is touched down or if a timeout expires,
* the motion filter is reenabled and remains enabled until all fingers
* are lifted.
*/
next_state = ts->mf_state;
switch (ts->mf_state) {
case MF_FILTERED:
if (touches == 1) {
next_state = MF_UNFILTERED;
ts->mf_downtime = ktime_get();
}
break;
case MF_UNFILTERED:
if (touches == 0) {
next_state = MF_FILTERED;
} else if (touches > 1 || ktime_after(ktime_get(),
ktime_add_ms(ts->mf_downtime, mf_timeout_ms))) {
next_state = MF_FILTERED_LOCKED;
}
break;
case MF_FILTERED_LOCKED:
if (touches == 0)
next_state = MF_FILTERED;
break;
}
} else if (ts->mf_mode == MF_ON) {
next_state = MF_FILTERED;
} else {
/* Set MF_DYNAMIC as default when an invalid value is found. */
ts->mf_mode = MF_DYNAMIC;
return;
}
/* Send command to update firmware continuous report */
if ((next_state == MF_UNFILTERED) !=
(ts->mf_state == MF_UNFILTERED)) {
bool en = (next_state == MF_UNFILTERED) ? true : false;
fts_set_continuous_mode(ts, en);
}
ts->mf_state = next_state;
}
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
static void fts_show_heatmap_data(struct fts_ts_data *ts_data) {
int i;
int idx_buff;
u8 tx = ts_data->pdata->tx_ch_num;
u8 rx = ts_data->pdata->rx_ch_num;
FTS_DEBUG("Show mutual data:\n");
idx_buff = 0;
for (i = 0; i < rx; i++) {
FTS_DEBUG("RX(%d):%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
idx_buff,
(s16)ts_data->heatmap_buff[idx_buff],
(s16)ts_data->heatmap_buff[idx_buff + 1],
(s16)ts_data->heatmap_buff[idx_buff + 2],
(s16)ts_data->heatmap_buff[idx_buff + 3],
(s16)ts_data->heatmap_buff[idx_buff + 4],
(s16)ts_data->heatmap_buff[idx_buff + 5],
(s16)ts_data->heatmap_buff[idx_buff + 6],
(s16)ts_data->heatmap_buff[idx_buff + 7],
(s16)ts_data->heatmap_buff[idx_buff + 8],
(s16)ts_data->heatmap_buff[idx_buff + 9],
(s16)ts_data->heatmap_buff[idx_buff + 10],
(s16)ts_data->heatmap_buff[idx_buff + 11],
(s16)ts_data->heatmap_buff[idx_buff + 12],
(s16)ts_data->heatmap_buff[idx_buff + 13],
(s16)ts_data->heatmap_buff[idx_buff + 14],
(s16)ts_data->heatmap_buff[idx_buff + 15]);
idx_buff += tx;
}
FTS_DEBUG("Show Tx self data:\n");
FTS_DEBUG("Tx(idx_buff=%d):%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
idx_buff,
(s16)ts_data->heatmap_buff[idx_buff],
(s16)ts_data->heatmap_buff[idx_buff + 1],
(s16)ts_data->heatmap_buff[idx_buff + 2],
(s16)ts_data->heatmap_buff[idx_buff + 3],
(s16)ts_data->heatmap_buff[idx_buff + 4],
(s16)ts_data->heatmap_buff[idx_buff + 5],
(s16)ts_data->heatmap_buff[idx_buff + 6],
(s16)ts_data->heatmap_buff[idx_buff + 7],
(s16)ts_data->heatmap_buff[idx_buff + 8],
(s16)ts_data->heatmap_buff[idx_buff + 9],
(s16)ts_data->heatmap_buff[idx_buff + 10],
(s16)ts_data->heatmap_buff[idx_buff + 11],
(s16)ts_data->heatmap_buff[idx_buff + 12],
(s16)ts_data->heatmap_buff[idx_buff + 13],
(s16)ts_data->heatmap_buff[idx_buff + 14],
(s16)ts_data->heatmap_buff[idx_buff + 15]);
idx_buff += 16;
FTS_DEBUG("Show Rx self data:\n");
FTS_DEBUG("Rx(idx_buff=%d)%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
idx_buff,
(short)ts_data->heatmap_buff[idx_buff],
(short)ts_data->heatmap_buff[idx_buff + 1],
(short)ts_data->heatmap_buff[idx_buff + 2],
(short)ts_data->heatmap_buff[idx_buff + 3],
(short)ts_data->heatmap_buff[idx_buff + 4],
(short)ts_data->heatmap_buff[idx_buff + 5],
(short)ts_data->heatmap_buff[idx_buff + 6],
(short)ts_data->heatmap_buff[idx_buff + 7],
(short)ts_data->heatmap_buff[idx_buff + 8],
(short)ts_data->heatmap_buff[idx_buff + 9],
(short)ts_data->heatmap_buff[idx_buff + 10],
(short)ts_data->heatmap_buff[idx_buff + 11],
(short)ts_data->heatmap_buff[idx_buff + 12],
(short)ts_data->heatmap_buff[idx_buff + 13],
(short)ts_data->heatmap_buff[idx_buff + 14],
(short)ts_data->heatmap_buff[idx_buff + 15]);
idx_buff += 16;
FTS_DEBUG("Rx(idx_buff=%d)%5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d",
idx_buff,
(short)ts_data->heatmap_buff[idx_buff],
(short)ts_data->heatmap_buff[idx_buff + 1],
(short)ts_data->heatmap_buff[idx_buff + 2],
(short)ts_data->heatmap_buff[idx_buff + 3],
(short)ts_data->heatmap_buff[idx_buff + 4],
(short)ts_data->heatmap_buff[idx_buff + 5],
(short)ts_data->heatmap_buff[idx_buff + 6],
(short)ts_data->heatmap_buff[idx_buff + 7],
(short)ts_data->heatmap_buff[idx_buff + 8],
(short)ts_data->heatmap_buff[idx_buff + 9],
(short)ts_data->heatmap_buff[idx_buff + 10],
(short)ts_data->heatmap_buff[idx_buff + 11],
(short)ts_data->heatmap_buff[idx_buff + 12],
(short)ts_data->heatmap_buff[idx_buff + 13],
(short)ts_data->heatmap_buff[idx_buff + 14],
(short)ts_data->heatmap_buff[idx_buff + 15]);
idx_buff += 16;
FTS_DEBUG("Rx(idx_buff=%d)%5d %5d", idx_buff,
(short)ts_data->heatmap_buff[idx_buff],
(short)ts_data->heatmap_buff[idx_buff + 1]);
idx_buff += 2;
FTS_DEBUG("Print done, idx_buff=%d", idx_buff);
}
#endif /* GOOGLE_HEATMAP_DEBUG */
static int fts_ptflib_decoder(struct fts_ts_data *ts_data, const u16 *in_array,
const int in_array_size, u16 *out_array, const int out_array_max_size)
{
const u16 ESCAPE_MASK = 0xF000;
const u16 ESCAPE_BIT = 0x8000;
int i;
int j;
int out_array_size = 0;
u16 prev_word = 0;
u16 repetition = 0;
u16 *temp_out_array = out_array;
for (i = 0; i < in_array_size; i++) {
/* The data form firmware is big-endian, and needs to transfer it to
* little-endian.
*/
u16 curr_word = (u16)(*((u8*)&in_array[i]) << 8) +
*((u8*)&in_array[i] + 1);
if ((curr_word & ESCAPE_MASK) == ESCAPE_BIT) {
repetition = (curr_word & ~ESCAPE_MASK);
if (out_array_size + repetition > out_array_max_size)
break;
for (j = 0; j < repetition; j++) {
*temp_out_array++ = prev_word;
out_array_size++;
}
} else {
if (out_array_size >= out_array_max_size)
break;
*temp_out_array++ = curr_word;
out_array_size++;
prev_word = curr_word;
}
}
if (i != in_array_size || out_array_size != out_array_max_size) {
FTS_ERROR("%d (in=%d, out=%d, rep=%d, out_max=%d).\n",
i, in_array_size, out_array_size,
repetition, out_array_max_size);
memset(out_array, 0, out_array_max_size * sizeof(u16));
return -1;
}
return out_array_size;
}
extern void transpose_raw(u8 *src, u8 *dist, int tx, int rx, bool big_endian);
static int fts_get_heatmap(struct fts_ts_data *ts_data) {
int ret = 0;
int i;
int idx_buff = 0;
int node_num = 0;
int self_node = 0;
int mutual_data_size = 0;
int self_data_size = 0;
int total_data_size = 0;
u8 cmd[1] = {0};
u8 tx = ts_data->pdata->tx_ch_num;
u8 rx = ts_data->pdata->rx_ch_num;
int idx_ms_raw = 0;
int idx_ss_tx_raw = 0;
int idx_ss_rx_raw = 0;
int idx_water_ss_tx_raw = 0;
int idx_water_ss_rx_raw = 0;
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_FUNC_ENTER();
#endif
node_num = tx * rx;
self_node = tx + rx;
/* The mutual sensing raw data size : 16*34*2=1088 */
mutual_data_size = node_num * sizeof(u16);
/* The self sensing raw data size : 68*2=136 */
self_data_size = FTS_SELF_DATA_LEN * sizeof(u16);
/* The index of mutual sensing data : 91+(68*2)*2=363 */
idx_ms_raw = FTS_CAP_DATA_LEN + self_data_size * 2;
/* The tx index of water self sensing data : 91+34*2=159 */
idx_water_ss_tx_raw = FTS_CAP_DATA_LEN + rx * sizeof(u16);
/* The rx index of water self sensing data : 91 */
idx_water_ss_rx_raw = FTS_CAP_DATA_LEN;
/* The tx index of normal self sensing data : 91+68*2+34*2=295 */
idx_ss_tx_raw = FTS_CAP_DATA_LEN + self_data_size + rx * sizeof(u16);
/* The rx index of normal self sensing data : 91+68*2=227 */
idx_ss_rx_raw = FTS_CAP_DATA_LEN + self_data_size;
if (!ts_data->heatmap_buff) {
FTS_ERROR("The heatmap_buff is not allocated!!");
ret = -ENOMEM;
goto exit;
}
if (!ts_data->fw_heatmap_mode) {
FTS_ERROR("The firmware heatmap is not enabled!!");
ret = -EINVAL;
goto exit;
}
cmd[0] = FTS_CMD_READ_TOUCH_DATA;
if (ts_data->fw_heatmap_mode == FW_HEATMAP_MODE_UNCOMPRESSED) {
/* The format of uncompressed heatmap from touch chip.
*
* |- cap header (91) -|- Water-SS -|- Normal-SS -|- Normal-MS -|
* |- 91 -|- 68*2 -|- 68*2 -|- 16*34*2 -|
*/
/* Total touch data: (cap header(91) + heatmap(N-MS + W-SS + N-SS)). */
total_data_size = FTS_CAP_DATA_LEN + self_data_size * 2 +
mutual_data_size;
if (total_data_size > ts_data->heatmap_raw_size) {
FTS_DEBUG("Warning : The total touch data size is %d!!",
total_data_size);
total_data_size = ts_data->heatmap_raw_size;
}
ret = fts_read(cmd, 1, ts_data->heatmap_raw, total_data_size);
if (ret < 0) {
FTS_ERROR("Failed to get heatmap raw data, ret=%d.", ret);
ret = -EIO;
goto exit;
}
/* Get the self-sensing type. */
ts_data->self_sensing_type =
ts_data->heatmap_raw[FTS_CAP_DATA_LEN - 1] & 0x80;
/*
* transform the order of MS from RX->TX, the output data is keep
* big-endian.
*/
transpose_raw(ts_data->heatmap_raw + idx_ms_raw, ts_data->trans_raw,
tx, rx, true);
} else {
/* The format of compressed heatmap from touch chip.
*
* |- cap header -|- Water-SS -|- Normal-SS -|- compressed heatmap(MS)-|
* |- 91 -|- 68*2 -|- 68*2 -|- (B2[1]<<8+B2[2])*2 -|
*/
if (ts_data->compress_heatmap_wlen < 0 ||
(ts_data->compress_heatmap_wlen * sizeof(u16)) > mutual_data_size) {
FTS_DEBUG("Warning : The compressed heatmap size is %d!!",
ts_data->compress_heatmap_wlen);
ts_data->compress_heatmap_wlen = 0;
memset(ts_data->trans_raw, 0, ts_data->trans_raw_size);
}
/* Total touch data:(cap header + W-SS + N-SS + compressed heatmap(N-MS)
*/
total_data_size = FTS_CAP_DATA_LEN +
self_data_size * 2 +
ts_data->compress_heatmap_wlen * sizeof(u16);
if (total_data_size > ts_data->heatmap_raw_size) {
FTS_DEBUG("Warning : The total touch data size is %d!!",
total_data_size);
total_data_size = ts_data->heatmap_raw_size;
}
ret = fts_read(cmd, 1, ts_data->heatmap_raw, total_data_size);
if (ret < 0) {
FTS_ERROR("Failed to get compressed heatmap raw data,ret=%d.", ret);
ret = -EIO;
goto exit;
}
/* Get the self-sensing type. */
ts_data->self_sensing_type =
ts_data->heatmap_raw[FTS_CAP_DATA_LEN - 1] & 0x80;
if (ts_data->compress_heatmap_wlen > 0) {
/* decode the compressed data from heatmap_raw to heatmap_buff. */
fts_ptflib_decoder(ts_data,
(u16*)(&ts_data->heatmap_raw[idx_ms_raw]),
ts_data->compress_heatmap_wlen,
ts_data->heatmap_buff,
mutual_data_size / sizeof(u16));
/* MS: Transform the order from RX->TX. */
/* After decoding, the data become to little-endian, but the output of
* transpose_raw is big-endian.
*/
transpose_raw(&((u8*)ts_data->heatmap_buff)[0], ts_data->trans_raw,
tx, rx, false);
}
}
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("Copy matual data,idx_buff=%d,idx_ms_raw=%d.",
idx_buff, idx_ms_raw);
#endif
/* copy mutual sensing data. */
for (i = 0; i < node_num; i++) {
((u16*)ts_data->heatmap_buff)[idx_buff++] =
(u16)(ts_data->trans_raw[(i * 2)] << 8) +
ts_data->trans_raw[(i * 2) + 1];
}
/* copy tx of Normal-SS. */
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("Copy the tx self data,idx_buff=%d,idx_ss_tx_raw=%d.",
idx_buff, idx_ss_tx_raw);
#endif
for (i = 0 ; i < tx; i++) {
((u16*)ts_data->heatmap_buff)[idx_buff++] =
(u16)(ts_data->heatmap_raw[idx_ss_tx_raw + (i * 2)] << 8) +
ts_data->heatmap_raw[idx_ss_tx_raw +(i * 2) + 1];
}
/* copy rx of Normal-SS. */
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("Copy the rx self data,idx_buff=%d,idx_ss_rx_raw=%d.",
idx_buff, idx_ss_rx_raw);
#endif
for (i = 0 ; i < rx; i++) {
((u16*)ts_data->heatmap_buff)[idx_buff++] =
(u16)(ts_data->heatmap_raw[idx_ss_rx_raw + (i * 2)] << 8) +
ts_data->heatmap_raw[idx_ss_rx_raw + (i * 2) + 1];
}
/* copy tx of Water-SS. */
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("Copy the tx of Water-SS,idx_buff=%d,idx_water_ss_tx_raw=%d.",
idx_buff, idx_water_ss_tx_raw);
#endif
for (i = 0 ; i < tx; i++) {
((u16*)ts_data->heatmap_buff)[idx_buff++] =
(u16)(ts_data->heatmap_raw[idx_water_ss_tx_raw + (i * 2)] << 8) +
ts_data->heatmap_raw[idx_water_ss_tx_raw +(i * 2) + 1];
}
/* copy rx of Water-SS. */
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("Copy the rx of Water-SS,idx_buff=%d,idx_water_ss_rx_raw=%d.",
idx_buff, idx_water_ss_rx_raw);
#endif
for (i = 0 ; i < rx; i++) {
((u16*)ts_data->heatmap_buff)[idx_buff++] =
(u16)(ts_data->heatmap_raw[idx_water_ss_rx_raw + (i * 2)] << 8) +
ts_data->heatmap_raw[idx_water_ss_rx_raw + (i * 2) + 1];
}
/* The format of heatmap data (U16) of heatmap_buff is:
*
* |- MS -|- Normal-SS -|- Water-SS -|
* |- 16*34 -|- 16+34 -|- 16+34 -|
*/
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_FUNC_EXIT();
#endif
exit:
return ret;
}
#endif /* CONFIG_TOUCHSCREEN_OFFLOAD || CONFIG_TOUCHSCREEN_HEATMAP */
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
static void fts_offload_set_running(struct fts_ts_data *ts_data, bool running)
{
ts_data->offload.offload_running = running;
/*
* Disable firmware grip_suppression/palm_rejection when offload is running
* and upper layer grip_suppression/palm_rejection is enabled.
*/
if (running) {
if (ts_data->enable_fw_grip < FW_GRIP_FORCE_DISABLE) {
int new_fw_grip = ts_data->offload.config.filter_grip ?
FW_GRIP_DISABLE : FW_GRIP_ENABLE;
if (ts_data->enable_fw_grip != new_fw_grip) {
ts_data->enable_fw_grip = new_fw_grip;
fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
}
}
if (ts_data->enable_fw_palm < FW_PALM_FORCE_DISABLE) {
int new_fw_palm = ts_data->offload.config.filter_palm ?
FW_PALM_DISABLE : FW_PALM_ENABLE;
if (ts_data->enable_fw_palm != new_fw_palm) {
ts_data->enable_fw_palm = new_fw_palm;
fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
}
}
} else {
if (ts_data->enable_fw_grip < FW_GRIP_FORCE_DISABLE &&
ts_data->enable_fw_grip != FW_GRIP_ENABLE) {
ts_data->enable_fw_grip = FW_GRIP_ENABLE;
fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
}
if (ts_data->enable_fw_palm < FW_PALM_FORCE_DISABLE &&
ts_data->enable_fw_palm != FW_PALM_ENABLE) {
ts_data->enable_fw_palm = FW_PALM_ENABLE;
fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
}
}
}
static void fts_offload_report(void *handle,
struct TouchOffloadIocReport *report)
{
struct fts_ts_data *ts_data = (struct fts_ts_data *)handle;
bool touch_down = 0;
int i;
int touch_count = 0;
int tool_type;
mutex_lock(&ts_data->report_mutex);
input_set_timestamp(ts_data->input_dev, report->timestamp);
for (i = 0; i < MAX_COORDS; i++) {
if (report->coords[i].status != COORD_STATUS_INACTIVE) {
input_mt_slot(ts_data->input_dev, i);
touch_count++;
touch_down = 1;
input_report_key(ts_data->input_dev, BTN_TOUCH, touch_down);
input_report_key(ts_data->input_dev, BTN_TOOL_FINGER, touch_down);
switch (report->coords[i].status) {
case COORD_STATUS_EDGE:
case COORD_STATUS_PALM:
case COORD_STATUS_CANCEL:
tool_type = MT_TOOL_PALM;
break;
case COORD_STATUS_FINGER:
default:
tool_type = MT_TOOL_FINGER;
break;
}
input_mt_report_slot_state(ts_data->input_dev, tool_type, 1);
input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X,
report->coords[i].x);
input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y,
report->coords[i].y);
input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
report->coords[i].major);
input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MINOR,
report->coords[i].minor);
input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE,
report->coords[i].pressure);
input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION,
report->coords[i].rotation);
} else {
input_mt_slot(ts_data->input_dev, i);
input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 0);
input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 0);
input_report_abs(ts_data->input_dev, ABS_MT_TRACKING_ID, -1);
input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION, 0);
}
}
input_report_key(ts_data->input_dev, BTN_TOUCH, touch_down);
input_report_key(ts_data->input_dev, BTN_TOOL_FINGER, touch_down);
input_sync(ts_data->input_dev);
fts_update_motion_filter(ts_data, touch_count);
mutex_unlock(&ts_data->report_mutex);
}
static void fts_populate_coordinate_channel(struct fts_ts_data *ts_data,
struct touch_offload_frame *frame,
int channel)
{
int i;
u8 active_coords = 0;
struct TouchOffloadDataCoord *dc =
(struct TouchOffloadDataCoord *)frame->channel_data[channel];
memset(dc, 0, frame->channel_data_size[channel]);
dc->header.channel_type = TOUCH_DATA_TYPE_COORD;
dc->header.channel_size = TOUCH_OFFLOAD_FRAME_SIZE_COORD;
for (i = 0; i < MAX_COORDS; i++) {
dc->coords[i].x = ts_data->offload.coords[i].x;
dc->coords[i].y = ts_data->offload.coords[i].y;
dc->coords[i].major = ts_data->offload.coords[i].major;
dc->coords[i].minor = ts_data->offload.coords[i].minor;
dc->coords[i].pressure = ts_data->offload.coords[i].pressure;
dc->coords[i].rotation = ts_data->offload.coords[i].rotation;
dc->coords[i].status = ts_data->offload.coords[i].status;
if (dc->coords[i].status != COORD_STATUS_INACTIVE)
active_coords += 1;
}
ts_data->touch_offload_active_coords = active_coords;
}
static void fts_populate_mutual_channel(struct fts_ts_data *ts_data,
struct touch_offload_frame *frame, int channel)
{
struct TouchOffloadData2d *mutual_strength =
(struct TouchOffloadData2d *)frame->channel_data[channel];
mutual_strength->tx_size = ts_data->pdata->tx_ch_num;
mutual_strength->rx_size = ts_data->pdata->rx_ch_num;
mutual_strength->header.channel_type = frame->channel_type[channel];
mutual_strength->header.channel_size =
TOUCH_OFFLOAD_FRAME_SIZE_2D(mutual_strength->rx_size,
mutual_strength->tx_size);
memcpy(mutual_strength->data, ts_data->heatmap_buff,
mutual_strength->tx_size * mutual_strength->rx_size * sizeof(u16));
}
static void fts_populate_self_channel(struct fts_ts_data *ts_data,
struct touch_offload_frame *frame, int channel)
{
u8 ss_type = 0;
int idx_ss_normal = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
int idx_ss_water = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num +
ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num;
int ss_size =
(ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num) * sizeof(u16);
struct TouchOffloadData1d *self_strength =
(struct TouchOffloadData1d *)frame->channel_data[channel];
self_strength->tx_size = ts_data->pdata->tx_ch_num;
self_strength->rx_size = ts_data->pdata->rx_ch_num;
self_strength->header.channel_type = frame->channel_type[channel];
self_strength->header.channel_size =
TOUCH_OFFLOAD_FRAME_SIZE_1D(self_strength->rx_size,
self_strength->tx_size);
switch (frame->channel_type[channel] & ~TOUCH_SCAN_TYPE_SELF) {
case TOUCH_DATA_TYPE_FILTERED:
ss_type = SS_WATER;
break;
case TOUCH_DATA_TYPE_STRENGTH:
default:
ss_type = SS_NORMAL;
break;
}
if (ss_type == SS_WATER) {
/* Copy Water-SS. */
memcpy(self_strength->data, ts_data->heatmap_buff + idx_ss_water,
ss_size);
} else {
/* Copy Normal-SS. */
memcpy(self_strength->data, ts_data->heatmap_buff + idx_ss_normal,
ss_size);
}
}
static void fts_populate_frame(struct fts_ts_data *ts_data, int populate_channel_types)
{
static u64 index;
int i;
struct touch_offload_frame *frame = ts_data->reserved_frame;
frame->header.index = index++;
frame->header.timestamp = ts_data->coords_timestamp;
/* Populate all channels */
for (i = 0; i < frame->num_channels; i++) {
if ((frame->channel_type[i] & populate_channel_types) == TOUCH_DATA_TYPE_COORD) {
fts_populate_coordinate_channel(ts_data, frame, i);
} else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_MUTUAL &
populate_channel_types) != 0) {
fts_populate_mutual_channel(ts_data, frame, i);
} else if ((frame->channel_type[i] & TOUCH_SCAN_TYPE_SELF &
populate_channel_types) != 0) {
fts_populate_self_channel(ts_data, frame, i);
}
}
}
static void fts_offload_push_coord_frame(struct fts_ts_data *ts)
{
int error;
FTS_INFO("active coords %u.", ts->touch_offload_active_coords);
error = touch_offload_reserve_frame(&ts->offload, &ts->reserved_frame);
if (error != 0) {
FTS_DEBUG("Could not reserve a frame: error=%d.\n", error);
} else {
fts_populate_frame(ts, TOUCH_DATA_TYPE_COORD);
error = touch_offload_queue_frame(&ts->offload, ts->reserved_frame);
if (error != 0) {
FTS_ERROR("Failed to queue reserved frame: error=%d.\n", error);
}
}
}
#endif /* CONFIG_TOUCHSCREEN_OFFLOAD */
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
static bool v4l2_read_frame(struct v4l2_heatmap *v4l2)
{
bool ret = true;
struct fts_ts_data *ts_data =
container_of(v4l2, struct fts_ts_data, v4l2);
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_FUNC_ENTER();
#endif
if (ts_data->v4l2.width == ts_data->pdata->tx_ch_num &&
ts_data->v4l2.height == ts_data->pdata->rx_ch_num) {
#if IS_ENABLED(GOOGLE_HEATMAP_DEBUG)
FTS_DEBUG("v4l2 mutual strength data is ready.");
#endif
memcpy(v4l2->frame, ts_data->heatmap_buff,
ts_data->v4l2.width * ts_data->v4l2.height * sizeof(u16));
} else {
FTS_ERROR("size mismatched, (%lu, %lu) vs (%u, %u)!\n",
ts_data->v4l2.width, ts_data->v4l2.height,
ts_data->pdata->tx_ch_num, ts_data->pdata->rx_ch_num);
ret = false;
}
return ret;
}
#endif /* CONFIG_TOUCHSCREEN_HEATMAP */
static irqreturn_t fts_irq_ts(int irq, void *data)
{
struct fts_ts_data *ts_data = data;
ts_data->isr_timestamp = ktime_get();
return IRQ_WAKE_THREAD;
}
extern int int_test_has_interrupt;
static irqreturn_t fts_irq_handler(int irq, void *data)
{
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
struct fts_ts_data *ts_data = fts_data;
if (fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_IRQ, true) < 0) {
if (!ts_data->gesture_mode) {
/* Interrupt during bus suspend */
FTS_INFO("Skipping stray interrupt since bus is suspended(power_status: %d)\n",
ts_data->power_status);
return IRQ_HANDLED;
}
}
#endif
#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
int ret = 0;
struct fts_ts_data *ts_data = fts_data;
if ((ts_data->suspended) && (ts_data->pm_suspend)) {
ret = wait_for_completion_timeout(
&ts_data->pm_completion,
msecs_to_jiffies(FTS_TIMEOUT_COMERR_PM));
if (!ret) {
FTS_ERROR("Bus don't resume from pm(deep),timeout,skip irq");
return IRQ_HANDLED;
}
}
#endif
int_test_has_interrupt++;
fts_data->coords_timestamp = fts_data->isr_timestamp;
cpu_latency_qos_update_request(&ts_data->pm_qos_req, 100 /* usec */);
fts_irq_read_report();
cpu_latency_qos_update_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_IRQ, false);
#endif
return IRQ_HANDLED;
}
static int fts_irq_registration(struct fts_ts_data *ts_data)
{
int ret = 0;
struct fts_ts_platform_data *pdata = ts_data->pdata;
ts_data->irq = gpio_to_irq(pdata->irq_gpio);
pdata->irq_gpio_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
FTS_INFO("irq:%d, flag:%x", ts_data->irq, pdata->irq_gpio_flags);
ret = request_threaded_irq(ts_data->irq, fts_irq_ts, fts_irq_handler,
pdata->irq_gpio_flags,
FTS_DRIVER_NAME, ts_data);
return ret;
}
#if FTS_PEN_EN
static int fts_input_pen_init(struct fts_ts_data *ts_data)
{
int ret = 0;
struct input_dev *pen_dev;
struct fts_ts_platform_data *pdata = ts_data->pdata;
FTS_FUNC_ENTER();
pen_dev = input_allocate_device();
if (!pen_dev) {
FTS_ERROR("Failed to allocate memory for input_pen device");
return -ENOMEM;
}
pen_dev->dev.parent = ts_data->dev;
pen_dev->name = FTS_DRIVER_PEN_NAME;
pen_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
__set_bit(ABS_X, pen_dev->absbit);
__set_bit(ABS_Y, pen_dev->absbit);
__set_bit(BTN_STYLUS, pen_dev->keybit);
__set_bit(BTN_STYLUS2, pen_dev->keybit);
__set_bit(BTN_TOUCH, pen_dev->keybit);
__set_bit(BTN_TOOL_PEN, pen_dev->keybit);
__set_bit(INPUT_PROP_DIRECT, pen_dev->propbit);
input_set_abs_params(pen_dev, ABS_X, pdata->x_min, pdata->x_max, 0, 0);
input_set_abs_params(pen_dev, ABS_Y, pdata->y_min, pdata->y_max, 0, 0);
input_set_abs_params(pen_dev, ABS_PRESSURE, 0, 4096, 0, 0);
ret = input_register_device(pen_dev);
if (ret) {
FTS_ERROR("Input device registration failed");
input_free_device(pen_dev);
pen_dev = NULL;
return ret;
}
ts_data->pen_dev = pen_dev;
FTS_FUNC_EXIT();
return 0;
}
#endif
static int fts_input_init(struct fts_ts_data *ts_data)
{
int ret = 0;
int key_num = 0;
struct fts_ts_platform_data *pdata = ts_data->pdata;
struct input_dev *input_dev;
FTS_FUNC_ENTER();
input_dev = input_allocate_device();
if (!input_dev) {
FTS_ERROR("Failed to allocate memory for input device");
return -ENOMEM;
}
/* Init and register Input device */
input_dev->name = FTS_DRIVER_NAME;
if (ts_data->bus_type == FTS_BUS_TYPE_I2C)
input_dev->id.bustype = BUS_I2C;
else
input_dev->id.bustype = BUS_SPI;
input_dev->dev.parent = ts_data->dev;
input_set_drvdata(input_dev, ts_data);
__set_bit(EV_SYN, input_dev->evbit);
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
if (pdata->have_key) {
FTS_INFO("set key capabilities");
for (key_num = 0; key_num < pdata->key_number; key_num++)
input_set_capability(input_dev, EV_KEY, pdata->keys[key_num]);
}
#if FTS_MT_PROTOCOL_B_EN
input_mt_init_slots(input_dev, pdata->max_touch_number, INPUT_MT_DIRECT);
#else
input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 0x0F, 0, 0);
#endif
input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, pdata->x_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, pdata->y_max, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0x3F, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 0x3F, 0, 0);
#if FTS_REPORT_PRESSURE_EN
input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0);
#endif
/* Units are (-4096, 4096), representing the range between rotation
* 90 degrees to left and 90 degrees to the right.
*/
input_set_abs_params(input_dev, ABS_MT_ORIENTATION, -4096, 4096, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER, MT_TOOL_PALM, 0, 0);
ret = input_register_device(input_dev);
if (ret) {
FTS_ERROR("Input device registration failed");
input_set_drvdata(input_dev, NULL);
input_free_device(input_dev);
input_dev = NULL;
return ret;
}
#if FTS_PEN_EN
ret = fts_input_pen_init(ts_data);
if (ret) {
FTS_ERROR("Input-pen device registration failed");
input_set_drvdata(input_dev, NULL);
input_free_device(input_dev);
input_dev = NULL;
return ret;
}
#endif
ts_data->input_dev = input_dev;
FTS_FUNC_EXIT();
return 0;
}
static int fts_report_buffer_init(struct fts_ts_data *ts_data)
{
int point_num = 0;
int events_num = 0;
point_num = FTS_MAX_POINTS_SUPPORT;
ts_data->pnt_buf_size = FTS_CAP_DATA_LEN;
ts_data->point_buf = (u8 *)kzalloc(ts_data->pnt_buf_size + 1, GFP_KERNEL);
if (!ts_data->point_buf) {
FTS_ERROR("failed to alloc memory for point buf");
return -ENOMEM;
}
events_num = point_num * sizeof(struct ts_event);
ts_data->events = (struct ts_event *)kzalloc(events_num, GFP_KERNEL);
if (!ts_data->events) {
FTS_ERROR("failed to alloc memory for point events");
kfree_safe(ts_data->point_buf);
return -ENOMEM;
}
return 0;
}
#if FTS_POWER_SOURCE_CUST_EN
/*****************************************************************************
* Power Control
*****************************************************************************/
#if FTS_PINCTRL_EN
static int fts_pinctrl_init(struct fts_ts_data *ts)
{
int ret = 0;
ts->pinctrl = devm_pinctrl_get(ts->dev);
if (IS_ERR_OR_NULL(ts->pinctrl)) {
FTS_ERROR("Failed to get pinctrl, please check dts");
ret = PTR_ERR(ts->pinctrl);
goto err_pinctrl_get;
}
ts->pins_active = pinctrl_lookup_state(ts->pinctrl, "ts_active");
if (IS_ERR_OR_NULL(ts->pins_active)) {
FTS_ERROR("Pin state[active] not found");
ret = PTR_ERR(ts->pins_active);
goto err_pinctrl_lookup;
}
ts->pins_suspend = pinctrl_lookup_state(ts->pinctrl, "ts_suspend");
if (IS_ERR_OR_NULL(ts->pins_suspend)) {
FTS_ERROR("Pin state[suspend] not found");
ret = PTR_ERR(ts->pins_suspend);
goto err_pinctrl_lookup;
}
return 0;
err_pinctrl_lookup:
if (ts->pinctrl) {
devm_pinctrl_put(ts->pinctrl);
}
err_pinctrl_get:
ts->pinctrl = NULL;
ts->pins_suspend = NULL;
ts->pins_active = NULL;
return ret;
}
static int fts_pinctrl_select_normal(struct fts_ts_data *ts)
{
int ret = 0;
FTS_DEBUG("Pins control select normal");
if (ts->pinctrl && ts->pins_active) {
ret = pinctrl_select_state(ts->pinctrl, ts->pins_active);
if (ret < 0) {
FTS_ERROR("Set normal pin state error:%d", ret);
}
}
return ret;
}
static int fts_pinctrl_select_suspend(struct fts_ts_data *ts)
{
int ret = 0;
FTS_DEBUG("Pins control select suspend");
if (ts->pinctrl && ts->pins_suspend) {
ret = pinctrl_select_state(ts->pinctrl, ts->pins_suspend);
if (ret < 0) {
FTS_ERROR("Set suspend pin state error:%d", ret);
}
}
return ret;
}
#endif /* FTS_PINCTRL_EN */
static int fts_power_source_ctrl(struct fts_ts_data *ts_data, int enable)
{
int ret = 0;
if (IS_ERR_OR_NULL(ts_data->avdd)) {
FTS_ERROR("avdd is invalid");
return -EINVAL;
}
FTS_FUNC_ENTER();
if (enable) {
if (ts_data->power_disabled) {
FTS_DEBUG("regulator enable !");
ret = regulator_enable(ts_data->avdd);
if (ret) {
FTS_ERROR("enable avdd regulator failed,ret=%d", ret);
}
if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
ret = regulator_enable(ts_data->dvdd);
if (ret) {
FTS_ERROR("enable dvdd regulator failed,ret=%d", ret);
}
}
/* sleep 1 ms to power on avdd/dvdd to match spec. */
msleep(1);
gpio_direction_output(ts_data->pdata->reset_gpio, 1);
ts_data->power_disabled = false;
}
} else {
if (!ts_data->power_disabled) {
FTS_DEBUG("regulator disable !");
gpio_direction_output(ts_data->pdata->reset_gpio, 0);
/* sleep 1 ms to power off avdd/dvdd to match spec. */
msleep(1);
ret = regulator_disable(ts_data->avdd);
if (ret) {
FTS_ERROR("disable avdd regulator failed,ret=%d", ret);
}
if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
ret = regulator_disable(ts_data->dvdd);
if (ret) {
FTS_ERROR("disable dvdd regulator failed,ret=%d", ret);
}
}
ts_data->power_disabled = true;
}
}
FTS_FUNC_EXIT();
return ret;
}
/*****************************************************************************
* Name: fts_power_source_init
* Brief: Init regulator power:avdd/dvdd(if have), generally, no dvdd
* avdd---->avdd-supply in dts, kernel will auto add "-supply" to parse
* Must be call after fts_gpio_configure() execute,because this function
* will operate reset-gpio which request gpio in fts_gpio_configure()
* Input:
* Output:
* Return: return 0 if init power successfully, otherwise return error code
*****************************************************************************/
static int fts_power_source_init(struct fts_ts_data *ts_data)
{
int ret = 0;
FTS_FUNC_ENTER();
#if FTS_PINCTRL_EN
fts_pinctrl_init(ts_data);
fts_pinctrl_select_normal(ts_data);
#endif
if (of_property_read_bool(ts_data->dev->of_node, "avdd-supply")) {
ts_data->avdd = regulator_get(ts_data->dev, "avdd");
if (IS_ERR_OR_NULL(ts_data->avdd)) {
ret = PTR_ERR(ts_data->avdd);
ts_data->avdd = NULL;
FTS_ERROR("get avdd regulator failed,ret=%d", ret);
return ret;
}
} else {
FTS_ERROR("avdd-supply not found!");
}
if (of_property_read_bool(ts_data->dev->of_node, "vdd-supply")) {
ts_data->dvdd = regulator_get(ts_data->dev, "vdd");
if (IS_ERR_OR_NULL(ts_data->dvdd)) {
ret = PTR_ERR(ts_data->dvdd);
ts_data->dvdd = NULL;
FTS_ERROR("get dvdd regulator failed,ret=%d", ret);
}
} else {
FTS_ERROR("vdd-supply not found!");
}
ts_data->power_disabled = true;
ret = fts_power_source_ctrl(ts_data, ENABLE);
if (ret) {
FTS_ERROR("fail to enable power(regulator)");
}
FTS_FUNC_EXIT();
return ret;
}
static int fts_power_source_exit(struct fts_ts_data *ts_data)
{
fts_power_source_ctrl(ts_data, DISABLE);
#if FTS_PINCTRL_EN
fts_pinctrl_select_suspend(ts_data);
#endif
if (!IS_ERR_OR_NULL(ts_data->avdd)) {
regulator_put(ts_data->avdd);
ts_data->avdd = NULL;
}
if (!IS_ERR_OR_NULL(ts_data->dvdd)) {
regulator_put(ts_data->dvdd);
ts_data->dvdd = NULL;
}
return 0;
}
static int fts_power_source_suspend(struct fts_ts_data *ts_data)
{
int ret = 0;
#if !defined(FTS_AOC_GESTURE_EN)
ret = fts_power_source_ctrl(ts_data, DISABLE);
if (ret < 0) {
FTS_ERROR("power off fail, ret=%d", ret);
}
#endif
#if FTS_PINCTRL_EN
fts_pinctrl_select_suspend(ts_data);
#endif
return ret;
}
static int fts_power_source_resume(struct fts_ts_data *ts_data)
{
int ret = 0;
#if FTS_PINCTRL_EN
fts_pinctrl_select_normal(ts_data);
#endif
#if !defined(FTS_AOC_GESTURE_EN)
ret = fts_power_source_ctrl(ts_data, ENABLE);
if (ret < 0) {
FTS_ERROR("power on fail, ret=%d", ret);
}
#endif
return ret;
}
#endif /* FTS_POWER_SOURCE_CUST_EN */
static int fts_gpio_configure(struct fts_ts_data *data)
{
int ret = 0;
FTS_FUNC_ENTER();
/* request irq gpio */
if (gpio_is_valid(data->pdata->irq_gpio)) {
ret = gpio_request(data->pdata->irq_gpio, "fts_irq_gpio");
if (ret) {
FTS_ERROR("[GPIO]irq gpio request failed");
goto err_irq_gpio_req;
}
ret = gpio_direction_input(data->pdata->irq_gpio);
if (ret) {
FTS_ERROR("[GPIO]set_direction for irq gpio failed");
goto err_irq_gpio_dir;
}
}
/* request reset gpio */
if (gpio_is_valid(data->pdata->reset_gpio)) {
ret = gpio_request(data->pdata->reset_gpio, "fts_reset_gpio");
if (ret) {
FTS_ERROR("[GPIO]reset gpio request failed");
goto err_irq_gpio_dir;
}
ret = gpio_direction_output(data->pdata->reset_gpio, 0);
if (ret) {
FTS_ERROR("[GPIO]set_direction for reset gpio failed");
goto err_reset_gpio_dir;
}
}
FTS_FUNC_EXIT();
return 0;
err_reset_gpio_dir:
if (gpio_is_valid(data->pdata->reset_gpio))
gpio_free(data->pdata->reset_gpio);
err_irq_gpio_dir:
if (gpio_is_valid(data->pdata->irq_gpio))
gpio_free(data->pdata->irq_gpio);
err_irq_gpio_req:
FTS_FUNC_EXIT();
return ret;
}
static int fts_get_dt_coords(struct device *dev, char *name,
struct fts_ts_platform_data *pdata)
{
int ret = 0;
u32 coords[FTS_COORDS_ARR_SIZE] = { 0 };
struct property *prop;
struct device_node *np = dev->of_node;
int coords_size;
prop = of_find_property(np, name, NULL);
if (!prop)
return -EINVAL;
if (!prop->value)
return -ENODATA;
coords_size = prop->length / sizeof(u32);
if (coords_size != FTS_COORDS_ARR_SIZE) {
FTS_ERROR("invalid:%s, size:%d", name, coords_size);
return -EINVAL;
}
ret = of_property_read_u32_array(np, name, coords, coords_size);
if (ret < 0) {
FTS_ERROR("Unable to read %s, please check dts", name);
pdata->x_min = FTS_X_MIN_DISPLAY_DEFAULT;
pdata->y_min = FTS_Y_MIN_DISPLAY_DEFAULT;
pdata->x_max = FTS_X_MAX_DISPLAY_DEFAULT;
pdata->y_max = FTS_Y_MAX_DISPLAY_DEFAULT;
return -ENODATA;
} else {
pdata->x_min = coords[0];
pdata->y_min = coords[1];
pdata->x_max = coords[2];
pdata->y_max = coords[3];
}
FTS_INFO("display x(%d %d) y(%d %d)", pdata->x_min, pdata->x_max,
pdata->y_min, pdata->y_max);
return 0;
}
static int fts_check_panel_map(struct device_node *np,
struct fts_ts_platform_data *pdata)
{
int ret = 0;
int index;
struct of_phandle_args panelmap;
struct drm_panel *panel = NULL;
if (of_property_read_bool(np, "focaltech,panel_map")) {
for (index = 0 ;; index++) {
ret = of_parse_phandle_with_fixed_args(np,
"focaltech,panel_map",
1,
index,
&panelmap);
if (ret) {
FTS_ERROR("Can't find display panel!\n");
return -EPROBE_DEFER;
}
panel = of_drm_find_panel(panelmap.np);
of_node_put(panelmap.np);
if (!IS_ERR_OR_NULL(panel)) {
pdata->panel = panel;
pdata->initial_panel_index = panelmap.args[0];
break;
}
}
}
return 0;
}
static int fts_parse_dt(struct device *dev, struct fts_ts_platform_data *pdata)
{
int ret = 0;
struct device_node *np = dev->of_node;
u32 temp_val = 0;
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
u8 offload_id[4];
#endif
FTS_FUNC_ENTER();
/* Check if panel(s) exist or not. */
ret = fts_check_panel_map(np, pdata);
if (ret)
return ret;
ret = fts_get_dt_coords(dev, "focaltech,display-coords", pdata);
if (ret < 0)
FTS_ERROR("Unable to get display-coords");
/* key */
pdata->have_key = of_property_read_bool(np, "focaltech,have-key");
if (pdata->have_key) {
ret = of_property_read_u32(np, "focaltech,key-number", &pdata->key_number);
if (ret < 0)
FTS_ERROR("Key number undefined!");
ret = of_property_read_u32_array(np, "focaltech,keys",
pdata->keys, pdata->key_number);
if (ret < 0)
FTS_ERROR("Keys undefined!");
else if (pdata->key_number > FTS_MAX_KEYS)
pdata->key_number = FTS_MAX_KEYS;
ret = of_property_read_u32_array(np, "focaltech,key-x-coords",
pdata->key_x_coords,
pdata->key_number);
if (ret < 0)
FTS_ERROR("Key Y Coords undefined!");
ret = of_property_read_u32_array(np, "focaltech,key-y-coords",
pdata->key_y_coords,
pdata->key_number);
if (ret < 0)
FTS_ERROR("Key X Coords undefined!");
FTS_INFO("VK Number:%d, key:(%d,%d,%d), "
"coords:(%d,%d),(%d,%d),(%d,%d)",
pdata->key_number,
pdata->keys[0], pdata->keys[1], pdata->keys[2],
pdata->key_x_coords[0], pdata->key_y_coords[0],
pdata->key_x_coords[1], pdata->key_y_coords[1],
pdata->key_x_coords[2], pdata->key_y_coords[2]);
}
/* reset, irq gpio info */
pdata->reset_gpio = of_get_named_gpio_flags(np, "focaltech,reset-gpio",
0, &pdata->reset_gpio_flags);
if (pdata->reset_gpio < 0)
FTS_ERROR("Unable to get reset_gpio");
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
pdata->offload_id = 0;
ret = of_property_read_u8_array(np, "focaltech,touch_offload_id",
offload_id, 4);
if (ret == -EINVAL) {
FTS_ERROR("Failed to read focaltech,touch_offload_id with error = %d\n",
ret);
} else {
pdata->offload_id = *(u32 *)offload_id;
FTS_DEBUG("Offload device ID = \"%c%c%c%c\" / 0x%08X\n",
offload_id[0], offload_id[1], offload_id[2], offload_id[3],
pdata->offload_id);
}
#endif
ret = of_property_read_u32(np, "focaltech,tx_ch_num", &temp_val);
if (ret < 0) {
FTS_ERROR("Unable to get tx_ch_num, please check dts");
} else {
pdata->tx_ch_num = temp_val;
FTS_DEBUG("tx_ch_num = %d", pdata->tx_ch_num);
}
ret = of_property_read_u32(np, "focaltech,rx_ch_num", &temp_val);
if (ret < 0) {
FTS_ERROR("Unable to get rx_ch_num, please check dts");
} else {
pdata->rx_ch_num = temp_val;
FTS_DEBUG("rx_ch_num = %d", pdata->rx_ch_num);
}
ret = of_property_read_u8(np, "focaltech,mm2px", &pdata->mm2px);
if (ret < 0) {
FTS_ERROR("Unable to get mm2px, please check dts");
pdata->mm2px = 1;
} else {
FTS_DEBUG("mm2px = %d", pdata->mm2px);
}
pdata->irq_gpio = of_get_named_gpio_flags(np, "focaltech,irq-gpio",
0, &pdata->irq_gpio_flags);
if (pdata->irq_gpio < 0)
FTS_ERROR("Unable to get irq_gpio");
ret = of_property_read_u32(np, "focaltech,max-touch-number", &temp_val);
if (ret < 0) {
FTS_ERROR("Unable to get max-touch-number, please check dts");
pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
} else {
if (temp_val < 2)
pdata->max_touch_number = 2; /* max_touch_number must >= 2 */
else if (temp_val > FTS_MAX_POINTS_SUPPORT)
pdata->max_touch_number = FTS_MAX_POINTS_SUPPORT;
else
pdata->max_touch_number = temp_val;
}
FTS_INFO("max touch number:%d, irq gpio:%d, reset gpio:%d",
pdata->max_touch_number, pdata->irq_gpio, pdata->reset_gpio);
FTS_FUNC_EXIT();
return 0;
}
static void fts_suspend_work(struct work_struct *work)
{
struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
suspend_work);
FTS_DEBUG("Entry");
mutex_lock(&ts_data->device_mutex);
reinit_completion(&ts_data->bus_resumed);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
if (ts_data->power_status == FTS_TS_STATE_SUSPEND) {
FTS_ERROR("Already suspended.\n");
mutex_unlock(&ts_data->device_mutex);
return;
}
#endif
fts_ts_suspend(ts_data->dev);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
ts_data->power_status = FTS_TS_STATE_SUSPEND;
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
if (ts_data->tbn_register_mask) {
tbn_release_bus(ts_data->tbn_register_mask);
ts_data->tbn_owner = TBN_AOC;
}
#endif
mutex_unlock(&ts_data->device_mutex);
}
static void fts_resume_work(struct work_struct *work)
{
struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data,
resume_work);
FTS_DEBUG("Entry");
mutex_lock(&ts_data->device_mutex);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
if (ts_data->tbn_register_mask) {
tbn_request_bus(ts_data->tbn_register_mask);
ts_data->tbn_owner = TBN_AP;
}
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
if (ts_data->power_status == FTS_TS_STATE_POWER_ON) {
FTS_ERROR("Already resumed.\n");
mutex_unlock(&ts_data->device_mutex);
return;
}
#endif
fts_ts_resume(ts_data->dev);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
ts_data->power_status = FTS_TS_STATE_POWER_ON;
#endif
complete_all(&ts_data->bus_resumed);
mutex_unlock(&ts_data->device_mutex);
}
#if defined(CONFIG_FB)
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct fb_event *evdata = data;
int *blank = NULL;
struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
fb_notif);
if (!evdata) {
FTS_ERROR("evdata is null");
return 0;
}
if (!(event == FB_EARLY_EVENT_BLANK || event == FB_EVENT_BLANK)) {
FTS_INFO("event(%lu) do not need process\n", event);
return 0;
}
blank = evdata->data;
FTS_INFO("FB event:%lu,blank:%d", event, *blank);
switch (*blank) {
case FB_BLANK_UNBLANK:
if (FB_EARLY_EVENT_BLANK == event) {
FTS_INFO("resume: event = %lu, not care\n", event);
} else if (FB_EVENT_BLANK == event) {
queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
}
break;
case FB_BLANK_POWERDOWN:
if (FB_EARLY_EVENT_BLANK == event) {
cancel_work_sync(&fts_data->resume_work);
fts_ts_suspend(ts_data->dev);
} else if (FB_EVENT_BLANK == event) {
FTS_INFO("suspend: event = %lu, not care\n", event);
}
break;
default:
FTS_INFO("FB BLANK(%d) do not need process\n", *blank);
break;
}
return 0;
}
#elif defined(CONFIG_DRM)
#if defined(CONFIG_DRM_PANEL)
static struct drm_panel *active_panel;
static int drm_check_dt(struct device_node *np)
{
int i = 0;
int count = 0;
struct device_node *node = NULL;
struct drm_panel *panel = NULL;
count = of_count_phandle_with_args(np, "panel", NULL);
if (count <= 0) {
FTS_ERROR("find drm_panel count(%d) fail", count);
return -ENODEV;
}
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)) {
FTS_INFO("find drm_panel successfully");
active_panel = panel;
return 0;
}
}
FTS_ERROR("no find drm_panel");
return -ENODEV;
}
static int drm_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct msm_drm_notifier *evdata = data;
int *blank = NULL;
struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
fb_notif);
if (!evdata) {
FTS_ERROR("evdata is null");
return 0;
}
if (!((event == DRM_PANEL_EARLY_EVENT_BLANK )
|| (event == DRM_PANEL_EVENT_BLANK))) {
FTS_INFO("event(%lu) do not need process\n", event);
return 0;
}
blank = evdata->data;
FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
switch (*blank) {
case DRM_PANEL_BLANK_UNBLANK:
if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
FTS_INFO("resume: event = %lu, not care\n", event);
} else if (DRM_PANEL_EVENT_BLANK == event) {
queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
}
break;
case DRM_PANEL_BLANK_POWERDOWN:
if (DRM_PANEL_EARLY_EVENT_BLANK == event) {
cancel_work_sync(&fts_data->resume_work);
fts_ts_suspend(ts_data->dev);
} else if (DRM_PANEL_EVENT_BLANK == event) {
FTS_INFO("suspend: event = %lu, not care\n", event);
}
break;
default:
FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
break;
}
return 0;
}
#elif defined(CONFIG_ARCH_MSM)
static int drm_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct msm_drm_notifier *evdata = data;
int *blank = NULL;
struct fts_ts_data *ts_data = container_of(self, struct fts_ts_data,
fb_notif);
if (!evdata) {
FTS_ERROR("evdata is null");
return 0;
}
if (!((event == MSM_DRM_EARLY_EVENT_BLANK )
|| (event == MSM_DRM_EVENT_BLANK))) {
FTS_INFO("event(%lu) do not need process\n", event);
return 0;
}
blank = evdata->data;
FTS_INFO("DRM event:%lu,blank:%d", event, *blank);
switch (*blank) {
case MSM_DRM_BLANK_UNBLANK:
if (MSM_DRM_EARLY_EVENT_BLANK == event) {
FTS_INFO("resume: event = %lu, not care\n", event);
} else if (MSM_DRM_EVENT_BLANK == event) {
queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
}
break;
case MSM_DRM_BLANK_POWERDOWN:
if (MSM_DRM_EARLY_EVENT_BLANK == event) {
cancel_work_sync(&fts_data->resume_work);
fts_ts_suspend(ts_data->dev);
} else if (MSM_DRM_EVENT_BLANK == event) {
FTS_INFO("suspend: event = %lu, not care\n", event);
}
break;
default:
FTS_INFO("DRM BLANK(%d) do not need process\n", *blank);
break;
}
return 0;
}
#endif
#elif defined(CONFIG_HAS_EARLYSUSPEND)
static void fts_ts_early_suspend(struct early_suspend *handler)
{
struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
early_suspend);
cancel_work_sync(&fts_data->resume_work);
fts_ts_suspend(ts_data->dev);
}
static void fts_ts_late_resume(struct early_suspend *handler)
{
struct fts_ts_data *ts_data = container_of(handler, struct fts_ts_data,
early_suspend);
queue_work(fts_data->ts_workqueue, &fts_data->resume_work);
}
#endif
static int fts_ts_probe_entry(struct fts_ts_data *ts_data)
{
int ret = 0;
int pdata_size = sizeof(struct fts_ts_platform_data);
FTS_FUNC_ENTER();
ts_data->driver_probed = false;
FTS_INFO("%s", FTS_DRIVER_VERSION);
ts_data->pdata = kzalloc(pdata_size, GFP_KERNEL);
if (!ts_data->pdata) {
FTS_ERROR("allocate memory for platform_data fail");
return -ENOMEM;
}
if (ts_data->dev->of_node) {
ret = fts_parse_dt(ts_data->dev, ts_data->pdata);
if (ret)
FTS_ERROR("device-tree parse fail");
#if defined(CONFIG_DRM)
#if defined(CONFIG_DRM_PANEL)
ret = drm_check_dt(ts_data->dev->of_node);
if (ret) {
FTS_ERROR("parse drm-panel fail");
}
#endif
#endif
} else {
if (ts_data->dev->platform_data) {
memcpy(ts_data->pdata, ts_data->dev->platform_data, pdata_size);
} else {
FTS_ERROR("platform_data is null");
return -ENODEV;
}
}
ts_data->ts_workqueue = create_singlethread_workqueue("fts_wq");
if (!ts_data->ts_workqueue) {
FTS_ERROR("create fts workqueue fail");
}
spin_lock_init(&ts_data->irq_lock);
mutex_init(&ts_data->report_mutex);
mutex_init(&ts_data->bus_lock);
mutex_init(&ts_data->reg_lock);
ts_data->is_deepsleep = false;
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
ts_data->power_status = FTS_TS_STATE_POWER_ON;
ts_data->bus_refmask = FTS_TS_BUS_REF_SCREEN_ON;
mutex_init(&ts_data->bus_mutex);
#endif
mutex_init(&ts_data->device_mutex);
init_completion(&ts_data->bus_resumed);
complete_all(&ts_data->bus_resumed);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
if (register_tbn(&ts_data->tbn_register_mask)) {
ret = -ENODEV;
FTS_ERROR("Failed to register tbn context.\n");
goto err_init_tbn;
}
ts_data->tbn_owner = TBN_AP;
FTS_INFO("tbn_register_mask = %#x.\n", ts_data->tbn_register_mask);
#endif
/* Init communication interface */
ret = fts_bus_init(ts_data);
if (ret) {
FTS_ERROR("bus initialize fail");
goto err_bus_init;
}
ret = fts_input_init(ts_data);
if (ret) {
FTS_ERROR("input initialize fail");
goto err_input_init;
}
ret = fts_report_buffer_init(ts_data);
if (ret) {
FTS_ERROR("report buffer init fail");
goto err_report_buffer;
}
ret = fts_gpio_configure(ts_data);
if (ret) {
FTS_ERROR("configure the gpios fail");
goto err_gpio_config;
}
#if FTS_POWER_SOURCE_CUST_EN
ret = fts_power_source_init(ts_data);
if (ret) {
FTS_ERROR("fail to get power(regulator)");
goto err_power_init;
}
#endif
#if (!FTS_CHIP_IDC)
fts_reset_proc(FTS_RESET_INTERVAL);
#endif
ret = fts_get_ic_information(ts_data);
if (ret) {
FTS_ERROR("not focal IC, unregister driver");
goto err_power_init;
}
ret = fts_create_apk_debug_channel(ts_data);
if (ret) {
FTS_ERROR("create apk debug node fail");
}
#if GOOGLE_REPORT_MODE
memset(ts_data->current_host_status, 0, FTS_CUSTOMER_STATUS_LEN);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_UNCOMPRESSED;
/* Update ts_data->fw_default_heatmap_mode from firmware setting */
fts_get_default_heatmap_mode(ts_data);
ts_data->fw_heatmap_mode = ts_data->fw_default_heatmap_mode;
ts_data->compress_heatmap_wlen = 0;
#endif
ts_data->enable_fw_grip = FW_GRIP_ENABLE;
ts_data->enable_fw_palm = FW_GRIP_ENABLE;
ts_data->set_continuously_report = ENABLE;
ts_data->glove_mode = DISABLE;
fts_update_feature_setting(ts_data);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
ts_data->offload.caps.touch_offload_major_version = TOUCH_OFFLOAD_INTERFACE_MAJOR_VERSION;
ts_data->offload.caps.touch_offload_minor_version = TOUCH_OFFLOAD_INTERFACE_MINOR_VERSION;
ts_data->offload.caps.device_id = ts_data->pdata->offload_id;
ts_data->offload.caps.display_width = ts_data->pdata->x_max;
ts_data->offload.caps.display_height = ts_data->pdata->y_max;
ts_data->offload.caps.tx_size = ts_data->pdata->tx_ch_num;
ts_data->offload.caps.rx_size = ts_data->pdata->rx_ch_num;
ts_data->offload.caps.bus_type = BUS_TYPE_SPI;
ts_data->offload.caps.bus_speed_hz = 10000000;
ts_data->offload.caps.heatmap_size = HEATMAP_SIZE_FULL;
ts_data->offload.caps.touch_data_types = TOUCH_DATA_TYPE_COORD |
TOUCH_DATA_TYPE_STRENGTH |
TOUCH_DATA_TYPE_FILTERED |
TOUCH_DATA_TYPE_RAW;
ts_data->offload.caps.touch_scan_types = TOUCH_SCAN_TYPE_MUTUAL |
TOUCH_SCAN_TYPE_SELF;
ts_data->offload.caps.continuous_reporting = true;
ts_data->offload.caps.noise_reporting = false;
ts_data->offload.caps.cancel_reporting = true;
ts_data->offload.caps.rotation_reporting = true;
ts_data->offload.caps.size_reporting = true;
ts_data->offload.caps.filter_grip = true;
ts_data->offload.caps.filter_palm = true;
ts_data->offload.caps.num_sensitivity_settings = 1;
ts_data->offload.hcallback = (void *)ts_data;
ts_data->offload.report_cb = fts_offload_report;
touch_offload_init(&ts_data->offload);
ts_data->touch_offload_active_coords = 0;
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
/* |- MS -|- Normal-SS -|- Water-SS -|
* |- Tx*Rx*2 -|- (Tx+Rx)*2 -|- (Tx+Rx)*2 -|
* Total size = 1288 bytes
*/
if (!ts_data->heatmap_buff) {
ts_data->heatmap_buff_size = sizeof(u16) *
((ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num) +
(ts_data->pdata->tx_ch_num + ts_data->pdata->rx_ch_num) * 2);
FTS_DEBUG("Allocate heatmap_buff size=%d\n", ts_data->heatmap_buff_size);
ts_data->heatmap_buff = kmalloc(ts_data->heatmap_buff_size, GFP_KERNEL);
if (!ts_data->heatmap_buff) {
FTS_ERROR("allocate heatmap_buff failed\n");
goto err_heatmap_buff;
}
}
/* |- cap header (91) -|- Water-SS -|- Normal-SS -|- Normal-MS -|
* |- 91 -|- 68*2 -|- 68*2 -|- 16*34*2 -|
* Total size = 1379 bytes
*/
if (!ts_data->heatmap_raw) {
int node_num = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
ts_data->heatmap_raw_size = FTS_CAP_DATA_LEN +
((node_num + FTS_SELF_DATA_LEN * 2) * sizeof(u16));
FTS_DEBUG("Allocate heatmap_raw size=%d\n", ts_data->heatmap_raw_size);
ts_data->heatmap_raw = kmalloc(ts_data->heatmap_raw_size, GFP_KERNEL);
if (!ts_data->heatmap_raw) {
FTS_ERROR("allocate heatmap_raw failed\n");
goto err_heatmap_raw;
}
}
/* |- MS -|
* |- 16*34*2 -|
*/
if (!ts_data->trans_raw) {
int node_num = ts_data->pdata->tx_ch_num * ts_data->pdata->rx_ch_num;
ts_data->trans_raw_size = node_num * sizeof(u16);
FTS_DEBUG("Allocate trans_raw size=%d\n", ts_data->trans_raw_size);
ts_data->trans_raw = kmalloc(ts_data->trans_raw_size, GFP_KERNEL);
if (!ts_data->trans_raw) {
FTS_ERROR("allocate trans_raw failed\n");
goto err_trans_raw;
}
}
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
/*
* Heatmap_probe must be called before irq routine is registered,
* because heatmap_read is called from interrupt context.
*/
ts_data->v4l2.parent_dev = ts_data->dev;
ts_data->v4l2.input_dev = ts_data->input_dev;
ts_data->v4l2.read_frame = v4l2_read_frame;
ts_data->v4l2.width = ts_data->pdata->tx_ch_num;
ts_data->v4l2.height = ts_data->pdata->rx_ch_num;
/* 180 Hz operation */
ts_data->v4l2.timeperframe.numerator = 1;
ts_data->v4l2.timeperframe.denominator = 180;
ret = heatmap_probe(&ts_data->v4l2);
if (ret < 0) {
FTS_ERROR("heatmap probe unsuccessfully!");
goto err_heatmap_probe;
} else {
FTS_DEBUG("heatmap probe successfully!");
}
#endif
/* init motion filter mode and state. */
ts_data->mf_mode = MF_DYNAMIC;
ts_data->mf_state = MF_UNFILTERED;
ret = fts_create_sysfs(ts_data);
if (ret) {
FTS_ERROR("create sysfs node fail");
}
#if FTS_POINT_REPORT_CHECK_EN
ret = fts_point_report_check_init(ts_data);
if (ret) {
FTS_ERROR("init point report check fail");
}
#endif
ret = fts_ex_mode_init(ts_data);
if (ret) {
FTS_ERROR("init glove/cover/charger fail");
}
ret = fts_gesture_init(ts_data);
if (ret) {
FTS_ERROR("init gesture fail");
}
#if FTS_TEST_EN
ret = fts_test_init(ts_data);
if (ret) {
FTS_ERROR("init production test fail");
}
#endif
#if FTS_ESDCHECK_EN
ret = fts_esdcheck_init(ts_data);
if (ret) {
FTS_ERROR("init esd check fail");
}
#endif
/* init pm_qos before interrupt registered. */
cpu_latency_qos_add_request(&ts_data->pm_qos_req, PM_QOS_DEFAULT_VALUE);
ret = fts_irq_registration(ts_data);
if (ret) {
FTS_ERROR("request irq failed");
goto err_irq_req;
}
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
ret = register_panel_bridge(ts_data);
if (ret < 0) {
FTS_ERROR("register_panel_bridge failed. ret = 0x%08X\n", ret);
}
#endif
if (ts_data->ts_workqueue) {
INIT_WORK(&ts_data->resume_work, fts_resume_work);
INIT_WORK(&ts_data->suspend_work, fts_suspend_work);
}
ret = fts_fwupg_init(ts_data);
if (ret) {
FTS_ERROR("init fw upgrade fail");
}
#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
init_completion(&ts_data->pm_completion);
ts_data->pm_suspend = false;
#endif
#if defined(CONFIG_FB)
ts_data->fb_notif.notifier_call = fb_notifier_callback;
ret = fb_register_client(&ts_data->fb_notif);
if (ret) {
FTS_ERROR("[FB]Unable to register fb_notifier: %d", ret);
}
#elif defined(CONFIG_DRM_PANEL) || defined(CONFIG_ARCH_MSM)
ts_data->fb_notif.notifier_call = drm_notifier_callback;
#if defined(CONFIG_DRM_PANEL)
if (active_panel) {
ret = drm_panel_notifier_register(active_panel, &ts_data->fb_notif);
if (ret)
FTS_ERROR("[DRM]drm_panel_notifier_register fail: %d\n", ret);
}
#elif defined(CONFIG_ARCH_MSM)
ret = msm_drm_register_client(&ts_data->fb_notif);
if (ret) {
FTS_ERROR("[DRM]Unable to register fb_notifier: %d\n", ret);
}
#endif
#elif defined(CONFIG_HAS_EARLYSUSPEND)
ts_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + FTS_SUSPEND_LEVEL;
ts_data->early_suspend.suspend = fts_ts_early_suspend;
ts_data->early_suspend.resume = fts_ts_late_resume;
register_early_suspend(&ts_data->early_suspend);
#endif
ts_data->work_mode = FTS_REG_WORKMODE_WORK_VALUE;
#if GOOGLE_REPORT_MODE
fts_read_reg(FTS_REG_CUSTOMER_STATUS, &ts_data->current_host_status[0]);
FTS_INFO("-------Palm mode %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_PALM)) ? "enter" : "exit");
FTS_INFO("-------Water mode %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_WATER)) ? "enter" : "exit");
FTS_INFO("-------Grip mode %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_GRIP)) ? "enter" : "exit");
FTS_INFO("-------Glove mode %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_GLOVE)) ? "enter" : "exit");
FTS_INFO("-------Edge palm %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_EDGE_PALM)) ? "enter" : "exit");
FTS_INFO("-------Reset %s\n",
(ts_data->current_host_status[0] & (1 << STATUS_RESET)) ? "enter" : "exit");
#endif
ts_data->driver_probed = true;
FTS_FUNC_EXIT();
return 0;
err_irq_req:
cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
heatmap_remove(&ts_data->v4l2);
err_heatmap_probe:
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
kfree_safe(ts_data->trans_raw);
err_trans_raw:
kfree_safe(ts_data->heatmap_raw);
err_heatmap_raw:
kfree_safe(ts_data->heatmap_buff);
err_heatmap_buff:
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
touch_offload_cleanup(&ts_data->offload);
#endif
#if FTS_POWER_SOURCE_CUST_EN
err_power_init:
fts_power_source_exit(ts_data);
#endif
if (gpio_is_valid(ts_data->pdata->reset_gpio))
gpio_free(ts_data->pdata->reset_gpio);
if (gpio_is_valid(ts_data->pdata->irq_gpio))
gpio_free(ts_data->pdata->irq_gpio);
err_gpio_config:
kfree_safe(ts_data->point_buf);
kfree_safe(ts_data->events);
err_report_buffer:
input_unregister_device(ts_data->input_dev);
#if FTS_PEN_EN
input_unregister_device(ts_data->pen_dev);
#endif
err_input_init:
if (ts_data->ts_workqueue)
destroy_workqueue(ts_data->ts_workqueue);
err_bus_init:
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
if (ts_data->tbn_register_mask)
unregister_tbn(&ts_data->tbn_register_mask);
err_init_tbn:
#endif
kfree_safe(ts_data->bus_tx_buf);
kfree_safe(ts_data->bus_rx_buf);
kfree_safe(ts_data->pdata);
FTS_FUNC_EXIT();
return ret;
}
static int fts_ts_remove_entry(struct fts_ts_data *ts_data)
{
FTS_FUNC_ENTER();
free_irq(ts_data->irq, ts_data);
#if FTS_POINT_REPORT_CHECK_EN
fts_point_report_check_exit(ts_data);
#endif
fts_release_apk_debug_channel(ts_data);
#if FTS_TEST_EN
/* remove the test nodes and sub-dir in /proc/focaltech_touch/selftest/ */
fts_test_exit(ts_data);
#endif
/* remove all nodes and sub-dir in /proc/focaltech_touch/ */
fts_remove_sysfs(ts_data);
fts_ex_mode_exit(ts_data);
fts_fwupg_exit(ts_data);
#if FTS_ESDCHECK_EN
fts_esdcheck_exit(ts_data);
#endif
fts_gesture_exit(ts_data);
fts_bus_exit(ts_data);
input_unregister_device(ts_data->input_dev);
#if FTS_PEN_EN
input_unregister_device(ts_data->pen_dev);
#endif
cancel_work_sync(&ts_data->suspend_work);
cancel_work_sync(&ts_data->resume_work);
#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN)
if (ts_data->tbn_register_mask)
unregister_tbn(&ts_data->tbn_register_mask);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE)
unregister_panel_bridge(&ts_data->panel_bridge);
#endif
if (ts_data->ts_workqueue)
destroy_workqueue(ts_data->ts_workqueue);
cpu_latency_qos_remove_request(&ts_data->pm_qos_req);
#if defined(CONFIG_FB)
if (fb_unregister_client(&ts_data->fb_notif))
FTS_ERROR("[FB]Error occurred while unregistering fb_notifier.");
#elif defined(CONFIG_DRM)
#if defined(CONFIG_DRM_PANEL)
if (active_panel)
drm_panel_notifier_unregister(active_panel, &ts_data->fb_notif);
#elif defined(CONFIG_ARCH_MSM)
if (msm_drm_unregister_client(&ts_data->fb_notif))
FTS_ERROR("[DRM]Error occurred while unregistering fb_notifier.\n");
#endif
#elif defined(CONFIG_HAS_EARLYSUSPEND)
unregister_early_suspend(&ts_data->early_suspend);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD)
touch_offload_cleanup(&ts_data->offload);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_OFFLOAD) || \
IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
kfree_safe(ts_data->heatmap_buff);
kfree_safe(ts_data->heatmap_raw);
kfree_safe(ts_data->trans_raw);
#endif
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
heatmap_remove(&ts_data->v4l2);
#endif
if (gpio_is_valid(ts_data->pdata->reset_gpio))
gpio_free(ts_data->pdata->reset_gpio);
if (gpio_is_valid(ts_data->pdata->irq_gpio))
gpio_free(ts_data->pdata->irq_gpio);
#if FTS_POWER_SOURCE_CUST_EN
fts_power_source_exit(ts_data);
#endif
kfree_safe(ts_data->point_buf);
kfree_safe(ts_data->events);
kfree_safe(ts_data->pdata);
kfree_safe(ts_data);
FTS_FUNC_EXIT();
return 0;
}
static int fts_write_reg_safe(u8 reg, u8 write_val) {
int ret = 0;
int i;
int j;
u8 reg_val;
for (i = 0; i < MAX_RETRY_CNT; i++) {
ret = fts_write_reg(reg, write_val);
if (ret < 0) {
FTS_DEBUG("write 0x%X failed", reg);
return ret;
}
for (j = 0; j < MAX_RETRY_CNT; j++) {
reg_val = 0xFF;
ret = fts_read_reg(reg, &reg_val);
if (ret < 0) {
FTS_DEBUG("read 0x%X failed", reg);
return ret;
}
if (write_val == reg_val) {
return ret;
}
msleep(1);
}
FTS_ERROR("%s failed, reg(0x%X), write_val(0x%x), reg_val(0x%x), " \
"retry(%d)", __func__, reg, write_val, reg_val, i);
}
if (i == MAX_RETRY_CNT)
ret = -EIO;
return ret;
}
static void fts_update_host_feature_setting(struct fts_ts_data *ts_data,
bool en, u8 fw_mode_setting){
if (en)
ts_data->current_host_status[1] |= 1 << fw_mode_setting;
else
ts_data->current_host_status[1] &= ~(1 << fw_mode_setting);
}
int fts_get_default_heatmap_mode(struct fts_ts_data *ts_data)
{
int ret = 0;
u8 value_heatmap = 0;
u8 value_compressed = 0;
u8 reg_heatmap = FTS_REG_HEATMAP_9E;
u8 reg_compressed = FTS_REG_HEATMAP_ED;
if (!ts_data->driver_probed || ts_data->fw_loading) {
ret = fts_read_reg(reg_compressed, &value_compressed);
if (ret) {
FTS_ERROR("Read reg(%2X) error!", reg_compressed);
goto exit;
}
ret = fts_read_reg(reg_heatmap, &value_heatmap);
if (ret) {
FTS_ERROR("Read reg(%2X) error!", reg_heatmap);
goto exit;
}
if (value_heatmap == 0)
ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_DISABLE;
else if (value_compressed)
ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_COMPRESSED;
else
ts_data->fw_default_heatmap_mode = FW_HEATMAP_MODE_UNCOMPRESSED;
exit:
if (ret == 0) {
FTS_DEBUG("Default fw_heatamp is %s and %s.\n",
value_compressed? "compressed" : "uncompressed",
value_heatmap ? "enabled" : "disabled");
}
}
return ret;
}
int fts_set_heatmap_mode(struct fts_ts_data *ts_data, u8 heatmap_mode)
{
int ret = 0;
u8 value_heatmap = 0;
u8 value_compressed = 0;
u8 reg_heatmap = FTS_REG_HEATMAP_9E;
u8 reg_compressed = FTS_REG_HEATMAP_ED;
int count = 0;
char tmpbuf[FTS_MESSAGE_LENGTH];
switch (heatmap_mode) {
case FW_HEATMAP_MODE_DISABLE:
value_heatmap = DISABLE;
count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
"Disable fw_heatmap");
break;
case FW_HEATMAP_MODE_COMPRESSED:
value_heatmap = ENABLE;
value_compressed = ENABLE;
count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
"Enable compressed fw_heatmap");
break;
case FW_HEATMAP_MODE_UNCOMPRESSED:
value_heatmap = ENABLE;
value_compressed = DISABLE;
count += scnprintf(tmpbuf + count, FTS_MESSAGE_LENGTH - count,
"Enable uncompressed fw_heatmap");
break;
default:
FTS_ERROR("The input heatmap more(%d) is invalid.", heatmap_mode);
return -EINVAL;
}
ret = fts_write_reg_safe(reg_compressed, value_compressed);
if (ret) {
goto exit;
}
ret = fts_write_reg_safe(reg_heatmap, value_heatmap);
if (ret == 0) {
ts_data->fw_heatmap_mode = heatmap_mode;
fts_update_host_feature_setting(ts_data, value_heatmap, FW_HEATMAP);
}
exit:
FTS_DEBUG("%s %s.\n", tmpbuf,
(ret == 0) ? "successfully" : "unsuccessfully");
return ret;
}
int fts_set_grip_mode(struct fts_ts_data *ts_data, u8 grip_mode)
{
int ret = 0;
bool en = grip_mode % 2;
u8 value = en ? 0x00 : 0xAA;
u8 reg = FTS_REG_EDGE_MODE_EN;
ret = fts_write_reg_safe(reg, value);
if (ret == 0) {
fts_update_host_feature_setting(ts_data, en, FW_GRIP);
}
FTS_DEBUG("%s fw_grip(%d) %s.\n", en ? "Enable" : "Disable",
ts_data->enable_fw_grip,
(ret == 0) ? "successfully" : "unsuccessfully");
return ret;
}
int fts_set_palm_mode(struct fts_ts_data *ts_data, u8 palm_mode)
{
int ret = 0;
bool en = palm_mode % 2;
u8 value = en ? ENABLE : DISABLE;
u8 reg = FTS_REG_PALM_EN;
ret = fts_write_reg_safe(reg, value);
if (ret == 0) {
fts_update_host_feature_setting(ts_data, en, FW_PALM);
}
FTS_DEBUG("%s fw_palm(%d) %s.\n", en ? "Enable" : "Disable",
ts_data->enable_fw_palm,
(ret == 0) ? "successfully" : "unsuccessfully");
return ret;
}
int fts_set_continuous_mode(struct fts_ts_data *ts_data, bool en)
{
int ret = 0;
u8 value = en ? ENABLE : DISABLE;
u8 reg = FTS_REG_CONTINUOUS_EN;
mutex_lock(&ts_data->reg_lock);
if (!ts_data->is_deepsleep) {
ret = fts_write_reg_safe(reg, value);
if (ret == 0) {
ts_data->set_continuously_report = value;
fts_update_host_feature_setting(ts_data, en, FW_CONTINUOUS);
}
PR_LOGD("%s fw_continuous %s.\n", en ? "Enable" : "Disable",
(ret == 0) ? "successfully" : "unsuccessfully");
}
mutex_unlock(&ts_data->reg_lock);
return ret;
}
int fts_set_glove_mode(struct fts_ts_data *ts_data, bool en)
{
int ret = 0;
u8 value = en ? ENABLE : DISABLE;
u8 reg = FTS_REG_GLOVE_MODE_EN;
ret = fts_write_reg_safe(reg, value);
if (ret == 0) {
ts_data->glove_mode = value;
fts_update_host_feature_setting(ts_data, en, FW_GLOVE);
}
FTS_DEBUG("%s fw_glove %s.\n", en ? "Enable" : "Disable",
(ret == 0) ? "successfully" : "unsuccessfully");
return ret;
}
/**
* fts_update_feature_setting()
*
* Restore the feature settings after the device resume.
*
* @param
* [ in] ts_data: touch driver handle.
*
*/
void fts_update_feature_setting(struct fts_ts_data *ts_data)
{
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
fts_set_heatmap_mode(ts_data, ts_data->fw_default_heatmap_mode);
#endif
fts_set_grip_mode(ts_data, ts_data->enable_fw_grip);
fts_set_palm_mode(ts_data, ts_data->enable_fw_palm);
fts_set_continuous_mode(ts_data, ts_data->set_continuously_report);
fts_set_glove_mode(ts_data, ts_data->glove_mode);
}
static int fts_ts_suspend(struct device *dev)
{
int ret = 0;
struct fts_ts_data *ts_data = fts_data;
FTS_FUNC_ENTER();
if (ts_data->bus_refmask)
FTS_DEBUG("bus_refmask 0x%X\n", ts_data->bus_refmask);
if (ts_data->suspended) {
FTS_INFO("Already in suspend state");
return 0;
}
if (ts_data->fw_loading) {
FTS_INFO("fw upgrade in process, can't suspend");
return 0;
}
#if FTS_ESDCHECK_EN
fts_esdcheck_suspend();
#endif
if (ts_data->gesture_mode) {
fts_gesture_suspend(ts_data);
} else {
if (ts_data->bus_refmask == FTS_TS_BUS_REF_BUGREPORT &&
ktime_ms_delta(ktime_get(), ts_data->bugreport_ktime_start) >
30 * MSEC_PER_SEC) {
fts_ts_set_bus_ref(ts_data, FTS_TS_BUS_REF_BUGREPORT, false);
pm_relax(ts_data->dev);
ts_data->bugreport_ktime_start = 0;
FTS_DEBUG("Force release FTS_TS_BUS_REF_BUGREPORT reference bit.");
return -EBUSY;
}
/* Disable irq */
fts_irq_disable();
#if IS_ENABLED(CONFIG_TOUCHSCREEN_HEATMAP)
fts_set_heatmap_mode(ts_data, FW_HEATMAP_MODE_DISABLE);
#endif
FTS_DEBUG("make TP enter into sleep mode");
mutex_lock(&ts_data->reg_lock);
ret = fts_write_reg(FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP);
ts_data->is_deepsleep = true;
mutex_unlock(&ts_data->reg_lock);
if (ret < 0)
FTS_ERROR("set TP to sleep mode fail, ret=%d", ret);
if (!ts_data->ic_info.is_incell) {
#if FTS_POWER_SOURCE_CUST_EN
ret = fts_power_source_suspend(ts_data);
if (ret < 0) {
FTS_ERROR("power enter suspend fail");
}
#endif
}
}
fts_release_all_finger();
ts_data->suspended = true;
FTS_FUNC_EXIT();
return 0;
}
/**
* Report a finger down event on the long press gesture area then immediately
* report a cancel event(MT_TOOL_PALM).
*/
static void fts_report_cancel_event(struct fts_ts_data *ts_data)
{
FTS_INFO("Report cancel event for UDFPS");
mutex_lock(&ts_data->report_mutex);
/* Finger down on UDFPS area. */
input_mt_slot(ts_data->input_dev, 0);
input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 1);
input_report_abs(ts_data->input_dev, ABS_MT_POSITION_X,
ts_data->fts_gesture_data.coordinate_x[0]);
input_report_abs(ts_data->input_dev, ABS_MT_POSITION_Y,
ts_data->fts_gesture_data.coordinate_y[0]);
input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MAJOR,
ts_data->fts_gesture_data.major[0]);
input_report_abs(ts_data->input_dev, ABS_MT_TOUCH_MINOR,
ts_data->fts_gesture_data.minor[0]);
#ifndef SKIP_PRESSURE
input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 1);
#endif
input_report_abs(ts_data->input_dev, ABS_MT_ORIENTATION,
ts_data->fts_gesture_data.orientation[0]);
input_sync(ts_data->input_dev);
/* Report MT_TOOL_PALM for canceling the touch event. */
input_mt_slot(ts_data->input_dev, 0);
input_report_key(ts_data->input_dev, BTN_TOUCH, 1);
input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_PALM, 1);
input_sync(ts_data->input_dev);
/* Release touches. */
input_mt_slot(ts_data->input_dev, 0);
#ifndef SKIP_PRESSURE
input_report_abs(ts_data->input_dev, ABS_MT_PRESSURE, 0);
#endif
input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, 0);
input_report_abs(ts_data->input_dev, ABS_MT_TRACKING_ID, -1);
input_report_key(ts_data->input_dev, BTN_TOUCH, 0);
input_sync(ts_data->input_dev);
mutex_unlock(&ts_data->report_mutex);
}
static void fts_check_finger_status(struct fts_ts_data *ts_data)
{
int ret = 0;
u8 power_mode = FTS_REG_POWER_MODE_SLEEP;
ktime_t timeout = ktime_add_ms(ktime_get(), 500); /* 500ms. */
/* If power mode is deep sleep mode, then reurn. */
ret = fts_read_reg(FTS_REG_POWER_MODE, &power_mode);
if (ret)
return;
if (power_mode == FTS_REG_POWER_MODE_SLEEP)
return;
while (ktime_get() < timeout) {
ret = fts_gesture_readdata(ts_data, false);
if (ret)
break;
if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_DOWN) {
msleep(30);
continue;
}
if (ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_LPTW_UP ||
ts_data->fts_gesture_data.gesture_id == FTS_GESTURE_ID_STTW) {
fts_report_cancel_event(ts_data);
}
break;
}
}
static int fts_ts_resume(struct device *dev)
{
struct fts_ts_data *ts_data = fts_data;
int ret = 0;
FTS_FUNC_ENTER();
if (!ts_data->suspended) {
FTS_DEBUG("Already in awake state");
return 0;
}
fts_release_all_finger();
if (!ts_data->ic_info.is_incell) {
if (!ts_data->gesture_mode) {
#if FTS_POWER_SOURCE_CUST_EN
fts_power_source_resume(ts_data);
#endif
fts_check_finger_status(ts_data);
}
fts_reset_proc(FTS_RESET_INTERVAL);
}
ret = fts_wait_tp_to_valid();
if (ret != 0) {
FTS_ERROR("Resume has been cancelled by wake up timeout");
#if FTS_POWER_SOURCE_CUST_EN
if (!ts_data->gesture_mode)
fts_power_source_suspend(ts_data);
#endif
return ret;
}
ts_data->is_deepsleep = false;
fts_ex_mode_recovery(ts_data);
#if FTS_ESDCHECK_EN
fts_esdcheck_resume();
#endif
if (ts_data->gesture_mode) {
fts_gesture_resume(ts_data);
} else {
fts_irq_enable();
}
ts_data->suspended = false;
FTS_FUNC_EXIT();
return 0;
}
#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
static int fts_pm_suspend(struct device *dev)
{
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
FTS_INFO("system enters into pm_suspend");
ts_data->pm_suspend = true;
reinit_completion(&ts_data->pm_completion);
return 0;
}
static int fts_pm_resume(struct device *dev)
{
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
FTS_INFO("system resumes from pm_suspend");
ts_data->pm_suspend = false;
complete(&ts_data->pm_completion);
return 0;
}
static const struct dev_pm_ops fts_dev_pm_ops = {
.suspend = fts_pm_suspend,
.resume = fts_pm_resume,
};
#endif
/*****************************************************************************
* TP Driver
*****************************************************************************/
static int fts_ts_probe(struct spi_device *spi)
{
int ret = 0;
struct fts_ts_data *ts_data = NULL;
FTS_INFO("Touch Screen(SPI BUS) driver proboe...");
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi->rt = true;
ret = spi_setup(spi);
if (ret) {
FTS_ERROR("spi setup fail");
return ret;
}
/* malloc memory for global struct variable */
ts_data = (struct fts_ts_data *)kzalloc(sizeof(*ts_data), GFP_KERNEL);
if (!ts_data) {
FTS_ERROR("allocate memory for fts_data fail");
return -ENOMEM;
}
fts_data = ts_data;
ts_data->spi = spi;
ts_data->dev = &spi->dev;
ts_data->log_level = FTS_KEY_LOG_LEVEL;
ts_data->bus_type = FTS_BUS_TYPE_SPI_V2;
spi_set_drvdata(spi, ts_data);
ret = fts_ts_probe_entry(ts_data);
if (ret) {
FTS_ERROR("Touch Screen(SPI BUS) driver probe fail");
kfree_safe(ts_data);
return ret;
}
FTS_INFO("Touch Screen(SPI BUS) driver probe successfully");
return 0;
}
static int fts_ts_remove(struct spi_device *spi)
{
return fts_ts_remove_entry(spi_get_drvdata(spi));
}
static void fts_ts_shutdown(struct spi_device *spi)
{
fts_ts_remove(spi);
}
static const struct spi_device_id fts_ts_id[] = {
{FTS_DRIVER_NAME, 0},
{},
};
static const struct of_device_id fts_dt_match[] = {
{.compatible = "focaltech,ts", },
{},
};
MODULE_DEVICE_TABLE(of, fts_dt_match);
static struct spi_driver fts_ts_driver = {
.probe = fts_ts_probe,
.remove = fts_ts_remove,
.shutdown = fts_ts_shutdown,
.driver = {
.name = FTS_DRIVER_NAME,
.owner = THIS_MODULE,
#if defined(CONFIG_PM) && FTS_PATCH_COMERR_PM
.pm = &fts_dev_pm_ops,
#endif
.of_match_table = of_match_ptr(fts_dt_match),
},
.id_table = fts_ts_id,
};
static int __init fts_ts_init(void)
{
int ret = 0;
FTS_FUNC_ENTER();
ret = spi_register_driver(&fts_ts_driver);
if ( ret != 0 ) {
FTS_ERROR("Focaltech touch screen driver init failed!");
}
FTS_FUNC_EXIT();
return ret;
}
static void __exit fts_ts_exit(void)
{
spi_unregister_driver(&fts_ts_driver);
}
module_init(fts_ts_init);
module_exit(fts_ts_exit);
MODULE_AUTHOR("FocalTech Driver Team");
MODULE_DESCRIPTION("FocalTech Touchscreen Driver");
MODULE_LICENSE("GPL v2");