* [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller
@ 2020-03-31 18:07 Weijie Gao
2020-03-31 18:07 ` [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver Weijie Gao
2020-03-31 18:47 ` [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Boris Brezillon
0 siblings, 2 replies; 4+ messages in thread
From: Weijie Gao @ 2020-03-31 18:07 UTC (permalink / raw)
To: linux-mtd
Cc: Mark Rutland, devicetree, Anders Roxell, Vignesh Raghavendra,
Arnd Bergmann, Boris Brezillon, Richard Weinberger,
Brendan Higgins, linux-kernel, Frieder Schrempf, linux-mips,
Greg Kroah-Hartman, Rob Herring, linux-mediatek, Miquel Raynal,
Matthias Brugger, Mason Yang, Weijie Gao, Piotr Sroka
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 <weijie.gao@mediatek.com>
---
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
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index a80a46bb5b8b..c07dcf174a0f 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -391,6 +391,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
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158fb7..f374945d6305 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o
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
diff --git a/drivers/mtd/nand/raw/mt7621_nand.c b/drivers/mtd/nand/raw/mt7621_nand.c
new file mode 100644
index 000000000000..7763137430f1
--- /dev/null
+++ b/drivers/mtd/nand/raw/mt7621_nand.c
@@ -0,0 +1,1348 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek MT7621 NAND Flash Controller driver
+ *
+ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#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_data_interface(struct nand_chip *nand, int csline,
+ const struct nand_data_interface *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.mode != NAND_ECC_HW_SYNDROME) {
+ 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_data_interface = mt7621_nfc_setup_data_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;
+
+ 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_USE_BOUNCE_BUFFER | NAND_NO_SUBPAGE_WRITE;
+ if (!nfc->nfi_clk)
+ nand->options |= NAND_KEEP_TIMINGS;
+
+ nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+ 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_release(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);
+
+ nand_release(&nfc->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 <weijie.gao@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek MT7621 NAND Flash Controller driver");
--
2.17.1
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver
2020-03-31 18:07 [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Weijie Gao
@ 2020-03-31 18:07 ` Weijie Gao
2020-03-31 22:00 ` Rob Herring
2020-03-31 18:47 ` [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Boris Brezillon
1 sibling, 1 reply; 4+ messages in thread
From: Weijie Gao @ 2020-03-31 18:07 UTC (permalink / raw)
To: linux-mtd
Cc: Mark Rutland, devicetree, Anders Roxell, Vignesh Raghavendra,
Arnd Bergmann, Boris Brezillon, Richard Weinberger,
Brendan Higgins, linux-kernel, Frieder Schrempf, linux-mips,
Greg Kroah-Hartman, Rob Herring, linux-mediatek, Miquel Raynal,
Matthias Brugger, Mason Yang, Weijie Gao, Piotr Sroka
This patch adds documentation for MediaTek MT7621 NAND flash controller
driver.
Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
---
.../bindings/mtd/mediatek,mt7621-nfc.yaml | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.yaml
diff --git a/Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.yaml b/Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.yaml
new file mode 100644
index 000000000000..1ca0c5e95e4c
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mtd/mediatek,mt7621-nfc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek MT7621 SoC NAND Flash Controller (NFC) DT binding
+
+maintainers:
+ - Weijie Gao <weijie.gao@mediatek.com>
+
+description: |
+ This driver uses a single node to describe both NAND Flash controller
+ interface (NFI) and ECC engine for MT7621 SoC.
+ MT7621 supports only one chip select.
+
+properties:
+ "#address-cells": false
+ "#size-cells": false
+
+ compatible:
+ enum:
+ - mediatek,mt7621-nfc
+
+ reg:
+ items:
+ - description: Register base of NFI core
+ - description: Register base of ECC engine
+
+ reg-names:
+ items:
+ - const: nfi
+ - const: ecc
+
+ clocks:
+ items:
+ - description: Source clock for NFI core, fixed 125MHz
+
+ clock-names:
+ items:
+ - const: nfi_clk
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+
+examples:
+ - |
+ nficlock: nficlock {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+
+ clock-frequency = <125000000>;
+ };
+
+ nand@1e003000 {
+ compatible = "mediatek,mt7621-nfc";
+
+ reg = <0x1e003000 0x800
+ 0x1e003800 0x800>;
+ reg-names = "nfi", "ecc";
+
+ clocks = <&nficlock>;
+ clock-names = "nfi_clk";
+ };
--
2.17.1
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver
2020-03-31 18:07 ` [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver Weijie Gao
@ 2020-03-31 22:00 ` Rob Herring
0 siblings, 0 replies; 4+ messages in thread
From: Rob Herring @ 2020-03-31 22:00 UTC (permalink / raw)
To: Weijie Gao
Cc: Mark Rutland, devicetree, Anders Roxell, Vignesh Raghavendra,
Arnd Bergmann, Boris Brezillon, Richard Weinberger,
Brendan Higgins, linux-kernel, Frieder Schrempf, linux-mips,
Greg Kroah-Hartman, linux-mediatek, Miquel Raynal,
Matthias Brugger, linux-mtd, Mason Yang, Weijie Gao, Piotr Sroka
On Wed, 1 Apr 2020 02:07:59 +0800, Weijie Gao wrote:
> This patch adds documentation for MediaTek MT7621 NAND flash controller
> driver.
>
> Signed-off-by: Weijie Gao <weijie.gao@mediatek.com>
> ---
> .../bindings/mtd/mediatek,mt7621-nfc.yaml | 68 +++++++++++++++++++
> 1 file changed, 68 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/mtd/mediatek,mt7621-nfc.example.dt.yaml: nand@1e003000: reg: [[503328768, 2048, 503330816, 2048]] is too short
See https://patchwork.ozlabs.org/patch/1264888
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:
pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade
Please check and re-submit.
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller
2020-03-31 18:07 [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Weijie Gao
2020-03-31 18:07 ` [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver Weijie Gao
@ 2020-03-31 18:47 ` Boris Brezillon
1 sibling, 0 replies; 4+ messages in thread
From: Boris Brezillon @ 2020-03-31 18:47 UTC (permalink / raw)
To: Weijie Gao
Cc: Mark Rutland, devicetree, Anders Roxell, Vignesh Raghavendra,
Arnd Bergmann, Boris Brezillon, Richard Weinberger,
Brendan Higgins, linux-kernel, Frieder Schrempf, linux-mips,
Greg Kroah-Hartman, Rob Herring, linux-mediatek, Miquel Raynal,
Matthias Brugger, linux-mtd, Mason Yang, Piotr Sroka
On Wed, 1 Apr 2020 02:07:58 +0800
Weijie Gao <weijie.gao@mediatek.com> wrote:
> 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.
>
Please don't duplicate driver the driver for those minor differences:
use regmap/reg_field to abstract reg field positions and make the DMA
optional.
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2020-03-31 22:01 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-31 18:07 [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Weijie Gao
2020-03-31 18:07 ` [PATCH 2/2] dt-bindings: add documentation for mt7621-nand driver Weijie Gao
2020-03-31 22:00 ` Rob Herring
2020-03-31 18:47 ` [PATCH 1/2] mtd: rawnand: add driver support for MT7621 nand flash controller Boris Brezillon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).