// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */
#include <linux/types.h>
#include <linux/io.h>
#include <linux/platform_device.h>

#include <apusys_device.h>

#include <common/mdla_driver.h>
#include <common/mdla_device.h>

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

#include <platform/mdla_plat_api.h>


struct mdla_pmu_result {
	u16 cmd_len;
	u16 cmd_id;
	u32 pmu_val[MDLA_PMU_COUNTERS + 1]; /* global counter + PMU counter*/
};

/* platform */
u32 mdla_util_get_core_num(void)
{
	return mdla_plat_get_core_num();
}

u32 mdla_util_get_ip_version(void)
{
	return get_major_num(mdla_plat_get_version());
}

const struct of_device_id *mdla_util_get_device_id(void)
{
	return mdla_plat_get_device();
}

int mdla_util_plat_init(struct platform_device *pdev)
{
	if (mdla_plat_init(pdev) != 0)
		return -1;

	mdla_dbg_fs_setup(&pdev->dev);

	if (mdla_plat_micro_p_support())
		return 1;

	//mdla_drv_create_device_node(&pdev->dev);
	mdla_prof_init(mdla_plat_get_prof_ver());

	return 0;
}

void mdla_util_plat_deinit(struct platform_device *pdev)
{
	if (!mdla_plat_micro_p_support()) {
		mdla_prof_deinit();
		mdla_drv_destroy_device_node();
	}
	mdla_plat_deinit(pdev);
}

/* pmu */
static void mdla_util_dummy_cnt_save(u32 a0, struct mdla_pmu_info *a1) {}
static void mdla_util_dummy_cnt_read(u32 a0, u32 *a1) {}
static void mdla_util_dummy_cnt_clr(struct mdla_pmu_info *pmu) {}
static u32 mdla_util_dummy_get_num_evt(u32 a0, int a1)
{
	return 0;
}
static void mdla_util_dummy_set_num_evt(u32 a0, int a1, int a2) {}
static void mdla_util_dummy_set_mode(struct mdla_pmu_info *a0, u32 a1) {}
static int mdla_util_dummy_get_mode(struct mdla_pmu_info *a0)
{
	return 0;
}
static u64 mdla_util_dummy_get_addr(struct mdla_pmu_info *a0)
{
	return 0;
}
static u32 mdla_util_dummy_get_val(struct mdla_pmu_info *a0)
{
	return 0;
}
static u16 mdla_util_dummy_get_cmdcnt(struct mdla_pmu_info *a0)
{
	return 0;
}
static u32 mdla_util_dummy_get_data(struct mdla_pmu_info *a0, u32 a1)
{
	return 0;
}
static void mdla_util_dummy_ops(u32 a0) {}
static void mdla_util_dummy_exec(u32 a0, u16 a1) {}
static struct mdla_pmu_info *mdla_util_dummy_get_info(u32 a0, u16 a1)
{
	return NULL;
}
static void mdla_util_dummy_set_evt(struct mdla_pmu_info *a0,
		u32 a1, u32 a2)
{
}
static int mdla_util_dummy_prepare(struct mdla_dev *mdla_info,
			struct apusys_cmd_handle *apusys_hd, u16 priority)
{
	return 0;
}

static struct mdla_util_pmu_ops pmu_ops = {
	.reg_counter_save = mdla_util_dummy_cnt_save,
	.reg_counter_read = mdla_util_dummy_cnt_read,
	.clr_counter_variable = mdla_util_dummy_cnt_clr,
	.clr_cycle_variable = mdla_util_dummy_cnt_clr,
	.get_num_evt = mdla_util_dummy_get_num_evt,
	.set_num_evt = mdla_util_dummy_set_num_evt,
	.set_percmd_mode = mdla_util_dummy_set_mode,
	.get_curr_mode = mdla_util_dummy_get_mode,
	.get_perf_end = mdla_util_dummy_get_val,
	.get_perf_cycle = mdla_util_dummy_get_val,
	.get_perf_cmdid = mdla_util_dummy_get_cmdcnt,
	.get_perf_cmdcnt = mdla_util_dummy_get_cmdcnt,
	.get_counter = mdla_util_dummy_get_data,
	.reset_counter = mdla_util_dummy_ops,
	.disable_counter = mdla_util_dummy_ops,
	.enable_counter = mdla_util_dummy_ops,
	.reset = mdla_util_dummy_ops,
	.write_evt_exec = mdla_util_dummy_exec,
	.reset_write_evt_exec = mdla_util_dummy_exec,
	.get_hnd_evt = mdla_util_dummy_get_data,
	.get_hnd_evt_num = mdla_util_dummy_get_val,
	.get_hnd_mode = mdla_util_dummy_get_val,
	.get_hnd_buf_addr = mdla_util_dummy_get_addr,
	.get_hnd_buf_size = mdla_util_dummy_get_val,
	.set_evt_handle = mdla_util_dummy_set_evt,
	.get_info = mdla_util_dummy_get_info,
	.apu_cmd_prepare = mdla_util_dummy_prepare,
};

struct mdla_util_pmu_ops *mdla_util_pmu_ops_get(void)
{
	return &pmu_ops;
}

/* apusys pmu */

static bool mdla_util_pmu_addr_is_invalid(struct apusys_cmd_handle *apusys_hd)
{
	if (apusys_hd->num_cmdbufs < CMD_PMU_BUF_0_IDX + 1)
		return true;

	if (!apusys_hd->cmdbufs[CMD_PMU_INFO_IDX].kva ||
			!apusys_hd->cmdbufs[CMD_PMU_BUF_0_IDX].kva)
		return true;

	if (apusys_hd->multicore_total == 2) {
		if ((apusys_hd->num_cmdbufs < CMD_PMU_BUF_1_IDX + 1) ||
		    !apusys_hd->cmdbufs[CMD_PMU_BUF_1_IDX].kva) {
			return true;
		}
		mdla_pmu_debug("pmu kva: buf0 = %08llx, buf1 = %08llx\n",
			       (u64)apusys_hd->cmdbufs[CMD_PMU_BUF_0_IDX].kva,
			       (u64)apusys_hd->cmdbufs[CMD_PMU_BUF_1_IDX].kva);
	} else {
		mdla_pmu_debug("pmu kva: buf0 = %08llx\n",
			       (u64)apusys_hd->cmdbufs[CMD_PMU_BUF_0_IDX].kva);
	}

	return false;
}

static bool apusys_pmu_support;

void mdla_util_pmu_cmd_timer(bool enable)
{
	/* Not support polling pmu data with per cmd mode yet */
}

/* initial local variable and extract pmu setting from input */
int mdla_util_apu_pmu_handle(struct mdla_dev *mdla_info,
	struct apusys_cmd_handle *apusys_hd, u16 priority)
{
	int i, pmu_mode;
	u32 evt, evt_num;
	struct mdla_pmu_info *pmu;
	u32 core_id;

	core_id = mdla_info->mdla_id;
	pmu = pmu_ops.get_info(core_id, priority);

	if (!pmu) {
		mdla_err("%s: No pmu info device\n", __func__);
		return -1;
	}

	pmu_ops.clr_counter_variable(pmu);
	pmu_ops.clr_cycle_variable(pmu);


	if (mdla_prof_use_dbgfs_pmu_event(mdla_info->mdla_id)) {
		pmu_ops.set_num_evt(core_id,
				priority, MDLA_PMU_COUNTERS);

		for (i = 0; i < MDLA_PMU_COUNTERS; i++) {
			evt = mdla_dbg_read_u32(FS_C1 + i);
			pmu_ops.set_evt_handle(pmu, i,
					(evt & 0x1f0000) | (evt & 0xf));
		}
		return 0;
	}

	if (!apusys_pmu_support)
		return -1;

	if (mdla_util_pmu_addr_is_invalid(apusys_hd)) {
		for (i = 0; i < MDLA_PMU_COUNTERS; i++)
			pmu_ops.set_evt_handle(pmu, i, COUNTER_CLEAR);
		return -1;
	}

	if (pmu_ops.apu_cmd_prepare(mdla_info, apusys_hd, priority))
		return -1;

	pmu_mode = pmu_ops.get_curr_mode(pmu);
	evt_num = pmu_ops.get_hnd_evt_num(pmu);

	/* save to local variable */
	pmu_ops.set_percmd_mode(pmu, pmu_mode);
	pmu_ops.set_num_evt(core_id, priority,
					evt_num);

	mdla_pmu_debug("PMU number_of_event:%d, mode: %d\n",
			evt_num,
			pmu_mode);

	for (i = 0; i < evt_num; i++) {
		evt = pmu_ops.get_hnd_evt(pmu, i);
		pmu_ops.set_evt_handle(pmu, i,
				((evt & 0x1f00) << 8) | (evt & 0xf));
	}

	return 0;
}

void mdla_util_apu_pmu_update(struct mdla_dev *mdla_info,
	struct apusys_cmd_handle *apusys_hd, u16 priority)
{
	int i;
	struct mdla_pmu_result result;
	void *base = NULL, *desc = NULL, *src = NULL;
	int sz = 0;
	//int event_num = 0;
	int offset = 0;
	//u16 final_len = 0;
	u16 loop_count;
	u32 core_id;
	struct mdla_pmu_info *pmu;

	if (!apusys_pmu_support)
		return;

	/* FIXME: NOT always upload pmu info to NN? */
	if (mdla_prof_use_dbgfs_pmu_event(mdla_info->mdla_id))
		return;

	if (mdla_util_pmu_addr_is_invalid(apusys_hd))
		return;

	core_id = mdla_info->mdla_id;
	pmu = pmu_ops.get_info(core_id, priority);

	if (!pmu) {
		mdla_err("%s: no pmu info device(core:%d, prio:%d)\n",
				__func__, core_id, priority);
		return;
	}

	//loop_count = pmu->pmu_hnd->number_of_event;
	loop_count = pmu_ops.get_hnd_evt_num(pmu);

	//event_num = loop_count + 1;

	//result.cmd_len = pmu->data.l_cmd_cnt;
	result.cmd_len = pmu_ops.get_perf_cmdcnt(pmu);
	result.cmd_id = pmu_ops.get_perf_cmdid(pmu);

	if (pmu_ops.get_curr_mode(pmu) == PER_CMD) {
		mdla_util_pmu_cmd_timer(false);
		result.pmu_val[0] = pmu_ops.get_perf_end(pmu);
	} else {
		result.pmu_val[0] = pmu_ops.get_perf_cycle(pmu);
	}

	mdla_pmu_debug("global counter:%08x\n", result.pmu_val[0]);

	for (i = 1; i < loop_count; i++) {
		result.pmu_val[i] =
			pmu_ops.get_counter(pmu, i);
		mdla_pmu_debug("event %d cnt :%08x\n",
			       i, result.pmu_val[i]);
	}

	mdla_pmu_debug("mode: %d, cmd_len: %d, cmd_id: %d\n",
		       pmu_ops.get_hnd_mode(pmu),
		       result.cmd_len, result.cmd_id);

	sz = sizeof(struct mdla_pmu_result) - sizeof(u16);
	base = (void *)pmu_ops.get_hnd_buf_addr(pmu);

	if (result.cmd_len)
		offset = sizeof(u16) + (result.cmd_len - 1) * sz;
	else
		return;

	/* update command length */
	((struct mdla_pmu_result *)base)->cmd_len = result.cmd_len;

	if (pmu_ops.get_hnd_buf_size(pmu) < offset + sz)
		return;

	/* update current command pmu result */
	desc = (void *)(base + offset);
	src = (void *)&(result.cmd_id);
	memcpy(desc, src, sz);


	if (result.cmd_id == mdla_info->max_cmd_id) {
		if (unlikely(mdla_dbg_read_u32(FS_KLOG) & MDLA_DBG_PMU)) {
			struct mdla_pmu_result check;
			struct mdla_pmu_result *p =
				container_of(desc, struct mdla_pmu_result,
						cmd_id);

			memcpy(&check, p, sizeof(check));
			check.cmd_len = ((struct mdla_pmu_result *)base)->cmd_len;

			mdla_pmu_debug("[-] check cmd_len: %d\n", check.cmd_len);
			mdla_pmu_debug("[-] check offset: %d\n", offset);
			mdla_pmu_debug("[-] check cmd_id: %d\n", check.cmd_id);
			mdla_pmu_debug("[-] check cmd_val[1]:  %08x\n", check.pmu_val[1]);
		}
	}
}

void mdla_util_apusys_pmu_support(bool enable)
{
	apusys_pmu_support = enable;
}

/* IO operation */
static struct mdla_reg_ctl *mdla_reg_control;
static void *apu_conn_top;
static void *infracfg_ao_top;

static u32 mdla_util_dummy_core_r(u32 i, u32 ofs) { return 0; }
static void mdla_util_dummy_core_w(u32 i, u32 ofs, u32 val) {}
static u32 mdla_util_dummy_r(u32 ofs) { return 0; }
static void mdla_util_dummy_w(u32 ofs, u32 val) {}

static struct mdla_util_io_ops io_ops = {
	.cfg = { .read = mdla_util_dummy_core_r,
			.write = mdla_util_dummy_core_w,
			.set_b = mdla_util_dummy_core_w,
			.clr_b = mdla_util_dummy_core_w},
	.cmde = { .read = mdla_util_dummy_core_r,
			.write = mdla_util_dummy_core_w,
			.set_b = mdla_util_dummy_core_w,
			.clr_b = mdla_util_dummy_core_w},
	.biu = { .read = mdla_util_dummy_core_r,
			.write = mdla_util_dummy_core_w,
			.set_b = mdla_util_dummy_core_w,
			.clr_b = mdla_util_dummy_core_w},
	.apu_conn = { .read = mdla_util_dummy_r,
			.write = mdla_util_dummy_w,
			.set_b = mdla_util_dummy_w,
			.clr_b = mdla_util_dummy_w},
	.infra_cfg = { .read = mdla_util_dummy_r,
			.write = mdla_util_dummy_w,
			.set_b = mdla_util_dummy_w,
			.clr_b = mdla_util_dummy_w},
};

const struct mdla_util_io_ops *mdla_util_io_ops_get(void)
{
	return &io_ops;
}

static inline void reg_set(void *base, u32 offset, u32 value)
{
	iowrite32(ioread32(base + offset) | value, base + offset);
}

static inline void reg_clr(void *base, u32 offset, u32 value)
{
	iowrite32(ioread32(base + offset) & (~value), base + offset);
}

static u32 mdla_cfg_read(u32 id, u32 offset)
{
	if (unlikely(core_id_is_invalid(id)))
		return 0;

	return ioread32(mdla_reg_control[id].apu_mdla_config_top + offset);
}

static void mdla_cfg_write(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	iowrite32(value, mdla_reg_control[id].apu_mdla_config_top + offset);
}

static void mdla_cfg_set_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_set(mdla_reg_control[id].apu_mdla_config_top, offset, value);
}

static void mdla_cfg_clr_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_clr(mdla_reg_control[id].apu_mdla_config_top, offset, value);
}

static u32 mdla_reg_read(u32 id, u32 offset)
{
	if (unlikely(core_id_is_invalid(id)))
		return 0;

	return ioread32(mdla_reg_control[id].apu_mdla_cmde_mreg_top + offset);
}

static void mdla_reg_write(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	iowrite32(value, mdla_reg_control[id].apu_mdla_cmde_mreg_top + offset);
}

static void mdla_reg_set_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_set(mdla_reg_control[id].apu_mdla_cmde_mreg_top, offset, value);
}

static void mdla_reg_clr_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_clr(mdla_reg_control[id].apu_mdla_cmde_mreg_top, offset, value);
}

static u32 mdla_biu_read(u32 id, u32 offset)
{
	if (unlikely(core_id_is_invalid(id)))
		return 0;

	return ioread32(mdla_reg_control[id].apu_mdla_biu_top + offset);
}

static void mdla_biu_write(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	iowrite32(value, mdla_reg_control[id].apu_mdla_biu_top + offset);
}

static void mdla_biu_set_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_set(mdla_reg_control[id].apu_mdla_biu_top, offset, value);
}

static void mdla_biu_clr_b(u32 id, u32 offset, u32 value)
{
	if (unlikely(core_id_is_invalid(id)))
		return;

	reg_clr(mdla_reg_control[id].apu_mdla_biu_top, offset, value);
}

static u32 mdla_apu_conn_read(u32 offset)
{
	return ioread32(apu_conn_top + offset);
}

static void mdla_apu_conn_write(u32 offset, u32 value)
{
	iowrite32(value, apu_conn_top + offset);
}

static void mdla_apu_conn_set_b(u32 offset, u32 value)
{
	reg_set(apu_conn_top, offset, value);
}

static void mdla_apu_conn_clr_b(u32 offset, u32 value)
{
	reg_clr(apu_conn_top, offset, value);
}

static u32 mdla_infracfg_ao_read(u32 offset)
{
	return ioread32(infracfg_ao_top + offset);
}

static void mdla_infracfg_ao_write(u32 offset, u32 value)
{
	iowrite32(value, infracfg_ao_top + offset);
}

static void mdla_infracfg_ao_set_b(u32 offset, u32 value)
{
	reg_set(infracfg_ao_top, offset, value);
}

static void mdla_infracfg_ao_clr_b(u32 offset, u32 value)
{
	reg_clr(infracfg_ao_top, offset, value);
}

void mdla_util_io_set_addr(struct mdla_reg_ctl *reg_ctl)
{
	if (reg_ctl == NULL)
		return;

	mdla_reg_control = reg_ctl;

	io_ops.cfg.read        = mdla_cfg_read;
	io_ops.cfg.write       = mdla_cfg_write;
	io_ops.cfg.set_b       = mdla_cfg_set_b;
	io_ops.cfg.clr_b       = mdla_cfg_clr_b;
	io_ops.cmde.read       = mdla_reg_read;
	io_ops.cmde.write      = mdla_reg_write;
	io_ops.cmde.set_b      = mdla_reg_set_b;
	io_ops.cmde.clr_b      = mdla_reg_clr_b;
	io_ops.biu.read        = mdla_biu_read;
	io_ops.biu.write       = mdla_biu_write;
	io_ops.biu.set_b       = mdla_biu_set_b;
	io_ops.biu.clr_b       = mdla_biu_clr_b;
}

void mdla_util_io_set_extra_addr(int type,
		void *addr1, void *addr2, void *addr3)
{
	switch (type) {
	case EXTRA_ADDR_V1P0:
		apu_conn_top    = addr1;
		infracfg_ao_top = addr2;
		break;
	case EXTRA_ADDR_V1PX:
		apu_conn_top    = addr1;
		break;
	default:
		return;
	}

	if (apu_conn_top) {
		io_ops.apu_conn.read   = mdla_apu_conn_read;
		io_ops.apu_conn.write  = mdla_apu_conn_write;
		io_ops.apu_conn.set_b  = mdla_apu_conn_set_b;
		io_ops.apu_conn.clr_b  = mdla_apu_conn_clr_b;
	}

	if (infracfg_ao_top) {
		io_ops.infra_cfg.read  = mdla_infracfg_ao_read;
		io_ops.infra_cfg.write = mdla_infracfg_ao_write;
		io_ops.infra_cfg.set_b = mdla_infracfg_ao_set_b;
		io_ops.infra_cfg.clr_b = mdla_infracfg_ao_clr_b;
	}
}
