// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/clk/msm-clk-provider.h>
#include <linux/clk/msm-clk.h>
#include <linux/clk/msm-clock-generic.h>
#include <dt-bindings/clock/msm-clocks-8996.h>

#include "pll_drv.h"
#include "hdmi_pll.h"

/* CONSTANTS */
#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO            10
#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD         3400000000UL
#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD          1500000000UL
#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD          750000000
#define HDMI_CLKS_PLL_DIVSEL                     0
#define HDMI_CORECLK_DIV                         5
#define HDMI_REF_CLOCK                           19200000
#define HDMI_64B_ERR_VAL                         0xFFFFFFFFFFFFFFFF
#define HDMI_VERSION_8996_V1                     1
#define HDMI_VERSION_8996_V2                     2
#define HDMI_VERSION_8996_V3                     3
#define HDMI_VERSION_8996_V3_1_8                 4

#define HDMI_VCO_MAX_FREQ                        12000000000
#define HDMI_VCO_MIN_FREQ                        8000000000
#define HDMI_2400MHZ_BIT_CLK_HZ                  2400000000UL
#define HDMI_2250MHZ_BIT_CLK_HZ                  2250000000UL
#define HDMI_2000MHZ_BIT_CLK_HZ                  2000000000UL
#define HDMI_1700MHZ_BIT_CLK_HZ                  1700000000UL
#define HDMI_1200MHZ_BIT_CLK_HZ                  1200000000UL
#define HDMI_1334MHZ_BIT_CLK_HZ                  1334000000UL
#define HDMI_1000MHZ_BIT_CLK_HZ                  1000000000UL
#define HDMI_850MHZ_BIT_CLK_HZ                   850000000
#define HDMI_667MHZ_BIT_CLK_HZ                   667000000
#define HDMI_600MHZ_BIT_CLK_HZ                   600000000
#define HDMI_500MHZ_BIT_CLK_HZ                   500000000
#define HDMI_450MHZ_BIT_CLK_HZ                   450000000
#define HDMI_334MHZ_BIT_CLK_HZ                   334000000
#define HDMI_300MHZ_BIT_CLK_HZ                   300000000
#define HDMI_282MHZ_BIT_CLK_HZ                   282000000
#define HDMI_250MHZ_BIT_CLK_HZ                   250000000
#define HDMI_KHZ_TO_HZ                           1000

/* PLL REGISTERS */
#define QSERDES_COM_ATB_SEL1                     (0x000)
#define QSERDES_COM_ATB_SEL2                     (0x004)
#define QSERDES_COM_FREQ_UPDATE                  (0x008)
#define QSERDES_COM_BG_TIMER                     (0x00C)
#define QSERDES_COM_SSC_EN_CENTER                (0x010)
#define QSERDES_COM_SSC_ADJ_PER1                 (0x014)
#define QSERDES_COM_SSC_ADJ_PER2                 (0x018)
#define QSERDES_COM_SSC_PER1                     (0x01C)
#define QSERDES_COM_SSC_PER2                     (0x020)
#define QSERDES_COM_SSC_STEP_SIZE1               (0x024)
#define QSERDES_COM_SSC_STEP_SIZE2               (0x028)
#define QSERDES_COM_POST_DIV                     (0x02C)
#define QSERDES_COM_POST_DIV_MUX                 (0x030)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN          (0x034)
#define QSERDES_COM_CLK_ENABLE1                  (0x038)
#define QSERDES_COM_SYS_CLK_CTRL                 (0x03C)
#define QSERDES_COM_SYSCLK_BUF_ENABLE            (0x040)
#define QSERDES_COM_PLL_EN                       (0x044)
#define QSERDES_COM_PLL_IVCO                     (0x048)
#define QSERDES_COM_LOCK_CMP1_MODE0              (0x04C)
#define QSERDES_COM_LOCK_CMP2_MODE0              (0x050)
#define QSERDES_COM_LOCK_CMP3_MODE0              (0x054)
#define QSERDES_COM_LOCK_CMP1_MODE1              (0x058)
#define QSERDES_COM_LOCK_CMP2_MODE1              (0x05C)
#define QSERDES_COM_LOCK_CMP3_MODE1              (0x060)
#define QSERDES_COM_LOCK_CMP1_MODE2              (0x064)
#define QSERDES_COM_CMN_RSVD0                    (0x064)
#define QSERDES_COM_LOCK_CMP2_MODE2              (0x068)
#define QSERDES_COM_EP_CLOCK_DETECT_CTRL         (0x068)
#define QSERDES_COM_LOCK_CMP3_MODE2              (0x06C)
#define QSERDES_COM_SYSCLK_DET_COMP_STATUS       (0x06C)
#define QSERDES_COM_BG_TRIM                      (0x070)
#define QSERDES_COM_CLK_EP_DIV                   (0x074)
#define QSERDES_COM_CP_CTRL_MODE0                (0x078)
#define QSERDES_COM_CP_CTRL_MODE1                (0x07C)
#define QSERDES_COM_CP_CTRL_MODE2                (0x080)
#define QSERDES_COM_CMN_RSVD1                    (0x080)
#define QSERDES_COM_PLL_RCTRL_MODE0              (0x084)
#define QSERDES_COM_PLL_RCTRL_MODE1              (0x088)
#define QSERDES_COM_PLL_RCTRL_MODE2              (0x08C)
#define QSERDES_COM_CMN_RSVD2                    (0x08C)
#define QSERDES_COM_PLL_CCTRL_MODE0              (0x090)
#define QSERDES_COM_PLL_CCTRL_MODE1              (0x094)
#define QSERDES_COM_PLL_CCTRL_MODE2              (0x098)
#define QSERDES_COM_CMN_RSVD3                    (0x098)
#define QSERDES_COM_PLL_CNTRL                    (0x09C)
#define QSERDES_COM_PHASE_SEL_CTRL               (0x0A0)
#define QSERDES_COM_PHASE_SEL_DC                 (0x0A4)
#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL         (0x0A8)
#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM          (0x0A8)
#define QSERDES_COM_SYSCLK_EN_SEL                (0x0AC)
#define QSERDES_COM_CML_SYSCLK_SEL               (0x0B0)
#define QSERDES_COM_RESETSM_CNTRL                (0x0B4)
#define QSERDES_COM_RESETSM_CNTRL2               (0x0B8)
#define QSERDES_COM_RESTRIM_CTRL                 (0x0BC)
#define QSERDES_COM_RESTRIM_CTRL2                (0x0C0)
#define QSERDES_COM_RESCODE_DIV_NUM              (0x0C4)
#define QSERDES_COM_LOCK_CMP_EN                  (0x0C8)
#define QSERDES_COM_LOCK_CMP_CFG                 (0x0CC)
#define QSERDES_COM_DEC_START_MODE0              (0x0D0)
#define QSERDES_COM_DEC_START_MODE1              (0x0D4)
#define QSERDES_COM_DEC_START_MODE2              (0x0D8)
#define QSERDES_COM_VCOCAL_DEADMAN_CTRL          (0x0D8)
#define QSERDES_COM_DIV_FRAC_START1_MODE0        (0x0DC)
#define QSERDES_COM_DIV_FRAC_START2_MODE0        (0x0E0)
#define QSERDES_COM_DIV_FRAC_START3_MODE0        (0x0E4)
#define QSERDES_COM_DIV_FRAC_START1_MODE1        (0x0E8)
#define QSERDES_COM_DIV_FRAC_START2_MODE1        (0x0EC)
#define QSERDES_COM_DIV_FRAC_START3_MODE1        (0x0F0)
#define QSERDES_COM_DIV_FRAC_START1_MODE2        (0x0F4)
#define QSERDES_COM_VCO_TUNE_MINVAL1             (0x0F4)
#define QSERDES_COM_DIV_FRAC_START2_MODE2        (0x0F8)
#define QSERDES_COM_VCO_TUNE_MINVAL2             (0x0F8)
#define QSERDES_COM_DIV_FRAC_START3_MODE2        (0x0FC)
#define QSERDES_COM_CMN_RSVD4                    (0x0FC)
#define QSERDES_COM_INTEGLOOP_INITVAL            (0x100)
#define QSERDES_COM_INTEGLOOP_EN                 (0x104)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0        (0x108)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0        (0x10C)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1        (0x110)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1        (0x114)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE2        (0x118)
#define QSERDES_COM_VCO_TUNE_MAXVAL1             (0x118)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE2        (0x11C)
#define QSERDES_COM_VCO_TUNE_MAXVAL2             (0x11C)
#define QSERDES_COM_RES_TRIM_CONTROL2            (0x120)
#define QSERDES_COM_VCO_TUNE_CTRL                (0x124)
#define QSERDES_COM_VCO_TUNE_MAP                 (0x128)
#define QSERDES_COM_VCO_TUNE1_MODE0              (0x12C)
#define QSERDES_COM_VCO_TUNE2_MODE0              (0x130)
#define QSERDES_COM_VCO_TUNE1_MODE1              (0x134)
#define QSERDES_COM_VCO_TUNE2_MODE1              (0x138)
#define QSERDES_COM_VCO_TUNE1_MODE2              (0x13C)
#define QSERDES_COM_VCO_TUNE_INITVAL1            (0x13C)
#define QSERDES_COM_VCO_TUNE2_MODE2              (0x140)
#define QSERDES_COM_VCO_TUNE_INITVAL2            (0x140)
#define QSERDES_COM_VCO_TUNE_TIMER1              (0x144)
#define QSERDES_COM_VCO_TUNE_TIMER2              (0x148)
#define QSERDES_COM_SAR                          (0x14C)
#define QSERDES_COM_SAR_CLK                      (0x150)
#define QSERDES_COM_SAR_CODE_OUT_STATUS          (0x154)
#define QSERDES_COM_SAR_CODE_READY_STATUS        (0x158)
#define QSERDES_COM_CMN_STATUS                   (0x15C)
#define QSERDES_COM_RESET_SM_STATUS              (0x160)
#define QSERDES_COM_RESTRIM_CODE_STATUS          (0x164)
#define QSERDES_COM_PLLCAL_CODE1_STATUS          (0x168)
#define QSERDES_COM_PLLCAL_CODE2_STATUS          (0x16C)
#define QSERDES_COM_BG_CTRL                      (0x170)
#define QSERDES_COM_CLK_SELECT                   (0x174)
#define QSERDES_COM_HSCLK_SEL                    (0x178)
#define QSERDES_COM_INTEGLOOP_BINCODE_STATUS     (0x17C)
#define QSERDES_COM_PLL_ANALOG                   (0x180)
#define QSERDES_COM_CORECLK_DIV                  (0x184)
#define QSERDES_COM_SW_RESET                     (0x188)
#define QSERDES_COM_CORE_CLK_EN                  (0x18C)
#define QSERDES_COM_C_READY_STATUS               (0x190)
#define QSERDES_COM_CMN_CONFIG                   (0x194)
#define QSERDES_COM_CMN_RATE_OVERRIDE            (0x198)
#define QSERDES_COM_SVS_MODE_CLK_SEL             (0x19C)
#define QSERDES_COM_DEBUG_BUS0                   (0x1A0)
#define QSERDES_COM_DEBUG_BUS1                   (0x1A4)
#define QSERDES_COM_DEBUG_BUS2                   (0x1A8)
#define QSERDES_COM_DEBUG_BUS3                   (0x1AC)
#define QSERDES_COM_DEBUG_BUS_SEL                (0x1B0)
#define QSERDES_COM_CMN_MISC1                    (0x1B4)
#define QSERDES_COM_CMN_MISC2                    (0x1B8)
#define QSERDES_COM_CORECLK_DIV_MODE1            (0x1BC)
#define QSERDES_COM_CORECLK_DIV_MODE2            (0x1C0)
#define QSERDES_COM_CMN_RSVD5                    (0x1C0)

/* Tx Channel base addresses */
#define HDMI_TX_L0_BASE_OFFSET                   (0x400)
#define HDMI_TX_L1_BASE_OFFSET                   (0x600)
#define HDMI_TX_L2_BASE_OFFSET                   (0x800)
#define HDMI_TX_L3_BASE_OFFSET                   (0xA00)

/* Tx Channel PHY registers */
#define QSERDES_TX_L0_BIST_MODE_LANENO                    (0x000)
#define QSERDES_TX_L0_BIST_INVERT                         (0x004)
#define QSERDES_TX_L0_CLKBUF_ENABLE                       (0x008)
#define QSERDES_TX_L0_CMN_CONTROL_ONE                     (0x00C)
#define QSERDES_TX_L0_CMN_CONTROL_TWO                     (0x010)
#define QSERDES_TX_L0_CMN_CONTROL_THREE                   (0x014)
#define QSERDES_TX_L0_TX_EMP_POST1_LVL                    (0x018)
#define QSERDES_TX_L0_TX_POST2_EMPH                       (0x01C)
#define QSERDES_TX_L0_TX_BOOST_LVL_UP_DN                  (0x020)
#define QSERDES_TX_L0_HP_PD_ENABLES                       (0x024)
#define QSERDES_TX_L0_TX_IDLE_LVL_LARGE_AMP               (0x028)
#define QSERDES_TX_L0_TX_DRV_LVL                          (0x02C)
#define QSERDES_TX_L0_TX_DRV_LVL_OFFSET                   (0x030)
#define QSERDES_TX_L0_RESET_TSYNC_EN                      (0x034)
#define QSERDES_TX_L0_PRE_STALL_LDO_BOOST_EN              (0x038)
#define QSERDES_TX_L0_TX_BAND                             (0x03C)
#define QSERDES_TX_L0_SLEW_CNTL                           (0x040)
#define QSERDES_TX_L0_INTERFACE_SELECT                    (0x044)
#define QSERDES_TX_L0_LPB_EN                              (0x048)
#define QSERDES_TX_L0_RES_CODE_LANE_TX                    (0x04C)
#define QSERDES_TX_L0_RES_CODE_LANE_RX                    (0x050)
#define QSERDES_TX_L0_RES_CODE_LANE_OFFSET                (0x054)
#define QSERDES_TX_L0_PERL_LENGTH1                        (0x058)
#define QSERDES_TX_L0_PERL_LENGTH2                        (0x05C)
#define QSERDES_TX_L0_SERDES_BYP_EN_OUT                   (0x060)
#define QSERDES_TX_L0_DEBUG_BUS_SEL                       (0x064)
#define QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN    (0x068)
#define QSERDES_TX_L0_TX_POL_INV                          (0x06C)
#define QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN          (0x070)
#define QSERDES_TX_L0_BIST_PATTERN1                       (0x074)
#define QSERDES_TX_L0_BIST_PATTERN2                       (0x078)
#define QSERDES_TX_L0_BIST_PATTERN3                       (0x07C)
#define QSERDES_TX_L0_BIST_PATTERN4                       (0x080)
#define QSERDES_TX_L0_BIST_PATTERN5                       (0x084)
#define QSERDES_TX_L0_BIST_PATTERN6                       (0x088)
#define QSERDES_TX_L0_BIST_PATTERN7                       (0x08C)
#define QSERDES_TX_L0_BIST_PATTERN8                       (0x090)
#define QSERDES_TX_L0_LANE_MODE                           (0x094)
#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE                  (0x098)
#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE_CONFIGURATION    (0x09C)
#define QSERDES_TX_L0_ATB_SEL1                            (0x0A0)
#define QSERDES_TX_L0_ATB_SEL2                            (0x0A4)
#define QSERDES_TX_L0_RCV_DETECT_LVL                      (0x0A8)
#define QSERDES_TX_L0_RCV_DETECT_LVL_2                    (0x0AC)
#define QSERDES_TX_L0_PRBS_SEED1                          (0x0B0)
#define QSERDES_TX_L0_PRBS_SEED2                          (0x0B4)
#define QSERDES_TX_L0_PRBS_SEED3                          (0x0B8)
#define QSERDES_TX_L0_PRBS_SEED4                          (0x0BC)
#define QSERDES_TX_L0_RESET_GEN                           (0x0C0)
#define QSERDES_TX_L0_RESET_GEN_MUXES                     (0x0C4)
#define QSERDES_TX_L0_TRAN_DRVR_EMP_EN                    (0x0C8)
#define QSERDES_TX_L0_TX_INTERFACE_MODE                   (0x0CC)
#define QSERDES_TX_L0_PWM_CTRL                            (0x0D0)
#define QSERDES_TX_L0_PWM_ENCODED_OR_DATA                 (0x0D4)
#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND2            (0x0D8)
#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND2            (0x0DC)
#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND2            (0x0E0)
#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND2            (0x0E4)
#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND0_1          (0x0E8)
#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND0_1          (0x0EC)
#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND0_1          (0x0F0)
#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND0_1          (0x0F4)
#define QSERDES_TX_L0_VMODE_CTRL1                         (0x0F8)
#define QSERDES_TX_L0_VMODE_CTRL2                         (0x0FC)
#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV_CNTL              (0x100)
#define QSERDES_TX_L0_BIST_STATUS                         (0x104)
#define QSERDES_TX_L0_BIST_ERROR_COUNT1                   (0x108)
#define QSERDES_TX_L0_BIST_ERROR_COUNT2                   (0x10C)
#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV                   (0x110)

/* HDMI PHY REGISTERS */
#define HDMI_PHY_BASE_OFFSET                  (0xC00)

#define HDMI_PHY_CFG                          (0x00)
#define HDMI_PHY_PD_CTL                       (0x04)
#define HDMI_PHY_MODE                         (0x08)
#define HDMI_PHY_MISR_CLEAR                   (0x0C)
#define HDMI_PHY_TX0_TX1_BIST_CFG0            (0x10)
#define HDMI_PHY_TX0_TX1_BIST_CFG1            (0x14)
#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE0      (0x18)
#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE1      (0x1C)
#define HDMI_PHY_TX0_TX1_BIST_PATTERN0        (0x20)
#define HDMI_PHY_TX0_TX1_BIST_PATTERN1        (0x24)
#define HDMI_PHY_TX2_TX3_BIST_CFG0            (0x28)
#define HDMI_PHY_TX2_TX3_BIST_CFG1            (0x2C)
#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE0      (0x30)
#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE1      (0x34)
#define HDMI_PHY_TX2_TX3_BIST_PATTERN0        (0x38)
#define HDMI_PHY_TX2_TX3_BIST_PATTERN1        (0x3C)
#define HDMI_PHY_DEBUG_BUS_SEL                (0x40)
#define HDMI_PHY_TXCAL_CFG0                   (0x44)
#define HDMI_PHY_TXCAL_CFG1                   (0x48)
#define HDMI_PHY_TX0_TX1_LANE_CTL             (0x4C)
#define HDMI_PHY_TX2_TX3_LANE_CTL             (0x50)
#define HDMI_PHY_LANE_BIST_CONFIG             (0x54)
#define HDMI_PHY_CLOCK                        (0x58)
#define HDMI_PHY_MISC1                        (0x5C)
#define HDMI_PHY_MISC2                        (0x60)
#define HDMI_PHY_TX0_TX1_BIST_STATUS0         (0x64)
#define HDMI_PHY_TX0_TX1_BIST_STATUS1         (0x68)
#define HDMI_PHY_TX0_TX1_BIST_STATUS2         (0x6C)
#define HDMI_PHY_TX2_TX3_BIST_STATUS0         (0x70)
#define HDMI_PHY_TX2_TX3_BIST_STATUS1         (0x74)
#define HDMI_PHY_TX2_TX3_BIST_STATUS2         (0x78)
#define HDMI_PHY_PRE_MISR_STATUS0             (0x7C)
#define HDMI_PHY_PRE_MISR_STATUS1             (0x80)
#define HDMI_PHY_PRE_MISR_STATUS2             (0x84)
#define HDMI_PHY_PRE_MISR_STATUS3             (0x88)
#define HDMI_PHY_POST_MISR_STATUS0            (0x8C)
#define HDMI_PHY_POST_MISR_STATUS1            (0x90)
#define HDMI_PHY_POST_MISR_STATUS2            (0x94)
#define HDMI_PHY_POST_MISR_STATUS3            (0x98)
#define HDMI_PHY_STATUS                       (0x9C)
#define HDMI_PHY_MISC3_STATUS                 (0xA0)
#define HDMI_PHY_MISC4_STATUS                 (0xA4)
#define HDMI_PHY_DEBUG_BUS0                   (0xA8)
#define HDMI_PHY_DEBUG_BUS1                   (0xAC)
#define HDMI_PHY_DEBUG_BUS2                   (0xB0)
#define HDMI_PHY_DEBUG_BUS3                   (0xB4)
#define HDMI_PHY_PHY_REVISION_ID0             (0xB8)
#define HDMI_PHY_PHY_REVISION_ID1             (0xBC)
#define HDMI_PHY_PHY_REVISION_ID2             (0xC0)
#define HDMI_PHY_PHY_REVISION_ID3             (0xC4)

#define HDMI_PLL_POLL_MAX_READS                100
#define HDMI_PLL_POLL_TIMEOUT_US               1500

enum hdmi_pll_freqs {
	HDMI_PCLK_25200_KHZ,
	HDMI_PCLK_27027_KHZ,
	HDMI_PCLK_27000_KHZ,
	HDMI_PCLK_74250_KHZ,
	HDMI_PCLK_148500_KHZ,
	HDMI_PCLK_154000_KHZ,
	HDMI_PCLK_268500_KHZ,
	HDMI_PCLK_297000_KHZ,
	HDMI_PCLK_594000_KHZ,
	HDMI_PCLK_MAX
};

struct hdmi_8996_phy_pll_reg_cfg {
	u32 tx_l0_lane_mode;
	u32 tx_l2_lane_mode;
	u32 tx_l0_tx_band;
	u32 tx_l1_tx_band;
	u32 tx_l2_tx_band;
	u32 tx_l3_tx_band;
	u32 com_svs_mode_clk_sel;
	u32 com_hsclk_sel;
	u32 com_pll_cctrl_mode0;
	u32 com_pll_rctrl_mode0;
	u32 com_cp_ctrl_mode0;
	u32 com_dec_start_mode0;
	u32 com_div_frac_start1_mode0;
	u32 com_div_frac_start2_mode0;
	u32 com_div_frac_start3_mode0;
	u32 com_integloop_gain0_mode0;
	u32 com_integloop_gain1_mode0;
	u32 com_lock_cmp_en;
	u32 com_lock_cmp1_mode0;
	u32 com_lock_cmp2_mode0;
	u32 com_lock_cmp3_mode0;
	u32 com_core_clk_en;
	u32 com_coreclk_div;
	u32 com_restrim_ctrl;
	u32 com_vco_tune_ctrl;

	u32 tx_l0_tx_drv_lvl;
	u32 tx_l0_tx_emp_post1_lvl;
	u32 tx_l1_tx_drv_lvl;
	u32 tx_l1_tx_emp_post1_lvl;
	u32 tx_l2_tx_drv_lvl;
	u32 tx_l2_tx_emp_post1_lvl;
	u32 tx_l3_tx_drv_lvl;
	u32 tx_l3_tx_emp_post1_lvl;
	u32 tx_l0_vmode_ctrl1;
	u32 tx_l0_vmode_ctrl2;
	u32 tx_l1_vmode_ctrl1;
	u32 tx_l1_vmode_ctrl2;
	u32 tx_l2_vmode_ctrl1;
	u32 tx_l2_vmode_ctrl2;
	u32 tx_l3_vmode_ctrl1;
	u32 tx_l3_vmode_ctrl2;
	u32 tx_l0_res_code_lane_tx;
	u32 tx_l1_res_code_lane_tx;
	u32 tx_l2_res_code_lane_tx;
	u32 tx_l3_res_code_lane_tx;

	u32 phy_mode;
};

struct hdmi_8996_v3_post_divider {
	u64 vco_freq;
	u64 hsclk_divsel;
	u64 vco_ratio;
	u64 tx_band_sel;
	u64 half_rate_mode;
};

static inline struct hdmi_pll_vco_clk *to_hdmi_8996_vco_clk(struct clk *clk)
{
	return container_of(clk, struct hdmi_pll_vco_clk, c);
}

static inline u64 hdmi_8996_v1_get_post_div_lt_2g(u64 bclk)
{
	if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ)
		return 2;
	else if (bclk >= HDMI_1700MHZ_BIT_CLK_HZ)
		return 3;
	else if (bclk >= HDMI_1200MHZ_BIT_CLK_HZ)
		return 4;
	else if (bclk >= HDMI_850MHZ_BIT_CLK_HZ)
		return 3;
	else if (bclk >= HDMI_600MHZ_BIT_CLK_HZ)
		return 4;
	else if (bclk >= HDMI_450MHZ_BIT_CLK_HZ)
		return 3;
	else if (bclk >= HDMI_300MHZ_BIT_CLK_HZ)
		return 4;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v2_get_post_div_lt_2g(u64 bclk, u64 vco_range)
{
	u64 hdmi_8ghz = vco_range;
	u64 tmp_calc;

	hdmi_8ghz <<= 2;
	tmp_calc = hdmi_8ghz;
	do_div(tmp_calc, 6U);

	if (bclk >= vco_range)
		return 2;
	else if (bclk >= tmp_calc)
		return 3;
	else if (bclk >= vco_range >> 1)
		return 4;

	tmp_calc = hdmi_8ghz;
	do_div(tmp_calc, 12U);
	if (bclk >= tmp_calc)
		return 3;
	else if (bclk >= vco_range >> 2)
		return 4;

	tmp_calc = hdmi_8ghz;
	do_div(tmp_calc, 24U);
	if (bclk >= tmp_calc)
		return 3;
	else if (bclk >= vco_range >> 3)
		return 4;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v2_get_post_div_gt_2g(u64 hsclk)
{
	if (hsclk >= 0 && hsclk <= 3)
		return hsclk + 1;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_get_coreclk_div_lt_2g(u64 bclk)
{
	if (bclk >= HDMI_1334MHZ_BIT_CLK_HZ)
		return 1;
	else if (bclk >= HDMI_1000MHZ_BIT_CLK_HZ)
		return 1;
	else if (bclk >= HDMI_667MHZ_BIT_CLK_HZ)
		return 2;
	else if (bclk >= HDMI_500MHZ_BIT_CLK_HZ)
		return 2;
	else if (bclk >= HDMI_334MHZ_BIT_CLK_HZ)
		return 3;
	else if (bclk >= HDMI_250MHZ_BIT_CLK_HZ)
		return 3;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_get_coreclk_div_ratio(u64 clks_pll_divsel,
						  u64 coreclk_div)
{
	if (clks_pll_divsel == 0)
		return coreclk_div*2;
	else if (clks_pll_divsel == 1)
		return coreclk_div*4;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v1_get_tx_band(u64 bclk)
{
	if (bclk >= 2400000000UL)
		return 0;
	if (bclk >= 1200000000UL)
		return 1;
	if (bclk >= 600000000UL)
		return 2;
	if (bclk >= 300000000UL)
		return 3;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v2_get_tx_band(u64 bclk, u64 vco_range)
{
	if (bclk >= vco_range)
		return 0;
	else if (bclk >= vco_range >> 1)
		return 1;
	else if (bclk >= vco_range >> 2)
		return 2;
	else if (bclk >= vco_range >> 3)
		return 3;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v1_get_hsclk(u64 fdata)
{
	if (fdata >= 9600000000UL)
		return 0;
	else if (fdata >= 4800000000UL)
		return 1;
	else if (fdata >= 3200000000UL)
		return 2;
	else if (fdata >= 2400000000UL)
		return 3;

	return HDMI_64B_ERR_VAL;
}

static inline u64 hdmi_8996_v2_get_hsclk(u64 fdata, u64 vco_range)
{
	u64 tmp_calc = vco_range;

	tmp_calc <<= 2;
	do_div(tmp_calc, 3U);
	if (fdata >= (vco_range << 2))
		return 0;
	else if (fdata >= (vco_range << 1))
		return 1;
	else if (fdata >= tmp_calc)
		return 2;
	else if (fdata >= vco_range)
		return 3;

	return HDMI_64B_ERR_VAL;

}

static inline u64 hdmi_8996_v2_get_vco_freq(u64 bclk, u64 vco_range)
{
	u64 tx_band_div_ratio = 1U << hdmi_8996_v2_get_tx_band(bclk, vco_range);
	u64 pll_post_div_ratio;

	if (bclk >= vco_range) {
		u64 hsclk = hdmi_8996_v2_get_hsclk(bclk, vco_range);

		pll_post_div_ratio = hdmi_8996_v2_get_post_div_gt_2g(hsclk);
	} else {
		pll_post_div_ratio = hdmi_8996_v2_get_post_div_lt_2g(bclk,
								vco_range);
	}

	return bclk * (pll_post_div_ratio * tx_band_div_ratio);
}

static inline u64 hdmi_8996_v2_get_fdata(u64 bclk, u64 vco_range)
{
	if (bclk >= vco_range)
		return bclk;
	u64 tmp_calc = hdmi_8996_v2_get_vco_freq(bclk, vco_range);
	u64 pll_post_div_ratio_lt_2g = hdmi_8996_v2_get_post_div_lt_2g(
						bclk, vco_range);
	if (pll_post_div_ratio_lt_2g == HDMI_64B_ERR_VAL)
		return HDMI_64B_ERR_VAL;

	do_div(tmp_calc, pll_post_div_ratio_lt_2g);
		return tmp_calc;
}

static inline u64 hdmi_8996_get_cpctrl(u64 frac_start, bool gen_ssc)
{
	if ((frac_start != 0) || gen_ssc)
		/*
		 * This should be ROUND(11/(19.2/20))).
		 * Since ref clock does not change, hardcoding to 11
		 */
		return 0xB;

	return 0x23;
}

static inline u64 hdmi_8996_get_rctrl(u64 frac_start, bool gen_ssc)
{
	if ((frac_start != 0) || gen_ssc)
		return 0x16;

	return 0x10;
}

static inline u64 hdmi_8996_get_cctrl(u64 frac_start, bool gen_ssc)
{
	if ((frac_start != 0) || (gen_ssc))
		return 0x28;

	return 0x1;
}

static inline u64 hdmi_8996_get_integloop_gain(u64 frac_start, bool gen_ssc)
{
	if ((frac_start != 0) || gen_ssc)
		return 0x80;

	return 0xC4;
}

static inline u64 hdmi_8996_v3_get_integloop_gain(u64 frac_start, u64 bclk,
							bool gen_ssc)
{
	u64 digclk_divsel = bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2;
	u64 base = ((frac_start != 0) || gen_ssc) ? 0x40 : 0xC4;

	base <<= digclk_divsel;

	return (base <= 2046 ? base : 0x7FE);
}

static inline u64 hdmi_8996_get_vco_tune(u64 fdata, u64 div)
{
	u64 vco_tune;

	vco_tune = fdata * div;
	do_div(vco_tune, 1000000);
	vco_tune = 13000 - vco_tune - 256;
	do_div(vco_tune, 5);

	return vco_tune;
}

static inline u64 hdmi_8996_get_pll_cmp(u64 pll_cmp_cnt, u64 core_clk)
{
	u64 pll_cmp;
	u64 rem;

	pll_cmp = pll_cmp_cnt * core_clk;
	rem = do_div(pll_cmp, HDMI_REF_CLOCK);
	if (rem > (HDMI_REF_CLOCK >> 1))
		pll_cmp++;
	pll_cmp -= 1;

	return pll_cmp;
}

static inline u64 hdmi_8996_v3_get_pll_cmp(u64 pll_cmp_cnt, u64 fdata)
{
	u64 dividend = pll_cmp_cnt * fdata;
	u64 divisor = HDMI_REF_CLOCK * 10;
	u64 rem;

	rem = do_div(dividend, divisor);
	if (rem > (divisor >> 1))
		dividend++;

	return dividend - 1;
}

static int hdmi_8996_v3_get_post_div(struct hdmi_8996_v3_post_divider *pd,
						u64 bclk)
{
	u32 ratio[] = {2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35};
	u32 tx_band_sel[] = {0, 1, 2, 3};
	u64 vco_freq[60];
	u64 vco, vco_optimal, half_rate_mode = 0;
	int vco_optimal_index, vco_freq_index;
	int i, j, k, x;

	for (i = 0; i <= 1; i++) {
		vco_optimal = HDMI_VCO_MAX_FREQ;
		vco_optimal_index = -1;
		vco_freq_index = 0;
		for (j = 0; j < 15; j++) {
			for (k = 0; k < 4; k++) {
				u64 ratio_mult = ratio[j] << tx_band_sel[k];

				vco = bclk >> half_rate_mode;
				vco *= ratio_mult;
				vco_freq[vco_freq_index++] = vco;
			}
		}

		for (x = 0; x < 60; x++) {
			u64 vco_tmp = vco_freq[x];

			if ((vco_tmp >= HDMI_VCO_MIN_FREQ) &&
					(vco_tmp <= vco_optimal)) {
				vco_optimal = vco_tmp;
				vco_optimal_index = x;
			}
		}

		if (vco_optimal_index == -1) {
			if (!half_rate_mode)
				half_rate_mode++;
			else
				return -EINVAL;
		} else {
			pd->vco_freq = vco_optimal;
			pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4];
			pd->vco_ratio = ratio[vco_optimal_index / 4];
			break;
		}
	}

	switch (pd->vco_ratio) {
	case 2:
		pd->hsclk_divsel = 0;
		break;
	case 3:
		pd->hsclk_divsel = 4;
		break;
	case 4:
		pd->hsclk_divsel = 8;
		break;
	case 5:
		pd->hsclk_divsel = 12;
		break;
	case 6:
		pd->hsclk_divsel = 1;
		break;
	case 9:
		pd->hsclk_divsel = 5;
		break;
	case 10:
		pd->hsclk_divsel = 2;
		break;
	case 12:
		pd->hsclk_divsel = 9;
		break;
	case 14:
		pd->hsclk_divsel = 3;
		break;
	case 15:
		pd->hsclk_divsel = 13;
		break;
	case 20:
		pd->hsclk_divsel = 10;
		break;
	case 21:
		pd->hsclk_divsel = 7;
		break;
	case 25:
		pd->hsclk_divsel = 14;
		break;
	case 28:
		pd->hsclk_divsel = 11;
		break;
	case 35:
		pd->hsclk_divsel = 15;
		break;
	}

	return 0;
}

static int hdmi_8996_v1_calculate(u32 pix_clk,
			       struct hdmi_8996_phy_pll_reg_cfg *cfg)
{
	int rc = -EINVAL;
	u64 fdata, clk_divtx, tmds_clk;
	u64 bclk;
	u64 post_div_gt_2g;
	u64 post_div_lt_2g;
	u64 coreclk_div1_lt_2g;
	u64 core_clk_div_ratio;
	u64 core_clk;
	u64 pll_cmp;
	u64 tx_band;
	u64 tx_band_div_ratio;
	u64 hsclk;
	u64 dec_start;
	u64 frac_start;
	u64 pll_divisor = 4 * HDMI_REF_CLOCK;
	u64 cpctrl;
	u64 rctrl;
	u64 cctrl;
	u64 integloop_gain;
	u64 vco_tune;
	u64 vco_freq;
	u64 rem;

	/* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */
	bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD)
		tmds_clk = bclk/4;
	else
		tmds_clk = bclk;

	post_div_lt_2g = hdmi_8996_v1_get_post_div_lt_2g(bclk);
	if (post_div_lt_2g == HDMI_64B_ERR_VAL)
		goto fail;

	coreclk_div1_lt_2g = hdmi_8996_get_coreclk_div_lt_2g(bclk);

	core_clk_div_ratio = hdmi_8996_get_coreclk_div_ratio(
				HDMI_CLKS_PLL_DIVSEL, HDMI_CORECLK_DIV);

	tx_band = hdmi_8996_v1_get_tx_band(bclk);
	if (tx_band == HDMI_64B_ERR_VAL)
		goto fail;

	tx_band_div_ratio = 1 << tx_band;

	if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ) {
		fdata = bclk;
		hsclk = hdmi_8996_v1_get_hsclk(fdata);
		if (hsclk == HDMI_64B_ERR_VAL)
			goto fail;

		post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL;
		if (post_div_gt_2g == HDMI_64B_ERR_VAL)
			goto fail;

		vco_freq = bclk * (post_div_gt_2g * tx_band_div_ratio);
		clk_divtx = vco_freq;
		do_div(clk_divtx, post_div_gt_2g);
	} else {
		vco_freq = bclk * (post_div_lt_2g * tx_band_div_ratio);
		fdata = vco_freq;
		do_div(fdata, post_div_lt_2g);
		hsclk = hdmi_8996_v1_get_hsclk(fdata);
		if (hsclk == HDMI_64B_ERR_VAL)
			goto fail;

		clk_divtx = vco_freq;
		do_div(clk_divtx, post_div_lt_2g);
		post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL;
		if (post_div_gt_2g == HDMI_64B_ERR_VAL)
			goto fail;
	}

	/* Decimal and fraction values */
	dec_start = fdata * post_div_gt_2g;
	do_div(dec_start, pll_divisor);
	frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) -
			(fdata * post_div_gt_2g))) * (1 << 20));
	rem = do_div(frac_start, pll_divisor);
	/* Round off frac_start to closest integer */
	if (rem >= (pll_divisor >> 1))
		frac_start++;

	cpctrl = hdmi_8996_get_cpctrl(frac_start, false);
	rctrl = hdmi_8996_get_rctrl(frac_start, false);
	cctrl = hdmi_8996_get_cctrl(frac_start, false);
	integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false);
	vco_tune = hdmi_8996_get_vco_tune(fdata, post_div_gt_2g);

	core_clk = clk_divtx;
	do_div(core_clk, core_clk_div_ratio);
	pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk);

	/* Debug dump */
	DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq);
	DEV_DBG("%s: fdata: %llu\n", __func__, fdata);
	DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx);
	DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk);
	DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk);
	DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk);
	DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start);
	DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start);
	DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl);
	DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl);
	DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl);
	DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain);
	DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune);
	DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band);
	DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp);

	/* Convert these values to register specific values */
	cfg->tx_l0_lane_mode = 0x3;
	cfg->tx_l2_lane_mode = 0x3;
	cfg->tx_l0_tx_band = tx_band + 4;
	cfg->tx_l1_tx_band = tx_band + 4;
	cfg->tx_l2_tx_band = tx_band + 4;
	cfg->tx_l3_tx_band = tx_band + 4;
	cfg->tx_l0_res_code_lane_tx = 0x33;
	cfg->tx_l1_res_code_lane_tx = 0x33;
	cfg->tx_l2_res_code_lane_tx = 0x33;
	cfg->tx_l3_res_code_lane_tx = 0x33;
	cfg->com_restrim_ctrl = 0x0;
	cfg->com_vco_tune_ctrl = 0x1C;

	cfg->com_svs_mode_clk_sel =
			(bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2);
	cfg->com_hsclk_sel = (0x28 | hsclk);
	cfg->com_pll_cctrl_mode0 = cctrl;
	cfg->com_pll_rctrl_mode0 = rctrl;
	cfg->com_cp_ctrl_mode0 = cpctrl;
	cfg->com_dec_start_mode0 = dec_start;
	cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF);
	cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8);
	cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16);
	cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF);
	cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8);
	cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF);
	cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8);
	cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
	cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4));
	cfg->com_coreclk_div = HDMI_CORECLK_DIV;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x22;
		cfg->tx_l3_tx_emp_post1_lvl = 0x27;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
		cfg->com_restrim_ctrl = 0x0;
	} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x25;
		cfg->tx_l3_tx_emp_post1_lvl = 0x23;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
		cfg->com_restrim_ctrl = 0x0;
	} else {
		cfg->tx_l0_tx_drv_lvl = 0x20;
		cfg->tx_l0_tx_emp_post1_lvl = 0x20;
		cfg->tx_l1_tx_drv_lvl = 0x20;
		cfg->tx_l1_tx_emp_post1_lvl = 0x20;
		cfg->tx_l2_tx_drv_lvl = 0x20;
		cfg->tx_l2_tx_emp_post1_lvl = 0x20;
		cfg->tx_l3_tx_drv_lvl = 0x20;
		cfg->tx_l3_tx_emp_post1_lvl = 0x20;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0E;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0E;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0E;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x0E;
		cfg->com_restrim_ctrl = 0xD8;
	}

	cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0;
	DEV_DBG("HDMI 8996 PLL: PLL Settings\n");
	DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode);
	DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode);
	DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band);
	DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band);
	DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band);
	DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band);
	DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n",
						cfg->com_svs_mode_clk_sel);
	DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel);
	DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n",
						cfg->com_pll_cctrl_mode0);
	DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n",
						cfg->com_pll_rctrl_mode0);
	DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n",
						cfg->com_cp_ctrl_mode0);
	DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n",
						cfg->com_dec_start_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n",
						cfg->com_div_frac_start1_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n",
						cfg->com_div_frac_start2_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n",
						cfg->com_div_frac_start3_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n",
						cfg->com_integloop_gain0_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n",
						cfg->com_integloop_gain1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n",
						cfg->com_lock_cmp1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n",
						cfg->com_lock_cmp2_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n",
						cfg->com_lock_cmp3_mode0);
	DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en);
	DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div);
	DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n",	cfg->com_restrim_ctrl);

	DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l0_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l1_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l2_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l3_tx_emp_post1_lvl);

	DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2);
	DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n",
					cfg->tx_l0_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n",
					cfg->tx_l1_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n",
					cfg->tx_l2_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n",
					cfg->tx_l3_res_code_lane_tx);

	DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode);
	rc = 0;
fail:
	return rc;
}

static int hdmi_8996_v2_calculate(u32 pix_clk,
			       struct hdmi_8996_phy_pll_reg_cfg *cfg)
{
	int rc = -EINVAL;
	u64 fdata, clk_divtx, tmds_clk;
	u64 bclk;
	u64 post_div;
	u64 core_clk_div;
	u64 core_clk_div_ratio;
	u64 core_clk;
	u64 pll_cmp;
	u64 tx_band;
	u64 tx_band_div_ratio;
	u64 hsclk;
	u64 dec_start;
	u64 frac_start;
	u64 pll_divisor = 4 * HDMI_REF_CLOCK;
	u64 cpctrl;
	u64 rctrl;
	u64 cctrl;
	u64 integloop_gain;
	u64 vco_tune;
	u64 vco_freq;
	u64 vco_range;
	u64 rem;

	/* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */
	bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD)
		tmds_clk = pix_clk >> 2;
	else
		tmds_clk = pix_clk;

	vco_range = bclk < HDMI_282MHZ_BIT_CLK_HZ ? HDMI_2000MHZ_BIT_CLK_HZ :
				HDMI_2250MHZ_BIT_CLK_HZ;

	fdata = hdmi_8996_v2_get_fdata(bclk, vco_range);
	if (fdata == HDMI_64B_ERR_VAL)
		goto fail;

	hsclk = hdmi_8996_v2_get_hsclk(fdata, vco_range);
	if (hsclk == HDMI_64B_ERR_VAL)
		goto fail;

	if (bclk >= vco_range)
		post_div = hdmi_8996_v2_get_post_div_gt_2g(hsclk);
	else
		post_div = hdmi_8996_v2_get_post_div_lt_2g(bclk, vco_range);

	if (post_div == HDMI_64B_ERR_VAL)
		goto fail;

	core_clk_div = 5;
	core_clk_div_ratio = core_clk_div * 2;

	tx_band = hdmi_8996_v2_get_tx_band(bclk, vco_range);
	if (tx_band == HDMI_64B_ERR_VAL)
		goto fail;

	tx_band_div_ratio = 1 << tx_band;

	vco_freq = hdmi_8996_v2_get_vco_freq(bclk, vco_range);
	clk_divtx = vco_freq;
	do_div(clk_divtx, post_div);

	/* Decimal and fraction values */
	dec_start = fdata * post_div;
	do_div(dec_start, pll_divisor);
	frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) -
			(fdata * post_div))) * (1 << 20));
	rem = do_div(frac_start, pll_divisor);
	/* Round off frac_start to closest integer */
	if (rem >= (pll_divisor >> 1))
		frac_start++;

	cpctrl = hdmi_8996_get_cpctrl(frac_start, false);
	rctrl = hdmi_8996_get_rctrl(frac_start, false);
	cctrl = hdmi_8996_get_cctrl(frac_start, false);
	integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false);
	vco_tune = hdmi_8996_get_vco_tune(fdata, post_div);

	core_clk = clk_divtx;
	do_div(core_clk, core_clk_div_ratio);
	pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk);

	/* Debug dump */
	DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq);
	DEV_DBG("%s: fdata: %llu\n", __func__, fdata);
	DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx);
	DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk);
	DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk);
	DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk);
	DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start);
	DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start);
	DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl);
	DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl);
	DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl);
	DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain);
	DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune);
	DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band);
	DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp);

	/* Convert these values to register specific values */
	cfg->tx_l0_lane_mode = 0x3;
	cfg->tx_l2_lane_mode = 0x3;
	cfg->tx_l0_tx_band = tx_band + 4;
	cfg->tx_l1_tx_band = tx_band + 4;
	cfg->tx_l2_tx_band = tx_band + 4;
	cfg->tx_l3_tx_band = tx_band + 4;

	if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD)
		cfg->com_svs_mode_clk_sel = 1;
	else
		cfg->com_svs_mode_clk_sel = 2;

	cfg->com_hsclk_sel = (0x28 | hsclk);
	cfg->com_pll_cctrl_mode0 = cctrl;
	cfg->com_pll_rctrl_mode0 = rctrl;
	cfg->com_cp_ctrl_mode0 = cpctrl;
	cfg->com_dec_start_mode0 = dec_start;
	cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF);
	cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8);
	cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16);
	cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF);
	cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8);
	cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF);
	cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8);
	cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
	cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4));
	cfg->com_coreclk_div = HDMI_CORECLK_DIV;
	cfg->com_vco_tune_ctrl = 0x0;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x22;
		cfg->tx_l3_tx_emp_post1_lvl = 0x27;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
		cfg->tx_l0_res_code_lane_tx = 0x3F;
		cfg->tx_l1_res_code_lane_tx = 0x3F;
		cfg->tx_l2_res_code_lane_tx = 0x3F;
		cfg->tx_l3_res_code_lane_tx = 0x3F;
		cfg->com_restrim_ctrl = 0x0;
	} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x25;
		cfg->tx_l3_tx_emp_post1_lvl = 0x23;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
		cfg->tx_l0_res_code_lane_tx = 0x39;
		cfg->tx_l1_res_code_lane_tx = 0x39;
		cfg->tx_l2_res_code_lane_tx = 0x39;
		cfg->tx_l3_res_code_lane_tx = 0x39;
		cfg->com_restrim_ctrl = 0x0;
	} else {
		cfg->tx_l0_tx_drv_lvl = 0x20;
		cfg->tx_l0_tx_emp_post1_lvl = 0x20;
		cfg->tx_l1_tx_drv_lvl = 0x20;
		cfg->tx_l1_tx_emp_post1_lvl = 0x20;
		cfg->tx_l2_tx_drv_lvl = 0x20;
		cfg->tx_l2_tx_emp_post1_lvl = 0x20;
		cfg->tx_l3_tx_drv_lvl = 0x20;
		cfg->tx_l3_tx_emp_post1_lvl = 0x20;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0E;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0E;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0E;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x0E;
		cfg->tx_l0_res_code_lane_tx = 0x3F;
		cfg->tx_l1_res_code_lane_tx = 0x3F;
		cfg->tx_l2_res_code_lane_tx = 0x3F;
		cfg->tx_l3_res_code_lane_tx = 0x3F;
		cfg->com_restrim_ctrl = 0xD8;
	}

	cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0;
	DEV_DBG("HDMI 8996 PLL: PLL Settings\n");
	DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode);
	DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode);
	DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band);
	DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band);
	DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band);
	DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band);
	DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n",
						cfg->com_svs_mode_clk_sel);
	DEV_DBG("PLL PARAM: com_vco_tune_ctrl = 0x%x\n",
						cfg->com_vco_tune_ctrl);
	DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel);
	DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en);
	DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n",
						cfg->com_pll_cctrl_mode0);
	DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n",
						cfg->com_pll_rctrl_mode0);
	DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n",
						cfg->com_cp_ctrl_mode0);
	DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n",
						cfg->com_dec_start_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n",
						cfg->com_div_frac_start1_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n",
						cfg->com_div_frac_start2_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n",
						cfg->com_div_frac_start3_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n",
						cfg->com_integloop_gain0_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n",
						cfg->com_integloop_gain1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n",
						cfg->com_lock_cmp1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n",
						cfg->com_lock_cmp2_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n",
						cfg->com_lock_cmp3_mode0);
	DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en);
	DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div);

	DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l0_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l1_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l2_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l3_tx_emp_post1_lvl);

	DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2);
	DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n",
					cfg->tx_l0_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n",
					cfg->tx_l1_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n",
					cfg->tx_l2_res_code_lane_tx);
	DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n",
					cfg->tx_l3_res_code_lane_tx);
	DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n",	cfg->com_restrim_ctrl);

	DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode);
	rc = 0;
fail:
	return rc;
}

static int hdmi_8996_v3_calculate(u32 pix_clk,
			       struct hdmi_8996_phy_pll_reg_cfg *cfg)
{
	int rc = -EINVAL;
	struct hdmi_8996_v3_post_divider pd;
	u64 fdata, tmds_clk;
	u64 bclk;
	u64 pll_cmp;
	u64 tx_band;
	u64 hsclk;
	u64 dec_start;
	u64 frac_start;
	u64 pll_divisor = 4 * HDMI_REF_CLOCK;
	u64 cpctrl;
	u64 rctrl;
	u64 cctrl;
	u64 integloop_gain;
	u64 vco_freq;
	u64 rem;

	/* FDATA, HSCLK, PIXEL_CLK, TMDS_CLK */
	bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD)
		tmds_clk = pix_clk >> 2;
	else
		tmds_clk = pix_clk;

	if (hdmi_8996_v3_get_post_div(&pd, bclk) || pd.vco_ratio <= 0 ||
			pd.vco_freq <= 0)
		goto fail;

	vco_freq = pd.vco_freq;
	fdata = pd.vco_freq;
	do_div(fdata, pd.vco_ratio);

	hsclk = pd.hsclk_divsel;
	dec_start = vco_freq;
	do_div(dec_start, pll_divisor);

	frac_start = vco_freq * (1 << 20);
	rem = do_div(frac_start, pll_divisor);
	frac_start -= dec_start * (1 << 20);
	if (rem > (pll_divisor >> 1))
		frac_start++;

	cpctrl = hdmi_8996_get_cpctrl(frac_start, false);
	rctrl = hdmi_8996_get_rctrl(frac_start, false);
	cctrl = hdmi_8996_get_cctrl(frac_start, false);
	integloop_gain = hdmi_8996_v3_get_integloop_gain(frac_start, bclk,
									false);
	pll_cmp = hdmi_8996_v3_get_pll_cmp(1024, fdata);
	tx_band = pd.tx_band_sel;

	/* Debug dump */
	DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq);
	DEV_DBG("%s: fdata: %llu\n", __func__, fdata);
	DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk);
	DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk);
	DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk);
	DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start);
	DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start);
	DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl);
	DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl);
	DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl);
	DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain);
	DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band);
	DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp);

	/* Convert these values to register specific values */
	cfg->tx_l0_tx_band = tx_band + 4;
	cfg->tx_l1_tx_band = tx_band + 4;
	cfg->tx_l2_tx_band = tx_band + 4;
	cfg->tx_l3_tx_band = tx_band + 4;

	if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD)
		cfg->com_svs_mode_clk_sel = 1;
	else
		cfg->com_svs_mode_clk_sel = 2;

	cfg->com_hsclk_sel = (0x20 | hsclk);
	cfg->com_pll_cctrl_mode0 = cctrl;
	cfg->com_pll_rctrl_mode0 = rctrl;
	cfg->com_cp_ctrl_mode0 = cpctrl;
	cfg->com_dec_start_mode0 = dec_start;
	cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF);
	cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8);
	cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16);
	cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF);
	cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8);
	cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF);
	cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8);
	cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
	cfg->com_lock_cmp_en = 0x04;
	cfg->com_core_clk_en = 0x2C;
	cfg->com_coreclk_div = HDMI_CORECLK_DIV;
	cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0;
	cfg->com_vco_tune_ctrl = 0x0;

	cfg->tx_l0_lane_mode = 0x43;
	cfg->tx_l2_lane_mode = 0x43;

	if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x22;
		cfg->tx_l3_tx_emp_post1_lvl = 0x27;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
	} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
		cfg->tx_l0_tx_drv_lvl = 0x25;
		cfg->tx_l0_tx_emp_post1_lvl = 0x23;
		cfg->tx_l1_tx_drv_lvl = 0x25;
		cfg->tx_l1_tx_emp_post1_lvl = 0x23;
		cfg->tx_l2_tx_drv_lvl = 0x25;
		cfg->tx_l2_tx_emp_post1_lvl = 0x23;
		cfg->tx_l3_tx_drv_lvl = 0x25;
		cfg->tx_l3_tx_emp_post1_lvl = 0x23;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0D;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0D;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0D;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x00;
	} else {
		cfg->tx_l0_tx_drv_lvl = 0x20;
		cfg->tx_l0_tx_emp_post1_lvl = 0x20;
		cfg->tx_l1_tx_drv_lvl = 0x20;
		cfg->tx_l1_tx_emp_post1_lvl = 0x20;
		cfg->tx_l2_tx_drv_lvl = 0x20;
		cfg->tx_l2_tx_emp_post1_lvl = 0x20;
		cfg->tx_l3_tx_drv_lvl = 0x20;
		cfg->tx_l3_tx_emp_post1_lvl = 0x20;
		cfg->tx_l0_vmode_ctrl1 = 0x00;
		cfg->tx_l0_vmode_ctrl2 = 0x0E;
		cfg->tx_l1_vmode_ctrl1 = 0x00;
		cfg->tx_l1_vmode_ctrl2 = 0x0E;
		cfg->tx_l2_vmode_ctrl1 = 0x00;
		cfg->tx_l2_vmode_ctrl2 = 0x0E;
		cfg->tx_l3_vmode_ctrl1 = 0x00;
		cfg->tx_l3_vmode_ctrl2 = 0x0E;
	}

	DEV_DBG("HDMI 8996 PLL: PLL Settings\n");
	DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band);
	DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band);
	DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band);
	DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band);
	DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n",
						cfg->com_svs_mode_clk_sel);
	DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel);
	DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en);
	DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n",
						cfg->com_pll_cctrl_mode0);
	DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n",
						cfg->com_pll_rctrl_mode0);
	DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n",
						cfg->com_cp_ctrl_mode0);
	DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n",
						cfg->com_dec_start_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n",
						cfg->com_div_frac_start1_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n",
						cfg->com_div_frac_start2_mode0);
	DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n",
						cfg->com_div_frac_start3_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n",
						cfg->com_integloop_gain0_mode0);
	DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n",
						cfg->com_integloop_gain1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n",
						cfg->com_lock_cmp1_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n",
						cfg->com_lock_cmp2_mode0);
	DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n",
						cfg->com_lock_cmp3_mode0);
	DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en);
	DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div);
	DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode);

	DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode);
	DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode);
	DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l0_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l1_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l2_tx_emp_post1_lvl);
	DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl);
	DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n",
						cfg->tx_l3_tx_emp_post1_lvl);

	DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1);
	DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2);
	rc = 0;
fail:
	return rc;
}

static int hdmi_8996_calculate(u32 pix_clk,
			       struct hdmi_8996_phy_pll_reg_cfg *cfg, u32 ver)
{
	switch (ver) {
	case HDMI_VERSION_8996_V3:
	case HDMI_VERSION_8996_V3_1_8:
		return hdmi_8996_v3_calculate(pix_clk, cfg);
	case HDMI_VERSION_8996_V2:
		return hdmi_8996_v2_calculate(pix_clk, cfg);
	default:
		return hdmi_8996_v1_calculate(pix_clk, cfg);
	}
}

static int hdmi_8996_phy_pll_set_clk_rate(struct clk *c, u32 tmds_clk, u32 ver)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	struct hdmi_8996_phy_pll_reg_cfg cfg = {0};

	rc = hdmi_8996_calculate(tmds_clk, &cfg, ver);
	if (rc) {
		DEV_ERR("%s: PLL calculation failed\n", __func__);
		return rc;
	}

	/* Initially shut down PHY */
	DEV_DBG("%s: Disabling PHY\n", __func__);
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x0);
	udelay(500);

	/* Power up sequence */
	switch (ver) {
	case HDMI_VERSION_8996_V2:
	case HDMI_VERSION_8996_V3:
	case HDMI_VERSION_8996_V3_1_8:
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x04);
		break;
	}

	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL, 0x20);
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX0_TX1_LANE_CTL, 0x0F);
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX2_TX3_LANE_CTL, 0x0F);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
				     QSERDES_TX_L0_CLKBUF_ENABLE, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
				     QSERDES_TX_L0_CLKBUF_ENABLE, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
				     QSERDES_TX_L0_CLKBUF_ENABLE, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
				     QSERDES_TX_L0_CLKBUF_ENABLE, 0x03);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		     QSERDES_TX_L0_LANE_MODE, cfg.tx_l0_lane_mode);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		     QSERDES_TX_L0_LANE_MODE, cfg.tx_l2_lane_mode);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		     QSERDES_TX_L0_TX_BAND, cfg.tx_l0_tx_band);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		     QSERDES_TX_L0_TX_BAND, cfg.tx_l1_tx_band);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		     QSERDES_TX_L0_TX_BAND, cfg.tx_l2_tx_band);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		     QSERDES_TX_L0_TX_BAND, cfg.tx_l3_tx_band);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
			       QSERDES_TX_L0_RESET_TSYNC_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
			       QSERDES_TX_L0_RESET_TSYNC_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
			       QSERDES_TX_L0_RESET_TSYNC_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
			       QSERDES_TX_L0_RESET_TSYNC_EN, 0x03);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1E);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_EN_SEL, 0x37);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x02);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_ENABLE1, 0x0E);
	if (ver == HDMI_VERSION_8996_V1)
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06);

	/* Bypass VCO calibration */
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL,
					cfg.com_svs_mode_clk_sel);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_TRIM, 0x0F);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IVCO, 0x0F);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_CTRL,
			cfg.com_vco_tune_ctrl);

	switch (ver) {
	case HDMI_VERSION_8996_V1:
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL,
					cfg.com_svs_mode_clk_sel);
		break;
	default:
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06);
	}

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_SELECT, 0x30);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_HSCLK_SEL,
		       cfg.com_hsclk_sel);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_EN,
		       cfg.com_lock_cmp_en);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CCTRL_MODE0,
		       cfg.com_pll_cctrl_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_RCTRL_MODE0,
		       cfg.com_pll_rctrl_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CP_CTRL_MODE0,
		       cfg.com_cp_ctrl_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START_MODE0,
		       cfg.com_dec_start_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START1_MODE0,
		       cfg.com_div_frac_start1_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START2_MODE0,
		       cfg.com_div_frac_start2_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START3_MODE0,
		       cfg.com_div_frac_start3_mode0);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN0_MODE0,
			cfg.com_integloop_gain0_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN1_MODE0,
			cfg.com_integloop_gain1_mode0);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0,
			cfg.com_lock_cmp1_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0,
			cfg.com_lock_cmp2_mode0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0,
			cfg.com_lock_cmp3_mode0);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_MAP, 0x00);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORE_CLK_EN,
		       cfg.com_core_clk_en);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORECLK_DIV,
		       cfg.com_coreclk_div);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_CONFIG, 0x02);

	if (ver == HDMI_VERSION_8996_V3 || ver == HDMI_VERSION_8996_V3_1_8)
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESCODE_DIV_NUM, 0x15);

	/* TX lanes setup (TX 0/1/2/3) */
	if (ver == HDMI_VERSION_8996_V3_1_8) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				0x00000023);
	} else {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				cfg.tx_l0_tx_drv_lvl);
	}
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_TX_EMP_POST1_LVL,
		       cfg.tx_l0_tx_emp_post1_lvl);

	if (ver == HDMI_VERSION_8996_V3_1_8) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				0x00000023);
	} else {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				cfg.tx_l1_tx_drv_lvl);
	}
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_TX_EMP_POST1_LVL,
		       cfg.tx_l1_tx_emp_post1_lvl);

	if (ver == HDMI_VERSION_8996_V3_1_8) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				0x00000023);
	} else {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				cfg.tx_l2_tx_drv_lvl);
	}
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_TX_EMP_POST1_LVL,
		       cfg.tx_l2_tx_emp_post1_lvl);

	if (ver == HDMI_VERSION_8996_V3_1_8) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				0x00000020);
	} else {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
				QSERDES_TX_L0_TX_DRV_LVL,
				cfg.tx_l3_tx_drv_lvl);
	}
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_TX_EMP_POST1_LVL,
		       cfg.tx_l3_tx_emp_post1_lvl);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL1,
		       cfg.tx_l0_vmode_ctrl1);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL2,
		       cfg.tx_l0_vmode_ctrl2);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL1,
		       cfg.tx_l1_vmode_ctrl1);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL2,
		       cfg.tx_l1_vmode_ctrl2);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL1,
		       cfg.tx_l2_vmode_ctrl1);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
			QSERDES_TX_L0_VMODE_CTRL2,
			cfg.tx_l2_vmode_ctrl2);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_VMODE_CTRL1,
		       cfg.tx_l3_vmode_ctrl1);
	if (ver == HDMI_VERSION_8996_V3_1_8) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
				QSERDES_TX_L0_VMODE_CTRL2,
				0x0000000D);
	} else {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
				QSERDES_TX_L0_VMODE_CTRL2,
				cfg.tx_l3_vmode_ctrl2);
	}

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00);

	if (ver < HDMI_VERSION_8996_V3) {
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
			       QSERDES_TX_L0_RES_CODE_LANE_TX,
			       cfg.tx_l0_res_code_lane_tx);
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
			       QSERDES_TX_L0_RES_CODE_LANE_TX,
			       cfg.tx_l1_res_code_lane_tx);
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
			       QSERDES_TX_L0_RES_CODE_LANE_TX,
			       cfg.tx_l2_res_code_lane_tx);
		MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
			       QSERDES_TX_L0_RES_CODE_LANE_TX,
			       cfg.tx_l3_res_code_lane_tx);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESTRIM_CTRL,
			       cfg.com_restrim_ctrl);

		MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG0, 0x00);
		MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG1, 0x05);
	}

	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_MODE, cfg.phy_mode);
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1F);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
			QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
			QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
			QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
			QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
			QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
			QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
			QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
			QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40);

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_HP_PD_ENABLES, 0x0C);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_HP_PD_ENABLES, 0x0C);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_HP_PD_ENABLES, 0x0C);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_HP_PD_ENABLES, 0x03);

	if (ver == HDMI_VERSION_8996_V2) {
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x01);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x01);
	}
	/*
	 * Ensure that vco configuration gets flushed to hardware before
	 * enabling the PLL
	 */
	wmb();
	return 0;
}

static int hdmi_8996_phy_ready_status(struct mdss_pll_resources *io)
{
	u32 status = 0;
	int phy_ready = 0;
	int rc;
	u32 read_count = 0;

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		DEV_ERR("%s: pll resource can't be enabled\n", __func__);
		return rc;
	}

	DEV_DBG("%s: Waiting for PHY Ready\n", __func__);

	/* Poll for PHY read status */
	while (read_count < HDMI_PLL_POLL_MAX_READS) {
		status = MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS);
		if ((status & BIT(0)) == 1) {
			phy_ready = 1;
			DEV_DBG("%s: PHY READY\n", __func__);
			break;
		}
		udelay(HDMI_PLL_POLL_TIMEOUT_US);
		read_count++;
	}

	if (read_count == HDMI_PLL_POLL_MAX_READS) {
		phy_ready = 0;
		DEV_DBG("%s: PHY READY TIMEOUT\n", __func__);
	}

	mdss_pll_resource_enable(io, false);

	return phy_ready;
}

static int hdmi_8996_pll_lock_status(struct mdss_pll_resources *io)
{
	u32 status;
	int pll_locked = 0;
	int rc;
	u32 read_count = 0;

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		DEV_ERR("%s: pll resource can't be enabled\n", __func__);
		return rc;
	}

	DEV_DBG("%s: Waiting for PLL lock\n", __func__);

	while (read_count < HDMI_PLL_POLL_MAX_READS) {
		status = MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_C_READY_STATUS);
		if ((status & BIT(0)) == 1) {
			pll_locked = 1;
			DEV_DBG("%s: C READY\n", __func__);
			break;
		}
		udelay(HDMI_PLL_POLL_TIMEOUT_US);
		read_count++;
	}

	if (read_count == HDMI_PLL_POLL_MAX_READS) {
		pll_locked = 0;
		DEV_DBG("%s: C READY TIMEOUT\n", __func__);
	}

	mdss_pll_resource_enable(io, false);

	return pll_locked;
}

static int hdmi_8996_v1_perform_sw_calibration(struct clk *c)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	u32 max_code = 0x190;
	u32 min_code = 0x0;
	u32 max_cnt = 0;
	u32 min_cnt = 0;
	u32 expected_counter_value = 0;
	u32 step = 0;
	u32 dbus_all = 0;
	u32 dbus_sel = 0;
	u32 vco_code = 0;
	u32 val = 0;

	vco_code = 0xC8;

	DEV_DBG("%s: Starting SW calibration with vco_code = %d\n", __func__,
		 vco_code);

	expected_counter_value =
	   (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0) << 16) |
	   (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0) << 8) |
	   (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0));

	DEV_DBG("%s: expected_counter_value = %d\n", __func__,
		 expected_counter_value);

	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1);
	val |= BIT(4);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val);

	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1);
	val |= BIT(3);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val);

	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x4);

	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG);
	val |= BIT(1);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val);

	udelay(60);

	while (1) {
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0,
			       vco_code & 0xFF);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0,
			       (vco_code >> 8) & 0x3);

		udelay(20);

		val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG);
		val &= ~BIT(1);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val);

		udelay(60);

		val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG);
		val |= BIT(1);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val);

		udelay(60);

		dbus_all =
		  (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS3) << 24) |
		  (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS2) << 16) |
		  (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS1) << 8) |
		  (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS0));

		dbus_sel = (dbus_all >> 9) & 0x3FFFF;
		DEV_DBG("%s: loop[%d], dbus_all = 0x%x, dbus_sel = 0x%x\n",
			__func__, step, dbus_all, dbus_sel);
		if (dbus_sel == 0)
			DEV_ERR("%s: CHECK HDMI REF CLK\n", __func__);

		if (dbus_sel == expected_counter_value) {
			max_code = vco_code;
			max_cnt = dbus_sel;
			min_code = vco_code;
			min_cnt = dbus_sel;
		} else if (dbus_sel == 0) {
			max_code = vco_code;
			max_cnt = dbus_sel;
			vco_code = (max_code + min_code)/2;
		} else if (dbus_sel > expected_counter_value) {
			min_code = vco_code;
			min_cnt = dbus_sel;
			vco_code = (max_code + min_code)/2;
		} else if (dbus_sel < expected_counter_value) {
			max_code = vco_code;
			max_cnt = dbus_sel;
			vco_code = (max_code + min_code)/2;
		}

		step++;

		if ((vco_code == 0) || (vco_code == 0x3FF) || (step > 0x3FF)) {
			DEV_ERR("%s: VCO tune code search failed\n", __func__);
			rc = -ENOTSUPP;
			break;
		}
		if ((max_code - min_code) <= 1) {
			if ((max_code - min_code) == 1) {
				if (abs((int)(max_cnt - expected_counter_value))
				    < abs((int)(min_cnt - expected_counter_value
					))) {
					vco_code = max_code;
				} else {
					vco_code = min_code;
				}
			}
			break;
		}
		DEV_DBG("%s: loop[%d], new vco_code = %d\n", __func__, step,
			 vco_code);
	}

	DEV_DBG("%s: CALIB done. vco_code = %d\n", __func__, vco_code);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0,
		       vco_code & 0xFF);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0,
		       (vco_code >> 8) & 0x3);
	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG);
	val &= ~BIT(1);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val);

	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1);
	val |= BIT(4);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val);

	val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1);
	val &= ~BIT(3);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val);

	return rc;
}

static int hdmi_8996_v2_perform_sw_calibration(struct clk *c)
{
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	u32 vco_code1, vco_code2, integral_loop, ready_poll;
	u32 read_count = 0;

	while (read_count < (HDMI_PLL_POLL_MAX_READS << 1)) {
		ready_poll = MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_C_READY_STATUS);
		if ((ready_poll & BIT(0)) == 1) {
			ready_poll = 1;
			DEV_DBG("%s: C READY\n", __func__);
			break;
		}
		udelay(HDMI_PLL_POLL_TIMEOUT_US);
		read_count++;
	}

	if (read_count == (HDMI_PLL_POLL_MAX_READS << 1)) {
		ready_poll = 0;
		DEV_DBG("%s: C READY TIMEOUT, TRYING SW CALIBRATION\n",
								__func__);
	}

	vco_code1 = MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_PLLCAL_CODE1_STATUS);
	vco_code2 = MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_PLLCAL_CODE2_STATUS);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x5);
	integral_loop = MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_DEBUG_BUS0);

	if (((ready_poll & 0x1) == 0) || (((ready_poll & 1) == 1) &&
			(vco_code1 == 0xFF) && ((vco_code2 & 0x3) == 0x1) &&
			(integral_loop > 0xC0))) {
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x04);
		MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x00);
		MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x17);
		udelay(100);

		MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x11);
		udelay(100);

		MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19);
	}
	return 0;
}

static int hdmi_8996_perform_sw_calibration(struct clk *c, u32 ver)
{
	switch (ver) {
	case HDMI_VERSION_8996_V1:
		return hdmi_8996_v1_perform_sw_calibration(c);
	case HDMI_VERSION_8996_V2:
		return hdmi_8996_v2_perform_sw_calibration(c);
	}
	return 0;
}

static int hdmi_8996_vco_enable(struct clk *c, u32 ver)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x1);
	udelay(100);

	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19);
	udelay(100);

	rc = hdmi_8996_perform_sw_calibration(c, ver);
	if (rc) {
		DEV_ERR("%s: software calibration failed\n", __func__);
		return rc;
	}

	rc = hdmi_8996_pll_lock_status(io);
	if (!rc) {
		DEV_ERR("%s: PLL not locked\n", __func__);
		return rc;
	}

	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
		       QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
		       0x6F);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET,
		       QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
		       0x6F);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET,
		       QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
		       0x6F);
	MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET,
		       QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
		       0x6F);

	/* Disable SSC */
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER1, 0x0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER2, 0x0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE1, 0x0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE2, 0x0);
	MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_EN_CENTER, 0x2);

	rc = hdmi_8996_phy_ready_status(io);
	if (!rc) {
		DEV_ERR("%s: PHY not READY\n", __func__);
		return rc;
	}

	/* Restart the retiming buffer */
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x18);
	udelay(1);
	MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19);

	io->pll_on = true;
	return 0;
}

static int hdmi_8996_v1_vco_enable(struct clk *c)
{
	return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V1);
}

static int hdmi_8996_v2_vco_enable(struct clk *c)
{
	return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V2);
}

static int hdmi_8996_v3_vco_enable(struct clk *c)
{
	return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3);
}

static int hdmi_8996_v3_1p8_vco_enable(struct clk *c)
{
	return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3_1_8);
}

static int hdmi_8996_vco_get_lock_range(struct clk *c, unsigned long pixel_clk)
{
	u32 rng = 64, cmp_cnt = 1024;
	u32 coreclk_div = 5, clks_pll_divsel = 2;
	u32 vco_freq, vco_ratio, ppm_range;
	u64 bclk;
	struct hdmi_8996_v3_post_divider pd;

	bclk = ((u64)pixel_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;

	DEV_DBG("%s: rate=%ld\n", __func__, pixel_clk);

	if (hdmi_8996_v3_get_post_div(&pd, bclk) ||
		pd.vco_ratio <= 0 || pd.vco_freq <= 0) {
		DEV_ERR("%s: couldn't get post div\n", __func__);
		return -EINVAL;
	}

	do_div(pd.vco_freq, HDMI_KHZ_TO_HZ * HDMI_KHZ_TO_HZ);

	vco_freq  = (u32) pd.vco_freq;
	vco_ratio = (u32) pd.vco_ratio;

	DEV_DBG("%s: freq %d, ratio %d\n", __func__,
		vco_freq, vco_ratio);

	ppm_range = (rng * HDMI_REF_CLOCK) / cmp_cnt;
	ppm_range /= vco_freq / vco_ratio;
	ppm_range *= coreclk_div * clks_pll_divsel;

	DEV_DBG("%s: ppm range: %d\n", __func__, ppm_range);

	return ppm_range;
}

static int hdmi_8996_vco_rate_atomic_update(struct clk *c,
	unsigned long rate, u32 ver)
{
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	void __iomem *pll;
	struct hdmi_8996_phy_pll_reg_cfg cfg = {0};
	int rc = 0;

	rc = hdmi_8996_calculate(rate, &cfg, ver);
	if (rc) {
		DEV_ERR("%s: PLL calculation failed\n", __func__);
		goto end;
	}

	pll = io->pll_base;

	MDSS_PLL_REG_W(pll, QSERDES_COM_DEC_START_MODE0,
		       cfg.com_dec_start_mode0);
	MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START1_MODE0,
		       cfg.com_div_frac_start1_mode0);
	MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START2_MODE0,
		       cfg.com_div_frac_start2_mode0);
	MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START3_MODE0,
		       cfg.com_div_frac_start3_mode0);

	MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x01);
	MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x00);

	DEV_DBG("%s: updated to rate %ld\n", __func__, rate);
end:
	return rc;
}

static int hdmi_8996_vco_set_rate(struct clk *c, unsigned long rate, u32 ver)
{
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	unsigned int set_power_dwn = 0;
	bool atomic_update = false;
	int rc, pll_lock_range;

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		DEV_ERR("pll resource can't be enabled\n");
		return rc;
	}

	DEV_DBG("%s: rate %ld\n", __func__, rate);

	if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0) &&
		MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) {
		pll_lock_range = hdmi_8996_vco_get_lock_range(c, vco->rate);

		if (pll_lock_range > 0 && vco->rate) {
			u32 range_limit;

			range_limit  = vco->rate *
				(pll_lock_range / HDMI_KHZ_TO_HZ);
			range_limit /= HDMI_KHZ_TO_HZ;

			DEV_DBG("%s: range limit %d\n", __func__, range_limit);

			if (abs(rate - vco->rate) < range_limit)
				atomic_update = true;
		}
	}

	if (io->pll_on && !atomic_update)
		set_power_dwn = 1;

	if (atomic_update) {
		hdmi_8996_vco_rate_atomic_update(c, rate, ver);
	} else {
		rc = hdmi_8996_phy_pll_set_clk_rate(c, rate, ver);
		if (rc)
			DEV_ERR("%s: Failed to set clk rate\n", __func__);
	}

	mdss_pll_resource_enable(io, false);

	if (set_power_dwn)
		hdmi_8996_vco_enable(c, ver);

	vco->rate = rate;
	vco->rate_set = true;

	return 0;
}

static int hdmi_8996_v1_vco_set_rate(struct clk *c, unsigned long rate)
{
	return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V1);
}

static int hdmi_8996_v2_vco_set_rate(struct clk *c, unsigned long rate)
{
	return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V2);
}

static int hdmi_8996_v3_vco_set_rate(struct clk *c, unsigned long rate)
{
	return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3);
}

static int hdmi_8996_v3_1p8_vco_set_rate(struct clk *c, unsigned long rate)
{
	return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3_1_8);
}

static unsigned long hdmi_get_hsclk_sel_divisor(unsigned long hsclk_sel)
{
	unsigned long divisor;

	switch (hsclk_sel) {
	case 0:
		divisor = 2;
		break;
	case 1:
		divisor = 6;
		break;
	case 2:
		divisor = 10;
		break;
	case 3:
		divisor = 14;
		break;
	case 4:
		divisor = 3;
		break;
	case 5:
		divisor = 9;
		break;
	case 6:
	case 13:
		divisor = 15;
		break;
	case 7:
		divisor = 21;
		break;
	case 8:
		divisor = 4;
		break;
	case 9:
		divisor = 12;
		break;
	case 10:
		divisor = 20;
		break;
	case 11:
		divisor = 28;
		break;
	case 12:
		divisor = 5;
		break;
	case 14:
		divisor = 25;
		break;
	case 15:
		divisor = 35;
		break;
	default:
		divisor = 1;
		DEV_ERR("%s: invalid hsclk_sel value = %lu",
				__func__, hsclk_sel);
		break;
	}

	return divisor;
}

static unsigned long hdmi_8996_vco_get_rate(struct clk *c)
{
	unsigned long freq = 0, hsclk_sel = 0, tx_band = 0, dec_start = 0,
		      div_frac_start = 0, vco_clock_freq = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	if (mdss_pll_resource_enable(io, true)) {
		DEV_ERR("%s: pll resource can't be enabled\n", __func__);
		return freq;
	}

	dec_start = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEC_START_MODE0);

	div_frac_start =
		MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_DIV_FRAC_START1_MODE0) |
		MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_DIV_FRAC_START2_MODE0) << 8 |
		MDSS_PLL_REG_R(io->pll_base,
				QSERDES_COM_DIV_FRAC_START3_MODE0) << 16;

	vco_clock_freq = (dec_start + (div_frac_start / (1 << 20)))
		* 4 * (HDMI_REF_CLOCK);

	hsclk_sel = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_HSCLK_SEL) & 0x15;
	hsclk_sel = hdmi_get_hsclk_sel_divisor(hsclk_sel);
	tx_band = MDSS_PLL_REG_R(io->pll_base + HDMI_TX_L0_BASE_OFFSET,
			QSERDES_TX_L0_TX_BAND) & 0x3;

	freq = vco_clock_freq / (10 * hsclk_sel * (1 << tx_band));

	mdss_pll_resource_enable(io, false);

	DEV_DBG("%s: freq = %lu\n", __func__, freq);

	return freq;
}

static long hdmi_8996_vco_round_rate(struct clk *c, unsigned long rate)
{
	unsigned long rrate = rate;

	DEV_DBG("rrate=%ld\n", rrate);

	return rrate;
}

static int hdmi_8996_vco_prepare(struct clk *c, u32 ver)
{
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	int ret = 0;

	DEV_DBG("rate=%ld\n", vco->rate);

	if (!vco->rate_set && vco->rate)
		ret = hdmi_8996_vco_set_rate(c, vco->rate, ver);

	if (!ret) {
		ret = mdss_pll_resource_enable(io, true);
		if (ret)
			DEV_ERR("pll resource can't be enabled\n");
	}

	return ret;
}

static int hdmi_8996_v1_vco_prepare(struct clk *c)
{
	return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V1);
}

static int hdmi_8996_v2_vco_prepare(struct clk *c)
{
	return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V2);
}

static int hdmi_8996_v3_vco_prepare(struct clk *c)
{
	return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3);
}

static int hdmi_8996_v3_1p8_vco_prepare(struct clk *c)
{
	return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3_1_8);
}

static void hdmi_8996_vco_unprepare(struct clk *c)
{
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	vco->rate_set = false;

	if (!io) {
		DEV_ERR("Invalid input parameter\n");
		return;
	}

	if (!io->pll_on &&
		mdss_pll_resource_enable(io, true)) {
		DEV_ERR("pll resource can't be enabled\n");
		return;
	}

	io->handoff_resources = false;
	mdss_pll_resource_enable(io, false);
	io->pll_on = false;
}

static enum handoff hdmi_8996_vco_handoff(struct clk *c)
{
	enum handoff ret = HANDOFF_DISABLED_CLK;
	struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	if (is_gdsc_disabled(io))
		return HANDOFF_DISABLED_CLK;

	if (mdss_pll_resource_enable(io, true)) {
		DEV_ERR("pll resource can't be enabled\n");
		return ret;
	}

	io->handoff_resources = true;

	if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0)) {
		if (MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) {
			io->pll_on = true;
			c->rate = hdmi_8996_vco_get_rate(c);
			vco->rate = c->rate;
			ret = HANDOFF_ENABLED_CLK;
		} else {
			io->handoff_resources = false;
			mdss_pll_resource_enable(io, false);
			DEV_DBG("%s: PHY not ready\n", __func__);
		}
	} else {
		io->handoff_resources = false;
		mdss_pll_resource_enable(io, false);
		DEV_DBG("%s: PLL not locked\n", __func__);
	}

	DEV_DBG("done, ret=%d\n", ret);
	return ret;
}

static const struct clk_ops hdmi_8996_v1_vco_clk_ops = {
	.enable = hdmi_8996_v1_vco_enable,
	.set_rate = hdmi_8996_v1_vco_set_rate,
	.get_rate = hdmi_8996_vco_get_rate,
	.round_rate = hdmi_8996_vco_round_rate,
	.prepare = hdmi_8996_v1_vco_prepare,
	.unprepare = hdmi_8996_vco_unprepare,
	.handoff = hdmi_8996_vco_handoff,
};

static const struct clk_ops hdmi_8996_v2_vco_clk_ops = {
	.enable = hdmi_8996_v2_vco_enable,
	.set_rate = hdmi_8996_v2_vco_set_rate,
	.get_rate = hdmi_8996_vco_get_rate,
	.round_rate = hdmi_8996_vco_round_rate,
	.prepare = hdmi_8996_v2_vco_prepare,
	.unprepare = hdmi_8996_vco_unprepare,
	.handoff = hdmi_8996_vco_handoff,
};

static const struct clk_ops hdmi_8996_v3_vco_clk_ops = {
	.enable = hdmi_8996_v3_vco_enable,
	.set_rate = hdmi_8996_v3_vco_set_rate,
	.get_rate = hdmi_8996_vco_get_rate,
	.round_rate = hdmi_8996_vco_round_rate,
	.prepare = hdmi_8996_v3_vco_prepare,
	.unprepare = hdmi_8996_vco_unprepare,
	.handoff = hdmi_8996_vco_handoff,
};

static const struct clk_ops hdmi_8996_v3_1p8_vco_clk_ops = {
	.enable = hdmi_8996_v3_1p8_vco_enable,
	.set_rate = hdmi_8996_v3_1p8_vco_set_rate,
	.get_rate = hdmi_8996_vco_get_rate,
	.round_rate = hdmi_8996_vco_round_rate,
	.prepare = hdmi_8996_v3_1p8_vco_prepare,
	.unprepare = hdmi_8996_vco_unprepare,
	.handoff = hdmi_8996_vco_handoff,
};


static struct hdmi_pll_vco_clk hdmi_vco_clk = {
	.c = {
		.dbg_name = "hdmi_8996_vco_clk",
		.ops = &hdmi_8996_v1_vco_clk_ops,
		CLK_INIT(hdmi_vco_clk.c),
	},
};

static struct clk_lookup hdmipllcc_8996[] = {
	CLK_LIST(hdmi_vco_clk),
};

int hdmi_8996_pll_clock_register(struct platform_device *pdev,
				 struct mdss_pll_resources *pll_res, u32 ver)
{
	int rc = -ENOTSUPP;

	if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) {
		DEV_ERR("%s: Invalid input parameters\n", __func__);
		return -EPROBE_DEFER;
	}

	/* Set client data for vco, mux and div clocks */
	hdmi_vco_clk.priv = pll_res;

	switch (ver) {
	case HDMI_VERSION_8996_V2:
		hdmi_vco_clk.c.ops = &hdmi_8996_v2_vco_clk_ops;
		break;
	case HDMI_VERSION_8996_V3:
		hdmi_vco_clk.c.ops = &hdmi_8996_v3_vco_clk_ops;
		break;
	case HDMI_VERSION_8996_V3_1_8:
		hdmi_vco_clk.c.ops = &hdmi_8996_v3_1p8_vco_clk_ops;
		break;
	default:
		hdmi_vco_clk.c.ops = &hdmi_8996_v1_vco_clk_ops;
		break;
	}

	rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8996,
					ARRAY_SIZE(hdmipllcc_8996));
	if (rc) {
		DEV_ERR("%s: Clock register failed rc=%d\n", __func__, rc);
		rc = -EPROBE_DEFER;
	} else {
		DEV_DBG("%s SUCCESS\n", __func__);
	}

	return rc;
}

int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev,
				 struct mdss_pll_resources *pll_res)
{
	return hdmi_8996_pll_clock_register(pdev, pll_res,
						HDMI_VERSION_8996_V1);
}

int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev,
				 struct mdss_pll_resources *pll_res)
{
	return hdmi_8996_pll_clock_register(pdev, pll_res,
						HDMI_VERSION_8996_V2);
}

int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev,
				 struct mdss_pll_resources *pll_res)
{
	return hdmi_8996_pll_clock_register(pdev, pll_res,
						HDMI_VERSION_8996_V3);
}

int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev,
				 struct mdss_pll_resources *pll_res)
{
	return hdmi_8996_pll_clock_register(pdev, pll_res,
						HDMI_VERSION_8996_V3_1_8);
}
