imx-drm: Add HDMI support
diff mbox

Message ID 20131019121218.GH25034@n2100.arm.linux.org.uk
State New
Headers show

Commit Message

Russell King - ARM Linux Oct. 19, 2013, 12:12 p.m. UTC
Okay, more on this.

If I switch to using the DI clock, sourced from PLL5, and then "fix"
the clock tree such that I can change PLL5's rate, then I get most of
the display modes working.

However, it will occasionally not work, and when that happens I need
to reboot to get any kind of output working again.  I haven't delved
into it with the scope to find out what the HDMI clock is doing yet.

Having solved it, what I think is going on is as follows:

When one of the PLLs is prepared and then subsequently enabled, the
following sequence is used:

- Clear bypass
- Power up the PLL
- Wait for the PLL to indicate lock
- Enable output

This causes problems for me.  If I change this to:

- Power up the PLL
- Wait for PLL to indicate lock
- Clear bypass
- Enable output

then I don't see any problems.  My theory is that the bypass mux and/or
enable gate settings are synchronised with the mux output, and if the
mux output glitches while the PLL is gaining lock, it knocks that out
preventing the output from ever being enabled until the chip is hit with
a reset.

The other issue is that the set_rate() functions don't wait for the PLL
to lock before returning, and don't set bypass mode.  It looks like this
code relies on drivers unpreparing the clocks before calling clk_set_rate().
I don't see the clk API enforcing this in any way, so I'd suggest that
the set_rate() function also does the bypass -> set rate -> wait lock
-> clear bypass transition too.  Maybe this should be applied to the
other PLLs too.

Here's a diff which updates the av PLL to do this:

 arch/arm/mach-imx/clk-pllv3.c |   77 ++++++++++++++++++++++++++++------------
 1 files changed, 54 insertions(+), 23 deletions(-)

Comments

Shawn Guo Oct. 21, 2013, 9:13 a.m. UTC | #1
On Sat, Oct 19, 2013 at 01:12:18PM +0100, Russell King - ARM Linux wrote:
> Okay, more on this.
> 
> If I switch to using the DI clock, sourced from PLL5, and then "fix"
> the clock tree such that I can change PLL5's rate, then I get most of
> the display modes working.
> 
> However, it will occasionally not work, and when that happens I need
> to reboot to get any kind of output working again.  I haven't delved
> into it with the scope to find out what the HDMI clock is doing yet.
> 
> Having solved it, what I think is going on is as follows:
> 
> When one of the PLLs is prepared and then subsequently enabled, the
> following sequence is used:
> 
> - Clear bypass
> - Power up the PLL
> - Wait for the PLL to indicate lock
> - Enable output
> 
> This causes problems for me.  If I change this to:
> 
> - Power up the PLL
> - Wait for PLL to indicate lock
> - Clear bypass
> - Enable output
> 
> then I don't see any problems.  My theory is that the bypass mux and/or
> enable gate settings are synchronised with the mux output, and if the
> mux output glitches while the PLL is gaining lock, it knocks that out
> preventing the output from ever being enabled until the chip is hit with
> a reset.

You're right.  I just checked FSL 3.0.35 kernel and found that BYPASS
bit is written after POWER bit.

> The other issue is that the set_rate() functions don't wait for the PLL
> to lock before returning, and don't set bypass mode.  It looks like this
> code relies on drivers unpreparing the clocks before calling clk_set_rate().
> I don't see the clk API enforcing this in any way, so I'd suggest that
> the set_rate() function also does the bypass -> set rate -> wait lock
> -> clear bypass transition too.  Maybe this should be applied to the
> other PLLs too.

Yes.  We experienced the problem when upgrading FSL kernel tree to 3.10.
The .set_rate() should wait for LOCK bit, but does not need to touch
BYPASS.

> Here's a diff which updates the av PLL to do this:

Some other PLLs needs update too.  I will cook up patches to fix these
problems.

Shawn

>  arch/arm/mach-imx/clk-pllv3.c |   77 ++++++++++++++++++++++++++++------------
>  1 files changed, 54 insertions(+), 23 deletions(-)
> 
> diff --git a/arch/arm/mach-imx/clk-pllv3.c b/arch/arm/mach-imx/clk-pllv3.c
> index f6640b6..11a7048 100644
> --- a/arch/arm/mach-imx/clk-pllv3.c
> +++ b/arch/arm/mach-imx/clk-pllv3.c
> @@ -45,21 +45,10 @@ struct clk_pllv3 {
>  
>  #define to_clk_pllv3(_hw) container_of(_hw, struct clk_pllv3, hw)
>  
> -static int clk_pllv3_prepare(struct clk_hw *hw)
> +static int clk_pllv3_wait_lock(struct clk_pllv3 *pll)
>  {
> -	struct clk_pllv3 *pll = to_clk_pllv3(hw);
> -	unsigned long timeout;
> -	u32 val;
> -
> -	val = readl_relaxed(pll->base);
> -	val &= ~BM_PLL_BYPASS;
> -	if (pll->powerup_set)
> -		val |= BM_PLL_POWER;
> -	else
> -		val &= ~BM_PLL_POWER;
> -	writel_relaxed(val, pll->base);
> +	unsigned long timeout = jiffies + msecs_to_jiffies(100);
>  
> -	timeout = jiffies + msecs_to_jiffies(10);
>  	/* Wait for PLL to lock */
>  	do {
>  		if (readl_relaxed(pll->base) & BM_PLL_LOCK)
> @@ -68,10 +57,31 @@ static int clk_pllv3_prepare(struct clk_hw *hw)
>  			break;
>  	} while (1);
>  
> -	if (readl_relaxed(pll->base) & BM_PLL_LOCK)
> -		return 0;
> +	return readl_relaxed(pll->base) & BM_PLL_LOCK ? 0 : -ETIMEDOUT;
> +}
> +
> +static int clk_pllv3_prepare(struct clk_hw *hw)
> +{
> +	struct clk_pllv3 *pll = to_clk_pllv3(hw);
> +	u32 val, newval;
> +	int ret;
> +
> +	val = readl_relaxed(pll->base);
> +	if (pll->powerup_set)
> +		newval = val | BM_PLL_POWER;
>  	else
> -		return -ETIMEDOUT;
> +		newval = val & ~BM_PLL_POWER;
> +	writel_relaxed(newval, pll->base);
> +
> +	ret = clk_pllv3_wait_lock(pll);
> +	if (ret == 0) {
> +		newval &= ~BM_PLL_BYPASS;
> +		writel_relaxed(newval, pll->base);
> +	} else {
> +		writel_relaxed(val, pll->base);
> +	}
> +
> +	return ret;
>  }
>  
>  static void clk_pllv3_unprepare(struct clk_hw *hw)
> @@ -80,6 +90,7 @@ static void clk_pllv3_unprepare(struct clk_hw *hw)
>  	u32 val;
>  
>  	val = readl_relaxed(pll->base);
> +	pr_info("%s: 0x%08x\n", __func__, val);
>  	val |= BM_PLL_BYPASS;
>  	if (pll->powerup_set)
>  		val &= ~BM_PLL_POWER;
> @@ -256,9 +267,10 @@ static int clk_pllv3_av_set_rate(struct clk_hw *hw, unsigned long rate,
>  	struct clk_pllv3 *pll = to_clk_pllv3(hw);
>  	unsigned long min_rate = parent_rate * 27;
>  	unsigned long max_rate = parent_rate * 54;
> -	u32 val, div;
> +	u32 val, newval, div;
>  	u32 mfn, mfd = 1000000;
>  	s64 temp64;
> +	int ret;
>  
>  	if (rate < min_rate || rate > max_rate)
>  		return -EINVAL;
> @@ -270,13 +282,32 @@ static int clk_pllv3_av_set_rate(struct clk_hw *hw, unsigned long rate,
>  	mfn = temp64;
>  
>  	val = readl_relaxed(pll->base);
> -	val &= ~pll->div_mask;
> -	val |= div;
> -	writel_relaxed(val, pll->base);
> +
> +	/* set the PLL into bypass mode */
> +	newval = val | BM_PLL_BYPASS;
> +	writel_relaxed(newval, pll->base);
> +
> +	/* configure the new frequency */
> +	newval &= ~pll->div_mask;
> +	newval |= div;
> +	writel_relaxed(newval, pll->base);
>  	writel_relaxed(mfn, pll->base + PLL_NUM_OFFSET);
> -	writel_relaxed(mfd, pll->base + PLL_DENOM_OFFSET);
> +	writel(mfd, pll->base + PLL_DENOM_OFFSET);
> +
> +	if (val & BM_PLL_POWER) {
> +		/* PLL is powered down */
> +		ret = 0;
> +	} else {
> +		ret = clk_pllv3_wait_lock(pll);
> +		if (ret == 0) {
> +			/* only if it locked can we switch back to the PLL */
> +			newval &= ~BM_PLL_BYPASS;
> +			newval |= val & BM_PLL_BYPASS;
> +			writel(newval, pll->base);
> +		}
> +	}
>  
> -	return 0;
> +	return ret;
>  }
>  
>  static const struct clk_ops clk_pllv3_av_ops = {
>
Russell King - ARM Linux Oct. 21, 2013, 9:34 a.m. UTC | #2
On Mon, Oct 21, 2013 at 05:13:03PM +0800, Shawn Guo wrote:
> On Sat, Oct 19, 2013 at 01:12:18PM +0100, Russell King - ARM Linux wrote:
> > The other issue is that the set_rate() functions don't wait for the PLL
> > to lock before returning, and don't set bypass mode.  It looks like this
> > code relies on drivers unpreparing the clocks before calling clk_set_rate().
> > I don't see the clk API enforcing this in any way, so I'd suggest that
> > the set_rate() function also does the bypass -> set rate -> wait lock
> > -> clear bypass transition too.  Maybe this should be applied to the
> > other PLLs too.
> 
> Yes.  We experienced the problem when upgrading FSL kernel tree to 3.10.
> The .set_rate() should wait for LOCK bit, but does not need to touch
> BYPASS.

However, the IPU is more stable with switching to bypass while it relocks
than not.

> > Here's a diff which updates the av PLL to do this:
> 
> Some other PLLs needs update too.  I will cook up patches to fix these
> problems.

Yes, I only changed the set_rate on the AV codecs as I haven't tested
that on the others.
Shawn Guo Oct. 21, 2013, 12:36 p.m. UTC | #3
On Mon, Oct 21, 2013 at 10:34:48AM +0100, Russell King - ARM Linux wrote:
> However, the IPU is more stable with switching to bypass while it relocks
> than not.

As far as I know, FSL kernel has always been doing relock without
switching to bypass, and I haven't heard unstable IPU issue from their
testing.  Do you have a case that I can set up here to see the issue?

Shawn

> > > Here's a diff which updates the av PLL to do this:
> > 
> > Some other PLLs needs update too.  I will cook up patches to fix these
> > problems.
> 
> Yes, I only changed the set_rate on the AV codecs as I haven't tested
> that on the others.
Russell King - ARM Linux Oct. 21, 2013, 1:17 p.m. UTC | #4
On Mon, Oct 21, 2013 at 08:36:55PM +0800, Shawn Guo wrote:
> On Mon, Oct 21, 2013 at 10:34:48AM +0100, Russell King - ARM Linux wrote:
> > However, the IPU is more stable with switching to bypass while it relocks
> > than not.
> 
> As far as I know, FSL kernel has always been doing relock without
> switching to bypass, and I haven't heard unstable IPU issue from their
> testing.  Do you have a case that I can set up here to see the issue?

export DISPLAY=:0
while :; do
  xrandr -s 1280x720 -r 50
  sleep 5
  xrandr -s 1280x720 -r 60
  sleep 5
done

is one way.  Another way is to switch between all possible resolutions
supported by the connected device, keeping each resolution for 30 seconds
a time.  Normally after one or two run-throughs, the IPU will be deader
than the parrot in Monty Python's parrot sketch, requiring a reboot to
get it working again.

As I've said previously, when it gets into this state, all the status
registers I can find report that all FIFOs in the IPU are sitting in
their empty state.  The TMDS clock is correct, and the frame composer
in the HDMI block is working, but it's not producing any sync signals or
pixel data.
Shawn Guo Oct. 30, 2013, 8:34 a.m. UTC | #5
On Mon, Oct 21, 2013 at 02:17:58PM +0100, Russell King - ARM Linux wrote:
> On Mon, Oct 21, 2013 at 08:36:55PM +0800, Shawn Guo wrote:
> > On Mon, Oct 21, 2013 at 10:34:48AM +0100, Russell King - ARM Linux wrote:
> > > However, the IPU is more stable with switching to bypass while it relocks
> > > than not.
> > 
> > As far as I know, FSL kernel has always been doing relock without
> > switching to bypass, and I haven't heard unstable IPU issue from their
> > testing.  Do you have a case that I can set up here to see the issue?
> 
> export DISPLAY=:0
> while :; do
>   xrandr -s 1280x720 -r 50
>   sleep 5
>   xrandr -s 1280x720 -r 60
>   sleep 5
> done

Sorry, it took me a long time to set up this test on xubuntu 13.10.
Building an image using your cubox-i branch, I can still see the above
test plays my HDMI device to death quite easily.  That said, I'm not
sure if the BYPASS handling in .set_rate() does help the unstable issue
we're seeing.

BTW, the xubuntu session can crash quite easily here if I play the
desktop a little bit, dragging a window and moving it around, popping
up a dialog, or something.

Shawn

> 
> is one way.  Another way is to switch between all possible resolutions
> supported by the connected device, keeping each resolution for 30 seconds
> a time.  Normally after one or two run-throughs, the IPU will be deader
> than the parrot in Monty Python's parrot sketch, requiring a reboot to
> get it working again.
> 
> As I've said previously, when it gets into this state, all the status
> registers I can find report that all FIFOs in the IPU are sitting in
> their empty state.  The TMDS clock is correct, and the frame composer
> in the HDMI block is working, but it's not producing any sync signals or
> pixel data.

Patch
diff mbox

diff --git a/arch/arm/mach-imx/clk-pllv3.c b/arch/arm/mach-imx/clk-pllv3.c
index f6640b6..11a7048 100644
--- a/arch/arm/mach-imx/clk-pllv3.c
+++ b/arch/arm/mach-imx/clk-pllv3.c
@@ -45,21 +45,10 @@  struct clk_pllv3 {
 
 #define to_clk_pllv3(_hw) container_of(_hw, struct clk_pllv3, hw)
 
-static int clk_pllv3_prepare(struct clk_hw *hw)
+static int clk_pllv3_wait_lock(struct clk_pllv3 *pll)
 {
-	struct clk_pllv3 *pll = to_clk_pllv3(hw);
-	unsigned long timeout;
-	u32 val;
-
-	val = readl_relaxed(pll->base);
-	val &= ~BM_PLL_BYPASS;
-	if (pll->powerup_set)
-		val |= BM_PLL_POWER;
-	else
-		val &= ~BM_PLL_POWER;
-	writel_relaxed(val, pll->base);
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
 
-	timeout = jiffies + msecs_to_jiffies(10);
 	/* Wait for PLL to lock */
 	do {
 		if (readl_relaxed(pll->base) & BM_PLL_LOCK)
@@ -68,10 +57,31 @@  static int clk_pllv3_prepare(struct clk_hw *hw)
 			break;
 	} while (1);
 
-	if (readl_relaxed(pll->base) & BM_PLL_LOCK)
-		return 0;
+	return readl_relaxed(pll->base) & BM_PLL_LOCK ? 0 : -ETIMEDOUT;
+}
+
+static int clk_pllv3_prepare(struct clk_hw *hw)
+{
+	struct clk_pllv3 *pll = to_clk_pllv3(hw);
+	u32 val, newval;
+	int ret;
+
+	val = readl_relaxed(pll->base);
+	if (pll->powerup_set)
+		newval = val | BM_PLL_POWER;
 	else
-		return -ETIMEDOUT;
+		newval = val & ~BM_PLL_POWER;
+	writel_relaxed(newval, pll->base);
+
+	ret = clk_pllv3_wait_lock(pll);
+	if (ret == 0) {
+		newval &= ~BM_PLL_BYPASS;
+		writel_relaxed(newval, pll->base);
+	} else {
+		writel_relaxed(val, pll->base);
+	}
+
+	return ret;
 }
 
 static void clk_pllv3_unprepare(struct clk_hw *hw)
@@ -80,6 +90,7 @@  static void clk_pllv3_unprepare(struct clk_hw *hw)
 	u32 val;
 
 	val = readl_relaxed(pll->base);
+	pr_info("%s: 0x%08x\n", __func__, val);
 	val |= BM_PLL_BYPASS;
 	if (pll->powerup_set)
 		val &= ~BM_PLL_POWER;
@@ -256,9 +267,10 @@  static int clk_pllv3_av_set_rate(struct clk_hw *hw, unsigned long rate,
 	struct clk_pllv3 *pll = to_clk_pllv3(hw);
 	unsigned long min_rate = parent_rate * 27;
 	unsigned long max_rate = parent_rate * 54;
-	u32 val, div;
+	u32 val, newval, div;
 	u32 mfn, mfd = 1000000;
 	s64 temp64;
+	int ret;
 
 	if (rate < min_rate || rate > max_rate)
 		return -EINVAL;
@@ -270,13 +282,32 @@  static int clk_pllv3_av_set_rate(struct clk_hw *hw, unsigned long rate,
 	mfn = temp64;
 
 	val = readl_relaxed(pll->base);
-	val &= ~pll->div_mask;
-	val |= div;
-	writel_relaxed(val, pll->base);
+
+	/* set the PLL into bypass mode */
+	newval = val | BM_PLL_BYPASS;
+	writel_relaxed(newval, pll->base);
+
+	/* configure the new frequency */
+	newval &= ~pll->div_mask;
+	newval |= div;
+	writel_relaxed(newval, pll->base);
 	writel_relaxed(mfn, pll->base + PLL_NUM_OFFSET);
-	writel_relaxed(mfd, pll->base + PLL_DENOM_OFFSET);
+	writel(mfd, pll->base + PLL_DENOM_OFFSET);
+
+	if (val & BM_PLL_POWER) {
+		/* PLL is powered down */
+		ret = 0;
+	} else {
+		ret = clk_pllv3_wait_lock(pll);
+		if (ret == 0) {
+			/* only if it locked can we switch back to the PLL */
+			newval &= ~BM_PLL_BYPASS;
+			newval |= val & BM_PLL_BYPASS;
+			writel(newval, pll->base);
+		}
+	}
 
-	return 0;
+	return ret;
 }
 
 static const struct clk_ops clk_pllv3_av_ops = {