// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/debugfs.h>
#include <linux/version.h>
#include <linux/dma-direct.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/kthread.h>
#include <linux/ktime.h>
#include <uapi/linux/sched/types.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_debug.h>
#include <utilities/mdla_profile.h>
#include <utilities/mdla_util.h>
#include <utilities/mdla_trace.h>
#include <interface/mdla_intf.h>
#include <interface/mdla_cmd_data_v2_0.h>
#include <platform/mdla_plat_api.h>
#include <platform/mdla_plat_internal.h>
#include <platform/v2_0/mdla_hw_reg_v2_0.h>
#include <platform/v2_0/mdla_irq_v2_0.h>
#include <platform/v2_0/mdla_pmu_v2_0.h>
#include <platform/v2_0/mdla_sched_v2_0.h>
#include <common/mdla_scheduler.h>

#include "mdla_plat_teec.h"
#include "mdla_plat_vlm.h"
#include "mdla_plat_work.h"
#include "mdla_plat_dbg.h"
#include "mdla_plat_model.h"

#include <comm_driver.h>

static DECLARE_WAIT_QUEUE_HEAD(suspend_wait);
static u32 mdla_context;
static u32 mdla_suspend;

static u32 preemption_en;

static int mdla_plat_context_init(u32 core_id, struct command_entry *ce)
{
	spin_lock(&suspend_wait.lock);
	if (mdla_suspend) {
		spin_unlock(&suspend_wait.lock);
		return -EFAULT;
	}

	mdla_context++;
	spin_unlock(&suspend_wait.lock);

	INIT_LIST_HEAD(&ce->work_list);

	ce->vlm_ctx = mdla_plat_vlm_ctx_get(ce, 0, 0);

	return mdla_plat_teec_create(ce);
}

static void mdla_plat_context_uninit(u32 core_id, struct command_entry *ce)
{
	spin_lock(&suspend_wait.lock);
	mdla_context--;
	if (mdla_context == 0 && mdla_suspend)
		wake_up_locked(&suspend_wait);
	spin_unlock(&suspend_wait.lock);

	// delay free context unit all works are finished
	mdla_plat_vlm_ctx_put(ce, ce->vlm_ctx);
}

static void mdla_plat_delete_ce(struct kref *kref)
{
	struct command_entry *ce =
			container_of(kref, struct command_entry, refcount);

	mdla_plat_teec_destroy(ce);
	kfree(ce);
}

/* NOTE: hw_lock should be acquired by caller */
static void mdla_plat_verbose_log(u32 core_id, const struct mdla_util_io_ops *io)
{
	if (mdla_dbg_read_u32(FS_KLOG) != MDLA_DBG_ALL)
		return;

	io = mdla_util_io_ops_get();

	pr_info("%d: CG_CON=0x%x\n", core_id, io->cfg.read(core_id, MDLA_CG_CON));
	pr_info("%d: AXI_CTRL=0x%x\n", core_id, io->cfg.read(core_id, MDLA_AXI_CTRL));
	pr_info("%d: AXI1_CTRL=0x%x\n", core_id, io->cfg.read(core_id, MDLA_AXI1_CTRL));
	pr_info("%d: TOP_G_INTP0=0x%x\n", core_id, io->cmde.read(core_id, MREG_TOP_G_INTP0));
	pr_info("%d: TOP_G_INTP1=0x%x\n", core_id, io->cmde.read(core_id, MREG_TOP_G_INTP1));
	pr_info("%d: TOP_G_INTP2=0x%x\n", core_id, io->cmde.read(core_id, MREG_TOP_G_INTP2));
	pr_info("%d: TOP_G_IDLE=0x%x\n", core_id, io->cmde.read(core_id, MREG_TOP_G_IDLE));
}

static int mdla_plat_pre_cmd_handle(u32 core_id, struct command_entry *ce)
{
	const struct mdla_util_io_ops *io = mdla_util_io_ops_get();

	if (mdla_plat_context_init(core_id, ce))
		return -EFAULT;

	if (mdla_dbg_read_u32(FS_DUMP_CMDBUF))
		mdla_dbg_plat_cb()->create_dump_cmdbuf(mdla_get_device(core_id), ce);

	if (!mdla_dbg_read_u32(FS_SECURE_DRV_EN))
		io->cmde.write(core_id, MREG_TOP_G_INTP2,
			io->cmde.read(core_id, MREG_TOP_G_INTP2) & ~(INTR_SWCMD_DONE));

	return 0;
}

static int mdla_plat_pre_cmd_handle_sw_sched(u32 core_id, struct command_entry *ce)
{
	const struct mdla_util_io_ops *io = mdla_util_io_ops_get();

	if (mdla_plat_context_init(core_id, ce))
		return -EFAULT;

	if (ce->multicore_total > 1)
		mdla_get_device(core_id)->sched->set_smp_deadline(ce->priority, ce->deadline_t);

	if (mdla_dbg_read_u32(FS_DUMP_CMDBUF))
		mdla_dbg_plat_cb()->create_dump_cmdbuf(mdla_get_device(core_id), ce);

	if (!mdla_dbg_read_u32(FS_SECURE_DRV_EN))
		io->cmde.write(core_id, MREG_TOP_G_INTP2,
			io->cmde.read(0, MREG_TOP_G_INTP2) & ~(INTR_SWCMD_DONE));

	return 0;
}

static int mdla_plat_post_cmd_handle(u32 core_id, struct command_entry *ce)
{
	ce->req_end_t = mdla_prof_get_ts(core_id, TS_HW_LAST_INTR);

	mdla_plat_context_uninit(core_id, ce);

	/* clear current event id */
	mdla_util_io_ops_get()->cmde.write(core_id, MREG_TOP_G_CDMA4, 1);

	return 0;
}

static int mdla_plat_post_cmd_handle_sched(u32 core_id, struct command_entry *ce)
{
	ce->req_end_t = mdla_prof_get_ts(core_id, TS_HW_LAST_INTR);

	mdla_plat_context_uninit(core_id, ce);

	return 0;
}

static void mdla_plat_raw_process_command(u32 core_id, u32 evt_id,
						dma_addr_t addr, u32 count)
{
	const struct mdla_util_io_ops *io = mdla_util_io_ops_get();

	mdla_plat_verbose_log(core_id, io);

	/* set command address */
	io->cmde.write(core_id, MREG_TOP_G_CDMA1, addr);
	/* set command number */
	io->cmde.write(core_id, MREG_TOP_G_CDMA2, count);
	/* trigger hw */
	io->cmde.write(core_id, MREG_TOP_G_CDMA3, evt_id);
	/* write event id for SW debug */
	io->cmde.write(core_id, MREG_TOP_G_STREAM0, evt_id);

	mdla_verbose("%s:Fire to mdla-%x: addr: %lx, count: %d, evt_id: %x\n",
		__func__, core_id, (unsigned long)addr, count, evt_id);
}

static int mdla_plat_raw_proc_cmd_dispr(struct command_entry *ce,
						u32 core_id, u32 evt_id,
						dma_addr_t addr, u32 count)
{
	int ret = 0;

	mdla_prof_set_ts(core_id, TS_HW_TRIGGER, sched_clock());

	if (likely(mdla_dbg_read_u32(FS_SECURE_DRV_EN)))
		if (preemption_en)
			ret = mdla_plat_process_work(ce, core_id, evt_id,
							addr, count);
		else
			ret = mdla_plat_teec_process_command(ce, core_id,
							evt_id, addr, count);
	else
		mdla_plat_raw_process_command(core_id, evt_id, addr, count);

	return ret;
}

static int mdla_plat_process_command_in_batch_no_lock(u32 core_id, struct command_entry *ce)
{
	dma_addr_t addr;
	u32 evt_id, count;
	struct command_batch *cb;
	int ret;

	count = ce->count - ce->fin_cid;
	if ((ce->batch_list_head != NULL)
			&& !list_empty(ce->batch_list_head)) {
		cb = list_first_entry(ce->batch_list_head,
					struct command_batch, node);
		count = cb->size;
	}
	ce->wish_fin_cid = ce->fin_cid + count;

	addr = ce->mva + ((dma_addr_t)ce->fin_cid) * MREG_CMD_SIZE;
	evt_id = ce->csn;

	ce->state = CE_RUN;
	ret = mdla_plat_raw_proc_cmd_dispr(ce, core_id, evt_id, addr, count);
	ce_func_trace(ce, F_ISSUE);

	return ret;
}

static int mdla_plat_process_command_in_batch(u32 core_id, struct command_entry *ce)
{
	dma_addr_t addr;
	u32 evt_id, count;
	unsigned long flags;
	struct command_batch *cb;
	int ret;

	count = ce->count - ce->fin_cid;
	if ((ce->batch_list_head != NULL)
			&& !list_empty(ce->batch_list_head)) {
		cb = list_first_entry(ce->batch_list_head,
					struct command_batch, node);
		count = cb->size;
	}
	ce->wish_fin_cid = ce->fin_cid + count;

	addr = ce->mva + ((dma_addr_t)ce->fin_cid) * MREG_CMD_SIZE;
	evt_id = ce->csn;

	spin_lock_irqsave(&mdla_get_device(core_id)->hw_lock, flags);

	ce->state = CE_RUN;
	ret = mdla_plat_raw_proc_cmd_dispr(ce, core_id, evt_id, addr, count);
	ce_func_trace(ce, F_ISSUE);

	spin_unlock_irqrestore(&mdla_get_device(core_id)->hw_lock, flags);

	return ret;
}

static int mdla_plat_process_command(u32 core_id, struct command_entry *ce)
{
	dma_addr_t addr;
	u32 evt_id, count;
	unsigned long flags;
	int ret;


	ce->state = CE_RUN;
	addr = ce->mva;
	count = ce->count;
	evt_id = ce->csn;

	mdla_verbose("%s: mdla-%x: fin_cid:%d\n", __func__, core_id, ce->fin_cid);

	spin_lock_irqsave(&mdla_get_device(core_id)->hw_lock, flags);

	ce->state = CE_RUN;
	ret = mdla_plat_raw_proc_cmd_dispr(ce, core_id, evt_id, addr, count);
	ce_func_trace(ce, F_ISSUE);

	spin_unlock_irqrestore(&mdla_get_device(core_id)->hw_lock, flags);

	return ret;
}

static int mdla_plat_process_command_no_lock(u32 core_id, struct command_entry *ce)
{
	int ret;
	dma_addr_t addr;
	u32 evt_id, count;

	addr = ce->mva + ((dma_addr_t)ce->fin_cid) * MREG_CMD_SIZE;
	count = ce->count - ce->fin_cid;
	evt_id = ce->csn;

	mdla_verbose("%s: mdla-%x: fin_cid:%d\n", __func__, core_id, ce->fin_cid);

	ce->state = CE_RUN;
	ret = mdla_plat_raw_proc_cmd_dispr(ce, core_id, evt_id, addr, count);
	ce_func_trace(ce, F_ISSUE);

	return ret;
}

static bool mdla_check_cmd_valid(uint64_t out_end,
				struct command_entry *ce)
{
	return true;
}

static int mdla_plat_suspend(u32 core_id)
{
	mdla_drv_debug("%s(): Core (%d) suspend\n", __func__, core_id);

	// wait unfinished inference job complete
	spin_lock(&suspend_wait.lock);
	mdla_suspend = 1;
	if (mdla_context)
		wait_event_interruptible_locked(suspend_wait, mdla_context == 0);
	spin_unlock(&suspend_wait.lock);

	return 0;
}

static int mdla_plat_resume(u32 core_id)
{
	mdla_drv_debug("%s(): Core (%d) resume\n", __func__, core_id);
	spin_lock(&suspend_wait.lock);
	mdla_suspend = 0;
	spin_unlock(&suspend_wait.lock);
	mdla_plat_model_reset();

	return 0;
}

static void mdla_v2_0_reset(u32 core_id, const char *str)
{
	struct mdla_dev *dev = mdla_get_device(core_id);

	if (unlikely(!dev)) {
		mdla_err("%s(): No mdla device (%d)\n", __func__, core_id);
		return;
	}

	/* use power down==>power on apis insted bus protect init */
	mdla_drv_debug("%s: MDLA RESET: %s\n", __func__,
		str);

	if (!mdla_dbg_read_u32(FS_SECURE_DRV_EN))
		mdla_util_io_ops_get()->cmde.write(core_id, MREG_TOP_G_INTP2,
						MDLA_IRQ_MASK & ~(INTR_SUPPORT_MASK));

	mdla_trace_reset(core_id, str);
}

static int mdla_plat_check_resource(u32 core)
{
#ifdef ENABLE_VIRTUAL_HASHKEY
	u32 hw_support;

	hw_support = comm_util_get_cb()->hw_supported();

	mdla_drv_debug("%s(): enable vir_hashkey option (%x)\n",
			__func__, hw_support);
	return (!hw_support | mdla_suspend);
#endif
	return mdla_suspend;
}

#define seq_redirect(s, ...)			\
do {						\
	if (s)					\
		seq_printf(s, __VA_ARGS__);	\
	else					\
		redirect_output(__VA_ARGS__);	\
} while (0)

static void mdla_plat_memory_show(struct seq_file *s)
{
	struct mdla_dev *mdla_device;
	int i;
	u32 core_id;
	u32 *cmd_addr;

	seq_redirect(s, "------- dump MDLA code buf -------\n");

	for_each_mdla_core(core_id) {
		mdla_device = mdla_get_device(core_id);
		if (mdla_device->cmd_buf_len == 0)
			continue;
		seq_redirect(s, "mdla %d code buf:\n", mdla_device->mdla_id);
		mutex_lock(&mdla_device->cmd_buf_dmp_lock);
		cmd_addr = mdla_device->cmd_buf_dmp;
		for (i = 0; i < mdla_device->cmd_buf_len/4; i++)
			seq_redirect(s, "count: %d, offset: %.8x, val: %.8x\n",
					(i * 4) / MREG_CMD_SIZE,
					(i * 4) % MREG_CMD_SIZE,
					cmd_addr[i]);
		mutex_unlock(&mdla_device->cmd_buf_dmp_lock);
	}
}
/* platform public functions */

int mdla_mt58xx_v2_0_init(struct platform_device *pdev)
{
	struct mdla_cmd_cb_func *cmd_cb = mdla_cmd_plat_cb();
	struct mdla_scheduler *sched;
	int i;

	if (mdla_v2_0_init(pdev)) {
		dev_info(&pdev->dev, "%s() mdla_v2_0 init fail.\n", __func__);
		return -1;
	}

	mdla_pwr_reset_setup(mdla_v2_0_reset);

	/* replace command callback */
	if (mdla_plat_sw_preemption_support()) {
		cmd_cb->pre_cmd_handle          = mdla_plat_pre_cmd_handle_sw_sched;
		cmd_cb->process_command     = mdla_plat_process_command_in_batch;
		cmd_cb->process_command_no_lock = mdla_plat_process_command_in_batch_no_lock;
		cmd_cb->post_cmd_handle     = mdla_plat_post_cmd_handle_sched;
	} else if (mdla_plat_hw_preemption_support()) {
		cmd_cb->pre_cmd_handle      = mdla_plat_pre_cmd_handle;
		cmd_cb->process_command     = mdla_plat_process_command;
		cmd_cb->process_command_no_lock = mdla_plat_process_command_no_lock;
		cmd_cb->post_cmd_handle     = mdla_plat_post_cmd_handle_sched;
	} else {
		cmd_cb->pre_cmd_handle      = mdla_plat_pre_cmd_handle;
		cmd_cb->process_command     = mdla_plat_process_command;
		cmd_cb->process_command_no_lock = mdla_plat_process_command_no_lock;
		cmd_cb->post_cmd_handle     = mdla_plat_post_cmd_handle;
	}

	cmd_cb->check_cmd_valid     = mdla_check_cmd_valid;
	cmd_cb->suspend_cb = mdla_plat_suspend;
	cmd_cb->resume_cb = mdla_plat_resume;
	cmd_cb->check_resource = mdla_plat_check_resource;
	cmd_cb->del_ce = mdla_plat_delete_ce;

	for_each_mdla_core(i) {
		sched = mdla_get_device(i)->sched;

		if (mdla_plat_hw_preemption_support())
			sched->stop_ce = mdla_plat_stop_work;
	}

	mdla_plat_vlm_init();

	preemption_en = mdla_plat_hw_preemption_support() ||
				mdla_plat_sw_preemption_support();

	if (preemption_en && mdla_plat_work_init()) {
		dev_err(&pdev->dev, "%s() worker init fail.\n", __func__);
		return -1;
	}

	if (mdla_plat_teec_init()) {
		dev_err(&pdev->dev, "%s() teec init fail.\n", __func__);
		return -1;
	}

	cmd_cb->load_model		= mdla_plat_model_load;
	cmd_cb->unload_model		= mdla_plat_model_unload;

	mdla_dbg_plat_cb()->model_show  = mdla_plat_model_dump_info;
	mdla_dbg_plat_cb()->memory_show = mdla_plat_memory_show;
	mdla_panic_notify_init(&pdev->dev);

	cmd_cb->update_model_param	= mdla_plat_model_param_update;
	cmd_cb->reset_model_param	= mdla_plat_model_param_reset;
	cmd_cb->release_model_param	= mdla_plat_model_param_release;

	return 0;
}

void mdla_mt58xx_v2_0_deinit(struct platform_device *pdev)
{
	mdla_drv_debug("%s unregister -\n", __func__);

	mdla_panic_notify_uninit(&pdev->dev);

	if (preemption_en)
		mdla_plat_work_uninit();

	mdla_plat_teec_uninit();

	mdla_v2_0_deinit(pdev);
}
