// 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 #include #include #include #include #include #include #include #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; }