| /* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/iopoll.h> |
| #include <linux/delay.h> |
| #include <linux/clk/msm-clock-generic.h> |
| |
| #include "mdss-pll.h" |
| #include "mdss-dp-pll.h" |
| #include "mdss-dp-pll-8998.h" |
| |
| int link2xclk_divsel_set_div(struct div_clk *clk, int div) |
| { |
| int rc; |
| u32 link2xclk_div_tx0, link2xclk_div_tx1; |
| u32 phy_mode; |
| struct mdss_pll_resources *dp_res = clk->priv; |
| |
| rc = mdss_pll_resource_enable(dp_res, true); |
| if (rc) { |
| pr_err("Failed to enable mdss DP PLL resources\n"); |
| return rc; |
| } |
| |
| link2xclk_div_tx0 = MDSS_PLL_REG_R(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_BAND); |
| link2xclk_div_tx1 = MDSS_PLL_REG_R(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_BAND); |
| |
| link2xclk_div_tx0 &= ~0x07; /* bits 0 to 2 */ |
| link2xclk_div_tx1 &= ~0x07; /* bits 0 to 2 */ |
| |
| /* Configure TX band Mux */ |
| link2xclk_div_tx0 |= 0x4; |
| link2xclk_div_tx1 |= 0x4; |
| |
| /*configure DP PHY MODE */ |
| phy_mode = 0x58; |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_BAND, |
| link2xclk_div_tx0); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_BAND, |
| link2xclk_div_tx1); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_MODE, phy_mode); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| |
| pr_debug("%s: div=%d link2xclk_div_tx0=%x, link2xclk_div_tx1=%x\n", |
| __func__, div, link2xclk_div_tx0, link2xclk_div_tx1); |
| |
| mdss_pll_resource_enable(dp_res, false); |
| |
| return rc; |
| } |
| |
| int link2xclk_divsel_get_div(struct div_clk *clk) |
| { |
| int rc; |
| u32 div = 0, phy_mode; |
| struct mdss_pll_resources *dp_res = clk->priv; |
| |
| rc = mdss_pll_resource_enable(dp_res, true); |
| if (rc) { |
| pr_err("Failed to enable dp_res resources\n"); |
| return rc; |
| } |
| |
| phy_mode = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_MODE); |
| |
| if (phy_mode & 0x48) |
| pr_err("%s: DP PAR Rate not correct\n", __func__); |
| |
| if ((phy_mode & 0x3) == 1) |
| div = 10; |
| else if ((phy_mode & 0x3) == 0) |
| div = 5; |
| else |
| pr_err("%s: unsupported div: %d\n", __func__, phy_mode); |
| |
| mdss_pll_resource_enable(dp_res, false); |
| pr_debug("%s: phy_mode=%d, div=%d\n", __func__, |
| phy_mode, div); |
| |
| return div; |
| } |
| |
| int vco_divided_clk_set_div(struct div_clk *clk, int div) |
| { |
| int rc; |
| u32 auxclk_div; |
| struct mdss_pll_resources *dp_res = clk->priv; |
| |
| rc = mdss_pll_resource_enable(dp_res, true); |
| if (rc) { |
| pr_err("Failed to enable mdss DP PLL resources\n"); |
| return rc; |
| } |
| |
| auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); |
| auxclk_div &= ~0x03; /* bits 0 to 1 */ |
| |
| auxclk_div |= 1; /* Default divider */ |
| |
| if (div == 4) |
| auxclk_div |= 2; |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_VCO_DIV, auxclk_div); |
| /* Make sure the PHY registers writes are done */ |
| wmb(); |
| pr_debug("%s: div=%d auxclk_div=%x\n", __func__, div, auxclk_div); |
| |
| mdss_pll_resource_enable(dp_res, false); |
| |
| return rc; |
| } |
| |
| |
| enum handoff vco_divided_clk_handoff(struct clk *c) |
| { |
| /* |
| * Since cont-splash is not enabled, disable handoff |
| * for vco_divider_clk. |
| */ |
| return HANDOFF_DISABLED_CLK; |
| } |
| |
| int vco_divided_clk_get_div(struct div_clk *clk) |
| { |
| int rc; |
| u32 div, auxclk_div; |
| struct mdss_pll_resources *dp_res = clk->priv; |
| |
| rc = mdss_pll_resource_enable(dp_res, true); |
| if (rc) { |
| pr_err("Failed to enable dp_res resources\n"); |
| return rc; |
| } |
| |
| auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); |
| auxclk_div &= 0x03; |
| |
| div = 2; /* Default divider */ |
| if (auxclk_div == 2) |
| div = 4; |
| |
| mdss_pll_resource_enable(dp_res, false); |
| |
| pr_debug("%s: auxclk_div=%d, div=%d\n", __func__, auxclk_div, div); |
| |
| return div; |
| } |
| |
| int dp_config_vco_rate(struct dp_pll_vco_clk *vco, unsigned long rate) |
| { |
| u32 res = 0; |
| struct mdss_pll_resources *dp_res = vco->priv; |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_PD_CTL, 0x3d); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SVS_MODE_CLK_SEL, 0x01); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SYSCLK_EN_SEL, 0x37); |
| |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CLK_ENABLE1, 0x0e); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CLK_SEL, 0x30); |
| |
| /* Different for each clock rates */ |
| if (rate == DP_VCO_HSCLK_RATE_1620MHZDIV1000) { |
| pr_debug("%s: VCO rate: %ld\n", __func__, |
| DP_VCO_RATE_8100MHZDIV1000); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SYS_CLK_CTRL, 0x02); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_HSCLK_SEL, 0x2c); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP_EN, 0x04); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DEC_START_MODE0, 0x69); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START2_MODE0, 0x80); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START3_MODE0, 0x07); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CMN_CONFIG, 0x42); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP1_MODE0, 0xbf); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP2_MODE0, 0x21); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP3_MODE0, 0x00); |
| } else if (rate == DP_VCO_HSCLK_RATE_2700MHZDIV1000) { |
| pr_debug("%s: VCO rate: %ld\n", __func__, |
| DP_VCO_RATE_8100MHZDIV1000); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SYS_CLK_CTRL, 0x06); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_HSCLK_SEL, 0x84); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP_EN, 0x08); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DEC_START_MODE0, 0x69); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START2_MODE0, 0x80); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START3_MODE0, 0x07); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CMN_CONFIG, 0x02); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP1_MODE0, 0x3f); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP2_MODE0, 0x38); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP3_MODE0, 0x00); |
| } else if (rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000) { |
| pr_debug("%s: VCO rate: %ld\n", __func__, |
| DP_VCO_RATE_10800MHZDIV1000); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_SYS_CLK_CTRL, 0x06); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_HSCLK_SEL, 0x80); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP_EN, 0x08); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DEC_START_MODE0, 0x8c); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_DIV_FRAC_START3_MODE0, 0xa0); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CMN_CONFIG, 0x12); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP1_MODE0, 0x7f); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP2_MODE0, 0x70); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_LOCK_CMP3_MODE0, 0x00); |
| } else { |
| pr_err("%s: unsupported rate: %ld\n", __func__, rate); |
| return -EINVAL; |
| } |
| /* Make sure the PLL register writes are done */ |
| wmb(); |
| |
| if ((rate == DP_VCO_HSCLK_RATE_1620MHZDIV1000) |
| || (rate == DP_VCO_HSCLK_RATE_2700MHZDIV1000)) { |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_VCO_DIV, 0x1); |
| } else { |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_VCO_DIV, 0x2); |
| } |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x3f); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_VCO_TUNE_MAP, 0x00); |
| |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_BG_TIMER, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_BG_TIMER, 0x0a); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CORECLK_DIV_MODE0, 0x05); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_VCO_TUNE_CTRL, 0x00); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CP_CTRL_MODE0, 0x06); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_PLL_CCTRL_MODE0, 0x36); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_PLL_RCTRL_MODE0, 0x16); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_PLL_IVCO, 0x07); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x37); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_CORE_CLK_EN, 0x0f); |
| |
| /* Make sure the PLL register writes are done */ |
| wmb(); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_MODE, 0x58); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_TX0_TX1_LANE_CTL, 0x05); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_TX2_TX3_LANE_CTL, 0x05); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TRANSCEIVER_BIAS_EN, |
| 0x1a); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TRANSCEIVER_BIAS_EN, |
| 0x1a); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_VMODE_CTRL1, |
| 0x40); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_VMODE_CTRL1, |
| 0x40); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_PRE_STALL_LDO_BOOST_EN, |
| 0x30); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_PRE_STALL_LDO_BOOST_EN, |
| 0x30); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_INTERFACE_SELECT, |
| 0x3d); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_INTERFACE_SELECT, |
| 0x3d); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x0f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x0f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_RESET_TSYNC_EN, |
| 0x03); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_RESET_TSYNC_EN, |
| 0x03); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TRAN_DRVR_EMP_EN, |
| 0x03); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TRAN_DRVR_EMP_EN, |
| 0x03); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_PARRATE_REC_DETECT_IDLE_EN, |
| 0x00); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_PARRATE_REC_DETECT_IDLE_EN, |
| 0x00); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_INTERFACE_MODE, |
| 0x00); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_INTERFACE_MODE, |
| 0x00); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_BAND, |
| 0x4); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_BAND, |
| 0x4); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| return res; |
| } |
| |
| static bool dp_pll_lock_status(struct mdss_pll_resources *dp_res) |
| { |
| u32 status; |
| bool pll_locked; |
| |
| /* poll for PLL ready status */ |
| if (readl_poll_timeout_atomic((dp_res->pll_base + |
| QSERDES_COM_C_READY_STATUS), |
| status, |
| ((status & BIT(0)) > 0), |
| DP_PLL_POLL_SLEEP_US, |
| DP_PLL_POLL_TIMEOUT_US)) { |
| pr_err("%s: C_READY status is not high. Status=%x\n", |
| __func__, status); |
| pll_locked = false; |
| } else if (readl_poll_timeout_atomic((dp_res->pll_base + |
| DP_PHY_STATUS), |
| status, |
| ((status & BIT(1)) > 0), |
| DP_PLL_POLL_SLEEP_US, |
| DP_PLL_POLL_TIMEOUT_US)) { |
| pr_err("%s: Phy_ready is not high. Status=%x\n", |
| __func__, status); |
| pll_locked = false; |
| } else { |
| pll_locked = true; |
| } |
| |
| return pll_locked; |
| } |
| |
| |
| static int dp_pll_enable(struct clk *c) |
| { |
| int rc = 0; |
| u32 status; |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *dp_res = vco->priv; |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x01); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x05); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x01); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x09); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| MDSS_PLL_REG_W(dp_res->pll_base, |
| QSERDES_COM_RESETSM_CNTRL, 0x20); |
| /* Make sure the PLL register writes are done */ |
| wmb(); |
| /* poll for PLL ready status */ |
| if (readl_poll_timeout_atomic((dp_res->pll_base + |
| QSERDES_COM_C_READY_STATUS), |
| status, |
| ((status & BIT(0)) > 0), |
| DP_PLL_POLL_SLEEP_US, |
| DP_PLL_POLL_TIMEOUT_US)) { |
| pr_err("%s: C_READY status is not high. Status=%x\n", |
| __func__, status); |
| rc = -EINVAL; |
| goto lock_err; |
| } |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x19); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| /* poll for PHY ready status */ |
| if (readl_poll_timeout_atomic((dp_res->phy_base + |
| DP_PHY_STATUS), |
| status, |
| ((status & BIT(1)) > 0), |
| DP_PLL_POLL_SLEEP_US, |
| DP_PLL_POLL_TIMEOUT_US)) { |
| pr_err("%s: Phy_ready is not high. Status=%x\n", |
| __func__, status); |
| rc = -EINVAL; |
| goto lock_err; |
| } |
| |
| pr_debug("%s: PLL is locked\n", __func__); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TRANSCEIVER_BIAS_EN, |
| 0x3f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_HIGHZ_DRVR_EN, |
| 0x10); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TRANSCEIVER_BIAS_EN, |
| 0x3f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_HIGHZ_DRVR_EN, |
| 0x10); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_POL_INV, |
| 0x0a); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_POL_INV, |
| 0x0a); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x18); |
| udelay(2000); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x19); |
| |
| /* |
| * Make sure all the register writes are completed before |
| * doing any other operation |
| */ |
| wmb(); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_LANE_MODE_1, |
| 0xf6); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_LANE_MODE_1, |
| 0xf6); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x1f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x1f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x0f); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_CLKBUF_ENABLE, |
| 0x0f); |
| /* |
| * Make sure all the register writes are completed before |
| * doing any other operation |
| */ |
| wmb(); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x09); |
| udelay(2000); |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_CFG, 0x19); |
| udelay(2000); |
| /* poll for PHY ready status */ |
| if (readl_poll_timeout_atomic((dp_res->phy_base + |
| DP_PHY_STATUS), |
| status, |
| ((status & BIT(1)) > 0), |
| DP_PLL_POLL_SLEEP_US, |
| DP_PLL_POLL_TIMEOUT_US)) { |
| pr_err("%s: Lane_mode: Phy_ready is not high. Status=%x\n", |
| __func__, status); |
| rc = -EINVAL; |
| goto lock_err; |
| } |
| |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_DRV_LVL, |
| 0x2a); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_DRV_LVL, |
| 0x2a); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_TX_EMP_POST1_LVL, |
| 0x20); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_TX_EMP_POST1_LVL, |
| 0x20); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_RES_CODE_LANE_OFFSET_TX, |
| 0x11); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_RES_CODE_LANE_OFFSET_TX, |
| 0x11); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX0_OFFSET + TXn_RES_CODE_LANE_OFFSET_RX, |
| 0x11); |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| QSERDES_TX1_OFFSET + TXn_RES_CODE_LANE_OFFSET_RX, |
| 0x11); |
| /* Make sure the PHY register writes are done */ |
| wmb(); |
| |
| lock_err: |
| return rc; |
| } |
| |
| static int dp_pll_disable(struct clk *c) |
| { |
| int rc = 0; |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *dp_res = vco->priv; |
| |
| /* Assert DP PHY power down */ |
| MDSS_PLL_REG_W(dp_res->phy_base, |
| DP_PHY_PD_CTL, 0x2); |
| /* |
| * Make sure all the register writes to disable PLL are |
| * completed before doing any other operation |
| */ |
| wmb(); |
| |
| return rc; |
| } |
| |
| |
| int dp_vco_prepare(struct clk *c) |
| { |
| int rc = 0; |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *dp_pll_res = vco->priv; |
| |
| DEV_DBG("rate=%ld\n", vco->rate); |
| rc = mdss_pll_resource_enable(dp_pll_res, true); |
| if (rc) { |
| pr_err("Failed to enable mdss DP pll resources\n"); |
| goto error; |
| } |
| |
| rc = dp_pll_enable(c); |
| if (rc) { |
| mdss_pll_resource_enable(dp_pll_res, false); |
| pr_err("ndx=%d failed to enable dsi pll\n", |
| dp_pll_res->index); |
| goto error; |
| } |
| |
| mdss_pll_resource_enable(dp_pll_res, false); |
| error: |
| return rc; |
| } |
| |
| void dp_vco_unprepare(struct clk *c) |
| { |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *io = vco->priv; |
| |
| 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; |
| } |
| dp_pll_disable(c); |
| |
| io->handoff_resources = false; |
| mdss_pll_resource_enable(io, false); |
| io->pll_on = false; |
| } |
| |
| int dp_vco_set_rate(struct clk *c, unsigned long rate) |
| { |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *io = vco->priv; |
| int rc; |
| |
| rc = mdss_pll_resource_enable(io, true); |
| if (rc) { |
| DEV_ERR("pll resource can't be enabled\n"); |
| return rc; |
| } |
| |
| DEV_DBG("DP lane CLK rate=%ld\n", rate); |
| |
| rc = dp_config_vco_rate(vco, rate); |
| if (rc) |
| DEV_ERR("%s: Failed to set clk rate\n", __func__); |
| |
| mdss_pll_resource_enable(io, false); |
| |
| vco->rate = rate; |
| |
| return 0; |
| } |
| |
| unsigned long dp_vco_get_rate(struct clk *c) |
| { |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| int rc; |
| u32 div, hsclk_div, link2xclk_div; |
| u64 vco_rate; |
| struct mdss_pll_resources *pll = vco->priv; |
| |
| rc = mdss_pll_resource_enable(pll, true); |
| if (rc) { |
| pr_err("Failed to enable mdss DP pll=%d\n", pll->index); |
| return rc; |
| } |
| |
| div = MDSS_PLL_REG_R(pll->pll_base, QSERDES_COM_HSCLK_SEL); |
| div &= 0x0f; |
| |
| if (div == 12) |
| hsclk_div = 5; /* Default */ |
| else if (div == 4) |
| hsclk_div = 3; |
| else if (div == 0) |
| hsclk_div = 2; |
| else { |
| pr_debug("unknown divider. forcing to default\n"); |
| hsclk_div = 5; |
| } |
| |
| div = MDSS_PLL_REG_R(pll->phy_base, DP_PHY_MODE); |
| |
| if (div & 0x58) |
| pr_err("%s: DP PAR Rate not correct\n", __func__); |
| |
| if ((div & 0x3) == 1) |
| link2xclk_div = 10; |
| else if ((div & 0x3) == 0) |
| link2xclk_div = 5; |
| else |
| pr_err("%s: unsupported div. Phy_mode: %d\n", __func__, div); |
| |
| if (link2xclk_div == 10) { |
| vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; |
| } else { |
| if (hsclk_div == 5) |
| vco_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000; |
| else if (hsclk_div == 3) |
| vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; |
| else |
| vco_rate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; |
| } |
| |
| pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); |
| |
| mdss_pll_resource_enable(pll, false); |
| |
| return (unsigned long)vco_rate; |
| } |
| |
| long dp_vco_round_rate(struct clk *c, unsigned long rate) |
| { |
| unsigned long rrate = rate; |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| |
| if (rate <= vco->min_rate) |
| rrate = vco->min_rate; |
| else if (rate <= DP_VCO_HSCLK_RATE_2700MHZDIV1000) |
| rrate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; |
| else |
| rrate = vco->max_rate; |
| |
| pr_debug("%s: rrate=%ld\n", __func__, rrate); |
| |
| return rrate; |
| } |
| |
| enum handoff dp_vco_handoff(struct clk *c) |
| { |
| enum handoff ret = HANDOFF_DISABLED_CLK; |
| struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); |
| struct mdss_pll_resources *io = vco->priv; |
| |
| if (mdss_pll_resource_enable(io, true)) { |
| DEV_ERR("pll resource can't be enabled\n"); |
| return ret; |
| } |
| |
| if (dp_pll_lock_status(io)) { |
| io->pll_on = true; |
| c->rate = dp_vco_get_rate(c); |
| io->handoff_resources = true; |
| ret = HANDOFF_ENABLED_CLK; |
| } 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; |
| } |