Patchwork [1/5] pxa3xx_nand: enable PXA3xx bad block management

login
register
mail settings
Submitter Haojian Zhuang
Date Sept. 26, 2009, 2 a.m.
Message ID <771cded00909251900v7ee60bf6uf4c5724075c65826@mail.gmail.com>
Download mbox | patch
Permalink /patch/34330/
State New, archived
Headers show

Comments

Haojian Zhuang - Sept. 26, 2009, 2 a.m.
On Sat, Sep 26, 2009 at 1:04 AM, Mok Keith <ek9852@gmail.com> wrote:
> Hi,
>
> Any reason to limit the max slot to 40 (PXA_MAX_SLOT). It seems the
> bad block management scheme is not scalable and have a limitation of
> up to 256M bytes flash size only. (256M, 128kB per block = 2048 blocks
> = 40 bad blocks)

Each slot is one page. It's used to record the relocation information.
Each relocation
information is composed by two items. One is the bad block number, the other one
is relocated block number. Both of these two items cost 16 bits. For
the large block
flash, it can save 511 relocation information at most.

In the head of slot, a 16-bit magic number and 16-bit total number of
relocation items are
recorders.

When the slot is used, the next written will be moved to the next
slot. It's used to protect
relocation recorders. If one slot is broken, driver could load the previous one.

When all 40 slots are used, driver could erase the whole block and
write again. This feature
isn't enabled in the patch.
>
> Also for the calibration of the 2% bad block area, according to most
> NAND specifications (checked Samsung, Hynix, Micron), nand flash
> vendor guarantee no more than 20 bad block per 1024 blocks and this
> rule is apply linearly.
>
> Would it be better to use the following otherwise when nand size is
> greater than 256M the no of bad block entries is not matched to
> specification:
> instead of:
>> +    entries = (entries * 2) / 100;
>
> use:
> +    entries = (entries * 10) / 512;

Yes, this formula is better. I will use this. Maybe I can use this
formula whatever
the flash is beyond 256M or not.

>
> May I know what is the use of PXA_BEGIN_SLOT ?
I'm sorry that I removed usage of PXA_BEGIN_SLOT. But I didn't clear the macro
definition.
>
> Keith
>

Now the new patch is in below.

From 57dc033ff95a12ee83f9189c5f72827d755c5432 Mon Sep 17 00:00:00 2001
From: Haojian Zhuang <haojian.zhuang@marvell.com>
Date: Fri, 25 Sep 2009 15:00:28 -0400
Subject: [PATCH] [MTD] [NAND] pxa3xx_nand: enable PXA3xx bad block management

There's a custom bad block management in PXA3xx series.

This BBM needs to allocate a reserved area at the bottom of NAND chip.
The reserved area should be protected from normal usage. The first block
of NAND is also reserved in order to storing the relocation information.

The reserved area is also called as relocation area. It occupies 2% of
the whole NAND space.

When NAND controller finds a bad block, it marks the block as bad and
allocate a unused block from relocation area in bottom. The new block is
used to replace the original bad one. From OS view, there's no bad block
at the time. It's handled by NAND driver. Then driver records the replacement
information in the first block.

There're 40 pages are reserved in the first block. Each page is a independant
relocation table. If the current relocation table is broken, driver will fetch
the previous one. Each relocation information cost a 32-bit word.

Signed-off-by: Haojian Zhuang <haojian.zhuang@marvell.com>
---
 arch/arm/plat-pxa/include/plat/pxa3xx_bbm.h |   61 ++++
 drivers/mtd/Kconfig                         |    6 +
 drivers/mtd/Makefile                        |    1 +
 drivers/mtd/nand/pxa3xx_nand.c              |  124 ++++++++
 drivers/mtd/pxa3xx_bbm.c                    |  427 +++++++++++++++++++++++++++
 5 files changed, 619 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/plat-pxa/include/plat/pxa3xx_bbm.h
 create mode 100644 drivers/mtd/pxa3xx_bbm.c
Haojian Zhuang - Oct. 8, 2009, 3:12 a.m.
On Fri, Sep 25, 2009 at 10:00 PM, Haojian Zhuang
<haojian.zhuang@gmail.com> wrote:
> On Sat, Sep 26, 2009 at 1:04 AM, Mok Keith <ek9852@gmail.com> wrote:
>> Hi,
>>
>> Any reason to limit the max slot to 40 (PXA_MAX_SLOT). It seems the
>> bad block management scheme is not scalable and have a limitation of
>> up to 256M bytes flash size only. (256M, 128kB per block = 2048 blocks
>> = 40 bad blocks)
>
> Each slot is one page. It's used to record the relocation information.
> Each relocation
> information is composed by two items. One is the bad block number, the other one
> is relocated block number. Both of these two items cost 16 bits. For
> the large block
> flash, it can save 511 relocation information at most.
>
> In the head of slot, a 16-bit magic number and 16-bit total number of
> relocation items are
> recorders.
>
> When the slot is used, the next written will be moved to the next
> slot. It's used to protect
> relocation recorders. If one slot is broken, driver could load the previous one.
>
> When all 40 slots are used, driver could erase the whole block and
> write again. This feature
> isn't enabled in the patch.
>>
>> Also for the calibration of the 2% bad block area, according to most
>> NAND specifications (checked Samsung, Hynix, Micron), nand flash
>> vendor guarantee no more than 20 bad block per 1024 blocks and this
>> rule is apply linearly.
>>
>> Would it be better to use the following otherwise when nand size is
>> greater than 256M the no of bad block entries is not matched to
>> specification:
>> instead of:
>>> +    entries = (entries * 2) / 100;
>>
>> use:
>> +    entries = (entries * 10) / 512;
>
> Yes, this formula is better. I will use this. Maybe I can use this
> formula whatever
> the flash is beyond 256M or not.
>
>>
>> May I know what is the use of PXA_BEGIN_SLOT ?
> I'm sorry that I removed usage of PXA_BEGIN_SLOT. But I didn't clear the macro
> definition.
>>
>> Keith
>>
>
> Now the new patch is in below.
>
> From 57dc033ff95a12ee83f9189c5f72827d755c5432 Mon Sep 17 00:00:00 2001
> From: Haojian Zhuang <haojian.zhuang@marvell.com>
> Date: Fri, 25 Sep 2009 15:00:28 -0400
> Subject: [PATCH] [MTD] [NAND] pxa3xx_nand: enable PXA3xx bad block management
>
> There's a custom bad block management in PXA3xx series.
>
> This BBM needs to allocate a reserved area at the bottom of NAND chip.
> The reserved area should be protected from normal usage. The first block
> of NAND is also reserved in order to storing the relocation information.
>
> The reserved area is also called as relocation area. It occupies 2% of
> the whole NAND space.
>
> When NAND controller finds a bad block, it marks the block as bad and
> allocate a unused block from relocation area in bottom. The new block is
> used to replace the original bad one. From OS view, there's no bad block
> at the time. It's handled by NAND driver. Then driver records the replacement
> information in the first block.
>
> There're 40 pages are reserved in the first block. Each page is a independant
> relocation table. If the current relocation table is broken, driver will fetch
> the previous one. Each relocation information cost a 32-bit word.
>
>

Hi David,

How do you think that this patch series on PXA3xx bad block
management? Is it acceptable or not?

Thanks
Haojian
Eric Miao - Oct. 8, 2009, 1:39 p.m.
> Hi David,
>
> How do you think that this patch series on PXA3xx bad block
> management? Is it acceptable or not?
>

David,

I can take care of this and send pull request to you if you don't have
time to look into this.

Patch

diff --git a/arch/arm/plat-pxa/include/plat/pxa3xx_bbm.h
b/arch/arm/plat-pxa/include/plat/pxa3xx_bbm.h
new file mode 100644
index 0000000..3cc8c7b
--- /dev/null
+++ b/arch/arm/plat-pxa/include/plat/pxa3xx_bbm.h
@@ -0,0 +1,61 @@ 
+#ifndef	__PXA3XX_BBT_H__
+#define	__PXA3XX_BBT_H__
+
+#include <linux/types.h>
+
+#define PXA_RLTABLE_HEADER		(0x524e)
+#define PXA_MAX_RLENTRY			(127)
+#define PXA_MAX_SLOT			(40)
+#define PXA_BBM_MAGIC			(0x4c56524d)	/* MRVL */
+
+enum {
+	PXA3xx_BBM_NAND = 0,
+	PXA3xx_BBM_ONENAND,
+	PXA3xx_BBM_INVALID = -1,
+};
+
+struct relocate_entry {
+	unsigned short from;
+	unsigned short to;
+};
+
+struct relocate_table {
+	unsigned short header;
+	unsigned short total;
+};
+
+struct pxa3xx_bbm {
+	int			magic;
+	/*
+	 * NOTES: this field impact the partition table. Please make sure
+	 * that this value align with partitions definition.
+	 */
+	int			max_relocate_entry;
+	int			max_slots;
+	int			current_slot;
+
+	void			*data_buf;
+
+	/*
+	 * These two fields should be in (one)nand_chip. Add here to handle
+	 * onenand_chip and nand_chip at the same time.
+	 */
+	int			page_shift;
+	int			erase_shift;
+
+	struct relocate_table	*table;
+	struct relocate_entry	*entry;
+
+	void	(*uninit)(struct mtd_info *mtd);
+	loff_t	(*search)(struct mtd_info *mtd,	loff_t ofs);
+	int	(*block_markbad)(struct mtd_info *mtd, int block);
+	int	(*scan_bbt)(struct mtd_info *mtd);
+};
+
+extern int verify_nand_bbm(struct mtd_info *mtd, struct pxa3xx_bbm **bbm);
+extern int verify_onenand_bbm(struct mtd_info *mtd, struct pxa3xx_bbm **bbm);
+extern int nand_badblockpos(struct mtd_info *mtd);
+extern int onenand_badblockpos(struct mtd_info *mtd);
+extern struct pxa3xx_bbm *pxa3xx_query_bbm(void);
+#endif
+
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index b8e35a0..3cdf7bf 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -315,6 +315,12 @@  config MTD_OOPS
 	  To use, add console=ttyMTDx to the kernel command line,
 	  where x is the MTD device number to use.

+config PXA3xx_BBM
+	bool "Marvell PXA3xx Bad Block Management"
+	depends on MTD && (MTD_NAND || MTD_ONENAND)
+	help
+	  This enables Marvell Bad block management on NAND/ONENAND on PXA3xx.
+
 source "drivers/mtd/chips/Kconfig"

 source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 82d1e4d..e637fa0 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -25,6 +25,7 @@  obj-$(CONFIG_INFTL)		+= inftl.o
 obj-$(CONFIG_RFD_FTL)		+= rfd_ftl.o
 obj-$(CONFIG_SSFDC)		+= ssfdc.o
 obj-$(CONFIG_MTD_OOPS)		+= mtdoops.o
+obj-$(CONFIG_PXA3xx_BBM)	+= pxa3xx_bbm.o

 nftl-objs		:= nftlcore.o nftlmount.o
 inftl-objs		:= inftlcore.o inftlmount.o
diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
index 134bfbc..d6c9524 100644
--- a/drivers/mtd/nand/pxa3xx_nand.c
+++ b/drivers/mtd/nand/pxa3xx_nand.c
@@ -24,6 +24,10 @@ 
 #include <mach/dma.h>
 #include <plat/pxa3xx_nand.h>

+#ifdef CONFIG_PXA3xx_BBM
+#include <plat/pxa3xx_bbm.h>
+#endif
+
 #define	CHIP_DELAY_TIMEOUT	(2 * HZ/10)

 /* registers and bit definitions */
@@ -112,6 +116,14 @@  enum {

 struct pxa3xx_nand_info {
 	struct nand_chip	nand_chip;
+#ifdef CONFIG_PXA3xx_BBM
+	/*
+	 * Restriction: nand_chip should be the first one of pxa3xx_nand_info.
+	 * bbm should be the second one of pxa3xx_nand_info.
+	 * Marvell PXA3xx BBM always access this field to get bbm.
+	 */
+	struct pxa3xx_bbm	*bbm;
+#endif

 	struct platform_device	 *pdev;
 	const struct pxa3xx_nand_flash *flash_info;
@@ -365,6 +377,8 @@  static struct pxa3xx_nand_flash *builtin_flash_types[] = {
 /* convert nand flash controller clock cycles to nano-seconds */
 #define cycle2ns(c, clk)	((((c) + 1) * 1000000 + clk / 500) / (clk / 1000))

+static int pxa3xx_nand_relocate_addr(struct mtd_info *mtd, int page_addr);
+
 static void pxa3xx_nand_set_timing(struct pxa3xx_nand_info *info,
 				   const struct pxa3xx_nand_timing *t)
 {
@@ -706,6 +720,8 @@  static void pxa3xx_nand_cmdfunc(struct mtd_info
*mtd, unsigned command,

 	init_completion(&info->cmd_complete);

+	page_addr = pxa3xx_nand_relocate_addr(mtd, page_addr);
+
 	switch (command) {
 	case NAND_CMD_READOOB:
 		/* disable HW ECC to get all the OOB data */
@@ -1165,6 +1181,113 @@  static struct nand_ecclayout hw_largepage_ecclayout = {
 	.oobfree = { {2, 38} }
 };

+#ifdef CONFIG_PXA3xx_BBM
+int verify_nand_bbm(struct mtd_info *mtd, struct pxa3xx_bbm **bbm)
+{
+	struct nand_chip *info = mtd->priv;
+	struct pxa3xx_bbm **nbbm = NULL;
+
+	/* check whether current flash is nand */
+	nbbm = (struct pxa3xx_bbm **)(++info);
+	if (((unsigned int)nbbm < PAGE_OFFSET)
+		|| ((unsigned int)*nbbm < PAGE_OFFSET))
+		return PXA3xx_BBM_INVALID;
+
+	if ((*nbbm)->magic == PXA_BBM_MAGIC) {
+		pr_debug("%s:Found Nand flash.\n", __func__);
+		*bbm = *nbbm;
+		return PXA3xx_BBM_NAND;
+	}
+	return PXA3xx_BBM_INVALID;
+}
+
+static int pxa3xx_nand_relocate_addr(struct mtd_info *mtd, int page_addr)
+{
+	struct pxa3xx_nand_info *info = mtd->priv;
+	struct pxa3xx_bbm *bbm = info->bbm;
+	loff_t addr;
+	int ret;
+
+	addr = page_addr << bbm->page_shift;
+	addr = bbm->search(mtd, addr);
+	ret = addr >> bbm->page_shift;
+	return ret;
+}
+
+static int pxa3xx_nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+	struct pxa3xx_nand_info *info = mtd->priv;
+	struct pxa3xx_bbm *bbm = info->bbm;
+	struct nand_chip *chip = mtd->priv;
+	uint8_t buf[2] = { 0, 0 };
+	int block, ret;
+
+	/* Get block number */
+	block = (int)(ofs >> chip->bbt_erase_shift);
+
+	/* We write two bytes, so we dont have to mess with 16 bit
+	 * access
+	 */
+	ofs += mtd->oobsize;
+	chip->ops.len = chip->ops.ooblen = 2;
+	chip->ops.datbuf = NULL;
+	chip->ops.oobbuf = buf;
+	chip->ops.ooboffs = chip->badblockpos & ~0x01;
+
+	ret = mtd->write_oob(mtd, ofs, &chip->ops);
+
+	if (!ret)
+		mtd->ecc_stats.badblocks++;
+
+	return bbm->block_markbad(mtd, block);
+}
+
+static void pxa3xx_nand_init_mtd(struct mtd_info *mtd,
+				 struct pxa3xx_nand_info *info)
+{
+	const struct pxa3xx_nand_flash *f = info->flash_info;
+	struct nand_chip *this = &info->nand_chip;
+	struct pxa3xx_bbm *bbm = NULL;
+
+	this->options = (f->flash_width == 16) ? NAND_BUSWIDTH_16: 0;
+
+	this->waitfunc		= pxa3xx_nand_waitfunc;
+	this->select_chip	= pxa3xx_nand_select_chip;
+	this->dev_ready		= pxa3xx_nand_dev_ready;
+	this->cmdfunc		= pxa3xx_nand_cmdfunc;
+	this->read_word		= pxa3xx_nand_read_word;
+	this->read_byte		= pxa3xx_nand_read_byte;
+	this->read_buf		= pxa3xx_nand_read_buf;
+	this->write_buf		= pxa3xx_nand_write_buf;
+	this->verify_buf	= pxa3xx_nand_verify_buf;
+
+	this->ecc.mode		= NAND_ECC_HW;
+	this->ecc.hwctl		= pxa3xx_nand_ecc_hwctl;
+	this->ecc.calculate	= pxa3xx_nand_ecc_calculate;
+	this->ecc.correct	= pxa3xx_nand_ecc_correct;
+	this->ecc.size		= f->page_size;
+
+	if (f->page_size == 2048)
+		this->ecc.layout = &hw_largepage_ecclayout;
+	else
+		this->ecc.layout = &hw_smallpage_ecclayout;
+
+	this->chip_delay = 25;
+
+	bbm = pxa3xx_query_bbm();
+	if (bbm) {
+		/* Marvell PXA3xx BBM is initialized successfully */
+		info->bbm = bbm;
+		this->scan_bbt = bbm->scan_bbt;
+		this->block_markbad = pxa3xx_nand_block_markbad;
+	}
+}
+#else
+static int pxa3xx_nand_relocate_addr(struct mtd_info *mtd, int page_addr)
+{
+	return page_addr;
+}
+
 static void pxa3xx_nand_init_mtd(struct mtd_info *mtd,
 				 struct pxa3xx_nand_info *info)
 {
@@ -1196,6 +1319,7 @@  static void pxa3xx_nand_init_mtd(struct mtd_info *mtd,

 	this->chip_delay = 25;
 }
+#endif

 static int pxa3xx_nand_probe(struct platform_device *pdev)
 {
diff --git a/drivers/mtd/pxa3xx_bbm.c b/drivers/mtd/pxa3xx_bbm.c
new file mode 100644
index 0000000..d9c1853
--- /dev/null
+++ b/drivers/mtd/pxa3xx_bbm.c
@@ -0,0 +1,427 @@ 
+/*
+ * linux/drivers/mtd/pxa3xx_bbm.c
+ *
+ * Support bad block management on PXA3xx.
+ * Copyright (C) 2007 Marvell International Ltd.
+ *
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <plat/pxa3xx_bbm.h>
+#include <asm/errno.h>
+
+static struct pxa3xx_bbm *pxa3xx_bbm = NULL;
+
+/*
+ * bbm should be the next field of nand_chip or onenand_chip.
+ */
+static int verify_bbm_magic(struct mtd_info *mtd, struct pxa3xx_bbm **bbm)
+{
+	int ret;
+
+	ret = verify_nand_bbm(mtd, bbm);
+	return ret;
+}
+
+static void dump_rltable(struct pxa3xx_bbm *bbm)
+{
+	int i;
+
+	if (bbm->table->total == 0) {
+		pr_info("The relocation table is empty now\n");
+		return;
+	}
+	for (i = 0; i < bbm->table->total; i++) {
+		if (bbm->entry[i].from == (unsigned short)(-1))
+			continue;
+		if (bbm->entry[i].to == (unsigned short)(-1))
+			pr_info("(%4d): block #%d is bad in relocation area\n",
+				i, bbm->entry[i].from);
+		else
+			pr_info("(%4d): block #%d is relocated to #%d\n",
+				i, bbm->entry[i].from, bbm->entry[i].to);
+	}
+}
+
+/* Initialize the relocation table */
+static int pxa3xx_init_rltable(struct mtd_info *mtd)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	int size = mtd->writesize + mtd->oobsize;
+	int pages, entries;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0) {
+		/* BBM don't support this type of flash */
+		return -EINVAL;
+	}
+
+	bbm->page_shift = ffs(mtd->writesize) - 1;
+	bbm->erase_shift = ffs(mtd->erasesize) - 1;
+
+	pages = mtd->erasesize >> bbm->page_shift;
+
+	entries = mtd->size >> bbm->erase_shift;
+	entries = (entries * 10) / 512;
+	if (mtd->writesize == 512)
+		entries = (entries < PXA_MAX_RLENTRY) ? entries
+			  : PXA_MAX_RLENTRY;
+
+	bbm->max_slots	= PXA_MAX_SLOT;
+	bbm->max_relocate_entry = entries;
+	bbm->current_slot = -1;
+
+	bbm->data_buf = kzalloc(size, GFP_KERNEL);
+	if (bbm->data_buf == NULL)
+		return -ENOMEM;
+
+	bbm->table = (struct relocate_table *)bbm->data_buf;
+	memset(bbm->table, 0, sizeof(struct relocate_table));
+
+	bbm->entry = (struct relocate_entry *)((uint8_t *)bbm->data_buf +
+			sizeof(struct relocate_entry));
+	memset(bbm->entry, 0, sizeof(struct relocate_entry)
+		* bbm->max_relocate_entry);
+
+	return 0;
+}
+
+/* Uninitialize the relocation table */
+static void pxa3xx_uninit_rltable(struct mtd_info *mtd)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0) {
+		/* BBM don't support this flash type */
+		return;
+	}
+
+	if (bbm) {
+		kfree(bbm->data_buf);
+		bbm = NULL;
+	}
+}
+
+/*
+ * Move larger data to left of pivot, and move smaller data to right of
+ * pivot.
+ */
+static int partition(unsigned short *array, int left, int right, int pivot_idx)
+{
+	int i, pivot, base_idx;
+
+	pivot = array[pivot_idx];
+	swap(array[left], array[right]);
+	base_idx = left;
+
+	for (i = left; i < right; i++) {
+		if (array[i] > pivot) {
+			swap(array[i], array[base_idx]);
+			base_idx++;
+		}
+	}
+	if (base_idx < right)
+		swap(array[base_idx], array[right]);
+	return base_idx;
+}
+
+static int quick_sort(unsigned short *array, int left, int right)
+{
+	int pivot_idx, new_idx;
+	if (right > left) {
+		pivot_idx = left;
+		new_idx = partition(array, left, right, pivot_idx);
+		quick_sort(array, left, new_idx - 1);
+		quick_sort(array, new_idx + 1, right);
+	}
+	return 0;
+}
+
+/*
+ * Add the relocation entry into the relocation table. If the relocated block
+ * is bad, an new entry will be added into the bottom of the relocation table.
+ */
+int update_rltable(struct mtd_info *mtd, int block)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	struct relocate_table *table = NULL;
+	struct relocate_entry *entry = NULL;
+	struct erase_info instr;
+	unsigned short array[PXA_MAX_RLENTRY], addr;
+	int i, idx, ret, relocated_idx = -1;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0) {
+		/* BBM don't support this type of flash */
+		return -EINVAL;
+	}
+
+	table = bbm->table;
+	entry = bbm->entry;
+
+	/* identify whether the block has been relocated */
+	for (i = 0; i < table->total; i++) {
+		if (entry[i].from == (unsigned short)(-1))
+			continue;
+		if (block == entry[i].from) {
+			relocated_idx = i;
+			break;
+		}
+	}
+
+scan:
+	if (table->total > bbm->max_relocate_entry) {
+		pr_warning("Relocation entries exceed max num. Can't relocate");
+		pr_warning(" block 0x%x\n", block);
+		return -ENOSPC;
+	}
+
+	memset(array, 0, PXA_MAX_RLENTRY);
+	/* Get all index of relocated blocks */
+	for (i = 0, idx = 0; i < table->total; i++) {
+		array[idx] = (entry[i].to != (unsigned short)(-1)) ? entry[i].to
+				: entry[i].from;
+		idx++;
+	}
+	/* sort array with descending order */
+	quick_sort(array, 0, idx - 1);
+	/*
+	 * find the available block with the largest number in reservered
+	 * area
+	 */
+	addr = (unsigned short)((mtd->size >> bbm->erase_shift) - 1);
+	for (i = 0; i < bbm->max_relocate_entry; i++, addr--) {
+		if (addr < ((mtd->size >> bbm->erase_shift)
+			- bbm->max_relocate_entry)) {
+			pr_warning("Relocation area is already full!\n");
+			return -ENOSPC;
+		}
+		if (array[i] < addr) {
+			memset(&instr, 0, sizeof(struct erase_info));
+			instr.mtd = mtd;
+			instr.addr = addr << bbm->erase_shift;
+			instr.len = 1 << bbm->erase_shift;
+			ret = mtd->erase(mtd, &instr);
+			if (ret == 0) {
+				/* fill the recorder into relocation table */
+				if (relocated_idx == -1) {
+					/* new entry in relocation table */
+					entry[table->total].from = block;
+					entry[table->total].to = addr;
+					table->total++;
+					goto done;
+				} else {
+					/* update entry in relocation table */
+					entry[table->total].from
+						= entry[relocated_idx].to;
+					entry[table->total].to
+						= (unsigned short)(-1);
+					table->total++;
+					entry[relocated_idx].to = addr;
+					goto done;
+				}
+			} else {
+				/* append new bad entry */
+				entry[table->total].from = addr;
+				entry[table->total].to = (unsigned short)(-1);
+				table->total++;
+				goto scan;
+			}
+		}
+	}
+
+done:
+	return 0;
+}
+
+/* Write the relocation table back to device, if there's room. */
+static int sync_rltable(struct mtd_info *mtd, int *idx)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	unsigned char *tmp;
+	size_t retlen;
+	int len, pages;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0) {
+		/* BBM don't support this type of flash */
+		return -EINVAL;
+	}
+
+	pages = mtd->erasesize >> bbm->page_shift;
+	if ((*idx >= pages) || (*idx <= (pages - bbm->max_slots))) {
+		printk(KERN_ERR "Wrong Slot is specified.\n");
+		return -EINVAL;
+	}
+
+	/* should write to the next slot*/
+	(*idx)--;
+
+	len = 4;		/* table header */
+	len += bbm->table->total << 2;
+
+	tmp = (unsigned char *)bbm->data_buf;
+	mtd->write(mtd, (*idx) << bbm->page_shift,
+		   1 << bbm->page_shift, &retlen, tmp);
+
+	return 0;
+}
+
+static int pxa3xx_scan_rltable(struct mtd_info *mtd)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	struct relocate_table *table;
+	int page, max_page;
+	size_t retlen;
+	int ret, retry_count = 3;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0) {
+		/* BBM don't support this type of flash */
+		return -EINVAL;
+	}
+
+	pxa3xx_init_rltable(mtd);
+
+	table = bbm->table;
+
+	bbm->current_slot = -1;
+	page = (mtd->erasesize >> bbm->page_shift) - bbm->max_slots;
+	max_page = mtd->erasesize >> bbm->page_shift;
+	for (; page < max_page; page++, retry_count = 3) {
+retry:
+		memset(bbm->data_buf, 0, mtd->writesize + mtd->oobsize);
+		ret = mtd->read(mtd, (page << bbm->page_shift),
+				mtd->writesize, &retlen, bbm->data_buf);
+		if (ret == 0) {
+			if (table->header == PXA_RLTABLE_HEADER) {
+				bbm->current_slot = page;
+				break;
+			}
+		} else {
+			if (retry_count--)
+				goto retry;
+		}
+	}
+	if (bbm->current_slot != -1) {
+		pr_debug("Found relocation table at page:%d\n",
+			bbm->current_slot);
+		dump_rltable(bbm);
+	} else {
+		pr_err("Can't recognize relocation table.\n");
+		pr_err("CAUTION: It may cause unpredicated error\n");
+		pr_err("Please re-initialize the flash.\n");
+		memset((unsigned char *)bbm->table, 0,
+			sizeof(struct relocate_table));
+		return -EFAULT;
+	}
+	return 0;
+}
+
+/*
+ * Find the relocated block of the bad one.
+ * If it's a good block, return 0. Otherwise, return a relocated one.
+ * idx points to the next relocation entry
+ * If the relocated block is bad, an new entry will be added into the
+ * bottom of the relocation table.
+ */
+static loff_t pxa3xx_search_rlentry(struct mtd_info *mtd, loff_t ofs)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	struct relocate_table *table = NULL;
+	struct relocate_entry *entry = NULL;
+	int i, block;
+
+	/*
+	 * In SLC, block 0 shouldn't be broken.
+	 * In some command, address is assigned to 0 if it doesn't need
+	 * to operate address. So just skip it.
+	 */
+	if (ofs <= 0)
+		return ofs;
+
+	if (verify_bbm_magic(mtd, &bbm) < 0)
+		return ofs;
+
+	table = bbm->table;
+	entry = bbm->entry;
+
+	block = ofs >> bbm->erase_shift;
+
+	if ((bbm->current_slot == -1) || (table->total <= 0)
+		|| (block >= (mtd->size >> bbm->erase_shift)))
+		return ofs;
+
+	ofs = ofs - (block << bbm->erase_shift);	/* save offset */
+
+	for (i = 0; i < table->total; i++) {
+		if (block == entry[i].from) {
+			block = entry[i].to;
+			break;
+		}
+	}
+	ofs += block << bbm->erase_shift;
+	return ofs;
+}
+
+static int pxa3xx_mark_rlentry(struct mtd_info *mtd, int block)
+{
+	struct pxa3xx_bbm *bbm = NULL;
+	int ret = 0;
+
+	ret = verify_bbm_magic(mtd, &bbm);
+	if (ret < 0) {
+		/* BBM don't support this type of flash */
+		return -EINVAL;
+	}
+
+	ret = update_rltable(mtd, block);
+	if (ret)
+		return ret;
+
+	return sync_rltable(mtd, &(bbm->current_slot));
+}
+
+/* If Marvell BBM is used, return 0. Otherwise, return negative value. */
+struct pxa3xx_bbm *pxa3xx_query_bbm(void)
+{
+	if (pxa3xx_bbm)
+		return pxa3xx_bbm;
+	return NULL;
+}
+EXPORT_SYMBOL(pxa3xx_query_bbm);
+
+static int __init pxa3xx_bbm_init(void)
+{
+	struct pxa3xx_bbm *bbm;
+
+	bbm = kzalloc(sizeof(struct pxa3xx_bbm), GFP_KERNEL);
+	if (!bbm)
+		return -ENOMEM;
+
+	bbm->magic		= PXA_BBM_MAGIC;
+	bbm->uninit		= pxa3xx_uninit_rltable;
+	bbm->search		= pxa3xx_search_rlentry;
+	bbm->block_markbad	= pxa3xx_mark_rlentry;
+	bbm->scan_bbt		= pxa3xx_scan_rltable;
+
+	pxa3xx_bbm = bbm;
+
+	return 0;
+}
+subsys_initcall(pxa3xx_bbm_init);
+
+static void pxa3xx_bbm_exit(void)
+{
+	if (pxa3xx_bbm) {
+		kfree(pxa3xx_bbm);
+		pxa3xx_bbm = NULL;
+	}
+}
+module_exit(pxa3xx_bbm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell PXA3xx Bad Block Management");