imx-drm: ipu-v3: more inteligent DI clock selection
authorRussell King <rmk+kernel@arm.linux.org.uk>
Sat, 19 Oct 2013 14:05:31 +0000 (15:05 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 24 Feb 2014 12:03:49 +0000 (12:03 +0000)
The DI clock selection was very rudimentary: it would statically use
either the IPU internal clock or the DI external clock depending on
which "encoder" was being used.  In the case of HDMI, it would always
use the IPU clock.

Moreover, using the IPU clock resulted in fractional divisors, which
are achieved by skipping clock pulses.  This can result in the HDMI
PHY PLL being frequency modulated, and the attached device is then
unable to properly lock on to the TMDS clock.

We need at least 1% accurate and stable clocks for HDMI.

Arrange for the DI clock to be sourced from the IPU internal clock
if it can satisfy our requirements, otherwise switch to the DI
external clock and try and set the external clock to our desired
pixel clock rate.

Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Reviewed-by: Fabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
drivers/staging/imx-drm/ipu-v3/ipu-di.c

index 948a49b289ef171036d3f39e8ac0eb6392e59b3e..8c7241bb435c64f33fefaa6bc02548743725cac2 100644 (file)
@@ -544,10 +544,48 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
        if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0))
                return -EINVAL;
 
+       dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n",
+               clk_get_rate(di->clk_ipu),
+               clk_get_rate(di->clk_di),
+               sig->pixelclock);
+
+       /*
+        * CLKMODE_EXT means we must use the DI clock: this is needed
+        * for things like LVDS which needs to feed the DI and LDB with
+        * the same pixel clock.
+        *
+        * For other interfaces, we can arbitarily select between the DI
+        * specific clock and the internal IPU clock.  See DI_GENERAL
+        * bit 20.  We select the IPU clock if it can give us a clock
+        * rate within 1% of the requested frequency, otherwise we use
+        * the DI clock.
+        */
        if (sig->clkflags & IPU_DI_CLKMODE_EXT)
                parent = di->clk_di;
-       else
-               parent = di->clk_ipu;
+       else {
+               unsigned long rate, clkrate;
+               unsigned div, error;
+
+               clkrate = clk_get_rate(di->clk_ipu);
+               div = (clkrate + sig->pixelclock / 2) / sig->pixelclock;
+               rate = clkrate / div;
+
+               error = rate / (sig->pixelclock / 1000);
+
+               dev_dbg(di->ipu->dev, "  IPU clock can give %lu with divider %u, error %d.%u%%\n",
+                       rate, div, (signed)(error - 1000) / 10, error % 10);
+
+               /* Allow a 1% error */
+               if (error < 1010 && error >= 990) {
+                       parent = di->clk_ipu;
+               } else {
+                       parent = di->clk_di;
+
+                       ret = clk_set_rate(parent, sig->pixelclock);
+                       if (ret)
+                               dev_err(di->ipu->dev, "Setting of DI clock failed: %d\n", ret);
+               }
+       }
 
        ret = clk_set_parent(di->clk_di_pixel, parent);
        if (ret) {
@@ -557,6 +595,11 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
                return ret;
        }
 
+       /*
+        * CLKMODE_SYNC means that we want the DI to be clocked at the
+        * same rate as the parent clock.  This is needed (eg) for LDB
+        * which needs to be fed with the same pixel clock.
+        */
        if (sig->clkflags & IPU_DI_CLKMODE_SYNC)
                round = clk_get_rate(parent);
        else
@@ -564,6 +607,13 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
 
        ret = clk_set_rate(di->clk_di_pixel, round);
 
+       dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, got %luHz\n",
+               sig->pixelclock,
+               clk_get_rate(di->clk_ipu),
+               clk_get_rate(di->clk_di),
+               parent == di->clk_di ? "DI" : "IPU",
+               clk_get_rate(di->clk_di_pixel));
+
        h_total = sig->width + sig->h_sync_width + sig->h_start_width +
                sig->h_end_width;
        v_total = sig->height + sig->v_sync_width + sig->v_start_width +