diff mbox

[U-Boot,09/15] iMX28: Add GPMI NAND driver

Message ID 1315800409-19876-10-git-send-email-marek.vasut@gmail.com
State Superseded
Headers show

Commit Message

Marek Vasut Sept. 12, 2011, 4:06 a.m. UTC
Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Cc: Scott Wood <scottwood@freescale.com>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Wolfgang Denk <wd@denx.de>
Cc: Detlev Zundel <dzu@denx.de>
---
 drivers/mtd/nand/Makefile   |    1 +
 drivers/mtd/nand/mxs_nand.c | 1159 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1160 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/nand/mxs_nand.c

Comments

Scott Wood Sept. 28, 2011, 9:26 p.m. UTC | #1
On 09/11/2011 11:06 PM, Marek Vasut wrote:
> +static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
> +{
> +	int i = info->desc_index;
> +	struct mxs_dma_desc *desc;
> +
> +	for (--i; i >= 0; i--) {

This is an awkward construct.

Why not just the usual:

for (i = 0; i < info->desc_index; i++)

> +		desc = info->desc[i];
> +		memset(desc, 0, sizeof(struct mxs_dma_desc));
> +		desc->address = (dma_addr_t)desc;
> +	}
> +
> +	info->desc_index = 0;
> +}
> +
> +static inline uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
> +{
> +	return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
> +}

No need for inline in .c files, GCC should take care of this automatically.

> +/*
> + * There are several places in this driver where we have to handle the OOB and
> + * block marks. This is the function where things are the most complicated, so
> + * this is where we try to explain it all. All the other places refer back to
> + * here.
> + *
> + * These are the rules, in order of decreasing importance:
> + *
> + * 1) Nothing the caller does can be allowed to imperil the block mark, so all
> + *    write operations take measures to protect it.
> + *
> + * 2) In read operations, the first byte of the OOB we return must reflect the
> + *    true state of the block mark, no matter where that block mark appears in
> + *    the physical page.
> + *
> + * 3) ECC-based read operations return an OOB full of set bits (since we never
> + *    allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads
> + *    return).
> + *
> + * 4) "Raw" read operations return a direct view of the physical bytes in the
> + *    page, using the conventional definition of which bytes are data and which
> + *    are OOB. This gives the caller a way to see the actual, physical bytes
> + *    in the page, without the distortions applied by our ECC engine.

Hmm, I thought raw was just supposed to disable ECC, not change the
layout from what is used in normal operation.

> + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not
> + * easy. When reading a page, for example, the NAND Flash MTD code calls our
> + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an
> + * ECC-based or raw view of the page is implicit in which function it calls
> + * (there is a similar pair of ECC-based/raw functions for writing).

This is an issue for the eLBC driver as well -- we need to know whether
to enable or disable ECC before we begin the operation (in cmdfunc), not
just at the time the buffer is accessed.  Currently we just always have
ECC on, and raw accesses aren't raw.  It would be nice to fix this more
generally (starting in Linux, so we don't diverge).

It's too bad we can't rely on chip->ops being used... maybe we could
copy the data into there?  Or just have a separate mtd_oob_mode_t in
nand_chip.

> +/*
> + * Write OOB data to NAND.
> + */
> +static int mxs_nand_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
> +					int page)
> +{
> +	struct mxs_nand_info *nand_info = nand->priv;
> +	uint8_t block_mark = 0;
> +
> +	/*
> +	 * There are fundamental incompatibilities between the i.MX GPMI NFC and
> +	 * the NAND Flash MTD model that make it essentially impossible to write
> +	 * the out-of-band bytes.
> +	 *
> +	 * We permit *ONE* exception. If the *intent* of writing the OOB is to
> +	 * mark a block bad, we can do that.
> +	 */

Is this just an issue with writing OOB separately from the main data
(which would also be an issue on MLC chips that don't allow multiple
partial programming), or can you not even write user OOB bytes as part
of a full page write?

Based on fake_ecc_layout I'm guessing the latter.

> +	if (nand_info->marking_block_bad) {
> +		printf("NXS NAND: Writing OOB isn't supported\n");
> +		return -EIO;
> +	}

Shouldn't this be if (!nand_info->marking_block_bad)?

> +/*
> + * Claims all blocks are good.
> + *
> + * In principle, this function is *only* called when the NAND Flash MTD system
> + * isn't allowed to keep an in-memory bad block table, so it is forced to ask
> + * the driver for bad block information.
> + *
> + * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so
> + * this function is *only* called when we take it away.
> + *
> + * We take away the in-memory BBT when the user sets the "ignorebad" parameter,
> + * which indicates that all blocks should be reported good.
> + *
> + * Thus, this function is only called when we want *all* blocks to look good,
> + * so it *always* return success.
> + */
> +static int mxs_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
> +{
> +	return 0;
> +}

What/where is the "ignorebad" parameter?

Other than when scrubbing (which has its own override), when would you
want to do this?

> +/*
> + * Nominally, the purpose of this function is to look for or create the bad
> + * block table. In fact, since the HIL calls this function at the very end of

HIL?

> + * the initialization process started by nand_scan(), and the HIL doesn't have a
> + * more formal mechanism, everyone "hooks" this function to continue the
> + * initialization process.

Everyone?  I only see diskonchip doing this.

The nand_base.c code actually does have a split here
(nand_scan_ident/nand_scan_tail), but U-Boot's glue code is too
inflexible, and insists on calling nand_scan.  The right fix is to let
drivers call nand_scan_ident/nand_scan_tail themselves.

-Scott
Marek Vasut Sept. 28, 2011, 9:42 p.m. UTC | #2
On Wednesday, September 28, 2011 11:26:45 PM Scott Wood wrote:
> On 09/11/2011 11:06 PM, Marek Vasut wrote:
> > +static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
> > +{
> > +	int i = info->desc_index;
> > +	struct mxs_dma_desc *desc;
> > +
> > +	for (--i; i >= 0; i--) {
> 
> This is an awkward construct.
> 
> Why not just the usual:
> 
> for (i = 0; i < info->desc_index; i++)

Good catch.

> 
> > +		desc = info->desc[i];
> > +		memset(desc, 0, sizeof(struct mxs_dma_desc));
> > +		desc->address = (dma_addr_t)desc;
> > +	}
> > +
> > +	info->desc_index = 0;
> > +}
> > +
> > +static inline uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
> > +{
> > +	return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
> > +}
> 
> No need for inline in .c files, GCC should take care of this automatically.

Does it really ?
> 
> > +/*
> > + * There are several places in this driver where we have to handle the
> > OOB and + * block marks. This is the function where things are the most
> > complicated, so + * this is where we try to explain it all. All the
> > other places refer back to + * here.
> > + *
> > + * These are the rules, in order of decreasing importance:
> > + *
> > + * 1) Nothing the caller does can be allowed to imperil the block mark,
> > so all + *    write operations take measures to protect it.
> > + *
> > + * 2) In read operations, the first byte of the OOB we return must
> > reflect the + *    true state of the block mark, no matter where that
> > block mark appears in + *    the physical page.
> > + *
> > + * 3) ECC-based read operations return an OOB full of set bits (since we
> > never + *    allow ECC-based writes to the OOB, it doesn't matter what
> > ECC-based reads + *    return).
> > + *
> > + * 4) "Raw" read operations return a direct view of the physical bytes
> > in the + *    page, using the conventional definition of which bytes are
> > data and which + *    are OOB. This gives the caller a way to see the
> > actual, physical bytes + *    in the page, without the distortions
> > applied by our ECC engine.
> 
> Hmm, I thought raw was just supposed to disable ECC, not change the
> layout from what is used in normal operation.

You see the page as is ... I see no problem with this part.
> 
> > + * It turns out that knowing whether we want an "ECC-based" or "raw"
> > read is not + * easy. When reading a page, for example, the NAND Flash
> > MTD code calls our + * ecc.read_page or ecc.read_page_raw function.
> > Thus, the fact that MTD wants an + * ECC-based or raw view of the page
> > is implicit in which function it calls + * (there is a similar pair of
> > ECC-based/raw functions for writing).
> 
> This is an issue for the eLBC driver as well -- we need to know whether
> to enable or disable ECC before we begin the operation (in cmdfunc), not
> just at the time the buffer is accessed.  Currently we just always have
> ECC on, and raw accesses aren't raw.  It would be nice to fix this more
> generally (starting in Linux, so we don't diverge).
> 
> It's too bad we can't rely on chip->ops being used... maybe we could
> copy the data into there?  Or just have a separate mtd_oob_mode_t in
> nand_chip.
> 
> > +/*
> > + * Write OOB data to NAND.
> > + */
> > +static int mxs_nand_ecc_write_oob(struct mtd_info *mtd, struct nand_chip
> > *nand, +					int page)
> > +{
> > +	struct mxs_nand_info *nand_info = nand->priv;
> > +	uint8_t block_mark = 0;
> > +
> > +	/*
> > +	 * There are fundamental incompatibilities between the i.MX GPMI NFC
> > and +	 * the NAND Flash MTD model that make it essentially impossible 
to
> > write +	 * the out-of-band bytes.
> > +	 *
> > +	 * We permit *ONE* exception. If the *intent* of writing the OOB is to
> > +	 * mark a block bad, we can do that.
> > +	 */
> 
> Is this just an issue with writing OOB separately from the main data
> (which would also be an issue on MLC chips that don't allow multiple
> partial programming), or can you not even write user OOB bytes as part
> of a full page write?
> 
> Based on fake_ecc_layout I'm guessing the latter.

My understanding of the original FSL driver is that you should never be allowed 
to access the physical NAND media at all. Only through the driver, which does 
the magic.
> 
> > +	if (nand_info->marking_block_bad) {
> > +		printf("NXS NAND: Writing OOB isn't supported\n");
> > +		return -EIO;
> > +	}
> 
> Shouldn't this be if (!nand_info->marking_block_bad)?
> 
> > +/*
> > + * Claims all blocks are good.
> > + *
> > + * In principle, this function is *only* called when the NAND Flash MTD
> > system + * isn't allowed to keep an in-memory bad block table, so it is
> > forced to ask + * the driver for bad block information.
> > + *
> > + * In fact, we permit the NAND Flash MTD system to have an in-memory
> > BBT, so + * this function is *only* called when we take it away.
> > + *
> > + * We take away the in-memory BBT when the user sets the "ignorebad"
> > parameter, + * which indicates that all blocks should be reported good.
> > + *
> > + * Thus, this function is only called when we want *all* blocks to look
> > good, + * so it *always* return success.
> > + */
> > +static int mxs_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int
> > getchip) +{
> > +	return 0;
> > +}
> 
> What/where is the "ignorebad" parameter?

Remnant from FSL code.
> 
> Other than when scrubbing (which has its own override), when would you
> want to do this?
> 
> > +/*
> > + * Nominally, the purpose of this function is to look for or create the
> > bad + * block table. In fact, since the HIL calls this function at the
> > very end of
> 
> HIL?

DTTO
> 
> > + * the initialization process started by nand_scan(), and the HIL
> > doesn't have a + * more formal mechanism, everyone "hooks" this function
> > to continue the + * initialization process.
> 
> Everyone?  I only see diskonchip doing this.

DTTO
> 
> The nand_base.c code actually does have a split here
> (nand_scan_ident/nand_scan_tail), but U-Boot's glue code is too
> inflexible, and insists on calling nand_scan.  The right fix is to let
> drivers call nand_scan_ident/nand_scan_tail themselves.

I can't test now, so this has to wait. I'd prefer to get this mainline and then 
start poking around fixing this.
> 
> -Scott
Scott Wood Sept. 28, 2011, 9:57 p.m. UTC | #3
On 09/28/2011 04:42 PM, Marek Vasut wrote:
> On Wednesday, September 28, 2011 11:26:45 PM Scott Wood wrote:
>> On 09/11/2011 11:06 PM, Marek Vasut wrote:
>>> +		desc = info->desc[i];
>>> +		memset(desc, 0, sizeof(struct mxs_dma_desc));
>>> +		desc->address = (dma_addr_t)desc;
>>> +	}
>>> +
>>> +	info->desc_index = 0;
>>> +}
>>> +
>>> +static inline uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
>>> +{
>>> +	return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
>>> +}
>>
>> No need for inline in .c files, GCC should take care of this automatically.
> 
> Does it really ?

Very small functions (and anything used only once) get inlined
automatically.

It might need the hint if you have a larger function that collapses down
to something small due to constant propagation (and of course if it's a
header or otherwise *must* be inlined), but usually it does a decent job
on its own.

>> Hmm, I thought raw was just supposed to disable ECC, not change the
>> layout from what is used in normal operation.
> 
> You see the page as is ... I see no problem with this part.

What if the raw access is being done to e.g. force bit flips for testing?

There seems to be a difference between U-Boot and Linux here.

Linux has this in mtd.h:
>  * MTD_OOB_RAW:         mode to read oob and data without doing ECC checking

Doesn't say anything about layout.

Whereas U-Boot says:
>  * MTD_OOB_RAW:         mode to read raw data+oob in one chunk. The oob data
>  *                      is inserted into the data. Thats a raw image of the
>  *                      flash contents.

Linux used to say what U-Boot says, but changed in commit
b64d39d8b03fea88417d53715ccbebf71d4dcc9f

This commit message includes the comment "Now MTD_OOB_RAW behaves just
like MTD_OOB_PLACE, but doesn't do ECC validation".

So I think if you need something that changes the layout from normal
operations, it needs to be a new mode.  And it's about time to sync up
U-Boot's NAND code with Linux again...

>>> +	/*
>>> +	 * There are fundamental incompatibilities between the i.MX GPMI NFC
>>> and +	 * the NAND Flash MTD model that make it essentially impossible 
> to
>>> write +	 * the out-of-band bytes.
>>> +	 *
>>> +	 * We permit *ONE* exception. If the *intent* of writing the OOB is to
>>> +	 * mark a block bad, we can do that.
>>> +	 */
>>
>> Is this just an issue with writing OOB separately from the main data
>> (which would also be an issue on MLC chips that don't allow multiple
>> partial programming), or can you not even write user OOB bytes as part
>> of a full page write?
>>
>> Based on fake_ecc_layout I'm guessing the latter.
> 
> My understanding of the original FSL driver is that you should never be allowed 
> to access the physical NAND media at all. Only through the driver, which does 
> the magic.

I'm not talking about circumventing the driver, just accessing some user
OOB bytes through it.

>> The nand_base.c code actually does have a split here
>> (nand_scan_ident/nand_scan_tail), but U-Boot's glue code is too
>> inflexible, and insists on calling nand_scan.  The right fix is to let
>> drivers call nand_scan_ident/nand_scan_tail themselves.
> 
> I can't test now, so this has to wait. I'd prefer to get this mainline and then 
> start poking around fixing this.

OK.  It's been on my TODO list for a while now...

-Scott
Marek Vasut Sept. 28, 2011, 10:09 p.m. UTC | #4
On Wednesday, September 28, 2011 11:57:49 PM Scott Wood wrote:
> On 09/28/2011 04:42 PM, Marek Vasut wrote:
> > On Wednesday, September 28, 2011 11:26:45 PM Scott Wood wrote:
> >> On 09/11/2011 11:06 PM, Marek Vasut wrote:
> >>> +		desc = info->desc[i];
> >>> +		memset(desc, 0, sizeof(struct mxs_dma_desc));
> >>> +		desc->address = (dma_addr_t)desc;
> >>> +	}
> >>> +
> >>> +	info->desc_index = 0;
> >>> +}
> >>> +
> >>> +static inline uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
> >>> +{
> >>> +	return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
> >>> +}
> >> 
> >> No need for inline in .c files, GCC should take care of this
> >> automatically.
> > 
> > Does it really ?
> 
> Very small functions (and anything used only once) get inlined
> automatically.
> 
> It might need the hint if you have a larger function that collapses down
> to something small due to constant propagation (and of course if it's a
> header or otherwise *must* be inlined), but usually it does a decent job
> on its own.
> 
> >> Hmm, I thought raw was just supposed to disable ECC, not change the
> >> layout from what is used in normal operation.
> > 
> > You see the page as is ... I see no problem with this part.
> 
> What if the raw access is being done to e.g. force bit flips for testing?
> 
> There seems to be a difference between U-Boot and Linux here.
> 
> Linux has this in mtd.h:
> >  * MTD_OOB_RAW:         mode to read oob and data without doing ECC
> >  checking
> 
> Doesn't say anything about layout.
> 
> Whereas U-Boot says:
> >  * MTD_OOB_RAW:         mode to read raw data+oob in one chunk. The oob
> >  data *                      is inserted into the data. Thats a raw
> >  image of the *                      flash contents.
> 
> Linux used to say what U-Boot says, but changed in commit
> b64d39d8b03fea88417d53715ccbebf71d4dcc9f
> 
> This commit message includes the comment "Now MTD_OOB_RAW behaves just
> like MTD_OOB_PLACE, but doesn't do ECC validation".
> 
> So I think if you need something that changes the layout from normal
> operations, it needs to be a new mode.  And it's about time to sync up
> U-Boot's NAND code with Linux again...

Well aren't you the maintainer that should take care of it ? ;-)
> 
> >>> +	/*
> >>> +	 * There are fundamental incompatibilities between the i.MX GPMI NFC
> >>> and +	 * the NAND Flash MTD model that make it essentially impossible
> > 
> > to
> > 
> >>> write +	 * the out-of-band bytes.
> >>> +	 *
> >>> +	 * We permit *ONE* exception. If the *intent* of writing the OOB is
> >>> to +	 * mark a block bad, we can do that.
> >>> +	 */
> >> 
> >> Is this just an issue with writing OOB separately from the main data
> >> (which would also be an issue on MLC chips that don't allow multiple
> >> partial programming), or can you not even write user OOB bytes as part
> >> of a full page write?
> >> 
> >> Based on fake_ecc_layout I'm guessing the latter.
> > 
> > My understanding of the original FSL driver is that you should never be
> > allowed to access the physical NAND media at all. Only through the
> > driver, which does the magic.
> 
> I'm not talking about circumventing the driver, just accessing some user
> OOB bytes through it.

There are no user OOB bytes, the driver does the ECC so user has no need to 
write any OOB.

> 
> >> The nand_base.c code actually does have a split here
> >> (nand_scan_ident/nand_scan_tail), but U-Boot's glue code is too
> >> inflexible, and insists on calling nand_scan.  The right fix is to let
> >> drivers call nand_scan_ident/nand_scan_tail themselves.
> > 
> > I can't test now, so this has to wait. I'd prefer to get this mainline
> > and then start poking around fixing this.
> 
> OK.  It's been on my TODO list for a while now...
> 
> -Scott
Marek Vasut Sept. 28, 2011, 10:12 p.m. UTC | #5
On Wednesday, September 28, 2011 11:26:45 PM Scott Wood wrote:
> On 09/11/2011 11:06 PM, Marek Vasut wrote:
> > +static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
> > +{
> > +	int i = info->desc_index;
> > +	struct mxs_dma_desc *desc;
> > +
> > +	for (--i; i >= 0; i--) {
> 
> This is an awkward construct.
> 
> Why not just the usual:
> 
> for (i = 0; i < info->desc_index; i++)
> 

Checking the driver, this modifies the global data so this is ok
Scott Wood Sept. 28, 2011, 10:13 p.m. UTC | #6
On 09/28/2011 05:09 PM, Marek Vasut wrote:
> On Wednesday, September 28, 2011 11:57:49 PM Scott Wood wrote:
>> So I think if you need something that changes the layout from normal
>> operations, it needs to be a new mode.  And it's about time to sync up
>> U-Boot's NAND code with Linux again...
> 
> Well aren't you the maintainer that should take care of it ? ;-)

Yes, that note was me adding something to my todo list. :-)

>> I'm not talking about circumventing the driver, just accessing some user
>> OOB bytes through it.
> 
> There are no user OOB bytes, the driver does the ECC so user has no need to 
> write any OOB.

By "user OOB bytes" I meant things other than the ECC and the bad-block
marker -- the space described by oobavail in ecclayout.  This is getting
less useful with partial-page restrictions and large ECC that fills up
most of the OOB, though, and thus there's a trend away from making use
of it.  So if it's a pain to provide access to any spare OOB bytes, it's
not that big of a deal.

-Scott
Scott Wood Sept. 28, 2011, 10:23 p.m. UTC | #7
On 09/28/2011 05:12 PM, Marek Vasut wrote:
> On Wednesday, September 28, 2011 11:26:45 PM Scott Wood wrote:
>> On 09/11/2011 11:06 PM, Marek Vasut wrote:
>>> +static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
>>> +{
>>> +	int i = info->desc_index;
>>> +	struct mxs_dma_desc *desc;
>>> +
>>> +	for (--i; i >= 0; i--) {
>>
>> This is an awkward construct.
>>
>> Why not just the usual:
>>
>> for (i = 0; i < info->desc_index; i++)
>>
> 
> Checking the driver, this modifies the global data so this is ok

I don't follow... I'm assuming by "this" you mean the original code
since you didn't change it, so are you saying that the global data has
to be modified in descending order?

If that's really a requirement, put in a comment saying so (and ideally
why), and you could still just do:

for (i = info->desc_index - 1; i >= 0; i--)

or

while (--i >= 0)

-Scott
Marek Vasut Sept. 28, 2011, 10:34 p.m. UTC | #8
On Thursday, September 29, 2011 12:13:19 AM Scott Wood wrote:
> On 09/28/2011 05:09 PM, Marek Vasut wrote:
> > On Wednesday, September 28, 2011 11:57:49 PM Scott Wood wrote:
> >> So I think if you need something that changes the layout from normal
> >> operations, it needs to be a new mode.  And it's about time to sync up
> >> U-Boot's NAND code with Linux again...
> > 
> > Well aren't you the maintainer that should take care of it ? ;-)
> 
> Yes, that note was me adding something to my todo list. :-)
> 
> >> I'm not talking about circumventing the driver, just accessing some user
> >> OOB bytes through it.
> > 
> > There are no user OOB bytes, the driver does the ECC so user has no need
> > to write any OOB.
> 
> By "user OOB bytes" I meant things other than the ECC and the bad-block
> marker -- the space described by oobavail in ecclayout.  This is getting
> less useful with partial-page restrictions and large ECC that fills up
> most of the OOB, though, and thus there's a trend away from making use
> of it.  So if it's a pain to provide access to any spare OOB bytes, it's
> not that big of a deal.

But I don't get it, you're still talking about OOB, but the OOB and the actual 
page make no difference here. They are used in the same manner. The OOB is 
spread across the page.

> 
> -Scott
diff mbox

Patch

diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index 8b598f6..8b6e8bf 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -43,6 +43,7 @@  COBJS-$(CONFIG_NAND_KIRKWOOD) += kirkwood_nand.o
 COBJS-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o
 COBJS-$(CONFIG_NAND_MPC5121_NFC) += mpc5121_nfc.o
 COBJS-$(CONFIG_NAND_MXC) += mxc_nand.o
+COBJS-$(CONFIG_NAND_MXS) += mxs_nand.o
 COBJS-$(CONFIG_NAND_NDFC) += ndfc.o
 COBJS-$(CONFIG_NAND_NOMADIK) += nomadik.o
 COBJS-$(CONFIG_NAND_S3C2410) += s3c2410_nand.o
diff --git a/drivers/mtd/nand/mxs_nand.c b/drivers/mtd/nand/mxs_nand.c
new file mode 100644
index 0000000..af2c571
--- /dev/null
+++ b/drivers/mtd/nand/mxs_nand.c
@@ -0,0 +1,1159 @@ 
+/*
+ * Freescale i.MX28 NAND flash driver
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * Based on code from LTIB:
+ * Freescale GPMI NFC NAND Flash Driver
+ *
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc.
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/types.h>
+#include <common.h>
+#include <malloc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/arch/dma.h>
+
+#define	MXS_NAND_DMA_DESCRIPTOR_COUNT		4
+
+#define	MXS_NAND_CHUNK_DATA_CHUNK_SIZE		512
+#define	MXS_NAND_METADATA_SIZE			10
+
+#define	MXS_NAND_COMMAND_BUFFER_SIZE		32
+
+#define	MXS_NAND_BCH_TIMEOUT			10000
+
+struct mxs_nand_info {
+	int		cur_chip;
+
+	uint32_t	cmd_queue_len;
+
+	uint8_t		*cmd_buf;
+	uint8_t		*data_buf;
+	uint8_t		*oob_buf;
+
+	uint8_t		marking_block_bad;
+	uint8_t		raw_oob_mode;
+
+	/* Functions with altered behaviour */
+	int		(*hooked_read_oob)(struct mtd_info *mtd,
+				loff_t from, struct mtd_oob_ops *ops);
+	int		(*hooked_write_oob)(struct mtd_info *mtd,
+				loff_t to, struct mtd_oob_ops *ops);
+	int		(*hooked_block_markbad)(struct mtd_info *mtd,
+				loff_t ofs);
+
+	/* DMA descriptors */
+	struct mxs_dma_desc	**desc;
+	uint32_t		desc_index;
+};
+
+struct nand_ecclayout fake_ecc_layout;
+
+static struct mxs_dma_desc *mxs_nand_get_dma_desc(struct mxs_nand_info *info)
+{
+	struct mxs_dma_desc *desc;
+
+	if (info->desc_index >= MXS_NAND_DMA_DESCRIPTOR_COUNT) {
+		printf("MXS NAND: Too many DMA descriptors requested\n");
+		return NULL;
+	}
+
+	desc = info->desc[info->desc_index];
+	info->desc_index++;
+
+	return desc;
+}
+
+static void mxs_nand_return_dma_descs(struct mxs_nand_info *info)
+{
+	int i = info->desc_index;
+	struct mxs_dma_desc *desc;
+
+	for (--i; i >= 0; i--) {
+		desc = info->desc[i];
+		memset(desc, 0, sizeof(struct mxs_dma_desc));
+		desc->address = (dma_addr_t)desc;
+	}
+
+	info->desc_index = 0;
+}
+
+static inline uint32_t mxs_nand_ecc_chunk_cnt(uint32_t page_data_size)
+{
+	return page_data_size / MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+}
+
+static inline uint32_t mxs_nand_ecc_size_in_bits(uint32_t ecc_strength)
+{
+	return ecc_strength * 13;
+}
+
+static inline uint32_t mxs_nand_aux_status_offset(void)
+{
+	return (MXS_NAND_METADATA_SIZE + 0x3) & ~0x3;
+}
+
+static inline uint32_t mxs_nand_aux_size(uint32_t page_size)
+{
+	uint32_t aux_status_off = mxs_nand_aux_status_offset();
+	uint32_t ecc_chunk_cnt =
+		(mxs_nand_ecc_chunk_cnt(page_size) + 0x3) & ~0x3;
+
+	return ecc_chunk_cnt + aux_status_off;
+}
+
+static inline uint32_t mxs_nand_get_ecc_strength(uint32_t page_data_size,
+						uint32_t page_oob_size)
+{
+	if (page_data_size == 2048)
+		return 8;
+
+	if (page_data_size == 4096) {
+		if (page_oob_size == 128)
+			return 8;
+
+		if (page_oob_size == 218)
+			return 16;
+	}
+
+	return 0;
+}
+
+static inline uint32_t mxs_nand_get_mark_offset(uint32_t page_data_size,
+						uint32_t ecc_strength)
+{
+	uint32_t chunk_data_size_in_bits;
+	uint32_t chunk_ecc_size_in_bits;
+	uint32_t chunk_total_size_in_bits;
+	uint32_t block_mark_chunk_number;
+	uint32_t block_mark_chunk_bit_offset;
+	uint32_t block_mark_bit_offset;
+
+	chunk_data_size_in_bits = MXS_NAND_CHUNK_DATA_CHUNK_SIZE * 8;
+	chunk_ecc_size_in_bits  = mxs_nand_ecc_size_in_bits(ecc_strength);
+
+	chunk_total_size_in_bits =
+			chunk_data_size_in_bits + chunk_ecc_size_in_bits;
+
+	/* Compute the bit offset of the block mark within the physical page. */
+	block_mark_bit_offset = page_data_size * 8;
+
+	/* Subtract the metadata bits. */
+	block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8;
+
+	/*
+	 * Compute the chunk number (starting at zero) in which the block mark
+	 * appears.
+	 */
+	block_mark_chunk_number =
+			block_mark_bit_offset / chunk_total_size_in_bits;
+
+	/*
+	 * Compute the bit offset of the block mark within its chunk, and
+	 * validate it.
+	 */
+	block_mark_chunk_bit_offset = block_mark_bit_offset -
+			(block_mark_chunk_number * chunk_total_size_in_bits);
+
+	if (block_mark_chunk_bit_offset > chunk_data_size_in_bits)
+		return 1;
+
+	/*
+	 * Now that we know the chunk number in which the block mark appears,
+	 * we can subtract all the ECC bits that appear before it.
+	 */
+	block_mark_bit_offset -=
+		block_mark_chunk_number * chunk_ecc_size_in_bits;
+
+	return block_mark_bit_offset;
+}
+
+static inline uint32_t mxs_nand_mark_byte_offset(struct mtd_info *mtd)
+{
+	uint32_t ecc_strength;
+	ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
+	return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) >> 3;
+}
+
+static inline uint32_t mxs_nand_mark_bit_offset(struct mtd_info *mtd)
+{
+	uint32_t ecc_strength;
+	ecc_strength = mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize);
+	return mxs_nand_get_mark_offset(mtd->writesize, ecc_strength) & 0x7;
+}
+
+/*
+ * Wait for BCH complete IRQ and clear the IRQ
+ */
+static int mxs_nand_wait_for_bch_complete(void)
+{
+	struct mx28_bch_regs *bch_regs = (struct mx28_bch_regs *)MXS_BCH_BASE;
+	int timeout = MXS_NAND_BCH_TIMEOUT;
+	int ret;
+
+	ret = mx28_wait_mask_set(&bch_regs->hw_bch_ctrl_reg,
+		BCH_CTRL_COMPLETE_IRQ, timeout);
+
+	writel(BCH_CTRL_COMPLETE_IRQ, &bch_regs->hw_bch_ctrl_clr);
+
+	return ret;
+}
+
+/*
+ * This is the function that we install in the cmd_ctrl function pointer of the
+ * owning struct nand_chip. The only functions in the reference implementation
+ * that use these functions pointers are cmdfunc and select_chip.
+ *
+ * In this driver, we implement our own select_chip, so this function will only
+ * be called by the reference implementation's cmdfunc. For this reason, we can
+ * ignore the chip enable bit and concentrate only on sending bytes to the
+ * NAND Flash.
+ */
+static void mxs_nand_cmd_ctrl(struct mtd_info *mtd, int data, unsigned int ctrl)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mxs_dma_desc *d;
+	uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+	int ret;
+
+	/*
+	 * If this condition is true, something is _VERY_ wrong in MTD
+	 * subsystem!
+	 */
+	if (nand_info->cmd_queue_len == MXS_NAND_COMMAND_BUFFER_SIZE) {
+		printf("MXS NAND: Command queue too long\n");
+		return;
+	}
+
+	/*
+	 * Every operation begins with a command byte and a series of zero or
+	 * more address bytes. These are distinguished by either the Address
+	 * Latch Enable (ALE) or Command Latch Enable (CLE) signals being
+	 * asserted. When MTD is ready to execute the command, it will
+	 * deasert both latch enables.
+	 *
+	 * Rather than run a separate DMA operation for every single byte, we
+	 * queue them up and run a single DMA operation for the entire series
+	 * of command and data bytes.
+	 */
+	if (ctrl & (NAND_ALE | NAND_CLE)) {
+		if (data != NAND_CMD_NONE)
+			nand_info->cmd_buf[nand_info->cmd_queue_len++] = data;
+		return;
+	}
+
+	/*
+	 * If control arrives here, MTD has deasserted both the ALE and CLE,
+	 * which means it's ready to run an operation. Check if we have any
+	 * bytes to send.
+	 */
+	if (nand_info->cmd_queue_len == 0)
+		return;
+
+	/* Compile the DMA descriptor -- a descriptor that sends command. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_CHAIN | MXS_DMA_DESC_DEC_SEM |
+		MXS_DMA_DESC_WAIT4END | (3 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+		(nand_info->cmd_queue_len << MXS_DMA_DESC_BYTES_OFFSET);
+
+	d->cmd.address = (dma_addr_t)nand_info->cmd_buf;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WRITE |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_CLE |
+		GPMI_CTRL0_ADDRESS_INCREMENT |
+		nand_info->cmd_queue_len;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Execute the DMA chain. */
+	ret = mxs_dma_go(channel);
+	if (ret)
+		printf("MXS NAND: Error sending command\n");
+
+	mxs_nand_return_dma_descs(nand_info);
+
+	/* Reset the command queue. */
+	nand_info->cmd_queue_len = 0;
+}
+
+/*
+ * Test if the NAND flash is ready.
+ */
+static int mxs_nand_device_ready(struct mtd_info *mtd)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct mxs_nand_info *nand_info = chip->priv;
+	struct mx28_gpmi_regs *gpmi_regs =
+		(struct mx28_gpmi_regs *)MXS_GPMI_BASE;
+	uint32_t tmp;
+
+	tmp = readl(&gpmi_regs->hw_gpmi_stat);
+	tmp >>= (GPMI_STAT_READY_BUSY_OFFSET + nand_info->cur_chip);
+
+	return tmp & 1;
+}
+
+/*
+ * Select the NAND chip.
+ */
+static void mxs_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct mxs_nand_info *nand_info = nand->priv;
+
+	nand_info->cur_chip = chip;
+}
+
+/*
+ * Handle block mark swapping.
+ *
+ * Note that, when this function is called, it doesn't know whether it's
+ * swapping the block mark, or swapping it *back* -- but it doesn't matter
+ * because the the operation is the same.
+ */
+static void mxs_nand_swap_block_mark(struct mtd_info *mtd,
+					uint8_t *data_buf, uint8_t *oob_buf)
+{
+	uint32_t bit_offset;
+	uint32_t buf_offset;
+
+	uint32_t src;
+	uint32_t dst;
+
+	bit_offset = mxs_nand_mark_bit_offset(mtd);
+	buf_offset = mxs_nand_mark_byte_offset(mtd);
+
+	/*
+	 * Get the byte from the data area that overlays the block mark. Since
+	 * the ECC engine applies its own view to the bits in the page, the
+	 * physical block mark won't (in general) appear on a byte boundary in
+	 * the data.
+	 */
+	src = data_buf[buf_offset] >> bit_offset;
+	src |= data_buf[buf_offset + 1] << (8 - bit_offset);
+
+	dst = oob_buf[0];
+
+	oob_buf[0] = src;
+
+	data_buf[buf_offset] &= ~(0xff << bit_offset);
+	data_buf[buf_offset + 1] &= 0xff << bit_offset;
+
+	data_buf[buf_offset] |= dst << bit_offset;
+	data_buf[buf_offset + 1] |= dst >> (8 - bit_offset);
+}
+
+/*
+ * Read data from NAND.
+ */
+static void mxs_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int length)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mxs_dma_desc *d;
+	uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+	int ret;
+
+	if (length > NAND_MAX_PAGESIZE) {
+		printf("MXS NAND: DMA buffer too big\n");
+		return;
+	}
+
+	if (!buf) {
+		printf("MXS NAND: DMA buffer is NULL\n");
+		return;
+	}
+
+	/* Compile the DMA descriptor - a descriptor that reads data. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_DMA_WRITE | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+		(1 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+		(length << MXS_DMA_DESC_BYTES_OFFSET);
+
+	d->cmd.address = (dma_addr_t)nand_info->data_buf;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_READ |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA |
+		length;
+
+	mxs_dma_desc_append(channel, d);
+
+	/*
+	 * A DMA descriptor that waits for the command to end and the chip to
+	 * become ready.
+	 *
+	 * I think we actually should *not* be waiting for the chip to become
+	 * ready because, after all, we don't care. I think the original code
+	 * did that and no one has re-thought it yet.
+	 */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_DEC_SEM |
+		MXS_DMA_DESC_WAIT4END | (4 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+	d->cmd.address = 0;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Execute the DMA chain. */
+	ret = mxs_dma_go(channel);
+	if (ret) {
+		printf("MXS NAND: DMA read error\n");
+		goto rtn;
+	}
+
+	memcpy(buf, nand_info->data_buf, length);
+
+rtn:
+	mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Write data to NAND.
+ */
+static void mxs_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf,
+				int length)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mxs_dma_desc *d;
+	uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+	int ret;
+
+	if (length > NAND_MAX_PAGESIZE) {
+		printf("MXS NAND: DMA buffer too big\n");
+		return;
+	}
+
+	if (!buf) {
+		printf("MXS NAND: DMA buffer is NULL\n");
+		return;
+	}
+
+	memcpy(nand_info->data_buf, buf, length);
+
+	/* Compile the DMA descriptor - a descriptor that writes data. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_DMA_READ | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+		(4 << MXS_DMA_DESC_PIO_WORDS_OFFSET) |
+		(length << MXS_DMA_DESC_BYTES_OFFSET);
+
+	d->cmd.address = (dma_addr_t)nand_info->data_buf;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WRITE |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA |
+		length;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Execute the DMA chain. */
+	ret = mxs_dma_go(channel);
+	if (ret)
+		printf("MXS NAND: DMA write error\n");
+
+	mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Read a single byte from NAND.
+ */
+static uint8_t mxs_nand_read_byte(struct mtd_info *mtd)
+{
+	uint8_t buf;
+	mxs_nand_read_buf(mtd, &buf, 1);
+	return buf;
+}
+
+/*
+ * Read a page from NAND.
+ */
+static int mxs_nand_ecc_read_page(struct mtd_info *mtd, struct nand_chip *nand,
+					uint8_t *buf, int page)
+{
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mxs_dma_desc *d;
+	uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+	uint32_t corrected = 0, failed = 0;
+	uint8_t	*status;
+	int i, ret;
+
+	/* Compile the DMA descriptor - wait for ready. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+		MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END |
+		(1 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+	d->cmd.address = 0;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Compile the DMA descriptor - enable the BCH block and read. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+		MXS_DMA_DESC_WAIT4END |	(6 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+	d->cmd.address = 0;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_READ |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA |
+		(mtd->writesize + mtd->oobsize);
+	d->cmd.pio_words[1] = 0;
+	d->cmd.pio_words[2] =
+		GPMI_ECCCTRL_ENABLE_ECC |
+		GPMI_ECCCTRL_ECC_CMD_DECODE |
+		GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE;
+	d->cmd.pio_words[3] = mtd->writesize + mtd->oobsize;
+	d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf;
+	d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Compile the DMA descriptor - disable the BCH block. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_CHAIN |
+		MXS_DMA_DESC_NAND_WAIT_4_READY | MXS_DMA_DESC_WAIT4END |
+		(3 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+	d->cmd.address = 0;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WAIT_FOR_READY |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA |
+		(mtd->writesize + mtd->oobsize);
+	d->cmd.pio_words[1] = 0;
+	d->cmd.pio_words[2] = 0;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Compile the DMA descriptor - deassert the NAND lock and interrupt. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_DEC_SEM;
+
+	d->cmd.address = 0;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Execute the DMA chain. */
+	ret = mxs_dma_go(channel);
+	if (ret) {
+		printf("MXS NAND: DMA read error\n");
+		goto rtn;
+	}
+
+	ret = mxs_nand_wait_for_bch_complete();
+	if (ret) {
+		printf("MXS NAND: BCH read timeout\n");
+		goto rtn;
+	}
+
+	/* Read DMA completed, now do the mark swapping. */
+	mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf);
+
+	/* Loop over status bytes, accumulating ECC status. */
+	status = nand_info->oob_buf + mxs_nand_aux_status_offset();
+	for (i = 0; i < mxs_nand_ecc_chunk_cnt(mtd->writesize); i++) {
+		if (status[i] == 0x00)
+			continue;
+
+		if (status[i] == 0xff)
+			continue;
+
+		if (status[i] == 0xfe) {
+			failed++;
+			continue;
+		}
+
+		corrected += status[i];
+	}
+
+	/* Propagate ECC status to the owning MTD. */
+	mtd->ecc_stats.failed += failed;
+	mtd->ecc_stats.corrected += corrected;
+
+	/*
+	 * It's time to deliver the OOB bytes. See mxs_nand_ecc_read_oob() for
+	 * details about our policy for delivering the OOB.
+	 *
+	 * We fill the caller's buffer with set bits, and then copy the block
+	 * mark to th caller's buffer. Note that, if block mark swapping was
+	 * necessary, it has already been done, so we can rely on the first
+	 * byte of the auxiliary buffer to contain the block mark.
+	 */
+	memset(nand->oob_poi, 0xff, mtd->oobsize);
+
+	nand->oob_poi[0] = nand_info->oob_buf[0];
+
+	memcpy(buf, nand_info->data_buf, mtd->writesize);
+
+rtn:
+	mxs_nand_return_dma_descs(nand_info);
+
+	return ret;
+}
+
+/*
+ * Write a page to NAND.
+ */
+static void mxs_nand_ecc_write_page(struct mtd_info *mtd,
+				struct nand_chip *nand, const uint8_t *buf)
+{
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mxs_dma_desc *d;
+	uint32_t channel = MXS_DMA_CHANNEL_AHB_APBH_GPMI0 + nand_info->cur_chip;
+	int ret;
+
+	memcpy(nand_info->data_buf, buf, mtd->writesize);
+	memcpy(nand_info->oob_buf, nand->oob_poi, mtd->oobsize);
+
+	/* Handle block mark swapping. */
+	mxs_nand_swap_block_mark(mtd, nand_info->data_buf, nand_info->oob_buf);
+
+	/* Compile the DMA descriptor - write data. */
+	d = mxs_nand_get_dma_desc(nand_info);
+	d->cmd.data =
+		MXS_DMA_DESC_COMMAND_NO_DMAXFER | MXS_DMA_DESC_IRQ |
+		MXS_DMA_DESC_DEC_SEM | MXS_DMA_DESC_WAIT4END |
+		(6 << MXS_DMA_DESC_PIO_WORDS_OFFSET);
+
+	d->cmd.address = 0;
+
+	d->cmd.pio_words[0] =
+		GPMI_CTRL0_COMMAND_MODE_WRITE |
+		GPMI_CTRL0_WORD_LENGTH |
+		(nand_info->cur_chip << GPMI_CTRL0_CS_OFFSET) |
+		GPMI_CTRL0_ADDRESS_NAND_DATA;
+	d->cmd.pio_words[1] = 0;
+	d->cmd.pio_words[2] =
+		GPMI_ECCCTRL_ENABLE_ECC |
+		GPMI_ECCCTRL_ECC_CMD_ENCODE |
+		GPMI_ECCCTRL_BUFFER_MASK_BCH_PAGE;
+	d->cmd.pio_words[3] = (mtd->writesize + mtd->oobsize);
+	d->cmd.pio_words[4] = (dma_addr_t)nand_info->data_buf;
+	d->cmd.pio_words[5] = (dma_addr_t)nand_info->oob_buf;
+
+	mxs_dma_desc_append(channel, d);
+
+	/* Execute the DMA chain. */
+	ret = mxs_dma_go(channel);
+	if (ret) {
+		printf("MXS NAND: DMA write error\n");
+		goto rtn;
+	}
+
+	ret = mxs_nand_wait_for_bch_complete();
+	if (ret) {
+		printf("MXS NAND: BCH write timeout\n");
+		goto rtn;
+	}
+
+rtn:
+	mxs_nand_return_dma_descs(nand_info);
+}
+
+/*
+ * Read OOB from NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_read_oob(struct mtd_info *mtd, loff_t from,
+					struct mtd_oob_ops *ops)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct mxs_nand_info *nand_info = chip->priv;
+	int ret;
+
+	if (ops->mode == MTD_OOB_RAW)
+		nand_info->raw_oob_mode = 1;
+	else
+		nand_info->raw_oob_mode = 0;
+
+	ret = nand_info->hooked_read_oob(mtd, from, ops);
+
+	nand_info->raw_oob_mode = 0;
+
+	return ret;
+}
+
+/*
+ * Write OOB to NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_write_oob(struct mtd_info *mtd, loff_t to,
+					struct mtd_oob_ops *ops)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct mxs_nand_info *nand_info = chip->priv;
+	int ret;
+
+	if (ops->mode == MTD_OOB_RAW)
+		nand_info->raw_oob_mode = 1;
+	else
+		nand_info->raw_oob_mode = 0;
+
+	ret = nand_info->hooked_write_oob(mtd, to, ops);
+
+	nand_info->raw_oob_mode = 0;
+
+	return ret;
+}
+
+/*
+ * Mark a block bad in NAND.
+ *
+ * This function is a veneer that replaces the function originally installed by
+ * the NAND Flash MTD code.
+ */
+static int mxs_nand_hook_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct mxs_nand_info *nand_info = chip->priv;
+	int ret;
+
+	nand_info->marking_block_bad = 1;
+
+	ret = nand_info->hooked_block_markbad(mtd, ofs);
+
+	nand_info->marking_block_bad = 0;
+
+	return ret;
+}
+
+/*
+ * There are several places in this driver where we have to handle the OOB and
+ * block marks. This is the function where things are the most complicated, so
+ * this is where we try to explain it all. All the other places refer back to
+ * here.
+ *
+ * These are the rules, in order of decreasing importance:
+ *
+ * 1) Nothing the caller does can be allowed to imperil the block mark, so all
+ *    write operations take measures to protect it.
+ *
+ * 2) In read operations, the first byte of the OOB we return must reflect the
+ *    true state of the block mark, no matter where that block mark appears in
+ *    the physical page.
+ *
+ * 3) ECC-based read operations return an OOB full of set bits (since we never
+ *    allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads
+ *    return).
+ *
+ * 4) "Raw" read operations return a direct view of the physical bytes in the
+ *    page, using the conventional definition of which bytes are data and which
+ *    are OOB. This gives the caller a way to see the actual, physical bytes
+ *    in the page, without the distortions applied by our ECC engine.
+ *
+ *
+ * What we do for this specific read operation depends on two questions:
+ *
+ * 1) Are we doing a "raw" read, or an ECC-based read?
+ *
+ * 2) Are we using block mark swapping or transcription?
+ *
+ * There are four cases, illustrated by the following Karnaugh map:
+ *
+ *                    |           Raw           |         ECC-based       |
+ *       -------------+-------------------------+-------------------------+
+ *                    | Read the conventional   |                         |
+ *                    | OOB at the end of the   |                         |
+ *       Swapping     | page and return it. It  |                         |
+ *                    | contains exactly what   |                         |
+ *                    | we want.                | Read the block mark and |
+ *       -------------+-------------------------+ return it in a buffer   |
+ *                    | Read the conventional   | full of set bits.       |
+ *                    | OOB at the end of the   |                         |
+ *                    | page and also the block |                         |
+ *       Transcribing | mark in the metadata.   |                         |
+ *                    | Copy the block mark     |                         |
+ *                    | into the first byte of  |                         |
+ *                    | the OOB.                |                         |
+ *       -------------+-------------------------+-------------------------+
+ *
+ * Note that we break rule #4 in the Transcribing/Raw case because we're not
+ * giving an accurate view of the actual, physical bytes in the page (we're
+ * overwriting the block mark). That's OK because it's more important to follow
+ * rule #2.
+ *
+ * It turns out that knowing whether we want an "ECC-based" or "raw" read is not
+ * easy. When reading a page, for example, the NAND Flash MTD code calls our
+ * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an
+ * ECC-based or raw view of the page is implicit in which function it calls
+ * (there is a similar pair of ECC-based/raw functions for writing).
+ *
+ * Since MTD assumes the OOB is not covered by ECC, there is no pair of
+ * ECC-based/raw functions for reading or or writing the OOB. The fact that the
+ * caller wants an ECC-based or raw view of the page is not propagated down to
+ * this driver.
+ *
+ * Since our OOB *is* covered by ECC, we need this information. So, we hook the
+ * ecc.read_oob and ecc.write_oob function pointers in the owning
+ * struct mtd_info with our own functions. These hook functions set the
+ * raw_oob_mode field so that, when control finally arrives here, we'll know
+ * what to do.
+ */
+static int mxs_nand_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand,
+				int page, int cmd)
+{
+	struct mxs_nand_info *nand_info = nand->priv;
+
+	/*
+	 * First, fill in the OOB buffer. If we're doing a raw read, we need to
+	 * get the bytes from the physical page. If we're not doing a raw read,
+	 * we need to fill the buffer with set bits.
+	 */
+	if (nand_info->raw_oob_mode) {
+		/*
+		 * If control arrives here, we're doing a "raw" read. Send the
+		 * command to read the conventional OOB and read it.
+		 */
+		nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+		nand->read_buf(mtd, nand->oob_poi, mtd->oobsize);
+	} else {
+		/*
+		 * If control arrives here, we're not doing a "raw" read. Fill
+		 * the OOB buffer with set bits and correct the block mark.
+		 */
+		memset(nand->oob_poi, 0xff, mtd->oobsize);
+
+		nand->cmdfunc(mtd, NAND_CMD_READ0, mtd->writesize, page);
+		mxs_nand_read_buf(mtd, nand->oob_poi, 1);
+	}
+
+	return 0;
+
+}
+
+/*
+ * Write OOB data to NAND.
+ */
+static int mxs_nand_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *nand,
+					int page)
+{
+	struct mxs_nand_info *nand_info = nand->priv;
+	uint8_t block_mark = 0;
+
+	/*
+	 * There are fundamental incompatibilities between the i.MX GPMI NFC and
+	 * the NAND Flash MTD model that make it essentially impossible to write
+	 * the out-of-band bytes.
+	 *
+	 * We permit *ONE* exception. If the *intent* of writing the OOB is to
+	 * mark a block bad, we can do that.
+	 */
+
+	if (nand_info->marking_block_bad) {
+		printf("NXS NAND: Writing OOB isn't supported\n");
+		return -EIO;
+	}
+
+	/* Write the block mark. */
+	nand->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
+	nand->write_buf(mtd, &block_mark, 1);
+	nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+	/* Check if it worked. */
+	if (nand->waitfunc(mtd, nand) & NAND_STATUS_FAIL)
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Claims all blocks are good.
+ *
+ * In principle, this function is *only* called when the NAND Flash MTD system
+ * isn't allowed to keep an in-memory bad block table, so it is forced to ask
+ * the driver for bad block information.
+ *
+ * In fact, we permit the NAND Flash MTD system to have an in-memory BBT, so
+ * this function is *only* called when we take it away.
+ *
+ * We take away the in-memory BBT when the user sets the "ignorebad" parameter,
+ * which indicates that all blocks should be reported good.
+ *
+ * Thus, this function is only called when we want *all* blocks to look good,
+ * so it *always* return success.
+ */
+static int mxs_nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+	return 0;
+}
+
+/*
+ * Nominally, the purpose of this function is to look for or create the bad
+ * block table. In fact, since the HIL calls this function at the very end of
+ * the initialization process started by nand_scan(), and the HIL doesn't have a
+ * more formal mechanism, everyone "hooks" this function to continue the
+ * initialization process.
+ *
+ * At this point, the physical NAND Flash chips have been identified and
+ * counted, so we know the physical geometry. This enables us to make some
+ * important configuration decisions.
+ *
+ * The return value of this function propogates directly back to this driver's
+ * call to nand_scan(). Anything other than zero will cause this driver to
+ * tear everything down and declare failure.
+ */
+static int mxs_nand_scan_bbt(struct mtd_info *mtd)
+{
+	struct nand_chip *nand = mtd->priv;
+	struct mxs_nand_info *nand_info = nand->priv;
+	struct mx28_bch_regs *bch_regs = (struct mx28_bch_regs *)MXS_BCH_BASE;
+	uint32_t tmp;
+
+	/* Configure BCH and set NFC geometry */
+	mx28_reset_block(&bch_regs->hw_bch_ctrl_reg);
+
+	/* Configure layout 0 */
+	tmp = (mxs_nand_ecc_chunk_cnt(mtd->writesize) - 1)
+		<< BCH_FLASHLAYOUT0_NBLOCKS_OFFSET;
+	tmp |= MXS_NAND_METADATA_SIZE << BCH_FLASHLAYOUT0_META_SIZE_OFFSET;
+	tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+		<< BCH_FLASHLAYOUT0_ECC0_OFFSET;
+	tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+	writel(tmp, &bch_regs->hw_bch_flash0layout0);
+
+	tmp = (mtd->writesize + mtd->oobsize)
+		<< BCH_FLASHLAYOUT1_PAGE_SIZE_OFFSET;
+	tmp |= (mxs_nand_get_ecc_strength(mtd->writesize, mtd->oobsize) >> 1)
+		<< BCH_FLASHLAYOUT1_ECCN_OFFSET;
+	tmp |= MXS_NAND_CHUNK_DATA_CHUNK_SIZE;
+	writel(tmp, &bch_regs->hw_bch_flash0layout1);
+
+	/* Set *all* chip selects to use layout 0 */
+	writel(0, &bch_regs->hw_bch_layoutselect);
+
+	/* Enable BCH complete interrupt */
+	writel(BCH_CTRL_COMPLETE_IRQ_EN, &bch_regs->hw_bch_ctrl_set);
+
+	/* Hook some operations at the MTD level. */
+	if (mtd->read_oob != mxs_nand_hook_read_oob) {
+		nand_info->hooked_read_oob = mtd->read_oob;
+		mtd->read_oob = mxs_nand_hook_read_oob;
+	}
+
+	if (mtd->write_oob != mxs_nand_hook_write_oob) {
+		nand_info->hooked_write_oob = mtd->write_oob;
+		mtd->write_oob = mxs_nand_hook_write_oob;
+	}
+
+	if (mtd->block_markbad != mxs_nand_hook_block_markbad) {
+		nand_info->hooked_block_markbad = mtd->block_markbad;
+		mtd->block_markbad = mxs_nand_hook_block_markbad;
+	}
+
+	/* We use the reference implementation for bad block management. */
+	return nand_default_bbt(mtd);
+}
+
+/*
+ * Allocate DMA buffers
+ */
+int mxs_nand_alloc_buffers(struct mxs_nand_info *nand_info)
+{
+	uint8_t *buf;
+	const int size = NAND_MAX_PAGESIZE + NAND_MAX_OOBSIZE;
+
+	/* DMA buffers */
+	buf = memalign(MXS_DMA_ALIGNMENT, size);
+	if (!buf) {
+		printf("MXS NAND: Error allocating DMA buffers\n");
+		return -ENOMEM;
+	}
+
+	memset(buf, 0, size);
+
+	nand_info->data_buf = buf;
+	nand_info->oob_buf = buf + NAND_MAX_PAGESIZE;
+
+	/* Command buffers */
+	nand_info->cmd_buf = memalign(MXS_DMA_ALIGNMENT,
+				MXS_NAND_COMMAND_BUFFER_SIZE);
+	if (!nand_info->cmd_buf) {
+		free(buf);
+		printf("MXS NAND: Error allocating command buffers\n");
+		return -ENOMEM;
+	}
+	memset(nand_info->cmd_buf, 0, MXS_NAND_COMMAND_BUFFER_SIZE);
+	nand_info->cmd_queue_len = 0;
+
+	return 0;
+}
+
+/*
+ * Initializes the NFC hardware.
+ */
+int mxs_nand_init(struct mxs_nand_info *info)
+{
+	struct mx28_gpmi_regs *gpmi_regs =
+		(struct mx28_gpmi_regs *)MXS_GPMI_BASE;
+	int i = 0;
+
+	info->desc = malloc(sizeof(struct mxs_dma_desc *) *
+				MXS_NAND_DMA_DESCRIPTOR_COUNT);
+	if (!info->desc)
+		goto err1;
+
+	/* Allocate the DMA descriptors. */
+	for (i = 0; i < MXS_NAND_DMA_DESCRIPTOR_COUNT; i++) {
+		info->desc[i] = mxs_dma_desc_alloc();
+		if (!info->desc[i])
+			goto err2;
+	}
+
+	/* Init the DMA controller. */
+	mxs_dma_init();
+
+	/* Reset the GPMI block. */
+	mx28_reset_block(&gpmi_regs->hw_gpmi_ctrl0_reg);
+
+	/*
+	 * Choose NAND mode, set IRQ polarity, disable write protection and
+	 * select BCH ECC.
+	 */
+	clrsetbits_le32(&gpmi_regs->hw_gpmi_ctrl1,
+			GPMI_CTRL1_GPMI_MODE,
+			GPMI_CTRL1_ATA_IRQRDY_POLARITY | GPMI_CTRL1_DEV_RESET |
+			GPMI_CTRL1_BCH_MODE);
+
+	return 0;
+
+err2:
+	free(info->desc);
+err1:
+	for (--i; i >= 0; i--)
+		mxs_dma_desc_free(info->desc[i]);
+	printf("MXS NAND: Unable to allocate DMA descriptors\n");
+	return -ENOMEM;
+}
+
+/*!
+ * This function is called during the driver binding process.
+ *
+ * @param   pdev  the device structure used to store device specific
+ *                information that is used by the suspend, resume and
+ *                remove functions
+ *
+ * @return  The function always returns 0.
+ */
+int board_nand_init(struct nand_chip *nand)
+{
+	struct mxs_nand_info *nand_info;
+	int err;
+
+	nand_info = malloc(sizeof(struct mxs_nand_info));
+	if (!nand_info) {
+		printf("MXS NAND: Failed to allocate private data\n");
+		return -ENOMEM;
+	}
+	memset(nand_info, 0, sizeof(struct mxs_nand_info));
+
+	err = mxs_nand_alloc_buffers(nand_info);
+	if (err)
+		goto err1;
+
+	err = mxs_nand_init(nand_info);
+	if (err)
+		goto err2;
+
+	memset(&fake_ecc_layout, 0, sizeof(fake_ecc_layout));
+
+	nand->priv = nand_info;
+	nand->options |= NAND_NO_SUBPAGE_WRITE;
+
+	nand->cmd_ctrl		= mxs_nand_cmd_ctrl;
+
+	nand->dev_ready		= mxs_nand_device_ready;
+	nand->select_chip	= mxs_nand_select_chip;
+	nand->block_bad		= mxs_nand_block_bad;
+	nand->scan_bbt		= mxs_nand_scan_bbt;
+
+	nand->read_byte		= mxs_nand_read_byte;
+
+	nand->read_buf		= mxs_nand_read_buf;
+	nand->write_buf		= mxs_nand_write_buf;
+
+	nand->ecc.read_page	= mxs_nand_ecc_read_page;
+	nand->ecc.write_page	= mxs_nand_ecc_write_page;
+	nand->ecc.read_oob	= mxs_nand_ecc_read_oob;
+	nand->ecc.write_oob	= mxs_nand_ecc_write_oob;
+
+	nand->ecc.layout	= &fake_ecc_layout;
+	nand->ecc.mode		= NAND_ECC_HW;
+	nand->ecc.bytes		= 9;
+	nand->ecc.size		= 512;
+
+	return 0;
+
+err2:
+	free(nand_info->data_buf);
+	free(nand_info->cmd_buf);
+err1:
+	free(nand_info);
+	return err;
+}