All of lore.kernel.org
 help / color / mirror / Atom feed
From: Johan Jonker <jbx6244@gmail.com>
To: miquel.raynal@bootlin.com
Cc: richard@nod.at, vigneshr@ti.com, robh+dt@kernel.org,
	mark.rutland@arm.com, heiko@sntech.de,
	linux-mtd@lists.infradead.org, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-rockchip@lists.infradead.org, linux-kernel@vger.kernel.org,
	shawn.lin@rock-chips.com, yifeng.zhao@rock-chips.com
Subject: [RFC PATCH v2 02/10] mtd: rawnand: rockchip: Add NAND controller driver
Date: Fri, 24 Jan 2020 17:29:53 +0100	[thread overview]
Message-ID: <20200124163001.28910-3-jbx6244@gmail.com> (raw)
In-Reply-To: <20200124163001.28910-1-jbx6244@gmail.com>

From: Yifeng Zhao <zyf@rock-chips.com>

Add basic Rockchip NAND controller driver.

Compatible with hardware version 6 and 9.
V6:16, 24, 40, 60 per 1024B BCH/ECC.
V9:16, 40, 60, 70 per 1024B BCH/ECC.
8 bit asynchronous flash interface support.
Supports up to 2 identical nandc nodes.
Max 4 nand chips per controller.
Able to select a different hardware ecc setup
for the loader blocks.
No bad block support.

Signed-off-by: Yifeng Zhao <zyf@rock-chips.com>
Signed-off-by: Johan Jonker <jbx6244@gmail.com>
---
 drivers/mtd/nand/raw/Kconfig                    |    9 +
 drivers/mtd/nand/raw/Makefile                   |    1 +
 drivers/mtd/nand/raw/rockchip-nand-controller.c | 1307 +++++++++++++++++++++++
 3 files changed, 1317 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c

diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 74fb91ade..acee3d6ee 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,15 @@ config MTD_NAND_CADENCE
 	  Enable the driver for NAND flash on platforms using a Cadence NAND
 	  controller.
 
+config MTD_NAND_ROCKCHIP
+	tristate "Rockchip raw NAND controller driver"
+	depends on ARCH_ROCKCHIP || COMPILE_TEST
+	depends on HAS_IOMEM
+	help
+	  Enables support for the Rockchip raw NAND controller driver.
+	  This controller is found on:
+	  px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108 SoCs.
+
 comment "Misc"
 
 config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158..a54aa85f4 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
 obj-$(CONFIG_MTD_NAND_STM32_FMC2)	+= stm32_fmc2_nand.o
 obj-$(CONFIG_MTD_NAND_MESON)		+= meson_nand.o
 obj-$(CONFIG_MTD_NAND_CADENCE)		+= cadence-nand-controller.o
+obj-$(CONFIG_MTD_NAND_ROCKCHIP)		+= rockchip-nand-controller.o
 
 nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
 nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
new file mode 100644
index 000000000..76ed1c08d
--- /dev/null
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on:
+ * https://github.com/rockchip-linux/kernel
+ *
+ * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@rock-chips.com
+ *
+ * Support NAND controller versions 6 and 9 found on SoCs:
+ * px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108
+ *
+ * Copyright (c) 2020 Johan Jonker jbx6244@gmail.com
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/mtd/rawnand.h>
+
+#define RK_NANDC_ID_V600		0x56363030
+#define RK_NANDC_ID_V622		0x56363232
+#define RK_NANDC_ID_V701		0x701
+#define RK_NANDC_ID_V800		0x56383030
+#define RK_NANDC_ID_V801		0x801
+#define RK_NANDC_ID_V900		0x56393030
+
+#define RK_NANDC_IDB_Res_Blk_Num	16
+#define RK_NANDC_IDB_Ecc_Bits		24
+
+#define RK_NANDC_V6_ECC_16		0x00000000
+#define RK_NANDC_V6_ECC_24		0x00000010
+#define RK_NANDC_V6_ECC_40		0x00040000
+#define RK_NANDC_V6_ECC_60		0x00040010
+
+#define RK_NANDC_V9_ECC_16		0x02000001
+#define RK_NANDC_V9_ECC_40		0x04000001
+#define RK_NANDC_V9_ECC_60		0x06000001
+#define RK_NANDC_V9_ECC_70		0x00000001
+
+#define RK_NANDC_NUM_BANKS		8
+#define RK_NANDC_DEF_TIMEOUT		10000
+
+#define RK_NANDC_REG_DATA		0x00
+#define RK_NANDC_REG_ADDR		0x04
+#define RK_NANDC_REG_CMD		0x08
+
+/* register offset nandc version 6 */
+#define RK_NANDC_REG_V6_FMCTL		0x00
+#define RK_NANDC_REG_V6_FMWAIT		0x04
+#define RK_NANDC_REG_V6_FLCTL		0x08
+#define RK_NANDC_REG_V6_BCHCTL		0x0c
+#define RK_NANDC_REG_V6_DMA_CFG		0x10
+#define RK_NANDC_REG_V6_DMA_BUF0	0x14
+#define RK_NANDC_REG_V6_DMA_BUF1	0x18
+#define RK_NANDC_REG_V6_DMA_ST		0x1C
+#define RK_NANDC_REG_V6_BCHST		0x20
+#define RK_NANDC_REG_V6_RANDMZ		0x150
+#define RK_NANDC_REG_V6_VER		0x160
+#define RK_NANDC_REG_V6_INTEN		0x16C
+#define RK_NANDC_REG_V6_INTCLR		0x170
+#define RK_NANDC_REG_V6_INTST		0x174
+#define RK_NANDC_REG_V6_SPARE0		0x200
+#define RK_NANDC_REG_V6_SPARE1		0x230
+
+/* register offset nandc version 9 */
+#define RK_NANDC_REG_V9_FMCTL		0x00
+#define RK_NANDC_REG_V9_FMWAIT		0x04
+#define RK_NANDC_REG_V9_FLCTL		0x10
+#define RK_NANDC_REG_V9_BCHCTL		0x20
+#define RK_NANDC_REG_V9_DMA_CFG		0x30
+#define RK_NANDC_REG_V9_DMA_BUF0	0x34
+#define RK_NANDC_REG_V9_DMA_BUF1	0x38
+#define RK_NANDC_REG_V9_DMA_ST		0x40
+#define RK_NANDC_REG_V9_VER		0x80
+#define RK_NANDC_REG_V9_INTEN		0x120
+#define RK_NANDC_REG_V9_INTCLR		0x124
+#define RK_NANDC_REG_V9_INTST		0x128
+#define RK_NANDC_REG_V9_BCHST		0x150
+#define RK_NANDC_REG_V9_SPARE0		0x200
+#define RK_NANDC_REG_V9_SPARE1		0x204
+#define RK_NANDC_REG_V9_RANDMZ		0x208
+
+/* register offset nandc common */
+#define RK_NANDC_REG_BANK0		0x800
+#define RK_NANDC_REG_SRAM0		0x1000
+
+/* calculate bank_base */
+#define RK_NANDC_BANK_SIZE		0x100
+
+#define RK_REG(ctrl, off) \
+	((ctrl)->regs + \
+	RK_NANDC_REG_BANK0 + \
+	(ctrl)->selected_bank * RK_NANDC_BANK_SIZE + \
+	(off))
+
+#define RK_REG_DATA(ctrl) RK_REG(ctrl, RK_NANDC_REG_DATA)
+#define RK_REG_ADDR(ctrl) RK_REG(ctrl, RK_NANDC_REG_ADDR)
+#define RK_REG_CMD(ctrl)  RK_REG(ctrl, RK_NANDC_REG_CMD)
+
+/* FMCTL */
+#define RK_NANDC_V6_FM_WP		BIT(8)
+#define RK_NANDC_V6_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V6_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V6_FM_FREADY		BIT(9)
+
+#define RK_NANDC_V9_FM_WP		BIT(8)
+#define RK_NANDC_V9_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V9_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V9_RDY			BIT(9)
+
+/* FLCTL */
+#define RK_NANDC_V6_FL_RST		BIT(0)
+#define RK_NANDC_V6_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V6_FL_XFER_START	BIT(2)
+#define RK_NANDC_V6_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V6_FL_ST_BUF_S		0x4
+#define RK_NANDC_V6_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V6_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V6_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V6_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V6_FL_ASYNC_TOG_MIX	BIT(29)
+
+#define RK_NANDC_V9_FL_RST		BIT(0)
+#define RK_NANDC_V9_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V9_FL_XFER_START	BIT(2)
+#define RK_NANDC_V9_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V9_FL_ST_BUF_S		0x4
+#define RK_NANDC_V9_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V9_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V9_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V9_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V9_FL_ASYNC_TOG_MIX	BIT(29)
+
+/* BCHCTL */
+#define RK_NANDC_V6_BCH_REGION_S	0x5
+#define RK_NANDC_V6_BCH_REGION_M	0x7
+
+#define RK_NANDC_V9_BCH_MODE_S		25
+#define RK_NANDC_V9_BCH_MODE_M		0x7
+
+/* BCHST */
+#define RK_NANDC_V6_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V6_BCH1_ST_ERR		BIT(15)
+#define RK_NANDC_V6_ECC_ERR_CNT0(x)	(((((x) & (0x1F << 3)) >> 3) \
+					| (((x) & (1 << 27)) >> 22)) & 0x3F)
+#define RK_NANDC_V6_ECC_ERR_CNT1(x)	(((((x) & (0x1F << 16)) >> 16) \
+					| (((x) & (1 << 29)) >> 24)) & 0x3F)
+
+#define RK_NANDC_V9_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V9_BCH1_ST_ERR		BIT(18)
+#define RK_NANDC_V9_ECC_ERR_CNT0(x)	(((x) & (0x7F << 3)) >> 3)
+#define RK_NANDC_V9_ECC_ERR_CNT1(x)	(((x) & (0x7F << 19)) >> 19)
+
+/* DMA_CFG */
+#define RK_NANDC_V6_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V6_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V6_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V6_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V6_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V6_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V6_DMA_CFG_BURST_1	0
+#define RK_NANDC_V6_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V6_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+#define RK_NANDC_V9_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V9_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V9_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V9_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V9_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V9_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V9_DMA_CFG_BURST_1	0
+#define RK_NANDC_V9_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V9_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+/* INTEN */
+#define RK_NANDC_V6_INT_DMA		BIT(0)
+
+#define RK_NANDC_V9_INT_DMA		BIT(0)
+
+/*
+ * The bit positions for
+ * RK_NANDC_REG_V6_FMCTL and RK_NANDC_REG_V9_FMCTL,
+ * RK_NANDC_REG_V6_FLCTL and RK_NANDC_REG_V9_FLCTL,
+ * RK_NANDC_REG_V6_DMA_CFG and RK_NANDC_REG_V9_DMA_CFG
+ * are identical.
+ */
+
+enum rk_nandc_version {
+	VERSION_6 = 6,
+	VERSION_9 = 9,
+};
+
+struct rk_nandc_reg_offset {
+	u32 fmctl;
+	u32 fmwait;
+	u32 flctl;
+	u32 bchctl;
+	u32 dma_cfg;
+	u32 dma_buf0;
+	u32 dma_buf1;
+	u32 dma_st;
+	u32 bchst;
+	u32 randmz;
+	u32 ver;
+	u32 inten;
+	u32 intclr;
+	u32 intst;
+	u32 spare0;
+	u32 spare1;
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V6_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V6_FMCTL,
+	.fmwait =   RK_NANDC_REG_V6_FMWAIT,
+	.flctl =    RK_NANDC_REG_V6_FLCTL,
+	.bchctl =   RK_NANDC_REG_V6_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V6_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V6_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V6_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V6_DMA_ST,
+	.bchst =    RK_NANDC_REG_V6_BCHST,
+	.randmz =   RK_NANDC_REG_V6_RANDMZ,
+	.ver =      RK_NANDC_REG_V6_VER,
+	.inten =    RK_NANDC_REG_V6_INTEN,
+	.intclr =   RK_NANDC_REG_V6_INTCLR,
+	.intst =    RK_NANDC_REG_V6_INTST,
+	.spare0 =   RK_NANDC_REG_V6_SPARE0,
+	.spare1 =   RK_NANDC_REG_V6_SPARE1,
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V9_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V9_FMCTL,
+	.fmwait =   RK_NANDC_REG_V9_FMWAIT,
+	.flctl =    RK_NANDC_REG_V9_FLCTL,
+	.bchctl =   RK_NANDC_REG_V9_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V9_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V9_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V9_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V9_DMA_ST,
+	.bchst =    RK_NANDC_REG_V9_BCHST,
+	.randmz =   RK_NANDC_REG_V9_RANDMZ,
+	.ver =      RK_NANDC_REG_V9_VER,
+	.inten =    RK_NANDC_REG_V9_INTEN,
+	.intclr =   RK_NANDC_REG_V9_INTCLR,
+	.intst =    RK_NANDC_REG_V9_INTST,
+	.spare0 =   RK_NANDC_REG_V9_SPARE0,
+	.spare1 =   RK_NANDC_REG_V9_SPARE1,
+};
+
+struct rk_nandc_cap {
+	u8 max_cs;
+};
+
+static const struct rk_nandc_cap rk_nandc_V600_cap = {
+	.max_cs = 8,
+};
+
+static const struct rk_nandc_cap rk_nandc_V622_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V701_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V801_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V900_cap = {
+	.max_cs = 4,
+};
+
+struct rk_nandc_data {
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_ctrl {
+	struct device *dev;
+	void __iomem *regs;
+	int irq;
+	struct clk *hclk;
+	struct clk *clk;
+	struct list_head chips;
+	struct completion complete;
+	struct nand_controller controller;
+	int cs[RK_NANDC_NUM_BANKS];
+	u32 *oob_buf;
+	u32 *page_buf;
+	int selected_bank;
+	unsigned long assigned_cs;
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_chip {
+	struct nand_chip nand;
+	struct list_head node;
+	bool bootromblocks;
+	int ecc_mode;
+	int max_ecc_strength;
+	int idbresblknum;
+};
+
+static struct rk_nand_chip *to_rk_nand(struct nand_chip *nand)
+{
+	return container_of(nand, struct rk_nand_chip, nand);
+}
+
+static void rk_nandc_init(struct rk_nand_ctrl *ctrl)
+{
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->randmz);
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed(RK_NANDC_V6_FM_WP, ctrl->regs + ctrl->ofs->fmctl);
+	writel_relaxed(RK_NANDC_V6_FL_RST, ctrl->regs + ctrl->ofs->flctl);
+	writel_relaxed(0x1081, ctrl->regs + ctrl->ofs->fmwait);
+}
+
+static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
+{
+	struct rk_nand_ctrl *ctrl = dev_id;
+
+	u32 st = readl(ctrl->regs + ctrl->ofs->intst);
+	u32 ien = readl(ctrl->regs + ctrl->ofs->inten);
+
+	if (!(ien & st))
+		return IRQ_NONE;
+
+	if ((ien & st) == ien)
+		complete(&ctrl->complete);
+
+	writel_relaxed(st, ctrl->regs + ctrl->ofs->intclr);
+	writel_relaxed(~st & ien, ctrl->regs + ctrl->ofs->inten);
+
+	return IRQ_HANDLED;
+}
+
+static void rk_nandc_select_chip(struct nand_chip *nand, int chipnr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+	int banknr;
+
+	reg = readl(ctrl->regs + ctrl->ofs->fmctl);
+	reg &= ~RK_NANDC_V6_FM_CE_SEL_M;
+
+	if (chipnr == -1) {
+		banknr = -1;
+	} else {
+		banknr = ctrl->cs[chipnr];
+
+		reg |= RK_NANDC_V6_FM_CE_SEL(banknr);
+	}
+
+	ctrl->selected_bank = banknr;
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->fmctl);
+}
+
+static int rk_nandc_hw_ecc_setup(struct nand_chip *nand,
+				 u32 strength)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+
+	nand->ecc.strength = strength;
+	/* HW ECC always request ECC bytes for 1024 bytes blocks */
+	nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * fls(8 * 1024), 8);
+	/* HW ECC only works with an even number of ECC bytes */
+	nand->ecc.bytes = ALIGN(nand->ecc.bytes, 2);
+
+	if (ctrl->version == VERSION_9) {
+		switch (nand->ecc.strength) {
+		case 70:
+			reg = RK_NANDC_V9_ECC_70;
+			break;
+		case 60:
+			reg = RK_NANDC_V9_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V9_ECC_40;
+			break;
+		case 16:
+			reg = RK_NANDC_V9_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		switch (nand->ecc.strength) {
+		case 60:
+			reg = RK_NANDC_V6_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V6_ECC_40;
+			break;
+		case 24:
+			reg = RK_NANDC_V6_ECC_24;
+			break;
+		case 16:
+			reg = RK_NANDC_V6_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+
+	return 0;
+}
+
+static void rk_nandc_xfer_start(struct rk_nand_ctrl *ctrl,
+				u8 dir, u8 n_KB,
+				dma_addr_t dma_data,
+				dma_addr_t dma_oob)
+{
+	u32 reg;
+
+	if (ctrl->version == VERSION_6) {
+		reg = readl(ctrl->regs + ctrl->ofs->bchctl);
+		reg = (reg & (~(RK_NANDC_V6_BCH_REGION_M <<
+				RK_NANDC_V6_BCH_REGION_S))) |
+		      (ctrl->selected_bank << RK_NANDC_V6_BCH_REGION_S);
+		writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+	}
+
+	reg = RK_NANDC_V6_DMA_CFG_WR_ST |
+	      RK_NANDC_V6_DMA_CFG_WR(dir) |
+	      RK_NANDC_V6_DMA_CFG_BUS_MODE |
+	      RK_NANDC_V6_DMA_CFG_HSIZE_32 |
+	      RK_NANDC_V6_DMA_CFG_BURST_16 |
+	      RK_NANDC_V6_DMA_CFG_INCR_NUM(16);
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed((u32)dma_data, ctrl->regs + ctrl->ofs->dma_buf0);
+	writel_relaxed((u32)dma_oob, ctrl->regs + ctrl->ofs->dma_buf1);
+
+	reg = RK_NANDC_V6_FL_DIR(dir) |
+	      RK_NANDC_V6_FL_XFER_EN |
+	      RK_NANDC_V6_FL_XFER_COUNT |
+	      RK_NANDC_V6_FL_ACORRECT |
+	      RK_NANDC_V6_FL_PAGE_NUM(n_KB) |
+	      RK_NANDC_V6_FL_ASYNC_TOG_MIX;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+	reg |= RK_NANDC_V6_FL_XFER_START;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+}
+
+static int rk_nandc_wait_for_xfer_done(struct rk_nand_ctrl *ctrl)
+{
+	u32 reg;
+	int ret;
+
+	void __iomem *ptr = ctrl->regs + ctrl->ofs->flctl;
+
+	ret = readl_poll_timeout_atomic(ptr, reg,
+					reg & RK_NANDC_V6_FL_XFER_READY,
+					1, RK_NANDC_DEF_TIMEOUT);
+
+	return ret;
+}
+
+static void rk_nandc_hw_ecc_setup_helper(struct nand_chip *nand,
+					 int page, bool entry)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+
+	int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+	/* only bank 0 used for boot rom blocks */
+	if ((page < pages_per_blk * rk_nand->idbresblknum) &&
+	    rk_nand->bootromblocks &&
+	    !ctrl->selected_bank &&
+	    entry)
+		rk_nandc_hw_ecc_setup(nand, RK_NANDC_IDB_Ecc_Bits);
+	else
+		rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+}
+
+static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand,
+					      u8 *buf,
+					      int oob_required,
+					      int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	int max_bitflips = 0;
+	dma_addr_t dma_data, dma_oob;
+	int ret, i;
+	int bch_st;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_FROM_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_FROM_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 0, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(5));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_FROM_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_FROM_DEVICE);
+
+	if (ret)
+		goto ecc_setup;
+
+	memcpy(buf, ctrl->page_buf, mtd->writesize);
+
+	if (oob_required) {
+		u8 *oob;
+		u32 tmp;
+
+		for (i = 0; i < ecc->steps; i++) {
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			if (ctrl->version == VERSION_9) {
+				tmp = ctrl->oob_buf[i];
+			} else {
+				u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+					      ? 64 : 128;
+				tmp = ctrl->oob_buf[i * oob_step / 4];
+			}
+			*oob++ = (u8)tmp;
+			*oob++ = (u8)(tmp >> 8);
+			*oob++ = (u8)(tmp >> 16);
+			*oob++ = (u8)(tmp >> 24);
+		}
+	}
+
+	if (ctrl->version == VERSION_9) {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V9_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V9_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V9_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V9_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	} else {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V6_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V6_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V6_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V6_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	}
+
+	ret = max_bitflips;
+	if (max_bitflips == -1) {
+		dev_err(ctrl->dev, "BCH status error\n");
+		ret = -ENODATA;
+	}
+
+ecc_setup:
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	return ret;
+}
+
+static int rk_nandc_hw_syndrome_ecc_write_page(struct nand_chip *nand,
+					       const u8 *buf,
+					       int oob_required,
+					       int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	dma_addr_t dma_data, dma_oob;
+	int i, ret;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+	for (i = 0; i < ecc->steps; i++) {
+		u32 tmp;
+
+		if (oob_required) {
+			u8 *oob;
+
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			tmp = oob[0] |
+			      (oob[1] << 8) |
+			      (oob[2] << 16) |
+			      (oob[3] << 24);
+		} else {
+			tmp = 0xFFFFFFFF;
+		}
+		if (ctrl->version == VERSION_9) {
+			ctrl->oob_buf[i] = tmp;
+		} else {
+			u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+				      ? 64 : 128;
+			ctrl->oob_buf[i * oob_step / 4] = tmp;
+		}
+	}
+
+	memcpy(ctrl->page_buf, buf, mtd->writesize);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_TO_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_TO_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 1, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete,
+					  msecs_to_jiffies(10));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_TO_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_TO_DEVICE);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	if (!ret)
+		ret = nand_prog_page_end_op(nand);
+
+	return ret;
+}
+
+static int rk_nandc_hw_ecc_read_oob(struct nand_chip *nand, int page)
+{
+	u8 *buf = nand_get_data_buf(nand);
+
+	return nand->ecc.read_page(nand, buf, true, page);
+}
+
+static int rk_nandc_hw_ecc_write_oob(struct nand_chip *nand, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	int ret;
+	u8 *buf = nand_get_data_buf(nand);
+
+	memset(buf, 0xFF, mtd->writesize);
+	ret = nand->ecc.write_page(nand, buf, true, page);
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(nand);
+}
+
+static void rk_nandc_read_buf(struct nand_chip *nand, u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		buf[offs] = readb(bank_base);
+}
+
+static void rk_nandc_write_buf(struct nand_chip *nand, const u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		writeb(buf[offs], bank_base);
+}
+
+static void rk_nandc_write_cmd(struct nand_chip *nand, u8 cmd)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_CMD(ctrl);
+
+	writeb(cmd, bank_base);
+}
+
+static void rk_nandc_write_addr(struct nand_chip *nand, u8 addr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_ADDR(ctrl);
+
+	writeb(addr, bank_base);
+}
+
+static int rk_nandc_dev_ready(struct nand_chip *nand)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	if (readl(ctrl->regs + ctrl->ofs->fmctl) & RK_NANDC_V6_FM_FREADY)
+		return 1;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section +
+			    nand->ecc.prepad;
+	oobregion->length = nand->ecc.steps * nand->ecc.bytes;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section;
+	oobregion->length = nand->ecc.steps * nand->ecc.prepad;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops rk_nandc_oob_ops = {
+	.ecc = rk_nandc_ooblayout_ecc,
+	.free = rk_nandc_ooblayout_free,
+};
+
+static int rk_nandc_buffer_init(struct nand_chip *nand)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	ctrl->page_buf = devm_kzalloc(ctrl->dev,
+				      mtd->writesize,
+				      GFP_KERNEL | GFP_DMA);
+	if (!ctrl->page_buf)
+		return -ENOMEM;
+
+	ctrl->oob_buf = devm_kzalloc(ctrl->dev,
+				     nand->ecc.steps * 128,
+				     GFP_KERNEL | GFP_DMA);
+	if (!ctrl->oob_buf) {
+		devm_kfree(ctrl->dev, ctrl->page_buf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_hw_ecc_ctrl_init(struct nand_chip *nand)
+{
+	u8 strengths_v6[] = {60, 40, 24, 16};
+	u8 strengths_v9[] = {70, 60, 40, 16};
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+	int max_strength;
+	u32 i, ver;
+
+	if (nand->options & NAND_IS_BOOT_MEDIUM)
+		rk_nand->bootromblocks = true;
+	else
+		rk_nand->bootromblocks = false;
+
+	nand->ecc.prepad = 4;
+	nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+	max_strength = ((mtd->oobsize / nand->ecc.steps) - 4) * 8 / 14;
+	if (ctrl->version == VERSION_9) {
+		rk_nand->max_ecc_strength = 70;
+		ver = readl(ctrl->regs + RK_NANDC_REG_V9_VER);
+		if (ver != RK_NANDC_ID_V900)
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v9); i++) {
+			if (max_strength >= strengths_v9[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v9)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v9[i];
+	} else {
+		rk_nand->max_ecc_strength = 60;
+
+		ver = readl(ctrl->regs + RK_NANDC_REG_V6_VER);
+		if (ver == RK_NANDC_ID_V801)
+			rk_nand->max_ecc_strength = 16;
+		else if (ver == RK_NANDC_ID_V600 ||
+			 ver == RK_NANDC_ID_V622 ||
+			 ver == RK_NANDC_ID_V701 ||
+			 ver == RK_NANDC_ID_V800)
+			rk_nand->max_ecc_strength = 60;
+		else
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v6); i++) {
+			if (max_strength >= strengths_v6[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v6)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v6[i];
+	}
+	rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+
+	mtd_set_ooblayout(mtd, &rk_nandc_oob_ops);
+
+	if (mtd->oobsize < ((nand->ecc.bytes + nand->ecc.prepad) *
+			    nand->ecc.steps)) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_attach_chip(struct nand_chip *nand)
+{
+	int ret;
+
+	switch (nand->ecc.mode) {
+	case NAND_ECC_HW_SYNDROME:
+	case NAND_ECC_HW:
+		ret = rk_nandc_hw_ecc_ctrl_init(nand);
+		if (ret)
+			return ret;
+		ret = rk_nandc_buffer_init(nand);
+		if (ret)
+			return -ENOMEM;
+		nand->ecc.read_page = rk_nandc_hw_syndrome_ecc_read_page;
+		nand->ecc.write_page = rk_nandc_hw_syndrome_ecc_write_page;
+		nand->ecc.read_oob = rk_nandc_hw_ecc_read_oob;
+		nand->ecc.write_oob = rk_nandc_hw_ecc_write_oob;
+		break;
+	case NAND_ECC_NONE:
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_exec_op(struct nand_chip *nand,
+			    const struct nand_operation *op,
+			    bool check_only)
+{
+	int i;
+	unsigned int op_id;
+	const struct nand_op_instr *instr = NULL;
+
+	rk_nandc_select_chip(nand, op->cs);
+
+	if (check_only)
+		return 0;
+
+	for (op_id = 0; op_id < op->ninstrs; op_id++) {
+		instr = &op->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			rk_nandc_write_cmd(nand, instr->ctx.cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			for (i = 0; i < instr->ctx.addr.naddrs; i++)
+				rk_nandc_write_addr(nand,
+						    instr->ctx.addr.addrs[i]);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			rk_nandc_read_buf(nand, instr->ctx.data.buf.in,
+					  instr->ctx.data.len);
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			rk_nandc_write_buf(nand, instr->ctx.data.buf.out,
+					   instr->ctx.data.len);
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			rk_nandc_dev_ready(nand);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static const struct nand_controller_ops rk_nand_controller_ops = {
+	.attach_chip = rk_nandc_attach_chip,
+	.exec_op = rk_nandc_exec_op,
+};
+
+static int rk_nandc_chip_init(struct rk_nand_ctrl *ctrl,
+			      struct device_node *np)
+{
+	struct rk_nand_chip *rk_nand;
+	struct nand_chip *nand;
+	struct mtd_info *mtd;
+	int nsels, ret, i;
+	u32 cs, val;
+
+	nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32));
+	if (nsels <= 0) {
+		dev_err(ctrl->dev, "missing/invalid reg property\n");
+		return -EINVAL;
+	}
+
+	rk_nand = devm_kzalloc(ctrl->dev, sizeof(*rk_nand), GFP_KERNEL);
+	if (!rk_nand)
+		return -ENOMEM;
+
+	rk_nand->idbresblknum = RK_NANDC_IDB_Res_Blk_Num;
+
+	for (i = 0; i < nsels; i++) {
+		ret = of_property_read_u32_index(np, "reg", i, &cs);
+		if (ret) {
+			dev_err(ctrl->dev,
+				"could not retrieve reg property: %d\n",
+				ret);
+			return ret;
+		}
+
+		if (cs >= ctrl->cap->max_cs) {
+			dev_err(ctrl->dev,
+				"invalid reg value: %u (max CS = %d)\n",
+				cs,
+				ctrl->cap->max_cs);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &ctrl->assigned_cs)) {
+			dev_err(ctrl->dev,
+				"CS %d already assigned\n",
+				cs);
+			return -EINVAL;
+		}
+
+		ctrl->cs[i] = cs;
+
+		/* only bank 0 used for boot rom blocks */
+		if (!of_property_read_u32_index(np, "rockchip,idb-res-blk-num",
+						i, &val)) {
+			if (!cs && val >= 2) {
+				rk_nand->idbresblknum = val;
+			} else {
+				dev_err(ctrl->dev,
+					"invalid idb-res-blk-num\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	nand = &rk_nand->nand;
+
+	nand_set_flash_node(nand, np);
+	nand_set_controller_data(nand, ctrl);
+
+	nand->controller = &ctrl->controller;
+	nand->controller->ops = &rk_nand_controller_ops;
+
+	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+	nand->ecc.size = 1024;
+	nand->ecc.strength = 40;
+
+	nand->options = NAND_SKIP_BBTSCAN | NAND_NO_SUBPAGE_WRITE;
+
+	mtd = nand_to_mtd(nand);
+	mtd->dev.parent = ctrl->dev;
+	mtd->name = devm_kasprintf(ctrl->dev, GFP_KERNEL, "%s",
+				   dev_name(ctrl->dev));
+
+	ret = nand_scan(nand, nsels);
+	if (ret)
+		return ret;
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(ctrl->dev, "mtd device register failed: %d\n", ret);
+		nand_cleanup(nand);
+		return ret;
+	}
+
+	list_add_tail(&rk_nand->node, &ctrl->chips);
+
+	return 0;
+}
+
+static void rk_nandc_cleanup_chips(struct rk_nand_ctrl *ctrl)
+{
+	struct rk_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &ctrl->chips, node) {
+		nand_release(&entry->nand);
+		list_del(&entry->node);
+	}
+}
+
+static int rk_nandc_chips_init(struct rk_nand_ctrl *ctrl)
+{
+	struct device_node *np = ctrl->dev->of_node;
+	struct device_node *nand_np;
+	int nchips;
+	int ret;
+
+	nchips = of_get_child_count(np);
+
+	if (nchips > ctrl->cap->max_cs) {
+		dev_err(ctrl->dev, "too many NAND chips: %d (max CS = %d)\n",
+			nchips,
+			ctrl->cap->max_cs);
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = rk_nandc_chip_init(ctrl, nand_np);
+		if (ret) {
+			rk_nandc_cleanup_chips(ctrl);
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rk_nandc_probe(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl;
+	const struct rk_nandc_data *data;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -ENODEV;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->dev = dev;
+	ctrl->version = data->version;
+	ctrl->ofs = data->ofs;
+	ctrl->cap = data->cap;
+
+	ctrl->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ctrl->regs)) {
+		dev_err(dev, "ioremap failed\n");
+		return PTR_ERR(ctrl->regs);
+	}
+
+	ctrl->irq = platform_get_irq(pdev, 0);
+	if (ctrl->irq < 0) {
+		dev_err(dev, "get irq failed\n");
+		return ctrl->irq;
+	}
+
+	ctrl->hclk = devm_clk_get(dev, "hclk_nandc");
+	if (IS_ERR(ctrl->hclk)) {
+		dev_err(dev, "get hclk_nandc failed\n");
+		return PTR_ERR(ctrl->hclk);
+	}
+
+	ret = clk_prepare_enable(ctrl->hclk);
+	if (ret)
+		return ret;
+
+	ctrl->clk = devm_clk_get(dev, "clk_nandc");
+	if (!(IS_ERR(ctrl->clk))) {
+		clk_set_rate(ctrl->clk, 150 * 1000 * 1000);
+
+		ret = clk_prepare_enable(ctrl->clk);
+		if (ret)
+			goto err_disable_hclk;
+	} else {
+		dev_dbg(dev, "get clk_nandc failed\n");
+	}
+
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->inten);
+	ret = devm_request_irq(dev, ctrl->irq, rk_nandc_interrupt,
+			       0, "nandc", ctrl);
+	if (ret)
+		goto err_disable_clk;
+
+	init_completion(&ctrl->complete);
+	nand_controller_init(&ctrl->controller);
+	INIT_LIST_HEAD(&ctrl->chips);
+
+	rk_nandc_init(ctrl);
+
+	ret = rk_nandc_chips_init(ctrl);
+	if (ret) {
+		dev_err(dev, "init nand chips failed\n");
+		goto err_disable_clk;
+	}
+
+	platform_set_drvdata(pdev, ctrl);
+
+	return 0;
+
+err_disable_clk:
+	clk_disable_unprepare(ctrl->clk);
+err_disable_hclk:
+	clk_disable_unprepare(ctrl->hclk);
+
+	return ret;
+}
+
+static int rk_nandc_remove(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static void rk_nandc_shutdown(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+}
+
+static const struct rk_nandc_data rk_nandc_V600_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V600_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V622_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V622_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V701_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V701_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V801_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V801_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V900_data = {
+	.version = VERSION_9,
+	.ofs = (void *)&rk_nandc_V9_reg_offset,
+	.cap = (void *)&rk_nandc_V900_cap,
+};
+
+static const struct of_device_id of_rk_nandc_match[] = {
+	{
+		.compatible = "rockchip,px30-nand-controller",
+		.data = &rk_nandc_V900_data,
+	},
+	{
+		.compatible = "rockchip,rk3066-nand-controller",
+		.data = &rk_nandc_V600_data,
+	},
+	{
+		.compatible = "rockchip,rk3228-nand-controller",
+		.data = &rk_nandc_V701_data,
+	},
+	{
+		.compatible = "rockchip,rk3288-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rk3308-nand-controller",
+		.data = &rk_nandc_V801_data,
+	},
+	{
+		.compatible = "rockchip,rk3368-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rv1108-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{ /* sentinel */ },
+};
+
+static struct platform_driver rk_nandc_driver = {
+	.probe  = rk_nandc_probe,
+	.remove = rk_nandc_remove,
+	.shutdown = rk_nandc_shutdown,
+	.driver = {
+		.name = "rockchip-nand-controller",
+		.of_match_table = of_rk_nandc_match,
+	},
+};
+module_platform_driver(rk_nandc_driver);
+
+MODULE_LICENSE("GPL v2");
-- 
2.11.0


WARNING: multiple messages have this Message-ID (diff)
From: Johan Jonker <jbx6244@gmail.com>
To: miquel.raynal@bootlin.com
Cc: mark.rutland@arm.com, devicetree@vger.kernel.org,
	vigneshr@ti.com, richard@nod.at, shawn.lin@rock-chips.com,
	linux-kernel@vger.kernel.org, linux-rockchip@lists.infradead.org,
	robh+dt@kernel.org, linux-mtd@lists.infradead.org,
	yifeng.zhao@rock-chips.com, linux-arm-kernel@lists.infradead.org,
	heiko@sntech.de
Subject: [RFC PATCH v2 02/10] mtd: rawnand: rockchip: Add NAND controller driver
Date: Fri, 24 Jan 2020 17:29:53 +0100	[thread overview]
Message-ID: <20200124163001.28910-3-jbx6244@gmail.com> (raw)
In-Reply-To: <20200124163001.28910-1-jbx6244@gmail.com>

From: Yifeng Zhao <zyf@rock-chips.com>

Add basic Rockchip NAND controller driver.

Compatible with hardware version 6 and 9.
V6:16, 24, 40, 60 per 1024B BCH/ECC.
V9:16, 40, 60, 70 per 1024B BCH/ECC.
8 bit asynchronous flash interface support.
Supports up to 2 identical nandc nodes.
Max 4 nand chips per controller.
Able to select a different hardware ecc setup
for the loader blocks.
No bad block support.

Signed-off-by: Yifeng Zhao <zyf@rock-chips.com>
Signed-off-by: Johan Jonker <jbx6244@gmail.com>
---
 drivers/mtd/nand/raw/Kconfig                    |    9 +
 drivers/mtd/nand/raw/Makefile                   |    1 +
 drivers/mtd/nand/raw/rockchip-nand-controller.c | 1307 +++++++++++++++++++++++
 3 files changed, 1317 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c

diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 74fb91ade..acee3d6ee 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,15 @@ config MTD_NAND_CADENCE
 	  Enable the driver for NAND flash on platforms using a Cadence NAND
 	  controller.
 
+config MTD_NAND_ROCKCHIP
+	tristate "Rockchip raw NAND controller driver"
+	depends on ARCH_ROCKCHIP || COMPILE_TEST
+	depends on HAS_IOMEM
+	help
+	  Enables support for the Rockchip raw NAND controller driver.
+	  This controller is found on:
+	  px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108 SoCs.
+
 comment "Misc"
 
 config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158..a54aa85f4 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
 obj-$(CONFIG_MTD_NAND_STM32_FMC2)	+= stm32_fmc2_nand.o
 obj-$(CONFIG_MTD_NAND_MESON)		+= meson_nand.o
 obj-$(CONFIG_MTD_NAND_CADENCE)		+= cadence-nand-controller.o
+obj-$(CONFIG_MTD_NAND_ROCKCHIP)		+= rockchip-nand-controller.o
 
 nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
 nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
new file mode 100644
index 000000000..76ed1c08d
--- /dev/null
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on:
+ * https://github.com/rockchip-linux/kernel
+ *
+ * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@rock-chips.com
+ *
+ * Support NAND controller versions 6 and 9 found on SoCs:
+ * px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108
+ *
+ * Copyright (c) 2020 Johan Jonker jbx6244@gmail.com
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/mtd/rawnand.h>
+
+#define RK_NANDC_ID_V600		0x56363030
+#define RK_NANDC_ID_V622		0x56363232
+#define RK_NANDC_ID_V701		0x701
+#define RK_NANDC_ID_V800		0x56383030
+#define RK_NANDC_ID_V801		0x801
+#define RK_NANDC_ID_V900		0x56393030
+
+#define RK_NANDC_IDB_Res_Blk_Num	16
+#define RK_NANDC_IDB_Ecc_Bits		24
+
+#define RK_NANDC_V6_ECC_16		0x00000000
+#define RK_NANDC_V6_ECC_24		0x00000010
+#define RK_NANDC_V6_ECC_40		0x00040000
+#define RK_NANDC_V6_ECC_60		0x00040010
+
+#define RK_NANDC_V9_ECC_16		0x02000001
+#define RK_NANDC_V9_ECC_40		0x04000001
+#define RK_NANDC_V9_ECC_60		0x06000001
+#define RK_NANDC_V9_ECC_70		0x00000001
+
+#define RK_NANDC_NUM_BANKS		8
+#define RK_NANDC_DEF_TIMEOUT		10000
+
+#define RK_NANDC_REG_DATA		0x00
+#define RK_NANDC_REG_ADDR		0x04
+#define RK_NANDC_REG_CMD		0x08
+
+/* register offset nandc version 6 */
+#define RK_NANDC_REG_V6_FMCTL		0x00
+#define RK_NANDC_REG_V6_FMWAIT		0x04
+#define RK_NANDC_REG_V6_FLCTL		0x08
+#define RK_NANDC_REG_V6_BCHCTL		0x0c
+#define RK_NANDC_REG_V6_DMA_CFG		0x10
+#define RK_NANDC_REG_V6_DMA_BUF0	0x14
+#define RK_NANDC_REG_V6_DMA_BUF1	0x18
+#define RK_NANDC_REG_V6_DMA_ST		0x1C
+#define RK_NANDC_REG_V6_BCHST		0x20
+#define RK_NANDC_REG_V6_RANDMZ		0x150
+#define RK_NANDC_REG_V6_VER		0x160
+#define RK_NANDC_REG_V6_INTEN		0x16C
+#define RK_NANDC_REG_V6_INTCLR		0x170
+#define RK_NANDC_REG_V6_INTST		0x174
+#define RK_NANDC_REG_V6_SPARE0		0x200
+#define RK_NANDC_REG_V6_SPARE1		0x230
+
+/* register offset nandc version 9 */
+#define RK_NANDC_REG_V9_FMCTL		0x00
+#define RK_NANDC_REG_V9_FMWAIT		0x04
+#define RK_NANDC_REG_V9_FLCTL		0x10
+#define RK_NANDC_REG_V9_BCHCTL		0x20
+#define RK_NANDC_REG_V9_DMA_CFG		0x30
+#define RK_NANDC_REG_V9_DMA_BUF0	0x34
+#define RK_NANDC_REG_V9_DMA_BUF1	0x38
+#define RK_NANDC_REG_V9_DMA_ST		0x40
+#define RK_NANDC_REG_V9_VER		0x80
+#define RK_NANDC_REG_V9_INTEN		0x120
+#define RK_NANDC_REG_V9_INTCLR		0x124
+#define RK_NANDC_REG_V9_INTST		0x128
+#define RK_NANDC_REG_V9_BCHST		0x150
+#define RK_NANDC_REG_V9_SPARE0		0x200
+#define RK_NANDC_REG_V9_SPARE1		0x204
+#define RK_NANDC_REG_V9_RANDMZ		0x208
+
+/* register offset nandc common */
+#define RK_NANDC_REG_BANK0		0x800
+#define RK_NANDC_REG_SRAM0		0x1000
+
+/* calculate bank_base */
+#define RK_NANDC_BANK_SIZE		0x100
+
+#define RK_REG(ctrl, off) \
+	((ctrl)->regs + \
+	RK_NANDC_REG_BANK0 + \
+	(ctrl)->selected_bank * RK_NANDC_BANK_SIZE + \
+	(off))
+
+#define RK_REG_DATA(ctrl) RK_REG(ctrl, RK_NANDC_REG_DATA)
+#define RK_REG_ADDR(ctrl) RK_REG(ctrl, RK_NANDC_REG_ADDR)
+#define RK_REG_CMD(ctrl)  RK_REG(ctrl, RK_NANDC_REG_CMD)
+
+/* FMCTL */
+#define RK_NANDC_V6_FM_WP		BIT(8)
+#define RK_NANDC_V6_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V6_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V6_FM_FREADY		BIT(9)
+
+#define RK_NANDC_V9_FM_WP		BIT(8)
+#define RK_NANDC_V9_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V9_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V9_RDY			BIT(9)
+
+/* FLCTL */
+#define RK_NANDC_V6_FL_RST		BIT(0)
+#define RK_NANDC_V6_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V6_FL_XFER_START	BIT(2)
+#define RK_NANDC_V6_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V6_FL_ST_BUF_S		0x4
+#define RK_NANDC_V6_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V6_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V6_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V6_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V6_FL_ASYNC_TOG_MIX	BIT(29)
+
+#define RK_NANDC_V9_FL_RST		BIT(0)
+#define RK_NANDC_V9_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V9_FL_XFER_START	BIT(2)
+#define RK_NANDC_V9_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V9_FL_ST_BUF_S		0x4
+#define RK_NANDC_V9_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V9_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V9_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V9_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V9_FL_ASYNC_TOG_MIX	BIT(29)
+
+/* BCHCTL */
+#define RK_NANDC_V6_BCH_REGION_S	0x5
+#define RK_NANDC_V6_BCH_REGION_M	0x7
+
+#define RK_NANDC_V9_BCH_MODE_S		25
+#define RK_NANDC_V9_BCH_MODE_M		0x7
+
+/* BCHST */
+#define RK_NANDC_V6_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V6_BCH1_ST_ERR		BIT(15)
+#define RK_NANDC_V6_ECC_ERR_CNT0(x)	(((((x) & (0x1F << 3)) >> 3) \
+					| (((x) & (1 << 27)) >> 22)) & 0x3F)
+#define RK_NANDC_V6_ECC_ERR_CNT1(x)	(((((x) & (0x1F << 16)) >> 16) \
+					| (((x) & (1 << 29)) >> 24)) & 0x3F)
+
+#define RK_NANDC_V9_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V9_BCH1_ST_ERR		BIT(18)
+#define RK_NANDC_V9_ECC_ERR_CNT0(x)	(((x) & (0x7F << 3)) >> 3)
+#define RK_NANDC_V9_ECC_ERR_CNT1(x)	(((x) & (0x7F << 19)) >> 19)
+
+/* DMA_CFG */
+#define RK_NANDC_V6_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V6_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V6_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V6_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V6_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V6_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V6_DMA_CFG_BURST_1	0
+#define RK_NANDC_V6_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V6_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+#define RK_NANDC_V9_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V9_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V9_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V9_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V9_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V9_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V9_DMA_CFG_BURST_1	0
+#define RK_NANDC_V9_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V9_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+/* INTEN */
+#define RK_NANDC_V6_INT_DMA		BIT(0)
+
+#define RK_NANDC_V9_INT_DMA		BIT(0)
+
+/*
+ * The bit positions for
+ * RK_NANDC_REG_V6_FMCTL and RK_NANDC_REG_V9_FMCTL,
+ * RK_NANDC_REG_V6_FLCTL and RK_NANDC_REG_V9_FLCTL,
+ * RK_NANDC_REG_V6_DMA_CFG and RK_NANDC_REG_V9_DMA_CFG
+ * are identical.
+ */
+
+enum rk_nandc_version {
+	VERSION_6 = 6,
+	VERSION_9 = 9,
+};
+
+struct rk_nandc_reg_offset {
+	u32 fmctl;
+	u32 fmwait;
+	u32 flctl;
+	u32 bchctl;
+	u32 dma_cfg;
+	u32 dma_buf0;
+	u32 dma_buf1;
+	u32 dma_st;
+	u32 bchst;
+	u32 randmz;
+	u32 ver;
+	u32 inten;
+	u32 intclr;
+	u32 intst;
+	u32 spare0;
+	u32 spare1;
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V6_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V6_FMCTL,
+	.fmwait =   RK_NANDC_REG_V6_FMWAIT,
+	.flctl =    RK_NANDC_REG_V6_FLCTL,
+	.bchctl =   RK_NANDC_REG_V6_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V6_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V6_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V6_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V6_DMA_ST,
+	.bchst =    RK_NANDC_REG_V6_BCHST,
+	.randmz =   RK_NANDC_REG_V6_RANDMZ,
+	.ver =      RK_NANDC_REG_V6_VER,
+	.inten =    RK_NANDC_REG_V6_INTEN,
+	.intclr =   RK_NANDC_REG_V6_INTCLR,
+	.intst =    RK_NANDC_REG_V6_INTST,
+	.spare0 =   RK_NANDC_REG_V6_SPARE0,
+	.spare1 =   RK_NANDC_REG_V6_SPARE1,
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V9_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V9_FMCTL,
+	.fmwait =   RK_NANDC_REG_V9_FMWAIT,
+	.flctl =    RK_NANDC_REG_V9_FLCTL,
+	.bchctl =   RK_NANDC_REG_V9_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V9_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V9_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V9_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V9_DMA_ST,
+	.bchst =    RK_NANDC_REG_V9_BCHST,
+	.randmz =   RK_NANDC_REG_V9_RANDMZ,
+	.ver =      RK_NANDC_REG_V9_VER,
+	.inten =    RK_NANDC_REG_V9_INTEN,
+	.intclr =   RK_NANDC_REG_V9_INTCLR,
+	.intst =    RK_NANDC_REG_V9_INTST,
+	.spare0 =   RK_NANDC_REG_V9_SPARE0,
+	.spare1 =   RK_NANDC_REG_V9_SPARE1,
+};
+
+struct rk_nandc_cap {
+	u8 max_cs;
+};
+
+static const struct rk_nandc_cap rk_nandc_V600_cap = {
+	.max_cs = 8,
+};
+
+static const struct rk_nandc_cap rk_nandc_V622_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V701_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V801_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V900_cap = {
+	.max_cs = 4,
+};
+
+struct rk_nandc_data {
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_ctrl {
+	struct device *dev;
+	void __iomem *regs;
+	int irq;
+	struct clk *hclk;
+	struct clk *clk;
+	struct list_head chips;
+	struct completion complete;
+	struct nand_controller controller;
+	int cs[RK_NANDC_NUM_BANKS];
+	u32 *oob_buf;
+	u32 *page_buf;
+	int selected_bank;
+	unsigned long assigned_cs;
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_chip {
+	struct nand_chip nand;
+	struct list_head node;
+	bool bootromblocks;
+	int ecc_mode;
+	int max_ecc_strength;
+	int idbresblknum;
+};
+
+static struct rk_nand_chip *to_rk_nand(struct nand_chip *nand)
+{
+	return container_of(nand, struct rk_nand_chip, nand);
+}
+
+static void rk_nandc_init(struct rk_nand_ctrl *ctrl)
+{
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->randmz);
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed(RK_NANDC_V6_FM_WP, ctrl->regs + ctrl->ofs->fmctl);
+	writel_relaxed(RK_NANDC_V6_FL_RST, ctrl->regs + ctrl->ofs->flctl);
+	writel_relaxed(0x1081, ctrl->regs + ctrl->ofs->fmwait);
+}
+
+static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
+{
+	struct rk_nand_ctrl *ctrl = dev_id;
+
+	u32 st = readl(ctrl->regs + ctrl->ofs->intst);
+	u32 ien = readl(ctrl->regs + ctrl->ofs->inten);
+
+	if (!(ien & st))
+		return IRQ_NONE;
+
+	if ((ien & st) == ien)
+		complete(&ctrl->complete);
+
+	writel_relaxed(st, ctrl->regs + ctrl->ofs->intclr);
+	writel_relaxed(~st & ien, ctrl->regs + ctrl->ofs->inten);
+
+	return IRQ_HANDLED;
+}
+
+static void rk_nandc_select_chip(struct nand_chip *nand, int chipnr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+	int banknr;
+
+	reg = readl(ctrl->regs + ctrl->ofs->fmctl);
+	reg &= ~RK_NANDC_V6_FM_CE_SEL_M;
+
+	if (chipnr == -1) {
+		banknr = -1;
+	} else {
+		banknr = ctrl->cs[chipnr];
+
+		reg |= RK_NANDC_V6_FM_CE_SEL(banknr);
+	}
+
+	ctrl->selected_bank = banknr;
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->fmctl);
+}
+
+static int rk_nandc_hw_ecc_setup(struct nand_chip *nand,
+				 u32 strength)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+
+	nand->ecc.strength = strength;
+	/* HW ECC always request ECC bytes for 1024 bytes blocks */
+	nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * fls(8 * 1024), 8);
+	/* HW ECC only works with an even number of ECC bytes */
+	nand->ecc.bytes = ALIGN(nand->ecc.bytes, 2);
+
+	if (ctrl->version == VERSION_9) {
+		switch (nand->ecc.strength) {
+		case 70:
+			reg = RK_NANDC_V9_ECC_70;
+			break;
+		case 60:
+			reg = RK_NANDC_V9_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V9_ECC_40;
+			break;
+		case 16:
+			reg = RK_NANDC_V9_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		switch (nand->ecc.strength) {
+		case 60:
+			reg = RK_NANDC_V6_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V6_ECC_40;
+			break;
+		case 24:
+			reg = RK_NANDC_V6_ECC_24;
+			break;
+		case 16:
+			reg = RK_NANDC_V6_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+
+	return 0;
+}
+
+static void rk_nandc_xfer_start(struct rk_nand_ctrl *ctrl,
+				u8 dir, u8 n_KB,
+				dma_addr_t dma_data,
+				dma_addr_t dma_oob)
+{
+	u32 reg;
+
+	if (ctrl->version == VERSION_6) {
+		reg = readl(ctrl->regs + ctrl->ofs->bchctl);
+		reg = (reg & (~(RK_NANDC_V6_BCH_REGION_M <<
+				RK_NANDC_V6_BCH_REGION_S))) |
+		      (ctrl->selected_bank << RK_NANDC_V6_BCH_REGION_S);
+		writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+	}
+
+	reg = RK_NANDC_V6_DMA_CFG_WR_ST |
+	      RK_NANDC_V6_DMA_CFG_WR(dir) |
+	      RK_NANDC_V6_DMA_CFG_BUS_MODE |
+	      RK_NANDC_V6_DMA_CFG_HSIZE_32 |
+	      RK_NANDC_V6_DMA_CFG_BURST_16 |
+	      RK_NANDC_V6_DMA_CFG_INCR_NUM(16);
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed((u32)dma_data, ctrl->regs + ctrl->ofs->dma_buf0);
+	writel_relaxed((u32)dma_oob, ctrl->regs + ctrl->ofs->dma_buf1);
+
+	reg = RK_NANDC_V6_FL_DIR(dir) |
+	      RK_NANDC_V6_FL_XFER_EN |
+	      RK_NANDC_V6_FL_XFER_COUNT |
+	      RK_NANDC_V6_FL_ACORRECT |
+	      RK_NANDC_V6_FL_PAGE_NUM(n_KB) |
+	      RK_NANDC_V6_FL_ASYNC_TOG_MIX;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+	reg |= RK_NANDC_V6_FL_XFER_START;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+}
+
+static int rk_nandc_wait_for_xfer_done(struct rk_nand_ctrl *ctrl)
+{
+	u32 reg;
+	int ret;
+
+	void __iomem *ptr = ctrl->regs + ctrl->ofs->flctl;
+
+	ret = readl_poll_timeout_atomic(ptr, reg,
+					reg & RK_NANDC_V6_FL_XFER_READY,
+					1, RK_NANDC_DEF_TIMEOUT);
+
+	return ret;
+}
+
+static void rk_nandc_hw_ecc_setup_helper(struct nand_chip *nand,
+					 int page, bool entry)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+
+	int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+	/* only bank 0 used for boot rom blocks */
+	if ((page < pages_per_blk * rk_nand->idbresblknum) &&
+	    rk_nand->bootromblocks &&
+	    !ctrl->selected_bank &&
+	    entry)
+		rk_nandc_hw_ecc_setup(nand, RK_NANDC_IDB_Ecc_Bits);
+	else
+		rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+}
+
+static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand,
+					      u8 *buf,
+					      int oob_required,
+					      int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	int max_bitflips = 0;
+	dma_addr_t dma_data, dma_oob;
+	int ret, i;
+	int bch_st;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_FROM_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_FROM_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 0, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(5));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_FROM_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_FROM_DEVICE);
+
+	if (ret)
+		goto ecc_setup;
+
+	memcpy(buf, ctrl->page_buf, mtd->writesize);
+
+	if (oob_required) {
+		u8 *oob;
+		u32 tmp;
+
+		for (i = 0; i < ecc->steps; i++) {
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			if (ctrl->version == VERSION_9) {
+				tmp = ctrl->oob_buf[i];
+			} else {
+				u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+					      ? 64 : 128;
+				tmp = ctrl->oob_buf[i * oob_step / 4];
+			}
+			*oob++ = (u8)tmp;
+			*oob++ = (u8)(tmp >> 8);
+			*oob++ = (u8)(tmp >> 16);
+			*oob++ = (u8)(tmp >> 24);
+		}
+	}
+
+	if (ctrl->version == VERSION_9) {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V9_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V9_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V9_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V9_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	} else {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V6_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V6_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V6_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V6_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	}
+
+	ret = max_bitflips;
+	if (max_bitflips == -1) {
+		dev_err(ctrl->dev, "BCH status error\n");
+		ret = -ENODATA;
+	}
+
+ecc_setup:
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	return ret;
+}
+
+static int rk_nandc_hw_syndrome_ecc_write_page(struct nand_chip *nand,
+					       const u8 *buf,
+					       int oob_required,
+					       int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	dma_addr_t dma_data, dma_oob;
+	int i, ret;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+	for (i = 0; i < ecc->steps; i++) {
+		u32 tmp;
+
+		if (oob_required) {
+			u8 *oob;
+
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			tmp = oob[0] |
+			      (oob[1] << 8) |
+			      (oob[2] << 16) |
+			      (oob[3] << 24);
+		} else {
+			tmp = 0xFFFFFFFF;
+		}
+		if (ctrl->version == VERSION_9) {
+			ctrl->oob_buf[i] = tmp;
+		} else {
+			u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+				      ? 64 : 128;
+			ctrl->oob_buf[i * oob_step / 4] = tmp;
+		}
+	}
+
+	memcpy(ctrl->page_buf, buf, mtd->writesize);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_TO_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_TO_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 1, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete,
+					  msecs_to_jiffies(10));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_TO_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_TO_DEVICE);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	if (!ret)
+		ret = nand_prog_page_end_op(nand);
+
+	return ret;
+}
+
+static int rk_nandc_hw_ecc_read_oob(struct nand_chip *nand, int page)
+{
+	u8 *buf = nand_get_data_buf(nand);
+
+	return nand->ecc.read_page(nand, buf, true, page);
+}
+
+static int rk_nandc_hw_ecc_write_oob(struct nand_chip *nand, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	int ret;
+	u8 *buf = nand_get_data_buf(nand);
+
+	memset(buf, 0xFF, mtd->writesize);
+	ret = nand->ecc.write_page(nand, buf, true, page);
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(nand);
+}
+
+static void rk_nandc_read_buf(struct nand_chip *nand, u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		buf[offs] = readb(bank_base);
+}
+
+static void rk_nandc_write_buf(struct nand_chip *nand, const u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		writeb(buf[offs], bank_base);
+}
+
+static void rk_nandc_write_cmd(struct nand_chip *nand, u8 cmd)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_CMD(ctrl);
+
+	writeb(cmd, bank_base);
+}
+
+static void rk_nandc_write_addr(struct nand_chip *nand, u8 addr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_ADDR(ctrl);
+
+	writeb(addr, bank_base);
+}
+
+static int rk_nandc_dev_ready(struct nand_chip *nand)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	if (readl(ctrl->regs + ctrl->ofs->fmctl) & RK_NANDC_V6_FM_FREADY)
+		return 1;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section +
+			    nand->ecc.prepad;
+	oobregion->length = nand->ecc.steps * nand->ecc.bytes;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section;
+	oobregion->length = nand->ecc.steps * nand->ecc.prepad;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops rk_nandc_oob_ops = {
+	.ecc = rk_nandc_ooblayout_ecc,
+	.free = rk_nandc_ooblayout_free,
+};
+
+static int rk_nandc_buffer_init(struct nand_chip *nand)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	ctrl->page_buf = devm_kzalloc(ctrl->dev,
+				      mtd->writesize,
+				      GFP_KERNEL | GFP_DMA);
+	if (!ctrl->page_buf)
+		return -ENOMEM;
+
+	ctrl->oob_buf = devm_kzalloc(ctrl->dev,
+				     nand->ecc.steps * 128,
+				     GFP_KERNEL | GFP_DMA);
+	if (!ctrl->oob_buf) {
+		devm_kfree(ctrl->dev, ctrl->page_buf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_hw_ecc_ctrl_init(struct nand_chip *nand)
+{
+	u8 strengths_v6[] = {60, 40, 24, 16};
+	u8 strengths_v9[] = {70, 60, 40, 16};
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+	int max_strength;
+	u32 i, ver;
+
+	if (nand->options & NAND_IS_BOOT_MEDIUM)
+		rk_nand->bootromblocks = true;
+	else
+		rk_nand->bootromblocks = false;
+
+	nand->ecc.prepad = 4;
+	nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+	max_strength = ((mtd->oobsize / nand->ecc.steps) - 4) * 8 / 14;
+	if (ctrl->version == VERSION_9) {
+		rk_nand->max_ecc_strength = 70;
+		ver = readl(ctrl->regs + RK_NANDC_REG_V9_VER);
+		if (ver != RK_NANDC_ID_V900)
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v9); i++) {
+			if (max_strength >= strengths_v9[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v9)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v9[i];
+	} else {
+		rk_nand->max_ecc_strength = 60;
+
+		ver = readl(ctrl->regs + RK_NANDC_REG_V6_VER);
+		if (ver == RK_NANDC_ID_V801)
+			rk_nand->max_ecc_strength = 16;
+		else if (ver == RK_NANDC_ID_V600 ||
+			 ver == RK_NANDC_ID_V622 ||
+			 ver == RK_NANDC_ID_V701 ||
+			 ver == RK_NANDC_ID_V800)
+			rk_nand->max_ecc_strength = 60;
+		else
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v6); i++) {
+			if (max_strength >= strengths_v6[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v6)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v6[i];
+	}
+	rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+
+	mtd_set_ooblayout(mtd, &rk_nandc_oob_ops);
+
+	if (mtd->oobsize < ((nand->ecc.bytes + nand->ecc.prepad) *
+			    nand->ecc.steps)) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_attach_chip(struct nand_chip *nand)
+{
+	int ret;
+
+	switch (nand->ecc.mode) {
+	case NAND_ECC_HW_SYNDROME:
+	case NAND_ECC_HW:
+		ret = rk_nandc_hw_ecc_ctrl_init(nand);
+		if (ret)
+			return ret;
+		ret = rk_nandc_buffer_init(nand);
+		if (ret)
+			return -ENOMEM;
+		nand->ecc.read_page = rk_nandc_hw_syndrome_ecc_read_page;
+		nand->ecc.write_page = rk_nandc_hw_syndrome_ecc_write_page;
+		nand->ecc.read_oob = rk_nandc_hw_ecc_read_oob;
+		nand->ecc.write_oob = rk_nandc_hw_ecc_write_oob;
+		break;
+	case NAND_ECC_NONE:
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_exec_op(struct nand_chip *nand,
+			    const struct nand_operation *op,
+			    bool check_only)
+{
+	int i;
+	unsigned int op_id;
+	const struct nand_op_instr *instr = NULL;
+
+	rk_nandc_select_chip(nand, op->cs);
+
+	if (check_only)
+		return 0;
+
+	for (op_id = 0; op_id < op->ninstrs; op_id++) {
+		instr = &op->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			rk_nandc_write_cmd(nand, instr->ctx.cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			for (i = 0; i < instr->ctx.addr.naddrs; i++)
+				rk_nandc_write_addr(nand,
+						    instr->ctx.addr.addrs[i]);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			rk_nandc_read_buf(nand, instr->ctx.data.buf.in,
+					  instr->ctx.data.len);
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			rk_nandc_write_buf(nand, instr->ctx.data.buf.out,
+					   instr->ctx.data.len);
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			rk_nandc_dev_ready(nand);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static const struct nand_controller_ops rk_nand_controller_ops = {
+	.attach_chip = rk_nandc_attach_chip,
+	.exec_op = rk_nandc_exec_op,
+};
+
+static int rk_nandc_chip_init(struct rk_nand_ctrl *ctrl,
+			      struct device_node *np)
+{
+	struct rk_nand_chip *rk_nand;
+	struct nand_chip *nand;
+	struct mtd_info *mtd;
+	int nsels, ret, i;
+	u32 cs, val;
+
+	nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32));
+	if (nsels <= 0) {
+		dev_err(ctrl->dev, "missing/invalid reg property\n");
+		return -EINVAL;
+	}
+
+	rk_nand = devm_kzalloc(ctrl->dev, sizeof(*rk_nand), GFP_KERNEL);
+	if (!rk_nand)
+		return -ENOMEM;
+
+	rk_nand->idbresblknum = RK_NANDC_IDB_Res_Blk_Num;
+
+	for (i = 0; i < nsels; i++) {
+		ret = of_property_read_u32_index(np, "reg", i, &cs);
+		if (ret) {
+			dev_err(ctrl->dev,
+				"could not retrieve reg property: %d\n",
+				ret);
+			return ret;
+		}
+
+		if (cs >= ctrl->cap->max_cs) {
+			dev_err(ctrl->dev,
+				"invalid reg value: %u (max CS = %d)\n",
+				cs,
+				ctrl->cap->max_cs);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &ctrl->assigned_cs)) {
+			dev_err(ctrl->dev,
+				"CS %d already assigned\n",
+				cs);
+			return -EINVAL;
+		}
+
+		ctrl->cs[i] = cs;
+
+		/* only bank 0 used for boot rom blocks */
+		if (!of_property_read_u32_index(np, "rockchip,idb-res-blk-num",
+						i, &val)) {
+			if (!cs && val >= 2) {
+				rk_nand->idbresblknum = val;
+			} else {
+				dev_err(ctrl->dev,
+					"invalid idb-res-blk-num\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	nand = &rk_nand->nand;
+
+	nand_set_flash_node(nand, np);
+	nand_set_controller_data(nand, ctrl);
+
+	nand->controller = &ctrl->controller;
+	nand->controller->ops = &rk_nand_controller_ops;
+
+	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+	nand->ecc.size = 1024;
+	nand->ecc.strength = 40;
+
+	nand->options = NAND_SKIP_BBTSCAN | NAND_NO_SUBPAGE_WRITE;
+
+	mtd = nand_to_mtd(nand);
+	mtd->dev.parent = ctrl->dev;
+	mtd->name = devm_kasprintf(ctrl->dev, GFP_KERNEL, "%s",
+				   dev_name(ctrl->dev));
+
+	ret = nand_scan(nand, nsels);
+	if (ret)
+		return ret;
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(ctrl->dev, "mtd device register failed: %d\n", ret);
+		nand_cleanup(nand);
+		return ret;
+	}
+
+	list_add_tail(&rk_nand->node, &ctrl->chips);
+
+	return 0;
+}
+
+static void rk_nandc_cleanup_chips(struct rk_nand_ctrl *ctrl)
+{
+	struct rk_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &ctrl->chips, node) {
+		nand_release(&entry->nand);
+		list_del(&entry->node);
+	}
+}
+
+static int rk_nandc_chips_init(struct rk_nand_ctrl *ctrl)
+{
+	struct device_node *np = ctrl->dev->of_node;
+	struct device_node *nand_np;
+	int nchips;
+	int ret;
+
+	nchips = of_get_child_count(np);
+
+	if (nchips > ctrl->cap->max_cs) {
+		dev_err(ctrl->dev, "too many NAND chips: %d (max CS = %d)\n",
+			nchips,
+			ctrl->cap->max_cs);
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = rk_nandc_chip_init(ctrl, nand_np);
+		if (ret) {
+			rk_nandc_cleanup_chips(ctrl);
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rk_nandc_probe(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl;
+	const struct rk_nandc_data *data;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -ENODEV;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->dev = dev;
+	ctrl->version = data->version;
+	ctrl->ofs = data->ofs;
+	ctrl->cap = data->cap;
+
+	ctrl->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ctrl->regs)) {
+		dev_err(dev, "ioremap failed\n");
+		return PTR_ERR(ctrl->regs);
+	}
+
+	ctrl->irq = platform_get_irq(pdev, 0);
+	if (ctrl->irq < 0) {
+		dev_err(dev, "get irq failed\n");
+		return ctrl->irq;
+	}
+
+	ctrl->hclk = devm_clk_get(dev, "hclk_nandc");
+	if (IS_ERR(ctrl->hclk)) {
+		dev_err(dev, "get hclk_nandc failed\n");
+		return PTR_ERR(ctrl->hclk);
+	}
+
+	ret = clk_prepare_enable(ctrl->hclk);
+	if (ret)
+		return ret;
+
+	ctrl->clk = devm_clk_get(dev, "clk_nandc");
+	if (!(IS_ERR(ctrl->clk))) {
+		clk_set_rate(ctrl->clk, 150 * 1000 * 1000);
+
+		ret = clk_prepare_enable(ctrl->clk);
+		if (ret)
+			goto err_disable_hclk;
+	} else {
+		dev_dbg(dev, "get clk_nandc failed\n");
+	}
+
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->inten);
+	ret = devm_request_irq(dev, ctrl->irq, rk_nandc_interrupt,
+			       0, "nandc", ctrl);
+	if (ret)
+		goto err_disable_clk;
+
+	init_completion(&ctrl->complete);
+	nand_controller_init(&ctrl->controller);
+	INIT_LIST_HEAD(&ctrl->chips);
+
+	rk_nandc_init(ctrl);
+
+	ret = rk_nandc_chips_init(ctrl);
+	if (ret) {
+		dev_err(dev, "init nand chips failed\n");
+		goto err_disable_clk;
+	}
+
+	platform_set_drvdata(pdev, ctrl);
+
+	return 0;
+
+err_disable_clk:
+	clk_disable_unprepare(ctrl->clk);
+err_disable_hclk:
+	clk_disable_unprepare(ctrl->hclk);
+
+	return ret;
+}
+
+static int rk_nandc_remove(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static void rk_nandc_shutdown(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+}
+
+static const struct rk_nandc_data rk_nandc_V600_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V600_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V622_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V622_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V701_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V701_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V801_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V801_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V900_data = {
+	.version = VERSION_9,
+	.ofs = (void *)&rk_nandc_V9_reg_offset,
+	.cap = (void *)&rk_nandc_V900_cap,
+};
+
+static const struct of_device_id of_rk_nandc_match[] = {
+	{
+		.compatible = "rockchip,px30-nand-controller",
+		.data = &rk_nandc_V900_data,
+	},
+	{
+		.compatible = "rockchip,rk3066-nand-controller",
+		.data = &rk_nandc_V600_data,
+	},
+	{
+		.compatible = "rockchip,rk3228-nand-controller",
+		.data = &rk_nandc_V701_data,
+	},
+	{
+		.compatible = "rockchip,rk3288-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rk3308-nand-controller",
+		.data = &rk_nandc_V801_data,
+	},
+	{
+		.compatible = "rockchip,rk3368-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rv1108-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{ /* sentinel */ },
+};
+
+static struct platform_driver rk_nandc_driver = {
+	.probe  = rk_nandc_probe,
+	.remove = rk_nandc_remove,
+	.shutdown = rk_nandc_shutdown,
+	.driver = {
+		.name = "rockchip-nand-controller",
+		.of_match_table = of_rk_nandc_match,
+	},
+};
+module_platform_driver(rk_nandc_driver);
+
+MODULE_LICENSE("GPL v2");
-- 
2.11.0


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

WARNING: multiple messages have this Message-ID (diff)
From: Johan Jonker <jbx6244@gmail.com>
To: miquel.raynal@bootlin.com
Cc: mark.rutland@arm.com, devicetree@vger.kernel.org,
	vigneshr@ti.com, richard@nod.at, shawn.lin@rock-chips.com,
	linux-kernel@vger.kernel.org, linux-rockchip@lists.infradead.org,
	robh+dt@kernel.org, linux-mtd@lists.infradead.org,
	yifeng.zhao@rock-chips.com, linux-arm-kernel@lists.infradead.org,
	heiko@sntech.de
Subject: [RFC PATCH v2 02/10] mtd: rawnand: rockchip: Add NAND controller driver
Date: Fri, 24 Jan 2020 17:29:53 +0100	[thread overview]
Message-ID: <20200124163001.28910-3-jbx6244@gmail.com> (raw)
In-Reply-To: <20200124163001.28910-1-jbx6244@gmail.com>

From: Yifeng Zhao <zyf@rock-chips.com>

Add basic Rockchip NAND controller driver.

Compatible with hardware version 6 and 9.
V6:16, 24, 40, 60 per 1024B BCH/ECC.
V9:16, 40, 60, 70 per 1024B BCH/ECC.
8 bit asynchronous flash interface support.
Supports up to 2 identical nandc nodes.
Max 4 nand chips per controller.
Able to select a different hardware ecc setup
for the loader blocks.
No bad block support.

Signed-off-by: Yifeng Zhao <zyf@rock-chips.com>
Signed-off-by: Johan Jonker <jbx6244@gmail.com>
---
 drivers/mtd/nand/raw/Kconfig                    |    9 +
 drivers/mtd/nand/raw/Makefile                   |    1 +
 drivers/mtd/nand/raw/rockchip-nand-controller.c | 1307 +++++++++++++++++++++++
 3 files changed, 1317 insertions(+)
 create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c

diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 74fb91ade..acee3d6ee 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,15 @@ config MTD_NAND_CADENCE
 	  Enable the driver for NAND flash on platforms using a Cadence NAND
 	  controller.
 
+config MTD_NAND_ROCKCHIP
+	tristate "Rockchip raw NAND controller driver"
+	depends on ARCH_ROCKCHIP || COMPILE_TEST
+	depends on HAS_IOMEM
+	help
+	  Enables support for the Rockchip raw NAND controller driver.
+	  This controller is found on:
+	  px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108 SoCs.
+
 comment "Misc"
 
 config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158..a54aa85f4 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_TEGRA)		+= tegra_nand.o
 obj-$(CONFIG_MTD_NAND_STM32_FMC2)	+= stm32_fmc2_nand.o
 obj-$(CONFIG_MTD_NAND_MESON)		+= meson_nand.o
 obj-$(CONFIG_MTD_NAND_CADENCE)		+= cadence-nand-controller.o
+obj-$(CONFIG_MTD_NAND_ROCKCHIP)		+= rockchip-nand-controller.o
 
 nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
 nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
new file mode 100644
index 000000000..76ed1c08d
--- /dev/null
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on:
+ * https://github.com/rockchip-linux/kernel
+ *
+ * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@rock-chips.com
+ *
+ * Support NAND controller versions 6 and 9 found on SoCs:
+ * px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108
+ *
+ * Copyright (c) 2020 Johan Jonker jbx6244@gmail.com
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/mtd/rawnand.h>
+
+#define RK_NANDC_ID_V600		0x56363030
+#define RK_NANDC_ID_V622		0x56363232
+#define RK_NANDC_ID_V701		0x701
+#define RK_NANDC_ID_V800		0x56383030
+#define RK_NANDC_ID_V801		0x801
+#define RK_NANDC_ID_V900		0x56393030
+
+#define RK_NANDC_IDB_Res_Blk_Num	16
+#define RK_NANDC_IDB_Ecc_Bits		24
+
+#define RK_NANDC_V6_ECC_16		0x00000000
+#define RK_NANDC_V6_ECC_24		0x00000010
+#define RK_NANDC_V6_ECC_40		0x00040000
+#define RK_NANDC_V6_ECC_60		0x00040010
+
+#define RK_NANDC_V9_ECC_16		0x02000001
+#define RK_NANDC_V9_ECC_40		0x04000001
+#define RK_NANDC_V9_ECC_60		0x06000001
+#define RK_NANDC_V9_ECC_70		0x00000001
+
+#define RK_NANDC_NUM_BANKS		8
+#define RK_NANDC_DEF_TIMEOUT		10000
+
+#define RK_NANDC_REG_DATA		0x00
+#define RK_NANDC_REG_ADDR		0x04
+#define RK_NANDC_REG_CMD		0x08
+
+/* register offset nandc version 6 */
+#define RK_NANDC_REG_V6_FMCTL		0x00
+#define RK_NANDC_REG_V6_FMWAIT		0x04
+#define RK_NANDC_REG_V6_FLCTL		0x08
+#define RK_NANDC_REG_V6_BCHCTL		0x0c
+#define RK_NANDC_REG_V6_DMA_CFG		0x10
+#define RK_NANDC_REG_V6_DMA_BUF0	0x14
+#define RK_NANDC_REG_V6_DMA_BUF1	0x18
+#define RK_NANDC_REG_V6_DMA_ST		0x1C
+#define RK_NANDC_REG_V6_BCHST		0x20
+#define RK_NANDC_REG_V6_RANDMZ		0x150
+#define RK_NANDC_REG_V6_VER		0x160
+#define RK_NANDC_REG_V6_INTEN		0x16C
+#define RK_NANDC_REG_V6_INTCLR		0x170
+#define RK_NANDC_REG_V6_INTST		0x174
+#define RK_NANDC_REG_V6_SPARE0		0x200
+#define RK_NANDC_REG_V6_SPARE1		0x230
+
+/* register offset nandc version 9 */
+#define RK_NANDC_REG_V9_FMCTL		0x00
+#define RK_NANDC_REG_V9_FMWAIT		0x04
+#define RK_NANDC_REG_V9_FLCTL		0x10
+#define RK_NANDC_REG_V9_BCHCTL		0x20
+#define RK_NANDC_REG_V9_DMA_CFG		0x30
+#define RK_NANDC_REG_V9_DMA_BUF0	0x34
+#define RK_NANDC_REG_V9_DMA_BUF1	0x38
+#define RK_NANDC_REG_V9_DMA_ST		0x40
+#define RK_NANDC_REG_V9_VER		0x80
+#define RK_NANDC_REG_V9_INTEN		0x120
+#define RK_NANDC_REG_V9_INTCLR		0x124
+#define RK_NANDC_REG_V9_INTST		0x128
+#define RK_NANDC_REG_V9_BCHST		0x150
+#define RK_NANDC_REG_V9_SPARE0		0x200
+#define RK_NANDC_REG_V9_SPARE1		0x204
+#define RK_NANDC_REG_V9_RANDMZ		0x208
+
+/* register offset nandc common */
+#define RK_NANDC_REG_BANK0		0x800
+#define RK_NANDC_REG_SRAM0		0x1000
+
+/* calculate bank_base */
+#define RK_NANDC_BANK_SIZE		0x100
+
+#define RK_REG(ctrl, off) \
+	((ctrl)->regs + \
+	RK_NANDC_REG_BANK0 + \
+	(ctrl)->selected_bank * RK_NANDC_BANK_SIZE + \
+	(off))
+
+#define RK_REG_DATA(ctrl) RK_REG(ctrl, RK_NANDC_REG_DATA)
+#define RK_REG_ADDR(ctrl) RK_REG(ctrl, RK_NANDC_REG_ADDR)
+#define RK_REG_CMD(ctrl)  RK_REG(ctrl, RK_NANDC_REG_CMD)
+
+/* FMCTL */
+#define RK_NANDC_V6_FM_WP		BIT(8)
+#define RK_NANDC_V6_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V6_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V6_FM_FREADY		BIT(9)
+
+#define RK_NANDC_V9_FM_WP		BIT(8)
+#define RK_NANDC_V9_FM_CE_SEL_M		0xFF
+#define RK_NANDC_V9_FM_CE_SEL(x)	(1 << (x))
+#define RK_NANDC_V9_RDY			BIT(9)
+
+/* FLCTL */
+#define RK_NANDC_V6_FL_RST		BIT(0)
+#define RK_NANDC_V6_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V6_FL_XFER_START	BIT(2)
+#define RK_NANDC_V6_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V6_FL_ST_BUF_S		0x4
+#define RK_NANDC_V6_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V6_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V6_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V6_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V6_FL_ASYNC_TOG_MIX	BIT(29)
+
+#define RK_NANDC_V9_FL_RST		BIT(0)
+#define RK_NANDC_V9_FL_DIR(x)		((x) ? BIT(1) : 0)
+#define RK_NANDC_V9_FL_XFER_START	BIT(2)
+#define RK_NANDC_V9_FL_XFER_EN		BIT(3)
+#define RK_NANDC_V9_FL_ST_BUF_S		0x4
+#define RK_NANDC_V9_FL_XFER_COUNT	BIT(5)
+#define RK_NANDC_V9_FL_ACORRECT		BIT(10)
+#define RK_NANDC_V9_FL_XFER_READY	BIT(20)
+#define RK_NANDC_V9_FL_PAGE_NUM(x)	((x) << 22)
+#define RK_NANDC_V9_FL_ASYNC_TOG_MIX	BIT(29)
+
+/* BCHCTL */
+#define RK_NANDC_V6_BCH_REGION_S	0x5
+#define RK_NANDC_V6_BCH_REGION_M	0x7
+
+#define RK_NANDC_V9_BCH_MODE_S		25
+#define RK_NANDC_V9_BCH_MODE_M		0x7
+
+/* BCHST */
+#define RK_NANDC_V6_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V6_BCH1_ST_ERR		BIT(15)
+#define RK_NANDC_V6_ECC_ERR_CNT0(x)	(((((x) & (0x1F << 3)) >> 3) \
+					| (((x) & (1 << 27)) >> 22)) & 0x3F)
+#define RK_NANDC_V6_ECC_ERR_CNT1(x)	(((((x) & (0x1F << 16)) >> 16) \
+					| (((x) & (1 << 29)) >> 24)) & 0x3F)
+
+#define RK_NANDC_V9_BCH0_ST_ERR		BIT(2)
+#define RK_NANDC_V9_BCH1_ST_ERR		BIT(18)
+#define RK_NANDC_V9_ECC_ERR_CNT0(x)	(((x) & (0x7F << 3)) >> 3)
+#define RK_NANDC_V9_ECC_ERR_CNT1(x)	(((x) & (0x7F << 19)) >> 19)
+
+/* DMA_CFG */
+#define RK_NANDC_V6_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V6_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V6_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V6_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V6_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V6_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V6_DMA_CFG_BURST_1	0
+#define RK_NANDC_V6_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V6_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+#define RK_NANDC_V9_DMA_CFG_WR_ST	BIT(0)
+#define RK_NANDC_V9_DMA_CFG_WR(x)	(!(x) ? BIT(1) : 0)
+#define RK_NANDC_V9_DMA_CFG_BUS_MODE	BIT(2)
+
+#define RK_NANDC_V9_DMA_CFG_HSIZE_8	0
+#define RK_NANDC_V9_DMA_CFG_HSIZE_16	BIT(3)
+#define RK_NANDC_V9_DMA_CFG_HSIZE_32	(2 << 3)
+
+#define RK_NANDC_V9_DMA_CFG_BURST_1	0
+#define RK_NANDC_V9_DMA_CFG_BURST_4	(3 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_8	(5 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_16	(7 << 6)
+
+#define RK_NANDC_V9_DMA_CFG_INCR_NUM(x)	((x) << 9)
+
+/* INTEN */
+#define RK_NANDC_V6_INT_DMA		BIT(0)
+
+#define RK_NANDC_V9_INT_DMA		BIT(0)
+
+/*
+ * The bit positions for
+ * RK_NANDC_REG_V6_FMCTL and RK_NANDC_REG_V9_FMCTL,
+ * RK_NANDC_REG_V6_FLCTL and RK_NANDC_REG_V9_FLCTL,
+ * RK_NANDC_REG_V6_DMA_CFG and RK_NANDC_REG_V9_DMA_CFG
+ * are identical.
+ */
+
+enum rk_nandc_version {
+	VERSION_6 = 6,
+	VERSION_9 = 9,
+};
+
+struct rk_nandc_reg_offset {
+	u32 fmctl;
+	u32 fmwait;
+	u32 flctl;
+	u32 bchctl;
+	u32 dma_cfg;
+	u32 dma_buf0;
+	u32 dma_buf1;
+	u32 dma_st;
+	u32 bchst;
+	u32 randmz;
+	u32 ver;
+	u32 inten;
+	u32 intclr;
+	u32 intst;
+	u32 spare0;
+	u32 spare1;
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V6_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V6_FMCTL,
+	.fmwait =   RK_NANDC_REG_V6_FMWAIT,
+	.flctl =    RK_NANDC_REG_V6_FLCTL,
+	.bchctl =   RK_NANDC_REG_V6_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V6_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V6_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V6_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V6_DMA_ST,
+	.bchst =    RK_NANDC_REG_V6_BCHST,
+	.randmz =   RK_NANDC_REG_V6_RANDMZ,
+	.ver =      RK_NANDC_REG_V6_VER,
+	.inten =    RK_NANDC_REG_V6_INTEN,
+	.intclr =   RK_NANDC_REG_V6_INTCLR,
+	.intst =    RK_NANDC_REG_V6_INTST,
+	.spare0 =   RK_NANDC_REG_V6_SPARE0,
+	.spare1 =   RK_NANDC_REG_V6_SPARE1,
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V9_reg_offset = {
+	.fmctl =    RK_NANDC_REG_V9_FMCTL,
+	.fmwait =   RK_NANDC_REG_V9_FMWAIT,
+	.flctl =    RK_NANDC_REG_V9_FLCTL,
+	.bchctl =   RK_NANDC_REG_V9_BCHCTL,
+	.dma_cfg =  RK_NANDC_REG_V9_DMA_CFG,
+	.dma_buf0 = RK_NANDC_REG_V9_DMA_BUF0,
+	.dma_buf1 = RK_NANDC_REG_V9_DMA_BUF1,
+	.dma_st =   RK_NANDC_REG_V9_DMA_ST,
+	.bchst =    RK_NANDC_REG_V9_BCHST,
+	.randmz =   RK_NANDC_REG_V9_RANDMZ,
+	.ver =      RK_NANDC_REG_V9_VER,
+	.inten =    RK_NANDC_REG_V9_INTEN,
+	.intclr =   RK_NANDC_REG_V9_INTCLR,
+	.intst =    RK_NANDC_REG_V9_INTST,
+	.spare0 =   RK_NANDC_REG_V9_SPARE0,
+	.spare1 =   RK_NANDC_REG_V9_SPARE1,
+};
+
+struct rk_nandc_cap {
+	u8 max_cs;
+};
+
+static const struct rk_nandc_cap rk_nandc_V600_cap = {
+	.max_cs = 8,
+};
+
+static const struct rk_nandc_cap rk_nandc_V622_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V701_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V801_cap = {
+	.max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V900_cap = {
+	.max_cs = 4,
+};
+
+struct rk_nandc_data {
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_ctrl {
+	struct device *dev;
+	void __iomem *regs;
+	int irq;
+	struct clk *hclk;
+	struct clk *clk;
+	struct list_head chips;
+	struct completion complete;
+	struct nand_controller controller;
+	int cs[RK_NANDC_NUM_BANKS];
+	u32 *oob_buf;
+	u32 *page_buf;
+	int selected_bank;
+	unsigned long assigned_cs;
+	enum rk_nandc_version version;
+	struct rk_nandc_reg_offset *ofs;
+	struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_chip {
+	struct nand_chip nand;
+	struct list_head node;
+	bool bootromblocks;
+	int ecc_mode;
+	int max_ecc_strength;
+	int idbresblknum;
+};
+
+static struct rk_nand_chip *to_rk_nand(struct nand_chip *nand)
+{
+	return container_of(nand, struct rk_nand_chip, nand);
+}
+
+static void rk_nandc_init(struct rk_nand_ctrl *ctrl)
+{
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->randmz);
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed(RK_NANDC_V6_FM_WP, ctrl->regs + ctrl->ofs->fmctl);
+	writel_relaxed(RK_NANDC_V6_FL_RST, ctrl->regs + ctrl->ofs->flctl);
+	writel_relaxed(0x1081, ctrl->regs + ctrl->ofs->fmwait);
+}
+
+static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
+{
+	struct rk_nand_ctrl *ctrl = dev_id;
+
+	u32 st = readl(ctrl->regs + ctrl->ofs->intst);
+	u32 ien = readl(ctrl->regs + ctrl->ofs->inten);
+
+	if (!(ien & st))
+		return IRQ_NONE;
+
+	if ((ien & st) == ien)
+		complete(&ctrl->complete);
+
+	writel_relaxed(st, ctrl->regs + ctrl->ofs->intclr);
+	writel_relaxed(~st & ien, ctrl->regs + ctrl->ofs->inten);
+
+	return IRQ_HANDLED;
+}
+
+static void rk_nandc_select_chip(struct nand_chip *nand, int chipnr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+	int banknr;
+
+	reg = readl(ctrl->regs + ctrl->ofs->fmctl);
+	reg &= ~RK_NANDC_V6_FM_CE_SEL_M;
+
+	if (chipnr == -1) {
+		banknr = -1;
+	} else {
+		banknr = ctrl->cs[chipnr];
+
+		reg |= RK_NANDC_V6_FM_CE_SEL(banknr);
+	}
+
+	ctrl->selected_bank = banknr;
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->fmctl);
+}
+
+static int rk_nandc_hw_ecc_setup(struct nand_chip *nand,
+				 u32 strength)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	u32 reg;
+
+	nand->ecc.strength = strength;
+	/* HW ECC always request ECC bytes for 1024 bytes blocks */
+	nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * fls(8 * 1024), 8);
+	/* HW ECC only works with an even number of ECC bytes */
+	nand->ecc.bytes = ALIGN(nand->ecc.bytes, 2);
+
+	if (ctrl->version == VERSION_9) {
+		switch (nand->ecc.strength) {
+		case 70:
+			reg = RK_NANDC_V9_ECC_70;
+			break;
+		case 60:
+			reg = RK_NANDC_V9_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V9_ECC_40;
+			break;
+		case 16:
+			reg = RK_NANDC_V9_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		switch (nand->ecc.strength) {
+		case 60:
+			reg = RK_NANDC_V6_ECC_60;
+			break;
+		case 40:
+			reg = RK_NANDC_V6_ECC_40;
+			break;
+		case 24:
+			reg = RK_NANDC_V6_ECC_24;
+			break;
+		case 16:
+			reg = RK_NANDC_V6_ECC_16;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+
+	return 0;
+}
+
+static void rk_nandc_xfer_start(struct rk_nand_ctrl *ctrl,
+				u8 dir, u8 n_KB,
+				dma_addr_t dma_data,
+				dma_addr_t dma_oob)
+{
+	u32 reg;
+
+	if (ctrl->version == VERSION_6) {
+		reg = readl(ctrl->regs + ctrl->ofs->bchctl);
+		reg = (reg & (~(RK_NANDC_V6_BCH_REGION_M <<
+				RK_NANDC_V6_BCH_REGION_S))) |
+		      (ctrl->selected_bank << RK_NANDC_V6_BCH_REGION_S);
+		writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+	}
+
+	reg = RK_NANDC_V6_DMA_CFG_WR_ST |
+	      RK_NANDC_V6_DMA_CFG_WR(dir) |
+	      RK_NANDC_V6_DMA_CFG_BUS_MODE |
+	      RK_NANDC_V6_DMA_CFG_HSIZE_32 |
+	      RK_NANDC_V6_DMA_CFG_BURST_16 |
+	      RK_NANDC_V6_DMA_CFG_INCR_NUM(16);
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->dma_cfg);
+	writel_relaxed((u32)dma_data, ctrl->regs + ctrl->ofs->dma_buf0);
+	writel_relaxed((u32)dma_oob, ctrl->regs + ctrl->ofs->dma_buf1);
+
+	reg = RK_NANDC_V6_FL_DIR(dir) |
+	      RK_NANDC_V6_FL_XFER_EN |
+	      RK_NANDC_V6_FL_XFER_COUNT |
+	      RK_NANDC_V6_FL_ACORRECT |
+	      RK_NANDC_V6_FL_PAGE_NUM(n_KB) |
+	      RK_NANDC_V6_FL_ASYNC_TOG_MIX;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+	reg |= RK_NANDC_V6_FL_XFER_START;
+	writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+}
+
+static int rk_nandc_wait_for_xfer_done(struct rk_nand_ctrl *ctrl)
+{
+	u32 reg;
+	int ret;
+
+	void __iomem *ptr = ctrl->regs + ctrl->ofs->flctl;
+
+	ret = readl_poll_timeout_atomic(ptr, reg,
+					reg & RK_NANDC_V6_FL_XFER_READY,
+					1, RK_NANDC_DEF_TIMEOUT);
+
+	return ret;
+}
+
+static void rk_nandc_hw_ecc_setup_helper(struct nand_chip *nand,
+					 int page, bool entry)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+
+	int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+	/* only bank 0 used for boot rom blocks */
+	if ((page < pages_per_blk * rk_nand->idbresblknum) &&
+	    rk_nand->bootromblocks &&
+	    !ctrl->selected_bank &&
+	    entry)
+		rk_nandc_hw_ecc_setup(nand, RK_NANDC_IDB_Ecc_Bits);
+	else
+		rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+}
+
+static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand,
+					      u8 *buf,
+					      int oob_required,
+					      int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	int max_bitflips = 0;
+	dma_addr_t dma_data, dma_oob;
+	int ret, i;
+	int bch_st;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_FROM_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_FROM_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 0, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(5));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_FROM_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_FROM_DEVICE);
+
+	if (ret)
+		goto ecc_setup;
+
+	memcpy(buf, ctrl->page_buf, mtd->writesize);
+
+	if (oob_required) {
+		u8 *oob;
+		u32 tmp;
+
+		for (i = 0; i < ecc->steps; i++) {
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			if (ctrl->version == VERSION_9) {
+				tmp = ctrl->oob_buf[i];
+			} else {
+				u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+					      ? 64 : 128;
+				tmp = ctrl->oob_buf[i * oob_step / 4];
+			}
+			*oob++ = (u8)tmp;
+			*oob++ = (u8)(tmp >> 8);
+			*oob++ = (u8)(tmp >> 16);
+			*oob++ = (u8)(tmp >> 24);
+		}
+	}
+
+	if (ctrl->version == VERSION_9) {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V9_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V9_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V9_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V9_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	} else {
+		for (i = 0; i < ecc->steps / 2; i++) {
+			bch_st = readl(ctrl->regs +
+				       ctrl->ofs->bchst +
+				       i * 4);
+			if (bch_st & RK_NANDC_V6_BCH0_ST_ERR ||
+			    bch_st & RK_NANDC_V6_BCH1_ST_ERR) {
+				mtd->ecc_stats.failed++;
+				max_bitflips = -1;
+			} else {
+				ret = RK_NANDC_V6_ECC_ERR_CNT0(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+
+				ret = RK_NANDC_V6_ECC_ERR_CNT1(bch_st);
+				mtd->ecc_stats.corrected += ret;
+				max_bitflips = max_t(unsigned int,
+						     max_bitflips, ret);
+			}
+		}
+	}
+
+	ret = max_bitflips;
+	if (max_bitflips == -1) {
+		dev_err(ctrl->dev, "BCH status error\n");
+		ret = -ENODATA;
+	}
+
+ecc_setup:
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	return ret;
+}
+
+static int rk_nandc_hw_syndrome_ecc_write_page(struct nand_chip *nand,
+					       const u8 *buf,
+					       int oob_required,
+					       int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct nand_ecc_ctrl *ecc = &nand->ecc;
+	dma_addr_t dma_data, dma_oob;
+	int i, ret;
+	int dma_oob_size = ecc->steps * 128;
+
+	rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+	for (i = 0; i < ecc->steps; i++) {
+		u32 tmp;
+
+		if (oob_required) {
+			u8 *oob;
+
+			oob = nand->oob_poi +
+			      i * (ecc->bytes + nand->ecc.prepad);
+			tmp = oob[0] |
+			      (oob[1] << 8) |
+			      (oob[2] << 16) |
+			      (oob[3] << 24);
+		} else {
+			tmp = 0xFFFFFFFF;
+		}
+		if (ctrl->version == VERSION_9) {
+			ctrl->oob_buf[i] = tmp;
+		} else {
+			u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+				      ? 64 : 128;
+			ctrl->oob_buf[i * oob_step / 4] = tmp;
+		}
+	}
+
+	memcpy(ctrl->page_buf, buf, mtd->writesize);
+
+	dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+				  DMA_TO_DEVICE);
+	dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+				 DMA_TO_DEVICE);
+
+	reinit_completion(&ctrl->complete);
+	writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+	rk_nandc_xfer_start(ctrl, 1, ecc->steps, dma_data, dma_oob);
+
+	ret = wait_for_completion_timeout(&ctrl->complete,
+					  msecs_to_jiffies(10));
+
+	if (!ret) {
+		dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+		ret = -ETIMEDOUT;
+		goto unmap_dma;
+	}
+
+	ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+	if (ret) {
+		dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+		ret = -ETIMEDOUT;
+	}
+
+unmap_dma:
+	dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_TO_DEVICE);
+	dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_TO_DEVICE);
+
+	rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+	if (!ret)
+		ret = nand_prog_page_end_op(nand);
+
+	return ret;
+}
+
+static int rk_nandc_hw_ecc_read_oob(struct nand_chip *nand, int page)
+{
+	u8 *buf = nand_get_data_buf(nand);
+
+	return nand->ecc.read_page(nand, buf, true, page);
+}
+
+static int rk_nandc_hw_ecc_write_oob(struct nand_chip *nand, int page)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	int ret;
+	u8 *buf = nand_get_data_buf(nand);
+
+	memset(buf, 0xFF, mtd->writesize);
+	ret = nand->ecc.write_page(nand, buf, true, page);
+	if (ret)
+		return ret;
+
+	return nand_prog_page_end_op(nand);
+}
+
+static void rk_nandc_read_buf(struct nand_chip *nand, u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		buf[offs] = readb(bank_base);
+}
+
+static void rk_nandc_write_buf(struct nand_chip *nand, const u8 *buf, int len)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	int offs = 0;
+	void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+	for (offs = 0; offs < len; offs++)
+		writeb(buf[offs], bank_base);
+}
+
+static void rk_nandc_write_cmd(struct nand_chip *nand, u8 cmd)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_CMD(ctrl);
+
+	writeb(cmd, bank_base);
+}
+
+static void rk_nandc_write_addr(struct nand_chip *nand, u8 addr)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	void __iomem *bank_base = RK_REG_ADDR(ctrl);
+
+	writeb(addr, bank_base);
+}
+
+static int rk_nandc_dev_ready(struct nand_chip *nand)
+{
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	if (readl(ctrl->regs + ctrl->ofs->fmctl) & RK_NANDC_V6_FM_FREADY)
+		return 1;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section +
+			    nand->ecc.prepad;
+	oobregion->length = nand->ecc.steps * nand->ecc.bytes;
+
+	return 0;
+}
+
+static int rk_nandc_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *oobregion)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section;
+	oobregion->length = nand->ecc.steps * nand->ecc.prepad;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops rk_nandc_oob_ops = {
+	.ecc = rk_nandc_ooblayout_ecc,
+	.free = rk_nandc_ooblayout_free,
+};
+
+static int rk_nandc_buffer_init(struct nand_chip *nand)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+	ctrl->page_buf = devm_kzalloc(ctrl->dev,
+				      mtd->writesize,
+				      GFP_KERNEL | GFP_DMA);
+	if (!ctrl->page_buf)
+		return -ENOMEM;
+
+	ctrl->oob_buf = devm_kzalloc(ctrl->dev,
+				     nand->ecc.steps * 128,
+				     GFP_KERNEL | GFP_DMA);
+	if (!ctrl->oob_buf) {
+		devm_kfree(ctrl->dev, ctrl->page_buf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_hw_ecc_ctrl_init(struct nand_chip *nand)
+{
+	u8 strengths_v6[] = {60, 40, 24, 16};
+	u8 strengths_v9[] = {70, 60, 40, 16};
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+	struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+	int max_strength;
+	u32 i, ver;
+
+	if (nand->options & NAND_IS_BOOT_MEDIUM)
+		rk_nand->bootromblocks = true;
+	else
+		rk_nand->bootromblocks = false;
+
+	nand->ecc.prepad = 4;
+	nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+	max_strength = ((mtd->oobsize / nand->ecc.steps) - 4) * 8 / 14;
+	if (ctrl->version == VERSION_9) {
+		rk_nand->max_ecc_strength = 70;
+		ver = readl(ctrl->regs + RK_NANDC_REG_V9_VER);
+		if (ver != RK_NANDC_ID_V900)
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v9); i++) {
+			if (max_strength >= strengths_v9[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v9)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v9[i];
+	} else {
+		rk_nand->max_ecc_strength = 60;
+
+		ver = readl(ctrl->regs + RK_NANDC_REG_V6_VER);
+		if (ver == RK_NANDC_ID_V801)
+			rk_nand->max_ecc_strength = 16;
+		else if (ver == RK_NANDC_ID_V600 ||
+			 ver == RK_NANDC_ID_V622 ||
+			 ver == RK_NANDC_ID_V701 ||
+			 ver == RK_NANDC_ID_V800)
+			rk_nand->max_ecc_strength = 60;
+		else
+			dev_err(mtd->dev.parent,
+				"unsupported nandc version %x\n", ver);
+
+		if (max_strength > rk_nand->max_ecc_strength)
+			max_strength = rk_nand->max_ecc_strength;
+
+		for (i = 0; i < ARRAY_SIZE(strengths_v6); i++) {
+			if (max_strength >= strengths_v6[i])
+				break;
+		}
+
+		if (i >= ARRAY_SIZE(strengths_v6)) {
+			dev_err(mtd->dev.parent,
+				"unsupported strength\n");
+			return -ENOTSUPP;
+		}
+
+		rk_nand->ecc_mode = strengths_v6[i];
+	}
+	rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+
+	mtd_set_ooblayout(mtd, &rk_nandc_oob_ops);
+
+	if (mtd->oobsize < ((nand->ecc.bytes + nand->ecc.prepad) *
+			    nand->ecc.steps)) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_attach_chip(struct nand_chip *nand)
+{
+	int ret;
+
+	switch (nand->ecc.mode) {
+	case NAND_ECC_HW_SYNDROME:
+	case NAND_ECC_HW:
+		ret = rk_nandc_hw_ecc_ctrl_init(nand);
+		if (ret)
+			return ret;
+		ret = rk_nandc_buffer_init(nand);
+		if (ret)
+			return -ENOMEM;
+		nand->ecc.read_page = rk_nandc_hw_syndrome_ecc_read_page;
+		nand->ecc.write_page = rk_nandc_hw_syndrome_ecc_write_page;
+		nand->ecc.read_oob = rk_nandc_hw_ecc_read_oob;
+		nand->ecc.write_oob = rk_nandc_hw_ecc_write_oob;
+		break;
+	case NAND_ECC_NONE:
+	case NAND_ECC_SOFT:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_nandc_exec_op(struct nand_chip *nand,
+			    const struct nand_operation *op,
+			    bool check_only)
+{
+	int i;
+	unsigned int op_id;
+	const struct nand_op_instr *instr = NULL;
+
+	rk_nandc_select_chip(nand, op->cs);
+
+	if (check_only)
+		return 0;
+
+	for (op_id = 0; op_id < op->ninstrs; op_id++) {
+		instr = &op->instrs[op_id];
+
+		switch (instr->type) {
+		case NAND_OP_CMD_INSTR:
+			rk_nandc_write_cmd(nand, instr->ctx.cmd.opcode);
+			break;
+		case NAND_OP_ADDR_INSTR:
+			for (i = 0; i < instr->ctx.addr.naddrs; i++)
+				rk_nandc_write_addr(nand,
+						    instr->ctx.addr.addrs[i]);
+			break;
+		case NAND_OP_DATA_IN_INSTR:
+			rk_nandc_read_buf(nand, instr->ctx.data.buf.in,
+					  instr->ctx.data.len);
+			break;
+		case NAND_OP_DATA_OUT_INSTR:
+			rk_nandc_write_buf(nand, instr->ctx.data.buf.out,
+					   instr->ctx.data.len);
+			break;
+		case NAND_OP_WAITRDY_INSTR:
+			rk_nandc_dev_ready(nand);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static const struct nand_controller_ops rk_nand_controller_ops = {
+	.attach_chip = rk_nandc_attach_chip,
+	.exec_op = rk_nandc_exec_op,
+};
+
+static int rk_nandc_chip_init(struct rk_nand_ctrl *ctrl,
+			      struct device_node *np)
+{
+	struct rk_nand_chip *rk_nand;
+	struct nand_chip *nand;
+	struct mtd_info *mtd;
+	int nsels, ret, i;
+	u32 cs, val;
+
+	nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32));
+	if (nsels <= 0) {
+		dev_err(ctrl->dev, "missing/invalid reg property\n");
+		return -EINVAL;
+	}
+
+	rk_nand = devm_kzalloc(ctrl->dev, sizeof(*rk_nand), GFP_KERNEL);
+	if (!rk_nand)
+		return -ENOMEM;
+
+	rk_nand->idbresblknum = RK_NANDC_IDB_Res_Blk_Num;
+
+	for (i = 0; i < nsels; i++) {
+		ret = of_property_read_u32_index(np, "reg", i, &cs);
+		if (ret) {
+			dev_err(ctrl->dev,
+				"could not retrieve reg property: %d\n",
+				ret);
+			return ret;
+		}
+
+		if (cs >= ctrl->cap->max_cs) {
+			dev_err(ctrl->dev,
+				"invalid reg value: %u (max CS = %d)\n",
+				cs,
+				ctrl->cap->max_cs);
+			return -EINVAL;
+		}
+
+		if (test_and_set_bit(cs, &ctrl->assigned_cs)) {
+			dev_err(ctrl->dev,
+				"CS %d already assigned\n",
+				cs);
+			return -EINVAL;
+		}
+
+		ctrl->cs[i] = cs;
+
+		/* only bank 0 used for boot rom blocks */
+		if (!of_property_read_u32_index(np, "rockchip,idb-res-blk-num",
+						i, &val)) {
+			if (!cs && val >= 2) {
+				rk_nand->idbresblknum = val;
+			} else {
+				dev_err(ctrl->dev,
+					"invalid idb-res-blk-num\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	nand = &rk_nand->nand;
+
+	nand_set_flash_node(nand, np);
+	nand_set_controller_data(nand, ctrl);
+
+	nand->controller = &ctrl->controller;
+	nand->controller->ops = &rk_nand_controller_ops;
+
+	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+	nand->ecc.size = 1024;
+	nand->ecc.strength = 40;
+
+	nand->options = NAND_SKIP_BBTSCAN | NAND_NO_SUBPAGE_WRITE;
+
+	mtd = nand_to_mtd(nand);
+	mtd->dev.parent = ctrl->dev;
+	mtd->name = devm_kasprintf(ctrl->dev, GFP_KERNEL, "%s",
+				   dev_name(ctrl->dev));
+
+	ret = nand_scan(nand, nsels);
+	if (ret)
+		return ret;
+
+	ret = mtd_device_register(mtd, NULL, 0);
+	if (ret) {
+		dev_err(ctrl->dev, "mtd device register failed: %d\n", ret);
+		nand_cleanup(nand);
+		return ret;
+	}
+
+	list_add_tail(&rk_nand->node, &ctrl->chips);
+
+	return 0;
+}
+
+static void rk_nandc_cleanup_chips(struct rk_nand_ctrl *ctrl)
+{
+	struct rk_nand_chip *entry, *temp;
+
+	list_for_each_entry_safe(entry, temp, &ctrl->chips, node) {
+		nand_release(&entry->nand);
+		list_del(&entry->node);
+	}
+}
+
+static int rk_nandc_chips_init(struct rk_nand_ctrl *ctrl)
+{
+	struct device_node *np = ctrl->dev->of_node;
+	struct device_node *nand_np;
+	int nchips;
+	int ret;
+
+	nchips = of_get_child_count(np);
+
+	if (nchips > ctrl->cap->max_cs) {
+		dev_err(ctrl->dev, "too many NAND chips: %d (max CS = %d)\n",
+			nchips,
+			ctrl->cap->max_cs);
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(np, nand_np) {
+		ret = rk_nandc_chip_init(ctrl, nand_np);
+		if (ret) {
+			rk_nandc_cleanup_chips(ctrl);
+			of_node_put(nand_np);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rk_nandc_probe(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl;
+	const struct rk_nandc_data *data;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -ENODEV;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->dev = dev;
+	ctrl->version = data->version;
+	ctrl->ofs = data->ofs;
+	ctrl->cap = data->cap;
+
+	ctrl->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ctrl->regs)) {
+		dev_err(dev, "ioremap failed\n");
+		return PTR_ERR(ctrl->regs);
+	}
+
+	ctrl->irq = platform_get_irq(pdev, 0);
+	if (ctrl->irq < 0) {
+		dev_err(dev, "get irq failed\n");
+		return ctrl->irq;
+	}
+
+	ctrl->hclk = devm_clk_get(dev, "hclk_nandc");
+	if (IS_ERR(ctrl->hclk)) {
+		dev_err(dev, "get hclk_nandc failed\n");
+		return PTR_ERR(ctrl->hclk);
+	}
+
+	ret = clk_prepare_enable(ctrl->hclk);
+	if (ret)
+		return ret;
+
+	ctrl->clk = devm_clk_get(dev, "clk_nandc");
+	if (!(IS_ERR(ctrl->clk))) {
+		clk_set_rate(ctrl->clk, 150 * 1000 * 1000);
+
+		ret = clk_prepare_enable(ctrl->clk);
+		if (ret)
+			goto err_disable_hclk;
+	} else {
+		dev_dbg(dev, "get clk_nandc failed\n");
+	}
+
+	writel_relaxed(0, ctrl->regs + ctrl->ofs->inten);
+	ret = devm_request_irq(dev, ctrl->irq, rk_nandc_interrupt,
+			       0, "nandc", ctrl);
+	if (ret)
+		goto err_disable_clk;
+
+	init_completion(&ctrl->complete);
+	nand_controller_init(&ctrl->controller);
+	INIT_LIST_HEAD(&ctrl->chips);
+
+	rk_nandc_init(ctrl);
+
+	ret = rk_nandc_chips_init(ctrl);
+	if (ret) {
+		dev_err(dev, "init nand chips failed\n");
+		goto err_disable_clk;
+	}
+
+	platform_set_drvdata(pdev, ctrl);
+
+	return 0;
+
+err_disable_clk:
+	clk_disable_unprepare(ctrl->clk);
+err_disable_hclk:
+	clk_disable_unprepare(ctrl->hclk);
+
+	return ret;
+}
+
+static int rk_nandc_remove(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static void rk_nandc_shutdown(struct platform_device *pdev)
+{
+	struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+	rk_nandc_cleanup_chips(ctrl);
+
+	clk_disable_unprepare(ctrl->clk);
+	clk_disable_unprepare(ctrl->hclk);
+	platform_set_drvdata(pdev, NULL);
+}
+
+static const struct rk_nandc_data rk_nandc_V600_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V600_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V622_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V622_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V701_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V701_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V801_data = {
+	.version = VERSION_6,
+	.ofs = (void *)&rk_nandc_V6_reg_offset,
+	.cap = (void *)&rk_nandc_V801_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V900_data = {
+	.version = VERSION_9,
+	.ofs = (void *)&rk_nandc_V9_reg_offset,
+	.cap = (void *)&rk_nandc_V900_cap,
+};
+
+static const struct of_device_id of_rk_nandc_match[] = {
+	{
+		.compatible = "rockchip,px30-nand-controller",
+		.data = &rk_nandc_V900_data,
+	},
+	{
+		.compatible = "rockchip,rk3066-nand-controller",
+		.data = &rk_nandc_V600_data,
+	},
+	{
+		.compatible = "rockchip,rk3228-nand-controller",
+		.data = &rk_nandc_V701_data,
+	},
+	{
+		.compatible = "rockchip,rk3288-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rk3308-nand-controller",
+		.data = &rk_nandc_V801_data,
+	},
+	{
+		.compatible = "rockchip,rk3368-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{
+		.compatible = "rockchip,rv1108-nand-controller",
+		.data = &rk_nandc_V622_data,
+	},
+	{ /* sentinel */ },
+};
+
+static struct platform_driver rk_nandc_driver = {
+	.probe  = rk_nandc_probe,
+	.remove = rk_nandc_remove,
+	.shutdown = rk_nandc_shutdown,
+	.driver = {
+		.name = "rockchip-nand-controller",
+		.of_match_table = of_rk_nandc_match,
+	},
+};
+module_platform_driver(rk_nandc_driver);
+
+MODULE_LICENSE("GPL v2");
-- 
2.11.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  parent reply	other threads:[~2020-01-24 16:30 UTC|newest]

Thread overview: 47+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-01-24 16:29 [RFC PATCH v2 00/10] Enable RK3066 NANDC for MK808 Johan Jonker
2020-01-24 16:29 ` Johan Jonker
2020-01-24 16:29 ` Johan Jonker
2020-01-24 16:29 ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 01/10] dt-bindings: mtd: add rockchip nand controller bindings Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-02-03 15:45   ` Rob Herring
2020-02-03 15:45     ` Rob Herring
2020-02-03 15:45     ` Rob Herring
2020-02-03 15:45     ` Rob Herring
2020-01-24 16:29 ` Johan Jonker [this message]
2020-01-24 16:29   ` [RFC PATCH v2 02/10] mtd: rawnand: rockchip: Add NAND controller driver Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 03/10] ARM: dts: rockchip: add nandc node for rk3066a/rk3188 Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 04/10] ARM: dts: rockchip: add nandc node for rk322x Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 05/10] ARM: dts: rockchip: add nandc nodes for rk3288 Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 06/10] ARM: dts: rockchip: add nandc node for rv1108 Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 07/10] arm64: dts: rockchip: add nandc node for px30 Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29 ` [RFC PATCH v2 08/10] arm64: dts: rockchip: add nandc node for rk3308 Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:29   ` Johan Jonker
2020-01-24 16:30 ` [RFC PATCH v2 09/10] arm64: dts: rockchip: add nandc node for rk3368 Johan Jonker
2020-01-24 16:30   ` Johan Jonker
2020-01-24 16:30   ` Johan Jonker
2020-01-24 16:30 ` [RFC PATCH v2 10/10] ARM: dts: rockchip: rk3066a-mk808: enable nandc node Johan Jonker
2020-01-24 16:30   ` Johan Jonker
2020-01-24 16:30   ` Johan Jonker
2020-01-24 16:30   ` Johan Jonker
2020-01-24 17:17 ` [RFC PATCH v2 00/10] Enable RK3066 NANDC for MK808 Miquel Raynal
2020-01-24 17:17   ` Miquel Raynal
2020-01-24 17:17   ` Miquel Raynal
2020-01-25 22:28   ` Johan Jonker
2020-01-25 22:28     ` Johan Jonker
2020-01-25 22:28     ` Johan Jonker
2020-01-25 22:28     ` Johan Jonker

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200124163001.28910-3-jbx6244@gmail.com \
    --to=jbx6244@gmail.com \
    --cc=devicetree@vger.kernel.org \
    --cc=heiko@sntech.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-rockchip@lists.infradead.org \
    --cc=mark.rutland@arm.com \
    --cc=miquel.raynal@bootlin.com \
    --cc=richard@nod.at \
    --cc=robh+dt@kernel.org \
    --cc=shawn.lin@rock-chips.com \
    --cc=vigneshr@ti.com \
    --cc=yifeng.zhao@rock-chips.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.