// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc. All rights reserved.
 */

#define pr_fmt(fmt) "dprx: " fmt

#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/videodev2.h>

#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>

#include "mtk_srccap.h"
#include "internal/mtk_srccap_dprx_sysfs.h"
#include "internal/mtk_srccap_dprx_irq.h"
#include "internal/mtk_srccap_dprx_ctrl.h"

#include "mtk-reserved-memory.h"

#include "hwreg_srccap_dprx.h"
#include "hwreg_srccap_dprx_dsc.h"
#include "hwreg_srccap_dprx_edid.h"
#include "hwreg_srccap_dprx_hdcp.h"
#include "hwreg_srccap_dprx_hpd.h"
#include "hwreg_srccap_dprx_irq.h"
#include "hwreg_srccap_dprx_mac.h"
#include "hwreg_srccap_dprx_mux.h"
#include "hwreg_srccap_dprx_phy.h"
#include "hwreg_srccap_dprx_packetreceiver.h"

int dprx_log_level = DPRX_LOG_ERR;
module_param(dprx_log_level, int, 0644);

static const struct v4l2_subdev_pad_ops dprx_pad_ops = {
	.set_edid = mtk_dprx_pad_set_edid,
	.get_edid = mtk_dprx_pad_get_edid,
};

static const struct v4l2_subdev_ops dprx_sd_ops = {
	.pad = &dprx_pad_ops,
};

static const struct v4l2_subdev_internal_ops dprx_sd_internal_ops = {};

int mtk_dprx_register_subdev(struct v4l2_device *v4l2_dev,
			     struct dprx_device *dprx_dev)
{
	int ret = 0;
	struct v4l2_subdev *sd;

	if ((!v4l2_dev) || (!dprx_dev))
		return -EINVAL;

	sd = &dprx_dev->sd;
	dprx_dev->dev = v4l2_dev->dev;

	/* init & ctrl handler */
	ret = mtk_dprx_ctrl_init_handler(dprx_dev);
	if (ret) {
		dprx_err("failed to init ctrl handler for dprx!\n");
		return ret;
	}

	/* init & register subdev */
	v4l2_subdev_init(sd, &dprx_sd_ops);
	//sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	sd->internal_ops = &dprx_sd_internal_ops;
	ret = snprintf(sd->name, ARRAY_SIZE(sd->name), "%s", DPRX_SUBDEV_NAME);
	if (ret < 0 || ret >= ARRAY_SIZE(sd->name)) {
		dprx_err("Snprintf error!\n");
		return ret;
	}
	v4l2_set_subdevdata(sd, dprx_dev);
	ret = v4l2_device_register_subdev(v4l2_dev, sd);
	if (ret) {
		dprx_err("failed to reg subdev for dprx!\n");
		return ret;
	}

	return ret;
}
EXPORT_SYMBOL(mtk_dprx_register_subdev);

void mtk_dprx_unregister_subdev(struct dprx_device *dprx_dev)
{
	struct v4l2_subdev *sd = &dprx_dev->sd;
	struct v4l2_ctrl_handler *hdl = &dprx_dev->ctrls;

	v4l2_ctrl_handler_free(hdl);
	v4l2_device_unregister_subdev(sd);
}
EXPORT_SYMBOL(mtk_dprx_unregister_subdev);

static int mtk_dprx_parser_dts_clks(struct device_node *np,
				    struct dprx_device *dprx_dev)
{
	int nums = 0;
	struct device *dev = dprx_dev->dev;
	struct clk_onecell_data *clk_data = &dprx_dev->clk_data;
	struct property *prop;
	const char *clk_name;
	int idx = 0;

	nums = of_property_count_strings(np, "clock-names");
	if (nums == -ENODATA) {
		dprx_info("no clks node in dprx dts ret@%d\n", nums);
		return 0;
	}

	if (nums < 0) {
		dprx_err("parser clks node error with ret@%d\n", nums);
		return -EINVAL;
	}

	clk_data->clk_num = nums;

	clk_data->clks = devm_kcalloc(dev, dprx_dev->clk_data.clk_num,
				      sizeof(struct clk *), GFP_KERNEL);
	if (!clk_data->clks)
		return -ENOMEM;

	idx = 0;
	of_property_for_each_string(np, "clock-names", prop, clk_name) {
		clk_data->clks[idx] = of_clk_get_by_name(np, clk_name);
		if (IS_ERR(clk_data->clks[idx])) {
			dprx_err("cannot get %s clk in dts\n", clk_name);
			return PTR_ERR(clk_data->clks[idx]);
		}
		idx++;

		dprx_info("get clk -> @%s\n", clk_name);
	}

	if (idx != clk_data->clk_num) {
		dprx_err("clks not all used in dts\n");
		return -ENODEV;
	}

	dprx_info("clks total counter %d OK\n", clk_data->clk_num);

	return 0;
}

static int mtk_dprx_parser_dts_regs(struct device_node *np,
				    struct dprx_device *dprx_dev)
{
	int ret = 0;
	struct of_mmap_info_data mmap_info = {0};
	struct device_node *mem_np = NULL;
	struct device_node *bank_np = NULL;
	struct device_node *riu_node = NULL;

	/* get memory info from main dts */
	mem_np = of_find_node_by_name(NULL, "memory_info");
	if (mem_np) {
		/* get mem riu ofs from dts */
		ret = of_property_read_u32(mem_np, "cpu_emi0_base",
					   &dprx_dev->cpu_emi0_base);
		if (ret)
			dprx_err("cannot found cpu_emi0 in dts\n");

		of_node_put(mem_np);
	}

	dprx_info("get cpu emi0 base -> 0x%lX\n", dprx_dev->cpu_emi0_base);

	/* get riu base from main dts */
	riu_node = of_find_node_by_path("/riu-base");
	if (!riu_node) {
		dprx_err("cannot found riu_base in dts\n");
		return -ENOENT;
	}

	of_property_read_u32_index(riu_node, "reg", 1, &dprx_dev->riu_phy_addr);
	of_node_put(riu_node);

	dprx_info("get riu base -> 0x%lX\n", dprx_dev->riu_phy_addr);

	/* get dprx mmap info */
	ret = of_mtk_get_reserved_memory_info_by_idx(np, 0, &mmap_info);
	if (ret) {
		dprx_err("cannot found the dprx mmap info in dts\n");
		//return -ENOENT;
	}

	dprx_dev->start_bus_address = mmap_info.start_bus_address;
	dprx_dev->buffer_size = mmap_info.buffer_size;
	dprx_info("dprx mmap start -> 0x%llX, size -> 0x%llX\n",
		mmap_info.start_bus_address, mmap_info.buffer_size);

	dprx_dev->pExchangeDataPA_51 = mmap_info.start_bus_address;
	dprx_dev->u32ExchangeDataSize_51 = mmap_info.buffer_size;

	dprx_info("DP51 ExchangeDataPA is 0x%llX\n",
						(u64)dprx_dev->pExchangeDataPA_51);
	dprx_info("DP51 ExchangeDataSize is 0x%X\n",
						(u32)dprx_dev->u32ExchangeDataSize_51);

	/* get the dprx hw bank reg */
	bank_np = of_find_node_by_name(np, "banks");
	if (!bank_np) {
		dprx_err("cannot found dprx bank node in dts\n");
		return -ENOENT;
	}

	of_property_read_u32(bank_np, "bank_dprx_phy_pm_start",
			     &dprx_dev->bank_dprx_phy_pm_start);

	dprx_info("phy pm strat -> 0x%lX\n", dprx_dev->bank_dprx_phy_pm_start);

	of_property_read_u32(bank_np, "bank_dprx_phy_pm_end",
			     &dprx_dev->bank_dprx_phy_pm_end);

	dprx_info("phy pm end -> 0x%lX\n", dprx_dev->bank_dprx_phy_pm_end);

	of_property_read_u32(bank_np, "bank_dprx_aux_pm_start",
			     &dprx_dev->bank_dprx_aux_pm_start);

	dprx_info("aux pm start -> 0x%lX\n", dprx_dev->bank_dprx_aux_pm_start);

	of_property_read_u32(bank_np, "bank_dprx_aux_pm_end",
			     &dprx_dev->bank_dprx_aux_pm_end);

	dprx_info("aux pm end -> 0x%lX\n", dprx_dev->bank_dprx_aux_pm_end);

	of_property_read_u32(bank_np, "bank_dprx_phy_top_start",
			     &dprx_dev->bank_dprx_phy_top_start);

	dprx_info("phy top start -> 0x%lX\n", dprx_dev->bank_dprx_phy_top_start);

	of_property_read_u32(bank_np, "bank_dprx_phy_top_end",
			     &dprx_dev->bank_dprx_phy_top_end);

	dprx_info("phy top end -> 0x%lX\n", dprx_dev->bank_dprx_phy_top_end);

	of_property_read_u32(bank_np, "bank_dprx_mac_top_start",
			     &dprx_dev->bank_dprx_mac_top_start);

	dprx_info("mac top start -> 0x%lX\n", dprx_dev->bank_dprx_mac_top_start);

	of_property_read_u32(bank_np, "bank_dprx_mac_top_end",
			     &dprx_dev->bank_dprx_mac_top_end);

	dprx_info("mac top end -> 0x%lX\n", dprx_dev->bank_dprx_mac_top_end);

	of_property_read_u32(bank_np, "bank_dprx_dsc_top_start",
			     &dprx_dev->bank_dprx_dsc_top_start);

	dprx_info("dsc top start -> 0x%lX\n", dprx_dev->bank_dprx_dsc_top_start);

	of_property_read_u32(bank_np, "bank_dprx_dsc_top_end",
			     &dprx_dev->bank_dprx_dsc_top_end);

	dprx_info("dsc top end -> 0x%lX\n", dprx_dev->bank_dprx_dsc_top_end);

	//DP51
	of_property_read_u32(bank_np, "bank_dprx_ckgen00_pm_start",
			     &dprx_dev->bank_dprx_ckgen00_pm_start);

	dprx_info("DP51 ckgen00_pm start -> 0x%lX\n", dprx_dev->bank_dprx_ckgen00_pm_start);

	of_property_read_u32(bank_np, "bank_dprx_ckgen00_pm_end",
			     &dprx_dev->bank_dprx_ckgen00_pm_end);

	dprx_info("DP51 ckgen00_pm end -> 0x%lX\n", dprx_dev->bank_dprx_ckgen00_pm_end);

	of_property_read_u32(bank_np, "bank_dprx_pm_sleep_start",
			     &dprx_dev->bank_dprx_pm_sleep_start);

	dprx_info("DP51 pm_sleep_ start -> 0x%lX\n", dprx_dev->bank_dprx_pm_sleep_start);

	of_property_read_u32(bank_np, "bank_dprx_pm_sleep_end",
			     &dprx_dev->bank_dprx_pm_sleep_end);

	dprx_info("DP51 pm_sleep_ end -> 0x%lX\n", dprx_dev->bank_dprx_pm_sleep_end);

	of_property_read_u32(bank_np, "bank_dprx_dp51_start",
			     &dprx_dev->bank_dprx_dp51_start);

	dprx_info("DP51 dp51 start -> 0x%lX\n", dprx_dev->bank_dprx_dp51_start);

	of_property_read_u32(bank_np, "bank_dprx_dp51_end",
			     &dprx_dev->bank_dprx_dp51_end);

	dprx_info("DP51 dp51 end -> 0x%lX\n", dprx_dev->bank_dprx_dp51_end);

	of_property_read_u32(bank_np, "bank_dprx_piu_misc_start",
			     &dprx_dev->bank_dprx_piu_misc_start);

	dprx_info("DP51 piu misc start -> 0x%lX\n", dprx_dev->bank_dprx_piu_misc_start);

	of_property_read_u32(bank_np, "bank_dprx_piu_misc_end",
			     &dprx_dev->bank_dprx_piu_misc_end);

	dprx_info("DP51 piu misc end -> 0x%lX\n", dprx_dev->bank_dprx_piu_misc_end);

	of_property_read_u32(bank_np, "bank_dprx_bdma_start",
			     &dprx_dev->bank_dprx_bdma_start);

	dprx_info("DP51 bdma start -> 0x%lX\n", dprx_dev->bank_dprx_bdma_start);

	of_property_read_u32(bank_np, "bank_dprx_bdma_end",
			     &dprx_dev->bank_dprx_bdma_end);

	dprx_info("DP51 bdma end -> 0x%lX\n", dprx_dev->bank_dprx_bdma_end);

	of_node_put(bank_np);

	return 0;
}

static int mtk_dprx_parser_dts_caps(struct device_node *np,
				    struct dprx_device *dprx_dev)
{
	struct device_node *caps_node = NULL;

	caps_node = of_find_node_by_name(np, "caps");
	if (!caps_node)
		return -ENODEV;

	of_property_read_u32(caps_node, "dprx_count", &dprx_dev->input_count);
	of_property_read_u32(caps_node, "hw_version", &dprx_dev->hw_version);
	of_node_put(caps_node);

	dprx_info("dprx_count -> %ld dprx_hw_version -> %lld\n",
		  dprx_dev->input_count, dprx_dev->hw_version);

	return 0;
}

static int mtk_dprx_parser_dts(struct dprx_device *dprx_dev)
{
	int ret = 0;
	struct device_node *np = NULL;
	struct device *dev = dprx_dev->dev;

	/* get dprx node in srccap dts */
	np = of_find_node_by_name(dev->of_node, "dprx");
	if (!np) {
		dprx_err("cannot found the dprx node in dts\n");
		return -ENODEV;
	}

	/* caps */
	ret = mtk_dprx_parser_dts_caps(np, dprx_dev);
	if (ret) {
		dprx_err("failed to parser caps node in dprx dts.\n");
		ret = -ENODEV;
		goto EXIT;
	}

	if (dprx_dev->input_count == 0) {
		dprx_info("No DPRX IP could be found, Skip dprx driver.\n");
		ret = -ENODEV;
		goto EXIT;
	}

	/* irq */
	ret = mtk_dprx_parser_dts_irq(np, dprx_dev);
	if (ret) {
		dprx_err("failed to parser irq node in dprx dts.\n");
		goto EXIT;
	}

	/* clocks */
	ret = mtk_dprx_parser_dts_clks(np, dprx_dev);
	if (ret) {
		dprx_err("failed to parser clk node in dprx dts.\n");
		goto EXIT;
	}

	/* regs */
	ret = mtk_dprx_parser_dts_regs(np, dprx_dev);
	if (ret) {
		dprx_err("failed to parser bank node in dprx dts.\n");
		goto EXIT;
	}

EXIT:
	of_node_put(np);
	return ret;
}

static int mtk_dprx_prepare_clk(struct dprx_device *dprx_dev)
{
	int ret = 0;
	int idx;
	struct clk_onecell_data *clk_data = &dprx_dev->clk_data;

	if (!clk_data) {
		dprx_err("no clocks to prepare\n");
		return -ENODEV;
	}

	for (idx = 0; idx < clk_data->clk_num; idx++) {
		if (!clk_data->clks[idx]) {
			dprx_err("enable clk @%d null\n", idx);
			return -EINVAL;
		}

		ret = clk_prepare_enable(clk_data->clks[idx]);
		if (ret) {
			dprx_err("enable clk %d failed\n", idx);
			break;
		}

		dprx_info("enable clk %d success.\n", idx);
	}

	return ret;
}

static int mtk_dprx_task(void *ptr)
{
	struct dprx_thread *thread = (struct dprx_thread *)ptr;
	struct mutex *mutex = &thread->mutex;
	void *priv = thread->priv;
	u32 times = thread->timers;
	int type = thread->type;

	while (!kthread_should_stop()) {
		mutex_lock(mutex);

		if (!thread->func) {
			mutex_unlock(mutex);
			break;
		}

		thread->func(priv);

		mutex_unlock(mutex);

		msleep(times);
	}

	dprx_info("%s 0x%x exits\n", __func__, type);

	return 0;
}

static int mtk_dprx_attach_hwreg(struct dprx_device *dprx_dev, bool enable)
{
	return KDrv_SRCCAP_DPRx_attach(dprx_dev->hw_version, enable) ? 0 : 1;
}

static int mtk_dprx_init_hw(struct dprx_device *dprx_dev)
{
	stDPRx_Bank dprx_bank = {0};

	dprx_bank.cpu_emi0_base = dprx_dev->cpu_emi0_base;
	dprx_bank.riu_phy_addr = dprx_dev->riu_phy_addr;
	dprx_bank.start_bus_address = dprx_dev->start_bus_address;
	dprx_bank.buffer_size = dprx_dev->buffer_size;
	dprx_bank.bank_phy_pm_start = dprx_dev->bank_dprx_phy_pm_start;
	dprx_bank.bank_phy_pm_end = dprx_dev->bank_dprx_phy_pm_end;
	dprx_bank.bank_aux_pm_start = dprx_dev->bank_dprx_aux_pm_start;
	dprx_bank.bank_aux_pm_end = dprx_dev->bank_dprx_aux_pm_end;
	dprx_bank.bank_phy_top_start = dprx_dev->bank_dprx_phy_top_start;
	dprx_bank.bank_phy_top_end = dprx_dev->bank_dprx_phy_top_end;
	dprx_bank.bank_mac_top_start = dprx_dev->bank_dprx_mac_top_start;
	dprx_bank.bank_mac_top_end = dprx_dev->bank_dprx_mac_top_end;
	dprx_bank.bank_dsc_top_start = dprx_dev->bank_dprx_dsc_top_start;
	dprx_bank.bank_dsc_top_end = dprx_dev->bank_dprx_dsc_top_end;
	dprx_bank.bank_ckgen00_pm_start = dprx_dev->bank_dprx_ckgen00_pm_start; //DP51
	dprx_bank.bank_ckgen00_pm_end = dprx_dev->bank_dprx_ckgen00_pm_end;
	dprx_bank.bank_pm_sleep_start = dprx_dev->bank_dprx_pm_sleep_start;
	dprx_bank.bank_pm_sleep_end = dprx_dev->bank_dprx_pm_sleep_end;
	dprx_bank.bank_dp51_start = dprx_dev->bank_dprx_dp51_start;
	dprx_bank.bank_dp51_end = dprx_dev->bank_dprx_dp51_end;
	dprx_bank.bank_piu_misc_start = dprx_dev->bank_dprx_piu_misc_start;
	dprx_bank.bank_mac_top_end = dprx_dev->bank_dprx_piu_misc_end;
	dprx_bank.bank_bdma_start = dprx_dev->bank_dprx_bdma_start;
	dprx_bank.bank_bdma_end = dprx_dev->bank_dprx_bdma_end; //DP51

	dprx_bank.pExchangeDataPA_51 = dprx_dev->pExchangeDataPA_51;
	dprx_bank.u32ExchangeDataSize_51 = dprx_dev->u32ExchangeDataSize_51;

	KDrv_SRCCAP_DPRx_ioremap(&dprx_bank);
	KDrv_SRCCAP_DPRx_initial();

	return 0;
}

static int mtk_dprx_init_sw(struct dprx_device *dprx_dev)
{
	int ret;
	struct device *dev = dprx_dev->dev;
	struct media_entity *entity = &dprx_dev->sd.entity;
	char *name = NULL;
	struct dprx_thread *thread;
	struct timer_list *timer;

	if (!dprx_dev->input_count) {
		dprx_err("no dprx input\n");
		return -ENODEV;
	}

	/* init the pads for dprx port
	 * We need to use the pad function for edid.
	 */
	dprx_dev->nr_of_pads = dprx_dev->input_count;
	dprx_dev->pads = devm_kcalloc(dev,
				      dprx_dev->nr_of_pads,
				      sizeof(struct media_pad),
				      GFP_KERNEL);
	if (!dprx_dev->pads)
		return -ENOMEM;

	ret = media_entity_pads_init(entity,
				     dprx_dev->nr_of_pads,
				     dprx_dev->pads);
	if (ret < 0) {
		dprx_err("cannot init the media entity pads\n");
		return -ENODEV;
	}

	/* init the sw info */
	dprx_dev->info = devm_kcalloc(dev,
				      dprx_dev->input_count,
				      sizeof(struct dprx_info),
				      GFP_KERNEL);
	if (!dprx_dev->info) {
		media_entity_cleanup(&dprx_dev->sd.entity);
		return -ENOMEM;
	}

	/* init the list head for task */
	mutex_init(&dprx_dev->thread_lock);
	INIT_LIST_HEAD(&dprx_dev->thread_head);

	/* create the first polling task for hwreg */
	mutex_lock(&dprx_dev->thread_lock);

	thread = kzalloc(sizeof(struct dprx_thread), GFP_KERNEL);
	if (!thread)
		return -ENOMEM;

	mutex_init(&thread->mutex);

	name = "polling";
	thread->type = DPRX_EVENT_POLLING_HW_STATUS;
	thread->priv = dprx_dev;
	thread->func = mtk_dprx_polling_hw_event;
	thread->timers = DPRX_TASK_SLEEP_TIME;

	list_add_tail(&thread->node, &dprx_dev->thread_head);

	thread->task = kthread_create(mtk_dprx_task, thread, "dprx-%s", name);
	if (IS_ERR(thread->task)) {
		dprx_err("failed to create task for %s\n", name);
		ret = PTR_ERR(thread->task);
		kfree(thread);
		return -EINVAL;
	}

	get_task_struct(thread->task);
	wake_up_process(thread->task);

	mutex_unlock(&dprx_dev->thread_lock);

	dprx_info("dprx polling thread created\n");

	/* create the timer for hwreg */
	timer = &dprx_dev->timer;
	timer_setup(timer, mtk_dprx_timer_handler, 0);
	mod_timer(timer, jiffies + msecs_to_jiffies(DPRX_TIMER_MS)); // 12ms

	return 0;
}

static int mtk_dprx_deinit_sw(struct dprx_device *dprx_dev)
{
	struct dprx_thread *thread;
	struct dprx_thread *temp;
	struct list_head *head = &dprx_dev->thread_head;
	struct mutex *mutex = &dprx_dev->thread_lock;

	/* clean up media entity */
	media_entity_cleanup(&dprx_dev->sd.entity);

	/* clear all task */
	mutex_lock(mutex);
	list_for_each_entry_safe(thread, temp, head, node) {
		if (thread) {
			mutex_lock(&thread->mutex);
			kthread_stop(thread->task);
			list_del(&thread->node);
			put_task_struct(thread->task);
			mutex_unlock(&thread->mutex);
			kfree(thread);
			break;
		}
	}
	mutex_unlock(mutex);

	dprx_info("dprx all thread deleted\n");

	/* clear timer */
	del_timer(&dprx_dev->timer);

	dprx_info("dprx all timer deleted\n");

	return 0;
}

int mtk_dprx_init(struct dprx_device *dprx_dev)
{
	int ret = 0;

	/* parser dts, MUST done at first */
	ret = mtk_dprx_parser_dts(dprx_dev);

	/* its ok if no dprx device node could be found. */
	if (ret == -ENODEV)
		return 0;

	if (ret) {
		dprx_err("parser dts fail\n");
		return ret;
	}

#ifndef DPRX_NOT_READY_FOR_IRQ_AND_CLK
	// FIXME:
	/* register irq */
	ret = mtk_dprx_irq_register(dprx_dev);
	if (ret) {
		dprx_err("register irq fail\n");
		return ret;
	}

	/* prepare clk */
	ret = mtk_dprx_prepare_clk(dprx_dev);
	if (ret) {
		dprx_err("prepare clk fail\n");
		return ret;
	}
#endif

	/* init sysfs */
	ret = mtk_dprx_sysfs_init(dprx_dev);
	if (ret) {
		dprx_err("init sysfs fail\n");
		return ret;
	}

	/* attach hwreg */
	ret = mtk_dprx_attach_hwreg(dprx_dev, true);
	if (ret) {
		dprx_err("attach hwreg fail\n");
		return ret;
	}

	/* init hw */
	ret = mtk_dprx_init_hw(dprx_dev);
	if (ret) {
		dprx_err("init hw fail\n");
		return ret;
	}

	/* init srccap/dprx info */
	ret = mtk_dprx_init_sw(dprx_dev);
	if (ret) {
		dprx_err("init sw fail\n");
		return ret;
	}

	return ret;
}
EXPORT_SYMBOL(mtk_dprx_init);

int mtk_dprx_deinit(struct dprx_device *dprx_dev)
{
	int ret;

	if (dprx_dev->input_count == 0) {
		dprx_info("No DPRX IP could be found, Skip deinit driver.\n");
		return 0;
	}

	/* remove sysfs */
	mtk_dprx_sysfs_deinit(dprx_dev);

	/* TODO: hw de-init */

	/* sw de-init */
	ret = mtk_dprx_deinit_sw(dprx_dev);
	if (ret) {
		dprx_err("de-init sw fail\n");
		return ret;
	}

	/* de-attach hwreg */
	ret = mtk_dprx_attach_hwreg(dprx_dev, false);
	if (ret) {
		dprx_err("de-attach hwreg fail\n");
		return ret;
	}

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_deinit);

int mtk_dprx_subscribe_event(struct dprx_device *dprx_dev,
			     const struct v4l2_event_subscription *event)
{
	int ret = 0;
	char *name = NULL;
	struct dprx_thread *thread;
	struct dprx_thread *temp;
	struct list_head *head = &dprx_dev->thread_head;
	struct mutex *mutex = &dprx_dev->thread_lock;
	struct dprx_info *info = dprx_dev->info;

	if (dprx_dev->input_count == 0) {
		dprx_info("No DPRX IP could be found, Skip subscribed.\n");
		return 0;
	}

	/* others id does not for dprx event, but we can not return -1 */
	if (event->id <= DPRX_EVENT_START || event->id >= DPRX_EVENT_END)
		return 0;

	if (!info)
		return 0;

	mutex_lock(mutex);

	/* found the same event type */
	list_for_each_entry_safe(thread, temp, head, node) {
		if (thread && thread->type == event->id)
			goto out;
	}

	thread = kzalloc(sizeof(struct dprx_thread), GFP_KERNEL);
	if (!thread) {
		ret = -ENOMEM;
		goto out;
	}

	/* initialize the task */
	mutex_init(&thread->mutex);

	thread->type = event->id;
	thread->priv = dprx_dev;

	switch (event->id) {
	case DPRX_EVENT_SIGNAL_CHG:
		name = "signal";
		thread->func = mtk_dprx_signal_change_event;
		thread->timers = DPRX_TASK_SLEEP_TIME;
		break;
	case DPRX_EVENT_CABLE_DETECT:
		name = "detect";
		thread->func = mtk_dprx_cable_detect_event;
		thread->timers = DPRX_TASK_SLEEP_TIME;
		info->cable_detect = 0;
		break;
	case DPRX_EVENT_HDCP_TYPE_CHG:
		name = "hdcp";
		thread->func = mtk_dprx_hdcp_type_change_event;
		thread->timers = DPRX_TASK_SLEEP_TIME;
		break;
	case DPRX_EVENT_HDCP1X_STATUS_CHG:
		name = "hdcp1x";
		thread->func = mtk_dprx_hdcp1x_status_change_event;
		thread->timers = DPRX_NOT_READY_TASK_SLEEP;
		break;
	case DPRX_EVENT_HDCP2X_STATUS_CHG:
		name = "hdcp2x";
		thread->func = mtk_dprx_hdcp2x_status_change_event;
		thread->timers = DPRX_NOT_READY_TASK_SLEEP;
		break;
	default:
		break;
	}

	list_add_tail(&thread->node, head);

	thread->task = kthread_create(mtk_dprx_task, thread, "dprx-%s", name);
	if (IS_ERR(thread->task)) {
		dprx_err("failed to create task for %s\n", name);
		ret = PTR_ERR(thread->task);
		kfree(thread);
		goto out;
	}

	get_task_struct(thread->task);
	wake_up_process(thread->task);

	dprx_info("thread %s %d created\n", name, event->id - DPRX_EVENT_START);

out:
	mutex_unlock(mutex);
	return ret;
}
EXPORT_SYMBOL(mtk_dprx_subscribe_event);

int mtk_dprx_unsubscribe_event(struct dprx_device *dprx_dev,
			       const struct v4l2_event_subscription *event)
{
	int ret = 0;
	struct dprx_thread *thread;
	struct dprx_thread *temp;
	struct list_head *head = &dprx_dev->thread_head;
	struct mutex *mutex = &dprx_dev->thread_lock;

	if (dprx_dev->input_count == 0) {
		dprx_info("No DPRX IP could be found, Skip subscribed.\n");
		return 0;
	}

	/* others id does not for dprx event, but we can not return -1 */
	if (event->id <= DPRX_EVENT_START || event->id >= DPRX_EVENT_END)
		return 0;

	mutex_lock(mutex);

	if (list_empty(head)) {
		ret = -ENODEV;
		goto out;
	}

	list_for_each_entry_safe(thread, temp, head, node) {
		if (thread && thread->type == event->id) {
			mutex_lock(&thread->mutex);
			kthread_stop(thread->task);
			list_del(&thread->node);
			put_task_struct(thread->task);
			mutex_unlock(&thread->mutex);
			kfree(thread);
			break;
		}
	}

	dprx_info("thread %d delete\n", event->id - DPRX_EVENT_START);

out:
	mutex_unlock(mutex);
	return ret;
}
EXPORT_SYMBOL(mtk_dprx_unsubscribe_event);

/*
 * v4l2_srccap_input_source -> user include port (90-93), ui port
 * srccap_input_port -> kernel port (150-153), drv port
 * E_MUX_INPUTPORT -> utp port (90-93)
 * DPRx_ID -> dprx hw ip port (0-3)
 *
 * we just need to care about the v4l2_srccap_input_source & DPRx_ID
 */
bool mtk_dprx_is_valid_input_port(struct dprx_device *dev,
				  enum v4l2_srccap_input_source input_port)
{
	return dev ? (input_port >= V4L2_SRCCAP_INPUT_SOURCE_DPRX &&
		input_port < V4L2_SRCCAP_INPUT_SOURCE_DPRX + dev->input_count) :
		false;
}
EXPORT_SYMBOL(mtk_dprx_is_valid_input_port);

void mtk_dprx_set_test_mode(struct dprx_device *dev, bool test_mode)
{
	if (dev)
		dev->test_mode = test_mode;

	KDrv_SRCCAP_DPRx_set_sim_mode(test_mode);

	dprx_info("%s: test_mode = %d\n", __func__, test_mode);
}
EXPORT_SYMBOL(mtk_dprx_set_test_mode);

bool mtk_dprx_get_test_mode(struct dprx_device *dev)
{
	bool test_mode = false;

	test_mode = KDrv_SRCCAP_DPRx_get_sim_mode();

	if (dev && test_mode != dev->test_mode)
		dprx_err("%s not mapping with hwreg", __func__, test_mode);

	dprx_info("%s: test_mode = %d\n", __func__, test_mode);

	return test_mode;
}
EXPORT_SYMBOL(mtk_dprx_get_test_mode);

void mtk_dprx_set_ktest_mode(struct dprx_device *dev, bool test_mode)
{
	if (dev)
		dev->ktest_mode = test_mode;

	dprx_info("%s: ktest_mode = %d\n", __func__, test_mode);
}
EXPORT_SYMBOL(mtk_dprx_set_ktest_mode);

int mtk_dprx_input_to_dprx_id(struct dprx_device *dev,
			      enum v4l2_srccap_input_source input_port)
{
	return mtk_dprx_is_valid_input_port(dev, input_port) ?
		(input_port - V4L2_SRCCAP_INPUT_SOURCE_DPRX) : DPRx_ID_MAX;
}
EXPORT_SYMBOL(mtk_dprx_input_to_dprx_id);

enum v4l2_srccap_input_source mtk_dprx_id_to_input_port(unsigned int port_id)
{
	return (port_id < DPRx_ID_MAX) ?
		port_id + V4L2_SRCCAP_INPUT_SOURCE_DPRX :
		V4L2_SRCCAP_INPUT_SOURCE_NUM;
}
EXPORT_SYMBOL(mtk_dprx_id_to_input_port);
