/* clk provider */
        struct clk_hw hw;
        unsigned long rate;
+       bool restrict_rate_change;
 
        atomic_t usage_count;
 
 
        if (mode != PHY_MODE_DP) {
                ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi);
-               if (ret)
+               if (ret) {
                        dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
-               else
+               } else {
                        hdptx->hdmi_cfg = opts->hdmi;
+                       hdptx->restrict_rate_change = true;
+               }
                return ret;
        }
 
 static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
                                        unsigned long *parent_rate)
 {
-       int i;
+       struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
 
-       if (rate > HDMI20_MAX_RATE)
-               return rate;
+       /*
+        * FIXME: Temporarily allow altering TMDS char rate via CCF.
+        * To be dropped as soon as the RK DW HDMI QP bridge driver
+        * switches to make use of phy_configure().
+        */
+       if (!hdptx->restrict_rate_change && rate != hdptx->hdmi_cfg.tmds_char_rate) {
+               struct phy_configure_opts_hdmi hdmi = {
+                       .tmds_char_rate = rate,
+               };
+               int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi);
 
-       for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
-               if (rate == ropll_tmds_cfg[i].rate)
-                       break;
+               if (ret)
+                       return ret;
 
-       if (i == ARRAY_SIZE(ropll_tmds_cfg) &&
-           !rk_hdptx_phy_clk_pll_calc(rate, NULL))
-               return -EINVAL;
+               hdptx->hdmi_cfg = hdmi;
+       }
 
-       return rate;
+       /*
+        * The TMDS char rate shall be adjusted via phy_configure() only,
+        * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with
+        * a different rate argument.
+        */
+       return hdptx->hdmi_cfg.tmds_char_rate;
 }
 
 static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
 {
        struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
 
+       /* Revert any unlikely TMDS char rate change since round_rate() */
+       if (hdptx->hdmi_cfg.tmds_char_rate != rate) {
+               dev_warn(hdptx->dev, "Reverting unexpected rate change from %lu to %llu\n",
+                        rate, hdptx->hdmi_cfg.tmds_char_rate);
+               hdptx->hdmi_cfg.tmds_char_rate = rate;
+       }
+
+       /*
+        * The TMDS char rate would be normally programmed in HW during
+        * phy_ops.power_on() or clk_ops.prepare() callbacks, but it might
+        * happen that the former gets fired too late, i.e. after this call,
+        * while the latter being executed only once, i.e. when clock remains
+        * in the prepared state during rate changes.
+        */
        return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
 }