blob: a9c130989fef16c6f916e476090897e9233ccebf [file] [log] [blame]
/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*
*/
/*
* I2C controller logging/Debugfs for QTI MSM platforms
*/
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <linux/debugfs.h>
#include <linux/msm-sps.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/i2c/i2c-msm-v2.h>
#ifdef CONFIG_DEBUG_FS
enum i2c_msm_dbgfs_file_type {
I2C_MSM_DFS_U8,
I2C_MSM_DFS_U32,
I2C_MSM_DFS_FILE,
};
/*
* i2c_msm_dbgfs_file: entry in a table of debugfs files
*
* @name debugfs file name
* @mode file permissions
* @fops used when type == I2C_MSM_DFS_FILE
* @value_ptr used when type != I2C_MSM_DFS_FILE
*/
struct i2c_msm_dbgfs_file {
const char *name;
const umode_t mode;
enum i2c_msm_dbgfs_file_type type;
const struct file_operations *fops;
u32 *value_ptr;
};
static const umode_t I2C_MSM_DFS_MD_R = S_IRUSR | S_IRGRP;
static const umode_t I2C_MSM_DFS_MD_W = S_IWUSR | S_IWGRP;
static const umode_t I2C_MSM_DFS_MD_RW = S_IRUSR | S_IRGRP |
S_IWUSR | S_IWGRP;
void i2c_msm_dbgfs_create(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_dbgfs_file *itr)
{
struct dentry *file;
ctrl->dbgfs.root = debugfs_create_dir(dev_name(ctrl->dev), NULL);
if (!ctrl->dbgfs.root) {
dev_err(ctrl->dev, "error on creating debugfs root\n");
return;
}
for (; itr->name; ++itr) {
switch (itr->type) {
case I2C_MSM_DFS_FILE:
file = debugfs_create_file(itr->name,
itr->mode,
ctrl->dbgfs.root,
ctrl, itr->fops);
break;
case I2C_MSM_DFS_U8:
file = debugfs_create_u8(itr->name,
itr->mode,
ctrl->dbgfs.root,
(u8 *) itr->value_ptr);
break;
default: /* I2C_MSM_DFS_U32 */
file = debugfs_create_u32(itr->name,
itr->mode,
ctrl->dbgfs.root,
(u32 *) itr->value_ptr);
break;
}
if (!file)
dev_err(ctrl->dev,
"error on creating debugfs entry:%s\n",
itr->name);
}
}
void i2c_msm_dbgfs_init(struct i2c_msm_ctrl *ctrl)
{
struct i2c_msm_dbgfs_file i2c_msm_dbgfs_map[] = {
{"dbg-lvl", I2C_MSM_DFS_MD_RW, I2C_MSM_DFS_U8,
NULL, &ctrl->dbgfs.dbg_lvl},
{"xfer-force-mode", I2C_MSM_DFS_MD_RW, I2C_MSM_DFS_U8,
NULL, &ctrl->dbgfs.force_xfer_mode},
{NULL, 0, 0, NULL , NULL}, /* null terminator */
};
return i2c_msm_dbgfs_create(ctrl, i2c_msm_dbgfs_map);
}
EXPORT_SYMBOL(i2c_msm_dbgfs_init);
void i2c_msm_dbgfs_teardown(struct i2c_msm_ctrl *ctrl)
{
debugfs_remove_recursive(ctrl->dbgfs.root);
}
EXPORT_SYMBOL(i2c_msm_dbgfs_teardown);
#else
void i2c_msm_dbgfs_init(struct i2c_msm_ctrl *ctrl) {}
EXPORT_SYMBOL(i2c_msm_dbgfs_init);
void i2c_msm_dbgfs_teardown(struct i2c_msm_ctrl *ctrl) {}
EXPORT_SYMBOL(i2c_msm_dbgfs_teardown);
#endif
/*
* i2c_msm_dbg_tag_byte: accessor for tag as four bytes array
*/
static u8 *i2c_msm_dbg_tag_byte(struct i2c_msm_tag *tag, int byte_n)
{
return ((u8 *)tag) + byte_n;
}
static const char * const i2c_msm_fifo_sz_str_tbl[]
= {"x2 blk sz", "x4 blk sz" , "x8 blk sz", "x16 blk sz"};
static const char * const i2c_msm_fifo_block_sz_str_tbl[]
= {"16", "16" , "32", "0"};
/* string table for qup_io_modes register */
static const char * const i2c_msm_qup_mode_str_tbl[] = {
"FIFO", "Block", "Reserved", "DMA",
};
static const char * const i2c_msm_mini_core_str_tbl[] = {
"null", "SPI", "I2C", "reserved",
};
/*
* i2c_msm_qup_reg_fld: a register field descriptor
* @name field name
* @to_str_tbl when not null, used to interpret the bits value. The bits value
* is the table entry number.
*/
struct i2c_msm_qup_reg_fld {
const char * const name;
int bit_idx;
int n_bits;
const char * const *to_str_tbl;
};
static const char * const i2c_msm_reg_qup_state_to_str[] = {
"Reset", "Run", "Clear", "Pause"
};
/* QUP_STATE register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_state_fields_map[] = {
{ "STATE", 0, 2, i2c_msm_reg_qup_state_to_str},
{ "VALID", 2, 1},
{ "MAST_GEN", 4, 1},
{ "WAIT_EOT", 5, 1},
{ "FLUSH", 6, 1},
{ NULL, 0, 1},
};
/* QUP_CONFIG register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_config_fields_map[] = {
{ "N", 0, 5},
{ "MINI_CORE", 8, 2, i2c_msm_mini_core_str_tbl},
{ "NO_OUTPUT", 6, 1},
{ "NO_INPUT", 7, 1},
{ "EN_EXT_OUT", 16, 1},
{ NULL, 0, 1},
};
/* QUP_OPERATIONAL register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_op_fields_map[] = {
{ "OUT_FF_N_EMPTY", 4, 1},
{ "IN_FF_N_EMPTY", 5, 1},
{ "OUT_FF_FUL", 6, 1},
{ "IN_FF_FUL", 7, 1},
{ "OUT_SRV_FLG", 8, 1},
{ "IN_SRV_FLG", 9, 1},
{ "MX_OUT_DN", 10, 1},
{ "MX_IN_DN", 11, 1},
{ "OUT_BLK_WR", 12, 1},
{ "IN_BLK_RD", 13, 1},
{ "DONE_TGL", 14, 1},
{ "NWD", 15, 1},
{ NULL, 0, 1},
};
/* QUP_I2C_STATUS (a.k.a I2C_MASTER_STATUS) register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_i2c_stat_fields_map[] = {
{ "BUS_ERR", 2, 1},
{ "NACK", 3, 1},
{ "ARB_LOST", 4, 1},
{ "INVLD_WR", 5, 1},
{ "FAIL", 6, 2},
{ "BUS_ACTV", 8, 1},
{ "BUS_MSTR", 9, 1},
{ "DAT_STATE", 10, 3},
{ "CLK_STATE", 13, 3},
{ "O_FSM_STAT", 16, 3},
{ "I_FSM_STAT", 19, 3},
{ "INVLD_TAG", 23, 1},
{ "INVLD_RD_ADDR", 24, 1},
{ "INVLD_RD_SEQ", 25, 1},
{ "SDA", 26, 1},
{ "SCL", 27, 1},
{ NULL, 0, 1},
};
/* QUP_ERROR_FLAGS register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_err_flags_fields_map[] = {
{ "IN_OVR_RUN", 2, 1},
{ "OUT_UNDR_RUN", 3, 1},
{ "IN_UNDR_RUN", 4, 1},
{ "OUT_OVR_RUN", 5, 1},
{ NULL, 0, 1},
};
/* QUP_OPERATIONAL_MASK register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_op_mask_fields_map[] = {
{ "OUT_SRVC_MASK", 8, 1},
{ "IN_SRVC_MASK", 9, 1},
{ NULL, 0, 1},
};
/* QUP_I2C_MASTER_CLK_CTL register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_master_clk_fields_map[] = {
{ "FS_DIV", 0, 8},
{ "HS_DIV", 8, 3},
{ "HI_TM_DIV", 16, 8},
{ "SCL_NS_RJCT", 24, 2},
{ "SDA_NS_RJCT", 26, 2},
{ "SCL_EXT_FRC_L", 28, 1},
{ NULL, 0, 1},
};
static const char * const i2c_msm_dbg_tag_val_str_tbl[] = {
"NOP_WAIT", /* 0x80 */
"START", /* 0x81 */
"DATAWRITE", /* 0x82 */
"DATAWRT_and_STOP", /* 0x83 */
NULL, /* 0x84 */
"DATAREAD", /* 0x85 */
"DATARD_and_NACK", /* 0x86 */
"DATARD_and_STOP", /* 0x87 */
"STOP_TAG", /* 0x88 */
NULL, /* 0x89 */
NULL, /* 0x8A */
NULL, /* 0x8B */
NULL, /* 0x8C */
NULL, /* 0x8D */
NULL, /* 0x8E */
NULL, /* 0x8F */
"NOP_MARK", /* 0x90 */
"NOP_ID", /* 0x91 */
"TIME_STAMP", /* 0x92 */
"INPUT_EOT", /* 0x93 */
"INPUT_EOT_FLUSH", /* 0x94 */
"NOP_LOCAL", /* 0x95 */
"FLUSH STOP", /* 0x96 */
};
/* QUP_IO_MODES register fields */
static struct i2c_msm_qup_reg_fld i2c_msm_qup_io_modes_map[] = {
{ "IN_BLK_SZ", 5, 2, i2c_msm_fifo_block_sz_str_tbl},
{ "IN_FF_SZ", 7, 3, i2c_msm_fifo_sz_str_tbl},
{ "OUT_BLK_SZ", 0, 2, i2c_msm_fifo_block_sz_str_tbl},
{ "OUT_FF_SZ", 2, 3, i2c_msm_fifo_sz_str_tbl},
{ "UNPACK", 14, 1},
{ "PACK", 15, 1},
{ "INP_MOD", 12, 2, i2c_msm_qup_mode_str_tbl},
{ "OUT_MOD", 10, 2, i2c_msm_qup_mode_str_tbl},
{ NULL, 0, 1},
};
/*
* i2c_msm_qup_reg_dump: desc fmt of reg to dump via i2c_msm_dbg_qup_reg_dump()
*
* @offset the register's offset in the QUP
* @name name to dump before value
* @field_map when set i2c_msm_dbg_qup_reg_flds_to_str() is used. Otherwise
* if val_to_str_func() is set, then it is used. When both are NULL
* none is used. Only the register's value is dumped.
*/
struct i2c_msm_qup_reg_dump {
u32 offset;
const char *name;
struct i2c_msm_qup_reg_fld *field_map;
};
static const struct i2c_msm_qup_reg_dump i2c_msm_qup_reg_dump_map[] = {
{QUP_CONFIG, "QUP_CONFIG", i2c_msm_qup_config_fields_map },
{QUP_STATE, "QUP_STATE", i2c_msm_qup_state_fields_map },
{QUP_IO_MODES, "QUP_IO_MDS", i2c_msm_qup_io_modes_map },
{QUP_ERROR_FLAGS, "QUP_ERR_FLGS", i2c_msm_qup_err_flags_fields_map },
{QUP_OPERATIONAL, "QUP_OP", i2c_msm_qup_op_fields_map },
{QUP_OPERATIONAL_MASK, "QUP_OP_MASK", i2c_msm_qup_op_mask_fields_map },
{QUP_I2C_STATUS, "QUP_I2C_STAT", i2c_msm_qup_i2c_stat_fields_map },
{QUP_I2C_MASTER_CLK_CTL, "QUP_MSTR_CLK", i2c_msm_qup_master_clk_fields_map},
{QUP_IN_DEBUG, "QUP_IN_DBG" },
{QUP_OUT_DEBUG, "QUP_OUT_DBG" },
{QUP_IN_FIFO_CNT, "QUP_IN_CNT" },
{QUP_OUT_FIFO_CNT, "QUP_OUT_CNT" },
{QUP_MX_READ_COUNT, "MX_RD_CNT" },
{QUP_MX_WRITE_COUNT, "MX_WR_CNT" },
{QUP_MX_INPUT_COUNT, "MX_IN_CNT" },
{QUP_MX_OUTPUT_COUNT, "MX_OUT_CNT" },
{0, NULL },
};
static const char *i2c_msm_dbg_tag_val_to_str(u8 tag_val)
{
if ((tag_val < 0x80) || (tag_val > 0x96) || (tag_val == 0x84) ||
((tag_val > 0x88) && (0x90 > tag_val)))
return "Invalid_tag";
return i2c_msm_dbg_tag_val_str_tbl[tag_val - 0x80];
}
/*
* i2c_msm_dbg_qup_reg_flds_to_str: format register's fields using a field map
*
* @fld an array of fields mapping bits of val to fields/flags values
* @val the register's value
* @buf buffer to format the strings into
* @len buf's len
*/
static const char *i2c_msm_dbg_qup_reg_flds_to_str(
u32 val, char *buf, int len, const struct i2c_msm_qup_reg_fld *fld)
{
char *ptr = buf;
int str_len;
int str_len_sum = 0;
int rem_len = len;
u32 field_val;
for (; fld->name && (rem_len > 0); ++fld) {
if (fld->n_bits == 1) {
field_val = BIT_IS_SET(val, fld->bit_idx);
/*
* Only dump interesting flags (skip flags who's value
* is zero).
*/
if (!field_val)
continue;
str_len = snprintf(ptr, rem_len, "%s ", fld->name);
} else {
field_val = BITS_AT(val, fld->bit_idx, fld->n_bits);
/*
* Only dump interesting fields (skip fields who's value
* is zero).
*/
if (!field_val)
continue;
if (fld->to_str_tbl)
str_len = snprintf(ptr, rem_len, "%s:%s ",
fld->name, fld->to_str_tbl[field_val]);
else
str_len = snprintf(ptr, rem_len, "%s:0x%x ",
fld->name, field_val);
}
if (str_len > rem_len) {
pr_err("%s insufficient buffer space\n", __func__);
/* snprintf does not guarantee NULL terminator */
buf[len - 1] = 0;
return buf;
}
rem_len -= str_len;
ptr += str_len;
str_len_sum += str_len;
}
/* snprintf does not guarantee NULL terminator */
buf[len - 1] = 0;
return buf;
}
const char *i2c_msm_dbg_tag_to_str(const struct i2c_msm_tag *tag,
char *buf, size_t buf_len)
{
/* cast const away. t is read-only here */
struct i2c_msm_tag *t = (struct i2c_msm_tag *) tag;
switch (tag->len) {
case 6:
snprintf(buf, buf_len, "val:0x%012llx %s:0x%x %s:0x%x %s:%d",
tag->val,
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)),
*i2c_msm_dbg_tag_byte(t, 1),
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 2)),
*i2c_msm_dbg_tag_byte(t, 3),
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 4)),
*i2c_msm_dbg_tag_byte(t, 5));
break;
case 4:
snprintf(buf, buf_len, "val:0x%08llx %s:0x%x %s:%d",
(tag->val & 0xffffffff),
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)),
*i2c_msm_dbg_tag_byte(t, 1),
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 2)),
*i2c_msm_dbg_tag_byte(t, 3));
break;
default: /* 2 bytes tag */
snprintf(buf, buf_len, "val:0x%04llx %s:%d",
(tag->val & 0xffff),
i2c_msm_dbg_tag_val_to_str(*i2c_msm_dbg_tag_byte(t, 0)),
*i2c_msm_dbg_tag_byte(t, 1));
}
return buf;
}
EXPORT_SYMBOL(i2c_msm_dbg_tag_to_str);
const char *
i2c_msm_dbg_dma_tag_to_str(const struct i2c_msm_dma_tag *dma_tag, char *buf,
size_t buf_len)
{
const char *ret;
u32 *val;
struct i2c_msm_tag tag;
val = phys_to_virt(dma_tag->buf);
if (!val) {
pr_err("Failed phys_to_virt(0x%llx)", (u64) dma_tag->buf);
return "Error";
}
tag = (struct i2c_msm_tag) {
.val = *val,
.len = dma_tag->len,
};
ret = i2c_msm_dbg_tag_to_str(&tag, buf, buf_len);
return ret;
}
EXPORT_SYMBOL(i2c_msm_dbg_dma_tag_to_str);
/*
* see: struct i2c_msm_qup_reg_dump for more
*/
int i2c_msm_dbg_qup_reg_dump(struct i2c_msm_ctrl *ctrl)
{
u32 val;
char buf[I2C_MSM_REG_2_STR_BUF_SZ];
void __iomem *base = ctrl->rsrcs.base;
const struct i2c_msm_qup_reg_dump *itr = i2c_msm_qup_reg_dump_map;
for (; itr->name; ++itr) {
val = readl_relaxed(base + itr->offset);
buf[0] = 0;
if (itr->field_map)
i2c_msm_dbg_qup_reg_flds_to_str(val, buf, sizeof(buf),
itr->field_map);
dev_err(ctrl->dev, "%-12s:0x%08x %s\n", itr->name, val, buf);
};
return 0;
}
EXPORT_SYMBOL(i2c_msm_dbg_qup_reg_dump);
typedef void (*i2c_msm_prof_dump_func_func_t)(struct i2c_msm_ctrl *,
struct i2c_msm_prof_event *, size_t msec, size_t usec);
/*
* i2c_msm_prof_evnt_add: pushes event into end of event array
*
* @dump_now log a copy immediately to kernel log
*
* Implementation of i2c_msm_prof_evnt_add(). When array overflows, the last
* entry is overwritten as many times as it overflows.
*/
void i2c_msm_prof_evnt_add(struct i2c_msm_ctrl *ctrl,
enum msm_i2_debug_level dbg_level,
enum i2c_msm_prof_evnt_type event_type,
u64 data0, u32 data1, u32 data2)
{
struct i2c_msm_xfer *xfer = &ctrl->xfer;
struct i2c_msm_prof_event *event;
int idx;
if (ctrl->dbgfs.dbg_lvl < dbg_level)
return;
atomic_add_unless(&xfer->event_cnt, 1, I2C_MSM_PROF_MAX_EVNTS - 1);
idx = atomic_read(&xfer->event_cnt) - 1;
if (idx > (I2C_MSM_PROF_MAX_EVNTS - 1))
dev_err(ctrl->dev, "error event index:%d max:%d\n",
idx, I2C_MSM_PROF_MAX_EVNTS);
event = &xfer->event[idx];
getnstimeofday(&event->time);
event->dump_func_id = event_type;
event->data0 = data0;
event->data1 = data1;
event->data2 = data2;
}
EXPORT_SYMBOL(i2c_msm_prof_evnt_add);
void i2c_msm_prof_dump_xfer_beg(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev,
"-->.%03zums XFER_BEG msg_cnt:%llx addr:0x%x\n",
usec, event->data0, event->data1);
}
void i2c_msm_prof_dump_actv_end(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev,
"%3zu.%03zums ACTV_END ret:%lld jiffies_left:%u/%u read_cnt:%u\n",
msec, usec, event->data0, event->data1,
I2C_MSM_MAX_POLL_MSEC, event->data2);
}
void i2c_msm_prof_dump_dma_flsh(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev, "%3zu.%03zums DMA_FLSH\n", msec, usec);
}
void i2c_msm_prof_dump_pip_dscn(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
struct i2c_msm_dma_chan *chan =
(struct i2c_msm_dma_chan *) ((ulong) event->data0);
int ret = event->data1;
dev_info(ctrl->dev,
"%3zu.%03zums PIP_DCNCT sps_disconnect(hndl:0x%p %s):%d\n",
msec, usec, chan->dma_chan, chan->name, ret);
}
void i2c_msm_prof_dump_pip_cnct(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
struct i2c_msm_dma_chan *chan =
(struct i2c_msm_dma_chan *) ((ulong) event->data0);
int ret = event->data1;
dev_info(ctrl->dev,
"%3zu.%03zums PIP_CNCT sps_connect(hndl:0x%p %s):%d\n",
msec, usec, chan->dma_chan, chan->name, ret);
}
void i2c_msm_prof_reset(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev, "%3zu.%03zums QUP_RSET\n", msec, usec);
}
/* string table for enum i2c_msm_err_bit_field */
const char * const i2c_msm_err_str_tbl[] = {
"NONE", "NACK", "ARB_LOST" , "ARB_LOST + NACK", "BUS_ERR",
"BUS_ERR + NACK", "BUS_ERR + ARB_LOST", "BUS_ERR + ARB_LOST + NACK",
"TIMEOUT", "TIMEOUT + NACK", "TIMEOUT + ARB_LOST",
"TIMEOUT + ARB_LOST + NACK", "TIMEOUT + BUS_ERR",
"TIMEOUT + BUS_ERR + NACK" , "TIMEOUT + BUS_ERR + ARB_LOST",
"TIMEOUT + BUS_ERR + ARB_LOST + NACK",
};
void i2c_msm_prof_dump_xfer_end(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
int ret = event->data0;
int err = event->data1;
int bc = ctrl->xfer.rx_cnt + ctrl->xfer.rx_ovrhd_cnt +
ctrl->xfer.tx_cnt + ctrl->xfer.tx_ovrhd_cnt;
int bc_sec = (bc * 1000000) / (msec * 1000 + usec);
const char *status = (!err && (ret == ctrl->xfer.msg_cnt)) ?
"OK" : "FAIL";
dev_info(ctrl->dev,
"%3zu.%03zums XFER_END ret:%d err:[%s] msgs_sent:%d BC:%d B/sec:%d i2c-stts:%s\n" ,
msec, usec, ret, i2c_msm_err_str_tbl[err], event->data2,
bc, bc_sec, status);
}
void i2c_msm_prof_dump_irq_begn(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev, "%3zu.%03zums IRQ_BEG irq:%lld\n",
msec, usec, event->data0);
}
void i2c_msm_prof_dump_irq_end(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
char str[I2C_MSM_REG_2_STR_BUF_SZ];
u32 mstr_stts = event->data0;
u32 qup_oper = event->data1;
u32 err_flgs = event->data2;
dev_info(ctrl->dev,
"%3zu.%03zums IRQ_END MSTR_STTS:0x%x QUP_OPER:0x%x ERR_FLGS:0x%x\n",
msec, usec, mstr_stts, qup_oper, err_flgs);
/*
* Dump fields and flags only of registers with interesting info
* (i.e. errors).
*/
/* register I2C_MASTER_STATUS */
if (mstr_stts & QUP_MSTR_STTS_ERR_MASK) {
i2c_msm_dbg_qup_reg_flds_to_str(
mstr_stts, str, sizeof(str),
i2c_msm_qup_i2c_stat_fields_map);
dev_info(ctrl->dev, " |->MSTR_STTS:0x%llx %s\n",
event->data0, str);
}
/* register QUP_OPERATIONAL */
if (qup_oper &
(QUP_OUTPUT_SERVICE_FLAG | QUP_INPUT_SERVICE_FLAG)) {
i2c_msm_dbg_qup_reg_flds_to_str(
qup_oper, str, sizeof(str),
i2c_msm_qup_op_fields_map);
dev_info(ctrl->dev, " |-> QUP_OPER:0x%x %s\n",
event->data1, str);
}
/* register ERR_FLAGS */
if (err_flgs) {
i2c_msm_dbg_qup_reg_flds_to_str(
err_flgs, str, sizeof(str),
i2c_msm_qup_err_flags_fields_map);
dev_info(ctrl->dev, " |-> ERR_FLGS:0x%x %s\n",
event->data2, str);
}
}
void i2c_msm_prof_dump_next_buf(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
struct i2c_msg *msg = ctrl->xfer.msgs + event->data0;
dev_info(ctrl->dev,
"%3zu.%03zums XFER_BUF msg[%lld] pos:%d adr:0x%x len:%d is_rx:0x%x last:0x%x\n",
msec, usec, event->data0, event->data1, msg->addr, msg->len,
(msg->flags & I2C_M_RD),
event->data0 == (ctrl->xfer.msg_cnt - 1));
}
void i2c_msm_prof_dump_scan_sum(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
u32 bc_rx = (event->data0 & 0xff);
u32 bc_rx_ovrhd = (event->data0 >> 16);
u32 bc_tx = (event->data1 & 0xff);
u32 bc_tx_ovrhd = (event->data1 >> 16);
u32 timeout = (event->data2 & 0xfff);
u32 mode = (event->data2 >> 24);
u32 bc = bc_rx + bc_rx_ovrhd + bc_tx + bc_tx_ovrhd;
dev_info(ctrl->dev,
"%3zu.%03zums SCN_SMRY BC:%u rx:%u+ovrhd:%u tx:%u+ovrhd:%u timeout:%umsec mode:%s\n",
msec, usec, bc, bc_rx, bc_rx_ovrhd, bc_tx, bc_tx_ovrhd,
jiffies_to_msecs(timeout), i2c_msm_mode_str_tbl[mode]);
}
void i2c_msm_prof_dump_cmplt_ok(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev,
"%3zu.%03zums DONE_OK timeout-used:%umsec time_left:%umsec\n",
msec, usec, jiffies_to_msecs(event->data0),
jiffies_to_msecs(event->data1));
}
void i2c_msm_prof_dump_cmplt_fl(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
dev_info(ctrl->dev,
"%3zu.%03zums TIMEOUT-error timeout-used:%umsec. Check GPIOs configuration\n",
msec, usec, jiffies_to_msecs(event->data0));
}
void i2c_msm_prof_dump_vlid_end(struct i2c_msm_ctrl *ctrl,
struct i2c_msm_prof_event *event, size_t msec, size_t usec)
{
int ret = (int)(event->data0 & 0xff);
enum i2c_msm_qup_state state = ((event->data0 << 16) & 0xf);
u32 status = event->data2;
dev_info(ctrl->dev,
"%3zu.%03zums SET_STTE set:%s ret:%d rd_cnt:%u reg_val:0x%x vld:%d\n",
msec, usec, i2c_msm_reg_qup_state_to_str[state], ret,
event->data1, status, BIT_IS_SET(status, 2));
}
/* match the corresponding prof event enum to the prof function declaration */
static i2c_msm_prof_dump_func_func_t event_dump_func_tbl[] = {
[I2C_MSM_VALID_END] = i2c_msm_prof_dump_vlid_end,
[I2C_MSM_PIP_DSCN] = i2c_msm_prof_dump_pip_dscn,
[I2C_MSM_PIP_CNCT] = i2c_msm_prof_dump_pip_cnct,
[I2C_MSM_ACTV_END] = i2c_msm_prof_dump_actv_end,
[I2C_MSM_IRQ_BGN] = i2c_msm_prof_dump_irq_begn,
[I2C_MSM_IRQ_END] = i2c_msm_prof_dump_irq_end,
[I2C_MSM_XFER_BEG] = i2c_msm_prof_dump_xfer_beg,
[I2C_MSM_XFER_END] = i2c_msm_prof_dump_xfer_end,
[I2C_MSM_SCAN_SUM] = i2c_msm_prof_dump_scan_sum,
[I2C_MSM_NEXT_BUF] = i2c_msm_prof_dump_next_buf,
[I2C_MSM_COMPLT_OK] = i2c_msm_prof_dump_cmplt_ok,
[I2C_MSM_COMPLT_FL] = i2c_msm_prof_dump_cmplt_fl,
[I2C_MSM_PROF_RESET] = i2c_msm_prof_reset,
};
/*
* i2c_msm_prof_evnt_dump: post processing, msg formatting and dumping of events
*/
void i2c_msm_prof_evnt_dump(struct i2c_msm_ctrl *ctrl)
{
size_t cnt = atomic_read(&ctrl->xfer.event_cnt);
struct i2c_msm_prof_event *event = ctrl->xfer.event;
struct timespec time0 = event->time;
struct timespec time_diff;
size_t diff_usec;
size_t diff_msec;
i2c_msm_prof_dump_func_func_t func;
for (; cnt; --cnt, ++event) {
time_diff = timespec_sub(event->time, time0);
diff_usec = time_diff.tv_sec * USEC_PER_SEC +
time_diff.tv_nsec / NSEC_PER_USEC;
diff_msec = diff_usec / USEC_PER_MSEC;
diff_usec -= diff_msec * USEC_PER_MSEC;
func = event_dump_func_tbl[event->dump_func_id];
func(ctrl, event, diff_msec, diff_usec);
}
}
EXPORT_SYMBOL(i2c_msm_prof_evnt_dump);