// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 HiSilicon Limited. */
#include <linux/hisi_acc_qm.h>
#include "qm_common.h"

#define QM_DFX_BASE			0x0100000
#define QM_DFX_STATE1			0x0104000
#define QM_DFX_STATE2			0x01040C8
#define QM_DFX_COMMON			0x0000
#define QM_DFX_BASE_LEN			0x5A
#define QM_DFX_STATE1_LEN		0x2E
#define QM_DFX_STATE2_LEN		0x11
#define QM_DFX_COMMON_LEN		0xC3
#define QM_DFX_REGS_LEN			4UL
#define QM_DBG_TMP_BUF_LEN		22
#define QM_XQC_ADDR_MASK		GENMASK(31, 0)
#define CURRENT_FUN_MASK		GENMASK(5, 0)
#define CURRENT_Q_MASK			GENMASK(31, 16)
#define QM_SQE_ADDR_MASK		GENMASK(7, 0)

#define QM_DFX_MB_CNT_VF		0x104010
#define QM_DFX_DB_CNT_VF		0x104020
#define QM_DFX_SQE_CNT_VF_SQN		0x104030
#define QM_DFX_CQE_CNT_VF_CQN		0x104040
#define QM_DFX_QN_SHIFT			16
#define QM_DFX_CNT_CLR_CE		0x100118
#define QM_DBG_WRITE_LEN		1024
#define QM_IN_IDLE_ST_REG		0x1040e4
#define QM_IN_IDLE_STATE		0x1

static const char * const qm_debug_file_name[] = {
	[CURRENT_QM]   = "current_qm",
	[CURRENT_Q]    = "current_q",
	[CLEAR_ENABLE] = "clear_enable",
};

static const char * const qm_s[] = {
	"work", "stop",
};

struct qm_dfx_item {
	const char *name;
	u32 offset;
};

struct qm_cmd_dump_item {
	const char *cmd;
	char *info_name;
	int (*dump_fn)(struct hisi_qm *qm, char *cmd, char *info_name);
};

static struct qm_dfx_item qm_dfx_files[] = {
	{"err_irq", offsetof(struct qm_dfx, err_irq_cnt)},
	{"aeq_irq", offsetof(struct qm_dfx, aeq_irq_cnt)},
	{"abnormal_irq", offsetof(struct qm_dfx, abnormal_irq_cnt)},
	{"create_qp_err", offsetof(struct qm_dfx, create_qp_err_cnt)},
	{"mb_err", offsetof(struct qm_dfx, mb_err_cnt)},
};

#define CNT_CYC_REGS_NUM		10
static const struct debugfs_reg32 qm_dfx_regs[] = {
	/* XXX_CNT are reading clear register */
	{"QM_ECC_1BIT_CNT               ",  0x104000},
	{"QM_ECC_MBIT_CNT               ",  0x104008},
	{"QM_DFX_MB_CNT                 ",  0x104018},
	{"QM_DFX_DB_CNT                 ",  0x104028},
	{"QM_DFX_SQE_CNT                ",  0x104038},
	{"QM_DFX_CQE_CNT                ",  0x104048},
	{"QM_DFX_SEND_SQE_TO_ACC_CNT    ",  0x104050},
	{"QM_DFX_WB_SQE_FROM_ACC_CNT    ",  0x104058},
	{"QM_DFX_ACC_FINISH_CNT         ",  0x104060},
	{"QM_DFX_CQE_ERR_CNT            ",  0x1040b4},
	{"QM_DFX_FUNS_ACTIVE_ST         ",  0x200},
	{"QM_ECC_1BIT_INF               ",  0x104004},
	{"QM_ECC_MBIT_INF               ",  0x10400c},
	{"QM_DFX_ACC_RDY_VLD0           ",  0x1040a0},
	{"QM_DFX_ACC_RDY_VLD1           ",  0x1040a4},
	{"QM_DFX_AXI_RDY_VLD            ",  0x1040a8},
	{"QM_DFX_FF_ST0                 ",  0x1040c8},
	{"QM_DFX_FF_ST1                 ",  0x1040cc},
	{"QM_DFX_FF_ST2                 ",  0x1040d0},
	{"QM_DFX_FF_ST3                 ",  0x1040d4},
	{"QM_DFX_FF_ST4                 ",  0x1040d8},
	{"QM_DFX_FF_ST5                 ",  0x1040dc},
	{"QM_DFX_FF_ST6                 ",  0x1040e0},
	{"QM_IN_IDLE_ST                 ",  0x1040e4},
	{"QM_CACHE_CTL                  ",  0x100050},
	{"QM_TIMEOUT_CFG                ",  0x100070},
	{"QM_DB_TIMEOUT_CFG             ",  0x100074},
	{"QM_FLR_PENDING_TIME_CFG       ",  0x100078},
	{"QM_ARUSR_MCFG1                ",  0x100088},
	{"QM_AWUSR_MCFG1                ",  0x100098},
	{"QM_AXI_M_CFG_ENABLE           ",  0x1000B0},
	{"QM_RAS_CE_THRESHOLD           ",  0x1000F8},
	{"QM_AXI_TIMEOUT_CTRL           ",  0x100120},
	{"QM_AXI_TIMEOUT_STATUS         ",  0x100124},
	{"QM_CQE_AGGR_TIMEOUT_CTRL      ",  0x100144},
	{"ACC_RAS_MSI_INT_SEL           ",  0x1040fc},
	{"QM_CQE_OUT                    ",  0x104100},
	{"QM_EQE_OUT                    ",  0x104104},
	{"QM_AEQE_OUT                   ",  0x104108},
	{"QM_DB_INFO0                   ",  0x104180},
	{"QM_DB_INFO1                   ",  0x104184},
	{"QM_AM_CTRL_GLOBAL             ",  0x300000},
	{"QM_AM_CURR_PORT_STS           ",  0x300100},
	{"QM_AM_CURR_TRANS_RETURN       ",  0x300150},
	{"QM_AM_CURR_RD_MAX_TXID        ",  0x300154},
	{"QM_AM_CURR_WR_MAX_TXID        ",  0x300158},
	{"QM_AM_ALARM_RRESP             ",  0x300180},
	{"QM_AM_ALARM_BRESP             ",  0x300184},
};

static const struct debugfs_reg32 qm_vf_dfx_regs[] = {
	{"QM_DFX_FUNS_ACTIVE_ST         ",  0x200},
};

/* define the QM's dfx regs region and region length */
static struct dfx_diff_registers qm_diff_regs[] = {
	{
		.reg_offset = QM_DFX_BASE,
		.reg_len = QM_DFX_BASE_LEN,
	}, {
		.reg_offset = QM_DFX_STATE1,
		.reg_len = QM_DFX_STATE1_LEN,
	}, {
		.reg_offset = QM_DFX_STATE2,
		.reg_len = QM_DFX_STATE2_LEN,
	}, {
		.reg_offset = QM_DFX_COMMON,
		.reg_len = QM_DFX_COMMON_LEN,
	},
};

static struct hisi_qm *file_to_qm(struct debugfs_file *file)
{
	struct qm_debug *debug = file->debug;

	return container_of(debug, struct hisi_qm, debug);
}

static ssize_t qm_cmd_read(struct file *filp, char __user *buffer,
			   size_t count, loff_t *pos)
{
	char buf[QM_DBG_READ_LEN];
	int len;

	len = scnprintf(buf, QM_DBG_READ_LEN, "%s\n",
			"Please echo help to cmd to get help information");

	return simple_read_from_buffer(buffer, count, pos, buf, len);
}

static void dump_show(struct hisi_qm *qm, void *info,
		     unsigned int info_size, char *info_name)
{
	struct device *dev = &qm->pdev->dev;
	u8 *info_curr = info;
	u32 i;
#define BYTE_PER_DW	4

	dev_info(dev, "%s DUMP\n", info_name);
	for (i = 0; i < info_size; i += BYTE_PER_DW, info_curr += BYTE_PER_DW) {
		pr_info("DW%u: %02X%02X %02X%02X\n", i / BYTE_PER_DW,
			*(info_curr + 3), *(info_curr + 2), *(info_curr + 1), *(info_curr));
	}
}

static int qm_sqc_dump(struct hisi_qm *qm, char *s, char *name)
{
	struct device *dev = &qm->pdev->dev;
	struct qm_sqc sqc;
	u32 qp_id;
	int ret;

	if (!s)
		return -EINVAL;

	ret = kstrtou32(s, 0, &qp_id);
	if (ret || qp_id >= qm->qp_num) {
		dev_err(dev, "Please input qp num (0-%u)", qm->qp_num - 1);
		return -EINVAL;
	}

	ret = qm_set_and_get_xqc(qm, QM_MB_CMD_SQC, &sqc, qp_id, 1);
	if (!ret) {
		sqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
		sqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
		dump_show(qm, &sqc, sizeof(struct qm_sqc), name);

		return 0;
	}

	down_read(&qm->qps_lock);
	if (qm->sqc) {
		memcpy(&sqc, qm->sqc + qp_id * sizeof(struct qm_sqc), sizeof(struct qm_sqc));
		sqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
		sqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
		dump_show(qm, &sqc, sizeof(struct qm_sqc), "SOFT SQC");
	}
	up_read(&qm->qps_lock);

	return 0;
}

static int qm_cqc_dump(struct hisi_qm *qm, char *s, char *name)
{
	struct device *dev = &qm->pdev->dev;
	struct qm_cqc cqc;
	u32 qp_id;
	int ret;

	if (!s)
		return -EINVAL;

	ret = kstrtou32(s, 0, &qp_id);
	if (ret || qp_id >= qm->qp_num) {
		dev_err(dev, "Please input qp num (0-%u)", qm->qp_num - 1);
		return -EINVAL;
	}

	ret = qm_set_and_get_xqc(qm, QM_MB_CMD_CQC, &cqc, qp_id, 1);
	if (!ret) {
		cqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
		cqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
		dump_show(qm, &cqc, sizeof(struct qm_cqc), name);

		return 0;
	}

	down_read(&qm->qps_lock);
	if (qm->cqc) {
		memcpy(&cqc, qm->cqc + qp_id * sizeof(struct qm_cqc), sizeof(struct qm_cqc));
		cqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
		cqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
		dump_show(qm, &cqc, sizeof(struct qm_cqc), "SOFT CQC");
	}
	up_read(&qm->qps_lock);

	return 0;
}

static int qm_eqc_aeqc_dump(struct hisi_qm *qm, char *s, char *name)
{
	struct device *dev = &qm->pdev->dev;
	struct qm_aeqc aeqc;
	struct qm_eqc eqc;
	size_t size;
	void *xeqc;
	int ret;
	u8 cmd;

	if (strsep(&s, " ")) {
		dev_err(dev, "Please do not input extra characters!\n");
		return -EINVAL;
	}

	if (!strcmp(name, "EQC")) {
		cmd = QM_MB_CMD_EQC;
		size = sizeof(struct qm_eqc);
		xeqc = &eqc;
	} else {
		cmd = QM_MB_CMD_AEQC;
		size = sizeof(struct qm_aeqc);
		xeqc = &aeqc;
	}

	ret = qm_set_and_get_xqc(qm, cmd, xeqc, 0, 1);
	if (ret)
		return ret;

	aeqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
	aeqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
	eqc.base_h = cpu_to_le32(QM_XQC_ADDR_MASK);
	eqc.base_l = cpu_to_le32(QM_XQC_ADDR_MASK);
	dump_show(qm, xeqc, size, name);

	return ret;
}

static int q_dump_param_parse(struct hisi_qm *qm, char *s,
			      u32 *e_id, u32 *q_id, u16 q_depth)
{
	struct device *dev = &qm->pdev->dev;
	unsigned int qp_num = qm->qp_num;
	char *presult;
	int ret;

	presult = strsep(&s, " ");
	if (!presult) {
		dev_err(dev, "Please input qp number!\n");
		return -EINVAL;
	}

	ret = kstrtou32(presult, 0, q_id);
	if (ret || *q_id >= qp_num) {
		dev_err(dev, "Please input qp num (0-%u)", qp_num - 1);
		return -EINVAL;
	}

	presult = strsep(&s, " ");
	if (!presult) {
		dev_err(dev, "Please input sqe number!\n");
		return -EINVAL;
	}

	ret = kstrtou32(presult, 0, e_id);
	if (ret || *e_id >= q_depth) {
		dev_err(dev, "Please input sqe num (0-%u)", q_depth - 1);
		return -EINVAL;
	}

	if (strsep(&s, " ")) {
		dev_err(dev, "Please do not input extra characters!\n");
		return -EINVAL;
	}

	return 0;
}

static int qm_sq_dump(struct hisi_qm *qm, char *s, char *name)
{
	u16 sq_depth = qm->qp_array->sq_depth;
	struct hisi_qp *qp;
	u32 qp_id, sqe_id;
	void *sqe;
	int ret;

	ret = q_dump_param_parse(qm, s, &sqe_id, &qp_id, sq_depth);
	if (ret)
		return ret;

	sqe = kzalloc(qm->sqe_size, GFP_KERNEL);
	if (!sqe)
		return -ENOMEM;

	qp = &qm->qp_array[qp_id];
	memcpy(sqe, qp->sqe + sqe_id * qm->sqe_size, qm->sqe_size);
	memset(sqe + qm->debug.sqe_mask_offset, QM_SQE_ADDR_MASK,
	       qm->debug.sqe_mask_len);

	dump_show(qm, sqe, qm->sqe_size, name);

	kfree(sqe);

	return 0;
}

static int qm_cq_dump(struct hisi_qm *qm, char *s, char *name)
{
	struct qm_cqe *cqe_curr;
	struct hisi_qp *qp;
	u32 qp_id, cqe_id;
	int ret;

	ret = q_dump_param_parse(qm, s, &cqe_id, &qp_id, qm->qp_array->cq_depth);
	if (ret)
		return ret;

	qp = &qm->qp_array[qp_id];
	cqe_curr = qp->cqe + cqe_id;
	dump_show(qm, cqe_curr, sizeof(struct qm_cqe), name);

	return 0;
}

static int qm_eq_aeq_dump(struct hisi_qm *qm, char *s, char *name)
{
	struct device *dev = &qm->pdev->dev;
	u16 xeq_depth;
	size_t size;
	void *xeqe;
	u32 xeqe_id;
	int ret;

	if (!s)
		return -EINVAL;

	ret = kstrtou32(s, 0, &xeqe_id);
	if (ret)
		return -EINVAL;

	if (!strcmp(name, "EQE")) {
		xeq_depth = qm->eq_depth;
		size = sizeof(struct qm_eqe);
	} else {
		xeq_depth = qm->aeq_depth;
		size = sizeof(struct qm_aeqe);
	}

	if (xeqe_id >= xeq_depth) {
		dev_err(dev, "Please input eqe or aeqe num (0-%u)", xeq_depth - 1);
		return -EINVAL;
	}

	down_read(&qm->qps_lock);

	if (qm->eqe && !strcmp(name, "EQE")) {
		xeqe = qm->eqe + xeqe_id;
	} else if (qm->aeqe && !strcmp(name, "AEQE")) {
		xeqe = qm->aeqe + xeqe_id;
	} else {
		ret = -EINVAL;
		goto err_unlock;
	}

	dump_show(qm, xeqe, size, name);

err_unlock:
	up_read(&qm->qps_lock);
	return ret;
}

static int qm_dbg_help(struct hisi_qm *qm, char *s)
{
	struct device *dev = &qm->pdev->dev;

	if (strsep(&s, " ")) {
		dev_err(dev, "Please do not input extra characters!\n");
		return -EINVAL;
	}

	dev_info(dev, "available commands:\n");
	dev_info(dev, "sqc <num>\n");
	dev_info(dev, "cqc <num>\n");
	dev_info(dev, "eqc\n");
	dev_info(dev, "aeqc\n");
	dev_info(dev, "sq <num> <e>\n");
	dev_info(dev, "cq <num> <e>\n");
	dev_info(dev, "eq <e>\n");
	dev_info(dev, "aeq <e>\n");

	return 0;
}

static const struct qm_cmd_dump_item qm_cmd_dump_table[] = {
	{
		.cmd = "sqc",
		.info_name = "SQC",
		.dump_fn = qm_sqc_dump,
	}, {
		.cmd = "cqc",
		.info_name = "CQC",
		.dump_fn = qm_cqc_dump,
	}, {
		.cmd = "eqc",
		.info_name = "EQC",
		.dump_fn = qm_eqc_aeqc_dump,
	}, {
		.cmd = "aeqc",
		.info_name = "AEQC",
		.dump_fn = qm_eqc_aeqc_dump,
	}, {
		.cmd = "sq",
		.info_name = "SQE",
		.dump_fn = qm_sq_dump,
	}, {
		.cmd = "cq",
		.info_name = "CQE",
		.dump_fn = qm_cq_dump,
	}, {
		.cmd = "eq",
		.info_name = "EQE",
		.dump_fn = qm_eq_aeq_dump,
	}, {
		.cmd = "aeq",
		.info_name = "AEQE",
		.dump_fn = qm_eq_aeq_dump,
	},
};

static int qm_cmd_write_dump(struct hisi_qm *qm, const char *cmd_buf)
{
	struct device *dev = &qm->pdev->dev;
	char *presult, *s, *s_tmp;
	int table_size, i, ret;

	s = kstrdup(cmd_buf, GFP_KERNEL);
	if (!s)
		return -ENOMEM;

	s_tmp = s;
	presult = strsep(&s, " ");
	if (!presult) {
		ret = -EINVAL;
		goto err_buffer_free;
	}

	if (!strcmp(presult, "help")) {
		ret = qm_dbg_help(qm, s);
		goto err_buffer_free;
	}

	table_size = ARRAY_SIZE(qm_cmd_dump_table);
	for (i = 0; i < table_size; i++) {
		if (!strcmp(presult, qm_cmd_dump_table[i].cmd)) {
			ret = qm_cmd_dump_table[i].dump_fn(qm, s,
				qm_cmd_dump_table[i].info_name);
			break;
		}
	}

	if (i == table_size) {
		dev_info(dev, "Please echo help\n");
		ret = -EINVAL;
	}

err_buffer_free:
	kfree(s_tmp);

	return ret;
}

static ssize_t qm_cmd_write(struct file *filp, const char __user *buffer,
			    size_t count, loff_t *pos)
{
	struct hisi_qm *qm = filp->private_data;
	char *cmd_buf, *cmd_buf_tmp;
	int ret;

	if (*pos)
		return 0;

	ret = hisi_qm_get_dfx_access(qm);
	if (ret)
		return ret;

	/* Judge if the instance is being reset. */
	if (unlikely(atomic_read(&qm->status.flags) == QM_STOP)) {
		ret = 0;
		goto put_dfx_access;
	}

	if (count > QM_DBG_WRITE_LEN) {
		ret = -ENOSPC;
		goto put_dfx_access;
	}

	cmd_buf = memdup_user_nul(buffer, count);
	if (IS_ERR(cmd_buf)) {
		ret = PTR_ERR(cmd_buf);
		goto put_dfx_access;
	}

	cmd_buf_tmp = strchr(cmd_buf, '\n');
	if (cmd_buf_tmp) {
		*cmd_buf_tmp = '\0';
		count = cmd_buf_tmp - cmd_buf + 1;
	}

	ret = qm_cmd_write_dump(qm, cmd_buf);
	if (ret) {
		kfree(cmd_buf);
		goto put_dfx_access;
	}

	kfree(cmd_buf);

	ret = count;

put_dfx_access:
	hisi_qm_put_dfx_access(qm);
	return ret;
}

static const struct file_operations qm_cmd_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = qm_cmd_read,
	.write = qm_cmd_write,
};

/**
 * hisi_qm_regs_dump() - Dump registers's value.
 * @s: debugfs file handle.
 * @regset: accelerator registers information.
 *
 * Dump accelerator registers.
 */
void hisi_qm_regs_dump(struct seq_file *s, struct debugfs_regset32 *regset)
{
	struct pci_dev *pdev = to_pci_dev(regset->dev);
	struct hisi_qm *qm = pci_get_drvdata(pdev);
	const struct debugfs_reg32 *regs = regset->regs;
	int regs_len = regset->nregs;
	int i, ret;
	u32 val;

	ret = hisi_qm_get_dfx_access(qm);
	if (ret)
		return;

	for (i = 0; i < regs_len; i++) {
		val = readl(regset->base + regs[i].offset);
		seq_printf(s, "%s= 0x%08x\n", regs[i].name, val);
	}

	hisi_qm_put_dfx_access(qm);
}
EXPORT_SYMBOL_GPL(hisi_qm_regs_dump);

static int qm_regs_show(struct seq_file *s, void *unused)
{
	struct hisi_qm *qm = s->private;
	struct debugfs_regset32 regset;

	if (qm->fun_type == QM_HW_PF) {
		regset.regs = qm_dfx_regs;
		regset.nregs = ARRAY_SIZE(qm_dfx_regs);
	} else {
		regset.regs = qm_vf_dfx_regs;
		regset.nregs = ARRAY_SIZE(qm_vf_dfx_regs);
	}

	regset.base = qm->io_base;
	regset.dev = &qm->pdev->dev;

	hisi_qm_regs_dump(s, &regset);

	return 0;
}

DEFINE_SHOW_ATTRIBUTE(qm_regs);

static u32 current_q_read(struct hisi_qm *qm)
{
	return readl(qm->io_base + QM_DFX_SQE_CNT_VF_SQN) >> QM_DFX_QN_SHIFT;
}

static int current_q_write(struct hisi_qm *qm, u32 val)
{
	u32 tmp;

	if (val >= qm->debug.curr_qm_qp_num)
		return -EINVAL;

	tmp = val << QM_DFX_QN_SHIFT |
	      (readl(qm->io_base + QM_DFX_SQE_CNT_VF_SQN) & CURRENT_FUN_MASK);
	writel(tmp, qm->io_base + QM_DFX_SQE_CNT_VF_SQN);

	tmp = val << QM_DFX_QN_SHIFT |
	      (readl(qm->io_base + QM_DFX_CQE_CNT_VF_CQN) & CURRENT_FUN_MASK);
	writel(tmp, qm->io_base + QM_DFX_CQE_CNT_VF_CQN);

	return 0;
}

static u32 clear_enable_read(struct hisi_qm *qm)
{
	return readl(qm->io_base + QM_DFX_CNT_CLR_CE);
}

/* rd_clr_ctrl 1 enable read clear, otherwise 0 disable it */
static int clear_enable_write(struct hisi_qm *qm, u32 rd_clr_ctrl)
{
	if (rd_clr_ctrl > 1)
		return -EINVAL;

	writel(rd_clr_ctrl, qm->io_base + QM_DFX_CNT_CLR_CE);

	return 0;
}

static u32 current_qm_read(struct hisi_qm *qm)
{
	return readl(qm->io_base + QM_DFX_MB_CNT_VF);
}

static int qm_get_vf_qp_num(struct hisi_qm *qm, u32 fun_num)
{
	u32 remain_q_num, vfq_num;
	u32 num_vfs = qm->vfs_num;

	vfq_num = (qm->ctrl_qp_num - qm->qp_num) / num_vfs;
	if (vfq_num >= qm->max_qp_num)
		return qm->max_qp_num;

	remain_q_num = (qm->ctrl_qp_num - qm->qp_num) % num_vfs;
	if (vfq_num + remain_q_num <= qm->max_qp_num)
		return fun_num == num_vfs ? vfq_num + remain_q_num : vfq_num;

	/*
	 * if vfq_num + remain_q_num > max_qp_num, the last VFs,
	 * each with one more queue.
	 */
	return fun_num + remain_q_num > num_vfs ? vfq_num + 1 : vfq_num;
}

static int current_qm_write(struct hisi_qm *qm, u32 val)
{
	u32 tmp;

	if (val > qm->vfs_num)
		return -EINVAL;

	/* According PF or VF Dev ID to calculation curr_qm_qp_num and store */
	if (!val)
		qm->debug.curr_qm_qp_num = qm->qp_num;
	else
		qm->debug.curr_qm_qp_num = qm_get_vf_qp_num(qm, val);

	writel(val, qm->io_base + QM_DFX_MB_CNT_VF);
	writel(val, qm->io_base + QM_DFX_DB_CNT_VF);

	tmp = val |
	      (readl(qm->io_base + QM_DFX_SQE_CNT_VF_SQN) & CURRENT_Q_MASK);
	writel(tmp, qm->io_base + QM_DFX_SQE_CNT_VF_SQN);

	tmp = val |
	      (readl(qm->io_base + QM_DFX_CQE_CNT_VF_CQN) & CURRENT_Q_MASK);
	writel(tmp, qm->io_base + QM_DFX_CQE_CNT_VF_CQN);

	return 0;
}

static ssize_t qm_debug_read(struct file *filp, char __user *buf,
			     size_t count, loff_t *pos)
{
	struct debugfs_file *file = filp->private_data;
	enum qm_debug_file index = file->index;
	struct hisi_qm *qm = file_to_qm(file);
	char tbuf[QM_DBG_TMP_BUF_LEN];
	u32 val;
	int ret;

	ret = hisi_qm_get_dfx_access(qm);
	if (ret)
		return ret;

	mutex_lock(&file->lock);
	switch (index) {
	case CURRENT_QM:
		val = current_qm_read(qm);
		break;
	case CURRENT_Q:
		val = current_q_read(qm);
		break;
	case CLEAR_ENABLE:
		val = clear_enable_read(qm);
		break;
	default:
		goto err_input;
	}
	mutex_unlock(&file->lock);

	hisi_qm_put_dfx_access(qm);
	ret = scnprintf(tbuf, QM_DBG_TMP_BUF_LEN, "%u\n", val);
	return simple_read_from_buffer(buf, count, pos, tbuf, ret);

err_input:
	mutex_unlock(&file->lock);
	hisi_qm_put_dfx_access(qm);
	return -EINVAL;
}

static ssize_t qm_debug_write(struct file *filp, const char __user *buf,
			      size_t count, loff_t *pos)
{
	struct debugfs_file *file = filp->private_data;
	enum qm_debug_file index = file->index;
	struct hisi_qm *qm = file_to_qm(file);
	unsigned long val;
	char tbuf[QM_DBG_TMP_BUF_LEN];
	int len, ret;

	if (*pos != 0)
		return 0;

	if (count >= QM_DBG_TMP_BUF_LEN)
		return -ENOSPC;

	len = simple_write_to_buffer(tbuf, QM_DBG_TMP_BUF_LEN - 1, pos, buf,
				     count);
	if (len < 0)
		return len;

	tbuf[len] = '\0';
	if (kstrtoul(tbuf, 0, &val))
		return -EFAULT;

	ret = hisi_qm_get_dfx_access(qm);
	if (ret)
		return ret;

	mutex_lock(&file->lock);
	switch (index) {
	case CURRENT_QM:
		ret = current_qm_write(qm, val);
		break;
	case CURRENT_Q:
		ret = current_q_write(qm, val);
		break;
	case CLEAR_ENABLE:
		ret = clear_enable_write(qm, val);
		break;
	default:
		ret = -EINVAL;
	}
	mutex_unlock(&file->lock);

	hisi_qm_put_dfx_access(qm);

	if (ret)
		return ret;

	return count;
}

static const struct file_operations qm_debug_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = qm_debug_read,
	.write = qm_debug_write,
};

static void dfx_regs_uninit(struct hisi_qm *qm,
		struct dfx_diff_registers *dregs, int reg_len)
{
	int i;

	if (!dregs)
		return;

	/* Setting the pointer is NULL to prevent double free */
	for (i = 0; i < reg_len; i++) {
		if (!dregs[i].regs)
			continue;

		kfree(dregs[i].regs);
		dregs[i].regs = NULL;
	}
	kfree(dregs);
}

static struct dfx_diff_registers *dfx_regs_init(struct hisi_qm *qm,
	const struct dfx_diff_registers *cregs, u32 reg_len)
{
	struct dfx_diff_registers *diff_regs;
	u32 j, base_offset;
	int i;

	diff_regs = kcalloc(reg_len, sizeof(*diff_regs), GFP_KERNEL);
	if (!diff_regs)
		return ERR_PTR(-ENOMEM);

	for (i = 0; i < reg_len; i++) {
		if (!cregs[i].reg_len)
			continue;

		diff_regs[i].reg_offset = cregs[i].reg_offset;
		diff_regs[i].reg_len = cregs[i].reg_len;
		diff_regs[i].regs = kcalloc(QM_DFX_REGS_LEN, cregs[i].reg_len,
					 GFP_KERNEL);
		if (!diff_regs[i].regs)
			goto alloc_error;

		for (j = 0; j < diff_regs[i].reg_len; j++) {
			base_offset = diff_regs[i].reg_offset +
					j * QM_DFX_REGS_LEN;
			diff_regs[i].regs[j] = readl(qm->io_base + base_offset);
		}
	}

	return diff_regs;

alloc_error:
	while (i > 0) {
		i--;
		kfree(diff_regs[i].regs);
	}
	kfree(diff_regs);
	return ERR_PTR(-ENOMEM);
}

static int qm_diff_regs_init(struct hisi_qm *qm,
		struct dfx_diff_registers *dregs, u32 reg_len)
{
	int ret;

	qm->debug.qm_diff_regs = dfx_regs_init(qm, qm_diff_regs, ARRAY_SIZE(qm_diff_regs));
	if (IS_ERR(qm->debug.qm_diff_regs)) {
		ret = PTR_ERR(qm->debug.qm_diff_regs);
		qm->debug.qm_diff_regs = NULL;
		return ret;
	}

	qm->debug.acc_diff_regs = dfx_regs_init(qm, dregs, reg_len);
	if (IS_ERR(qm->debug.acc_diff_regs)) {
		dfx_regs_uninit(qm, qm->debug.qm_diff_regs, ARRAY_SIZE(qm_diff_regs));
		ret = PTR_ERR(qm->debug.acc_diff_regs);
		qm->debug.acc_diff_regs = NULL;
		return ret;
	}

	return 0;
}

static void qm_last_regs_uninit(struct hisi_qm *qm)
{
	struct qm_debug *debug = &qm->debug;

	if (qm->fun_type == QM_HW_VF || !debug->qm_last_words)
		return;

	kfree(debug->qm_last_words);
	debug->qm_last_words = NULL;
}

static int qm_last_regs_init(struct hisi_qm *qm)
{
	int dfx_regs_num = ARRAY_SIZE(qm_dfx_regs);
	struct qm_debug *debug = &qm->debug;
	int i;

	if (qm->fun_type == QM_HW_VF)
		return 0;

	debug->qm_last_words = kcalloc(dfx_regs_num, sizeof(unsigned int), GFP_KERNEL);
	if (!debug->qm_last_words)
		return -ENOMEM;

	for (i = 0; i < dfx_regs_num; i++) {
		debug->qm_last_words[i] = readl_relaxed(qm->io_base +
			qm_dfx_regs[i].offset);
	}

	return 0;
}

static void qm_diff_regs_uninit(struct hisi_qm *qm, u32 reg_len)
{
	dfx_regs_uninit(qm, qm->debug.acc_diff_regs, reg_len);
	qm->debug.acc_diff_regs = NULL;
	dfx_regs_uninit(qm, qm->debug.qm_diff_regs, ARRAY_SIZE(qm_diff_regs));
	qm->debug.qm_diff_regs = NULL;
}

/**
 * hisi_qm_regs_debugfs_init() - Allocate memory for registers.
 * @qm: device qm handle.
 * @dregs: diff registers handle.
 * @reg_len: diff registers region length.
 */
int hisi_qm_regs_debugfs_init(struct hisi_qm *qm,
		struct dfx_diff_registers *dregs, u32 reg_len)
{
	int ret;

	if (!qm || !dregs)
		return -EINVAL;

	if (qm->fun_type != QM_HW_PF)
		return 0;

	ret = qm_last_regs_init(qm);
	if (ret) {
		dev_info(&qm->pdev->dev, "failed to init qm words memory!\n");
		return ret;
	}

	ret = qm_diff_regs_init(qm, dregs, reg_len);
	if (ret) {
		qm_last_regs_uninit(qm);
		return ret;
	}

	return 0;
}
EXPORT_SYMBOL_GPL(hisi_qm_regs_debugfs_init);

/**
 * hisi_qm_regs_debugfs_uninit() - Free memory for registers.
 * @qm: device qm handle.
 * @reg_len: diff registers region length.
 */
void hisi_qm_regs_debugfs_uninit(struct hisi_qm *qm, u32 reg_len)
{
	if (!qm || qm->fun_type != QM_HW_PF)
		return;

	qm_diff_regs_uninit(qm, reg_len);
	qm_last_regs_uninit(qm);
}
EXPORT_SYMBOL_GPL(hisi_qm_regs_debugfs_uninit);

/**
 * hisi_qm_acc_diff_regs_dump() - Dump registers's value.
 * @qm: device qm handle.
 * @s: Debugfs file handle.
 * @dregs: diff registers handle.
 * @regs_len: diff registers region length.
 */
void hisi_qm_acc_diff_regs_dump(struct hisi_qm *qm, struct seq_file *s,
	struct dfx_diff_registers *dregs, u32 regs_len)
{
	u32 j, val, base_offset;
	int i, ret;

	if (!qm || !s || !dregs)
		return;

	ret = hisi_qm_get_dfx_access(qm);
	if (ret)
		return;

	down_read(&qm->qps_lock);
	for (i = 0; i < regs_len; i++) {
		if (!dregs[i].reg_len)
			continue;

		for (j = 0; j < dregs[i].reg_len; j++) {
			base_offset = dregs[i].reg_offset + j * QM_DFX_REGS_LEN;
			val = readl(qm->io_base + base_offset);
			if (val != dregs[i].regs[j])
				seq_printf(s, "0x%08x = 0x%08x ---> 0x%08x\n",
					   base_offset, dregs[i].regs[j], val);
		}
	}
	up_read(&qm->qps_lock);

	hisi_qm_put_dfx_access(qm);
}
EXPORT_SYMBOL_GPL(hisi_qm_acc_diff_regs_dump);

void hisi_qm_show_last_dfx_regs(struct hisi_qm *qm)
{
	struct qm_debug *debug = &qm->debug;
	struct pci_dev *pdev = qm->pdev;
	u32 val;
	int i;

	if (qm->fun_type == QM_HW_VF || !debug->qm_last_words)
		return;

	for (i = 0; i < ARRAY_SIZE(qm_dfx_regs); i++) {
		val = readl_relaxed(qm->io_base + qm_dfx_regs[i].offset);
		if (debug->qm_last_words[i] != val)
			pci_info(pdev, "%s \t= 0x%08x => 0x%08x\n",
			qm_dfx_regs[i].name, debug->qm_last_words[i], val);
	}
}

static int qm_diff_regs_show(struct seq_file *s, void *unused)
{
	struct hisi_qm *qm = s->private;

	hisi_qm_acc_diff_regs_dump(qm, s, qm->debug.qm_diff_regs,
					ARRAY_SIZE(qm_diff_regs));

	return 0;
}
DEFINE_SHOW_ATTRIBUTE(qm_diff_regs);

static int qm_state_show(struct seq_file *s, void *unused)
{
	struct hisi_qm *qm = s->private;
	u32 val;
	int ret;

	/* If device is in suspended, directly return the idle state. */
	ret = hisi_qm_get_dfx_access(qm);
	if (!ret) {
		val = readl(qm->io_base + QM_IN_IDLE_ST_REG);
		hisi_qm_put_dfx_access(qm);
	} else if (ret == -EAGAIN) {
		val = QM_IN_IDLE_STATE;
	} else {
		return ret;
	}

	seq_printf(s, "%u\n", val);

	return 0;
}

DEFINE_SHOW_ATTRIBUTE(qm_state);

static ssize_t qm_status_read(struct file *filp, char __user *buffer,
			      size_t count, loff_t *pos)
{
	struct hisi_qm *qm = filp->private_data;
	char buf[QM_DBG_READ_LEN];
	int val, len;

	val = atomic_read(&qm->status.flags);
	len = scnprintf(buf, QM_DBG_READ_LEN, "%s\n", qm_s[val]);

	return simple_read_from_buffer(buffer, count, pos, buf, len);
}

static const struct file_operations qm_status_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = qm_status_read,
};

static void qm_create_debugfs_file(struct hisi_qm *qm, struct dentry *dir,
				   enum qm_debug_file index)
{
	struct debugfs_file *file = qm->debug.files + index;

	file->index = index;
	mutex_init(&file->lock);
	file->debug = &qm->debug;

	debugfs_create_file(qm_debug_file_name[index], 0600, dir, file,
			    &qm_debug_fops);
}

static int qm_debugfs_atomic64_set(void *data, u64 val)
{
	if (val)
		return -EINVAL;

	atomic64_set((atomic64_t *)data, 0);

	return 0;
}

static int qm_debugfs_atomic64_get(void *data, u64 *val)
{
	*val = atomic64_read((atomic64_t *)data);

	return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(qm_atomic64_ops, qm_debugfs_atomic64_get,
			 qm_debugfs_atomic64_set, "%llu\n");

/**
 * hisi_qm_debug_init() - Initialize qm related debugfs files.
 * @qm: The qm for which we want to add debugfs files.
 *
 * Create qm related debugfs files.
 */
void hisi_qm_debug_init(struct hisi_qm *qm)
{
	struct dfx_diff_registers *qm_regs = qm->debug.qm_diff_regs;
	struct qm_dev_dfx *dev_dfx = &qm->debug.dev_dfx;
	struct qm_dfx *dfx = &qm->debug.dfx;
	struct dentry *qm_d;
	void *data;
	int i;

	qm_d = debugfs_create_dir("qm", qm->debug.debug_root);
	qm->debug.qm_d = qm_d;

	/* only show this in PF */
	if (qm->fun_type == QM_HW_PF) {
		debugfs_create_file("qm_state", 0444, qm->debug.qm_d,
					qm, &qm_state_fops);

		qm_create_debugfs_file(qm, qm->debug.debug_root, CURRENT_QM);
		for (i = CURRENT_Q; i < DEBUG_FILE_NUM; i++)
			qm_create_debugfs_file(qm, qm->debug.qm_d, i);
	}

	if (qm_regs)
		debugfs_create_file("diff_regs", 0444, qm->debug.qm_d,
					qm, &qm_diff_regs_fops);

	debugfs_create_file("regs", 0444, qm->debug.qm_d, qm, &qm_regs_fops);

	debugfs_create_file("cmd", 0600, qm->debug.qm_d, qm, &qm_cmd_fops);

	debugfs_create_file("status", 0444, qm->debug.qm_d, qm,
			&qm_status_fops);

	debugfs_create_u32("dev_state", 0444, qm->debug.qm_d, &dev_dfx->dev_state);
	debugfs_create_u32("dev_timeout", 0644, qm->debug.qm_d, &dev_dfx->dev_timeout);

	for (i = 0; i < ARRAY_SIZE(qm_dfx_files); i++) {
		data = (atomic64_t *)((uintptr_t)dfx + qm_dfx_files[i].offset);
		debugfs_create_file(qm_dfx_files[i].name,
			0644,
			qm_d,
			data,
			&qm_atomic64_ops);
	}

	if (test_bit(QM_SUPPORT_FUNC_QOS, &qm->caps))
		hisi_qm_set_algqos_init(qm);
}
EXPORT_SYMBOL_GPL(hisi_qm_debug_init);

/**
 * hisi_qm_debug_regs_clear() - clear qm debug related registers.
 * @qm: The qm for which we want to clear its debug registers.
 */
void hisi_qm_debug_regs_clear(struct hisi_qm *qm)
{
	const struct debugfs_reg32 *regs;
	int i;

	/* clear current_qm */
	writel(0x0, qm->io_base + QM_DFX_MB_CNT_VF);
	writel(0x0, qm->io_base + QM_DFX_DB_CNT_VF);

	/* clear current_q */
	writel(0x0, qm->io_base + QM_DFX_SQE_CNT_VF_SQN);
	writel(0x0, qm->io_base + QM_DFX_CQE_CNT_VF_CQN);

	/*
	 * these registers are reading and clearing, so clear them after
	 * reading them.
	 */
	writel(0x1, qm->io_base + QM_DFX_CNT_CLR_CE);

	regs = qm_dfx_regs;
	for (i = 0; i < CNT_CYC_REGS_NUM; i++) {
		readl(qm->io_base + regs->offset);
		regs++;
	}

	/* clear clear_enable */
	writel(0x0, qm->io_base + QM_DFX_CNT_CLR_CE);
}
EXPORT_SYMBOL_GPL(hisi_qm_debug_regs_clear);
