diff options
Diffstat (limited to 'drivers/mtd/nand/nand_base.c')
| -rw-r--r-- | drivers/mtd/nand/nand_base.c | 127 | 
1 files changed, 119 insertions, 8 deletions
| diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index fe34a4864..4069ab127 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -7,7 +7,7 @@   *   Basic support for AG-AND chips is provided.   *   *	Additional technical information is available on - *	http://www.linux-mtd.infradead.org/tech/nand.html + *	http://www.linux-mtd.infradead.org/doc/nand.html   *   *  Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)   *		  2002-2006 Thomas Gleixner (tglx@linutronix.de) @@ -24,6 +24,7 @@   *	if we have HW ecc support.   *	The AG-AND chips have nice features for speed improvement,   *	which are not supported yet. Read / program 4 pages in one go. + *	BBT table is not serialized, has to be fixed   *   * 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 @@ -128,7 +129,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,  static int nand_wait(struct mtd_info *mtd, struct nand_chip *this);  /* - * For devices which display every fart in the system on a seperate LED. Is + * For devices which display every fart in the system on a separate LED. Is   * compiled away when LED support is disabled.   */  /* XXX U-BOOT XXX */ @@ -412,6 +413,7 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)  		/* We write two bytes, so we dont have to mess with 16 bit  		 * access  		 */ +		nand_get_device(chip, mtd, FL_WRITING);  		ofs += mtd->oobsize;  		chip->ops.len = chip->ops.ooblen = 2;  		chip->ops.datbuf = NULL; @@ -419,9 +421,11 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)  		chip->ops.ooboffs = chip->badblockpos & ~0x01;  		ret = nand_do_write_oob(mtd, ofs, &chip->ops); +		nand_release_device(mtd);  	}  	if (!ret)  		mtd->ecc_stats.badblocks++; +  	return ret;  } @@ -911,7 +915,88 @@ static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip,  		int stat;  		stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); -		if (stat == -1) +		if (stat < 0) +			mtd->ecc_stats.failed++; +		else +			mtd->ecc_stats.corrected += stat; +	} +	return 0; +} + +/** + * nand_read_subpage - [REPLACABLE] software ecc based sub-page read function + * @mtd:	mtd info structure + * @chip:	nand chip info structure + * @dataofs	offset of requested data within the page + * @readlen	data length + * @buf:	buffer to store read data + */ +static int nand_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, uint32_t data_offs, uint32_t readlen, uint8_t *bufpoi) +{ +	int start_step, end_step, num_steps; +	uint32_t *eccpos = chip->ecc.layout->eccpos; +	uint8_t *p; +	int data_col_addr, i, gaps = 0; +	int datafrag_len, eccfrag_len, aligned_len, aligned_pos; +	int busw = (chip->options & NAND_BUSWIDTH_16) ? 2 : 1; + +	/* Column address wihin the page aligned to ECC size (256bytes). */ +	start_step = data_offs / chip->ecc.size; +	end_step = (data_offs + readlen - 1) / chip->ecc.size; +	num_steps = end_step - start_step + 1; + +	/* Data size aligned to ECC ecc.size*/ +	datafrag_len = num_steps * chip->ecc.size; +	eccfrag_len = num_steps * chip->ecc.bytes; + +	data_col_addr = start_step * chip->ecc.size; +	/* If we read not a page aligned data */ +	if (data_col_addr != 0) +		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, data_col_addr, -1); + +	p = bufpoi + data_col_addr; +	chip->read_buf(mtd, p, datafrag_len); + +	/* Calculate  ECC */ +	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) +		chip->ecc.calculate(mtd, p, &chip->buffers->ecccalc[i]); + +	/* The performance is faster if to position offsets +	   according to ecc.pos. Let make sure here that +	   there are no gaps in ecc positions */ +	for (i = 0; i < eccfrag_len - 1; i++) { +		if (eccpos[i + start_step * chip->ecc.bytes] + 1 != +			eccpos[i + start_step * chip->ecc.bytes + 1]) { +			gaps = 1; +			break; +		} +	} +	if (gaps) { +		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); +		chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); +	} else { +		/* send the command to read the particular ecc bytes */ +		/* take care about buswidth alignment in read_buf */ +		aligned_pos = eccpos[start_step * chip->ecc.bytes] & ~(busw - 1); +		aligned_len = eccfrag_len; +		if (eccpos[start_step * chip->ecc.bytes] & (busw - 1)) +			aligned_len++; +		if (eccpos[(start_step + num_steps) * chip->ecc.bytes] & (busw - 1)) +			aligned_len++; + +		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize + aligned_pos, -1); +		chip->read_buf(mtd, &chip->oob_poi[aligned_pos], aligned_len); +	} + +	for (i = 0; i < eccfrag_len; i++) +		chip->buffers->ecccode[i] = chip->oob_poi[eccpos[i + start_step * chip->ecc.bytes]]; + +	p = bufpoi + data_col_addr; +	for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) { +		int stat; + +		stat = chip->ecc.correct(mtd, p, &chip->buffers->ecccode[i], &chip->buffers->ecccalc[i]); +		if (stat < 0)  			mtd->ecc_stats.failed++;  		else  			mtd->ecc_stats.corrected += stat; @@ -996,7 +1081,7 @@ static int nand_read_page_syndrome(struct mtd_info *mtd, struct nand_chip *chip,  		chip->read_buf(mtd, oob, eccbytes);  		stat = chip->ecc.correct(mtd, p, oob, NULL); -		if (stat == -1) +		if (stat < 0)  			mtd->ecc_stats.failed++;  		else  			mtd->ecc_stats.corrected += stat; @@ -1116,6 +1201,8 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,  			/* Now read the page into the buffer */  			if (unlikely(ops->mode == MTD_OOB_RAW))  				ret = chip->ecc.read_page_raw(mtd, chip, bufpoi); +			else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob) +				ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);  			else  				ret = chip->ecc.read_page(mtd, chip, bufpoi);  			if (ret < 0) @@ -1123,7 +1210,8 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,  			/* Transfer not aligned data */  			if (!aligned) { -				chip->pagebuf = realpage; +				if (!NAND_SUBPAGE_READ(chip) && !oob) +					chip->pagebuf = realpage;  				memcpy(buf, chip->buffers->databuf + col, bytes);  			} @@ -2193,13 +2281,14 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,   erase_exit:  	ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; -	/* Do call back function */ -	if (!ret) -		mtd_erase_callback(instr);  	/* Deselect and wake up anyone waiting on the device */  	nand_release_device(mtd); +	/* Do call back function */ +	if (!ret) +		mtd_erase_callback(instr); +  	/*  	 * If BBT requires refresh and erase was successful, rewrite any  	 * selected bad block tables @@ -2356,6 +2445,7 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,  {  	struct nand_flash_dev *type = NULL;  	int i, dev_id, maf_idx; +	int tmp_id, tmp_manf;  	/* Select the device */  	chip->select_chip(mtd, 0); @@ -2367,6 +2457,26 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,  	*maf_id = chip->read_byte(mtd);  	dev_id = chip->read_byte(mtd); +	/* Try again to make sure, as some systems the bus-hold or other +	 * interface concerns can cause random data which looks like a +	 * possibly credible NAND flash to appear. If the two results do +	 * not match, ignore the device completely. +	 */ + +	chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + +	/* Read manufacturer and device IDs */ + +	tmp_manf = chip->read_byte(mtd); +	tmp_id = chip->read_byte(mtd); + +	if (tmp_manf != *maf_id || tmp_id != dev_id) { +		printk(KERN_INFO "%s: second ID read did not match " +		       "%02x,%02x against %02x,%02x\n", __func__, +		       *maf_id, dev_id, tmp_manf, tmp_id); +		return ERR_PTR(-ENODEV); +	} +  	/* Lookup the flash id */  	for (i = 0; nand_flash_ids[i].name != NULL; i++) {  		if (dev_id == nand_flash_ids[i].id) { @@ -2630,6 +2740,7 @@ int nand_scan_tail(struct mtd_info *mtd)  		chip->ecc.calculate = nand_calculate_ecc;  		chip->ecc.correct = nand_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_oob = nand_read_oob_std;  		chip->ecc.write_oob = nand_write_oob_std; |