diff options
| -rw-r--r-- | arch/arm/include/asm/omap_gpmc.h | 16 | ||||
| -rw-r--r-- | doc/README.nand | 11 | ||||
| -rw-r--r-- | drivers/mtd/nand/omap_gpmc.c | 324 | 
3 files changed, 227 insertions, 124 deletions
| diff --git a/arch/arm/include/asm/omap_gpmc.h b/arch/arm/include/asm/omap_gpmc.h index dd40cb6c1..d4143ecd8 100644 --- a/arch/arm/include/asm/omap_gpmc.h +++ b/arch/arm/include/asm/omap_gpmc.h @@ -68,4 +68,20 @@  }  #endif +enum omap_ecc { +	/* 1-bit  ECC calculation by Software, Error detection by Software */ +	OMAP_ECC_HAM1_CODE_SW = 1, /* avoid un-initialized int can be 0x0 */ +	/* 1-bit  ECC calculation by GPMC, Error detection by Software */ +	/* ECC layout compatible to legacy ROMCODE. */ +	OMAP_ECC_HAM1_CODE_HW, +	/* 4-bit  ECC calculation by GPMC, Error detection by Software */ +	OMAP_ECC_BCH4_CODE_HW_DETECTION_SW, +	/* 4-bit  ECC calculation by GPMC, Error detection by ELM */ +	OMAP_ECC_BCH4_CODE_HW, +	/* 8-bit  ECC calculation by GPMC, Error detection by Software */ +	OMAP_ECC_BCH8_CODE_HW_DETECTION_SW, +	/* 8-bit  ECC calculation by GPMC, Error detection by ELM */ +	OMAP_ECC_BCH8_CODE_HW, +}; +  #endif /* __ASM_OMAP_GPMC_H */ diff --git a/doc/README.nand b/doc/README.nand index ce7ea5e1e..487548fcb 100644 --- a/doc/README.nand +++ b/doc/README.nand @@ -180,6 +180,17 @@ Configuration Options:        flexibility, so that one day we can eliminate the old mechanism. +   CONFIG_SYS_NAND_ONFI_DETECTION +	Enables detection of ONFI compliant devices during probe. +	And fetching device parameters flashed on device, by parsing +	ONFI parameter page. + +   CONFIG_BCH +	Enables software based BCH ECC algorithm present in lib/bch.c +	This is used by SoC platforms which do not have built-in ELM +	hardware engine required for BCH ECC correction. + +  Platform specific options  =========================     CONFIG_NAND_OMAP_GPMC diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c index c8288597a..e6b289dec 100644 --- a/drivers/mtd/nand/omap_gpmc.c +++ b/drivers/mtd/nand/omap_gpmc.c @@ -15,15 +15,13 @@  #include <linux/bch.h>  #include <linux/compiler.h>  #include <nand.h> -#ifdef CONFIG_AM33XX  #include <asm/omap_elm.h> -#endif + +#define BADBLOCK_MARKER_LENGTH	2 +#define SECTOR_BYTES		512  static uint8_t cs; -static __maybe_unused struct nand_ecclayout hw_nand_oob = -	GPMC_NAND_HW_ECC_LAYOUT; -static __maybe_unused struct nand_ecclayout hw_bch8_nand_oob = -	GPMC_NAND_HW_BCH8_ECC_LAYOUT; +static __maybe_unused struct nand_ecclayout omap_ecclayout;  /*   * omap_nand_hwcontrol - Set the address pointers corretly for the @@ -233,6 +231,7 @@ struct nand_bch_priv {  	uint8_t type;  	uint8_t nibbles;  	struct bch_control *control; +	enum omap_ecc ecc_scheme;  };  /* bch types */ @@ -274,17 +273,15 @@ static void omap_hwecc_init_bch(struct nand_chip *chip, int32_t mode)  {  	uint32_t val;  	uint32_t dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; -#ifdef CONFIG_AM33XX  	uint32_t unused_length = 0; -#endif  	uint32_t wr_mode = BCH_WRAPMODE_6;  	struct nand_bch_priv *bch = chip->priv;  	/* Clear the ecc result registers, select ecc reg as 1 */  	writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control); -#ifdef CONFIG_AM33XX -	wr_mode = BCH_WRAPMODE_1; +	if (bch->ecc_scheme == OMAP_ECC_BCH8_CODE_HW) { +		wr_mode = BCH_WRAPMODE_1;  	switch (bch->nibbles) {  	case ECC_BCH4_NIBBLES: @@ -320,7 +317,7 @@ static void omap_hwecc_init_bch(struct nand_chip *chip, int32_t mode)  		val |= (unused_length << 22);  		break;  	} -#else +	} else {  	/*  	 * This ecc_size_config setting is for BCH sw library.  	 * @@ -333,7 +330,7 @@ static void omap_hwecc_init_bch(struct nand_chip *chip, int32_t mode)  	 *  size1 = 32 (skip 32 nibbles = 16 bytes per sector in spare area)  	 */  	val = (32 << 22) | (0 << 12); -#endif +	}  	/* ecc size configuration */  	writel(val, &gpmc_cfg->ecc_size_config); @@ -376,9 +373,9 @@ static void __maybe_unused omap_ecc_disable(struct mtd_info *mtd)  }  /* - * BCH8 support (needs ELM and thus AM33xx-only) + * BCH support using ELM module   */ -#ifdef CONFIG_AM33XX +#ifdef CONFIG_NAND_OMAP_ELM  /*   * omap_read_bch8_result - Read BCH result for BCH8 level   * @@ -631,20 +628,20 @@ static int omap_read_page_bch(struct mtd_info *mtd, struct nand_chip *chip,  	}  	return 0;  } -#endif /* CONFIG_AM33XX */ +#endif /* CONFIG_NAND_OMAP_ELM */  /*   * OMAP3 BCH8 support (with BCH library)   */ -#ifdef CONFIG_NAND_OMAP_BCH8 +#ifdef CONFIG_BCH  /* - *  omap_calculate_ecc_bch - Read BCH ECC result + *  omap_calculate_ecc_bch_sw - Read BCH ECC result   *   *  @mtd:	MTD device structure   *  @dat:	The pointer to data on which ecc is computed (unused here)   *  @ecc:	The ECC output buffer   */ -static int omap_calculate_ecc_bch(struct mtd_info *mtd, const uint8_t *dat, +static int omap_calculate_ecc_bch_sw(struct mtd_info *mtd, const uint8_t *dat,  				uint8_t *ecc)  {  	int ret = 0; @@ -689,13 +686,13 @@ static int omap_calculate_ecc_bch(struct mtd_info *mtd, const uint8_t *dat,  }  /** - * omap_correct_data_bch - Decode received data and correct errors + * omap_correct_data_bch_sw - Decode received data and correct errors   * @mtd: MTD device structure   * @data: page data   * @read_ecc: ecc read from nand flash   * @calc_ecc: ecc read from HW ECC registers   */ -static int omap_correct_data_bch(struct mtd_info *mtd, u_char *data, +static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data,  				 u_char *read_ecc, u_char *calc_ecc)  {  	int i, count; @@ -752,7 +749,150 @@ static void __maybe_unused omap_free_bch(struct mtd_info *mtd)  		chip_priv->control = NULL;  	}  } -#endif /* CONFIG_NAND_OMAP_BCH8 */ +#endif /* CONFIG_BCH */ + +/** + * omap_select_ecc_scheme - configures driver for particular ecc-scheme + * @nand: NAND chip device structure + * @ecc_scheme: ecc scheme to configure + * @pagesize: number of main-area bytes per page of NAND device + * @oobsize: number of OOB/spare bytes per page of NAND device + */ +static int omap_select_ecc_scheme(struct nand_chip *nand, +	enum omap_ecc ecc_scheme, unsigned int pagesize, unsigned int oobsize) { +	struct nand_bch_priv	*bch		= nand->priv; +	struct nand_ecclayout	*ecclayout	= nand->ecc.layout; +	int eccsteps = pagesize / SECTOR_BYTES; +	int i; + +	switch (ecc_scheme) { +	case OMAP_ECC_HAM1_CODE_SW: +		debug("nand: selected OMAP_ECC_HAM1_CODE_SW\n"); +		/* For this ecc-scheme, ecc.bytes, ecc.layout, ... are +		 * initialized in nand_scan_tail(), so just set ecc.mode */ +		bch_priv.control	= NULL; +		bch_priv.type		= 0; +		nand->ecc.mode		= NAND_ECC_SOFT; +		nand->ecc.layout	= NULL; +		nand->ecc.size		= pagesize; +		bch->ecc_scheme		= OMAP_ECC_HAM1_CODE_SW; +		break; + +	case OMAP_ECC_HAM1_CODE_HW: +		debug("nand: selected OMAP_ECC_HAM1_CODE_HW\n"); +		/* check ecc-scheme requirements before updating ecc info */ +		if ((3 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { +			printf("nand: error: insufficient OOB: require=%d\n", ( +				(3 * eccsteps) + BADBLOCK_MARKER_LENGTH)); +			return -EINVAL; +		} +		bch_priv.control	= NULL; +		bch_priv.type		= 0; +		/* populate ecc specific fields */ +		nand->ecc.mode		= NAND_ECC_HW; +		nand->ecc.strength	= 1; +		nand->ecc.size		= SECTOR_BYTES; +		nand->ecc.bytes		= 3; +		nand->ecc.hwctl		= omap_enable_hwecc; +		nand->ecc.correct	= omap_correct_data; +		nand->ecc.calculate	= omap_calculate_ecc; +		/* define ecc-layout */ +		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps; +		for (i = 0; i < ecclayout->eccbytes; i++) +			ecclayout->eccpos[i] = i + BADBLOCK_MARKER_LENGTH; +		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH; +		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes - +						BADBLOCK_MARKER_LENGTH; +		bch->ecc_scheme		= OMAP_ECC_HAM1_CODE_HW; +		break; + +	case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: +#ifdef CONFIG_BCH +		debug("nand: selected OMAP_ECC_BCH8_CODE_HW_DETECTION_SW\n"); +		/* check ecc-scheme requirements before updating ecc info */ +		if ((13 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { +			printf("nand: error: insufficient OOB: require=%d\n", ( +				(13 * eccsteps) + BADBLOCK_MARKER_LENGTH)); +			return -EINVAL; +		} +		/* check if BCH S/W library can be used for error detection */ +		bch_priv.control = init_bch(13, 8, 0x201b); +		if (!bch_priv.control) { +			printf("nand: error: could not init_bch()\n"); +			return -ENODEV; +		} +		bch_priv.type = ECC_BCH8; +		/* populate ecc specific fields */ +		nand->ecc.mode		= NAND_ECC_HW; +		nand->ecc.strength	= 8; +		nand->ecc.size		= SECTOR_BYTES; +		nand->ecc.bytes		= 13; +		nand->ecc.hwctl		= omap_enable_ecc_bch; +		nand->ecc.correct	= omap_correct_data_bch_sw; +		nand->ecc.calculate	= omap_calculate_ecc_bch_sw; +		/* define ecc-layout */ +		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps; +		ecclayout->eccpos[0]	= BADBLOCK_MARKER_LENGTH; +		for (i = 1; i < ecclayout->eccbytes; i++) { +			if (i % nand->ecc.bytes) +				ecclayout->eccpos[i] = +						ecclayout->eccpos[i - 1] + 1; +			else +				ecclayout->eccpos[i] = +						ecclayout->eccpos[i - 1] + 2; +		} +		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH; +		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes - +						BADBLOCK_MARKER_LENGTH; +		omap_hwecc_init_bch(nand, NAND_ECC_READ); +		bch->ecc_scheme		= OMAP_ECC_BCH8_CODE_HW_DETECTION_SW; +		break; +#else +		printf("nand: error: CONFIG_BCH required for ECC\n"); +		return -EINVAL; +#endif + +	case OMAP_ECC_BCH8_CODE_HW: +#ifdef CONFIG_NAND_OMAP_ELM +		debug("nand: selected OMAP_ECC_BCH8_CODE_HW\n"); +		/* check ecc-scheme requirements before updating ecc info */ +		if ((14 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) { +			printf("nand: error: insufficient OOB: require=%d\n", ( +				(14 * eccsteps) + BADBLOCK_MARKER_LENGTH)); +			return -EINVAL; +		} +		/* intialize ELM for ECC error detection */ +		elm_init(); +		bch_priv.type		= ECC_BCH8; +		/* populate ecc specific fields */ +		nand->ecc.mode		= NAND_ECC_HW; +		nand->ecc.strength	= 8; +		nand->ecc.size		= SECTOR_BYTES; +		nand->ecc.bytes		= 14; +		nand->ecc.hwctl		= omap_enable_ecc_bch; +		nand->ecc.correct	= omap_correct_data_bch; +		nand->ecc.calculate	= omap_calculate_ecc_bch; +		nand->ecc.read_page	= omap_read_page_bch; +		/* define ecc-layout */ +		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps; +		for (i = 0; i < ecclayout->eccbytes; i++) +			ecclayout->eccpos[i] = i + BADBLOCK_MARKER_LENGTH; +		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH; +		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes - +						BADBLOCK_MARKER_LENGTH; +		bch->ecc_scheme		= OMAP_ECC_BCH8_CODE_HW; +		break; +#else +		printf("nand: error: CONFIG_NAND_OMAP_ELM required for ECC\n"); +		return -EINVAL; +#endif + +	default: +		debug("nand: error: ecc scheme not enabled or supported\n"); +		return -EINVAL; +	} +	return 0; +}  #ifndef CONFIG_SPL_BUILD  /* @@ -763,77 +903,45 @@ static void __maybe_unused omap_free_bch(struct mtd_info *mtd)   * @eccstrength		- the number of bits that could be corrected   *			  (1 - hamming, 4 - BCH4, 8 - BCH8, 16 - BCH16)   */ -void omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength) +int __maybe_unused omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength)  {  	struct nand_chip *nand;  	struct mtd_info *mtd; +	int err = 0;  	if (nand_curr_device < 0 ||  	    nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||  	    !nand_info[nand_curr_device].name) { -		printf("Error: Can't switch ecc, no devices available\n"); -		return; +		printf("nand: error: no NAND devices found\n"); +		return -ENODEV;  	}  	mtd = &nand_info[nand_curr_device];  	nand = mtd->priv; -  	nand->options |= NAND_OWN_BUFFERS; - -	/* Reset ecc interface */ -	nand->ecc.mode = NAND_ECC_NONE; -	nand->ecc.read_page = NULL; -	nand->ecc.write_page = NULL; -	nand->ecc.read_oob = NULL; -	nand->ecc.write_oob = NULL; -	nand->ecc.hwctl = NULL; -	nand->ecc.correct = NULL; -	nand->ecc.calculate = NULL; -	nand->ecc.strength = eccstrength; -  	/* Setup the ecc configurations again */  	if (hardware) {  		if (eccstrength == 1) { -			nand->ecc.mode = NAND_ECC_HW; -			nand->ecc.layout = &hw_nand_oob; -			nand->ecc.size = 512; -			nand->ecc.bytes = 3; -			nand->ecc.hwctl = omap_enable_hwecc; -			nand->ecc.correct = omap_correct_data; -			nand->ecc.calculate = omap_calculate_ecc; -			omap_hwecc_init(nand); -			printf("1-bit hamming HW ECC selected\n"); -		} -#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) -		else if (eccstrength == 8) { -			nand->ecc.mode = NAND_ECC_HW; -			nand->ecc.layout = &hw_bch8_nand_oob; -			nand->ecc.size = 512; -#ifdef CONFIG_AM33XX -			nand->ecc.bytes = 14; -			nand->ecc.read_page = omap_read_page_bch; -#else -			nand->ecc.bytes = 13; -#endif -			nand->ecc.hwctl = omap_enable_ecc_bch; -			nand->ecc.correct = omap_correct_data_bch; -			nand->ecc.calculate = omap_calculate_ecc_bch; -			omap_hwecc_init_bch(nand, NAND_ECC_READ); -			printf("8-bit BCH HW ECC selected\n"); +			err = omap_select_ecc_scheme(nand, +					OMAP_ECC_HAM1_CODE_HW, +					mtd->writesize, mtd->oobsize); +		} else if (eccstrength == 8) { +			err = omap_select_ecc_scheme(nand, +					OMAP_ECC_BCH8_CODE_HW, +					mtd->writesize, mtd->oobsize); +		} else { +			printf("nand: error: unsupported ECC scheme\n"); +			return -EINVAL;  		} -#endif  	} else { -		nand->ecc.mode = NAND_ECC_SOFT; -		/* Use mtd default settings */ -		nand->ecc.layout = NULL; -		nand->ecc.size = 0; -		printf("SW ECC selected\n"); +		err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW, +					mtd->writesize, mtd->oobsize);  	}  	/* Update NAND handling after ECC mode switch */ -	nand_scan_tail(mtd); - -	nand->options &= ~NAND_OWN_BUFFERS; +	if (!err) +		err = nand_scan_tail(mtd); +	return err;  }  #endif /* CONFIG_SPL_BUILD */ @@ -856,7 +964,7 @@ int board_nand_init(struct nand_chip *nand)  {  	int32_t gpmc_config = 0;  	cs = 0; - +	int err = 0;  	/*  	 * xloader/Uboot's gpmc configuration would have configured GPMC for  	 * nand type of memory. The following logic scans and latches on to the @@ -873,7 +981,7 @@ int board_nand_init(struct nand_chip *nand)  		cs++;  	}  	if (cs >= GPMC_MAX_CS) { -		printf("NAND: Unable to find NAND settings in " +		printf("nand: error: Unable to find NAND settings in "  			"GPMC Configuration - quitting\n");  		return -ENODEV;  	} @@ -885,64 +993,32 @@ int board_nand_init(struct nand_chip *nand)  	nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;  	nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd; - -	nand->cmd_ctrl = omap_nand_hwcontrol; -	nand->options = NAND_NO_PADDING | NAND_CACHEPRG; +	nand->priv	= &bch_priv; +	nand->cmd_ctrl	= omap_nand_hwcontrol; +	nand->options	|= NAND_NO_PADDING | NAND_CACHEPRG;  	/* If we are 16 bit dev, our gpmc config tells us that */  	if ((readl(&gpmc_cfg->cs[cs].config1) & 0x3000) == 0x1000)  		nand->options |= NAND_BUSWIDTH_16;  	nand->chip_delay = 100; +	nand->ecc.layout = &omap_ecclayout; -#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) -#ifdef CONFIG_AM33XX -	/* AM33xx uses the ELM */ -	/* required in case of BCH */ -	elm_init(); -#else -	/* -	 * Whereas other OMAP based SoC do not have the ELM, they use the BCH -	 * SW library. -	 */ -	bch_priv.control = init_bch(13, 8, 0x201b /* hw polynominal */); -	if (!bch_priv.control) { -		puts("Could not init_bch()\n"); -		return -ENODEV; -	} -#endif -	/* BCH info that will be correct for SPL or overridden otherwise. */ -	nand->priv = &bch_priv; -#endif - -	/* Default ECC mode */ -#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8) -	nand->ecc.mode = NAND_ECC_HW; -	nand->ecc.layout = &hw_bch8_nand_oob; -	nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE; -	nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES; -	nand->ecc.strength = 8; -	nand->ecc.hwctl = omap_enable_ecc_bch; -	nand->ecc.correct = omap_correct_data_bch; -	nand->ecc.calculate = omap_calculate_ecc_bch; -#ifdef CONFIG_AM33XX -	nand->ecc.read_page = omap_read_page_bch; -#endif -	omap_hwecc_init_bch(nand, NAND_ECC_READ); -#else -#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC) -	nand->ecc.mode = NAND_ECC_SOFT; +	/* select ECC scheme */ +#if defined(CONFIG_NAND_OMAP_ELM) +	err = omap_select_ecc_scheme(nand, OMAP_ECC_BCH8_CODE_HW, +			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE); +#elif defined(CONFIG_NAND_OMAP_BCH8) +	err = omap_select_ecc_scheme(nand, OMAP_ECC_BCH8_CODE_HW_DETECTION_SW, +			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE); +#elif !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC) +	err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW, +			0, 0);  #else -	nand->ecc.mode = NAND_ECC_HW; -	nand->ecc.layout = &hw_nand_oob; -	nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE; -	nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES; -	nand->ecc.hwctl = omap_enable_hwecc; -	nand->ecc.correct = omap_correct_data; -	nand->ecc.calculate = omap_calculate_ecc; -	nand->ecc.strength = 1; -	omap_hwecc_init(nand); -#endif +	err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_HW, +			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);  #endif +	if (err) +		return err;  #ifdef CONFIG_SPL_BUILD  	if (nand->options & NAND_BUSWIDTH_16) |