Commit 24013ea8 authored by Russell King's avatar Russell King

imx-drm: ipu-v3: more inteligent DI clock selection

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: default avatarPhilipp Zabel <p.zabel@pengutronix.de>
Acked-by: default avatarShawn Guo <shawn.guo@linaro.org>
Reviewed-by: default avatarFabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 40678388
......@@ -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 +
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment