// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */
#ifdef CONFIG_AMLOGIC_HDMITX
#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/of_gpio.h>
#include <linux/amlogic/media/frame_provider/tvin/tvin.h>
/* Local include */
#include "hdmi_rx_repeater.h"
#include "hdmi_rx_drv.h"
#include "hdmi_rx_hw.h"
#include "hdmi_rx_wrapper.h"
#include "hdmi_rx_edid.h"
#include "hdmi_rx_hw_t7.h"
/*edid original data from device*/
static unsigned char receive_edid[MAX_RECEIVE_EDID];
int receive_edid_len = MAX_RECEIVE_EDID;
MODULE_PARM_DESC(receive_edid, "\n receive_edid\n");
module_param_array(receive_edid, byte, &receive_edid_len, 0664);
int tx_hpd_event;
int edid_len;
MODULE_PARM_DESC(edid_len, "\n edid_len\n");
module_param(edid_len, int, 0664);
bool new_edid;
/*original bksv from device*/
//unsigned char receive_hdcp[MAX_KSV_LIST_SIZE];
//int hdcp_array_len = MAX_KSV_LIST_SIZE;
//MODULE_PARM_DESC(receive_hdcp, "\n receive_hdcp\n");
//module_param_array(receive_hdcp, byte, &hdcp_array_len, 0664);
int hdcp_len;
int hdcp_repeat_depth;
bool new_hdcp;
bool start_auth_14;
MODULE_PARM_DESC(start_auth_14, "\n start_auth_14\n");
module_param(start_auth_14, bool, 0664);

bool repeat_plug;
MODULE_PARM_DESC(repeat_plug, "\n repeat_plug\n");
module_param(repeat_plug, bool, 0664);

int up_phy_addr;/*d c b a 4bit*/
MODULE_PARM_DESC(up_phy_addr, "\n up_phy_addr\n");
module_param(up_phy_addr, int, 0664);
int hdcp22_firm_switch_timeout;

u8 ksvlist[10] = {
	0xc1, 0x29, 0x9b, 0xb6, 0x66,
	0x77, 0x34, 0x43, 0x54, 0x6b,
	//0x88, 0xcb, 0xbc, 0xab, 0x95
};

//unsigned char *rx_get_dw_hdcp_addr(void)
//{
	//return receive_hdcp;
///}

void rx_start_repeater_auth(void)
{
	rx[rx_info.main_port].hdcp.state = REPEATER_STATE_START;
	start_auth_14 = 1;
	rx[rx_info.main_port].hdcp.delay = 0;
	hdcp_len = 0;
	hdcp_repeat_depth = 0;
	rx[rx_info.main_port].hdcp.dev_exceed = 0;
	rx[rx_info.main_port].hdcp.cascade_exceed = 0;
	rx[rx_info.main_port].hdcp.depth = 0;
	rx[rx_info.main_port].hdcp.count = 0;
	//memset(&receive_hdcp, 0, sizeof(receive_hdcp));
}

void rx_update_rpt_sts(struct hdcp_topo_s *topo)
{
	/* only update topo for corresponding hdcp version */
	if (rx[rx_info.main_port].hdcp.rpt_reauth_event == HDCP_VER_22)
		rpt_update_hdcp2x(topo, rx_info.main_port);
	else if (rx[rx_info.main_port].hdcp.rpt_reauth_event == HDCP_VER_14)
		rpt_update_hdcp1x(topo, rx_info.main_port);
	rx_pr("update ksv, rpt_reauth_event: %d\n", rx[rx_info.main_port].hdcp.rpt_reauth_event);
}

/* save downstream topo if no hdcp converter */
static struct hdcp_topo_s pre_topo;
/* save downstream topo after hdcp converter */
static struct hdcp_topo_s hdcp_convert_topo;

/* for HDMIRX/CEC notify */
#define HDMITX_PLUG				1
#define HDMITX_UNPLUG			2
#define HDMITX_PHY_ADDR_VALID	3
#define HDMITX_KSVLIST			4
#define HDMITX_HDR_PRIORITY		5
int rx_hdmi_tx_notify_handler(struct notifier_block *nb,
				     unsigned long value, void *p)
{
	int ret = NOTIFY_DONE;
	int phy_addr = 0;
	struct hdcp_topo_s *topo;
	u8 i, j;
	u8 temp_bype;

	switch (value) {
	case HDMITX_PLUG:
		if (log_level & EDID_LOG)
			rx_pr("%s, HDMITX_PLUG\n", __func__);
		if (p) {
			rx_pr("update EDID from HDMITX\n");
			rx_update_tx_edid_with_audio_block(p, rx_audio_block);
		}
		rx_irq_en(false, rx_info.main_port);
		rx_set_cur_hpd(0, 4, rx_info.main_port);
		if (!rx_info.open_fg)
			port_hpd_rst_flag = 7;
		//if (hdmirx_repeat_support())
		rx[rx_info.main_port].hdcp.repeat = true;
		fsm_restart(rx_info.main_port);
		ret = NOTIFY_OK;
		break;
	case HDMITX_UNPLUG:
		if (log_level & EDID_LOG)
			rx_pr("%s, HDMITX_UNPLUG, recover primary EDID\n",
			      __func__);
		rx[rx_info.main_port].hdcp.repeat = false;
		if (rpt_only_mode == 1) {
			rx_force_hpd_rxsense_cfg(0);
		} else {
			rx_update_tx_edid_with_audio_block(NULL, NULL);
			hdmi_rx_top_edid_update();
			hdcp_init_t7(rx_info.main_port);
		}
		//rx_irq_en(false, rx_info.main_port);
		//rx_set_cur_hpd(0, 4);
		//fsm_restart();
		ret = NOTIFY_OK;
		break;
	case HDMITX_HDR_PRIORITY:
		tx_hdr_priority = *((u32 *)p);
		if (log_level & EDID_LOG)
			rx_pr("tx_hdr_priority = %d\n", tx_hdr_priority);
		rx_irq_en(false, rx_info.main_port);
		rx_set_cur_hpd(0, 4, rx_info.main_port);
		if (!rx_info.open_fg)
			port_hpd_rst_flag = 7;
		fsm_restart(rx_info.main_port);
		ret = NOTIFY_OK;
		break;
	case HDMITX_PHY_ADDR_VALID:
		phy_addr = *((int *)p);
		if (log_level & EDID_LOG)
			rx_pr("%s, HDMITX_PHY_ADDR_VALID %x\n",
			      __func__, phy_addr & 0xffff);
		ret = NOTIFY_OK;
		break;
	case HDMITX_KSVLIST:
		if (!hdmirx_repeat_support())
			break;
		topo = (struct hdcp_topo_s *)p;
		rx_pr("ksv list!!\n");
		if (!topo) {
			rx_pr("ksv_list is empty\n");
			break;
		}
		/* before hdcp auth start on upstream side,
		 * no need to care ds ksv list
		 */
		if (rx[rx_info.main_port].hdcp.rpt_reauth_event == HDCP_VER_NONE)
			break;
		/* before request ds re-auth, no need to care ds ksv list */
		if (rx[rx_info.main_port].hdcp.rpt_reauth_event & HDCP_NEED_REQ_DS_AUTH)
			break;
		if (!memcmp(&pre_topo, topo, sizeof(pre_topo))) {
			rx_pr("same topo, exit\n");
			ret = NOTIFY_OK;
			break;
		}
		memcpy(&pre_topo, topo, sizeof(pre_topo));
		rx[rx_info.main_port].hdcp.ds_hdcp_ver = topo->hdcp_ver;
		/* see hdcp2.3 spec 2.11.1 HDCP2 - HDCP1.X Converters
		 * and 2.11.2 HDCP1.X-HDCP2 Converters
		 * receiver ID is in big-endian, KSV is in little-endian
		 * if hdcp version of downstream and upstream side
		 * not match, need to do ksv~receiverID convert
		 */
		if (rx[rx_info.main_port].hdcp.ds_hdcp_ver !=
			rx[rx_info.main_port].hdcp.rpt_reauth_event) {
			memcpy(&hdcp_convert_topo, &pre_topo, sizeof(pre_topo));
			for (i = 0; i < hdcp_convert_topo.dev_cnt; i++) {
				for (j = 0; j <= 1; j++) {
					temp_bype = hdcp_convert_topo.ksv_list[5 * i + j];
					hdcp_convert_topo.ksv_list[5 * i + j] =
						hdcp_convert_topo.ksv_list[5 * i + 5 - 1 - j];
					hdcp_convert_topo.ksv_list[5 * i + 5 - 1 - j] = temp_bype;
				}
			}
			rx_update_rpt_sts(&hdcp_convert_topo);
		} else {
			rx_update_rpt_sts(&pre_topo);
		}
		if (log_level & HDCP_LOG)
			rx_pr("seq_num_V: 0x%x\n", rx[rx_info.main_port].hdcp.topo_updated);
		if (rx[rx_info.main_port].hdcp.rpt_reauth_event == HDCP_VER_22) {
			rx[rx_info.main_port].hdcp.topo_updated++;
			if (rx[rx_info.main_port].hdcp.topo_updated > 0xFFFFFFUL)
				rx[rx_info.main_port].hdcp.topo_updated = 0;
		}
		//hdmirx_wr_cor(RX_SHA_ctrl_HDCP1X_IVCRX, 1, port);
		mdelay(1);
		if (log_level & HDCP_LOG)
			rx_pr("step3\n");
		/* only update topo for corresponding hdcp version */
		if (rx[rx_info.main_port].hdcp.rpt_reauth_event == HDCP_VER_14)
			rx[rx_info.main_port].hdcp.hdcp14_ready = true;
		ret = NOTIFY_OK;
		break;
	default:
		rx_pr("unsupported hdmitx notify:%ld, arg:%p\n",
		      value, p);
		ret = NOTIFY_DONE;
		break;
	}
	return ret;
}

void rx_check_repeat(u8 port)
{
	//struct hdcp14_topo_s *topo_data = (struct hdcp14_topo_s *)receive_hdcp;
	//struct hdcp_topo_s *topo;
	u8 data8 = 0;

	if (!hdmirx_repeat_support())
		return;

	//topo->depth = 1;
	//topo->dev_cnt = 2;
	//RX detect hdcp auth from upstream, but not request tx re-auth yet
	if (rx[port].hdcp.rpt_reauth_event & HDCP_NEED_REQ_DS_AUTH) {
		rx[port].hdcp.rpt_reauth_event &= (~HDCP_NEED_REQ_DS_AUTH);
		//rx_update_rpt_sts(NULL);
		memset(&pre_topo, 0, sizeof(pre_topo));
		memset(&hdcp_convert_topo, 0, sizeof(hdcp_convert_topo));
		rx[port].hdcp.topo_updated = 0;
#ifdef CONFIG_AMLOGIC_HDMITX
		hdmitx_reauth_request(0);
#endif
		//rx_start_repeater_auth();
		//rx[rx_info.main_port].hdcp.rpt_reauth_event = 0;
	}
	if (rx[port].hdcp.hdcp14_ready) {
		//hdmirx_wr_cor(RX_SHA_ctrl_HDCP1X_IVCRX, 1, rx_info.main_port);
		hdmirx_wr_cor(RX_BCAPS_SET_HDCP1X_IVCRX, 0xe0, port);
		if (log_level & HDCP_LOG)
			rx_pr("step4\n");
		rx[port].hdcp.hdcp14_ready = false;
	}
	if (rx[port].hdcp.stream_manage_rcvd) {
		data8 = rx_get_stream_manage_info(port);
		if (log_level & HDCP_LOG)
			rx_pr("step5-%d\n", data8);
		rx[port].hdcp.stream_type = data8;
#ifdef CONFIG_AMLOGIC_HDMITX
		hdmitx_reauth_request(data8 | STREAMTYPE_UPDATE);
#endif
		rx[port].hdcp.stream_manage_rcvd = false;
	}
}

unsigned char *rx_get_dw_edid_addr(void)
{
	return receive_edid;
}

bool get_rx_active_sts(void)
{
	return rx_info.open_fg;
}
EXPORT_SYMBOL(get_rx_active_sts);

void rx_set_repeater_support(bool enable)
{
	downstream_repeat_support = enable;
	repeat_plug = enable;
	rx_pr("****************=%d\n", downstream_repeat_support);
}
EXPORT_SYMBOL(rx_set_repeater_support);

bool rx_poll_dwc(u16 addr, u32 exp_data,
		 u32 mask, u32 max_try)
{
	u32 rd_data;
	u32 cnt = 0;
	u8 done = 0;

	rd_data = hdmirx_rd_dwc(addr);
	while (((cnt < max_try) || (max_try == 0)) && (done != 1)) {
		if ((rd_data & mask) == (exp_data & mask)) {
			done = 1;
		} else {
			cnt++;
			rd_data = hdmirx_rd_dwc(addr);
		}
	}
	if (done == 0) {
		rx_pr("poll dwc%x time-out!\n", addr);
		return false;
	}
	return true;
}

bool rx_set_repeat_aksv(unsigned char *data, int len, int depth,
			bool dev_exceed, bool cascade_exceed)
{
	int i;
	bool ksvlist_ready = 0;

	if (data == 0 || ((depth == 0 || len == 0) &&
			  !dev_exceed && !cascade_exceed))
		return false;
	rx_pr("set ksv list len:%d,depth:%d, exceed count:%d,cascade:%d\n",
	      len, depth, dev_exceed, cascade_exceed);
	/*set repeat depth*/
	if (depth <= MAX_REPEAT_DEPTH && !cascade_exceed) {
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS,
				   MAX_CASCADE_EXCEEDED, 0);
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, DEPTH, depth);
		rx[rx_info.main_port].hdcp.depth = depth;
	} else {
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS,
				   MAX_CASCADE_EXCEEDED, 1);
		rx[rx_info.main_port].hdcp.depth = 0;
	}
	/*set repeat count*/
	if (len <= HDCP14_KSV_MAX_COUNT && !dev_exceed) {
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS,
				   MAX_DEVS_EXCEEDED, 0);
		rx[rx_info.main_port].hdcp.count = len;
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS, DEVICE_COUNT,
				   rx[rx_info.main_port].hdcp.count);
	} else {
		hdmirx_wr_bits_dwc(DWC_HDCP_RPT_BSTATUS,
				   MAX_DEVS_EXCEEDED, 1);
		rx[rx_info.main_port].hdcp.count = 0;
	}
	/*set repeat status*/
	/* if (rx[rx_info.main_port].hdcp.count > 0) {
	 *	rx[rx_info.main_port].hdcp.repeat = true;
	 *	hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, REPEATER, 1);
	 *} else {
	 *	rx[rx_info.main_port].hdcp.repeat = false;
	 *	hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, REPEATER, 0);
	 *}
	 */
	ksvlist_ready = ((rx[rx_info.main_port].hdcp.count > 0) &&
	(rx[rx_info.main_port].hdcp.depth > 0));
	rx_pr("[RX]write ksv list count:%d\n", rx[rx_info.main_port].hdcp.count);
	/*write ksv list to fifo*/
	for (i = 0; i < rx[rx_info.main_port].hdcp.count; i++) {
		if (rx_poll_dwc(DWC_HDCP_RPT_CTRL, ~KSV_HOLD, KSV_HOLD,
				KSV_LIST_WR_TH)) {
			/*check fifo can be written*/
			hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFOCTRL, i);
			hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFO1,
				      *(data + i * MAX_KSV_SIZE + 4));
			hdmirx_wr_dwc(DWC_HDCP_RPT_KSVFIFO0,
				      *((u32 *)(data + i * MAX_KSV_SIZE)));
			if (log_level & VIDEO_LOG)
				rx_pr("[RX]write ksv list index:%d, ksv hi:%#x, low:%#x\n",
				      i, *(data + i * MAX_KSV_SIZE + 4),
				      *((u32 *)(data + i * MAX_KSV_SIZE)));
		} else {
			return false;
		}
	}
	/* Wait for ksv_hold=0*/
	rx_poll_dwc(DWC_HDCP_RPT_CTRL, ~KSV_HOLD, KSV_HOLD, KSV_LIST_WR_TH);
	/*set ksv list ready*/
	hdmirx_wr_bits_dwc(DWC_HDCP_RPT_CTRL, KSVLIST_READY, ksvlist_ready);
	/* Wait for HW completion of V value*/
	if (ksvlist_ready)
		rx_poll_dwc(DWC_HDCP_RPT_CTRL, FIFO_READY,
			    FIFO_READY, KSV_V_WR_TH);
	rx_pr("[RX]write Ready signal! ready:%u\n",
	      (unsigned int)ksvlist_ready);

	return true;
}

void rx_set_repeat_signal(bool repeat)
{
	//rx[rx_info.main_port].hdcp.repeat = repeat;
}
#endif
