From 4d42942cff0785d8a153bafcd62b6870726cd5f1 Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Tue, 11 Jan 2011 14:33:35 +0300 Subject: mtd: make MTD_CONCAT support mandatory Move mtdconcat to be an integral part of the mtd core. It's a tiny bit of code, which bears 'say Y if you don't know what to do' note in the Kconfig. OTOH there are several ugly ifdefs depending on the MTD_CONCAT. So, making MTD_CONCAT support mandatory will allow us to clean up code a lot. Kconfig entry is changed to be a bool defaulting to Y, so all code pieces depending on it, will have MTD_CONCAT Kconfig symbol and CONFIG_MTD_CONCAT define. This will be removed in one of next patches. Signed-off-by: Dmitry Eremin-Solenikov Acked-by: Stefan Roese Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 7741470..c800247 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -34,7 +34,8 @@ config MTD_TESTS various checks and verifications when loaded. config MTD_CONCAT - tristate "MTD concatenating support" + bool + default y help Support for concatenating several MTD devices into a single (virtual) one. This allows you to have -for example- a JFFS(2) diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index d4e7f25..eff3dd5 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -4,11 +4,10 @@ # Core functionality. obj-$(CONFIG_MTD) += mtd.o -mtd-y := mtdcore.o mtdsuper.o +mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtd-$(CONFIG_MTD_PARTITIONS) += mtdpart.o mtd-$(CONFIG_MTD_OF_PARTS) += ofpart.o -obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o obj-$(CONFIG_MTD_AFS_PARTS) += afs.o -- cgit v0.10.2 From e1d6ee722285278b427007fc3fa4421a0c1b3b3c Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Tue, 11 Jan 2011 14:33:36 +0300 Subject: mtd: drop MTD_CONCAT dependencies from Kconfig As MTD_CONCAT is becoming a part of mtd core, it's no longer necessary to depend on it in Kconfig scripts. Drop such references. Signed-off-by: Dmitry Eremin-Solenikov Acked-by: Stefan Roese Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 5d37d31..803072a 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -114,7 +114,7 @@ config MTD_SUN_UFLASH config MTD_SC520CDP tristate "CFI Flash device mapped on AMD SC520 CDP" - depends on X86 && MTD_CFI && MTD_CONCAT + depends on X86 && MTD_CFI help The SC520 CDP board has two banks of CFI-compliant chips and one Dual-in-line JEDEC chip. This 'mapping' driver supports that @@ -262,7 +262,7 @@ config MTD_BCM963XX config MTD_DILNETPC tristate "CFI Flash device mapped on DIL/Net PC" - depends on X86 && MTD_CONCAT && MTD_PARTITIONS && MTD_CFI_INTELEXT && BROKEN + depends on X86 && MTD_PARTITIONS && MTD_CFI_INTELEXT && BROKEN help MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP". For details, see -- cgit v0.10.2 From a8adc3f01b9a757a0d290b2987af6b998697399f Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Tue, 11 Jan 2011 14:33:37 +0300 Subject: mtd: drop CONFIG_MTD_CONCAT ifdefs As MTD_CONCAT is becoming a part of mtd core, it's now meaningless to to check for it in ifdefs. Drop such references from MTD code. Signed-off-by: Dmitry Eremin-Solenikov Acked-by: Stefan Roese Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/maps/ceiva.c b/drivers/mtd/maps/ceiva.c index c09f4f5..e5f645b 100644 --- a/drivers/mtd/maps/ceiva.c +++ b/drivers/mtd/maps/ceiva.c @@ -194,16 +194,10 @@ static int __init clps_setup_mtd(struct clps_info *clps, int nr, struct mtd_info * We detected multiple devices. Concatenate * them together. */ -#ifdef CONFIG_MTD_CONCAT *rmtd = mtd_concat_create(subdev, found, "clps flash"); if (*rmtd == NULL) ret = -ENXIO; -#else - printk(KERN_ERR "clps flash: multiple devices " - "found but MTD concat support disabled.\n"); - ret = -ENXIO; -#endif } } diff --git a/drivers/mtd/maps/integrator-flash.c b/drivers/mtd/maps/integrator-flash.c index 2aac41b..e22ff5a 100644 --- a/drivers/mtd/maps/integrator-flash.c +++ b/drivers/mtd/maps/integrator-flash.c @@ -202,7 +202,6 @@ static int armflash_probe(struct platform_device *dev) if (info->nr_subdev == 1) info->mtd = info->subdev[0].mtd; else if (info->nr_subdev > 1) { -#ifdef CONFIG_MTD_CONCAT struct mtd_info *cdev[info->nr_subdev]; /* @@ -215,11 +214,6 @@ static int armflash_probe(struct platform_device *dev) dev_name(&dev->dev)); if (info->mtd == NULL) err = -ENXIO; -#else - printk(KERN_ERR "armflash: multiple devices found but " - "MTD concat support disabled.\n"); - err = -ENXIO; -#endif } if (err < 0) @@ -244,10 +238,8 @@ static int armflash_probe(struct platform_device *dev) cleanup: if (info->mtd) { del_mtd_partitions(info->mtd); -#ifdef CONFIG_MTD_CONCAT if (info->mtd != info->subdev[0].mtd) mtd_concat_destroy(info->mtd); -#endif } kfree(info->parts); subdev_err: @@ -272,10 +264,8 @@ static int armflash_remove(struct platform_device *dev) if (info) { if (info->mtd) { del_mtd_partitions(info->mtd); -#ifdef CONFIG_MTD_CONCAT if (info->mtd != info->subdev[0].mtd) mtd_concat_destroy(info->mtd); -#endif } kfree(info->parts); diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c index 4c18b98..7522df4 100644 --- a/drivers/mtd/maps/physmap.c +++ b/drivers/mtd/maps/physmap.c @@ -59,10 +59,8 @@ static int physmap_flash_remove(struct platform_device *dev) #else del_mtd_device(info->cmtd); #endif -#ifdef CONFIG_MTD_CONCAT if (info->cmtd != info->mtd[0]) mtd_concat_destroy(info->cmtd); -#endif } for (i = 0; i < MAX_RESOURCES; i++) { @@ -159,15 +157,9 @@ static int physmap_flash_probe(struct platform_device *dev) /* * We detected multiple devices. Concatenate them together. */ -#ifdef CONFIG_MTD_CONCAT info->cmtd = mtd_concat_create(info->mtd, devices_found, dev_name(&dev->dev)); if (info->cmtd == NULL) err = -ENXIO; -#else - printk(KERN_ERR "physmap-flash: multiple devices " - "found but MTD concat support disabled.\n"); - err = -ENXIO; -#endif } if (err) goto err_out; diff --git a/drivers/mtd/maps/physmap_of.c b/drivers/mtd/maps/physmap_of.c index 8506578..c85be83 100644 --- a/drivers/mtd/maps/physmap_of.c +++ b/drivers/mtd/maps/physmap_of.c @@ -104,12 +104,10 @@ static int of_flash_remove(struct platform_device *dev) return 0; dev_set_drvdata(&dev->dev, NULL); -#ifdef CONFIG_MTD_CONCAT if (info->cmtd != info->list[0].mtd) { del_mtd_device(info->cmtd); mtd_concat_destroy(info->cmtd); } -#endif if (info->cmtd) { if (OF_FLASH_PARTS(info)) { @@ -334,16 +332,10 @@ static int __devinit of_flash_probe(struct platform_device *dev, /* * We detected multiple devices. Concatenate them together. */ -#ifdef CONFIG_MTD_CONCAT info->cmtd = mtd_concat_create(mtd_list, info->list_size, dev_name(&dev->dev)); if (info->cmtd == NULL) err = -ENXIO; -#else - printk(KERN_ERR "physmap_of: multiple devices " - "found but MTD concat support disabled.\n"); - err = -ENXIO; -#endif } if (err) goto err_out; diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c index f3af87e..da875908 100644 --- a/drivers/mtd/maps/sa1100-flash.c +++ b/drivers/mtd/maps/sa1100-flash.c @@ -232,10 +232,8 @@ static void sa1100_destroy(struct sa_info *info, struct flash_platform_data *pla else del_mtd_partitions(info->mtd); #endif -#ifdef CONFIG_MTD_CONCAT if (info->mtd != info->subdev[0].mtd) mtd_concat_destroy(info->mtd); -#endif } kfree(info->parts); @@ -321,7 +319,6 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) info->mtd = info->subdev[0].mtd; ret = 0; } else if (info->num_subdev > 1) { -#ifdef CONFIG_MTD_CONCAT struct mtd_info *cdev[nr]; /* * We detected multiple devices. Concatenate them together. @@ -333,11 +330,6 @@ sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *plat) plat->name); if (info->mtd == NULL) ret = -ENXIO; -#else - printk(KERN_ERR "SA1100 flash: multiple devices " - "found but MTD concat support disabled.\n"); - ret = -ENXIO; -#endif } if (ret == 0) -- cgit v0.10.2 From 06f2ae3c91ea04b0d640e0dd9cf69a265f5ecda6 Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Tue, 11 Jan 2011 14:33:38 +0300 Subject: CRIS: stop checking for MTD_CONCAT As MTD_CONCAT support is becoming an integral part of MTD core, there is no need for it's special treatment. So stop checking for MTD_CONCAT availability. Acked by Jesper Nilsson for merging this via the MTD tree. Signed-off-by: Dmitry Eremin-Solenikov Acked-by: Stefan Roese Signed-off-by: Jesper Nilsson Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/arch/cris/Kconfig b/arch/cris/Kconfig index 0a7a4c1..8298503 100644 --- a/arch/cris/Kconfig +++ b/arch/cris/Kconfig @@ -275,7 +275,6 @@ config ETRAX_AXISFLASHMAP select MTD_CHAR select MTD_BLOCK select MTD_PARTITIONS - select MTD_CONCAT select MTD_COMPLEX_MAPPINGS help This option enables MTD mapping of flash devices. Needed to use diff --git a/arch/cris/arch-v10/drivers/axisflashmap.c b/arch/cris/arch-v10/drivers/axisflashmap.c index b207970..ed708e1 100644 --- a/arch/cris/arch-v10/drivers/axisflashmap.c +++ b/arch/cris/arch-v10/drivers/axisflashmap.c @@ -234,7 +234,6 @@ static struct mtd_info *flash_probe(void) } if (mtd_cse0 && mtd_cse1) { -#ifdef CONFIG_MTD_CONCAT struct mtd_info *mtds[] = { mtd_cse0, mtd_cse1 }; /* Since the concatenation layer adds a small overhead we @@ -246,11 +245,6 @@ static struct mtd_info *flash_probe(void) */ mtd_cse = mtd_concat_create(mtds, ARRAY_SIZE(mtds), "cse0+cse1"); -#else - printk(KERN_ERR "%s and %s: Cannot concatenate due to kernel " - "(mis)configuration!\n", map_cse0.name, map_cse1.name); - mtd_cse = NULL; -#endif if (!mtd_cse) { printk(KERN_ERR "%s and %s: Concatenation failed!\n", map_cse0.name, map_cse1.name); diff --git a/arch/cris/arch-v32/drivers/Kconfig b/arch/cris/arch-v32/drivers/Kconfig index a2dd740..1633b12 100644 --- a/arch/cris/arch-v32/drivers/Kconfig +++ b/arch/cris/arch-v32/drivers/Kconfig @@ -406,7 +406,6 @@ config ETRAX_AXISFLASHMAP select MTD_CHAR select MTD_BLOCK select MTD_PARTITIONS - select MTD_CONCAT select MTD_COMPLEX_MAPPINGS help This option enables MTD mapping of flash devices. Needed to use diff --git a/arch/cris/arch-v32/drivers/axisflashmap.c b/arch/cris/arch-v32/drivers/axisflashmap.c index 51e1e85..3d75125 100644 --- a/arch/cris/arch-v32/drivers/axisflashmap.c +++ b/arch/cris/arch-v32/drivers/axisflashmap.c @@ -275,7 +275,6 @@ static struct mtd_info *flash_probe(void) } if (count > 1) { -#ifdef CONFIG_MTD_CONCAT /* Since the concatenation layer adds a small overhead we * could try to figure out if the chips in cse0 and cse1 are * identical and reprobe the whole cse0+cse1 window. But since @@ -284,11 +283,6 @@ static struct mtd_info *flash_probe(void) * complicating the probing procedure. */ mtd_total = mtd_concat_create(mtds, count, "cse0+cse1"); -#else - printk(KERN_ERR "%s and %s: Cannot concatenate due to kernel " - "(mis)configuration!\n", map_cse0.name, map_cse1.name); - mtd_toal = NULL; -#endif if (!mtd_total) { printk(KERN_ERR "%s and %s: Concatenation failed!\n", map_cse0.name, map_cse1.name); -- cgit v0.10.2 From f53fdebcc3e1e5d15ffd0b1204d5944b4ceb8a07 Mon Sep 17 00:00:00 2001 From: Dmitry Eremin-Solenikov Date: Tue, 11 Jan 2011 14:33:39 +0300 Subject: mtd: drop MTD_CONCAT from Kconfig entirely As there are no more dependencies on MTD_CONCAT or CONFIG_MTD_CONCAT, drop this entry from Kconfig entirely. Signed-off-by: Dmitry Eremin-Solenikov Acked-by: Stefan Roese Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index c800247..23088b3 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -33,15 +33,6 @@ config MTD_TESTS should normally be compiled as kernel modules. The modules perform various checks and verifications when loaded. -config MTD_CONCAT - bool - default y - help - Support for concatenating several MTD devices into a single - (virtual) one. This allows you to have -for example- a JFFS(2) - file system spanning multiple physical flash chips. If unsure, - say 'Y'. - config MTD_PARTITIONS bool "MTD partitioning support" help -- cgit v0.10.2 From 83025c824fce44b6d007b9bbc506c4189fb89da1 Mon Sep 17 00:00:00 2001 From: Jamie Iles Date: Tue, 11 Jan 2011 12:43:49 +0000 Subject: mtd: mpc5121_nfc: don't treat NULL clk as an error clk_get() returns a struct clk cookie to the driver and some platforms may return NULL if they only support a single clock. clk_get() has only failed if it returns a ERR_PTR() encoded pointer. Signed-off-by: Jamie Iles Reviewed-by: Wolfram Sang Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/mpc5121_nfc.c b/drivers/mtd/nand/mpc5121_nfc.c index 469e649..ddaf001 100644 --- a/drivers/mtd/nand/mpc5121_nfc.c +++ b/drivers/mtd/nand/mpc5121_nfc.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -758,9 +759,9 @@ static int __devinit mpc5121_nfc_probe(struct platform_device *op, /* Enable NFC clock */ prv->clk = clk_get(dev, "nfc_clk"); - if (!prv->clk) { + if (IS_ERR(prv->clk)) { dev_err(dev, "Unable to acquire NFC clock!\n"); - retval = -ENODEV; + retval = PTR_ERR(prv->clk); goto error; } -- cgit v0.10.2 From ca91facf6babe548b5386993bb2820b7dc9b743d Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Tue, 11 Jan 2011 18:37:25 -0600 Subject: mtd: local symbols should be static The three backing_dev_info symbols are only used in this file and should be static. Signed-off-by: H Hartley Sweeten Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 527cebf..da69bc8 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -43,7 +43,7 @@ * backing device capabilities for non-mappable devices (such as NAND flash) * - permits private mappings, copies are taken of the data */ -struct backing_dev_info mtd_bdi_unmappable = { +static struct backing_dev_info mtd_bdi_unmappable = { .capabilities = BDI_CAP_MAP_COPY, }; @@ -52,7 +52,7 @@ struct backing_dev_info mtd_bdi_unmappable = { * - permits private mappings, copies are taken of the data * - permits non-writable shared mappings */ -struct backing_dev_info mtd_bdi_ro_mappable = { +static struct backing_dev_info mtd_bdi_ro_mappable = { .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP), }; @@ -62,7 +62,7 @@ struct backing_dev_info mtd_bdi_ro_mappable = { * - permits private mappings, copies are taken of the data * - permits non-writable shared mappings */ -struct backing_dev_info mtd_bdi_rw_mappable = { +static struct backing_dev_info mtd_bdi_rw_mappable = { .capabilities = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT | BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP), -- cgit v0.10.2 From 7f53f12f02b0b60af373a07fcac62b578381308e Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Tue, 11 Jan 2011 18:46:10 -0600 Subject: mtd: mtd_blkdevs: local symbols should be static The function blktrans_dev_release and blktrans_dev_put are only used locally in this file and should be static. Signed-off-by: H Hartley Sweeten Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index e0a2373..344ac10 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -40,7 +40,7 @@ static LIST_HEAD(blktrans_majors); static DEFINE_MUTEX(blktrans_ref_mutex); -void blktrans_dev_release(struct kref *kref) +static void blktrans_dev_release(struct kref *kref) { struct mtd_blktrans_dev *dev = container_of(kref, struct mtd_blktrans_dev, ref); @@ -67,7 +67,7 @@ unlock: return dev; } -void blktrans_dev_put(struct mtd_blktrans_dev *dev) +static void blktrans_dev_put(struct mtd_blktrans_dev *dev) { mutex_lock(&blktrans_ref_mutex); kref_put(&dev->ref, blktrans_dev_release); -- cgit v0.10.2 From cbc6c5e73dfa598b1fa5e67cef28096186e2a7cd Mon Sep 17 00:00:00 2001 From: Hong Xu Date: Tue, 18 Jan 2011 14:36:05 +0800 Subject: mtd: atmel_nand: Add DMA support to access Nandflash Some SAM9 chips have the ability to perform DMA between CPU and SMC controller. This patch adds DMA support for SAM9RL, SAM9G45, SSAM9G46,AM9M10, SAM9M11. Signed-off-by: Hong Xu Tested-by: Ryan Mallon Acked-by: Ryan Mallon Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index ccce0f0..6fae04b 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -48,6 +48,9 @@ #define no_ecc 0 #endif +static int use_dma = 1; +module_param(use_dma, int, 0); + static int on_flash_bbt = 0; module_param(on_flash_bbt, int, 0); @@ -89,11 +92,20 @@ struct atmel_nand_host { struct nand_chip nand_chip; struct mtd_info mtd; void __iomem *io_base; + dma_addr_t io_phys; struct atmel_nand_data *board; struct device *dev; void __iomem *ecc; + + struct completion comp; + struct dma_chan *dma_chan; }; +static int cpu_has_dma(void) +{ + return cpu_is_at91sam9rl() || cpu_is_at91sam9g45(); +} + /* * Enable NAND. */ @@ -150,7 +162,7 @@ static int atmel_nand_device_ready(struct mtd_info *mtd) /* * Minimal-overhead PIO for data access. */ -static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len) +static void atmel_read_buf8(struct mtd_info *mtd, u8 *buf, int len) { struct nand_chip *nand_chip = mtd->priv; @@ -164,7 +176,7 @@ static void atmel_read_buf16(struct mtd_info *mtd, u8 *buf, int len) __raw_readsw(nand_chip->IO_ADDR_R, buf, len / 2); } -static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +static void atmel_write_buf8(struct mtd_info *mtd, const u8 *buf, int len) { struct nand_chip *nand_chip = mtd->priv; @@ -178,6 +190,121 @@ static void atmel_write_buf16(struct mtd_info *mtd, const u8 *buf, int len) __raw_writesw(nand_chip->IO_ADDR_W, buf, len / 2); } +static void dma_complete_func(void *completion) +{ + complete(completion); +} + +static int atmel_nand_dma_op(struct mtd_info *mtd, void *buf, int len, + int is_read) +{ + struct dma_device *dma_dev; + enum dma_ctrl_flags flags; + dma_addr_t dma_src_addr, dma_dst_addr, phys_addr; + struct dma_async_tx_descriptor *tx = NULL; + dma_cookie_t cookie; + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + void *p = buf; + int err = -EIO; + enum dma_data_direction dir = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (buf >= high_memory) { + struct page *pg; + + if (((size_t)buf & PAGE_MASK) != + ((size_t)(buf + len - 1) & PAGE_MASK)) { + dev_warn(host->dev, "Buffer not fit in one page\n"); + goto err_buf; + } + + pg = vmalloc_to_page(buf); + if (pg == 0) { + dev_err(host->dev, "Failed to vmalloc_to_page\n"); + goto err_buf; + } + p = page_address(pg) + ((size_t)buf & ~PAGE_MASK); + } + + dma_dev = host->dma_chan->device; + + flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT | DMA_COMPL_SKIP_SRC_UNMAP | + DMA_COMPL_SKIP_DEST_UNMAP; + + phys_addr = dma_map_single(dma_dev->dev, p, len, dir); + if (dma_mapping_error(dma_dev->dev, phys_addr)) { + dev_err(host->dev, "Failed to dma_map_single\n"); + goto err_buf; + } + + if (is_read) { + dma_src_addr = host->io_phys; + dma_dst_addr = phys_addr; + } else { + dma_src_addr = phys_addr; + dma_dst_addr = host->io_phys; + } + + tx = dma_dev->device_prep_dma_memcpy(host->dma_chan, dma_dst_addr, + dma_src_addr, len, flags); + if (!tx) { + dev_err(host->dev, "Failed to prepare DMA memcpy\n"); + goto err_dma; + } + + init_completion(&host->comp); + tx->callback = dma_complete_func; + tx->callback_param = &host->comp; + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(host->dev, "Failed to do DMA tx_submit\n"); + goto err_dma; + } + + dma_async_issue_pending(host->dma_chan); + wait_for_completion(&host->comp); + + err = 0; + +err_dma: + dma_unmap_single(dma_dev->dev, phys_addr, len, dir); +err_buf: + if (err != 0) + dev_warn(host->dev, "Fall back to CPU I/O\n"); + return err; +} + +static void atmel_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + + if (use_dma && len >= mtd->oobsize) + if (atmel_nand_dma_op(mtd, buf, len, 1) == 0) + return; + + if (host->board->bus_width_16) + atmel_read_buf16(mtd, buf, len); + else + atmel_read_buf8(mtd, buf, len); +} + +static void atmel_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct atmel_nand_host *host = chip->priv; + + if (use_dma && len >= mtd->oobsize) + if (atmel_nand_dma_op(mtd, (void *)buf, len, 0) == 0) + return; + + if (host->board->bus_width_16) + atmel_write_buf16(mtd, buf, len); + else + atmel_write_buf8(mtd, buf, len); +} + /* * Calculate HW ECC * @@ -398,6 +525,8 @@ static int __init atmel_nand_probe(struct platform_device *pdev) return -ENOMEM; } + host->io_phys = (dma_addr_t)mem->start; + host->io_base = ioremap(mem->start, mem->end - mem->start + 1); if (host->io_base == NULL) { printk(KERN_ERR "atmel_nand: ioremap failed\n"); @@ -448,14 +577,11 @@ static int __init atmel_nand_probe(struct platform_device *pdev) nand_chip->chip_delay = 20; /* 20us command delay time */ - if (host->board->bus_width_16) { /* 16-bit bus width */ + if (host->board->bus_width_16) /* 16-bit bus width */ nand_chip->options |= NAND_BUSWIDTH_16; - nand_chip->read_buf = atmel_read_buf16; - nand_chip->write_buf = atmel_write_buf16; - } else { - nand_chip->read_buf = atmel_read_buf; - nand_chip->write_buf = atmel_write_buf; - } + + nand_chip->read_buf = atmel_read_buf; + nand_chip->write_buf = atmel_write_buf; platform_set_drvdata(pdev, host); atmel_nand_enable(host); @@ -473,6 +599,22 @@ static int __init atmel_nand_probe(struct platform_device *pdev) nand_chip->options |= NAND_USE_FLASH_BBT; } + if (cpu_has_dma() && use_dma) { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + host->dma_chan = dma_request_channel(mask, 0, NULL); + if (!host->dma_chan) { + dev_err(host->dev, "Failed to request DMA channel\n"); + use_dma = 0; + } + } + if (use_dma) + dev_info(host->dev, "Using DMA for NAND access.\n"); + else + dev_info(host->dev, "No DMA support for NAND access.\n"); + /* first scan to find the device and get the page size */ if (nand_scan_ident(mtd, 1, NULL)) { res = -ENXIO; @@ -555,6 +697,8 @@ err_scan_ident: err_no_card: atmel_nand_disable(host); platform_set_drvdata(pdev, NULL); + if (host->dma_chan) + dma_release_channel(host->dma_chan); if (host->ecc) iounmap(host->ecc); err_ecc_ioremap: @@ -578,6 +722,10 @@ static int __exit atmel_nand_remove(struct platform_device *pdev) if (host->ecc) iounmap(host->ecc); + + if (host->dma_chan) + dma_release_channel(host->dma_chan); + iounmap(host->io_base); kfree(host); -- cgit v0.10.2 From 771df61949cf2d6ae9ff07e209c80693cdbc9302 Mon Sep 17 00:00:00 2001 From: Holger Brunck Date: Mon, 24 Jan 2011 17:45:42 +0100 Subject: mtd: adapt writebufsize calculation for concat If different chips with different writebufsize are concatenated, the writebufsize from the concat device has to be taken from the device with the largest writebuffer. This writebufsize is used later on in the UBI layer for the min I/O size. Signed-off-by: Holger Brunck Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 5f5777b..5060e60 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -750,6 +750,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c struct mtd_concat *concat; uint32_t max_erasesize, curr_erasesize; int num_erase_region; + int max_writebufsize = 0; printk(KERN_NOTICE "Concatenating MTD devices:\n"); for (i = 0; i < num_devs; i++) @@ -776,7 +777,12 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c concat->mtd.size = subdev[0]->size; concat->mtd.erasesize = subdev[0]->erasesize; concat->mtd.writesize = subdev[0]->writesize; - concat->mtd.writebufsize = subdev[0]->writebufsize; + + for (i = 0; i < num_devs; i++) + if (max_writebufsize < subdev[i]->writebufsize) + max_writebufsize = subdev[i]->writebufsize; + concat->mtd.writebufsize = max_writebufsize; + concat->mtd.subpage_sft = subdev[0]->subpage_sft; concat->mtd.oobsize = subdev[0]->oobsize; concat->mtd.oobavail = subdev[0]->oobavail; -- cgit v0.10.2 From 37a23c20181aaddb8019f0a57bcda334412b89f8 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 25 Jan 2011 11:20:26 +0100 Subject: mtd: m25p80: add support for the EON EN25F32 chip Signed-off-by: Gabor Juhos Signed-off-by: David Woodhouse diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index e4eba6c..8d83a8b 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -655,7 +655,8 @@ static const struct spi_device_id m25p_ids[] = { { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) }, { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) }, - /* EON -- en25pxx */ + /* EON -- en25xxx */ + { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) }, { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) }, { "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) }, -- cgit v0.10.2 From 07be303d22ac7e067b8a7172a4043ebe32b74b33 Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Wed, 2 Feb 2011 08:53:55 +0200 Subject: mtd: mtdram: initialize writebufsize field Set the 'mtd->writebufsize' field to 64 to mimic modern CFI flashes. Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c index 26a6e80..1483e18 100644 --- a/drivers/mtd/devices/mtdram.c +++ b/drivers/mtd/devices/mtdram.c @@ -121,6 +121,7 @@ int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, mtd->flags = MTD_CAP_RAM; mtd->size = size; mtd->writesize = 1; + mtd->writebufsize = 64; /* Mimic CFI NOR flashes */ mtd->erasesize = MTDRAM_ERASE_SIZE; mtd->priv = mapped_address; -- cgit v0.10.2 From f17f12ce9dd6ec0a8e1f415ecdbaebfce0207464 Mon Sep 17 00:00:00 2001 From: Mathias Krause Date: Sun, 30 Jan 2011 10:31:48 +0100 Subject: mtd: phram: fix memory leak Commit 4f678a58 (mtd: fix memory leaks in phram_setup) missed two cases where the memory allocated for name would be leaked. This commit frees the memory when register_device() fails and on unregister_devices(). Signed-off-by: Mathias Krause Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c index 5239328..8d28fa0 100644 --- a/drivers/mtd/devices/phram.c +++ b/drivers/mtd/devices/phram.c @@ -117,6 +117,7 @@ static void unregister_devices(void) list_for_each_entry_safe(this, safe, &phram_list, list) { del_mtd_device(&this->mtd); iounmap(this->mtd.priv); + kfree(this->mtd.name); kfree(this); } } @@ -275,6 +276,8 @@ static int phram_setup(const char *val, struct kernel_param *kp) ret = register_device(name, start, len); if (!ret) pr_info("%s device: %#x at %#x\n", name, len, start); + else + kfree(name); return ret; } -- cgit v0.10.2 From 3ee7451a023dd197fcfc1ba46dcce6d8ec5e1b78 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Wed, 2 Feb 2011 18:13:36 +0100 Subject: mtd: ts5500_flash: avoid calling map_destroy on NULL map_destroy dereferences its argument. The call is furthermore only reachable when this argument is NULL. Thus the call is dropped. The semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @@ expression *x; @@ *if (x == NULL) { ... * map_destroy(x); ... return ...; } // Signed-off-by: Julia Lawall Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c index e2147bf..e02dfa9 100644 --- a/drivers/mtd/maps/ts5500_flash.c +++ b/drivers/mtd/maps/ts5500_flash.c @@ -94,7 +94,6 @@ static int __init init_ts5500_map(void) return 0; err1: - map_destroy(mymtd); iounmap(ts5500_map.virt); err2: return rc; -- cgit v0.10.2 From 7b7e905ec2ec511e06279181ca95a892db21e292 Mon Sep 17 00:00:00 2001 From: Roman Tereshonkov Date: Thu, 3 Feb 2011 13:45:56 +0200 Subject: mtd: tests: print correct values The ebcnt and pgcnt variable initialization is moved before printk which uses them. Signed-off-by: Roman Tereshonkov Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/tests/mtd_subpagetest.c b/drivers/mtd/tests/mtd_subpagetest.c index 11204e8..334eae5 100644 --- a/drivers/mtd/tests/mtd_subpagetest.c +++ b/drivers/mtd/tests/mtd_subpagetest.c @@ -394,6 +394,11 @@ static int __init mtd_subpagetest_init(void) } subpgsize = mtd->writesize >> mtd->subpage_sft; + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + pgcnt = mtd->erasesize / mtd->writesize; + printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, " "page size %u, subpage size %u, count of eraseblocks %u, " "pages per eraseblock %u, OOB size %u\n", @@ -413,11 +418,6 @@ static int __init mtd_subpagetest_init(void) goto out; } - tmp = mtd->size; - do_div(tmp, mtd->erasesize); - ebcnt = tmp; - pgcnt = mtd->erasesize / mtd->writesize; - err = scan_for_bad_eraseblocks(); if (err) goto out; -- cgit v0.10.2 From 7912a5e7f37512d8d105785046137435b70347ce Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 7 Feb 2011 23:48:25 +0300 Subject: mtd: nand: fix pointer NULL pointer dereferencing In 'verify_bbt_descr()', first check the "bd" pointer, then dereference it. Comments amended by Artem. Signed-off-by: Stanislav Fomichev Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 6ebd869..a1e8b300 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -1101,12 +1101,16 @@ static void mark_bbt_region(struct mtd_info *mtd, struct nand_bbt_descr *td) static void verify_bbt_descr(struct mtd_info *mtd, struct nand_bbt_descr *bd) { struct nand_chip *this = mtd->priv; - u32 pattern_len = bd->len; - u32 bits = bd->options & NAND_BBT_NRBITS_MSK; + u32 pattern_len; + u32 bits; u32 table_size; if (!bd) return; + + pattern_len = bd->len; + bits = bd->options & NAND_BBT_NRBITS_MSK; + BUG_ON((this->options & NAND_USE_FLASH_BBT_NO_OOB) && !(this->options & NAND_USE_FLASH_BBT)); BUG_ON(!bits); -- cgit v0.10.2 From b085058fe556328443f982d892c9657b3aff9d4a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 8 Feb 2011 12:02:38 +0200 Subject: mtd: OneNAND: return read error for 4KiB page read When reading using the 4KiB page read function, I/O errors could be ignored if more than 1 page was read at a time. Signed-off-by: Adrian Hunter Acked-by: Kyungmin Park Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index bac41ca..38e6d76 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -1132,6 +1132,8 @@ static int onenand_mlc_read_ops_nolock(struct mtd_info *mtd, loff_t from, onenand_update_bufferram(mtd, from, !ret); if (ret == -EBADMSG) ret = 0; + if (ret) + break; } this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); -- cgit v0.10.2 From e6da85685b2dec1e69e58366c22d1f883d6da575 Mon Sep 17 00:00:00 2001 From: Roman Tereshonkov Date: Tue, 8 Feb 2011 12:02:42 +0200 Subject: mtd: onenand_base: onenand_verify bugfix for writepage non-aligned address In onenand_verify function the address can be writepage non-aligned. When a page is read for comparing the right offset should be used for "this->verify_buf" to get the right matching with compared "buf" buffer. Signed-off-by: Roman Tereshonkov Signed-off-by: Adrian Hunter Acked-by: Kyungmin Park Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 38e6d76..4205b94 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -1648,11 +1648,10 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, int ret = 0; int thislen, column; + column = addr & (this->writesize - 1); + while (len != 0) { - thislen = min_t(int, this->writesize, len); - column = addr & (this->writesize - 1); - if (column + thislen > this->writesize) - thislen = this->writesize - column; + thislen = min_t(int, this->writesize - column, len); this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); @@ -1666,12 +1665,13 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, this->read_bufferram(mtd, ONENAND_DATARAM, this->verify_buf, 0, mtd->writesize); - if (memcmp(buf, this->verify_buf, thislen)) + if (memcmp(buf, this->verify_buf + column, thislen)) return -EBADMSG; len -= thislen; buf += thislen; addr += thislen; + column = 0; } return 0; -- cgit v0.10.2 From fc7fe7691c6bbd5f8f51a87e759770975c648410 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 8 Feb 2011 12:02:39 +0200 Subject: mtd: tests: add count parameter to mtd_speedtest By default mtd_speedtest uses all the eraseblocks of the MTD partition being tested. For large partitions a smaller number is sufficient and makes running the test quicker. For that reason, add a parameter 'count' to specify the maximum number of eraseblocks to use for testing. Signed-off-by: Adrian Hunter Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/tests/mtd_speedtest.c b/drivers/mtd/tests/mtd_speedtest.c index 161feeb..9bd986e 100644 --- a/drivers/mtd/tests/mtd_speedtest.c +++ b/drivers/mtd/tests/mtd_speedtest.c @@ -16,7 +16,7 @@ * * Test read and write speed of a MTD device. * - * Author: Adrian Hunter + * Author: Adrian Hunter */ #include @@ -33,6 +33,11 @@ static int dev; module_param(dev, int, S_IRUGO); MODULE_PARM_DESC(dev, "MTD device number to use"); +static int count; +module_param(count, int, S_IRUGO); +MODULE_PARM_DESC(count, "Maximum number of eraseblocks to use " + "(0 means use all)"); + static struct mtd_info *mtd; static unsigned char *iobuf; static unsigned char *bbt; @@ -326,7 +331,10 @@ static int __init mtd_speedtest_init(void) printk(KERN_INFO "\n"); printk(KERN_INFO "=================================================\n"); - printk(PRINT_PREF "MTD device: %d\n", dev); + if (count) + printk(PRINT_PREF "MTD device: %d count: %d\n", dev, count); + else + printk(PRINT_PREF "MTD device: %d\n", dev); mtd = get_mtd_device(NULL, dev); if (IS_ERR(mtd)) { @@ -353,6 +361,9 @@ static int __init mtd_speedtest_init(void) (unsigned long long)mtd->size, mtd->erasesize, pgsize, ebcnt, pgcnt, mtd->oobsize); + if (count > 0 && count < ebcnt) + ebcnt = count; + err = -ENOMEM; iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); if (!iobuf) { -- cgit v0.10.2 From 4085bcc682e1276ab4ac2da9866daadf944d7167 Mon Sep 17 00:00:00 2001 From: Roman Tereshonkov Date: Tue, 8 Feb 2011 12:02:40 +0200 Subject: mtd: tests: add multiblock erase test to the mtd_speedtest New multiblock erase speed test is added to mtd_speedtest. It consists of 2-, 4-, 8-, 16-, 32- and 64-blocks at once multiblock erase tests. Signed-off-by: Roman Tereshonkov Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/tests/mtd_speedtest.c b/drivers/mtd/tests/mtd_speedtest.c index 9bd986e..3ce6fce 100644 --- a/drivers/mtd/tests/mtd_speedtest.c +++ b/drivers/mtd/tests/mtd_speedtest.c @@ -94,6 +94,33 @@ static int erase_eraseblock(int ebnum) return 0; } +static int multiblock_erase(int ebnum, int blocks) +{ + int err; + struct erase_info ei; + loff_t addr = ebnum * mtd->erasesize; + + memset(&ei, 0, sizeof(struct erase_info)); + ei.mtd = mtd; + ei.addr = addr; + ei.len = mtd->erasesize * blocks; + + err = mtd->erase(mtd, &ei); + if (err) { + printk(PRINT_PREF "error %d while erasing EB %d, blocks %d\n", + err, ebnum, blocks); + return err; + } + + if (ei.state == MTD_ERASE_FAILED) { + printk(PRINT_PREF "some erase error occurred at EB %d," + "blocks %d\n", ebnum, blocks); + return -EIO; + } + + return 0; +} + static int erase_whole_device(void) { int err; @@ -292,7 +319,10 @@ static long calc_speed(void) ms = (finish.tv_sec - start.tv_sec) * 1000 + (finish.tv_usec - start.tv_usec) / 1000; k = goodebcnt * mtd->erasesize / 1024; - speed = (k * 1000) / ms; + if (ms) + speed = (k * 1000) / ms; + else + speed = 0; return speed; } @@ -325,7 +355,7 @@ out: static int __init mtd_speedtest_init(void) { - int err, i; + int err, i, blocks, j, k; long speed; uint64_t tmp; @@ -495,6 +525,31 @@ static int __init mtd_speedtest_init(void) speed = calc_speed(); printk(PRINT_PREF "erase speed is %ld KiB/s\n", speed); + /* Multi-block erase all eraseblocks */ + for (k = 1; k < 7; k++) { + blocks = 1 << k; + printk(PRINT_PREF "Testing %dx multi-block erase speed\n", + blocks); + start_timing(); + for (i = 0; i < ebcnt; ) { + for (j = 0; j < blocks && (i + j) < ebcnt; j++) + if (bbt[i + j]) + break; + if (j < 1) { + i++; + continue; + } + err = multiblock_erase(i, j); + if (err) + goto out; + cond_resched(); + i += j; + } + stop_timing(); + speed = calc_speed(); + printk(PRINT_PREF "%dx multi-block erase speed is %ld KiB/s\n", + blocks, speed); + } printk(PRINT_PREF "finished\n"); out: kfree(iobuf); -- cgit v0.10.2 From d8f90b2c9d521500bcd4a1ecdcdb283bfdd2186f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Wed, 9 Feb 2011 17:00:33 +0900 Subject: mtd: m25p80: add support for M25PX64 Signed-off-by: Yoshihiro Shimoda Signed-off-by: David Woodhouse diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 8d83a8b..3fb981d 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -729,6 +729,8 @@ static const struct spi_device_id m25p_ids[] = { { "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) }, { "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) }, + { "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) }, + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ { "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) }, { "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) }, -- cgit v0.10.2 From 13ce77f46c79a3839e4c2ff9722c9416c165f498 Mon Sep 17 00:00:00 2001 From: Anatolij Gustschin Date: Thu, 10 Feb 2011 16:01:46 +0100 Subject: mtd: cfi: fix writebufsize initialization When initializing mtd->writebufsize, we must take into account possible flash chip interleaving. Wrong writebufsize initialization caused UBIFS recovery issues resulting in unmountable UBIFS file system on NOR flash partitions. Signed-off-by: Anatolij Gustschin Acked-by: Guillaume LECERF Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 4aaa88f..092aef1 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -455,7 +455,7 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; mtd->writesize = 1; - mtd->writebufsize = 1 << cfi->cfiq->MaxBufWriteSize; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index f072fcf..7e9c4e9 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -440,7 +440,7 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; mtd->writesize = 1; - mtd->writebufsize = 1 << cfi->cfiq->MaxBufWriteSize; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; DEBUG(MTD_DEBUG_LEVEL3, "MTD %s(): write buffer size %d\n", __func__, mtd->writebufsize); diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c index c04b765..ed56ad3 100644 --- a/drivers/mtd/chips/cfi_cmdset_0020.c +++ b/drivers/mtd/chips/cfi_cmdset_0020.c @@ -238,7 +238,7 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map) mtd->resume = cfi_staa_resume; mtd->flags = MTD_CAP_NORFLASH & ~MTD_BIT_WRITEABLE; mtd->writesize = 8; /* FIXME: Should be 0 for STMicro flashes w/out ECC */ - mtd->writebufsize = 1 << cfi->cfiq->MaxBufWriteSize; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; map->fldrv = &cfi_staa_chipdrv; __module_get(THIS_MODULE); mtd->name = map->name; -- cgit v0.10.2 From c7519dbf6f4b4408229d279d799c938ffdd06f21 Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Mon, 14 Feb 2011 16:16:09 +0200 Subject: mtd_blkdevs: Add background processing support Add a new background method into mtd_blktrans_ops, add background support into mtd_blktrans_thread(), and add mtd_blktrans_cease_background(). If the mtd blktrans dev has the background support, the thread will call background function when the request queue becomes empty. The background operation may run as long as needs to until mtd_blktrans_cease_background() tells to stop. Signed-off-by: Jarkko Lavinen Tested-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index 344ac10..e0b5f64 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -119,11 +119,22 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr, } } +int mtd_blktrans_cease_background(struct mtd_blktrans_dev *dev) +{ + if (kthread_should_stop()) + return 1; + + return !elv_queue_empty(dev->rq); +} +EXPORT_SYMBOL_GPL(mtd_blktrans_cease_background); + static int mtd_blktrans_thread(void *arg) { struct mtd_blktrans_dev *dev = arg; + struct mtd_blktrans_ops *tr = dev->tr; struct request_queue *rq = dev->rq; struct request *req = NULL; + int background_done = 0; spin_lock_irq(rq->queue_lock); @@ -131,6 +142,19 @@ static int mtd_blktrans_thread(void *arg) int res; if (!req && !(req = blk_fetch_request(rq))) { + if (tr->background && !background_done) { + spin_unlock_irq(rq->queue_lock); + mutex_lock(&dev->lock); + tr->background(dev); + mutex_unlock(&dev->lock); + spin_lock_irq(rq->queue_lock); + /* + * Do background processing just once per idle + * period. + */ + background_done = 1; + continue; + } set_current_state(TASK_INTERRUPTIBLE); if (kthread_should_stop()) @@ -152,6 +176,8 @@ static int mtd_blktrans_thread(void *arg) if (!__blk_end_request_cur(req, res)) req = NULL; + + background_done = 0; } if (req) diff --git a/include/linux/mtd/blktrans.h b/include/linux/mtd/blktrans.h index 26529eb..66bec4b 100644 --- a/include/linux/mtd/blktrans.h +++ b/include/linux/mtd/blktrans.h @@ -62,6 +62,7 @@ struct mtd_blktrans_ops { unsigned long block, char *buffer); int (*discard)(struct mtd_blktrans_dev *dev, unsigned long block, unsigned nr_blocks); + void (*background)(struct mtd_blktrans_dev *dev); /* Block layer ioctls */ int (*getgeo)(struct mtd_blktrans_dev *dev, struct hd_geometry *geo); @@ -85,6 +86,7 @@ extern int register_mtd_blktrans(struct mtd_blktrans_ops *tr); extern int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr); extern int add_mtd_blktrans_dev(struct mtd_blktrans_dev *dev); extern int del_mtd_blktrans_dev(struct mtd_blktrans_dev *dev); +extern int mtd_blktrans_cease_background(struct mtd_blktrans_dev *dev); #endif /* __MTD_TRANS_H__ */ -- cgit v0.10.2 From 115ee88c15b55859a8b59c5dccb3882bbd47e542 Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Mon, 14 Feb 2011 16:16:10 +0200 Subject: mtd_blkdevs: Set the maximum discards size Set max_discard_sectors to UINT_MAX. Signed-off-by: Jarkko Lavinen Tested-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index e0b5f64..6505113 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -405,9 +405,10 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) new->rq->queuedata = new; blk_queue_logical_block_size(new->rq, tr->blksize); - if (tr->discard) - queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, - new->rq); + if (tr->discard) { + queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, new->rq); + new->rq->limits.max_discard_sectors = UINT_MAX; + } gd->queue = new->rq; -- cgit v0.10.2 From a321590246202f2598aca2f4ef63b992e8fc97ef Mon Sep 17 00:00:00 2001 From: Jarkko Lavinen Date: Mon, 14 Feb 2011 16:16:11 +0200 Subject: mtd: Add mtdswap block driver Add a driver for allowing an mtd device to be used as a block device for swapping. The block device is volatile, and the mapping of swapped pages is not stored on flash. Signed-off-by: Jarkko Lavinen Tested-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 23088b3..5fe01d5 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -325,6 +325,24 @@ config MTD_OOPS To use, add console=ttyMTDx to the kernel command line, where x is the MTD device number to use. +config MTD_SWAP + tristate "Swap on MTD device support" + depends on MTD && SWAP + select MTD_BLKDEVS + help + Provides volatile block device driver on top of mtd partition + suitable for swapping. The mapping of written blocks is not saved. + The driver provides wear leveling by storing erase counter into the + OOB. + +config MTD_SWAP_STRICT + bool "Strict erase error handling" + depends on MTD_SWAP + help + Enables strict tolerance on failed erasures, marking erase blocks bad + right after the first failed operation. With non-strict mode the + erase operation is retried. + source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index eff3dd5..d578095 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_RFD_FTL) += rfd_ftl.o obj-$(CONFIG_SSFDC) += ssfdc.o obj-$(CONFIG_SM_FTL) += sm_ftl.o obj-$(CONFIG_MTD_OOPS) += mtdoops.o +obj-$(CONFIG_MTD_SWAP) += mtdswap.o nftl-objs := nftlcore.o nftlmount.o inftl-objs := inftlcore.o inftlmount.o diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c new file mode 100644 index 0000000..57e6cc3 --- /dev/null +++ b/drivers/mtd/mtdswap.c @@ -0,0 +1,1593 @@ +/* + * Swap block device support for MTDs + * Turns an MTD device into a swap device with block wear leveling + * + * Copyright © 2007,2011 Nokia Corporation. All rights reserved. + * + * Authors: Jarkko Lavinen + * + * Based on Richard Purdie's earlier implementation in 2007. Background + * support and lock-less operation written by Adrian Hunter. + * + * 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. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MTDSWAP_PREFIX "mtdswap" + +/* + * The number of free eraseblocks when GC should stop + */ +#define CLEAN_BLOCK_THRESHOLD 20 + +/* + * Number of free eraseblocks below which GC can also collect low frag + * blocks. + */ +#define LOW_FRAG_GC_TRESHOLD 5 + +/* + * Wear level cost amortization. We want to do wear leveling on the background + * without disturbing gc too much. This is made by defining max GC frequency. + * Frequency value 6 means 1/6 of the GC passes will pick an erase block based + * on the biggest wear difference rather than the biggest dirtiness. + * + * The lower freq2 should be chosen so that it makes sure the maximum erase + * difference will decrease even if a malicious application is deliberately + * trying to make erase differences large. + */ +#define MAX_ERASE_DIFF 4000 +#define COLLECT_NONDIRTY_BASE MAX_ERASE_DIFF +#define COLLECT_NONDIRTY_FREQ1 6 +#define COLLECT_NONDIRTY_FREQ2 4 + +#define PAGE_UNDEF UINT_MAX +#define BLOCK_UNDEF UINT_MAX +#define BLOCK_ERROR (UINT_MAX - 1) +#define BLOCK_MAX (UINT_MAX - 2) + +#define EBLOCK_BAD (1 << 0) +#define EBLOCK_NOMAGIC (1 << 1) +#define EBLOCK_BITFLIP (1 << 2) +#define EBLOCK_FAILED (1 << 3) +#define EBLOCK_READERR (1 << 4) +#define EBLOCK_IDX_SHIFT 5 + +struct swap_eb { + struct rb_node rb; + struct rb_root *root; + + unsigned int flags; + unsigned int active_count; + unsigned int erase_count; + unsigned int pad; /* speeds up pointer decremtnt */ +}; + +#define MTDSWAP_ECNT_MIN(rbroot) (rb_entry(rb_first(rbroot), struct swap_eb, \ + rb)->erase_count) +#define MTDSWAP_ECNT_MAX(rbroot) (rb_entry(rb_last(rbroot), struct swap_eb, \ + rb)->erase_count) + +struct mtdswap_tree { + struct rb_root root; + unsigned int count; +}; + +enum { + MTDSWAP_CLEAN, + MTDSWAP_USED, + MTDSWAP_LOWFRAG, + MTDSWAP_HIFRAG, + MTDSWAP_DIRTY, + MTDSWAP_BITFLIP, + MTDSWAP_FAILING, + MTDSWAP_TREE_CNT, +}; + +struct mtdswap_dev { + struct mtd_blktrans_dev *mbd_dev; + struct mtd_info *mtd; + struct device *dev; + + unsigned int *page_data; + unsigned int *revmap; + + unsigned int eblks; + unsigned int spare_eblks; + unsigned int pages_per_eblk; + unsigned int max_erase_count; + struct swap_eb *eb_data; + + struct mtdswap_tree trees[MTDSWAP_TREE_CNT]; + + unsigned long long sect_read_count; + unsigned long long sect_write_count; + unsigned long long mtd_write_count; + unsigned long long mtd_read_count; + unsigned long long discard_count; + unsigned long long discard_page_count; + + unsigned int curr_write_pos; + struct swap_eb *curr_write; + + char *page_buf; + char *oob_buf; + + struct dentry *debugfs_root; +}; + +struct mtdswap_oobdata { + __le16 magic; + __le32 count; +} __attribute__((packed)); + +#define MTDSWAP_MAGIC_CLEAN 0x2095 +#define MTDSWAP_MAGIC_DIRTY (MTDSWAP_MAGIC_CLEAN + 1) +#define MTDSWAP_TYPE_CLEAN 0 +#define MTDSWAP_TYPE_DIRTY 1 +#define MTDSWAP_OOBSIZE sizeof(struct mtdswap_oobdata) + +#define MTDSWAP_ERASE_RETRIES 3 /* Before marking erase block bad */ +#define MTDSWAP_IO_RETRIES 3 + +#ifdef CONFIG_MTD_SWAP_STRICT +#define MTDSWAP_STRICT 1 +#else +#define MTDSWAP_STRICT 0 +#endif + +enum { + MTDSWAP_SCANNED_CLEAN, + MTDSWAP_SCANNED_DIRTY, + MTDSWAP_SCANNED_BITFLIP, + MTDSWAP_SCANNED_BAD, +}; + +/* + * In the worst case mtdswap_writesect() has allocated the last clean + * page from the current block and is then pre-empted by the GC + * thread. The thread can consume a full erase block when moving a + * block. + */ +#define MIN_SPARE_EBLOCKS 2 +#define MIN_ERASE_BLOCKS (MIN_SPARE_EBLOCKS + 1) + +#define TREE_ROOT(d, name) (&d->trees[MTDSWAP_ ## name].root) +#define TREE_EMPTY(d, name) (TREE_ROOT(d, name)->rb_node == NULL) +#define TREE_NONEMPTY(d, name) (!TREE_EMPTY(d, name)) +#define TREE_COUNT(d, name) (d->trees[MTDSWAP_ ## name].count) + +#define MTDSWAP_MBD_TO_MTDSWAP(dev) ((struct mtdswap_dev *)dev->priv) + +static char partitions[128] = ""; +module_param_string(partitions, partitions, sizeof(partitions), 0444); +MODULE_PARM_DESC(partitions, "MTD partition numbers to use as swap " + "partitions=\"1,3,5\""); + +static unsigned int spare_eblocks = 10; +module_param(spare_eblocks, uint, 0444); +MODULE_PARM_DESC(spare_eblocks, "Percentage of spare erase blocks for " + "garbage collection (default 10%)"); + +static bool header; /* false */ +module_param(header, bool, 0444); +MODULE_PARM_DESC(header, + "Include builtin swap header (default 0, without header)"); + +static int mtdswap_gc(struct mtdswap_dev *d, unsigned int background); + +static loff_t mtdswap_eb_offset(struct mtdswap_dev *d, struct swap_eb *eb) +{ + return (loff_t)(eb - d->eb_data) * d->mtd->erasesize; +} + +static void mtdswap_eb_detach(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int oldidx; + struct mtdswap_tree *tp; + + if (eb->root) { + tp = container_of(eb->root, struct mtdswap_tree, root); + oldidx = tp - &d->trees[0]; + + d->trees[oldidx].count--; + rb_erase(&eb->rb, eb->root); + } +} + +static void __mtdswap_rb_add(struct rb_root *root, struct swap_eb *eb) +{ + struct rb_node **p, *parent = NULL; + struct swap_eb *cur; + + p = &root->rb_node; + while (*p) { + parent = *p; + cur = rb_entry(parent, struct swap_eb, rb); + if (eb->erase_count > cur->erase_count) + p = &(*p)->rb_right; + else + p = &(*p)->rb_left; + } + + rb_link_node(&eb->rb, parent, p); + rb_insert_color(&eb->rb, root); +} + +static void mtdswap_rb_add(struct mtdswap_dev *d, struct swap_eb *eb, int idx) +{ + struct rb_root *root; + + if (eb->root == &d->trees[idx].root) + return; + + mtdswap_eb_detach(d, eb); + root = &d->trees[idx].root; + __mtdswap_rb_add(root, eb); + eb->root = root; + d->trees[idx].count++; +} + +static struct rb_node *mtdswap_rb_index(struct rb_root *root, unsigned int idx) +{ + struct rb_node *p; + unsigned int i; + + p = rb_first(root); + i = 0; + while (i < idx && p) { + p = rb_next(p); + i++; + } + + return p; +} + +static int mtdswap_handle_badblock(struct mtdswap_dev *d, struct swap_eb *eb) +{ + int ret; + loff_t offset; + + d->spare_eblks--; + eb->flags |= EBLOCK_BAD; + mtdswap_eb_detach(d, eb); + eb->root = NULL; + + /* badblocks not supported */ + if (!d->mtd->block_markbad) + return 1; + + offset = mtdswap_eb_offset(d, eb); + dev_warn(d->dev, "Marking bad block at %08llx\n", offset); + ret = d->mtd->block_markbad(d->mtd, offset); + + if (ret) { + dev_warn(d->dev, "Mark block bad failed for block at %08llx " + "error %d\n", offset, ret); + return ret; + } + + return 1; + +} + +static int mtdswap_handle_write_error(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int marked = eb->flags & EBLOCK_FAILED; + struct swap_eb *curr_write = d->curr_write; + + eb->flags |= EBLOCK_FAILED; + if (curr_write == eb) { + d->curr_write = NULL; + + if (!marked && d->curr_write_pos != 0) { + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + return 0; + } + } + + return mtdswap_handle_badblock(d, eb); +} + +static int mtdswap_read_oob(struct mtdswap_dev *d, loff_t from, + struct mtd_oob_ops *ops) +{ + int ret = d->mtd->read_oob(d->mtd, from, ops); + + if (ret == -EUCLEAN) + return ret; + + if (ret) { + dev_warn(d->dev, "Read OOB failed %d for block at %08llx\n", + ret, from); + return ret; + } + + if (ops->oobretlen < ops->ooblen) { + dev_warn(d->dev, "Read OOB return short read (%zd bytes not " + "%d) for block at %08llx\n", + ops->oobretlen, ops->ooblen, from); + return -EIO; + } + + return 0; +} + +static int mtdswap_read_markers(struct mtdswap_dev *d, struct swap_eb *eb) +{ + struct mtdswap_oobdata *data, *data2; + int ret; + loff_t offset; + struct mtd_oob_ops ops; + + offset = mtdswap_eb_offset(d, eb); + + /* Check first if the block is bad. */ + if (d->mtd->block_isbad && d->mtd->block_isbad(d->mtd, offset)) + return MTDSWAP_SCANNED_BAD; + + ops.ooblen = 2 * d->mtd->ecclayout->oobavail; + ops.oobbuf = d->oob_buf; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OOB_AUTO; + + ret = mtdswap_read_oob(d, offset, &ops); + + if (ret && ret != -EUCLEAN) + return ret; + + data = (struct mtdswap_oobdata *)d->oob_buf; + data2 = (struct mtdswap_oobdata *) + (d->oob_buf + d->mtd->ecclayout->oobavail); + + if (le16_to_cpu(data->magic) == MTDSWAP_MAGIC_CLEAN) { + eb->erase_count = le32_to_cpu(data->count); + if (ret == -EUCLEAN) + ret = MTDSWAP_SCANNED_BITFLIP; + else { + if (le16_to_cpu(data2->magic) == MTDSWAP_MAGIC_DIRTY) + ret = MTDSWAP_SCANNED_DIRTY; + else + ret = MTDSWAP_SCANNED_CLEAN; + } + } else { + eb->flags |= EBLOCK_NOMAGIC; + ret = MTDSWAP_SCANNED_DIRTY; + } + + return ret; +} + +static int mtdswap_write_marker(struct mtdswap_dev *d, struct swap_eb *eb, + u16 marker) +{ + struct mtdswap_oobdata n; + int ret; + loff_t offset; + struct mtd_oob_ops ops; + + ops.ooboffs = 0; + ops.oobbuf = (uint8_t *)&n; + ops.mode = MTD_OOB_AUTO; + ops.datbuf = NULL; + + if (marker == MTDSWAP_TYPE_CLEAN) { + n.magic = cpu_to_le16(MTDSWAP_MAGIC_CLEAN); + n.count = cpu_to_le32(eb->erase_count); + ops.ooblen = MTDSWAP_OOBSIZE; + offset = mtdswap_eb_offset(d, eb); + } else { + n.magic = cpu_to_le16(MTDSWAP_MAGIC_DIRTY); + ops.ooblen = sizeof(n.magic); + offset = mtdswap_eb_offset(d, eb) + d->mtd->writesize; + } + + ret = d->mtd->write_oob(d->mtd, offset , &ops); + + if (ret) { + dev_warn(d->dev, "Write OOB failed for block at %08llx " + "error %d\n", offset, ret); + if (ret == -EIO || ret == -EBADMSG) + mtdswap_handle_write_error(d, eb); + return ret; + } + + if (ops.oobretlen != ops.ooblen) { + dev_warn(d->dev, "Short OOB write for block at %08llx: " + "%zd not %d\n", + offset, ops.oobretlen, ops.ooblen); + return ret; + } + + return 0; +} + +/* + * Are there any erase blocks without MAGIC_CLEAN header, presumably + * because power was cut off after erase but before header write? We + * need to guestimate the erase count. + */ +static void mtdswap_check_counts(struct mtdswap_dev *d) +{ + struct rb_root hist_root = RB_ROOT; + struct rb_node *medrb; + struct swap_eb *eb; + unsigned int i, cnt, median; + + cnt = 0; + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_BAD | EBLOCK_READERR)) + continue; + + __mtdswap_rb_add(&hist_root, eb); + cnt++; + } + + if (cnt == 0) + return; + + medrb = mtdswap_rb_index(&hist_root, cnt / 2); + median = rb_entry(medrb, struct swap_eb, rb)->erase_count; + + d->max_erase_count = MTDSWAP_ECNT_MAX(&hist_root); + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_READERR)) + eb->erase_count = median; + + if (eb->flags & (EBLOCK_NOMAGIC | EBLOCK_BAD | EBLOCK_READERR)) + continue; + + rb_erase(&eb->rb, &hist_root); + } +} + +static void mtdswap_scan_eblks(struct mtdswap_dev *d) +{ + int status; + unsigned int i, idx; + struct swap_eb *eb; + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + status = mtdswap_read_markers(d, eb); + if (status < 0) + eb->flags |= EBLOCK_READERR; + else if (status == MTDSWAP_SCANNED_BAD) { + eb->flags |= EBLOCK_BAD; + continue; + } + + switch (status) { + case MTDSWAP_SCANNED_CLEAN: + idx = MTDSWAP_CLEAN; + break; + case MTDSWAP_SCANNED_DIRTY: + case MTDSWAP_SCANNED_BITFLIP: + idx = MTDSWAP_DIRTY; + break; + default: + idx = MTDSWAP_FAILING; + } + + eb->flags |= (idx << EBLOCK_IDX_SHIFT); + } + + mtdswap_check_counts(d); + + for (i = 0; i < d->eblks; i++) { + eb = d->eb_data + i; + + if (eb->flags & EBLOCK_BAD) + continue; + + idx = eb->flags >> EBLOCK_IDX_SHIFT; + mtdswap_rb_add(d, eb, idx); + } +} + +/* + * Place eblk into a tree corresponding to its number of active blocks + * it contains. + */ +static void mtdswap_store_eb(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int weight = eb->active_count; + unsigned int maxweight = d->pages_per_eblk; + + if (eb == d->curr_write) + return; + + if (eb->flags & EBLOCK_BITFLIP) + mtdswap_rb_add(d, eb, MTDSWAP_BITFLIP); + else if (eb->flags & (EBLOCK_READERR | EBLOCK_FAILED)) + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + if (weight == maxweight) + mtdswap_rb_add(d, eb, MTDSWAP_USED); + else if (weight == 0) + mtdswap_rb_add(d, eb, MTDSWAP_DIRTY); + else if (weight > (maxweight/2)) + mtdswap_rb_add(d, eb, MTDSWAP_LOWFRAG); + else + mtdswap_rb_add(d, eb, MTDSWAP_HIFRAG); +} + + +static void mtdswap_erase_callback(struct erase_info *done) +{ + wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; + wake_up(wait_q); +} + +static int mtdswap_erase_block(struct mtdswap_dev *d, struct swap_eb *eb) +{ + struct mtd_info *mtd = d->mtd; + struct erase_info erase; + wait_queue_head_t wq; + unsigned int retries = 0; + int ret; + + eb->erase_count++; + if (eb->erase_count > d->max_erase_count) + d->max_erase_count = eb->erase_count; + +retry: + init_waitqueue_head(&wq); + memset(&erase, 0, sizeof(struct erase_info)); + + erase.mtd = mtd; + erase.callback = mtdswap_erase_callback; + erase.addr = mtdswap_eb_offset(d, eb); + erase.len = mtd->erasesize; + erase.priv = (u_long)&wq; + + ret = mtd->erase(mtd, &erase); + if (ret) { + if (retries++ < MTDSWAP_ERASE_RETRIES && !MTDSWAP_STRICT) { + dev_warn(d->dev, + "erase of erase block %#llx on %s failed", + erase.addr, mtd->name); + yield(); + goto retry; + } + + dev_err(d->dev, "Cannot erase erase block %#llx on %s\n", + erase.addr, mtd->name); + + mtdswap_handle_badblock(d, eb); + return -EIO; + } + + ret = wait_event_interruptible(wq, erase.state == MTD_ERASE_DONE || + erase.state == MTD_ERASE_FAILED); + if (ret) { + dev_err(d->dev, "Interrupted erase block %#llx erassure on %s", + erase.addr, mtd->name); + return -EINTR; + } + + if (erase.state == MTD_ERASE_FAILED) { + if (retries++ < MTDSWAP_ERASE_RETRIES) { + dev_warn(d->dev, + "erase of erase block %#llx on %s failed", + erase.addr, mtd->name); + yield(); + goto retry; + } + + mtdswap_handle_badblock(d, eb); + return -EIO; + } + + return 0; +} + +static int mtdswap_map_free_block(struct mtdswap_dev *d, unsigned int page, + unsigned int *block) +{ + int ret; + struct swap_eb *old_eb = d->curr_write; + struct rb_root *clean_root; + struct swap_eb *eb; + + if (old_eb == NULL || d->curr_write_pos >= d->pages_per_eblk) { + do { + if (TREE_EMPTY(d, CLEAN)) + return -ENOSPC; + + clean_root = TREE_ROOT(d, CLEAN); + eb = rb_entry(rb_first(clean_root), struct swap_eb, rb); + rb_erase(&eb->rb, clean_root); + eb->root = NULL; + TREE_COUNT(d, CLEAN)--; + + ret = mtdswap_write_marker(d, eb, MTDSWAP_TYPE_DIRTY); + } while (ret == -EIO || ret == -EBADMSG); + + if (ret) + return ret; + + d->curr_write_pos = 0; + d->curr_write = eb; + if (old_eb) + mtdswap_store_eb(d, old_eb); + } + + *block = (d->curr_write - d->eb_data) * d->pages_per_eblk + + d->curr_write_pos; + + d->curr_write->active_count++; + d->revmap[*block] = page; + d->curr_write_pos++; + + return 0; +} + +static unsigned int mtdswap_free_page_cnt(struct mtdswap_dev *d) +{ + return TREE_COUNT(d, CLEAN) * d->pages_per_eblk + + d->pages_per_eblk - d->curr_write_pos; +} + +static unsigned int mtdswap_enough_free_pages(struct mtdswap_dev *d) +{ + return mtdswap_free_page_cnt(d) > d->pages_per_eblk; +} + +static int mtdswap_write_block(struct mtdswap_dev *d, char *buf, + unsigned int page, unsigned int *bp, int gc_context) +{ + struct mtd_info *mtd = d->mtd; + struct swap_eb *eb; + size_t retlen; + loff_t writepos; + int ret; + +retry: + if (!gc_context) + while (!mtdswap_enough_free_pages(d)) + if (mtdswap_gc(d, 0) > 0) + return -ENOSPC; + + ret = mtdswap_map_free_block(d, page, bp); + eb = d->eb_data + (*bp / d->pages_per_eblk); + + if (ret == -EIO || ret == -EBADMSG) { + d->curr_write = NULL; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + goto retry; + } + + if (ret < 0) + return ret; + + writepos = (loff_t)*bp << PAGE_SHIFT; + ret = mtd->write(mtd, writepos, PAGE_SIZE, &retlen, buf); + if (ret == -EIO || ret == -EBADMSG) { + d->curr_write_pos--; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + mtdswap_handle_write_error(d, eb); + goto retry; + } + + if (ret < 0) { + dev_err(d->dev, "Write to MTD device failed: %d (%d written)", + ret, retlen); + goto err; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short write to MTD device: %d written", + retlen); + ret = -EIO; + goto err; + } + + return ret; + +err: + d->curr_write_pos--; + eb->active_count--; + d->revmap[*bp] = PAGE_UNDEF; + + return ret; +} + +static int mtdswap_move_block(struct mtdswap_dev *d, unsigned int oldblock, + unsigned int *newblock) +{ + struct mtd_info *mtd = d->mtd; + struct swap_eb *eb, *oldeb; + int ret; + size_t retlen; + unsigned int page, retries; + loff_t readpos; + + page = d->revmap[oldblock]; + readpos = (loff_t) oldblock << PAGE_SHIFT; + retries = 0; + +retry: + ret = mtd->read(mtd, readpos, PAGE_SIZE, &retlen, d->page_buf); + + if (ret < 0 && ret != -EUCLEAN) { + oldeb = d->eb_data + oldblock / d->pages_per_eblk; + oldeb->flags |= EBLOCK_READERR; + + dev_err(d->dev, "Read Error: %d (block %u)\n", ret, + oldblock); + retries++; + if (retries < MTDSWAP_IO_RETRIES) + goto retry; + + goto read_error; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short read: %d (block %u)\n", retlen, + oldblock); + ret = -EIO; + goto read_error; + } + + ret = mtdswap_write_block(d, d->page_buf, page, newblock, 1); + if (ret < 0) { + d->page_data[page] = BLOCK_ERROR; + dev_err(d->dev, "Write error: %d\n", ret); + return ret; + } + + eb = d->eb_data + *newblock / d->pages_per_eblk; + d->page_data[page] = *newblock; + d->revmap[oldblock] = PAGE_UNDEF; + eb = d->eb_data + oldblock / d->pages_per_eblk; + eb->active_count--; + + return 0; + +read_error: + d->page_data[page] = BLOCK_ERROR; + d->revmap[oldblock] = PAGE_UNDEF; + return ret; +} + +static int mtdswap_gc_eblock(struct mtdswap_dev *d, struct swap_eb *eb) +{ + unsigned int i, block, eblk_base, newblock; + int ret, errcode; + + errcode = 0; + eblk_base = (eb - d->eb_data) * d->pages_per_eblk; + + for (i = 0; i < d->pages_per_eblk; i++) { + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return -ENOSPC; + + block = eblk_base + i; + if (d->revmap[block] == PAGE_UNDEF) + continue; + + ret = mtdswap_move_block(d, block, &newblock); + if (ret < 0 && !errcode) + errcode = ret; + } + + return errcode; +} + +static int __mtdswap_choose_gc_tree(struct mtdswap_dev *d) +{ + int idx, stopat; + + if (TREE_COUNT(d, CLEAN) < LOW_FRAG_GC_TRESHOLD) + stopat = MTDSWAP_LOWFRAG; + else + stopat = MTDSWAP_HIFRAG; + + for (idx = MTDSWAP_BITFLIP; idx >= stopat; idx--) + if (d->trees[idx].root.rb_node != NULL) + return idx; + + return -1; +} + +static int mtdswap_wlfreq(unsigned int maxdiff) +{ + unsigned int h, x, y, dist, base; + + /* + * Calculate linear ramp down from f1 to f2 when maxdiff goes from + * MAX_ERASE_DIFF to MAX_ERASE_DIFF + COLLECT_NONDIRTY_BASE. Similar + * to triangle with height f1 - f1 and width COLLECT_NONDIRTY_BASE. + */ + + dist = maxdiff - MAX_ERASE_DIFF; + if (dist > COLLECT_NONDIRTY_BASE) + dist = COLLECT_NONDIRTY_BASE; + + /* + * Modelling the slop as right angular triangle with base + * COLLECT_NONDIRTY_BASE and height freq1 - freq2. The ratio y/x is + * equal to the ratio h/base. + */ + h = COLLECT_NONDIRTY_FREQ1 - COLLECT_NONDIRTY_FREQ2; + base = COLLECT_NONDIRTY_BASE; + + x = dist - base; + y = (x * h + base / 2) / base; + + return COLLECT_NONDIRTY_FREQ2 + y; +} + +static int mtdswap_choose_wl_tree(struct mtdswap_dev *d) +{ + static unsigned int pick_cnt; + unsigned int i, idx, wear, max; + struct rb_root *root; + + max = 0; + for (i = 0; i <= MTDSWAP_DIRTY; i++) { + root = &d->trees[i].root; + if (root->rb_node == NULL) + continue; + + wear = d->max_erase_count - MTDSWAP_ECNT_MIN(root); + if (wear > max) { + max = wear; + idx = i; + } + } + + if (max > MAX_ERASE_DIFF && pick_cnt >= mtdswap_wlfreq(max) - 1) { + pick_cnt = 0; + return idx; + } + + pick_cnt++; + return -1; +} + +static int mtdswap_choose_gc_tree(struct mtdswap_dev *d, + unsigned int background) +{ + int idx; + + if (TREE_NONEMPTY(d, FAILING) && + (background || (TREE_EMPTY(d, CLEAN) && TREE_EMPTY(d, DIRTY)))) + return MTDSWAP_FAILING; + + idx = mtdswap_choose_wl_tree(d); + if (idx >= MTDSWAP_CLEAN) + return idx; + + return __mtdswap_choose_gc_tree(d); +} + +static struct swap_eb *mtdswap_pick_gc_eblk(struct mtdswap_dev *d, + unsigned int background) +{ + struct rb_root *rp = NULL; + struct swap_eb *eb = NULL; + int idx; + + if (background && TREE_COUNT(d, CLEAN) > CLEAN_BLOCK_THRESHOLD && + TREE_EMPTY(d, DIRTY) && TREE_EMPTY(d, FAILING)) + return NULL; + + idx = mtdswap_choose_gc_tree(d, background); + if (idx < 0) + return NULL; + + rp = &d->trees[idx].root; + eb = rb_entry(rb_first(rp), struct swap_eb, rb); + + rb_erase(&eb->rb, rp); + eb->root = NULL; + d->trees[idx].count--; + return eb; +} + +static unsigned int mtdswap_test_patt(unsigned int i) +{ + return i % 2 ? 0x55555555 : 0xAAAAAAAA; +} + +static unsigned int mtdswap_eblk_passes(struct mtdswap_dev *d, + struct swap_eb *eb) +{ + struct mtd_info *mtd = d->mtd; + unsigned int test, i, j, patt, mtd_pages; + loff_t base, pos; + unsigned int *p1 = (unsigned int *)d->page_buf; + unsigned char *p2 = (unsigned char *)d->oob_buf; + struct mtd_oob_ops ops; + int ret; + + ops.mode = MTD_OOB_AUTO; + ops.len = mtd->writesize; + ops.ooblen = mtd->ecclayout->oobavail; + ops.ooboffs = 0; + ops.datbuf = d->page_buf; + ops.oobbuf = d->oob_buf; + base = mtdswap_eb_offset(d, eb); + mtd_pages = d->pages_per_eblk * PAGE_SIZE / mtd->writesize; + + for (test = 0; test < 2; test++) { + pos = base; + for (i = 0; i < mtd_pages; i++) { + patt = mtdswap_test_patt(test + i); + memset(d->page_buf, patt, mtd->writesize); + memset(d->oob_buf, patt, mtd->ecclayout->oobavail); + ret = mtd->write_oob(mtd, pos, &ops); + if (ret) + goto error; + + pos += mtd->writesize; + } + + pos = base; + for (i = 0; i < mtd_pages; i++) { + ret = mtd->read_oob(mtd, pos, &ops); + if (ret) + goto error; + + patt = mtdswap_test_patt(test + i); + for (j = 0; j < mtd->writesize/sizeof(int); j++) + if (p1[j] != patt) + goto error; + + for (j = 0; j < mtd->ecclayout->oobavail; j++) + if (p2[j] != (unsigned char)patt) + goto error; + + pos += mtd->writesize; + } + + ret = mtdswap_erase_block(d, eb); + if (ret) + goto error; + } + + eb->flags &= ~EBLOCK_READERR; + return 1; + +error: + mtdswap_handle_badblock(d, eb); + return 0; +} + +static int mtdswap_gc(struct mtdswap_dev *d, unsigned int background) +{ + struct swap_eb *eb; + int ret; + + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return 1; + + eb = mtdswap_pick_gc_eblk(d, background); + if (!eb) + return 1; + + ret = mtdswap_gc_eblock(d, eb); + if (ret == -ENOSPC) + return 1; + + if (eb->flags & EBLOCK_FAILED) { + mtdswap_handle_badblock(d, eb); + return 0; + } + + eb->flags &= ~EBLOCK_BITFLIP; + ret = mtdswap_erase_block(d, eb); + if ((eb->flags & EBLOCK_READERR) && + (ret || !mtdswap_eblk_passes(d, eb))) + return 0; + + if (ret == 0) + ret = mtdswap_write_marker(d, eb, MTDSWAP_TYPE_CLEAN); + + if (ret == 0) + mtdswap_rb_add(d, eb, MTDSWAP_CLEAN); + else if (ret != -EIO && ret != -EBADMSG) + mtdswap_rb_add(d, eb, MTDSWAP_DIRTY); + + return 0; +} + +static void mtdswap_background(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + int ret; + + while (1) { + ret = mtdswap_gc(d, 1); + if (ret || mtd_blktrans_cease_background(dev)) + return; + } +} + +static void mtdswap_cleanup(struct mtdswap_dev *d) +{ + vfree(d->eb_data); + vfree(d->revmap); + vfree(d->page_data); + kfree(d->oob_buf); + kfree(d->page_buf); +} + +static int mtdswap_flush(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + + if (d->mtd->sync) + d->mtd->sync(d->mtd); + return 0; +} + +static unsigned int mtdswap_badblocks(struct mtd_info *mtd, uint64_t size) +{ + loff_t offset; + unsigned int badcnt; + + badcnt = 0; + + if (mtd->block_isbad) + for (offset = 0; offset < size; offset += mtd->erasesize) + if (mtd->block_isbad(mtd, offset)) + badcnt++; + + return badcnt; +} + +static int mtdswap_writesect(struct mtd_blktrans_dev *dev, + unsigned long page, char *buf) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + unsigned int newblock, mapped; + struct swap_eb *eb; + int ret; + + d->sect_write_count++; + + if (d->spare_eblks < MIN_SPARE_EBLOCKS) + return -ENOSPC; + + if (header) { + /* Ignore writes to the header page */ + if (unlikely(page == 0)) + return 0; + + page--; + } + + mapped = d->page_data[page]; + if (mapped <= BLOCK_MAX) { + eb = d->eb_data + (mapped / d->pages_per_eblk); + eb->active_count--; + mtdswap_store_eb(d, eb); + d->page_data[page] = BLOCK_UNDEF; + d->revmap[mapped] = PAGE_UNDEF; + } + + ret = mtdswap_write_block(d, buf, page, &newblock, 0); + d->mtd_write_count++; + + if (ret < 0) + return ret; + + eb = d->eb_data + (newblock / d->pages_per_eblk); + d->page_data[page] = newblock; + + return 0; +} + +/* Provide a dummy swap header for the kernel */ +static int mtdswap_auto_header(struct mtdswap_dev *d, char *buf) +{ + union swap_header *hd = (union swap_header *)(buf); + + memset(buf, 0, PAGE_SIZE - 10); + + hd->info.version = 1; + hd->info.last_page = d->mbd_dev->size - 1; + hd->info.nr_badpages = 0; + + memcpy(buf + PAGE_SIZE - 10, "SWAPSPACE2", 10); + + return 0; +} + +static int mtdswap_readsect(struct mtd_blktrans_dev *dev, + unsigned long page, char *buf) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + struct mtd_info *mtd = d->mtd; + unsigned int realblock, retries; + loff_t readpos; + struct swap_eb *eb; + size_t retlen; + int ret; + + d->sect_read_count++; + + if (header) { + if (unlikely(page == 0)) + return mtdswap_auto_header(d, buf); + + page--; + } + + realblock = d->page_data[page]; + if (realblock > BLOCK_MAX) { + memset(buf, 0x0, PAGE_SIZE); + if (realblock == BLOCK_UNDEF) + return 0; + else + return -EIO; + } + + eb = d->eb_data + (realblock / d->pages_per_eblk); + BUG_ON(d->revmap[realblock] == PAGE_UNDEF); + + readpos = (loff_t)realblock << PAGE_SHIFT; + retries = 0; + +retry: + ret = mtd->read(mtd, readpos, PAGE_SIZE, &retlen, buf); + + d->mtd_read_count++; + if (ret == -EUCLEAN) { + eb->flags |= EBLOCK_BITFLIP; + mtdswap_rb_add(d, eb, MTDSWAP_BITFLIP); + ret = 0; + } + + if (ret < 0) { + dev_err(d->dev, "Read error %d\n", ret); + eb->flags |= EBLOCK_READERR; + mtdswap_rb_add(d, eb, MTDSWAP_FAILING); + retries++; + if (retries < MTDSWAP_IO_RETRIES) + goto retry; + + return ret; + } + + if (retlen != PAGE_SIZE) { + dev_err(d->dev, "Short read %d\n", retlen); + return -EIO; + } + + return 0; +} + +static int mtdswap_discard(struct mtd_blktrans_dev *dev, unsigned long first, + unsigned nr_pages) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + unsigned long page; + struct swap_eb *eb; + unsigned int mapped; + + d->discard_count++; + + for (page = first; page < first + nr_pages; page++) { + mapped = d->page_data[page]; + if (mapped <= BLOCK_MAX) { + eb = d->eb_data + (mapped / d->pages_per_eblk); + eb->active_count--; + mtdswap_store_eb(d, eb); + d->page_data[page] = BLOCK_UNDEF; + d->revmap[mapped] = PAGE_UNDEF; + d->discard_page_count++; + } else if (mapped == BLOCK_ERROR) { + d->page_data[page] = BLOCK_UNDEF; + d->discard_page_count++; + } + } + + return 0; +} + +static int mtdswap_show(struct seq_file *s, void *data) +{ + struct mtdswap_dev *d = (struct mtdswap_dev *) s->private; + unsigned long sum; + unsigned int count[MTDSWAP_TREE_CNT]; + unsigned int min[MTDSWAP_TREE_CNT]; + unsigned int max[MTDSWAP_TREE_CNT]; + unsigned int i, cw = 0, cwp = 0, cwecount = 0, bb_cnt, mapped, pages; + uint64_t use_size; + char *name[] = {"clean", "used", "low", "high", "dirty", "bitflip", + "failing"}; + + mutex_lock(&d->mbd_dev->lock); + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) { + struct rb_root *root = &d->trees[i].root; + + if (root->rb_node) { + count[i] = d->trees[i].count; + min[i] = rb_entry(rb_first(root), struct swap_eb, + rb)->erase_count; + max[i] = rb_entry(rb_last(root), struct swap_eb, + rb)->erase_count; + } else + count[i] = 0; + } + + if (d->curr_write) { + cw = 1; + cwp = d->curr_write_pos; + cwecount = d->curr_write->erase_count; + } + + sum = 0; + for (i = 0; i < d->eblks; i++) + sum += d->eb_data[i].erase_count; + + use_size = (uint64_t)d->eblks * d->mtd->erasesize; + bb_cnt = mtdswap_badblocks(d->mtd, use_size); + + mapped = 0; + pages = d->mbd_dev->size; + for (i = 0; i < pages; i++) + if (d->page_data[i] != BLOCK_UNDEF) + mapped++; + + mutex_unlock(&d->mbd_dev->lock); + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) { + if (!count[i]) + continue; + + if (min[i] != max[i]) + seq_printf(s, "%s:\t%5d erase blocks, erased min %d, " + "max %d times\n", + name[i], count[i], min[i], max[i]); + else + seq_printf(s, "%s:\t%5d erase blocks, all erased %d " + "times\n", name[i], count[i], min[i]); + } + + if (bb_cnt) + seq_printf(s, "bad:\t%5u erase blocks\n", bb_cnt); + + if (cw) + seq_printf(s, "current erase block: %u pages used, %u free, " + "erased %u times\n", + cwp, d->pages_per_eblk - cwp, cwecount); + + seq_printf(s, "total erasures: %lu\n", sum); + + seq_printf(s, "\n"); + + seq_printf(s, "mtdswap_readsect count: %llu\n", d->sect_read_count); + seq_printf(s, "mtdswap_writesect count: %llu\n", d->sect_write_count); + seq_printf(s, "mtdswap_discard count: %llu\n", d->discard_count); + seq_printf(s, "mtd read count: %llu\n", d->mtd_read_count); + seq_printf(s, "mtd write count: %llu\n", d->mtd_write_count); + seq_printf(s, "discarded pages count: %llu\n", d->discard_page_count); + + seq_printf(s, "\n"); + seq_printf(s, "total pages: %lu\n", pages); + seq_printf(s, "pages mapped: %u\n", mapped); + + return 0; +} + +static int mtdswap_open(struct inode *inode, struct file *file) +{ + return single_open(file, mtdswap_show, inode->i_private); +} + +static const struct file_operations mtdswap_fops = { + .open = mtdswap_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int mtdswap_add_debugfs(struct mtdswap_dev *d) +{ + struct gendisk *gd = d->mbd_dev->disk; + struct device *dev = disk_to_dev(gd); + + struct dentry *root; + struct dentry *dent; + + root = debugfs_create_dir(gd->disk_name, NULL); + if (IS_ERR(root)) + return 0; + + if (!root) { + dev_err(dev, "failed to initialize debugfs\n"); + return -1; + } + + d->debugfs_root = root; + + dent = debugfs_create_file("stats", S_IRUSR, root, d, + &mtdswap_fops); + if (!dent) { + dev_err(d->dev, "debugfs_create_file failed\n"); + debugfs_remove_recursive(root); + d->debugfs_root = NULL; + return -1; + } + + return 0; +} + +static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks, + unsigned int spare_cnt) +{ + struct mtd_info *mtd = d->mbd_dev->mtd; + unsigned int i, eblk_bytes, pages, blocks; + int ret = -ENOMEM; + + d->mtd = mtd; + d->eblks = eblocks; + d->spare_eblks = spare_cnt; + d->pages_per_eblk = mtd->erasesize >> PAGE_SHIFT; + + pages = d->mbd_dev->size; + blocks = eblocks * d->pages_per_eblk; + + for (i = 0; i < MTDSWAP_TREE_CNT; i++) + d->trees[i].root = RB_ROOT; + + d->page_data = vmalloc(sizeof(int)*pages); + if (!d->page_data) + goto page_data_fail; + + d->revmap = vmalloc(sizeof(int)*blocks); + if (!d->revmap) + goto revmap_fail; + + eblk_bytes = sizeof(struct swap_eb)*d->eblks; + d->eb_data = vmalloc(eblk_bytes); + if (!d->eb_data) + goto eb_data_fail; + + memset(d->eb_data, 0, eblk_bytes); + for (i = 0; i < pages; i++) + d->page_data[i] = BLOCK_UNDEF; + + for (i = 0; i < blocks; i++) + d->revmap[i] = PAGE_UNDEF; + + d->page_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!d->page_buf) + goto page_buf_fail; + + d->oob_buf = kmalloc(2 * mtd->ecclayout->oobavail, GFP_KERNEL); + if (!d->oob_buf) + goto oob_buf_fail; + + mtdswap_scan_eblks(d); + + return 0; + +oob_buf_fail: + kfree(d->page_buf); +page_buf_fail: + vfree(d->eb_data); +eb_data_fail: + vfree(d->revmap); +revmap_fail: + vfree(d->page_data); +page_data_fail: + printk(KERN_ERR "%s: init failed (%d)\n", MTDSWAP_PREFIX, ret); + return ret; +} + +static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) +{ + struct mtdswap_dev *d; + struct mtd_blktrans_dev *mbd_dev; + char *parts; + char *this_opt; + unsigned long part; + unsigned int eblocks, eavailable, bad_blocks, spare_cnt; + uint64_t swap_size, use_size, size_limit; + struct nand_ecclayout *oinfo; + int ret; + + parts = &partitions[0]; + if (!*parts) + return; + + while ((this_opt = strsep(&parts, ",")) != NULL) { + if (strict_strtoul(this_opt, 0, &part) < 0) + return; + + if (mtd->index == part) + break; + } + + if (mtd->index != part) + return; + + if (mtd->erasesize < PAGE_SIZE || mtd->erasesize % PAGE_SIZE) { + printk(KERN_ERR "%s: Erase size %u not multiple of PAGE_SIZE " + "%lu\n", MTDSWAP_PREFIX, mtd->erasesize, PAGE_SIZE); + return; + } + + if (PAGE_SIZE % mtd->writesize || mtd->writesize > PAGE_SIZE) { + printk(KERN_ERR "%s: PAGE_SIZE %lu not multiple of write size" + " %u\n", MTDSWAP_PREFIX, PAGE_SIZE, mtd->writesize); + return; + } + + oinfo = mtd->ecclayout; + if (!mtd->oobsize || !oinfo || oinfo->oobavail < MTDSWAP_OOBSIZE) { + printk(KERN_ERR "%s: Not enough free bytes in OOB, " + "%d available, %u needed.\n", + MTDSWAP_PREFIX, oinfo->oobavail, MTDSWAP_OOBSIZE); + return; + } + + if (spare_eblocks > 100) + spare_eblocks = 100; + + use_size = mtd->size; + size_limit = (uint64_t) BLOCK_MAX * PAGE_SIZE; + + if (mtd->size > size_limit) { + printk(KERN_WARNING "%s: Device too large. Limiting size to " + "%llu bytes\n", MTDSWAP_PREFIX, size_limit); + use_size = size_limit; + } + + eblocks = mtd_div_by_eb(use_size, mtd); + use_size = eblocks * mtd->erasesize; + bad_blocks = mtdswap_badblocks(mtd, use_size); + eavailable = eblocks - bad_blocks; + + if (eavailable < MIN_ERASE_BLOCKS) { + printk(KERN_ERR "%s: Not enough erase blocks. %u available, " + "%d needed\n", MTDSWAP_PREFIX, eavailable, + MIN_ERASE_BLOCKS); + return; + } + + spare_cnt = div_u64((uint64_t)eavailable * spare_eblocks, 100); + + if (spare_cnt < MIN_SPARE_EBLOCKS) + spare_cnt = MIN_SPARE_EBLOCKS; + + if (spare_cnt > eavailable - 1) + spare_cnt = eavailable - 1; + + swap_size = (uint64_t)(eavailable - spare_cnt) * mtd->erasesize + + (header ? PAGE_SIZE : 0); + + printk(KERN_INFO "%s: Enabling MTD swap on device %lu, size %llu KB, " + "%u spare, %u bad blocks\n", + MTDSWAP_PREFIX, part, swap_size / 1024, spare_cnt, bad_blocks); + + d = kzalloc(sizeof(struct mtdswap_dev), GFP_KERNEL); + if (!d) + return; + + mbd_dev = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL); + if (!mbd_dev) { + kfree(d); + return; + } + + d->mbd_dev = mbd_dev; + mbd_dev->priv = d; + + mbd_dev->mtd = mtd; + mbd_dev->devnum = mtd->index; + mbd_dev->size = swap_size >> PAGE_SHIFT; + mbd_dev->tr = tr; + + if (!(mtd->flags & MTD_WRITEABLE)) + mbd_dev->readonly = 1; + + if (mtdswap_init(d, eblocks, spare_cnt) < 0) + goto init_failed; + + if (add_mtd_blktrans_dev(mbd_dev) < 0) + goto cleanup; + + d->dev = disk_to_dev(mbd_dev->disk); + + ret = mtdswap_add_debugfs(d); + if (ret < 0) + goto debugfs_failed; + + return; + +debugfs_failed: + del_mtd_blktrans_dev(mbd_dev); + +cleanup: + mtdswap_cleanup(d); + +init_failed: + kfree(mbd_dev); + kfree(d); +} + +static void mtdswap_remove_dev(struct mtd_blktrans_dev *dev) +{ + struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev); + + debugfs_remove_recursive(d->debugfs_root); + del_mtd_blktrans_dev(dev); + mtdswap_cleanup(d); + kfree(d); +} + +static struct mtd_blktrans_ops mtdswap_ops = { + .name = "mtdswap", + .major = 0, + .part_bits = 0, + .blksize = PAGE_SIZE, + .flush = mtdswap_flush, + .readsect = mtdswap_readsect, + .writesect = mtdswap_writesect, + .discard = mtdswap_discard, + .background = mtdswap_background, + .add_mtd = mtdswap_add_mtd, + .remove_dev = mtdswap_remove_dev, + .owner = THIS_MODULE, +}; + +static int __init mtdswap_modinit(void) +{ + return register_mtd_blktrans(&mtdswap_ops); +} + +static void __exit mtdswap_modexit(void) +{ + deregister_mtd_blktrans(&mtdswap_ops); +} + +module_init(mtdswap_modinit); +module_exit(mtdswap_modexit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen "); +MODULE_DESCRIPTION("Block device access to an MTD suitable for using as " + "swap space"); -- cgit v0.10.2 From dcfb81d61da1367e52f7f7e3ceff0d0044c3c7ee Mon Sep 17 00:00:00 2001 From: David Griego Date: Tue, 30 Nov 2010 15:32:05 +0530 Subject: mtd: NOR flash driver for OMAP-L137/AM17x OMAP-L137/AM17x has limited number of dedicated EMIFA address pins, enough to interface directly to an SDRAM. If a device such as an asynchronous flash needs to be attached to the EMIFA, then either GPIO pins or a chip select may be used to control the flash device's upper address lines. This patch adds support for the NOR flash on the OMAP-L137/ AM17x user interface daughter board using the latch-addr-flash MTD mapping driver which allows flashes to be partially physically addressed. The upper address lines are set by a board specific code which is a separate patch. Signed-off-by: David Griego Signed-off-by: Aleksey Makarov Signed-off-by: Sergei Shtylyov Signed-off-by: Savinay Dharmappa Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 803072a..44b1f46 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -552,4 +552,13 @@ config MTD_PISMO When built as a module, it will be called pismo.ko +config MTD_LATCH_ADDR + tristate "Latch-assisted Flash Chip Support" + depends on MTD_COMPLEX_MAPPINGS + help + Map driver which allows flashes to be partially physically addressed + and have the upper address lines set by a board specific code. + + If compiled as a module, it will be called latch-addr-flash. + endmenu diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index c7869c7..08533bd 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -59,3 +59,4 @@ obj-$(CONFIG_MTD_RBTX4939) += rbtx4939-flash.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o obj-$(CONFIG_MTD_GPIO_ADDR) += gpio-addr-flash.o obj-$(CONFIG_MTD_BCM963XX) += bcm963xx-flash.o +obj-$(CONFIG_MTD_LATCH_ADDR) += latch-addr-flash.o diff --git a/drivers/mtd/maps/latch-addr-flash.c b/drivers/mtd/maps/latch-addr-flash.c new file mode 100644 index 0000000..ee25480 --- /dev/null +++ b/drivers/mtd/maps/latch-addr-flash.c @@ -0,0 +1,272 @@ +/* + * Interface for NOR flash driver whose high address lines are latched + * + * Copyright © 2000 Nicolas Pitre + * Copyright © 2005-2008 Analog Devices Inc. + * Copyright © 2008 MontaVista Software, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "latch-addr-flash" + +struct latch_addr_flash_info { + struct mtd_info *mtd; + struct map_info map; + struct resource *res; + + void (*set_window)(unsigned long offset, void *data); + void *data; + + /* cache; could be found out of res */ + unsigned long win_mask; + + int nr_parts; + struct mtd_partition *parts; + + spinlock_t lock; +}; + +static map_word lf_read(struct map_info *map, unsigned long ofs) +{ + struct latch_addr_flash_info *info; + map_word datum; + + info = (struct latch_addr_flash_info *)map->map_priv_1; + + spin_lock(&info->lock); + + info->set_window(ofs, info->data); + datum = inline_map_read(map, info->win_mask & ofs); + + spin_unlock(&info->lock); + + return datum; +} + +static void lf_write(struct map_info *map, map_word datum, unsigned long ofs) +{ + struct latch_addr_flash_info *info; + + info = (struct latch_addr_flash_info *)map->map_priv_1; + + spin_lock(&info->lock); + + info->set_window(ofs, info->data); + inline_map_write(map, datum, info->win_mask & ofs); + + spin_unlock(&info->lock); +} + +static void lf_copy_from(struct map_info *map, void *to, + unsigned long from, ssize_t len) +{ + struct latch_addr_flash_info *info = + (struct latch_addr_flash_info *) map->map_priv_1; + unsigned n; + + while (len > 0) { + n = info->win_mask + 1 - (from & info->win_mask); + if (n > len) + n = len; + + spin_lock(&info->lock); + + info->set_window(from, info->data); + memcpy_fromio(to, map->virt + (from & info->win_mask), n); + + spin_unlock(&info->lock); + + to += n; + from += n; + len -= n; + } +} + +static char *rom_probe_types[] = { "cfi_probe", NULL }; + +static char *part_probe_types[] = { "cmdlinepart", NULL }; + +static int latch_addr_flash_remove(struct platform_device *dev) +{ + struct latch_addr_flash_info *info; + struct latch_addr_flash_data *latch_addr_data; + + info = platform_get_drvdata(dev); + if (info == NULL) + return 0; + platform_set_drvdata(dev, NULL); + + latch_addr_data = dev->dev.platform_data; + + if (info->mtd != NULL) { + if (mtd_has_partitions()) { + if (info->nr_parts) { + del_mtd_partitions(info->mtd); + kfree(info->parts); + } else if (latch_addr_data->nr_parts) { + del_mtd_partitions(info->mtd); + } else { + del_mtd_device(info->mtd); + } + } else { + del_mtd_device(info->mtd); + } + map_destroy(info->mtd); + } + + if (info->map.virt != NULL) + iounmap(info->map.virt); + + if (info->res != NULL) + release_mem_region(info->res->start, resource_size(info->res)); + + kfree(info); + + if (latch_addr_data->done) + latch_addr_data->done(latch_addr_data->data); + + return 0; +} + +static int __devinit latch_addr_flash_probe(struct platform_device *dev) +{ + struct latch_addr_flash_data *latch_addr_data; + struct latch_addr_flash_info *info; + resource_size_t win_base = dev->resource->start; + resource_size_t win_size = resource_size(dev->resource); + char **probe_type; + int chipsel; + int err; + + latch_addr_data = dev->dev.platform_data; + if (latch_addr_data == NULL) + return -ENODEV; + + pr_notice("latch-addr platform flash device: %#llx byte " + "window at %#.8llx\n", + (unsigned long long)win_size, (unsigned long long)win_base); + + chipsel = dev->id; + + if (latch_addr_data->init) { + err = latch_addr_data->init(latch_addr_data->data, chipsel); + if (err != 0) + return err; + } + + info = kzalloc(sizeof(struct latch_addr_flash_info), GFP_KERNEL); + if (info == NULL) { + err = -ENOMEM; + goto done; + } + + platform_set_drvdata(dev, info); + + info->res = request_mem_region(win_base, win_size, DRIVER_NAME); + if (info->res == NULL) { + dev_err(&dev->dev, "Could not reserve memory region\n"); + err = -EBUSY; + goto free_info; + } + + info->map.name = DRIVER_NAME; + info->map.size = latch_addr_data->size; + info->map.bankwidth = latch_addr_data->width; + + info->map.phys = NO_XIP; + info->map.virt = ioremap(win_base, win_size); + if (!info->map.virt) { + err = -ENOMEM; + goto free_res; + } + + info->map.map_priv_1 = (unsigned long)info; + + info->map.read = lf_read; + info->map.copy_from = lf_copy_from; + info->map.write = lf_write; + info->set_window = latch_addr_data->set_window; + info->data = latch_addr_data->data; + info->win_mask = win_size - 1; + + spin_lock_init(&info->lock); + + for (probe_type = rom_probe_types; !info->mtd && *probe_type; + probe_type++) + info->mtd = do_map_probe(*probe_type, &info->map); + + if (info->mtd == NULL) { + dev_err(&dev->dev, "map_probe failed\n"); + err = -ENODEV; + goto iounmap; + } + info->mtd->owner = THIS_MODULE; + + if (mtd_has_partitions()) { + + err = parse_mtd_partitions(info->mtd, + (const char **)part_probe_types, + &info->parts, 0); + if (err > 0) { + add_mtd_partitions(info->mtd, info->parts, err); + return 0; + } + if (latch_addr_data->nr_parts) { + pr_notice("Using latch-addr-flash partition information\n"); + add_mtd_partitions(info->mtd, latch_addr_data->parts, + latch_addr_data->nr_parts); + return 0; + } + } + add_mtd_device(info->mtd); + return 0; + +iounmap: + iounmap(info->map.virt); +free_res: + release_mem_region(info->res->start, resource_size(info->res)); +free_info: + kfree(info); +done: + if (latch_addr_data->done) + latch_addr_data->done(latch_addr_data->data); + return err; +} + +static struct platform_driver latch_addr_flash_driver = { + .probe = latch_addr_flash_probe, + .remove = __devexit_p(latch_addr_flash_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init latch_addr_flash_init(void) +{ + return platform_driver_register(&latch_addr_flash_driver); +} +module_init(latch_addr_flash_init); + +static void __exit latch_addr_flash_exit(void) +{ + platform_driver_unregister(&latch_addr_flash_driver); +} +module_exit(latch_addr_flash_exit); + +MODULE_AUTHOR("David Griego "); +MODULE_DESCRIPTION("MTD map driver for flashes addressed physically with upper " + "address lines being set board specifically"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mtd/latch-addr-flash.h b/include/linux/mtd/latch-addr-flash.h new file mode 100644 index 0000000..e94b8e1 --- /dev/null +++ b/include/linux/mtd/latch-addr-flash.h @@ -0,0 +1,29 @@ +/* + * Interface for NOR flash driver whose high address lines are latched + * + * Copyright © 2008 MontaVista Software, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#ifndef __LATCH_ADDR_FLASH__ +#define __LATCH_ADDR_FLASH__ + +struct map_info; +struct mtd_partition; + +struct latch_addr_flash_data { + unsigned int width; + unsigned int size; + + int (*init)(void *data, int cs); + void (*done)(void *data); + void (*set_window)(unsigned long offset, void *data); + void *data; + + unsigned int nr_parts; + struct mtd_partition *parts; +}; + +#endif -- cgit v0.10.2 From b3dcfd35244e1cb8dc8dfa5c05013b133dbb437a Mon Sep 17 00:00:00 2001 From: Roman Tereshonkov Date: Thu, 17 Feb 2011 13:44:41 +0200 Subject: mtd: onenand: add new option to control initial onenand unlocking A new option ONENAND_SKIP_INITIAL_UNLOCKING is added. This allows to disable initial onenand unlocking when the driver is initialized. Signed-off-by: Roman Tereshonkov Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index 4205b94..56a8b20 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -4085,7 +4085,8 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) mtd->writebufsize = mtd->writesize; /* Unlock whole block */ - this->unlock_all(mtd); + if (!(this->options & ONENAND_SKIP_INITIAL_UNLOCKING)) + this->unlock_all(mtd); ret = this->scan_bbt(mtd); if ((!FLEXONENAND(this)) || ret) diff --git a/include/linux/mtd/onenand.h b/include/linux/mtd/onenand.h index ae418e4..52b6f18 100644 --- a/include/linux/mtd/onenand.h +++ b/include/linux/mtd/onenand.h @@ -198,6 +198,7 @@ struct onenand_chip { #define ONENAND_SKIP_UNLOCK_CHECK (0x0100) #define ONENAND_PAGEBUF_ALLOC (0x1000) #define ONENAND_OOBBUF_ALLOC (0x2000) +#define ONENAND_SKIP_INITIAL_UNLOCKING (0x4000) #define ONENAND_IS_4KB_PAGE(this) \ (this->options & ONENAND_HAS_4KB_PAGE) -- cgit v0.10.2 From c93ff6bf16523d33e991a1fadde1b8d63eb7cd2c Mon Sep 17 00:00:00 2001 From: Roman Tereshonkov Date: Thu, 17 Feb 2011 13:44:42 +0200 Subject: mtd: omap: add new variable to platform data to control onenand unlocking New variable skip_initial_unlocking is added to the omap_onenand_platform_data. This is used to inform the onenand driver to skip onenand unlocking when it is initialized. Signed-off-by: Roman Tereshonkov Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/arch/arm/plat-omap/include/plat/onenand.h b/arch/arm/plat-omap/include/plat/onenand.h index affe87e..1b430d5 100644 --- a/arch/arm/plat-omap/include/plat/onenand.h +++ b/arch/arm/plat-omap/include/plat/onenand.h @@ -24,6 +24,7 @@ struct omap_onenand_platform_data { int dma_channel; u8 flags; u8 regulator_can_sleep; + u8 skip_initial_unlocking; }; #define ONENAND_MAX_PARTITIONS 8 diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c index c849cac..ea32c2f 100644 --- a/drivers/mtd/onenand/omap2.c +++ b/drivers/mtd/onenand/omap2.c @@ -631,6 +631,7 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) { struct omap_onenand_platform_data *pdata; struct omap2_onenand *c; + struct onenand_chip *this; int r; pdata = pdev->dev.platform_data; @@ -728,9 +729,8 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) c->mtd.dev.parent = &pdev->dev; + this = &c->onenand; if (c->dma_channel >= 0) { - struct onenand_chip *this = &c->onenand; - this->wait = omap2_onenand_wait; if (cpu_is_omap34xx()) { this->read_bufferram = omap3_onenand_read_bufferram; @@ -751,6 +751,9 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) c->onenand.disable = omap2_onenand_disable; } + if (pdata->skip_initial_unlocking) + this->options |= ONENAND_SKIP_INITIAL_UNLOCKING; + if ((r = onenand_scan(&c->mtd, 1)) < 0) goto err_release_regulator; -- cgit v0.10.2 From 775adc3d44c8076d3e475fabebb61c08421c4421 Mon Sep 17 00:00:00 2001 From: Jason Liu Date: Fri, 25 Feb 2011 13:06:18 +0800 Subject: mtd: nand: fix a typo in a commentary Signed-off-by: Jason Liu Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index a9c6ce7..da76040 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2377,7 +2377,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, return -EINVAL; } - /* Do not allow reads past end of device */ + /* Do not allow write past end of device */ if (unlikely(to >= mtd->size || ops->ooboffs + ops->ooblen > ((mtd->size >> chip->page_shift) - -- cgit v0.10.2 From bea9312839c51853a2837336405c829b0bf18f88 Mon Sep 17 00:00:00 2001 From: Tracey Dent Date: Sat, 26 Feb 2011 11:15:13 -0500 Subject: jffs2: remove a trailing white space in commentaries Signed-off-by: Tracey Dent Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/fs/jffs2/xattr.c b/fs/jffs2/xattr.c index 4f9cc04..3e93cdd 100644 --- a/fs/jffs2/xattr.c +++ b/fs/jffs2/xattr.c @@ -31,7 +31,7 @@ * is used to release xattr name/value pair and detach from c->xattrindex. * reclaim_xattr_datum(c) * is used to reclaim xattr name/value pairs on the xattr name/value pair cache when - * memory usage by cache is over c->xdatum_mem_threshold. Currently, this threshold + * memory usage by cache is over c->xdatum_mem_threshold. Currently, this threshold * is hard coded as 32KiB. * do_verify_xattr_datum(c, xd) * is used to load the xdatum informations without name/value pair from the medium. -- cgit v0.10.2 From 74f1b7244e502c5a98906a5dcc5ec389aacd25e6 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 28 Feb 2011 13:12:46 +0100 Subject: mtd: omap3: nand: report corrected ecc errors The number of corrected ECC errors should be reported since other MTD systems make use of this information (such as UBI data scrubbing). Signed-off-by: John Ogness Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c index 28af71c..d7a4e25 100644 --- a/drivers/mtd/nand/omap2.c +++ b/drivers/mtd/nand/omap2.c @@ -517,6 +517,8 @@ static void gen_true_ecc(u8 *ecc_buf) * * This function compares two ECC's and indicates if there is an error. * If the error can be corrected it will be corrected to the buffer. + * If there is no error, %0 is returned. If there is an error but it + * was corrected, %1 is returned. Otherwise, %-1 is returned. */ static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ u8 *ecc_data2, /* read from register */ @@ -622,7 +624,7 @@ static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ page_data[find_byte] ^= (1 << find_bit); - return 0; + return 1; default: if (isEccFF) { if (ecc_data2[0] == 0 && @@ -643,8 +645,11 @@ static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ * @calc_ecc: ecc read from HW ECC registers * * Compares the ecc read from nand spare area with ECC registers values - * and if ECC's mismached, it will call 'omap_compare_ecc' for error detection - * and correction. + * and if ECC's mismatched, it will call 'omap_compare_ecc' for error + * detection and correction. If there are no errors, %0 is returned. If + * there were errors and all of the errors were corrected, the number of + * corrected errors is returned. If uncorrectable errors exist, %-1 is + * returned. */ static int omap_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) @@ -652,6 +657,7 @@ static int omap_correct_data(struct mtd_info *mtd, u_char *dat, struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); int blockCnt = 0, i = 0, ret = 0; + int stat = 0; /* Ex NAND_ECC_HW12_2048 */ if ((info->nand.ecc.mode == NAND_ECC_HW) && @@ -665,12 +671,14 @@ static int omap_correct_data(struct mtd_info *mtd, u_char *dat, ret = omap_compare_ecc(read_ecc, calc_ecc, dat); if (ret < 0) return ret; + /* keep track of the number of corrected errors */ + stat += ret; } read_ecc += 3; calc_ecc += 3; dat += 512; } - return 0; + return stat; } /** -- cgit v0.10.2 From 5d9d9936209e589569a97985285b3282310cf2db Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Wed, 2 Mar 2011 16:47:55 +0200 Subject: mtd: mxc_nand: fix read past buffer end Signed-off-by: Baruch Siach Acked-by: Sascha hauer Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index ef932ba..6cd78ed 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -641,9 +641,9 @@ static void mxc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) n = min(n, len); - memcpy(buf, host->data_buf + col, len); + memcpy(buf, host->data_buf + col, n); - host->buf_start += len; + host->buf_start += n; } /* Used by the upper layer to verify the data in NAND Flash -- cgit v0.10.2 From e70727e442eb61581f836a9a0ca0c3b70d8a3ff2 Mon Sep 17 00:00:00 2001 From: David Lambert Date: Mon, 7 Mar 2011 12:00:46 +0200 Subject: mtd: speedtest: fix integer overflow 32-bit integers used in 'calc_speed()' may overflow and lead to incorrect results. Use 64-bit integers instead. Signed-off-by: David Lambert Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/tests/mtd_speedtest.c b/drivers/mtd/tests/mtd_speedtest.c index 3ce6fce..627d4e2 100644 --- a/drivers/mtd/tests/mtd_speedtest.c +++ b/drivers/mtd/tests/mtd_speedtest.c @@ -314,16 +314,16 @@ static inline void stop_timing(void) static long calc_speed(void) { - long ms, k, speed; + uint64_t k; + long ms; ms = (finish.tv_sec - start.tv_sec) * 1000 + (finish.tv_usec - start.tv_usec) / 1000; - k = goodebcnt * mtd->erasesize / 1024; - if (ms) - speed = (k * 1000) / ms; - else - speed = 0; - return speed; + if (ms == 0) + return 0; + k = goodebcnt * (mtd->erasesize / 1024) * 1000; + do_div(k, ms); + return k; } static int scan_for_bad_eraseblocks(void) -- cgit v0.10.2 From e353a20afaee1e3e67fc4fa663a76c68a4c1fb74 Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Thu, 3 Mar 2011 11:08:30 +0800 Subject: mtd: pxa3xx_nand: make scan procedure more clear The previous probe function is some kind of big part. This patch seperate the resource allocation to keep the probe process more clear than before. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index ea2c288..f440443 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -126,6 +126,7 @@ struct pxa3xx_nand_info { unsigned int buf_start; unsigned int buf_count; + struct mtd_info *mtd; /* DMA information */ int drcmr_dat; int drcmr_cmd; @@ -1044,34 +1045,27 @@ static void pxa3xx_nand_init_mtd(struct mtd_info *mtd, this->chip_delay = 25; } -static int pxa3xx_nand_probe(struct platform_device *pdev) +static +struct pxa3xx_nand_info *alloc_nand_resource(struct platform_device *pdev) { - struct pxa3xx_nand_platform_data *pdata; + struct pxa3xx_nand_platform_data *pdata = pdev->dev.platform_data; struct pxa3xx_nand_info *info; - struct nand_chip *this; struct mtd_info *mtd; struct resource *r; - int ret = 0, irq; - - pdata = pdev->dev.platform_data; - - if (!pdata) { - dev_err(&pdev->dev, "no platform data defined\n"); - return -ENODEV; - } + int ret, irq; mtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct pxa3xx_nand_info), GFP_KERNEL); if (!mtd) { dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; + return NULL; } info = (struct pxa3xx_nand_info *)(&mtd[1]); info->pdev = pdev; - this = &info->nand_chip; mtd->priv = info; + info->mtd = mtd; mtd->owner = THIS_MODULE; info->clk = clk_get(&pdev->dev, NULL); @@ -1149,31 +1143,9 @@ static int pxa3xx_nand_probe(struct platform_device *pdev) } pxa3xx_nand_init_mtd(mtd, info); + platform_set_drvdata(pdev, info); - platform_set_drvdata(pdev, mtd); - - if (nand_scan(mtd, 1)) { - dev_err(&pdev->dev, "failed to scan nand\n"); - ret = -ENXIO; - goto fail_free_irq; - } - -#ifdef CONFIG_MTD_PARTITIONS - if (mtd_has_cmdlinepart()) { - static const char *probes[] = { "cmdlinepart", NULL }; - struct mtd_partition *parts; - int nr_parts; - - nr_parts = parse_mtd_partitions(mtd, probes, &parts, 0); - - if (nr_parts) - return add_mtd_partitions(mtd, parts, nr_parts); - } - - return add_mtd_partitions(mtd, pdata->parts, pdata->nr_parts); -#else - return 0; -#endif + return info; fail_free_irq: free_irq(irq, info); @@ -1193,13 +1165,13 @@ fail_put_clk: clk_put(info->clk); fail_free_mtd: kfree(mtd); - return ret; + return NULL; } static int pxa3xx_nand_remove(struct platform_device *pdev) { - struct mtd_info *mtd = platform_get_drvdata(pdev); - struct pxa3xx_nand_info *info = mtd->priv; + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct mtd_info *mtd = info->mtd; struct resource *r; int irq; @@ -1230,11 +1202,50 @@ static int pxa3xx_nand_remove(struct platform_device *pdev) return 0; } +static int pxa3xx_nand_probe(struct platform_device *pdev) +{ + struct pxa3xx_nand_platform_data *pdata; + struct pxa3xx_nand_info *info; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -ENODEV; + } + + info = alloc_nand_resource(pdev); + if (info == NULL) + return -ENOMEM; + + if (nand_scan(info->mtd, 1)) { + dev_err(&pdev->dev, "failed to scan nand\n"); + pxa3xx_nand_remove(pdev); + return -ENODEV; + } + +#ifdef CONFIG_MTD_PARTITIONS + if (mtd_has_cmdlinepart()) { + const char *probes[] = { "cmdlinepart", NULL }; + struct mtd_partition *parts; + int nr_parts; + + nr_parts = parse_mtd_partitions(info->mtd, probes, &parts, 0); + + if (nr_parts) + return add_mtd_partitions(mtd, parts, nr_parts); + } + + return add_mtd_partitions(mtd, pdata->parts, pdata->nr_parts); +#else + return 0; +#endif +} + #ifdef CONFIG_PM static int pxa3xx_nand_suspend(struct platform_device *pdev, pm_message_t state) { - struct mtd_info *mtd = (struct mtd_info *)platform_get_drvdata(pdev); - struct pxa3xx_nand_info *info = mtd->priv; + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct mtd_info *mtd = info->mtd; if (info->state != STATE_READY) { dev_err(&pdev->dev, "driver busy, state = %d\n", info->state); @@ -1246,8 +1257,8 @@ static int pxa3xx_nand_suspend(struct platform_device *pdev, pm_message_t state) static int pxa3xx_nand_resume(struct platform_device *pdev) { - struct mtd_info *mtd = (struct mtd_info *)platform_get_drvdata(pdev); - struct pxa3xx_nand_info *info = mtd->priv; + struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); + struct mtd_info *mtd = info->mtd; nand_writel(info, NDTR0CS0, info->ndtr0cs0); nand_writel(info, NDTR1CS0, info->ndtr1cs0); -- cgit v0.10.2 From f8155a404db95656f1519b28fdb96cb68f8b2364 Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Mon, 28 Feb 2011 10:32:11 +0800 Subject: mtd: pxa3xx_nand: rework irq logic Enable all irq when we start the nand controller, and put all the transaction logic in the pxa3xx_nand_irq. By doing this way, we could dramatically increase the performance by avoid unnecessary delay. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index f440443..3264b1d 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -27,6 +27,7 @@ #include #define CHIP_DELAY_TIMEOUT (2 * HZ/10) +#define NAND_STOP_DELAY (2 * HZ/50) /* registers and bit definitions */ #define NDCR (0x00) /* Control register */ @@ -52,16 +53,18 @@ #define NDCR_ND_MODE (0x3 << 21) #define NDCR_NAND_MODE (0x0) #define NDCR_CLR_PG_CNT (0x1 << 20) -#define NDCR_CLR_ECC (0x1 << 19) +#define NDCR_STOP_ON_UNCOR (0x1 << 19) #define NDCR_RD_ID_CNT_MASK (0x7 << 16) #define NDCR_RD_ID_CNT(x) (((x) << 16) & NDCR_RD_ID_CNT_MASK) #define NDCR_RA_START (0x1 << 15) #define NDCR_PG_PER_BLK (0x1 << 14) #define NDCR_ND_ARB_EN (0x1 << 12) +#define NDCR_INT_MASK (0xFFF) #define NDSR_MASK (0xfff) -#define NDSR_RDY (0x1 << 11) +#define NDSR_RDY (0x1 << 12) +#define NDSR_FLASH_RDY (0x1 << 11) #define NDSR_CS0_PAGED (0x1 << 10) #define NDSR_CS1_PAGED (0x1 << 9) #define NDSR_CS0_CMDD (0x1 << 8) @@ -104,13 +107,15 @@ enum { }; enum { - STATE_READY = 0, + STATE_IDLE = 0, STATE_CMD_HANDLE, STATE_DMA_READING, STATE_DMA_WRITING, STATE_DMA_DONE, STATE_PIO_READING, STATE_PIO_WRITING, + STATE_CMD_DONE, + STATE_READY, }; struct pxa3xx_nand_info { @@ -292,7 +297,48 @@ static void pxa3xx_set_datasize(struct pxa3xx_nand_info *info) } } -static int prepare_read_prog_cmd(struct pxa3xx_nand_info *info, +/** + * NOTE: it is a must to set ND_RUN firstly, then write + * command buffer, otherwise, it does not work. + * We enable all the interrupt at the same time, and + * let pxa3xx_nand_irq to handle all logic. + */ +static void pxa3xx_nand_start(struct pxa3xx_nand_info *info) +{ + uint32_t ndcr; + + ndcr = info->reg_ndcr; + ndcr |= info->use_ecc ? NDCR_ECC_EN : 0; + ndcr |= info->use_dma ? NDCR_DMA_EN : 0; + ndcr |= NDCR_ND_RUN; + + /* clear status bits and run */ + nand_writel(info, NDCR, 0); + nand_writel(info, NDSR, NDSR_MASK); + nand_writel(info, NDCR, ndcr); +} + +static void pxa3xx_nand_stop(struct pxa3xx_nand_info *info) +{ + uint32_t ndcr; + int timeout = NAND_STOP_DELAY; + + /* wait RUN bit in NDCR become 0 */ + ndcr = nand_readl(info, NDCR); + while ((ndcr & NDCR_ND_RUN) && (timeout-- > 0)) { + ndcr = nand_readl(info, NDCR); + udelay(1); + } + + if (timeout <= 0) { + ndcr &= ~NDCR_ND_RUN; + nand_writel(info, NDCR, ndcr); + } + /* clear status bits */ + nand_writel(info, NDSR, NDSR_MASK); +} + +static void prepare_read_prog_cmd(struct pxa3xx_nand_info *info, uint16_t cmd, int column, int page_addr) { const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; @@ -319,21 +365,18 @@ static int prepare_read_prog_cmd(struct pxa3xx_nand_info *info, if (cmd == cmdset->program) info->ndcb0 |= NDCB0_CMD_TYPE(1) | NDCB0_AUTO_RS; - - return 0; } -static int prepare_erase_cmd(struct pxa3xx_nand_info *info, +static void prepare_erase_cmd(struct pxa3xx_nand_info *info, uint16_t cmd, int page_addr) { info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0); info->ndcb0 |= NDCB0_CMD_TYPE(2) | NDCB0_AUTO_RS | NDCB0_ADDR_CYC(3); info->ndcb1 = page_addr; info->ndcb2 = 0; - return 0; } -static int prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd) +static void prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd) { const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; @@ -343,7 +386,7 @@ static int prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd) info->oob_size = 0; if (cmd == cmdset->read_id) { - info->ndcb0 |= NDCB0_CMD_TYPE(3); + info->ndcb0 |= NDCB0_CMD_TYPE(3) | NDCB0_ADDR_CYC(1); info->data_size = 8; } else if (cmd == cmdset->read_status) { info->ndcb0 |= NDCB0_CMD_TYPE(4); @@ -352,9 +395,7 @@ static int prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd) cmd == cmdset->unlock) { info->ndcb0 |= NDCB0_CMD_TYPE(5); } else - return -EINVAL; - - return 0; + BUG(); } static void enable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) @@ -402,10 +443,8 @@ static int write_cmd(struct pxa3xx_nand_info *info) return 0; } -static int handle_data_pio(struct pxa3xx_nand_info *info) +static void handle_data_pio(struct pxa3xx_nand_info *info) { - int ret, timeout = CHIP_DELAY_TIMEOUT; - switch (info->state) { case STATE_PIO_WRITING: __raw_writesl(info->mmio_base + NDDB, info->data_buff, @@ -413,14 +452,6 @@ static int handle_data_pio(struct pxa3xx_nand_info *info) if (info->oob_size > 0) __raw_writesl(info->mmio_base + NDDB, info->oob_buff, DIV_ROUND_UP(info->oob_size, 4)); - - enable_int(info, NDSR_CS0_BBD | NDSR_CS0_CMDD); - - ret = wait_for_completion_timeout(&info->cmd_complete, timeout); - if (!ret) { - printk(KERN_ERR "program command time out\n"); - return -1; - } break; case STATE_PIO_READING: __raw_readsl(info->mmio_base + NDDB, info->data_buff, @@ -432,14 +463,11 @@ static int handle_data_pio(struct pxa3xx_nand_info *info) default: printk(KERN_ERR "%s: invalid state %d\n", __func__, info->state); - return -EINVAL; + BUG(); } - - info->state = STATE_READY; - return 0; } -static void start_data_dma(struct pxa3xx_nand_info *info, int dir_out) +static void start_data_dma(struct pxa3xx_nand_info *info) { struct pxa_dma_desc *desc = info->data_desc; int dma_len = ALIGN(info->data_size + info->oob_size, 32); @@ -447,14 +475,21 @@ static void start_data_dma(struct pxa3xx_nand_info *info, int dir_out) desc->ddadr = DDADR_STOP; desc->dcmd = DCMD_ENDIRQEN | DCMD_WIDTH4 | DCMD_BURST32 | dma_len; - if (dir_out) { + switch (info->state) { + case STATE_DMA_WRITING: desc->dsadr = info->data_buff_phys; desc->dtadr = info->mmio_phys + NDDB; desc->dcmd |= DCMD_INCSRCADDR | DCMD_FLOWTRG; - } else { + break; + case STATE_DMA_READING: desc->dtadr = info->data_buff_phys; desc->dsadr = info->mmio_phys + NDDB; desc->dcmd |= DCMD_INCTRGADDR | DCMD_FLOWSRC; + break; + default: + printk(KERN_ERR "%s: invalid state %d\n", __func__, + info->state); + BUG(); } DRCMR(info->drcmr_dat) = DRCMR_MAPVLD | info->data_dma_ch; @@ -472,93 +507,60 @@ static void pxa3xx_nand_data_dma_irq(int channel, void *data) if (dcsr & DCSR_BUSERR) { info->retcode = ERR_DMABUSERR; - complete(&info->cmd_complete); } - if (info->state == STATE_DMA_WRITING) { - info->state = STATE_DMA_DONE; - enable_int(info, NDSR_CS0_BBD | NDSR_CS0_CMDD); - } else { - info->state = STATE_READY; - complete(&info->cmd_complete); - } + info->state = STATE_DMA_DONE; + enable_int(info, NDCR_INT_MASK); + nand_writel(info, NDSR, NDSR_WRDREQ | NDSR_RDDREQ); } static irqreturn_t pxa3xx_nand_irq(int irq, void *devid) { struct pxa3xx_nand_info *info = devid; - unsigned int status; + unsigned int status, is_completed = 0; status = nand_readl(info, NDSR); - if (status & (NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR)) { - if (status & NDSR_DBERR) - info->retcode = ERR_DBERR; - else if (status & NDSR_SBERR) - info->retcode = ERR_SBERR; - - disable_int(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR); - - if (info->use_dma) { - info->state = STATE_DMA_READING; - start_data_dma(info, 0); - } else { - info->state = STATE_PIO_READING; - complete(&info->cmd_complete); - } - } else if (status & NDSR_WRDREQ) { - disable_int(info, NDSR_WRDREQ); + if (status & NDSR_DBERR) + info->retcode = ERR_DBERR; + if (status & NDSR_SBERR) + info->retcode = ERR_SBERR; + if (status & (NDSR_RDDREQ | NDSR_WRDREQ)) { + /* whether use dma to transfer data */ if (info->use_dma) { - info->state = STATE_DMA_WRITING; - start_data_dma(info, 1); + disable_int(info, NDCR_INT_MASK); + info->state = (status & NDSR_RDDREQ) ? + STATE_DMA_READING : STATE_DMA_WRITING; + start_data_dma(info); + goto NORMAL_IRQ_EXIT; } else { - info->state = STATE_PIO_WRITING; - complete(&info->cmd_complete); + info->state = (status & NDSR_RDDREQ) ? + STATE_PIO_READING : STATE_PIO_WRITING; + handle_data_pio(info); } - } else if (status & (NDSR_CS0_BBD | NDSR_CS0_CMDD)) { - if (status & NDSR_CS0_BBD) - info->retcode = ERR_BBERR; - - disable_int(info, NDSR_CS0_BBD | NDSR_CS0_CMDD); - info->state = STATE_READY; - complete(&info->cmd_complete); } - nand_writel(info, NDSR, status); - return IRQ_HANDLED; -} - -static int pxa3xx_nand_do_cmd(struct pxa3xx_nand_info *info, uint32_t event) -{ - uint32_t ndcr; - int ret, timeout = CHIP_DELAY_TIMEOUT; - - if (write_cmd(info)) { - info->retcode = ERR_SENDCMD; - goto fail_stop; + if (status & NDSR_CS0_CMDD) { + info->state = STATE_CMD_DONE; + is_completed = 1; } + if (status & NDSR_FLASH_RDY) + info->state = STATE_READY; - info->state = STATE_CMD_HANDLE; - - enable_int(info, event); - - ret = wait_for_completion_timeout(&info->cmd_complete, timeout); - if (!ret) { - printk(KERN_ERR "command execution timed out\n"); - info->retcode = ERR_SENDCMD; - goto fail_stop; + if (status & NDSR_WRCMDREQ) { + nand_writel(info, NDSR, NDSR_WRCMDREQ); + status &= ~NDSR_WRCMDREQ; + info->state = STATE_CMD_HANDLE; + nand_writel(info, NDCB0, info->ndcb0); + nand_writel(info, NDCB0, info->ndcb1); + nand_writel(info, NDCB0, info->ndcb2); } - if (info->use_dma == 0 && info->data_size > 0) - if (handle_data_pio(info)) - goto fail_stop; - - return 0; - -fail_stop: - ndcr = nand_readl(info, NDCR); - nand_writel(info, NDCR, ndcr & ~NDCR_ND_RUN); - udelay(10); - return -ETIMEDOUT; + /* clear NDSR to let the controller exit the IRQ */ + nand_writel(info, NDSR, status); + if (is_completed) + complete(&info->cmd_complete); +NORMAL_IRQ_EXIT: + return IRQ_HANDLED; } static int pxa3xx_nand_dev_ready(struct mtd_info *mtd) @@ -580,14 +582,13 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, { struct pxa3xx_nand_info *info = mtd->priv; const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; - int ret; + int ret, exec_cmd = 0; info->use_dma = (use_dma) ? 1 : 0; info->use_ecc = 0; info->data_size = 0; - info->state = STATE_READY; - - init_completion(&info->cmd_complete); + info->state = 0; + info->retcode = ERR_NONE; switch (command) { case NAND_CMD_READOOB: @@ -596,36 +597,18 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, info->buf_start = mtd->writesize + column; memset(info->data_buff, 0xFF, info->buf_count); - if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr)) - break; - - pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR); - - /* We only are OOB, so if the data has error, does not matter */ - if (info->retcode == ERR_DBERR) - info->retcode = ERR_NONE; + prepare_read_prog_cmd(info, cmdset->read1, column, page_addr); + exec_cmd = 1; break; case NAND_CMD_READ0: info->use_ecc = 1; - info->retcode = ERR_NONE; info->buf_start = column; info->buf_count = mtd->writesize + mtd->oobsize; memset(info->data_buff, 0xFF, info->buf_count); - if (prepare_read_prog_cmd(info, cmdset->read1, column, page_addr)) - break; - - pxa3xx_nand_do_cmd(info, NDSR_RDDREQ | NDSR_DBERR | NDSR_SBERR); - - if (info->retcode == ERR_DBERR) { - /* for blank page (all 0xff), HW will calculate its ECC as - * 0, which is different from the ECC information within - * OOB, ignore such double bit errors - */ - if (is_buf_blank(info->data_buff, mtd->writesize)) - info->retcode = ERR_NONE; - } + prepare_read_prog_cmd(info, cmdset->read1, column, page_addr); + exec_cmd = 1; break; case NAND_CMD_SEQIN: info->buf_start = column; @@ -639,17 +622,13 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, case NAND_CMD_PAGEPROG: info->use_ecc = (info->seqin_column >= mtd->writesize) ? 0 : 1; - if (prepare_read_prog_cmd(info, cmdset->program, - info->seqin_column, info->seqin_page_addr)) - break; - - pxa3xx_nand_do_cmd(info, NDSR_WRDREQ); + prepare_read_prog_cmd(info, cmdset->program, + info->seqin_column, info->seqin_page_addr); + exec_cmd = 1; break; case NAND_CMD_ERASE1: - if (prepare_erase_cmd(info, cmdset->erase, page_addr)) - break; - - pxa3xx_nand_do_cmd(info, NDSR_CS0_BBD | NDSR_CS0_CMDD); + prepare_erase_cmd(info, cmdset->erase, page_addr); + exec_cmd = 1; break; case NAND_CMD_ERASE2: break; @@ -660,40 +639,69 @@ static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, info->buf_count = (command == NAND_CMD_READID) ? info->read_id_bytes : 1; - if (prepare_other_cmd(info, (command == NAND_CMD_READID) ? - cmdset->read_id : cmdset->read_status)) - break; - - pxa3xx_nand_do_cmd(info, NDSR_RDDREQ); + prepare_other_cmd(info, (command == NAND_CMD_READID) ? + cmdset->read_id : cmdset->read_status); + exec_cmd = 1; break; case NAND_CMD_RESET: - if (prepare_other_cmd(info, cmdset->reset)) - break; - - ret = pxa3xx_nand_do_cmd(info, NDSR_CS0_CMDD); - if (ret == 0) { - int timeout = 2; - uint32_t ndcr; - - while (timeout--) { - if (nand_readl(info, NDSR) & NDSR_RDY) - break; - msleep(10); - } - - ndcr = nand_readl(info, NDCR); - nand_writel(info, NDCR, ndcr & ~NDCR_ND_RUN); - } + prepare_other_cmd(info, cmdset->reset); + exec_cmd = 1; break; default: printk(KERN_ERR "non-supported command.\n"); break; } - if (info->retcode == ERR_DBERR) { - printk(KERN_ERR "double bit error @ page %08x\n", page_addr); - info->retcode = ERR_NONE; + if (exec_cmd) { + init_completion(&info->cmd_complete); + pxa3xx_nand_start(info); + + ret = wait_for_completion_timeout(&info->cmd_complete, + CHIP_DELAY_TIMEOUT); + if (!ret) { + printk(KERN_ERR "Wait time out!!!\n"); + /* Stop State Machine for next command cycle */ + pxa3xx_nand_stop(info); + } + info->state = STATE_IDLE; + } +} + +static void pxa3xx_nand_write_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t *buf) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); +} + +static int pxa3xx_nand_read_page_hwecc(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, int page) +{ + struct pxa3xx_nand_info *info = mtd->priv; + + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + + if (info->retcode == ERR_SBERR) { + switch (info->use_ecc) { + case 1: + mtd->ecc_stats.corrected++; + break; + case 0: + default: + break; + } + } else if (info->retcode == ERR_DBERR) { + /* + * for blank page (all 0xff), HW will calculate its ECC as + * 0, which is different from the ECC information within + * OOB, ignore such double bit errors + */ + if (is_buf_blank(buf, mtd->writesize)) + mtd->ecc_stats.failed++; } + + return 0; } static uint8_t pxa3xx_nand_read_byte(struct mtd_info *mtd) @@ -770,47 +778,13 @@ static int pxa3xx_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *this) return 0; } -static void pxa3xx_nand_ecc_hwctl(struct mtd_info *mtd, int mode) -{ - return; -} - -static int pxa3xx_nand_ecc_calculate(struct mtd_info *mtd, - const uint8_t *dat, uint8_t *ecc_code) -{ - return 0; -} - -static int pxa3xx_nand_ecc_correct(struct mtd_info *mtd, - uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc) -{ - struct pxa3xx_nand_info *info = mtd->priv; - /* - * Any error include ERR_SEND_CMD, ERR_DBERR, ERR_BUSERR, we - * consider it as a ecc error which will tell the caller the - * read fail We have distinguish all the errors, but the - * nand_read_ecc only check this function return value - * - * Corrected (single-bit) errors must also be noted. - */ - if (info->retcode == ERR_SBERR) - return 1; - else if (info->retcode != ERR_NONE) - return -1; - - return 0; -} - static int __readid(struct pxa3xx_nand_info *info, uint32_t *id) { const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; uint32_t ndcr; uint8_t id_buff[8]; - if (prepare_other_cmd(info, cmdset->read_id)) { - printk(KERN_ERR "failed to prepare command\n"); - return -EINVAL; - } + prepare_other_cmd(info, cmdset->read_id); /* Send command */ if (write_cmd(info)) @@ -836,7 +810,7 @@ static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, { struct platform_device *pdev = info->pdev; struct pxa3xx_nand_platform_data *pdata = pdev->dev.platform_data; - uint32_t ndcr = 0x00000FFF; /* disable all interrupts */ + uint32_t ndcr = 0x0; /* enable all interrupts */ if (f->page_size != 2048 && f->page_size != 512) return -EINVAL; @@ -888,11 +862,12 @@ static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) info->reg_ndcr = ndcr; info->cmdset = &default_cmdset; - if (__readid(info, &id)) + pxa3xx_nand_cmdfunc(info->mtd, NAND_CMD_READID, 0, 0); + id = *((uint16_t *)(info->data_buff)); + if (id == 0) return -ENODEV; /* Lookup the flash id */ - id = (id >> 8) & 0xff; /* device id is byte 2 */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { if (id == nand_flash_ids[i].id) { type = &nand_flash_ids[i]; @@ -935,8 +910,8 @@ static int pxa3xx_nand_detect_flash(struct pxa3xx_nand_info *info, /* we use default timing to detect id */ f = DEFAULT_FLASH_TYPE; pxa3xx_nand_config_flash(info, f); - if (__readid(info, &id)) - goto fail_detect; + pxa3xx_nand_cmdfunc(info->mtd, NAND_CMD_READID, 0, 0); + id = *((uint16_t *)(info->data_buff)); for (i=0; inum_flash - 1; i++) { /* we first choose the flash definition from platfrom */ @@ -954,7 +929,6 @@ static int pxa3xx_nand_detect_flash(struct pxa3xx_nand_info *info, dev_warn(&info->pdev->dev, "failed to detect configured nand flash; found %04x instead of\n", id); -fail_detect: return -ENODEV; } @@ -1025,6 +999,8 @@ static void pxa3xx_nand_init_mtd(struct mtd_info *mtd, this->select_chip = pxa3xx_nand_select_chip; this->dev_ready = pxa3xx_nand_dev_ready; this->cmdfunc = pxa3xx_nand_cmdfunc; + this->ecc.read_page = pxa3xx_nand_read_page_hwecc; + this->ecc.write_page = pxa3xx_nand_write_page_hwecc; this->read_word = pxa3xx_nand_read_word; this->read_byte = pxa3xx_nand_read_byte; this->read_buf = pxa3xx_nand_read_buf; @@ -1032,9 +1008,6 @@ static void pxa3xx_nand_init_mtd(struct mtd_info *mtd, 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 = info->page_size; if (info->page_size == 2048) @@ -1177,10 +1150,6 @@ static int pxa3xx_nand_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); - del_mtd_device(mtd); -#ifdef CONFIG_MTD_PARTITIONS - del_mtd_partitions(mtd); -#endif irq = platform_get_irq(pdev, 0); if (irq >= 0) free_irq(irq, info); @@ -1198,7 +1167,13 @@ static int pxa3xx_nand_remove(struct platform_device *pdev) clk_disable(info->clk); clk_put(info->clk); - kfree(mtd); + if (mtd) { + del_mtd_device(mtd); +#ifdef CONFIG_MTD_PARTITIONS + del_mtd_partitions(mtd); +#endif + kfree(mtd); + } return 0; } @@ -1232,10 +1207,10 @@ static int pxa3xx_nand_probe(struct platform_device *pdev) nr_parts = parse_mtd_partitions(info->mtd, probes, &parts, 0); if (nr_parts) - return add_mtd_partitions(mtd, parts, nr_parts); + return add_mtd_partitions(info->mtd, parts, nr_parts); } - return add_mtd_partitions(mtd, pdata->parts, pdata->nr_parts); + return add_mtd_partitions(info->mtd, pdata->parts, pdata->nr_parts); #else return 0; #endif @@ -1247,7 +1222,7 @@ static int pxa3xx_nand_suspend(struct platform_device *pdev, pm_message_t state) struct pxa3xx_nand_info *info = platform_get_drvdata(pdev); struct mtd_info *mtd = info->mtd; - if (info->state != STATE_READY) { + if (info->state) { dev_err(&pdev->dev, "driver busy, state = %d\n", info->state); return -EAGAIN; } -- cgit v0.10.2 From 38caf7f68e6b4ad84094095887a490e3fa2ec501 Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Mon, 28 Feb 2011 10:32:12 +0800 Subject: mtd: pxa3xx_nand: discard wait_for_event,write_cmd,__readid function Since we have rework the irq process, we don't need additional delay in wait_for_event. Also write_cmd and __readid is also discarded for the same reason. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Acked-by: Eric Miao Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index 3264b1d..be0aa44 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -258,25 +258,6 @@ static void pxa3xx_nand_set_timing(struct pxa3xx_nand_info *info, nand_writel(info, NDTR1CS0, ndtr1); } -#define WAIT_EVENT_TIMEOUT 10 - -static int wait_for_event(struct pxa3xx_nand_info *info, uint32_t event) -{ - int timeout = WAIT_EVENT_TIMEOUT; - uint32_t ndsr; - - while (timeout--) { - ndsr = nand_readl(info, NDSR) & NDSR_MASK; - if (ndsr & event) { - nand_writel(info, NDSR, ndsr); - return 0; - } - udelay(10); - } - - return -ETIMEDOUT; -} - static void pxa3xx_set_datasize(struct pxa3xx_nand_info *info) { int oob_enable = info->reg_ndcr & NDCR_SPARE_EN; @@ -414,35 +395,6 @@ static void disable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) nand_writel(info, NDCR, ndcr | int_mask); } -/* NOTE: it is a must to set ND_RUN firstly, then write command buffer - * otherwise, it does not work - */ -static int write_cmd(struct pxa3xx_nand_info *info) -{ - uint32_t ndcr; - - /* clear status bits and run */ - nand_writel(info, NDSR, NDSR_MASK); - - ndcr = info->reg_ndcr; - - ndcr |= info->use_ecc ? NDCR_ECC_EN : 0; - ndcr |= info->use_dma ? NDCR_DMA_EN : 0; - ndcr |= NDCR_ND_RUN; - - nand_writel(info, NDCR, ndcr); - - if (wait_for_event(info, NDSR_WRCMDREQ)) { - printk(KERN_ERR "timed out writing command\n"); - return -ETIMEDOUT; - } - - nand_writel(info, NDCB0, info->ndcb0); - nand_writel(info, NDCB0, info->ndcb1); - nand_writel(info, NDCB0, info->ndcb2); - return 0; -} - static void handle_data_pio(struct pxa3xx_nand_info *info) { switch (info->state) { @@ -778,33 +730,6 @@ static int pxa3xx_nand_waitfunc(struct mtd_info *mtd, struct nand_chip *this) return 0; } -static int __readid(struct pxa3xx_nand_info *info, uint32_t *id) -{ - const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; - uint32_t ndcr; - uint8_t id_buff[8]; - - prepare_other_cmd(info, cmdset->read_id); - - /* Send command */ - if (write_cmd(info)) - goto fail_timeout; - - /* Wait for CMDDM(command done successfully) */ - if (wait_for_event(info, NDSR_RDDREQ)) - goto fail_timeout; - - __raw_readsl(info->mmio_base + NDDB, id_buff, 2); - *id = id_buff[0] | (id_buff[1] << 8); - return 0; - -fail_timeout: - ndcr = nand_readl(info, NDCR); - nand_writel(info, NDCR, ndcr & ~NDCR_ND_RUN); - udelay(10); - return -ETIMEDOUT; -} - static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, const struct pxa3xx_nand_flash *f) { @@ -857,7 +782,7 @@ static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) page_per_block = ndcr & NDCR_PG_PER_BLK ? 64 : 32; info->page_size = ndcr & NDCR_PAGE_SZ ? 2048 : 512; - /* set info fields needed to __readid */ + /* set info fields needed to read id */ info->read_id_bytes = (info->page_size == 2048) ? 4 : 2; info->reg_ndcr = ndcr; info->cmdset = &default_cmdset; -- cgit v0.10.2 From 4eb2da8994042d68e84e31138788429a102da2ea Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Mon, 28 Feb 2011 10:32:13 +0800 Subject: mtd: pxa3xx_nand: unify prepare command Make the interface simpler which could make both debug and enhancement easier. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index be0aa44..de0a2a2 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -28,6 +28,7 @@ #define CHIP_DELAY_TIMEOUT (2 * HZ/10) #define NAND_STOP_DELAY (2 * HZ/50) +#define PAGE_CHUNK_SIZE (2048) /* registers and bit definitions */ #define NDCR (0x00) /* Control register */ @@ -77,6 +78,7 @@ #define NDSR_RDDREQ (0x1 << 1) #define NDSR_WRCMDREQ (0x1) +#define NDCB0_ST_ROW_EN (0x1 << 26) #define NDCB0_AUTO_RS (0x1 << 25) #define NDCB0_CSEL (0x1 << 24) #define NDCB0_CMD_TYPE_MASK (0x7 << 21) @@ -319,66 +321,6 @@ static void pxa3xx_nand_stop(struct pxa3xx_nand_info *info) nand_writel(info, NDSR, NDSR_MASK); } -static void prepare_read_prog_cmd(struct pxa3xx_nand_info *info, - uint16_t cmd, int column, int page_addr) -{ - const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; - pxa3xx_set_datasize(info); - - /* generate values for NDCBx registers */ - info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0); - info->ndcb1 = 0; - info->ndcb2 = 0; - info->ndcb0 |= NDCB0_ADDR_CYC(info->row_addr_cycles + info->col_addr_cycles); - - if (info->col_addr_cycles == 2) { - /* large block, 2 cycles for column address - * row address starts from 3rd cycle - */ - info->ndcb1 |= page_addr << 16; - if (info->row_addr_cycles == 3) - info->ndcb2 = (page_addr >> 16) & 0xff; - } else - /* small block, 1 cycles for column address - * row address starts from 2nd cycle - */ - info->ndcb1 = page_addr << 8; - - if (cmd == cmdset->program) - info->ndcb0 |= NDCB0_CMD_TYPE(1) | NDCB0_AUTO_RS; -} - -static void prepare_erase_cmd(struct pxa3xx_nand_info *info, - uint16_t cmd, int page_addr) -{ - info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0); - info->ndcb0 |= NDCB0_CMD_TYPE(2) | NDCB0_AUTO_RS | NDCB0_ADDR_CYC(3); - info->ndcb1 = page_addr; - info->ndcb2 = 0; -} - -static void prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd) -{ - const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; - - info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0); - info->ndcb1 = 0; - info->ndcb2 = 0; - - info->oob_size = 0; - if (cmd == cmdset->read_id) { - info->ndcb0 |= NDCB0_CMD_TYPE(3) | NDCB0_ADDR_CYC(1); - info->data_size = 8; - } else if (cmd == cmdset->read_status) { - info->ndcb0 |= NDCB0_CMD_TYPE(4); - info->data_size = 8; - } else if (cmd == cmdset->reset || cmd == cmdset->lock || - cmd == cmdset->unlock) { - info->ndcb0 |= NDCB0_CMD_TYPE(5); - } else - BUG(); -} - static void enable_int(struct pxa3xx_nand_info *info, uint32_t int_mask) { uint32_t ndcr; @@ -529,81 +471,167 @@ static inline int is_buf_blank(uint8_t *buf, size_t len) return 1; } -static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, - int column, int page_addr) +static int prepare_command_pool(struct pxa3xx_nand_info *info, int command, + uint16_t column, int page_addr) { - struct pxa3xx_nand_info *info = mtd->priv; - const struct pxa3xx_nand_cmdset *cmdset = info->cmdset; - int ret, exec_cmd = 0; + uint16_t cmd; + int addr_cycle, exec_cmd, ndcb0; + struct mtd_info *mtd = info->mtd; + + ndcb0 = 0; + addr_cycle = 0; + exec_cmd = 1; - info->use_dma = (use_dma) ? 1 : 0; - info->use_ecc = 0; - info->data_size = 0; - info->state = 0; - info->retcode = ERR_NONE; + /* reset data and oob column point to handle data */ + info->buf_start = 0; + info->buf_count = 0; + info->oob_size = 0; + info->use_ecc = 0; + info->retcode = ERR_NONE; switch (command) { + case NAND_CMD_READ0: + case NAND_CMD_PAGEPROG: + info->use_ecc = 1; case NAND_CMD_READOOB: - /* disable HW ECC to get all the OOB data */ - info->buf_count = mtd->writesize + mtd->oobsize; - info->buf_start = mtd->writesize + column; - memset(info->data_buff, 0xFF, info->buf_count); - - prepare_read_prog_cmd(info, cmdset->read1, column, page_addr); - exec_cmd = 1; + pxa3xx_set_datasize(info); + break; + case NAND_CMD_SEQIN: + exec_cmd = 0; + break; + default: + info->ndcb1 = 0; + info->ndcb2 = 0; break; + } + + info->ndcb0 = ndcb0; + addr_cycle = NDCB0_ADDR_CYC(info->row_addr_cycles + + info->col_addr_cycles); + switch (command) { + case NAND_CMD_READOOB: case NAND_CMD_READ0: - info->use_ecc = 1; - info->buf_start = column; - info->buf_count = mtd->writesize + mtd->oobsize; - memset(info->data_buff, 0xFF, info->buf_count); + cmd = info->cmdset->read1; + if (command == NAND_CMD_READOOB) + info->buf_start = mtd->writesize + column; + else + info->buf_start = column; + + if (unlikely(info->page_size < PAGE_CHUNK_SIZE)) + info->ndcb0 |= NDCB0_CMD_TYPE(0) + | addr_cycle + | (cmd & NDCB0_CMD1_MASK); + else + info->ndcb0 |= NDCB0_CMD_TYPE(0) + | NDCB0_DBC + | addr_cycle + | cmd; - prepare_read_prog_cmd(info, cmdset->read1, column, page_addr); - exec_cmd = 1; - break; case NAND_CMD_SEQIN: - info->buf_start = column; + /* small page addr setting */ + if (unlikely(info->page_size < PAGE_CHUNK_SIZE)) { + info->ndcb1 = ((page_addr & 0xFFFFFF) << 8) + | (column & 0xFF); + + info->ndcb2 = 0; + } else { + info->ndcb1 = ((page_addr & 0xFFFF) << 16) + | (column & 0xFFFF); + + if (page_addr & 0xFF0000) + info->ndcb2 = (page_addr & 0xFF0000) >> 16; + else + info->ndcb2 = 0; + } + info->buf_count = mtd->writesize + mtd->oobsize; - memset(info->data_buff, 0xff, info->buf_count); + memset(info->data_buff, 0xFF, info->buf_count); - /* save column/page_addr for next CMD_PAGEPROG */ - info->seqin_column = column; - info->seqin_page_addr = page_addr; break; + case NAND_CMD_PAGEPROG: - info->use_ecc = (info->seqin_column >= mtd->writesize) ? 0 : 1; + if (is_buf_blank(info->data_buff, + (mtd->writesize + mtd->oobsize))) { + exec_cmd = 0; + break; + } - prepare_read_prog_cmd(info, cmdset->program, - info->seqin_column, info->seqin_page_addr); - exec_cmd = 1; - break; - case NAND_CMD_ERASE1: - prepare_erase_cmd(info, cmdset->erase, page_addr); - exec_cmd = 1; - break; - case NAND_CMD_ERASE2: + cmd = info->cmdset->program; + info->ndcb0 |= NDCB0_CMD_TYPE(0x1) + | NDCB0_AUTO_RS + | NDCB0_ST_ROW_EN + | NDCB0_DBC + | cmd + | addr_cycle; break; + case NAND_CMD_READID: + cmd = info->cmdset->read_id; + info->buf_count = info->read_id_bytes; + info->ndcb0 |= NDCB0_CMD_TYPE(3) + | NDCB0_ADDR_CYC(1) + | cmd; + + info->data_size = 8; + break; case NAND_CMD_STATUS: - info->use_dma = 0; /* force PIO read */ - info->buf_start = 0; - info->buf_count = (command == NAND_CMD_READID) ? - info->read_id_bytes : 1; - - prepare_other_cmd(info, (command == NAND_CMD_READID) ? - cmdset->read_id : cmdset->read_status); - exec_cmd = 1; + cmd = info->cmdset->read_status; + info->buf_count = 1; + info->ndcb0 |= NDCB0_CMD_TYPE(4) + | NDCB0_ADDR_CYC(1) + | cmd; + + info->data_size = 8; + break; + + case NAND_CMD_ERASE1: + cmd = info->cmdset->erase; + info->ndcb0 |= NDCB0_CMD_TYPE(2) + | NDCB0_AUTO_RS + | NDCB0_ADDR_CYC(3) + | NDCB0_DBC + | cmd; + info->ndcb1 = page_addr; + info->ndcb2 = 0; + break; case NAND_CMD_RESET: - prepare_other_cmd(info, cmdset->reset); - exec_cmd = 1; + cmd = info->cmdset->reset; + info->ndcb0 |= NDCB0_CMD_TYPE(5) + | cmd; + + break; + + case NAND_CMD_ERASE2: + exec_cmd = 0; break; + default: - printk(KERN_ERR "non-supported command.\n"); + exec_cmd = 0; + printk(KERN_ERR "pxa3xx-nand: non-supported" + " command %x\n", command); break; } + return exec_cmd; +} + +static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct pxa3xx_nand_info *info = mtd->priv; + int ret, exec_cmd; + + /* + * if this is a x16 device ,then convert the input + * "byte" address into a "word" address appropriate + * for indexing a word-oriented device + */ + if (info->reg_ndcr & NDCR_DWIDTH_M) + column /= 2; + + exec_cmd = prepare_command_pool(info, command, column, page_addr); if (exec_cmd) { init_completion(&info->cmd_complete); pxa3xx_nand_start(info); @@ -919,6 +947,7 @@ static void pxa3xx_nand_init_mtd(struct mtd_info *mtd, struct nand_chip *this = &info->nand_chip; this->options = (info->reg_ndcr & NDCR_DWIDTH_C) ? NAND_BUSWIDTH_16: 0; + this->options |= NAND_NO_AUTOINCR; this->waitfunc = pxa3xx_nand_waitfunc; this->select_chip = pxa3xx_nand_select_chip; -- cgit v0.10.2 From 401e67e225f350757e11e39392dcc6a0fddcea63 Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Mon, 28 Feb 2011 10:32:14 +0800 Subject: mtd: pxa3xx_nand: mtd scan id process could be defined by driver itself Different NAND driver may require its unique detection. For pxa3xx_nand, it use its self id database to get the necessary info. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index de0a2a2..bb50cf2 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -123,6 +123,7 @@ enum { struct pxa3xx_nand_info { struct nand_chip nand_chip; + struct nand_hw_control controller; struct platform_device *pdev; struct pxa3xx_nand_cmdset *cmdset; @@ -157,6 +158,7 @@ struct pxa3xx_nand_info { int use_ecc; /* use HW ECC ? */ int use_dma; /* use DMA ? */ + int is_ready; unsigned int page_size; /* page size of attached chip */ unsigned int data_size; /* data size in FIFO */ @@ -223,6 +225,8 @@ static struct pxa3xx_nand_flash builtin_flash_types[] = { /* Define a default flash type setting serve as flash detecting only */ #define DEFAULT_FLASH_TYPE (&builtin_flash_types[0]) +const char *mtd_names[] = {"pxa3xx_nand-0", NULL}; + #define NDTR0_tCH(c) (min((c), 7) << 19) #define NDTR0_tCS(c) (min((c), 7) << 16) #define NDTR0_tWH(c) (min((c), 7) << 11) @@ -437,8 +441,10 @@ static irqreturn_t pxa3xx_nand_irq(int irq, void *devid) info->state = STATE_CMD_DONE; is_completed = 1; } - if (status & NDSR_FLASH_RDY) + if (status & NDSR_FLASH_RDY) { + info->is_ready = 1; info->state = STATE_READY; + } if (status & NDSR_WRCMDREQ) { nand_writel(info, NDSR, NDSR_WRCMDREQ); @@ -483,10 +489,11 @@ static int prepare_command_pool(struct pxa3xx_nand_info *info, int command, exec_cmd = 1; /* reset data and oob column point to handle data */ - info->buf_start = 0; - info->buf_count = 0; + info->buf_start = 0; + info->buf_count = 0; info->oob_size = 0; info->use_ecc = 0; + info->is_ready = 0; info->retcode = ERR_NONE; switch (command) { @@ -849,42 +856,6 @@ static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) return 0; } -static int pxa3xx_nand_detect_flash(struct pxa3xx_nand_info *info, - const struct pxa3xx_nand_platform_data *pdata) -{ - const struct pxa3xx_nand_flash *f; - uint32_t id = -1; - int i; - - if (pdata->keep_config) - if (pxa3xx_nand_detect_config(info) == 0) - return 0; - - /* we use default timing to detect id */ - f = DEFAULT_FLASH_TYPE; - pxa3xx_nand_config_flash(info, f); - pxa3xx_nand_cmdfunc(info->mtd, NAND_CMD_READID, 0, 0); - id = *((uint16_t *)(info->data_buff)); - - for (i=0; inum_flash - 1; i++) { - /* we first choose the flash definition from platfrom */ - if (i < pdata->num_flash) - f = pdata->flash + i; - else - f = &builtin_flash_types[i - pdata->num_flash + 1]; - if (f->chip_id == id) { - dev_info(&info->pdev->dev, "detect chip id: 0x%x\n", id); - pxa3xx_nand_config_flash(info, f); - return 0; - } - } - - dev_warn(&info->pdev->dev, - "failed to detect configured nand flash; found %04x instead of\n", - id); - return -ENODEV; -} - /* the maximum possible buffer size for large page with OOB data * is: 2048 + 64 = 2112 bytes, allocate a page here for both the * data buffer and the DMA descriptor @@ -926,57 +897,110 @@ static int pxa3xx_nand_init_buff(struct pxa3xx_nand_info *info) return 0; } -static struct nand_ecclayout hw_smallpage_ecclayout = { - .eccbytes = 6, - .eccpos = {8, 9, 10, 11, 12, 13 }, - .oobfree = { {2, 6} } -}; +static int pxa3xx_nand_sensing(struct pxa3xx_nand_info *info) +{ + struct mtd_info *mtd = info->mtd; + struct nand_chip *chip = mtd->priv; -static struct nand_ecclayout hw_largepage_ecclayout = { - .eccbytes = 24, - .eccpos = { - 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63}, - .oobfree = { {2, 38} } -}; + /* use the common timing to make a try */ + pxa3xx_nand_config_flash(info, &builtin_flash_types[0]); + chip->cmdfunc(mtd, NAND_CMD_RESET, 0, 0); + if (info->is_ready) + return 1; + else + return 0; +} -static void pxa3xx_nand_init_mtd(struct mtd_info *mtd, - struct pxa3xx_nand_info *info) +static int pxa3xx_nand_scan(struct mtd_info *mtd) { - struct nand_chip *this = &info->nand_chip; - - this->options = (info->reg_ndcr & NDCR_DWIDTH_C) ? NAND_BUSWIDTH_16: 0; - this->options |= NAND_NO_AUTOINCR; - - 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->ecc.read_page = pxa3xx_nand_read_page_hwecc; - this->ecc.write_page = pxa3xx_nand_write_page_hwecc; - 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.size = info->page_size; - - if (info->page_size == 2048) - this->ecc.layout = &hw_largepage_ecclayout; - else - this->ecc.layout = &hw_smallpage_ecclayout; + struct pxa3xx_nand_info *info = mtd->priv; + struct platform_device *pdev = info->pdev; + struct pxa3xx_nand_platform_data *pdata = pdev->dev.platform_data; + const struct pxa3xx_nand_flash *f = NULL; + struct nand_chip *chip = mtd->priv; + uint32_t id = -1; + int i, ret, num; + + if (pdata->keep_config && !pxa3xx_nand_detect_config(info)) + return 0; + + ret = pxa3xx_nand_sensing(info); + if (!ret) { + kfree(mtd); + info->mtd = NULL; + printk(KERN_INFO "There is no nand chip on cs 0!\n"); + + return -EINVAL; + } + + chip->cmdfunc(mtd, NAND_CMD_READID, 0, 0); + id = *((uint16_t *)(info->data_buff)); + if (id != 0) + printk(KERN_INFO "Detect a flash id %x\n", id); + else { + kfree(mtd); + info->mtd = NULL; + printk(KERN_WARNING "Read out ID 0, potential timing set wrong!!\n"); + + return -EINVAL; + } + + num = ARRAY_SIZE(builtin_flash_types) + pdata->num_flash - 1; + for (i = 0; i < num; i++) { + if (i < pdata->num_flash) + f = pdata->flash + i; + else + f = &builtin_flash_types[i - pdata->num_flash + 1]; + + /* find the chip in default list */ + if (f->chip_id == id) { + pxa3xx_nand_config_flash(info, f); + mtd->writesize = f->page_size; + mtd->writesize_shift = ffs(mtd->writesize) - 1; + mtd->writesize_mask = (1 << mtd->writesize_shift) - 1; + mtd->oobsize = mtd->writesize / 32; + mtd->erasesize = f->page_size * f->page_per_block; + mtd->erasesize_shift = ffs(mtd->erasesize) - 1; + mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1; + + mtd->name = mtd_names[0]; + break; + } + } + + if (i >= (ARRAY_SIZE(builtin_flash_types) + pdata->num_flash)) { + kfree(mtd); + info->mtd = NULL; + printk(KERN_ERR "ERROR!! flash not defined!!!\n"); + + return -EINVAL; + } + + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = f->page_size; + chip->chipsize = (uint64_t)f->num_blocks * f->page_per_block + * f->page_size; + mtd->size = chip->chipsize; + + /* Calculate the address shift from the page size */ + chip->page_shift = ffs(mtd->writesize) - 1; + chip->pagemask = mtd_div_by_ws(chip->chipsize, mtd) - 1; + chip->numchips = 1; + chip->phys_erase_shift = ffs(mtd->erasesize) - 1; + chip->bbt_erase_shift = chip->phys_erase_shift; + + chip->options = (f->flash_width == 16) ? NAND_BUSWIDTH_16 : 0; + chip->options |= NAND_NO_AUTOINCR; + chip->options |= NAND_NO_READRDY; - this->chip_delay = 25; + return nand_scan_tail(mtd); } static struct pxa3xx_nand_info *alloc_nand_resource(struct platform_device *pdev) { - struct pxa3xx_nand_platform_data *pdata = pdev->dev.platform_data; struct pxa3xx_nand_info *info; + struct nand_chip *chip; struct mtd_info *mtd; struct resource *r; int ret, irq; @@ -989,12 +1013,27 @@ struct pxa3xx_nand_info *alloc_nand_resource(struct platform_device *pdev) } info = (struct pxa3xx_nand_info *)(&mtd[1]); + chip = (struct nand_chip *)(&mtd[1]); info->pdev = pdev; - - mtd->priv = info; info->mtd = mtd; + mtd->priv = info; mtd->owner = THIS_MODULE; + chip->ecc.read_page = pxa3xx_nand_read_page_hwecc; + chip->ecc.write_page = pxa3xx_nand_write_page_hwecc; + chip->controller = &info->controller; + chip->waitfunc = pxa3xx_nand_waitfunc; + chip->select_chip = pxa3xx_nand_select_chip; + chip->dev_ready = pxa3xx_nand_dev_ready; + chip->cmdfunc = pxa3xx_nand_cmdfunc; + chip->read_word = pxa3xx_nand_read_word; + chip->read_byte = pxa3xx_nand_read_byte; + chip->read_buf = pxa3xx_nand_read_buf; + chip->write_buf = pxa3xx_nand_write_buf; + chip->verify_buf = pxa3xx_nand_verify_buf; + + spin_lock_init(&chip->controller->lock); + init_waitqueue_head(&chip->controller->wq); info->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(info->clk)) { dev_err(&pdev->dev, "failed to get nand clock\n"); @@ -1062,21 +1101,12 @@ struct pxa3xx_nand_info *alloc_nand_resource(struct platform_device *pdev) goto fail_free_buf; } - ret = pxa3xx_nand_detect_flash(info, pdata); - if (ret) { - dev_err(&pdev->dev, "failed to detect flash\n"); - ret = -ENODEV; - goto fail_free_irq; - } - - pxa3xx_nand_init_mtd(mtd, info); platform_set_drvdata(pdev, info); return info; -fail_free_irq: - free_irq(irq, info); fail_free_buf: + free_irq(irq, info); if (use_dma) { pxa_free_dma(info->data_dma_ch); dma_free_coherent(&pdev->dev, info->data_buff_size, @@ -1146,7 +1176,7 @@ static int pxa3xx_nand_probe(struct platform_device *pdev) if (info == NULL) return -ENOMEM; - if (nand_scan(info->mtd, 1)) { + if (pxa3xx_nand_scan(info->mtd)) { dev_err(&pdev->dev, "failed to scan nand\n"); pxa3xx_nand_remove(pdev); return -ENODEV; -- cgit v0.10.2 From 4332c116869ceca58638beabd9e9a5d7db4cef83 Mon Sep 17 00:00:00 2001 From: Lei Wen Date: Thu, 3 Mar 2011 11:27:01 +0800 Subject: mtd: pxa3xx_nand: clean the keep configure code Use nand_scan_ident to unify the need of mtd member initilization for both normal detection and keep configuration method. Signed-off-by: Lei Wen Signed-off-by: Haojian Zhuang Signed-off-by: David Woodhouse diff --git a/arch/arm/plat-pxa/include/plat/pxa3xx_nand.h b/arch/arm/plat-pxa/include/plat/pxa3xx_nand.h index 01a8448..442301f 100644 --- a/arch/arm/plat-pxa/include/plat/pxa3xx_nand.h +++ b/arch/arm/plat-pxa/include/plat/pxa3xx_nand.h @@ -30,6 +30,7 @@ struct pxa3xx_nand_cmdset { }; struct pxa3xx_nand_flash { + char *name; uint32_t chip_id; unsigned int page_per_block; /* Pages per block (PG_PER_BLK) */ unsigned int page_size; /* Page size in bytes (PAGE_SZ) */ @@ -37,7 +38,6 @@ struct pxa3xx_nand_flash { unsigned int dfc_width; /* Width of flash controller(DWIDTH_C) */ unsigned int num_blocks; /* Number of physical blocks in Flash */ - struct pxa3xx_nand_cmdset *cmdset; /* NAND command set */ struct pxa3xx_nand_timing *timing; /* NAND Flash timing */ }; diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c index bb50cf2..ab7f4c3 100644 --- a/drivers/mtd/nand/pxa3xx_nand.c +++ b/drivers/mtd/nand/pxa3xx_nand.c @@ -211,15 +211,15 @@ static struct pxa3xx_nand_timing timing[] = { }; static struct pxa3xx_nand_flash builtin_flash_types[] = { - { 0, 0, 2048, 8, 8, 0, &default_cmdset, &timing[0] }, - { 0x46ec, 32, 512, 16, 16, 4096, &default_cmdset, &timing[1] }, - { 0xdaec, 64, 2048, 8, 8, 2048, &default_cmdset, &timing[1] }, - { 0xd7ec, 128, 4096, 8, 8, 8192, &default_cmdset, &timing[1] }, - { 0xa12c, 64, 2048, 8, 8, 1024, &default_cmdset, &timing[2] }, - { 0xb12c, 64, 2048, 16, 16, 1024, &default_cmdset, &timing[2] }, - { 0xdc2c, 64, 2048, 8, 8, 4096, &default_cmdset, &timing[2] }, - { 0xcc2c, 64, 2048, 16, 16, 4096, &default_cmdset, &timing[2] }, - { 0xba20, 64, 2048, 16, 16, 2048, &default_cmdset, &timing[3] }, +{ "DEFAULT FLASH", 0, 0, 2048, 8, 8, 0, &timing[0] }, +{ "64MiB 16-bit", 0x46ec, 32, 512, 16, 16, 4096, &timing[1] }, +{ "256MiB 8-bit", 0xdaec, 64, 2048, 8, 8, 2048, &timing[1] }, +{ "4GiB 8-bit", 0xd7ec, 128, 4096, 8, 8, 8192, &timing[1] }, +{ "128MiB 8-bit", 0xa12c, 64, 2048, 8, 8, 1024, &timing[2] }, +{ "128MiB 16-bit", 0xb12c, 64, 2048, 16, 16, 1024, &timing[2] }, +{ "512MiB 8-bit", 0xdc2c, 64, 2048, 8, 8, 4096, &timing[2] }, +{ "512MiB 16-bit", 0xcc2c, 64, 2048, 16, 16, 4096, &timing[2] }, +{ "256MiB 16-bit", 0xba20, 64, 2048, 16, 16, 2048, &timing[3] }, }; /* Define a default flash type setting serve as flash detecting only */ @@ -779,9 +779,8 @@ static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, return -EINVAL; /* calculate flash information */ - info->cmdset = f->cmdset; + info->cmdset = &default_cmdset; info->page_size = f->page_size; - info->oob_buff = info->data_buff + f->page_size; info->read_id_bytes = (f->page_size == 2048) ? 4 : 2; /* calculate addressing information */ @@ -811,45 +810,12 @@ static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info, static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) { uint32_t ndcr = nand_readl(info, NDCR); - struct nand_flash_dev *type = NULL; - uint32_t id = -1, page_per_block, num_blocks; - int i; - - page_per_block = ndcr & NDCR_PG_PER_BLK ? 64 : 32; info->page_size = ndcr & NDCR_PAGE_SZ ? 2048 : 512; /* set info fields needed to read id */ info->read_id_bytes = (info->page_size == 2048) ? 4 : 2; info->reg_ndcr = ndcr; info->cmdset = &default_cmdset; - pxa3xx_nand_cmdfunc(info->mtd, NAND_CMD_READID, 0, 0); - id = *((uint16_t *)(info->data_buff)); - if (id == 0) - return -ENODEV; - - /* Lookup the flash id */ - for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (id == nand_flash_ids[i].id) { - type = &nand_flash_ids[i]; - break; - } - } - - if (!type) - return -ENODEV; - - /* fill the missing flash information */ - i = __ffs(page_per_block * info->page_size); - num_blocks = type->chipsize << (20 - i); - - /* calculate addressing information */ - info->col_addr_cycles = (info->page_size == 2048) ? 2 : 1; - - if (num_blocks * page_per_block > 65536) - info->row_addr_cycles = 3; - else - info->row_addr_cycles = 2; - info->ndtr0cs0 = nand_readl(info, NDTR0CS0); info->ndtr1cs0 = nand_readl(info, NDTR1CS0); @@ -916,13 +882,15 @@ static int pxa3xx_nand_scan(struct mtd_info *mtd) struct pxa3xx_nand_info *info = mtd->priv; struct platform_device *pdev = info->pdev; struct pxa3xx_nand_platform_data *pdata = pdev->dev.platform_data; + struct nand_flash_dev pxa3xx_flash_ids[2] = { {NULL,}, {NULL,} }; const struct pxa3xx_nand_flash *f = NULL; struct nand_chip *chip = mtd->priv; uint32_t id = -1; + uint64_t chipsize; int i, ret, num; if (pdata->keep_config && !pxa3xx_nand_detect_config(info)) - return 0; + goto KEEP_CONFIG; ret = pxa3xx_nand_sensing(info); if (!ret) { @@ -953,22 +921,11 @@ static int pxa3xx_nand_scan(struct mtd_info *mtd) f = &builtin_flash_types[i - pdata->num_flash + 1]; /* find the chip in default list */ - if (f->chip_id == id) { - pxa3xx_nand_config_flash(info, f); - mtd->writesize = f->page_size; - mtd->writesize_shift = ffs(mtd->writesize) - 1; - mtd->writesize_mask = (1 << mtd->writesize_shift) - 1; - mtd->oobsize = mtd->writesize / 32; - mtd->erasesize = f->page_size * f->page_per_block; - mtd->erasesize_shift = ffs(mtd->erasesize) - 1; - mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1; - - mtd->name = mtd_names[0]; + if (f->chip_id == id) break; - } } - if (i >= (ARRAY_SIZE(builtin_flash_types) + pdata->num_flash)) { + if (i >= (ARRAY_SIZE(builtin_flash_types) + pdata->num_flash - 1)) { kfree(mtd); info->mtd = NULL; printk(KERN_ERR "ERROR!! flash not defined!!!\n"); @@ -976,18 +933,28 @@ static int pxa3xx_nand_scan(struct mtd_info *mtd) return -EINVAL; } + pxa3xx_nand_config_flash(info, f); + pxa3xx_flash_ids[0].name = f->name; + pxa3xx_flash_ids[0].id = (f->chip_id >> 8) & 0xffff; + pxa3xx_flash_ids[0].pagesize = f->page_size; + chipsize = (uint64_t)f->num_blocks * f->page_per_block * f->page_size; + pxa3xx_flash_ids[0].chipsize = chipsize >> 20; + pxa3xx_flash_ids[0].erasesize = f->page_size * f->page_per_block; + if (f->flash_width == 16) + pxa3xx_flash_ids[0].options = NAND_BUSWIDTH_16; +KEEP_CONFIG: + if (nand_scan_ident(mtd, 1, pxa3xx_flash_ids)) + return -ENODEV; + /* calculate addressing information */ + info->col_addr_cycles = (mtd->writesize >= 2048) ? 2 : 1; + info->oob_buff = info->data_buff + mtd->writesize; + if ((mtd->size >> chip->page_shift) > 65536) + info->row_addr_cycles = 3; + else + info->row_addr_cycles = 2; + mtd->name = mtd_names[0]; chip->ecc.mode = NAND_ECC_HW; chip->ecc.size = f->page_size; - chip->chipsize = (uint64_t)f->num_blocks * f->page_per_block - * f->page_size; - mtd->size = chip->chipsize; - - /* Calculate the address shift from the page size */ - chip->page_shift = ffs(mtd->writesize) - 1; - chip->pagemask = mtd_div_by_ws(chip->chipsize, mtd) - 1; - chip->numchips = 1; - chip->phys_erase_shift = ffs(mtd->erasesize) - 1; - chip->bbt_erase_shift = chip->phys_erase_shift; chip->options = (f->flash_width == 16) ? NAND_BUSWIDTH_16 : 0; chip->options |= NAND_NO_AUTOINCR; -- cgit v0.10.2 From f43272c482661bc88347bba237976eecfc1dbf5b Mon Sep 17 00:00:00 2001 From: Sandeep Paulraj Date: Mon, 7 Mar 2011 13:50:44 -0500 Subject: mtd: DaVinci: Removed header file that is not required The DaVinci NAND driver was including the mach-types.h header file. This prevented the DaVinci NAND driver from getting used in a DSP only device. The linux port on c6x devices can make use of the same driver and does not define a corresponding header file. This header file was required in the driver because earlier there was code dependent on a machine_* check. This piece of code has now been factored out and is in arch/arm/mach-davinci/aemif.c Thus removing the header file is harmless Signed-off-by: Sandeep Paulraj Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/davinci_nand.c b/drivers/mtd/nand/davinci_nand.c index a90fde3..aff3468 100644 --- a/drivers/mtd/nand/davinci_nand.c +++ b/drivers/mtd/nand/davinci_nand.c @@ -37,9 +37,6 @@ #include #include -#include - - /* * This is a device driver for the NAND flash controller found on the * various DaVinci family chips. It handles up to four SoC chipselects, -- cgit v0.10.2 From 2c1c5f199482356c00f70b6f2f368c3455d1230c Mon Sep 17 00:00:00 2001 From: Baruch Siach Date: Wed, 9 Mar 2011 16:12:20 +0200 Subject: mtd: mxc_nand: fix OOB corruption when page size > 2KiB When page size is 4KiB, ecc.total is set to 8*9, and this causes nand_write_page_hwecc() to read past the initialized part of the eccpos array, which corrupts chip->oob_poi with bogus values from ecc_calc. Fix this by creating a proper nand_ecclayout for 4KiB flashes. Signed-off-by: Baruch Siach Acked-by: Sascha Hauer Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 6cd78ed..b7d5a5b 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -211,6 +211,31 @@ static struct nand_ecclayout nandv2_hw_eccoob_largepage = { } }; +/* OOB description for 4096 byte pages with 128 byte OOB */ +static struct nand_ecclayout nandv2_hw_eccoob_4k = { + .eccbytes = 8 * 9, + .eccpos = { + 7, 8, 9, 10, 11, 12, 13, 14, 15, + 23, 24, 25, 26, 27, 28, 29, 30, 31, + 39, 40, 41, 42, 43, 44, 45, 46, 47, + 55, 56, 57, 58, 59, 60, 61, 62, 63, + 71, 72, 73, 74, 75, 76, 77, 78, 79, + 87, 88, 89, 90, 91, 92, 93, 94, 95, + 103, 104, 105, 106, 107, 108, 109, 110, 111, + 119, 120, 121, 122, 123, 124, 125, 126, 127, + }, + .oobfree = { + {.offset = 2, .length = 4}, + {.offset = 16, .length = 7}, + {.offset = 32, .length = 7}, + {.offset = 48, .length = 7}, + {.offset = 64, .length = 7}, + {.offset = 80, .length = 7}, + {.offset = 96, .length = 7}, + {.offset = 112, .length = 7}, + } +}; + #ifdef CONFIG_MTD_PARTITIONS static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; #endif @@ -1186,6 +1211,8 @@ static int __init mxcnd_probe(struct platform_device *pdev) if (mtd->writesize == 2048) this->ecc.layout = oob_largepage; + if (nfc_is_v21() && mtd->writesize == 4096) + this->ecc.layout = &nandv2_hw_eccoob_4k; /* second phase scan */ if (nand_scan_tail(mtd)) { -- cgit v0.10.2 From 437aa565e2656776a7104aaacd792fe789ea8b2d Mon Sep 17 00:00:00 2001 From: Ivan Djelic Date: Fri, 11 Mar 2011 11:05:32 +0100 Subject: lib: add shared BCH ECC library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a new software BCH encoding/decoding library, similar to the shared Reed-Solomon library. Binary BCH (Bose-Chaudhuri-Hocquenghem) codes are widely used to correct errors in NAND flash devices requiring more than 1-bit ecc correction; they are generally better suited for NAND flash than RS codes because NAND bit errors do not occur in bursts. Latest SLC NAND devices typically require at least 4-bit ecc protection per 512 bytes block. This library provides software encoding/decoding, but may also be used with ASIC/SoC hardware BCH engines to perform error correction. It is being currently used for this purpose on an OMAP3630 board (4bit/8bit HW BCH). It has also been used to decode raw dumps of NAND devices with on-die BCH ecc engines (e.g. Micron 4bit ecc SLC devices). Latest NAND devices (including SLC) can exhibit high error rates (typically a dozen or more bitflips per hour during stress tests); in order to minimize the performance impact of error correction, this library implements recently developed algorithms for fast polynomial root finding (see bch.c header for details) instead of the traditional exhaustive Chien root search; a few performance figures are provided below: Platform: arm926ejs @ 468 MHz, 32 KiB icache, 16 KiB dcache BCH ecc : 4-bit per 512 bytes Encoding average throughput: 250 Mbits/s Error correction time (compared with Chien search): average worst average (Chien) worst (Chien) ---------------------------------------------------------- 1 bit 8.5 µs 11 µs 200 µs 383 µs 2 bit 9.7 µs 12.5 µs 477 µs 728 µs 3 bit 18.1 µs 20.6 µs 758 µs 1010 µs 4 bit 19.5 µs 23 µs 1028 µs 1280 µs In the above figures, "worst" is meant in terms of error pattern, not in terms of cache miss / page faults effects (not taken into account here). The library has been extensively tested on the following platforms: x86, x86_64, arm926ejs, omap3630, qemu-ppc64, qemu-mips. Signed-off-by: Ivan Djelic Signed-off-by: David Woodhouse diff --git a/include/linux/bch.h b/include/linux/bch.h new file mode 100644 index 0000000..295b4ef --- /dev/null +++ b/include/linux/bch.h @@ -0,0 +1,79 @@ +/* + * Generic binary BCH encoding/decoding library + * + * 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. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2011 Parrot S.A. + * + * Author: Ivan Djelic + * + * Description: + * + * This library provides runtime configurable encoding/decoding of binary + * Bose-Chaudhuri-Hocquenghem (BCH) codes. +*/ +#ifndef _BCH_H +#define _BCH_H + +#include + +/** + * struct bch_control - BCH control structure + * @m: Galois field order + * @n: maximum codeword size in bits (= 2^m-1) + * @t: error correction capability in bits + * @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t) + * @ecc_bytes: ecc max size (m*t bits) in bytes + * @a_pow_tab: Galois field GF(2^m) exponentiation lookup table + * @a_log_tab: Galois field GF(2^m) log lookup table + * @mod8_tab: remainder generator polynomial lookup tables + * @ecc_buf: ecc parity words buffer + * @ecc_buf2: ecc parity words buffer + * @xi_tab: GF(2^m) base for solving degree 2 polynomial roots + * @syn: syndrome buffer + * @cache: log-based polynomial representation buffer + * @elp: error locator polynomial + * @poly_2t: temporary polynomials of degree 2t + */ +struct bch_control { + unsigned int m; + unsigned int n; + unsigned int t; + unsigned int ecc_bits; + unsigned int ecc_bytes; +/* private: */ + uint16_t *a_pow_tab; + uint16_t *a_log_tab; + uint32_t *mod8_tab; + uint32_t *ecc_buf; + uint32_t *ecc_buf2; + unsigned int *xi_tab; + unsigned int *syn; + int *cache; + struct gf_poly *elp; + struct gf_poly *poly_2t[4]; +}; + +struct bch_control *init_bch(int m, int t, unsigned int prim_poly); + +void free_bch(struct bch_control *bch); + +void encode_bch(struct bch_control *bch, const uint8_t *data, + unsigned int len, uint8_t *ecc); + +int decode_bch(struct bch_control *bch, const uint8_t *data, unsigned int len, + const uint8_t *recv_ecc, const uint8_t *calc_ecc, + const unsigned int *syn, unsigned int *errloc); + +#endif /* _BCH_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 0ee67e0..b9fef78 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -155,6 +155,45 @@ config REED_SOLOMON_DEC16 boolean # +# BCH support is selected if needed +# +config BCH + tristate + +config BCH_CONST_PARAMS + boolean + help + Drivers may select this option to force specific constant + values for parameters 'm' (Galois field order) and 't' + (error correction capability). Those specific values must + be set by declaring default values for symbols BCH_CONST_M + and BCH_CONST_T. + Doing so will enable extra compiler optimizations, + improving encoding and decoding performance up to 2x for + usual (m,t) values (typically such that m*t < 200). + When this option is selected, the BCH library supports + only a single (m,t) configuration. This is mainly useful + for NAND flash board drivers requiring known, fixed BCH + parameters. + +config BCH_CONST_M + int + range 5 15 + help + Constant value for Galois field order 'm'. If 'k' is the + number of data bits to protect, 'm' should be chosen such + that (k + m*t) <= 2**m - 1. + Drivers should declare a default value for this symbol if + they select option BCH_CONST_PARAMS. + +config BCH_CONST_T + int + help + Constant value for error correction capability in bits 't'. + Drivers should declare a default value for this symbol if + they select option BCH_CONST_PARAMS. + +# # Textsearch support is select'ed if needed # config TEXTSEARCH diff --git a/lib/Makefile b/lib/Makefile index cbb774f..0544c81 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_GENERIC_ALLOCATOR) += genalloc.o obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/ obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/ obj-$(CONFIG_REED_SOLOMON) += reed_solomon/ +obj-$(CONFIG_BCH) += bch.o obj-$(CONFIG_LZO_COMPRESS) += lzo/ obj-$(CONFIG_LZO_DECOMPRESS) += lzo/ obj-$(CONFIG_XZ_DEC) += xz/ diff --git a/lib/bch.c b/lib/bch.c new file mode 100644 index 0000000..bc89dfe --- /dev/null +++ b/lib/bch.c @@ -0,0 +1,1368 @@ +/* + * Generic binary BCH encoding/decoding library + * + * 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. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright © 2011 Parrot S.A. + * + * Author: Ivan Djelic + * + * Description: + * + * This library provides runtime configurable encoding/decoding of binary + * Bose-Chaudhuri-Hocquenghem (BCH) codes. + * + * Call init_bch to get a pointer to a newly allocated bch_control structure for + * the given m (Galois field order), t (error correction capability) and + * (optional) primitive polynomial parameters. + * + * Call encode_bch to compute and store ecc parity bytes to a given buffer. + * Call decode_bch to detect and locate errors in received data. + * + * On systems supporting hw BCH features, intermediate results may be provided + * to decode_bch in order to skip certain steps. See decode_bch() documentation + * for details. + * + * Option CONFIG_BCH_CONST_PARAMS can be used to force fixed values of + * parameters m and t; thus allowing extra compiler optimizations and providing + * better (up to 2x) encoding performance. Using this option makes sense when + * (m,t) are fixed and known in advance, e.g. when using BCH error correction + * on a particular NAND flash device. + * + * Algorithmic details: + * + * Encoding is performed by processing 32 input bits in parallel, using 4 + * remainder lookup tables. + * + * The final stage of decoding involves the following internal steps: + * a. Syndrome computation + * b. Error locator polynomial computation using Berlekamp-Massey algorithm + * c. Error locator root finding (by far the most expensive step) + * + * In this implementation, step c is not performed using the usual Chien search. + * Instead, an alternative approach described in [1] is used. It consists in + * factoring the error locator polynomial using the Berlekamp Trace algorithm + * (BTA) down to a certain degree (4), after which ad hoc low-degree polynomial + * solving techniques [2] are used. The resulting algorithm, called BTZ, yields + * much better performance than Chien search for usual (m,t) values (typically + * m >= 13, t < 32, see [1]). + * + * [1] B. Biswas, V. Herbert. Efficient root finding of polynomials over fields + * of characteristic 2, in: Western European Workshop on Research in Cryptology + * - WEWoRC 2009, Graz, Austria, LNCS, Springer, July 2009, to appear. + * [2] [Zin96] V.A. Zinoviev. On the solution of equations of degree 10 over + * finite fields GF(2^q). In Rapport de recherche INRIA no 2829, 1996. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_BCH_CONST_PARAMS) +#define GF_M(_p) (CONFIG_BCH_CONST_M) +#define GF_T(_p) (CONFIG_BCH_CONST_T) +#define GF_N(_p) ((1 << (CONFIG_BCH_CONST_M))-1) +#else +#define GF_M(_p) ((_p)->m) +#define GF_T(_p) ((_p)->t) +#define GF_N(_p) ((_p)->n) +#endif + +#define BCH_ECC_WORDS(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 32) +#define BCH_ECC_BYTES(_p) DIV_ROUND_UP(GF_M(_p)*GF_T(_p), 8) + +#ifndef dbg +#define dbg(_fmt, args...) do {} while (0) +#endif + +/* + * represent a polynomial over GF(2^m) + */ +struct gf_poly { + unsigned int deg; /* polynomial degree */ + unsigned int c[0]; /* polynomial terms */ +}; + +/* given its degree, compute a polynomial size in bytes */ +#define GF_POLY_SZ(_d) (sizeof(struct gf_poly)+((_d)+1)*sizeof(unsigned int)) + +/* polynomial of degree 1 */ +struct gf_poly_deg1 { + struct gf_poly poly; + unsigned int c[2]; +}; + +/* + * same as encode_bch(), but process input data one byte at a time + */ +static void encode_bch_unaligned(struct bch_control *bch, + const unsigned char *data, unsigned int len, + uint32_t *ecc) +{ + int i; + const uint32_t *p; + const int l = BCH_ECC_WORDS(bch)-1; + + while (len--) { + p = bch->mod8_tab + (l+1)*(((ecc[0] >> 24)^(*data++)) & 0xff); + + for (i = 0; i < l; i++) + ecc[i] = ((ecc[i] << 8)|(ecc[i+1] >> 24))^(*p++); + + ecc[l] = (ecc[l] << 8)^(*p); + } +} + +/* + * convert ecc bytes to aligned, zero-padded 32-bit ecc words + */ +static void load_ecc8(struct bch_control *bch, uint32_t *dst, + const uint8_t *src) +{ + uint8_t pad[4] = {0, 0, 0, 0}; + unsigned int i, nwords = BCH_ECC_WORDS(bch)-1; + + for (i = 0; i < nwords; i++, src += 4) + dst[i] = (src[0] << 24)|(src[1] << 16)|(src[2] << 8)|src[3]; + + memcpy(pad, src, BCH_ECC_BYTES(bch)-4*nwords); + dst[nwords] = (pad[0] << 24)|(pad[1] << 16)|(pad[2] << 8)|pad[3]; +} + +/* + * convert 32-bit ecc words to ecc bytes + */ +static void store_ecc8(struct bch_control *bch, uint8_t *dst, + const uint32_t *src) +{ + uint8_t pad[4]; + unsigned int i, nwords = BCH_ECC_WORDS(bch)-1; + + for (i = 0; i < nwords; i++) { + *dst++ = (src[i] >> 24); + *dst++ = (src[i] >> 16) & 0xff; + *dst++ = (src[i] >> 8) & 0xff; + *dst++ = (src[i] >> 0) & 0xff; + } + pad[0] = (src[nwords] >> 24); + pad[1] = (src[nwords] >> 16) & 0xff; + pad[2] = (src[nwords] >> 8) & 0xff; + pad[3] = (src[nwords] >> 0) & 0xff; + memcpy(dst, pad, BCH_ECC_BYTES(bch)-4*nwords); +} + +/** + * encode_bch - calculate BCH ecc parity of data + * @bch: BCH control structure + * @data: data to encode + * @len: data length in bytes + * @ecc: ecc parity data, must be initialized by caller + * + * The @ecc parity array is used both as input and output parameter, in order to + * allow incremental computations. It should be of the size indicated by member + * @ecc_bytes of @bch, and should be initialized to 0 before the first call. + * + * The exact number of computed ecc parity bits is given by member @ecc_bits of + * @bch; it may be less than m*t for large values of t. + */ +void encode_bch(struct bch_control *bch, const uint8_t *data, + unsigned int len, uint8_t *ecc) +{ + const unsigned int l = BCH_ECC_WORDS(bch)-1; + unsigned int i, mlen; + unsigned long m; + uint32_t w, r[l+1]; + const uint32_t * const tab0 = bch->mod8_tab; + const uint32_t * const tab1 = tab0 + 256*(l+1); + const uint32_t * const tab2 = tab1 + 256*(l+1); + const uint32_t * const tab3 = tab2 + 256*(l+1); + const uint32_t *pdata, *p0, *p1, *p2, *p3; + + if (ecc) { + /* load ecc parity bytes into internal 32-bit buffer */ + load_ecc8(bch, bch->ecc_buf, ecc); + } else { + memset(bch->ecc_buf, 0, sizeof(r)); + } + + /* process first unaligned data bytes */ + m = ((unsigned long)data) & 3; + if (m) { + mlen = (len < (4-m)) ? len : 4-m; + encode_bch_unaligned(bch, data, mlen, bch->ecc_buf); + data += mlen; + len -= mlen; + } + + /* process 32-bit aligned data words */ + pdata = (uint32_t *)data; + mlen = len/4; + data += 4*mlen; + len -= 4*mlen; + memcpy(r, bch->ecc_buf, sizeof(r)); + + /* + * split each 32-bit word into 4 polynomials of weight 8 as follows: + * + * 31 ...24 23 ...16 15 ... 8 7 ... 0 + * xxxxxxxx yyyyyyyy zzzzzzzz tttttttt + * tttttttt mod g = r0 (precomputed) + * zzzzzzzz 00000000 mod g = r1 (precomputed) + * yyyyyyyy 00000000 00000000 mod g = r2 (precomputed) + * xxxxxxxx 00000000 00000000 00000000 mod g = r3 (precomputed) + * xxxxxxxx yyyyyyyy zzzzzzzz tttttttt mod g = r0^r1^r2^r3 + */ + while (mlen--) { + /* input data is read in big-endian format */ + w = r[0]^cpu_to_be32(*pdata++); + p0 = tab0 + (l+1)*((w >> 0) & 0xff); + p1 = tab1 + (l+1)*((w >> 8) & 0xff); + p2 = tab2 + (l+1)*((w >> 16) & 0xff); + p3 = tab3 + (l+1)*((w >> 24) & 0xff); + + for (i = 0; i < l; i++) + r[i] = r[i+1]^p0[i]^p1[i]^p2[i]^p3[i]; + + r[l] = p0[l]^p1[l]^p2[l]^p3[l]; + } + memcpy(bch->ecc_buf, r, sizeof(r)); + + /* process last unaligned bytes */ + if (len) + encode_bch_unaligned(bch, data, len, bch->ecc_buf); + + /* store ecc parity bytes into original parity buffer */ + if (ecc) + store_ecc8(bch, ecc, bch->ecc_buf); +} +EXPORT_SYMBOL_GPL(encode_bch); + +static inline int modulo(struct bch_control *bch, unsigned int v) +{ + const unsigned int n = GF_N(bch); + while (v >= n) { + v -= n; + v = (v & n) + (v >> GF_M(bch)); + } + return v; +} + +/* + * shorter and faster modulo function, only works when v < 2N. + */ +static inline int mod_s(struct bch_control *bch, unsigned int v) +{ + const unsigned int n = GF_N(bch); + return (v < n) ? v : v-n; +} + +static inline int deg(unsigned int poly) +{ + /* polynomial degree is the most-significant bit index */ + return fls(poly)-1; +} + +static inline int parity(unsigned int x) +{ + /* + * public domain code snippet, lifted from + * http://www-graphics.stanford.edu/~seander/bithacks.html + */ + x ^= x >> 1; + x ^= x >> 2; + x = (x & 0x11111111U) * 0x11111111U; + return (x >> 28) & 1; +} + +/* Galois field basic operations: multiply, divide, inverse, etc. */ + +static inline unsigned int gf_mul(struct bch_control *bch, unsigned int a, + unsigned int b) +{ + return (a && b) ? bch->a_pow_tab[mod_s(bch, bch->a_log_tab[a]+ + bch->a_log_tab[b])] : 0; +} + +static inline unsigned int gf_sqr(struct bch_control *bch, unsigned int a) +{ + return a ? bch->a_pow_tab[mod_s(bch, 2*bch->a_log_tab[a])] : 0; +} + +static inline unsigned int gf_div(struct bch_control *bch, unsigned int a, + unsigned int b) +{ + return a ? bch->a_pow_tab[mod_s(bch, bch->a_log_tab[a]+ + GF_N(bch)-bch->a_log_tab[b])] : 0; +} + +static inline unsigned int gf_inv(struct bch_control *bch, unsigned int a) +{ + return bch->a_pow_tab[GF_N(bch)-bch->a_log_tab[a]]; +} + +static inline unsigned int a_pow(struct bch_control *bch, int i) +{ + return bch->a_pow_tab[modulo(bch, i)]; +} + +static inline int a_log(struct bch_control *bch, unsigned int x) +{ + return bch->a_log_tab[x]; +} + +static inline int a_ilog(struct bch_control *bch, unsigned int x) +{ + return mod_s(bch, GF_N(bch)-bch->a_log_tab[x]); +} + +/* + * compute 2t syndromes of ecc polynomial, i.e. ecc(a^j) for j=1..2t + */ +static void compute_syndromes(struct bch_control *bch, uint32_t *ecc, + unsigned int *syn) +{ + int i, j, s; + unsigned int m; + uint32_t poly; + const int t = GF_T(bch); + + s = bch->ecc_bits; + + /* make sure extra bits in last ecc word are cleared */ + m = ((unsigned int)s) & 31; + if (m) + ecc[s/32] &= ~((1u << (32-m))-1); + memset(syn, 0, 2*t*sizeof(*syn)); + + /* compute v(a^j) for j=1 .. 2t-1 */ + do { + poly = *ecc++; + s -= 32; + while (poly) { + i = deg(poly); + for (j = 0; j < 2*t; j += 2) + syn[j] ^= a_pow(bch, (j+1)*(i+s)); + + poly ^= (1 << i); + } + } while (s > 0); + + /* v(a^(2j)) = v(a^j)^2 */ + for (j = 0; j < t; j++) + syn[2*j+1] = gf_sqr(bch, syn[j]); +} + +static void gf_poly_copy(struct gf_poly *dst, struct gf_poly *src) +{ + memcpy(dst, src, GF_POLY_SZ(src->deg)); +} + +static int compute_error_locator_polynomial(struct bch_control *bch, + const unsigned int *syn) +{ + const unsigned int t = GF_T(bch); + const unsigned int n = GF_N(bch); + unsigned int i, j, tmp, l, pd = 1, d = syn[0]; + struct gf_poly *elp = bch->elp; + struct gf_poly *pelp = bch->poly_2t[0]; + struct gf_poly *elp_copy = bch->poly_2t[1]; + int k, pp = -1; + + memset(pelp, 0, GF_POLY_SZ(2*t)); + memset(elp, 0, GF_POLY_SZ(2*t)); + + pelp->deg = 0; + pelp->c[0] = 1; + elp->deg = 0; + elp->c[0] = 1; + + /* use simplified binary Berlekamp-Massey algorithm */ + for (i = 0; (i < t) && (elp->deg <= t); i++) { + if (d) { + k = 2*i-pp; + gf_poly_copy(elp_copy, elp); + /* e[i+1](X) = e[i](X)+di*dp^-1*X^2(i-p)*e[p](X) */ + tmp = a_log(bch, d)+n-a_log(bch, pd); + for (j = 0; j <= pelp->deg; j++) { + if (pelp->c[j]) { + l = a_log(bch, pelp->c[j]); + elp->c[j+k] ^= a_pow(bch, tmp+l); + } + } + /* compute l[i+1] = max(l[i]->c[l[p]+2*(i-p]) */ + tmp = pelp->deg+k; + if (tmp > elp->deg) { + elp->deg = tmp; + gf_poly_copy(pelp, elp_copy); + pd = d; + pp = 2*i; + } + } + /* di+1 = S(2i+3)+elp[i+1].1*S(2i+2)+...+elp[i+1].lS(2i+3-l) */ + if (i < t-1) { + d = syn[2*i+2]; + for (j = 1; j <= elp->deg; j++) + d ^= gf_mul(bch, elp->c[j], syn[2*i+2-j]); + } + } + dbg("elp=%s\n", gf_poly_str(elp)); + return (elp->deg > t) ? -1 : (int)elp->deg; +} + +/* + * solve a m x m linear system in GF(2) with an expected number of solutions, + * and return the number of found solutions + */ +static int solve_linear_system(struct bch_control *bch, unsigned int *rows, + unsigned int *sol, int nsol) +{ + const int m = GF_M(bch); + unsigned int tmp, mask; + int rem, c, r, p, k, param[m]; + + k = 0; + mask = 1 << m; + + /* Gaussian elimination */ + for (c = 0; c < m; c++) { + rem = 0; + p = c-k; + /* find suitable row for elimination */ + for (r = p; r < m; r++) { + if (rows[r] & mask) { + if (r != p) { + tmp = rows[r]; + rows[r] = rows[p]; + rows[p] = tmp; + } + rem = r+1; + break; + } + } + if (rem) { + /* perform elimination on remaining rows */ + tmp = rows[p]; + for (r = rem; r < m; r++) { + if (rows[r] & mask) + rows[r] ^= tmp; + } + } else { + /* elimination not needed, store defective row index */ + param[k++] = c; + } + mask >>= 1; + } + /* rewrite system, inserting fake parameter rows */ + if (k > 0) { + p = k; + for (r = m-1; r >= 0; r--) { + if ((r > m-1-k) && rows[r]) + /* system has no solution */ + return 0; + + rows[r] = (p && (r == param[p-1])) ? + p--, 1u << (m-r) : rows[r-p]; + } + } + + if (nsol != (1 << k)) + /* unexpected number of solutions */ + return 0; + + for (p = 0; p < nsol; p++) { + /* set parameters for p-th solution */ + for (c = 0; c < k; c++) + rows[param[c]] = (rows[param[c]] & ~1)|((p >> c) & 1); + + /* compute unique solution */ + tmp = 0; + for (r = m-1; r >= 0; r--) { + mask = rows[r] & (tmp|1); + tmp |= parity(mask) << (m-r); + } + sol[p] = tmp >> 1; + } + return nsol; +} + +/* + * this function builds and solves a linear system for finding roots of a degree + * 4 affine monic polynomial X^4+aX^2+bX+c over GF(2^m). + */ +static int find_affine4_roots(struct bch_control *bch, unsigned int a, + unsigned int b, unsigned int c, + unsigned int *roots) +{ + int i, j, k; + const int m = GF_M(bch); + unsigned int mask = 0xff, t, rows[16] = {0,}; + + j = a_log(bch, b); + k = a_log(bch, a); + rows[0] = c; + + /* buid linear system to solve X^4+aX^2+bX+c = 0 */ + for (i = 0; i < m; i++) { + rows[i+1] = bch->a_pow_tab[4*i]^ + (a ? bch->a_pow_tab[mod_s(bch, k)] : 0)^ + (b ? bch->a_pow_tab[mod_s(bch, j)] : 0); + j++; + k += 2; + } + /* + * transpose 16x16 matrix before passing it to linear solver + * warning: this code assumes m < 16 + */ + for (j = 8; j != 0; j >>= 1, mask ^= (mask << j)) { + for (k = 0; k < 16; k = (k+j+1) & ~j) { + t = ((rows[k] >> j)^rows[k+j]) & mask; + rows[k] ^= (t << j); + rows[k+j] ^= t; + } + } + return solve_linear_system(bch, rows, roots, 4); +} + +/* + * compute root r of a degree 1 polynomial over GF(2^m) (returned as log(1/r)) + */ +static int find_poly_deg1_roots(struct bch_control *bch, struct gf_poly *poly, + unsigned int *roots) +{ + int n = 0; + + if (poly->c[0]) + /* poly[X] = bX+c with c!=0, root=c/b */ + roots[n++] = mod_s(bch, GF_N(bch)-bch->a_log_tab[poly->c[0]]+ + bch->a_log_tab[poly->c[1]]); + return n; +} + +/* + * compute roots of a degree 2 polynomial over GF(2^m) + */ +static int find_poly_deg2_roots(struct bch_control *bch, struct gf_poly *poly, + unsigned int *roots) +{ + int n = 0, i, l0, l1, l2; + unsigned int u, v, r; + + if (poly->c[0] && poly->c[1]) { + + l0 = bch->a_log_tab[poly->c[0]]; + l1 = bch->a_log_tab[poly->c[1]]; + l2 = bch->a_log_tab[poly->c[2]]; + + /* using z=a/bX, transform aX^2+bX+c into z^2+z+u (u=ac/b^2) */ + u = a_pow(bch, l0+l2+2*(GF_N(bch)-l1)); + /* + * let u = sum(li.a^i) i=0..m-1; then compute r = sum(li.xi): + * r^2+r = sum(li.(xi^2+xi)) = sum(li.(a^i+Tr(a^i).a^k)) = + * u + sum(li.Tr(a^i).a^k) = u+a^k.Tr(sum(li.a^i)) = u+a^k.Tr(u) + * i.e. r and r+1 are roots iff Tr(u)=0 + */ + r = 0; + v = u; + while (v) { + i = deg(v); + r ^= bch->xi_tab[i]; + v ^= (1 << i); + } + /* verify root */ + if ((gf_sqr(bch, r)^r) == u) { + /* reverse z=a/bX transformation and compute log(1/r) */ + roots[n++] = modulo(bch, 2*GF_N(bch)-l1- + bch->a_log_tab[r]+l2); + roots[n++] = modulo(bch, 2*GF_N(bch)-l1- + bch->a_log_tab[r^1]+l2); + } + } + return n; +} + +/* + * compute roots of a degree 3 polynomial over GF(2^m) + */ +static int find_poly_deg3_roots(struct bch_control *bch, struct gf_poly *poly, + unsigned int *roots) +{ + int i, n = 0; + unsigned int a, b, c, a2, b2, c2, e3, tmp[4]; + + if (poly->c[0]) { + /* transform polynomial into monic X^3 + a2X^2 + b2X + c2 */ + e3 = poly->c[3]; + c2 = gf_div(bch, poly->c[0], e3); + b2 = gf_div(bch, poly->c[1], e3); + a2 = gf_div(bch, poly->c[2], e3); + + /* (X+a2)(X^3+a2X^2+b2X+c2) = X^4+aX^2+bX+c (affine) */ + c = gf_mul(bch, a2, c2); /* c = a2c2 */ + b = gf_mul(bch, a2, b2)^c2; /* b = a2b2 + c2 */ + a = gf_sqr(bch, a2)^b2; /* a = a2^2 + b2 */ + + /* find the 4 roots of this affine polynomial */ + if (find_affine4_roots(bch, a, b, c, tmp) == 4) { + /* remove a2 from final list of roots */ + for (i = 0; i < 4; i++) { + if (tmp[i] != a2) + roots[n++] = a_ilog(bch, tmp[i]); + } + } + } + return n; +} + +/* + * compute roots of a degree 4 polynomial over GF(2^m) + */ +static int find_poly_deg4_roots(struct bch_control *bch, struct gf_poly *poly, + unsigned int *roots) +{ + int i, l, n = 0; + unsigned int a, b, c, d, e = 0, f, a2, b2, c2, e4; + + if (poly->c[0] == 0) + return 0; + + /* transform polynomial into monic X^4 + aX^3 + bX^2 + cX + d */ + e4 = poly->c[4]; + d = gf_div(bch, poly->c[0], e4); + c = gf_div(bch, poly->c[1], e4); + b = gf_div(bch, poly->c[2], e4); + a = gf_div(bch, poly->c[3], e4); + + /* use Y=1/X transformation to get an affine polynomial */ + if (a) { + /* first, eliminate cX by using z=X+e with ae^2+c=0 */ + if (c) { + /* compute e such that e^2 = c/a */ + f = gf_div(bch, c, a); + l = a_log(bch, f); + l += (l & 1) ? GF_N(bch) : 0; + e = a_pow(bch, l/2); + /* + * use transformation z=X+e: + * z^4+e^4 + a(z^3+ez^2+e^2z+e^3) + b(z^2+e^2) +cz+ce+d + * z^4 + az^3 + (ae+b)z^2 + (ae^2+c)z+e^4+be^2+ae^3+ce+d + * z^4 + az^3 + (ae+b)z^2 + e^4+be^2+d + * z^4 + az^3 + b'z^2 + d' + */ + d = a_pow(bch, 2*l)^gf_mul(bch, b, f)^d; + b = gf_mul(bch, a, e)^b; + } + /* now, use Y=1/X to get Y^4 + b/dY^2 + a/dY + 1/d */ + if (d == 0) + /* assume all roots have multiplicity 1 */ + return 0; + + c2 = gf_inv(bch, d); + b2 = gf_div(bch, a, d); + a2 = gf_div(bch, b, d); + } else { + /* polynomial is already affine */ + c2 = d; + b2 = c; + a2 = b; + } + /* find the 4 roots of this affine polynomial */ + if (find_affine4_roots(bch, a2, b2, c2, roots) == 4) { + for (i = 0; i < 4; i++) { + /* post-process roots (reverse transformations) */ + f = a ? gf_inv(bch, roots[i]) : roots[i]; + roots[i] = a_ilog(bch, f^e); + } + n = 4; + } + return n; +} + +/* + * build monic, log-based representation of a polynomial + */ +static void gf_poly_logrep(struct bch_control *bch, + const struct gf_poly *a, int *rep) +{ + int i, d = a->deg, l = GF_N(bch)-a_log(bch, a->c[a->deg]); + + /* represent 0 values with -1; warning, rep[d] is not set to 1 */ + for (i = 0; i < d; i++) + rep[i] = a->c[i] ? mod_s(bch, a_log(bch, a->c[i])+l) : -1; +} + +/* + * compute polynomial Euclidean division remainder in GF(2^m)[X] + */ +static void gf_poly_mod(struct bch_control *bch, struct gf_poly *a, + const struct gf_poly *b, int *rep) +{ + int la, p, m; + unsigned int i, j, *c = a->c; + const unsigned int d = b->deg; + + if (a->deg < d) + return; + + /* reuse or compute log representation of denominator */ + if (!rep) { + rep = bch->cache; + gf_poly_logrep(bch, b, rep); + } + + for (j = a->deg; j >= d; j--) { + if (c[j]) { + la = a_log(bch, c[j]); + p = j-d; + for (i = 0; i < d; i++, p++) { + m = rep[i]; + if (m >= 0) + c[p] ^= bch->a_pow_tab[mod_s(bch, + m+la)]; + } + } + } + a->deg = d-1; + while (!c[a->deg] && a->deg) + a->deg--; +} + +/* + * compute polynomial Euclidean division quotient in GF(2^m)[X] + */ +static void gf_poly_div(struct bch_control *bch, struct gf_poly *a, + const struct gf_poly *b, struct gf_poly *q) +{ + if (a->deg >= b->deg) { + q->deg = a->deg-b->deg; + /* compute a mod b (modifies a) */ + gf_poly_mod(bch, a, b, NULL); + /* quotient is stored in upper part of polynomial a */ + memcpy(q->c, &a->c[b->deg], (1+q->deg)*sizeof(unsigned int)); + } else { + q->deg = 0; + q->c[0] = 0; + } +} + +/* + * compute polynomial GCD (Greatest Common Divisor) in GF(2^m)[X] + */ +static struct gf_poly *gf_poly_gcd(struct bch_control *bch, struct gf_poly *a, + struct gf_poly *b) +{ + struct gf_poly *tmp; + + dbg("gcd(%s,%s)=", gf_poly_str(a), gf_poly_str(b)); + + if (a->deg < b->deg) { + tmp = b; + b = a; + a = tmp; + } + + while (b->deg > 0) { + gf_poly_mod(bch, a, b, NULL); + tmp = b; + b = a; + a = tmp; + } + + dbg("%s\n", gf_poly_str(a)); + + return a; +} + +/* + * Given a polynomial f and an integer k, compute Tr(a^kX) mod f + * This is used in Berlekamp Trace algorithm for splitting polynomials + */ +static void compute_trace_bk_mod(struct bch_control *bch, int k, + const struct gf_poly *f, struct gf_poly *z, + struct gf_poly *out) +{ + const int m = GF_M(bch); + int i, j; + + /* z contains z^2j mod f */ + z->deg = 1; + z->c[0] = 0; + z->c[1] = bch->a_pow_tab[k]; + + out->deg = 0; + memset(out, 0, GF_POLY_SZ(f->deg)); + + /* compute f log representation only once */ + gf_poly_logrep(bch, f, bch->cache); + + for (i = 0; i < m; i++) { + /* add a^(k*2^i)(z^(2^i) mod f) and compute (z^(2^i) mod f)^2 */ + for (j = z->deg; j >= 0; j--) { + out->c[j] ^= z->c[j]; + z->c[2*j] = gf_sqr(bch, z->c[j]); + z->c[2*j+1] = 0; + } + if (z->deg > out->deg) + out->deg = z->deg; + + if (i < m-1) { + z->deg *= 2; + /* z^(2(i+1)) mod f = (z^(2^i) mod f)^2 mod f */ + gf_poly_mod(bch, z, f, bch->cache); + } + } + while (!out->c[out->deg] && out->deg) + out->deg--; + + dbg("Tr(a^%d.X) mod f = %s\n", k, gf_poly_str(out)); +} + +/* + * factor a polynomial using Berlekamp Trace algorithm (BTA) + */ +static void factor_polynomial(struct bch_control *bch, int k, struct gf_poly *f, + struct gf_poly **g, struct gf_poly **h) +{ + struct gf_poly *f2 = bch->poly_2t[0]; + struct gf_poly *q = bch->poly_2t[1]; + struct gf_poly *tk = bch->poly_2t[2]; + struct gf_poly *z = bch->poly_2t[3]; + struct gf_poly *gcd; + + dbg("factoring %s...\n", gf_poly_str(f)); + + *g = f; + *h = NULL; + + /* tk = Tr(a^k.X) mod f */ + compute_trace_bk_mod(bch, k, f, z, tk); + + if (tk->deg > 0) { + /* compute g = gcd(f, tk) (destructive operation) */ + gf_poly_copy(f2, f); + gcd = gf_poly_gcd(bch, f2, tk); + if (gcd->deg < f->deg) { + /* compute h=f/gcd(f,tk); this will modify f and q */ + gf_poly_div(bch, f, gcd, q); + /* store g and h in-place (clobbering f) */ + *h = &((struct gf_poly_deg1 *)f)[gcd->deg].poly; + gf_poly_copy(*g, gcd); + gf_poly_copy(*h, q); + } + } +} + +/* + * find roots of a polynomial, using BTZ algorithm; see the beginning of this + * file for details + */ +static int find_poly_roots(struct bch_control *bch, unsigned int k, + struct gf_poly *poly, unsigned int *roots) +{ + int cnt; + struct gf_poly *f1, *f2; + + switch (poly->deg) { + /* handle low degree polynomials with ad hoc techniques */ + case 1: + cnt = find_poly_deg1_roots(bch, poly, roots); + break; + case 2: + cnt = find_poly_deg2_roots(bch, poly, roots); + break; + case 3: + cnt = find_poly_deg3_roots(bch, poly, roots); + break; + case 4: + cnt = find_poly_deg4_roots(bch, poly, roots); + break; + default: + /* factor polynomial using Berlekamp Trace Algorithm (BTA) */ + cnt = 0; + if (poly->deg && (k <= GF_M(bch))) { + factor_polynomial(bch, k, poly, &f1, &f2); + if (f1) + cnt += find_poly_roots(bch, k+1, f1, roots); + if (f2) + cnt += find_poly_roots(bch, k+1, f2, roots+cnt); + } + break; + } + return cnt; +} + +#if defined(USE_CHIEN_SEARCH) +/* + * exhaustive root search (Chien) implementation - not used, included only for + * reference/comparison tests + */ +static int chien_search(struct bch_control *bch, unsigned int len, + struct gf_poly *p, unsigned int *roots) +{ + int m; + unsigned int i, j, syn, syn0, count = 0; + const unsigned int k = 8*len+bch->ecc_bits; + + /* use a log-based representation of polynomial */ + gf_poly_logrep(bch, p, bch->cache); + bch->cache[p->deg] = 0; + syn0 = gf_div(bch, p->c[0], p->c[p->deg]); + + for (i = GF_N(bch)-k+1; i <= GF_N(bch); i++) { + /* compute elp(a^i) */ + for (j = 1, syn = syn0; j <= p->deg; j++) { + m = bch->cache[j]; + if (m >= 0) + syn ^= a_pow(bch, m+j*i); + } + if (syn == 0) { + roots[count++] = GF_N(bch)-i; + if (count == p->deg) + break; + } + } + return (count == p->deg) ? count : 0; +} +#define find_poly_roots(_p, _k, _elp, _loc) chien_search(_p, len, _elp, _loc) +#endif /* USE_CHIEN_SEARCH */ + +/** + * decode_bch - decode received codeword and find bit error locations + * @bch: BCH control structure + * @data: received data, ignored if @calc_ecc is provided + * @len: data length in bytes, must always be provided + * @recv_ecc: received ecc, if NULL then assume it was XORed in @calc_ecc + * @calc_ecc: calculated ecc, if NULL then calc_ecc is computed from @data + * @syn: hw computed syndrome data (if NULL, syndrome is calculated) + * @errloc: output array of error locations + * + * Returns: + * The number of errors found, or -EBADMSG if decoding failed, or -EINVAL if + * invalid parameters were provided + * + * Depending on the available hw BCH support and the need to compute @calc_ecc + * separately (using encode_bch()), this function should be called with one of + * the following parameter configurations - + * + * by providing @data and @recv_ecc only: + * decode_bch(@bch, @data, @len, @recv_ecc, NULL, NULL, @errloc) + * + * by providing @recv_ecc and @calc_ecc: + * decode_bch(@bch, NULL, @len, @recv_ecc, @calc_ecc, NULL, @errloc) + * + * by providing ecc = recv_ecc XOR calc_ecc: + * decode_bch(@bch, NULL, @len, NULL, ecc, NULL, @errloc) + * + * by providing syndrome results @syn: + * decode_bch(@bch, NULL, @len, NULL, NULL, @syn, @errloc) + * + * Once decode_bch() has successfully returned with a positive value, error + * locations returned in array @errloc should be interpreted as follows - + * + * if (errloc[n] >= 8*len), then n-th error is located in ecc (no need for + * data correction) + * + * if (errloc[n] < 8*len), then n-th error is located in data and can be + * corrected with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8); + * + * Note that this function does not perform any data correction by itself, it + * merely indicates error locations. + */ +int decode_bch(struct bch_control *bch, const uint8_t *data, unsigned int len, + const uint8_t *recv_ecc, const uint8_t *calc_ecc, + const unsigned int *syn, unsigned int *errloc) +{ + const unsigned int ecc_words = BCH_ECC_WORDS(bch); + unsigned int nbits; + int i, err, nroots; + uint32_t sum; + + /* sanity check: make sure data length can be handled */ + if (8*len > (bch->n-bch->ecc_bits)) + return -EINVAL; + + /* if caller does not provide syndromes, compute them */ + if (!syn) { + if (!calc_ecc) { + /* compute received data ecc into an internal buffer */ + if (!data || !recv_ecc) + return -EINVAL; + encode_bch(bch, data, len, NULL); + } else { + /* load provided calculated ecc */ + load_ecc8(bch, bch->ecc_buf, calc_ecc); + } + /* load received ecc or assume it was XORed in calc_ecc */ + if (recv_ecc) { + load_ecc8(bch, bch->ecc_buf2, recv_ecc); + /* XOR received and calculated ecc */ + for (i = 0, sum = 0; i < (int)ecc_words; i++) { + bch->ecc_buf[i] ^= bch->ecc_buf2[i]; + sum |= bch->ecc_buf[i]; + } + if (!sum) + /* no error found */ + return 0; + } + compute_syndromes(bch, bch->ecc_buf, bch->syn); + syn = bch->syn; + } + + err = compute_error_locator_polynomial(bch, syn); + if (err > 0) { + nroots = find_poly_roots(bch, 1, bch->elp, errloc); + if (err != nroots) + err = -1; + } + if (err > 0) { + /* post-process raw error locations for easier correction */ + nbits = (len*8)+bch->ecc_bits; + for (i = 0; i < err; i++) { + if (errloc[i] >= nbits) { + err = -1; + break; + } + errloc[i] = nbits-1-errloc[i]; + errloc[i] = (errloc[i] & ~7)|(7-(errloc[i] & 7)); + } + } + return (err >= 0) ? err : -EBADMSG; +} +EXPORT_SYMBOL_GPL(decode_bch); + +/* + * generate Galois field lookup tables + */ +static int build_gf_tables(struct bch_control *bch, unsigned int poly) +{ + unsigned int i, x = 1; + const unsigned int k = 1 << deg(poly); + + /* primitive polynomial must be of degree m */ + if (k != (1u << GF_M(bch))) + return -1; + + for (i = 0; i < GF_N(bch); i++) { + bch->a_pow_tab[i] = x; + bch->a_log_tab[x] = i; + if (i && (x == 1)) + /* polynomial is not primitive (a^i=1 with 0a_pow_tab[GF_N(bch)] = 1; + bch->a_log_tab[0] = 0; + + return 0; +} + +/* + * compute generator polynomial remainder tables for fast encoding + */ +static void build_mod8_tables(struct bch_control *bch, const uint32_t *g) +{ + int i, j, b, d; + uint32_t data, hi, lo, *tab; + const int l = BCH_ECC_WORDS(bch); + const int plen = DIV_ROUND_UP(bch->ecc_bits+1, 32); + const int ecclen = DIV_ROUND_UP(bch->ecc_bits, 32); + + memset(bch->mod8_tab, 0, 4*256*l*sizeof(*bch->mod8_tab)); + + for (i = 0; i < 256; i++) { + /* p(X)=i is a small polynomial of weight <= 8 */ + for (b = 0; b < 4; b++) { + /* we want to compute (p(X).X^(8*b+deg(g))) mod g(X) */ + tab = bch->mod8_tab + (b*256+i)*l; + data = i << (8*b); + while (data) { + d = deg(data); + /* subtract X^d.g(X) from p(X).X^(8*b+deg(g)) */ + data ^= g[0] >> (31-d); + for (j = 0; j < ecclen; j++) { + hi = (d < 31) ? g[j] << (d+1) : 0; + lo = (j+1 < plen) ? + g[j+1] >> (31-d) : 0; + tab[j] ^= hi|lo; + } + } + } + } +} + +/* + * build a base for factoring degree 2 polynomials + */ +static int build_deg2_base(struct bch_control *bch) +{ + const int m = GF_M(bch); + int i, j, r; + unsigned int sum, x, y, remaining, ak = 0, xi[m]; + + /* find k s.t. Tr(a^k) = 1 and 0 <= k < m */ + for (i = 0; i < m; i++) { + for (j = 0, sum = 0; j < m; j++) + sum ^= a_pow(bch, i*(1 << j)); + + if (sum) { + ak = bch->a_pow_tab[i]; + break; + } + } + /* find xi, i=0..m-1 such that xi^2+xi = a^i+Tr(a^i).a^k */ + remaining = m; + memset(xi, 0, sizeof(xi)); + + for (x = 0; (x <= GF_N(bch)) && remaining; x++) { + y = gf_sqr(bch, x)^x; + for (i = 0; i < 2; i++) { + r = a_log(bch, y); + if (y && (r < m) && !xi[r]) { + bch->xi_tab[r] = x; + xi[r] = 1; + remaining--; + dbg("x%d = %x\n", r, x); + break; + } + y ^= ak; + } + } + /* should not happen but check anyway */ + return remaining ? -1 : 0; +} + +static void *bch_alloc(size_t size, int *err) +{ + void *ptr; + + ptr = kmalloc(size, GFP_KERNEL); + if (ptr == NULL) + *err = 1; + return ptr; +} + +/* + * compute generator polynomial for given (m,t) parameters. + */ +static uint32_t *compute_generator_polynomial(struct bch_control *bch) +{ + const unsigned int m = GF_M(bch); + const unsigned int t = GF_T(bch); + int n, err = 0; + unsigned int i, j, nbits, r, word, *roots; + struct gf_poly *g; + uint32_t *genpoly; + + g = bch_alloc(GF_POLY_SZ(m*t), &err); + roots = bch_alloc((bch->n+1)*sizeof(*roots), &err); + genpoly = bch_alloc(DIV_ROUND_UP(m*t+1, 32)*sizeof(*genpoly), &err); + + if (err) { + kfree(genpoly); + genpoly = NULL; + goto finish; + } + + /* enumerate all roots of g(X) */ + memset(roots , 0, (bch->n+1)*sizeof(*roots)); + for (i = 0; i < t; i++) { + for (j = 0, r = 2*i+1; j < m; j++) { + roots[r] = 1; + r = mod_s(bch, 2*r); + } + } + /* build generator polynomial g(X) */ + g->deg = 0; + g->c[0] = 1; + for (i = 0; i < GF_N(bch); i++) { + if (roots[i]) { + /* multiply g(X) by (X+root) */ + r = bch->a_pow_tab[i]; + g->c[g->deg+1] = 1; + for (j = g->deg; j > 0; j--) + g->c[j] = gf_mul(bch, g->c[j], r)^g->c[j-1]; + + g->c[0] = gf_mul(bch, g->c[0], r); + g->deg++; + } + } + /* store left-justified binary representation of g(X) */ + n = g->deg+1; + i = 0; + + while (n > 0) { + nbits = (n > 32) ? 32 : n; + for (j = 0, word = 0; j < nbits; j++) { + if (g->c[n-1-j]) + word |= 1u << (31-j); + } + genpoly[i++] = word; + n -= nbits; + } + bch->ecc_bits = g->deg; + +finish: + kfree(g); + kfree(roots); + + return genpoly; +} + +/** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ +struct bch_control *init_bch(int m, int t, unsigned int prim_poly) +{ + int err = 0; + unsigned int i, words; + uint32_t *genpoly; + struct bch_control *bch = NULL; + + const int min_m = 5; + const int max_m = 15; + + /* default primitive polynomials */ + static const unsigned int prim_poly_tab[] = { + 0x25, 0x43, 0x83, 0x11d, 0x211, 0x409, 0x805, 0x1053, 0x201b, + 0x402b, 0x8003, + }; + +#if defined(CONFIG_BCH_CONST_PARAMS) + if ((m != (CONFIG_BCH_CONST_M)) || (t != (CONFIG_BCH_CONST_T))) { + printk(KERN_ERR "bch encoder/decoder was configured to support " + "parameters m=%d, t=%d only!\n", + CONFIG_BCH_CONST_M, CONFIG_BCH_CONST_T); + goto fail; + } +#endif + if ((m < min_m) || (m > max_m)) + /* + * values of m greater than 15 are not currently supported; + * supporting m > 15 would require changing table base type + * (uint16_t) and a small patch in matrix transposition + */ + goto fail; + + /* sanity checks */ + if ((t < 1) || (m*t >= ((1 << m)-1))) + /* invalid t value */ + goto fail; + + /* select a primitive polynomial for generating GF(2^m) */ + if (prim_poly == 0) + prim_poly = prim_poly_tab[m-min_m]; + + bch = kzalloc(sizeof(*bch), GFP_KERNEL); + if (bch == NULL) + goto fail; + + bch->m = m; + bch->t = t; + bch->n = (1 << m)-1; + words = DIV_ROUND_UP(m*t, 32); + bch->ecc_bytes = DIV_ROUND_UP(m*t, 8); + bch->a_pow_tab = bch_alloc((1+bch->n)*sizeof(*bch->a_pow_tab), &err); + bch->a_log_tab = bch_alloc((1+bch->n)*sizeof(*bch->a_log_tab), &err); + bch->mod8_tab = bch_alloc(words*1024*sizeof(*bch->mod8_tab), &err); + bch->ecc_buf = bch_alloc(words*sizeof(*bch->ecc_buf), &err); + bch->ecc_buf2 = bch_alloc(words*sizeof(*bch->ecc_buf2), &err); + bch->xi_tab = bch_alloc(m*sizeof(*bch->xi_tab), &err); + bch->syn = bch_alloc(2*t*sizeof(*bch->syn), &err); + bch->cache = bch_alloc(2*t*sizeof(*bch->cache), &err); + bch->elp = bch_alloc((t+1)*sizeof(struct gf_poly_deg1), &err); + + for (i = 0; i < ARRAY_SIZE(bch->poly_2t); i++) + bch->poly_2t[i] = bch_alloc(GF_POLY_SZ(2*t), &err); + + if (err) + goto fail; + + err = build_gf_tables(bch, prim_poly); + if (err) + goto fail; + + /* use generator polynomial for computing encoding tables */ + genpoly = compute_generator_polynomial(bch); + if (genpoly == NULL) + goto fail; + + build_mod8_tables(bch, genpoly); + kfree(genpoly); + + err = build_deg2_base(bch); + if (err) + goto fail; + + return bch; + +fail: + free_bch(bch); + return NULL; +} +EXPORT_SYMBOL_GPL(init_bch); + +/** + * free_bch - free the BCH control structure + * @bch: BCH control structure to release + */ +void free_bch(struct bch_control *bch) +{ + unsigned int i; + + if (bch) { + kfree(bch->a_pow_tab); + kfree(bch->a_log_tab); + kfree(bch->mod8_tab); + kfree(bch->ecc_buf); + kfree(bch->ecc_buf2); + kfree(bch->xi_tab); + kfree(bch->syn); + kfree(bch->cache); + kfree(bch->elp); + + for (i = 0; i < ARRAY_SIZE(bch->poly_2t); i++) + kfree(bch->poly_2t[i]); + + kfree(bch); + } +} +EXPORT_SYMBOL_GPL(free_bch); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Djelic "); +MODULE_DESCRIPTION("Binary BCH encoder/decoder"); -- cgit v0.10.2 From 1065cda8a1a57d0d3bfccdce1a655a84439d24e0 Mon Sep 17 00:00:00 2001 From: Steffen Sledz Date: Thu, 10 Mar 2011 09:05:12 +0100 Subject: mtd: cfi: add support for AMIC flashes (e.g. A29L160AT) Signed-off-by: Steffen Sledz Signed-off-by: David Woodhouse diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c index 7e9c4e9..f9a5331 100644 --- a/drivers/mtd/chips/cfi_cmdset_0002.c +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -349,6 +349,7 @@ static struct cfi_fixup cfi_fixup_table[] = { { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, #ifdef AMD_BOOTLOC_BUG { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock }, + { CFI_MFR_AMIC, CFI_ID_ANY, fixup_amd_bootblock }, { CFI_MFR_MACRONIX, CFI_ID_ANY, fixup_amd_bootblock }, #endif { CFI_MFR_AMD, 0x0050, fixup_use_secsi }, diff --git a/include/linux/mtd/cfi.h b/include/linux/mtd/cfi.h index a9baee6..0d823f2 100644 --- a/include/linux/mtd/cfi.h +++ b/include/linux/mtd/cfi.h @@ -535,6 +535,7 @@ struct cfi_fixup { #define CFI_MFR_CONTINUATION 0x007F #define CFI_MFR_AMD 0x0001 +#define CFI_MFR_AMIC 0x0037 #define CFI_MFR_ATMEL 0x001F #define CFI_MFR_EON 0x001C #define CFI_MFR_FUJITSU 0x0004 -- cgit v0.10.2 From 629286b9561982e90a6d49893a1c641e71b6a2a3 Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Thu, 10 Mar 2011 22:31:39 +0800 Subject: mtd: sm_rtl: check kmalloc return value Because malloc/kzalloc may fail, we should check kmalloc/kzalloc return value in sm_create_sysfs_attributes(), mtd/sm_rtl.c and do error handling. Meanwhile, we should check sm_create_sysfs_attributes return value. Signed-off-by: Xiaochen Wang Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c index 67822cf..6405a95 100644 --- a/drivers/mtd/sm_ftl.c +++ b/drivers/mtd/sm_ftl.c @@ -64,12 +64,16 @@ struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl) SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET); char *vendor = kmalloc(vendor_len, GFP_KERNEL); + if (!vendor) + goto error1; memcpy(vendor, ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, vendor_len); vendor[vendor_len] = 0; /* Initialize sysfs attributes */ vendor_attribute = kzalloc(sizeof(struct sm_sysfs_attribute), GFP_KERNEL); + if (!vendor_attribute) + goto error2; sysfs_attr_init(&vendor_attribute->dev_attr.attr); @@ -83,12 +87,24 @@ struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl) /* Create array of pointers to the attributes */ attributes = kzalloc(sizeof(struct attribute *) * (NUM_ATTRIBUTES + 1), GFP_KERNEL); + if (!attributes) + goto error3; attributes[0] = &vendor_attribute->dev_attr.attr; /* Finally create the attribute group */ attr_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); + if (!attr_group) + goto error4; attr_group->attrs = attributes; return attr_group; +error4: + kfree(attributes); +error3: + kfree(vendor_attribute); +error2: + kfree(vendor); +error1: + return NULL; } void sm_delete_sysfs_attributes(struct sm_ftl *ftl) @@ -1178,6 +1194,8 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) } ftl->disk_attributes = sm_create_sysfs_attributes(ftl); + if (!ftl->disk_attributes) + goto error6; trans->disk_attributes = ftl->disk_attributes; sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d", -- cgit v0.10.2 From 89d8d32060de17c23f761df74799c7c07b79dd01 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Fri, 11 Mar 2011 15:17:41 +0000 Subject: mtd: fix printf format warnings, mostly lack of %zd for size_t, in mtdswap Signed-off-by: David Woodhouse diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c index 57e6cc3..a65594f 100644 --- a/drivers/mtd/mtdswap.c +++ b/drivers/mtd/mtdswap.c @@ -331,7 +331,7 @@ static int mtdswap_read_oob(struct mtdswap_dev *d, loff_t from, if (ops->oobretlen < ops->ooblen) { dev_warn(d->dev, "Read OOB return short read (%zd bytes not " - "%d) for block at %08llx\n", + "%zd) for block at %08llx\n", ops->oobretlen, ops->ooblen, from); return -EIO; } @@ -421,7 +421,7 @@ static int mtdswap_write_marker(struct mtdswap_dev *d, struct swap_eb *eb, if (ops.oobretlen != ops.ooblen) { dev_warn(d->dev, "Short OOB write for block at %08llx: " - "%zd not %d\n", + "%zd not %zd\n", offset, ops.oobretlen, ops.ooblen); return ret; } @@ -705,13 +705,13 @@ retry: } if (ret < 0) { - dev_err(d->dev, "Write to MTD device failed: %d (%d written)", + dev_err(d->dev, "Write to MTD device failed: %d (%zd written)", ret, retlen); goto err; } if (retlen != PAGE_SIZE) { - dev_err(d->dev, "Short write to MTD device: %d written", + dev_err(d->dev, "Short write to MTD device: %zd written", retlen); ret = -EIO; goto err; @@ -758,7 +758,7 @@ retry: } if (retlen != PAGE_SIZE) { - dev_err(d->dev, "Short read: %d (block %u)\n", retlen, + dev_err(d->dev, "Short read: %zd (block %u)\n", retlen, oldblock); ret = -EIO; goto read_error; @@ -1188,7 +1188,7 @@ retry: } if (retlen != PAGE_SIZE) { - dev_err(d->dev, "Short read %d\n", retlen); + dev_err(d->dev, "Short read %zd\n", retlen); return -EIO; } @@ -1304,7 +1304,7 @@ static int mtdswap_show(struct seq_file *s, void *data) seq_printf(s, "discarded pages count: %llu\n", d->discard_page_count); seq_printf(s, "\n"); - seq_printf(s, "total pages: %lu\n", pages); + seq_printf(s, "total pages: %u\n", pages); seq_printf(s, "pages mapped: %u\n", mapped); return 0; @@ -1458,7 +1458,7 @@ static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) oinfo = mtd->ecclayout; if (!mtd->oobsize || !oinfo || oinfo->oobavail < MTDSWAP_OOBSIZE) { printk(KERN_ERR "%s: Not enough free bytes in OOB, " - "%d available, %u needed.\n", + "%d available, %lu needed.\n", MTDSWAP_PREFIX, oinfo->oobavail, MTDSWAP_OOBSIZE); return; } -- cgit v0.10.2 From 193bd40026443835e1b96c79d5efe559d01509ae Mon Sep 17 00:00:00 2001 From: Ivan Djelic Date: Fri, 11 Mar 2011 11:05:33 +0100 Subject: mtd: nand: add software BCH ECC support This patch adds software BCH ECC support to mtd, in order to handle recent NAND device ecc requirements (4 bits or more). It does so by adding a new ecc mode (NAND_ECC_SOFT_BCH) for use by board drivers, and a new Kconfig option to enable BCH support. It relies on the generic BCH library introduced in a previous patch. When a board driver uses mode NAND_ECC_SOFT_BCH, it should also set fields chip->ecc.size and chip->ecc.bytes to select BCH ecc data size and required error correction capability. See nand_bch_init() documentation for details. It has been tested on the following platforms using mtd-utils, UBI and UBIFS: x86 (with nandsim), arm926ejs. Signed-off-by: Ivan Djelic Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index c895922..78205ac 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -31,6 +31,21 @@ config MTD_NAND_VERIFY_WRITE device thinks the write was successful, a bit could have been flipped accidentally due to device wear or something else. +config MTD_NAND_BCH + tristate + select BCH + depends on MTD_NAND_ECC_BCH + default MTD_NAND + +config MTD_NAND_ECC_BCH + bool "Support software BCH ECC" + default n + help + This enables support for software BCH error correction. Binary BCH + codes are more powerful and cpu intensive than traditional Hamming + ECC codes. They are used with NAND devices requiring more than 1 bit + of error correction. + config MTD_SM_COMMON tristate default n diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 8ad6fae..5745d83 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_MTD_NAND) += nand.o obj-$(CONFIG_MTD_NAND_ECC) += nand_ecc.o +obj-$(CONFIG_MTD_NAND_BCH) += nand_bch.o obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index da76040..85cfc06 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -3248,7 +3249,7 @@ int nand_scan_tail(struct mtd_info *mtd) /* * If no default placement scheme is given, select an appropriate one */ - if (!chip->ecc.layout) { + if (!chip->ecc.layout && (chip->ecc.mode != NAND_ECC_SOFT_BCH)) { switch (mtd->oobsize) { case 8: chip->ecc.layout = &nand_oob_8; @@ -3351,6 +3352,40 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.bytes = 3; break; + case NAND_ECC_SOFT_BCH: + if (!mtd_nand_has_bch()) { + printk(KERN_WARNING "CONFIG_MTD_ECC_BCH not enabled\n"); + BUG(); + } + chip->ecc.calculate = nand_bch_calculate_ecc; + chip->ecc.correct = nand_bch_correct_data; + chip->ecc.read_page = nand_read_page_swecc; + chip->ecc.read_subpage = nand_read_subpage; + chip->ecc.write_page = nand_write_page_swecc; + chip->ecc.read_page_raw = nand_read_page_raw; + chip->ecc.write_page_raw = nand_write_page_raw; + chip->ecc.read_oob = nand_read_oob_std; + chip->ecc.write_oob = nand_write_oob_std; + /* + * Board driver should supply ecc.size and ecc.bytes values to + * select how many bits are correctable; see nand_bch_init() + * for details. + * Otherwise, default to 4 bits for large page devices + */ + if (!chip->ecc.size && (mtd->oobsize >= 64)) { + chip->ecc.size = 512; + chip->ecc.bytes = 7; + } + chip->ecc.priv = nand_bch_init(mtd, + chip->ecc.size, + chip->ecc.bytes, + &chip->ecc.layout); + if (!chip->ecc.priv) { + printk(KERN_WARNING "BCH ECC initialization failed!\n"); + BUG(); + } + break; + case NAND_ECC_NONE: printk(KERN_WARNING "NAND_ECC_NONE selected by board driver. " "This is not recommended !!\n"); @@ -3501,6 +3536,9 @@ void nand_release(struct mtd_info *mtd) { struct nand_chip *chip = mtd->priv; + if (chip->ecc.mode == NAND_ECC_SOFT_BCH) + nand_bch_free((struct nand_bch_control *)chip->ecc.priv); + #ifdef CONFIG_MTD_PARTITIONS /* Deregister partitions */ del_mtd_partitions(mtd); diff --git a/drivers/mtd/nand/nand_bch.c b/drivers/mtd/nand/nand_bch.c new file mode 100644 index 0000000..0f931e7 --- /dev/null +++ b/drivers/mtd/nand/nand_bch.c @@ -0,0 +1,243 @@ +/* + * This file provides ECC correction for more than 1 bit per block of data, + * using binary BCH codes. It relies on the generic BCH library lib/bch.c. + * + * Copyright © 2011 Ivan Djelic + * + * This file 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 or (at your option) any + * later version. + * + * This file 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 file; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct nand_bch_control - private NAND BCH control structure + * @bch: BCH control structure + * @ecclayout: private ecc layout for this BCH configuration + * @errloc: error location array + * @eccmask: XOR ecc mask, allows erased pages to be decoded as valid + */ +struct nand_bch_control { + struct bch_control *bch; + struct nand_ecclayout ecclayout; + unsigned int *errloc; + unsigned char *eccmask; +}; + +/** + * nand_bch_calculate_ecc - [NAND Interface] Calculate ECC for data block + * @mtd: MTD block structure + * @buf: input buffer with raw data + * @code: output buffer with ECC + */ +int nand_bch_calculate_ecc(struct mtd_info *mtd, const unsigned char *buf, + unsigned char *code) +{ + const struct nand_chip *chip = mtd->priv; + struct nand_bch_control *nbc = chip->ecc.priv; + unsigned int i; + + memset(code, 0, chip->ecc.bytes); + encode_bch(nbc->bch, buf, chip->ecc.size, code); + + /* apply mask so that an erased page is a valid codeword */ + for (i = 0; i < chip->ecc.bytes; i++) + code[i] ^= nbc->eccmask[i]; + + return 0; +} +EXPORT_SYMBOL(nand_bch_calculate_ecc); + +/** + * nand_bch_correct_data - [NAND Interface] Detect and correct bit error(s) + * @mtd: MTD block structure + * @buf: raw data read from the chip + * @read_ecc: ECC from the chip + * @calc_ecc: the ECC calculated from raw data + * + * Detect and correct bit errors for a data byte block + */ +int nand_bch_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + const struct nand_chip *chip = mtd->priv; + struct nand_bch_control *nbc = chip->ecc.priv; + unsigned int *errloc = nbc->errloc; + int i, count; + + count = decode_bch(nbc->bch, NULL, chip->ecc.size, read_ecc, calc_ecc, + NULL, errloc); + if (count > 0) { + for (i = 0; i < count; i++) { + if (errloc[i] < (chip->ecc.size*8)) + /* error is located in data, correct it */ + buf[errloc[i] >> 3] ^= (1 << (errloc[i] & 7)); + /* else error in ecc, no action needed */ + + DEBUG(MTD_DEBUG_LEVEL0, "%s: corrected bitflip %u\n", + __func__, errloc[i]); + } + } else if (count < 0) { + printk(KERN_ERR "ecc unrecoverable error\n"); + count = -1; + } + return count; +} +EXPORT_SYMBOL(nand_bch_correct_data); + +/** + * nand_bch_init - [NAND Interface] Initialize NAND BCH error correction + * @mtd: MTD block structure + * @eccsize: ecc block size in bytes + * @eccbytes: ecc length in bytes + * @ecclayout: output default layout + * + * Returns: + * a pointer to a new NAND BCH control structure, or NULL upon failure + * + * Initialize NAND BCH error correction. Parameters @eccsize and @eccbytes + * are used to compute BCH parameters m (Galois field order) and t (error + * correction capability). @eccbytes should be equal to the number of bytes + * required to store m*t bits, where m is such that 2^m-1 > @eccsize*8. + * + * Example: to configure 4 bit correction per 512 bytes, you should pass + * @eccsize = 512 (thus, m=13 is the smallest integer such that 2^m-1 > 512*8) + * @eccbytes = 7 (7 bytes are required to store m*t = 13*4 = 52 bits) + */ +struct nand_bch_control * +nand_bch_init(struct mtd_info *mtd, unsigned int eccsize, unsigned int eccbytes, + struct nand_ecclayout **ecclayout) +{ + unsigned int m, t, eccsteps, i; + struct nand_ecclayout *layout; + struct nand_bch_control *nbc = NULL; + unsigned char *erased_page; + + if (!eccsize || !eccbytes) { + printk(KERN_WARNING "ecc parameters not supplied\n"); + goto fail; + } + + m = fls(1+8*eccsize); + t = (eccbytes*8)/m; + + nbc = kzalloc(sizeof(*nbc), GFP_KERNEL); + if (!nbc) + goto fail; + + nbc->bch = init_bch(m, t, 0); + if (!nbc->bch) + goto fail; + + /* verify that eccbytes has the expected value */ + if (nbc->bch->ecc_bytes != eccbytes) { + printk(KERN_WARNING "invalid eccbytes %u, should be %u\n", + eccbytes, nbc->bch->ecc_bytes); + goto fail; + } + + eccsteps = mtd->writesize/eccsize; + + /* if no ecc placement scheme was provided, build one */ + if (!*ecclayout) { + + /* handle large page devices only */ + if (mtd->oobsize < 64) { + printk(KERN_WARNING "must provide an oob scheme for " + "oobsize %d\n", mtd->oobsize); + goto fail; + } + + layout = &nbc->ecclayout; + layout->eccbytes = eccsteps*eccbytes; + + /* reserve 2 bytes for bad block marker */ + if (layout->eccbytes+2 > mtd->oobsize) { + printk(KERN_WARNING "no suitable oob scheme available " + "for oobsize %d eccbytes %u\n", mtd->oobsize, + eccbytes); + goto fail; + } + /* put ecc bytes at oob tail */ + for (i = 0; i < layout->eccbytes; i++) + layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i; + + layout->oobfree[0].offset = 2; + layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes; + + *ecclayout = layout; + } + + /* sanity checks */ + if (8*(eccsize+eccbytes) >= (1 << m)) { + printk(KERN_WARNING "eccsize %u is too large\n", eccsize); + goto fail; + } + if ((*ecclayout)->eccbytes != (eccsteps*eccbytes)) { + printk(KERN_WARNING "invalid ecc layout\n"); + goto fail; + } + + nbc->eccmask = kmalloc(eccbytes, GFP_KERNEL); + nbc->errloc = kmalloc(t*sizeof(*nbc->errloc), GFP_KERNEL); + if (!nbc->eccmask || !nbc->errloc) + goto fail; + /* + * compute and store the inverted ecc of an erased ecc block + */ + erased_page = kmalloc(eccsize, GFP_KERNEL); + if (!erased_page) + goto fail; + + memset(erased_page, 0xff, eccsize); + memset(nbc->eccmask, 0, eccbytes); + encode_bch(nbc->bch, erased_page, eccsize, nbc->eccmask); + kfree(erased_page); + + for (i = 0; i < eccbytes; i++) + nbc->eccmask[i] ^= 0xff; + + return nbc; +fail: + nand_bch_free(nbc); + return NULL; +} +EXPORT_SYMBOL(nand_bch_init); + +/** + * nand_bch_free - [NAND Interface] Release NAND BCH ECC resources + * @nbc: NAND BCH control structure + */ +void nand_bch_free(struct nand_bch_control *nbc) +{ + if (nbc) { + free_bch(nbc->bch); + kfree(nbc->errloc); + kfree(nbc->eccmask); + kfree(nbc); + } +} +EXPORT_SYMBOL(nand_bch_free); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Djelic "); +MODULE_DESCRIPTION("NAND software BCH ECC support"); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 1f489b2..ae67ef5 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -140,6 +140,7 @@ typedef enum { NAND_ECC_HW, NAND_ECC_HW_SYNDROME, NAND_ECC_HW_OOB_FIRST, + NAND_ECC_SOFT_BCH, } nand_ecc_modes_t; /* @@ -339,6 +340,7 @@ struct nand_hw_control { * @prepad: padding information for syndrome based ecc generators * @postpad: padding information for syndrome based ecc generators * @layout: ECC layout control struct pointer + * @priv: pointer to private ecc control data * @hwctl: function to control hardware ecc generator. Must only * be provided if an hardware ECC is available * @calculate: function for ecc calculation or readback from ecc hardware @@ -362,6 +364,7 @@ struct nand_ecc_ctrl { int prepad; int postpad; struct nand_ecclayout *layout; + void *priv; void (*hwctl)(struct mtd_info *mtd, int mode); int (*calculate)(struct mtd_info *mtd, const uint8_t *dat, uint8_t *ecc_code); diff --git a/include/linux/mtd/nand_bch.h b/include/linux/mtd/nand_bch.h new file mode 100644 index 0000000..74acf53 --- /dev/null +++ b/include/linux/mtd/nand_bch.h @@ -0,0 +1,72 @@ +/* + * Copyright © 2011 Ivan Djelic + * + * 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. + * + * This file is the header for the NAND BCH ECC implementation. + */ + +#ifndef __MTD_NAND_BCH_H__ +#define __MTD_NAND_BCH_H__ + +struct mtd_info; +struct nand_bch_control; + +#if defined(CONFIG_MTD_NAND_ECC_BCH) + +static inline int mtd_nand_has_bch(void) { return 1; } + +/* + * Calculate BCH ecc code + */ +int nand_bch_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code); + +/* + * Detect and correct bit errors + */ +int nand_bch_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, + u_char *calc_ecc); +/* + * Initialize BCH encoder/decoder + */ +struct nand_bch_control * +nand_bch_init(struct mtd_info *mtd, unsigned int eccsize, + unsigned int eccbytes, struct nand_ecclayout **ecclayout); +/* + * Release BCH encoder/decoder resources + */ +void nand_bch_free(struct nand_bch_control *nbc); + +#else /* !CONFIG_MTD_NAND_ECC_BCH */ + +static inline int mtd_nand_has_bch(void) { return 0; } + +static inline int +nand_bch_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + return -1; +} + +static inline int +nand_bch_correct_data(struct mtd_info *mtd, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + return -1; +} + +static inline struct nand_bch_control * +nand_bch_init(struct mtd_info *mtd, unsigned int eccsize, + unsigned int eccbytes, struct nand_ecclayout **ecclayout) +{ + return NULL; +} + +static inline void nand_bch_free(struct nand_bch_control *nbc) {} + +#endif /* CONFIG_MTD_NAND_ECC_BCH */ + +#endif /* __MTD_NAND_BCH_H__ */ -- cgit v0.10.2 From fc2ff592b5b41f3f32e790dd124eeb3bc80706c8 Mon Sep 17 00:00:00 2001 From: Ivan Djelic Date: Fri, 11 Mar 2011 11:05:34 +0100 Subject: mtd: nand: enable software BCH ECC in nand simulator This patch adds option 'bch' to nandsim, which can be used to enable software BCH ECC (introduced in previous patches) and select BCH error correction capability. Signed-off-by: Ivan Djelic Signed-off-by: David Woodhouse diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index a5aa99f..213181b 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ static unsigned int rptwear = 0; static unsigned int overridesize = 0; static char *cache_file = NULL; static unsigned int bbt; +static unsigned int bch; module_param(first_id_byte, uint, 0400); module_param(second_id_byte, uint, 0400); @@ -132,6 +134,7 @@ module_param(rptwear, uint, 0400); module_param(overridesize, uint, 0400); module_param(cache_file, charp, 0400); module_param(bbt, uint, 0400); +module_param(bch, uint, 0400); MODULE_PARM_DESC(first_id_byte, "The first byte returned by NAND Flash 'read ID' command (manufacturer ID)"); MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); @@ -165,6 +168,8 @@ MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the I " e.g. 5 means a size of 32 erase blocks"); MODULE_PARM_DESC(cache_file, "File to use to cache nand pages instead of memory"); MODULE_PARM_DESC(bbt, "0 OOB, 1 BBT with marker in OOB, 2 BBT with marker in data area"); +MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should " + "be correctable in 512-byte blocks"); /* The largest possible page size */ #define NS_LARGEST_PAGE_SIZE 4096 @@ -2309,7 +2314,43 @@ static int __init ns_init_module(void) if ((retval = parse_gravepages()) != 0) goto error; - if ((retval = nand_scan(nsmtd, 1)) != 0) { + retval = nand_scan_ident(nsmtd, 1, NULL); + if (retval) { + NS_ERR("cannot scan NAND Simulator device\n"); + if (retval > 0) + retval = -ENXIO; + goto error; + } + + if (bch) { + unsigned int eccsteps, eccbytes; + if (!mtd_nand_has_bch()) { + NS_ERR("BCH ECC support is disabled\n"); + retval = -EINVAL; + goto error; + } + /* use 512-byte ecc blocks */ + eccsteps = nsmtd->writesize/512; + eccbytes = (bch*13+7)/8; + /* do not bother supporting small page devices */ + if ((nsmtd->oobsize < 64) || !eccsteps) { + NS_ERR("bch not available on small page devices\n"); + retval = -EINVAL; + goto error; + } + if ((eccbytes*eccsteps+2) > nsmtd->oobsize) { + NS_ERR("invalid bch value %u\n", bch); + retval = -EINVAL; + goto error; + } + chip->ecc.mode = NAND_ECC_SOFT_BCH; + chip->ecc.size = 512; + chip->ecc.bytes = eccbytes; + NS_INFO("using %u-bit/%u bytes BCH ECC\n", bch, chip->ecc.size); + } + + retval = nand_scan_tail(nsmtd); + if (retval) { NS_ERR("can't register NAND Simulator\n"); if (retval > 0) retval = -ENXIO; -- cgit v0.10.2 From e21fa86ad0ffa2c292b104ea19bdaa46ceab7105 Mon Sep 17 00:00:00 2001 From: Yang Ruirui Date: Mon, 14 Mar 2011 09:50:56 +0800 Subject: mtdswap: kill strict error handling option As Artem Bityutskiy suggested: this option is controversial, as we do not really have any prove it is useful. And it is too much to have it as a config parameter. So, better kill it and let the real mtdswap users decide whether it is needed or not. Signed-off-by: Yang Ruirui Signed-off-by: Artem Bityutskiy diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 5fe01d5..b4567c3 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -335,14 +335,6 @@ config MTD_SWAP The driver provides wear leveling by storing erase counter into the OOB. -config MTD_SWAP_STRICT - bool "Strict erase error handling" - depends on MTD_SWAP - help - Enables strict tolerance on failed erasures, marking erase blocks bad - right after the first failed operation. With non-strict mode the - erase operation is retried. - source "drivers/mtd/chips/Kconfig" source "drivers/mtd/maps/Kconfig" diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c index a65594f..dc4b136 100644 --- a/drivers/mtd/mtdswap.c +++ b/drivers/mtd/mtdswap.c @@ -156,12 +156,6 @@ struct mtdswap_oobdata { #define MTDSWAP_ERASE_RETRIES 3 /* Before marking erase block bad */ #define MTDSWAP_IO_RETRIES 3 -#ifdef CONFIG_MTD_SWAP_STRICT -#define MTDSWAP_STRICT 1 -#else -#define MTDSWAP_STRICT 0 -#endif - enum { MTDSWAP_SCANNED_CLEAN, MTDSWAP_SCANNED_DIRTY, @@ -575,7 +569,7 @@ retry: ret = mtd->erase(mtd, &erase); if (ret) { - if (retries++ < MTDSWAP_ERASE_RETRIES && !MTDSWAP_STRICT) { + if (retries++ < MTDSWAP_ERASE_RETRIES) { dev_warn(d->dev, "erase of erase block %#llx on %s failed", erase.addr, mtd->name); -- cgit v0.10.2 From 68b1a1e786f29c900fa1c516a402e24f0ece622a Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Fri, 25 Mar 2011 17:10:16 +0200 Subject: mtd: mtdswap: fix compilation warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following compilation warning: drivers/mtd/mtdswap.c: In function ‘mtdswap_pick_gc_eblk’: drivers/mtd/mtdswap.c:859: warning: ‘idx’ may be used uninitialized in this function drivers/mtd/mtdswap.c:859: note: ‘idx’ was declared here Initialize idx to -1 because this value means "no tree choosen". Signed-off-by: Artem Bityutskiy diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c index dc4b136..237913c 100644 --- a/drivers/mtd/mtdswap.c +++ b/drivers/mtd/mtdswap.c @@ -850,7 +850,7 @@ static int mtdswap_wlfreq(unsigned int maxdiff) static int mtdswap_choose_wl_tree(struct mtdswap_dev *d) { static unsigned int pick_cnt; - unsigned int i, idx, wear, max; + unsigned int i, idx = -1, wear, max; struct rb_root *root; max = 0; -- cgit v0.10.2