// 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 "mtk_srccap.h"
#include "hwreg_srccap_dprx.h"
#include "hwreg_srccap_dprx_mac.h"
#include "hwreg_srccap_dprx_hpd.h"

void mtk_dprx_cable_detect_event(void *ptr)
{
	u8 index;
	bool cable_detect;
	enum v4l2_srccap_input_source port;
	struct dprx_device *dev = (struct dprx_device *)ptr;
	struct mtk_srccap_dev *srccap_dev =
		container_of(dev, struct mtk_srccap_dev, dprx_dev);
	struct v4l2_event event = {0};
	struct dprx_info *info = dev->info;

	if (!info)
		return;

	for (port = V4L2_SRCCAP_INPUT_SOURCE_DPRX;
	     port < V4L2_SRCCAP_INPUT_SOURCE_DPRX + dev->input_count;
	     port++) {
		index = mtk_dprx_input_to_dprx_id(dev, port);
		cable_detect = mtk_dprx_get_cable_connected(dev, port);
		if (cable_detect != info[index].cable_detect) {
			info[index].cable_detect = cable_detect;

			event.type = V4L2_EVENT_CTRL;
			event.id = DPRX_EVENT_CABLE_DETECT;
			event.u.ctrl.changes = cable_detect;
			event.u.ctrl.value = port;
			v4l2_event_queue(srccap_dev->vdev, &event);
		}
	}
}

void mtk_dprx_signal_change_event(void *ptr)
{
	u8 index;
	enum dprx_signal_status signal_status;
	enum v4l2_srccap_input_source port;
	struct dprx_device *dev = (struct dprx_device *)ptr;
	struct mtk_srccap_dev *srccap_dev =
		container_of(dev, struct mtk_srccap_dev, dprx_dev);
	struct v4l2_event event = {0};
	struct dprx_info *info = dev->info;

	if (!info)
		return;

	for (port = V4L2_SRCCAP_INPUT_SOURCE_DPRX;
	     port < V4L2_SRCCAP_INPUT_SOURCE_DPRX + dev->input_count;
	     port++) {
		index = mtk_dprx_input_to_dprx_id(dev, port);
		signal_status = mtk_dprx_get_signal_status(dev, port);
		if (signal_status != info[index].signal_status) {
			info[index].signal_status = signal_status;

			event.type = V4L2_EVENT_CTRL;
			event.id = DPRX_EVENT_SIGNAL_CHG;
			event.u.ctrl.changes = signal_status;
			event.u.ctrl.value = port;
			v4l2_event_queue(srccap_dev->vdev, &event);
		}
	}
}

void mtk_dprx_polling_hw_event(void *ptr)
{
	KDrv_SRCCAP_DPRx_handler();
	//KDrv_SRCCAP_DPRx_timer_timeup_handler();
}

void mtk_dprx_timer_handler(struct timer_list *t)
{
	struct dprx_device *dev = from_timer(dev, t, timer);

	//KDrv_SRCCAP_DPRx_timer_irq_handler();

	mod_timer(t, jiffies + msecs_to_jiffies(DPRX_TIMER_MS));
}

int mtk_dprx_set_power_state(struct dprx_device *dev, int power)
{
	struct mtk_srccap_dev *srccap_dev =
		container_of(dev, struct mtk_srccap_dev, dprx_dev);

	if (srccap_dev->dev_id != 0)
		return 0;

	return KDrv_SRCCAP_DPRx_str_event_proc((EN_POWER_MODE)power);
}
EXPORT_SYMBOL(mtk_dprx_set_power_state);

bool mtk_dprx_check_if_freesync(struct dprx_device *dev,
				enum v4l2_srccap_input_source port)
{
	int ret;
	u8 idx;
	u8 spd_data[DPRX_PKT_HEADER_SIZE + DPRX_PKT_DATA_LENGTH];

	ret = mtk_dprx_pkt_get_spd(dev,
				   port,
				   DPRX_PKT_HEADER_SIZE + DPRX_PKT_DATA_LENGTH,
				   spd_data);
	if (ret)
		return false;

	/* 4 bytes header + 32 bytes data, index starts from DB2 */
	idx = DPRX_PKT_HEADER_SIZE + 2;

	/* DB2 = checksum */
	idx++;

	if (spd_data[idx] == 0x1A &&
	    spd_data[idx + 1] == 0x00 &&
	    spd_data[idx + 2] == 0x00 &&
	    spd_data[idx + 5] & BIT(0) &&
	    spd_data[idx + 5] & BIT(1))
		return true;

	return false;
}
EXPORT_SYMBOL(mtk_dprx_check_if_freesync);

enum dprx_signal_status mtk_dprx_get_signal_status(struct dprx_device *dev,
					enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;
	DPRX_SIGNAL_STATE hw_signal_status;
	enum dprx_signal_status status;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return DPRX_SIGNAL_NOT_EXISTED;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	hw_signal_status = KDrv_SRCCAP_DPRx_mac_get_signal_status(dprx_id);

	switch (hw_signal_status) {
	case DPRX_TIMING_UNSTABLE:
		status = DPRX_SIGNAL_UNSTABLE;
		break;
	case DPRX_TIMING_STABLE:
		status = DPRX_SIGNAL_STABLE;
		break;
	case DPRX_NO_SIGNAL:
	default:
		status = DPRX_SIGNAL_NOT_EXISTED;
		break;
	}

	return status;
}
EXPORT_SYMBOL(mtk_dprx_get_signal_status);

u32 mtk_dprx_get_pixel_clock_10khz(struct dprx_device *dev,
				   enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;
	u32 pixel_clock_10khz;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	pixel_clock_10khz = KDrv_SRCCAP_DPRx_mac_get_pixel_clock_10khz(dprx_id);

	return pixel_clock_10khz;
}
EXPORT_SYMBOL(mtk_dprx_get_pixel_clock_10khz);

bool mtk_dprx_get_event_change(struct dprx_device *dev,
			       enum v4l2_srccap_input_source port)
{
	/* TODO: check something event changed
	 * 1) color format
	 * 2) vidoe mute
	 * 3) colorimetry
	 * 4) ...
	 */

	return false;
}
EXPORT_SYMBOL(mtk_dprx_get_event_change);

bool mtk_dprx_get_h_polarity(struct dprx_device *dev,
			     enum v4l2_srccap_input_source port)
{
	u16 timing[DPRX_TIMING_DATA_LENGTH] = {0};

	mtk_dprx_get_timing_data(dev, port, timing);

	/* 0 = active high pulse */
	return timing[DPRX_TIMING_DATA_H_POLARITY_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_h_polarity);

bool mtk_dprx_get_v_polarity(struct dprx_device *dev,
			     enum v4l2_srccap_input_source port)
{
	u16 timing[DPRX_TIMING_DATA_LENGTH] = {0};

	mtk_dprx_get_timing_data(dev, port, timing);

	/* 0 = active high pulse */
	return timing[DPRX_TIMING_DATA_V_POLARITY_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_v_polarity);

enum dprx_version mtk_dprx_get_version(struct dprx_device *dev,
				       enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return (enum dprx_version)KDrv_SRCCAP_DPRx_mac_get_dp_version(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_version);

int mtk_dprx_set_version(struct dprx_device *dev,
			 enum v4l2_srccap_input_source port,
			 enum dprx_version version)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	KDrv_SRCCAP_DPRx_mac_set_dp_version(dprx_id, version);

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_set_version);

bool mtk_dprx_get_cable_connected(struct dprx_device *dev,
				  enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_aux_n_level(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_cable_connected);

int mtk_dprx_control_hpd(struct dprx_device *dev,
			 enum v4l2_srccap_input_source port,
			 bool is_low)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	KDrv_SRCCAP_DPRx_hpd_control(dprx_id, is_low ? 0 : 1);

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_control_hpd);

bool mtk_dprx_get_hpd_status(struct dprx_device *dev,
			     enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_hpd_get_hpd_states(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_hpd_status);

int mtk_dprx_get_hwcaps(struct dprx_device *dev,
			enum v4l2_srccap_input_source port,
			struct v4l2_dprx_hwcaps *hw_caps)
{
	DPRx_ID dprx_id;
	DPRx_CAPABILITY_INFO caps = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	if (!hw_caps)
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	KDrv_SRCCAP_DPRx_get_support_capability(dev->hw_version, &caps);

	hw_caps->size_edid = caps.usEDIDSize_Support;
	hw_caps->support_dsc = caps.bDSC_Support ? 1 : 0;
	hw_caps->support_mst = caps.bMST_Support ? 1 : 0;
	hw_caps->offset_vsc_ext_to_dram = caps.usDRAM_Offset_Dvi2Miu;
	hw_caps->support_adaptive_sync = caps.bAdaptiveSync_Support ? 1 : 0;

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_get_hwcaps);

int mtk_dprx_get_lane_count(struct dprx_device *dev,
			    enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_dp_lane_cnt(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_lane_count);

int mtk_dprx_get_link_rate(struct dprx_device *dev,
			   enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_dpcd_link_rate(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_link_rate);

int mtk_dprx_get_timing_data(struct dprx_device *dev,
			     enum v4l2_srccap_input_source port,
			     u16 *timing_data)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	if (!timing_data)
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	// u16 timing_data[13]
	KDrv_SRCCAP_DPRx_mac_get_timing_data(dprx_id, timing_data);

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_get_timing_data);

enum dprx_colorimetry mtk_dprx_get_colorimetry(struct dprx_device *dev,
					enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;
	u8 hw_colorimetry;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	hw_colorimetry = KDrv_SRCCAP_DPRx_mac_get_colorimetry(dprx_id);

	return (enum dprx_colorimetry)hw_colorimetry;
}
EXPORT_SYMBOL(mtk_dprx_get_colorimetry);

enum dprx_color_depth mtk_dprx_get_color_depth(struct dprx_device *dev,
					enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;
	u8 hw_color_depth;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	hw_color_depth = KDrv_SRCCAP_DPRx_mac_get_color_depth_info(dprx_id);

	return (enum dprx_color_depth)hw_color_depth;
}
EXPORT_SYMBOL(mtk_dprx_get_color_depth);

enum dprx_color_space mtk_dprx_get_color_space(struct dprx_device *dev,
					enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;
	u8 hw_color_space;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return DPRX_COLOR_FORMAT_RESERVED;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	hw_color_space = KDrv_SRCCAP_DPRx_mac_get_color_depth_info(dprx_id);

	return (enum dprx_color_space)hw_color_space;
}
EXPORT_SYMBOL(mtk_dprx_get_color_space);

u32 mtk_dprx_get_pkt_status(struct dprx_device *dev,
			    enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_packet_status(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_pkt_status);

u8 mtk_dprx_get_vbid(struct dprx_device *dev,
		      enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_vbid(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_vbid);

u32 mtk_dprx_get_framerate(struct dprx_device *dev,
			   enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_frame_rate(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_framerate);

int mtk_dprx_get_msa(struct dprx_device *dev,
		     enum v4l2_srccap_input_source port,
		     struct v4l2_dprx_msa *msa)
{
	DPRx_ID dprx_id;
	DPRX_MSA_DATA_INFO hw_msa = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	if (!msa)
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	KDrv_SRCCAP_DPRx_mac_get_msa_data_info(dprx_id, &hw_msa);

	msa->mvid = hw_msa.ulMvid;
	msa->nvid = hw_msa.ulNvid;
	msa->timing_info.h_total = hw_msa.udHtt;
	msa->timing_info.v_total = hw_msa.udVtt;
	msa->timing_info.h_width = hw_msa.udHWidth;
	msa->timing_info.v_height = hw_msa.udVHeight;
	msa->timing_info.h_start = hw_msa.udHStart;
	msa->timing_info.v_start = hw_msa.udVStart;
	msa->timing_info.hsync_polarity = hw_msa.bHsyncPolarity; // !!!
	msa->timing_info.hsync_width = hw_msa.udHsyncWidth; // !!!
	msa->timing_info.vsync_polarity = hw_msa.bVsyncPolarity; // !!!
	msa->timing_info.vsync_width = hw_msa.udVsyncWidth; // !!!

	msa->reserved[0] = hw_msa.ucMISC0;
	msa->reserved[1] = hw_msa.ucMISC1;

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_get_msa);

u8 mtk_dprx_get_color_range(struct dprx_device *dev,
			    enum v4l2_srccap_input_source port)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	return KDrv_SRCCAP_DPRx_mac_get_color_range(dprx_id);
}
EXPORT_SYMBOL(mtk_dprx_get_color_range);

int mtk_dprx_get_hw_detect_timing_info(struct dprx_device *dev,
				       enum v4l2_srccap_input_source port,
				       u16 *timing_info)
{
	DPRx_ID dprx_id;

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	if (!timing_info)
		return -1;

	dprx_id = mtk_dprx_input_to_dprx_id(dev, port);

	// timing_info[4]
	KDrv_SRCCAP_DPRx_mac_get_timing_data_decoder_det(dprx_id, timing_info);

	return 0;
}
EXPORT_SYMBOL(mtk_dprx_get_hw_detect_timing_info);

u16 mtk_dprx_get_hw_detect_htt(struct dprx_device *dev,
			       enum v4l2_srccap_input_source port)
{
	u16 hw_timing_info[DPRX_HW_DETECT_TIMING_LENGTH] = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	mtk_dprx_get_hw_detect_timing_info(dev, port, hw_timing_info);

	return hw_timing_info[DPRX_HW_DETECT_TIMING_HTT_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_hw_detect_htt);

u16 mtk_dprx_get_hw_detect_vtt(struct dprx_device *dev,
			       enum v4l2_srccap_input_source port)
{
	u16 hw_timing_info[DPRX_HW_DETECT_TIMING_LENGTH] = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	mtk_dprx_get_hw_detect_timing_info(dev, port, hw_timing_info);

	return hw_timing_info[DPRX_HW_DETECT_TIMING_VTT_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_hw_detect_vtt);

u16 mtk_dprx_get_hw_detect_h_width(struct dprx_device *dev,
				   enum v4l2_srccap_input_source port)
{
	u16 hw_timing_info[DPRX_HW_DETECT_TIMING_LENGTH] = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	mtk_dprx_get_hw_detect_timing_info(dev, port, hw_timing_info);

	return hw_timing_info[DPRX_HW_DETECT_TIMING_HWIDTH_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_hw_detect_h_width);

u16 mtk_dprx_get_hw_detect_v_height(struct dprx_device *dev,
				    enum v4l2_srccap_input_source port)
{
	u16 hw_timing_info[DPRX_HW_DETECT_TIMING_LENGTH] = {0};

	if (!mtk_dprx_is_valid_input_port(dev, port))
		return -1;

	mtk_dprx_get_hw_detect_timing_info(dev, port, hw_timing_info);

	return hw_timing_info[DPRX_HW_DETECT_TIMING_VHEIGHT_INDEX];
}
EXPORT_SYMBOL(mtk_dprx_get_hw_detect_v_height);
