// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */
#include <linux/completion.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/sched/clock.h>
#include <asm-generic/div64.h>

#include <apusys_device.h>

#include <common/mdla_device.h>
#include <common/mdla_cmd_proc.h>
#include <common/mdla_power_ctrl.h>
#include <common/mdla_ioctl.h>

#include <utilities/mdla_profile.h>
#include <utilities/mdla_util.h>
#include <utilities/mdla_trace.h>
#include <utilities/mdla_debug.h>

#include "mdla_mem_intf.h"
#include "mdla_cmd_data_v2_0.h"

#include "comm_driver.h"

#include "platform/v2_0/mdla_hw_reg_v2_0.h"

static int mdla_cmd_prepare_v2_0(void *cmd_data,
			struct apusys_cmd_hnd *apusys_hd,
			struct command_entry *ce,
			u32 priority,
			bool secure)
{
	struct mdla_run_cmd *cd;

	apusys_hd->ip_time = 0;
	ce->state = CE_NONE;
	ce->flags = CE_NOP;
	ce->bandwidth = 0;
	ce->result = MDLA_SUCCESS;
	ce->fin_cid = 0;
	ce->footprint = 0;

	if (!secure) {
		cd = (struct mdla_run_cmd *)cmd_data;
		ce->count = cd->count;
		ce->mva = cd->mva + cd->offset;

		if (ce->count == 0)
			return -EFAULT;

		ce->kva = (void *)mdla_buf_map(NULL, (u32)ce->mva, ce->count * MREG_CMD_SIZE);
		if (!ce->kva) {
			mdla_err("%s(): buf_map fail, mva:%#08x count:%x\n",
				__func__, ce->mva, ce->count);
			return -EINVAL;
		}

		mdla_cmd_debug("%s: kva=0x%llx mva=0x%08x(0x%08x+0x%x) cnt=%u sz=0x%x\n",
				__func__,
				(u64)ce->kva,
				ce->mva,
				cd->mva,
				cd->offset,
				ce->count,
				cd->size);
	} else {
		ce->count = 1;
	}

	ce->secure = secure;
	ce->csn = (ce->mva & 0xFFFFFFFE) | priority;
	ce->cmd_data = cmd_data;
	ce->priority = priority;

	/* Initialize timestamp*/
	ce->exec_time = 0;
	ce->poweron_t = 0;
	ce->req_start_t = 0;
	ce->req_end_t = 0;
	ce->queue_t = 0;

	return 0;
}

static int mdla_ut_cmd_prepare_v2_0(void *cmd_data,
					struct command_entry *ce)
{
	struct mdla_run_cmd_sync_svp *cmd = cmd_data;
	struct mdla_run_cmd_svp *p = &cmd->req;
	int i;

	ce->state = CE_NONE;
	ce->flags = CE_NOP;
	ce->bandwidth = 0;
	ce->result = MDLA_SUCCESS;
	ce->count = p->cmd_count;
	ce->boost_val = p->boost_value;

	if (!p->secure) {
		ce->mva = UINT_MAX;
		for (i = 0; i < p->buf_count; i++) {
			if (p->buf[i].buf_type == EN_AI_BUF_TYPE_CODE) {
				ce->mva	= p->buf[i].iova;
				break;
			}
		}

		ce->kva = mdla_buf_get(&cmd->cmd_info,
				(u64)ce->mva,
				ce->count * MREG_CMD_SIZE);
		if (!ce->kva) {
			mdla_err("%s(): map fail, iova:%#08x dev:%lx\n",
				__func__, ce->mva, cmd->cmd_info.cmd_user);
			return -EINVAL;
		}

		if (check_cmdbuf(ce)) {
			mdla_buf_put(&cmd->cmd_info,
				(u64)ce->mva,
				ce->count * MREG_CMD_SIZE);
			ce->kva = NULL;
			mdla_err("%s(): check_cmd fail\n", __func__);
			return -EINVAL;
		}
	} else {
		ce->count = 1;
	}

	ce->secure = p->secure;
	ce->cmd_data = cmd_data;

	/* Initialize timestamp*/
	ce->exec_time = 0;
	ce->poweron_t = 0;
	ce->req_start_t = 0;
	ce->req_end_t = 0;
	ce->queue_t = 0;

	return 0;
}

static void mdla_cmd_finish_v2_0(struct mdla_run_cmd *cd,
					struct command_entry *ce,
					void *cmd_data)
{
	ce->state |= (1 << CE_EXIT);
}

static void mdla_cmd_ut_finish_v2_0(void *cmd_data,
					struct command_entry *ce)
{
	struct mdla_run_cmd_sync_svp *p = cmd_data;
	struct mdla_wait_cmd *wt;

	if (!ce->secure && ce->kva)
		mdla_buf_put(&p->cmd_info,
			(u64)ce->mva,
			ce->count * MREG_CMD_SIZE);
	wt = &p->res;
	wt->queue_time = ce->poweron_t - ce->receive_t;
	wt->busy_time = ce->wait_t - ce->poweron_t;
	wt->bandwidth = ce->bandwidth;


	/* Initialize timestamp */
	ce->wait_t = 0;
	ce->queue_t = 0;
	ce->state |= (1 << CE_EXIT);
}

int mdla_cmd_run_sync_v2_0(void *cmd_data,
				struct mdla_dev *mdla_info,
				struct apusys_cmd_hnd *apusys_hd,
				bool secure,
				uint32_t priority)
{
	u64 deadline = 0;
	struct mdla_run_cmd *cd = (struct mdla_run_cmd *)cmd_data;
	struct command_entry *ce;
	int boost_val;
	int ret;
	u32 core_id = 0;
	u64 cmd_start_t;
#ifdef COMMAND_UTILIZATION
	void *entry;
#endif

	ce = mdla_cmd_plat_cb()->alloc_ce();
	if (ce == NULL)
		return -ENOMEM;

	cmd_start_t = sched_clock();

	core_id = mdla_info->mdla_id;

	if (mdla_cmd_plat_cb()->check_resource(core_id))
		return -EFAULT;

	ret = mdla_pwr_ops_get()->on(core_id, false);
	if (ret) {
		if (ret == -EAGAIN)
			mdla_pwr_ops_get()->off(core_id, 0, false);
		return -EFAULT;
	}

	ce->receive_t = sched_clock();

	mdla_prof_set_ts(core_id, TS_CMD_START, cmd_start_t);

	/* The critical region of command enqueue */
	mdla_cmd_ops_get()->lock(mdla_info);

	mdla_pwr_ops_get()->wake_lock(core_id);

	ret = mdla_cmd_prepare_v2_0(cmd_data, apusys_hd, ce, priority, secure);
	if (ret)
		goto cmd_done;

	mdla_get_device(core_id)->sched->pro_ce = ce;

	deadline = get_jiffies_64()
			+ msecs_to_jiffies(mdla_dbg_read_u32(FS_TIMEOUT));

	mdla_info->max_cmd_id = 0;

	mdla_verbose("%s: core: %d max_cmd_id: %d id: %d\n",
			__func__, core_id, mdla_info->max_cmd_id, ce->count);

	boost_val = apusys_hd->boost_val;

	if (unlikely(mdla_dbg_read_u32(FS_DVFS_RAND)))
		boost_val = mdla_pwr_get_random_boost_val();

	mdla_pwr_ops_get()->set_opp_by_boost(core_id, boost_val);

	mdla_prof_start(core_id);
	mdla_trace_begin(core_id, ce);

	if (mdla_util_apu_pmu_handle(mdla_info, apusys_hd, priority) == 0)
		mdla_util_pmu_ops_get()->reset_write_evt_exec(core_id, priority);

	mdla_cmd_plat_cb()->pre_cmd_handle(core_id, ce);

#ifdef COMMAND_UTILIZATION
	entry = exec_time_log_start();
#endif

	/* Fill HW reg */
	ret = mdla_cmd_plat_cb()->process_command_no_lock(core_id, ce);
	if (ret)
		goto exit_wait;

	/* Wait for timeout */
	while (mdla_info->max_cmd_id < ce->count
			&& time_before64(get_jiffies_64(), deadline)) {

		wait_for_completion_interruptible_timeout(
			&mdla_info->command_done,
			mdla_cmd_plat_cb()->get_wait_time(core_id));
	}

exit_wait:
#ifdef COMMAND_UTILIZATION
	if (entry)
		exec_time_log_end(entry);
#endif
	mdla_cmd_plat_cb()->post_cmd_handle(core_id, ce);
	mdla_cmd_plat_cb()->post_cmd_info(core_id);

	mdla_trace_end(core_id, 0, ce);
	mdla_util_apu_pmu_update(mdla_info, apusys_hd, 0);

	cd->id = mdla_info->max_cmd_id;

	if ((ret == 0) && (mdla_info->max_cmd_id < ce->count)) {
		mdla_timeout_debug("command: %d, max_cmd_id: %d\n",
				ce->count,
				mdla_info->max_cmd_id);

		mdla_cmd_plat_cb()->post_cmd_hw_detect(core_id);

		mdla_dbg_dump(mdla_info, ce);

		/* Enable & Relase bus protect */
		mdla_pwr_ops_get()->switch_off_on(core_id);

		mdla_pwr_ops_get()->hw_reset(mdla_info->mdla_id,
				mdla_dbg_get_reason_str(REASON_TIMEOUT));
	}

cmd_done:
	ce->exec_time = mdla_prof_get_ts(core_id, TS_HW_LAST_INTR)
					- mdla_prof_get_ts(core_id, TS_HW_TRIGGER);
	apusys_hd->ip_time = (u32)ce->exec_time / 1000;

	mdla_prof_set_ts(core_id, TS_CMD_FINISH, sched_clock());
	mdla_prof_iter(core_id);

	mdla_get_device(core_id)->sched->pro_ce = NULL;
	mdla_cmd_finish_v2_0(cd, ce, cmd_data);

	mdla_pwr_ops_get()->wake_unlock(core_id);
	mdla_cmd_ops_get()->unlock(mdla_info);

	if (mdla_dbg_read_u32(FS_POWEROFF_TIME) == 0)
		mdla_pwr_ops_get()->off(core_id, 0, true);
	else
		mdla_pwr_ops_get()->off_timer_start(core_id);

	mdla_cmd_plat_cb()->free_ce(ce);

	return ret;
}

int mdla_cmd_ut_run_sync_v2_0(void *cmd_data, void *wait_cmd,
			struct mdla_dev *mdla_info)
{
	u64 deadline = 0, cmd_start_t;
	struct command_entry *ce;
	struct mdla_wait_cmd *wt = (struct mdla_wait_cmd *)wait_cmd;
	int boost_val;
	int ret;
	u32 core_id = 0;
#ifdef COMMAND_UTILIZATION
	void *entry;
#endif

	ce = mdla_cmd_plat_cb()->alloc_ce();
	if (ce == NULL)
		return -ENOMEM;

	cmd_start_t = sched_clock();
	core_id = mdla_info->mdla_id;
	ce->receive_t = cmd_start_t;
	ce->queue_t = cmd_start_t;
	wt->result = 0;

	if (mdla_cmd_plat_cb()->check_resource(core_id))
		return -EFAULT;

	ret = mdla_pwr_ops_get()->on(core_id, false);
	if (ret) {
		if (ret == -EAGAIN)
			mdla_pwr_ops_get()->off(core_id, 0, true);
		return -EFAULT;
	}

	/* The critical region of command enqueue */
	mdla_cmd_ops_get()->lock(mdla_info);
	mdla_pwr_ops_get()->wake_lock(core_id);

	ret = mdla_ut_cmd_prepare_v2_0(cmd_data, ce);
	if (ret)
		goto cmd_done;

	mdla_get_device(core_id)->sched->pro_ce = ce;

	deadline = get_jiffies_64()
			+ msecs_to_jiffies(mdla_dbg_read_u32(FS_TIMEOUT));

	mdla_info->max_cmd_id = 0;

	mdla_verbose("%s: core: %d max_cmd_id: %d id: %d\n",
			__func__, core_id, mdla_info->max_cmd_id, ce->count);

	boost_val = ce->boost_val;

	if (unlikely(mdla_dbg_read_u32(FS_DVFS_RAND)))
		boost_val = mdla_pwr_get_random_boost_val();

	mdla_pwr_ops_get()->set_opp_by_boost(core_id, boost_val);

	mdla_prof_start(core_id);
	mdla_trace_begin(core_id, ce);

	ce->poweron_t = sched_clock();

	mdla_cmd_plat_cb()->pre_cmd_handle(core_id, ce);

#ifdef COMMAND_UTILIZATION
	entry = exec_time_log_start();
#endif

	/* Fill HW reg */
	ret = mdla_cmd_plat_cb()->process_command_no_lock(core_id, ce);
	if (ret)
		goto exit_wait;

	/* Wait for timeout */
	while (mdla_info->max_cmd_id < ce->count
			&& time_before64(get_jiffies_64(), deadline)) {

		wait_for_completion_interruptible_timeout(
			&mdla_info->command_done,
			mdla_cmd_plat_cb()->get_wait_time(core_id));
	}

exit_wait:
#ifdef COMMAND_UTILIZATION
	if (entry)
		exec_time_log_end(entry);
#endif
	mdla_cmd_plat_cb()->post_cmd_handle(core_id, ce);
	mdla_cmd_plat_cb()->post_cmd_info(core_id);

	mdla_prof_iter(core_id);
	mdla_trace_end(core_id, 0, ce);

	if ((ret == 0) && (mdla_info->max_cmd_id < ce->count)) {
		mdla_timeout_debug("command: %d, max_cmd_id: %d\n",
				ce->count,
				mdla_info->max_cmd_id);

		mdla_cmd_plat_cb()->post_cmd_hw_detect(core_id);

		mdla_dbg_dump(mdla_info, ce);

		/* Enable & Relase bus protect */
		mdla_pwr_ops_get()->switch_off_on(core_id);

		mdla_pwr_ops_get()->hw_reset(mdla_info->mdla_id,
				mdla_dbg_get_reason_str(REASON_TIMEOUT));

		wt->result = 1;
	}

	ce->wait_t = sched_clock();

cmd_done:
	mdla_get_device(core_id)->sched->pro_ce = NULL;
	mdla_cmd_ut_finish_v2_0(cmd_data, ce);

	mdla_pwr_ops_get()->wake_unlock(core_id);
	mdla_cmd_ops_get()->unlock(mdla_info);

	if (mdla_dbg_read_u32(FS_POWEROFF_TIME) == 0)
		mdla_pwr_ops_get()->off(core_id, 0, false);
	else
		mdla_pwr_ops_get()->off_timer_start(core_id);

	mdla_cmd_plat_cb()->free_ce(ce);

	return ret;
}
