From patchwork Mon Apr 10 07:51:49 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?b?UGV0ZXIgUGFuIOa9mOagiyAocGV0ZXJwYW5kb25nKQ==?= X-Patchwork-Id: 748863 X-Patchwork-Delegate: boris.brezillon@free-electrons.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3w1hv03L3Xz9s8B for ; Mon, 10 Apr 2017 17:41:00 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="E51FMkwB"; dkim-atps=neutral DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=KOAQBxCtYpgjo4ymEF//eruBeH7iTZVNyya8MQqQlSo=; b=E51FMkwBQi96Gx CdB+VLGEJW+mZ20pDAprJJequ9f1tzts7Hvf4+N6Cq5n44fSGueqhQD9xlZiBGKCc6SvsfeHsFEWh RMnOaGkRX6NKSHk5hMtmCpNoKF3t3Q99UldsNZl/YK8x8UfPgmhQECVhP1i24MObfmSPPRyMYlyYL BH+logb73T7k5m/3bdYvJ72BtAIs9LfXbhL8RpcquEExpqYppYWqkTR/zvPZt1szyRqG6sI2/wLB2 ssBPM/TbCbCI32+RONhCm5BU2bU46PVMwftIIaMRqk7TDs2gW4f/OwhkJhSq832naJPShd7M4t6Ci 58SR0TWVIqtWsBf7/JFQ==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1cxTwS-0003Rj-Fk; Mon, 10 Apr 2017 07:40:52 +0000 Received: from mailout.micron.com ([137.201.242.129]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1cxTw2-00026V-MQ for linux-mtd@lists.infradead.org; Mon, 10 Apr 2017 07:40:37 +0000 Received: from mail.micron.com (bowex36e.micron.com [137.201.85.221]) by mailout.micron.com (8.14.4/8.14.6) with ESMTP id v3A7dNZH017700; Mon, 10 Apr 2017 01:39:23 -0600 Received: from SIWEX4D.sing.micron.com (10.160.29.68) by bowex36e.micron.com (137.201.85.221) with Microsoft SMTP Server (TLS) id 15.0.1263.5; Mon, 10 Apr 2017 01:39:22 -0600 Received: from BOWEX36F.micron.com (137.201.84.51) by SIWEX4D.sing.micron.com (10.160.29.68) with Microsoft SMTP Server (TLS) id 15.0.1263.5; Mon, 10 Apr 2017 15:39:18 +0800 Received: from peterpan-Linux-Desktop.micron.com (10.66.12.56) by BOWEX36F.micron.com (137.201.84.51) with Microsoft SMTP Server id 15.0.1263.5 via Frontend Transport; Mon, 10 Apr 2017 01:39:15 -0600 From: Peter Pan To: , , , , , , , Subject: [PATCH v5 2/6] nand: spi: add basic operations support Date: Mon, 10 Apr 2017 15:51:49 +0800 Message-ID: <1491810713-27795-3-git-send-email-peterpandong@micron.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1491810713-27795-1-git-send-email-peterpandong@micron.com> References: <1491810713-27795-1-git-send-email-peterpandong@micron.com> MIME-Version: 1.0 X-TM-AS-Product-Ver: SMEX-12.0.0.1464-8.100.1062-22996.005 X-TM-AS-Result: No--4.329300-0.000000-31 X-TM-AS-MatchedID: 700398-704425-863432-700648-862883-188019-706290-708712-7 07788-704318-704568-702898-704983-863596-707451-700811-847298-710207-701604 -704473-703712-704713-705753-702609-851458-701594-700264-186003-700251-8515 47-703267-705450-700324-705414-700057-709584-700476-702598-188199-701775-70 2005-703529-700200-703399-105250-700383-703788-702098-700693-708797-300010- 148004-148036-42000-42003-52000 X-TM-AS-User-Approved-Sender: Yes X-TM-AS-User-Blocked-Sender: No X-MT-CheckInternalSenderRule: True X-Scanned-By: MIMEDefang 2.78 on 137.201.130.65 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170410_004027_114371_97EE4F31 X-CRM114-Status: GOOD ( 15.58 ) X-Spam-Score: -4.2 (----) X-Spam-Report: SpamAssassin version 3.4.1 on bombadil.infradead.org summary: Content analysis details: (-4.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [137.201.242.129 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -0.0 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: peterpansjtu@gmail.com, linshunquan1@hisilicon.com, peterpandong@micron.com Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org This commit is to support read, readoob, write, writeoob and erase operations in the new spi nand framework. Signed-off-by: Peter Pan --- drivers/mtd/nand/spi/core.c | 750 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/spinand.h | 2 + 2 files changed, 752 insertions(+) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 510c7cb..eb7af9d 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -108,6 +108,232 @@ static int spinand_read_status(struct spinand_device *chip, u8 *status) } /* + * spinand_get_cfg - get configuration register value + * @chip: SPI NAND device structure + * @cfg: buffer to store value + * Description: + * Configuration register includes OTP config, Lock Tight enable/disable + * and Internal ECC enable/disable. + */ +static int spinand_get_cfg(struct spinand_device *chip, u8 *cfg) +{ + return spinand_read_reg(chip, REG_CFG, cfg); +} + +/* + * spinand_set_cfg - set value to configuration register + * @chip: SPI NAND device structure + * @cfg: value to set + * Description: + * Configuration register includes OTP config, Lock Tight enable/disable + * and Internal ECC enable/disable. + */ +static int spinand_set_cfg(struct spinand_device *chip, u8 cfg) +{ + return spinand_write_reg(chip, REG_CFG, cfg); +} + +/* + * spinand_enable_ecc - enable internal ECC + * @chip: SPI NAND device structure + */ +static void spinand_enable_ecc(struct spinand_device *chip) +{ + u8 cfg = 0; + + spinand_get_cfg(chip, &cfg); + if ((cfg & CFG_ECC_MASK) == CFG_ECC_ENABLE) + return; + cfg |= CFG_ECC_ENABLE; + spinand_set_cfg(chip, cfg); +} + +/* + * spinand_disable_ecc - disable internal ECC + * @chip: SPI NAND device structure + */ +static void spinand_disable_ecc(struct spinand_device *chip) +{ + u8 cfg = 0; + + spinand_get_cfg(chip, &cfg); + if ((cfg & CFG_ECC_MASK) == CFG_ECC_ENABLE) { + cfg &= ~CFG_ECC_ENABLE; + spinand_set_cfg(chip, cfg); + } +} + +/* + * spinand_write_enable - send command 06h to enable write or erase the + * NAND cells + * @chip: SPI NAND device structure + */ +static int spinand_write_enable(struct spinand_device *chip) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = SPINAND_CMD_WR_ENABLE; + + return spinand_exec_op(chip, &op); +} + +/* + * spinand_read_page_to_cache - send command 13h to read data from NAND array + * to cache + * @chip: SPI NAND device structure + * @page_addr: page to read + */ +static int spinand_read_page_to_cache(struct spinand_device *chip, + u32 page_addr) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = SPINAND_CMD_PAGE_READ; + op.n_addr = 3; + op.addr[0] = (u8)(page_addr >> 16); + op.addr[1] = (u8)(page_addr >> 8); + op.addr[2] = (u8)page_addr; + + return spinand_exec_op(chip, &op); +} + +/* + * spinand_get_address_bits - return address should be transferred + * by how many bits + * @opcode: command's operation code + */ +static int spinand_get_address_bits(u8 opcode) +{ + switch (opcode) { + case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO: + return 4; + case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO: + return 2; + default: + return 1; + } +} + +/* + * spinand_get_data_bits - return data should be transferred by how many bits + * @opcode: command's operation code + */ +static int spinand_get_data_bits(u8 opcode) +{ + switch (opcode) { + case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO: + case SPINAND_CMD_READ_FROM_CACHE_X4: + case SPINAND_CMD_PROG_LOAD_X4: + case SPINAND_CMD_PROG_LOAD_RDM_DATA_X4: + return 4; + case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO: + case SPINAND_CMD_READ_FROM_CACHE_X2: + return 2; + default: + return 1; + } +} + +/* + * spinand_read_from_cache - read data out from cache register + * @chip: SPI NAND device structure + * @page_addr: page to read + * @column: the location to read from the cache + * @len: number of bytes to read + * @rbuf: buffer held @len bytes + */ +static int spinand_read_from_cache(struct spinand_device *chip, u32 page_addr, + u32 column, size_t len, u8 *rbuf) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = chip->read_cache_op; + op.n_addr = 2; + op.addr[0] = (u8)(column >> 8); + op.addr[1] = (u8)column; + op.addr_nbits = spinand_get_address_bits(chip->read_cache_op); + op.n_rx = len; + op.rx_buf = rbuf; + op.data_nbits = spinand_get_data_bits(chip->read_cache_op); + if (chip->manufacturer.manu->ops->prepare_op) + chip->manufacturer.manu->ops->prepare_op(chip, &op, + page_addr, column); + + return spinand_exec_op(chip, &op); +} + +/* + * spinand_write_to_cache - write data to cache register + * @chip: SPI NAND device structure + * @page_addr: page to write + * @column: the location to write to the cache + * @len: number of bytes to write + * @wrbuf: buffer held @len bytes + */ +static int spinand_write_to_cache(struct spinand_device *chip, u32 page_addr, + u32 column, size_t len, const u8 *wbuf) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = chip->write_cache_op; + op.n_addr = 2; + op.addr[0] = (u8)(column >> 8); + op.addr[1] = (u8)column; + op.addr_nbits = spinand_get_address_bits(chip->write_cache_op); + op.n_tx = len; + op.tx_buf = wbuf; + op.data_nbits = spinand_get_data_bits(chip->write_cache_op); + if (chip->manufacturer.manu->ops->prepare_op) + chip->manufacturer.manu->ops->prepare_op(chip, &op, + page_addr, column); + + return spinand_exec_op(chip, &op); +} + +/* + * spinand_program_execute - send command 10h to write a page from + * cache to the NAND array + * @chip: SPI NAND device structure + * @page_addr: the physical page location to write the page. + */ +static int spinand_program_execute(struct spinand_device *chip, u32 page_addr) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = SPINAND_CMD_PROG_EXC; + op.n_addr = 3; + op.addr[0] = (u8)(page_addr >> 16); + op.addr[1] = (u8)(page_addr >> 8); + op.addr[2] = (u8)page_addr; + + return spinand_exec_op(chip, &op); +} + +/* + * spinand_erase_block_erase - send command D8h to erase a block + * @chip: SPI NAND device structure + * @page_addr: the start page address of block to be erased. + */ +static int spinand_erase_block(struct spinand_device *chip, u32 page_addr) +{ + struct spinand_op op; + + spinand_init_op(&op); + op.cmd = SPINAND_CMD_BLK_ERASE; + op.n_addr = 3; + op.addr[0] = (u8)(page_addr >> 16); + op.addr[1] = (u8)(page_addr >> 8); + op.addr[2] = (u8)page_addr; + + return spinand_exec_op(chip, &op); +} + +/* * spinand_wait - wait until the command is done * @chip: SPI NAND device structure * @s: buffer to store status register value (can be NULL) @@ -192,6 +418,525 @@ static int spinand_lock_block(struct spinand_device *chip, u8 lock) } /* + * spinand_get_ecc_status - get ecc correction information from status register + * @chip: SPI NAND device structure + * @status: status register value + * @corrected: corrected bit flip number + * @ecc_error: ecc correction error or not + */ +static void spinand_get_ecc_status(struct spinand_device *chip, + unsigned int status, + unsigned int *corrected, + unsigned int *ecc_error) +{ + return chip->ecc.engine->ops->get_ecc_status(chip, status, corrected, + ecc_error); +} + +/* + * spinand_do_read_page - read page from device to buffer + * @mtd: MTD device structure + * @page_addr: page address/raw address + * @ecc_off: without ecc or not + * @corrected: how many bit flip corrected + * @oob_only: read OOB only or the whole page + */ +static int spinand_do_read_page(struct mtd_info *mtd, u32 page_addr, + bool ecc_off, int *corrected, bool oob_only) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + int ret, ecc_error = 0; + u8 status; + + spinand_read_page_to_cache(chip, page_addr); + ret = spinand_wait(chip, &status); + if (ret < 0) { + dev_err(chip->dev, "error %d waiting page 0x%x to cache\n", + ret, page_addr); + return ret; + } + if (!oob_only) + spinand_read_from_cache(chip, page_addr, 0, + nand_page_size(nand) + + nand_per_page_oobsize(nand), + chip->buf); + else + spinand_read_from_cache(chip, page_addr, nand_page_size(nand), + nand_per_page_oobsize(nand), + chip->oobbuf); + if (!ecc_off) { + spinand_get_ecc_status(chip, status, corrected, &ecc_error); + /* + * If there's an ECC error, print a message and notify MTD + * about it. Then complete the read, to load actual data on + * the buffer (instead of the status result). + */ + if (ecc_error) { + dev_err(chip->dev, + "internal ECC error reading page 0x%x\n", + page_addr); + mtd->ecc_stats.failed++; + } else if (*corrected) { + mtd->ecc_stats.corrected += *corrected; + } + } + + return 0; +} + +/* + * spinand_do_write_page - write data from buffer to device + * @mtd: MTD device structure + * @page_addr: page address/raw address + * @oob_only: write OOB only or the whole page + */ +static int spinand_do_write_page(struct mtd_info *mtd, u32 page_addr, + bool oob_only) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + u8 status; + int ret = 0; + + spinand_write_enable(chip); + if (!oob_only) + spinand_write_to_cache(chip, page_addr, 0, + nand_page_size(nand) + + nand_per_page_oobsize(nand), chip->buf); + else + spinand_write_to_cache(chip, page_addr, nand_page_size(nand), + nand_per_page_oobsize(nand), + chip->oobbuf); + spinand_program_execute(chip, page_addr); + ret = spinand_wait(chip, &status); + if (ret < 0) { + dev_err(chip->dev, "error %d reading page 0x%x from cache\n", + ret, page_addr); + return ret; + } + if ((status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL) { + dev_err(chip->dev, "program page 0x%x failed\n", page_addr); + ret = -EIO; + } + return ret; +} + +/* + * spinand_transfer_oob - transfer oob to client buffer + * @chip: SPI NAND device structure + * @oob: oob destination address + * @ops: oob ops structure + * @len: size of oob to transfer + */ +static int spinand_transfer_oob(struct spinand_device *chip, u8 *oob, + struct mtd_oob_ops *ops, size_t len) +{ + struct mtd_info *mtd = spinand_to_mtd(chip); + int ret = 0; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + memcpy(oob, chip->oobbuf + ops->ooboffs, len); + break; + case MTD_OPS_AUTO_OOB: + ret = mtd_ooblayout_get_databytes(mtd, oob, chip->oobbuf, + ops->ooboffs, len); + break; + default: + ret = -EINVAL; + } + return ret; +} + +/* + * spinand_fill_oob - transfer client buffer to oob + * @chip: SPI NAND device structure + * @oob: oob data buffer + * @len: oob data write length + * @ops: oob ops structure + */ +static int spinand_fill_oob(struct spinand_device *chip, uint8_t *oob, + size_t len, struct mtd_oob_ops *ops) +{ + struct mtd_info *mtd = spinand_to_mtd(chip); + struct nand_device *nand = mtd_to_nand(mtd); + int ret = 0; + + memset(chip->oobbuf, 0xff, nand_per_page_oobsize(nand)); + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + memcpy(chip->oobbuf + ops->ooboffs, oob, len); + break; + case MTD_OPS_AUTO_OOB: + ret = mtd_ooblayout_set_databytes(mtd, oob, chip->oobbuf, + ops->ooboffs, len); + break; + default: + ret = -EINVAL; + } + return ret; +} + +/* + * spinand_read_pages - read data from device to buffer + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operations description structure + * @max_bitflips: maximum bitflip count + */ +static int spinand_read_pages(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops, + unsigned int *max_bitflips) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + int size, ret; + unsigned int corrected = 0; + bool ecc_off = ops->mode == MTD_OPS_RAW; + int ooblen = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; + bool oob_only = !ops->datbuf; + struct nand_page_iter iter; + + ops->retlen = 0; + ops->oobretlen = 0; + *max_bitflips = 0; + + nand_for_each_page(nand, from, ops->len, ops->ooboffs, ops->ooblen, + ooblen, &iter) { + ret = spinand_do_read_page(mtd, iter.page, ecc_off, + &corrected, oob_only); + if (ret) + break; + *max_bitflips = max(*max_bitflips, corrected); + if (ops->datbuf) { + size = min_t(int, iter.dataleft, + nand_page_size(nand) - iter.pageoffs); + memcpy(ops->datbuf + ops->retlen, + chip->buf + iter.pageoffs, size); + ops->retlen += size; + } + if (ops->oobbuf) { + size = min_t(int, iter.oobleft, ooblen); + ret = spinand_transfer_oob(chip, + ops->oobbuf + ops->oobretlen, + ops, size); + if (ret) { + dev_err(chip->dev, "Transfer oob error %d\n", ret); + return ret; + } + ops->oobretlen += size; + } + } + + return ret; +} + +/* + * spinand_do_read_ops - read data from device to buffer + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operations description structure + */ +static int spinand_do_read_ops(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + int ret; + struct mtd_ecc_stats stats; + unsigned int max_bitflips = 0; + bool ecc_off = ops->mode == MTD_OPS_RAW; + + ret = nand_check_address(nand, from); + if (ret) { + dev_err(chip->dev, "%s: invalid read address\n", __func__); + return ret; + } + ret = nand_check_oob_ops(nand, from, ops); + if (ret) { + dev_err(chip->dev, + "%s: invalid oob operation input\n", __func__); + return ret; + } + mutex_lock(&chip->lock); + stats = mtd->ecc_stats; + if (ecc_off) + spinand_disable_ecc(chip); + ret = spinand_read_pages(mtd, from, ops, &max_bitflips); + if (ecc_off) + spinand_enable_ecc(chip); + if (ret) + goto out; + + if (mtd->ecc_stats.failed - stats.failed) { + ret = -EBADMSG; + goto out; + } + ret = max_bitflips; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +/* + * spinand_write_pages - write data from buffer to device + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operations description structure + */ +static int spinand_write_pages(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + int ret = 0; + int size = 0; + int oob_size = 0; + int ooblen = ops->mode == MTD_OPS_AUTO_OOB ? + mtd->oobavail : mtd->oobsize; + bool oob_only = !ops->datbuf; + struct nand_page_iter iter; + + ops->retlen = 0; + ops->oobretlen = 0; + + nand_for_each_page(nand, to, ops->len, ops->ooboffs, ops->ooblen, + ooblen, &iter) { + memset(chip->buf, 0xff, + nand_page_size(nand) + nand_per_page_oobsize(nand)); + if (ops->oobbuf) { + oob_size = min_t(int, iter.oobleft, ooblen); + ret = spinand_fill_oob(chip, + ops->oobbuf + ops->oobretlen, + oob_size, ops); + if (ret) { + dev_err(chip->dev, "Fill oob error %d\n", ret); + return ret; + } + } + if (ops->datbuf) { + size = min_t(int, iter.dataleft, + nand_page_size(nand) - iter.pageoffs); + memcpy(chip->buf + iter.pageoffs, + ops->datbuf + ops->retlen, size); + } + ret = spinand_do_write_page(mtd, iter.page, oob_only); + if (ret) { + dev_err(chip->dev, "error %d writing page 0x%x\n", + ret, iter.page); + return ret; + } + if (ops->datbuf) + ops->retlen += size; + if (ops->oobbuf) + ops->oobretlen += oob_size; + } + + return ret; +} + +/* + * spinand_do_write_ops - write data from buffer to device + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operations description structure + */ +static int spinand_do_write_ops(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + int ret = 0; + bool ecc_off = ops->mode == MTD_OPS_RAW; + + ret = nand_check_address(nand, to); + if (ret) { + dev_err(chip->dev, "%s: invalid write address\n", __func__); + return ret; + } + ret = nand_check_oob_ops(nand, to, ops); + if (ret) { + dev_err(chip->dev, + "%s: invalid oob operation input\n", __func__); + return ret; + } + if (nand_oob_ops_across_page(mtd_to_nand(mtd), ops)) { + dev_err(chip->dev, + "%s: try to across page when writing with OOB\n", + __func__); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (ecc_off) + spinand_disable_ecc(chip); + ret = spinand_write_pages(mtd, to, ops); + if (ecc_off) + spinand_enable_ecc(chip); + mutex_unlock(&chip->lock); + + return ret; +} + +/* + * spinand_read - [MTD Interface] read page data + * @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 + */ +static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u8 *buf) +{ + struct mtd_oob_ops ops; + int ret; + + memset(&ops, 0, sizeof(ops)); + ops.len = len; + ops.datbuf = buf; + ops.mode = MTD_OPS_PLACE_OOB; + ret = spinand_do_read_ops(mtd, from, &ops); + *retlen = ops.retlen; + + return ret; +} + +/* + * spinand_write - [MTD Interface] write page data + * @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 + */ +static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u8 *buf) +{ + struct mtd_oob_ops ops; + int ret; + + memset(&ops, 0, sizeof(ops)); + ops.len = len; + ops.datbuf = (uint8_t *)buf; + ops.mode = MTD_OPS_PLACE_OOB; + ret = spinand_do_write_ops(mtd, to, &ops); + *retlen = ops.retlen; + + return ret; +} + +/* + * spinand_read_oob - [MTD Interface] read page data and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + */ +static int spinand_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + int ret = -ENOTSUPP; + + ops->retlen = 0; + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + case MTD_OPS_RAW: + ret = spinand_do_read_ops(mtd, from, ops); + break; + } + + return ret; +} + +/* + * spinand_write_oob - [MTD Interface] write page data and/or out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + */ +static int spinand_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + int ret = -ENOTSUPP; + + ops->retlen = 0; + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + case MTD_OPS_RAW: + ret = spinand_do_write_ops(mtd, to, ops); + break; + } + + return ret; +} + +/* + * spinand_erase - [MTD Interface] erase block(s) + * @mtd: MTD device structure + * @einfo: erase instruction + */ +static int spinand_erase(struct mtd_info *mtd, struct erase_info *einfo) +{ + struct spinand_device *chip = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nand(mtd); + loff_t offs = einfo->addr, len = einfo->len; + u8 status; + int ret; + + ret = nand_check_erase_ops(nand, einfo); + if (ret) { + dev_err(chip->dev, "invalid erase operation input\n"); + return ret; + } + + mutex_lock(&chip->lock); + einfo->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + einfo->state = MTD_ERASING; + + while (len) { + spinand_write_enable(chip); + spinand_erase_block(chip, nand_offs_to_page(nand, offs)); + ret = spinand_wait(chip, &status); + if (ret < 0) { + dev_err(chip->dev, "block erase command wait failed\n"); + einfo->state = MTD_ERASE_FAILED; + goto erase_exit; + } + if ((status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL) { + dev_err(chip->dev, "erase block 0x%012llx failed\n", offs); + einfo->state = MTD_ERASE_FAILED; + einfo->fail_addr = offs; + goto erase_exit; + } + + /* Increment page address and decrement length */ + len -= nand_eraseblock_size(nand); + offs += nand_eraseblock_size(nand); + } + + einfo->state = MTD_ERASE_DONE; + +erase_exit: + + ret = einfo->state == MTD_ERASE_DONE ? 0 : -EIO; + + mutex_unlock(&chip->lock); + + /* Do call back function */ + if (!ret) + mtd_erase_callback(einfo); + + return ret; +} + +/* * spinand_set_rd_wr_op - choose the best read write command * @chip: SPI NAND device structure * Description: @@ -364,6 +1109,11 @@ static int spinand_init(struct spinand_device *chip) if (ret < 0) ret = 0; mtd->oobavail = ret; + mtd->_erase = spinand_erase; + mtd->_read = spinand_read; + mtd->_write = spinand_write; + mtd->_read_oob = spinand_read_oob; + mtd->_write_oob = spinand_write_oob; if (!mtd->bitflip_threshold) mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 78ea9a6..5c8bb70 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -101,6 +101,8 @@ struct spinand_manufacturer_ops { bool (*detect)(struct spinand_device *chip); int (*init)(struct spinand_device *chip); void (*cleanup)(struct spinand_device *chip); + void (*prepare_op)(struct spinand_device *chip, struct spinand_op *op, + u32 page, u32 column); }; struct spinand_manufacturer {