diff mbox

[1/3,v2,MTD] P4080/eLBC: Make Freescale elbc interrupt common to elbc devices

Message ID 1284027632-32573-1-git-send-email-tie-fei.zang@freescale.com (mailing list archive)
State Superseded
Headers show

Commit Message

Zang Roy-R61911 Sept. 9, 2010, 10:20 a.m. UTC
From: Lan Chunhe-B25806 <b25806@freescale.com>

Move Freescale elbc interrupt from nand dirver to elbc driver.
Then all elbc devices can use the interrupt instead of ONLY nand.

Signed-off-by: Lan Chunhe-B25806 <b25806@freescale.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
Comparing with v1, according to the feedback, add some decorations.

 arch/powerpc/Kconfig               |    7 +-
 arch/powerpc/include/asm/fsl_lbc.h |   34 +++++-
 arch/powerpc/sysdev/fsl_lbc.c      |  248 ++++++++++++++++++++++++++++++------
 3 files changed, 247 insertions(+), 42 deletions(-)

Comments

Anton Vorontsov Sept. 9, 2010, 11:53 a.m. UTC | #1
Just a few cosmetic nits for this patch...

On Thu, Sep 09, 2010 at 06:20:30PM +0800, Roy Zang wrote:
[...]
> @@ -94,22 +73,26 @@ int fsl_upm_find(phys_addr_t addr_base, struct fsl_upm *upm)
>  {
>  	int bank;
>  	__be32 br;
> +	struct fsl_lbc_regs __iomem *lbc;
>  
>  	bank = fsl_lbc_find(addr_base);
>  	if (bank < 0)
>  		return bank;
>  
> -	br = in_be32(&fsl_lbc_regs->bank[bank].br);
> +	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
> +		return -ENODEV;

I'd add an empty line here.

> +	lbc = fsl_lbc_ctrl_dev->regs;
> +	br = in_be32(&lbc->bank[bank].br);
>  
>  	switch (br & BR_MSEL) {
>  	case BR_MS_UPMA:
> -		upm->mxmr = &fsl_lbc_regs->mamr;
> +		upm->mxmr = &lbc->mamr;
>  		break;
>  	case BR_MS_UPMB:
> -		upm->mxmr = &fsl_lbc_regs->mbmr;
> +		upm->mxmr = &lbc->mbmr;
>  		break;
>  	case BR_MS_UPMC:
> -		upm->mxmr = &fsl_lbc_regs->mcmr;
> +		upm->mxmr = &lbc->mcmr;
>  		break;
>  	default:
>  		return -EINVAL;
> @@ -143,14 +126,18 @@ EXPORT_SYMBOL(fsl_upm_find);
>   * thus UPM pattern actually executed. Note that mar usage depends on the
>   * pre-programmed AMX bits in the UPM RAM.
>   */
> +
>  int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base, u32 mar)
>  {
>  	int ret = 0;
>  	unsigned long flags;
>  
> +	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
> +		return -ENODEV;
> +
>  	spin_lock_irqsave(&fsl_lbc_lock, flags);
>  
> -	out_be32(&fsl_lbc_regs->mar, mar);
> +	out_be32(&fsl_lbc_ctrl_dev->regs->mar, mar);
>  
>  	switch (upm->width) {
>  	case 8:
> @@ -172,3 +159,188 @@ int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base, u32 mar)
>  	return ret;
>  }
>  EXPORT_SYMBOL(fsl_upm_run_pattern);
> +
> +static int __devinit fsl_lbc_ctrl_init(struct fsl_lbc_ctrl *ctrl)
> +{
> +	struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
> +
> +	/* clear event registers */
> +	setbits32(&lbc->ltesr, LTESR_CLEAR);
> +	out_be32(&lbc->lteatr, 0);
> +	out_be32(&lbc->ltear, 0);
> +	out_be32(&lbc->lteccr, LTECCR_CLEAR);
> +	out_be32(&lbc->ltedr, LTEDR_ENABLE);
> +
> +	/* Enable interrupts for any detected events */
> +	out_be32(&lbc->lteir, LTEIR_ENABLE);
> +
> +	return 0;
> +}
> +
> +static int __devexit fsl_lbc_ctrl_remove(struct platform_device *ofdev)
> +{
> +	struct fsl_lbc_ctrl *ctrl = dev_get_drvdata(&ofdev->dev);
> +
> +	if (ctrl->irq)
> +		free_irq(ctrl->irq, ctrl);
> +
> +	if (ctrl->regs)
> +		iounmap(ctrl->regs);
> +
> +	dev_set_drvdata(&ofdev->dev, NULL);
> +
> +	kfree(ctrl);
> +
> +	return 0;
> +}
> +
> +/*
> + * NOTE: This interrupt is used to report localbus events of various kinds,
> + * such as transaction errors on the chipselects.
> + */
> +
> +static irqreturn_t fsl_lbc_ctrl_irq(int irqno, void *data)
> +{
> +	struct fsl_lbc_ctrl *ctrl = data;
> +	struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
> +	u32 status;
> +
> +	status = in_be32(&lbc->ltesr);
> +
> +	if (status) {

You could save indentation and improve readability by writing

status = in_be32(&lbc->ltesr);
if (!status)
	return IRQ_NONE;

...the rest of the function...

I understand that you just move the code around though.

> +		out_be32(&lbc->ltesr, LTESR_CLEAR);
> +		out_be32(&lbc->lteatr, 0);
> +		out_be32(&lbc->ltear, 0);
> +		ctrl->irq_status = status;
> +
> +		if (status & LTESR_BM)
> +			dev_err(ctrl->dev, "Local bus monitor time-out: "
> +				"LTESR 0x%08X\n", status);
> +		if (status & LTESR_WP)
> +			dev_err(ctrl->dev, "Write protect error: "
> +				"LTESR 0x%08X\n", status);
> +		if (status & LTESR_ATMW)
> +			dev_err(ctrl->dev, "Atomic write error: "
> +				"LTESR 0x%08X\n", status);
> +		if (status & LTESR_ATMR)
> +			dev_err(ctrl->dev, "Atomic read error: "
> +				"LTESR 0x%08X\n", status);
> +		if (status & LTESR_CS)
> +			dev_err(ctrl->dev, "Chip select error: "
> +				"LTESR 0x%08X\n", status);
> +		if (status & LTESR_UPM)
> +			;
> +		if (status & LTESR_FCT) {
> +			dev_err(ctrl->dev, "FCM command time-out: "
> +				"LTESR 0x%08X\n", status);
> +			smp_wmb();
> +			wake_up(&ctrl->irq_wait);
> +		}
> +		if (status & LTESR_PAR) {
> +			dev_err(ctrl->dev, "Parity or Uncorrectable ECC error: "
> +				"LTESR 0x%08X\n", status);
> +			smp_wmb();
> +			wake_up(&ctrl->irq_wait);
> +		}
> +		if (status & LTESR_CC) {
> +			smp_wmb();
> +			wake_up(&ctrl->irq_wait);
> +		}
> +		if (status & ~LTESR_MASK)
> +			dev_err(ctrl->dev, "Unknown error: "
> +				"LTESR 0x%08X\n", status);
> +
> +		return IRQ_HANDLED;
> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +/*
> + * fsl_lbc_ctrl_probe
> + *
> + * called by device layer when it finds a device matching
> + * one our driver can handled. This code allocates all of
> + * the resources needed for the controller only.  The
> + * resources for the NAND banks themselves are allocated
> + * in the chip probe function.
> +*/
> +
> +static int __devinit fsl_lbc_ctrl_probe(struct platform_device *ofdev,
> +					 const struct of_device_id *match)
> +{
> +	int ret;
> +
> +	if (!ofdev->dev.of_node) {
> +		dev_err(&ofdev->dev, "Device OF-Node is NULL");
> +		return -EFAULT;
> +	}
> +	fsl_lbc_ctrl_dev = kzalloc(sizeof(*fsl_lbc_ctrl_dev), GFP_KERNEL);
> +	if (!fsl_lbc_ctrl_dev)
> +		return -ENOMEM;
> +
> +	dev_set_drvdata(&ofdev->dev, fsl_lbc_ctrl_dev);
> +
> +	spin_lock_init(&fsl_lbc_ctrl_dev->lock);
> +	init_waitqueue_head(&fsl_lbc_ctrl_dev->irq_wait);
> +
> +	fsl_lbc_ctrl_dev->regs = of_iomap(ofdev->dev.of_node, 0);
> +	if (!fsl_lbc_ctrl_dev->regs) {
> +		dev_err(&ofdev->dev, "failed to get memory region\n");
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +
> +	fsl_lbc_ctrl_dev->irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
> +	if (fsl_lbc_ctrl_dev->irq == NO_IRQ) {
> +		dev_err(&ofdev->dev, "failed to get irq resource\n");
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +
> +	fsl_lbc_ctrl_dev->dev = &ofdev->dev;
> +
> +	ret = fsl_lbc_ctrl_init(fsl_lbc_ctrl_dev);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = request_irq(fsl_lbc_ctrl_dev->irq, fsl_lbc_ctrl_irq, 0,
> +				"fsl-lbc", fsl_lbc_ctrl_dev);
> +	if (ret != 0) {
> +		dev_err(&ofdev->dev, "failed to install irq (%d)\n",
> +			fsl_lbc_ctrl_dev->irq);
> +		ret = fsl_lbc_ctrl_dev->irq;
> +		goto err;
> +	}
> +
> +	return 0;
> +
> +err:
> +	iounmap(fsl_lbc_ctrl_dev->regs);
> +	kfree(fsl_lbc_ctrl_dev);
> +	return ret;
> +}
> +
> +static const struct of_device_id fsl_lbc_match[] = {
> +	{ .compatible = "fsl,elbc", },
> +	{ .compatible = "fsl,pq3-localbus", },
> +	{ .compatible = "fsl,pq2-localbus", },
> +	{ .compatible = "fsl,pq2pro-localbus", },
> +	{},
> +};

You need linux/mod_devicetable.h for this.

> +
> +static struct of_platform_driver fsl_lbc_ctrl_driver = {

Need linux/of_platform.h for this.

But you actually don't need of_platform_driver, as for the
new code you can use platform_driver (and thus
linux/platform_device.h).

> +	.driver = {
> +		.name = "fsl-lbc",
> +		.of_match_table = fsl_lbc_match,
> +	},
> +	.probe = fsl_lbc_ctrl_probe,
> +};
> +
> +static int __init fsl_lbc_init(void)
> +{
> +	return of_register_platform_driver(&fsl_lbc_ctrl_driver);
> +}
> +

No need for this empty line.

> +module_init(fsl_lbc_init);

Thanks,
Zang Roy-R61911 Sept. 10, 2010, 6:58 a.m. UTC | #2
> -----Original Message-----
> From: Anton Vorontsov [mailto:cbouatmailru@gmail.com]
> Sent: Thursday, September 09, 2010 19:54 PM
> To: Zang Roy-R61911
> Cc: linux-mtd@lists.infradead.org; dwmw2@infradead.org; dedekind1@gmail.com;
> akpm@linux-foundation.org; Lan Chunhe-B25806; Wood Scott-B07421; Gala Kumar-
> B11780; linuxppc-dev@ozlabs.org
> Subject: Re: [PATCH 1/3 v2][MTD] P4080/eLBC: Make Freescale elbc interrupt
> common to elbc devices
> 
> Just a few cosmetic nits for this patch...
> 
> On Thu, Sep 09, 2010 at 06:20:30PM +0800, Roy Zang wrote:
> [...]
[snip]
> > +static const struct of_device_id fsl_lbc_match[] = {
> > +	{ .compatible = "fsl,elbc", },
> > +	{ .compatible = "fsl,pq3-localbus", },
> > +	{ .compatible = "fsl,pq2-localbus", },
> > +	{ .compatible = "fsl,pq2pro-localbus", },
> > +	{},
> > +};
> 
> You need linux/mod_devicetable.h for this.
It has been include in linux/of.h.

> 
> > +
> > +static struct of_platform_driver fsl_lbc_ctrl_driver = {
> 
> Need linux/of_platform.h for this.
It has been include by
fsl_lbc.h->linux/of_platform.h-> linux/platform_device.h
Before submitting the patch, I have built and tested it.

Do you think I do not build the tree before I send out the patch?
> 
> > +
> > +static struct of_platform_driver fsl_lbc_ctrl_driver = {
> 
> Need linux/of_platform.h for this.
> 
> But you actually don't need of_platform_driver, as for the
> new code you can use platform_driver (and thus
> linux/platform_device.h).
I'd prefer using of_platform_driver here for simplified code.
Any special reason to use platform_device here?
Thanks.
Roy
Anton Vorontsov Sept. 10, 2010, 9:31 a.m. UTC | #3
On Fri, Sep 10, 2010 at 02:58:15PM +0800, Zang Roy-R61911 wrote:
[...]
> > > +static struct of_platform_driver fsl_lbc_ctrl_driver = {
> > 
> > Need linux/of_platform.h for this.
> It has been include by
> fsl_lbc.h->linux/of_platform.h-> linux/platform_device.h
> Before submitting the patch, I have built and tested it.

In Linux we try to include all the headers explicitly (except
for asm/* if the same header name exists in linux/).

That's to avoid problems if for some reason fsl_lbc.h will stop
including of_plaform.h some day.

Oh, and by the way, there is absolutely no reason to add
linux/of_platform.h and interrupts.h into fsl_lbc.h, they're
is simply not needed in fsl_lbc.h.

> Do you think I do not build the tree before I send out the patch?

Nope, that's not what I think. I didn't say that the file
won't build w/o these fixes, but they're still needed.

> > > +
> > > +static struct of_platform_driver fsl_lbc_ctrl_driver = {
> > 
> > Need linux/of_platform.h for this.
> > 
> > But you actually don't need of_platform_driver, as for the
> > new code you can use platform_driver (and thus
> > linux/platform_device.h).
> I'd prefer using of_platform_driver here for simplified code.
> Any special reason to use platform_device here?

In the new kernels, of_platform_driver is almost a synonym of
platform_driver, and 'of_platform_driver' stuff is soon to be
deleted. You can use platform_driver just like
of_platform_driver nowadays.

Thanks,
diff mbox

Patch

diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 631e5a0..44df1ba 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -687,9 +687,12 @@  config 4xx_SOC
 	bool
 
 config FSL_LBC
-	bool
+	bool "Freescale Local Bus support"
+	depends on FSL_SOC
 	help
-	  Freescale Localbus support
+	  Enables reporting of errors from the Freescale local bus
+	  controller.  Also contains some common code used by
+	  drivers for specific local bus peripherals.
 
 config FSL_GTM
 	bool
diff --git a/arch/powerpc/include/asm/fsl_lbc.h b/arch/powerpc/include/asm/fsl_lbc.h
index 1b5a210..9b95eab 100644
--- a/arch/powerpc/include/asm/fsl_lbc.h
+++ b/arch/powerpc/include/asm/fsl_lbc.h
@@ -1,9 +1,10 @@ 
 /* Freescale Local Bus Controller
  *
- * Copyright (c) 2006-2007 Freescale Semiconductor
+ * Copyright (c) 2006-2007, 2010 Freescale Semiconductor
  *
  * Authors: Nick Spence <nick.spence@freescale.com>,
  *          Scott Wood <scottwood@freescale.com>
+ *          Jack Lan <jack.lan@freescale.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,6 +28,9 @@ 
 #include <linux/types.h>
 #include <linux/io.h>
 
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+
 struct fsl_lbc_bank {
 	__be32 br;             /**< Base Register  */
 #define BR_BA           0xFFFF8000
@@ -125,13 +129,23 @@  struct fsl_lbc_regs {
 #define LTESR_ATMW 0x00800000
 #define LTESR_ATMR 0x00400000
 #define LTESR_CS   0x00080000
+#define LTESR_UPM  0x00000002
 #define LTESR_CC   0x00000001
 #define LTESR_NAND_MASK (LTESR_FCT | LTESR_PAR | LTESR_CC)
+#define LTESR_MASK      (LTESR_BM | LTESR_FCT | LTESR_PAR | LTESR_WP \
+			 | LTESR_ATMW | LTESR_ATMR | LTESR_CS | LTESR_UPM \
+			 | LTESR_CC)
+#define LTESR_CLEAR	0xFFFFFFFF
+#define LTECCR_CLEAR	0xFFFFFFFF
+#define LTESR_STATUS	LTESR_MASK
+#define LTEIR_ENABLE	LTESR_MASK
+#define LTEDR_ENABLE	0x00000000
 	__be32 ltedr;           /**< Transfer Error Disable Register */
 	__be32 lteir;           /**< Transfer Error Interrupt Register */
 	__be32 lteatr;          /**< Transfer Error Attributes Register */
 	__be32 ltear;           /**< Transfer Error Address Register */
-	u8 res6[0xC];
+	__be32 lteccr;          /**< Transfer Error ECC Register */
+	u8 res6[0x8];
 	__be32 lbcr;            /**< Configuration Register */
 #define LBCR_LDIS  0x80000000
 #define LBCR_LDIS_SHIFT    31
@@ -265,7 +279,23 @@  static inline void fsl_upm_end_pattern(struct fsl_upm *upm)
 		cpu_relax();
 }
 
+/* overview of the fsl lbc controller */
+
+struct fsl_lbc_ctrl {
+	/* device info */
+	struct device			*dev;
+	struct fsl_lbc_regs __iomem	*regs;
+	int				irq;
+	wait_queue_head_t		irq_wait;
+	spinlock_t			lock;
+	void				*nand;
+
+	/* status read from LTESR by irq handler */
+	unsigned int			irq_status;
+};
+
 extern int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base,
 			       u32 mar);
+extern struct fsl_lbc_ctrl *fsl_lbc_ctrl_dev;
 
 #endif /* __ASM_FSL_LBC_H */
diff --git a/arch/powerpc/sysdev/fsl_lbc.c b/arch/powerpc/sysdev/fsl_lbc.c
index dceb8d1..f4eca8d 100644
--- a/arch/powerpc/sysdev/fsl_lbc.c
+++ b/arch/powerpc/sysdev/fsl_lbc.c
@@ -2,8 +2,11 @@ 
  * Freescale LBC and UPM routines.
  *
  * Copyright (c) 2007-2008  MontaVista Software, Inc.
+ * Copyright (c) 2010 Freescale Semiconductor
  *
  * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
+ * Author: Jack Lan <Jack.Lan@freescale.com>
+ * Author: Roy Zang <tie-fei.zang@freescale.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,37 +24,11 @@ 
 #include <linux/of.h>
 #include <asm/prom.h>
 #include <asm/fsl_lbc.h>
+#include <linux/slab.h>
 
 static spinlock_t fsl_lbc_lock = __SPIN_LOCK_UNLOCKED(fsl_lbc_lock);
-static struct fsl_lbc_regs __iomem *fsl_lbc_regs;
-
-static char __initdata *compat_lbc[] = {
-	"fsl,pq2-localbus",
-	"fsl,pq2pro-localbus",
-	"fsl,pq3-localbus",
-	"fsl,elbc",
-};
-
-static int __init fsl_lbc_init(void)
-{
-	struct device_node *lbus;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(compat_lbc); i++) {
-		lbus = of_find_compatible_node(NULL, NULL, compat_lbc[i]);
-		if (lbus)
-			goto found;
-	}
-	return -ENODEV;
-
-found:
-	fsl_lbc_regs = of_iomap(lbus, 0);
-	of_node_put(lbus);
-	if (!fsl_lbc_regs)
-		return -ENOMEM;
-	return 0;
-}
-arch_initcall(fsl_lbc_init);
+struct fsl_lbc_ctrl *fsl_lbc_ctrl_dev;
+EXPORT_SYMBOL(fsl_lbc_ctrl_dev);
 
 /**
  * fsl_lbc_find - find Localbus bank
@@ -65,13 +42,15 @@  arch_initcall(fsl_lbc_init);
 int fsl_lbc_find(phys_addr_t addr_base)
 {
 	int i;
+	struct fsl_lbc_regs __iomem *lbc;
 
-	if (!fsl_lbc_regs)
+	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
 		return -ENODEV;
 
-	for (i = 0; i < ARRAY_SIZE(fsl_lbc_regs->bank); i++) {
-		__be32 br = in_be32(&fsl_lbc_regs->bank[i].br);
-		__be32 or = in_be32(&fsl_lbc_regs->bank[i].or);
+	lbc = fsl_lbc_ctrl_dev->regs;
+	for (i = 0; i < ARRAY_SIZE(lbc->bank); i++) {
+		__be32 br = in_be32(&lbc->bank[i].br);
+		__be32 or = in_be32(&lbc->bank[i].or);
 
 		if (br & BR_V && (br & or & BR_BA) == addr_base)
 			return i;
@@ -94,22 +73,26 @@  int fsl_upm_find(phys_addr_t addr_base, struct fsl_upm *upm)
 {
 	int bank;
 	__be32 br;
+	struct fsl_lbc_regs __iomem *lbc;
 
 	bank = fsl_lbc_find(addr_base);
 	if (bank < 0)
 		return bank;
 
-	br = in_be32(&fsl_lbc_regs->bank[bank].br);
+	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
+		return -ENODEV;
+	lbc = fsl_lbc_ctrl_dev->regs;
+	br = in_be32(&lbc->bank[bank].br);
 
 	switch (br & BR_MSEL) {
 	case BR_MS_UPMA:
-		upm->mxmr = &fsl_lbc_regs->mamr;
+		upm->mxmr = &lbc->mamr;
 		break;
 	case BR_MS_UPMB:
-		upm->mxmr = &fsl_lbc_regs->mbmr;
+		upm->mxmr = &lbc->mbmr;
 		break;
 	case BR_MS_UPMC:
-		upm->mxmr = &fsl_lbc_regs->mcmr;
+		upm->mxmr = &lbc->mcmr;
 		break;
 	default:
 		return -EINVAL;
@@ -143,14 +126,18 @@  EXPORT_SYMBOL(fsl_upm_find);
  * thus UPM pattern actually executed. Note that mar usage depends on the
  * pre-programmed AMX bits in the UPM RAM.
  */
+
 int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base, u32 mar)
 {
 	int ret = 0;
 	unsigned long flags;
 
+	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
+		return -ENODEV;
+
 	spin_lock_irqsave(&fsl_lbc_lock, flags);
 
-	out_be32(&fsl_lbc_regs->mar, mar);
+	out_be32(&fsl_lbc_ctrl_dev->regs->mar, mar);
 
 	switch (upm->width) {
 	case 8:
@@ -172,3 +159,188 @@  int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base, u32 mar)
 	return ret;
 }
 EXPORT_SYMBOL(fsl_upm_run_pattern);
+
+static int __devinit fsl_lbc_ctrl_init(struct fsl_lbc_ctrl *ctrl)
+{
+	struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
+
+	/* clear event registers */
+	setbits32(&lbc->ltesr, LTESR_CLEAR);
+	out_be32(&lbc->lteatr, 0);
+	out_be32(&lbc->ltear, 0);
+	out_be32(&lbc->lteccr, LTECCR_CLEAR);
+	out_be32(&lbc->ltedr, LTEDR_ENABLE);
+
+	/* Enable interrupts for any detected events */
+	out_be32(&lbc->lteir, LTEIR_ENABLE);
+
+	return 0;
+}
+
+static int __devexit fsl_lbc_ctrl_remove(struct platform_device *ofdev)
+{
+	struct fsl_lbc_ctrl *ctrl = dev_get_drvdata(&ofdev->dev);
+
+	if (ctrl->irq)
+		free_irq(ctrl->irq, ctrl);
+
+	if (ctrl->regs)
+		iounmap(ctrl->regs);
+
+	dev_set_drvdata(&ofdev->dev, NULL);
+
+	kfree(ctrl);
+
+	return 0;
+}
+
+/*
+ * NOTE: This interrupt is used to report localbus events of various kinds,
+ * such as transaction errors on the chipselects.
+ */
+
+static irqreturn_t fsl_lbc_ctrl_irq(int irqno, void *data)
+{
+	struct fsl_lbc_ctrl *ctrl = data;
+	struct fsl_lbc_regs __iomem *lbc = ctrl->regs;
+	u32 status;
+
+	status = in_be32(&lbc->ltesr);
+
+	if (status) {
+		out_be32(&lbc->ltesr, LTESR_CLEAR);
+		out_be32(&lbc->lteatr, 0);
+		out_be32(&lbc->ltear, 0);
+		ctrl->irq_status = status;
+
+		if (status & LTESR_BM)
+			dev_err(ctrl->dev, "Local bus monitor time-out: "
+				"LTESR 0x%08X\n", status);
+		if (status & LTESR_WP)
+			dev_err(ctrl->dev, "Write protect error: "
+				"LTESR 0x%08X\n", status);
+		if (status & LTESR_ATMW)
+			dev_err(ctrl->dev, "Atomic write error: "
+				"LTESR 0x%08X\n", status);
+		if (status & LTESR_ATMR)
+			dev_err(ctrl->dev, "Atomic read error: "
+				"LTESR 0x%08X\n", status);
+		if (status & LTESR_CS)
+			dev_err(ctrl->dev, "Chip select error: "
+				"LTESR 0x%08X\n", status);
+		if (status & LTESR_UPM)
+			;
+		if (status & LTESR_FCT) {
+			dev_err(ctrl->dev, "FCM command time-out: "
+				"LTESR 0x%08X\n", status);
+			smp_wmb();
+			wake_up(&ctrl->irq_wait);
+		}
+		if (status & LTESR_PAR) {
+			dev_err(ctrl->dev, "Parity or Uncorrectable ECC error: "
+				"LTESR 0x%08X\n", status);
+			smp_wmb();
+			wake_up(&ctrl->irq_wait);
+		}
+		if (status & LTESR_CC) {
+			smp_wmb();
+			wake_up(&ctrl->irq_wait);
+		}
+		if (status & ~LTESR_MASK)
+			dev_err(ctrl->dev, "Unknown error: "
+				"LTESR 0x%08X\n", status);
+
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+/*
+ * fsl_lbc_ctrl_probe
+ *
+ * called by device layer when it finds a device matching
+ * one our driver can handled. This code allocates all of
+ * the resources needed for the controller only.  The
+ * resources for the NAND banks themselves are allocated
+ * in the chip probe function.
+*/
+
+static int __devinit fsl_lbc_ctrl_probe(struct platform_device *ofdev,
+					 const struct of_device_id *match)
+{
+	int ret;
+
+	if (!ofdev->dev.of_node) {
+		dev_err(&ofdev->dev, "Device OF-Node is NULL");
+		return -EFAULT;
+	}
+	fsl_lbc_ctrl_dev = kzalloc(sizeof(*fsl_lbc_ctrl_dev), GFP_KERNEL);
+	if (!fsl_lbc_ctrl_dev)
+		return -ENOMEM;
+
+	dev_set_drvdata(&ofdev->dev, fsl_lbc_ctrl_dev);
+
+	spin_lock_init(&fsl_lbc_ctrl_dev->lock);
+	init_waitqueue_head(&fsl_lbc_ctrl_dev->irq_wait);
+
+	fsl_lbc_ctrl_dev->regs = of_iomap(ofdev->dev.of_node, 0);
+	if (!fsl_lbc_ctrl_dev->regs) {
+		dev_err(&ofdev->dev, "failed to get memory region\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	fsl_lbc_ctrl_dev->irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
+	if (fsl_lbc_ctrl_dev->irq == NO_IRQ) {
+		dev_err(&ofdev->dev, "failed to get irq resource\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	fsl_lbc_ctrl_dev->dev = &ofdev->dev;
+
+	ret = fsl_lbc_ctrl_init(fsl_lbc_ctrl_dev);
+	if (ret < 0)
+		goto err;
+
+	ret = request_irq(fsl_lbc_ctrl_dev->irq, fsl_lbc_ctrl_irq, 0,
+				"fsl-lbc", fsl_lbc_ctrl_dev);
+	if (ret != 0) {
+		dev_err(&ofdev->dev, "failed to install irq (%d)\n",
+			fsl_lbc_ctrl_dev->irq);
+		ret = fsl_lbc_ctrl_dev->irq;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	iounmap(fsl_lbc_ctrl_dev->regs);
+	kfree(fsl_lbc_ctrl_dev);
+	return ret;
+}
+
+static const struct of_device_id fsl_lbc_match[] = {
+	{ .compatible = "fsl,elbc", },
+	{ .compatible = "fsl,pq3-localbus", },
+	{ .compatible = "fsl,pq2-localbus", },
+	{ .compatible = "fsl,pq2pro-localbus", },
+	{},
+};
+
+static struct of_platform_driver fsl_lbc_ctrl_driver = {
+	.driver = {
+		.name = "fsl-lbc",
+		.of_match_table = fsl_lbc_match,
+	},
+	.probe = fsl_lbc_ctrl_probe,
+};
+
+static int __init fsl_lbc_init(void)
+{
+	return of_register_platform_driver(&fsl_lbc_ctrl_driver);
+}
+
+module_init(fsl_lbc_init);
+