summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c188
1 files changed, 145 insertions, 43 deletions
diff --git a/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c b/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c
index c74162ae8148..ec0c492b2fa6 100644
--- a/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c
+++ b/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c
@@ -29,6 +29,7 @@
#define _R(x, y) MDSS_PLL_REG_R(x, y)
/* PLL REGISTERS */
+#define FREQ_UPDATE (0x008)
#define BIAS_EN_CLKBUFLR_EN (0x034)
#define CLK_ENABLE1 (0x038)
#define SYS_CLK_CTRL (0x03C)
@@ -80,6 +81,7 @@
#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO 10
#define HDMI_MHZ_TO_HZ 1000000
#define HDMI_HZ_TO_MHZ 1000000
+#define HDMI_KHZ_TO_HZ 1000
#define HDMI_REF_CLOCK_MHZ 19.2
#define HDMI_REF_CLOCK_HZ (HDMI_REF_CLOCK_MHZ * 1000000)
#define HDMI_VCO_MIN_RATE_HZ 25000000
@@ -609,49 +611,6 @@ static int hdmi_8998_phy_ready_status(struct mdss_pll_resources *io)
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;
@@ -700,6 +659,149 @@ static int hdmi_8998_pll_enable(struct clk *c)
return rc;
}
+/*
+ * Get the clock range allowed in atomic update. If clock rate
+ * goes beyond this range, a full tear down is required to set
+ * the new pixel clock.
+ */
+static int hdmi_8998_vco_get_lock_range(struct clk *c,
+ unsigned long pixel_clk)
+{
+ const u32 rng = 64, cmp_cnt = 1024;
+ const u32 coreclk_div = 5, clks_pll_divsel = 2;
+ u32 vco_freq, vco_ratio, ppm_range;
+ struct hdmi_8998_reg_cfg cfg = {0};
+
+ pr_debug("rate=%ld\n", pixel_clk);
+
+ hdmi_8998_get_div(&cfg, pixel_clk);
+ if (cfg.vco_ratio <= 0 || cfg.vco_freq <= 0) {
+ pr_err("couldn't get post div\n");
+ return -EINVAL;
+ }
+
+ vco_freq = (u32) cfg.vco_freq;
+ vco_ratio = (u32) cfg.vco_ratio;
+
+ pr_debug("freq %d, ratio %d\n", vco_freq, vco_ratio);
+
+ ppm_range = (rng * HDMI_REF_CLOCK_HZ) / cmp_cnt;
+ ppm_range /= vco_freq / vco_ratio;
+ ppm_range *= coreclk_div * clks_pll_divsel;
+
+ pr_debug("ppm range: %d\n", ppm_range);
+
+ return ppm_range;
+}
+
+static int hdmi_8998_vco_rate_atomic_update(struct clk *c,
+ unsigned long rate)
+{
+ struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);
+ struct mdss_pll_resources *io = vco->priv;
+ void __iomem *pll;
+ struct hdmi_8998_reg_cfg cfg = {0};
+ int rc = 0;
+
+ rc = hdmi_8998_config_phy(rate, &cfg);
+ if (rc) {
+ pr_err("rate calculation failed\n, rc=%d", rc);
+ goto end;
+ }
+
+ pll = io->pll_base;
+
+ _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, FREQ_UPDATE, 0x01);
+ _W(pll, FREQ_UPDATE, 0x00);
+
+ pr_debug("updated to rate %ld\n", rate);
+end:
+ return rc;
+}
+
+static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate)
+{
+ struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);
+ struct mdss_pll_resources *io = vco->priv;
+ unsigned int set_power_dwn = 0;
+ bool atomic_update = false;
+ int pll_lock_range = 0;
+ int rc = 0;
+
+ rc = mdss_pll_resource_enable(io, true);
+ if (rc) {
+ pr_err("pll resource enable failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_debug("rate %ld\n", rate);
+
+ if (_R(io->pll_base, C_READY_STATUS) & BIT(0) &&
+ _R(io->phy_base, PHY_STATUS) & BIT(0)) {
+ pll_lock_range = hdmi_8998_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;
+
+ pr_debug("range limit %d\n", 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)
+ rc = hdmi_8998_vco_rate_atomic_update(c, rate);
+ else
+ rc = hdmi_8998_pll_set_clk_rate(c, rate);
+
+ if (rc) {
+ pr_err("failed to set clk rate\n");
+ goto error;
+ }
+
+ if (set_power_dwn) {
+ rc = hdmi_8998_pll_enable(c);
+ if (rc) {
+ pr_err("failed to enable pll, 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_vco_prepare(struct clk *c)
{
struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c);