From patchwork Thu Jan 30 13:41:35 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Boris BREZILLON X-Patchwork-Id: 3557021 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id A9022C02DC for ; Thu, 30 Jan 2014 13:42:19 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 694602017A for ; Thu, 30 Jan 2014 13:42:18 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C1C8420173 for ; Thu, 30 Jan 2014 13:42:16 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8rsi-0000Mu-UO; Thu, 30 Jan 2014 13:42:13 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8rsg-0006eb-8m; Thu, 30 Jan 2014 13:42:10 +0000 Received: from mail-ee0-x22c.google.com ([2a00:1450:4013:c00::22c]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8rsc-0006cs-9i; Thu, 30 Jan 2014 13:42:07 +0000 Received: by mail-ee0-f44.google.com with SMTP id c13so1627048eek.3 for ; Thu, 30 Jan 2014 05:41:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=brF6m4dY+2DRoUHgzmsKMpPW31aXxK+2O2NOe67rCbI=; b=yP693H7lMyT5berzC5GSIg+X8KifqkZYyzShWqis/ImwVmsE36P4tGbqdKn1NU/Kax WxiBlvM7wb+foBbej/TJP8c/ojEBWVo3Ii3ghkJvwZ/LD6X/BnOGSUNZk1y1XjUekNxZ OCR+QhH6eqH91hmhcYPVsSdclz88Fo5IqgNKjxzJXKaIQGXV6aNZBFO8HxpeEwBLDOuE 7RcbnmWvpoqxyTZaqT/cHF0AJ/LjDWN/XcF3/9IDef9DU+gp/R9YIjyxakZcdUQITxpU wmM9C7Kaet9/8M1SwO/RgvMEb+MPMHATiqgMyggz1qsYzFKBHsfg4/qzAPyyFHBOrlmI t2IA== X-Received: by 10.14.106.193 with SMTP id m41mr10839499eeg.62.1391089304397; Thu, 30 Jan 2014 05:41:44 -0800 (PST) Received: from bbrezillon-laptop.int.overkiz.com ([80.245.18.66]) by mx.google.com with ESMTPSA id k6sm22654471eep.17.2014.01.30.05.41.40 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Thu, 30 Jan 2014 05:41:43 -0800 (PST) From: Boris BREZILLON To: Maxime Ripard , Rob Landley , Russell King , David Woodhouse , Grant Likely , Brian Norris , Jason Gunthorpe , Arnd Bergmann Subject: [RFC PATCH pre-v3 13/14] mtd: nand: add sunxi HW ECC support Date: Thu, 30 Jan 2014 14:41:35 +0100 Message-Id: <1391089295-8227-1-git-send-email-b.brezillon.dev@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1391006064-28890-1-git-send-email-b.brezillon.dev@gmail.com> References: <1391006064-28890-1-git-send-email-b.brezillon.dev@gmail.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140130_084206_600867_1D021E75 X-CRM114-Status: GOOD ( 21.89 ) X-Spam-Score: -2.0 (--) Cc: devicetree@vger.kernel.org, Boris BREZILLON , linux-doc@vger.kernel.org, dev@linux-sunxi.org, linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.5 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Add HW ECC support for the sunxi NAND Flash Controller. Signed-off-by: Boris BREZILLON --- drivers/mtd/nand/sunxi_nand.c | 279 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 266 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/nand/sunxi_nand.c b/drivers/mtd/nand/sunxi_nand.c index 1014b2a..b90268f 100644 --- a/drivers/mtd/nand/sunxi_nand.c +++ b/drivers/mtd/nand/sunxi_nand.c @@ -163,6 +163,11 @@ struct sunxi_nand_chip_sel { #define DEFAULT_NAME_FORMAT "nand@%d" #define MAX_NAME_SIZE (sizeof("nand@") + 2) +struct sunxi_nand_hw_ecc { + int mode; + struct nand_ecclayout layout; +}; + struct sunxi_nand_chip { struct list_head node; struct nand_chip nand; @@ -402,6 +407,126 @@ static void sunxi_nfc_cmd_ctrl(struct mtd_info *mtd, int dat, sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0); } +static int sunxi_nfc_hwecc_read_page(struct mtd_info *mtd, + struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct nand_ecclayout *layout = ecc->layout; + struct sunxi_nand_hw_ecc *data = ecc->priv; + unsigned int max_bitflips = 0; + int offset; + u32 tmp; + int i; + + tmp = readl(nfc->regs + NFC_REG_ECC_CTL); + tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE | + NFC_ECC_BLOCK_SIZE); + tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT); + writel(tmp, nfc->regs + NFC_REG_ECC_CTL); + + for (i = 0; i < mtd->writesize / ecc->size; i++) { + if (i) + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, i * ecc->size, -1); + chip->read_buf(mtd, NULL, chip->ecc.size); + offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4; + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1); + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS)) + ; + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30); + writel(tmp, nfc->regs + NFC_REG_CMD); + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0); + memcpy_fromio(buf + (i * ecc->size), nfc->regs + NFC_RAM0_BASE, + chip->ecc.size); + + if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) { + mtd->ecc_stats.failed++; + } else { + tmp = readl(nfc->regs + NFC_REG_ECC_CNT0) & 0xff; + mtd->ecc_stats.corrected += tmp; + max_bitflips = max_t(unsigned int, max_bitflips, tmp); + } + } + + if (oob_required) { + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + } + + tmp = readl(nfc->regs + NFC_REG_ECC_CTL); + tmp &= ~NFC_ECC_EN; + + writel(tmp, nfc->regs + NFC_REG_ECC_CTL); + + return max_bitflips; +} + +static int sunxi_nfc_hwecc_write_page(struct mtd_info *mtd, + struct nand_chip *chip, + const uint8_t *buf, + int oob_required) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(mtd); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct nand_ecclayout *layout = ecc->layout; + struct sunxi_nand_hw_ecc *data = ecc->priv; + int offset; + u32 tmp; + int i; + int j; + + tmp = readl(nfc->regs + NFC_REG_ECC_CTL); + tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE | + NFC_ECC_BLOCK_SIZE); + tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT); + + writel(tmp, nfc->regs + NFC_REG_ECC_CTL); + + for (i = 0; i < mtd->writesize / ecc->size; i++) { + if (i) + chip->cmdfunc(mtd, NAND_CMD_RNDIN, i * ecc->size, -1); + + chip->write_buf(mtd, buf + (i * ecc->size), ecc->size); + offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4; + chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1); + while ((readl(nfc->regs + NFC_REG_ST) & NFC_CMD_FIFO_STATUS)) + ; + + /* Fill OOB data in */ + for (j = 0; j < 4; j++) { + if (oob_required) { + offset = layout->eccpos[i * ecc->size] - 4; + writeb(chip->oob_poi[offset + j], + nfc->regs + NFC_REG_USER_DATA_BASE + j); + } else { + writeb(0xff, + nfc->regs + NFC_REG_USER_DATA_BASE + j); + } + } + + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | + NFC_ACCESS_DIR | (1 << 30); + writel(tmp, nfc->regs + NFC_REG_CMD); + sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0); + } + + if (oob_required && chip->ecc.layout->oobfree[0].length > 2) { + chip->cmdfunc(mtd, NAND_CMD_RNDIN, mtd->writesize, -1); + chip->write_buf(mtd, chip->oob_poi, + chip->ecc.layout->oobfree[0].length - 2); + } + + tmp = readl(nfc->regs + NFC_REG_ECC_CTL); + tmp &= ~(NFC_ECC_EN | NFC_ECC_PIPELINE); + + writel(tmp, nfc->regs + NFC_REG_ECC_CTL); + + return 0; +} + static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip, const struct nand_sdr_timings *timings) { @@ -504,6 +629,144 @@ static int sunxi_nand_chip_init_timings(struct sunxi_nand_chip *chip, return sunxi_nand_chip_set_timings(chip, timings); } +static int sunxi_nand_chip_hwecc_init(struct device *dev, + struct sunxi_nand_chip *chip, + struct mtd_info *mtd, + struct device_node *np) +{ + struct nand_chip *nand = &chip->nand; + struct nand_ecc_ctrl *ecc = &nand->ecc; + struct sunxi_nand_hw_ecc *data; + struct nand_ecclayout *layout; + int nsectors; + int i; + int j; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ecc->read_page = sunxi_nfc_hwecc_read_page; + ecc->write_page = sunxi_nfc_hwecc_write_page; + + if (nand->ecc_strength_ds <= 16) { + nand->ecc_strength_ds = 16; + data->mode = 0; + } else if (nand->ecc_strength_ds <= 24) { + nand->ecc_strength_ds = 24; + data->mode = 1; + } else if (nand->ecc_strength_ds <= 28) { + nand->ecc_strength_ds = 28; + data->mode = 2; + } else if (nand->ecc_strength_ds <= 32) { + nand->ecc_strength_ds = 32; + data->mode = 3; + } else if (nand->ecc_strength_ds <= 40) { + nand->ecc_strength_ds = 40; + data->mode = 4; + } else if (nand->ecc_strength_ds <= 48) { + nand->ecc_strength_ds = 48; + data->mode = 5; + } else if (nand->ecc_strength_ds <= 56) { + nand->ecc_strength_ds = 56; + data->mode = 6; + } else if (nand->ecc_strength_ds <= 60) { + nand->ecc_strength_ds = 60; + data->mode = 7; + } else if (nand->ecc_strength_ds <= 64) { + nand->ecc_strength_ds = 64; + data->mode = 8; + } else { + dev_err(dev, "unsupported strength\n"); + return -ENOTSUPP; + } + + /* HW ECC always request ECC bytes for 1024 bytes blocks */ + ecc->bytes = ((nand->ecc_strength_ds * fls(8 * 1024)) + 7) / 8; + + /* HW ECC always work with even numbers of ECC bytes */ + if (ecc->bytes % 2) + ecc->bytes++; + ecc->strength = nand->ecc_strength_ds; + ecc->size = nand->ecc_step_ds; + + layout = &data->layout; + nsectors = mtd->writesize / ecc->size; + + if (mtd->oobsize < ((ecc->bytes + 4) * nsectors)) + return -EINVAL; + + layout->eccbytes = (ecc->bytes * nsectors); + + /* + * The first 2 bytes are used for BB markers. + * We merge the 4 user available bytes from HW ECC with this + * first section, hence why the + 2 operation (- 2 + 4). + */ + layout->oobfree[0].length = mtd->oobsize + 2 - + ((ecc->bytes + 4) * nsectors); + layout->oobfree[0].offset = 2; + for (i = 0; i < nsectors; i++) { + /* + * The first 4 ECC block bytes are already counted in the first + * obbfree entry. + */ + if (i) { + layout->oobfree[i].offset = + layout->oobfree[i - 1].offset + + layout->oobfree[i - 1].length + + ecc->bytes; + layout->oobfree[i].length = 4; + } + + for (j = 0; j < ecc->bytes; j++) + layout->eccpos[(ecc->bytes * i) + j] = + layout->oobfree[i].offset + + layout->oobfree[i].length + j; + } + + ecc->layout = layout; + ecc->priv = data; + + return 0; +} + +static int sunxi_nand_chip_ecc_init(struct device *dev, + struct sunxi_nand_chip *chip, + struct mtd_info *mtd, + struct device_node *np) +{ + struct nand_chip *nand = &chip->nand; + u32 strength; + u32 blk_size; + int ret; + + nand->ecc.mode = of_get_nand_ecc_mode(np); + + if (!of_get_nand_ecc_level(np, &strength, &blk_size)) { + nand->ecc_step_ds = blk_size; + nand->ecc_strength_ds = strength; + } + + switch (nand->ecc.mode) { + case NAND_ECC_SOFT_BCH: + nand->ecc.size = nand->ecc_step_ds; + nand->ecc.bytes = ((nand->ecc_strength_ds * + fls(8 * nand->ecc_step_ds)) + 7) / 8; + break; + case NAND_ECC_HW: + ret = sunxi_nand_chip_hwecc_init(dev, chip, mtd, np); + if (ret) + return ret; + break; + case NAND_ECC_NONE: + default: + break; + } + + return 0; +} + static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, struct device_node *np) { @@ -512,8 +775,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, struct mtd_part_parser_data ppdata; struct mtd_info *mtd; struct nand_chip *nand; - u32 strength; - u32 blk_size; int nsels; int ret; int i; @@ -586,7 +847,6 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, nand->write_buf = sunxi_nfc_write_buf; nand->read_byte = sunxi_nfc_read_byte; - nand->ecc.mode = of_get_nand_ecc_mode(np); if (of_get_nand_on_flash_bbt(np)) nand->bbt_options |= NAND_BBT_USE_FLASH; @@ -602,16 +862,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, if (ret) return ret; - if (nand->ecc.mode == NAND_ECC_SOFT_BCH) { - if (!of_get_nand_ecc_level(np, &strength, &blk_size)) { - nand->ecc_step_ds = blk_size; - nand->ecc_strength_ds = strength; - } - - nand->ecc.size = nand->ecc_step_ds; - nand->ecc.bytes = (((nand->ecc_strength_ds * - fls(8 * nand->ecc_step_ds)) + 7) / 8); - } + ret = sunxi_nand_chip_ecc_init(dev, chip, mtd, np); + if (ret) + return ret; ret = nand_scan_tail(mtd); if (ret)