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

#include <asm/barrier.h>
#include <linux/version.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/ktime.h>
#include <linux/timer.h>
#include <linux/tee.h>
#include <linux/uuid.h>
#include <linux/kthread.h>
#include "mtk-reserved-memory.h"

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
#include "tee_drv_5_15_13.h"
#else
#include <linux/tee_drv.h>
#endif

#include "remoteproc_internal.h"

#define SLEEP_POLL_CLI_CHANGE 1000
#define SLEEP_POLL_WPTR_CHANGE_MIN 2000
#define SLEEP_POLL_WPTR_CHANGE_MAX 3000
#define MIN_DRAMLOG_BUFFER_SIZE 0x20000
#define FW_SIZE_RESERVED 0x240000
#define SIZE_PATCH 0x1000

#define VCP_R2_REG_RST_BASE_LOW                 (0x00DC)
#define VCP_R2_REG_RST_BASE                     (0x00B4)
#define VCP_R2_REG_STOP                         (0x0080)
#define VCP_R2_REG_RST                          (0x00BA)

#define VCP_R2_VAL_STOP                         (0x20)
#define VCP_R2_VAL_RST                          (0x24)
#define VCP_R2_VAL_BOOT                         (0x27)

#define VCP_R2_RESETED                          (0x40)
#define VCP_R2_SAFE_RESET                       (0x80)
#define VCP_R2_FORCE_RESET                      (0x100)

#define E_VCP_CLK_NUM		16

enum {
	E_VCP_FW_LOAD_ATTR_NONE     = 0x0,
	E_VCP_FW_LOAD_ATTR_STR_KEEP = 0x1,
	E_VCP_FW_LOAD_ATTR_MAX
};

enum {
	E_VCP_TEE_LOAD_FW_PARAM_EX_V0,
	E_VCP_TEE_LOAD_FW_PARAM_EX_VER_MAX,
};

enum {
	E_VCP_FW_SEC_SHM_V0,
	E_VCP_FW_SEC_SHM_VER_MAX,
};

struct mtk_vcp_tee_load_fw_param_base {
	u64 u64FwMemAddr;    // in (physical address)
	u32 u32FwMemSize;    // in (buffer size)
	u32 u32FwBinSize;    // in (fw binary size)
	u32 u32FwLoadAttr;   // in (fw load attribute)
	u32 u32ExParamVer;   // in
	u32 u32ExParamSize;  // in
};

struct mtk_vcp_tee_load_fw_param_ex_v0 {
	u8 u8FwSecShmVer;    // in (secure SHM version)
};

struct mtk_vcp_tee_context {
	void *tee_context;
	u32  session_id;
	bool initialized;
};

struct mtk_vcp_pm {
	u32             vcp_clk_count;
	struct clk      *vcp_clk[E_VCP_CLK_NUM];
	const char      *vcp_clkname[E_VCP_CLK_NUM];
};

struct mtk_vcp_ctl_info {
	const u32 readonly[4];         // CTL_INFO_ADDR + 0x00 read only for tag.
	u64 u64FwAddr;           // CTL_INFO_ADDR + 0x10
	u32 u32FwSize;                 // CTL_INFO_ADDR + 0x18
	u32 u32FwBinSize;              // CTL_INFO_ADDR + 0x1C
	u32 u32SvpShmVersion;          // CTL_INFO_ADDR + 0x20
	u32 u32SvpShmSize;             // CTL_INFO_ADDR + 0x24
	u32 u32SvpShmOffset;           // CTL_INFO_ADDR + 0x28
	u32 u32KeyValue;               // CTL_INFO_ADDR + 0x2C
	u8 u8Reserveds[0xE0-0x30];   // CTL_INFO_ADDR + 0x30
	u8 u8VerRevision[8];         // CTL_INFO_ADDR + 0xE0
	u8 u8VerLib[6];              // CTL_INFO_ADDR + 0xE8
	u8 u8VerChk[7];              // CTL_INFO_ADDR + 0xEE
	u8 u8VerChip[3];             // CTL_INFO_ADDR + 0xF5
	u8 u8VerBranchId;            // CTL_INFO_ADDR + 0xF8
	u8 u8VerChangelist[7];       // CTL_INFO_ADDR + 0xF9
};

struct mtk_vcp_dram_log_info {
	u32 dram_log_addr;
	u32 dram_log_size;
	u32 dram_log_write_ptr;
};

struct mtk_vcp {
	bool support_suspend;
	bool ac_ready;
	struct device *dev;
	struct rproc *rproc;
	struct clk *clk;
	void *fw_base;
	u64 phy_base;
	u32 emi_base;
	u32 fw_buf_size;
	u32 fw_bin_size;
	struct regmap  *vcp_regmap;
	struct list_head setting_init_seq;
	struct mtk_vcp_tee_context tee_ctx;
	struct mtk_vcp_pm pm;
	u32 protect_size;
	struct task_struct *dump_task;
	u32 log_write_ptr;
};

struct vcp_setting {
	struct list_head list;
	u32 offset, mask, val;
};

#define OFFSET_INDEX	0
#define MASK_INDEX	1
#define VAL_INDEX	2
#define PLL_DELAY	100
#define RST_DELAY	100

static s64 d0_vcp_clk_init;
static s64 d0_vcp_start;
static s64 d0_vcp_bin_load;
static s64 d0_mtk_vcp_resume;

#define REG_BIT_MASK	0xFFFF
#define REG_BIT_OFFSET	16

static struct clk *vd_r2_clk;
static struct clk_hw *vd_r2_clk_parent;
static const char clk_top_vd_r2_name[] = "clk_top_vd_r2";
static u32 clk_top_vd_r2_sel;

static bool auto_boot;
module_param(auto_boot, bool, 0444);
MODULE_PARM_DESC(auto_boot, "Enable auto boot");

static bool tee_load_firmware = true;
module_param(tee_load_firmware, bool, 0644);
MODULE_PARM_DESC(tee_load_firmware, "Enable TEE load firmware");

static bool dram_log_enable;
module_param(dram_log_enable, bool, 0644);
MODULE_PARM_DESC(dram_log_enable, "Enable Dram log");

// VCP TEE Interface
#define TEE_SHM_DEFAULT_LEN 8

#define TEEC_PARAM_TYPE_GET(p, i) (((p) >> ((i) * 4)) & 0xF)

#define VDEC_TA_UUID UUID_INIT(0x61328cc8, 0x8df1, 0x4821, \
	0x89, 0x01, 0x76, 0x1b, 0xb2, 0xb9, 0x11, 0x5c)

enum vdec_ta_tee_cmd {
	E_VDEC_OPTEE_VCP_LOAD_FW    = 17,
	E_VDEC_OPTEE_MAX,
};

enum mtk_vcp_tee_param_index {
	VCP_TEE_PARAM_0,
	VCP_TEE_PARAM_1,
	VCP_TEE_PARAM_2,
	VCP_TEE_PARAM_3,
	VCP_TEE_PARAM_COUNT
};

enum mtk_vcp_tee_param_type {
	VCP_TEE_PARAM_NONE,
	VCP_TEE_PARAM_VALUE_IN,
	VCP_TEE_PARAM_VALUE_OUT,
	VCP_TEE_PARAM_VALUE_INOUT,
	VCP_TEE_PARAM_MEMREF_IN,
	VCP_TEE_PARAM_MEMREF_OUT,
	VCP_TEE_PARAM_MEMREF_INOUT,
};

struct mtk_vcp_tee_share_mem {
	void *buffer;
	size_t size;
	uint32_t flags;
	size_t alloced_size;
	void *shadow_buffer;
};

struct mtk_vcp_tee_value {
	u32 val_a;
	u32 val_b;
};

struct mtk_vcp_tee_temp_ref {
	void    *buffer;
	size_t  size;
};

struct mtk_vcp_tee_param {
	enum mtk_vcp_tee_param_type type;
	struct mtk_vcp_tee_value    value;
	struct mtk_vcp_tee_temp_ref tmpref;
};

struct mtk_vcp_tee_operation {
	struct mtk_vcp_tee_param params[VCP_TEE_PARAM_COUNT];
};

static int _vcp_optee_match(struct tee_ioctl_version_data *data, const void *vers)
{
	return 1;
}

static int _vcp_tee_open_context(struct mtk_vcp_tee_context *vcp_tee_ctx)
{
	struct tee_ioctl_version_data vers = {
		.impl_id = TEE_OPTEE_CAP_TZ,
		.impl_caps = TEE_IMPL_ID_OPTEE,
		.gen_caps = TEE_GEN_CAP_GP,
	};
	struct tee_context *ctx;
	struct tee_ioctl_open_session_arg arg;
	struct tee_param param;
	static const uuid_t vdec_ta_uuid = VDEC_TA_UUID;
	int rc;

	if (!vcp_tee_ctx) {
		pr_err("VCP_RPROC TEE ERR: Open NULL vcp_tee_ctx\n");
		return -EINVAL;
	}

	if (vcp_tee_ctx->initialized)
		return 0;

	ctx = tee_client_open_context(NULL, _vcp_optee_match, NULL, &vers);
	if (IS_ERR_OR_NULL(ctx)) {
		rc = PTR_ERR(ctx);
		pr_err("VCP_RPROC TEE ERR: VCP tee_client_open_context fail, rc=%d\n", rc);
		return rc;
	}

	memset(&arg, 0, sizeof(arg));
	memset(&param, 0, sizeof(param));
	memcpy(arg.uuid, &vdec_ta_uuid, TEE_IOCTL_UUID_LEN);
	arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;

	rc = tee_client_open_session(ctx, &arg, &param);
	if (rc || arg.ret) {
		pr_err("VCP_RPROC TEE ERR: VCP tee_client_open_session fail, rc=%d ret=%d\n",
			rc, arg.ret);
		tee_client_close_context(ctx);
		return -EINVAL;
	}

	vcp_tee_ctx->tee_context = (void *)ctx;
	vcp_tee_ctx->session_id = arg.session;
	vcp_tee_ctx->initialized = true;

	return 0;
}

static int _vcp_tee_close_context(struct mtk_vcp_tee_context *vcp_tee_ctx)
{
	if (!vcp_tee_ctx) {
		pr_err("VCP_RPROC TEE ERR: Close NULL vcp_tee_ctx\n");
		return -EINVAL;
	}

	if (vcp_tee_ctx->tee_context) {
		tee_client_close_session((struct tee_context *)vcp_tee_ctx->tee_context,
			vcp_tee_ctx->session_id);

		tee_client_close_context((struct tee_context *)vcp_tee_ctx->tee_context);
	}

	vcp_tee_ctx->tee_context = NULL;
	vcp_tee_ctx->initialized = false;

	return 0;
}

static int _teec_param_type(enum mtk_vcp_tee_param_type param_type)
{
	switch (param_type) {
	case VCP_TEE_PARAM_NONE:
		return TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
	case VCP_TEE_PARAM_VALUE_IN:
		return TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
	case VCP_TEE_PARAM_VALUE_OUT:
		return TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;
	case VCP_TEE_PARAM_VALUE_INOUT:
		return TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT;
	case VCP_TEE_PARAM_MEMREF_IN:
		return TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
	case VCP_TEE_PARAM_MEMREF_OUT:
		return TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
	case VCP_TEE_PARAM_MEMREF_INOUT:
		return TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT;
	default:
		return TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
	}
}

static int _teec_allocatesharedmemory(struct tee_context *ctx,
				      struct mtk_vcp_tee_share_mem *shm)
{
	struct tee_shm *reg_tee_shm = NULL;
	void *shm_va = NULL;
	size_t len;
	struct tee_context *context = ctx;

	shm->shadow_buffer = shm->buffer = NULL;
	len = shm->size;
	if (!len) {
		pr_err("%s %d: Invalid zero shm size!\n", __func__, __LINE__);
		return -EINVAL;
	}

	shm_va = vmalloc(len);
	if (!shm_va || IS_ERR(shm_va)) {
		pr_err("%s %d: tee_shm vmalloc Fail\n",
		       __func__, __LINE__);
		return -ENOMEM;
	}

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
	reg_tee_shm = tee_shm_register(context, (uintptr_t)shm_va, len,
		TEE_SHM_KERNEL_MAPPED | TEE_SHM_DMA_BUF);
#else
	reg_tee_shm = tee_shm_register_kernel_buf(context, (void *)shm_va, len);
#endif
	if (!reg_tee_shm || IS_ERR(reg_tee_shm)) {
		pr_err("%s %d: tee_shm_register Fail\n",
		       __func__, __LINE__);
		vfree(shm_va);
		return -ENOMEM;
	}

	shm->buffer = shm_va;
	shm->shadow_buffer = (void *)reg_tee_shm;
	shm->alloced_size = len;

	return 0;
}

static void _teec_freesharedmemory(struct mtk_vcp_tee_share_mem *shm)
{
	if (shm->shadow_buffer)
		tee_shm_put((struct tee_shm *)shm->shadow_buffer);

	if (shm->buffer)
		vfree(shm->buffer);

	shm->shadow_buffer = NULL;
	shm->buffer = NULL;
}

static int _teec_pre_process_tmpref(struct tee_context *ctx,
	uint32_t param_type,
	struct mtk_vcp_tee_temp_ref *tmpref,
	struct tee_param *param,
	struct mtk_vcp_tee_share_mem *shm)
{
	int res;

	param->attr = _teec_param_type(param_type);
	shm->size = tmpref->size;

	res = _teec_allocatesharedmemory(ctx, shm);
	if (res != 0)
		return res;

	memcpy(shm->buffer, tmpref->buffer, tmpref->size);
	param->u.memref.size = tmpref->size;
	// shm->shadow_buffer is shm object.
	param->u.memref.shm = (struct tee_shm *)shm->shadow_buffer;

	return 0;
}

static int _teec_pre_process_operation(struct mtk_vcp_tee_context *vcp_tee_ctx,
	struct mtk_vcp_tee_operation *operation,
	struct tee_param *params,
	struct mtk_vcp_tee_share_mem *shms)
{
	int res;
	size_t n;
	uint32_t param_type;
	struct tee_context *ctx;

	if (!vcp_tee_ctx)
		return -EINVAL;

	ctx = vcp_tee_ctx->tee_context;

	memset(shms, 0, sizeof(*shms) * VCP_TEE_PARAM_COUNT);
	if (!operation) {
		memset(params, 0, sizeof(*params) * VCP_TEE_PARAM_COUNT);
		return 0;
	}

	for (n = 0; n < VCP_TEE_PARAM_COUNT; n++) {
		param_type = operation->params[n].type;
		switch (param_type) {
		case VCP_TEE_PARAM_NONE:
			params[n].attr = _teec_param_type(param_type);
			break;
		case VCP_TEE_PARAM_VALUE_IN:
		case VCP_TEE_PARAM_VALUE_OUT:
		case VCP_TEE_PARAM_VALUE_INOUT:
			params[n].attr = _teec_param_type(param_type);
			params[n].u.value.a = operation->params[n].value.val_a;
			params[n].u.value.b = operation->params[n].value.val_b;
			break;
		case VCP_TEE_PARAM_MEMREF_IN:
		case VCP_TEE_PARAM_MEMREF_OUT:
		case VCP_TEE_PARAM_MEMREF_INOUT:
			res = _teec_pre_process_tmpref(ctx, param_type,
				&operation->params[n].tmpref,
				params + n, shms + n);
			if (res != 0)
				return res;
			break;
		default:
			pr_err("[%s] param_type %d not support!",
				__func__, param_type);
			return -1;
		}
	}

	return 0;
}

static void _teec_post_process_tmpref(uint32_t param_type,
	struct mtk_vcp_tee_temp_ref *tmpref,
	struct tee_param *param,
	struct mtk_vcp_tee_share_mem *shm)
{
	if (param_type != VCP_TEE_PARAM_MEMREF_IN) {
		if (param->u.memref.size <= tmpref->size && tmpref->buffer)
			memcpy(tmpref->buffer, shm->buffer,
			       param->u.memref.size);

		tmpref->size = param->u.memref.size;
	}
}

static void _teec_post_process_operation(struct mtk_vcp_tee_operation *operation,
	struct tee_param *params,
	struct mtk_vcp_tee_share_mem *shms)
{
	size_t n;
	uint32_t param_type;

	if (!operation)
		return;

	for (n = 0; n < VCP_TEE_PARAM_COUNT; n++) {
		param_type = operation->params[n].type;
		switch (param_type) {
		case VCP_TEE_PARAM_VALUE_IN:
			break;
		case VCP_TEE_PARAM_VALUE_OUT:
		case VCP_TEE_PARAM_VALUE_INOUT:
			operation->params[n].value.val_a = params[n].u.value.a;
			operation->params[n].value.val_b = params[n].u.value.b;
			break;
		case VCP_TEE_PARAM_MEMREF_IN:
		case VCP_TEE_PARAM_MEMREF_OUT:
		case VCP_TEE_PARAM_MEMREF_INOUT:
			_teec_post_process_tmpref(param_type,
				&operation->params[n].tmpref,
				params + n, shms + n);
			break;
		default:
			break;
		}
	}
}

static void _teec_free_temp_refs(struct mtk_vcp_tee_operation *operation,
	struct mtk_vcp_tee_share_mem *shms)
{
	size_t n;

	if (!operation)
		return;

	for (n = 0; n < VCP_TEE_PARAM_COUNT; n++) {
		switch (operation->params[n].type) {
		case VCP_TEE_PARAM_MEMREF_IN:
		case VCP_TEE_PARAM_MEMREF_OUT:
		case VCP_TEE_PARAM_MEMREF_INOUT:
			_teec_freesharedmemory(shms + n);
			break;
		default:
			break;
		}
	}
}

int _vcp_tee_invoke_command(struct mtk_vcp_tee_context *vcp_tee_ctx,
	u32 command_id,
	struct mtk_vcp_tee_operation *vcp_tee_op)
{
	struct mtk_vcp_tee_context *act_tee_ctx;
	struct mtk_vcp_tee_context inner_tee_ctx;
	struct tee_ioctl_invoke_arg arg;
	struct tee_param params[VCP_TEE_PARAM_COUNT];
	struct mtk_vcp_tee_share_mem shm[VCP_TEE_PARAM_COUNT];
	int res;
	int rc;

	if (!vcp_tee_ctx || !vcp_tee_op)
		return -EINVAL;

	if (!vcp_tee_ctx->tee_context) {
		memset(&inner_tee_ctx, 0, sizeof(inner_tee_ctx));
		res = _vcp_tee_open_context(&inner_tee_ctx);
		if (res) {
			pr_err("VCP_RPROC TEE ERR: Open inner VCP TEE context fail\n");
			return res;
		}
		act_tee_ctx = &inner_tee_ctx;
	} else {
		act_tee_ctx = vcp_tee_ctx;
	}

	memset(&arg, 0x0, sizeof(arg));
	memset(params, 0x0, sizeof(params));
	memset(shm, 0x0, sizeof(shm));

	arg.num_params = VCP_TEE_PARAM_COUNT;
	arg.session = act_tee_ctx->session_id;
	arg.func = command_id;

	res = _teec_pre_process_operation(act_tee_ctx, vcp_tee_op, params, shm);
	if (res != 0) {
		pr_err("VCP_RPROC TEE ERR: Pre process operation fail. res=%d\n", res);
		goto out_free_temp_refs;
	}

	rc = tee_client_invoke_func(act_tee_ctx->tee_context, &arg, params);
	if (rc) {
		pr_err("VCP_RPROC TEE ERR: Invoke tee cmd fail. rc=%d ret=%d orig=%d\n",
			rc, arg.ret, arg.ret_origin);
		res = rc;
		goto out_free_temp_refs;
	}

	res = arg.ret;
	_teec_post_process_operation(vcp_tee_op, params, shm);

out_free_temp_refs:
	_teec_free_temp_refs(vcp_tee_op, shm);

	if (!vcp_tee_ctx->tee_context)
		_vcp_tee_close_context(&inner_tee_ctx);

	return res;
}

int vcp_tee_load_fw(struct mtk_vcp *vcp)
{
	struct mtk_vcp_tee_operation op;
	struct mtk_vcp_tee_load_fw_param_base fw_param_base;
	struct mtk_vcp_tee_load_fw_param_ex_v0 fw_param_ex;
	int res;

	if (!vcp)
		return -EINVAL;

	// TEE load fw base param
	fw_param_base.u64FwMemAddr = vcp->phy_base - vcp->emi_base;
	fw_param_base.u32FwMemSize = FW_SIZE_RESERVED;
	fw_param_base.u32FwBinSize = vcp->fw_bin_size;
	fw_param_base.u32FwLoadAttr = E_VCP_FW_LOAD_ATTR_STR_KEEP;
	fw_param_base.u32ExParamVer = E_VCP_TEE_LOAD_FW_PARAM_EX_V0;
	fw_param_base.u32ExParamSize = sizeof(struct mtk_vcp_tee_load_fw_param_ex_v0);

	// TEE load fw extend param
	fw_param_ex.u8FwSecShmVer = E_VCP_FW_SEC_SHM_V0;

	// TEE cmd operation
	memset(&op, 0, sizeof(op));
	op.params[VCP_TEE_PARAM_0].type = VCP_TEE_PARAM_MEMREF_IN;
	op.params[VCP_TEE_PARAM_1].type = VCP_TEE_PARAM_MEMREF_INOUT;
	op.params[VCP_TEE_PARAM_2].type = VCP_TEE_PARAM_NONE;
	op.params[VCP_TEE_PARAM_3].type = VCP_TEE_PARAM_NONE;

	op.params[VCP_TEE_PARAM_0].tmpref.buffer = (void *)&fw_param_base;
	op.params[VCP_TEE_PARAM_0].tmpref.size = sizeof(fw_param_base);

	op.params[VCP_TEE_PARAM_1].tmpref.buffer = (void *)&fw_param_ex;
	op.params[VCP_TEE_PARAM_1].tmpref.size = sizeof(fw_param_ex);

	// Invoke TEE load VCP FW CMD
	res = _vcp_tee_invoke_command(&vcp->tee_ctx, E_VDEC_OPTEE_VCP_LOAD_FW, &op);
	if (res != 0)
		dev_err(vcp->dev, "VCP_RPROC TEE ERR: VCP TEE Load FW fail. res=%d\n", res);

	return res;
}

int vcp_tee_init(struct mtk_vcp *vcp)
{
	int ret = 0;

	if (!vcp)
		return -EINVAL;

	if (tee_load_firmware)
		ret = _vcp_tee_open_context(&vcp->tee_ctx);

	return ret;
}

int vcp_tee_deinit(struct mtk_vcp *vcp)
{
	if (!vcp)
		return -EINVAL;

	if (vcp->tee_ctx.initialized)
		_vcp_tee_close_context(&vcp->tee_ctx);

	return 0;
}

struct mtk_vcp_rproc_watchdog {
	struct device		*dev;
	struct task_struct	*tsk;
	struct timer_list	timer;
};

static int get_emi0_base(u32 *addr)
{
	struct device_node *target_memory_np = NULL;
	__be32 *p = NULL;

	// find memory_info node in dts
	target_memory_np = of_find_node_by_name(NULL, "memory_info");
	if (!target_memory_np)
		return -ENODEV;

	// query cpu_emi0_base value from memory_info node
	p = (__be32 *)of_get_property(target_memory_np, "cpu_emi0_base", NULL);
	of_node_put(target_memory_np);
	if (p == NULL) {
		pr_err("can not find cpu_emi0_base info\n");
		return -EINVAL;
	}

	*addr = be32_to_cpup(p);
	return 0;
}

static int mtk_vcp_print_dram_log(void *data)
{
	struct mtk_vcp *vcp = (struct mtk_vcp *)data;
	struct mtk_vcp_dram_log_info *dram_log_info = NULL;
	void *buff = NULL;
	u32 write_ptr, read_ptr;
	u32 valid_size;
	u32 buf_size;
	u32 read_offset;

	if (vcp->protect_size + MIN_DRAMLOG_BUFFER_SIZE <= vcp->fw_buf_size)
		dram_log_info = (struct mtk_vcp_dram_log_info *)(vcp->fw_base + vcp->protect_size);

	if (dram_log_info == NULL) {
		pr_err("dram_log_info is null\n");
		return 0;
	}
	// TODO: patch for random kernel panic, fix it.
	buff = ioremap_wc(
		(dram_log_info->dram_log_addr + vcp->emi_base),
		(dram_log_info->dram_log_size + SIZE_PATCH));

	if (buff == NULL) {
		pr_err("dram log buf map fail\n");
		return 0;
	}

	buf_size = dram_log_info->dram_log_size;

	while (!kthread_should_stop()) {
		if (dram_log_enable == false) {
			msleep(SLEEP_POLL_CLI_CHANGE);
			continue;
		}
		write_ptr = READ_ONCE(dram_log_info->dram_log_write_ptr);
		read_ptr = vcp->log_write_ptr;

		if (read_ptr != write_ptr) {
			if (write_ptr > read_ptr)
				valid_size = write_ptr - read_ptr;
			else
				valid_size = (write_ptr + buf_size) - read_ptr;

			read_offset = 0;

			if (read_ptr + valid_size < buf_size) {
				char *temp = &buff[read_ptr];
				char *token = NULL;

				while (read_offset < valid_size && (token = strsep(&temp, "\n"))) {
					read_offset += (strlen(token) + 1);
					pr_info("%s\n", token);
				}
				read_ptr += read_offset;
			} else {
				u32 remain;

				remain = buf_size - read_ptr;
				pr_info("%.*s", remain, &buff[read_ptr]);
				read_ptr = 0;
			}

			vcp->log_write_ptr = read_ptr;
		}

		usleep_range(SLEEP_POLL_WPTR_CHANGE_MIN, SLEEP_POLL_WPTR_CHANGE_MAX);
	}

	iounmap(buff);

	return 0;
}


static void mtk_vcp_rproc_watchdog_handler(struct timer_list *t)
{
	struct mtk_vcp_rproc_watchdog *wd = from_timer(wd, t, timer);

	dev_emerg(wd->dev, "**** vcp_rproc timeout ****\n");
	dev_emerg(wd->dev, "d0_mtk_vcp_resume time  = %lld\n", d0_mtk_vcp_resume);
	dev_emerg(wd->dev, "d0_vcp_bin_load time  = %lld\n", d0_vcp_bin_load);
	dev_emerg(wd->dev, "d0_vcp_start time  = %lld\n", d0_vcp_start);
	dev_emerg(wd->dev, "d0_vcp_clk_init time  = %lld\n", d0_vcp_clk_init);
}

static void mtk_vcp_rproc_watchdog_set(struct mtk_vcp_rproc_watchdog *wd, struct device *dev)
{
	struct timer_list *timer = &wd->timer;

	wd->dev = dev;
	wd->tsk = current;

	timer_setup_on_stack(timer, mtk_vcp_rproc_watchdog_handler, 0);
	/* use same timeout value for both suspend and resume */
	timer->expires = jiffies + HZ * 2;
	add_timer(timer);
}

static void mtk_vcp_rproc_watchdog_clear(struct mtk_vcp_rproc_watchdog *wd)
{
	struct timer_list *timer = &wd->timer;

	del_timer_sync(timer);
	destroy_timer_on_stack(timer);
}

static void mtk_vcp_clk_on(struct device *dev, struct mtk_vcp_pm *pm)
{
	int i = 0, ret = 0;

	//clk_top_vd_r2 need mux to right sel
	if (vd_r2_clk_parent) {
		ret = clk_set_parent(vd_r2_clk, vd_r2_clk_parent->clk);
		if (ret < 0)
			dev_err(dev, "set topgen clk fails ret %d\n", ret);
	}

	for (i = 0; i < pm->vcp_clk_count; i++) {
		if (pm->vcp_clk[i]) {
			ret = clk_prepare_enable(pm->vcp_clk[i]);
			if (ret < 0)
				dev_err(dev, "enable clk %d ret %d\n", i, ret);
		}
	}
}

static void mtk_vcp_clk_off(struct device *dev, struct mtk_vcp_pm *pm)
{
	int i = 0;

	for (i = 0; i < pm->vcp_clk_count; i++) {
		if (pm->vcp_clk[i])
			clk_disable_unprepare(pm->vcp_clk[i]);
	}
}

static int mtk_vcp_pm_init(struct device *dev, struct mtk_vcp *vcp)
{
	struct mtk_vcp_pm *pm = &vcp->pm;
	int i = 0, ret = 0;

	ret = of_property_read_u32(dev->of_node, "CLK_COUNT", &pm->vcp_clk_count);
	if (ret < 0) {
		dev_err(dev, "Failed to get CLK_COUNT. ret %d\n", ret);
		return -EINVAL;
	}

	if (pm->vcp_clk_count > E_VCP_CLK_NUM) {
		dev_err(dev, "Failed! clk count %d is too large\n", pm->vcp_clk_count);
		return -EINVAL;
	}

	for (i = 0; i < pm->vcp_clk_count; i++) {
		if (!pm->vcp_clk[i]) {
			ret = of_property_read_string_index(dev->of_node,
						"clock-names", i, &pm->vcp_clkname[i]);
			if (ret < 0) {
				dev_info(dev, "get %d clk name failed ret %d\n", i, ret);
				pm->vcp_clk[i] = NULL;
				continue;
			}

			pm->vcp_clk[i] = devm_clk_get(dev, pm->vcp_clkname[i]);
			if (IS_ERR(pm->vcp_clk[i])) {
				dev_err(dev, "get clk[%d] %s fail", i, pm->vcp_clkname[i]);
				pm->vcp_clk[i] = NULL;
				continue;
			}

			//clk_top_vd_r2
			if ((strlen(pm->vcp_clkname[i]) == strlen(clk_top_vd_r2_name))
			&& (strncmp(pm->vcp_clkname[i], clk_top_vd_r2_name,
						strlen(clk_top_vd_r2_name)) == 0)) {
				ret = of_property_read_u32(dev->of_node,
						"VD_R2_CLK_SEL", &clk_top_vd_r2_sel);
				if (ret < 0) {
					dev_err(dev, "Failed to get CLK_SEL ret %d\n", ret);
					return -EINVAL;
				}
				vd_r2_clk = pm->vcp_clk[i];
				vd_r2_clk_parent =
				clk_hw_get_parent_by_index(__clk_get_hw(vd_r2_clk),
					clk_top_vd_r2_sel);
				if (IS_ERR_OR_NULL(vd_r2_clk_parent))
					dev_err(dev, "target_parent of clk_top_vd_r2 get fail\n");
			}
		}
	}

	return 0;
}

static int setup_init_seq(struct device *dev, struct mtk_vcp *vcp,
			   struct device_node *np)
{
	int count, i, rc, ret;
	struct of_phandle_args clkspec;

	count = of_count_phandle_with_args(np, "vcp_setting", "#vcp_bank_map_cells");
	if (count < 0) {
		dev_warn(dev, "vcp_setting not found\n");
		return count;
	}

	for (i = 0; i < count; i++) {
		struct vcp_setting *clk;

		rc = of_parse_phandle_with_args(np, "vcp_setting",
				"#vcp_bank_map_cells", i, &clkspec);
		if (rc < 0) {
			dev_warn(dev,  "vcp-setting parse fail with err %d\n", rc);
			return rc;
		}

		clk = kzalloc(sizeof(struct vcp_setting), GFP_KERNEL);
		if (!clk)
			return -ENOMEM;

		clk->offset = clkspec.args[OFFSET_INDEX];
		clk->mask = clkspec.args[MASK_INDEX];
		clk->val = clkspec.args[VAL_INDEX];

		list_add_tail(&clk->list, &vcp->setting_init_seq);
	}

	ret = mtk_vcp_pm_init(dev, vcp);
	if (ret) {
		dev_err(dev, "Failed to get mt vcp clock source");
		return ret;
	}

	return 0;
}

static void mtk_vcp_setting_init(struct device *dev, struct mtk_vcp *vcp)
{
	struct vcp_setting *setting;
	ktime_t start_time;
	ktime_t end_time;

	start_time = ktime_get();

	mtk_vcp_clk_on(vcp->dev, &vcp->pm);

	if (list_empty(&vcp->setting_init_seq)) {
		dev_warn(dev, "setting_init_seq is not set\n");
		return;
	}

	list_for_each_entry(setting, &vcp->setting_init_seq, list) {
		regmap_update_bits_base(vcp->vcp_regmap, setting->offset, setting->mask,
					setting->val, NULL, false, true);
	}
	end_time = ktime_get();
	d0_vcp_clk_init = (s64)ktime_to_ns(ktime_sub(end_time, start_time));
}


static int vcp_start(struct rproc *rproc)
{
	struct mtk_vcp *vcp = rproc->priv;
	u32 emi_offset;
	ktime_t start_time;
	ktime_t end_time;

	start_time = ktime_get();

	emi_offset = vcp->phy_base - vcp->emi_base;
	if (vcp->ac_ready != 1) {
		regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_RST_BASE_LOW << 1,
					0xFFFF, emi_offset & REG_BIT_MASK, NULL, false, true);
		regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_RST_BASE << 1,
					0xFFFF, emi_offset >> REG_BIT_OFFSET, NULL, false, true);

		regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_STOP << 1,
					0x00FF, VCP_R2_VAL_RST, NULL, false, true);
		regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_STOP << 1,
					0x00FF, VCP_R2_VAL_BOOT, NULL, false, true);

		vcp->ac_ready = 1;
	}

	end_time = ktime_get();
	d0_vcp_start = (s64)ktime_to_ns(ktime_sub(end_time, start_time));

	return 0;
}

static int vcp_stop(struct rproc *rproc)
{
	struct mtk_vcp *vcp = rproc->priv;
	unsigned int val = 0;
	int try_cnt = 0;

	regmap_read(vcp->vcp_regmap, VCP_R2_REG_RST << 1, &val);

	if (val & VCP_R2_SAFE_RESET) {
		regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_RST << 1,
			0x0FFF, (val | VCP_R2_FORCE_RESET), NULL, false, true);

		regmap_read(vcp->vcp_regmap, VCP_R2_REG_RST << 1, &val);

		while (!(val & VCP_R2_RESETED)) {
			if (try_cnt > 5) {
				dev_err(vcp->dev, "can't wait reg_r2_reseted done\n");
				break;
			}
			msleep(RST_DELAY);
			regmap_read(vcp->vcp_regmap, VCP_R2_REG_RST << 1, &val);
			try_cnt++;
		}
	}

	/* pll disable */
	regmap_update_bits_base(vcp->vcp_regmap, VCP_R2_REG_STOP << 1,
					0x00FF, VCP_R2_VAL_STOP, NULL, false, true);

	return 0;
}

static void *vcp_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
	struct mtk_vcp *vcp = rproc->priv;

	return vcp->fw_base + da;
}

static int vcp_bin_load(struct rproc *rproc, const struct firmware *fw)
{
	struct mtk_vcp *vcp = rproc->priv;
	void *ptr;
	bool ree_load_firmware = true;
	struct mtk_vcp_dram_log_info *dram_log_info = NULL;
	ktime_t start_time;
	ktime_t end_time;

	start_time = ktime_get();

	vcp->protect_size = FW_SIZE_RESERVED;
	if (vcp->protect_size + MIN_DRAMLOG_BUFFER_SIZE <= vcp->fw_buf_size)
		dram_log_info = (struct mtk_vcp_dram_log_info *)(vcp->fw_base + vcp->protect_size);

	// TEE load fw
	if (tee_load_firmware) {
		vcp->fw_bin_size = fw->size;
		if (vcp_tee_load_fw(vcp) == 0)
			ree_load_firmware = false;
		else
			dev_err(vcp->dev, "VCP_RPROC TEE load fw fail. Try REE load fw..\n");
	}

	// REE load fw
	if (ree_load_firmware) {
		ptr = ioremap_wc(vcp->phy_base, fw->size);
		if (ptr == NULL) {
			dev_err(vcp->dev, "Failed to map firmware address\n");
			return -EIO;
		}
		memcpy(ptr, fw->data, fw->size);
		iounmap(ptr);

		// Fill VCP FW info to FW control area if Ree load fw
		((struct mtk_vcp_ctl_info *)vcp->fw_base)->u64FwAddr =
			vcp->phy_base - vcp->emi_base;
		((struct mtk_vcp_ctl_info *)vcp->fw_base)->u32FwSize =
			vcp->fw_buf_size;
		((struct mtk_vcp_ctl_info *)vcp->fw_base)->u32FwBinSize =
			fw->size;
	}

	if (dram_log_info) {
		// Dram log init
		dram_log_info->dram_log_addr =
			vcp->phy_base + vcp->protect_size +
			sizeof(struct mtk_vcp_dram_log_info) - vcp->emi_base;
		dram_log_info->dram_log_write_ptr = 0;
		// TODO: patch for random kernel panic, fix it.
		dram_log_info->dram_log_size =
			vcp->fw_buf_size - sizeof(struct mtk_vcp_dram_log_info) -
			vcp->protect_size - SIZE_PATCH;
	}

	if (!vcp->dump_task)
		vcp->dump_task = kthread_run(mtk_vcp_print_dram_log, vcp, "vcp_dram_log");

	if (IS_ERR_OR_NULL(vcp->dump_task)) {
		vcp->dump_task = NULL;
		dev_err(vcp->dev, "Can not start vcp_dram_log thread\n");
	}

	end_time = ktime_get();
	d0_vcp_bin_load = (s64)ktime_to_ns(ktime_sub(end_time, start_time));
	return 0;
}

/* kick a virtqueue */
static void vcp_kick(struct rproc *rproc, int vqid)
{
	// TODO: Still need?
}

static const struct rproc_ops vcp_ops = {
	.start		= vcp_start,
	.stop		= vcp_stop,
	.kick		= vcp_kick,
	.da_to_va	= vcp_da_to_va,
	.load		= vcp_bin_load
};

static int mtk_vcp_parse_firmware(struct platform_device *pdev, const char **firmware)
{
	int ret;

	ret = of_property_read_string_index(pdev->dev.of_node, "firmware-name",
							0, firmware);

	return ret;
}

static int mtk_vcp_mcu_parse_dt(struct platform_device *pdev)
{
	int ret;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node, *syscon_np;
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_vcp *vcp = rproc->priv;
	struct of_mmap_info_data mmap_info;

	// TODO: Need to consider VCP str flow
	vcp->support_suspend = of_property_read_bool(np,
					"mediatek,vcp-support-suspend");

	syscon_np = of_parse_phandle(np, "vcp_bank", 0);
	if (!syscon_np) {
		dev_warn(dev, "no mtk,vcp_bank node\n");
		return -ENODEV;
	}
	vcp->vcp_regmap = syscon_node_to_regmap(syscon_np);
	if (IS_ERR(vcp->vcp_regmap))
		return PTR_ERR(vcp->vcp_regmap);

	ret = setup_init_seq(dev, vcp, np);
	if (ret)
		return ret;

	ret = of_mtk_get_reserved_memory_info_by_idx(np, 0, &mmap_info);
	if (ret)
		return ret;

	if (mmap_info.buffer_size == 0)
		return -ENODEV;

	vcp->fw_base = devm_ioremap_wc(dev, mmap_info.start_bus_address, mmap_info.buffer_size);
	vcp->phy_base = mmap_info.start_bus_address;
	vcp->fw_buf_size = mmap_info.buffer_size;

	if (IS_ERR(vcp->fw_base))
		return PTR_ERR(vcp->fw_base);

	ret = get_emi0_base(&vcp->emi_base);
	if (ret) {
		dev_err(dev, "no defined miu0_base\n");
		return -EINVAL;
	}

	vcp->ac_ready = 0;

	return 0;
}

static void mtk_vcp_free_resource(struct platform_device *pdev)
{
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_vcp *vcp = rproc->priv;
	struct vcp_setting *setting, *tmp;

	list_for_each_entry_safe(setting, tmp, &vcp->setting_init_seq, list) {
		list_del(&setting->list);
		kfree(setting);
	}
}

static int __maybe_unused mtk_vcp_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_vcp *vcp = rproc->priv;

	if (!vcp->support_suspend) {
		dev_warn(dev, "device doesn't support supsend\n");
		return 0;
	}

	/* do not handle pm ops if !auto_boot*/
	if (!rproc->auto_boot)
		return 0;

	vcp_stop(rproc);

	rproc_boot(rproc);

	rproc_shutdown(rproc);

	mtk_vcp_clk_off(dev, &vcp->pm);

	return 0;
}

static int __maybe_unused mtk_vcp_resume(struct device *dev)
{
	int ret;
	ktime_t start_time_resume;
	ktime_t end_time_resume;
	struct mtk_vcp_rproc_watchdog wd;

	struct platform_device *pdev = to_platform_device(dev);
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_vcp *vcp = rproc->priv;

	start_time_resume = ktime_get();

	if (!vcp->support_suspend) {
		dev_warn(dev, "device doesn't support supsend\n");
		return 0;
	}

	/* do not handle pm ops if !auto_boot*/
	if (!rproc->auto_boot)
		return 0;

	mtk_vcp_rproc_watchdog_set(&wd, dev);

	mtk_vcp_setting_init(dev, vcp);

	udelay(PLL_DELAY);

	vcp->ac_ready = 0;

	ret = vcp_start(rproc);

	end_time_resume = ktime_get();
	d0_mtk_vcp_resume = (s64)ktime_to_ns(ktime_sub(start_time_resume, end_time_resume));
	mtk_vcp_rproc_watchdog_clear(&wd);
	return ret;
}

static const struct dev_pm_ops mtk_vcp_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(mtk_vcp_suspend, mtk_vcp_resume)
};

static int mtk_vcp_rproc_probe(struct platform_device *pdev)
{
	int ret;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct mtk_vcp *vcp;
	struct rproc *rproc;
	const char *firmware = NULL;

	ret = mtk_vcp_parse_firmware(pdev, &firmware);
	if (ret < 0 && ret != -EINVAL)
		return ret;

	rproc = rproc_alloc(dev, np->name, &vcp_ops,
				firmware, sizeof(*vcp));

	if (!rproc)
		return -ENOMEM;

	rproc->auto_boot = of_property_read_bool(np, "mediatek,auto-boot");
	if (!rproc->auto_boot)
		rproc->auto_boot = auto_boot;

	rproc->has_iommu = false;

	vcp = (struct mtk_vcp *)rproc->priv;
	vcp->rproc = rproc;
	vcp->dev = dev;
	INIT_LIST_HEAD(&vcp->setting_init_seq);

	platform_set_drvdata(pdev, rproc);
	ret = mtk_vcp_mcu_parse_dt(pdev);
	if (ret)
		return ret;

	device_enable_async_suspend(dev);

	mtk_vcp_setting_init(dev, vcp);

	udelay(PLL_DELAY);

	// Init VCP TEE environment
	vcp_tee_init(vcp);

	ret = rproc_add(rproc);
	if (ret)
		return ret;

	return 0;
}

static int mtk_vcp_rproc_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_vcp *vcp = (struct mtk_vcp *)rproc->priv;

	if (vcp->dump_task)
		kthread_stop(vcp->dump_task);

	// Deinit VCP TEE environment
	vcp_tee_deinit(vcp);

	mtk_vcp_clk_off(dev, &vcp->pm);

	mtk_vcp_free_resource(pdev);

	of_reserved_mem_device_release(dev);
	rproc_del(rproc);
	rproc_free(rproc);
	return 0;
}

static const struct of_device_id mtk_vcp_of_match[] = {
	{ .compatible = "mediatek,vcp_r2"},
	{},
};

MODULE_DEVICE_TABLE(of, mtk_vcp_of_match);

static struct platform_driver mtk_vcp_rproc = {
	.probe = mtk_vcp_rproc_probe,
	.remove = mtk_vcp_rproc_remove,
	.driver = {
		.name = "mtk_vcp_r2",
		.pm = &mtk_vcp_pm_ops,
		.of_match_table = of_match_ptr(mtk_vcp_of_match),
	},
};

module_platform_driver(mtk_vcp_rproc);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wade.Wang <wade.wang@mediatek.com>");
MODULE_DESCRIPTION("MediaTek VCP Remoteproc driver");

