963 lines
28 KiB
C
Executable file
963 lines
28 KiB
C
Executable file
/*
|
|
*
|
|
* 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_debug.c
|
|
*
|
|
* Author: Focaltech Driver Team
|
|
*
|
|
* Created: 2023-12-01
|
|
*
|
|
* Abstract: Fw Debug
|
|
*
|
|
* Version: V1.0
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Included header files
|
|
*****************************************************************************/
|
|
#include "focaltech_core.h"
|
|
|
|
/*****************************************************************************
|
|
* Private constant and macro definitions using #define
|
|
*****************************************************************************/
|
|
#define FTS_REG_FW_DEBUG_EN 0x9E
|
|
#define FTS_REG_TPINFO 0x96
|
|
#define FTS_REG_DBGCFG 0x9D
|
|
|
|
#define DEFAULT_VAL_REG01 0xFFFF
|
|
#define DEFAULT_MAX_FRAME_NUM 10000
|
|
#define MAX_SIZE_TP_INFO 8
|
|
#define MAX_SIZE_DBG_CFG 16
|
|
#define MAX_COUNT_READ_REGFB 3
|
|
|
|
/*****************************************************************************
|
|
* Private enumerations, structures and unions using typedef
|
|
*****************************************************************************/
|
|
typedef enum {
|
|
FRAME_WAITQ_DEFAULT,
|
|
FRAME_WAITQ_WAIT,
|
|
FRAME_WAITQ_WAKEUP,
|
|
} FRAME_WAITQ_FLAG;
|
|
|
|
struct fwdbg_frame {
|
|
u64 tv;
|
|
unsigned char value[0];
|
|
};
|
|
|
|
struct fwdbg_queue {
|
|
int head;
|
|
int tail;
|
|
int count;
|
|
int max_count;
|
|
int elem_size;
|
|
u8 *buffer;
|
|
struct mutex mutexq;
|
|
};
|
|
|
|
struct fwdbg_config {
|
|
int total_len;
|
|
int dbgoff;
|
|
int dbghdr_len;
|
|
int diff_len;
|
|
int addinfo_len;
|
|
int regfa_len;
|
|
int regfb_len;
|
|
int tx;
|
|
int rx;
|
|
};
|
|
|
|
struct fts_fwdbg {
|
|
struct fts_ts_data *ts_data;
|
|
struct proc_dir_entry *proc_fwdbg;
|
|
struct mutex mutex;
|
|
wait_queue_head_t frame_waitq;
|
|
struct fwdbg_queue q;
|
|
struct fwdbg_config cfg;
|
|
int max_frame_num; //maximum frame number kept in memory
|
|
int frame_size; // is equal to size of one frame
|
|
int touch_size_bak;
|
|
int proc_ppos;
|
|
int frame_waitq_flag;
|
|
int reg01_val;
|
|
char *proc_frame; /* save a frame value comes from queue */
|
|
unsigned char *regfb_val;
|
|
unsigned char *regfa_val;
|
|
|
|
bool queue_stop;
|
|
bool frame_logging;
|
|
bool frame_block;
|
|
};
|
|
|
|
/*****************************************************************************
|
|
* Global variable or extern global variabls/functions
|
|
*****************************************************************************/
|
|
static struct fts_fwdbg *fts_fwdbg_data;
|
|
|
|
/*****************************************************************************
|
|
* Static function prototypes
|
|
*****************************************************************************/
|
|
static u64 fwdbg_get_timestamp(void)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
|
|
ktime_t tv;
|
|
tv = ktime_get_real() / 1000;
|
|
return (u64)tv;
|
|
#else
|
|
struct timeval tv;
|
|
do_gettimeofday(&tv);
|
|
return (u64)(((u64)tv.tv_sec * 1000000) + tv.tv_usec);
|
|
#endif
|
|
}
|
|
|
|
static int dbgq_open(struct fwdbg_queue *q, int max_framenum, int frame_size)
|
|
{
|
|
if (!q || !max_framenum || !frame_size) {
|
|
FTS_ERROR("q is null/max_framenum(%d)/frame_size(%d) is invalid", max_framenum, frame_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (q->buffer) {
|
|
vfree(q->buffer);
|
|
q->buffer = NULL;
|
|
}
|
|
|
|
if (!q->buffer) {
|
|
q->head = q->tail = q->count = 0;
|
|
q->max_count = max_framenum;
|
|
q->elem_size = frame_size;
|
|
FTS_INFO("queque,max_count=%d,elem_size=%d", q->max_count, q->elem_size);
|
|
q->buffer = vmalloc(q->max_count * q->elem_size);
|
|
if (!q->buffer) {
|
|
FTS_ERROR("malloc queue buffer failed");
|
|
return -ENOMEM;
|
|
}
|
|
memset(q->buffer, 0, q->max_count * q->elem_size);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool dbgq_full(struct fwdbg_queue *q)
|
|
{
|
|
return q->count == q->max_count;
|
|
}
|
|
|
|
static bool dbgq_empty(struct fwdbg_queue *q)
|
|
{
|
|
return q->count == 0;
|
|
}
|
|
|
|
static int dbgq_enqueue(struct fwdbg_queue *q, u8 *val, u64 timestamp)
|
|
{
|
|
struct fwdbg_frame *tail_elem = NULL;
|
|
|
|
if (!q || !val || !q->buffer) {
|
|
FTS_ERROR("q/val/buffer is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&(q->mutexq));
|
|
tail_elem = (struct fwdbg_frame *)&q->buffer[q->tail * q->elem_size];
|
|
tail_elem->tv = timestamp;
|
|
memcpy(tail_elem->value, val, q->elem_size);
|
|
q->tail = (q->tail + 1) % q->max_count;
|
|
if (dbgq_full(q)) {
|
|
q->head = (q->head + 1) % q->max_count;
|
|
} else {
|
|
q->count++;
|
|
}
|
|
mutex_unlock(&(q->mutexq));
|
|
return 0;
|
|
}
|
|
|
|
static int dbgq_dequeue(struct fwdbg_queue *q, u8 *val)
|
|
{
|
|
if (!q || !val || !q->buffer) {
|
|
FTS_ERROR("q/val/buffer is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&(q->mutexq));
|
|
if (dbgq_empty(q)) {
|
|
mutex_unlock(&(q->mutexq));
|
|
return 1;
|
|
}
|
|
memcpy(val, &q->buffer[q->head * q->elem_size], q->elem_size);
|
|
q->head = (q->head + 1) % q->max_count;
|
|
q->count--;
|
|
mutex_unlock(&(q->mutexq));
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Name: dbgq_dequeue_to_proc
|
|
* Brief: dequeue frames from queue, and send it to proc read buffer.
|
|
* Input: @q
|
|
* @buff_maxcount, the maximum frame count
|
|
* Output:@buff, address of user space buffer
|
|
* Return:size that send to the buff, or 0 if queue is null, or error code
|
|
*****************************************************************************/
|
|
static int dbgq_dequeue_to_proc(struct fwdbg_queue *q, char __user *buff, int buff_maxcount)
|
|
{
|
|
int valid_count = 0;
|
|
int i = 0;
|
|
char *head_elem = NULL;
|
|
if (!q || !buff || !q->buffer || !buff_maxcount) {
|
|
FTS_ERROR("q/buff/buffer is null/buff_maxcount is 0");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&(q->mutexq));
|
|
if (dbgq_empty(q)) {
|
|
mutex_unlock(&(q->mutexq));
|
|
return 0;
|
|
}
|
|
valid_count = (buff_maxcount > q->count) ? q->count : buff_maxcount;
|
|
for (i = 0; i < valid_count; i++) {
|
|
head_elem = (char *)&q->buffer[q->head * q->elem_size];
|
|
if (copy_to_user(buff + i * q->elem_size, head_elem, q->elem_size)) {
|
|
FTS_ERROR("copy debug frame(%d) to user failed", i);
|
|
mutex_unlock(&(q->mutexq));
|
|
return i * q->elem_size;
|
|
}
|
|
q->head = (q->head + 1) % q->max_count;
|
|
q->count--;
|
|
}
|
|
mutex_unlock(&(q->mutexq));
|
|
return valid_count * q->elem_size;
|
|
}
|
|
|
|
static int dbgq_release(struct fwdbg_queue *q)
|
|
{
|
|
q->head = q->tail = q->count = 0;
|
|
q->max_count = q->elem_size = 0;
|
|
if (q && q->buffer) {
|
|
vfree(q->buffer);
|
|
q->buffer = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Name: proc_get_one_frame
|
|
* Brief: Get a frame, and send the frame data to proc read buffer.
|
|
* Input: @dbg
|
|
* @buff_maxsize, the maximum size of buff
|
|
* Output:@buff, address of user space buffer
|
|
* Return:size that send to the buff, or 0 if queue is null, or error code
|
|
*****************************************************************************/
|
|
static int proc_get_one_frame(struct fts_fwdbg *dbg, char __user *buff, int buff_maxsize)
|
|
{
|
|
int ret = 0;
|
|
int frame_remaining_size = 0;
|
|
int valid_size = 0;
|
|
|
|
if (!dbg || !buff || !dbg->proc_frame || !buff_maxsize) {
|
|
FTS_ERROR("dbg/buff/proc_frame is null/buff_maxsize is 0");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dbg->proc_ppos == 0) {
|
|
ret = dbgq_dequeue(&dbg->q, dbg->proc_frame);
|
|
if (ret < 0) {
|
|
FTS_ERROR("get a frame from queue failed");
|
|
return ret;
|
|
} else if (ret == 1) {
|
|
/* queque is null */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
frame_remaining_size = dbg->frame_size - dbg->proc_ppos;
|
|
valid_size = (frame_remaining_size > buff_maxsize) ? buff_maxsize : frame_remaining_size;
|
|
if (copy_to_user(buff, &dbg->proc_frame[dbg->proc_ppos], valid_size)) {
|
|
FTS_ERROR("copy debug frame to user failed");
|
|
return -EFAULT;
|
|
}
|
|
dbg->proc_ppos = (dbg->proc_ppos + valid_size) % dbg->frame_size;
|
|
|
|
return valid_size;
|
|
}
|
|
|
|
static int fts_fwdbg_get_cfg(struct fts_fwdbg *dbg)
|
|
{
|
|
int ret = 0;
|
|
u8 cmd = 0;
|
|
u8 tp_info[MAX_SIZE_TP_INFO] = { 0 };
|
|
u8 dbg_cfg[MAX_SIZE_DBG_CFG] = { 0 };
|
|
cmd = FTS_REG_TPINFO;
|
|
ret = fts_read(&cmd, 1, tp_info, MAX_SIZE_TP_INFO);
|
|
if (ret < 0) {
|
|
FTS_ERROR("read tp info failed");
|
|
return ret;
|
|
}
|
|
cmd = FTS_REG_DBGCFG;
|
|
ret = fts_read(&cmd, 1, dbg_cfg, MAX_SIZE_DBG_CFG);
|
|
if (ret < 0) {
|
|
FTS_ERROR("read debug config failed");
|
|
return ret;
|
|
}
|
|
|
|
dbg->cfg.total_len = (dbg_cfg[2] << 8) + dbg_cfg[3];
|
|
dbg->cfg.dbgoff = dbg_cfg[4];
|
|
dbg->cfg.dbghdr_len = dbg_cfg[5];
|
|
dbg->cfg.diff_len = (dbg_cfg[6] << 8) + dbg_cfg[7];
|
|
dbg->cfg.addinfo_len = (dbg_cfg[8] << 8) + dbg_cfg[9];
|
|
dbg->cfg.regfb_len = (dbg_cfg[10] << 8) + dbg_cfg[11];
|
|
dbg->cfg.regfa_len = (dbg_cfg[12] << 8) + dbg_cfg[13];
|
|
dbg->cfg.tx = tp_info[2];
|
|
dbg->cfg.rx = tp_info[3];
|
|
return 0;
|
|
}
|
|
|
|
static int fts_fwdbg_enable(struct fts_fwdbg *dbg, int value)
|
|
{
|
|
int ret = 0;
|
|
if (!dbg || !dbg->ts_data || !value) {
|
|
FTS_ERROR("fwdbg/ts_data is null/value(%d) is invalid", value);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = fts_fwdbg_get_cfg(dbg);
|
|
if (ret < 0) {
|
|
FTS_ERROR("get cfg from tp failed");
|
|
return ret;
|
|
}
|
|
|
|
if ((dbg->cfg.total_len < FTS_TOUCH_DATA_LEN_V2) || (dbg->cfg.total_len > FTS_MAX_TOUCH_BUF)) {
|
|
FTS_ERROR("report buffer length(%d),not in[%d,%d]", dbg->cfg.total_len,
|
|
FTS_TOUCH_DATA_LEN_V2, FTS_MAX_TOUCH_BUF);
|
|
return -EIO;
|
|
}
|
|
|
|
dbg->frame_size = dbg->cfg.total_len + sizeof(struct fwdbg_frame);
|
|
FTS_INFO("FwDebug enable,max_frame_num=%d,frame_size=%d", dbg->max_frame_num, dbg->frame_size);
|
|
if (!dbg->frame_logging) {
|
|
ret = dbgq_open(&dbg->q, dbg->max_frame_num, dbg->frame_size);
|
|
if (ret < 0) {
|
|
FTS_ERROR("dbgq_open failed");
|
|
goto dbgen_err;
|
|
}
|
|
}
|
|
|
|
if (dbg->cfg.regfb_len) {
|
|
if (dbg->regfb_val) {
|
|
vfree(dbg->regfb_val);
|
|
dbg->regfb_val = NULL;
|
|
}
|
|
dbg->regfb_val = vmalloc(dbg->cfg.regfb_len);
|
|
if (!dbg->regfb_val) {
|
|
ret = -ENOMEM;
|
|
goto dbgen_err;
|
|
}
|
|
}
|
|
|
|
if (dbg->cfg.regfa_len) {
|
|
if (dbg->regfa_val) {
|
|
vfree(dbg->regfa_val);
|
|
dbg->regfa_val = NULL;
|
|
}
|
|
dbg->regfa_val = vmalloc(dbg->cfg.regfa_len);
|
|
if (!dbg->regfa_val) {
|
|
ret = -ENOMEM;
|
|
goto dbgen_err;
|
|
}
|
|
}
|
|
|
|
dbg->touch_size_bak = dbg->ts_data->touch_size;
|
|
dbg->ts_data->touch_size = dbg->cfg.total_len;
|
|
ret = fts_write_reg(FTS_REG_FW_DEBUG_EN, value);
|
|
if (ret < 0) {
|
|
FTS_ERROR("write FwDebug to enable failed");
|
|
goto dbgen_err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
dbgen_err:
|
|
if (dbg->regfa_val) {
|
|
vfree(dbg->regfa_val);
|
|
dbg->regfa_val = NULL;
|
|
}
|
|
if (dbg->regfb_val) {
|
|
vfree(dbg->regfb_val);
|
|
dbg->regfb_val = NULL;
|
|
}
|
|
dbgq_release(&dbg->q);
|
|
dbg->ts_data->touch_size = dbg->touch_size_bak;
|
|
fts_write_reg(FTS_REG_FW_DEBUG_EN, 0);
|
|
return ret;
|
|
}
|
|
|
|
static int fts_fwdbg_disable(struct fts_fwdbg *dbg)
|
|
{
|
|
int ret = 0;
|
|
if (!dbg || !dbg->ts_data) {
|
|
FTS_ERROR("fwdbg/ts_data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!dbg->frame_logging) {
|
|
dbgq_release(&dbg->q);
|
|
}
|
|
|
|
if (dbg->regfa_val) {
|
|
vfree(dbg->regfa_val);
|
|
dbg->regfa_val = NULL;
|
|
}
|
|
if (dbg->regfb_val) {
|
|
vfree(dbg->regfb_val);
|
|
dbg->regfb_val = NULL;
|
|
}
|
|
|
|
dbg->ts_data->touch_size = dbg->touch_size_bak;
|
|
ret = fts_write_reg(FTS_REG_FW_DEBUG_EN, 0);
|
|
if (ret < 0) {
|
|
FTS_ERROR("write FwDebug to disable failed");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void fts_logging_frame(struct fwdbg_config *cfg, u8 *frame_buf, u64 timestamp)
|
|
{
|
|
int i = 0;
|
|
int n = 0;
|
|
int index = 0;
|
|
char logbuf[512] = { 0 };
|
|
if (!cfg | !frame_buf)
|
|
return ;
|
|
|
|
FTS_DEBUG("logging a frame,timestamp=%lld", timestamp);
|
|
for (i = 0; i < cfg->dbgoff; i++) {
|
|
n += snprintf(logbuf + n, 512 - n, "%02x,", frame_buf[i]);
|
|
if (n >= 512) break;
|
|
}
|
|
FTS_DEBUG("%s", logbuf);
|
|
|
|
/**/
|
|
n = 0;
|
|
index = cfg->dbgoff;
|
|
for (i = 0; i < cfg->dbghdr_len; i++) {
|
|
n += snprintf(logbuf + n, 512 - n, "%02x,", frame_buf[index + i]);
|
|
if (n >= 512) break;
|
|
}
|
|
FTS_DEBUG("%s", logbuf);
|
|
|
|
|
|
index = cfg->dbgoff + cfg->dbghdr_len;
|
|
FTS_DEBUG("%d", frame_buf[index]);
|
|
n = 0;
|
|
index = cfg->dbgoff + cfg->dbghdr_len + 1;
|
|
for (i = 0; i < cfg->diff_len; i += 2) {
|
|
n += snprintf(logbuf + n, 512 - n, "%d,", (short)((frame_buf[index + i] << 8) + frame_buf[index + i + 1]));
|
|
if (n >= 512) break;
|
|
else if (((i + 1) % cfg->rx) == 0) {
|
|
FTS_DEBUG("%s", logbuf);
|
|
n = 0;
|
|
}
|
|
}
|
|
|
|
n = 0;
|
|
index = cfg->dbgoff + cfg->dbghdr_len + cfg->diff_len;
|
|
for (i = 0; i < cfg->addinfo_len; i += 2) {
|
|
n += snprintf(logbuf + n, 512 - n, "%d,", (short)((frame_buf[index + i] << 8) + frame_buf[index + i + 1]));
|
|
if (n >= 512) break;
|
|
else if (((i + 1) % cfg->rx) == 0) {
|
|
FTS_DEBUG("%s", logbuf);
|
|
n = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fts_logging_regfb(struct fts_fwdbg *dbg, u64 timestamp)
|
|
{
|
|
int ret = 0;
|
|
int i = 0;
|
|
int n = 0;
|
|
u8 cmd = 0xFB;
|
|
char logbuf[512] = { 0 };
|
|
|
|
if (!dbg || ! dbg->regfb_val || !dbg->cfg.regfb_len || !dbg->cfg.rx) {
|
|
FTS_ERROR("dbg/regfb_val/regfb_len(%d)/rx(%d) is invalid", dbg->cfg.regfb_len, dbg->cfg.rx);
|
|
return ;
|
|
}
|
|
|
|
ret = fts_read(&cmd, 1, dbg->regfb_val, dbg->cfg.regfb_len);
|
|
if (ret < 0) {
|
|
FTS_ERROR("read regfb failed,ret=%d", ret);
|
|
return ;
|
|
}
|
|
|
|
FTS_DEBUG("logging regfb,timestamp=%lld", timestamp);
|
|
for (i = 0; i < dbg->cfg.regfb_len; i += 2) {
|
|
n += snprintf(logbuf + n, 512 - n, "%d,", (short)((dbg->regfb_val[i] << 8) + dbg->regfb_val[i + 1]));
|
|
if (n >= 512) break;
|
|
else if (((i + 1) % dbg->cfg.rx) == 0) {
|
|
FTS_DEBUG("%s", logbuf);
|
|
n = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fts_logging_regfa(struct fts_fwdbg *dbg, u64 timestamp)
|
|
{
|
|
int ret = 0;
|
|
int i = 0;
|
|
int n = 0;
|
|
u8 cmd = 0xFA;
|
|
int line_count = 0;
|
|
char logbuf[512] = { 0 };
|
|
|
|
if (!dbg || ! dbg->regfa_val || !dbg->cfg.regfa_len || !dbg->cfg.rx) {
|
|
FTS_ERROR("dbg/regfa_val/regfa_len(%d)/rx(%d) is invalid", dbg->cfg.regfa_len, dbg->cfg.rx);
|
|
return ;
|
|
}
|
|
|
|
ret = fts_read(&cmd, 1, dbg->regfa_val, dbg->cfg.regfa_len);
|
|
if (ret < 0) {
|
|
FTS_ERROR("read regfa failed,ret=%d", ret);
|
|
return ;
|
|
}
|
|
|
|
FTS_DEBUG("logging regfa,timestamp=%lld", timestamp);
|
|
line_count = dbg->cfg.rx * 2;
|
|
for (i = 0; i < dbg->cfg.regfa_len; i++) {
|
|
n += snprintf(logbuf + n, 512 - n, "%02X,", dbg->regfa_val[i]);
|
|
if (n >= 512) break;
|
|
else if (((i + 1) % line_count) == 0) {
|
|
FTS_DEBUG("%s", logbuf);
|
|
n = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int fts_fwdbg_readdata(struct fts_ts_data *ts_data, u8 *buf)
|
|
{
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
u64 timestamp = 0;
|
|
if (!ts_data || !buf || !dbg) {
|
|
FTS_ERROR("ts_data/buf/dbg is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ts_data->fwdbg_support)
|
|
return 0;
|
|
|
|
timestamp = fwdbg_get_timestamp();
|
|
if (dbg->frame_logging) {
|
|
fts_logging_frame(&dbg->cfg, buf, timestamp);
|
|
} else if (!dbg->queue_stop) {
|
|
dbgq_enqueue(&dbg->q, buf, timestamp);
|
|
if (dbg->frame_waitq_flag == FRAME_WAITQ_WAIT) {
|
|
dbg->frame_waitq_flag = FRAME_WAITQ_WAKEUP;
|
|
wake_up_interruptible(&dbg->frame_waitq);
|
|
}
|
|
}
|
|
|
|
if (dbg->reg01_val == DEFAULT_VAL_REG01)
|
|
dbg->reg01_val = buf[0];
|
|
else if (buf[0] != dbg->reg01_val) {
|
|
if (!dbg->frame_logging) fts_logging_frame(&dbg->cfg, buf, timestamp);
|
|
if (dbg->cfg.regfb_len) fts_logging_regfb(dbg, timestamp);
|
|
dbg->reg01_val = buf[0];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* proc node:fts_fwdbg */
|
|
static ssize_t fts_fwdbg_read(struct file *filp, char __user *buff, size_t count, loff_t *ppos)
|
|
{
|
|
int read_byte_num = (int)count;
|
|
int cnt = 0;
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0))
|
|
struct fts_fwdbg *dbg = pde_data(file_inode(filp));
|
|
#else
|
|
struct fts_fwdbg *dbg = PDE_DATA(file_inode(filp));
|
|
#endif
|
|
if (!dbg || !dbg->ts_data || !dbg->ts_data->fwdbg_support) {
|
|
FTS_ERROR("dbg/ts_data is null/fwdbg isn't support");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dbg->frame_logging) {
|
|
FTS_ERROR("frame logging is null,not return frame data");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((dbg->frame_block) && (read_byte_num < dbg->frame_size)) {
|
|
FTS_ERROR("in block mode, proc count(%d) < frame size(%d)", read_byte_num, dbg->frame_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dbgq_empty(&dbg->q) && dbg->frame_block) {
|
|
dbg->queue_stop = false;
|
|
dbg->frame_waitq_flag = FRAME_WAITQ_WAIT;
|
|
wait_event_interruptible(dbg->frame_waitq, dbg->frame_waitq_flag == FRAME_WAITQ_WAKEUP);
|
|
}
|
|
|
|
if (read_byte_num >= dbg->frame_size)
|
|
cnt = dbgq_dequeue_to_proc(&dbg->q, buff, read_byte_num / dbg->frame_size);
|
|
else
|
|
cnt = proc_get_one_frame(dbg, buff, read_byte_num);
|
|
|
|
FTS_DEBUG("cnt=%d", cnt);
|
|
return cnt;
|
|
}
|
|
|
|
static int fts_fwdbg_open(struct inode *inode, struct file *file)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0))
|
|
struct fts_fwdbg *dbg = pde_data(inode);
|
|
#else
|
|
struct fts_fwdbg *dbg = PDE_DATA(inode);
|
|
#endif
|
|
if (!dbg || !dbg->ts_data || !dbg->ts_data->fwdbg_support) {
|
|
FTS_ERROR("dbg/ts_data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
FTS_DEBUG("frame,block=%d,logging=%d,size=%d,macount=%d,queuecount=%d", dbg->frame_block,
|
|
dbg->frame_logging, dbg->frame_size, dbg->max_frame_num, dbg->q.count);
|
|
if ((!dbg->frame_logging) && (dbg->q.elem_size != dbg->frame_size)) {
|
|
FTS_ERROR("elem_size(%d) != frame_size(%d)", dbg->q.elem_size, dbg->frame_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dbg->proc_ppos = 0;
|
|
dbg->queue_stop = false;
|
|
if (!dbg->frame_block) {
|
|
dbg->queue_stop = true;
|
|
/* get fa/fb info */
|
|
if (dbg->cfg.regfb_len) fts_logging_regfb(dbg, fwdbg_get_timestamp());
|
|
if (dbg->cfg.regfa_len) fts_logging_regfa(dbg, fwdbg_get_timestamp());
|
|
|
|
if (dbg->proc_frame) {
|
|
vfree(dbg->proc_frame);
|
|
dbg->proc_frame = NULL;
|
|
}
|
|
dbg->proc_frame = vmalloc(dbg->frame_size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fts_fwdbg_release(struct inode *inode, struct file *file)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0))
|
|
struct fts_fwdbg *dbg = pde_data(inode);
|
|
#else
|
|
struct fts_fwdbg *dbg = PDE_DATA(inode);
|
|
#endif
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dbg->proc_frame) {
|
|
vfree(dbg->proc_frame);
|
|
dbg->proc_frame = NULL;
|
|
}
|
|
dbg->proc_ppos = 0;
|
|
dbg->queue_stop = false;
|
|
return 0;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
|
|
static const struct proc_ops fts_fwdbg_fops = {
|
|
.proc_open = fts_fwdbg_open,
|
|
.proc_read = fts_fwdbg_read,
|
|
.proc_release = fts_fwdbg_release,
|
|
};
|
|
#else
|
|
static const struct file_operations fts_fwdbg_fops = {
|
|
.open = fts_fwdbg_open,
|
|
.read = fts_fwdbg_read,
|
|
.release = fts_fwdbg_release,
|
|
};
|
|
#endif
|
|
|
|
|
|
/* sysfs node:fts_fwdbg_mode */
|
|
static ssize_t fts_fwdbg_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int count = 0;
|
|
u8 val = 0;
|
|
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg || !ts_data) {
|
|
FTS_ERROR("dbg/ts_data is null");
|
|
return count;
|
|
}
|
|
mutex_lock(&dbg->mutex);
|
|
fts_read_reg(FTS_REG_FW_DEBUG_EN, &val);
|
|
count = snprintf(buf, PAGE_SIZE, "FwDebug support:%d,value:0x%x\n", ts_data->fwdbg_support, ts_data->fwdbg_value);
|
|
count += snprintf(buf + count, PAGE_SIZE, "Reg(0x9E)=0x%x\n", val);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fts_fwdbg_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int n = 0;
|
|
int value = 0;
|
|
struct fts_ts_data *ts_data = dev_get_drvdata(dev);
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg || !ts_data) {
|
|
FTS_ERROR("dbg/ts_data is null");
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&dbg->mutex);
|
|
n = sscanf(buf, "%d", &value);
|
|
if ((n == 1) && (!!value ^ ts_data->fwdbg_support)) {
|
|
if (value) {
|
|
if (0 == fts_fwdbg_enable(dbg, value)) {
|
|
ts_data->fwdbg_value = (u8)value;
|
|
ts_data->fwdbg_support = ENABLE;
|
|
}
|
|
} else {
|
|
ts_data->fwdbg_support = DISABLE;
|
|
fts_fwdbg_disable(dbg);
|
|
}
|
|
} else FTS_INFO("n(%d)!=/value(%d)==fwdbg_support(%d)", n, !!value, ts_data->fwdbg_support);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
/* sysfs node:fts_fwdbg_maxcount */
|
|
static ssize_t fts_fwdbg_maxcount_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int count = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
mutex_lock(&dbg->mutex);
|
|
count = snprintf(buf, PAGE_SIZE, "FwDebug,maximum frame count:%d\n", dbg->max_frame_num);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fts_fwdbg_maxcount_store(
|
|
struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int n = 0;
|
|
int value = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg || !dbg->ts_data) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&dbg->mutex);
|
|
n = sscanf(buf, "%d", &value);
|
|
if ((n == 1) && (value > 0) && !dbg->ts_data->fwdbg_support) {
|
|
FTS_INFO("maximum frame count: %d->%d", dbg->max_frame_num, value);
|
|
dbg->max_frame_num = value;
|
|
} else FTS_INFO("n(%d)!=1/value(%d)=0/fwdbg_support(%d)!=0", n, value, dbg->ts_data->fwdbg_support);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
/* sysfs node:fts_fwdbg_logging */
|
|
static ssize_t fts_fwdbg_logging_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int count = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
mutex_lock(&dbg->mutex);
|
|
count = snprintf(buf, PAGE_SIZE, "FwDebug,frame logging:%d\n", dbg->frame_logging);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fts_fwdbg_logging_store(
|
|
struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int n = 0;
|
|
int value = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&dbg->mutex);
|
|
n = sscanf(buf, "%d", &value);
|
|
if ((n == 1) && !dbg->ts_data->fwdbg_support) {
|
|
FTS_INFO("frame logging: %d->%d", dbg->frame_logging, !!value);
|
|
dbg->frame_logging = !!value;
|
|
} else FTS_INFO("n(%d)!=1/fwdbg_support(%d)!=0", n, dbg->ts_data->fwdbg_support);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
/* sysfs node:fts_fwdbg_block */
|
|
static ssize_t fts_fwdbg_block_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
int count = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
mutex_lock(&dbg->mutex);
|
|
count = snprintf(buf, PAGE_SIZE, "frame block:%d\n", dbg->frame_block);
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fts_fwdbg_block_store(
|
|
struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int n = 0;
|
|
int value = 0;
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (!dbg) {
|
|
FTS_ERROR("dbg is null");
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&dbg->mutex);
|
|
n = sscanf(buf, "%d", &value);
|
|
if (n == 1) {
|
|
FTS_INFO("frame block: %d->%d", dbg->frame_block, !!value);
|
|
dbg->frame_block = !!value;
|
|
}
|
|
mutex_unlock(&dbg->mutex);
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(fts_fwdbg_mode, S_IRUGO | S_IWUSR, fts_fwdbg_mode_show, fts_fwdbg_mode_store);
|
|
static DEVICE_ATTR(fts_fwdbg_maxcount, S_IRUGO | S_IWUSR, fts_fwdbg_maxcount_show, fts_fwdbg_maxcount_store);
|
|
static DEVICE_ATTR(fts_fwdbg_logging, S_IRUGO | S_IWUSR, fts_fwdbg_logging_show, fts_fwdbg_logging_store);
|
|
static DEVICE_ATTR(fts_fwdbg_block, S_IRUGO | S_IWUSR, fts_fwdbg_block_show, fts_fwdbg_block_store);
|
|
static struct attribute *fts_fwdbg_attrs[] = {
|
|
&dev_attr_fts_fwdbg_mode.attr,
|
|
&dev_attr_fts_fwdbg_maxcount.attr,
|
|
&dev_attr_fts_fwdbg_logging.attr,
|
|
&dev_attr_fts_fwdbg_block.attr,
|
|
NULL,
|
|
};
|
|
static struct attribute_group fts_fwdbg_group = {.attrs = fts_fwdbg_attrs,};
|
|
|
|
static void fts_fwdbg_work_func(struct work_struct *work)
|
|
{
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
struct fts_ts_data *ts_data = container_of(work, struct fts_ts_data, fwdbg_work.work);
|
|
if (ts_data && ts_data->fwdbg_support && dbg) {
|
|
if (dbg->cfg.regfb_len) {
|
|
fts_logging_regfb(dbg, fwdbg_get_timestamp());
|
|
fts_logging_regfb(dbg, fwdbg_get_timestamp());
|
|
fts_logging_regfb(dbg, fwdbg_get_timestamp());
|
|
}
|
|
|
|
if (dbg->cfg.regfa_len) fts_logging_regfa(dbg, fwdbg_get_timestamp());
|
|
|
|
}
|
|
}
|
|
|
|
void fts_fwdbg_handle_reset(struct fts_ts_data *ts_data)
|
|
{
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
if (ts_data && ts_data->ts_workqueue && ts_data->fwdbg_support && dbg) {
|
|
dbg->reg01_val = DEFAULT_VAL_REG01;
|
|
if (dbg->cfg.regfb_len || dbg->cfg.regfa_len)
|
|
queue_delayed_work(ts_data->ts_workqueue, &ts_data->fwdbg_work, msecs_to_jiffies(200));
|
|
}
|
|
}
|
|
|
|
int fts_fwdbg_init(struct fts_ts_data *ts_data)
|
|
{
|
|
int ret = 0;
|
|
struct fts_fwdbg *dbg = NULL;
|
|
|
|
FTS_FUNC_ENTER();
|
|
dbg = kzalloc(sizeof(struct fts_fwdbg), GFP_KERNEL);
|
|
if (!dbg) {
|
|
FTS_ERROR("allocate memory for fwdbg failed");
|
|
return -ENOMEM;
|
|
}
|
|
fts_fwdbg_data = dbg;
|
|
dbg->ts_data = ts_data;
|
|
dbg->max_frame_num = DEFAULT_MAX_FRAME_NUM;
|
|
dbg->frame_block = false;
|
|
dbg->frame_logging = false;
|
|
dbg->frame_waitq_flag = FRAME_WAITQ_DEFAULT;
|
|
dbg->reg01_val = DEFAULT_VAL_REG01;
|
|
mutex_init(&dbg->mutex);
|
|
mutex_init(&dbg->q.mutexq);
|
|
init_waitqueue_head(&dbg->frame_waitq);
|
|
|
|
dbg->proc_fwdbg = proc_create_data("fts_fwdbg", 0777, NULL, &fts_fwdbg_fops, dbg);
|
|
if (NULL == dbg->proc_fwdbg) {
|
|
FTS_ERROR("create proc_fwdbg entry failed");
|
|
}
|
|
|
|
ret = sysfs_create_group(&ts_data->dev->kobj, &fts_fwdbg_group);
|
|
if (ret) {
|
|
FTS_ERROR("create fwdebug sysfs node failed");
|
|
sysfs_remove_group(&ts_data->dev->kobj, &fts_fwdbg_group);
|
|
}
|
|
|
|
if (ts_data->ts_workqueue) INIT_DELAYED_WORK(&ts_data->fwdbg_work, fts_fwdbg_work_func);
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
int fts_fwdbg_exit(struct fts_ts_data *ts_data)
|
|
{
|
|
struct fts_fwdbg *dbg = fts_fwdbg_data;
|
|
FTS_FUNC_ENTER();
|
|
if (dbg) {
|
|
if (dbg->proc_fwdbg) proc_remove(dbg->proc_fwdbg);
|
|
if (dbg->regfa_val) {
|
|
vfree(dbg->regfa_val);
|
|
dbg->regfa_val = NULL;
|
|
}
|
|
|
|
if (dbg->regfb_val) {
|
|
vfree(dbg->regfb_val);
|
|
dbg->regfb_val = NULL;
|
|
}
|
|
|
|
if (dbg->q.buffer) {
|
|
vfree(dbg->q.buffer);
|
|
dbg->q.buffer = NULL;
|
|
}
|
|
|
|
if (dbg->proc_frame) {
|
|
vfree(dbg->proc_frame);
|
|
dbg->proc_frame = NULL;
|
|
}
|
|
|
|
kfree_safe(dbg);
|
|
}
|
|
|
|
if (ts_data) {
|
|
sysfs_remove_group(&ts_data->dev->kobj, &fts_fwdbg_group);
|
|
cancel_delayed_work_sync(&ts_data->fwdbg_work);
|
|
}
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|