[RFC PATCH v1 02/10] mtd: nand: raw: add rockchip nand controller driver

From: Johan Jonker
Date: Wed Jan 08 2020 - 15:54:27 EST


From: Yifeng Zhao <zyf@xxxxxxxxxxxxxx>

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@xxxxxxxxxxxxxx>
Signed-off-by: Johan Jonker <jbx6244@xxxxxxxxx>
---
drivers/mtd/nand/raw/Kconfig | 8 +
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/rockchip_nandc.c | 1224 +++++++++++++++++++++++++++++++++
3 files changed, 1233 insertions(+)
create mode 100644 drivers/mtd/nand/raw/rockchip_nandc.c

diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 74fb91ade..68dc9a36d 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,14 @@ 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 rk3066, rk3188, rk3288 and more.
+
comment "Misc"

config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158..3063fe74a 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_nandc.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_nandc.c b/drivers/mtd/nand/raw/rockchip_nandc.c
new file mode 100644
index 000000000..018308e58
--- /dev/null
+++ b/drivers/mtd/nand/raw/rockchip_nandc.c
@@ -0,0 +1,1224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on:
+ * https://github.com/rockchip-linux/kernel/blob/develop-4.4/drivers/mtd/nand/
+ * rockchip_nand_v6.c
+ * https://github.com/rockchip-linux/kernel/blob/develop-4.4/drivers/mtd/nand/
+ * rockchip_nand_v9.c
+ * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@xxxxxxxxxxxxxx
+ *
+ * Update/restyle for linux-next.
+ * Add exec_op function.
+ * Combine driver for nandc version 6 and 9.
+ * Copyright (c) 2020 Johan Jonker jbx6244@xxxxxxxxx
+ */
+
+#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 NANDC_ID_V600 0x56363030
+#define NANDC_ID_V622 0x56363232
+#define NANDC_ID_V701 0x701
+#define NANDC_ID_V800 0x56383030
+#define NANDC_ID_V801 0x801
+#define NANDC_ID_V900 0x56393030
+
+#define NANDC_IDBResBlkNum 16
+#define NANDC_IDBEccBits 24
+#define NANDC_IDBStartAddr 0
+
+#define NANDC_V6_ECC_16 0x00000000
+#define NANDC_V6_ECC_24 0x00000010
+#define NANDC_V6_ECC_40 0x00040000
+#define NANDC_V6_ECC_60 0x00040010
+
+#define NANDC_V9_ECC_16 0x02000001
+#define NANDC_V9_ECC_40 0x04000001
+#define NANDC_V9_ECC_60 0x06000001
+#define NANDC_V9_ECC_70 0x00000001
+
+#define NANDC_NUM_BANKS 4
+#define NANDC_DEF_TIMEOUT 10000
+
+#define NANDC_REG_DATA 0x00
+#define NANDC_REG_ADDR 0x04
+#define NANDC_REG_CMD 0x08
+
+/* register offset nandc version 6 */
+#define NANDC_REG_V6_FMCTL 0x00
+#define NANDC_REG_V6_FMWAIT 0x04
+#define NANDC_REG_V6_FLCTL 0x08
+#define NANDC_REG_V6_BCHCTL 0x0c
+#define NANDC_REG_V6_DMA_CFG 0x10
+#define NANDC_REG_V6_DMA_BUF0 0x14
+#define NANDC_REG_V6_DMA_BUF1 0x18
+#define NANDC_REG_V6_DMA_ST 0x1C
+#define NANDC_REG_V6_BCHST 0x20
+#define NANDC_REG_V6_RANDMZ 0x150
+#define NANDC_REG_V6_VER 0x160
+#define NANDC_REG_V6_INTEN 0x16C
+#define NANDC_REG_V6_INTCLR 0x170
+#define NANDC_REG_V6_INTST 0x174
+#define NANDC_REG_V6_SPARE0 0x200
+#define NANDC_REG_V6_SPARE1 0x230
+
+/* register offset nandc version 9 */
+#define NANDC_REG_V9_FMCTL 0x00
+#define NANDC_REG_V9_FMWAIT 0x04
+#define NANDC_REG_V9_FLCTL 0x10
+#define NANDC_REG_V9_BCHCTL 0x20
+#define NANDC_REG_V9_DMA_CFG 0x30
+#define NANDC_REG_V9_DMA_BUF0 0x34
+#define NANDC_REG_V9_DMA_BUF1 0x38
+#define NANDC_REG_V9_DMA_ST 0x40
+#define NANDC_REG_V9_VER 0x80
+#define NANDC_REG_V9_INTEN 0x120
+#define NANDC_REG_V9_INTCLR 0x124
+#define NANDC_REG_V9_INTST 0x128
+#define NANDC_REG_V9_BCHST 0x150
+#define NANDC_REG_V9_SPARE0 0x200
+#define NANDC_REG_V9_SPARE1 0x204
+#define NANDC_REG_V9_RANDMZ 0x208
+
+/* register offset nandc common */
+#define NANDC_REG_BANK0 0x800
+#define NANDC_REG_SRAM0 0x1000
+
+/* FMCTL */
+#define NANDC_V6_FM_WP BIT(8)
+#define NANDC_V6_FM_CE_SEL_M 0xFF
+#define NANDC_V6_FM_CE_SEL(x) (1 << (x))
+#define NANDC_V6_FM_FREADY BIT(9)
+
+#define NANDC_V9_FM_WP BIT(8)
+#define NANDC_V9_FM_CE_SEL_M 0xFF
+#define NANDC_V9_FM_CE_SEL(x) (1 << (x))
+#define NANDC_V9_RDY BIT(9)
+
+/* FLCTL */
+#define NANDC_V6_FL_RST BIT(0)
+#define NANDC_V6_FL_DIR(x) ((x) ? BIT(1) : 0)
+#define NANDC_V6_FL_XFER_START BIT(2)
+#define NANDC_V6_FL_XFER_EN BIT(3)
+#define NANDC_V6_FL_ST_BUF_S 0x4
+#define NANDC_V6_FL_XFER_COUNT BIT(5)
+#define NANDC_V6_FL_ACORRECT BIT(10)
+#define NANDC_V6_FL_XFER_READY BIT(20)
+#define NANDC_V6_FL_PAGE_NUM(x) ((x) << 22)
+#define NANDC_V6_FL_ASYNC_TOG_MIX BIT(29)
+
+#define NANDC_V9_FL_RST BIT(0)
+#define NANDC_V9_FL_DIR(x) ((x) ? BIT(1) : 0)
+#define NANDC_V9_FL_XFER_START BIT(2)
+#define NANDC_V9_FL_XFER_EN BIT(3)
+#define NANDC_V9_FL_ST_BUF_S 0x4
+#define NANDC_V9_FL_XFER_COUNT BIT(5)
+#define NANDC_V9_FL_ACORRECT BIT(10)
+#define NANDC_V9_FL_XFER_READY BIT(20)
+#define NANDC_V9_FL_PAGE_NUM(x) ((x) << 22)
+#define NANDC_V9_FL_ASYNC_TOG_MIX BIT(29)
+
+/* BCHCTL */
+#define NAND_V6_BCH_REGION_S 0x5
+#define NAND_V6_BCH_REGION_M 0x7
+
+#define NAND_V9_BCH_MODE_S 25
+#define NAND_V9_BCH_MODE_M 0x7
+
+/* BCHST */
+#define NANDC_V6_BCH0_ST_ERR BIT(2)
+#define NANDC_V6_BCH1_ST_ERR BIT(15)
+#define NANDC_V6_ECC_ERR_CNT0(x) ((((x & (0x1F << 3)) >> 3) \
+ | ((x & (1 << 27)) >> 22)) & 0x3F)
+#define NANDC_V6_ECC_ERR_CNT1(x) ((((x & (0x1F << 16)) >> 16) \
+ | ((x & (1 << 29)) >> 24)) & 0x3F)
+
+#define NANDC_V9_BCH0_ST_ERR BIT(2)
+#define NANDC_V9_BCH1_ST_ERR BIT(18)
+#define NANDC_V9_ECC_ERR_CNT0(x) (((x) & (0x7F << 3)) >> 3)
+#define NANDC_V9_ECC_ERR_CNT1(x) (((x) & (0x7F << 19)) >> 19)
+
+/* DMA_CFG */
+#define NANDC_V6_DMA_CFG_WR_ST BIT(0)
+#define NANDC_V6_DMA_CFG_WR(x) ((!x) ? BIT(1) : 0)
+#define NANDC_V6_DMA_CFG_BUS_MODE BIT(2)
+
+#define NANDC_V6_DMA_CFG_HSIZE_8 0
+#define NANDC_V6_DMA_CFG_HSIZE_16 (1 << 3)
+#define NANDC_V6_DMA_CFG_HSIZE_32 (2 << 3)
+
+#define NANDC_V6_DMA_CFG_BURST_1 0
+#define NANDC_V6_DMA_CFG_BURST_4 (3 << 6)
+#define NANDC_V6_DMA_CFG_BURST_8 (5 << 6)
+#define NANDC_V6_DMA_CFG_BURST_16 (7 << 6)
+
+#define NANDC_V6_DMA_CFG_INCR_NUM(x) ((x) << 9)
+
+#define NANDC_V9_DMA_CFG_WR_ST BIT(0)
+#define NANDC_V9_DMA_CFG_WR(x) ((!x) ? BIT(1) : 0)
+#define NANDC_V9_DMA_CFG_BUS_MODE BIT(2)
+
+#define NANDC_V9_DMA_CFG_HSIZE_8 0
+#define NANDC_V9_DMA_CFG_HSIZE_16 (1 << 3)
+#define NANDC_V9_DMA_CFG_HSIZE_32 (2 << 3)
+
+#define NANDC_V9_DMA_CFG_BURST_1 0
+#define NANDC_V9_DMA_CFG_BURST_4 (3 << 6)
+#define NANDC_V9_DMA_CFG_BURST_8 (5 << 6)
+#define NANDC_V9_DMA_CFG_BURST_16 (7 << 6)
+
+#define NANDC_V9_DMA_CFG_INCR_NUM(x) ((x) << 9)
+
+/* INTEN */
+#define NANDC_V6_INT_DMA BIT(0)
+
+#define NANDC_V9_INT_DMA BIT(0)
+
+enum rk_nandc_version {
+ VERSION_6 = 6,
+ VERSION_9 = 9,
+};
+
+struct rk_nandc_data {
+ enum rk_nandc_version version;
+};
+
+struct rk_nand_controller {
+ void __iomem *regs;
+ int irq;
+ struct clk *hclk;
+ struct clk *clk;
+ struct list_head chips;
+ struct completion complete;
+ struct nand_controller controller;
+ int banks[NANDC_NUM_BANKS];
+ bool bootromblocks;
+ int ecc_mode;
+ uint32_t ecc_strength;
+ int max_ecc_strength;
+ uint32_t *oob_buf;
+ uint32_t *page_buf;
+ int selected_bank;
+ enum rk_nandc_version version;
+};
+
+struct rk_nand_chip {
+ struct nand_chip nand;
+ struct list_head chip_list;
+};
+
+static struct rk_nand_controller g_nandc_info[2];
+static int g_id_counter;
+
+static void rk_nandc_init(struct rk_nand_controller *ctrl)
+{
+ if (ctrl->version == VERSION_9) {
+ writel(0, ctrl->regs + NANDC_REG_V9_RANDMZ);
+ writel(0, ctrl->regs + NANDC_REG_V9_DMA_CFG);
+ writel(NANDC_V9_FM_WP, ctrl->regs + NANDC_REG_V9_FMCTL);
+ writel(NANDC_V9_FL_RST, ctrl->regs + NANDC_REG_V9_FLCTL);
+ writel(0x1081, ctrl->regs + NANDC_REG_V9_FMWAIT);
+ } else {
+ writel(0, ctrl->regs + NANDC_REG_V6_RANDMZ);
+ writel(0, ctrl->regs + NANDC_REG_V6_DMA_CFG);
+ writel(NANDC_V6_FM_WP, ctrl->regs + NANDC_REG_V6_FMCTL);
+ writel(NANDC_V6_FL_RST, ctrl->regs + NANDC_REG_V6_FLCTL);
+ writel(0x1081, ctrl->regs + NANDC_REG_V6_FMWAIT);
+ }
+}
+
+static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
+{
+ struct rk_nand_controller *ctrl = dev_id;
+
+ if (ctrl->version == VERSION_9) {
+ uint32_t st = readl(ctrl->regs + NANDC_REG_V9_INTST);
+ uint32_t ien = readl(ctrl->regs + NANDC_REG_V9_INTEN);
+
+ if (!(ien & st))
+ return IRQ_NONE;
+
+ if ((ien & st) == ien)
+ complete(&ctrl->complete);
+
+ writel(st, ctrl->regs + NANDC_REG_V9_INTCLR);
+ writel(~st & ien, ctrl->regs + NANDC_REG_V9_INTEN);
+ } else {
+ uint32_t st = readl(ctrl->regs + NANDC_REG_V6_INTST);
+ uint32_t ien = readl(ctrl->regs + NANDC_REG_V6_INTEN);
+
+ if (!(ien & st))
+ return IRQ_NONE;
+
+ if ((ien & st) == ien)
+ complete(&ctrl->complete);
+
+ writel(st, ctrl->regs + NANDC_REG_V6_INTCLR);
+ writel(~st & ien, ctrl->regs + NANDC_REG_V6_INTEN);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void rk_nandc_select_chip(struct nand_chip *nand, int chipnr)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ uint32_t reg;
+ int banknr;
+
+ /* The register offset and bit positions for
+ * NANDC_REG_V6_FMCTL and NANDC_REG_V9_FMCTL
+ * are identical.
+ */
+ reg = readl(ctrl->regs + NANDC_REG_V6_FMCTL);
+ reg &= ~NANDC_V6_FM_CE_SEL_M;
+
+ if (chipnr == -1) {
+ banknr = -1;
+ } else {
+ banknr = ctrl->banks[chipnr];
+
+ reg |= NANDC_V6_FM_CE_SEL(banknr);
+ }
+ writel(reg, ctrl->regs + NANDC_REG_V6_FMCTL);
+
+ ctrl->selected_bank = banknr;
+}
+
+static int rk_nandc_hw_ecc_setup(struct nand_chip *nand,
+ uint32_t strength)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ uint32_t reg;
+
+ nand->ecc.strength = strength;
+ nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * 14, 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 = NANDC_V9_ECC_70;
+ break;
+ case 60:
+ reg = NANDC_V9_ECC_60;
+ break;
+ case 40:
+ reg = NANDC_V9_ECC_40;
+ break;
+ case 16:
+ reg = NANDC_V9_ECC_16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(reg, ctrl->regs + NANDC_REG_V9_BCHCTL);
+ } else {
+ switch (nand->ecc.strength) {
+ case 60:
+ reg = NANDC_V6_ECC_60;
+ break;
+ case 40:
+ reg = NANDC_V6_ECC_40;
+ break;
+ case 24:
+ reg = NANDC_V6_ECC_24;
+ break;
+ case 16:
+ reg = NANDC_V6_ECC_16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ writel(reg, ctrl->regs + NANDC_REG_V6_BCHCTL);
+ }
+
+ return 0;
+}
+
+static void rk_nandc_xfer_start(struct rk_nand_controller *ctrl,
+ uint8_t dir, uint8_t n_KB,
+ dma_addr_t dma_data, dma_addr_t dma_oob)
+{
+ uint32_t reg;
+
+ if (ctrl->version == VERSION_9) {
+ reg = NANDC_V9_DMA_CFG_WR_ST |
+ NANDC_V9_DMA_CFG_WR(dir) |
+ NANDC_V9_DMA_CFG_BUS_MODE |
+ NANDC_V9_DMA_CFG_HSIZE_32 |
+ NANDC_V9_DMA_CFG_BURST_16 |
+ NANDC_V9_DMA_CFG_INCR_NUM(16);
+ writel(reg, ctrl->regs + NANDC_REG_V9_DMA_CFG);
+ writel((uint32_t)dma_data, ctrl->regs + NANDC_REG_V9_DMA_BUF0);
+ writel((uint32_t)dma_oob, ctrl->regs + NANDC_REG_V9_DMA_BUF1);
+
+ reg = NANDC_V9_FL_DIR(dir) |
+ NANDC_V9_FL_XFER_EN |
+ NANDC_V9_FL_XFER_COUNT |
+ NANDC_V9_FL_ACORRECT |
+ NANDC_V9_FL_PAGE_NUM(n_KB) |
+ NANDC_V9_FL_ASYNC_TOG_MIX;
+ writel(reg, ctrl->regs + NANDC_REG_V9_FLCTL);
+ reg |= NANDC_V9_FL_XFER_START;
+ writel(reg, ctrl->regs + NANDC_REG_V9_FLCTL);
+ } else {
+ reg = readl(ctrl->regs + NANDC_REG_V6_BCHCTL);
+ reg = (reg & (~(NAND_V6_BCH_REGION_M <<
+ NAND_V6_BCH_REGION_S))) |
+ (ctrl->selected_bank << NAND_V6_BCH_REGION_S);
+ writel(reg, ctrl->regs + NANDC_REG_V6_BCHCTL);
+
+ reg = NANDC_V6_DMA_CFG_WR_ST |
+ NANDC_V6_DMA_CFG_WR(dir) |
+ NANDC_V6_DMA_CFG_BUS_MODE |
+ NANDC_V6_DMA_CFG_HSIZE_32 |
+ NANDC_V6_DMA_CFG_BURST_16 |
+ NANDC_V6_DMA_CFG_INCR_NUM(16);
+ writel(reg, ctrl->regs + NANDC_REG_V6_DMA_CFG);
+ writel(dma_data, ctrl->regs + NANDC_REG_V6_DMA_BUF0);
+ writel(dma_oob, ctrl->regs + NANDC_REG_V6_DMA_BUF1);
+
+ reg = NANDC_V6_FL_DIR(dir) |
+ NANDC_V6_FL_XFER_EN |
+ NANDC_V6_FL_XFER_COUNT |
+ NANDC_V6_FL_ACORRECT |
+ NANDC_V6_FL_PAGE_NUM(n_KB) |
+ NANDC_V6_FL_ASYNC_TOG_MIX;
+ writel(reg, ctrl->regs + NANDC_REG_V6_FLCTL);
+ reg |= NANDC_V6_FL_XFER_START;
+ writel(reg, ctrl->regs + NANDC_REG_V6_FLCTL);
+ }
+}
+
+static int rk_nandc_wait_for_xfer_done(struct rk_nand_controller *ctrl)
+{
+ uint32_t reg;
+ int ret;
+
+ if (ctrl->version == VERSION_9) {
+ void __iomem *ptr = ctrl->regs + NANDC_REG_V9_FLCTL;
+
+ ret = readl_poll_timeout_atomic(ptr, reg,
+ reg & NANDC_V9_FL_XFER_READY,
+ 1, NANDC_DEF_TIMEOUT);
+ } else {
+ void __iomem *ptr = ctrl->regs + NANDC_REG_V6_FLCTL;
+
+ ret = readl_poll_timeout_atomic(ptr, reg,
+ reg & NANDC_V6_FL_XFER_READY,
+ 1, NANDC_DEF_TIMEOUT);
+ }
+ if (ret)
+ pr_err("timeout reg=%x\n", reg);
+
+ return ret;
+}
+
+static unsigned long rk_nandc_dma_map_single(struct device *dev,
+ void *ptr, int size, int dir)
+{
+#ifdef CONFIG_ARM64
+ __dma_map_area((void *)ptr, size, dir);
+ return ((unsigned long)virt_to_phys((void *)ptr));
+#else
+ return dma_map_single(dev, (void *)ptr, size, dir);
+#endif
+}
+
+static void rk_nandc_dma_unmap_single(struct device *dev,
+ unsigned long ptr, int size, int dir)
+{
+#ifdef CONFIG_ARM64
+ __dma_unmap_area(phys_to_virt(ptr), size, dir);
+#else
+ dma_unmap_single(dev, (dma_addr_t)ptr, size, dir);
+#endif
+}
+
+static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand,
+ uint8_t *buf,
+ int oob_required, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_controller *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;
+ int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+ rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+ if ((page < pages_per_blk * NANDC_IDBResBlkNum) &&
+ ctrl->bootromblocks)
+ rk_nandc_hw_ecc_setup(nand, NANDC_IDBEccBits);
+
+ nand_read_page_op(nand, page, 0, NULL, 0);
+
+ dma_data = rk_nandc_dma_map_single(mtd->dev.parent,
+ ctrl->page_buf, mtd->writesize,
+ DMA_FROM_DEVICE);
+ dma_oob = rk_nandc_dma_map_single(mtd->dev.parent,
+ ctrl->oob_buf, dma_oob_size,
+ DMA_FROM_DEVICE);
+
+ init_completion(&ctrl->complete);
+ if (ctrl->version == VERSION_9)
+ writel(NANDC_V9_INT_DMA, ctrl->regs + NANDC_REG_V9_INTEN);
+ else
+ writel(NANDC_V6_INT_DMA, ctrl->regs + NANDC_REG_V6_INTEN);
+ rk_nandc_xfer_start(ctrl, 0, ecc->steps, dma_data, dma_oob);
+ wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(5));
+ rk_nandc_wait_for_xfer_done(ctrl);
+ rk_nandc_dma_unmap_single(mtd->dev.parent, dma_data, mtd->writesize,
+ DMA_FROM_DEVICE);
+ rk_nandc_dma_unmap_single(mtd->dev.parent, dma_oob, dma_oob_size,
+ DMA_FROM_DEVICE);
+
+ memcpy(buf, ctrl->page_buf, mtd->writesize);
+
+ if (oob_required) {
+ uint8_t *oob;
+ uint32_t 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 {
+ uint8_t oob_step = (ctrl->ecc_mode <= 24) ?
+ 64 : 128;
+ tmp = ctrl->oob_buf[i * oob_step / 4];
+ }
+ *oob++ = (uint8_t)tmp;
+ *oob++ = (uint8_t)(tmp >> 8);
+ *oob++ = (uint8_t)(tmp >> 16);
+ *oob++ = (uint8_t)(tmp >> 24);
+ }
+ }
+
+ if (ctrl->version == VERSION_9) {
+ for (i = 0; i < ecc->steps / 2; i++) {
+ bch_st = readl(ctrl->regs + NANDC_REG_V9_BCHST + i * 4);
+ if (bch_st & NANDC_V9_BCH0_ST_ERR ||
+ bch_st & NANDC_V9_BCH1_ST_ERR) {
+ mtd->ecc_stats.failed++;
+ max_bitflips = -1;
+ } else {
+ ret = NANDC_V9_ECC_ERR_CNT0(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+
+ ret = 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 + NANDC_REG_V6_BCHST + i * 4);
+ if (bch_st & NANDC_V6_BCH0_ST_ERR ||
+ bch_st & NANDC_V6_BCH1_ST_ERR) {
+ mtd->ecc_stats.failed++;
+ max_bitflips = -1;
+ } else {
+ ret = NANDC_V6_ECC_ERR_CNT0(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+
+ ret = NANDC_V6_ECC_ERR_CNT1(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+ }
+ }
+ }
+
+ if (max_bitflips == -1) {
+ dev_err(mtd->dev.parent,
+ "read_page %x %x %x %x %x %p %x\n",
+ page,
+ max_bitflips,
+ bch_st,
+ ((uint32_t *)buf)[0],
+ ((uint32_t *)buf)[1],
+ buf,
+ (uint32_t)dma_data);
+ }
+
+ if (ctrl->bootromblocks)
+ rk_nandc_hw_ecc_setup(nand, ctrl->ecc_mode);
+
+ return max_bitflips;
+}
+
+static int rk_nandc_hw_syndrome_ecc_write_page(struct nand_chip *nand,
+ const uint8_t *buf,
+ int oob_required,
+ int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ dma_addr_t dma_data, dma_oob;
+ int i;
+ int dma_oob_size = ecc->steps * 128;
+ int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+ rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+ if ((page < pages_per_blk * NANDC_IDBResBlkNum) &&
+ ctrl->bootromblocks)
+ rk_nandc_hw_ecc_setup(nand, NANDC_IDBEccBits);
+
+ nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+ for (i = 0; i < ecc->steps; i++) {
+ uint32_t tmp;
+
+ if (oob_required) {
+ uint8_t *oob;
+
+ oob = nand->oob_poi +
+ i * (ecc->bytes + nand->ecc.prepad);
+ tmp = oob[0] |
+ (oob[1] << 8) |
+ (oob[2] << 16) |
+ (oob[3] << 24);
+ } else {
+ /* The first NANDC_IDBResBlkNum blocks are
+ * for the stored loader. The first 32 bits
+ * of oob must contain a sort of link to
+ * the next page address in that same block
+ * for the Bootrom.
+ * Depending on what FTL from Rockchip is used,
+ * the first 2 pages in the NANDC_IDBResBlkNum blocks
+ * are reserved for FlashPhyInfo.
+ * Raw IDB data then starts at page 2 or higher.
+ */
+ if (!i &&
+ page < pages_per_blk * NANDC_IDBResBlkNum &&
+ page >= NANDC_IDBStartAddr)
+ tmp = (page & (pages_per_blk - 1)) * 4;
+ else
+ tmp = 0xFFFFFFFF;
+ }
+ if (ctrl->version == VERSION_9) {
+ ctrl->oob_buf[i] = tmp;
+ } else {
+ uint8_t oob_step = (ctrl->ecc_mode <= 24) ?
+ 64 : 128;
+ ctrl->oob_buf[i * oob_step / 4] = tmp;
+ }
+ }
+
+ memcpy(ctrl->page_buf, buf, mtd->writesize);
+ dma_data = rk_nandc_dma_map_single(mtd->dev.parent,
+ ctrl->page_buf, mtd->writesize,
+ DMA_TO_DEVICE);
+ dma_oob = rk_nandc_dma_map_single(mtd->dev.parent,
+ ctrl->oob_buf, dma_oob_size,
+ DMA_TO_DEVICE);
+ init_completion(&ctrl->complete);
+ if (ctrl->version == VERSION_9)
+ writel(NANDC_V9_INT_DMA, ctrl->regs + NANDC_REG_V9_INTEN);
+ else
+ writel(NANDC_V6_INT_DMA, ctrl->regs + NANDC_REG_V6_INTEN);
+ rk_nandc_xfer_start(ctrl, 1, ecc->steps, dma_data, dma_oob);
+ wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(10));
+ rk_nandc_wait_for_xfer_done(ctrl);
+ rk_nandc_dma_unmap_single(mtd->dev.parent, dma_data, mtd->writesize,
+ DMA_TO_DEVICE);
+ rk_nandc_dma_unmap_single(mtd->dev.parent, dma_oob, dma_oob_size,
+ DMA_TO_DEVICE);
+
+ if (ctrl->bootromblocks)
+ rk_nandc_hw_ecc_setup(nand, ctrl->ecc_mode);
+
+ return nand_prog_page_end_op(nand);
+}
+
+static int rk_nandc_hw_ecc_read_oob(struct nand_chip *nand, int page)
+{
+ uint8_t *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;
+ uint8_t *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, uint8_t *buf, int len)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ int offs = 0;
+ void __iomem *bank_base = ctrl->regs + NANDC_REG_BANK0 +
+ ctrl->selected_bank * 0x100;
+
+ for (offs = 0; offs < len; offs++)
+ buf[offs] = readb(bank_base);
+}
+
+static void rk_nandc_write_buf(struct nand_chip *nand,
+ const uint8_t *buf, int len)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ int offs = 0;
+ void __iomem *bank_base = ctrl->regs + NANDC_REG_BANK0 +
+ ctrl->selected_bank * 0x100;
+
+ for (offs = 0; offs < len; offs++)
+ writeb(buf[offs], bank_base);
+}
+
+static void rk_nandc_write_cmd(struct nand_chip *nand, uint8_t cmd)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+
+ void __iomem *bank_base = ctrl->regs + NANDC_REG_BANK0 +
+ ctrl->selected_bank * 0x100 +
+ NANDC_REG_CMD;
+
+ writeb(cmd, bank_base);
+}
+
+static void rk_nandc_write_addr(struct nand_chip *nand, uint8_t addr)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+
+ void __iomem *bank_base = ctrl->regs + NANDC_REG_BANK0 +
+ ctrl->selected_bank * 0x100 +
+ NANDC_REG_ADDR;
+
+ writeb(addr, bank_base);
+}
+
+static int rk_nandc_dev_ready(struct nand_chip *nand)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+
+ if (readl(ctrl->regs + NANDC_REG_V6_FMCTL) & 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 void rk_nandc_free_buffer(struct nand_chip *nand)
+{
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+
+ kfree(ctrl->page_buf);
+ kfree(ctrl->oob_buf);
+}
+
+static int rk_nandc_buffer_init(struct nand_chip *nand)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+
+ ctrl->page_buf = kmalloc(mtd->writesize, GFP_KERNEL | GFP_DMA);
+ if (!ctrl->page_buf)
+ return -ENOMEM;
+
+ ctrl->oob_buf = kmalloc(nand->ecc.steps * 128, GFP_KERNEL | GFP_DMA);
+ if (!ctrl->oob_buf) {
+ kfree(ctrl->page_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int rk_nandc_hw_ecc_ctrl_init(struct nand_chip *nand)
+{
+ uint8_t strengths_v6[] = {60, 40, 24, 16};
+ uint8_t strengths_v9[] = {70, 60, 40, 16};
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_controller *ctrl = nand_get_controller_data(nand);
+ int max_strength;
+ uint32_t i, ver;
+
+ if (nand->options & NAND_IS_BOOT_MEDIUM)
+ ctrl->bootromblocks = true;
+ else
+ ctrl->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) {
+ ctrl->max_ecc_strength = 70;
+ ver = readl(ctrl->regs + NANDC_REG_V9_VER);
+ if (ver != NANDC_ID_V900)
+ dev_err(mtd->dev.parent,
+ "unsupported nandc version %x\n", ver);
+
+ if (max_strength > ctrl->max_ecc_strength)
+ max_strength = ctrl->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;
+ }
+
+ ctrl->ecc_mode = strengths_v9[i];
+ } else {
+ ctrl->max_ecc_strength = 60;
+
+ ver = readl(ctrl->regs + NANDC_REG_V6_VER);
+ if (ver == NANDC_ID_V801)
+ ctrl->max_ecc_strength = 16;
+ else if (ver == NANDC_ID_V600 ||
+ ver == NANDC_ID_V622 ||
+ ver == NANDC_ID_V701 ||
+ ver == NANDC_ID_V800)
+ ctrl->max_ecc_strength = 60;
+ else
+ dev_err(mtd->dev.parent,
+ "unsupported nandc version %x\n", ver);
+
+ if (max_strength > ctrl->max_ecc_strength)
+ max_strength = ctrl->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;
+ }
+
+ ctrl->ecc_mode = strengths_v6[i];
+ }
+ rk_nandc_hw_ecc_setup(nand, ctrl->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 void rk_nandc_detach_chip(struct nand_chip *nand)
+{
+ switch (nand->ecc.mode) {
+ case NAND_ECC_HW_SYNDROME:
+ rk_nandc_free_buffer(nand);
+ break;
+ default:
+ break;
+ }
+}
+
+static int rk_nandc_attach_chip(struct nand_chip *nand)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ int ret;
+
+ switch (nand->ecc.mode) {
+ case NAND_ECC_HW_SYNDROME:
+ 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_HW:
+ 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,
+ .detach_chip = rk_nandc_detach_chip,
+ .exec_op = rk_nandc_exec_op,
+};
+
+static int rk_nandc_chip_init(struct device *dev,
+ struct rk_nand_controller *ctrl,
+ struct device_node *np, unsigned int chipnr)
+{
+ struct rk_nand_chip *node;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ const __be32 *reg;
+ int ret;
+
+ reg = of_get_property(np, "reg", NULL);
+ if (!reg)
+ return -EINVAL;
+
+ ctrl->banks[chipnr] = be32_to_cpu(*reg);
+
+ if (ctrl->banks[chipnr] < 0)
+ return -EINVAL;
+
+ node = devm_kzalloc(dev, sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return -ENOMEM;
+
+ nand = &node->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 = dev;
+ mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev),
+ ctrl->banks[chipnr]);
+
+ ret = nand_scan(nand, 1);
+ if (ret)
+ return ret;
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ dev_err(dev, "mtd device register failed: %d\n", ret);
+ nand_release(nand);
+ return ret;
+ }
+
+ list_add_tail(&node->chip_list, &ctrl->chips);
+
+ return 0;
+}
+
+static int rk_nandc_cleanup_chips(struct rk_nand_controller *ctrl)
+{
+ struct rk_nand_chip *node;
+ struct mtd_info *mtd;
+ int ret;
+
+ while (!list_empty(&ctrl->chips)) {
+ node = list_first_entry(&ctrl->chips, struct rk_nand_chip,
+ chip_list);
+ mtd = nand_to_mtd(&node->nand);
+ ret = mtd_device_unregister(mtd);
+ if (ret)
+ return ret;
+
+ rk_nandc_free_buffer(&node->nand);
+ nand_cleanup(&node->nand);
+ list_del(&node->chip_list);
+ }
+
+ return 0;
+}
+
+static int rk_nandc_chips_init(struct device *dev,
+ struct rk_nand_controller *ctrl)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *nand_np;
+ int nchips = of_get_child_count(np);
+ int i = 0;
+ int ret;
+
+ if (nchips > NANDC_NUM_BANKS) {
+ dev_err(dev, "too many NAND chips: %d (max = 4)\n", nchips);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node(np, nand_np) {
+ ret = rk_nandc_chip_init(dev, ctrl, nand_np, i);
+ if (ret) {
+ rk_nandc_cleanup_chips(ctrl);
+ of_node_put(nand_np);
+ return ret;
+ }
+ i++;
+ }
+
+ return 0;
+}
+
+static int rk_nandc_probe(struct platform_device *pdev)
+{
+ const struct rk_nandc_data *data;
+ struct device *dev = &pdev->dev;
+ struct device_node *node;
+ int id;
+ int ret;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data)
+ return -ENODEV;
+
+ node = pdev->dev.of_node;
+
+ id = of_alias_get_id(node, "nandc");
+ if (id < 0)
+ id = g_id_counter;
+ if ((id >= ARRAY_SIZE(g_nandc_info) || g_nandc_info[id].regs)) {
+ dev_err(
+ &pdev->dev,
+ "failed to get id for nandc node '%pOFn'\n",
+ node);
+ of_node_put(node);
+ return -ENODEV;
+ }
+ ++g_id_counter;
+
+ g_nandc_info[id].version = data->version;
+
+ g_nandc_info[id].regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(g_nandc_info[id].regs)) {
+ dev_err(dev, "ioremap failed\n");
+ return PTR_ERR(g_nandc_info[id].regs);
+ }
+
+ g_nandc_info[id].irq = platform_get_irq(pdev, 0);
+ if (g_nandc_info[id].irq < 0) {
+ dev_err(dev, "get irq failed\n");
+ return g_nandc_info[id].irq;
+ }
+
+ g_nandc_info[id].hclk = devm_clk_get(dev, "hclk_nandc");
+ if (IS_ERR(g_nandc_info[id].hclk)) {
+ dev_err(dev, "get hclk_nandc failed\n");
+ return PTR_ERR(g_nandc_info[id].hclk);
+ }
+
+ ret = clk_prepare_enable(g_nandc_info[id].hclk);
+ if (ret)
+ return ret;
+
+ g_nandc_info[id].clk = devm_clk_get(dev, "clk_nandc");
+ if (!(IS_ERR(g_nandc_info[id].clk))) {
+ clk_set_rate(g_nandc_info[id].clk, 150 * 1000 * 1000);
+
+ ret = clk_prepare_enable(g_nandc_info[id].clk);
+ if (ret)
+ goto err_disable_hclk;
+ } else
+ dev_err(dev, "get clk_nandc failed\n");
+
+ if (g_nandc_info[id].version == VERSION_9)
+ writel(0, g_nandc_info[id].regs + NANDC_REG_V9_INTEN);
+ else
+ writel(0, g_nandc_info[id].regs + NANDC_REG_V6_INTEN);
+ ret = devm_request_irq(dev, g_nandc_info[id].irq, rk_nandc_interrupt,
+ 0, "nandc", &g_nandc_info[id]);
+ if (ret)
+ goto err_disable_clk;
+
+ nand_controller_init(&g_nandc_info[id].controller);
+ INIT_LIST_HEAD(&g_nandc_info[id].chips);
+
+ rk_nandc_init(&g_nandc_info[id]);
+
+ ret = rk_nandc_chips_init(dev, &g_nandc_info[id]);
+ if (ret) {
+ dev_err(dev, "init nand chips failed\n");
+ goto err_disable_clk;
+ }
+
+ platform_set_drvdata(pdev, &g_nandc_info[id]);
+
+ return 0;
+
+err_disable_clk:
+ clk_disable_unprepare(g_nandc_info[id].clk);
+err_disable_hclk:
+ clk_disable_unprepare(g_nandc_info[id].hclk);
+
+ return ret;
+}
+
+static int rk_nandc_remove(struct platform_device *pdev)
+{
+ struct rk_nand_controller *ctrl = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = rk_nandc_cleanup_chips(ctrl);
+ if (ret)
+ return ret;
+
+ 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_controller *ctrl = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = rk_nandc_cleanup_chips(ctrl);
+ if (ret)
+ return;
+
+ clk_disable_unprepare(ctrl->clk);
+ clk_disable_unprepare(ctrl->hclk);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static const struct rk_nandc_data rk_nandc_v6_data = {
+ .version = VERSION_6,
+};
+
+static const struct rk_nandc_data rk_nandc_v9_data = {
+ .version = VERSION_9,
+};
+
+static const struct of_device_id of_rk_nandc_match[] = {
+ {
+ .compatible = "rockchip,nandc-v6",
+ .data = &rk_nandc_v6_data,
+ },
+ {
+ .compatible = "rockchip,nandc-v9",
+ .data = &rk_nandc_v9_data,
+ },
+ { /* sentinel */ },
+};
+
+static struct platform_driver rk_nandc_driver = {
+ .probe = rk_nandc_probe,
+ .remove = rk_nandc_remove,
+ .shutdown = rk_nandc_shutdown,
+ .driver = {
+ .name = "rockchip-nandc",
+ .of_match_table = of_rk_nandc_match,
+ },
+};
+
+module_platform_driver(rk_nandc_driver);
+MODULE_LICENSE("GPL v2");
--
2.11.0