/*
 * UFS debugging functions for Exynos specific extensions
 *
 * Copyright (C) 2016 Samsung Electronics Co., Ltd.
 *
 * Authors:
 *	Kiwoong <kwmad.kim@samsung.com>
 */

#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/module.h>

#include "ufshcd.h"
#include <soc/samsung/exynos-smc.h>
#include <soc/samsung/memlogger.h>
#include "ufs-vs-mmio.h"
#include "ufs-vs-regs.h"
#include "ufs-dump.h"

struct exynos_ufs_memlog {
	struct memlog *desc;
	struct memlog_obj *log_obj_file;
	struct memlog_obj *log_obj;
	unsigned int log_enable;
};

/* Structure for ufs cmd logging */
#define MAX_CMD_LOGS    128
/* Hardware */
#define UIC_ARG_MIB_SEL(attr, sel)		((((attr) & 0xFFFF) << 16) |\
						((sel) & 0xFFFF))
#define UIC_ARG_MIB(attr)			UIC_ARG_MIB_SEL(attr, 0)
#define PCS_CMN_OFFSET(ofs)			(0x2800 + ((ofs) - 0x200) * 4)
#define PCS_TRSV_OFFSET(ofs)			(0x2000 + (ofs) * 4)
#define PHY_PMA_COMN_ADDR(reg)			(reg)
#define PHY_PMA_TRSV_ADDR(reg, lane)		((reg) + (0x400 * (lane)))
#define UNIP_COMP_AXI_AUX_FIELD			0x040
#define __WSTRB					(0xF << 24)
#define __SEL_IDX(L)				((L) & 0xFFFF)
enum {
	TX_LANE_0 = 0,
	RX_LANE_0 = 4,
};

struct cmd_data {
	unsigned int tag;
	unsigned int sct;
	unsigned long lba;
	u64 start_time;
	u64 end_time;
	u64 outstanding_reqs;
	int retries;
	unsigned char op;
};

struct ufs_cmd_info {
	u32 total;
	u32 last;
	struct cmd_data data[MAX_CMD_LOGS];
	struct cmd_data *pdata[32];	/* Currently, 32 slots */
};

static const char *ufs_sfr_field_name[3] = {
	"NAME",	"OFFSET", "VALUE",
};
static const char *ufs_attr_field_name[4]= {
	"MIB",	"OFFSET(SFR)", "VALUE(lane #0)", "VALUE(lane #1)",
};

#define ATTR_LANE_OFFSET		16
#define ATTR_TYPE_MASK(addr)		((addr >> ATTR_LANE_OFFSET) & 0xF)
#define ATTR_SET(addr, type)		((addr) | ((type & 0xF) << ATTR_LANE_OFFSET))

#define ATTR_NUM_MAX_LANES		2

#define CPORT_TX_BUF_SIZE       0x100
#define CPORT_RX_BUF_SIZE       0x100
#define CPORT_LOG_PTR_SIZE      0x100
#define CPORT_UTRL_SIZE         0x400
#define CPORT_UCD_SIZE          0x400
#define CPORT_UTMRL_SIZE        0xc0
#define CPORT_BUF_SIZE          (CPORT_TX_BUF_SIZE + CPORT_RX_BUF_SIZE + \
                                CPORT_LOG_PTR_SIZE +  CPORT_UTRL_SIZE + \
                                CPORT_UCD_SIZE +  CPORT_UTMRL_SIZE)

#define CPORT_TX_BUF_PTR        0x0
#define CPORT_RX_BUF_PTR        (CPORT_TX_BUF_PTR + CPORT_TX_BUF_SIZE)
#define CPORT_LOG_PTR_OFFSET    (CPORT_RX_BUF_PTR + CPORT_RX_BUF_SIZE)
#define CPORT_UTRL_PTR          (CPORT_LOG_PTR_OFFSET + CPORT_LOG_PTR_SIZE)
#define CPORT_UCD_PTR           (CPORT_UTRL_PTR + CPORT_UTRL_SIZE)
#define CPORT_UTMRL_PTR         (CPORT_UCD_PTR + CPORT_UCD_SIZE)

#define pr_memlog(dev, fmt, ...)					\
	do {								\
		if (dev->log_enable)					\
			memlog_write_printf(dev->log_obj,		\
					MEMLOG_LEVEL_EMERG,		\
					fmt, ##__VA_ARGS__);		\
		else							\
			pr_err(fmt, ##__VA_ARGS__);			\
	} while (0)

struct ufs_log_cport {
        u32 ptr;
        u8 buf[CPORT_BUF_SIZE];
} ufs_log_cport;

#define DBG_NUM_OF_HOSTS	1
struct ufs_dbg_mgr {
	struct ufs_vs_handle *handle;
	int active;
	u64 first_time;
	u64 time;
	u32 lanes;

	/* cport */
	struct ufs_log_cport log_cport;

	/* cmd log */
	struct ufs_cmd_info cmd_info;
	struct cmd_data cmd_log;		/* temp buffer to put */
	spinlock_t cmd_lock;

	/* mem log*/
	struct exynos_ufs_memlog *mem_log;
	struct ufs_stats ufs_stats;
};
static struct ufs_dbg_mgr ufs_dbg[DBG_NUM_OF_HOSTS];
static struct exynos_ufs_memlog ufs_memlog[DBG_NUM_OF_HOSTS];
static int ufs_dbg_mgr_idx = 0;

static void __ufs_get_sfr(struct ufs_dbg_mgr *mgr,
					struct exynos_ufs_sfr_log* cfg)
{
	struct ufs_vs_handle *handle = mgr->handle;
	int sel_api = 0;
	u32 reg = 0;
	u32 *pval;

	while(cfg) {
		if (!cfg->name)
			break;

		if (cfg->offset >= LOG_STD_HCI_SFR) {
			/* Select an API to get SFRs */
			sel_api = cfg->offset;
			if (sel_api == LOG_PMA_SFR) {
				/* Enable MPHY APB */
				reg = hci_readl(handle, HCI_CLKSTOP_CTRL);
				hci_writel(handle, reg & ~MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
			}
			cfg++;
			continue;
		}

		/* Fetch value */
		pval = &cfg->val[SFR_VAL_H_0];
		if (sel_api == LOG_STD_HCI_SFR)
			*pval = std_readl(handle, cfg->offset);
		else if (sel_api == LOG_VS_HCI_SFR)
			*pval = hci_readl(handle, cfg->offset);
		else if (sel_api == LOG_UNIPRO_SFR)
			*pval = unipro_readl(handle, cfg->offset);
		else if (sel_api == LOG_PMA_SFR)
			*pval = pma_readl(handle, cfg->offset);
		else
			*pval = 0xFFFFFFFF;

		/* Keep the first contexts permanently */
		if (mgr->first_time == 0ULL)
			cfg->val[SFR_VAL_H_0_FIRST] = *pval;

		/* Next SFR */
		cfg++;
	}

	/* Disable MPHY APB */
	hci_writel(handle, reg | MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
}

static u32 __read_pcs(struct ufs_vs_handle *handle, int lane,
		      struct exynos_ufs_attr_log* cfg) {
	unipro_writel(handle, __WSTRB | __SEL_IDX(lane),
		      UNIP_COMP_AXI_AUX_FIELD);
	return unipro_readl(handle, PCS_TRSV_OFFSET(cfg->mib));
}
static void __ufs_get_attr(struct ufs_dbg_mgr *mgr,
					struct exynos_ufs_attr_log* cfg)
{
	struct ufs_vs_handle *handle = mgr->handle;
	int sel_api = 0;
	u32 val;
	u32 *pval;
	int i;

	while(cfg) {
		if (cfg->mib == 0)
			break;

		/* Fetch result and value */
		pval = &cfg->val[ATTR_VAL_H_0_L_0];
		if (cfg->mib >= DBG_ATTR_UNIPRO) {
			/* Select an API to get attributes */
			sel_api = cfg->mib;
			cfg++;
			continue;
		}

		cfg->val[ATTR_VAL_H_0_L_1] = 0xFFFFFFFF;
		if (sel_api == DBG_ATTR_UNIPRO) {
			*pval = unipro_readl(handle, cfg->offset);
		} else if (sel_api == DBG_ATTR_PCS_CMN) {
			*pval = unipro_readl(handle, PCS_CMN_OFFSET(cfg->mib));
		} else if (sel_api == DBG_ATTR_PCS_TX) {
			for (i = 0 ; i < mgr->lanes; i++) {
				val = __read_pcs(handle, TX_LANE_0 + i, cfg);
				*(pval + i) = val;
			}
		} else if (sel_api == DBG_ATTR_PCS_RX) {
			for (i = 0 ; i < mgr->lanes; i++) {
				val = __read_pcs(handle, RX_LANE_0 + i, cfg);
				*(pval + i) = val;
			}
		} else
			// TODO:
			;

		/* Keep the first contexts permanently */
		if (mgr->first_time == 0ULL) {
			cfg->val[ATTR_VAL_H_0_L_0_FIRST] = *pval;
			cfg->val[ATTR_VAL_H_0_L_1_FIRST] = *(pval + 1);
		}

		/* Next attribute */
		cfg++;
	}
}

static void __ufs_print_sfr(struct ufs_vs_handle *handle,
					struct device *dev,
					struct exynos_ufs_sfr_log* cfg)
{

	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;

	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ":\t\tREGISTER\n");
	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ": %-30s\t%-012s\t%-014s\n",
				ufs_sfr_field_name[0],
				ufs_sfr_field_name[1],
				ufs_sfr_field_name[2]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ":\t\tREGISTER\n");
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ": %-30s\t%-012s\t%-014s\n",
		ufs_sfr_field_name[0],
		ufs_sfr_field_name[1],
		ufs_sfr_field_name[2]);
#endif

	while(cfg) {
		if (!cfg->name)
			break;

		/* show */
		if (cfg->offset >= LOG_STD_HCI_SFR)
			pr_memlog(mgr->mem_log, "\n");
		pr_memlog(mgr->mem_log, ": %-30s\t0x%-012x\t0x%-014x\n",
				cfg->name, cfg->offset, cfg->val[SFR_VAL_H_0]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
		dev_err(dev, ": %-30s\t0x%-012x\t0x%-014x\n",
			cfg->name, cfg->offset, cfg->val[SFR_VAL_H_0]);
#endif
		if (cfg->offset >= LOG_STD_HCI_SFR)
			pr_memlog(mgr->mem_log, "\n");

		/* Next SFR */
		cfg++;
	}
}

static void __ufs_print_attr(struct ufs_vs_handle *handle,
					struct device *dev,
					struct exynos_ufs_attr_log* cfg)
{
	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;

	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ":\t\tATTRIBUTE\n");
	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ": %-30s\t%-12s%-14s\t%-14s\n",
				ufs_attr_field_name[0],
				ufs_attr_field_name[1],
				ufs_attr_field_name[2],
				ufs_attr_field_name[3]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ":\t\tATTRIBUTE\n");
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ": %-30s\t%-12s%-14s\t%-14s\n",
		ufs_attr_field_name[0],
		ufs_attr_field_name[1],
		ufs_attr_field_name[2],
		ufs_attr_field_name[3]);
#endif

	while(cfg) {
		if (!cfg->mib)
			break;

		/* show */
		if (cfg->offset >= DBG_ATTR_UNIPRO)
			pr_memlog(mgr->mem_log, "\n");
		pr_memlog(mgr->mem_log, ": 0x%-27x\t0x%-012x\t0x%-014x\t0x%-014x\n",
				cfg->mib, cfg->offset,
				cfg->val[ATTR_VAL_H_0_L_0], cfg->val[ATTR_VAL_H_0_L_1]);
#if IS_ENABLED(CONFIG_SAMSUNG_PRODUCT_SHIP)
		dev_err(dev, ": 0x%-27x\t0x%-012x\t0x%-014x\t0x%-014x\n",
			cfg->mib, cfg->offset,
			cfg->val[ATTR_VAL_H_0_L_0], cfg->val[ATTR_VAL_H_0_L_1]);
#endif
		if (cfg->offset >= DBG_ATTR_UNIPRO)
			pr_memlog(mgr->mem_log, "\n");

		/* Next SFR */
		cfg++;
	}
}

static void __print_cport(struct device *dev, int tag_printed,
						u32 tag, u32 *ptr,
						struct ufs_dbg_mgr *mgr)
{
	if (tag_printed)
		pr_memlog(mgr->mem_log, "%08x %08x %08x %08x tag# %u\n",
			be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
			be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)),
			tag);
	else
		pr_memlog(mgr->mem_log, "%08x %08x %08x %08x\n",
			be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
			be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
	if (tag_printed)
		dev_err(dev, "%08x %08x %08x %08x tag# %u\n",
			be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
			be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)),
			tag);
	else
		dev_err(dev, "%08x %08x %08x %08x\n",
			be32_to_cpu(*(ptr + 0)), be32_to_cpu(*(ptr + 1)),
			be32_to_cpu(*(ptr + 2)), be32_to_cpu(*(ptr + 3)));
#endif
}

static void __ufs_print_cport(struct ufs_dbg_mgr *mgr, struct device *dev)
{
	struct ufs_vs_handle *handle = mgr->handle;
	struct ufs_log_cport *log_cport = &mgr->log_cport;
	u32 *buf_ptr;
	u8 *buf_ptr_out;
	u32 offset;
	u32 size = 0;
	u32 cur_ptr = 0;
	u32 tag = 0;
	u32 idx = 0;
	int tag_printed;

	/*
	 * CPort logger
	 *
	 * [ log type 0 ]
	 * First 4 double words
	 *
	 * [ log type 1 ]
	 * 6 double words, for Rx
	 * 8 double words, for Tx
	 *
	 * [ log type 2 ]
	 * 4 double words, for Command UPIU, DATA OUT/IN UPIU and RTT.
	 * 2 double words, otherwise.
	 *
	 */
	log_cport->ptr = cport_readl(handle, CPORT_LOG_PTR_OFFSET);
	buf_ptr = (u32 *)&log_cport->buf[0];
	size = 0;
	offset = 0;

	while (size < CPORT_BUF_SIZE) {
		*buf_ptr = cport_readl(handle, offset);
		size += 4;
		buf_ptr += 1;
		offset += 4;
	}

	/* memory barrier for ufs cport dump */
	mb();


	/* Print data */
	buf_ptr_out = &log_cport->buf[0];
	cur_ptr = 0;

	pr_memlog(mgr->mem_log, "cport logging finished\n");
	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ":\t\tCPORT\n");
	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
	dev_err(dev, "cport logging finished\n");
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ":\t\tCPORT\n");
	dev_err(dev, ":---------------------------------------------------\n");
#endif
	while (cur_ptr < CPORT_BUF_SIZE) {
		switch (cur_ptr) {
		case CPORT_TX_BUF_PTR:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tTX BUF (%d)\n", ((log_cport->ptr >> 0) & 0x3F)/2);
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tTX BUF (%d)\n", ((log_cport->ptr >> 0) & 0x3F)/2);
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			break;
		case  CPORT_RX_BUF_PTR:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tRX BUF (%d)\n", ((log_cport->ptr >> 8) & 0x3F)/2);
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tRX BUF (%d)\n", ((log_cport->ptr >> 8) & 0x3F)/2);
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			break;
		case CPORT_LOG_PTR_OFFSET:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tCPORT LOG PTR\n");
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tCPORT LOG PTR\n");
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			break;
		case CPORT_UTRL_PTR:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tUTRL\n");
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tUTRL\n");
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			tag = -1;
			idx = 0;
			break;
		case CPORT_UCD_PTR:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tUCD\n");
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tUCD\n");
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			tag = -1;
			idx = 0;
			break;
		case CPORT_UTMRL_PTR:
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
			pr_memlog(mgr->mem_log, ": \t\tUTMRL\n");
			pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, ":---------------------------------------------------\n");
			dev_err(dev, ": \t\tUTMRL\n");
			dev_err(dev, ":---------------------------------------------------\n");
#endif
			break;
		default:
			break;
		}

		if (cur_ptr == CPORT_LOG_PTR_OFFSET) {
			pr_memlog(mgr->mem_log, "%02x%02x%02x%02x ",
				*(buf_ptr_out+0x3), *(buf_ptr_out+0x2), *(buf_ptr_out+0x1), *(buf_ptr_out+0x0));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
			dev_err(dev, "%02x%02x%02x%02x ",
				*(buf_ptr_out+0x3), *(buf_ptr_out+0x2), *(buf_ptr_out+0x1), *(buf_ptr_out+0x0));
#endif
			buf_ptr_out += 0x100;
			cur_ptr += 0x100;
		} else {
			tag_printed = 0;
			if (cur_ptr >= CPORT_UTRL_PTR &&
					cur_ptr < CPORT_UTMRL_PTR) {
				if (idx++ % 2 == 0) {
					tag_printed = 1;
					tag++;
				}
			}
			__print_cport(dev, tag_printed, tag,
					(u32 *)buf_ptr_out, mgr);
			buf_ptr_out += 0x10;
			cur_ptr += 0x10;
		}
	}
}

static void __ufs_print_cmd_log(struct ufs_dbg_mgr *mgr, struct device *dev) {
	struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
	struct cmd_data *data = cmd_info->data;
	u32 i;
	u32 last;
	u32 max = MAX_CMD_LOGS;
	unsigned long flags;
	u32 total;

	spin_lock_irqsave(&mgr->cmd_lock, flags);
	total = cmd_info->total;
	if (cmd_info->total < max)
		max = cmd_info->total;
	last = (cmd_info->last + MAX_CMD_LOGS - 1) % MAX_CMD_LOGS;
	spin_unlock_irqrestore(&mgr->cmd_lock, flags);

	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ":\t\tSCSI CMD(%u)\n", total - 1);
	pr_memlog(mgr->mem_log, ":---------------------------------------------------\n");
	pr_memlog(mgr->mem_log, ": OP, TAG, LBA, SCT, RETRIES, STIME, ETIME, REQS\n\n");
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ":\t\tSCSI CMD(%u)\n", total - 1);
	dev_err(dev, ":---------------------------------------------------\n");
	dev_err(dev, ": OP, TAG, LBA, SCT, RETRIES, STIME, ETIME, REQS\n\n");
#endif

	for (i = 0 ; i < max ; i++, data++) {
		pr_memlog(mgr->mem_log, ": 0x%02x, %02d, 0x%08lx, 0x%04x, %d, %llu, %llu, 0x%lx %s\n",
				data->op,
				data->tag,
				data->lba,
				data->sct,
				data->retries,
				data->start_time,
				data->end_time,
				data->outstanding_reqs,
				((last == i) ? "<--" : " "));
#ifdef CONFIG_SCSI_UFS_EXYNOS_DUMP_TO_CONSOLE
		dev_err(dev, ": 0x%02x, %02d, 0x%08lx, 0x%04x, %d, %llu, %llu, 0x%lx %s\n",
			data->op,
			data->tag,
			data->lba,
			data->sct,
			data->retries,
			data->start_time,
			data->end_time,
			data->outstanding_reqs,
			((last == i) ? "<--" : " "));
#endif
		if (last == i)
			pr_memlog(mgr->mem_log, "\n");
	}
}

static void __ufs_put_cmd_log(struct ufs_dbg_mgr *mgr, struct cmd_data *cmd_data)
{
	struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
	unsigned long flags;
	struct cmd_data *pdata;

	spin_lock_irqsave(&mgr->cmd_lock, flags);
	pdata = &cmd_info->data[cmd_info->last];
	++cmd_info->total;
	cmd_info->last = (cmd_info->last + 1) % MAX_CMD_LOGS;
	spin_unlock_irqrestore(&mgr->cmd_lock, flags);

	pdata->op = cmd_data->op;
	pdata->tag = cmd_data->tag;
	pdata->lba = cmd_data->lba;
	pdata->sct = cmd_data->sct;
	pdata->retries = cmd_data->retries;
	pdata->start_time = cmd_data->start_time;
	pdata->end_time = 0;
	pdata->outstanding_reqs = cmd_data->outstanding_reqs;
	cmd_info->pdata[cmd_data->tag] = pdata;
}

static void __ufs_print_evt(struct ufs_dbg_mgr *mgr, u32 id,
			     char *err_name)
{
	int i;
	bool found = false;
	struct ufs_event_hist *e;

	if (id >= UFS_EVT_CNT)
		return;

	e = &mgr->ufs_stats.event[id];

	for (i = 0; i < UFS_EVENT_HIST_LENGTH; i++) {
		int p = (i + e->pos) % UFS_EVENT_HIST_LENGTH;

		if (e->tstamp[p] == 0)
			continue;
		pr_memlog(mgr->mem_log, "%s[%d] = 0x%x at %lld us\n", err_name, p,
		       e->val[p], ktime_to_us(e->tstamp[p]));
		found = true;
	}

	if (!found)
		pr_memlog(mgr->mem_log, "No record of %s\n", err_name);
}

static void __ufs_print_evt_hist(struct ufs_dbg_mgr *mgr)
{
	__ufs_print_evt(mgr, UFS_EVT_PA_ERR, "pa_err");
	__ufs_print_evt(mgr, UFS_EVT_DL_ERR, "dl_err");
	__ufs_print_evt(mgr, UFS_EVT_NL_ERR, "nl_err");
	__ufs_print_evt(mgr, UFS_EVT_TL_ERR, "tl_err");
	__ufs_print_evt(mgr, UFS_EVT_DME_ERR, "dme_err");
	__ufs_print_evt(mgr, UFS_EVT_AUTO_HIBERN8_ERR,
			 "auto_hibern8_err");
	__ufs_print_evt(mgr, UFS_EVT_FATAL_ERR, "fatal_err");
	__ufs_print_evt(mgr, UFS_EVT_LINK_STARTUP_FAIL,
			 "link_startup_fail");
	__ufs_print_evt(mgr, UFS_EVT_RESUME_ERR, "resume_fail");
	__ufs_print_evt(mgr, UFS_EVT_SUSPEND_ERR,
			 "suspend_fail");
	__ufs_print_evt(mgr, UFS_EVT_DEV_RESET, "dev_reset");
	__ufs_print_evt(mgr, UFS_EVT_HOST_RESET, "host_reset");
	__ufs_print_evt(mgr, UFS_EVT_ABORT, "task_abort");
}

/*
 * EXTERNAL FUNCTIONS
 *
 * There are two classes that are to initialize data structures for debug
 * and to define actual behavior.
 */
void exynos_ufs_dump_info(struct ufs_hba *hba,
			  struct ufs_vs_handle *handle, struct device *dev)
{
	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;

	if (mgr->active == 0)
		goto out;

	mgr->time = cpu_clock(raw_smp_processor_id());

	/* get context */
	__ufs_get_sfr(mgr, ufs_log_sfr);
	__ufs_get_attr(mgr, ufs_log_attr);
	memcpy(&mgr->ufs_stats, &hba->ufs_stats, sizeof(struct ufs_stats));

	/* show context */
	__ufs_print_sfr(handle, dev, ufs_log_sfr);
	__ufs_print_attr(handle, dev, ufs_log_attr);
	__ufs_print_cport(mgr, dev);
	__ufs_print_cmd_log(mgr, dev);
	__ufs_print_evt_hist(mgr);

	if (mgr->first_time == 0ULL)
		mgr->first_time = mgr->time;
out:
	return;
}

void exynos_ufs_cmd_log_start(struct ufs_vs_handle *handle,
				struct ufs_hba *hba, struct scsi_cmnd *cmd)
{
	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
	int cpu = raw_smp_processor_id();
	struct cmd_data *cmd_log = &mgr->cmd_log;	/* temp buffer to put */
	unsigned long lba = (cmd->cmnd[2] << 24) |
					(cmd->cmnd[3] << 16) |
					(cmd->cmnd[4] << 8) |
					(cmd->cmnd[5] << 0);
	unsigned int sct = (cmd->cmnd[7] << 8) |
					(cmd->cmnd[8] << 0);

	if (mgr->active == 0)
		return;

	cmd_log->start_time = cpu_clock(cpu);
	cmd_log->op = cmd->cmnd[0];
	cmd_log->tag = cmd->request->tag;
	/* This function runtime is protected by spinlock from outside */
	cmd_log->outstanding_reqs = hba->outstanding_reqs;

	/* unmap */
	if(cmd->cmnd[0] != UNMAP)	// TODO: for unmap
		cmd_log->lba = lba;

	cmd_log->sct = sct;
	cmd_log->retries = cmd->allowed;

	__ufs_put_cmd_log(mgr, cmd_log);
}

void exynos_ufs_cmd_log_end(struct ufs_vs_handle *handle,
				struct ufs_hba *hba, int tag)
{
	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
	struct ufs_cmd_info *cmd_info = &mgr->cmd_info;
	int cpu = raw_smp_processor_id();

	if (mgr->active == 0)
		return;

	if (!cmd_info->pdata[tag]) {
		pr_err("%s: there is no cmd logging inform about tag: %d\n",
				__func__, tag);
		return;
	}
	cmd_info->pdata[tag]->end_time = cpu_clock(cpu);
}

int exynos_ufs_dbg_set_lanes(struct ufs_vs_handle *handle,
				struct device *dev, u32 lanes)
{
	struct ufs_dbg_mgr *mgr = (struct ufs_dbg_mgr *)handle->private;
	int ret = 0;

	mgr->lanes = lanes;

	if (mgr->lanes > ATTR_NUM_MAX_LANES) {
		printk("%s: input lanes is too big: %u > %d\n",
				__func__, mgr->lanes, ATTR_NUM_MAX_LANES);
		ret = -1;
	}

	return ret;
}

static int ufs_memlog_file_completed(struct memlog_obj *obj, u32 flags)
{
	/* NOP */
	return 0;
}

static int ufs_memlog_status_notify(struct memlog_obj *obj, u32 flags)
{
	/* NOP */
	return 0;
}

static int ufs_memlog_level_notify(struct memlog_obj *obj, u32 flags)
{
	/* NOP */
	return 0;
}

static int ufs_memlog_enable_notify(struct memlog_obj *obj, u32 flags)
{
	/* NOP */
	return 0;
}

static const struct memlog_ops ufs_memlog_ops = {
	.file_ops_completed = ufs_memlog_file_completed,
	.log_status_notify = ufs_memlog_status_notify,
	.log_level_notify = ufs_memlog_level_notify,
	.log_enable_notify = ufs_memlog_enable_notify,
};

int exynos_ufs_init_mem_log(struct platform_device *pdev)
{
	struct exynos_ufs_memlog *memlog = &ufs_memlog[ufs_dbg_mgr_idx];
	struct memlog *desc;
	struct memlog_obj *log_obj;
	int ret;

	pr_info("%s: +++\n", __func__);

	ret = memlog_register("UFS", &pdev->dev, &desc);
	if (ret) {
		pr_err("failed to register memlog\n");
		return -1;
	}

	memlog->desc = desc;
	desc->ops = ufs_memlog_ops;

	log_obj = memlog_alloc_printf(desc,
					SZ_512K,
					NULL,
					"log-mem",
					0);

	if (log_obj) {
		memlog->log_obj = log_obj;
		memlog->log_enable = 1;
	} else {
		pr_err("%s: failed to alloc memlog memory for log\n",
			__func__);
		return -1;
	}
	pr_info("%s: ---\n", __func__);

	return 0;
}

int exynos_ufs_init_dbg(struct ufs_vs_handle *handle)
{
	struct ufs_dbg_mgr *mgr;
	int ret = -1;

	if (ufs_dbg_mgr_idx >= DBG_NUM_OF_HOSTS)
		goto out;

	mgr = &ufs_dbg[ufs_dbg_mgr_idx];
	mgr->mem_log = &ufs_memlog[ufs_dbg_mgr_idx];
	ufs_dbg_mgr_idx++;

	handle->private = (void *)mgr;
	mgr->handle = handle;
	mgr->active = 1;

	/* cmd log */
	spin_lock_init(&mgr->cmd_lock);
	ret = 0;
out:
	return ret;
}
MODULE_AUTHOR("Kiwoong Kim <kwmad.kim@samsung.com>");
MODULE_DESCRIPTION("Exynos UFS debug information");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1");