Skip to content
Snippets Groups Projects
nand_base.c 74.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	for(;;) {
    		for (j = 0; j < eccsteps; j++) {
    			/* Loop through and verify the data */
    			if (this->verify_buf(mtd, &this->data_poi[datidx], mtd->eccsize)) {
    				DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
    				goto out;
    			}
    			datidx += mtd->eccsize;
    			/* Have we a hw generator layout ? */
    			if (!hweccbytes)
    				continue;
    			if (this->verify_buf(mtd, &this->oob_buf[oobofs], hweccbytes)) {
    				DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
    				goto out;
    			}
    			oobofs += hweccbytes;
    		}
    
    		/* check, if we must compare all data or if we just have to
    		 * compare the ecc bytes
    		 */
    		if (oobmode) {
    			if (this->verify_buf(mtd, &oob_buf[oobofs], mtd->oobsize - hweccbytes * eccsteps)) {
    				DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
    				goto out;
    			}
    		} else {
    			/* Read always, else autoincrement fails */
    			this->read_buf(mtd, oobdata, mtd->oobsize - hweccbytes * eccsteps);
    
    			if (oobsel->useecc != MTD_NANDECC_OFF && !hweccbytes) {
    				int ecccnt = oobsel->eccbytes;
    
    				for (i = 0; i < ecccnt; i++) {
    					int idx = oobsel->eccpos[i];
    					if (oobdata[idx] != oob_buf[oobofs + idx] ) {
    						DEBUG (MTD_DEBUG_LEVEL0,
    					       	"%s: Failed ECC write "
    						"verify, page 0x%08x, " "%6i bytes were succesful\n", __FUNCTION__, page, i);
    						goto out;
    					}
    				}
    
    		}
    		oobofs += mtd->oobsize - hweccbytes * eccsteps;
    		page++;
    		numpages--;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		/* Apply delay or wait for ready/busy pin
    
    		 * Do this before the AUTOINCR check, so no problems
    		 * arise if a chip which does auto increment
    		 * is marked as NOAUTOINCR by the board driver.
    		 * Do this also before returning, so the chip is
    		 * ready for the next command.
    		*/
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (!this->dev_ready)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			while (!this->dev_ready(mtd));
    
    
    		/* All done, return happy */
    		if (!numpages)
    			return 0;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    
    		/* Check, if the chip supports auto page increment */
    
    		if (!NAND_CANAUTOINCR(this))
    			this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page);
    	}
    
    	 * Terminate the read command. We come here in case of an error
    	 * So we must issue a reset command.
    	 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    out:
    
    	this->cmdfunc (mtd, NAND_CMD_RESET, -1, -1);
    	return res;
    }
    #endif
    
    /**
     * nand_read - [MTD Interface] MTD compability function for nand_read_ecc
     * @mtd:	MTD device structure
     * @from:	offset to read from
     * @len:	number of bytes to read
     * @retlen:	pointer to variable to store the number of read bytes
     * @buf:	the databuffer to put data
     *
     * This function simply calls nand_read_ecc with oob buffer and oobsel = NULL
    */
    static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf)
    {
    	return nand_read_ecc (mtd, from, len, retlen, buf, NULL, NULL);
    
    
    
    /**
     * nand_read_ecc - [MTD Interface] Read data with ECC
     * @mtd:	MTD device structure
     * @from:	offset to read from
     * @len:	number of bytes to read
     * @retlen:	pointer to variable to store the number of read bytes
     * @buf:	the databuffer to put data
     * @oob_buf:	filesystem supplied oob data buffer
     * @oobsel:	oob selection structure
     *
     * NAND read with ECC
     */
    static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
    			  size_t * retlen, u_char * buf, u_char * oob_buf, struct nand_oobinfo *oobsel)
    {
    	int i, j, col, realpage, page, end, ecc, chipnr, sndcmd = 1;
    	int read = 0, oob = 0, ecc_status = 0, ecc_failed = 0;
    	struct nand_chip *this = mtd->priv;
    	u_char *data_poi, *oob_data = oob_buf;
    	u_char ecc_calc[32];
    	u_char ecc_code[32];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	int eccmode, eccsteps;
    
    	unsigned *oob_config;
    	int	datidx;
    
    	int	blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
    	int	eccbytes;
    	int	compareecc = 1;
    	int	oobreadlen;
    
    
    	DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
    
    	/* Do not allow reads past end of device */
    	if ((from + len) > mtd->size) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: Attempt read beyond end of device\n");
    		*retlen = 0;
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd ,FL_READING);
    
    	/* use userspace supplied oobinfo, if zero */
    	if (oobsel == NULL)
    		oobsel = &mtd->oobinfo;
    
    	/* Autoplace of oob data ? Use the default placement scheme */
    	if (oobsel->useecc == MTD_NANDECC_AUTOPLACE)
    		oobsel = this->autooob;
    
    	eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
    	oob_config = oobsel->eccpos;
    
    	/* Select the NAND device */
    	chipnr = (int)(from >> this->chip_shift);
    	this->select_chip(mtd, chipnr);
    
    	/* First we calculate the starting page */
    	realpage = (int) (from >> this->page_shift);
    	page = realpage & this->pagemask;
    
    	/* Get raw starting column */
    	col = from & (mtd->oobblock - 1);
    
    	end = mtd->oobblock;
    	ecc = this->eccsize;
    	eccbytes = this->eccbytes;
    
    	if ((eccmode == NAND_ECC_NONE) || (this->options & NAND_HWECC_SYNDROME))
    		compareecc = 0;
    
    	oobreadlen = mtd->oobsize;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (this->options & NAND_HWECC_SYNDROME)
    
    		oobreadlen -= oobsel->eccbytes;
    
    	/* Loop until all data read */
    	while (read < len) {
    
    		int aligned = (!col && (len - read) >= end);
    
    		 * If the read is not page aligned, we have to read into data buffer
    		 * due to ecc, else we read into return buffer direct
    		 */
    		if (aligned)
    			data_poi = &buf[read];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		else
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    		/* Check, if we have this page in the buffer
    
    		 *
    		 * FIXME: Make it work when we must provide oob data too,
    		 * check the usage of data_buf oob field
    		 */
    		if (realpage == this->pagebuf && !oob_buf) {
    			/* aligned read ? */
    			if (aligned)
    				memcpy (data_poi, this->data_buf, end);
    			goto readdata;
    		}
    
    		/* Check, if we must send the read command */
    		if (sndcmd) {
    			this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page);
    			sndcmd = 0;
    
    
    		/* get oob area, if we have no oob buffer from fs-driver */
    		if (!oob_buf || oobsel->useecc == MTD_NANDECC_AUTOPLACE ||
    			oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
    			oob_data = &this->data_buf[end];
    
    		eccsteps = this->eccsteps;
    
    		switch (eccmode) {
    		case NAND_ECC_NONE: {	/* No ECC, Read in a page */
    /* XXX U-BOOT XXX */
    #if 0
    			static unsigned long lastwhinge = 0;
    			if ((lastwhinge / HZ) != (jiffies / HZ)) {
    				printk (KERN_WARNING "Reading data from NAND FLASH without ECC is not recommended\n");
    				lastwhinge = jiffies;
    			}
    #else
    			puts("Reading data from NAND FLASH without ECC is not recommended\n");
    #endif
    			this->read_buf(mtd, data_poi, end);
    			break;
    		}
    
    		case NAND_ECC_SOFT:	/* Software ECC 3/256: Read in a page + oob data */
    			this->read_buf(mtd, data_poi, end);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=3, datidx += ecc)
    
    				this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			break;
    
    
    		default:
    			for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=eccbytes, datidx += ecc) {
    				this->enable_hwecc(mtd, NAND_ECC_READ);
    				this->read_buf(mtd, &data_poi[datidx], ecc);
    
    				/* HW ecc with syndrome calculation must read the
    				 * syndrome from flash immidiately after the data */
    				if (!compareecc) {
    					/* Some hw ecc generators need to know when the
    					 * syndrome is read from flash */
    					this->enable_hwecc(mtd, NAND_ECC_READSYN);
    					this->read_buf(mtd, &oob_data[i], eccbytes);
    					/* We calc error correction directly, it checks the hw
    					 * generator for an error, reads back the syndrome and
    					 * does the error correction on the fly */
    					if (this->correct_data(mtd, &data_poi[datidx], &oob_data[i], &ecc_code[i]) == -1) {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    						DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: "
    
    							"Failed ECC read, page 0x%08x on chip %d\n", page, chipnr);
    						ecc_failed++;
    					}
    				} else {
    					this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			break;
    
    		}
    
    		/* read oobdata */
    		this->read_buf(mtd, &oob_data[mtd->oobsize - oobreadlen], oobreadlen);
    
    		/* Skip ECC check, if not requested (ECC_NONE or HW_ECC with syndromes) */
    		if (!compareecc)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			goto readoob;
    
    
    		/* Pick the ECC bytes out of the oob data */
    		for (j = 0; j < oobsel->eccbytes; j++)
    			ecc_code[j] = oob_data[oob_config[j]];
    
    		/* correct data, if neccecary */
    		for (i = 0, j = 0, datidx = 0; i < this->eccsteps; i++, datidx += ecc) {
    			ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]);
    
    			/* Get next chunk of ecc bytes */
    			j += eccbytes;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    			/* Check, if we have a fs supplied oob-buffer,
    
    			 * This is the legacy mode. Used by YAFFS1
    			 * Should go away some day
    			 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			if (oob_buf && oobsel->useecc == MTD_NANDECC_PLACE) {
    
    				int *p = (int *)(&oob_data[mtd->oobsize]);
    				p[i] = ecc_status;
    			}
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    			if (ecc_status == -1) {
    
    				DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " "Failed ECC read, page 0x%08x\n", page);
    				ecc_failed++;
    			}
    
    
    	readoob:
    		/* check, if we have a fs supplied oob-buffer */
    		if (oob_buf) {
    			/* without autoplace. Legacy mode used by YAFFS1 */
    			switch(oobsel->useecc) {
    			case MTD_NANDECC_AUTOPLACE:
    			case MTD_NANDECC_AUTOPL_USR:
    				/* Walk through the autoplace chunks */
    				for (i = 0, j = 0; j < mtd->oobavail; i++) {
    					int from = oobsel->oobfree[i][0];
    					int num = oobsel->oobfree[i][1];
    					memcpy(&oob_buf[oob], &oob_data[from], num);
    					j+= num;
    				}
    				oob += mtd->oobavail;
    				break;
    			case MTD_NANDECC_PLACE:
    				/* YAFFS1 legacy mode */
    				oob_data += this->eccsteps * sizeof (int);
    			default:
    				oob_data += mtd->oobsize;
    			}
    		}
    	readdata:
    		/* Partial page read, transfer data into fs buffer */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (!aligned) {
    
    			for (j = col; j < end && read < len; j++)
    				buf[read++] = data_poi[j];
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			this->pagebuf = realpage;
    		} else
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		/* Apply delay or wait for ready/busy pin
    
    		 * Do this before the AUTOINCR check, so no problems
    		 * arise if a chip which does auto increment
    		 * is marked as NOAUTOINCR by the board driver.
    		*/
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (!this->dev_ready)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			while (!this->dev_ready(mtd));
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			break;
    
    
    		/* For subsequent reads align to page boundary. */
    		col = 0;
    		/* Increment page address */
    		realpage++;
    
    		page = realpage & this->pagemask;
    		/* Check, if we cross a chip boundary */
    		if (!page) {
    			chipnr++;
    			this->select_chip(mtd, -1);
    			this->select_chip(mtd, chipnr);
    		}
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		/* Check, if the chip supports auto page increment
    		 * or if we have hit a block boundary.
    		*/
    
    		if (!NAND_CANAUTOINCR(this) || !(page & blockcheck))
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			sndcmd = 1;
    
    	}
    
    	/* Deselect and wake up anyone waiting on the device */
    	nand_release_device(mtd);
    
    	/*
    	 * Return success, if no ECC failures, else -EBADMSG
    	 * fs driver will take care of that, because
    	 * retlen == desired len and result == -EBADMSG
    	 */
    	*retlen = read;
    	return ecc_failed ? -EBADMSG : 0;
    }
    
    /**
     * nand_read_oob - [MTD Interface] NAND read out-of-band
     * @mtd:	MTD device structure
     * @from:	offset to read from
     * @len:	number of bytes to read
     * @retlen:	pointer to variable to store the number of read bytes
     * @buf:	the databuffer to put data
     *
     * NAND read out-of-band data from the spare area
     */
    static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf)
    {
    	int i, col, page, chipnr;
    	struct nand_chip *this = mtd->priv;
    	int	blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
    
    	DEBUG (MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
    
    	/* Shift to get page */
    	page = (int)(from >> this->page_shift);
    	chipnr = (int)(from >> this->chip_shift);
    
    	/* Mask to get column */
    	col = from & (mtd->oobsize - 1);
    
    	/* Initialize return length value */
    	*retlen = 0;
    
    	/* Do not allow reads past end of device */
    	if ((from + len) > mtd->size) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_read_oob: Attempt read beyond end of device\n");
    		*retlen = 0;
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd , FL_READING);
    
    	/* Select the NAND device */
    	this->select_chip(mtd, chipnr);
    
    	/* Send the read command */
    	this->cmdfunc (mtd, NAND_CMD_READOOB, col, page & this->pagemask);
    
    	 * Read the data, if we read more than one page
    	 * oob data, let the device transfer the data !
    	 */
    	i = 0;
    	while (i < len) {
    		int thislen = mtd->oobsize - col;
    		thislen = min_t(int, thislen, len);
    		this->read_buf(mtd, &buf[i], thislen);
    		i += thislen;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    		/* Apply delay or wait for ready/busy pin
    
    		 * Do this before the AUTOINCR check, so no problems
    		 * arise if a chip which does auto increment
    		 * is marked as NOAUTOINCR by the board driver.
    		*/
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (!this->dev_ready)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			while (!this->dev_ready(mtd));
    
    
    		/* Read more ? */
    		if (i < len) {
    			page++;
    			col = 0;
    
    			/* Check, if we cross a chip boundary */
    			if (!(page & this->pagemask)) {
    				chipnr++;
    				this->select_chip(mtd, -1);
    				this->select_chip(mtd, chipnr);
    			}
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    			/* Check, if the chip supports auto page increment
    			 * or if we have hit a block boundary.
    			*/
    
    			if (!NAND_CANAUTOINCR(this) || !(page & blockcheck)) {
    				/* For subsequent page reads set offset to 0 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				this->cmdfunc (mtd, NAND_CMD_READOOB, 0x0, page & this->pagemask);
    
    			}
    		}
    	}
    
    	/* Deselect and wake up anyone waiting on the device */
    	nand_release_device(mtd);
    
    	/* Return happy */
    	*retlen = len;
    	return 0;
    }
    
    /**
     * nand_read_raw - [GENERIC] Read raw data including oob into buffer
     * @mtd:	MTD device structure
     * @buf:	temporary buffer
     * @from:	offset to read from
     * @len:	number of bytes to read
     * @ooblen:	number of oob data bytes to read
     *
     * Read raw data including oob into buffer
     */
    int nand_read_raw (struct mtd_info *mtd, uint8_t *buf, loff_t from, size_t len, size_t ooblen)
    {
    	struct nand_chip *this = mtd->priv;
    	int page = (int) (from >> this->page_shift);
    	int chip = (int) (from >> this->chip_shift);
    	int sndcmd = 1;
    	int cnt = 0;
    	int pagesize = mtd->oobblock + mtd->oobsize;
    	int	blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
    
    	/* Do not allow reads past end of device */
    	if ((from + len) > mtd->size) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_read_raw: Attempt read beyond end of device\n");
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd , FL_READING);
    
    	this->select_chip (mtd, chip);
    
    	/* Add requested oob length */
    	len += ooblen;
    
    	while (len) {
    		if (sndcmd)
    			this->cmdfunc (mtd, NAND_CMD_READ0, 0, page & this->pagemask);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		sndcmd = 0;
    
    
    		this->read_buf (mtd, &buf[cnt], pagesize);
    
    		len -= pagesize;
    		cnt += pagesize;
    		page++;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    		if (!this->dev_ready)
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			while (!this->dev_ready(mtd));
    
    		/* Check, if the chip supports auto page increment */
    
    		if (!NAND_CANAUTOINCR(this) || !(page & blockcheck))
    			sndcmd = 1;
    	}
    
    	/* Deselect and wake up anyone waiting on the device */
    	nand_release_device(mtd);
    	return 0;
    }
    
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    /**
     * nand_prepare_oobbuf - [GENERIC] Prepare the out of band buffer
    
     * @mtd:	MTD device structure
     * @fsbuf:	buffer given by fs driver
     * @oobsel:	out of band selection structre
     * @autoplace:	1 = place given buffer into the oob bytes
     * @numpages:	number of pages to prepare
     *
     * Return:
     * 1. Filesystem buffer available and autoplacement is off,
     *    return filesystem buffer
     * 2. No filesystem buffer or autoplace is off, return internal
     *    buffer
     * 3. Filesystem buffer is given and autoplace selected
     *    put data from fs buffer into internal buffer and
     *    retrun internal buffer
     *
     * Note: The internal buffer is filled with 0xff. This must
     * be done only once, when no autoplacement happens
     * Autoplacement sets the buffer dirty flag, which
     * forces the 0xff fill before using the buffer again.
     *
    */
    static u_char * nand_prepare_oobbuf (struct mtd_info *mtd, u_char *fsbuf, struct nand_oobinfo *oobsel,
    		int autoplace, int numpages)
    {
    	struct nand_chip *this = mtd->priv;
    	int i, len, ofs;
    
    	/* Zero copy fs supplied buffer */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (fsbuf && !autoplace)
    
    		return fsbuf;
    
    	/* Check, if the buffer must be filled with ff again */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (this->oobdirty) {
    		memset (this->oob_buf, 0xff,
    
    			mtd->oobsize << (this->phys_erase_shift - this->page_shift));
    		this->oobdirty = 0;
    
    	/* If we have no autoplacement or no fs buffer use the internal one */
    	if (!autoplace || !fsbuf)
    		return this->oob_buf;
    
    	/* Walk through the pages and place the data */
    	this->oobdirty = 1;
    	ofs = 0;
    	while (numpages--) {
    		for (i = 0, len = 0; len < mtd->oobavail; i++) {
    			int to = ofs + oobsel->oobfree[i][0];
    			int num = oobsel->oobfree[i][1];
    			memcpy (&this->oob_buf[to], fsbuf, num);
    			len += num;
    			fsbuf += num;
    		}
    		ofs += mtd->oobavail;
    	}
    	return this->oob_buf;
    }
    
    #define NOTALIGNED(x) (x & (mtd->oobblock-1)) != 0
    
    /**
     * nand_write - [MTD Interface] compability function for nand_write_ecc
     * @mtd:	MTD device structure
     * @to:		offset to write to
     * @len:	number of bytes to write
     * @retlen:	pointer to variable to store the number of written bytes
     * @buf:	the data to write
     *
     * This function simply calls nand_write_ecc with oob buffer and oobsel = NULL
     *
    */
    static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
    {
    	return (nand_write_ecc (mtd, to, len, retlen, buf, NULL, NULL));
    }
    
    /**
     * nand_write_ecc - [MTD Interface] NAND write with ECC
     * @mtd:	MTD device structure
     * @to:		offset to write to
     * @len:	number of bytes to write
     * @retlen:	pointer to variable to store the number of written bytes
     * @buf:	the data to write
     * @eccbuf:	filesystem supplied oob data buffer
     * @oobsel:	oob selection structure
     *
     * NAND write with ECC
     */
    static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
    			   size_t * retlen, const u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel)
    {
    	int startpage, page, ret = -EIO, oob = 0, written = 0, chipnr;
    	int autoplace = 0, numpages, totalpages;
    	struct nand_chip *this = mtd->priv;
    	u_char *oobbuf, *bufstart;
    	int	ppblock = (1 << (this->phys_erase_shift - this->page_shift));
    
    	DEBUG (MTD_DEBUG_LEVEL3, "nand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
    
    	/* Initialize retlen, in case of early exit */
    	*retlen = 0;
    
    	/* Do not allow write past end of device */
    	if ((to + len) > mtd->size) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: Attempt to write past end of page\n");
    		return -EINVAL;
    	}
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	/* reject writes, which are not page aligned */
    
    	if (NOTALIGNED (to) || NOTALIGNED(len)) {
    		printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n");
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd, FL_WRITING);
    
    	/* Calculate chipnr */
    	chipnr = (int)(to >> this->chip_shift);
    	/* Select the NAND device */
    	this->select_chip(mtd, chipnr);
    
    	/* Check, if it is write protected */
    	if (nand_check_wp(mtd))
    		goto out;
    
    	/* if oobsel is NULL, use chip defaults */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (oobsel == NULL)
    		oobsel = &mtd->oobinfo;
    
    
    	/* Autoplace of oob data ? Use the default placement scheme */
    	if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
    		oobsel = this->autooob;
    		autoplace = 1;
    
    	if (oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
    		autoplace = 1;
    
    	/* Setup variables and oob buffer */
    	totalpages = len >> this->page_shift;
    	page = (int) (to >> this->page_shift);
    	/* Invalidate the page cache, if we write to the cached page */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (page <= this->pagebuf && this->pagebuf < (page + totalpages))
    
    	/* Set it relative to chip */
    	page &= this->pagemask;
    	startpage = page;
    	/* Calc number of pages we can write in one go */
    	numpages = min (ppblock - (startpage  & (ppblock - 1)), totalpages);
    	oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel, autoplace, numpages);
    	bufstart = (u_char *)buf;
    
    	/* Loop until all data is written */
    	while (written < len) {
    
    		this->data_poi = (u_char*) &buf[written];
    		/* Write one page. If this is the last page to write
    		 * or the last page in this block, then use the
    		 * real pageprogram command, else select cached programming
    		 * if supported by the chip.
    		 */
    		ret = nand_write_page (mtd, this, page, &oobbuf[oob], oobsel, (--numpages > 0));
    		if (ret) {
    			DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: write_page failed %d\n", ret);
    			goto out;
    
    		/* Next oob page */
    		oob += mtd->oobsize;
    		/* Update written bytes count */
    		written += mtd->oobblock;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		if (written == len)
    
    		/* Increment page address */
    		page++;
    
    		/* Have we hit a block boundary ? Then we have to verify and
    		 * if verify is ok, we have to setup the oob buffer for
    		 * the next pages.
    		*/
    		if (!(page & (ppblock - 1))){
    			int ofs;
    			this->data_poi = bufstart;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			ret = nand_verify_pages (mtd, this, startpage,
    
    				page - startpage,
    				oobbuf, oobsel, chipnr, (eccbuf != NULL));
    			if (ret) {
    				DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);
    				goto out;
    
    			bufstart = (u_char*) &buf[written];
    
    
    			ofs = autoplace ? mtd->oobavail : mtd->oobsize;
    			if (eccbuf)
    				eccbuf += (page - startpage) * ofs;
    			totalpages -= page - startpage;
    			numpages = min (totalpages, ppblock);
    			page &= this->pagemask;
    			startpage = page;
    
    			oob = 0;
    			this->oobdirty = 1;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel,
    
    					autoplace, numpages);
    			/* Check, if we cross a chip boundary */
    			if (!page) {
    				chipnr++;
    				this->select_chip(mtd, -1);
    				this->select_chip(mtd, chipnr);
    			}
    		}
    	}
    	/* Verify the remaining pages */
    cmp:
    	this->data_poi = bufstart;
     	ret = nand_verify_pages (mtd, this, startpage, totalpages,
    		oobbuf, oobsel, chipnr, (eccbuf != NULL));
    	if (!ret)
    		*retlen = written;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	else
    
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);
    
    out:
    	/* Deselect and wake up anyone waiting on the device */
    	nand_release_device(mtd);
    
    	return ret;
    }
    
    
    /**
     * nand_write_oob - [MTD Interface] NAND write out-of-band
     * @mtd:	MTD device structure
     * @to:		offset to write to
     * @len:	number of bytes to write
     * @retlen:	pointer to variable to store the number of written bytes
     * @buf:	the data to write
     *
     * NAND write out-of-band
     */
    static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
    {
    	int column, page, status, ret = -EIO, chipnr;
    	struct nand_chip *this = mtd->priv;
    
    	DEBUG (MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
    
    	/* Shift to get page */
    	page = (int) (to >> this->page_shift);
    	chipnr = (int) (to >> this->chip_shift);
    
    	/* Mask to get column */
    	column = to & (mtd->oobsize - 1);
    
    	/* Initialize return length value */
    	*retlen = 0;
    
    	/* Do not allow write past end of page */
    	if ((column + len) > mtd->oobsize) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: Attempt to write past end of page\n");
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd, FL_WRITING);
    
    	/* Select the NAND device */
    	this->select_chip(mtd, chipnr);
    
    	/* Reset the chip. Some chips (like the Toshiba TC5832DC found
    	   in one of my DiskOnChip 2000 test units) will clear the whole
    	   data page too if we don't do this. I have no clue why, but
    	   I seem to have 'fixed' it in the doc2000 driver in
    	   August 1999.  dwmw2. */
    	this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
    
    	/* Check, if it is write protected */
    	if (nand_check_wp(mtd))
    		goto out;
    
    	/* Invalidate the page cache, if we write to the cached page */
    	if (page == this->pagebuf)
    		this->pagebuf = -1;
    
    	if (NAND_MUST_PAD(this)) {
    		/* Write out desired data */
    		this->cmdfunc (mtd, NAND_CMD_SEQIN, mtd->oobblock, page & this->pagemask);
    		/* prepad 0xff for partial programming */
    		this->write_buf(mtd, ffchars, column);
    		/* write data */
    		this->write_buf(mtd, buf, len);
    		/* postpad 0xff for partial programming */
    		this->write_buf(mtd, ffchars, mtd->oobsize - (len+column));
    	} else {
    		/* Write out desired data */
    		this->cmdfunc (mtd, NAND_CMD_SEQIN, mtd->oobblock + column, page & this->pagemask);
    		/* write data */
    		this->write_buf(mtd, buf, len);
    	}
    	/* Send command to program the OOB data */
    	this->cmdfunc (mtd, NAND_CMD_PAGEPROG, -1, -1);
    
    	status = this->waitfunc (mtd, this, FL_WRITING);
    
    	/* See if device thinks it succeeded */
    	if (status & 0x01) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: " "Failed write, page 0x%08x\n", page);
    		ret = -EIO;
    		goto out;
    	}
    	/* Return happy */
    	*retlen = len;
    
    #ifdef CONFIG_MTD_NAND_VERIFY_WRITE
    	/* Send command to read back the data */
    	this->cmdfunc (mtd, NAND_CMD_READOOB, column, page & this->pagemask);
    
    	if (this->verify_buf(mtd, buf, len)) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: " "Failed write verify, page 0x%08x\n", page);
    		ret = -EIO;
    		goto out;
    	}
    #endif
    	ret = 0;
    out:
    	/* Deselect and wake up anyone waiting on the device */
    	nand_release_device(mtd);
    
    	return ret;
    }
    
    /* XXX U-BOOT XXX */
    #if 0
    /**
     * nand_writev - [MTD Interface] compabilty function for nand_writev_ecc
     * @mtd:	MTD device structure
     * @vecs:	the iovectors to write
     * @count:	number of vectors
     * @to:		offset to write to
     * @retlen:	pointer to variable to store the number of written bytes
     *
     * NAND write with kvec. This just calls the ecc function
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int nand_writev (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count,
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	return (nand_writev_ecc (mtd, vecs, count, to, retlen, NULL, NULL));
    
    }
    
    /**
     * nand_writev_ecc - [MTD Interface] write with iovec with ecc
     * @mtd:	MTD device structure
     * @vecs:	the iovectors to write
     * @count:	number of vectors
     * @to:		offset to write to
     * @retlen:	pointer to variable to store the number of written bytes
     * @eccbuf:	filesystem supplied oob data buffer
     * @oobsel:	oob selection structure
     *
     * NAND write with iovec with ecc
     */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int nand_writev_ecc (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count,
    
    		loff_t to, size_t * retlen, u_char *eccbuf, struct nand_oobinfo *oobsel)
    {
    	int i, page, len, total_len, ret = -EIO, written = 0, chipnr;
    	int oob, numpages, autoplace = 0, startpage;
    	struct nand_chip *this = mtd->priv;
    	int	ppblock = (1 << (this->phys_erase_shift - this->page_shift));
    	u_char *oobbuf, *bufstart;
    
    	/* Preset written len for early exit */
    	*retlen = 0;
    
    	/* Calculate total length of data */
    	total_len = 0;
    	for (i = 0; i < count; i++)
    		total_len += (int) vecs[i].iov_len;
    
    	DEBUG (MTD_DEBUG_LEVEL3,
    	       "nand_writev: to = 0x%08x, len = %i, count = %ld\n", (unsigned int) to, (unsigned int) total_len, count);
    
    	/* Do not allow write past end of page */
    	if ((to + total_len) > mtd->size) {
    		DEBUG (MTD_DEBUG_LEVEL0, "nand_writev: Attempted write past end of device\n");
    		return -EINVAL;
    	}
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	/* reject writes, which are not page aligned */
    
    	if (NOTALIGNED (to) || NOTALIGNED(total_len)) {
    		printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n");
    		return -EINVAL;
    	}
    
    	/* Grab the lock and see if the device is available */
    	nand_get_device (this, mtd, FL_WRITING);
    
    	/* Get the current chip-nr */
    	chipnr = (int) (to >> this->chip_shift);
    	/* Select the NAND device */
    	this->select_chip(mtd, chipnr);
    
    	/* Check, if it is write protected */
    	if (nand_check_wp(mtd))
    		goto out;
    
    	/* if oobsel is NULL, use chip defaults */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (oobsel == NULL)
    		oobsel = &mtd->oobinfo;
    
    
    	/* Autoplace of oob data ? Use the default placement scheme */
    	if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
    		oobsel = this->autooob;
    		autoplace = 1;
    
    	if (oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
    		autoplace = 1;
    
    	/* Setup start page */
    	page = (int) (to >> this->page_shift);
    	/* Invalidate the page cache, if we write to the cached page */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (page <= this->pagebuf && this->pagebuf < ((to + total_len) >> this->page_shift))
    
    		this->pagebuf = -1;
    
    	startpage = page & this->pagemask;
    
    	/* Loop until all kvec' data has been written */
    	len = 0;
    	while (count) {
    		/* If the given tuple is >= pagesize then
    		 * write it out from the iov
    		 */
    		if ((vecs->iov_len - len) >= mtd->oobblock) {
    			/* Calc number of pages we can write
    			 * out of this iov in one go */
    			numpages = (vecs->iov_len - len) >> this->page_shift;
    			/* Do not cross block boundaries */
    			numpages = min (ppblock - (startpage & (ppblock - 1)), numpages);
    			oobbuf = nand_prepare_oobbuf (mtd, NULL, oobsel, autoplace, numpages);
    			bufstart = (u_char *)vecs->iov_base;
    			bufstart += len;
    			this->data_poi = bufstart;
    			oob = 0;
    			for (i = 1; i <= numpages; i++) {
    				/* Write one page. If this is the last page to write
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				 * then use the real pageprogram command, else select
    
    				 * cached programming if supported by the chip.
    				 */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				ret = nand_write_page (mtd, this, page & this->pagemask,
    
    					&oobbuf[oob], oobsel, i != numpages);
    				if (ret)
    					goto out;
    				this->data_poi += mtd->oobblock;
    				len += mtd->oobblock;
    				oob += mtd->oobsize;
    				page++;
    			}
    			/* Check, if we have to switch to the next tuple */
    			if (len >= (int) vecs->iov_len) {
    				vecs++;
    				len = 0;
    				count--;
    			}
    		} else {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    			/* We must use the internal buffer, read data out of each
    
    			 * tuple until we have a full page to write
    			 */
    			int cnt = 0;
    			while (cnt < mtd->oobblock) {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    				if (vecs->iov_base != NULL && vecs->iov_len)
    
    					this->data_buf[cnt++] = ((u_char *) vecs->iov_base)[len++];
    				/* Check, if we have to switch to the next tuple */
    				if (len >= (int) vecs->iov_len) {
    					vecs++;
    					len = 0;
    					count--;
    				}