From patchwork Fri Mar 13 09:38:54 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Agner X-Patchwork-Id: 6003551 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 085EA9F318 for ; Fri, 13 Mar 2015 09:43:55 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id CE570202AE for ; Fri, 13 Mar 2015 09:43:53 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (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 AC9E320266 for ; Fri, 13 Mar 2015 09:43:52 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YWM5b-0007kA-Ab; Fri, 13 Mar 2015 09:41:07 +0000 Received: from mail.kmu-office.ch ([178.209.48.109]) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YWM42-0005jS-0R; Fri, 13 Mar 2015 09:39:36 +0000 Received: from trochilidae.toradex.int (unknown [46.140.72.82]) by mail.kmu-office.ch (Postfix) with ESMTPSA id B19475C0CE5; Fri, 13 Mar 2015 10:38:49 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=agner.ch; s=dkim; t=1426239530; bh=Fo/v9kIzzQHEQ8T37XkmZ8/+TjYnHPpf8Ez8EED5/jU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QXqQznKn0Tu8GtbdyUX0CApgK+2V28KaLszr9E0bDqI11zGdoq+VNIeFzRIIycpal c2yQkvpgCT34Rx14WFm2Xr5G73kHgKzXQYTykktdnYByI5rtPku9lwY5n/VO72Xa42 Y5j3O00BO9hD0x3h1pl6wc/0gFVu1pkGYLFz756c= From: Stefan Agner To: dwmw2@infradead.org, computersforpeace@gmail.com Subject: [PATCH v3 2/6] mtd: nand: vf610_nfc: add hardware BCH-ECC support Date: Fri, 13 Mar 2015 10:38:54 +0100 Message-Id: <1426239538-11529-3-git-send-email-stefan@agner.ch> X-Mailer: git-send-email 2.3.1 In-Reply-To: <1426239538-11529-1-git-send-email-stefan@agner.ch> References: <1426239538-11529-1-git-send-email-stefan@agner.ch> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150313_023930_458211_C7EFFC05 X-CRM114-Status: GOOD ( 21.98 ) X-Spam-Score: -0.1 (/) Cc: mark.rutland@arm.com, boris.brezillon@free-electrons.com, aaron@tastycactus.com, marb@ixxat.de, pawel.moll@arm.com, ijc+devicetree@hellion.org.uk, linux-kernel@vger.kernel.org, Stefan Agner , devicetree@vger.kernel.org, robh+dt@kernel.org, linux-mtd@lists.infradead.org, kernel@pengutronix.de, galak@codeaurora.org, shawn.guo@linaro.org, linux-arm-kernel@lists.infradead.org, bpringlemeir@nbsps.com X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 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.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, T_RP_MATCHES_RCVD, 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 This adds hardware ECC support using the BCH encoder in the NFC IP. The ECC encoder supports up to 32-bit correction by using 60 error correction bytes. There is no sub-page ECC step, ECC is calculated always accross the whole page (up to 2k pages). Signed-off-by: Bill Pringlemeir Signed-off-by: Stefan Agner --- drivers/mtd/nand/Kconfig | 6 +- drivers/mtd/nand/vf610_nfc.c | 193 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 172 insertions(+), 27 deletions(-) diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 1be30a6..0fb97b3 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -464,8 +464,10 @@ config MTD_NAND_VF610_NFC help Enables support for NAND Flash Controller on some Freescale processors like the VF610, MPC5125, MCF54418 or Kinetis K70. - The driver supports a maximum 2k page size. The driver - currently does not support hardware ECC. + The driver supports a maximum 2k page size. With 2k pages and + 64 bytes or more of OOB, hardware ECC with up to 32-bit error + correction is supported. Hardware ECC is only enabled through + device tree. config MTD_NAND_MXC tristate "MXC NAND support" diff --git a/drivers/mtd/nand/vf610_nfc.c b/drivers/mtd/nand/vf610_nfc.c index 5c8be4a..3540168 100644 --- a/drivers/mtd/nand/vf610_nfc.c +++ b/drivers/mtd/nand/vf610_nfc.c @@ -19,6 +19,7 @@ * - Untested on MPC5125 and M54418. * - DMA not used. * - 2K pages or less. + * - Only 2K page w. 64+OOB and hardware ECC. */ #include @@ -71,6 +72,8 @@ /* NFC ECC mode define */ #define ECC_BYPASS 0 +#define ECC_45_BYTE 6 +#define ECC_60_BYTE 7 /*** Register Mask and bit definitions */ @@ -103,6 +106,8 @@ #define STATUS_BYTE1_MASK 0x000000FF /* NFC_FLASH_CONFIG Field */ +#define CONFIG_ECC_SRAM_ADDR_MASK 0x7FC00000 +#define CONFIG_ECC_SRAM_ADDR_SHIFT 22 #define CONFIG_ECC_SRAM_REQ_BIT (1<<21) #define CONFIG_DMA_REQ_BIT (1<<20) #define CONFIG_ECC_MODE_MASK 0x000E0000 @@ -123,12 +128,31 @@ #define NFC_TIMEOUT (HZ) +/* ECC status placed at end of buffers. */ +#define ECC_SRAM_ADDR ((PAGE_2K+256-8) >> 3) +#define ECC_STATUS_MASK 0x80 +#define ECC_ERR_COUNT 0x3F + +/* + * ECC status is stored at NFC_CFG[ECCADD] +4 for little-endian + * and +7 for big-endian SoCs. + */ +#ifdef CONFIG_SOC_VF610 +#define ECC_OFFSET 4 +#else +#define ECC_OFFSET 7 +#endif + struct vf610_nfc_config { + bool hardware_ecc; + int ecc_strength; + int ecc_step_size; int width; int flash_bbt; + u32 ecc_mode; }; -struct fsl_nfc { +struct vf610_nfc { struct mtd_info mtd; struct nand_chip chip; struct device *dev; @@ -148,27 +172,32 @@ struct fsl_nfc { #define mtd_to_nfc(_mtd) container_of(_mtd, struct vf610_nfc, mtd) -static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; -static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; - -static struct nand_bbt_descr bbt_main_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | - NAND_BBT_2BIT | NAND_BBT_VERSION, - .offs = 11, - .len = 4, - .veroffs = 15, - .maxblocks = 4, - .pattern = bbt_pattern, +static struct nand_ecclayout vf610_nfc_ecc45 = { + .eccbytes = 45, + .eccpos = {19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}, + .oobfree = { + {.offset = 2, + .length = 17} } }; -static struct nand_bbt_descr bbt_mirror_descr = { - .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | - NAND_BBT_2BIT | NAND_BBT_VERSION, - .offs = 11, - .len = 4, - .veroffs = 15, - .maxblocks = 4, - .pattern = mirror_pattern, +static struct nand_ecclayout vf610_nfc_ecc60 = { + .eccbytes = 60, + .eccpos = { 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63 }, + .oobfree = { + {.offset = 2, + .length = 2} } }; static inline u32 vf610_nfc_read(struct vf610_nfc *nfc, uint reg) @@ -469,6 +498,60 @@ static void vf610_nfc_select_chip(struct mtd_info *mtd, int chip) #endif } +/* Count the number of 0's in buff up to max_bits */ +static inline int count_written_bits(uint8_t *buff, int size, int max_bits) +{ + uint32_t *buff32 = (uint32_t *)buff; + int k, written_bits = 0; + + for (k = 0; k < (size / 4); k++) { + written_bits += hweight32(~buff32[k]); + if (written_bits > max_bits) + break; + } + + return written_bits; +} + +static inline int vf610_nfc_correct_data(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct vf610_nfc *nfc = mtd_to_nfc(mtd); + u8 ecc_status; + u8 ecc_count; + int flip; + + ecc_status = __raw_readb(nfc->regs + ECC_SRAM_ADDR * 8 + ECC_OFFSET); + ecc_count = ecc_status & ECC_ERR_COUNT; + if (!(ecc_status & ECC_STATUS_MASK)) + return ecc_count; + + /* + * On an erased page, bit count should be zero or at least + * less then half of the ECC strength + */ + flip = count_written_bits(dat, nfc->chip.ecc.size, ecc_count); + + if (flip > ecc_count && flip > (nfc->chip.ecc.strength / 2)) { + nfc->page = -1; + return -1; + } + + /* Erased page. */ + memset(dat, 0xff, nfc->chip.ecc.size); + return 0; +} + +static int vf610_nfc_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + return 0; +} + +static void vf610_nfc_enable_hwecc(struct mtd_info *mtd, int mode) +{ +} + #ifdef CONFIG_OF_MTD static const struct of_device_id vf610_nfc_dt_ids[] = { { .compatible = "fsl,vf610-nfc" }, @@ -485,7 +568,17 @@ static int vf610_nfc_probe_dt(struct device *dev, struct vf610_nfc_config *cfg) if (!np) return 1; + if (of_get_nand_ecc_mode(np) >= NAND_ECC_HW) + cfg->hardware_ecc = 1; + cfg->flash_bbt = of_get_nand_on_flash_bbt(np); + cfg->ecc_strength = of_get_nand_ecc_strength(np); + if (cfg->ecc_strength < 0) + cfg->ecc_strength = 0; + + cfg->ecc_step_size = of_get_nand_ecc_step_size(np); + if (cfg->ecc_step_size < 0) + cfg->ecc_step_size = 0; buswidth = of_get_nand_bus_width(np); if (buswidth < 0) @@ -529,6 +622,15 @@ static int vf610_nfc_init_controller(struct vf610_nfc *nfc, int page_sz) vf610_nfc_write(nfc, NFC_SECTOR_SIZE, page_sz); + if (cfg->hardware_ecc) { + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_MODE_MASK, + CONFIG_ECC_MODE_SHIFT, cfg->ecc_mode); + + /* Enable ECC_STATUS */ + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_ECC_SRAM_REQ_BIT); + } + return 0; } @@ -597,10 +699,8 @@ static int vf610_nfc_probe(struct platform_device *pdev) /* Bad block options. */ if (cfg->flash_bbt) - chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_CREATE; - - chip->bbt_td = &bbt_main_descr; - chip->bbt_md = &bbt_mirror_descr; + chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB | + NAND_BBT_CREATE; init_waitqueue_head(&nfc->irq_waitq); @@ -627,13 +727,56 @@ static int vf610_nfc_probe(struct platform_device *pdev) /* Single buffer only, max 256 OOB minus ECC status */ if (page_sz > PAGE_2K + 256 - 8) { - dev_err(nfc->dev, "Unsupported flash size\n"); + dev_err(nfc->dev, "Unsupported flash page size\n"); err = -ENXIO; goto error; } page_sz += cfg->width == 16 ? 1 : 0; vf610_nfc_write(nfc, NFC_SECTOR_SIZE, page_sz); + if (cfg->hardware_ecc) { + if (mtd->writesize != PAGE_2K && mtd->oobsize < 64) { + dev_err(nfc->dev, "Unsupported flash with hwecc\n"); + err = -ENXIO; + goto error; + } + + if (cfg->ecc_step_size != mtd->writesize) { + dev_err(nfc->dev, "Step size needs to be page size\n"); + err = -ENXIO; + goto error; + } + + if (cfg->ecc_strength == 32) { + cfg->ecc_mode = ECC_60_BYTE; + chip->ecc.bytes = 60; + chip->ecc.layout = &vf610_nfc_ecc60; + } else if (cfg->ecc_strength == 24) { + cfg->ecc_mode = ECC_45_BYTE; + chip->ecc.bytes = 45; + chip->ecc.layout = &vf610_nfc_ecc45; + } else { + dev_err(nfc->dev, "Unsupported ECC strength\n"); + err = -ENXIO; + goto error; + } + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = chip->ecc.layout; + chip->ecc.calculate = vf610_nfc_calculate_ecc; + chip->ecc.hwctl = vf610_nfc_enable_hwecc; + chip->ecc.correct = vf610_nfc_correct_data; + chip->ecc.mode = NAND_ECC_HW; + + chip->ecc.size = PAGE_2K; + chip->ecc.strength = cfg->ecc_strength; + + /* set ECC mode according to required ECC strength */ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_MODE_MASK, + CONFIG_ECC_MODE_SHIFT, cfg->ecc_mode); + } + /* second phase scan */ if (nand_scan_tail(mtd)) { err = -ENXIO;