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

#define pr_fmt(fmt) "%s: " fmt, __func__

#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-8998.h>

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

#define _W(x, y, z) MDSS_PLL_REG_W(x, y, z)
#define _R(x, y)    MDSS_PLL_REG_R(x, y)

/* PLL REGISTERS */
#define BIAS_EN_CLKBUFLR_EN          (0x034)
#define CLK_ENABLE1                  (0x038)
#define SYS_CLK_CTRL                 (0x03C)
#define SYSCLK_BUF_ENABLE            (0x040)
#define PLL_IVCO                     (0x048)
#define CP_CTRL_MODE0                (0x060)
#define PLL_RCTRL_MODE0              (0x068)
#define PLL_CCTRL_MODE0              (0x070)
#define SYSCLK_EN_SEL                (0x080)
#define RESETSM_CNTRL                (0x088)
#define LOCK_CMP_EN                  (0x090)
#define LOCK_CMP1_MODE0              (0x098)
#define LOCK_CMP2_MODE0              (0x09C)
#define LOCK_CMP3_MODE0              (0x0A0)
#define DEC_START_MODE0              (0x0B0)
#define DIV_FRAC_START1_MODE0        (0x0B8)
#define DIV_FRAC_START2_MODE0        (0x0BC)
#define DIV_FRAC_START3_MODE0        (0x0C0)
#define INTEGLOOP_GAIN0_MODE0        (0x0D8)
#define INTEGLOOP_GAIN1_MODE0        (0x0DC)
#define VCO_TUNE_CTRL                (0x0EC)
#define VCO_TUNE_MAP                 (0x0F0)
#define CLK_SELECT                   (0x138)
#define HSCLK_SEL                    (0x13C)
#define CORECLK_DIV_MODE0            (0x148)
#define CORE_CLK_EN                  (0x154)
#define C_READY_STATUS               (0x158)
#define SVS_MODE_CLK_SEL             (0x164)

/* Tx Channel PHY registers */
#define PHY_TX_EMP_POST1_LVL(n)              ((((n) * 0x200) + 0x400) + 0x000)
#define PHY_TX_INTERFACE_SELECT_TX_BAND(n)   ((((n) * 0x200) + 0x400) + 0x008)
#define PHY_TX_CLKBUF_TERM_ENABLE(n)         ((((n) * 0x200) + 0x400) + 0x00C)
#define PHY_TX_DRV_LVL_RES_CODE_OFFSET(n)    ((((n) * 0x200) + 0x400) + 0x014)
#define PHY_TX_DRV_LVL(n)                    ((((n) * 0x200) + 0x400) + 0x018)
#define PHY_TX_LANE_CONFIG(n)                ((((n) * 0x200) + 0x400) + 0x01C)
#define PHY_TX_PRE_DRIVER_1(n)               ((((n) * 0x200) + 0x400) + 0x024)
#define PHY_TX_PRE_DRIVER_2(n)               ((((n) * 0x200) + 0x400) + 0x028)
#define PHY_TX_LANE_MODE(n)                  ((((n) * 0x200) + 0x400) + 0x02C)

/* HDMI PHY registers */
#define PHY_CFG                      (0x00)
#define PHY_PD_CTL                   (0x04)
#define PHY_MODE                     (0x10)
#define PHY_CLOCK                    (0x5C)
#define PHY_CMN_CTRL                 (0x68)
#define PHY_STATUS                   (0xB4)

#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO		10
#define HDMI_MHZ_TO_HZ				1000000
#define HDMI_HZ_TO_MHZ				1000000
#define HDMI_REF_CLOCK_MHZ			19.2
#define HDMI_REF_CLOCK_HZ			(HDMI_REF_CLOCK_MHZ * 1000000)
#define HDMI_VCO_MIN_RATE_HZ			25000000
#define HDMI_VCO_MAX_RATE_HZ			600000000

struct 8998_reg_cfg {
	u32 tx_band;
	u32 svs_mode_clk_sel;
	u32 hsclk_sel;
	u32 lock_cmp_en;
	u32 cctrl_mode0;
	u32 rctrl_mode0;
	u32 cpctrl_mode0;
	u32 dec_start_mode0;
	u32 div_frac_start1_mode0;
	u32 div_frac_start2_mode0;
	u32 div_frac_start3_mode0;
	u32 integloop_gain0_mode0;
	u32 integloop_gain1_mode0;
	u32 lock_cmp1_mode0;
	u32 lock_cmp2_mode0;
	u32 lock_cmp3_mode0;
	u32 ssc_per1;
	u32 ssc_per2;
	u32 ssc_step_size1;
	u32 ssc_step_size2;
	u32 core_clk_en;
	u32 coreclk_div_mode0;
	u32 phy_mode;
	u32 vco_freq;
	u32 hsclk_divsel;
	u32 vco_ratio;
	u32 ssc_en_center;

	u32 l0_tx_drv_lvl;
	u32 l0_tx_emp_post1_lvl;
	u32 l1_tx_drv_lvl;
	u32 l1_tx_emp_post1_lvl;
	u32 l2_tx_drv_lvl;
	u32 l2_tx_emp_post1_lvl;
	u32 l3_tx_drv_lvl;
	u32 l3_tx_emp_post1_lvl;

	u32 l0_pre_driver_1;
	u32 l0_pre_driver_2;
	u32 l1_pre_driver_1;
	u32 l1_pre_driver_2;
	u32 l2_pre_driver_1;
	u32 l2_pre_driver_2;
	u32 l3_pre_driver_1;
	u32 l3_pre_driver_2;

	bool debug;
};

static void hdmi_8998_get_div(struct 8998_reg_cfg * cfg, unsigned long pclk)
{
	u32 const ratio_list[] = {1, 2, 3, 4, 5, 6,
				     9, 10, 12, 15, 25};
	u32 const band_list[] = {0, 1, 2, 3};
	u32 const sz_ratio = ARRAY_SIZE(ratio_list);
	u32 const sz_band = ARRAY_SIZE(band_list);
	u32 const min_freq = 8000, max_freq = 12000;
	u32 const cmp_cnt = 1024;
	u32 const th_min = 500, th_max = 1000;
	u64 bit_clk = pclk * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;
	u32 half_rate_mode = 0;
	u32 freq_optimal, list_elements;
	int optimal_index;
	u32 i, j, k;
	u32 freq_list[sz_ratio * sz_band];
	u32 found_hsclk_divsel = 0, found_vco_ratio;
	u32 found_tx_band_sel, found_vco_freq;

find_optimal_index:
	freq_optimal = max_freq;
	optimal_index = -1;
	list_elements = 0;

	for (i = 0; i < sz_ratio; i++) {
		for (j = 0; j < sz_band; j++) {
			u64 freq = (bit_clk / (1 << half_rate_mode));

			freq *= (ratio_list[i] * (1 << band_list[j]));
			do_div(freq, (u64) HDMI_MHZ_TO_HZ);
			freq_list[list_elements++] = freq;
		}
	}

	for (k = 0; k < ARRAY_SIZE(freq_list); k++) {
		u32 const clks_pll_div = 2, core_clk_div = 5;
		u32 const rng1 = 16, rng2 = 8;
		u32 core_clk, rvar1;
		u32 th1, th2;

		core_clk = (((freq_list[k] /
			      ratio_list[k / sz_band]) /
			      clks_pll_div) / core_clk_div);

		rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt;
		rvar1 *= rng1;
		rvar1 /= core_clk;

		th1 = rvar1;

		rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt;
		rvar1 *= rng2;
		rvar1 /= core_clk;

		th2 = rvar1;

		if (freq_list[k] >= min_freq &&
				freq_list[k] <= max_freq) {
			if ((th1 >= th_min && th1 <= th_max) ||
					(th2 >= th_min && th2 <= th_max)) {
				if (freq_list[k] <= freq_optimal) {
					freq_optimal = freq_list[k];
					optimal_index = k;
				}
			}
		}
	}

	if (optimal_index == -1) {
		if (!half_rate_mode) {
			half_rate_mode = 1;
			goto find_optimal_index;
		} else {
			/* set to default values */
			found_vco_freq = max_freq;
			found_hsclk_divsel = 0;
			found_vco_ratio = 2;
			found_tx_band_sel = 0;
			pr_err("Config error for pclk %ld\n", pclk);
		}
	} else {
		found_vco_ratio = ratio_list[optimal_index / sz_band];
		found_tx_band_sel = band_list[optimal_index % sz_band];
		found_vco_freq = freq_optimal;
	}

	switch (found_vco_ratio) {
	case 1:
		found_hsclk_divsel = 15;
		break;
	case 2:
		found_hsclk_divsel = 0;
		break;
	case 3:
		found_hsclk_divsel = 4;
		break;
	case 4:
		found_hsclk_divsel = 8;
		break;
	case 5:
		found_hsclk_divsel = 12;
		break;
	case 6:
		found_hsclk_divsel = 1;
		break;
	case 9:
		found_hsclk_divsel = 5;
		break;
	case 10:
		found_hsclk_divsel = 2;
		break;
	case 12:
		found_hsclk_divsel = 9;
		break;
	case 15:
		found_hsclk_divsel = 13;
		break;
	case 25:
		found_hsclk_divsel = 14;
		break;
	}

	pr_debug("found_vco_freq=%d\n", found_vco_freq);
	pr_debug("found_hsclk_divsel=%d\n", found_hsclk_divsel);
	pr_debug("found_vco_ratio=%d\n", found_vco_ratio);
	pr_debug("found_tx_band_sel=%d\n", found_tx_band_sel);
	pr_debug("half_rate_mode=%d\n", half_rate_mode);
	pr_debug("optimal_index=%d\n", optimal_index);

	cfg->vco_freq = found_vco_freq;
	cfg->hsclk_divsel = found_hsclk_divsel;
	cfg->vco_ratio = found_vco_ratio;
	cfg->tx_band = found_tx_band_sel;
}

static int hdmi_8998_config_phy(unsigned long rate,
		struct 8998_reg_cfg * cfg)
{
	u64 const high_freq_bit_clk_threshold = 3400000000UL;
	u64 const dig_freq_bit_clk_threshold = 1500000000UL;
	u64 const mid_freq_bit_clk_threshold = 750000000;
	u64 fdata, tmds_clk;
	u64 pll_div = 4 * HDMI_REF_CLOCK_HZ;
	u64 bclk;
	u64 vco_freq_mhz;
	u64 hsclk_sel, dec_start, div_frac_start;
	u64 rem;
	u64 cpctrl, rctrl, cctrl;
	u64 integloop_gain;
	u32 digclk_divsel;
	u32 tmds_bclk_ratio;
	u64 cmp_rng, cmp_cnt = 1024, pll_cmp;
	bool gen_ssc = false;

	bclk = rate * HDMI_BIT_CLK_TO_PIX_CLK_RATIO;

	if (bclk > high_freq_bit_clk_threshold) {
		tmds_clk = rate / 4;
		tmds_bclk_ratio = 1;
	} else {
		tmds_clk = rate;
		tmds_bclk_ratio = 0;
	}

	hdmi_8998_get_div(cfg, rate);

	vco_freq_mhz = cfg->vco_freq * (u64) HDMI_HZ_TO_MHZ;
	fdata = cfg->vco_freq;
	do_div(fdata, cfg->vco_ratio);

	hsclk_sel = cfg->hsclk_divsel;
	dec_start = vco_freq_mhz;
	do_div(dec_start, pll_div);

	div_frac_start = vco_freq_mhz * (1 << 20);
	rem = do_div(div_frac_start, pll_div);
	div_frac_start -= (dec_start * (1 << 20));
	if (rem > (pll_div >> 1))
		div_frac_start++;

	if ((div_frac_start != 0) || gen_ssc) {
		cpctrl = 0x8;
		rctrl = 0x16;
		cctrl = 0x34;
	} else {
		cpctrl = 0x30;
		rctrl = 0x18;
		cctrl = 0x2;
	}

	digclk_divsel = (bclk > dig_freq_bit_clk_threshold) ? 0x1 : 0x2;

	integloop_gain = ((div_frac_start != 0) ||
			gen_ssc) ? 0x3F : 0xC4;
	integloop_gain <<= digclk_divsel;
	integloop_gain = (integloop_gain <= 2046 ? integloop_gain : 0x7FE);

	cmp_rng = gen_ssc ? 0x40 : 0x10;

	pll_cmp = cmp_cnt * fdata;
	rem = do_div(pll_cmp, (u64)(HDMI_REF_CLOCK_MHZ * 10));
	if (rem > ((u64)(HDMI_REF_CLOCK_MHZ * 10) >> 1))
		pll_cmp++;

	pll_cmp =  pll_cmp - 1;

	pr_debug("VCO_FREQ = %u\n", cfg->vco_freq);
	pr_debug("FDATA = %llu\n", fdata);
	pr_debug("DEC_START = %llu\n", dec_start);
	pr_debug("DIV_FRAC_START = %llu\n", div_frac_start);
	pr_debug("CPCTRL = %llu\n", cpctrl);
	pr_debug("RCTRL = %llu\n", rctrl);
	pr_debug("CCTRL = %llu\n", cctrl);
	pr_debug("DIGCLK_DIVSEL = %u\n", digclk_divsel);
	pr_debug("INTEGLOOP_GAIN = %llu\n", integloop_gain);
	pr_debug("CMP_RNG = %llu\n", cmp_rng);
	pr_debug("PLL_CMP = %llu\n", pll_cmp);

	cfg->svs_mode_clk_sel = (digclk_divsel & 0xFF);
	cfg->hsclk_sel = (0x20 | hsclk_sel);
	cfg->lock_cmp_en = (gen_ssc ? 0x4 : 0x0);
	cfg->cctrl_mode0 = (cctrl & 0xFF);
	cfg->rctrl_mode0 = (rctrl & 0xFF);
	cfg->cpctrl_mode0 = (cpctrl & 0xFF);
	cfg->dec_start_mode0 = (dec_start & 0xFF);
	cfg->div_frac_start1_mode0 = (div_frac_start & 0xFF);
	cfg->div_frac_start2_mode0 = ((div_frac_start & 0xFF00) >> 8);
	cfg->div_frac_start3_mode0 = ((div_frac_start & 0xF0000) >> 16);
	cfg->integloop_gain0_mode0 = (integloop_gain & 0xFF);
	cfg->integloop_gain1_mode0 = (integloop_gain & 0xF00) >> 8;
	cfg->lock_cmp1_mode0 = (pll_cmp & 0xFF);
	cfg->lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8);
	cfg->lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
	cfg->ssc_per1 = 0;
	cfg->ssc_per2 = 0;
	cfg->ssc_step_size1 = 0;
	cfg->ssc_step_size2 = 0;
	cfg->core_clk_en = 0x2C;
	cfg->coreclk_div_mode0 = 0x5;
	cfg->phy_mode = (tmds_bclk_ratio ? 0x5 : 0x4);
	cfg->ssc_en_center = 0x0;

	if (bclk > high_freq_bit_clk_threshold) {
		cfg->l0_tx_drv_lvl = 0xA;
		cfg->l0_tx_emp_post1_lvl = 0x3;
		cfg->l1_tx_drv_lvl = 0xA;
		cfg->l1_tx_emp_post1_lvl = 0x3;
		cfg->l2_tx_drv_lvl = 0xA;
		cfg->l2_tx_emp_post1_lvl = 0x3;
		cfg->l3_tx_drv_lvl = 0x8;
		cfg->l3_tx_emp_post1_lvl = 0x3;
		cfg->l0_pre_driver_1 = 0x0;
		cfg->l0_pre_driver_2 = 0x1C;
		cfg->l1_pre_driver_1 = 0x0;
		cfg->l1_pre_driver_2 = 0x1C;
		cfg->l2_pre_driver_1 = 0x0;
		cfg->l2_pre_driver_2 = 0x1C;
		cfg->l3_pre_driver_1 = 0x0;
		cfg->l3_pre_driver_2 = 0x0;
	} else if (bclk > dig_freq_bit_clk_threshold) {
		cfg->l0_tx_drv_lvl = 0x9;
		cfg->l0_tx_emp_post1_lvl = 0x3;
		cfg->l1_tx_drv_lvl = 0x9;
		cfg->l1_tx_emp_post1_lvl = 0x3;
		cfg->l2_tx_drv_lvl = 0x9;
		cfg->l2_tx_emp_post1_lvl = 0x3;
		cfg->l3_tx_drv_lvl = 0x8;
		cfg->l3_tx_emp_post1_lvl = 0x3;
		cfg->l0_pre_driver_1 = 0x0;
		cfg->l0_pre_driver_2 = 0x16;
		cfg->l1_pre_driver_1 = 0x0;
		cfg->l1_pre_driver_2 = 0x16;
		cfg->l2_pre_driver_1 = 0x0;
		cfg->l2_pre_driver_2 = 0x16;
		cfg->l3_pre_driver_1 = 0x0;
		cfg->l3_pre_driver_2 = 0x0;
	} else if (bclk > mid_freq_bit_clk_threshold) {
		cfg->l0_tx_drv_lvl = 0x9;
		cfg->l0_tx_emp_post1_lvl = 0x3;
		cfg->l1_tx_drv_lvl = 0x9;
		cfg->l1_tx_emp_post1_lvl = 0x3;
		cfg->l2_tx_drv_lvl = 0x9;
		cfg->l2_tx_emp_post1_lvl = 0x3;
		cfg->l3_tx_drv_lvl = 0x8;
		cfg->l3_tx_emp_post1_lvl = 0x3;
		cfg->l0_pre_driver_1 = 0x0;
		cfg->l0_pre_driver_2 = 0x0E;
		cfg->l1_pre_driver_1 = 0x0;
		cfg->l1_pre_driver_2 = 0x0E;
		cfg->l2_pre_driver_1 = 0x0;
		cfg->l2_pre_driver_2 = 0x0E;
		cfg->l3_pre_driver_1 = 0x0;
		cfg->l3_pre_driver_2 = 0x0;
	} else {
		cfg->l0_tx_drv_lvl = 0x0;
		cfg->l0_tx_emp_post1_lvl = 0x0;
		cfg->l1_tx_drv_lvl = 0x0;
		cfg->l1_tx_emp_post1_lvl = 0x0;
		cfg->l2_tx_drv_lvl = 0x0;
		cfg->l2_tx_emp_post1_lvl = 0x0;
		cfg->l3_tx_drv_lvl = 0x0;
		cfg->l3_tx_emp_post1_lvl = 0x0;
		cfg->l0_pre_driver_1 = 0x0;
		cfg->l0_pre_driver_2 = 0x01;
		cfg->l1_pre_driver_1 = 0x0;
		cfg->l1_pre_driver_2 = 0x01;
		cfg->l2_pre_driver_1 = 0x0;
		cfg->l2_pre_driver_2 = 0x01;
		cfg->l3_pre_driver_1 = 0x0;
		cfg->l3_pre_driver_2 = 0x0;
	}

	return 0;
}

static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	struct 8998_reg_cfg cfg = {0};
	void __iomem *phy = io->phy_base, *pll = io->pll_base;

	rc = hdmi_8998_config_phy(rate, &cfg);
	if (rc) {
		pr_err("rate calculation failed\n, rc=%d\n", rc);
		return rc;
	}

	_W(phy, PHY_PD_CTL, 0x0);
	udelay(500);

	_W(phy, PHY_PD_CTL, 0x1);
	_W(pll, RESETSM_CNTRL, 0x20);
	_W(phy, PHY_CMN_CTRL, 0x6);
	_W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(0), cfg.tx_band);
	_W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(1), cfg.tx_band);
	_W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(2), cfg.tx_band);
	_W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(3), cfg.tx_band);
	_W(pll, PHY_TX_CLKBUF_TERM_ENABLE(0), 0x1);
	_W(pll, PHY_TX_LANE_MODE(0), 0x20);
	_W(pll, PHY_TX_LANE_MODE(1), 0x20);
	_W(pll, PHY_TX_LANE_MODE(2), 0x20);
	_W(pll, PHY_TX_LANE_MODE(3), 0x20);
	_W(pll, PHY_TX_CLKBUF_TERM_ENABLE(1), 0x1);
	_W(pll, PHY_TX_CLKBUF_TERM_ENABLE(2), 0x1);
	_W(pll, PHY_TX_CLKBUF_TERM_ENABLE(3), 0x1);
	_W(pll, SYSCLK_BUF_ENABLE, 0x2);
	_W(pll, BIAS_EN_CLKBUFLR_EN, 0xB);
	_W(pll, SYSCLK_EN_SEL, 0x37);
	_W(pll, SYS_CLK_CTRL, 0x2);
	_W(pll, CLK_ENABLE1, 0xE);
	_W(pll, PLL_IVCO, 0xF);
	_W(pll, VCO_TUNE_CTRL, 0x0);
	_W(pll, SVS_MODE_CLK_SEL, cfg.svs_mode_clk_sel);
	_W(pll, CLK_SELECT, 0x30);
	_W(pll, HSCLK_SEL, cfg.hsclk_sel);
	_W(pll, LOCK_CMP_EN, cfg.lock_cmp_en);
	_W(pll, PLL_CCTRL_MODE0, cfg.cctrl_mode0);
	_W(pll, PLL_RCTRL_MODE0, cfg.rctrl_mode0);
	_W(pll, CP_CTRL_MODE0, cfg.cpctrl_mode0);
	_W(pll, DEC_START_MODE0, cfg.dec_start_mode0);
	_W(pll, DIV_FRAC_START1_MODE0, cfg.div_frac_start1_mode0);
	_W(pll, DIV_FRAC_START2_MODE0, cfg.div_frac_start2_mode0);
	_W(pll, DIV_FRAC_START3_MODE0, cfg.div_frac_start3_mode0);
	_W(pll, INTEGLOOP_GAIN0_MODE0, cfg.integloop_gain0_mode0);
	_W(pll, INTEGLOOP_GAIN1_MODE0, cfg.integloop_gain1_mode0);
	_W(pll, LOCK_CMP1_MODE0, cfg.lock_cmp1_mode0);
	_W(pll, LOCK_CMP2_MODE0, cfg.lock_cmp2_mode0);
	_W(pll, LOCK_CMP3_MODE0, cfg.lock_cmp3_mode0);
	_W(pll, VCO_TUNE_MAP, 0x0);
	_W(pll, CORE_CLK_EN, cfg.core_clk_en);
	_W(pll, CORECLK_DIV_MODE0, cfg.coreclk_div_mode0);

	_W(pll, PHY_TX_DRV_LVL(0), cfg.l0_tx_drv_lvl);
	_W(pll, PHY_TX_DRV_LVL(1), cfg.l1_tx_drv_lvl);
	_W(pll, PHY_TX_DRV_LVL(2), cfg.l2_tx_drv_lvl);
	_W(pll, PHY_TX_DRV_LVL(3), cfg.l3_tx_drv_lvl);

	_W(pll, PHY_TX_EMP_POST1_LVL(0), cfg.l0_tx_emp_post1_lvl);
	_W(pll, PHY_TX_EMP_POST1_LVL(1), cfg.l1_tx_emp_post1_lvl);
	_W(pll, PHY_TX_EMP_POST1_LVL(2), cfg.l2_tx_emp_post1_lvl);
	_W(pll, PHY_TX_EMP_POST1_LVL(3), cfg.l3_tx_emp_post1_lvl);

	_W(pll, PHY_TX_PRE_DRIVER_1(0), cfg.l0_pre_driver_1);
	_W(pll, PHY_TX_PRE_DRIVER_1(1), cfg.l1_pre_driver_1);
	_W(pll, PHY_TX_PRE_DRIVER_1(2), cfg.l2_pre_driver_1);
	_W(pll, PHY_TX_PRE_DRIVER_1(3), cfg.l3_pre_driver_1);

	_W(pll, PHY_TX_PRE_DRIVER_2(0), cfg.l0_pre_driver_2);
	_W(pll, PHY_TX_PRE_DRIVER_2(1), cfg.l1_pre_driver_2);
	_W(pll, PHY_TX_PRE_DRIVER_2(2), cfg.l2_pre_driver_2);
	_W(pll, PHY_TX_PRE_DRIVER_2(3), cfg.l3_pre_driver_2);

	_W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(0), 0x0);
	_W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(1), 0x0);
	_W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(2), 0x0);
	_W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(3), 0x0);

	_W(phy, PHY_MODE, cfg.phy_mode);

	_W(pll, PHY_TX_LANE_CONFIG(0), 0x10);
	_W(pll, PHY_TX_LANE_CONFIG(1), 0x10);
	_W(pll, PHY_TX_LANE_CONFIG(2), 0x10);
	_W(pll, PHY_TX_LANE_CONFIG(3), 0x10);

	/* Ensure all registers are flushed to hardware */
	wmb();

	return 0;
}

static int hdmi_8998_pll_lock_status(struct mdss_pll_resources *io)
{
	u32 const delay_us = 100;
	u32 const timeout_us = 5000;
	u32 status;
	int rc = 0;
	void __iomem *pll = io->pll_base;

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		pr_err("pll resource can't be enabled\n");
		return rc;
	}
	rc = readl_poll_timeout_atomic(pll + C_READY_STATUS,
			status,
			((status & BIT(0)) > 0),
			delay_us,
			timeout_us);
	if (rc)
		pr_err("HDMI PLL(%d) lock failed, status=0x%08x\n",
				io->index, status);
	else
		pr_debug("HDMI PLL(%d) lock passed, status=0x%08x\n",
				io->index, status);

	mdss_pll_resource_enable(io, false);

	return rc;
}

static int hdmi_8998_phy_ready_status(struct mdss_pll_resources *io)
{
	u32 const delay_us = 100;
	u32 const timeout_us = 5000;
	u32 status;
	int rc = 0;
	void __iomem *phy = io->phy_base;

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

	rc = readl_poll_timeout_atomic(phy + PHY_STATUS,
			status,
			((status & BIT(0)) > 0),
			delay_us,
			timeout_us);
	if (rc)
		pr_err("HDMI PHY(%d) not ready, status=0x%08x\n",
				io->index, status);
	else
		pr_debug("HDMI PHY(%d) ready, status=0x%08x\n",
				io->index, status);

	mdss_pll_resource_enable(io, false);

	return rc;
}

static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		pr_err("pll resource enable failed, rc=%d\n", rc);
		return rc;
	}

	if (io->pll_on)
		goto error;

	rc = hdmi_8998_pll_set_clk_rate(c, rate);
	if (rc) {
		pr_err("failed to set clk rate, rc=%d\n", rc);
		goto error;
	}

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

error:
	(void)mdss_pll_resource_enable(io, false);

	return rc;
}

static long hdmi_8998_vco_round_rate(struct clk *c, unsigned long rate)
{
	unsigned long rrate = rate;
	struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);

	if (rate < vco->min_rate)
		rrate = vco->min_rate;
	if (rate > vco->max_rate)
		rrate = vco->max_rate;

	return rrate;
}

static int hdmi_8998_pll_enable(struct clk *c)
{
	int rc = 0;
	struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);
	struct mdss_pll_resources *io = vco->priv;
	void __iomem *phy = io->phy_base, *pll = io->pll_base;

	_W(phy, PHY_CFG, 0x1);
	udelay(100);
	_W(phy, PHY_CFG, 0x59);
	udelay(100);

	_W(phy, PHY_CLOCK, 0x6);

	/* Ensure all registers are flushed to hardware */
	wmb();

	rc = hdmi_8998_pll_lock_status(io);
	if (rc) {
		pr_err("PLL not locked, rc=%d\n", rc);
		return rc;
	}

	_W(pll, PHY_TX_LANE_CONFIG(0), 0x1F);
	_W(pll, PHY_TX_LANE_CONFIG(1), 0x1F);
	_W(pll, PHY_TX_LANE_CONFIG(2), 0x1F);
	_W(pll, PHY_TX_LANE_CONFIG(3), 0x1F);

	/* Ensure all registers are flushed to hardware */
	wmb();

	rc = hdmi_8998_phy_ready_status(io);
	if (rc) {
		pr_err("PHY NOT READY, rc=%d\n", rc);
		return rc;
	}

	_W(phy, PHY_CFG, 0x58);
	udelay(1);
	_W(phy, PHY_CFG, 0x59);

	/* Ensure all registers are flushed to hardware */
	wmb();

	io->pll_on = true;
	return rc;
}

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

	if (!io) {
		pr_err("hdmi pll resources are not available\n");
		return -EINVAL;
	}

	rc = mdss_pll_resource_enable(io, true);
	if (rc) {
		pr_err("pll resource enable failed, rc=%d\n", rc);
		return rc;
	}

	if (!vco->rate_set && vco->rate) {
		rc = hdmi_8998_pll_set_clk_rate(c, vco->rate);
		if (rc) {
			pr_err("set rate failed, rc=%d\n", rc);
			goto error;
		}
	}

	rc = hdmi_8998_pll_enable(c);
	if (rc)
		pr_err("pll enabled failed, rc=%d\n", rc);

error:
	if (rc)
		mdss_pll_resource_enable(io, false);

	return rc;
}

static void hdmi_8998_pll_disable(struct hdmi_pll_vco_clk *vco)
{
	struct mdss_pll_resources *io = vco->priv;
	void __iomem *phy = io->phy_base;

	if (!io->pll_on)
		return;

	_W(phy, PHY_PD_CTL, 0x0);

	/* Ensure all registers are flushed to hardware */
	wmb();

	vco->rate_set = false;
	io->handoff_resources = false;
	io->pll_on = false;
}

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

	if (!io) {
		pr_err("HDMI pll resources not available\n");
		return;
	}

	hdmi_8998_pll_disable(vco);
	mdss_pll_resource_enable(io, false);
}

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

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

	io->handoff_resources = true;

	if (_R(io->pll_base, C_READY_STATUS) & BIT(0) &&
			_R(io->phy_base, PHY_STATUS) & BIT(0)) {
		io->pll_on = true;
		/* TODO: calculate rate based on the phy/pll register values. */
		ret = HANDOFF_ENABLED_CLK;
	} else {
		io->handoff_resources = false;
		mdss_pll_resource_enable(io, false);
		pr_debug("%s: PHY/PLL not ready\n", __func__);
	}

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

static const struct clk_ops hdmi_8998_vco_clk_ops = {
	.set_rate = hdmi_8998_vco_set_rate,
	.round_rate = hdmi_8998_vco_round_rate,
	.prepare = hdmi_8998_vco_prepare,
	.unprepare = hdmi_8998_vco_unprepare,
	.handoff = hdmi_8998_vco_handoff,
};

static struct hdmi_pll_vco_clk hdmi_vco_clk = {
	.min_rate = HDMI_VCO_MIN_RATE_HZ,
	.max_rate = HDMI_VCO_MAX_RATE_HZ,
	.c = {
		.dbg_name = "hdmi_8998_vco_clk",
		.ops = &hdmi_8998_vco_clk_ops,
		CLK_INIT(hdmi_vco_clk.c),
	},
};

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

int hdmi_8998_pll_clock_register(struct platform_device *pdev,
				   struct mdss_pll_resources *pll_res)
{
	int rc = 0;

	hdmi_vco_clk.priv = pll_res;

	rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8998,
				   ARRAY_SIZE(hdmipllcc_8998));
	if (rc) {
		pr_err("clock register failed, rc=%d\n", rc);
		return rc;
	}

	return rc;
}
