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