// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2015 MediaTek Inc.
 */

#include "cmdq_prof.h"
#include "cmdq_helper_ext.h"

/* expect no EMI latency, GCE spends 80 ns per 4 cycle*/
#define CMDQ_HW_EXEC_NS(hw_cycle) (hw_cycle * (80 / 4))

enum CMDQ_OP_STATISTIC_ENUM {
	CMDQ_STA_READ = 0,
	CMDQ_STA_MOVE,
	CMDQ_STA_WRI,
	CMDQ_STA_WRI_WITH_MASK,
	CMDQ_STA_POLL,
	CMDQ_STA_JUMP,
	CMDQ_STA_WFE,		/* wait for event */
	CMDQ_STA_SYNC,		/* sync op and no wait for event */
	CMDQ_STA_EOC,
	CMDQ_STA_MAX_COUNT,	/* always keep at the end */
};

const char *cmdq_prof_get_statistic_id_name(
	const enum CMDQ_OP_STATISTIC_ENUM statisticId)
{
	const char *IDName = "UNKNOWN";

#undef DECLARE_CMDQ_INSTR_CYCLE
#define DECLARE_CMDQ_INSTR_CYCLE(id, hw_op, cycle, name) { \
	if (id == statisticId) { \
		IDName = #name; \
		break; \
		} }

	do {
#include "cmdq_instr_cycle.h"
	} while (0);
#undef DECLARE_CMDQ_INSTR_CYCLE

	return IDName;
}

u32 cmdq_prof_get_statistic_id(const u32 *pCmd)
{
	const u32 arg_b = pCmd[0];
	const u32 arg_a = pCmd[1];
	const u32 op = arg_a >> 24;
	u32 addr;
	u32 maskEn = 0;

	switch (op) {
	case CMDQ_CODE_WRITE_S:
		return CMDQ_STA_WRI;
	case CMDQ_CODE_WRITE_S_W_MASK:
		return CMDQ_STA_WRI_WITH_MASK;
	case CMDQ_CODE_WRITE:
		/* check address */
		if (arg_a & (1 << 23)) {
			/* address is GPR */
			return CMDQ_STA_WRI;
		}
		addr = cmdq_core_subsys_to_reg_addr(arg_a);
		maskEn = (addr & 0x1);
		if (maskEn)
			return CMDQ_STA_WRI_WITH_MASK;
		else
			return CMDQ_STA_WRI;

	case CMDQ_CODE_WFE:
		/* check waiting flag */
		if ((arg_b >> 15) & 0x1)
			return CMDQ_STA_WFE;
		else
			return CMDQ_STA_SYNC;

	case CMDQ_CODE_READ:
	case CMDQ_CODE_READ_S:
		return CMDQ_STA_READ;

	case CMDQ_CODE_MOVE:
		return CMDQ_STA_MOVE;

	case CMDQ_CODE_POLL:
		return CMDQ_STA_POLL;

	case CMDQ_CODE_JUMP:
		return CMDQ_STA_JUMP;

	case CMDQ_CODE_EOC:
	case CMDQ_CODE_LOGIC:
	case CMDQ_CODE_JUMP_C_RELATIVE:
		return CMDQ_STA_EOC;
	default:
		CMDQ_ERR("unknown op, arg_a: 0x%08x\n", arg_a);
		return 0;
	}

	CMDQ_ERR("unknown op, arg_a: 0x%08x\n", arg_a);
	return 0;
}

u32 cmdq_prof_calculate_HW_cycle(
	const enum CMDQ_OP_STATISTIC_ENUM statisticId, const u32 count)
{
	u32 hwCycle = -1;

#undef DECLARE_CMDQ_INSTR_CYCLE
#define DECLARE_CMDQ_INSTR_CYCLE(id, hw_op, cycle, name) { \
	if (id == statisticId) { \
		hwCycle = (count * cycle); \
		break; } }

	do {
#include "cmdq_instr_cycle.h"
	} while (0);
#undef DECLARE_CMDQ_INSTR_CYCLE

	if (-1 == hwCycle) {
		/* Error message dump */
		CMDQ_ERR("unknown statisticId: %d\n", statisticId);
	}

	return hwCycle;
}

s32 cmdq_prof_estimate_command_exe_time(const u32 *pCmd,
	u32 commandSize)
{
	u32 statistic[CMDQ_STA_MAX_COUNT] = { 0 };
	int i;
	u32 statisticId = 0;
	u32 cycle = 0;
	u32 totalCycle = 0;
	u32 totalNS = 0;

	if (pCmd == NULL)
		return -EFAULT;

	/* gather statistic */
	for (i = 0; i < commandSize; i += CMDQ_INST_SIZE, pCmd += 2) {
		statisticId = cmdq_prof_get_statistic_id(pCmd);
		statistic[statisticId] += 1;
	}

	CMDQ_LOG("NAME, COUNT, least HW exec cycle\n");
	/* calculate execution time */
	for (i = 0; i < CMDQ_STA_MAX_COUNT; i++) {
		cycle = cmdq_prof_calculate_HW_cycle(i, statistic[i]);
		totalCycle += cycle;
		CMDQ_LOG("%d:%11s, %d, %3d\n",
			i, cmdq_prof_get_statistic_id_name(i), statistic[i],
			cycle);
	}

	totalNS = CMDQ_HW_EXEC_NS(totalCycle);
	CMDQ_LOG("=====================================\n");
	CMDQ_LOG("estimated least HW exec time(ns): %6d\n", totalNS);
	CMDQ_LOG("***each HW cycle spends time(ns): %6d\n",
		CMDQ_HW_EXEC_NS(1));
	CMDQ_LOG(
		"***Real exec time will be longer when POLL/WAIT instr cannot pass their condition immediately.\n");

	return totalNS;
}
