tegra: T124: HDMI: fix pixel clock setting
Jong Kim [Thu, 27 Mar 2014 16:18:58 +0000 (09:18 -0700)]
- Enhance the HDMI pixel clock setting by determining a better parent
  clock rate.  Half resolutions for HDMI pclk are not used due to
  uneven duty cycle.
- Fix the divider value out of sync problem between two registers,
  DISP_DISP_CLOCK_CONTROL and CLK_RST_CONTROLLER_CLK_SOURCE_HDMI, due
  to the rounding difference.  The clk_set_rate() routine uses round-up,
  while the tegra_dc_program_mode() routine uses round-closest.  Due to
  the DVFS, the frequency determination can not exceed the requested
  rate and this means that round-up must be used for divider handling
  instead of round-closest.

bug 1420652

Change-Id: Ib32e79f96dcd272a392de7f852c3c0285f9c453a
Signed-off-by: Sungwook Kim <sungwookk@nvidia.com>
Signed-off-by: Jong Kim <jongk@nvidia.com>
Reviewed-on: http://git-master/r/366390
(cherry picked from commit 6eb5d7e7b5dcd9a118649e5a8d02e35cf45a4fc6)
Reviewed-on: http://git-master/r/392419
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Matthew Pedro <mapedro@nvidia.com>
Reviewed-by: Winnie Hsu <whsu@nvidia.com>

drivers/video/tegra/dc/clock.c
drivers/video/tegra/dc/hdmi.c
drivers/video/tegra/dc/mode.c

index d6fd51c..281d333 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2010 Google, Inc.
  *
- * Copyright (c) 2010-2012, NVIDIA CORPORATION, All rights reserved.
+ * Copyright (c) 2010-2014, NVIDIA CORPORATION, All rights reserved.
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -33,7 +33,7 @@ unsigned long tegra_dc_pclk_round_rate(struct tegra_dc *dc, int pclk)
 
        rate = tegra_dc_clk_get_rate(dc);
 
-       div = DIV_ROUND_CLOSEST(rate * 2, pclk);
+       div = DIV_ROUND_UP(rate * 2, pclk);
 
        if (div < 2)
                return 0;
@@ -48,7 +48,7 @@ unsigned long tegra_dc_pclk_predict_rate(struct clk *parent, int pclk)
 
        rate = clk_get_rate(parent);
 
-       div = DIV_ROUND_CLOSEST(rate * 2, pclk);
+       div = DIV_ROUND_UP(rate * 2, pclk);
 
        if (div < 2)
                return 0;
index 6419cb7..9400e30 100644 (file)
@@ -1969,6 +1969,59 @@ static void tegra_dc_hdmi_disable(struct tegra_dc *dc)
        tegra_dvfs_set_rate(hdmi->clk, 0);
 }
 
+
+/* To determine the best parent clock rate for a nominal HDMI pixel clock
+ * rate for T124 host1x display controller
+ * o inputs:
+ *  - dc: pointer to the display controller
+ *  - parent_clk: pointer to the parent clock
+ *  - pclk: rate of nominal HDMI pixel clock in Hz
+ * o outputs:
+ *  - return: best parent clock rate in Hz
+ */
+static unsigned long  tegra12x_hdmi_determine_parent(
+       struct tegra_dc *dc, struct clk *parent_clk, int pclk)
+{
+       /* T124 hdmi pclk:
+        *   parentClk = pclk * m  (m=1,1.5,2,2.5,...,128.5)
+        *   (refclk * n) = pclk * m  (n=1,1.5,2,2.5,...,128.5)
+        *     (no half resolutions for m due to uneven out duty cycle)
+        *   (refclk * N / 2) = pclk * m  (N=2,3,4,...,257)
+        *   m = (refclk / 2 * N) / pclk  (m=1,2,3,...,128)
+        *     looking for N to make m whole number
+        */
+       int  n, m;
+       int  b, fr, f;
+
+       /* following parameters should come from parent clock */
+       const int  ref  = 12000000;   /* reference clock to parent */
+       const int  pmax = 600000000;  /* max freq of parent clock */
+       const int  pmin = 200000000;  /* min freq of parent clock */
+
+       b = 0;
+       fr = 1000;
+       for (n = 4; (ref / 2 * n) <= pmax; n++) {
+               if ((ref / 2 * n) < pmin)  /* too low */
+                       continue;
+               m = (ref / 2 * n) / (pclk / 1000);
+               if (m <= 1700)  /* for 2 <= m */
+                       continue;
+               f = m % 1000;  /* fractional parts */
+               f = (0 == f) ? f : (1000 - f);  /* round-up */
+               if (0 == f) {  /* exact match */
+                       b = n;
+                       fr = f;
+                       break;
+               } else if (f < fr) {
+                       b = n;
+                       fr = f;
+               }
+       }
+
+       return (unsigned long)(ref / 2 * b);
+}
+
+
 static long tegra_dc_hdmi_setup_clk(struct tegra_dc *dc, struct clk *clk)
 {
        unsigned long rate;
@@ -2000,13 +2053,7 @@ static long tegra_dc_hdmi_setup_clk(struct tegra_dc *dc, struct clk *clk)
         * as out0 is 1/2 of the actual PLL output.
         */
 #if defined(CONFIG_ARCH_TEGRA_12x_SOC)
-       if (dc->mode.pclk == 25200000)
-               rate = dc->mode.pclk  * 10;
-       else {
-               rate = dc->mode.pclk * 2;
-               while (rate < 400000000)
-                       rate *= 2;
-       }
+       rate = tegra12x_hdmi_determine_parent(dc, parent_clk, dc->mode.pclk);
 #else
        rate = dc->mode.pclk * 2;
        while (rate < 500000000)
index feb71c8..6008fa7 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2010 Google, Inc.
  *
- * Copyright (c) 2010-2013, NVIDIA CORPORATION, All rights reserved.
+ * Copyright (c) 2010-2014, NVIDIA CORPORATION, All rights reserved.
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -305,20 +305,18 @@ int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode)
        tegra_dc_writel(dc, val, DC_DISP_DISP_INTERFACE_CONTROL);
 
        rate = tegra_dc_clk_get_rate(dc);
-
        pclk = tegra_dc_pclk_round_rate(dc, mode->pclk);
+       div = (rate * 2 / pclk) - 2;
+       dev_info(&dc->ndev->dev,
+               "nominal-pclk:%d parent:%lu div:%lu.%lu pclk:%lu %d~%d\n",
+               mode->pclk, rate, (div + 2) / 2, ((div + 2) % 2) * 5, pclk,
+               mode->pclk / 100 * 99, mode->pclk / 100 * 109);
        if (!pclk || pclk < (mode->pclk / 100 * 99) ||
            pclk > (mode->pclk / 100 * 109)) {
-               dev_err(&dc->ndev->dev,
-                       "can't divide %ld clock to %d -1/+9%% %ld %d %d\n",
-                       rate, mode->pclk,
-                       pclk, (mode->pclk / 100 * 99),
-                       (mode->pclk / 100 * 109));
+               dev_err(&dc->ndev->dev, "pclk out of range!\n");
                return -EINVAL;
        }
 
-       div = (rate * 2 / pclk) - 2;
-
        /* SW WAR for bug 1045373. To make the shift clk dividor effect under
         * all circumstances, write N+2 to SHIFT_CLK_DIVIDER and activate it.
         * After 2us delay, write the target values to it. */