[U-Boot] [PATCH] arm: iproc: add NAND driver
Scott Wood
scottwood at freescale.com
Wed Aug 12 03:17:21 CEST 2015
On Sat, Aug 08, 2015 at 05:58:04PM -0700, Steve Rae wrote:
> From: Jiandong Zheng <jdzheng at broadcom.com>
>
> Add support for the iproc NAND, and enable on Cygnus and NSP boards.
>
> Signed-off-by: Jiandong Zheng <jdzheng at broadcom.com>
> Signed-off-by: Steve Rae <srae at broadcom.com>
> ---
>
> arch/arm/include/asm/arch-bcmcygnus/configs.h | 11 +
> arch/arm/include/asm/arch-bcmnsp/configs.h | 11 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/iproc_nand.c | 1732 +++++++++++++++++++++++++
> drivers/mtd/nand/iproc_nand_cygnus.h | 111 ++
> drivers/mtd/nand/iproc_nand_ns_plus.h | 113 ++
> 6 files changed, 1979 insertions(+)
> create mode 100644 drivers/mtd/nand/iproc_nand.c
> create mode 100644 drivers/mtd/nand/iproc_nand_cygnus.h
> create mode 100644 drivers/mtd/nand/iproc_nand_ns_plus.h
>
> diff --git a/arch/arm/include/asm/arch-bcmcygnus/configs.h b/arch/arm/include/asm/arch-bcmcygnus/configs.h
> index 5354637..5338598 100644
> --- a/arch/arm/include/asm/arch-bcmcygnus/configs.h
> +++ b/arch/arm/include/asm/arch-bcmcygnus/configs.h
> @@ -10,6 +10,7 @@
> #include <asm/iproc-common/configs.h>
>
> /* uArchitecture specifics */
> +#define CONFIG_CYGNUS
Undocumented, should be in kconfig, and the name is too vague.
> /* Serial Info */
> /* Post pad 3 bytes after each reg addr */
> @@ -22,4 +23,14 @@
> #define CONFIG_CONS_INDEX 3
> #define CONFIG_SYS_NS16550_COM3 0x18023000
>
> +/* NAND configuration */
> +#define CONFIG_CMD_NAND
> +#define CONFIG_NAND_IPROC
> +#define CONFIG_IPROC_NAND_TIMING_MODE 5
> +#define CONFIG_SYS_NAND_BASE 0
> +#define CONFIG_SYS_MAX_NAND_DEVICE 1
> +#define CONFIG_SYS_MAX_NAND_CHIPS 1
> +#define CONFIG_SYS_NAND_SELF_INIT
> +#define CONFIG_SYS_NAND_ONFI_DETECTION
CONFIG_IPROC_NAND_TIMING_MODE and CONFIG_NAND_IPROC are undocumented, and
new symbols should be done in kconfig.
Please be consistent about CONFIG_NAND_IPROC* versus CONFIG_IPROC_NAND*
(I prefer the former).
If this timing mode is describing hardware, rather than user config, it
should be CONFIG_SYS_NAND_IPROC_TIMING_MODE.
> #endif /* __ARCH_CONFIGS_H */
> diff --git a/arch/arm/include/asm/arch-bcmnsp/configs.h b/arch/arm/include/asm/arch-bcmnsp/configs.h
> index 786deae..66f2266 100644
> --- a/arch/arm/include/asm/arch-bcmnsp/configs.h
> +++ b/arch/arm/include/asm/arch-bcmnsp/configs.h
> @@ -10,6 +10,7 @@
> #include <asm/iproc-common/configs.h>
>
> /* uArchitecture specifics */
> +#define CONFIG_NS_PLUS
Undocumented, should be in kconfig, and the name is too vague.
> +/*
> + * Definitions
> + */
> +#define DRV_NAME "iproc_nand"
I suggest using __func__ instead.
> +/*
> + * IRQ operations
> + */
> +#define NAND_ACK_IRQ(bit) writel(1, ((u32 *)ctrl.nand_intr_regs) + (bit))
> +
> +#define NAND_TEST_IRQ(bit) (readl(((u32 *)ctrl.nand_intr_regs) + (bit)) & 1)
This is the only place you ever use nand_intr_regs, so why not just
define it as a u32 pointer? Also, use __iomem.
> +
> +/*
> + * Data access macros for endianness
> + */
> +#ifdef __LITTLE_ENDIAN
> +#define NAND_BEGIN_DATA_ACCESS() \
> + writel(readl(ctrl.nand_idm_io_ctrl_direct_reg) | \
> + IDMFLD_NAND_IO_CONTROL_DIRECT_APB_LE_MODE, \
> + ctrl.nand_idm_io_ctrl_direct_reg)
> +#define NAND_END_DATA_ACCESS() \
> + writel(readl(ctrl.nand_idm_io_ctrl_direct_reg) & \
> + ~IDMFLD_NAND_IO_CONTROL_DIRECT_APB_LE_MODE, \
> + ctrl.nand_idm_io_ctrl_direct_reg)
> +#else /* !__LITTLE_ENDIAN */
> +#define NAND_BEGIN_DATA_ACCESS()
> +#define NAND_END_DATA_ACCESS()
> +#endif /* !__LITTLE_ENDIAN */
Huh?
> +
> +/*
> + * Misc NAND controller configuration/status macros
> + */
> +#define NC_REG_CONFIG(cs) (NCREG_CONFIG_CS0 + ((cs) << 4))
> +
> +#define WR_CONFIG(cs, field, val) do { \
> + u32 reg = NC_REG_CONFIG(cs), contents = NAND_REG_RD(reg); \
> + contents &= ~(NCFLD_CONFIG_CS0_##field##_MASK); \
> + contents |= (val) << NCFLD_CONFIG_CS0_##field##_SHIFT; \
> + NAND_REG_WR(reg, contents); \
> +} while (0)
> +
> +#define RD_CONFIG(cs, field) \
> + ((NAND_REG_RD(NC_REG_CONFIG(cs)) & NCFLD_CONFIG_CS0_##field##_MASK) \
> + >> NCFLD_CONFIG_CS0_##field##_SHIFT)
> +
> +#define NC_REG_ACC_CONTROL(cs) (NCREG_ACC_CONTROL_CS0 + ((cs) << 4))
> +
> +#define WR_ACC_CONTROL(cs, field, val) do { \
> + u32 reg = NC_REG_ACC_CONTROL(cs), contents = NAND_REG_RD(reg); \
> + contents &= ~(NCFLD_ACC_CONTROL_CS0_##field##_MASK); \
> + contents |= (val) << NCFLD_ACC_CONTROL_CS0_##field##_SHIFT; \
> + NAND_REG_WR(reg, contents); \
> +} while (0)
> +
> +#define RD_ACC_CONTROL(cs, field) \
> + ((NAND_REG_RD(NC_REG_ACC_CONTROL(cs)) & \
> + NCFLD_ACC_CONTROL_CS0_##field##_MASK) \
> + >> NCFLD_ACC_CONTROL_CS0_##field##_SHIFT)
> +
> +#define CORR_ERROR_COUNT (NAND_REG_RD(NCREG_CORR_ERROR_COUNT))
> +#define UNCORR_ERROR_COUNT (NAND_REG_RD(NCREG_UNCORR_ERROR_COUNT))
> +
> +#define WR_CORR_THRESH(cs, val) do { \
> + u32 contents = NAND_REG_RD(NCREG_CORR_STAT_THRESHOLD); \
> + u32 shift = NCFLD_CORR_STAT_THRESHOLD_CS1_SHIFT * (cs); \
> + contents &= ~(NCFLD_CORR_STAT_THRESHOLD_CS0_MASK << shift); \
> + contents |= ((val) & NCFLD_CORR_STAT_THRESHOLD_CS0_MASK) << shift; \
> + NAND_REG_WR(NCREG_CORR_STAT_THRESHOLD, contents); \
> +} while (0)
> +
> +#define NC_REG_TIMING1(cs) (NCREG_TIMING_1_CS0 + ((cs) << 4))
> +#define NC_REG_TIMING2(cs) (NCREG_TIMING_2_CS0 + ((cs) << 4))
> +
> +#define NAND_STRAP_TYPE \
> + ((readl(ctrl.nand_strap_regs) & ctrl.data->strap_type_bitfield.mask) \
> + >> ctrl.data->strap_type_bitfield.shift)
> +#define NAND_STRAP_PAGE \
> + ((readl(ctrl.nand_strap_regs) & ctrl.data->strap_page_bitfield.mask) \
> + >> ctrl.data->strap_page_bitfield.shift)
Please use functions instead of macros where possible.
> +
> +/*
> + * Internal structures
> + */
> +struct nand_strap_type {
> + uint8_t sector_1k;
> + uint8_t ecc_level;
> + uint16_t spare_size;
> +};
> +
> +struct nand_strap_bitfield {
> + uint32_t mask;
> + uint32_t shift;
> +};
What does "strap" mean here?
> +/*
> + * Internal support functions
> + */
> +static inline int fls64(u64 x)
> +{
> + u32 h = x >> 32;
> + if (h)
> + return fls(h) + 32;
> + return fls(x);
> +}
Put this in a generic header.
> +/*
> + * NAND MTD API: read/program/erase
> + */
> +
> +static void iproc_nand_cmd_ctrl(struct mtd_info *mtd,
> + int dat, unsigned int ctrl)
> +{
> + /* intentionally left blank */
> +}
Leave cmd_ctrl NULL instead of making it empty.
> +static int iproc_nand_read(struct mtd_info *mtd,
> + struct nand_chip *chip, u64 addr, unsigned int trans,
> + u32 *buf, u8 *oob)
> +{
> + struct iproc_nand_host *host = chip->priv;
> + u64 start_addr = addr;
> + int i;
> + int oob_bytes;
> + unsigned int max_bitflips, bitflips;
> + unsigned int corr_error_count, uncorr_error_count;
> + u32 *sector_buf = buf;
> + u8 *sector_oob = oob;
> +
> + debug("%s %llx -> %p (trans %x)\n", __func__,
> + (unsigned long long)addr, buf, trans);
> +
> + BUG_ON(!oob);
> +
> + NAND_ACK_IRQ(NCINTR_UNC);
> + NAND_ACK_IRQ(NCINTR_CORR);
> + max_bitflips = 0;
> + corr_error_count = 0;
> + uncorr_error_count = 0;
> +
> + NAND_REG_WR(NCREG_CMD_EXT_ADDRESS,
> + (host->cs << 16) | ((addr >> 32) & 0xffff));
> +
> + for (i = 0; i < trans; i++, addr += FC_BYTES) {
> + if (!host->hwcfg.sector_size_1k || ((i & 0x1) == 0)) {
> + sector_buf = buf;
> + sector_oob = oob;
> + }
> +
> + NAND_REG_WR(NCREG_CMD_ADDRESS, addr & 0xffffffff);
> +
> + if (ctrl.data_cache_invalid) {
> + if ((i == 0) && RD_ACC_CONTROL(host->cs, PAGE_HIT_EN))
> + /*
> + * temporarily disable the PAGE_HIT to force
> + * data to be read from NAND
> + */
> + WR_ACC_CONTROL(host->cs, PAGE_HIT_EN, 0);
> + else
> + ctrl.data_cache_invalid = 0;
> + }
> +
> + /* SPARE_AREA_READ does not use ECC, so just use PAGE_READ */
> + iproc_nand_send_cmd(CMD_PAGE_READ);
> + iproc_nand_waitfunc(mtd, chip);
> +
> + /* OOB bytes per sector */
> + oob_bytes = (mtd->oobsize / trans) <<
> + host->hwcfg.sector_size_1k;
> + /* OOB bytes per 512B transfer */
> + if (host->hwcfg.sector_size_1k && (i & 0x01))
> + oob_bytes = max(0, oob_bytes -
> + MAX_CONTROLLER_OOB_BYTES);
> + oob_bytes = min(oob_bytes, MAX_CONTROLLER_OOB_BYTES);
> +
> + iproc_nand_cache_read(&buf, &oob, oob_bytes);
> +
> + if (ctrl.data_cache_invalid) {
> + /* re-enable PAGE_HIT */
> + WR_ACC_CONTROL(host->cs, PAGE_HIT_EN, 1);
> + ctrl.data_cache_invalid = 0;
> + }
> +
> + if (buf && (!host->hwcfg.sector_size_1k || (i & 0x1))) {
> + /* check uncorrectable errors */
> + if (NAND_TEST_IRQ(NCINTR_UNC)) {
> + if (erased_sector(mtd,
> + (u8 *)sector_buf,
> + sector_oob,
> + &bitflips)) {
> + corr_error_count += bitflips;
> + if (bitflips > max_bitflips)
> + max_bitflips = bitflips;
> + } else {
> + uncorr_error_count += 1;
> + }
This level of indentation should be a hint that this function needs to be
broken up into smaller ones.
> + NAND_ACK_IRQ(NCINTR_UNC);
> + ctrl.data_cache_invalid = 1;
> + }
> + /* check correctable errors */
> + if (NAND_TEST_IRQ(NCINTR_CORR)) {
> + bitflips = CORR_ERROR_COUNT;
> + corr_error_count += bitflips;
> + if (bitflips > max_bitflips)
> + max_bitflips = bitflips;
> + NAND_ACK_IRQ(NCINTR_CORR);
> + ctrl.data_cache_invalid = 1;
> + }
> + }
> + }
> + if (uncorr_error_count) {
> + printf(DRV_NAME ": %d uncorrectable errors at 0x%llx\n",
> + uncorr_error_count, (unsigned long long)start_addr);
> + mtd->ecc_stats.failed += uncorr_error_count;
> + /* NAND layer expects zero on ECC errors */
> + return 0;
> + }
> + if (max_bitflips) {
> + debug("%s: corrected %d bit errors at 0x%llx\n",
> + __func__, max_bitflips, (unsigned long long)start_addr);
> + mtd->ecc_stats.corrected += corr_error_count;
> + return max_bitflips;
> + }
> +
> + return 0;
> +}
> +
> +static int iproc_nand_read_page(struct mtd_info *mtd,
> + struct nand_chip *chip,
> + uint8_t *buf,
> + int oob_required,
> + int page)
> +{
> + struct iproc_nand_host *host = chip->priv;
> +
> + BUG_ON(!buf);
> +
What's with all these !buf checks? Do you think it's more likely that
you'll get a NULL buf than any of the other pointers that get passed in?
> +static int iproc_nand_mark_bad(struct mtd_info *mtd, loff_t ofs)
> +{
> + struct nand_chip *chip = mtd->priv;
> + u64 blk_addr = (u64)ofs;
> +
> + debug("%s %llx\n", __func__, (unsigned long long)ofs);
> +
> + /* get NAND block address */
> + blk_addr &= ~((1 << chip->phys_erase_shift) - 1);
> +
> + memset(chip->oob_poi, 0xff, mtd->oobsize);
> + chip->oob_poi[chip->badblockpos] = 0;
> +
> + return iproc_nand_write(mtd, chip, blk_addr, NULL,
> + (u8 *)chip->oob_poi);
> +}
Why do you need a custom version of this?
> +
> +/*
> + * Per-CS setup (1 NAND device)
> + */
> +
> +static const unsigned int block_sizes[] = { 8, 16, 128, 256, 512, 1024, 2048 };
> +static const unsigned int page_sizes[] = { 512, 2048, 4096, 8192 };
> +
> +static void iproc_nand_set_cfg(struct iproc_nand_host *host,
> + struct iproc_nand_cfg *cfg)
> +{
> + int i, found;
> +
> + for (i = 0, found = 0; i < ARRAY_SIZE(block_sizes); i++)
> + if ((block_sizes[i] << 10) == cfg->block_size) {
> + WR_CONFIG(host->cs, BLOCK_SIZE, i);
> + found = 1;
> + }
U-Boot coding style requires braces around multiline statements.
> + debug("%s: mtd configuration\n"
> + "\tdevice_size 0x%llx\n"
> + "\tblock_size 0x%x\n"
> + "\tpage_size 0x%x\n"
> + "\tdevice_width 0x%x\n"
> + "\tcol_adr_bytes 0x%x\n"
> + "\tblk_adr_bytes 0x%x\n"
> + "\tspare_area_size 0x%x\n"
> + "\tecc_level 0x%x\n"
> + "\tsector_size_1k 0x%x\n",
> + __func__,
> + new_cfg.device_size,
> + new_cfg.block_size,
> + new_cfg.page_size,
> + new_cfg.device_width,
> + new_cfg.col_adr_bytes,
> + new_cfg.blk_adr_bytes,
> + new_cfg.spare_area_size,
> + new_cfg.ecc_level,
> + new_cfg.sector_size_1k);
> +
> + /* check settings determined by controller auto-init */
> + if (ctrl.auto_inited) {
> + debug("%s: auto-init configuration\n"
> + "\tdevice_size 0x%llx\n"
> + "\tblock_size 0x%x\n"
> + "\tpage_size 0x%x\n"
> + "\tdevice_width 0x%x\n"
> + "\tcol_adr_bytes 0x%x\n"
> + "\tblk_adr_bytes 0x%x\n"
> + "\tspare_area_size 0x%x\n"
> + "\tecc_level 0x%x\n"
> + "\tsector_size_1k 0x%x\n",
> + __func__,
> + orig_cfg.device_size,
> + orig_cfg.block_size,
> + orig_cfg.page_size,
> + orig_cfg.device_width,
> + orig_cfg.col_adr_bytes,
> + orig_cfg.blk_adr_bytes,
> + orig_cfg.spare_area_size,
> + orig_cfg.ecc_level,
> + orig_cfg.sector_size_1k);
> + /* check basic device attributes first */
> + if (orig_cfg.device_size != new_cfg.device_size ||
> + orig_cfg.block_size != new_cfg.block_size ||
> + orig_cfg.page_size != new_cfg.page_size ||
> + orig_cfg.device_width != new_cfg.device_width ||
> + orig_cfg.col_adr_bytes != new_cfg.col_adr_bytes ||
> + orig_cfg.blk_adr_bytes != new_cfg.blk_adr_bytes ||
> + orig_cfg.ful_adr_bytes != new_cfg.ful_adr_bytes ||
> + orig_cfg.ecc_level == 0 ||
> + ((orig_cfg.ecc_code == ECC_CODE_BCH) &&
> + (orig_cfg.ecc_level >
> + iproc_max_bch_ecc_level[orig_cfg.sector_size_1k])) ||
> + orig_cfg.spare_area_size > new_cfg.spare_area_size ||
> + ((orig_cfg.ecc_code == ECC_CODE_BCH) &&
> + (iproc_bch_ecc_bytes[orig_cfg.ecc_level] >
> + orig_cfg.spare_area_size))) {
> + /* ignore invalid auto-init settings */
> + ctrl.auto_inited = 0;
> + printf(DRV_NAME ": invalid auto-init settings\n");
> +
> + } else {
> + /* auto-init has initialized the flash correctly. */
> + new_cfg = orig_cfg;
> + printf(DRV_NAME ": following auto-init settings\n");
> + }
> + }
This is a bit elaborate... what is "auto-init"?
> +static void iproc_nand_timing_setup(struct iproc_nand_host *host)
> +{
> + struct nand_chip *chip = &host->chip;
> + int onfi_tmode; /* bit mask of supported onfi timing modes */
> +
> + /*
> + * ctrl.tmode has the configured tmode upper limit [0-5]
> + * or is -1 to indicate power-on default timing
> + */
> + if (ctrl.tmode < 0 || ctrl.tmode >= ONFI_TIMING_MODES)
> + return;
> +
> + if (chip->onfi_version) {
> + onfi_tmode = le16_to_cpu(get_unaligned(
> + (u16 *)&chip->onfi_params.async_timing_mode));
Fix onfi_get_async_timing_mode() instead, and have it use
get_unaligned_le16().
> + if ((onfi_tmode == 0) || (onfi_tmode & ~0x3F)) {
> + printf(DRV_NAME
> + ": invalid ONFI timing mode ignored 0x%x\n",
> + onfi_tmode);
> + } else {
> + /*
> + * select the maximum supported ONFI timing mode
> + * that is lower than the configured limit
> + */
> + while (ctrl.tmode > 0) {
> + if (onfi_tmode & (1 << ctrl.tmode))
> + break;
> + ctrl.tmode--;
> + }
> + }
> + }
> +
> + NAND_REG_WR(NC_REG_TIMING1(host->cs),
> + ctrl.data->onfi_tmode[ctrl.tmode].timing1);
> + NAND_REG_WR(NC_REG_TIMING2(host->cs),
> + ctrl.data->onfi_tmode[ctrl.tmode].timing2);
> + printf(DRV_NAME ": timing mode %d\n", ctrl.tmode);
This verbosity is not appropriate for normal driver init.
> +#ifdef CONFIG_SYS_MAX_NAND_CHIPS
> +#if (CONFIG_SYS_MAX_NAND_CHIPS > NAND_MAX_CS)
> +#error "Invalid CONFIG_SYS_MAX_NAND_CHIPS value"
> +#endif
> + ctrl.max_cs = CONFIG_SYS_MAX_NAND_CHIPS;
> +#else
> + ctrl.max_cs = 1;
> +#endif
> +
> + /* write protect configuration */
> +#ifdef CONFIG_IPROC_NAND_WP_MODE
> +#if ((CONFIG_IPROC_NAND_WP_MODE < 0) || \
> + (CONFIG_IPROC_NAND_WP_MODE > WP_ALWAYS_CLEARED))
> +#error "Invalid CONFIG_IPROC_NAND_WP_MODE value"
> +#endif
> + ctrl.wp_mode = CONFIG_IPROC_NAND_WP_MODE;
> +#else
> + ctrl.wp_mode = WP_SET_BY_DEFAULT;
> +#endif
> +
> + /* timing mode configuration */
> +#ifdef CONFIG_IPROC_NAND_TIMING_MODE
> +#if ((CONFIG_IPROC_NAND_TIMING_MODE < 0) || \
> + (CONFIG_IPROC_NAND_TIMING_MODE >= ONFI_TIMING_MODES))
> +#error "Invalid CONFIG_IPROC_NAND_TIMING_MODE value"
> +#endif
> + ctrl.tmode = CONFIG_IPROC_NAND_TIMING_MODE;
> +#else
> + ctrl.tmode = -1; /* use default timing configuration */
> +#endif
These would be more readable as:
#ifdef CONFIG_FOO
#define CONFIG_FOO <default_value>
#endif
...
BUILD_BUG_ON(CONFIG_FOO is bad);
ctrl.foo = CONFIG_FOO;
> + debug("%s: nand_regs %p\n", __func__, ctrl.nand_regs);
> + debug("%s: nand_intr_regs %p\n", __func__, ctrl.nand_intr_regs);
> + debug("%s: nand_idm_regs %p\n", __func__, ctrl.nand_idm_regs);
> + debug("%s: nand_idm_io_ctrl_direct_reg %p\n", __func__,
> + ctrl.nand_idm_io_ctrl_direct_reg);
> + debug("%s: nand_strap_regs %p\n", __func__, ctrl.nand_strap_regs);
> + debug("%s: max_cs %d\n", __func__, ctrl.max_cs);
> + debug("%s: wp_mode %d\n", __func__, ctrl.wp_mode);
This driver has a lot of debug clutter. Is it really
> +#ifdef CONFIG_IPROC_NAND_AUTO_INIT
Undocumented, and should be in kconfig.
> +int iproc_nand_init(void)
> +{
> + struct iproc_nand_host *host;
> + struct mtd_info *mtd;
> + struct nand_chip *chip;
> +
> + NAND_REG_WR(NCREG_SEMAPHORE, 0);
> +
> + host = &nand_host;
> + memset(host, 0, sizeof(*host));
> +
> + host->mtd = &nand_info[0];
> +
> + host->cs = 0;
> +
> + mtd = host->mtd;
> + chip = &host->chip;
> +
> + chip->priv = host;
> + mtd->priv = chip;
> + mtd->name = "iproc_nand";
> + /*
> + * set mtd bitflip threshold to 1 as the desired threshold will
> + * be set in the controller register.
> + */
> + mtd->bitflip_threshold = 1;
> +
> + chip->IO_ADDR_R = (void *)0xdeadbeef;
> + chip->IO_ADDR_W = (void *)0xdeadbeef;
Why?
> + chip->cmd_ctrl = iproc_nand_cmd_ctrl;
> + chip->cmdfunc = iproc_nand_cmdfunc;
> + chip->waitfunc = iproc_nand_waitfunc;
> + chip->read_byte = iproc_nand_read_byte;
> + chip->read_buf = iproc_nand_read_buf;
> + if (ctrl.max_cs > 1)
> + chip->select_chip = iproc_nand_select_chip;
> +
> + chip->block_bad = iproc_nand_block_bad;
> + chip->block_markbad = iproc_nand_mark_bad;
> +
> + chip->ecc.mode = NAND_ECC_HW;
> + chip->ecc.layout = &iproc_nand_oob_layout;
> + chip->ecc.read_page = iproc_nand_read_page;
> + chip->ecc.write_page = iproc_nand_write_page;
> + chip->ecc.read_page_raw = iproc_nand_read_page_raw;
> + chip->ecc.write_page_raw = iproc_nand_write_page_raw;
> +
> + chip->ecc.write_oob_raw = iproc_nand_write_oob_raw;
> + chip->ecc.read_oob_raw = iproc_nand_read_oob_raw;
> +
> + chip->ecc.read_oob = (void *)iproc_nand_read_oob;
> + chip->ecc.write_oob = iproc_nand_write_oob;
> +
> + chip->controller = &ctrl.controller;
> +
> + if (nand_scan_ident(mtd, ctrl.max_cs, NULL))
> + return -ENXIO;
> +
> + chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_SKIP_BBTSCAN;
> +
> + chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;
Why skip the BBT scan?
-Scott
More information about the U-Boot
mailing list