diff options
Diffstat (limited to 'drivers/mtd/nand/gpmi/gpmi-bch.c')
-rw-r--r-- | drivers/mtd/nand/gpmi/gpmi-bch.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/drivers/mtd/nand/gpmi/gpmi-bch.c b/drivers/mtd/nand/gpmi/gpmi-bch.c new file mode 100644 index 000000000000..58f7354bcd18 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bch.c @@ -0,0 +1,289 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * STMP378X BCH hardware ECC engine + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> + +#include <asm/dma.h> +#include <mach/stmp3xxx.h> +#include <mach/irqs.h> +#include "gpmi.h" + +#define BCH_MAX_NANDS 4 + +static int bch_available(void *context); +static int bch_setup(void *context, int index, int writesize, int oobsize); +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int bch_stat(void *ctx, int index, struct mtd_ecc_stats *r); +static int bch_reset(void *context, int index); + +struct bch_state_t { + struct gpmi_hwecc_chip chip; + struct { + struct mtd_ecc_stats stat; + struct completion done; + u32 writesize, oobsize; + } nands[BCH_MAX_NANDS]; +}; + +static struct bch_state_t state = { + .chip = { + .name = "bch", + .setup = bch_setup, + .stat = bch_stat, + .read = bch_read, + .reset = bch_reset, + }, +}; + +static int bch_reset(void *context, int index) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, true); + HW_BCH_CTRL_SET(BM_BCH_CTRL_COMPLETE_IRQ_EN); + return 0; +} + +static int bch_stat(void *context, int index, struct mtd_ecc_stats *r) +{ + struct bch_state_t *state = context; + + *r = state->nands[index].stat; + state->nands[index].stat.failed = 0; + state->nands[index].stat.corrected = 0; + return 0; +} + +static irqreturn_t bch_irq(int irq, void *context) +{ + u32 b0, s0; + struct mtd_ecc_stats stat; + int r; + struct bch_state_t *state = context; + + s0 = HW_BCH_STATUS0_RD(); + r = (s0 & BM_BCH_STATUS0_COMPLETED_CE) >> 16; + + stat.corrected = stat.failed = 0; + + b0 = (s0 & BM_BCH_STATUS0_STATUS_BLK0) >> 8; + if (b0 <= 4) + stat.corrected += b0; + if (b0 == 0xFE) + stat.failed++; + + if (s0 & BM_BCH_STATUS0_CORRECTED) + stat.corrected += (s0 & BM_BCH_STATUS0_CORRECTED); + if (s0 & BM_BCH_STATUS0_UNCORRECTABLE) + stat.failed++; + + HW_BCH_CTRL_CLR(BM_BCH_CTRL_COMPLETE_IRQ); + + pr_debug("%s: chip %d, failed %d, corrected %d\n", + __func__, r, + state->nands[r].stat.failed, + state->nands[r].stat.corrected); + state->nands[r].stat.corrected += stat.corrected; + state->nands[r].stat.failed += stat.failed; + complete(&state->nands[r].done); + + return IRQ_HANDLED; +} + +static int bch_available(void *context) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, 0); + return HW_BCH_BLOCKNAME_RD() == 0x20484342; +} + +static int bch_setup(void *context, int index, int writesize, int oobsize) +{ + struct bch_state_t *state = context; + u32 layout = (REGS_BCH_BASE + 0x80 /* HW_BCH_FLASH0LAYOUT0_ADDR */) + + index * 0x20; + u32 ecc0, eccN; + int meta; + + switch (writesize) { + case 2048: + ecc0 = 4; + eccN = 4; + meta = 5; + break; + case 4096: + ecc0 = 16; + eccN = 14; + meta = 10; + break; + default: + printk(KERN_ERR"%s: cannot tune BCH for page size %d\n", + __func__, writesize); + return -EINVAL; + } + + state->nands[index].oobsize = oobsize; + state->nands[index].writesize = writesize; + + __raw_writel(BF_BCH_FLASH0LAYOUT0_NBLOCKS(writesize/512) | + BF_BCH_FLASH0LAYOUT0_META_SIZE(oobsize) | + BF_BCH_FLASH0LAYOUT0_ECC0(ecc0 >> 1) | /* for oob */ + BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(0x00), layout); + __raw_writel(BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(writesize + oobsize) | + BF_BCH_FLASH0LAYOUT1_ECCN(eccN >> 1) | /* for dblock */ + BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(512), layout + 0x10); + + /* + * since driver only supports CS 0..3, layouts are mapped 1:1 : + * FLASHnLAYOUT[1,2] => LAYOUTSELECT[n*2:n2*+1] + */ + HW_BCH_LAYOUTSELECT_CLR(0x03 << (index * 2)); + HW_BCH_LAYOUTSELECT_SET(index << (index * 2)); + + bch_reset(context, index); + + printk(KERN_DEBUG"%s: CS = %d, LAYOUT = 0x%08X, layout_reg = " + "0x%08x+0x%08x: 0x%08x+0x%08x\n", + __func__, + index, HW_BCH_LAYOUTSELECT_RD(), + layout, layout + 0x10, + __raw_readl(layout), + __raw_readl(layout+0x10)); + return 0; +} + +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + unsigned long readsize = 0; + u32 bufmask = 0; + struct bch_state_t *state = context; + + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + readsize += state->nands[index].oobsize; + } + if (!dma_mapping_error(NULL, page)) { + bufmask |= (BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + & ~BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY); + readsize += state->nands[index].writesize; + } + + printk(KERN_DEBUG"readsize = %ld, bufmask = 0x%X\n", readsize, bufmask); + bch_reset(context, index); + + /* wait for ready */ + chain->command->cmd = + BF_APBH_CHn_CMD_CMDWORDS(1) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_CHAIN | + BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER); + chain->command->pio_words[0] = + BF_GPMI_CTRL0_COMMAND_MODE( + BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) | + BF_GPMI_CTRL0_CS(index); + chain->command->alternate = 0; + chain++; + + /* enable BCH and read NAND data */ + chain->command->cmd = + BF_APBH_CHn_CMD_CMDWORDS(6) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER); + chain->command->pio_words[0] = + BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF_GPMI_CTRL0_CS(index) | + BF_GPMI_CTRL0_XFER_COUNT(readsize); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + BF_GPMI_ECCCTRL_ECC_CMD(0x02) | + BF_GPMI_ECCCTRL_BUFFER_MASK(bufmask); + chain->command->pio_words[3] = readsize; + chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0; + chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0; + chain->command->alternate = 0; + chain++; + + /* disable BCH block */ + chain->command->cmd = + BF_APBH_CHn_CMD_CMDWORDS(3) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER); + chain->command->pio_words[0] = + BF_GPMI_CTRL0_COMMAND_MODE( + BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF_GPMI_CTRL0_CS(index) | + BF_GPMI_CTRL0_XFER_COUNT(readsize); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->alternate = 0; + chain++; + + /* and deassert nand lock */ + chain->command->cmd = + BF_APBH_CHn_CMD_CMDWORDS(0) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF_APBH_CHn_CMD_COMMAND(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER); + chain->command->alternate = 0; + + return 0; +} + +int __init bch_init(void) +{ + int err; + + if (!bch_available(&state.chip)) + return -ENXIO; + gpmi_hwecc_chip_add(&state.chip); + err = request_irq(IRQ_BCH, bch_irq, 0, state.chip.name, &state); + if (err) + return err; + + printk(KERN_DEBUG"%s: initialized\n", __func__); + return 0; +} + +void bch_exit(void) +{ + free_irq(IRQ_BCH, &state); + gpmi_hwecc_chip_remove(&state.chip); +} |