// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2012-2018, 2020-2021, The Linux Foundation. All rights reserved. */
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/kthread.h>

#include "mdss_dsi.h"
#include "mdss_dsi_phy.h"

#define MDSS_DSI_DSIPHY_REGULATOR_CTRL_0	0x00
#define MDSS_DSI_DSIPHY_REGULATOR_CTRL_1	0x04
#define MDSS_DSI_DSIPHY_REGULATOR_CTRL_2	0x08
#define MDSS_DSI_DSIPHY_REGULATOR_CTRL_3	0x0c
#define MDSS_DSI_DSIPHY_REGULATOR_CTRL_4	0x10
#define MDSS_DSI_DSIPHY_REGULATOR_CAL_PWR_CFG	0x18
#define MDSS_DSI_DSIPHY_LDO_CNTRL		0x1dc
#define MDSS_DSI_DSIPHY_REGULATOR_TEST		0x294
#define MDSS_DSI_DSIPHY_STRENGTH_CTRL_0		0x184
#define MDSS_DSI_DSIPHY_STRENGTH_CTRL_1		0x188
#define MDSS_DSI_DSIPHY_STRENGTH_CTRL_2		0x18c
#define MDSS_DSI_DSIPHY_TIMING_CTRL_0		0x140
#define MDSS_DSI_DSIPHY_GLBL_TEST_CTRL		0x1d4
#define MDSS_DSI_DSIPHY_CTRL_0			0x170
#define MDSS_DSI_DSIPHY_CTRL_1			0x174
#define MDSS_DSI_DSIPHY_CMN_CLK_CFG0		0x0010
#define MDSS_DSI_DSIPHY_CMN_CLK_CFG1		0x0014

#define MDSS_DSI_NUM_DATA_LANES		0x04
#define MDSS_DSI_NUM_CLK_LANES		0x01

#define SW_RESET BIT(2)
#define SW_RESET_PLL BIT(0)
#define PWRDN_B BIT(7)

/* 8996 */
#define DATALANE_OFFSET_FROM_BASE_8996		0x100
#define CLKLANE_OFFSET_FROM_BASE_8996		0x300
#define DATALANE_SIZE_8996			0x80
#define CLKLANE_SIZE_8996			0x80

#define DSIPHY_CMN_PLL_CNTRL			0x0048
#define DSIPHY_CMN_GLBL_TEST_CTRL		0x0018
#define DSIPHY_CMN_CTRL_0			0x001c
#define DSIPHY_CMN_CTRL_1			0x0020
#define DSIPHY_CMN_LDO_CNTRL			0x004c
#define DSIPHY_PLL_CLKBUFLR_EN			0x041c
#define DSIPHY_PLL_PLL_BANDGAP			0x0508

#define DSIPHY_LANE_STRENGTH_CTRL_NUM		0x0002
#define DSIPHY_LANE_STRENGTH_CTRL_OFFSET	0x0004
#define DSIPHY_LANE_STRENGTH_CTRL_BASE		0x0038

#define DSIPHY_LANE_CFG_NUM			0x0004
#define DSIPHY_LANE_CFG_OFFSET			0x0004
#define DSIPHY_LANE_CFG_BASE			0x0000

#define DSIPHY_LANE_VREG_NUM			0x0001
#define DSIPHY_LANE_VREG_OFFSET			0x0004
#define DSIPHY_LANE_VREG_BASE			0x0064

#define DSIPHY_LANE_TIMING_CTRL_NUM		0x0008
#define DSIPHY_LANE_TIMING_CTRL_OFFSET		0x0004
#define DSIPHY_LANE_TIMING_CTRL_BASE		0x0018

#define DSIPHY_LANE_TEST_STR			0x0014

#define DSIPHY_LANE_STRENGTH_CTRL_1		0x003c
#define DSIPHY_LANE_VREG_CNTRL			0x0064

#define DSI_DYNAMIC_REFRESH_PLL_CTRL0		0x214
#define DSI_DYNAMIC_REFRESH_PLL_CTRL1		0x218
#define DSI_DYNAMIC_REFRESH_PLL_CTRL2		0x21C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL3		0x220
#define DSI_DYNAMIC_REFRESH_PLL_CTRL4		0x224
#define DSI_DYNAMIC_REFRESH_PLL_CTRL5		0x228
#define DSI_DYNAMIC_REFRESH_PLL_CTRL6		0x22C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL7		0x230
#define DSI_DYNAMIC_REFRESH_PLL_CTRL8		0x234
#define DSI_DYNAMIC_REFRESH_PLL_CTRL9		0x238
#define DSI_DYNAMIC_REFRESH_PLL_CTRL10		0x23C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL11		0x240
#define DSI_DYNAMIC_REFRESH_PLL_CTRL12		0x244
#define DSI_DYNAMIC_REFRESH_PLL_CTRL13		0x248
#define DSI_DYNAMIC_REFRESH_PLL_CTRL14		0x24C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL15		0x250
#define DSI_DYNAMIC_REFRESH_PLL_CTRL16		0x254
#define DSI_DYNAMIC_REFRESH_PLL_CTRL17		0x258
#define DSI_DYNAMIC_REFRESH_PLL_CTRL18		0x25C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL19		0x260
#define DSI_DYNAMIC_REFRESH_PLL_CTRL19		0x260
#define DSI_DYNAMIC_REFRESH_PLL_CTRL20		0x264
#define DSI_DYNAMIC_REFRESH_PLL_CTRL21		0x268
#define DSI_DYNAMIC_REFRESH_PLL_CTRL22		0x26C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL23		0x270
#define DSI_DYNAMIC_REFRESH_PLL_CTRL24		0x274
#define DSI_DYNAMIC_REFRESH_PLL_CTRL25		0x278
#define DSI_DYNAMIC_REFRESH_PLL_CTRL26		0x27C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL27		0x280
#define DSI_DYNAMIC_REFRESH_PLL_CTRL28		0x284
#define DSI_DYNAMIC_REFRESH_PLL_CTRL29		0x288
#define DSI_DYNAMIC_REFRESH_PLL_CTRL30		0x28C
#define DSI_DYNAMIC_REFRESH_PLL_CTRL31		0x290
#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR	0x294
#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2	0x298

#define DSIPHY_DLN0_CFG1		0x0104
#define DSIPHY_DLN0_TIMING_CTRL_4	0x0118
#define DSIPHY_DLN0_TIMING_CTRL_5	0x011C
#define DSIPHY_DLN0_TIMING_CTRL_6	0x0120
#define DSIPHY_DLN0_TIMING_CTRL_7	0x0124
#define DSIPHY_DLN0_TIMING_CTRL_8	0x0128

#define DSIPHY_DLN1_CFG1		0x0184
#define DSIPHY_DLN1_TIMING_CTRL_4	0x0198
#define DSIPHY_DLN1_TIMING_CTRL_5	0x019C
#define DSIPHY_DLN1_TIMING_CTRL_6	0x01A0
#define DSIPHY_DLN1_TIMING_CTRL_7	0x01A4
#define DSIPHY_DLN1_TIMING_CTRL_8	0x01A8

#define DSIPHY_DLN2_CFG1		0x0204
#define DSIPHY_DLN2_TIMING_CTRL_4	0x0218
#define DSIPHY_DLN2_TIMING_CTRL_5	0x021C
#define DSIPHY_DLN2_TIMING_CTRL_6	0x0220
#define DSIPHY_DLN2_TIMING_CTRL_7	0x0224
#define DSIPHY_DLN2_TIMING_CTRL_8	0x0228

#define DSIPHY_DLN3_CFG1		0x0284
#define DSIPHY_DLN3_TIMING_CTRL_4	0x0298
#define DSIPHY_DLN3_TIMING_CTRL_5	0x029C
#define DSIPHY_DLN3_TIMING_CTRL_6	0x02A0
#define DSIPHY_DLN3_TIMING_CTRL_7	0x02A4
#define DSIPHY_DLN3_TIMING_CTRL_8	0x02A8

#define DSIPHY_CKLN_CFG1		0x0304
#define DSIPHY_CKLN_TIMING_CTRL_4	0x0318
#define DSIPHY_CKLN_TIMING_CTRL_5	0x031C
#define DSIPHY_CKLN_TIMING_CTRL_6	0x0320
#define DSIPHY_CKLN_TIMING_CTRL_7	0x0324
#define DSIPHY_CKLN_TIMING_CTRL_8	0x0328

#define DSIPHY_PLL_RESETSM_CNTRL5	0x043c

#define DSIPHY_CMN_CLK_CFG1_SPLIT_LINK	0x1

#define PLL_CALC_DATA(addr0, addr1, data0, data1)      \
	(((data1) << 24) | ((((addr1)/4) & 0xFF) << 16) | \
	 ((data0) << 8) | (((addr0)/4) & 0xFF))

#define MDSS_DYN_REF_REG_W(base, offset, addr0, addr1, data0, data1)   \
	writel_relaxed(PLL_CALC_DATA(addr0, addr1, data0, data1), \
			(base) + (offset))

void mdss_dsi_dfps_config_8996(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_panel_data *pdata;
	struct mdss_panel_info *pinfo;
	struct mdss_dsi_phy_ctrl *pd;
	int glbl_tst_cntrl =
		MIPI_INP(ctrl->phy_io.base + DSIPHY_CMN_GLBL_TEST_CTRL);

	pdata = &ctrl->panel_data;
	if (!pdata) {
		pr_err("%s: Invalid panel data\n", __func__);
		return;
	}
	pinfo = &pdata->panel_info;
	pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db);

	if (mdss_dsi_is_ctrl_clk_slave(ctrl)) {
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL0,
				DSIPHY_DLN0_CFG1, DSIPHY_DLN1_CFG1,
				0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL1,
				DSIPHY_DLN2_CFG1, DSIPHY_DLN3_CFG1,
				0x0, 0x0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL2,
				DSIPHY_CKLN_CFG1, DSIPHY_DLN0_TIMING_CTRL_4,
				0x0, pd->timing_8996[0]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL3,
				DSIPHY_DLN1_TIMING_CTRL_4,
				DSIPHY_DLN2_TIMING_CTRL_4,
				pd->timing_8996[8],
				pd->timing_8996[16]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL4,
				DSIPHY_DLN3_TIMING_CTRL_4,
				DSIPHY_CKLN_TIMING_CTRL_4,
				pd->timing_8996[24],
				pd->timing_8996[32]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL5,
				DSIPHY_DLN0_TIMING_CTRL_5,
				DSIPHY_DLN1_TIMING_CTRL_5,
				pd->timing_8996[1],
				pd->timing_8996[9]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL6,
				DSIPHY_DLN2_TIMING_CTRL_5,
				DSIPHY_DLN3_TIMING_CTRL_5,
				pd->timing_8996[17],
				pd->timing_8996[25]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL7,
				DSIPHY_CKLN_TIMING_CTRL_5,
				DSIPHY_DLN0_TIMING_CTRL_6,
				pd->timing_8996[33],
				pd->timing_8996[2]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL8,
				DSIPHY_DLN1_TIMING_CTRL_6,
				DSIPHY_DLN2_TIMING_CTRL_6,
				pd->timing_8996[10],
				pd->timing_8996[18]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL9,
				DSIPHY_DLN3_TIMING_CTRL_6,
				DSIPHY_CKLN_TIMING_CTRL_6,
				pd->timing_8996[26],
				pd->timing_8996[34]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL10,
				DSIPHY_DLN0_TIMING_CTRL_7,
				DSIPHY_DLN1_TIMING_CTRL_7,
				pd->timing_8996[3],
				pd->timing_8996[11]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL11,
				DSIPHY_DLN2_TIMING_CTRL_7,
				DSIPHY_DLN3_TIMING_CTRL_7,
				pd->timing_8996[19],
				pd->timing_8996[27]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL12,
				DSIPHY_CKLN_TIMING_CTRL_7,
				DSIPHY_DLN0_TIMING_CTRL_8,
				pd->timing_8996[35],
				pd->timing_8996[4]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL13,
				DSIPHY_DLN1_TIMING_CTRL_8,
				DSIPHY_DLN2_TIMING_CTRL_8,
				pd->timing_8996[12],
				pd->timing_8996[20]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL14,
				DSIPHY_DLN3_TIMING_CTRL_8,
				DSIPHY_CKLN_TIMING_CTRL_8,
				pd->timing_8996[28],
				pd->timing_8996[36]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL15,
				0x0110, 0x0110,	0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL16,
				0x0110, 0x0110,	0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL17,
				0x0110, 0x0110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL18,
				0x0110, 0x0110,	0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL19,
				0x0110, 0x0110,	0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL20,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL21,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL22,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL23,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL24,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL25,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL26,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL27,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL28,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL29,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL30,
				0x110, 0x110, 0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL31,
				0x110, 0x110, 0, 0);
		MIPI_OUTP(ctrl->ctrl_base +
				DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR, 0x0);
		MIPI_OUTP(ctrl->ctrl_base +
				DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2, 0x0);
	} else {
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL0,
				DSIPHY_CMN_GLBL_TEST_CTRL,
				DSIPHY_PLL_PLL_BANDGAP,
				glbl_tst_cntrl | BIT(1), 0x1);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL1,
				DSIPHY_PLL_RESETSM_CNTRL5,
				DSIPHY_PLL_PLL_BANDGAP,
				0x0D, 0x03);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL2,
				DSIPHY_PLL_RESETSM_CNTRL5,
				DSIPHY_CMN_PLL_CNTRL,
				0x1D, 0x00);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL3,
				DSIPHY_CMN_CTRL_1, DSIPHY_DLN0_CFG1,
				0x20, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL4,
				DSIPHY_DLN1_CFG1, DSIPHY_DLN2_CFG1,
				0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL5,
				DSIPHY_DLN3_CFG1, DSIPHY_CKLN_CFG1,
				0, 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL6,
				DSIPHY_DLN0_TIMING_CTRL_4,
				DSIPHY_DLN1_TIMING_CTRL_4,
				pd->timing_8996[0],
				pd->timing_8996[8]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL7,
				DSIPHY_DLN2_TIMING_CTRL_4,
				DSIPHY_DLN3_TIMING_CTRL_4,
				pd->timing_8996[16],
				pd->timing_8996[24]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL8,
				DSIPHY_CKLN_TIMING_CTRL_4,
				DSIPHY_DLN0_TIMING_CTRL_5,
				pd->timing_8996[32],
				pd->timing_8996[1]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL9,
				DSIPHY_DLN1_TIMING_CTRL_5,
				DSIPHY_DLN2_TIMING_CTRL_5,
				pd->timing_8996[9],
				pd->timing_8996[17]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL10,
				DSIPHY_DLN3_TIMING_CTRL_5,
				DSIPHY_CKLN_TIMING_CTRL_5,
				pd->timing_8996[25],
				pd->timing_8996[33]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL11,
				DSIPHY_DLN0_TIMING_CTRL_6,
				DSIPHY_DLN1_TIMING_CTRL_6,
				pd->timing_8996[2],
				pd->timing_8996[10]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL12,
				DSIPHY_DLN2_TIMING_CTRL_6,
				DSIPHY_DLN3_TIMING_CTRL_6,
				pd->timing_8996[18],
				pd->timing_8996[26]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL13,
				DSIPHY_CKLN_TIMING_CTRL_6,
				DSIPHY_DLN0_TIMING_CTRL_7,
				pd->timing_8996[34],
				pd->timing_8996[3]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL14,
				DSIPHY_DLN1_TIMING_CTRL_7,
				DSIPHY_DLN2_TIMING_CTRL_7,
				pd->timing_8996[11],
				pd->timing_8996[19]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL15,
				DSIPHY_DLN3_TIMING_CTRL_7,
				DSIPHY_CKLN_TIMING_CTRL_7,
				pd->timing_8996[27],
				pd->timing_8996[35]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL16,
				DSIPHY_DLN0_TIMING_CTRL_8,
				DSIPHY_DLN1_TIMING_CTRL_8,
				pd->timing_8996[4],
				pd->timing_8996[12]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL17,
				DSIPHY_DLN2_TIMING_CTRL_8,
				DSIPHY_DLN3_TIMING_CTRL_8,
				pd->timing_8996[20],
				pd->timing_8996[28]);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL18,
				DSIPHY_CKLN_TIMING_CTRL_8,
				DSIPHY_CMN_CTRL_1,
				pd->timing_8996[36], 0);
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL30,
				DSIPHY_CMN_GLBL_TEST_CTRL,
				DSIPHY_CMN_GLBL_TEST_CTRL,
				((glbl_tst_cntrl) & (~BIT(2))),
				((glbl_tst_cntrl) & (~BIT(2))));
		MDSS_DYN_REF_REG_W(ctrl->ctrl_base,
				DSI_DYNAMIC_REFRESH_PLL_CTRL31,
				DSIPHY_CMN_GLBL_TEST_CTRL,
				DSIPHY_CMN_GLBL_TEST_CTRL,
				((glbl_tst_cntrl) & (~BIT(2))),
				((glbl_tst_cntrl) & (~BIT(2))));
	}

	wmb(); /* make sure phy timings are updated*/
}

void mdss_dsi_ctrl_phy_reset(struct mdss_dsi_ctrl_pdata *ctrl)
{
	/* start phy sw reset */
	MIPI_OUTP(ctrl->ctrl_base + 0x12c, 0x0001);
	udelay(1000);
	wmb();	/* make sure reset */
	/* end phy sw reset */
	MIPI_OUTP(ctrl->ctrl_base + 0x12c, 0x0000);
	udelay(100);
	wmb();	/* maek sure reset cleared */
}

int mdss_dsi_phy_pll_reset_status(struct mdss_dsi_ctrl_pdata *ctrl)
{
	int rc;
	u32 val;
	u32 const sleep_us = 10, timeout_us = 100;

	pr_debug("%s: polling for RESETSM_READY_STATUS.CORE_READY\n",
		__func__);
	rc = readl_poll_timeout(ctrl->phy_io.base + 0x4cc, val,
		(val & 0x1), sleep_us, timeout_us);

	return rc;
}

static void mdss_dsi_phy_sw_reset_sub(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_ctrl_pdata *sctrl = NULL;
	struct dsi_shared_data *sdata;
	struct mdss_dsi_ctrl_pdata *octrl;
	u32 reg_val = 0;

	if (ctrl == NULL) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	sdata = ctrl->shared_data;
	octrl = mdss_dsi_get_other_ctrl(ctrl);

	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20) {
		if (mdss_dsi_is_ctrl_clk_master(ctrl))
			sctrl = mdss_dsi_get_ctrl_clk_slave();
		else
			return;
	}

	/*
	 * For dual dsi case if we do DSI PHY sw reset,
	 * this will reset DSI PHY regulators also.
	 * Since DSI PHY regulator is shared among both
	 * the DSI controllers, we should not do DSI PHY
	 * sw reset when the other DSI controller is still
	 * active.
	 */
	mutex_lock(&sdata->phy_reg_lock);
	if ((mdss_dsi_is_hw_config_dual(sdata) &&
		(octrl && octrl->is_phyreg_enabled))) {
		/* start phy lane and HW reset */
		reg_val = MIPI_INP(ctrl->ctrl_base + 0x12c);
		reg_val |= (BIT(16) | BIT(8));
		MIPI_OUTP(ctrl->ctrl_base + 0x12c, reg_val);
		/* wait for 1ms as per HW design */
		usleep_range(1000, 2000);
		/* ensure phy lane and HW reset starts */
		wmb();
		/* end phy lane and HW reset */
		reg_val = MIPI_INP(ctrl->ctrl_base + 0x12c);
		reg_val &= ~(BIT(16) | BIT(8));
		MIPI_OUTP(ctrl->ctrl_base + 0x12c, reg_val);
		/* wait for 100us as per HW design */
		usleep_range(100, 200);
		/* ensure phy lane and HW reset ends */
		wmb();
	} else {
		/* start phy sw reset */
		mdss_dsi_ctrl_phy_reset(ctrl);
		if (sctrl)
			mdss_dsi_ctrl_phy_reset(sctrl);

	}
	mutex_unlock(&sdata->phy_reg_lock);
}

void mdss_dsi_phy_sw_reset(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_ctrl_pdata *sctrl = NULL;
	struct dsi_shared_data *sdata;

	if (ctrl == NULL) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	sdata = ctrl->shared_data;

	/*
	 * When operating in split display mode, make sure that the PHY reset
	 * is only done from the clock master. This will ensure that the PLL is
	 * off when PHY reset is called.
	 */
	if (mdss_dsi_is_ctrl_clk_slave(ctrl))
		return;

	mdss_dsi_phy_sw_reset_sub(ctrl);

	if (mdss_dsi_is_ctrl_clk_master(ctrl)) {
		sctrl = mdss_dsi_get_ctrl_clk_slave();
		if (sctrl)
			mdss_dsi_phy_sw_reset_sub(sctrl);
		else
			pr_warn("%s: unable to get slave ctrl\n", __func__);
	}

	/* All other quirks go here */
	if ((sdata->hw_rev == MDSS_DSI_HW_REV_103) &&
		!mdss_dsi_is_hw_config_dual(sdata) &&
		mdss_dsi_is_right_ctrl(ctrl)) {

		/*
		 * phy sw reset will wipe out the pll settings for PLL.
		 * Need to explicitly turn off PLL1 if unused to avoid
		 * current leakage issues.
		 */
		if ((mdss_dsi_is_hw_config_split(sdata) ||
			mdss_dsi_is_pll_src_pll0(sdata)) &&
			ctrl->vco_dummy_clk) {
			pr_debug("Turn off unused PLL1 registers\n");
			clk_set_rate(ctrl->vco_dummy_clk, 1);
		}
	}
}

static void mdss_dsi_phy_regulator_disable(struct mdss_dsi_ctrl_pdata *ctrl)
{
	if (!ctrl) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20)
		return;

	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30)
		return;

	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM)
		return;

	MIPI_OUTP(ctrl->phy_regulator_io.base + 0x018, 0x000);
}

static void mdss_dsi_phy_shutdown(struct mdss_dsi_ctrl_pdata *ctrl)
{
	if (!ctrl) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20) {
		MIPI_OUTP(ctrl->phy_io.base + DSIPHY_PLL_CLKBUFLR_EN, 0);
		MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_GLBL_TEST_CTRL, 0);
		MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0, 0);
	} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) {
		mdss_dsi_phy_v3_shutdown(ctrl);
	} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) {
		mdss_dsi_12nm_phy_shutdown(ctrl);
	} else {
		MIPI_OUTP(ctrl->phy_io.base + MDSS_DSI_DSIPHY_CTRL_0, 0x000);
	}
}

/**
 * mdss_dsi_lp_cd_rx() -- enable LP and CD at receiving
 * @ctrl: pointer to DSI controller structure
 *
 * LP: low power
 * CD: contention detection
 */
void mdss_dsi_lp_cd_rx(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_phy_ctrl *pd;

	if (!ctrl) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_20) ||
		(ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM))
		return;

	pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db);
	MIPI_OUTP((ctrl->phy_io.base) + 0x0188, pd->strength[1]);
	/* Strength ctrl 1, LP Rx + CD Rxcontention detection */
	wmb();
}

static void mdss_dsi_28nm_phy_regulator_enable(
		struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
	struct mdss_dsi_phy_ctrl *pd;

	pd = &(((ctrl_pdata->panel_data).panel_info.mipi).dsi_phy_db);

	if (pd->regulator_len == 0) {
		pr_warn("%s: invalid regulator settings\n", __func__);
		return;
	}

	if (pd->reg_ldo_mode) {
		/* Regulator ctrl 0 */
		MIPI_OUTP(ctrl_pdata->phy_regulator_io.base, 0x0);
		/* Regulator ctrl - CAL_PWR_CFG */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x18, pd->regulator[6]);
		/* Add H/w recommended delay */
		udelay(1000);
		/* Regulator ctrl - TEST */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x14, pd->regulator[5]);
		/* Regulator ctrl 3 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0xc, pd->regulator[3]);
		/* Regulator ctrl 2 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x8, pd->regulator[2]);
		/* Regulator ctrl 1 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x4, pd->regulator[1]);
		/* Regulator ctrl 4 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x10, pd->regulator[4]);
		/* LDO ctrl */
		if ((ctrl_pdata->shared_data->hw_rev ==
			MDSS_DSI_HW_REV_103_1)
			|| (ctrl_pdata->shared_data->hw_rev ==
			MDSS_DSI_HW_REV_104_2))
			MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x1dc, 0x05);
		else
			MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x1dc, 0x0d);
	} else {
		/* Regulator ctrl 0 */
		MIPI_OUTP(ctrl_pdata->phy_regulator_io.base,
					0x0);
		/* Regulator ctrl - CAL_PWR_CFG */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x18, pd->regulator[6]);
		/* Add H/w recommended delay */
		udelay(1000);
		/* Regulator ctrl 1 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x4, pd->regulator[1]);
		/* Regulator ctrl 2 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x8, pd->regulator[2]);
		/* Regulator ctrl 3 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0xc, pd->regulator[3]);
		/* Regulator ctrl 4 */
		MIPI_OUTP((ctrl_pdata->phy_regulator_io.base)
				+ 0x10, pd->regulator[4]);
		/* LDO ctrl */
		MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x1dc, 0x00);
		/* Regulator ctrl 0 */
		MIPI_OUTP(ctrl_pdata->phy_regulator_io.base,
				pd->regulator[0]);
	}
}

static void mdss_dsi_28nm_phy_config(struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
	struct mdss_dsi_phy_ctrl *pd;
	int i, off, ln, offset;

	if (!ctrl_pdata) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	pd = &(((ctrl_pdata->panel_data).panel_info.mipi).dsi_phy_db);

	/* Strength ctrl 0 for 28nm PHY*/
	if ((ctrl_pdata->shared_data->hw_rev <= MDSS_DSI_HW_REV_104_2) &&
		(ctrl_pdata->shared_data->hw_rev != MDSS_DSI_HW_REV_103)) {
		MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x0170, 0x5b);
		MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x0184, pd->strength[0]);
		/* make sure PHY strength ctrl is set */
		wmb();
	}

	off = 0x0140;	/* phy timing ctrl 0 - 11 */
	for (i = 0; i < 12; i++) {
		MIPI_OUTP((ctrl_pdata->phy_io.base) + off, pd->timing[i]);
		/* make sure phy timing register is programed */
		wmb();
		off += 4;
	}

	/* 4 lanes + clk lane configuration */
	/* lane config n * (0 - 4) & DataPath setup */
	for (ln = 0; ln < 5; ln++) {
		off = (ln * 0x40);
		for (i = 0; i < 9; i++) {
			offset = i + (ln * 9);
			MIPI_OUTP((ctrl_pdata->phy_io.base) + off,
							pd->lanecfg[offset]);
			/* make sure lane config register is programed */
			wmb();
			off += 4;
		}
	}

	MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x0180, 0x0a);
	/* MMSS_DSI_0_PHY_DSIPHY_CTRL_4 */
	wmb();

	/* DSI_0_PHY_DSIPHY_GLBL_TEST_CTRL */
	if (!mdss_dsi_is_hw_config_split(ctrl_pdata->shared_data)) {
		MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x01d4, 0x01);
	} else {
		if (((ctrl_pdata->panel_data).panel_info.pdest == DISPLAY_1) ||
		(ctrl_pdata->shared_data->hw_rev == MDSS_DSI_HW_REV_103_1))
			MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x01d4, 0x01);
		else
			MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x01d4, 0x00);
	}
	/* ensure DSIPHY_GLBL_TEST_CTRL is set */
	wmb();

	/* MMSS_DSI_0_PHY_DSIPHY_CTRL_0 */
	MIPI_OUTP((ctrl_pdata->phy_io.base) + 0x0170, 0x5f);
	/* make sure PHY lanes are powered on */
	wmb();

	off = 0x01b4;	/* phy BIST ctrl 0 - 5 */
	for (i = 0; i < 6; i++) {
		MIPI_OUTP((ctrl_pdata->phy_io.base) + off, pd->bistctrl[i]);
		/* make sure PHY bit control is configured */
		wmb();
		off += 4;
	}

}

static void mdss_dsi_20nm_phy_regulator_enable(struct mdss_dsi_ctrl_pdata
	*ctrl_pdata)
{
	struct mdss_dsi_phy_ctrl *pd;
	void __iomem *phy_io_base;

	pd = &(((ctrl_pdata->panel_data).panel_info.mipi).dsi_phy_db);
	phy_io_base = ctrl_pdata->phy_regulator_io.base;

	if (pd->regulator_len != 7) {
		pr_err("%s: wrong regulator settings\n", __func__);
		return;
	}

	if (pd->reg_ldo_mode) {
		MIPI_OUTP(ctrl_pdata->phy_io.base + MDSS_DSI_DSIPHY_LDO_CNTRL,
			0x1d);
	} else {
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CTRL_1,
			pd->regulator[1]);
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CTRL_2,
			pd->regulator[2]);
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CTRL_3,
			pd->regulator[3]);
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CTRL_4,
			pd->regulator[4]);
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CAL_PWR_CFG,
			pd->regulator[6]);
		MIPI_OUTP(ctrl_pdata->phy_io.base + MDSS_DSI_DSIPHY_LDO_CNTRL,
			0x00);
		MIPI_OUTP(phy_io_base + MDSS_DSI_DSIPHY_REGULATOR_CTRL_0,
			pd->regulator[0]);
	}
}

static void mdss_dsi_20nm_phy_config(struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
	struct mdss_dsi_phy_ctrl *pd;
	int i, off, ln, offset;

	pd = &(((ctrl_pdata->panel_data).panel_info.mipi).dsi_phy_db);

	if (pd->strength_len != 2) {
		pr_err("%s: wrong strength ctrl\n", __func__);
		return;
	}

	MIPI_OUTP((ctrl_pdata->phy_io.base) + MDSS_DSI_DSIPHY_STRENGTH_CTRL_0,
		pd->strength[0]);


	if (!mdss_dsi_is_hw_config_dual(ctrl_pdata->shared_data)) {
		if (mdss_dsi_is_hw_config_split(ctrl_pdata->shared_data) ||
			mdss_dsi_is_left_ctrl(ctrl_pdata) ||
			(mdss_dsi_is_right_ctrl(ctrl_pdata) &&
			mdss_dsi_is_pll_src_pll0(ctrl_pdata->shared_data)))
			MIPI_OUTP((ctrl_pdata->phy_io.base) +
				MDSS_DSI_DSIPHY_GLBL_TEST_CTRL, 0x00);
		else
			MIPI_OUTP((ctrl_pdata->phy_io.base) +
				MDSS_DSI_DSIPHY_GLBL_TEST_CTRL, 0x01);
	} else {
		if (mdss_dsi_is_left_ctrl(ctrl_pdata))
			MIPI_OUTP((ctrl_pdata->phy_io.base) +
				MDSS_DSI_DSIPHY_GLBL_TEST_CTRL, 0x00);
		else
			MIPI_OUTP((ctrl_pdata->phy_io.base) +
				MDSS_DSI_DSIPHY_GLBL_TEST_CTRL, 0x01);
	}

	if (pd->lanecfg_len != 45) {
		pr_err("%s: wrong lane cfg\n", __func__);
		return;
	}

	/* 4 lanes + clk lane configuration */
	/* lane config n * (0 - 4) & DataPath setup */
	for (ln = 0; ln < 5; ln++) {
		off = (ln * 0x40);
		for (i = 0; i < 9; i++) {
			offset = i + (ln * 9);
			MIPI_OUTP((ctrl_pdata->phy_io.base) + off,
				pd->lanecfg[offset]);
			/* make sure lane config register is programed */
			wmb();
			off += 4;
		}
	}

	off = 0;	/* phy timing ctrl 0 - 11 */
	for (i = 0; i < 12; i++) {
		MIPI_OUTP((ctrl_pdata->phy_io.base) +
			MDSS_DSI_DSIPHY_TIMING_CTRL_0 + off, pd->timing[i]);
		wmb(); /* make sure phy timing register is programed */
		off += 4;
	}

	MIPI_OUTP((ctrl_pdata->phy_io.base) + MDSS_DSI_DSIPHY_CTRL_1, 0);
	/* make sure everything is written before enable */
	wmb();
	MIPI_OUTP((ctrl_pdata->phy_io.base) + MDSS_DSI_DSIPHY_CTRL_0, 0x7f);
}

static void mdss_dsi_8996_pll_source_standalone(
				struct mdss_dsi_ctrl_pdata *ctrl)
{
	u32 data;

	/*
	 * pll right output enabled
	 * bit clk select from left
	 */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_PLL_CLKBUFLR_EN, 0x01);
	data = MIPI_INP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL);
	data &= ~BIT(2);
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL, data);
}

static void mdss_dsi_8996_pll_source_from_right(
				struct mdss_dsi_ctrl_pdata *ctrl)
{
	u32 data;

	/*
	 * pll left + right output disabled
	 * bit clk select from right
	 */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_PLL_CLKBUFLR_EN, 0x00);
	data = MIPI_INP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL);
	data |= BIT(2);
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL, data);

	/* enable bias current for pll1 during split display case */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_PLL_PLL_BANDGAP, 0x3);
}

static void mdss_dsi_8996_pll_source_from_left(
				struct mdss_dsi_ctrl_pdata *ctrl)
{
	u32 data;

	/*
	 * pll left + right output enabled
	 * bit clk select from left
	 */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_PLL_CLKBUFLR_EN, 0x03);
	data = MIPI_INP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL);
	data &= ~BIT(2);
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL, data);
}

static void mdss_dsi_8996_phy_regulator_enable(
	struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_phy_ctrl *pd;
	int j, off, ln, cnt, ln_off;
	char *ip;
	void __iomem *base;
	struct mdss_panel_info *panel_info;

	if (!ctrl) {
		pr_warn("%s: null ctrl pdata\n", __func__);
		return;
	}

	panel_info = &((ctrl->panel_data).panel_info);
	pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db);

	if (pd->regulator_len != (MDSS_DSI_NUM_DATA_LANES +
					MDSS_DSI_NUM_CLK_LANES)) {
		pr_warn("%s: invalid regulator settings\n", __func__);
		return;
	}

	/*
	 * data lane offset from base: 0x100
	 * data lane size: 0x80
	 */
	base = ctrl->phy_io.base + DATALANE_OFFSET_FROM_BASE_8996;
	/* data lanes configuration */
	for (ln = 0; ln < MDSS_DSI_NUM_DATA_LANES; ln++) {
		/* vreg ctrl, 1 * MDSS_DSI_NUM_DATA_LANES */
		cnt = DSIPHY_LANE_VREG_NUM;
		off = DSIPHY_LANE_VREG_BASE;
		ln_off = cnt * ln;
		ip = &pd->regulator[ln_off];
		for (j = 0; j < cnt; j++) {
			MIPI_OUTP(base + off, *ip++);
			off += DSIPHY_LANE_VREG_OFFSET;
		}
		base += DATALANE_SIZE_8996; /* next lane */
	}

	/*
	 * clk lane offset from base: 0x300
	 * clk lane size: 0x80
	 */
	base = ctrl->phy_io.base + CLKLANE_OFFSET_FROM_BASE_8996;
	/*
	 * clk lane configuration for vreg ctrl
	 * for split link there are two clock lanes, one
	 * clock lane per sublink needs to be configured
	 */
	off = DSIPHY_LANE_VREG_BASE;
	ln_off = MDSS_DSI_NUM_DATA_LANES;
	ip = &pd->regulator[ln_off];
	MIPI_OUTP(base + off, *ip);
	if (panel_info->split_link_enabled)
		MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, *ip);

	wmb(); /* make sure registers committed */
}

static void mdss_dsi_8996_phy_power_off(
	struct mdss_dsi_ctrl_pdata *ctrl)
{
	int ln;
	void __iomem *base;
	u32 data;
	struct mdss_panel_info *panel_info;

	if (ctrl) {
		panel_info = &((ctrl->panel_data).panel_info);
	} else {
		pr_warn("%s: null ctrl pdata\n", __func__);
		return;
	}

	/* Turn off PLL power */
	data = MIPI_INP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0);
	MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0, data & ~BIT(7));

	/* data lanes configuration */
	base = ctrl->phy_io.base + DATALANE_OFFSET_FROM_BASE_8996;
	for (ln = 0; ln < MDSS_DSI_NUM_DATA_LANES; ln++) {
		/* turn off phy ldo */
		MIPI_OUTP(base + DSIPHY_LANE_VREG_BASE, 0x1c);
		base += DATALANE_SIZE_8996; /* next lane */
	}

	/* clk lane configuration */
	base = ctrl->phy_io.base + CLKLANE_OFFSET_FROM_BASE_8996;
	/* turn off phy ldo */
	MIPI_OUTP(base + DSIPHY_LANE_VREG_BASE, 0x1c);
	if (panel_info->split_link_enabled)
		MIPI_OUTP(base + CLKLANE_SIZE_8996 +
				DSIPHY_LANE_VREG_BASE, 0x1c);

	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_LDO_CNTRL, 0x1c);

	/* data lanes configuration */
	base = ctrl->phy_io.base + DATALANE_OFFSET_FROM_BASE_8996;
	for (ln = 0; ln < MDSS_DSI_NUM_DATA_LANES; ln++) {
		MIPI_OUTP(base + DSIPHY_LANE_STRENGTH_CTRL_1, 0x0);
		base += DATALANE_SIZE_8996; /* next lane */
	}

	/* clk lane configuration */
	base = ctrl->phy_io.base + CLKLANE_OFFSET_FROM_BASE_8996;
	MIPI_OUTP(base + DSIPHY_LANE_STRENGTH_CTRL_1, 0x0);
	if (panel_info->split_link_enabled)
		MIPI_OUTP(base + CLKLANE_SIZE_8996 +
				DSIPHY_LANE_STRENGTH_CTRL_1, 0x0);

	wmb(); /* make sure registers committed */
}

static void mdss_dsi_phy_power_off(
	struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_panel_info *pinfo;

	if (ctrl->phy_power_off)
		return;

	pinfo = &ctrl->panel_data.panel_info;

	if ((ctrl->shared_data->phy_rev != DSI_PHY_REV_20) ||
		!pinfo->allow_phy_power_off) {
		pr_debug("%s: ctrl%d phy rev:%d panel support for phy off:%d\n",
			__func__, ctrl->ndx, ctrl->shared_data->phy_rev,
			pinfo->allow_phy_power_off);
		return;
	}

	/* supported for phy rev 2.0 and if panel allows it*/
	mdss_dsi_8996_phy_power_off(ctrl);

	ctrl->phy_power_off = true;
}

static void mdss_dsi_8996_phy_power_on(
	struct mdss_dsi_ctrl_pdata *ctrl)
{
	int j, off, ln, cnt, ln_off;
	void __iomem *base;
	struct mdss_dsi_phy_ctrl *pd;
	char *ip;
	u32 data;
	struct mdss_panel_info *panel_info;

	if (ctrl) {
		panel_info = &((ctrl->panel_data).panel_info);
	} else {
		pr_warn("%s: null ctrl pdata\n", __func__);
		return;
	}

	pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db);

	/* data lanes configuration */
	base = ctrl->phy_io.base + DATALANE_OFFSET_FROM_BASE_8996;
	for (ln = 0; ln < MDSS_DSI_NUM_DATA_LANES; ln++) {
		/* strength, 2 * MDSS_DSI_NUM_DATA_LANES */
		cnt = DSIPHY_LANE_STRENGTH_CTRL_NUM;
		ln_off = cnt * ln;
		ip = &pd->strength[ln_off];
		off = DSIPHY_LANE_STRENGTH_CTRL_BASE;
		for (j = 0; j < cnt; j++,
			off += DSIPHY_LANE_STRENGTH_CTRL_OFFSET)
			MIPI_OUTP(base + off, *ip++);
		base += DATALANE_SIZE_8996; /* next lane */
	}

	/*
	 * clk lane configuration for strength ctrl
	 * for split link there are two clock lanes, one
	 * clock lane per sublink needs to be configured
	 */
	base = ctrl->phy_io.base + CLKLANE_OFFSET_FROM_BASE_8996;
	cnt = DSIPHY_LANE_STRENGTH_CTRL_NUM;
	ln_off = MDSS_DSI_NUM_DATA_LANES;
	ip = &pd->strength[ln_off];
	off = DSIPHY_LANE_STRENGTH_CTRL_BASE;
	for (j = 0; j < cnt; j++,
		off += DSIPHY_LANE_STRENGTH_CTRL_OFFSET) {
		MIPI_OUTP(base + off, *ip);
		if (panel_info->split_link_enabled)
			MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, *ip);
	}

	mdss_dsi_8996_phy_regulator_enable(ctrl);

	/* Turn on PLL power */
	data = MIPI_INP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0);
	MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0, data | BIT(7));
}

static void mdss_dsi_phy_power_on(
	struct mdss_dsi_ctrl_pdata *ctrl, bool mmss_clamp)
{
	if (mmss_clamp && !ctrl->phy_power_off)
		mdss_dsi_phy_init(ctrl);
	else if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_20) &&
	    ctrl->phy_power_off)
		mdss_dsi_8996_phy_power_on(ctrl);

	ctrl->phy_power_off = false;
}

static void mdss_dsi_8996_phy_config(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_phy_ctrl *pd;
	int j, off, ln, cnt, ln_off;
	char *ip;
	void __iomem *base;
	struct mdss_panel_info *panel_info;
	int num_of_lanes = 0;

	if (ctrl) {
		panel_info = &((ctrl->panel_data).panel_info);
	} else {
		pr_warn("%s: null ctrl pdata\n", __func__);
		return;
	}

	pd = &(((ctrl->panel_data).panel_info.mipi).dsi_phy_db);
	num_of_lanes = MDSS_DSI_NUM_DATA_LANES + MDSS_DSI_NUM_CLK_LANES;

	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_LDO_CNTRL, 0x1c);

	/* clk_en */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_GLBL_TEST_CTRL, 0x1);

	if (pd->lanecfg_len != (num_of_lanes * DSIPHY_LANE_CFG_NUM)) {
		pr_err("%s: wrong lane cfg\n", __func__);
		return;
	}

	if (pd->strength_len != (num_of_lanes *
				DSIPHY_LANE_STRENGTH_CTRL_NUM)) {
		pr_err("%s: wrong strength ctrl\n", __func__);
		return;
	}

	if (pd->regulator_len != (num_of_lanes * DSIPHY_LANE_VREG_NUM)) {
		pr_err("%s: wrong regulator setting\n", __func__);
		return;
	}

	/* data lanes configuration */
	base = ctrl->phy_io.base + DATALANE_OFFSET_FROM_BASE_8996;
	for (ln = 0; ln < MDSS_DSI_NUM_DATA_LANES; ln++) {
		/* lane cfg, 4 * MDSS_DSI_NUM_DATA_LANES */
		cnt = DSIPHY_LANE_CFG_NUM;
		off = DSIPHY_LANE_CFG_BASE;
		ln_off = cnt * ln;
		ip = &pd->lanecfg[ln_off];
		for (j = 0; j < cnt; j++) {
			MIPI_OUTP(base + off, *ip++);
			off += DSIPHY_LANE_CFG_OFFSET;
		}

		/* test str */
		MIPI_OUTP(base + DSIPHY_LANE_TEST_STR, 0x88);	/* fixed */

		/* phy timing, 8 * MDSS_DSI_NUM_DATA_LANES */
		cnt = DSIPHY_LANE_TIMING_CTRL_NUM;
		off = DSIPHY_LANE_TIMING_CTRL_BASE;
		ln_off = cnt * ln;
		ip = &pd->timing_8996[ln_off];
		for (j = 0; j < cnt; j++) {
			MIPI_OUTP(base + off, *ip++);
			off += DSIPHY_LANE_TIMING_CTRL_OFFSET;
		}

		/* strength, 2 * MDSS_DSI_NUM_DATA_LANES */
		cnt = DSIPHY_LANE_STRENGTH_CTRL_NUM;
		off = DSIPHY_LANE_STRENGTH_CTRL_BASE;
		ln_off = cnt * ln;
		ip = &pd->strength[ln_off];
		for (j = 0; j < cnt; j++) {
			MIPI_OUTP(base + off, *ip++);
			off += DSIPHY_LANE_STRENGTH_CTRL_OFFSET;
		}

		base += DATALANE_SIZE_8996; /* next lane */
	}

	/*
	 * clk lane configuration
	 * for split link there are two clock lanes, one
	 * clock lane per sublink needs to be configured
	 */
	base = ctrl->phy_io.base + CLKLANE_OFFSET_FROM_BASE_8996;
	cnt = DSIPHY_LANE_CFG_NUM;
	off = DSIPHY_LANE_CFG_BASE;
	ln_off = cnt * MDSS_DSI_NUM_DATA_LANES;
	ip = &pd->lanecfg[ln_off];
	for (j = 0; j < cnt; j++) {
		MIPI_OUTP(base + off, *ip);
		if (panel_info->split_link_enabled)
			MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, *ip);
		ip++;
		off += DSIPHY_LANE_CFG_OFFSET;
	}

	/* test str */
	MIPI_OUTP(base + DSIPHY_LANE_TEST_STR, 0x88);	/* fixed */
	if (panel_info->split_link_enabled)
		MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, 0x88);

	cnt = DSIPHY_LANE_TIMING_CTRL_NUM;
	off = DSIPHY_LANE_TIMING_CTRL_BASE;
	ln_off = cnt * MDSS_DSI_NUM_DATA_LANES;
	ip = &pd->timing_8996[ln_off];
	for (j = 0; j < cnt; j++) {
		MIPI_OUTP(base + off, *ip);
		if (panel_info->split_link_enabled)
			MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, *ip);
		ip++;
		off += DSIPHY_LANE_TIMING_CTRL_OFFSET;
	}

	/*
	 * clk lane configuration for timing
	 * for split link there are two clock lanes, one
	 * clock lane per sublink needs to be configured
	 */
	cnt = DSIPHY_LANE_STRENGTH_CTRL_NUM;
	off = DSIPHY_LANE_STRENGTH_CTRL_BASE;
	ln_off = cnt * MDSS_DSI_NUM_DATA_LANES;
	ip = &pd->strength[ln_off];
	for (j = 0; j < cnt; j++) {
		MIPI_OUTP(base + off, *ip);
		if (panel_info->split_link_enabled)
			MIPI_OUTP(base + CLKLANE_SIZE_8996 + off, *ip);
		ip++;
		off += DSIPHY_LANE_STRENGTH_CTRL_OFFSET;
	}

	wmb(); /* make sure registers committed */

	/* reset digital block */
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_CTRL_1, 0x80);
	udelay(100);
	MIPI_OUTP((ctrl->phy_io.base) + DSIPHY_CMN_CTRL_1, 0x00);

	if (mdss_dsi_is_hw_config_split(ctrl->shared_data)) {
		if (mdss_dsi_is_left_ctrl(ctrl))
			mdss_dsi_8996_pll_source_from_left(ctrl);
		else
			mdss_dsi_8996_pll_source_from_right(ctrl);
	} else {
		if (mdss_dsi_is_right_ctrl(ctrl) &&
			mdss_dsi_is_pll_src_pll0(ctrl->shared_data))
			mdss_dsi_8996_pll_source_from_left(ctrl);
		else
			mdss_dsi_8996_pll_source_standalone(ctrl);
	}

	MIPI_OUTP(ctrl->phy_io.base + DSIPHY_CMN_CTRL_0, 0x7f);
	wmb(); /* make sure registers committed */
}

static void mdss_dsi_phy_regulator_ctrl(struct mdss_dsi_ctrl_pdata *ctrl,
	bool enable)
{
	struct mdss_dsi_ctrl_pdata *other_ctrl;
	struct dsi_shared_data *sdata;

	if (!ctrl) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	sdata = ctrl->shared_data;
	other_ctrl = mdss_dsi_get_other_ctrl(ctrl);

	mutex_lock(&sdata->phy_reg_lock);
	if (enable) {
		if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20) {
			mdss_dsi_8996_phy_regulator_enable(ctrl);
		} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) {
			mdss_dsi_phy_v3_regulator_enable(ctrl);
		} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) {
			mdss_dsi_12nm_phy_regulator_enable(ctrl);
		} else {
			switch (ctrl->shared_data->hw_rev) {
			case MDSS_DSI_HW_REV_103:
				mdss_dsi_20nm_phy_regulator_enable(ctrl);
				break;
			default:
			/*
			 * For dual dsi case, do not reconfigure dsi phy
			 * regulator if the other dsi controller is still
			 * active.
			 */
			if (!mdss_dsi_is_hw_config_dual(sdata) ||
				(other_ctrl && (!other_ctrl->is_phyreg_enabled
					|| other_ctrl->mmss_clamp)))
				mdss_dsi_28nm_phy_regulator_enable(ctrl);
			break;
			}
		}
		ctrl->is_phyreg_enabled = true;
	} else {
		/*
		 * In split-dsi/dual-dsi configuration, the dsi phy regulator
		 * should be turned off only when both the DSI devices are
		 * going to be turned off since it is shared.
		 */
		if (mdss_dsi_is_hw_config_split(ctrl->shared_data) ||
			mdss_dsi_is_hw_config_dual(ctrl->shared_data)) {
			if (other_ctrl && !other_ctrl->is_phyreg_enabled)
				mdss_dsi_phy_regulator_disable(ctrl);
		} else {
			mdss_dsi_phy_regulator_disable(ctrl);
		}
		ctrl->is_phyreg_enabled = false;
	}
	mutex_unlock(&sdata->phy_reg_lock);
}

static void mdss_dsi_phy_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, bool enable)
{
	struct mdss_dsi_ctrl_pdata *other_ctrl;

	if (!ctrl) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	if (enable) {

		if (ctrl->shared_data->phy_rev == DSI_PHY_REV_20) {
			mdss_dsi_8996_phy_config(ctrl);
		} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30) {
			mdss_dsi_phy_v3_init(ctrl, DSI_PHY_MODE_DPHY);
		} else if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM) {
			mdss_dsi_12nm_phy_config(ctrl);
		} else {
			switch (ctrl->shared_data->hw_rev) {
			case MDSS_DSI_HW_REV_103:
				mdss_dsi_20nm_phy_config(ctrl);
				break;
			default:
				mdss_dsi_28nm_phy_config(ctrl);
				break;
			}
		}
	} else {
		/*
		 * In split-dsi configuration, the phy should be disabled for
		 * the first controller only when the second controller is
		 * disabled. This is true regardless of whether broadcast
		 * mode is enabled.
		 */
		if (mdss_dsi_is_hw_config_split(ctrl->shared_data)) {
			other_ctrl = mdss_dsi_get_other_ctrl(ctrl);
			if (mdss_dsi_is_right_ctrl(ctrl) && other_ctrl) {
				mdss_dsi_phy_shutdown(other_ctrl);
				mdss_dsi_phy_shutdown(ctrl);
			}
		} else {
			mdss_dsi_phy_shutdown(ctrl);
		}
	}
}

void mdss_dsi_phy_disable(struct mdss_dsi_ctrl_pdata *ctrl)
{
	if (ctrl == NULL) {
		pr_err("%s: Invalid input data\n", __func__);
		return;
	}

	mdss_dsi_phy_ctrl(ctrl, false);
	mdss_dsi_phy_regulator_ctrl(ctrl, false);
	/*
	 * Wait for the registers writes to complete in order to
	 * ensure that the phy is completely disabled
	 */
	wmb();
}

static void mdss_dsi_phy_init_sub(struct mdss_dsi_ctrl_pdata *ctrl)
{
	mdss_dsi_phy_regulator_ctrl(ctrl, true);
	mdss_dsi_phy_ctrl(ctrl, true);
}

void mdss_dsi_phy_init(struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct mdss_dsi_ctrl_pdata *sctrl = NULL;

	/*
	 * When operating in split display mode, make sure that both the PHY
	 * blocks are initialized together prior to the PLL being enabled. This
	 * is achieved by calling the phy_init function for the clk_slave from
	 * the clock_master.
	 */
	if (mdss_dsi_is_ctrl_clk_slave(ctrl))
		return;

	mdss_dsi_phy_init_sub(ctrl);

	if (mdss_dsi_is_ctrl_clk_master(ctrl)) {
		sctrl = mdss_dsi_get_ctrl_clk_slave();
		if (sctrl)
			mdss_dsi_phy_init_sub(sctrl);
		else
			pr_warn("%s: unable to get slave ctrl\n", __func__);
	}
}

static void mdss_dsi_phy_hstx_drv_ctrl(
	struct mdss_dsi_ctrl_pdata *ctrl, bool enable)
{
	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_12NM)
		mdss_dsi_12nm_phy_hstx_drv_ctrl(ctrl, enable);
}

void mdss_dsi_core_clk_deinit(struct device *dev, struct dsi_shared_data *sdata)
{
}

int mdss_dsi_clk_refresh(struct mdss_panel_data *pdata, bool update_phy)
{
	struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
	struct mdss_panel_info *pinfo = NULL;
	int rc = 0;

	if (!pdata) {
		pr_err("%s: invalid panel data\n", __func__);
		return -EINVAL;
	}

	ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
							panel_data);
	pinfo = &pdata->panel_info;

	if (!ctrl_pdata || !pinfo) {
		pr_err("%s: invalid ctrl data\n", __func__);
		return -EINVAL;
	}

	if (update_phy) {
		pinfo->mipi.frame_rate = mdss_panel_calc_frame_rate(pinfo);
		pr_debug("%s: new frame rate %d\n",
				__func__, pinfo->mipi.frame_rate);
	}

	pinfo->clk_rate = mdss_dsi_calc_bitclk(pinfo, pinfo->mipi.frame_rate);
	if (!pinfo->clk_rate) {
		pr_err("%s: unable to calculate the DSI bit clock\n", __func__);
		return -EINVAL;
	}

	pinfo->mipi.dsi_pclk_rate = mdss_dsi_get_pclk_rate(pinfo,
		pinfo->clk_rate);
	if (!pinfo->mipi.dsi_pclk_rate) {
		pr_err("%s: unable to calculate the DSI pclk\n", __func__);
		return -EINVAL;
	}

	ctrl_pdata->refresh_clk_rate = false;
	ctrl_pdata->pclk_rate = pdata->panel_info.mipi.dsi_pclk_rate;
	ctrl_pdata->byte_clk_rate = pdata->panel_info.clk_rate / 8;
	pr_debug("%s ctrl_pdata->byte_clk_rate=%d ctrl_pdata->pclk_rate=%d\n",
		__func__, ctrl_pdata->byte_clk_rate, ctrl_pdata->pclk_rate);

	rc = mdss_dsi_clk_set_link_rate(ctrl_pdata->dsi_clk_handle,
			MDSS_DSI_LINK_BYTE_CLK, ctrl_pdata->byte_clk_rate,
			MDSS_DSI_CLK_UPDATE_CLK_RATE_AT_ON);
	if (rc) {
		pr_err("%s: dsi_byte_clk - clk_set_rate failed\n",
				__func__);
		return rc;
	}

	rc = mdss_dsi_clk_set_link_rate(ctrl_pdata->dsi_clk_handle,
			MDSS_DSI_LINK_PIX_CLK, ctrl_pdata->pclk_rate,
			MDSS_DSI_CLK_UPDATE_CLK_RATE_AT_ON);
	if (rc) {
		pr_err("%s: dsi_pixel_clk - clk_set_rate failed\n",
				__func__);
		return rc;
	}

	if (update_phy) {
		/* phy panel timing calaculation */
		rc = mdss_dsi_phy_calc_timing_param(pinfo,
				ctrl_pdata->shared_data->phy_rev,
				pdata->panel_info.clk_rate);
		if (rc) {
			pr_err("Error in calculating phy timings\n");
			return rc;
		}
		ctrl_pdata->update_phy_timing = false;
	}

	return rc;
}

int mdss_dsi_core_clk_init(struct platform_device *pdev,
	struct dsi_shared_data *sdata)
{
	struct device *dev = NULL;
	int rc = 0;

	if (!pdev) {
		pr_err("%s: Invalid pdev\n", __func__);
		goto error;
	}

	dev = &pdev->dev;

	/* Mandatory Clocks */
	sdata->mdp_core_clk = devm_clk_get(dev, "mdp_core_clk");
	if (IS_ERR(sdata->mdp_core_clk)) {
		rc = PTR_ERR(sdata->mdp_core_clk);
		pr_err("%s: Unable to get mdp core clk. rc=%d\n",
			__func__, rc);
		goto error;
	}

	sdata->ahb_clk = devm_clk_get(dev, "iface_clk");
	if (IS_ERR(sdata->ahb_clk)) {
		rc = PTR_ERR(sdata->ahb_clk);
		pr_err("%s: Unable to get mdss ahb clk. rc=%d\n",
			__func__, rc);
		goto error;
	}

	sdata->axi_clk = devm_clk_get(dev, "bus_clk");
	if (IS_ERR(sdata->axi_clk)) {
		rc = PTR_ERR(sdata->axi_clk);
		pr_err("%s: Unable to get axi bus clk. rc=%d\n",
			__func__, rc);
		goto error;
	}

	/* Optional Clocks */
	sdata->ext_byte0_clk = devm_clk_get(dev, "ext_byte0_clk");
	if (IS_ERR(sdata->ext_byte0_clk)) {
		pr_debug("%s: unable to get byte0 clk rcg. rc=%d\n",
			__func__, rc);
		sdata->ext_byte0_clk = NULL;
	}

	sdata->ext_pixel0_clk = devm_clk_get(dev, "ext_pixel0_clk");
	if (IS_ERR(sdata->ext_pixel0_clk)) {
		pr_debug("%s: unable to get pixel0 clk rcg. rc=%d\n",
			__func__, rc);
		sdata->ext_pixel0_clk = NULL;
	}

	sdata->ext_byte1_clk = devm_clk_get(dev, "ext_byte1_clk");
	if (IS_ERR(sdata->ext_byte1_clk)) {
		pr_debug("%s: unable to get byte1 clk rcg. rc=%d\n",
			__func__, rc);
		sdata->ext_byte1_clk = NULL;
	}

	sdata->ext_pixel1_clk = devm_clk_get(dev, "ext_pixel1_clk");
	if (IS_ERR(sdata->ext_pixel1_clk)) {
		pr_debug("%s: unable to get pixel1 clk rcg. rc=%d\n",
			__func__, rc);
		sdata->ext_pixel1_clk = NULL;
	}

	sdata->mmss_misc_ahb_clk = devm_clk_get(dev, "core_mmss_clk");
	if (IS_ERR(sdata->mmss_misc_ahb_clk)) {
		sdata->mmss_misc_ahb_clk = NULL;
		pr_debug("%s: Unable to get mmss misc ahb clk\n",
			__func__);
	}

	sdata->mnoc_clk = devm_clk_get(dev, "mnoc_clk");
	if (IS_ERR(sdata->mnoc_clk)) {
		pr_debug("%s: Unable to get mnoc clk\n", __func__);
		sdata->mnoc_clk = NULL;
	}

error:
	if (rc)
		mdss_dsi_core_clk_deinit(dev, sdata);
	return rc;
}

void mdss_dsi_link_clk_deinit(struct device *dev,
	struct mdss_dsi_ctrl_pdata *ctrl)
{
}

int mdss_dsi_link_clk_init(struct platform_device *pdev,
	struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct device *dev = NULL;
	int rc = 0;

	if (!pdev) {
		pr_err("%s: Invalid pdev\n", __func__);
		goto error;
	}

	dev = &pdev->dev;

	/* Mandatory Clocks */
	ctrl->byte_clk = devm_clk_get(dev, "byte_clk");
	if (IS_ERR(ctrl->byte_clk)) {
		rc = PTR_ERR(ctrl->byte_clk);
		pr_err("%s: can't find dsi_byte_clk. rc=%d\n",
			__func__, rc);
		ctrl->byte_clk = NULL;
		goto error;
	}

	ctrl->pixel_clk = devm_clk_get(dev, "pixel_clk");
	if (IS_ERR(ctrl->pixel_clk)) {
		rc = PTR_ERR(ctrl->pixel_clk);
		pr_err("%s: can't find dsi_pixel_clk. rc=%d\n",
			__func__, rc);
		ctrl->pixel_clk = NULL;
		goto error;
	}

	ctrl->esc_clk = devm_clk_get(dev, "core_clk");
	if (IS_ERR(ctrl->esc_clk)) {
		rc = PTR_ERR(ctrl->esc_clk);
		pr_err("%s: can't find dsi_esc_clk. rc=%d\n",
			__func__, rc);
		ctrl->esc_clk = NULL;
		goto error;
	}

	/* Optional Clocks */
	ctrl->byte_clk_rcg = devm_clk_get(dev, "byte_clk_rcg");
	if (IS_ERR(ctrl->byte_clk_rcg)) {
		pr_debug("%s: can't find byte clk rcg. rc=%d\n", __func__, rc);
		ctrl->byte_clk_rcg = NULL;
	}

	ctrl->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg");
	if (IS_ERR(ctrl->pixel_clk_rcg)) {
		pr_debug("%s: can't find pixel clk rcg. rc=%d\n", __func__, rc);
		ctrl->pixel_clk_rcg = NULL;
	}

	ctrl->vco_dummy_clk = devm_clk_get(dev, "pll_vco_dummy_clk");
	if (IS_ERR(ctrl->vco_dummy_clk)) {
		pr_debug("%s: can't find vco dummy clk. rc=%d\n", __func__, rc);
		ctrl->vco_dummy_clk = NULL;
	}

	ctrl->byte_intf_clk = devm_clk_get(dev, "byte_intf_clk");
	if (IS_ERR(ctrl->byte_intf_clk)) {
		pr_debug("%s: can't find byte int clk. rc=%d\n", __func__, rc);
		ctrl->byte_intf_clk = NULL;
	}


error:
	if (rc)
		mdss_dsi_link_clk_deinit(dev, ctrl);
	return rc;
}

void mdss_dsi_shadow_clk_deinit(struct device *dev,
	struct mdss_dsi_ctrl_pdata *ctrl)
{
}

int mdss_dsi_shadow_clk_init(struct platform_device *pdev,
		struct mdss_dsi_ctrl_pdata *ctrl)
{
	struct device *dev = NULL;
	int rc = 0;

	if (!pdev) {
		pr_err("%s: Invalid pdev\n", __func__);
		return -EINVAL;
	}

	dev = &pdev->dev;
	ctrl->mux_byte_clk = devm_clk_get(dev, "pll_byte_clk_mux");
	if (IS_ERR(ctrl->mux_byte_clk)) {
		rc = PTR_ERR(ctrl->mux_byte_clk);
		pr_err("%s: can't find mux_byte_clk. rc=%d\n",
			__func__, rc);
		ctrl->mux_byte_clk = NULL;
		goto error;
	}

	ctrl->mux_pixel_clk = devm_clk_get(dev, "pll_pixel_clk_mux");
	if (IS_ERR(ctrl->mux_pixel_clk)) {
		rc = PTR_ERR(ctrl->mux_pixel_clk);
		pr_err("%s: can't find mdss_mux_pixel_clk. rc=%d\n",
			__func__, rc);
		ctrl->mux_pixel_clk = NULL;
		goto error;
	}

	ctrl->pll_byte_clk = devm_clk_get(dev, "pll_byte_clk_src");
	if (IS_ERR(ctrl->pll_byte_clk)) {
		rc = PTR_ERR(ctrl->pll_byte_clk);
		pr_err("%s: can't find pll_byte_clk. rc=%d\n",
			__func__, rc);
		ctrl->pll_byte_clk = NULL;
		goto error;
	}

	ctrl->pll_pixel_clk = devm_clk_get(dev, "pll_pixel_clk_src");
	if (IS_ERR(ctrl->pll_pixel_clk)) {
		rc = PTR_ERR(ctrl->pll_pixel_clk);
		pr_err("%s: can't find pll_pixel_clk. rc=%d\n",
			__func__, rc);
		ctrl->pll_pixel_clk = NULL;
		goto error;
	}

	ctrl->shadow_byte_clk = devm_clk_get(dev, "pll_shadow_byte_clk_src");
	if (IS_ERR(ctrl->shadow_byte_clk)) {
		rc = PTR_ERR(ctrl->shadow_byte_clk);
		pr_err("%s: can't find shadow_byte_clk. rc=%d\n",
			__func__, rc);
		ctrl->shadow_byte_clk = NULL;
		goto error;
	}

	ctrl->shadow_pixel_clk = devm_clk_get(dev, "pll_shadow_pixel_clk_src");
	if (IS_ERR(ctrl->shadow_pixel_clk)) {
		rc = PTR_ERR(ctrl->shadow_pixel_clk);
		pr_err("%s: can't find shadow_pixel_clk. rc=%d\n",
			__func__, rc);
		ctrl->shadow_pixel_clk = NULL;
		goto error;
	}

error:
	if (rc)
		mdss_dsi_shadow_clk_deinit(dev, ctrl);
	return rc;
}

bool is_diff_frame_rate(struct mdss_panel_info *panel_info,
	u32 frame_rate)
{
	if (panel_info->dynamic_fps && panel_info->current_fps)
		return (frame_rate != panel_info->current_fps);
	else
		return (frame_rate != panel_info->mipi.frame_rate);
}

static u8 mdss_dsi_get_lane_cnt(struct mdss_panel_info *panel_info)
{
	u8 lanes = 0;

	if (!panel_info)
		return -EINVAL;

	if (panel_info->mipi.data_lane3)
		lanes += 1;
	if (panel_info->mipi.data_lane2)
		lanes += 1;
	if (panel_info->mipi.data_lane1)
		lanes += 1;
	if (panel_info->mipi.data_lane0)
		lanes += 1;

	if (!lanes)
		lanes = 1;

	return lanes;
}

static u8 mdss_dsi_get_bpp(char dst_format)
{
	u8 bpp = 0;

	switch (dst_format) {
	case DSI_CMD_DST_FORMAT_RGB888:
	case DSI_VIDEO_DST_FORMAT_RGB888:
	case DSI_VIDEO_DST_FORMAT_RGB666_LOOSE:
		bpp = 3;
		break;
	case DSI_CMD_DST_FORMAT_RGB565:
	case DSI_VIDEO_DST_FORMAT_RGB565:
		bpp = 2;
		break;
	default:
		bpp = 3;	/* Default format set to RGB888 */
		break;
	}
	return bpp;
}

u64 mdss_dsi_calc_bitclk(struct mdss_panel_info *panel_info, int frame_rate)
{
	struct mdss_panel_data *pdata  = container_of(panel_info,
		struct mdss_panel_data, panel_info);
	struct  mdss_dsi_ctrl_pdata *ctrl_pdata = container_of(pdata,
		struct mdss_dsi_ctrl_pdata, panel_data);
	u64 h_period, v_period, clk_rate = 0;
	u8 lanes = 0, bpp;

	lanes = mdss_dsi_get_lane_cnt(panel_info);

	bpp = mdss_dsi_get_bpp(panel_info->mipi.dst_format);

	h_period = mdss_panel_get_htotal(panel_info, true);
	if (panel_info->split_link_enabled)
		h_period *= panel_info->mipi.num_of_sublinks;
	v_period = mdss_panel_get_vtotal(panel_info);

	if (ctrl_pdata->refresh_clk_rate || is_diff_frame_rate(panel_info,
		frame_rate) || (!panel_info->clk_rate)) {
		clk_rate = h_period * v_period * frame_rate * bpp * 8;
		do_div(clk_rate, lanes);
	} else if (panel_info->clk_rate) {
		clk_rate = panel_info->clk_rate;
	}

	if (clk_rate == 0)
		clk_rate = 454000000;

	return clk_rate;
}

u32 mdss_dsi_get_pclk_rate(struct mdss_panel_info *panel_info, u64 clk_rate)
{
	u8 lanes = 0, bpp;
	u32 pclk_rate = 0;

	lanes = mdss_dsi_get_lane_cnt(panel_info);

	bpp = mdss_dsi_get_bpp(panel_info->mipi.dst_format);

	do_div(clk_rate, 8 * bpp);

	if (panel_info->split_link_enabled)
		pclk_rate = (u32) clk_rate *
			panel_info->mipi.lanes_per_sublink;
	else
		pclk_rate = (u32) clk_rate * lanes;

	if ((pclk_rate < 3300000) || (pclk_rate > 250000000))
		pclk_rate = 35000000;

	return pclk_rate;
}

static bool mdss_dsi_is_ulps_req_valid(struct mdss_dsi_ctrl_pdata *ctrl,
		int enable, bool reconfig)
{
	struct mdss_dsi_ctrl_pdata *octrl = NULL;
	struct mdss_panel_data *pdata = &ctrl->panel_data;
	struct mdss_panel_info *pinfo = &pdata->panel_info;

	pr_debug("%s: checking ulps req validity for ctrl%d\n",
		__func__, ctrl->ndx);

	if (!mdss_dsi_ulps_feature_enabled(pdata) &&
			!pinfo->ulps_suspend_enabled) {
		pr_debug("%s: ULPS feature is not enabled\n", __func__);
		return false;
	}

	/*
	 * No need to enter ULPS when transitioning from splash screen to
	 * boot animation since it is expected that the clocks would be turned
	 * right back on.
	 */
	if (enable && pinfo->cont_splash_enabled) {
		pr_debug("%s: skip ULPS config with splash screen enabled\n",
			__func__);
		return false;
	}

	/*
	 * No need to enable ULPS if panel is not yet initialized.
	 * However, this should be allowed in following usecases:
	 *   1. If ULPS during suspend feature is enabled, where we
	 *      configure the lanes in ULPS after turning off the panel.
	 *   2. When coming out of idle PC with ULPS enabled, where we need to
	 *      reconfigure the controller HW state again to ULPS prior to
	 *      disabling ULPS.
	 */
	if (enable && !reconfig &&
		!(ctrl->ctrl_state & CTRL_STATE_PANEL_INIT) &&
		!pdata->panel_info.ulps_suspend_enabled) {
		pr_debug("%s: panel not yet initialized\n", __func__);
		return false;
	}

	/*
	 * For split-DSI usecase, wait till both controllers are initialized.
	 * The same exceptions as above are applicable here too.
	 */
	if (mdss_dsi_is_hw_config_split(ctrl->shared_data)) {
		octrl = mdss_dsi_get_other_ctrl(ctrl);
		if (enable && !ctrl->mmss_clamp && octrl &&
			!(octrl->ctrl_state & CTRL_STATE_PANEL_INIT) &&
			!pdata->panel_info.ulps_suspend_enabled) {
			pr_debug("%s: split-DSI, other ctrl not ready yet\n",
				__func__);
			return false;
		}
	}

	return true;
}

/**
 * mdss_dsi_ulps_config_default() - Program DSI lanes to enter/exit ULPS mode
 * @ctrl: pointer to DSI controller structure
 * @enable: true to enter ULPS, false to exit ULPS
 *
 * Executes the default hardware programming sequence to enter/exit DSI
 * Ultra-Low Power State (ULPS). This function would be called whenever there
 * are no hardware version sepcific functions for configuring ULPS mode. This
 * function assumes that the link and core clocks are already on.
 */
static int mdss_dsi_ulps_config_default(struct mdss_dsi_ctrl_pdata *ctrl,
	bool enable)
{
	int rc = 0;
	struct mdss_panel_data *pdata = NULL;
	struct mdss_panel_info *pinfo;
	struct mipi_panel_info *mipi;
	u32 lane_status = 0;
	u32 active_lanes = 0;

	if (!ctrl) {
		pr_err("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	pdata = &ctrl->panel_data;
	if (!pdata) {
		pr_err("%s: Invalid panel data\n", __func__);
		return -EINVAL;
	}
	pinfo = &pdata->panel_info;
	mipi = &pinfo->mipi;

	/* clock lane will always be programmed for ulps */
	active_lanes = BIT(4);
	/*
	 * make a note of all active data lanes for which ulps entry/exit
	 * is needed
	 */
	if (mipi->data_lane0)
		active_lanes |= BIT(0);
	if (mipi->data_lane1)
		active_lanes |= BIT(1);
	if (mipi->data_lane2)
		active_lanes |= BIT(2);
	if (mipi->data_lane3)
		active_lanes |= BIT(3);

	pr_debug("%s: configuring ulps (%s) for ctrl%d, active lanes=0x%08x\n",
		__func__, (enable ? "on" : "off"), ctrl->ndx, active_lanes);

	if (enable) {
		/*
		 * ULPS Entry Request.
		 * Wait for a short duration to ensure that the lanes
		 * enter ULP state.
		 */
		MIPI_OUTP(ctrl->ctrl_base + 0x0AC, active_lanes);
		usleep_range(100, 110);

		/* Check to make sure that all active data lanes are in ULPS */
		lane_status = MIPI_INP(ctrl->ctrl_base + 0xA8);
		if (lane_status & (active_lanes << 8)) {
			pr_err("%s: ULPS entry req failed for ctrl%d. Lane status=0x%08x\n",
				__func__, ctrl->ndx, lane_status);
			rc = -EINVAL;
			goto error;
		}
	} else {
		/*
		 * ULPS Exit Request
		 * Hardware requirement is to wait for at least 1ms
		 */
		MIPI_OUTP(ctrl->ctrl_base + 0x0AC, active_lanes << 8);
		usleep_range(1000, 1010);

		/*
		 * Sometimes when exiting ULPS, it is possible that some DSI
		 * lanes are not in the stop state which could lead to DSI
		 * commands not going through. To avoid this, force the lanes
		 * to be in stop state.
		 */
		MIPI_OUTP(ctrl->ctrl_base + 0x0AC, active_lanes << 16);
		wmb(); /* ensure lanes are put to stop state */

		MIPI_OUTP(ctrl->ctrl_base + 0x0AC, 0x0);
		wmb(); /* ensure lanes are in proper state */

		lane_status = MIPI_INP(ctrl->ctrl_base + 0xA8);
	}

	pr_debug("%s: DSI lane status = 0x%08x. Ulps %s\n", __func__,
		lane_status, enable ? "enabled" : "disabled");

error:
	return rc;
}

/**
 * mdss_dsi_ulps_config() - Program DSI lanes to enter/exit ULPS mode
 * @ctrl: pointer to DSI controller structure
 * @enable: 1 to enter ULPS, 0 to exit ULPS
 * @reconfig: boolean to specify if DSI controller is reconfigured to enter ULPS
 *
 * Execute the necessary programming sequence to enter/exit DSI Ultra-Low Power
 * State (ULPS). This function the validity of the ULPS config request and
 * executes and pre/post steps before/after the necessary hardware programming.
 * This function assumes that the link and core clocks are already on.
 */
static int mdss_dsi_ulps_config(struct mdss_dsi_ctrl_pdata *ctrl,
	int enable, bool reconfig)
{
	int ret = 0;

	if (!ctrl) {
		pr_err("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	if (!mdss_dsi_is_ulps_req_valid(ctrl, enable, reconfig)) {
		pr_debug("%s: skiping ULPS config for ctrl%d, enable=%d\n",
			__func__, ctrl->ndx, enable);
		return 0;
	}

	pr_debug("%s: configuring ulps (%s) for ctrl%d, reconfig=%s\n",
		__func__, (enable ? "on" : "off"), ctrl->ndx,
		reconfig ? "true" : "false");

	if (enable && !ctrl->ulps) {
		/*
		 * Ensure that the lanes are idle prior to placing a ULPS entry
		 * request. This is needed to ensure that there is no overlap
		 * between any HS or LP commands being sent out on the lane and
		 * a potential ULPS entry request.
		 *
		 * This check needs to be avoided when we are resuming from idle
		 * power collapse and just restoring the controller state to
		 * ULPS with the clamps still in place.
		 */
		if (!reconfig) {
			ret = mdss_dsi_wait_for_lane_idle(ctrl);
			if (ret) {
				pr_warn_ratelimited("%s: lanes not idle, skip ulps\n",
					__func__);
				ret = 0;
				goto error;
			}
		}

		if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30)
			ret = mdss_dsi_phy_v3_ulps_config(ctrl, true);
		else
			ret = mdss_dsi_ulps_config_default(ctrl, true);
		if (ret)
			goto error;

		ctrl->ulps = true;
	} else if (!enable && ctrl->ulps) {
		/*
		 * Clear out any phy errors prior to exiting ULPS
		 * This fixes certain instances where phy does not exit
		 * ULPS cleanly. Also, do not print error during such cases.
		 */
		mdss_dsi_dln0_phy_err(ctrl, false);

		if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30)
			ret = mdss_dsi_phy_v3_ulps_config(ctrl, false);
		else
			ret = mdss_dsi_ulps_config_default(ctrl, false);
		if (ret)
			goto error;

		/*
		 * Wait for a short duration before enabling
		 * data transmission
		 */
		usleep_range(100, 110);

		ctrl->ulps = false;
	} else {
		pr_debug("%s: No change requested: %s -> %s\n", __func__,
			ctrl->ulps ? "enabled" : "disabled",
			enable ? "enabled" : "disabled");
	}

error:
	return ret;
}

/**
 * mdss_dsi_phy_idle_pc_exit() - Called after exit Idle PC
 * @ctrl: pointer to DSI controller structure
 *
 * Perform any programming needed after Idle PC exit.
 */
static int mdss_dsi_phy_idle_pc_exit(struct mdss_dsi_ctrl_pdata *ctrl)
{
	if (ctrl->shared_data->phy_rev == DSI_PHY_REV_30)
		mdss_dsi_phy_v3_idle_pc_exit(ctrl);

	return 0;
}

/**
 * mdss_dsi_clamp_ctrl_default() - Program DSI clamps
 * @ctrl: pointer to DSI controller structure
 * @enable: true to enable clamps, false to disable clamps
 *
 * Execute the required programming sequence to configure DSI clamps.  This
 * function would be called whenever there are no hardware version sepcific
 * functions for programming the DSI clamps.  This function assumes that the
 * core clocks are already on.
 */
static int mdss_dsi_clamp_ctrl_default(struct mdss_dsi_ctrl_pdata *ctrl,
	bool enable)
{
	struct mipi_panel_info *mipi = NULL;
	u32 clamp_reg, regval = 0;
	u32 clamp_reg_off;
	u32 intf_num = 0;

	if (!ctrl) {
		pr_err("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	if (!ctrl->mmss_misc_io.base) {
		pr_err("%s: mmss_misc_io not mapped\n", __func__);
		return -EINVAL;
	}

	/*
	 * For DSI HW version 2.1.0 ULPS_CLAMP register
	 * is moved to interface level.
	 */
	if (ctrl->shared_data->hw_rev == MDSS_DSI_HW_REV_201) {
		intf_num = ctrl->ndx ? MDSS_MDP_INTF2 : MDSS_MDP_INTF1;
		if (ctrl->clamp_handler) {
			ctrl->clamp_handler->fxn(ctrl->clamp_handler->data,
				intf_num, enable);
			pr_debug("%s: ndx: %d enable: %d\n",
					__func__, ctrl->ndx, enable);
		}
		return 0;
	}

	clamp_reg_off = ctrl->shared_data->ulps_clamp_ctrl_off;
	mipi = &ctrl->panel_data.panel_info.mipi;

	/* clock lane will always be clamped */
	clamp_reg = BIT(9);
	if (ctrl->ulps)
		clamp_reg |= BIT(8);
	/* make a note of all active data lanes which need to be clamped */
	if (mipi->data_lane0) {
		clamp_reg |= BIT(7);
		if (ctrl->ulps)
			clamp_reg |= BIT(6);
	}
	if (mipi->data_lane1) {
		clamp_reg |= BIT(5);
		if (ctrl->ulps)
			clamp_reg |= BIT(4);
	}
	if (mipi->data_lane2) {
		clamp_reg |= BIT(3);
		if (ctrl->ulps)
			clamp_reg |= BIT(2);
	}
	if (mipi->data_lane3) {
		clamp_reg |= BIT(1);
		if (ctrl->ulps)
			clamp_reg |= BIT(0);
	}
	pr_debug("%s: called for ctrl%d, enable=%d, clamp_reg=0x%08x\n",
		__func__, ctrl->ndx, enable, clamp_reg);

	if (enable) {
		regval = MIPI_INP(ctrl->mmss_misc_io.base + clamp_reg_off);
		/* Enable MMSS DSI Clamps */
		if (ctrl->ndx == DSI_CTRL_0) {
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval | clamp_reg);
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval | (clamp_reg | BIT(15)));
		} else if (ctrl->ndx == DSI_CTRL_1) {
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval | (clamp_reg << 16));
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval | ((clamp_reg << 16) | BIT(31)));
		}
	} else {
		regval = MIPI_INP(ctrl->mmss_misc_io.base + clamp_reg_off);
		/* Disable MMSS DSI Clamps */
		if (ctrl->ndx == DSI_CTRL_0)
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval & ~(clamp_reg | BIT(15)));
		else if (ctrl->ndx == DSI_CTRL_1)
			MIPI_OUTP(ctrl->mmss_misc_io.base + clamp_reg_off,
				regval & ~((clamp_reg << 16) | BIT(31)));
	}

	/* make sure clamps are configured */
	wmb();

	return 0;
}

/**
 * mdss_dsi_clamp_ctrl() - Program DSI clamps for supporting power collapse
 * @ctrl: pointer to DSI controller structure
 * @enable: 1 to enable clamps, 0 to disable clamps
 *
 * For idle-screen usecases with command mode panels, MDSS can be power
 * collapsed. However, DSI phy needs to remain on. To avoid any mismatch
 * between the DSI controller state, DSI phy needs to be clamped before
 * power collapsing. This function executes the required programming
 * sequence to configure these DSI clamps. This function should only be called
 * when the DSI link clocks are disabled.
 */
static int mdss_dsi_clamp_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, int enable)
{
	int rc = 0;

	if (!ctrl) {
		pr_err("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	pr_debug("%s: called for ctrl%d, enable=%d\n", __func__, ctrl->ndx,
		enable);

	if (enable && !ctrl->mmss_clamp) {
		if (ctrl->shared_data->phy_rev < DSI_PHY_REV_30) {
			rc = mdss_dsi_clamp_ctrl_default(ctrl, true);
			if (rc)
				goto error;
		}
		ctrl->mmss_clamp = true;
	} else if (!enable && ctrl->mmss_clamp) {
		if (ctrl->shared_data->phy_rev < DSI_PHY_REV_30) {
			rc = mdss_dsi_clamp_ctrl_default(ctrl, false);
			if (rc)
				goto error;
		}
		ctrl->mmss_clamp = false;
	} else {
		pr_debug("%s: No change requested: %s -> %s\n", __func__,
			ctrl->mmss_clamp ? "enabled" : "disabled",
			enable ? "enabled" : "disabled");
	}

error:
	if (rc)
		pr_err("%s: failed to %s clamps for ctrl%d\n", __func__,
			enable ? "enable" : "disable", ctrl->ndx);

	return rc;
}

DEFINE_MUTEX(dsi_clk_mutex);

int mdss_dsi_clk_ctrl(struct mdss_dsi_ctrl_pdata *ctrl, void *clk_handle,
	enum mdss_dsi_clk_type clk_type, enum mdss_dsi_clk_state clk_state)
{
	int rc = 0;
	struct mdss_dsi_ctrl_pdata *mctrl = NULL;
	int i, *vote_cnt;

	void *m_clk_handle;
	bool is_ecg = false;
	int state = MDSS_DSI_CLK_OFF;

	if (!ctrl) {
		pr_err("%s: Invalid arg\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&dsi_clk_mutex);
	/*
	 * In sync_wait_broadcast mode, we need to enable clocks
	 * for the other controller as well when enabling clocks
	 * for the trigger controller.
	 *
	 * If sync wait_broadcase mode is not enabled, but if split display
	 * mode is enabled where both DSI controller's branch clocks are
	 * sourced out of a single PLL, then we need to ensure that the
	 * controller associated with that PLL also has it's clocks turned
	 * on. This is required to make sure that if that controller's PLL/PHY
	 * are clamped then they can be removed.
	 */
	if (mdss_dsi_sync_wait_trigger(ctrl)) {
		mctrl = mdss_dsi_get_other_ctrl(ctrl);
		if (!mctrl)
			pr_warn("%s: Unable to get other control\n", __func__);
	} else if (mdss_dsi_is_ctrl_clk_slave(ctrl)) {
		mctrl = mdss_dsi_get_ctrl_clk_master();
		if (!mctrl)
			pr_warn("%s: Unable to get clk master control\n",
				__func__);
	}

	/*
	 * it should add and remove extra votes based on voting clients to avoid
	 * removal of legitimate vote from DSI client.
	 */
	if (mctrl && (clk_handle == ctrl->dsi_clk_handle)) {
		m_clk_handle = mctrl->dsi_clk_handle;
		vote_cnt = &mctrl->m_dsi_vote_cnt;
	} else if (mctrl) {
		m_clk_handle = mctrl->mdp_clk_handle;
		vote_cnt = &mctrl->m_mdp_vote_cnt;
	}

	/*
	 * When DSI is used in split mode, the link clock for master controller
	 * has to be turned on first before the link clock for slave can be
	 * turned on. In case the current controller is a slave, an ON vote is
	 * cast for master before changing the state of the slave clock. After
	 * the state change for slave, the ON votes will be removed depending on
	 * the new state.
	 */
	pr_debug("%s: DSI_%d: clk = %d, state = %d, caller = %pS, mctrl=%d\n",
		 __func__, ctrl->ndx, clk_type, clk_state,
		 __builtin_return_address(0), mctrl ? 1 : 0);
	if (mctrl && (clk_type & MDSS_DSI_LINK_CLK)) {
		if (clk_state != MDSS_DSI_CLK_ON) {
			/* preserve clk state; do not turn off forcefully */
			is_ecg = is_dsi_clk_in_ecg_state(m_clk_handle);
			if (is_ecg)
				state = MDSS_DSI_CLK_EARLY_GATE;
		}

		rc = mdss_dsi_clk_req_state(m_clk_handle,
			MDSS_DSI_ALL_CLKS, MDSS_DSI_CLK_ON, mctrl->ndx);
		if (rc) {
			pr_err("%s: failed to turn on mctrl clocks, rc=%d\n",
				 __func__, rc);
			goto error;
		}
		(*vote_cnt)++;
	}

	rc = mdss_dsi_clk_req_state(clk_handle, clk_type, clk_state, ctrl->ndx);
	if (rc) {
		pr_err("%s: failed set clk state, rc = %d\n", __func__, rc);
		goto error;
	}

	if (mctrl && (clk_type & MDSS_DSI_LINK_CLK) &&
			clk_state != MDSS_DSI_CLK_ON) {

		/*
		 * In case of split dsi, an ON vote is cast for all state change
		 * requests. If the current state is ON, then the vote would not
		 * be released.
		 *
		 * If the current state is ECG, there is one possible way to
		 * transition in to this state, which is ON -> ECG. In this case
		 * two votes will be removed because one was cast at ON and
		 * other when entering ECG.
		 *
		 * If the current state is OFF, it could have been due to two
		 * possible transitions in to OFF state.
		 *	1. ON -> OFF: In this case two votes were cast by the
		 *	   slave controller, one during ON (which is not
		 *	   removed) and one during OFF. So we need to remove two
		 *	   votes.
		 *	2. ECG -> OFF: In this case there is only one vote
		 *	   for ON, since the previous ECG state must have
		 *	   removed two votes to let clocks turn off.
		 *
		 * To satisfy the above requirement, vote_cnt keeps track of
		 * the number of ON votes for master requested by slave. For
		 * every OFF/ECG state request, Either 2 or vote_cnt number of
		 * votes are removed depending on which is lower.
		 */
		for (i = 0; (i < *vote_cnt && i < 2); i++) {
			rc = mdss_dsi_clk_req_state(m_clk_handle,
				MDSS_DSI_ALL_CLKS, state, mctrl->ndx);
			if (rc) {
				pr_err("%s: failed to set mctrl clk state, rc = %d\n",
				       __func__, rc);
				goto error;
			}
		}
		(*vote_cnt) -= i;
		pr_debug("%s: ctrl=%d, vote_cnt=%d dsi_vote_cnt=%d mdp_vote_cnt:%d\n",
			__func__, ctrl->ndx, *vote_cnt, mctrl->m_dsi_vote_cnt,
			mctrl->m_mdp_vote_cnt);
	}

error:
	mutex_unlock(&dsi_clk_mutex);
	return rc;
}

int mdss_dsi_pre_clkoff_cb(void *priv,
			   enum mdss_dsi_clk_type clk,
			   enum mdss_dsi_lclk_type l_type,
			   enum mdss_dsi_clk_state new_state)
{
	int rc = 0;
	struct mdss_dsi_ctrl_pdata *ctrl = priv;
	struct mdss_panel_data *pdata = NULL;

	pdata = &ctrl->panel_data;

	if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_HS_CLK) &&
		(new_state == MDSS_DSI_CLK_OFF)) {
		/* Disable HS TX driver in DSI PHY if applicable */
		mdss_dsi_phy_hstx_drv_ctrl(ctrl, false);
	}

	if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_LP_CLK) &&
		(new_state == MDSS_DSI_CLK_OFF)) {
		if (pdata->panel_info.mipi.force_clk_lane_hs)
			mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 0);
		/*
		 * If ULPS feature is enabled, enter ULPS first.
		 * However, when blanking the panel, we should enter ULPS
		 * only if ULPS during suspend feature is enabled.
		 */
		if (!(ctrl->ctrl_state & CTRL_STATE_PANEL_INIT)) {
			if (pdata->panel_info.ulps_suspend_enabled)
				mdss_dsi_ulps_config(ctrl, 1, false);
		} else if (mdss_dsi_ulps_feature_enabled(pdata)) {
			rc = mdss_dsi_ulps_config(ctrl, 1, false);
		}
		if (rc) {
			pr_err("%s: failed enable ulps, rc = %d\n",
			       __func__, rc);
		}
	}

	if ((clk & MDSS_DSI_CORE_CLK) && (new_state == MDSS_DSI_CLK_OFF)) {
		/*
		 * Enable DSI clamps only if entering idle power collapse or
		 * when ULPS during suspend is enabled.
		 */
		if ((ctrl->ctrl_state & CTRL_STATE_DSI_ACTIVE) ||
			pdata->panel_info.ulps_suspend_enabled) {
			mdss_dsi_phy_power_off(ctrl);
			rc = mdss_dsi_clamp_ctrl(ctrl, 1);
			if (rc)
				pr_err("%s: Failed to enable dsi clamps. rc=%d\n",
					__func__, rc);
		} else {
			/*
			 * Make sure that controller is not in ULPS state when
			 * the DSI link is not active.
			 */
			rc = mdss_dsi_ulps_config(ctrl, 0, false);
			if (rc)
				pr_err("%s: failed to disable ulps. rc=%d\n",
					__func__, rc);
		}
	}

	return rc;
}

static void mdss_dsi_split_link_clk_cfg(struct mdss_dsi_ctrl_pdata *ctrl,
						int enable)
{
	struct mdss_panel_data *pdata = NULL;
	void __iomem *base;
	u32 data = 0;

	if (ctrl)
		pdata = &ctrl->panel_data;
	else {
		pr_err("%s: ctrl pdata is NULL\n", __func__);
		return;
	}

	/*
	 * for split link there are two clock lanes, and
	 * both clock lanes needs to be enabled
	 */
	if (pdata->panel_info.split_link_enabled) {
		base = ctrl->phy_io.base;
		data = MIPI_INP(base + MDSS_DSI_DSIPHY_CMN_CLK_CFG1);
		data |= (enable << DSIPHY_CMN_CLK_CFG1_SPLIT_LINK);
		MIPI_OUTP(base + MDSS_DSI_DSIPHY_CMN_CLK_CFG1, data);
	}
}

int mdss_dsi_post_clkon_cb(void *priv,
			   enum mdss_dsi_clk_type clk,
			   enum mdss_dsi_lclk_type l_type,
			   enum mdss_dsi_clk_state curr_state)
{
	int rc = 0;
	struct mdss_panel_data *pdata = NULL;
	struct mdss_dsi_ctrl_pdata *ctrl = priv;
	bool mmss_clamp;

	pdata = &ctrl->panel_data;

	if (clk & MDSS_DSI_CORE_CLK) {
		mmss_clamp = ctrl->mmss_clamp;
		/*
		 * controller setup is needed if coming out of idle
		 * power collapse with clamps enabled.
		 */
		if (mmss_clamp)
			mdss_dsi_ctrl_setup(ctrl);

		rc = mdss_dsi_clamp_ctrl(ctrl, 0);
		if (rc) {
			pr_err("%s: Failed to disable dsi clamps. rc=%d\n",
				__func__, rc);
			goto error;
		}

		/*
		 * Phy setup is needed if coming out of idle
		 * power collapse with clamps enabled.
		 */
		if (ctrl->phy_power_off || mmss_clamp)
			mdss_dsi_phy_power_on(ctrl, mmss_clamp);
	}

	if ((clk & MDSS_DSI_LINK_CLK) && (l_type == MDSS_DSI_LINK_LP_CLK)) {
		if (ctrl->ulps) {
			/*
			 * ULPS Entry Request. This is needed if the lanes were
			 * in ULPS prior to power collapse, since after
			 * power collapse and reset, the DSI controller resets
			 * back to idle state and not ULPS. This ulps entry
			 * request will transition the state of the DSI
			 * controller to ULPS which will match the state of the
			 * DSI phy. This needs to be done prior to disabling
			 * the DSI clamps.
			 *
			 * Also, reset the ulps flag so that ulps_config
			 * function would reconfigure the controller state to
			 * ULPS.
			 */
			ctrl->ulps = false;
			rc = mdss_dsi_ulps_config(ctrl, 1, true);
			if (rc) {
				pr_err("%s: Failed to enter ULPS. rc=%d\n",
					__func__, rc);
				goto error;
			}

			/* toggle the resync FIFO everytime clock changes */
			if ((ctrl->shared_data->phy_rev == DSI_PHY_REV_30) &&
					!pdata->panel_info.cont_splash_enabled)
				mdss_dsi_phy_v3_toggle_resync_fifo(ctrl);

			rc = mdss_dsi_ulps_config(ctrl, 0, false);
			if (rc) {
				pr_err("%s: failed to disable ulps, rc= %d\n",
				       __func__, rc);
				goto error;
			}
		}

		if (pdata->panel_info.mipi.force_clk_lane_hs)
			mdss_dsi_cfg_lane_ctrl(ctrl, BIT(28), 1);

		/* enable split link for cmn clk cfg1 */
		mdss_dsi_split_link_clk_cfg(ctrl, 1);

		/* Enable HS TX driver in DSI PHY if applicable */
		if ((clk & MDSS_DSI_LINK_CLK) &&
				(l_type == MDSS_DSI_LINK_HS_CLK))
			mdss_dsi_phy_hstx_drv_ctrl(ctrl, true);
	}


error:
	return rc;
}

int mdss_dsi_post_clkoff_cb(void *priv,
			    enum mdss_dsi_clk_type clk_type,
			    enum mdss_dsi_lclk_type l_type,
			    enum mdss_dsi_clk_state curr_state)
{
	int rc = 0;
	struct mdss_dsi_ctrl_pdata *ctrl = priv;
	struct mdss_panel_data *pdata = NULL;
	struct dsi_shared_data *sdata;
	int i;

	if (!ctrl) {
		pr_err("%s: Invalid arg\n", __func__);
		return -EINVAL;
	}

	if ((clk_type & MDSS_DSI_CORE_CLK) &&
	    (curr_state == MDSS_DSI_CLK_OFF)) {
		sdata = ctrl->shared_data;
		pdata = &ctrl->panel_data;

		for (i = DSI_MAX_PM - 1; i >= DSI_CORE_PM; i--) {
			/**
			 * If DSI_CTRL is active, proceed to turn off
			 * supplies which support turning off in low power
			 * state
			 */
			if (ctrl->ctrl_state & CTRL_STATE_DSI_ACTIVE)
				if (!sdata->power_data[i].vreg_config
						->lp_disable_allowed)
					continue;

			rc = msm_dss_enable_vreg(
				sdata->power_data[i].vreg_config,
				sdata->power_data[i].num_vreg, 0);
			if (rc) {
				pr_warn("%s: failed to disable vregs for %s\n",
					__func__,
					__mdss_dsi_pm_name(i));
				rc = 0;
			} else {
				pr_debug("%s: disabled vreg for %s panel_state %d\n",
					__func__,
					__mdss_dsi_pm_name(i),
					pdata->panel_info.panel_power_state);
				sdata->power_data[i].vreg_config->disabled =
					true;
				ctrl->core_power = false;
			}
		}
	}
	return rc;
}

int mdss_dsi_pre_clkon_cb(void *priv,
			  enum mdss_dsi_clk_type clk_type,
			  enum mdss_dsi_lclk_type l_type,
			  enum mdss_dsi_clk_state new_state)
{
	int rc = 0;
	struct mdss_dsi_ctrl_pdata *ctrl = priv;
	struct mdss_panel_data *pdata = NULL;
	struct dsi_shared_data *sdata;
	int i;

	if (!ctrl) {
		pr_err("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	if ((clk_type & MDSS_DSI_CORE_CLK) && (new_state == MDSS_DSI_CLK_ON) &&
	    (!ctrl->core_power)) {
		sdata = ctrl->shared_data;
		pdata = &ctrl->panel_data;
		/*
		 * Enable DSI core power
		 * 1.> PANEL_PM are controlled as part of
		 *     panel_power_ctrl. Needed not be handled here.
		 * 2.> CORE_PM are controlled by dsi clk manager.
		 * 3.> CTRL_PM need to be enabled/disabled
		 *     only during unblank/blank. Their state should
		 *     not be changed during static screen.
		 */
		pr_debug("%s: Enable DSI core power\n", __func__);
		for (i = DSI_CORE_PM; i < DSI_MAX_PM; i++) {
			if ((ctrl->ctrl_state & CTRL_STATE_DSI_ACTIVE) &&
				(!pdata->panel_info.cont_splash_enabled) &&
				(!sdata->power_data[i].vreg_config
						->lp_disable_allowed))
				continue;
			rc = msm_dss_enable_vreg(
				sdata->power_data[i].vreg_config,
				sdata->power_data[i].num_vreg, 1);
			if (rc) {
				pr_err("%s: failed to enable vregs for %s\n",
					__func__,
					__mdss_dsi_pm_name(i));
			} else {
				pr_debug("%s: enabled vregs for %s\n",
					__func__,
					__mdss_dsi_pm_name(i));
				sdata->power_data[i].vreg_config->disabled =
					false;
				ctrl->core_power = true;
			}

		}
	}

	/* Disable dynamic clock gating*/
	if (ctrl->mdss_util->dyn_clk_gating_ctrl)
		ctrl->mdss_util->dyn_clk_gating_ctrl(0);

	if ((clk_type & MDSS_DSI_LINK_CLK) &&
		(l_type == MDSS_DSI_LINK_HS_CLK)) {
		u32 data = 0;

		data = MIPI_INP((ctrl->ctrl_io.base) + 0x0120);
		/*
		 * For 12nm PHY, the PLL unlock bit in DSI_CLK_STATUS gets set
		 * when PLL is turned off. When device comes out of static
		 * screen without the DSI controller getting power collapsed,
		 * the bit might not clear sometimes. Clear the bit before
		 * turning ON the PLL. This avoids false error interrupt due to
		 * PLL unlocked bit after PLL is turned ON.
		 */
		if (data & BIT(16)) {
			pr_debug("pll unlocked: 0x%x\n", data);
			MIPI_OUTP((ctrl->ctrl_io.base) + 0x120, BIT(16));
		}

	}

	if ((clk_type & MDSS_DSI_LINK_CLK) &&
		(new_state == MDSS_DSI_CLK_ON) &&
		!ctrl->panel_data.panel_info.cont_splash_enabled)
		mdss_dsi_phy_idle_pc_exit(ctrl);

	return rc;
}
