// SPDX-License-Identifier: GPL-2.0-only

#include <device/mmio.h>
#include <commonlib/rational.h>
#include <console/console.h>
#include <edid.h>
#include <delay.h>
#include <types.h>
#include <string.h>
#include <soc/clock.h>
#include <gpio.h>
#include <soc/display/edp_reg.h>
#include <soc/display/edp_aux.h>
#include <soc/display/edp_ctrl.h>
#include <soc/display/edp_phy.h>

#define DPCD_LINK_VOLTAGE_MAX		4
#define DPCD_LINK_PRE_EMPHASIS_MAX	4
#define MAX_LINK_TRAINING_LOOP		5

/* DP_TX Registers */
#define MAX_16BITS_VALUE				((1 << 16) - 1) /* 16 bits value */
#define EDP_INTR_AUX_I2C_DONE				BIT(3)
#define EDP_INTR_WRONG_ADDR				BIT(6)
#define EDP_INTR_TIMEOUT				BIT(9)
#define EDP_INTR_NACK_DEFER				BIT(12)
#define EDP_INTR_WRONG_DATA_CNT				BIT(15)
#define EDP_INTR_I2C_NACK				BIT(18)
#define EDP_INTR_I2C_DEFER				BIT(21)
#define EDP_INTR_PLL_UNLOCKED				BIT(24)
#define EDP_INTR_AUX_ERROR				BIT(27)
#define EDP_INTR_READY_FOR_VIDEO			BIT(0)
#define EDP_INTR_IDLE_PATTERN_SENT			BIT(3)
#define EDP_INTR_FRAME_END				BIT(6)
#define EDP_INTR_CRC_UPDATED				BIT(9)
#define EDP_INTR_SST_FIFO_UNDERFLOW			BIT(28)
#define REG_EDP_DP_HPD_CTRL				(0x00000000)
#define EDP_DP_HPD_CTRL_HPD_EN				(0x00000001)
#define EDP_DP_HPD_PLUG_INT_ACK				(0x00000001)
#define EDP_DP_IRQ_HPD_INT_ACK				(0x00000002)
#define EDP_DP_HPD_REPLUG_INT_ACK			(0x00000004)
#define EDP_DP_HPD_UNPLUG_INT_ACK			(0x00000008)
#define EDP_DP_HPD_STATE_STATUS_BITS_MASK		(0x0000000F)
#define EDP_DP_HPD_STATE_STATUS_BITS_SHIFT		(0x1C)
#define EDP_DP_HPD_PLUG_INT_MASK			(0x00000001)
#define EDP_DP_IRQ_HPD_INT_MASK				(0x00000002)
#define EDP_DP_HPD_REPLUG_INT_MASK			(0x00000004)
#define EDP_DP_HPD_UNPLUG_INT_MASK			(0x00000008)
#define EDP_DP_HPD_INT_MASK				(EDP_DP_HPD_PLUG_INT_MASK | \
							EDP_DP_IRQ_HPD_INT_MASK | \
							EDP_DP_HPD_REPLUG_INT_MASK | \
							EDP_DP_HPD_UNPLUG_INT_MASK)
#define EDP_DP_HPD_STATE_STATUS_CONNECTED		(0x40000000)
#define EDP_DP_HPD_STATE_STATUS_PENDING			(0x20000000)
#define EDP_DP_HPD_STATE_STATUS_DISCONNECTED		(0x00000000)
#define EDP_DP_HPD_STATE_STATUS_MASK			(0xE0000000)
#define EDP_DP_HPD_REFTIMER_ENABLE			(1 << 16)
#define EDP_DP_HPD_EVENT_TIME_0_VAL			(0x3E800FA)
#define EDP_DP_HPD_EVENT_TIME_1_VAL			(0x1F407D0)
#define EDP_INTERRUPT_TRANS_NUM				(0x000000A0)
#define EDP_MAINLINK_CTRL_ENABLE			(0x00000001)
#define EDP_MAINLINK_CTRL_RESET				(0x00000002)
#define EDP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER		(0x00000010)
#define EDP_MAINLINK_FB_BOUNDARY_SEL			(0x02000000)
#define EDP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK		(0x00000001)
#define EDP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN	(0x00000002)
#define EDP_CONFIGURATION_CTRL_P_INTERLACED		(0x00000004)
#define EDP_CONFIGURATION_CTRL_INTERLACED_BTF		(0x00000008)
#define EDP_CONFIGURATION_CTRL_NUM_OF_LANES		(0x00000010)
#define EDP_CONFIGURATION_CTRL_ENHANCED_FRAMING		(0x00000040)
#define EDP_CONFIGURATION_CTRL_SEND_VSC			(0x00000080)
#define EDP_CONFIGURATION_CTRL_BPC			(0x00000100)
#define EDP_CONFIGURATION_CTRL_ASSR			(0x00000400)
#define EDP_CONFIGURATION_CTRL_RGB_YUV			(0x00000800)
#define EDP_CONFIGURATION_CTRL_LSCLK_DIV		(0x00002000)
#define EDP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT	(0x04)
#define EDP_CONFIGURATION_CTRL_BPC_SHIFT		(0x08)
#define EDP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT		(0x0D)
#define EDP_TOTAL_HOR_VER_HORIZ__MASK			(0x0000FFFF)
#define EDP_TOTAL_HOR_VER_HORIZ__SHIFT			(0)
#define DP_EDP_CONFIGURATION_CAP			0x00d   /* XXX 1.2? */
#define DP_ALTERNATE_SCRAMBLER_RESET_CAP		(1 << 0)
#define DP_FRAMING_CHANGE_CAP				(1 << 1)
#define DP_DPCD_DISPLAY_CONTROL_CAPABLE			(1 << 3) /* edp v1.2 or higher */
#define EDP_MISC0_SYNCHRONOUS_CLK			(0x00000001)
#define EDP_MISC0_COLORIMETRY_CFG_SHIFT			(0x00000001)
#define EDP_MISC0_TEST_BITS_DEPTH_SHIFT			(0x00000005)
#define LANE0_MAPPING_SHIFT				(0x00000000)
#define LANE1_MAPPING_SHIFT				(0x00000002)
#define LANE2_MAPPING_SHIFT				(0x00000004)
#define LANE3_MAPPING_SHIFT				(0x00000006)
#define EDP_MAINLINK_READY_FOR_VIDEO			(0x00000001)
#define EDP_MAINLINK_READY_TRAIN_PATTERN_1_READY	(0x00000008)
#define EDP_MAINLINK_SAFE_TO_EXIT_LEVEL_2		(0x00000002)
#define EDP_LINK_BW_MAX					DP_LINK_BW_5_4
#define DP_RECEIVER_CAP_SIZE				0xf
#define DP_LINK_STATUS_SIZE				6
#define DP_TRAINING_AUX_RD_MASK				0x7F    /* DP 1.3 */

/* AUX CH addresses */
/* DPCD */
#define DP_DPCD_REV				0x000
#define DP_DPCD_REV_10				0x10
#define DP_DPCD_REV_11				0x11
#define DP_DPCD_REV_12				0x12
#define DP_DPCD_REV_13				0x13
#define DP_DPCD_REV_14				0x14
#define DP_SET_POWER				0x600
#define DP_SET_POWER_D0				0x1
#define DP_SET_POWER_D3				0x2
#define DP_SET_POWER_MASK			0x3
#define DP_MAX_LINK_RATE			0x001
#define DP_MAX_LANE_COUNT			0x002
#define DP_MAX_LANE_COUNT_MASK			0x1f
#define DP_TPS3_SUPPORTED			(1 << 6)
#define DP_ENHANCED_FRAME_CAP			(1 << 7)
#define DP_MAX_DOWNSPREAD			0x003
#define DP_NO_AUX_HANDSHAKE_LINK_TRAINING	(1 << 6)
#define DP_NORP					0x004
#define DP_DOWNSTREAMPORT_PRESENT		0x005
#define DP_DWN_STRM_PORT_PRESENT		(1 << 0)
#define DP_DWN_STRM_PORT_TYPE_MASK		0x06
#define DP_FORMAT_CONVERSION			(1 << 3)
#define DP_MAIN_LINK_CHANNEL_CODING		0x006
#define DP_EDP_CONFIGURATION_CAP		0x00d
#define DP_TRAINING_AUX_RD_INTERVAL		0x00e

/* link configuration */
#define	DP_LINK_BW_SET				0x100
#define DP_LINK_RATE_TABLE			0x00    /* eDP 1.4 */
#define DP_LINK_BW_1_62				0x06
#define DP_LINK_BW_2_7				0x0a
#define DP_LINK_BW_5_4				0x14    /* 1.2 */
#define DP_LINK_BW_8_1				0x1e    /* 1.4 */
#define DP_LANE_COUNT_SET			0x101
#define DP_LANE_COUNT_MASK			0x0f
#define DP_LANE_COUNT_ENHANCED_FRAME_EN		(1 << 7)
#define DP_TRAINING_PATTERN_SET			0x102
#define DP_TRAINING_PATTERN_DISABLE		0
#define DP_TRAINING_PATTERN_1			1
#define DP_TRAINING_PATTERN_2			2
#define DP_TRAINING_PATTERN_3			3
#define DP_TRAINING_PATTERN_MASK		0x3
#define DP_LINK_QUAL_PATTERN_DISABLE		(0 << 2)
#define DP_LINK_QUAL_PATTERN_D10_2		(1 << 2)
#define DP_LINK_QUAL_PATTERN_ERROR_RATE		(2 << 2)
#define DP_LINK_QUAL_PATTERN_PRBS7		(3 << 2)
#define DP_LINK_QUAL_PATTERN_MASK		(3 << 2)
#define DP_RECOVERED_CLOCK_OUT_EN		(1 << 4)
#define DP_LINK_SCRAMBLING_DISABLE		(1 << 5)
#define DP_EDP_CONFIGURATION_SET		0x10a
#define DP_ALTERNATE_SCRAMBLER_RESET_ENABLE	(1 << 0)
#define DP_FRAMING_CHANGE_ENABLE		(1 << 1)
#define DP_PANEL_SELF_TEST_ENABLE		(1 << 7)
#define DP_SYMBOL_ERROR_COUNT_BOTH		(0 << 6)
#define DP_SYMBOL_ERROR_COUNT_DISPARITY		(1 << 6)
#define DP_SYMBOL_ERROR_COUNT_SYMBOL		(2 << 6)
#define DP_SYMBOL_ERROR_COUNT_MASK		(3 << 6)
#define DP_TRAINING_LANE0_SET			0x103
#define DP_TRAINING_LANE1_SET			0x104
#define DP_TRAINING_LANE2_SET			0x105
#define DP_TRAINING_LANE3_SET			0x106

#define DP_TRAIN_VOLTAGE_SWING_MASK		0x3
#define DP_TRAIN_VOLTAGE_SWING_SHIFT		0
#define DP_TRAIN_MAX_SWING_REACHED		(1 << 2)
#define DP_TRAIN_VOLTAGE_SWING_400		(0 << 0)
#define DP_TRAIN_VOLTAGE_SWING_600		(1 << 0)
#define DP_TRAIN_VOLTAGE_SWING_800		(2 << 0)
#define DP_TRAIN_VOLTAGE_SWING_1200		(3 << 0)
#define DP_TRAIN_PRE_EMPHASIS_MASK		(3 << 3)
#define DP_TRAIN_PRE_EMPHASIS_0			(0 << 3)
#define DP_TRAIN_PRE_EMPHASIS_3_5		(1 << 3)
#define DP_TRAIN_PRE_EMPHASIS_6			(2 << 3)
#define DP_TRAIN_PRE_EMPHASIS_9_5		(3 << 3)
#define DP_TRAIN_PRE_EMPHASIS_SHIFT		3
#define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED	(1 << 5)
#define DP_DOWNSPREAD_CTRL			0x107
#define DP_SPREAD_AMP_0_5			(1 << 4)
#define DP_MAIN_LINK_CHANNEL_CODING_SET		0x108
#define DP_SET_ANSI_8B10B			(1 << 0)
#define DP_LANE0_1_STATUS			0x202
#define DP_LANE2_3_STATUS			0x203
#define DP_LANE_CR_DONE				(1 << 0)
#define DP_LANE_CHANNEL_EQ_DONE			(1 << 1)
#define DP_LANE_SYMBOL_LOCKED			(1 << 2)
#define DP_CHANNEL_EQ_BITS			(DP_LANE_CR_DONE | \
						DP_LANE_CHANNEL_EQ_DONE | \
						DP_LANE_SYMBOL_LOCKED)
#define DP_LANE_ALIGN_STATUS_UPDATED		0x204
#define DP_INTERLANE_ALIGN_DONE			(1 << 0)
#define DP_DOWNSTREAM_PORT_STATUS_CHANGED	(1 << 6)
#define DP_LINK_STATUS_UPDATED			(1 << 7)
#define DP_ADJUST_REQUEST_LANE0_1		0x206
#define DP_ADJUST_REQUEST_LANE2_3		0x207
#define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK	0x03
#define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT	0
#define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK	0x0c
#define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT	2
#define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK	0x30
#define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT	4
#define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK	0xc0
#define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT	6
#define DP_TEST_REQUEST				0x218
#define DP_TEST_LINK_TRAINING			(1 << 0)
#define DP_TEST_LINK_PATTERN			(1 << 1)
#define DP_TEST_LINK_EDID_READ			(1 << 2)
#define DP_TEST_LINK_PHY_TEST_PATTERN		(1 << 3) /* DPCD >= 1.1 */
#define DP_TEST_LINK_RATE			0x219
#define DP_LINK_RATE_162			(0x6)
#define DP_LINK_RATE_27				(0xa)

#define DP_TEST_LANE_COUNT			0x220
#define DP_TEST_PATTERN				0x221
#define DP_TEST_RESPONSE			0x260
#define DP_TEST_ACK				(1 << 0)
#define DP_TEST_NAK				(1 << 1)
#define DP_TEST_EDID_CHECKSUM_WRITE		(1 << 2)

#define DP_SET_POWER				0x600
#define DP_SET_POWER_D0				0x1
#define DP_SET_POWER_D3				0x2

/* Link training return value */
#define EDP_TRAIN_FAIL		-1
#define EDP_TRAIN_SUCCESS	0
#define EDP_TRAIN_RECONFIG	1

#define EDP_INTERRUPT_STATUS_ACK_SHIFT	1
#define EDP_INTERRUPT_STATUS_MASK_SHIFT	2

#define EDP_INTERRUPT_STATUS1 \
	(EDP_INTR_AUX_I2C_DONE| \
	EDP_INTR_WRONG_ADDR | EDP_INTR_TIMEOUT | \
	EDP_INTR_NACK_DEFER | EDP_INTR_WRONG_DATA_CNT | \
	EDP_INTR_I2C_NACK | EDP_INTR_I2C_DEFER | \
	EDP_INTR_PLL_UNLOCKED | EDP_INTR_AUX_ERROR)

#define EDP_INTERRUPT_STATUS1_ACK \
	(EDP_INTERRUPT_STATUS1 << EDP_INTERRUPT_STATUS_ACK_SHIFT)
#define EDP_INTERRUPT_STATUS1_MASK \
	(EDP_INTERRUPT_STATUS1 << EDP_INTERRUPT_STATUS_MASK_SHIFT)

#define EDP_INTERRUPT_STATUS2 \
	(EDP_INTR_READY_FOR_VIDEO | EDP_INTR_IDLE_PATTERN_SENT | \
	EDP_INTR_FRAME_END | EDP_INTR_CRC_UPDATED | EDP_INTR_SST_FIFO_UNDERFLOW)

#define EDP_INTERRUPT_STATUS2_ACK \
	(EDP_INTERRUPT_STATUS2 << EDP_INTERRUPT_STATUS_ACK_SHIFT)
#define EDP_INTERRUPT_STATUS2_MASK \
	(EDP_INTERRUPT_STATUS2 << EDP_INTERRUPT_STATUS_MASK_SHIFT)

enum edp_color_depth {
	EDP_6BIT = 0,
	EDP_8BIT = 1,
	EDP_10BIT = 2,
	EDP_12BIT = 3,
	EDP_16BIT = 4,
};

struct tu_algo_data {
	int64_t lclk_fp;
	int64_t lclk;
	int64_t pclk;
	int64_t pclk_fp;
	int64_t lwidth;
	int64_t lwidth_fp;
	int64_t hbp_relative_to_pclk;
	int64_t hbp_relative_to_pclk_fp;
	int nlanes;
	int bpp;
	int async_en;
	int bpc;
	int delay_start_link_extra_pclk;
	int extra_buffer_margin;
	int64_t ratio_fp;
	int64_t original_ratio_fp;
	int64_t err_fp;
	int64_t old_err_fp;
	int64_t new_err_fp;
	int tu_size;
	int tu_size_desired;
	int tu_size_minus1;
	int valid_boundary_link;
	int64_t resulting_valid_fp;
	int64_t total_valid_fp;
	int64_t effective_valid_fp;
	int64_t effective_valid_recorded_fp;
	int num_tus;
	int num_tus_per_lane;
	int paired_tus;
	int remainder_tus;
	int remainder_tus_upper;
	int remainder_tus_lower;
	int extra_bytes;
	int filler_size;
	int delay_start_link;
	int extra_pclk_cycles;
	int extra_pclk_cycles_in_link_clk;
	int64_t ratio_by_tu_fp;
	int64_t average_valid2_fp;
	int new_valid_boundary_link;
	int remainder_symbols_exist;
	int n_symbols;
	int64_t n_remainder_symbols_per_lane_fp;
	int64_t last_partial_tu_fp;
	int64_t TU_ratio_err_fp;
	int n_tus_incl_last_incomplete_tu;
	int extra_pclk_cycles_tmp;
	int extra_pclk_cycles_in_link_clk_tmp;
	int extra_required_bytes_new_tmp;
	int filler_size_tmp;
	int lower_filler_size_tmp;
	int delay_start_link_tmp;
	bool boundary_moderation_en;
	int boundary_mod_lower_err;
	int upper_boundary_count;
	int lower_boundary_count;
	int i_upper_boundary_count;
	int i_lower_boundary_count;
	int valid_lower_boundary_link;
	int even_distribution_BF;
	int even_distribution_legacy;
	int even_distribution;
	int min_hblank_violated;
	int64_t delay_start_time_fp;
	int64_t hbp_time_fp;
	int64_t hactive_time_fp;
	int64_t diff_abs_fp;
	int64_t ratio;
};

struct edp_ctrl {
	/* Link status */
	uint32_t link_rate_khz;
	uint8_t link_rate;
	uint32_t lane_cnt;
	uint8_t v_level;
	uint8_t p_level;

	/* Timing status */
	uint32_t pixel_rate; /* in kHz */
	uint32_t color_depth;
};

struct edp_ctrl_tu {
	uint32_t tu_size_minus1;		/* Desired TU Size */
	uint32_t valid_boundary_link;		/* Upper valid boundary */
	uint32_t delay_start_link;		/* # of clock cycles to delay */
	bool boundary_moderation_en;		/* Enable boundary Moderation? */
	uint32_t valid_lower_boundary_link;	/* Valid lower boundary link */
	uint32_t upper_boundary_count;		/* Upper boundary Count */
	uint32_t lower_boundary_count;		/* Lower boundary Count */
};

static uint8_t dp_get_lane_status(const uint8_t link_status[DP_LINK_STATUS_SIZE], int lane)
{
	int i = DP_LANE0_1_STATUS + (lane >> 1);
	int s = (lane & 1) * 4;
	uint8_t l = link_status[i - DP_LANE0_1_STATUS];
	return (l >> s) & 0xf;
}


static uint8_t edp_get_adjust_request_voltage(
				const uint8_t link_status[DP_LINK_STATUS_SIZE],
				int lane)
{
	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
	int s = ((lane & 1) ?
		 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
		 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
	uint8_t l = link_status[i - DP_LANE0_1_STATUS];

	return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
}

static uint8_t edp_get_adjust_request_pre_emphasis(
					const uint8_t link_status[DP_LINK_STATUS_SIZE],
					int lane)
{
	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
	int s = ((lane & 1) ?
		 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
		 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
	uint8_t l = link_status[i - DP_LANE0_1_STATUS];

	return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
}

static void edp_link_train_clock_recovery_delay(const uint8_t dpcd[DP_RECEIVER_CAP_SIZE])
{
	int rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & DP_TRAINING_AUX_RD_MASK;

	if (rd_interval > 4)
		printk(BIOS_ERR, "AUX interval %d, out of range (max 4)\n", rd_interval);

	/*
	 * The DPCD stores the AUX read interval in units of 4 ms.
	 * for DP v1.4 and above, clock recovery should use 100 us for AUX read intervals.
	 */
	if (rd_interval == 0 || dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
		udelay(100);
	else
		mdelay(rd_interval * 4);
}

static bool edp_clock_recovery_ok(const uint8_t link_status[DP_LINK_STATUS_SIZE],
				  int lane_count)
{
	int lane;
	uint8_t lane_status;

	for (lane = 0; lane < lane_count; lane++) {
		lane_status = dp_get_lane_status(link_status, lane);
		if ((lane_status & DP_LANE_CR_DONE) == 0) {
			printk(BIOS_ERR, "clock recovery ok failed : %x\n", lane_status);
			return false;
		}
	}
	return true;
}

static void edp_link_train_channel_eq_delay(const uint8_t dpcd[DP_RECEIVER_CAP_SIZE])
{
	int rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
			  DP_TRAINING_AUX_RD_MASK;

	if (rd_interval > 4)
		printk(BIOS_INFO, "AUX interval %d, out of range (max 4)\n",
			      rd_interval);

	/*
	 * The DPCD stores the AUX read interval in units of 4 ms.
	 * if the TRAINING_AUX_RD_INTERVAL field is 0, the channel equalization
	 * should use 400 us AUX read intervals.
	 */
	if (rd_interval == 0)
		udelay(400);
	else
		mdelay(rd_interval * 4);
}

static bool edp_channel_eq_ok(const uint8_t link_status[DP_LINK_STATUS_SIZE], int lane_count)
{
	uint8_t lane_align;
	uint8_t lane_status;
	int lane;

	lane_align = link_status[DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE0_1_STATUS];
	if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
		return false;

	for (lane = 0; lane < lane_count; lane++) {
		lane_status = dp_get_lane_status(link_status, lane);
		if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS)
			return false;
	}
	return true;
}

static void edp_ctrl_irq_enable(int enable)
{
	if (enable) {
		write32(&edp_ahbclk->interrupt_status, EDP_INTERRUPT_STATUS1_MASK);
		write32(&edp_ahbclk->interrupt_status2,
			EDP_INTERRUPT_STATUS2_MASK);
	} else {
		write32(&edp_ahbclk->interrupt_status,
			EDP_INTERRUPT_STATUS1_ACK);
		write32(&edp_ahbclk->interrupt_status2,
			EDP_INTERRUPT_STATUS2_ACK);
	}
}

static void edp_config_ctrl(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	uint32_t config = 0, depth = 0;

	/* Default-> LSCLK DIV: 1/4 LCLK  */
	config |= (2 << EDP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);

	/* Scrambler reset enable */
	if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP)
		config |= EDP_CONFIGURATION_CTRL_ASSR;

	if (ctrl->color_depth == 8)
		depth = EDP_8BIT;
	else if (ctrl->color_depth == 10)
		depth = EDP_10BIT;
	else if (ctrl->color_depth == 12)
		depth = EDP_12BIT;
	else if (ctrl->color_depth == 16)
		depth = EDP_16BIT;
	else
		depth = EDP_6BIT;

	config |= depth << EDP_CONFIGURATION_CTRL_BPC_SHIFT;

	/* Num of Lanes */
	config |= ((ctrl->lane_cnt - 1)
			<< EDP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);

	if (dpcd[DP_DPCD_REV] >= 0x11 &&
		(dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP))
		config |= EDP_CONFIGURATION_CTRL_ENHANCED_FRAMING;

	config |= EDP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */

	/* sync clock & static Mvid */
	config |= EDP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
	config |= EDP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;

	write32(&edp_lclk->configuration_ctrl, config);
}

static void edp_state_ctrl(uint32_t state)
{
	write32(&edp_lclk->state_ctrl, state);
}

static int edp_lane_set_write(uint8_t voltage_level, uint8_t pre_emphasis_level)
{
	int i;
	uint8_t buf[4];

	if (voltage_level >= DPCD_LINK_VOLTAGE_MAX)
		voltage_level |= 0x04;

	if (pre_emphasis_level >= DPCD_LINK_PRE_EMPHASIS_MAX)
		pre_emphasis_level |= 0x20;

	for (i = 0; i < 4; i++)
		buf[i] = voltage_level | pre_emphasis_level;

	if (edp_aux_transfer(DP_TRAINING_LANE0_SET, DP_AUX_NATIVE_WRITE, buf, 2) < 2) {
		printk(BIOS_ERR, "%s: Set sw/pe to panel failed\n", __func__);
		return -1;
	}

	return 0;
}

static int edp_train_pattern_set_write(uint8_t pattern)
{
	uint8_t p = pattern;

	printk(BIOS_INFO, "pattern=%x\n", p);
	if (edp_aux_transfer(DP_TRAINING_PATTERN_SET, DP_AUX_NATIVE_WRITE, &p, 1) < 1) {
		printk(BIOS_ERR, "%s: Set training pattern to panel failed\n", __func__);
		return -1;
	}

	return 0;
}

static void edp_sink_train_set_adjust(struct edp_ctrl *ctrl,
	const uint8_t *link_status)
{
	int i;
	uint8_t max = 0;
	uint8_t data;

	/* use the max level across lanes */
	for (i = 0; i < ctrl->lane_cnt; i++) {
		data = edp_get_adjust_request_voltage(link_status, i);
		printk(BIOS_INFO, "lane=%d req_voltage_swing=0x%x\n", i, data);
		if (max < data)
			max = data;
	}

	ctrl->v_level = max >> DP_TRAIN_VOLTAGE_SWING_SHIFT;

	/* use the max level across lanes */
	max = 0;
	for (i = 0; i < ctrl->lane_cnt; i++) {
		data = edp_get_adjust_request_pre_emphasis(link_status, i);
		printk(BIOS_INFO, "lane=%d req_pre_emphasis=0x%x\n", i, data);
		if (max < data)
			max = data;
	}

	ctrl->p_level = max >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
	printk(BIOS_INFO, "v_level=%d, p_level=%d\n", ctrl->v_level, ctrl->p_level);
}

static void edp_host_train_set(uint32_t train)
{
	int cnt = 10;
	uint32_t data = 0;
	uint32_t shift = train - 1;

	printk(BIOS_INFO, "train=%d\n", train);

	edp_state_ctrl(SW_LINK_TRAINING_PATTERN1 << shift);
	while (--cnt) {
		data = read32(&edp_lclk->mainlink_ready);
		if (data & (EDP_MAINLINK_READY_TRAIN_PATTERN_1_READY << shift))
			break;
	}

	if (cnt == 0)
		printk(BIOS_INFO, "%s: set link_train=%u failed\n", __func__, data);
}

static int edp_voltage_pre_emphasis_set(struct edp_ctrl *ctrl)
{
	printk(BIOS_INFO, "v=%d p=%d\n", ctrl->v_level, ctrl->p_level);

	edp_phy_config(ctrl->v_level, ctrl->p_level);
	return edp_lane_set_write(ctrl->v_level, ctrl->p_level);
}

static int edp_start_link_train_1(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	uint8_t link_status[DP_LINK_STATUS_SIZE];
	uint8_t old_v_level;
	int tries;
	int ret, rlen;

	edp_state_ctrl(0);
	edp_host_train_set(DP_TRAINING_PATTERN_1);

	ret = edp_train_pattern_set_write(DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE);
	if (ret)
		return ret;

	ret = edp_voltage_pre_emphasis_set(ctrl);
	if (ret)
		return ret;

	tries = 0;
	old_v_level = ctrl->v_level;
	while (1) {
		edp_link_train_clock_recovery_delay(dpcd);

		rlen = edp_aux_transfer(DP_LANE0_1_STATUS, DP_AUX_NATIVE_READ,
					&link_status, DP_LINK_STATUS_SIZE);
		if (rlen < DP_LINK_STATUS_SIZE) {
			printk(BIOS_ERR, "%s: read link status failed\n", __func__);
			return -1;
		}

		if (edp_clock_recovery_ok(link_status, ctrl->lane_cnt)) {
			ret = 0;
			break;
		}

		if (ctrl->v_level == DPCD_LINK_VOLTAGE_MAX) {
			ret = -1;
			break;
		}

		if (old_v_level != ctrl->v_level) {
			tries++;
			if (tries >= 5) {
				ret = -1;
				break;
			}
		} else {
			tries = 0;
			old_v_level = ctrl->v_level;
		}

		edp_sink_train_set_adjust(ctrl, link_status);
		ret = edp_voltage_pre_emphasis_set(ctrl);
		if (ret)
			return ret;
	}

	return 0;
}

static int edp_start_link_train_2(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	uint8_t link_status[DP_LINK_STATUS_SIZE];
	int tries = 0;
	int ret, rlen;

	edp_host_train_set(DP_TRAINING_PATTERN_2);
	ret = edp_voltage_pre_emphasis_set(ctrl);
	if (ret)
		return ret;

	ret = edp_train_pattern_set_write(DP_TRAINING_PATTERN_2 | DP_RECOVERED_CLOCK_OUT_EN);
	if (ret)
		return ret;

	while (1) {
		edp_link_train_channel_eq_delay(dpcd);

		rlen = edp_aux_transfer(DP_LANE0_1_STATUS, DP_AUX_NATIVE_READ,
					&link_status, DP_LINK_STATUS_SIZE);
		if (rlen < DP_LINK_STATUS_SIZE) {
			printk(BIOS_ERR, "%s: read link status failed\n", __func__);
			return -1;
		}

		if (edp_channel_eq_ok(link_status, ctrl->lane_cnt)) {
			ret = 0;
			break;
		}

		tries++;
		if (tries > 10) {
			ret = -1;
			break;
		}

		edp_sink_train_set_adjust(ctrl, link_status);
		ret = edp_voltage_pre_emphasis_set(ctrl);
		if (ret)
			return ret;
	}

	return ret;
}

static int edp_link_rate_down_shift(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	int ret = 0;
	int link_rate = ctrl->link_rate_khz;

	switch (link_rate) {
	case 810000:
		link_rate = 540000;
		break;
	case 540000:
		link_rate = 270000;
		break;
	case 270000:
		link_rate = 162000;
		break;
	case 162000:
	default:
		ret = -1;
		break;
	}

	if (!ret) {
		ctrl->link_rate_khz = link_rate;
		ctrl->link_rate = link_rate / 27000;
		printk(BIOS_INFO, "new rate=0x%x\n", ctrl->link_rate_khz);
	}

	return ret;
}

static bool edp_clock_recovery_reduced_lanes(
			const uint8_t link_status[DP_LINK_STATUS_SIZE], uint32_t lane_cnt)
{
	int reduced_lanes = 0;

	if (lane_cnt <= 1)
		return -1;

	reduced_lanes = lane_cnt >> 1;

	return edp_clock_recovery_ok(link_status, reduced_lanes);
}

static int edp_link_lane_down_shift(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	if (ctrl->lane_cnt <= 1)
		return -1;

	ctrl->lane_cnt = ctrl->lane_cnt >> 1;
	ctrl->link_rate_khz = dpcd[DP_MAX_LINK_RATE] * 27000;
	ctrl->link_rate = dpcd[DP_MAX_LINK_RATE];
	ctrl->p_level = 0;
	ctrl->v_level = 0;

	return 0;
}

static int edp_clear_training_pattern(uint8_t *dpcd)
{
	int ret;

	ret = edp_train_pattern_set_write(0);
	edp_link_train_channel_eq_delay(dpcd);

	return ret;
}

static int edp_do_link_train(struct edp_ctrl *ctrl, uint8_t *dpcd)
{
	uint8_t values[2], edp_config = 0;
	int ret;
	int rlen;
	uint8_t link_status[DP_LINK_STATUS_SIZE];

	/*
	 * Set the current link rate and lane cnt to panel. They may have been
	 * adjusted and the values are different from them in DPCD CAP
	 */

	values[0] = ctrl->link_rate;
	values[1] = ctrl->lane_cnt;

	if (dpcd[DP_DPCD_REV] >= 0x11 &&
	    (dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP))
		values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;

	if (edp_aux_transfer(DP_LINK_BW_SET, DP_AUX_NATIVE_WRITE, &values[0], 1) < 0)
		return EDP_TRAIN_FAIL;

	edp_aux_transfer(DP_LANE_COUNT_SET, DP_AUX_NATIVE_WRITE, &values[1], 1);
	ctrl->v_level = 0; /* start from default level */
	ctrl->p_level = 0;

	values[0] = 0x10;

	if (dpcd[DP_MAX_DOWNSPREAD] & 1)
		values[0] = DP_SPREAD_AMP_0_5;

	values[1] = 1;
	edp_aux_transfer(DP_DOWNSPREAD_CTRL, DP_AUX_NATIVE_WRITE, &values[0], 1);
	edp_aux_transfer(DP_MAIN_LINK_CHANNEL_CODING_SET, DP_AUX_NATIVE_WRITE, &values[1], 1);

	ret = edp_start_link_train_1(ctrl, dpcd);
	if (ret < 0) {
		rlen = edp_aux_transfer(DP_LANE0_1_STATUS, DP_AUX_NATIVE_READ,
					&link_status, DP_LINK_STATUS_SIZE);
		if (rlen < DP_LINK_STATUS_SIZE) {
			printk(BIOS_ERR, "%s: read link status failed\n", __func__);
			return -1;
		}

		ret = edp_link_rate_down_shift(ctrl, dpcd);
		if (!ret) {
			printk(BIOS_ERR, "link reconfig\n");
			ret = EDP_TRAIN_RECONFIG;
		} else if (ret < 0) {
			if (edp_clock_recovery_reduced_lanes(link_status,
							     ctrl->lane_cnt) == 0) {
				if (edp_link_lane_down_shift(ctrl, dpcd) < 0) {
					printk(BIOS_ERR, "%s: Training 1 failed\n", __func__);
					ret = EDP_TRAIN_FAIL;
				} else {
					printk(BIOS_ERR, "link reconfig\n");
					ret = EDP_TRAIN_RECONFIG;
				}
			} else {
				printk(BIOS_ERR, "%s: Training 1 failed\n", __func__);
				ret = EDP_TRAIN_FAIL;
			}
		}
		edp_clear_training_pattern(dpcd);
		return ret;
	}

	printk(BIOS_INFO, "Training 1 completed successfully\n");
	edp_state_ctrl(0);
	if (edp_clear_training_pattern(dpcd))
		return EDP_TRAIN_FAIL;

	ret = edp_start_link_train_2(ctrl, dpcd);
	if (ret < 0) {
		rlen = edp_aux_transfer(DP_LANE0_1_STATUS, DP_AUX_NATIVE_READ,
					&link_status, DP_LINK_STATUS_SIZE);
		if (rlen < DP_LINK_STATUS_SIZE) {
			printk(BIOS_ERR, "%s: read link status failed\n", __func__);
			return -1;
		}

		if (edp_clock_recovery_ok(link_status, ctrl->lane_cnt)) {
			if (edp_link_rate_down_shift(ctrl, dpcd) == 0) {
				printk(BIOS_ERR, "link reconfig\n");
				ret = EDP_TRAIN_RECONFIG;
			} else {
				printk(BIOS_ERR, "%s: Training 2 failed\n", __func__);
				ret = EDP_TRAIN_FAIL;
			}
		} else {
			if (edp_link_lane_down_shift(ctrl, dpcd) < 0) {
				printk(BIOS_ERR, "%s: Training 1 failed\n", __func__);
				ret = EDP_TRAIN_FAIL;
			} else {
				printk(BIOS_ERR, "link reconfig\n");
				ret = EDP_TRAIN_RECONFIG;
			}
		}

		edp_clear_training_pattern(dpcd);
		return ret;
	}

	printk(BIOS_INFO, "Training 2 completed successfully\n");
	edp_config = DP_ALTERNATE_SCRAMBLER_RESET_ENABLE;
	edp_aux_transfer(DP_EDP_CONFIGURATION_SET, DP_AUX_NATIVE_WRITE, &edp_config, 1);

	return ret;
}

static void edp_ctrl_config_misc(struct edp_ctrl *ctrl)
{
	uint32_t misc_val;
	enum edp_color_depth depth = EDP_6BIT;

	misc_val = read32(&edp_lclk->misc1_misc0);
	if (ctrl->color_depth == 8)
		depth = EDP_8BIT;
	else if (ctrl->color_depth == 10)
		depth = EDP_10BIT;
	else if (ctrl->color_depth == 12)
		depth = EDP_12BIT;
	else if (ctrl->color_depth == 16)
		depth = EDP_16BIT;

	/* clear bpp bits */
	misc_val &= ~(0x07 << EDP_MISC0_TEST_BITS_DEPTH_SHIFT);
	misc_val |= depth << EDP_MISC0_TEST_BITS_DEPTH_SHIFT;

	/* Configure clock to synchronous mode */
	misc_val |= EDP_MISC0_SYNCHRONOUS_CLK;
	write32(&edp_lclk->misc1_misc0, misc_val);
}

static int edp_ctrl_pixel_clock_dividers(struct edp_ctrl *ctrl,
					  uint32_t *pixel_m, uint32_t *pixel_n)
{
	uint32_t pixel_div = 0, dispcc_input_rate;
	unsigned long den, num;
	uint8_t rate = ctrl->link_rate;
	uint32_t stream_rate_khz = ctrl->pixel_rate;

	if (rate == DP_LINK_BW_8_1) {
		pixel_div = 6;
	} else if (rate == DP_LINK_BW_1_62 || rate == DP_LINK_BW_2_7) {
		pixel_div = 2;
	} else if (rate == DP_LINK_BW_5_4) {
		pixel_div = 4;
	} else {
		printk(BIOS_ERR, "Invalid pixel mux divider\n");
		return -1;
	}

	dispcc_input_rate = (ctrl->link_rate_khz * 10) / pixel_div;

	rational_best_approximation(dispcc_input_rate, stream_rate_khz,
				    (unsigned long)(1 << 16) - 1,
				    (unsigned long)(1 << 16) - 1, &den, &num);
	*pixel_m = num;
	*pixel_n = den;

	return 0;
}

static int edp_ctrl_config_msa(struct edp_ctrl *ctrl)
{
	uint32_t pixel_m, pixel_n;
	uint32_t mvid, nvid;
	u32 const nvid_fixed = 0x8000;
	uint8_t rate = ctrl->link_rate;

	if (edp_ctrl_pixel_clock_dividers(ctrl, &pixel_m, &pixel_n) < 0)
		return -1;

	pixel_n = ~(pixel_n - pixel_m);
	pixel_n = pixel_n & 0xFFFF;
	mvid = (pixel_m & 0xFFFF) * 5;
	nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF);

	if (nvid < nvid_fixed) {
		u32 temp;

		temp = (nvid_fixed / nvid) * nvid;
		mvid = (nvid_fixed / nvid) * mvid;
		nvid = temp;
	}

	if (rate == DP_LINK_BW_5_4)
		nvid *= 2;

	if (rate == DP_LINK_BW_8_1)
		nvid *= 3;

	printk(BIOS_INFO, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
	write32(&edp_lclk->software_mvid, mvid);
	write32(&edp_lclk->software_nvid, nvid);
	write32(&edp_p0clk->dsc_dto, 0x0);

	return 0;
}

static void tu_valid_boundary_calc(struct tu_algo_data *tu)
{
	int64_t f = 100000;

	tu->new_valid_boundary_link = DIV_ROUND_UP(tu->tu_size * tu->ratio_fp, f);
	tu->average_valid2_fp = ((tu->i_upper_boundary_count * tu->new_valid_boundary_link +
				 tu->i_lower_boundary_count  *
				 (tu->new_valid_boundary_link - 1)) * f) /
				 (tu->i_upper_boundary_count + tu->i_lower_boundary_count);
	tu->num_tus = ((tu->bpp * tu->lwidth_fp) / 8) / tu->average_valid2_fp;
	tu->n_remainder_symbols_per_lane_fp = ((tu->n_symbols * f) -
					       (tu->num_tus * tu->average_valid2_fp)) /
						tu->nlanes;
	tu->last_partial_tu_fp = (tu->n_remainder_symbols_per_lane_fp / tu->tu_size);
	if (tu->n_remainder_symbols_per_lane_fp != 0)
		tu->remainder_symbols_exist = 1;
	else
		tu->remainder_symbols_exist = 0;

	tu->num_tus_per_lane = tu->num_tus / tu->nlanes;
	tu->paired_tus = (int)((tu->num_tus_per_lane) / (tu->i_upper_boundary_count +
			       tu->i_lower_boundary_count));
	tu->remainder_tus = tu->num_tus_per_lane - tu->paired_tus *
			    (tu->i_upper_boundary_count + tu->i_lower_boundary_count);
	if ((tu->remainder_tus - tu->i_upper_boundary_count) > 0) {
		tu->remainder_tus_upper = tu->i_upper_boundary_count;
		tu->remainder_tus_lower = tu->remainder_tus - tu->i_upper_boundary_count;
	} else {
		tu->remainder_tus_upper = tu->remainder_tus;
		tu->remainder_tus_lower = 0;
	}

	tu->total_valid_fp = (tu->paired_tus * (tu->i_upper_boundary_count *
			      tu->new_valid_boundary_link + tu->i_lower_boundary_count *
			      (tu->new_valid_boundary_link - 1)) + (tu->remainder_tus_upper *
			      tu->new_valid_boundary_link) + (tu->remainder_tus_lower *
			      (tu->new_valid_boundary_link - 1))) * f;
	if (tu->remainder_symbols_exist) {
		tu->effective_valid_fp = (((tu->total_valid_fp +
					  tu->n_remainder_symbols_per_lane_fp) * f) /
					  (tu->num_tus_per_lane * f + tu->last_partial_tu_fp));
	} else {
		tu->effective_valid_fp = (tu->total_valid_fp / tu->num_tus_per_lane);
	}

	tu->new_err_fp = tu->effective_valid_fp - (tu->ratio_fp * tu->tu_size);
	tu->old_err_fp = tu->average_valid2_fp - (tu->ratio_fp * tu->tu_size);
	tu->even_distribution = tu->num_tus % tu->nlanes == 0 ? 1 : 0;
	tu->n_tus_incl_last_incomplete_tu = DIV_ROUND_UP((tu->bpp * tu->lwidth_fp / 8),
							 tu->average_valid2_fp);
	tu->extra_required_bytes_new_tmp = DIV_ROUND_UP((tu->average_valid2_fp - (tu->tu_size *
							tu->original_ratio_fp)) *
							tu->n_tus_incl_last_incomplete_tu, f);
	tu->extra_required_bytes_new_tmp += DIV_ROUND_UP(((tu->i_upper_boundary_count *
							 tu->nlanes) *
							 (tu->new_valid_boundary_link * f -
							 tu->tu_size * tu->original_ratio_fp)),
							 f);
	tu->extra_pclk_cycles_tmp = DIV_ROUND_UP(8 * tu->extra_required_bytes_new_tmp,
						 tu->bpp);
	tu->extra_pclk_cycles_in_link_clk_tmp = DIV_ROUND_UP(tu->extra_pclk_cycles_tmp *
							     tu->lclk_fp, tu->pclk_fp);
	tu->filler_size_tmp = tu->tu_size - tu->new_valid_boundary_link;
	tu->lower_filler_size_tmp = tu->filler_size_tmp + 1;
	tu->delay_start_link_tmp = tu->extra_pclk_cycles_in_link_clk_tmp +
				   tu->lower_filler_size_tmp + tu->extra_buffer_margin;
	tu->delay_start_time_fp = tu->delay_start_link_tmp * f / tu->lclk;
	if (((tu->even_distribution == 1) ||
	    ((tu->even_distribution_BF == 0) &&
	     (tu->even_distribution_legacy == 0))) &&
	      tu->old_err_fp >= 0 && tu->new_err_fp >= 0 &&
	      (tu->new_err_fp < tu->err_fp) &&
	      ((tu->new_err_fp < tu->diff_abs_fp) ||
	       (tu->min_hblank_violated == 1)) &&
	      (tu->new_valid_boundary_link - 1) > 0 &&
	      (tu->hbp_time_fp > tu->delay_start_time_fp) &&
	      (tu->delay_start_link_tmp <= 1023)) {
		tu->upper_boundary_count = tu->i_upper_boundary_count;
		tu->lower_boundary_count = tu->i_lower_boundary_count;
		tu->err_fp = tu->new_err_fp;
		tu->boundary_moderation_en = true;
		tu->tu_size_desired = tu->tu_size;
		tu->valid_boundary_link = tu->new_valid_boundary_link;
		tu->effective_valid_recorded_fp = tu->effective_valid_fp;
		tu->even_distribution_BF = 1;
		tu->delay_start_link = tu->delay_start_link_tmp;
	} else if (tu->boundary_mod_lower_err == 0) {
		if (tu->new_err_fp < tu->diff_abs_fp)
			tu->boundary_mod_lower_err = 1;
	}
}

static void edp_ctrl_calc_tu(struct edp_ctrl *ctrl, struct edid *edid,
			struct edp_ctrl_tu *tu_table)
{
	struct tu_algo_data tu = {0};
	int64_t f = 100000;
	int64_t LCLK_FAST_SKEW_fp = (6 * f) / 1000; /* 0.0006 */
	uint8_t DP_BRUTE_FORCE = 1;
	int64_t BRUTE_FORCE_THRESHOLD_fp = (1 * f) / 10; /* 0.1 */
	int RATIO_SCALE_NUM = 1001;
	int RATIO_SCALE_DEN = 1000;
	int HBLANK_MARGIN = 4;
	int EXTRA_PIXCLK_CYCLE_DELAY = 4;
	int64_t temp = 0;
	int64_t temp_fp = 0;

	tu.lclk_fp = ctrl->link_rate_khz * f;
	tu.lclk = ctrl->link_rate_khz;
	tu.pclk = edid->mode.pixel_clock;
	tu.pclk_fp = edid->mode.pixel_clock * f;
	tu.nlanes = ctrl->lane_cnt;
	tu.bpp = edid->panel_bits_per_pixel;
	tu.lwidth = edid->mode.ha;
	tu.lwidth_fp = tu.lwidth * f;
	tu.hbp_relative_to_pclk = edid->mode.hbl;
	tu.hbp_relative_to_pclk_fp = tu.hbp_relative_to_pclk * f;
	tu.err_fp = 1000 * f;
	tu.extra_buffer_margin = DIV_ROUND_UP(tu.lclk * 4, tu.pclk);
	tu.ratio_fp =  ((int64_t)(tu.pclk_fp * tu.bpp) / 8) / (tu.nlanes * tu.lclk);
	tu.original_ratio_fp = tu.ratio_fp;
	if (((tu.lwidth % tu.nlanes) != 0) && (tu.ratio_fp < f)) {
		tu.ratio_fp = (tu.ratio_fp * RATIO_SCALE_NUM) / RATIO_SCALE_DEN;
		tu.ratio_fp = tu.ratio_fp < f ? tu.ratio_fp : f;
	}

	if (tu.ratio_fp > f)
		tu.ratio_fp = f;

	for (tu.tu_size = 32; tu.tu_size <= 64; tu.tu_size++) {
		tu.new_err_fp = (tu.ratio_fp * tu.tu_size) -
				 (((tu.ratio_fp * tu.tu_size) / f) * f);
		if (tu.new_err_fp > 0)
			tu.new_err_fp = f - tu.new_err_fp;

		if (tu.new_err_fp < tu.err_fp) {
			tu.err_fp = tu.new_err_fp;
			tu.tu_size_desired = tu.tu_size;
		}
	}

	tu.tu_size_minus1 = tu.tu_size_desired - 1;
	tu.valid_boundary_link = DIV_ROUND_UP(tu.tu_size_desired * tu.ratio_fp, f);
	tu.num_tus = ((tu.bpp * tu.lwidth) / 8) / tu.valid_boundary_link;
	tu.even_distribution_legacy = tu.num_tus % tu.nlanes == 0 ? 1 : 0;
	tu.extra_bytes = DIV_ROUND_UP(((tu.num_tus + 1) * (tu.valid_boundary_link * f -
					tu.original_ratio_fp *	tu.tu_size_desired)), f);
	tu.extra_pclk_cycles = DIV_ROUND_UP(tu.extra_bytes * 8, tu.bpp);
	tu.extra_pclk_cycles_in_link_clk = DIV_ROUND_UP(tu.extra_pclk_cycles * tu.lclk,
							 tu.pclk);
	tu.filler_size = tu.tu_size_desired - tu.valid_boundary_link;
	tu.ratio_by_tu_fp = tu.ratio_fp * tu.tu_size_desired;
	tu.delay_start_link = tu.extra_pclk_cycles_in_link_clk + tu.filler_size +
			       tu.extra_buffer_margin;
	tu.resulting_valid_fp = tu.valid_boundary_link * f;
	tu.TU_ratio_err_fp = (tu.resulting_valid_fp / tu.tu_size_desired) -
			       tu.original_ratio_fp;
	tu.hbp_time_fp = (tu.hbp_relative_to_pclk_fp - HBLANK_MARGIN * f) / tu.pclk;
	tu.delay_start_time_fp = (tu.delay_start_link * f) / tu.lclk;
	if (tu.hbp_time_fp < tu.delay_start_time_fp)
		tu.min_hblank_violated = 1;

	tu.hactive_time_fp = (tu.lwidth * f) / tu.pclk;
	if (tu.hactive_time_fp < tu.delay_start_time_fp)
		tu.min_hblank_violated = 1;

	tu.delay_start_time_fp = 0;

	/* brute force */
	tu.delay_start_link_extra_pclk = EXTRA_PIXCLK_CYCLE_DELAY;
	tu.diff_abs_fp = tu.resulting_valid_fp - tu.ratio_by_tu_fp;
	if (tu.diff_abs_fp < 0)
		tu.diff_abs_fp = tu.diff_abs_fp * -1;

	tu.boundary_mod_lower_err = 0;
	if ((tu.diff_abs_fp != 0 &&
	    ((tu.diff_abs_fp > BRUTE_FORCE_THRESHOLD_fp) ||
	     (tu.even_distribution_legacy == 0) ||
	     (DP_BRUTE_FORCE == 1))) ||
	     (tu.min_hblank_violated == 1)) {
		do {
			tu.err_fp = 1000 * f;
			tu.extra_buffer_margin = DIV_ROUND_UP(tu.lclk_fp *
							       tu.delay_start_link_extra_pclk,
							       tu.pclk_fp);
			tu.n_symbols = DIV_ROUND_UP(tu.bpp * tu.lwidth, 8);
			for (tu.tu_size = 32; tu.tu_size <= 64; tu.tu_size++) {
				for (tu.i_upper_boundary_count = 1;
					tu.i_upper_boundary_count <= 15;
					tu.i_upper_boundary_count++) {
					for (tu.i_lower_boundary_count = 1;
					     tu.i_lower_boundary_count <= 15;
					     tu.i_lower_boundary_count++) {
						tu_valid_boundary_calc(&tu);
					}
				}
			}
			tu.delay_start_link_extra_pclk--;
		} while (tu.boundary_moderation_en != true &&
			tu.boundary_mod_lower_err == 1 &&
			tu.delay_start_link_extra_pclk != 0);

		if (tu.boundary_moderation_en == true) {
			tu.resulting_valid_fp = f * (tu.upper_boundary_count *
						      tu.valid_boundary_link +
						      tu.lower_boundary_count *
						      (tu.valid_boundary_link - 1));
			tu.resulting_valid_fp /= (tu.upper_boundary_count +
						   tu.lower_boundary_count);
			tu.ratio_by_tu_fp = tu.original_ratio_fp * tu.tu_size_desired;
			tu.valid_lower_boundary_link = tu.valid_boundary_link - 1;
			tu.num_tus = ((tu.bpp / 8) * tu.lwidth_fp) / tu.resulting_valid_fp;
			tu.tu_size_minus1 = tu.tu_size_desired - 1;
			tu.even_distribution_BF = 1;
			tu.TU_ratio_err_fp = ((tu.tu_size_desired * f /
						tu.resulting_valid_fp) * f);
			tu.TU_ratio_err_fp -= tu.original_ratio_fp;
		}
	}

	temp_fp = LCLK_FAST_SKEW_fp * tu.lwidth;
	temp = DIV_ROUND_UP(temp_fp, f);
	temp_fp = ((tu.bpp * f / 8) / (tu.nlanes * tu.original_ratio_fp)) * f * temp;
	temp = temp_fp / f;

	tu.delay_start_time_fp = (tu.delay_start_link * f) / tu.lclk;

	/* OUTPUTS */
	tu_table->valid_boundary_link       = tu.valid_boundary_link;
	tu_table->delay_start_link          = tu.delay_start_link;
	tu_table->boundary_moderation_en    = tu.boundary_moderation_en;
	tu_table->valid_lower_boundary_link = tu.valid_lower_boundary_link;
	tu_table->upper_boundary_count      = tu.upper_boundary_count;
	tu_table->lower_boundary_count      = tu.lower_boundary_count;
	tu_table->tu_size_minus1            = tu.tu_size_minus1;

	printk(BIOS_INFO, "TU: valid_boundary_link: %d\n",
	       tu_table->valid_boundary_link);
	printk(BIOS_INFO, "TU: delay_start_link: %d\n",
	       tu_table->delay_start_link);
	printk(BIOS_INFO, "TU: boundary_moderation_en: %d\n",
	       tu_table->boundary_moderation_en);
	printk(BIOS_INFO, "TU: valid_lower_boundary_link: %d\n",
	       tu_table->valid_lower_boundary_link);
	printk(BIOS_INFO, "TU: upper_boundary_count: %d\n",
	       tu_table->upper_boundary_count);
	printk(BIOS_INFO, "TU: lower_boundary_count: %d\n",
	       tu_table->lower_boundary_count);
	printk(BIOS_INFO, "TU: tu_size_minus1: %d\n", tu_table->tu_size_minus1);
}

static void edp_ctrl_config_TU(struct edp_ctrl *ctrl, struct edid *edid)
{
	struct edp_ctrl_tu tu_config;

	edp_ctrl_calc_tu(ctrl, edid, &tu_config);
	write32(&edp_lclk->valid_boundary, tu_config.delay_start_link << 16 |
		tu_config.valid_boundary_link);
	write32(&edp_lclk->tu, tu_config.tu_size_minus1);
	write32(&edp_lclk->valid_boundary2, tu_config.boundary_moderation_en |
		tu_config.valid_lower_boundary_link << 1|
		tu_config.upper_boundary_count << 16 |
		tu_config.lower_boundary_count << 20);
}

static void edp_ctrl_timing_cfg(struct edid *edid)
{
	uint32_t hpolarity;
	uint32_t vpolarity;

	hpolarity = (edid->mode.phsync == '+');
	vpolarity = (edid->mode.pvsync == '+');

	/* Configure eDP timing to HW */
	write32(&edp_lclk->total_hor_ver,
		(edid->mode.ha + edid->mode.hbl) |
		(((edid->mode.va + edid->mode.vbl) << 16) & 0xffff0000));

	write32(&edp_lclk->start_hor_ver_from_sync,
		(edid->mode.hbl - edid->mode.hso) |
		(((edid->mode.vbl - edid->mode.vso) << 16) & 0xffff0000));

	write32(&edp_lclk->hysnc_vsync_width_polarity, edid->mode.hspw |
		((hpolarity << 15) & 0x8000) | ((edid->mode.vspw << 16) &
		0x7fff0000) | ((vpolarity << 31) & 0x80000000));

	write32(&edp_lclk->active_hor_ver,
		(edid->mode.ha) |
		((edid->mode.va << 16) & 0xffff0000));
}

static void edp_mainlink_ctrl(int enable)
{
	uint32_t data = 0;

	write32(&edp_lclk->mainlink_ctrl, 0x2);
	if (enable)
		data |= (0x1);

	write32(&edp_lclk->mainlink_ctrl, data);
}

static void edp_ctrl_phy_enable(int enable)
{
	if (enable) {
		write32(&edp_ahbclk->phy_ctrl, 0x4 | 0x1);
		write32(&edp_ahbclk->phy_ctrl, 0x0);
		edp_phy_enable();
	}
}

static void edp_ctrl_phy_aux_enable(int enable)
{
	if (enable) {
		/* regulators are enabled in QCLIB */
		edp_ctrl_phy_enable(1);
		edp_aux_ctrl(1);
	} else {
		edp_aux_ctrl(0);
	}
}

static void edp_ctrl_link_enable(struct edp_ctrl *ctrl,
				struct edid *edid, uint8_t *dpcd, int enable)
{
	int ret = 0;
	uint32_t m = 0, n = 0;

	if (enable) {
		edp_phy_power_on(ctrl->link_rate_khz);
		edp_phy_vm_pe_init();
		mdss_clock_configure(MDSS_CLK_EDP_LINK, 0, 1, 0, 0, 0, 0);
		ret = mdss_clock_enable(MDSS_CLK_EDP_LINK);
		if (ret != 0)
			printk(BIOS_ERR, "failed to enable link clk\n");

		mdss_clock_configure(MDSS_CLK_EDP_LINK_INTF, 0, 1, 0, 0, 0, 0);
		ret = mdss_clock_enable(MDSS_CLK_EDP_LINK_INTF);
		if (ret != 0)
			printk(BIOS_ERR, "failed to enable link intf clk\n");

		edp_ctrl_pixel_clock_dividers(ctrl, &m, &n);
		mdss_clock_configure(MDSS_CLK_EDP_PIXEL, 0, 2, 0, m, n, n);
		ret = mdss_clock_enable(MDSS_CLK_EDP_PIXEL);
		if (ret != 0)
			printk(BIOS_ERR, "failed to enable pixel clk\n");

		edp_mainlink_ctrl(1);
	} else {
		edp_mainlink_ctrl(0);
	}
}

static int edp_ctrl_training(struct edp_ctrl *ctrl, struct edid *edid, uint8_t *dpcd)
{
	int ret;

	/* Do link training only when power is on */
	ret = edp_do_link_train(ctrl, dpcd);
	while (ret == EDP_TRAIN_RECONFIG) {
		/* Re-configure main link */
		edp_ctrl_irq_enable(0);
		edp_ctrl_link_enable(ctrl, edid, dpcd, 0);
		edp_ctrl_phy_enable(1);
		edp_ctrl_irq_enable(1);
		edp_ctrl_link_enable(ctrl, edid, dpcd, 1);
		ret = edp_do_link_train(ctrl, dpcd);
	}

	return ret;
}

static int edp_ctrl_on(struct edp_ctrl *ctrl, struct edid *edid, uint8_t *dpcd)
{
	uint8_t value;
	int ret;

	/*
	 * By default, use the maximum link rate and minimum lane count,
	 * so that we can do rate down shift during link training.
	 */
	ctrl->link_rate_khz = dpcd[DP_MAX_LINK_RATE] * 27000;
	ctrl->link_rate = dpcd[DP_MAX_LINK_RATE];
	ctrl->lane_cnt = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
	ctrl->color_depth = edid->panel_bits_per_color;
	ctrl->pixel_rate = edid->mode.pixel_clock;

	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
	if (dpcd[DP_DPCD_REV] >= 0x11) {
		ret = edp_aux_transfer(DP_SET_POWER, DP_AUX_NATIVE_READ, &value, 1);
		if (ret < 0) {
			printk(BIOS_ERR, "edp native read failure\n");
			return -1;
		}

		value &= ~DP_SET_POWER_MASK;
		value |= DP_SET_POWER_D0;

		ret = edp_aux_transfer(DP_SET_POWER, DP_AUX_NATIVE_WRITE, &value, 1);
		if (ret < 0) {
			printk(BIOS_ERR, "edp native read failure\n");
			return -1;
		}

		/*
		 * According to the DP 1.1 specification, a "Sink Device must
		 * exit the power saving state within 1 ms" (Section 2.5.3.1,
		 * Table 5-52, "Sink Control Field" (register 0x600).
		 */
		udelay(1000);
	}

	edp_ctrl_irq_enable(1);
	edp_ctrl_link_enable(ctrl, edid, dpcd, 1);
	/* Start link training */
	ret = edp_ctrl_training(ctrl, edid, dpcd);
	if (ret != EDP_TRAIN_SUCCESS) {
		printk(BIOS_ERR, "edp training failure\n");
		return -1;
	}

	edp_train_pattern_set_write(0);

	write32(&edp_lclk->mainlink_ctrl, 0x2000000);
	write32(&edp_lclk->mainlink_ctrl, 0x2000002);
	write32(&edp_lclk->mainlink_ctrl, 0x2000000);
	write32(&edp_lclk->mainlink_ctrl, 0x2000001);
	edp_config_ctrl(ctrl, dpcd);
	edp_ctrl_config_misc(ctrl);
	edp_ctrl_timing_cfg(edid);

	if (edp_ctrl_config_msa(ctrl) < 0)
		return -1;

	edp_ctrl_config_TU(ctrl, edid);
	edp_state_ctrl(SW_SEND_VIDEO);
	edp_ctrl_irq_enable(0);

	return 0;
}

static bool edp_ctrl_panel_connected(uint8_t *dpcd)
{
	/* enable aux clk */
	mdss_clock_configure(MDSS_CLK_EDP_AUX, 0, 0, 0, 0, 0, 0);
	mdss_clock_enable(MDSS_CLK_EDP_AUX);
	edp_ctrl_phy_aux_enable(1);
	edp_ctrl_irq_enable(1);
	if (edp_aux_transfer(DP_DPCD_REV, DP_AUX_NATIVE_READ, dpcd,
			     DP_RECEIVER_CAP_SIZE) < DP_RECEIVER_CAP_SIZE) {
		printk(BIOS_ERR, "%s: AUX channel is NOT ready\n", __func__);

	} else {
		return true;
	}

	return false;
}

enum cb_err edp_ctrl_init(struct edid *edid)
{
	uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
	struct edp_ctrl ctrl;

	memset(&ctrl, 0, sizeof(struct edp_ctrl));
	mdss_clock_enable(GCC_EDP_CLKREF_EN);

	if (edp_ctrl_panel_connected(dpcd) && edp_read_edid(edid) == 0) {
		if (edp_ctrl_on(&ctrl, edid, dpcd) < 0)
			return CB_ERR;

		return CB_SUCCESS;
	}

	edp_ctrl_irq_enable(0);

	return CB_ERR;
}
