Add NAND dual plane program function. Signed-off-by: Bean Huo <bean...@micron.com> --- drivers/mtd/mtdpart.c | 21 +++ drivers/mtd/nand/nand_base.c | 401 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+)
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index cafdb88..3ee96e7 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -147,6 +147,7 @@ static int part_read_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct mtd_part *part = PART(mtd); + return part->master->_read_user_prot_reg(part->master, from, len, retlen, buf); } @@ -155,6 +156,7 @@ static int part_get_user_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen, struct otp_info *buf) { struct mtd_part *part = PART(mtd); + return part->master->_get_user_prot_info(part->master, len, retlen, buf); } @@ -203,6 +205,23 @@ static int part_write_oob(struct mtd_info *mtd, loff_t to, return part->master->_write_oob(part->master, to + part->offset, ops); } +static int part_write_dual_plane_oob(struct mtd_info *mtd, loff_t to_plane0, + struct mtd_oob_ops *ops_plane0, loff_t to_plane1, + struct mtd_oob_ops *ops_plane1) +{ + struct mtd_part *part = PART(mtd); + + if ((to_plane0 >= mtd->size) || ((to_plane1 >= mtd->size))) + return -EINVAL; + if ((ops_plane0->datbuf && to_plane0 + ops_plane0->len > mtd->size) || + (ops_plane1->datbuf && to_plane1 + ops_plane0->len > mtd->size)) + return -EINVAL; + + return part->master->_dual_plane_write_oob(part->master, + to_plane0 + part->offset, ops_plane0, + to_plane1 + part->offset, ops_plane1); +} + static int part_write_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { @@ -409,6 +428,8 @@ static struct mtd_part *allocate_partition(struct mtd_info *master, slave->mtd._read_oob = part_read_oob; if (master->_write_oob) slave->mtd._write_oob = part_write_oob; + if (master->_dual_plane_write_oob) + slave->mtd._dual_plane_write_oob = part_write_dual_plane_oob; if (master->_read_user_prot_reg) slave->mtd._read_user_prot_reg = part_read_user_prot_reg; if (master->_read_fact_prot_reg) diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ceb68ca..bcc9e92 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2249,6 +2249,75 @@ static int nand_write_page_syndrome(struct mtd_info *mtd, return 0; } +/** + * nand_write_plane_page - [REPLACEABLE] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor + * @offset: address offset within the page + * @data_len: length of actual data to be written + * @buf: the data to write + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * @plane: multiple plane programming + * @raw: use _raw version of write_page + */ +static int nand_write_plane_page(struct mtd_info *mtd, struct nand_chip *chip, + uint32_t offset, int data_len, const uint8_t *buf, + int oob_required, int page, int plane, int raw) +{ + int status, subpage; + + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && + chip->ecc.write_subpage) + subpage = offset || (data_len < mtd->writesize); + else + subpage = 0; + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + + if (unlikely(raw)) + status = chip->ecc.write_page_raw(mtd, chip, buf, + oob_required); + else if (subpage) + status = chip->ecc.write_subpage(mtd, chip, offset, data_len, + buf, oob_required); + else + status = chip->ecc.write_page(mtd, chip, buf, oob_required); + + if (status < 0) + return status; + + /* Multipal plane progamming */ + if (plane) { + chip->cmdfunc(mtd, NAND_CMD_MULTI_PAGEPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + /* + * See if operation failed and additional status checks are + * available. + */ + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, page); + + if (status & NAND_STATUS_FAIL) + return -EIO; + + } else if (!plane || !NAND_HAS_CACHEPROG(chip)) { + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + /* + * See if operation failed and additional status checks are + * available. + */ + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, page); + + if (status & NAND_STATUS_FAIL) + return -EIO; + } + + return 0; +} /** * nand_write_page - [REPLACEABLE] write one page @@ -2373,6 +2442,277 @@ static uint8_t *nand_fill_oob(struct mtd_info *mtd, uint8_t *oob, size_t len, } #define NOTALIGNED(x) ((x & (chip->subpagesize - 1)) != 0) +/** + * nand_do_dual_plane_write_ops - [INTERN] NAND write with ECC by dual plane + * @mtd: MTD device structure + * @to_plane0: offset of write plane 0 + * @ops_plane0: oob operations description structure for plane 0 + * @to_plane1: offset of write plane 1 + * @ops_plane1: oob operations description structure for plane 1 + * + * NAND write with ECC through dual plane program. + */ +static int nand_do_dual_plane_write_ops(struct mtd_info *mtd, loff_t to_plane0, + struct mtd_oob_ops *ops_plane0, loff_t to_plane1, + struct mtd_oob_ops *ops_plane1) +{ + int chipnr0, chipnr1, chipnr = 0, blockmask; + uint32_t oobwritelen = 0; + uint32_t oobmaxlen = 0; + int ret; + int column = 0, realpage = 0, page = 0; + uint32_t writelen = 0; + char flag = 0, cycle = 0; + int oob_required = 0; + uint8_t *oob = NULL; + uint8_t *buf = NULL; + uint32_t bak0_oobwritelen = 0, bak1_oobwritelen = 0; + int bak0_column = 0, bak1_column = 0; + int bak0_realpage = 0, bak1_realpage = 0; + int bak0_page = 0, bak1_page = 0; + int bak0_writelen = 0, bak1_writelen = 0; + uint8_t *bak0_buf = NULL, *bak1_buf = NULL; + uint8_t *bak0_oob = NULL, *bak1_oob = NULL; + uint8_t bak0_pagebuf = 0, bak1_pagebuf = 0; + int bytes = 0; + int cached = 0; + uint8_t *wbuf = NULL; + int use_bufpoi = 0; + int part_pagewr = 0; + struct nand_chip *chip = mtd->priv; + struct mtd_oob_ops *ops = NULL; + + ops_plane0->retlen = 0; + ops_plane1->retlen = 0; + + if ((!ops_plane0->len) || (!ops_plane1->len)) + return 0; + + /* Reject writes, which are not page aligned */ + if (NOTALIGNED(to_plane0) || NOTALIGNED(ops_plane0->len) || + NOTALIGNED(to_plane1) || NOTALIGNED(ops_plane1->len)) { + pr_notice("%s: attempt to write non page aligned data\n", + __func__); + return -EINVAL; + } + + chipnr0 = (int)(to_plane0 >> chip->chip_shift); + chipnr1 = (int)(to_plane1 >> chip->chip_shift); + + if (unlikely(chipnr0 != chipnr1)) { + pr_notice("%s: attempt to write different nand chip\n", + __func__); + return -EINVAL; + } + + chip->select_chip(mtd, chipnr0); + + /* Check, if it is write protected */ + if (nand_check_wp(mtd)) { + ret = -EIO; + goto err_out; + } + + blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1; + + /* Don't allow multipage oob writes with offset */ + if ((ops_plane0->oobbuf && ops_plane0->ooboffs && + (ops_plane0->ooboffs + ops_plane0->ooblen > oobmaxlen)) || + (ops_plane1->oobbuf && ops_plane1->ooboffs && + (ops_plane1->ooboffs + ops_plane1->ooblen > oobmaxlen))) { + ret = -EINVAL; + goto err_out; + } + + while (1) { +retry: + if (flag == 0) { + /* operate plane 0 */ + ops = ops_plane0; + oobmaxlen = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; + chipnr = chipnr0; + oob_required = ops->oobbuf ? 1 : 0; + + if (cycle == 0) { + /* plane 0 first write,backup programming infor */ + bak0_oobwritelen = oobwritelen = ops->ooblen; + bak0_column = column = to_plane0 & (mtd->writesize - 1); + realpage = (int)(to_plane0 >> chip->page_shift); + bak0_realpage = realpage; + bak0_page = page = realpage & chip->pagemask; + bak0_writelen = writelen = ops->len; + bak0_buf = buf = ops->datbuf; + bak0_oob = oob = ops->oobbuf; + + if (to_plane0 <= ((loff_t)chip->pagebuf << chip->page_shift) && + ((loff_t)chip->pagebuf << chip->page_shift) < (to_plane0 + ops->len)) + chip->pagebuf = -1; + + bak0_pagebuf = chip->pagebuf; + + } else { + oobwritelen = bak0_oobwritelen; + column = bak0_column; + realpage = bak0_realpage; + page = bak0_page; + writelen = bak0_writelen; + buf = bak0_buf; + oob = bak0_oob; + chip->pagebuf = bak0_pagebuf; + } + } else if (flag == 1) { + /* operate plane 1 */ + ops = ops_plane1; + oobmaxlen = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; + chipnr = chipnr1; + oob_required = ops->oobbuf ? 1 : 0; + + if (cycle == 0) { + /* plane 1 first write,backup programming infor */ + bak1_oobwritelen = oobwritelen = ops->ooblen; + bak1_column = column = to_plane1 & (mtd->writesize - 1); + realpage = (int)(to_plane1 >> chip->page_shift); + bak1_realpage = realpage; + bak1_page = page = realpage & chip->pagemask; + bak1_writelen = writelen = ops->len; + bak1_buf = buf = ops->datbuf; + bak1_oob = oob = ops->oobbuf; + + if (to_plane1 <= ((loff_t)chip->pagebuf << chip->page_shift) && + ((loff_t)chip->pagebuf << chip->page_shift) < (to_plane1 + ops->len)) + chip->pagebuf = -1; + + bak1_pagebuf = chip->pagebuf; + } else { + oobwritelen = bak1_oobwritelen; + column = bak1_column; + realpage = bak1_realpage; + page = bak1_page; + writelen = bak1_writelen; + buf = bak1_buf; + oob = bak1_oob; + chip->pagebuf = bak1_pagebuf; + } + } + + /* Don't allow multipage oob writes with offset */ + if (ops->oobbuf && ops->ooboffs && + (ops->ooboffs + ops->ooblen > oobmaxlen)) { + ret = -EINVAL; + goto err_out; + } + + bytes = mtd->writesize; + cached = writelen > bytes && page != blockmask; + wbuf = buf; + + part_pagewr = (column || writelen < (mtd->writesize - 1)); + + if (part_pagewr) + use_bufpoi = 1; + else if (chip->options & NAND_USE_BOUNCE_BUFFER) + use_bufpoi = !virt_addr_valid(buf); + else + use_bufpoi = 0; + + /* Partial page write?, or need to use bounce buffer */ + if (use_bufpoi) { + pr_debug("%s: using write bounce buffer for buf@%p\n", + __func__, buf); + cached = 0; + if (part_pagewr) + bytes = min_t(int, bytes - column, writelen); + chip->pagebuf = -1; + memset(chip->buffers->databuf, 0xff, mtd->writesize); + memcpy(&chip->buffers->databuf[column], buf, bytes); + wbuf = chip->buffers->databuf; + } + + if (unlikely(oob)) { + size_t len = min(oobwritelen, oobmaxlen); + + oob = nand_fill_oob(mtd, oob, len, ops); + oobwritelen -= len; + } else { + /* We still need to erase leftover OOB data */ + memset(chip->oob_poi, 0xff, mtd->oobsize); + } + + if (flag == 0) { + ret = chip->write_plane_page(mtd, chip, column, bytes, + wbuf, oob_required, page, 1, (ops->mode == MTD_OPS_RAW)); + } else if (flag == 1) { + ret = chip->write_page(mtd, chip, column, bytes, wbuf, + oob_required, page, cached, (ops->mode == MTD_OPS_RAW)); + } + + if (ret) + break; + + writelen -= bytes; + column = 0; + + if (flag == 0) { + bak0_writelen = writelen; + bak0_column = column; + bak0_oobwritelen = oobwritelen; + } else { + bak1_writelen = writelen; + bak1_column = column; + bak1_oobwritelen = oobwritelen; + } + + if ((!writelen) && (flag == 1)) + break; + + buf += bytes; + realpage++; + + if (flag == 0) { + bak0_buf = buf; + bak0_oob = oob; + bak0_realpage = realpage; + } else { + bak1_buf = buf; + bak1_oob = oob; + bak1_realpage = realpage; + } + + if (flag == 0) { + flag = 1; + goto retry; + } + + page = realpage & chip->pagemask; + + /* Check, if we cross a chip boundary */ + if (!page) { + chipnr++; + chip->select_chip(mtd, -1); + chip->select_chip(mtd, chipnr); + } + + flag = 0; + cycle++; + + } + + ops_plane0->retlen = ops_plane0->len - bak0_writelen; + ops_plane1->retlen = ops_plane1->len - bak1_writelen; + + if (unlikely(bak0_oob)) + ops_plane0->oobretlen = ops_plane0->ooblen; + if (unlikely(bak1_oob)) + ops_plane1->oobretlen = ops_plane1->ooblen; + +err_out: + flag = 0; + cycle = 0; + chip->select_chip(mtd, -1); + return ret; +} /** * nand_do_write_ops - [INTERN] NAND write with ECC @@ -2564,6 +2904,14 @@ static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, return ret; } +static int nand_do_dual_plane_write_oob(struct mtd_info *mtd, loff_t to_plane0, + struct mtd_oob_ops *ops_plane0, loff_t to_plane1, + struct mtd_oob_ops *ops_plane1) +{ + return 0; + +} + /** * nand_do_write_oob - [MTD Interface] NAND write out-of-band * @mtd: MTD device structure @@ -2692,6 +3040,52 @@ out: return ret; } +static int nand_dual_plane_write_oob(struct mtd_info *mtd, loff_t to_plane0, + struct mtd_oob_ops *ops_plane0, loff_t to_plane1, + struct mtd_oob_ops *ops_plane1) +{ + int ret = -ENOTSUPP; + + /* Do not allow writes past end of device */ + if ((ops_plane0->datbuf && (to_plane0 + ops_plane0->len) > mtd->size) || + (ops_plane1->datbuf && (to_plane1 + ops_plane1->len) > mtd->size)) { + pr_debug("%s: attempt to write beyond end of device\n", + __func__); + return -EINVAL; + } + nand_get_device(mtd, FL_WRITING); + + switch (ops_plane0->mode) { + case MTD_OPS_PLACE_OOB: + if (ops_plane1->mode != MTD_OPS_PLACE_OOB) + goto out; + break; + case MTD_OPS_AUTO_OOB: + if (ops_plane1->mode != MTD_OPS_AUTO_OOB) + goto out; + break; + case MTD_OPS_RAW: + if (ops_plane1->mode != MTD_OPS_RAW) + goto out; + break; + + default: + goto out; + } + + if (!ops_plane0->datbuf && !ops_plane1->datbuf) + ret = nand_do_dual_plane_write_oob(mtd, to_plane0, ops_plane0, + to_plane1, ops_plane1); + else + ret = nand_do_dual_plane_write_ops(mtd, to_plane0, ops_plane0, + to_plane1, ops_plane1); + +out: + nand_release_device(mtd); + return ret; +} +EXPORT_SYMBOL(nand_dual_plane_write_oob); + /** * single_erase - [GENERIC] NAND standard block erase command function * @mtd: MTD device structure @@ -3991,6 +4385,8 @@ int nand_scan_tail(struct mtd_info *mtd) if (!chip->write_page) chip->write_page = nand_write_page; + if (!chip->write_plane_page) + chip->write_plane_page = nand_write_plane_page; /* * Check ECC mode, default to software if 3byte/512byte hardware ECC is @@ -4206,6 +4602,11 @@ int nand_scan_tail(struct mtd_info *mtd) mtd->_panic_write = panic_nand_write; mtd->_read_oob = nand_read_oob; mtd->_write_oob = nand_write_oob; +#ifdef CONFIG_MTD_UBI_MLC_NAND_BAKVOL + mtd->_dual_plane_write_oob = nand_dual_plane_write_oob; +#else + mtd->_dual_plane_write_oob = NULL; +#endif mtd->_sync = nand_sync; mtd->_lock = NULL; mtd->_unlock = NULL; -- 1.9.1