diff mbox

[2/2] powerpc/85xx: p1022ds: enable monitor switching via pixis indirect mode

Message ID 1321556259-4459-2-git-send-email-timur@freescale.com (mailing list archive)
State Superseded
Delegated to: Kumar Gala
Headers show

Commit Message

Timur Tabi Nov. 17, 2011, 6:57 p.m. UTC
When the P1022's DIU video controller is active, the pixis must be accessed
in "indirect" mode, which uses localbus chip select addresses.

Switching between the DVI and LVDS monitor ports is handled by the pixis,
so that switching needs to be done via indirect mode.

This has the side-effect of no longer requiring U-Boot to enable the DIU.
Now Linux can enable the DIU all by itself.

Signed-off-by: Timur Tabi <timur@freescale.com>
---
 arch/powerpc/platforms/85xx/p1022_ds.c |  132 ++++++++++++++++++++++++++-----
 1 files changed, 110 insertions(+), 22 deletions(-)

Comments

Stephen Rothwell Nov. 17, 2011, 9:29 p.m. UTC | #1
Hi Timur,

On Thu, 17 Nov 2011 12:57:39 -0600 Timur Tabi <timur@freescale.com> wrote:
>
> @@ -129,44 +144,117 @@ static void p1022ds_set_gamma_table(enum fsl_diu_monitor_port port,
>   */
>  static void p1022ds_set_monitor_port(enum fsl_diu_monitor_port port)
>  {
> -	struct device_node *np;
> -	void __iomem *pixis;
> -	u8 __iomem *brdcfg1;
> +	struct device_node *guts_node = NULL;

There is no point in initialising this as it is assigned before being
used.

> +	struct device_node *indirect_node = NULL;
> +	struct ccsr_guts_85xx __iomem *guts = NULL;
> +	u8 __iomem *lbc_lcs0_ba = NULL;
> +	u8 __iomem *lbc_lcs1_ba = NULL;

And if you had multiple error path labels, you could avoid these others
as well (and the NULL checks on the error path).
Scott Wood Nov. 17, 2011, 9:37 p.m. UTC | #2
On Thu, Nov 17, 2011 at 12:57:39PM -0600, Timur Tabi wrote:
> When the P1022's DIU video controller is active, the pixis must be accessed
> in "indirect" mode, which uses localbus chip select addresses.
> 
> Switching between the DVI and LVDS monitor ports is handled by the pixis,
> so that switching needs to be done via indirect mode.
> 
> This has the side-effect of no longer requiring U-Boot to enable the DIU.
> Now Linux can enable the DIU all by itself.

Under what circumstances does Linux do this?  How does Linux prevent the
NOR flash driver from binding to the device when this mode has been or
will be used?

-Scott
Timur Tabi Nov. 17, 2011, 10:09 p.m. UTC | #3
Stephen Rothwell wrote:

>>  static void p1022ds_set_monitor_port(enum fsl_diu_monitor_port port)
>>  {
>> -	struct device_node *np;
>> -	void __iomem *pixis;
>> -	u8 __iomem *brdcfg1;
>> +	struct device_node *guts_node = NULL;
> 
> There is no point in initialising this as it is assigned before being
> used.

Ok.

>> +	struct device_node *indirect_node = NULL;
>> +	struct ccsr_guts_85xx __iomem *guts = NULL;
>> +	u8 __iomem *lbc_lcs0_ba = NULL;
>> +	u8 __iomem *lbc_lcs1_ba = NULL;
> 
> And if you had multiple error path labels, you could avoid these others
> as well (and the NULL checks on the error path).

But I don't want multiple error path labels.  The error path could only happen if someone passed in a broken device tree (i.e. pretty much never), so I'm not keen on complicating my code just to optimize something that will never be used.
Timur Tabi Nov. 17, 2011, 10:12 p.m. UTC | #4
Scott Wood wrote:

>> This has the side-effect of no longer requiring U-Boot to enable the DIU.
>> Now Linux can enable the DIU all by itself.
> 
> Under what circumstances does Linux do this?  

p1022ds_set_monitor_port() is called by the DIU driver when it enables the DIU.  This happens on boot, for example, if you enable the framebuffer console.

> How does Linux prevent the
> NOR flash driver from binding to the device when this mode has been or
> will be used?

It doesn't.  This isn't a simple problem to solve.  On the P1022, NOR flash and the DIU are incompatible, and yet that's exactly what we ship on the P1022DS board.  We could just remove the NOR flash node from the DTS.
Scott Wood Nov. 17, 2011, 10:25 p.m. UTC | #5
On Thu, Nov 17, 2011 at 04:12:02PM -0600, Timur Tabi wrote:
> Scott Wood wrote:
> 
> >> This has the side-effect of no longer requiring U-Boot to enable the DIU.
> >> Now Linux can enable the DIU all by itself.
> > 
> > Under what circumstances does Linux do this?  
> 
> p1022ds_set_monitor_port() is called by the DIU driver when it enables
> the DIU.  This happens on boot, for example, if you enable the
> framebuffer console.
> 
> > How does Linux prevent the
> > NOR flash driver from binding to the device when this mode has been or
> > will be used?
> 
> It doesn't.  This isn't a simple problem to solve.  On the P1022, NOR
> flash and the DIU are incompatible, and yet that's exactly what we ship
> on the P1022DS board.  We could just remove the NOR flash node from the
> DTS.

As we discussed earlier, you could have a kernel boot parameter that
indicates what mode you'd like the localbus to run in.  Then, platform
code could update the device tree before any drivers bind.

Or, have U-boot set up the desired mode before entering the kernel, and
provide an appropriate device tree.

Letting the kernel bind to localbus devices such as NOR flash, and then
taking the devices away without notice just because another device gets
initialized, is not the way to go.  We've already left "it just works"
territory, might as well make the user choose explicitly.

-Scott
Timur Tabi Nov. 17, 2011, 10:28 p.m. UTC | #6
Scott Wood wrote:

> As we discussed earlier, you could have a kernel boot parameter that
> indicates what mode you'd like the localbus to run in.  Then, platform
> code could update the device tree before any drivers bind.

How do I update the device tree from platform code?

> Or, have U-boot set up the desired mode before entering the kernel, and
> provide an appropriate device tree.

I can both of these features, but not in this patch.
Scott Wood Nov. 17, 2011, 10:45 p.m. UTC | #7
On Thu, Nov 17, 2011 at 04:28:48PM -0600, Timur Tabi wrote:
> Scott Wood wrote:
> 
> > As we discussed earlier, you could have a kernel boot parameter that
> > indicates what mode you'd like the localbus to run in.  Then, platform
> > code could update the device tree before any drivers bind.
> 
> How do I update the device tree from platform code?

prom_update_property()

Or, since you shouldn't be declaring these devices directly under a
simple-bus node, selectively probe the devices that are accessible.

> > Or, have U-boot set up the desired mode before entering the kernel, and
> > provide an appropriate device tree.
> 
> I can both of these features, but not in this patch.

This patch is the one that is adding a switch to indirect mode.  You're
adding it to the wrong place (it shouldn't be in a function called by the
DIU driver), so it is relevant to this patch.

-Scott
Timur Tabi Nov. 18, 2011, 5 p.m. UTC | #8
Scott Wood wrote:
>> > How do I update the device tree from platform code?

> prom_update_property()

I assume this needs to be called after the DT has been unflattened, since it takes a device_node* as a parameter.  Is there any way I can modify the tree before it's unflattened?  I'd like to fixup the tree in an early_param() function.
Scott Wood Nov. 18, 2011, 6:06 p.m. UTC | #9
On Fri, Nov 18, 2011 at 11:00:04AM -0600, Timur Tabi wrote:
> Scott Wood wrote:
> >> > How do I update the device tree from platform code?
> 
> > prom_update_property()
> 
> I assume this needs to be called after the DT has been unflattened,
> since it takes a device_node* as a parameter.  Is there any way I can
> modify the tree before it's unflattened?  I'd like to fixup the tree in
> an early_param() function.

What's wrong with doing it in setup_arch()?

Modifying a flat device tree is a more complicated task, and the kernel's
code is read-only.  It's not worth bringing libfdt or similar complexity
in for this, when there are other alternatives.

If it really needs to be done early, consider doing it from U-Boot.  The
bootwrapper would be a decent place as well, but we probably shouldn't
require its use just for this.

-Scott
Timur Tabi Nov. 18, 2011, 6:08 p.m. UTC | #10
Scott Wood wrote:
> What's wrong with doing it in setup_arch()?

Well, I was hoping to encapsulate everything into one function -- the early_param() callback function.  But I guess that's not going to work.
Stephen Rothwell Nov. 19, 2011, 1:04 a.m. UTC | #11
Hi Timur,

On Thu, 17 Nov 2011 16:09:17 -0600 Timur Tabi <timur@freescale.com> wrote:
>
> Stephen Rothwell wrote:
> 
> >> +	struct device_node *indirect_node = NULL;
> >> +	struct ccsr_guts_85xx __iomem *guts = NULL;
> >> +	u8 __iomem *lbc_lcs0_ba = NULL;
> >> +	u8 __iomem *lbc_lcs1_ba = NULL;
> > 
> > And if you had multiple error path labels, you could avoid these others
> > as well (and the NULL checks on the error path).
> 
> But I don't want multiple error path labels.  The error path could only
> happen if someone passed in a broken device tree (i.e. pretty much
> never), so I'm not keen on complicating my code just to optimize
> something that will never be used.

But you are already optimizing for the error path by doing the
assignments and NULL checks that are unneeded in the non error path.
What I am suggesting is adding a little more code that could end up doing
less at run time.

How about (not even compile tested):

static void p1022ds_set_monitor_port(enum fsl_diu_monitor_port port)
{
	struct device_node *guts_node;
	struct device_node *indirect_node;
	struct ccsr_guts_85xx __iomem *guts;
	u8 __iomem *lbc_lcs0_ba;
	u8 __iomem *lbc_lcs1_ba;
	u8 b;

	/* Map the global utilities registers. */
	guts_node = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
	if (!guts_node) {
		pr_err("p1022ds: missing global utilties device node\n");
		return;
	}

	guts = of_iomap(guts_node, 0);
	of_node_put(guts_node);
	if (!guts) {
		pr_err("p1022ds: could not map global utilties device\n");
		return;
	}

	indirect_node = of_find_compatible_node(NULL, NULL,
					     "fsl,p1022ds-indirect-pixis");
	if (!indirect_node) {
		pr_err("p1022ds: missing pixis indirect mode node\n");
		goto exit_guts_iounmap;
	}

	lbc_lcs0_ba = of_iomap(indirect_node, 0);
	if (!lbc_lcs0_ba) {
		pr_err("p1022ds: could not map localbus chip select 0\n");
		goto exit_indirect_node;
	}

	lbc_lcs1_ba = of_iomap(indirect_node, 1);
	if (!lbc_lcs1_ba) {
		pr_err("p1022ds: could not map localbus chip select 1\n");
		goto exit_lcs0_iounmap;
	}

	/* Make sure we're in indirect mode first. */
	if ((in_be32(&guts->pmuxcr) & PMUXCR_ELBCDIU_MASK) !=
	    PMUXCR_ELBCDIU_DIU) {
		struct device_node *pixis_node;
		void __iomem *pixis;

		pixis_node =
			of_find_compatible_node(NULL, NULL, "fsl,p1022ds-fpga");
		if (!pixis_node) {
			pr_err("p1022ds: missing pixis node\n");
			goto exit_lcs1_iounmap;
		}

		pixis = of_iomap(pixis_node, 0);
		of_node_put(pixis_node);
		if (!pixis) {
			pr_err("p1022ds: could not map pixis registers\n");
			goto exit_lcs1_iounmap;
		}

		/* Enable indirect PIXIS mode.  */
		setbits8(pixis + PX_CTL, PX_CTL_ALTACC);
		iounmap(pixis);

		/* Switch the board mux to the DIU */
		out_8(lbc_lcs0_ba, PX_BRDCFG0);	/* BRDCFG0 */
		b = in_8(lbc_lcs1_ba);
		b |= PX_BRDCFG0_ELBC_DIU;
		out_8(lbc_lcs1_ba, b);

		/* Set the chip mux to DIU mode. */
		clrsetbits_be32(&guts->pmuxcr, PMUXCR_ELBCDIU_MASK,
				PMUXCR_ELBCDIU_DIU);
		in_be32(&guts->pmuxcr);
	}


	switch (port) {
	case FSL_DIU_PORT_DVI:
		/* Enable the DVI port, disable the DFP and the backlight */
		out_8(lbc_lcs0_ba, PX_BRDCFG1);
		b = in_8(lbc_lcs1_ba);
		b &= ~(PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT);
		b |= PX_BRDCFG1_DVIEN;
		out_8(lbc_lcs1_ba, b);
		break;
	case FSL_DIU_PORT_LVDS:
		/*
		 * LVDS also needs backlight enabled, otherwise the display
		 * will be blank.
		 */
		/* Enable the DFP port, disable the DVI and the backlight */
		out_8(lbc_lcs0_ba, PX_BRDCFG1);
		b = in_8(lbc_lcs1_ba);
		b &= ~PX_BRDCFG1_DVIEN;
		b |= PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT;
		out_8(lbc_lcs1_ba, b);
		break;
	default:
		pr_err("p1022ds: unsupported monitor port %i\n", port);
	}

exit_lcs1_iounmap:
	iounmap(lbc_lcs1_ba);
exit_lcs0_iounmap:
	iounmap(lbc_lcs0_ba);
exit_indirect_node:
	of_node_put(indirect_node);
exit_guts_iounmap:
	iounmap(guts);
}
Timur Tabi Nov. 21, 2011, 5:01 p.m. UTC | #12
Stephen Rothwell wrote:

> exit_lcs1_iounmap:
> 	iounmap(lbc_lcs1_ba);
> exit_lcs0_iounmap:
> 	iounmap(lbc_lcs0_ba);
> exit_indirect_node:
> 	of_node_put(indirect_node);
> exit_guts_iounmap:
> 	iounmap(guts);

The point I'm trying to make is that I don't want to have multiple goto exit labels.  If I have to rearrange the code or add/delete code, then I probably would need to make similar changes to these labels.  I just don't like that sort of thing.  

I appreciate your taking the time to review my code and provide suggestions.  However, considering that I'm modifying my own code, I would hope that you can appreciate my personal coding style preference.  And my style is to reduce the number of labels, even if it means superfluous checks in the exit code.

So Kumar, if there are no further objections, please apply this patch as-is.  Thank you.
diff mbox

Patch

diff --git a/arch/powerpc/platforms/85xx/p1022_ds.c b/arch/powerpc/platforms/85xx/p1022_ds.c
index fda1571..7bdb9af 100644
--- a/arch/powerpc/platforms/85xx/p1022_ds.c
+++ b/arch/powerpc/platforms/85xx/p1022_ds.c
@@ -29,6 +29,10 @@ 
 
 #if defined(CONFIG_FB_FSL_DIU) || defined(CONFIG_FB_FSL_DIU_MODULE)
 
+#define PMUXCR_ELBCDIU_MASK	0xc0000000
+#define PMUXCR_ELBCDIU_NOR16	0x80000000
+#define PMUXCR_ELBCDIU_DIU	0x40000000
+
 /*
  * Board-specific initialization of the DIU.  This code should probably be
  * executed when the DIU is opened, rather than in arch code, but the DIU
@@ -46,11 +50,22 @@ 
 #define CLKDVDR_PXCLK_MASK	0x00FF0000
 
 /* Some ngPIXIS register definitions */
+#define PX_CTL		3
+#define PX_BRDCFG0	8
+#define PX_BRDCFG1	9
+
+#define PX_BRDCFG0_ELBC_SPI_MASK	0xc0
+#define PX_BRDCFG0_ELBC_SPI_ELBC	0x00
+#define PX_BRDCFG0_ELBC_SPI_NULL	0xc0
+#define PX_BRDCFG0_ELBC_DIU		0x02
+
 #define PX_BRDCFG1_DVIEN	0x80
 #define PX_BRDCFG1_DFPEN	0x40
 #define PX_BRDCFG1_BACKLIGHT	0x20
 #define PX_BRDCFG1_DDCEN	0x10
 
+#define PX_CTL_ALTACC		0x80
+
 /*
  * DIU Area Descriptor
  *
@@ -129,44 +144,117 @@  static void p1022ds_set_gamma_table(enum fsl_diu_monitor_port port,
  */
 static void p1022ds_set_monitor_port(enum fsl_diu_monitor_port port)
 {
-	struct device_node *np;
-	void __iomem *pixis;
-	u8 __iomem *brdcfg1;
+	struct device_node *guts_node = NULL;
+	struct device_node *indirect_node = NULL;
+	struct ccsr_guts_85xx __iomem *guts = NULL;
+	u8 __iomem *lbc_lcs0_ba = NULL;
+	u8 __iomem *lbc_lcs1_ba = NULL;
+	u8 b;
 
-	np = of_find_compatible_node(NULL, NULL, "fsl,p1022ds-fpga");
-	if (!np)
-		/* older device trees used "fsl,p1022ds-pixis" */
-		np = of_find_compatible_node(NULL, NULL, "fsl,p1022ds-pixis");
-	if (!np) {
-		pr_err("p1022ds: missing ngPIXIS node\n");
+	/* Map the global utilities registers. */
+	guts_node = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+	if (!guts_node) {
+		pr_err("p1022ds: missing global utilties device node\n");
 		return;
 	}
 
-	pixis = of_iomap(np, 0);
-	if (!pixis) {
-		pr_err("p1022ds: could not map ngPIXIS registers\n");
-		return;
+	guts = of_iomap(guts_node, 0);
+	if (!guts) {
+		pr_err("p1022ds: could not map global utilties device\n");
+		goto exit;
 	}
-	brdcfg1 = pixis + 9;	/* BRDCFG1 is at offset 9 in the ngPIXIS */
+
+	indirect_node = of_find_compatible_node(NULL, NULL,
+					     "fsl,p1022ds-indirect-pixis");
+	if (!indirect_node) {
+		pr_err("p1022ds: missing pixis indirect mode node\n");
+		goto exit;
+	}
+
+	lbc_lcs0_ba = of_iomap(indirect_node, 0);
+	if (!lbc_lcs0_ba) {
+		pr_err("p1022ds: could not map localbus chip select 0\n");
+		goto exit;
+	}
+
+	lbc_lcs1_ba = of_iomap(indirect_node, 1);
+	if (!lbc_lcs1_ba) {
+		pr_err("p1022ds: could not map localbus chip select 1\n");
+		goto exit;
+	}
+
+	/* Make sure we're in indirect mode first. */
+	if ((in_be32(&guts->pmuxcr) & PMUXCR_ELBCDIU_MASK) !=
+	    PMUXCR_ELBCDIU_DIU) {
+		struct device_node *pixis_node;
+		void __iomem *pixis;
+
+		pixis_node =
+			of_find_compatible_node(NULL, NULL, "fsl,p1022ds-fpga");
+		if (!pixis_node) {
+			pr_err("p1022ds: missing pixis node\n");
+			goto exit;
+		}
+
+		pixis = of_iomap(pixis_node, 0);
+		of_node_put(pixis_node);
+		if (!pixis) {
+			pr_err("p1022ds: could not map pixis registers\n");
+			goto exit;
+		}
+
+		/* Enable indirect PIXIS mode.  */
+		setbits8(pixis + PX_CTL, PX_CTL_ALTACC);
+		iounmap(pixis);
+
+		/* Switch the board mux to the DIU */
+		out_8(lbc_lcs0_ba, PX_BRDCFG0);	/* BRDCFG0 */
+		b = in_8(lbc_lcs1_ba);
+		b |= PX_BRDCFG0_ELBC_DIU;
+		out_8(lbc_lcs1_ba, b);
+
+		/* Set the chip mux to DIU mode. */
+		clrsetbits_be32(&guts->pmuxcr, PMUXCR_ELBCDIU_MASK,
+				PMUXCR_ELBCDIU_DIU);
+		in_be32(&guts->pmuxcr);
+	}
+
 
 	switch (port) {
 	case FSL_DIU_PORT_DVI:
-		printk(KERN_INFO "%s:%u\n", __func__, __LINE__);
 		/* Enable the DVI port, disable the DFP and the backlight */
-		clrsetbits_8(brdcfg1, PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT,
-			     PX_BRDCFG1_DVIEN);
+		out_8(lbc_lcs0_ba, PX_BRDCFG1);
+		b = in_8(lbc_lcs1_ba);
+		b &= ~(PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT);
+		b |= PX_BRDCFG1_DVIEN;
+		out_8(lbc_lcs1_ba, b);
 		break;
 	case FSL_DIU_PORT_LVDS:
-		printk(KERN_INFO "%s:%u\n", __func__, __LINE__);
+		/*
+		 * LVDS also needs backlight enabled, otherwise the display
+		 * will be blank.
+		 */
 		/* Enable the DFP port, disable the DVI and the backlight */
-		clrsetbits_8(brdcfg1, PX_BRDCFG1_DVIEN | PX_BRDCFG1_BACKLIGHT,
-			     PX_BRDCFG1_DFPEN);
+		out_8(lbc_lcs0_ba, PX_BRDCFG1);
+		b = in_8(lbc_lcs1_ba);
+		b &= ~PX_BRDCFG1_DVIEN;
+		b |= PX_BRDCFG1_DFPEN | PX_BRDCFG1_BACKLIGHT;
+		out_8(lbc_lcs1_ba, b);
 		break;
 	default:
 		pr_err("p1022ds: unsupported monitor port %i\n", port);
 	}
 
-	iounmap(pixis);
+exit:
+	if (lbc_lcs1_ba)
+		iounmap(lbc_lcs1_ba);
+	if (lbc_lcs0_ba)
+		iounmap(lbc_lcs0_ba);
+	if (guts)
+		iounmap(guts);
+
+	of_node_put(indirect_node);
+	of_node_put(guts_node);
 }
 
 /**