|
@@ -0,0 +1,1403 @@
|
|
|
+From e84e2430ee0e483842b4ff013ae8a6e7e2fa2734 Mon Sep 17 00:00:00 2001
|
|
|
+From: Weijie Gao <[email protected]>
|
|
|
+Date: Wed, 1 Apr 2020 02:07:58 +0800
|
|
|
+Subject: [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand
|
|
|
+ flash controller
|
|
|
+
|
|
|
+This patch adds NAND flash controller driver for MediaTek MT7621 SoC.
|
|
|
+
|
|
|
+The NAND flash controller is similar with controllers described in
|
|
|
+mtk_nand.c, except that the controller from MT7621 doesn't support DMA
|
|
|
+transmission, and some registers' offset and fields are different.
|
|
|
+
|
|
|
+Signed-off-by: Weijie Gao <[email protected]>
|
|
|
+---
|
|
|
+ drivers/mtd/nand/raw/Kconfig | 8 +
|
|
|
+ drivers/mtd/nand/raw/Makefile | 1 +
|
|
|
+ drivers/mtd/nand/raw/mt7621_nand.c | 1348 ++++++++++++++++++++++++++++++++++++
|
|
|
+ 3 files changed, 1357 insertions(+)
|
|
|
+ create mode 100644 drivers/mtd/nand/raw/mt7621_nand.c
|
|
|
+
|
|
|
+--- a/drivers/mtd/nand/raw/Kconfig
|
|
|
++++ b/drivers/mtd/nand/raw/Kconfig
|
|
|
+@@ -387,6 +387,14 @@ config MTD_NAND_QCOM
|
|
|
+ Enables support for NAND flash chips on SoCs containing the EBI2 NAND
|
|
|
+ controller. This controller is found on IPQ806x SoC.
|
|
|
+
|
|
|
++config MTD_NAND_MT7621
|
|
|
++ tristate "MT7621 NAND controller"
|
|
|
++ depends on SOC_MT7621 || COMPILE_TEST
|
|
|
++ depends on HAS_IOMEM
|
|
|
++ help
|
|
|
++ Enables support for NAND controller on MT7621 SoC.
|
|
|
++ This driver uses PIO mode for data transmission instead of DMA mode.
|
|
|
++
|
|
|
+ config MTD_NAND_MTK
|
|
|
+ tristate "MTK NAND controller"
|
|
|
+ depends on ARCH_MEDIATEK || COMPILE_TEST
|
|
|
+--- a/drivers/mtd/nand/raw/Makefile
|
|
|
++++ b/drivers/mtd/nand/raw/Makefile
|
|
|
+@@ -51,6 +51,7 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_n
|
|
|
+ obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o
|
|
|
+ obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
|
|
|
+ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
|
|
|
++obj-$(CONFIG_MTD_NAND_MT7621) += mt7621_nand.o
|
|
|
+ obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o
|
|
|
+ obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o
|
|
|
+ obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
|
|
|
+--- /dev/null
|
|
|
++++ b/drivers/mtd/nand/raw/mt7621_nand.c
|
|
|
+@@ -0,0 +1,1353 @@
|
|
|
++// SPDX-License-Identifier: GPL-2.0
|
|
|
++/*
|
|
|
++ * MediaTek MT7621 NAND Flash Controller driver
|
|
|
++ *
|
|
|
++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
|
|
|
++ *
|
|
|
++ * Author: Weijie Gao <[email protected]>
|
|
|
++ */
|
|
|
++
|
|
|
++#include <linux/io.h>
|
|
|
++#include <linux/clk.h>
|
|
|
++#include <linux/init.h>
|
|
|
++#include <linux/errno.h>
|
|
|
++#include <linux/sizes.h>
|
|
|
++#include <linux/iopoll.h>
|
|
|
++#include <linux/kernel.h>
|
|
|
++#include <linux/module.h>
|
|
|
++#include <linux/mtd/mtd.h>
|
|
|
++#include <linux/mtd/rawnand.h>
|
|
|
++#include <linux/mtd/partitions.h>
|
|
|
++#include <linux/platform_device.h>
|
|
|
++#include <asm/addrspace.h>
|
|
|
++
|
|
|
++/* NFI core registers */
|
|
|
++#define NFI_CNFG 0x000
|
|
|
++#define CNFG_OP_MODE_S 12
|
|
|
++#define CNFG_OP_MODE_M GENMASK(14, 12)
|
|
|
++#define CNFG_OP_CUSTOM 6
|
|
|
++#define CNFG_AUTO_FMT_EN BIT(9)
|
|
|
++#define CNFG_HW_ECC_EN BIT(8)
|
|
|
++#define CNFG_BYTE_RW BIT(6)
|
|
|
++#define CNFG_READ_MODE BIT(1)
|
|
|
++
|
|
|
++#define NFI_PAGEFMT 0x004
|
|
|
++#define PAGEFMT_FDM_ECC_S 12
|
|
|
++#define PAGEFMT_FDM_ECC_M GENMASK(15, 12)
|
|
|
++#define PAGEFMT_FDM_S 8
|
|
|
++#define PAGEFMT_FDM_M GENMASK(11, 8)
|
|
|
++#define PAGEFMT_SPARE_S 4
|
|
|
++#define PAGEFMT_SPARE_M GENMASK(5, 4)
|
|
|
++#define PAGEFMT_PAGE_S 0
|
|
|
++#define PAGEFMT_PAGE_M GENMASK(1, 0)
|
|
|
++
|
|
|
++#define NFI_CON 0x008
|
|
|
++#define CON_NFI_SEC_S 12
|
|
|
++#define CON_NFI_SEC_M GENMASK(15, 12)
|
|
|
++#define CON_NFI_BWR BIT(9)
|
|
|
++#define CON_NFI_BRD BIT(8)
|
|
|
++#define CON_NFI_RST BIT(1)
|
|
|
++#define CON_FIFO_FLUSH BIT(0)
|
|
|
++
|
|
|
++#define NFI_ACCCON 0x00c
|
|
|
++#define ACCCON_POECS_S 28
|
|
|
++#define ACCCON_POECS_MAX 0x0f
|
|
|
++#define ACCCON_POECS_DEF 3
|
|
|
++#define ACCCON_PRECS_S 22
|
|
|
++#define ACCCON_PRECS_MAX 0x3f
|
|
|
++#define ACCCON_PRECS_DEF 3
|
|
|
++#define ACCCON_C2R_S 16
|
|
|
++#define ACCCON_C2R_MAX 0x3f
|
|
|
++#define ACCCON_C2R_DEF 7
|
|
|
++#define ACCCON_W2R_S 12
|
|
|
++#define ACCCON_W2R_MAX 0x0f
|
|
|
++#define ACCCON_W2R_DEF 7
|
|
|
++#define ACCCON_WH_S 8
|
|
|
++#define ACCCON_WH_MAX 0x0f
|
|
|
++#define ACCCON_WH_DEF 15
|
|
|
++#define ACCCON_WST_S 4
|
|
|
++#define ACCCON_WST_MAX 0x0f
|
|
|
++#define ACCCON_WST_DEF 15
|
|
|
++#define ACCCON_WST_MIN 3
|
|
|
++#define ACCCON_RLT_S 0
|
|
|
++#define ACCCON_RLT_MAX 0x0f
|
|
|
++#define ACCCON_RLT_DEF 15
|
|
|
++#define ACCCON_RLT_MIN 3
|
|
|
++
|
|
|
++#define NFI_CMD 0x020
|
|
|
++
|
|
|
++#define NFI_ADDRNOB 0x030
|
|
|
++#define ADDR_ROW_NOB_S 4
|
|
|
++#define ADDR_ROW_NOB_M GENMASK(6, 4)
|
|
|
++#define ADDR_COL_NOB_S 0
|
|
|
++#define ADDR_COL_NOB_M GENMASK(2, 0)
|
|
|
++
|
|
|
++#define NFI_COLADDR 0x034
|
|
|
++#define NFI_ROWADDR 0x038
|
|
|
++
|
|
|
++#define NFI_STRDATA 0x040
|
|
|
++#define STR_DATA BIT(0)
|
|
|
++
|
|
|
++#define NFI_CNRNB 0x044
|
|
|
++#define CB2R_TIME_S 4
|
|
|
++#define CB2R_TIME_M GENMASK(7, 4)
|
|
|
++#define STR_CNRNB BIT(0)
|
|
|
++
|
|
|
++#define NFI_DATAW 0x050
|
|
|
++#define NFI_DATAR 0x054
|
|
|
++
|
|
|
++#define NFI_PIO_DIRDY 0x058
|
|
|
++#define PIO_DIRDY BIT(0)
|
|
|
++
|
|
|
++#define NFI_STA 0x060
|
|
|
++#define STA_NFI_FSM_S 16
|
|
|
++#define STA_NFI_FSM_M GENMASK(19, 16)
|
|
|
++#define STA_FSM_CUSTOM_DATA 14
|
|
|
++#define STA_BUSY BIT(8)
|
|
|
++#define STA_ADDR BIT(1)
|
|
|
++#define STA_CMD BIT(0)
|
|
|
++
|
|
|
++#define NFI_ADDRCNTR 0x070
|
|
|
++#define SEC_CNTR_S 12
|
|
|
++#define SEC_CNTR_M GENMASK(15, 12)
|
|
|
++#define SEC_ADDR_S 0
|
|
|
++#define SEC_ADDR_M GENMASK(9, 0)
|
|
|
++
|
|
|
++#define NFI_CSEL 0x090
|
|
|
++#define CSEL_S 0
|
|
|
++#define CSEL_M GENMASK(1, 0)
|
|
|
++
|
|
|
++#define NFI_FDM0L 0x0a0
|
|
|
++#define NFI_FDML(n) (0x0a0 + ((n) << 3))
|
|
|
++
|
|
|
++#define NFI_FDM0M 0x0a4
|
|
|
++#define NFI_FDMM(n) (0x0a4 + ((n) << 3))
|
|
|
++
|
|
|
++#define NFI_MASTER_STA 0x210
|
|
|
++#define MAS_ADDR GENMASK(11, 9)
|
|
|
++#define MAS_RD GENMASK(8, 6)
|
|
|
++#define MAS_WR GENMASK(5, 3)
|
|
|
++#define MAS_RDDLY GENMASK(2, 0)
|
|
|
++
|
|
|
++/* ECC engine registers */
|
|
|
++#define ECC_ENCCON 0x000
|
|
|
++#define ENC_EN BIT(0)
|
|
|
++
|
|
|
++#define ECC_ENCCNFG 0x004
|
|
|
++#define ENC_CNFG_MSG_S 16
|
|
|
++#define ENC_CNFG_MSG_M GENMASK(28, 16)
|
|
|
++#define ENC_MODE_S 4
|
|
|
++#define ENC_MODE_M GENMASK(5, 4)
|
|
|
++#define ENC_MODE_NFI 1
|
|
|
++#define ENC_TNUM_S 0
|
|
|
++#define ENC_TNUM_M GENMASK(2, 0)
|
|
|
++
|
|
|
++#define ECC_ENCIDLE 0x00c
|
|
|
++#define ENC_IDLE BIT(0)
|
|
|
++
|
|
|
++#define ECC_DECCON 0x100
|
|
|
++#define DEC_EN BIT(0)
|
|
|
++
|
|
|
++#define ECC_DECCNFG 0x104
|
|
|
++#define DEC_EMPTY_EN BIT(31)
|
|
|
++#define DEC_CS_S 16
|
|
|
++#define DEC_CS_M GENMASK(28, 16)
|
|
|
++#define DEC_CON_S 12
|
|
|
++#define DEC_CON_M GENMASK(13, 12)
|
|
|
++#define DEC_CON_EL 2
|
|
|
++#define DEC_MODE_S 4
|
|
|
++#define DEC_MODE_M GENMASK(5, 4)
|
|
|
++#define DEC_MODE_NFI 1
|
|
|
++#define DEC_TNUM_S 0
|
|
|
++#define DEC_TNUM_M GENMASK(2, 0)
|
|
|
++
|
|
|
++#define ECC_DECIDLE 0x10c
|
|
|
++#define DEC_IDLE BIT(1)
|
|
|
++
|
|
|
++#define ECC_DECENUM 0x114
|
|
|
++#define ERRNUM_S 2
|
|
|
++#define ERRNUM_M GENMASK(3, 0)
|
|
|
++
|
|
|
++#define ECC_DECDONE 0x118
|
|
|
++#define DEC_DONE7 BIT(7)
|
|
|
++#define DEC_DONE6 BIT(6)
|
|
|
++#define DEC_DONE5 BIT(5)
|
|
|
++#define DEC_DONE4 BIT(4)
|
|
|
++#define DEC_DONE3 BIT(3)
|
|
|
++#define DEC_DONE2 BIT(2)
|
|
|
++#define DEC_DONE1 BIT(1)
|
|
|
++#define DEC_DONE0 BIT(0)
|
|
|
++
|
|
|
++#define ECC_DECEL(n) (0x11c + (n) * 4)
|
|
|
++#define DEC_EL_ODD_S 16
|
|
|
++#define DEC_EL_EVEN_S 0
|
|
|
++#define DEC_EL_M 0x1fff
|
|
|
++#define DEC_EL_BYTE_POS_S 3
|
|
|
++#define DEC_EL_BIT_POS_M GENMASK(3, 0)
|
|
|
++
|
|
|
++#define ECC_FDMADDR 0x13c
|
|
|
++
|
|
|
++/* ENCIDLE and DECIDLE */
|
|
|
++#define ECC_IDLE BIT(0)
|
|
|
++
|
|
|
++#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \
|
|
|
++ ((tpoecs) << ACCCON_POECS_S | (tprecs) << ACCCON_PRECS_S | \
|
|
|
++ (tc2r) << ACCCON_C2R_S | (tw2r) << ACCCON_W2R_S | \
|
|
|
++ (twh) << ACCCON_WH_S | (twst) << ACCCON_WST_S | (trlt))
|
|
|
++
|
|
|
++#define MASTER_STA_MASK (MAS_ADDR | MAS_RD | MAS_WR | \
|
|
|
++ MAS_RDDLY)
|
|
|
++#define NFI_RESET_TIMEOUT 1000000
|
|
|
++#define NFI_CORE_TIMEOUT 500000
|
|
|
++#define ECC_ENGINE_TIMEOUT 500000
|
|
|
++
|
|
|
++#define ECC_SECTOR_SIZE 512
|
|
|
++#define ECC_PARITY_BITS 13
|
|
|
++
|
|
|
++#define NFI_FDM_SIZE 8
|
|
|
++
|
|
|
++#define MT7621_NFC_NAME "mt7621-nand"
|
|
|
++
|
|
|
++struct mt7621_nfc {
|
|
|
++ struct nand_controller controller;
|
|
|
++ struct nand_chip nand;
|
|
|
++ struct clk *nfi_clk;
|
|
|
++ struct device *dev;
|
|
|
++
|
|
|
++ void __iomem *nfi_regs;
|
|
|
++ void __iomem *ecc_regs;
|
|
|
++
|
|
|
++ u32 spare_per_sector;
|
|
|
++};
|
|
|
++
|
|
|
++static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K };
|
|
|
++static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 };
|
|
|
++static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 };
|
|
|
++
|
|
|
++static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg)
|
|
|
++{
|
|
|
++ return readl(nfc->nfi_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
|
|
|
++{
|
|
|
++ writel(val, nfc->nfi_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg)
|
|
|
++{
|
|
|
++ return readw(nfc->nfi_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
|
|
|
++{
|
|
|
++ writew(val, nfc->nfi_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
|
|
|
++{
|
|
|
++ writew(val, nfc->ecc_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg)
|
|
|
++{
|
|
|
++ return readl(nfc->ecc_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
|
|
|
++{
|
|
|
++ return writel(val, nfc->ecc_regs + reg);
|
|
|
++}
|
|
|
++
|
|
|
++static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect)
|
|
|
++{
|
|
|
++ return nand->oob_poi + sect * NFI_FDM_SIZE;
|
|
|
++}
|
|
|
++
|
|
|
++static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++
|
|
|
++ return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE +
|
|
|
++ sect * (nfc->spare_per_sector - NFI_FDM_SIZE);
|
|
|
++}
|
|
|
++
|
|
|
++static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf,
|
|
|
++ int sect)
|
|
|
++{
|
|
|
++ return (u8 *)buf + sect * nand->ecc.size;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ u32 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ ret = readw_poll_timeout_atomic(nfc->ecc_regs + reg, val,
|
|
|
++ val & ECC_IDLE, 10,
|
|
|
++ ECC_ENGINE_TIMEOUT);
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(dev, "ECC engine timed out entering idle mode\n");
|
|
|
++ return -EIO;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ u32 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ ret = readw_poll_timeout_atomic(nfc->ecc_regs + ECC_DECDONE, val,
|
|
|
++ val & (1 << sect), 10,
|
|
|
++ ECC_ENGINE_TIMEOUT);
|
|
|
++
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(dev, "ECC decoder for sector %d timed out\n",
|
|
|
++ sect);
|
|
|
++ return -ETIMEDOUT;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable)
|
|
|
++{
|
|
|
++ mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE);
|
|
|
++ ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0);
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable)
|
|
|
++{
|
|
|
++ mt7621_ecc_wait_idle(nfc, ECC_DECIDLE);
|
|
|
++ ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf,
|
|
|
++ u8 *fdm_buf, u32 sect)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ u32 decnum, num_error_bits, fdm_end_bits;
|
|
|
++ u32 error_locations, error_bit_loc;
|
|
|
++ u32 error_byte_pos, error_bit_pos;
|
|
|
++ int bitflips = 0;
|
|
|
++ u32 i;
|
|
|
++
|
|
|
++ decnum = ecc_read32(nfc, ECC_DECENUM);
|
|
|
++ num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M;
|
|
|
++ fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3;
|
|
|
++
|
|
|
++ if (!num_error_bits)
|
|
|
++ return 0;
|
|
|
++
|
|
|
++ if (num_error_bits == ERRNUM_M)
|
|
|
++ return -1;
|
|
|
++
|
|
|
++ for (i = 0; i < num_error_bits; i++) {
|
|
|
++ error_locations = ecc_read32(nfc, ECC_DECEL(i / 2));
|
|
|
++ error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) &
|
|
|
++ DEC_EL_M;
|
|
|
++ error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S;
|
|
|
++ error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M;
|
|
|
++
|
|
|
++ if (error_bit_loc < (nand->ecc.size << 3)) {
|
|
|
++ if (sector_buf) {
|
|
|
++ sector_buf[error_byte_pos] ^=
|
|
|
++ (1 << error_bit_pos);
|
|
|
++ }
|
|
|
++ } else if (error_bit_loc < fdm_end_bits) {
|
|
|
++ if (fdm_buf) {
|
|
|
++ fdm_buf[error_byte_pos - nand->ecc.size] ^=
|
|
|
++ (1 << error_bit_pos);
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ bitflips++;
|
|
|
++ }
|
|
|
++
|
|
|
++ return bitflips;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc,
|
|
|
++ struct nand_chip *nand)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ u16 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_ADDRCNTR, val,
|
|
|
++ ((val & SEC_CNTR_M) >> SEC_CNTR_S) >= nand->ecc.steps, 10,
|
|
|
++ NFI_CORE_TIMEOUT);
|
|
|
++
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(dev, "NFI core write operation timed out\n");
|
|
|
++ return -ETIMEDOUT;
|
|
|
++ }
|
|
|
++
|
|
|
++ return ret;
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ u32 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ /* reset all registers and force the NFI master to terminate */
|
|
|
++ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
|
|
|
++
|
|
|
++ /* wait for the master to finish the last transaction */
|
|
|
++ ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val,
|
|
|
++ !(val & MASTER_STA_MASK), 50,
|
|
|
++ NFI_RESET_TIMEOUT);
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(nfc->dev, "Failed to reset NFI master in %dms\n",
|
|
|
++ NFI_RESET_TIMEOUT);
|
|
|
++ }
|
|
|
++
|
|
|
++ /* ensure any status register affected by the NFI master is reset */
|
|
|
++ nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
|
|
|
++ nfi_write16(nfc, NFI_STRDATA, 0);
|
|
|
++}
|
|
|
++
|
|
|
++static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ u32 acccon;
|
|
|
++
|
|
|
++ /*
|
|
|
++ * CNRNB: nand ready/busy register
|
|
|
++ * -------------------------------
|
|
|
++ * 7:4: timeout register for polling the NAND busy/ready signal
|
|
|
++ * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles.
|
|
|
++ */
|
|
|
++ nfi_write16(nfc, NFI_CNRNB, CB2R_TIME_M | STR_CNRNB);
|
|
|
++
|
|
|
++ mt7621_nfc_hw_reset(nfc);
|
|
|
++
|
|
|
++ /* Apply default access timing */
|
|
|
++ acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF,
|
|
|
++ ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF,
|
|
|
++ ACCCON_RLT_DEF);
|
|
|
++
|
|
|
++ nfi_write32(nfc, NFI_ACCCON, acccon);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ u32 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ nfi_write32(nfc, NFI_CMD, command);
|
|
|
++
|
|
|
++ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val,
|
|
|
++ !(val & STA_CMD), 10,
|
|
|
++ NFI_CORE_TIMEOUT);
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(dev, "NFI core timed out entering command mode\n");
|
|
|
++ return -EIO;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ u32 val;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ nfi_write32(nfc, NFI_COLADDR, addr);
|
|
|
++ nfi_write32(nfc, NFI_ROWADDR, 0);
|
|
|
++ nfi_write16(nfc, NFI_ADDRNOB, 1);
|
|
|
++
|
|
|
++ ret = readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val,
|
|
|
++ !(val & STA_ADDR), 10,
|
|
|
++ NFI_CORE_TIMEOUT);
|
|
|
++ if (ret) {
|
|
|
++ dev_warn(dev, "NFI core timed out entering address mode\n");
|
|
|
++ return -EIO;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_send_address(struct mt7621_nfc *nfc, const u8 *addr,
|
|
|
++ unsigned int naddrs)
|
|
|
++{
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ while (naddrs) {
|
|
|
++ ret = mt7621_nfc_send_address_byte(nfc, *addr);
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++
|
|
|
++ addr++;
|
|
|
++ naddrs--;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct device *dev = nfc->dev;
|
|
|
++ int ret;
|
|
|
++ u16 val;
|
|
|
++
|
|
|
++ ret = readw_poll_timeout_atomic(nfc->nfi_regs + NFI_PIO_DIRDY, val,
|
|
|
++ val & PIO_DIRDY, 10,
|
|
|
++ NFI_CORE_TIMEOUT);
|
|
|
++ if (ret < 0)
|
|
|
++ dev_err(dev, "NFI core PIO mode not ready\n");
|
|
|
++}
|
|
|
++
|
|
|
++static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br)
|
|
|
++{
|
|
|
++ u32 reg;
|
|
|
++
|
|
|
++ /* after each byte read, the NFI_STA reg is reset by the hardware */
|
|
|
++ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S;
|
|
|
++ if (reg != STA_FSM_CUSTOM_DATA) {
|
|
|
++ reg = nfi_read16(nfc, NFI_CNFG);
|
|
|
++ reg |= CNFG_READ_MODE | CNFG_BYTE_RW;
|
|
|
++ if (!br)
|
|
|
++ reg &= ~CNFG_BYTE_RW;
|
|
|
++ nfi_write16(nfc, NFI_CNFG, reg);
|
|
|
++
|
|
|
++ /*
|
|
|
++ * set to max sector to allow the HW to continue reading over
|
|
|
++ * unaligned accesses
|
|
|
++ */
|
|
|
++ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BRD);
|
|
|
++
|
|
|
++ /* trigger to fetch data */
|
|
|
++ nfi_write16(nfc, NFI_STRDATA, STR_DATA);
|
|
|
++ }
|
|
|
++
|
|
|
++ mt7621_nfc_wait_pio_ready(nfc);
|
|
|
++
|
|
|
++ return nfi_read32(nfc, NFI_DATAR);
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len)
|
|
|
++{
|
|
|
++ while (((uintptr_t)buf & 3) && len) {
|
|
|
++ *buf = mt7621_nfc_pio_read(nfc, true);
|
|
|
++ buf++;
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len >= 4) {
|
|
|
++ *(u32 *)buf = mt7621_nfc_pio_read(nfc, false);
|
|
|
++ buf += 4;
|
|
|
++ len -= 4;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len) {
|
|
|
++ *buf = mt7621_nfc_pio_read(nfc, true);
|
|
|
++ buf++;
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len)
|
|
|
++{
|
|
|
++ while (len >= 4) {
|
|
|
++ mt7621_nfc_pio_read(nfc, false);
|
|
|
++ len -= 4;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len) {
|
|
|
++ mt7621_nfc_pio_read(nfc, true);
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw)
|
|
|
++{
|
|
|
++ u32 reg;
|
|
|
++
|
|
|
++ reg = (nfi_read32(nfc, NFI_STA) & STA_NFI_FSM_M) >> STA_NFI_FSM_S;
|
|
|
++ if (reg != STA_FSM_CUSTOM_DATA) {
|
|
|
++ reg = nfi_read16(nfc, NFI_CNFG);
|
|
|
++ reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW);
|
|
|
++ if (bw)
|
|
|
++ reg |= CNFG_BYTE_RW;
|
|
|
++ nfi_write16(nfc, NFI_CNFG, reg);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON, CON_NFI_SEC_M | CON_NFI_BWR);
|
|
|
++ nfi_write16(nfc, NFI_STRDATA, STR_DATA);
|
|
|
++ }
|
|
|
++
|
|
|
++ mt7621_nfc_wait_pio_ready(nfc);
|
|
|
++ nfi_write32(nfc, NFI_DATAW, val);
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf,
|
|
|
++ u32 len)
|
|
|
++{
|
|
|
++ while (((uintptr_t)buf & 3) && len) {
|
|
|
++ mt7621_nfc_pio_write(nfc, *buf, true);
|
|
|
++ buf++;
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len >= 4) {
|
|
|
++ mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false);
|
|
|
++ buf += 4;
|
|
|
++ len -= 4;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len) {
|
|
|
++ mt7621_nfc_pio_write(nfc, *buf, true);
|
|
|
++ buf++;
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len)
|
|
|
++{
|
|
|
++ while (len >= 4) {
|
|
|
++ mt7621_nfc_pio_write(nfc, 0xffffffff, false);
|
|
|
++ len -= 4;
|
|
|
++ }
|
|
|
++
|
|
|
++ while (len) {
|
|
|
++ mt7621_nfc_pio_write(nfc, 0xff, true);
|
|
|
++ len--;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_dev_ready(struct mt7621_nfc *nfc,
|
|
|
++ unsigned int timeout_ms)
|
|
|
++{
|
|
|
++ u32 val;
|
|
|
++
|
|
|
++ return readl_poll_timeout_atomic(nfc->nfi_regs + NFI_STA, val,
|
|
|
++ !(val & STA_BUSY), 10,
|
|
|
++ timeout_ms * 1000);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_exec_instr(struct nand_chip *nand,
|
|
|
++ const struct nand_op_instr *instr)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++
|
|
|
++ switch (instr->type) {
|
|
|
++ case NAND_OP_CMD_INSTR:
|
|
|
++ mt7621_nfc_hw_reset(nfc);
|
|
|
++ nfi_write16(nfc, NFI_CNFG, CNFG_OP_CUSTOM << CNFG_OP_MODE_S);
|
|
|
++ return mt7621_nfc_send_command(nfc, instr->ctx.cmd.opcode);
|
|
|
++ case NAND_OP_ADDR_INSTR:
|
|
|
++ return mt7621_nfc_send_address(nfc, instr->ctx.addr.addrs,
|
|
|
++ instr->ctx.addr.naddrs);
|
|
|
++ case NAND_OP_DATA_IN_INSTR:
|
|
|
++ mt7621_nfc_read_data(nfc, instr->ctx.data.buf.in,
|
|
|
++ instr->ctx.data.len);
|
|
|
++ return 0;
|
|
|
++ case NAND_OP_DATA_OUT_INSTR:
|
|
|
++ mt7621_nfc_write_data(nfc, instr->ctx.data.buf.out,
|
|
|
++ instr->ctx.data.len);
|
|
|
++ return 0;
|
|
|
++ case NAND_OP_WAITRDY_INSTR:
|
|
|
++ return mt7621_nfc_dev_ready(nfc,
|
|
|
++ instr->ctx.waitrdy.timeout_ms);
|
|
|
++ default:
|
|
|
++ WARN_ONCE(1, "unsupported NAND instruction type: %d\n",
|
|
|
++ instr->type);
|
|
|
++
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_exec_op(struct nand_chip *nand,
|
|
|
++ const struct nand_operation *op, bool check_only)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ int i, ret;
|
|
|
++
|
|
|
++ if (check_only)
|
|
|
++ return 0;
|
|
|
++
|
|
|
++ /* Only CS0 available */
|
|
|
++ nfi_write16(nfc, NFI_CSEL, 0);
|
|
|
++
|
|
|
++ for (i = 0; i < op->ninstrs; i++) {
|
|
|
++ ret = mt7621_nfc_exec_instr(nand, &op->instrs[i]);
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_setup_interface(struct nand_chip *nand, int csline,
|
|
|
++ const struct nand_interface_config *conf)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ const struct nand_sdr_timings *timings;
|
|
|
++ u32 acccon, temp, rate, tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt;
|
|
|
++
|
|
|
++ if (!nfc->nfi_clk)
|
|
|
++ return -ENOTSUPP;
|
|
|
++
|
|
|
++ timings = nand_get_sdr_timings(conf);
|
|
|
++ if (IS_ERR(timings))
|
|
|
++ return -ENOTSUPP;
|
|
|
++
|
|
|
++ rate = clk_get_rate(nfc->nfi_clk);
|
|
|
++
|
|
|
++ /* turn clock rate into KHZ */
|
|
|
++ rate /= 1000;
|
|
|
++
|
|
|
++ tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000;
|
|
|
++ tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000);
|
|
|
++ tpoecs = min_t(u32, tpoecs, ACCCON_POECS_MAX);
|
|
|
++
|
|
|
++ tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000;
|
|
|
++ tprecs = DIV_ROUND_UP(tprecs * rate, 1000000);
|
|
|
++ tprecs = min_t(u32, tprecs, ACCCON_PRECS_MAX);
|
|
|
++
|
|
|
++ /* sdr interface has no tCR which means CE# low to RE# low */
|
|
|
++ tc2r = 0;
|
|
|
++
|
|
|
++ tw2r = timings->tWHR_min / 1000;
|
|
|
++ tw2r = DIV_ROUND_UP(tw2r * rate, 1000000);
|
|
|
++ tw2r = DIV_ROUND_UP(tw2r - 1, 2);
|
|
|
++ tw2r = min_t(u32, tw2r, ACCCON_W2R_MAX);
|
|
|
++
|
|
|
++ twh = max(timings->tREH_min, timings->tWH_min) / 1000;
|
|
|
++ twh = DIV_ROUND_UP(twh * rate, 1000000) - 1;
|
|
|
++ twh = min_t(u32, twh, ACCCON_WH_MAX);
|
|
|
++
|
|
|
++ /* Calculate real WE#/RE# hold time in nanosecond */
|
|
|
++ temp = (twh + 1) * 1000000 / rate;
|
|
|
++ /* nanosecond to picosecond */
|
|
|
++ temp *= 1000;
|
|
|
++
|
|
|
++ /*
|
|
|
++ * WE# low level time should be expaned to meet WE# pulse time
|
|
|
++ * and WE# cycle time at the same time.
|
|
|
++ */
|
|
|
++ if (temp < timings->tWC_min)
|
|
|
++ twst = timings->tWC_min - temp;
|
|
|
++ else
|
|
|
++ twst = 0;
|
|
|
++ twst = max(timings->tWP_min, twst) / 1000;
|
|
|
++ twst = DIV_ROUND_UP(twst * rate, 1000000) - 1;
|
|
|
++ twst = min_t(u32, twst, ACCCON_WST_MAX);
|
|
|
++
|
|
|
++ /*
|
|
|
++ * RE# low level time should be expaned to meet RE# pulse time
|
|
|
++ * and RE# cycle time at the same time.
|
|
|
++ */
|
|
|
++ if (temp < timings->tRC_min)
|
|
|
++ trlt = timings->tRC_min - temp;
|
|
|
++ else
|
|
|
++ trlt = 0;
|
|
|
++ trlt = max(trlt, timings->tRP_min) / 1000;
|
|
|
++ trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1;
|
|
|
++ trlt = min_t(u32, trlt, ACCCON_RLT_MAX);
|
|
|
++
|
|
|
++ if (csline == NAND_DATA_IFACE_CHECK_ONLY) {
|
|
|
++ if (twst < ACCCON_WST_MIN || trlt < ACCCON_RLT_MIN)
|
|
|
++ return -ENOTSUPP;
|
|
|
++ }
|
|
|
++
|
|
|
++ acccon = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt);
|
|
|
++
|
|
|
++ dev_info(nfc->dev, "Using programmed access timing: %08x\n", acccon);
|
|
|
++
|
|
|
++ nfi_write32(nfc, NFI_ACCCON, acccon);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc,
|
|
|
++ u32 avail_ecc_bytes)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ u32 strength;
|
|
|
++ int i;
|
|
|
++
|
|
|
++ strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS;
|
|
|
++
|
|
|
++ /* Find the closest supported ecc strength */
|
|
|
++ for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) {
|
|
|
++ if (mt7621_ecc_strength[i] <= strength)
|
|
|
++ break;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (unlikely(i < 0)) {
|
|
|
++ dev_err(nfc->dev, "OOB size (%u) is not supported\n",
|
|
|
++ mtd->oobsize);
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ nand->ecc.strength = mt7621_ecc_strength[i];
|
|
|
++ nand->ecc.bytes =
|
|
|
++ DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8);
|
|
|
++
|
|
|
++ dev_info(nfc->dev, "ECC strength adjusted to %u bits\n",
|
|
|
++ nand->ecc.strength);
|
|
|
++
|
|
|
++ return i;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ u32 size;
|
|
|
++ int i;
|
|
|
++
|
|
|
++ size = nand->ecc.bytes + NFI_FDM_SIZE;
|
|
|
++
|
|
|
++ /* Find the closest supported spare size */
|
|
|
++ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) {
|
|
|
++ if (mt7621_nfi_spare_size[i] >= size)
|
|
|
++ break;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) {
|
|
|
++ dev_err(nfc->dev, "OOB size (%u) is not supported\n",
|
|
|
++ mtd->oobsize);
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ nfc->spare_per_sector = mt7621_nfi_spare_size[i];
|
|
|
++
|
|
|
++ return i;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ u32 spare_per_sector, encode_block_size, decode_block_size;
|
|
|
++ u32 ecc_enccfg, ecc_deccfg;
|
|
|
++ int ecc_cap;
|
|
|
++
|
|
|
++ /* Only hardware ECC mode is supported */
|
|
|
++ if (nand->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) {
|
|
|
++ dev_err(nfc->dev, "Only hardware ECC mode is supported\n");
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ nand->ecc.size = ECC_SECTOR_SIZE;
|
|
|
++ nand->ecc.steps = mtd->writesize / nand->ecc.size;
|
|
|
++
|
|
|
++ spare_per_sector = mtd->oobsize / nand->ecc.steps;
|
|
|
++
|
|
|
++ ecc_cap = mt7621_nfc_calc_ecc_strength(nfc,
|
|
|
++ spare_per_sector - NFI_FDM_SIZE);
|
|
|
++ if (ecc_cap < 0)
|
|
|
++ return ecc_cap;
|
|
|
++
|
|
|
++ /* Sector + FDM */
|
|
|
++ encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8;
|
|
|
++ ecc_enccfg = ecc_cap | (ENC_MODE_NFI << ENC_MODE_S) |
|
|
|
++ (encode_block_size << ENC_CNFG_MSG_S);
|
|
|
++
|
|
|
++ /* Sector + FDM + ECC parity bits */
|
|
|
++ decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) +
|
|
|
++ nand->ecc.strength * ECC_PARITY_BITS;
|
|
|
++ ecc_deccfg = ecc_cap | (DEC_MODE_NFI << DEC_MODE_S) |
|
|
|
++ (decode_block_size << DEC_CS_S) |
|
|
|
++ (DEC_CON_EL << DEC_CON_S) | DEC_EMPTY_EN;
|
|
|
++
|
|
|
++ mt7621_ecc_encoder_op(nfc, false);
|
|
|
++ ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg);
|
|
|
++
|
|
|
++ mt7621_ecc_decoder_op(nfc, false);
|
|
|
++ ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ int i, spare_size;
|
|
|
++ u32 pagefmt;
|
|
|
++
|
|
|
++ spare_size = mt7621_nfc_set_spare_per_sector(nfc);
|
|
|
++ if (spare_size < 0)
|
|
|
++ return spare_size;
|
|
|
++
|
|
|
++ for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) {
|
|
|
++ if (mt7621_nfi_page_size[i] == mtd->writesize)
|
|
|
++ break;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) {
|
|
|
++ dev_err(nfc->dev, "Page size (%u) is not supported\n",
|
|
|
++ mtd->writesize);
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ pagefmt = i | (spare_size << PAGEFMT_SPARE_S) |
|
|
|
++ (NFI_FDM_SIZE << PAGEFMT_FDM_S) |
|
|
|
++ (NFI_FDM_SIZE << PAGEFMT_FDM_ECC_S);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_PAGEFMT, pagefmt);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_attach_chip(struct nand_chip *nand)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ if (nand->options & NAND_BUSWIDTH_16) {
|
|
|
++ dev_err(nfc->dev, "16-bit buswidth is not supported");
|
|
|
++ return -EINVAL;
|
|
|
++ }
|
|
|
++
|
|
|
++ ret = mt7621_nfc_ecc_init(nfc);
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++
|
|
|
++ return mt7621_nfc_set_page_format(nfc);
|
|
|
++}
|
|
|
++
|
|
|
++static const struct nand_controller_ops mt7621_nfc_controller_ops = {
|
|
|
++ .attach_chip = mt7621_nfc_attach_chip,
|
|
|
++ .exec_op = mt7621_nfc_exec_op,
|
|
|
++ .setup_interface = mt7621_nfc_setup_interface,
|
|
|
++};
|
|
|
++
|
|
|
++static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section,
|
|
|
++ struct mtd_oob_region *oob_region)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = mtd_to_nand(mtd);
|
|
|
++
|
|
|
++ if (section >= nand->ecc.steps)
|
|
|
++ return -ERANGE;
|
|
|
++
|
|
|
++ oob_region->length = NFI_FDM_SIZE - 1;
|
|
|
++ oob_region->offset = section * NFI_FDM_SIZE + 1;
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section,
|
|
|
++ struct mtd_oob_region *oob_region)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = mtd_to_nand(mtd);
|
|
|
++
|
|
|
++ if (section)
|
|
|
++ return -ERANGE;
|
|
|
++
|
|
|
++ oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps;
|
|
|
++ oob_region->length = mtd->oobsize - oob_region->offset;
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = {
|
|
|
++ .free = mt7621_nfc_ooblayout_free,
|
|
|
++ .ecc = mt7621_nfc_ooblayout_ecc,
|
|
|
++};
|
|
|
++
|
|
|
++static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ u32 vall, valm;
|
|
|
++ u8 *oobptr;
|
|
|
++ int i, j;
|
|
|
++
|
|
|
++ for (i = 0; i < nand->ecc.steps; i++) {
|
|
|
++ vall = 0;
|
|
|
++ valm = 0;
|
|
|
++ oobptr = oob_fdm_ptr(nand, i);
|
|
|
++
|
|
|
++ for (j = 0; j < 4; j++)
|
|
|
++ vall |= (u32)oobptr[j] << (j * 8);
|
|
|
++
|
|
|
++ for (j = 0; j < 4; j++)
|
|
|
++ valm |= (u32)oobptr[j + 4] << ((j - 4) * 8);
|
|
|
++
|
|
|
++ nfi_write32(nfc, NFI_FDML(i), vall);
|
|
|
++ nfi_write32(nfc, NFI_FDMM(i), valm);
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ u32 vall, valm;
|
|
|
++ u8 *oobptr;
|
|
|
++ int i;
|
|
|
++
|
|
|
++ vall = nfi_read32(nfc, NFI_FDML(sect));
|
|
|
++ valm = nfi_read32(nfc, NFI_FDMM(sect));
|
|
|
++ oobptr = oob_fdm_ptr(nand, sect);
|
|
|
++
|
|
|
++ for (i = 0; i < 4; i++)
|
|
|
++ oobptr[i] = (vall >> (i * 8)) & 0xff;
|
|
|
++
|
|
|
++ for (i = 0; i < 4; i++)
|
|
|
++ oobptr[i + 4] = (valm >> (i * 8)) & 0xff;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_read_page_hwecc(struct nand_chip *nand, uint8_t *buf,
|
|
|
++ int oob_required, int page)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ int bitflips = 0;
|
|
|
++ int rc, i;
|
|
|
++
|
|
|
++ nand_read_page_op(nand, page, 0, NULL, 0);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) |
|
|
|
++ CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
|
|
|
++
|
|
|
++ mt7621_ecc_decoder_op(nfc, true);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON,
|
|
|
++ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S));
|
|
|
++
|
|
|
++ for (i = 0; i < nand->ecc.steps; i++) {
|
|
|
++ if (buf)
|
|
|
++ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
|
|
|
++ nand->ecc.size);
|
|
|
++ else
|
|
|
++ mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
|
|
|
++
|
|
|
++ rc = mt7621_ecc_decoder_wait_done(nfc, i);
|
|
|
++
|
|
|
++ mt7621_nfc_read_sector_fdm(nfc, i);
|
|
|
++
|
|
|
++ if (rc < 0) {
|
|
|
++ bitflips = -EIO;
|
|
|
++ continue;
|
|
|
++ }
|
|
|
++
|
|
|
++ rc = mt7621_ecc_correct_check(nfc,
|
|
|
++ buf ? page_data_ptr(nand, buf, i) : NULL,
|
|
|
++ oob_fdm_ptr(nand, i), i);
|
|
|
++
|
|
|
++ if (rc < 0) {
|
|
|
++ dev_warn(nfc->dev,
|
|
|
++ "Uncorrectable ECC error at page %d.%d\n",
|
|
|
++ page, i);
|
|
|
++ bitflips = -EBADMSG;
|
|
|
++ mtd->ecc_stats.failed++;
|
|
|
++ } else if (bitflips >= 0) {
|
|
|
++ bitflips += rc;
|
|
|
++ mtd->ecc_stats.corrected += rc;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ mt7621_ecc_decoder_op(nfc, false);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON, 0);
|
|
|
++
|
|
|
++ return bitflips;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_read_page_raw(struct nand_chip *nand, uint8_t *buf,
|
|
|
++ int oob_required, int page)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ int i;
|
|
|
++
|
|
|
++ nand_read_page_op(nand, page, 0, NULL, 0);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) |
|
|
|
++ CNFG_READ_MODE);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON,
|
|
|
++ CON_NFI_BRD | (nand->ecc.steps << CON_NFI_SEC_S));
|
|
|
++
|
|
|
++ for (i = 0; i < nand->ecc.steps; i++) {
|
|
|
++ /* Read data */
|
|
|
++ if (buf)
|
|
|
++ mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
|
|
|
++ nand->ecc.size);
|
|
|
++ else
|
|
|
++ mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
|
|
|
++
|
|
|
++ /* Read FDM */
|
|
|
++ mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE);
|
|
|
++
|
|
|
++ /* Read ECC parity data */
|
|
|
++ mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i),
|
|
|
++ nfc->spare_per_sector - NFI_FDM_SIZE);
|
|
|
++ }
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON, 0);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_read_oob_hwecc(struct nand_chip *nand, int page)
|
|
|
++{
|
|
|
++ return mt7621_nfc_read_page_hwecc(nand, NULL, 1, page);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_read_oob_raw(struct nand_chip *nand, int page)
|
|
|
++{
|
|
|
++ return mt7621_nfc_read_page_raw(nand, NULL, 1, page);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf)
|
|
|
++{
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++ uint32_t i, j;
|
|
|
++ u8 *oobptr;
|
|
|
++
|
|
|
++ if (buf) {
|
|
|
++ for (i = 0; i < mtd->writesize; i++)
|
|
|
++ if (buf[i] != 0xff)
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ for (i = 0; i < nand->ecc.steps; i++) {
|
|
|
++ oobptr = oob_fdm_ptr(nand, i);
|
|
|
++ for (j = 0; j < NFI_FDM_SIZE; j++)
|
|
|
++ if (oobptr[j] != 0xff)
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 1;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_write_page_hwecc(struct nand_chip *nand,
|
|
|
++ const uint8_t *buf, int oob_required,
|
|
|
++ int page)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++
|
|
|
++ if (mt7621_nfc_check_empty_page(nand, buf)) {
|
|
|
++ /*
|
|
|
++ * MT7621 ECC engine always generates parity code for input
|
|
|
++ * pages, even for empty pages. Doing so will write back ECC
|
|
|
++ * parity code to the oob region, which means such pages will
|
|
|
++ * no longer be empty pages.
|
|
|
++ *
|
|
|
++ * To avoid this, stop write operation if current page is an
|
|
|
++ * empty page.
|
|
|
++ */
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ nand_prog_page_begin_op(nand, page, 0, NULL, 0);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S) |
|
|
|
++ CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
|
|
|
++
|
|
|
++ mt7621_ecc_encoder_op(nfc, true);
|
|
|
++
|
|
|
++ mt7621_nfc_write_fdm(nfc);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON,
|
|
|
++ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S));
|
|
|
++
|
|
|
++ if (buf)
|
|
|
++ mt7621_nfc_write_data(nfc, buf, mtd->writesize);
|
|
|
++ else
|
|
|
++ mt7621_nfc_write_data_empty(nfc, mtd->writesize);
|
|
|
++
|
|
|
++ mt7621_nfc_wait_write_completion(nfc, nand);
|
|
|
++
|
|
|
++ mt7621_ecc_encoder_op(nfc, false);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON, 0);
|
|
|
++
|
|
|
++ return nand_prog_page_end_op(nand);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_write_page_raw(struct nand_chip *nand,
|
|
|
++ const uint8_t *buf, int oob_required,
|
|
|
++ int page)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = nand_get_controller_data(nand);
|
|
|
++ int i;
|
|
|
++
|
|
|
++ nand_prog_page_begin_op(nand, page, 0, NULL, 0);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CNFG, (CNFG_OP_CUSTOM << CNFG_OP_MODE_S));
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON,
|
|
|
++ CON_NFI_BWR | (nand->ecc.steps << CON_NFI_SEC_S));
|
|
|
++
|
|
|
++ for (i = 0; i < nand->ecc.steps; i++) {
|
|
|
++ /* Write data */
|
|
|
++ if (buf)
|
|
|
++ mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i),
|
|
|
++ nand->ecc.size);
|
|
|
++ else
|
|
|
++ mt7621_nfc_write_data_empty(nfc, nand->ecc.size);
|
|
|
++
|
|
|
++ /* Write FDM */
|
|
|
++ mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i),
|
|
|
++ NFI_FDM_SIZE);
|
|
|
++
|
|
|
++ /* Write dummy ECC parity data */
|
|
|
++ mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector -
|
|
|
++ NFI_FDM_SIZE);
|
|
|
++ }
|
|
|
++
|
|
|
++ mt7621_nfc_wait_write_completion(nfc, nand);
|
|
|
++
|
|
|
++ nfi_write16(nfc, NFI_CON, 0);
|
|
|
++
|
|
|
++ return nand_prog_page_end_op(nand);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_write_oob_hwecc(struct nand_chip *nand, int page)
|
|
|
++{
|
|
|
++ return mt7621_nfc_write_page_hwecc(nand, NULL, 1, page);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_write_oob_raw(struct nand_chip *nand, int page)
|
|
|
++{
|
|
|
++ return mt7621_nfc_write_page_raw(nand, NULL, 1, page);
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_init_chip(struct mt7621_nfc *nfc)
|
|
|
++{
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ nand->controller = &nfc->controller;
|
|
|
++ nand_set_controller_data(nand, (void *)nfc);
|
|
|
++ nand_set_flash_node(nand, nfc->dev->of_node);
|
|
|
++
|
|
|
++ nand->options |= NAND_USES_DMA | NAND_NO_SUBPAGE_WRITE;
|
|
|
++ if (!nfc->nfi_clk)
|
|
|
++ nand->options |= NAND_KEEP_TIMINGS;
|
|
|
++
|
|
|
++ nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
|
|
++ nand->ecc.read_page = mt7621_nfc_read_page_hwecc;
|
|
|
++ nand->ecc.read_page_raw = mt7621_nfc_read_page_raw;
|
|
|
++ nand->ecc.write_page = mt7621_nfc_write_page_hwecc;
|
|
|
++ nand->ecc.write_page_raw = mt7621_nfc_write_page_raw;
|
|
|
++ nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc;
|
|
|
++ nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw;
|
|
|
++ nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc;
|
|
|
++ nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw;
|
|
|
++
|
|
|
++ mtd = nand_to_mtd(nand);
|
|
|
++ mtd->owner = THIS_MODULE;
|
|
|
++ mtd->dev.parent = nfc->dev;
|
|
|
++ mtd->name = MT7621_NFC_NAME;
|
|
|
++ mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops);
|
|
|
++
|
|
|
++ mt7621_nfc_hw_init(nfc);
|
|
|
++
|
|
|
++ ret = nand_scan(nand, 1);
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++
|
|
|
++ ret = mtd_device_register(mtd, NULL, 0);
|
|
|
++ if (ret) {
|
|
|
++ dev_err(nfc->dev, "Failed to register MTD: %d\n", ret);
|
|
|
++ nand_cleanup(nand);
|
|
|
++ return ret;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_probe(struct platform_device *pdev)
|
|
|
++{
|
|
|
++ struct device *dev = &pdev->dev;
|
|
|
++ struct mt7621_nfc *nfc;
|
|
|
++ struct resource *res;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
|
|
|
++ if (!nfc)
|
|
|
++ return -ENOMEM;
|
|
|
++
|
|
|
++ nand_controller_init(&nfc->controller);
|
|
|
++ nfc->controller.ops = &mt7621_nfc_controller_ops;
|
|
|
++ nfc->dev = dev;
|
|
|
++
|
|
|
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi");
|
|
|
++ nfc->nfi_regs = devm_ioremap_resource(dev, res);
|
|
|
++ if (IS_ERR(nfc->nfi_regs)) {
|
|
|
++ ret = PTR_ERR(nfc->nfi_regs);
|
|
|
++ return ret;
|
|
|
++ }
|
|
|
++
|
|
|
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc");
|
|
|
++ nfc->ecc_regs = devm_ioremap_resource(dev, res);
|
|
|
++ if (IS_ERR(nfc->ecc_regs)) {
|
|
|
++ ret = PTR_ERR(nfc->ecc_regs);
|
|
|
++ return ret;
|
|
|
++ }
|
|
|
++
|
|
|
++ nfc->nfi_clk = devm_clk_get(dev, "nfi_clk");
|
|
|
++ if (IS_ERR(nfc->nfi_clk)) {
|
|
|
++ dev_warn(dev, "nfi clk not provided\n");
|
|
|
++ nfc->nfi_clk = NULL;
|
|
|
++ } else {
|
|
|
++ ret = clk_prepare_enable(nfc->nfi_clk);
|
|
|
++ if (ret) {
|
|
|
++ dev_err(dev, "Failed to enable nfi core clock\n");
|
|
|
++ return ret;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ platform_set_drvdata(pdev, nfc);
|
|
|
++
|
|
|
++ ret = mt7621_nfc_init_chip(nfc);
|
|
|
++ if (ret) {
|
|
|
++ dev_err(dev, "Failed to initialize nand chip\n");
|
|
|
++ goto clk_disable;
|
|
|
++ }
|
|
|
++
|
|
|
++ return 0;
|
|
|
++
|
|
|
++clk_disable:
|
|
|
++ clk_disable_unprepare(nfc->nfi_clk);
|
|
|
++
|
|
|
++ return ret;
|
|
|
++}
|
|
|
++
|
|
|
++static int mt7621_nfc_remove(struct platform_device *pdev)
|
|
|
++{
|
|
|
++ struct mt7621_nfc *nfc = platform_get_drvdata(pdev);
|
|
|
++ struct nand_chip *nand = &nfc->nand;
|
|
|
++ struct mtd_info *mtd = nand_to_mtd(nand);
|
|
|
++
|
|
|
++ mtd_device_unregister(mtd);
|
|
|
++ nand_cleanup(nand);
|
|
|
++ clk_disable_unprepare(nfc->nfi_clk);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static const struct of_device_id mt7621_nfc_id_table[] = {
|
|
|
++ { .compatible = "mediatek,mt7621-nfc" },
|
|
|
++ { },
|
|
|
++};
|
|
|
++MODULE_DEVICE_TABLE(of, match);
|
|
|
++
|
|
|
++static struct platform_driver mt7621_nfc_driver = {
|
|
|
++ .probe = mt7621_nfc_probe,
|
|
|
++ .remove = mt7621_nfc_remove,
|
|
|
++ .driver = {
|
|
|
++ .name = MT7621_NFC_NAME,
|
|
|
++ .owner = THIS_MODULE,
|
|
|
++ .of_match_table = mt7621_nfc_id_table,
|
|
|
++ },
|
|
|
++};
|
|
|
++module_platform_driver(mt7621_nfc_driver);
|
|
|
++
|
|
|
++MODULE_LICENSE("GPL");
|
|
|
++MODULE_AUTHOR("Weijie Gao <[email protected]>");
|
|
|
++MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver");
|