diff options
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/onenand/onenand_base.c | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index b281b116aaeb..8d7d21be1541 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/delay.h> #include <linux/interrupt.h> #include <linux/jiffies.h> #include <linux/mtd/mtd.h> @@ -1284,6 +1285,112 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, #define NOTALIGNED(x) ((x & (this->subpagesize - 1)) != 0) +static void onenand_panic_wait(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int interrupt; + int i; + + for (i = 0; i < 2000; i++) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + udelay(10); + } +} + +/** + * onenand_panic_write - [MTD Interface] write buffer to FLASH in a panic context + * @param mtd MTD device structure + * @param to offset to write to + * @param len number of bytes to write + * @param retlen pointer to variable to store the number of written bytes + * @param buf the data to write + * + * Write with ECC + */ +static int onenand_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + int column, subpage; + int written = 0; + int ret = 0; + + if (this->state == FL_PM_SUSPENDED) + return -EBUSY; + + /* Wait for any existing operation to clear */ + onenand_panic_wait(mtd); + + DEBUG(MTD_DEBUG_LEVEL3, "onenand_panic_write: to = 0x%08x, len = %i\n", + (unsigned int) to, (int) len); + + /* Initialize retlen, in case of early exit */ + *retlen = 0; + + /* Do not allow writes past end of device */ + if (unlikely((to + len) > mtd->size)) { + printk(KERN_ERR "onenand_panic_write: Attempt write to past end of device\n"); + return -EINVAL; + } + + /* Reject writes, which are not page aligned */ + if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) { + printk(KERN_ERR "onenand_panic_write: Attempt to write not page aligned data\n"); + return -EINVAL; + } + + column = to & (mtd->writesize - 1); + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, mtd->writesize - column, len - written); + u_char *wbuf = (u_char *) buf; + + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); + + /* Partial page write */ + subpage = thislen < mtd->writesize; + if (subpage) { + memset(this->page_buf, 0xff, mtd->writesize); + memcpy(this->page_buf + column, buf, thislen); + wbuf = this->page_buf; + } + + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize); + + this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); + + onenand_panic_wait(mtd); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !ret && !subpage); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage); + } + + if (ret) { + printk(KERN_ERR "onenand_panic_write: write failed %d\n", ret); + break; + } + + written += thislen; + + if (written == len) + break; + + column = 0; + to += thislen; + buf += thislen; + } + + *retlen = written; + return ret; +} + /** * onenand_fill_auto_oob - [Internal] oob auto-placement transfer * @param mtd MTD device structure @@ -2673,6 +2780,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) mtd->write = onenand_write; mtd->read_oob = onenand_read_oob; mtd->write_oob = onenand_write_oob; + mtd->panic_write = onenand_panic_write; #ifdef CONFIG_MTD_ONENAND_OTP mtd->get_fact_prot_info = onenand_get_fact_prot_info; mtd->read_fact_prot_reg = onenand_read_fact_prot_reg; |