Message ID | 20200108205338.11369-3-jbx6244@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Enable RK3066 NANDC for MK808 | expand |
Hi Johan, Johan Jonker <jbx6244@gmail.com> wrote on Wed, 8 Jan 2020 21:53:30 +0100: > From: Yifeng Zhao <zyf@rock-chips.com> > Can you change the title to "mtd: rawnand: rockchip: Add NAND controller driver" > 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. Thank you very much for this series, as the bad blocks support is absolutely fundamental, I wonder what is the issue here? > > Signed-off-by: Yifeng Zhao <zyf@rock-chips.com> > Signed-off-by: Johan Jonker <jbx6244@gmail.com> > --- > 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. Can you write an exhaustive list if you know it? Or at least the families? It is quite challenging when you are not in the Rockchip world to know what SoC is similar to another random SoC. > + > 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 A driver named rockchip-nand-controller.c would be nice! > > 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 I'm not sure the entire link is relevant, I would simple mention the Rockchip official Github repository and work from Yifeng. > + * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@rock-chips.com > + * > + * Update/restyle for linux-next. > + * Add exec_op function. You can drop these two lines too. This is more a "commit description" thing. > + * Combine driver for nandc version 6 and 9. Support NAND controller versions 6 and 9 found on SoCs ... > + * 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 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 I would prefer prefixing everything RK_NANDC_ or RK_ > + > +#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; If you make a distinction between both version, maybe you can add more parameters here and do foo = data->param instead of if (data->version == 6) foo = this; else foo = that; > +}; > + > +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; I have not read yet the entire driver but I believe the above 4 parameters should probably be moved in rk_nand_chip, right? Anything that is NAND chip related should not be in the controller structure. It depends if you can change ECC requirements on the fly or not. > + 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]; I don't like this idea so much. I prefer a dynamic allocation in the probe. > +static int g_id_counter; Don't know what this is yet, but it is probably a bad idea :) > + > +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); > + } My above comment about the platform data would make a lot of sense here! > +} > + > +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); > + } Same comment! > + > + 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 should be: /* * The register... Please run checkpatch.pl --strict on this file, this kind of mistake would have been detected. > + * 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); Maybe you can spare this writel by returning early if banknr == ctrl->selected_bank. > + > + 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); What do 14 and 8 mean? > + /* 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); I'm pretty sure most of these writel could be turned into writel_relaxed. Also I am not a big fan of these casts, maybe you should change dma_data and dma_oob types (be careful: you enabled COMPILE_TEST in Kconfig, you must support 32-bit and 64-bit pointers, please try to compile this driver with different toolchains). > + > + 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); > + } Space > + 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) Unaligned parameters > +{ > +#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 Please try to remove these #ifdefs, I don't know why would you need the former block? > +} > + > +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 Same > +} > + > +static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand, > + uint8_t *buf, > + int oob_required, int page) Unaligned parameters (please check all of them). > +{ > + 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) && Camel case is usually not welcome > + ctrl->bootromblocks) This would probably deserve a helper > + 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); One init_completion is needed (in the probe, probably) then please use reinit_completion(). > + 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); Yous should check the return status of almost all the functions here. > + 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; Please use u8, u16 and u32 types. > + > + 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; Why not using ret = $(real error) instead of using max_bitflips here? Then: if (ret) { dev_err(); return ret; } return max_bitflips; > + } 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); This is not very informative, please write a real error message. Avoid putting too much debug information, people will troubleshoot themselves if needed. > + } > + > + if (ctrl->bootromblocks) > + rk_nandc_hw_ecc_setup(nand, ctrl->ecc_mode); > + You don't use the same condition as above. Why ? Maybe the helper would help. > + 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. I would separate the function to read/write the loader than the usual read/write helpers to avoid any confusion on why you do these tricks. Maybe not the whole function, but at least the data/oob placement? (This is really a suggestion) > + */ > + 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; 0x100: Maybe a define > + > + 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; You might want to write an helper to calculate bank_base, to avoid repeating these lines. > + > + 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); device managed allocations (devm_...) would be nice > + 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: I would either refuse ECC_HW or put it besides HW_SYNDROME. > + case NAND_ECC_NONE: > + case NAND_ECC_SOFT: Have you tested with SW BCH? > + 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); Why 1 here? > + 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, > + }, > +}; > + Move this empty line... > +module_platform_driver(rk_nandc_driver); ...Here > +MODULE_LICENSE("GPL v2"); Thanks, Miquèl
Hi Miquel, Thank you for your detailed and useful review. Without manufacturer support I must scrape my information from all over the internet, together with slow interpretation of Rockchip drivers. So please have some patience with my updates and new versions. Below are some comments and questions in random order. ///////////////////////////// To prevent guessing games could you confirm the following names: driver file name: rockchip-nand-controller.c document file name: rockchip-nand-controller.yaml compatible: "rockchip,nandc-v6" compatible: "rockchip,nandc-v9" ///////////////////////////// My incomplete sort list for controller versions. Can someone with access to the RV1108 TRM (manual) report the details for the Nandc Version Register and whether it is compatible. Thanks. Added version 7 for RK3228A/RK3228B. Can someone with insider info confirm if this works or not. RK3066/PX2 NANDC_NANDC_VER 0x0160 W 0x56363030 Nandc Version Register RK3188 NANDC_NANDC_VER 0x0160 W 0x56363030 Nandc Version Register PX3 NANDC_NANDC_VER 0x0160 W 0x56363030 Nandc Version Register RK312X NANDC_NANDC_VER 0x0160 W 0x56363232 Nandc Version Register RK3288 NANDC_NANDC_VER 0x0160 W 0x56363232 Nandc Version Register RK3368/PX5 NANDC_NANDC_VER 0x0160 W 0x56363232 Nandc Version Register RK3228A/RK3228B NANDC_NANDC_VER 0x0160 W 0x00000701 Nandc Version Register RK3308 NANDC_NANDC_VER 0x0160 W 0x00000801 Nandc Version Register RK3326/PX30 NANDC_NANDC_VER 0x0080 W 0x56393030 Nandc Version Register RK3328 NO NANDC RK3399 NO NANDC RV1108 unknown ///////////////////////////// My debug kernel.log with 1 partition in dts. Jan 1 00:02:27 mk808 kernel: [ 147.051587] rockchip-nandc 10500000.nand-controller: get clk_nandc failed Jan 1 00:02:27 mk808 kernel: [ 147.052402] nand: device found, Manufacturer ID: 0xad, Chip ID: 0xde Jan 1 00:02:27 mk808 kernel: [ 147.052945] nand: Hynix H27UCG8T2ATR-BC 64G 3.3V 8-bit Jan 1 00:02:27 mk808 kernel: [ 147.053388] nand: 8192 MiB, MLC, erase size: 2048 KiB, page size: 8192, OOB size: 640 Jan 1 00:02:27 mk808 kernel: [ 147.054050] rockchip-nandc 10500000.nand-controller: nand->numchips = 1 Jan 1 00:02:27 mk808 kernel: [ 147.054740] rockchip-nandc 10500000.nand-controller: nand->chipsize = 8589934592 Jan 1 00:02:27 mk808 kernel: [ 147.055380] rockchip-nandc 10500000.nand-controller: nand->pagemask = fffff Jan 1 00:02:27 mk808 kernel: [ 147.055994] rockchip-nandc 10500000.nand-controller: nand->badblockpos = 0 Jan 1 00:02:27 mk808 kernel: [ 147.056591] rockchip-nandc 10500000.nand-controller: nand->chip_shift = 33 Jan 1 00:02:27 mk808 kernel: [ 147.057174] rockchip-nandc 10500000.nand-controller: nand->page_shift = 13 Jan 1 00:02:27 mk808 kernel: [ 147.057751] rockchip-nandc 10500000.nand-controller: nand->phys_erase_shift = 21 Jan 1 00:02:27 mk808 kernel: [ 147.058366] rockchip-nandc 10500000.nand-controller: nand->ecc.mode = 3 Jan 1 00:02:27 mk808 kernel: [ 147.058920] rockchip-nandc 10500000.nand-controller: nand->ecc.steps = 8 Jan 1 00:02:27 mk808 kernel: [ 147.059481] rockchip-nandc 10500000.nand-controller: nand->ecc.bytes = 70 Jan 1 00:02:27 mk808 kernel: [ 147.060049] rockchip-nandc 10500000.nand-controller: nand->ecc.total = 0 Jan 1 00:02:27 mk808 kernel: [ 147.060607] rockchip-nandc 10500000.nand-controller: nand->ecc.prepad = 4 Jan 1 00:02:27 mk808 kernel: [ 147.061175] rockchip-nandc 10500000.nand-controller: nand->ecc.size = 1024 Jan 1 00:02:27 mk808 kernel: [ 147.061748] rockchip-nandc 10500000.nand-controller: nand->ecc.strength = 40 Jan 1 00:02:27 mk808 kernel: [ 147.062341] rockchip-nandc 10500000.nand-controller: mtd->ooblayout = 91ce9ce2 Jan 1 00:02:27 mk808 kernel: [ 147.062943] rockchip-nandc 10500000.nand-controller: mtd->flags = 00000000 Jan 1 00:02:27 mk808 kernel: [ 147.063518] rockchip-nandc 10500000.nand-controller: mtd->size = 8589934592 Jan 1 00:02:27 mk808 kernel: [ 147.064098] rockchip-nandc 10500000.nand-controller: mtd->erasesize = 2097152 Jan 1 00:02:27 mk808 kernel: [ 147.064815] rockchip-nandc 10500000.nand-controller: mtd->writesize = 8192 Jan 1 00:02:27 mk808 kernel: [ 147.065413] rockchip-nandc 10500000.nand-controller: mtd->oobsize = 640 Jan 1 00:02:27 mk808 kernel: [ 147.068985] 1 fixed-partitions partitions found on MTD device 10500000.nand-controller.0 Jan 1 00:02:27 mk808 kernel: [ 147.069190] Creating 1 MTD partitions on "10500000.nand-controller.0": Jan 1 00:02:27 mk808 kernel: [ 147.072375] 0x000000000000-0x000000400000 : "parameter" Jan 1 00:02:27 mk808 kernel: [ 147.075649] rockchip-nandc 10500000.nand-controller: R:0x00ff cs:0 Jan 1 00:02:27 mk808 kernel: [ 147.079423] rockchip-nandc 10500000.nand-controller: R:0x01ff cs:0 Despite nand->options = NAND_SKIP_BBTSCAN. What is the reason for these 2 rk_nandc_hw_syndrome_ecc_read_page() commands at page R:0x00ff and R:0x01ff right after creating partitions. When enabled BBTSCAN MTD starts to store at all kind of places. Can you state there page address logic, ie. Would that damage the excisting Rockchip layout? ///////////////////////////// No bad block support Based on: drivers: mtd: nand: rockchip nandc add bad block detect api https://github.com/rockchip-linux/u-boot/commit/ 7aec704a4e9d9322f1102bcf61ee5c3cf6ec794d rockchip: drivers: mtd: nand: modify the bad block detection process https://github.com/rockchip-linux/u-boot/commit/ d6d708d1a329a6369143e8dd34cf4e2c81d5d92f BCH | oob size ------------------------ 16: bytes: 28 + 4 = 32 24: bytes: 42 + 4 = 46 40: bytes: 70 + 4 = 74 60: bytes: 106 + 4 = 110 The data layout that is written by an internal Rockchip nandc dma is: 1024 bytes data + 32 obb + 1024 data + 32 obb ... The MTD system however tries to detect bad block flags located at: 2048, 4096, 8192... The system checks for bad blocks and looks at the wrong bad block marker location. Yifeng Zhao proposes to add a bad block detecting strategy by doing a read with rk_nandc_hw_syndrome_ecc_read_page() first, if failure then assume it's still raw unwritten NAND and test bytes are at the position MTD normaly would check for right from the factory. When this function is used on a FTL controlled NAND it creates an awful lot of errors in the kernel log, because it uses the BB marker for dirty tag tricks for there data storage. So what is good for a raw empty NAND without FTL does not work for the majority of Rockchip devices I think. Please advise for other options. static uint8_t rk_nand_read_byte(struct nand_chip *nand) { uint8_t ret; rk_nand_read_buf(nand, &ret, 1); return ret; } static int rk_nand_block_bad(struct nand_chip *nand, loff_t ofs) { struct mtd_info *mtd = nand_to_mtd(nand); int page, res = 0; u16 bad = 0xff; u8 *buf = nand_get_data_buf(nand); int chipnr = (int)(ofs >> nand->chip_shift); page = (int)(ofs >> nand->page_shift) & nand->pagemask; rk_nand_select_chip(nand, chipnr); if (rk_nand_hw_syndrome_ecc_read_page(nand, buf, false, page) == -1) { /* first page of a block*/ nand_read_page_op(nand, page, nand->badblockpos, NULL, 0); bad = rk_nand_read_byte(nand); if (bad != 0xFF) res = 1; /* second page of a block*/ nand_read_page_op(nand, page + 1, nand->badblockpos, NULL, 0); bad = rk_nand_read_byte(nand); if (bad != 0xFF) res = 1; /* last page of a block */ page += ((mtd->erasesize - mtd->writesize) >> nand->chip_shift); page--; nand_read_page_op(nand, page, nand->badblockpos, NULL, 0); bad = rk_nand_read_byte(nand); if (bad != 0xFF) res = 1; } rk_nand_select_chip(nand, -1); return res; } This also requires a patch for nand_bbt.c As I try to get to get some shape in the rest of this driver, I have left it out for version 1 and as I wait for our respons first. drivers/mtd/nand/nand_bbt.c @@ -487,8 +487,10 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, int ret; BUG_ON(bd->options & NAND_BBT_NO_OOB); ret = scan_block_fast(mtd, bd, from, buf, numpages); if (this->block_bad) ret = this->block_bad(mtd, from); else ret = scan_block_fast(mtd, bd, from, buf, numpages); ///////////////////////////// Data structures/Partitions The majority of Rockchip devices use a closed source FTL driver, so when we want to read or write we must deal with it. Example MTD string: mtdparts=rk29xxnand: 0x00002000@0x00002000(misc), 0x00008000@0x00004000(kernel), 0x00008000@0x0000C000(boot), 0x00008000@0x00014000(recovery), 0x000C0000@0x0001C000(backup), 0x00040000@0x000DC000(cache), 0x00300000@0x0011C000(userdata), 0x00002000@0x0041C000(kpanic), 0x00200000@0x0041E000(system), -@0x0063E000(user)" When Rockchip mentions a string like this it has nothing to do with the real position on NAND. FTL write where wants, so reading there is not useful. All sizes have to be multiplied by 512 and casted to (u64)! All partitions need to contain at least 2 erase blocks. One for normal use and one spare. -------------------- FlashSavePhyInfo RawIdbData -------------------- ... FTL data ... -------------------- Map blocks: + L2pMapInfo + VendorBlkInfo Sys info: + sys_save_data Vendor partition: + sys_ext_data 0 + ect_tbl_info 64 + vendor 256 + ? + BootConfig 512 + 0 + DrmKeyInfo 512 + 1 + Vendor0Info 512 + 2 + Vendor1Info 512 + 3 + sys 512 + ? + public key 520 -------------------- Bad Block Map Tbl(not compatible with MTD) -------------------- reserved: last NAND block - n -------------------- From the above diagram only RawIdbData has to be located in the first erase block. Boot ROM searches for the tag ID_IDRW = 0xFCDC8C3B. Only the first 4 sections (4*1024) of a page are used. When writing multiple pages extra spaces in the data for between the sections are required. Older cpu's (RK3066) might need extra RC4 coding. FTL uses that RawIdbData area unfortunately also to save struct FlashSavePhyInfo. For a bare basic application only RawIdbData is needed. For users that might consider MTD as a option to do something on a Rockchip NAND see the source code below to get an indication of what is needed for a useful setup to just read and write a bootloader alone. Writing of any user partition is not even included. Having a basic MTD driver is just not enough. Please advise if such a overhead can interface with MTD? Have fun! ///////////////////////////// On 1/10/20 12:05 PM, Miquel Raynal wrote: > Hi Johan, > > Johan Jonker <jbx6244@gmail.com> wrote on Wed, 8 Jan 2020 21:53:30 > +0100: > >> From: Yifeng Zhao <zyf@rock-chips.com> >> > > Can you change the title to "mtd: rawnand: rockchip: Add NAND > controller driver" > >> 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. > > Thank you very much for this series, as the bad blocks support is > absolutely fundamental, I wonder what is the issue here? > >> >> Signed-off-by: Yifeng Zhao <zyf@rock-chips.com> >> Signed-off-by: Johan Jonker <jbx6244@gmail.com> >> --- >> 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. > > Can you write an exhaustive list if you know it? Or at least the > families? It is quite challenging when you are not in the Rockchip > world to know what SoC is similar to another random SoC. See above. > >> + >> 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 > > A driver named rockchip-nand-controller.c would be nice! > >> >> 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 > > I'm not sure the entire link is relevant, I would simple mention the > Rockchip official Github repository and work from Yifeng. > >> + * Copyright (c) 2016-2019 Yifeng Zhao yifeng.zhao@rock-chips.com >> + * >> + * Update/restyle for linux-next. >> + * Add exec_op function. > > You can drop these two lines too. This is more a "commit description" > thing. > >> + * Combine driver for nandc version 6 and 9. > > Support NAND controller versions 6 and 9 found on SoCs ... > >> + * 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 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 > > I would prefer prefixing everything RK_NANDC_ or RK_ Will change define list above and below to RK_NANDC_ It takes more space though. Already difficult to stay below 80 char/line. > >> + >> +#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; > > If you make a distinction between both version, maybe you can add more > parameters here and do > > foo = data->param > > instead of > > if (data->version == 6) > foo = this; > else > foo = that; > TODO v2 >> +}; >> + >> +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; > > I have not read yet the entire driver but I believe the above 4 > parameters should probably be moved in rk_nand_chip, right? Anything > that is NAND chip related should not be in the controller structure. It > depends if you can change ECC requirements on the fly or not. Short answer: The reason that it is the most convenience place to have them for now. With one pointer from nand_get_controller_data() I have all data available. struct rk_nand_controller *ctrl = nand_get_controller_data(nand); The ECC is now sort of fixed to 24 and 40 for legacy reasons. The older rk3066 bootrom apparently only works for ecc 24. See info based on older work by Paweł Jarosz for Uboot. I'm not too familiar with all inner working of MTD, so please advise. Can the users get access to struct rk_nand_chip? Would you like to give users control over what ecc to use? What program can we use for that? Can't use dd then any more. How do we regain ecc control if we really have to for example rk3066? Or remove that bootrom check and always set ECC with every rk_nandc_hw_syndrome_ecc_read_page and rk_nandc_hw_syndrome_ecc_write_page with whatever passed along? > >> + 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]; > > I don't like this idea so much. I prefer a dynamic allocation in the > probe. > >> +static int g_id_counter; > > Don't know what this is yet, but it is probably a bad idea :) > Maybe not interesting for MTD, but RK3288 has 2 identical NANDC's. The currect FTL setup only works with nandc0. To reuse the probe function for other things then MTD it needs to be aware of alias order. Will remove it for version 2. >> + >> +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); >> + } > > My above comment about the platform data would make a lot of sense here! > >> +} >> + >> +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); >> + } > > Same comment! > >> + >> + 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 > > should be: > > /* > * The register... > > Please run checkpatch.pl --strict on this file, this kind of mistake > would have been detected. > When you think that you got rid of all warnings there's always an extra option for more... Thanks for that advice. >> + * 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); > > Maybe you can spare this writel by returning early if banknr == > ctrl->selected_bank. > >> + >> + 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; Should I add this comment below? /* HW ECC always request ECC bytes for 1024 bytes blocks */ >> + nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * 14, 8); > > What do 14 and 8 mean? fls - find last (most-significant) bit set int fls(unsigned int x) { int r = 32; if (!x) return 0; if (!(x & 0xffff0000u)) { x <<= 16; r -= 16; } if (!(x & 0xff000000u)) { x <<= 8; r -= 8; } if (!(x & 0xf0000000u)) { x <<= 4; r -= 4; } if (!(x & 0xc0000000u)) { x <<= 2; r -= 2; } if (!(x & 0x80000000u)) { x <<= 1; r -= 1; } return r; } nand->ecc.bytes = DIV_ROUND_UP(ecc->strength * fls(8 * 1024), 8); formule is used to translate strength in bit/1024 BCH/ECC to MTD ECC bytes for 1024 bytes blocks in nand->ecc.bytes 14 is replacement for fls(8 * 1024) 8 bits in a byte > >> + /* 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); > > I'm pretty sure most of these writel could be turned into > writel_relaxed. writel() -- write to the little-endian hardware register with compiler memory barrier, writel_relaxed -- as above, without barrier, __raw_writel() -- as above (writel_relaxed()) without endianess conversion (CPU ordering will be used). Architecture-dependent. I don't know. > > Also I am not a big fan of these casts, maybe you should change > dma_data and dma_oob types (be careful: you enabled COMPILE_TEST in > Kconfig, you must support 32-bit and 64-bit pointers, please try to > compile this driver with different toolchains). Driver is used for both ARM as arm64. These registers are 32 bit. Please advise what happens when writel gets dma_addr_t and dma_data as 64 bit. Don't have the hardware to find out. > >> + >> + 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); Same here. >> + >> + 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); >> + } > > Space > >> + 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) > > Unaligned parameters To restyle I use: astyle -T8 --max-code-length=80 --style=linux rockchip_nandc.c Please advise for a better solution. > >> +{ >> +#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 > > Please try to remove these #ifdefs, I don't know why would you need the > former block? This driver is used both for ARM as arm64. Original from Rockchip: arm64 doesn't have dma_map_single() as I remember. Don't know what to use instead. Please advise. > >> +} >> + >> +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 > > Same > >> +} >> + >> +static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand, >> + uint8_t *buf, >> + int oob_required, int page) > > Unaligned parameters (please check all of them). Again blame astyle ... After correcting it manually a few time I just left it as it is. > >> +{ >> + 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) && > > Camel case is usually not welcome Any suggestions for a beter replace for NANDC_IDBResBlkNum are welcome. > >> + ctrl->bootromblocks) > > This would probably deserve a helper > >> + 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); > > One init_completion is needed (in the probe, probably) then please use > reinit_completion(). Must study this later. > >> + 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); > > Yous should check the return status of almost all the functions here. Please advise what ERROR code should be returned here that is compatible with MTD. > >> + 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; > > Please use u8, u16 and u32 types. Should this be changed for the entire source? > >> + >> + 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; > > Why not using ret = $(real error) instead of using max_bitflips here? > > Then: > > if (ret) { > dev_err(); Do you really want to litter the kernel log with each time you hit a bad block, in case you use this function in a search bad block loop? Don't thinks so ... Please advise. > return ret; > } > > return max_bitflips; rk_nandc_hw_syndrome_ecc_read_page() is used in a bad block search strategy. I think max_bitflips = -1 was chosen as a saver value to return. Please advise what number range MTD interprets as max_bitflips or as fault. Also consider some uncontrolled status return value, it would be better if we are in control of what goes return. > >> + } 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); > > This is not very informative, please write a real error message. Avoid > putting too much debug information, people will troubleshoot themselves > if needed. OK, agree. > >> + } >> + >> + if (ctrl->bootromblocks) >> + rk_nandc_hw_ecc_setup(nand, ctrl->ecc_mode); >> + > > You don't use the same condition as above. Why ? Maybe the helper would > help. A few less conditions to check. It won't do damage in case ecc is same before and after. Will make conditions equal. > >> + 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. > > I would separate the function to read/write the loader than the usual > read/write helpers to avoid any confusion on why you do these tricks. > > Maybe not the whole function, but at least the data/oob placement? > (This is really a suggestion) > >> + */ >> + 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; > > 0x100: Maybe a define OK, agree. > >> + >> + 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; > > You might want to write an helper to calculate bank_base, to avoid > repeating these lines. Even with a separate define or helper function I still have to supply my reg, selected_bank and offset. It doesn't make things cleaner pumping date to and from a helper or neither doesn't it shorten my source with a define. Tend to keep it as it is for now. If you agree of course. > >> + >> + 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); > > device managed allocations (devm_...) would be nice devm_kzalloc() needs an extra struct device. Does MTD safe-gard the correct order to detach from struct rk_nand_controller without rk_nandc_free_buffer()? > >> + 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 || Added version 7 for RK3228A/RK3228B. Can someone with insider info confirm if this works or not. >> + 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: > > I would either refuse ECC_HW or put it besides HW_SYNDROME. Is there a fundamental difference in handling ECC_HW and HW_SYNDROME from the MTD point of view? Other then a indication how it's done on the driver side? Will drop it. > >> + case NAND_ECC_NONE: >> + case NAND_ECC_SOFT: > > Have you tested with SW BCH? Short answer: No Just copied it from the original. Please advise a tool to do a test between the individual ecc read options. Or do I have to write the tool my self with mtd-utils? > >> + 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); > > Why 1 here? TODO for version 2. A little misunderstanding on how for_each_child_of_node works. All chips should be scanned. ///// Derive chipnr Example from sunxi_nand.c if (!of_get_property(np, "reg", &nsels)) return -EINVAL; nsels /= sizeof(u32); if (!nsels) { dev_err(dev, "invalid reg property size\n"); return -EINVAL; } ///// From rk_nandc_chips_init() for_each_child_of_node(np, nand_np) { ret = rk_nandc_chip_init(dev, ctrl, nand_np, i); Why does sunxi_nand.c need this extra for_each_child_of_node? > >> + 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; See comments above about Rk3288. Keeping track node alias for nandc0. To remove or not? >> + >> + 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, >> + }, Again to prevent guessing games please advise if "rockchip,nandc-v6" and "rockchip,nandc-v9" are correct or state the desired compatible strings. >> + { /* 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, >> + }, >> +}; >> + > > Move this empty line... > >> +module_platform_driver(rk_nandc_driver); > > ...Here > >> +MODULE_LICENSE("GPL v2"); > > Thanks, > Miquèl > ////////////////////////////////// // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019, Johan Jonker <jbx6244@gmail.com> * * Based on: * https://github.com/rockchip-linux/kernel/blob/develop-4.4/drivers/rk_nand/rk_ftl_arm_v7.S * https://raw.githubusercontent.com/rockchip-linux/kernel/develop-4.4/drivers/rk_nand/rk_ftl_arm_v7.S * Copyright (c) 2016-2018, Fuzhou Rockchip Electronics Co., Ltd * SPDX-License-Identifier: GPL-2.0+ * * https://github.com/rockchip-linux/u-boot/blob/next-dev/drivers/rknand/rk_ftl_arm_v7.S * https://raw.githubusercontent.com/rockchip-linux/u-boot/next-dev/drivers/rknand/rk_ftl_arm_v7.S * Copyright (c) 2016-2018, Fuzhou Rockchip Electronics Co., Ltd * SPDX-License-Identifier: GPL-2.0+ * * https://github.com/rockchip-linux/rkdeveloptool/blob/master/crc.cpp * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd * SPDX-License-Identifier: GPL-2.0+ */ enum NAND_TAG { ID_IDBT = 0xEFF0, ID_VNDR = 0xF086, ID_SBFX = 0xF095, ID_SYSB = 0xF0A4, ID_L2PM = 0xF0C2, ID_BBTB = 0xF0D1, ID_BBTF = 0xF0E0, ID_RGMP = 0xFAF5, ID_XXXW = 0xFFFF, ID_SBTG = 0x12345678, ID_CHCK = 0x12348765, ID_BTCE = 0x42544345, ID_FTLS = 0x46544C53, ID_HASH = 0x47C6A7E6, ID_NAND = 0x4E414E44, ID_VPCT = 0x50000056, ID_IDRW = 0xFCDC8C3B, ID_XXXD = 0xFFFFFFFF, }; struct __attribute__((aligned(4))) tagFlashSaveInfo { uint32_t Id; uint32_t Size; uint32_t JSHash; uint16_t gNandMaxDie; uint16_t gNandIDBResBlkNum; uint64_t IDByte[8]; uint16_t DieCsIndex[8]; uint64_t DieAddrs[8]; uint32_t gNandParaInfo[8]; uint32_t gNandOptPara[8]; uint8_t gReadRetryInfo[852]; uint32_t gFlashToggleModeEn; }; struct __attribute__((aligned(2))) tagNandOptPara { uint8_t res1[8]; uint8_t StartDpCmd; uint8_t EndDpCmd; uint8_t DpFirstCmd; uint8_t DpSecondCmd; uint8_t res6; uint8_t cmd2; uint8_t cmd1; uint8_t max; uint8_t option; uint8_t res11[15]; }; struct __attribute__((aligned(2))) tagNandParaInfo { uint8_t id_bytes; uint8_t nand_id[6]; uint8_t vendor; uint8_t die_per_chip; uint8_t sec_per_page; uint16_t page_per_blk; uint8_t cell; uint8_t plane_per_die; uint16_t blk_per_plane; uint16_t operation_opt; uint8_t lsb_mode; uint8_t read_retry_mode; uint8_t ecc_bits; uint8_t access_freq; uint8_t opt_mode; uint8_t die_gap; uint8_t bad_block_mode; uint8_t multi_plane_mode; uint8_t slc_mode; uint8_t reserved[5]; }; struct __attribute__((aligned(4))) tagNandPhyInfo { uint32_t chip_id; uint32_t vendor; uint16_t nand_type; uint16_t die_num; uint16_t planes_per_die; uint16_t blk_per_plane; uint16_t page_per_blk; uint16_t page_per_slc_blk; uint16_t sec_per_page; uint16_t block_size; uint16_t sector_size; uint16_t reserved_blks; }; struct tagReadRetryInfo { uint8_t retryMode; uint8_t size; uint8_t max; uint8_t res3; uint8_t addr[8]; uint8_t offset1[8]; uint8_t buf[832]; }; uint8_t gNandIDataBuf[2048]; struct tagNandParaInfo *gpNandParaInfo; struct tagNandOptPara gNandOptPara; struct tagNandPhyInfo gNandPhyInfo; struct tagReadRetryInfo gReadRetryInfo; uint16_t mlcPageToSlcPageTbl[512]; uint16_t slcPageToMlcPageTbl[256]; uint32_t *gFlashPageBuffer0; uint32_t *gFlashPageBuffer1; void P_RC4(uint8_t *buf, uint32_t size) { uint8_t key[16] = { 0x7C, 0x4E, 0x03, 0x04, 0x55, 0x05, 0x09, 0x07, 0x2D, 0x2C, 0x7B, 0x38, 0x17, 0x0D, 0x17, 0x11, }; uint8_t S[256], K[256], temp; uint32_t i, j, t, x; j = 0; for (i = 0; i < 256; i++) { S[i] = (uint8_t)i; j &= 0x0f; K[i] = key[j]; j++; } j = 0; for (i = 0; i < 256; i++) { j = (j + S[i] + K[i]) % 256; temp = S[i]; S[i] = S[j]; S[j] = temp; } i = j = 0; for (x = 0; x < size; x++) { i = (i+1) % 256; j = (j + S[i]) % 256; temp = S[i]; S[i] = S[j]; S[j] = temp; t = (S[i] + (S[j] % 256)) % 256; buf[x] = buf[x] ^ S[t]; } } uint32_t js_hash(uint8_t *buf, uint32_t size) { uint32_t hash; uint8_t *p_max; uint32_t tmp; hash = ID_HASH; p_max = &buf[size]; while (buf != p_max) { tmp = *buf++; hash ^= (hash >> 2) + 32 * hash + tmp; } return hash; } void BuildFlashLsbPageTable(uint32_t lsb_mode, uint16_t size) { uint32_t counter1; uint32_t counter2; uint32_t counter3; uint32_t counter4; uint32_t counter5; uint32_t counter6; uint32_t counter7; uint32_t counter8; uint16_t offset1; uint16_t offset2; uint32_t offset3; uint16_t offset4; uint16_t *p_table1; uint16_t *p_table2; uint16_t *p_table3; uint16_t tmp1; uint16_t tmp2; uint16_t tmp3; uint32_t tmp4; uint16_t tmp5; uint32_t tmp6; if (lsb_mode) { switch (lsb_mode) { case 1u: counter2 = 0; do { tmp1 = counter2; if (counter2 > 3) { if (counter2 & 1) offset1 = 3; else offset1 = 2; tmp1 = 2 * counter2 - offset1; } slcPageToMlcPageTbl[counter2++] = tmp1; } while (counter2 != 256); break; case 2u: counter3 = 0; do { tmp2 = counter3; if (counter3 > 1) tmp2 = 2 * counter3 - 1; slcPageToMlcPageTbl[counter3++] = tmp2; } while (counter3 != 256); break; case 3u: counter4 = 0; do { tmp3 = counter4; if (counter4 > 5) { if (counter4 & 1) offset2 = 5; else offset2 = 4; tmp3 = 2 * counter4 - offset2; } slcPageToMlcPageTbl[counter4++] = tmp3; } while (counter4 != 256); break; default: counter5 = 0; switch (lsb_mode) { case 4u: slcPageToMlcPageTbl[0] = 0; slcPageToMlcPageTbl[1] = 1; slcPageToMlcPageTbl[2] = 2; slcPageToMlcPageTbl[3] = 3; slcPageToMlcPageTbl[5] = 5; slcPageToMlcPageTbl[6] = 7; counter6 = 8; slcPageToMlcPageTbl[4] = 4; slcPageToMlcPageTbl[7] = 8; p_table1 = &slcPageToMlcPageTbl[7]; do { if (counter6 & 1) offset3 = 7; else offset3 = 6; tmp4 = 2 * counter6 - offset3; counter6 = (uint16_t)(counter6 + 1); p_table1[1] = tmp4; ++p_table1; } while (counter6 != 256); break; case 5u: do { slcPageToMlcPageTbl[counter5] = counter5; ++counter5; } while (counter5 != 16); p_table2 = &slcPageToMlcPageTbl[15]; do { p_table2[1] = counter5; ++p_table2; counter5 = (uint16_t)(counter5 + 2); } while (counter5 != 496); break; case 6u: counter7 = 0; do { tmp5 = counter7; if (counter7 > 5) { if (counter7 & 1) offset4 = 12; else offset4 = 10; tmp5 = counter5 - offset4; } slcPageToMlcPageTbl[counter7++] = tmp5; counter5 = (uint16_t)(counter5 + 3); } while (counter7 != 256); break; case 9u: slcPageToMlcPageTbl[0] = 0; slcPageToMlcPageTbl[1] = 1; slcPageToMlcPageTbl[2] = 2; p_table3 = &slcPageToMlcPageTbl[2]; counter8 = 3; do { p_table3[1] = counter8; ++p_table3; counter8 = (uint16_t)(counter8 + 2); } while (counter8 != 509); break; } break; } } else { do { slcPageToMlcPageTbl[lsb_mode] = lsb_mode; ++lsb_mode; } while (lsb_mode != 256); } ftl_memset(mlcPageToSlcPageTbl, 0xFFu, 1024u); counter1 = 0; while (size > (uint16_t)counter1) { tmp6 = slcPageToMlcPageTbl[counter1++]; mlcPageToSlcPageTbl[tmp6] = tmp6; } } int FlashEraseBlock(uint32_t cs, uint32_t page_addr, uint32_t slc_mode) { uint8_t status; NandcWaitFlashReady(cs); NandcFlashCs(cs); FlashEraseCmd(cs, page_addr, slc_mode); NandcWaitFlashReady(cs); status = FlashReadStatus(cs); NandcFlashDeCs(cs); return status & 1; } int FlashProgPageRaw(uint32_t cs, uint32_t page_addr, uint32_t *p_data, uint16_t *p_spare) { uint8_t sec_per_page; uint8_t status; sec_per_page = gNandParaInfo.sec_per_page; if (!cs && gBlockPageAlignSize * gNandIDBResBlkNum > page_addr) { sec_per_page = 4; } NandcWaitFlashReady(cs); NandcFlashCs(cs); FlashProgFirstCmd(cs, page_addr); NandcXferData(cs, 1u, sec_per_page, p_data, p_spare); FlashProgSecondCmd(cs); NandcWaitFlashReady(cs); status = FlashReadStatus(cs); NandcFlashDeCs(cs); return status & 1; } uint32_t FlashReadRawPage(uint32_t cs, uint32_t page_addr, uint32_t *p_data, uint16_t *p_spare) { uint32_t sec_per_page; uint32_t status; sec_per_page = gNandParaInfo.sec_per_page; if (!cs && gBlockPageAlignSize * gNandIDBResBlkNum > page_addr) { sec_per_page = 4; } NandcWaitFlashReady(cs); NandcFlashCs(cs); FlashReadCmd(cs, page_addr); NandcWaitFlashReady(cs); status = NandcXferData(cs, 0, sec_per_page, p_data, p_spare); NandcFlashDeCs(cs); return status; } void FlashBlockAlignInit(uint32_t page_per_blk) { uint32_t align_size; if (page_per_blk > 256) { align_size = 512; label_3: gBlockPageAlignSize = align_size; return; } if (page_per_blk > 128) { align_size = 256; goto label_3; } gBlockPageAlignSize = page_per_blk; } uint32_t FlashLoadPhyInfo(void) { struct tagFlashSaveInfo *info; uint32_t align_size; uint32_t bch_counter; uint32_t counter; uint32_t page_addr; uint32_t status; uint8_t bch[4]; page_addr = 0; counter = 4; bch[0] = 60; bch[1] = 40; bch[2] = 24; bch[3] = 16; status = NAND_STS_ERROR; align_size = gBlockPageAlignSize; gNandFlashInfoBlockAddr = 0; gpFlashSaveInfo = (struct tagFlashSaveInfo *)gFlashPageBuffer0; flash_enter_slc_mode(0); while (1) { bch_counter = 0; while (1) { FlashBchSel(bch[bch_counter]); if (FlashReadRawPage( 0, page_addr, gFlashPageBuffer0, 0) != NAND_STS_ERROR || FlashReadRawPage( 0, page_addr + 1, gFlashPageBuffer0, 0) != NAND_STS_ERROR) { break; } if (++bch_counter == 4) goto label_6; } info = gpFlashSaveInfo; if (gpFlashSaveInfo->Id == ID_NAND) { if (!status) { gNandFlashIdbBlockAddr = page_addr / gBlockPageAlignSize + 1; break; } if ( info->JSHash == js_hash( (uint8_t *)&gpFlashSaveInfo->gNandMaxDie, 2036u)) { ftl_memcpy( &gNandParaInfo, info->gNandParaInfo, 32u); ftl_memcpy( &gNandOptPara, gpFlashSaveInfo->gNandOptPara, 32u); ftl_memcpy( &gReadRetryInfo, gpFlashSaveInfo->gReadRetryInfo, 852u); FlashBlockAlignInit(gNandParaInfo.page_per_blk); gFlashToggleModeEn = gpFlashSaveInfo->gFlashToggleModeEn; gNandFlashInfoBlockAddr = page_addr; if (page_addr / gBlockPageAlignSize + 1 > 1) gNandFlashIdbBlockAddr = page_addr / gBlockPageAlignSize + 1; else gNandFlashIdbBlockAddr = 2; status = NAND_STS_OK; gNandIDBResBlkNumSaveInFlash = gpFlashSaveInfo->gNandIDBResBlkNum; } else { status = NAND_STS_ERROR; } } label_6: --counter; page_addr += align_size; if (counter) continue; break; } flash_exit_slc_mode(0); return status; } void FlashSavePhyInfo(void) { uint32_t counter1; uint32_t counter2; uint32_t done; uint32_t hash; uint32_t status; gpFlashSaveInfo = (struct tagFlashSaveInfo *)gFlashPageBuffer0; FlashBchSel(gNandFlashIDBEccBits); ftl_memset(gFlashPageBuffer0, 0, 2048u); gpFlashSaveInfo->Id = ID_NAND; gpFlashSaveInfo->gNandMaxDie = gNandMaxDie; gpFlashSaveInfo->gNandIDBResBlkNum = gNandIDBResBlkNum; gpFlashSaveInfo->gFlashToggleModeEn = gFlashToggleModeEn; ftl_memcpy(gpFlashSaveInfo->IDByte, IDByte, 32u); ftl_memcpy(gpFlashSaveInfo->DieCsIndex, DieCsIndex, 8u); ftl_memcpy(gpFlashSaveInfo->DieAddrs, DieAddrs, 32u); ftl_memcpy( gpFlashSaveInfo->gNandParaInfo, &gNandParaInfo, 32u); ftl_memcpy(gpFlashSaveInfo->gNandOptPara, &gNandOptPara, 32u); ftl_memcpy( gpFlashSaveInfo->gReadRetryInfo, &gReadRetryInfo, 852u); gpFlashSaveInfo->JSHash = js_hash( (uint8_t *) &gpFlashSaveInfo->gNandMaxDie, 2036u); gpFlashSaveInfo->Size = 1592; done = 0; counter1 = 0; gpFlashSaveInfo = (struct tagFlashSaveInfo *)gFlashPageBuffer1; flash_enter_slc_mode(0); do { FlashEraseBlock(0, gBlockPageAlignSize * counter1, 0); FlashProgPageRaw( 0, gBlockPageAlignSize * counter1, gFlashPageBuffer0, 0); FlashProgPageRaw( 0, gBlockPageAlignSize * counter1 + 1, gFlashPageBuffer0, 0); status = FlashReadRawPage( 0, gBlockPageAlignSize * counter1, gFlashPageBuffer1, 0); counter2 = counter1 + 1; if (status != NAND_STS_ERROR && gpFlashSaveInfo->Id == ID_NAND) { hash = js_hash( (uint8_t *) &gpFlashSaveInfo->gNandMaxDie, 2036u); counter2 = counter1 + 1; if (gpFlashSaveInfo->JSHash == hash) { gNandFlashIdbBlockAddr = counter1 + 1; gNandFlashInfoBlockAddr = counter1 * gBlockPageAlignSize; if (done == 1) break; done = 1; } } counter1 = counter2; } while (counter2 != 4); flash_exit_slc_mode(0); } int FlashReadIdbDataRaw(uint8_t *p_buf) { uint32_t bch_counter; uint32_t ecc_bits; uint32_t ecc_bits2; uint32_t page_counter; int status; uint8_t bch[4]; bch[0] = 60; bch[1] = 40; bch[2] = 24; bch[3] = 16; ecc_bits2 = gNandFlashEccBits; if (idb_flash_slc_mode) flash_enter_slc_mode(0); status = NAND_STS_ERROR; page_counter = 2; ftl_memset(p_buf, 0, 2048u); while (1) { if (page_counter < gNandIDBResBlkNum) { bch_counter = 0; while (1) { ecc_bits = bch[bch_counter]; FlashBchSel(ecc_bits); if ( FlashReadRawPage( 0, gBlockPageAlignSize * page_counter, gFlashPageBuffer0, 0) != NAND_STS_ERROR) break; if (++bch_counter == 4) goto label_11; } if (*gFlashPageBuffer0 == ID_IDRW) { FTL_INFO("ECC:%d\n", ecc_bits); ftl_memcpy( p_buf, gFlashPageBuffer0, 2048u); gNandIDBResBlkNum = gFlashPageBuffer0[128]; if (page_counter >= gNandFlashIdbBlockAddr) { status = NAND_STS_OK; break; } gNandFlashIdbBlockAddr = page_counter; status = NAND_STS_OK; FlashSavePhyInfo(); } label_11: ++page_counter; continue; } break; } FlashBchSel(ecc_bits2); if (idb_flash_slc_mode) flash_exit_slc_mode(0); return status; } void FlashPageProgMsbFFData(uint32_t cs, uint32_t page_addr, uint32_t count) { uint32_t retry_mode; uint32_t shift; uint32_t tmp; if (!gFlashSlcMode || !idb_flash_slc_mode) { retry_mode = gpNandParaInfo->read_retry_mode; shift = (uint8_t)(retry_mode - 5); if (shift > 30) { if (retry_mode != 68) return; } else if (!((0x4000400Fu >> shift) & 1)) { return; } while (gpNandParaInfo->page_per_blk > count && mlcPageToSlcPageTbl[count] == 0xFFFF) { if (retry_mode == 8) tmp = 0; else tmp = 0xFFu; ftl_memset(gFlashPageBuffer1, tmp, 32768u); FlashProgPageRaw( cs, count + page_addr, gFlashPageBuffer1, (uint16_t *)gFlashPageBuffer1); count = (uint16_t)(count + 1); } } } void IdBlockReadData(uint32_t index, uint32_t count, uint32_t *buf) { uint32_t counter; uint16_t counter_add; uint32_t ecc_bits; uint32_t min_sector; uint32_t page; uint32_t sector; uint32_t sector2; uint16_t size; uint32_t start_offset; size = gpNandParaInfo->sec_per_page * (uint16_t)gBlockPageAlignSize; FTL_INFO( "IdBlockReadData %x %x\n", index, count); counter = 0; sector2 = index % size; min_sector = index - index % size; start_offset = (gpNandParaInfo->sec_per_page * (index % size) >> 2) & 3; while (counter < count) { counter_add = 4 - start_offset; page = slcPageToMlcPageTbl[((counter + sector2) >> 2) & 0xFFFF]; if (gFlashSlcMode && g_nandc_version_data == NAND_VERSION_V800) page = ((counter + sector2) >> 2) & 0xFFFF; ecc_bits = gNandFlashEccBits; sector = start_offset + min_sector + gpNandParaInfo->sec_per_page * page; FlashBchSel(gNandFlashIDBEccBits); flash_boot_enter_slc_mode(0); FlashReadPage( 0, sector / gpNandParaInfo->sec_per_page, gFlashPageBuffer1, 0); flash_boot_exit_slc_mode(0); FlashBchSel(ecc_bits); memcpy(&buf[128 * counter], gFlashPageBuffer1, 2048u); start_offset = 0; counter = (uint16_t)(counter_add + counter); } FTL_INFO( "IdBlockReadData %x %x ret= %x\n", index, count, 0); } void IDBlockWriteData(uint32_t index, uint32_t count, uint32_t *buf) { uint32_t counter; uint32_t ecc_bits; uint32_t min_sector; uint32_t page; uint32_t page1; uint32_t page2; uint32_t sector2; uint16_t size; uint32_t spare[32]; size = gpNandParaInfo->sec_per_page * (uint16_t)gBlockPageAlignSize; FTL_INFO( "IDBlockWriteData %x %x\n", index, count); flash_boot_enter_slc_mode(0); FlashEraseBlock(0, index / gNandPhyInfo.sec_per_page, 0); flash_boot_exit_slc_mode(0); counter = 0; sector2 = index % size; min_sector = index - index % size; while (counter < count) { page = ((counter + sector2) >> 2) & 0xFFFF; if (page) { page1 = slcPageToMlcPageTbl[page + 1]; if (gFlashSlcMode && g_nandc_version_data == NAND_VERSION_V800) page1 = (uint16_t) (((counter + sector2) >> 2) + 1); spare[0] = 4 * (page1 - 1); spare[1] = 0; } page2 = slcPageToMlcPageTbl[page]; if (gFlashSlcMode && g_nandc_version_data == NAND_VERSION_V800) page2 = ((counter + sector2) >> 2) & 0xFFFF; ecc_bits = gNandFlashEccBits; FlashBchSel(gNandFlashIDBEccBits); flash_boot_enter_slc_mode(0); FlashProgPageRaw( 0, (min_sector + gpNandParaInfo->sec_per_page * page2) / gpNandParaInfo->sec_per_page, &buf[128 * counter], (uint16_t *)spare); flash_boot_exit_slc_mode(0); FlashBchSel(ecc_bits); FlashPageProgMsbFFData( 0, min_sector / gpNandParaInfo->sec_per_page, (uint16_t)(page2 + 1)); counter = (uint16_t)(counter + 4); } FTL_INFO( "IDBlockWriteData %x %x ret= %x\n", index, count, 0); } int write_idblock(uint32_t size, uint32_t *buf, uint32_t *p_data) { uint32_t count; uint32_t counter; uint32_t counter2; uint32_t index; uint32_t max_size; uint32_t offset; uint32_t offset2; uint32_t page_addr; uint32_t *p_read; uint32_t *p_write; uint32_t r; uint32_t w; uint16_t size2; uint32_t status; uint32_t write_counter; size2 = gpNandParaInfo->sec_per_page * (uint16_t)gBlockPageAlignSize; p_read = (uint32_t *)kmalloc_order_trace(256000, 208, 6); if (p_read) { index = (size + 511) >> 9; if (index <= 255) memcpy(&buf[128 * index], buf, 256 - index); count = index + 128; rknand_print_hex("idblk:", p_data, 4, 5); if (count >= 256) count = 256; counter = 0; FTL_INFO( "idb reverse %x %x\n", buf[128], gNandIDBResBlkNum); write_counter = 0; p_write = buf; if (buf[128] > gNandIDBResBlkNum) buf[128] = gNandIDBResBlkNum; FTL_INFO( "write_idblock total_sec %x %x\n", count, size); max_size = count << 7; do { page_addr = *p_data; ++p_data; if (page_addr < gNandPhyInfo.reserved_blks && page_addr >= gNandFlashIdbBlockAddr) { ftl_memset(p_read, 0, 512); IDBlockWriteData( *(p_data - 1) * size2, count, p_write); IdBlockReadData( *(p_data - 1) * size2, count, p_read); counter2 = 0; offset = 0; while (1) { r = p_read[counter2]; w = p_write[counter2]; ++counter2; if (r != w) break; ++offset; if (offset == max_size) goto label_1; } offset2 = offset & 0xFFFFFF00; FTL_INFO( "write and check error:" "%d idb=%x,offset=%x,r=%x,w=%x\n", write_counter, *(p_data - 1), offset, r, w); rknand_print_hex( "write", &p_write[offset2], 4, 256); rknand_print_hex( "read", &p_read[offset2], 4, 256); ftl_memset(p_read, 0, 512); IDBlockWriteData( *(p_data - 1) * size2, 4u, p_read); FTL_INFO("write_idblock error\n"); if (offset < max_size) goto label_0; label_1: ++counter; } label_0: ++write_counter; } while (write_counter != 5); ftl_free(p_read); if (counter) status = 0; else status = -1; } else { status = -1; } return status; } void write_loader_lba(uint32_t index, uint32_t count, uint32_t *buf) { uint32_t counter1; uint32_t counter2; uint32_t lba1; uint32_t lba2; uint32_t size; uint32_t tmp; uint32_t block[130]; if (index == 64 && *buf == ID_IDRW) { idb_write_enable = 1; idb_buf = ftl_malloc(256000); ftl_memset( idb_buf, 0, 256000); idb_last_lba = index; } FTL_INFO("wl_lba %p %x %x %x\n", idb_buf, *buf, index, count); if (idb_write_enable) { if (index - 64 >= 500) { if (index >= 564) { lba1 = idb_last_lba - 64; if (idb_last_lba - 64 >= 500) lba1 = 500; if (gpNandParaInfo->sec_per_page == 4) { counter1 = 0; do { tmp = 2 * counter1; if (lba1 <= 256) tmp = counter1; block[counter1++] = tmp; } while (counter1 != 5); } else { block[0] = 2; block[1] = 3; block[2] = 4; block[3] = 5; block[4] = 6; } counter2 = 63872; do { if (idb_buf[counter2]) { size = 4 * (counter2 + 128); goto label_28; } --counter2; } while (counter2 != 4096); size = lba1 << 9; label_28: write_idblock( size, idb_buf, block); idb_write_enable = 0; ftl_free(idb_buf); idb_buf = 0; goto label_14; } } else { lba2 = 564 - index; if (564 - index >= count) lba2 = count; ftl_memcpy( (void *) (idb_buf + ((index - 64) << 9)), buf, lba2 << 9); } if (idb_last_lba != index) { idb_write_enable = 0; if (idb_buf) ftl_free(idb_buf); idb_buf = 0; } label_14: idb_last_lba = index + count; } } #define GetIdblockDataNoRc4(p, s) P_RC4(p, s) int FtlGetIdBlockSysData(void *p_buf, int index) { if (index > 3) return -1; ftl_memcpy(p_buf, &gNandIDataBuf[index * 512], 512); if (index == 1) return 0; GetIdblockDataNoRc4(p_buf, 512); return 0; } int FtlGetChipSectorInfo(void *p_buf) { return FtlGetIdBlockSysData(p_buf, 2); } int FtlGetSNSectorInfo(void *p_buf) { return FtlGetIdBlockSysData(p_buf, 3); } /* init */ gFlashPageBuffer0 = (uint32_t *) ftl_malloc(32768u); gFlashPageBuffer1 = (uint32_t *) ftl_malloc(32768u); gBlockPageAlignSize = 128; /* more init */ if (FlashLoadPhyInfo()) { /* sort out your mess here */ FlashSavePhyInfo(); } /* more init */ gFlashSlcMode = gpNandParaInfo->slc_mode; gNandRandomizer = (gpNandParaInfo->operation_opt >> 7) & 1; gMultiPageReadEn = (gpNandParaInfo->operation_opt >> 3) & 1; gMultiPageProgEn = (gpNandParaInfo->operation_opt >> 4) & 1; gFlashInterfaceMode = (gpNandParaInfo->operation_opt >> 8) & 7; BuildFlashLsbPageTable( gpNandParaInfo->lsb_mode, (uint32_t)gpNandParaInfo->page_per_blk / (uint32_t)gpNandParaInfo->cell); FlashReadIdbDataRaw(gNandIDataBuf); gNandIDBResBlkNum = 16; gNandPhyInfo.nand_type = gpNandParaInfo->cell; gNandPhyInfo.vendor = gpNandParaInfo->vendor; gNandPhyInfo.chip_id = *(uint32_t *)IDByte; gNandPhyInfo.die_num = gNandMaxDie; gNandPhyInfo.page_per_blk = gpNandParaInfo->page_per_blk; gNandPhyInfo.blk_per_plane = gpNandParaInfo->blk_per_plane; gNandPhyInfo.planes_per_die = gpNandParaInfo->plane_per_die; gNandPhyInfo.page_per_slc_blk = (uint32_t)gpNandParaInfo->page_per_blk / (uint32_t)gpNandParaInfo->cell; gNandPhyInfo.sector_size = 512; gNandPhyInfo.sec_per_page = gpNandParaInfo->sec_per_page; gNandPhyInfo.reserved_blks = 16; gNandPhyInfo.block_size = gpNandParaInfo->page_per_blk * gpNandParaInfo->sec_per_page;
Hi Johan, Johan Jonker <jbx6244@gmail.com> wrote on Sun, 12 Jan 2020 18:26:20 +0100: > Hi Miquel, > > Thank you for your detailed and useful review. > > Without manufacturer support I must scrape my information > from all over the internet, together with slow interpretation of > Rockchip drivers. > So please have some patience with my updates and new versions. There is absolutely no hurry, it is great that you work on this! > > Below are some comments and questions in random order. > > ///////////////////////////// > > To prevent guessing games could you confirm the following names: > > driver file name: rockchip-nand-controller.c Fine > document file name: rockchip-nand-controller.yaml I think rockchip,nand-controller.yaml is preferred. > > compatible: "rockchip,nandc-v6" rockchip,nand-controller-v6 > compatible: "rockchip,nandc-v9" same as above with -v9 as suffix. [...] > Jan 1 00:02:27 mk808 kernel: [ 147.053388] nand: 8192 MiB, MLC, erase Just to be clear, MLC is not yet supported in mainline. There is a patch series that aims at supporting MLC in pseudo-SLC mode but do not use MLC as a reliable storage medium. > size: 2048 KiB, page size: 8192, OOB size: 640 > Jan 1 00:02:27 mk808 kernel: [ 147.054050] rockchip-nandc > 10500000.nand-controller: nand->numchips = 1 > Jan 1 00:02:27 mk808 kernel: [ 147.054740] rockchip-nandc > 10500000.nand-controller: nand->chipsize = 8589934592 > Jan 1 00:02:27 mk808 kernel: [ 147.055380] rockchip-nandc > 10500000.nand-controller: nand->pagemask = fffff > Jan 1 00:02:27 mk808 kernel: [ 147.055994] rockchip-nandc > 10500000.nand-controller: nand->badblockpos = 0 > Jan 1 00:02:27 mk808 kernel: [ 147.056591] rockchip-nandc > 10500000.nand-controller: nand->chip_shift = 33 > Jan 1 00:02:27 mk808 kernel: [ 147.057174] rockchip-nandc > 10500000.nand-controller: nand->page_shift = 13 > Jan 1 00:02:27 mk808 kernel: [ 147.057751] rockchip-nandc > 10500000.nand-controller: nand->phys_erase_shift = 21 > Jan 1 00:02:27 mk808 kernel: [ 147.058366] rockchip-nandc > 10500000.nand-controller: nand->ecc.mode = 3 > Jan 1 00:02:27 mk808 kernel: [ 147.058920] rockchip-nandc > 10500000.nand-controller: nand->ecc.steps = 8 > Jan 1 00:02:27 mk808 kernel: [ 147.059481] rockchip-nandc > 10500000.nand-controller: nand->ecc.bytes = 70 > Jan 1 00:02:27 mk808 kernel: [ 147.060049] rockchip-nandc > 10500000.nand-controller: nand->ecc.total = 0 > Jan 1 00:02:27 mk808 kernel: [ 147.060607] rockchip-nandc > 10500000.nand-controller: nand->ecc.prepad = 4 > Jan 1 00:02:27 mk808 kernel: [ 147.061175] rockchip-nandc > 10500000.nand-controller: nand->ecc.size = 1024 > Jan 1 00:02:27 mk808 kernel: [ 147.061748] rockchip-nandc > 10500000.nand-controller: nand->ecc.strength = 40 > Jan 1 00:02:27 mk808 kernel: [ 147.062341] rockchip-nandc > 10500000.nand-controller: mtd->ooblayout = 91ce9ce2 > Jan 1 00:02:27 mk808 kernel: [ 147.062943] rockchip-nandc > 10500000.nand-controller: mtd->flags = 00000000 > Jan 1 00:02:27 mk808 kernel: [ 147.063518] rockchip-nandc > 10500000.nand-controller: mtd->size = 8589934592 > Jan 1 00:02:27 mk808 kernel: [ 147.064098] rockchip-nandc > 10500000.nand-controller: mtd->erasesize = 2097152 > Jan 1 00:02:27 mk808 kernel: [ 147.064815] rockchip-nandc > 10500000.nand-controller: mtd->writesize = 8192 > Jan 1 00:02:27 mk808 kernel: [ 147.065413] rockchip-nandc > 10500000.nand-controller: mtd->oobsize = 640 > Jan 1 00:02:27 mk808 kernel: [ 147.068985] 1 fixed-partitions > partitions found on MTD device 10500000.nand-controller.0 > Jan 1 00:02:27 mk808 kernel: [ 147.069190] Creating 1 MTD partitions > on "10500000.nand-controller.0": > Jan 1 00:02:27 mk808 kernel: [ 147.072375] > 0x000000000000-0x000000400000 : "parameter" > > > Jan 1 00:02:27 mk808 kernel: [ 147.075649] rockchip-nandc > 10500000.nand-controller: R:0x00ff cs:0 > Jan 1 00:02:27 mk808 kernel: [ 147.079423] rockchip-nandc > 10500000.nand-controller: R:0x01ff cs:0 > > > Despite nand->options = NAND_SKIP_BBTSCAN. > > What is the reason for these 2 rk_nandc_hw_syndrome_ecc_read_page() > commands at page R:0x00ff and R:0x01ff right after creating > partitions. > > When enabled BBTSCAN MTD starts to store at all kind of places. Can > you state Please do not mix the two concepts: - on chip bbt: the last blocks will be used to store the BB, without, the BBT must be reconstructed by reading all the blocks. - SKIP_BBT_SCAN: do not construct the BBT in RAM. Please trace the calls (dump_stack() might help you) to see what function actually calls the ->read_page() helpers. > there page address logic, ie. Would that damage the excisting Rockchip > layout? I don't know about the existing Rockchip layout. > > ///////////////////////////// > > No bad block support > > Based on: > drivers: mtd: nand: rockchip nandc add bad block detect api > https://github.com/rockchip-linux/u-boot/commit/ > 7aec704a4e9d9322f1102bcf61ee5c3cf6ec794d > > rockchip: drivers: mtd: nand: modify the bad block detection process > https://github.com/rockchip-linux/u-boot/commit/ > d6d708d1a329a6369143e8dd34cf4e2c81d5d92f > > BCH | oob size > ------------------------ > 16: bytes: 28 + 4 = 32 > 24: bytes: 42 + 4 = 46 > 40: bytes: 70 + 4 = 74 > 60: bytes: 106 + 4 = 110 > > The data layout that is written by an internal Rockchip nandc dma is: > 1024 bytes data + 32 obb + 1024 data + 32 obb ... > > The MTD system however tries to detect bad block flags located at: > 2048, 4096, 8192... Bad block flags (so called bad block markers, abreviated BBM) are the first two bytes of the OOB. A page is always: <X in-band bytes (data)><Y out-of-band bytes (OOB)> No matter the way this is stored on the NAND chip, the user is expecting all the data bytes together and all the OOB bytes together. You must reconstruct this. > The system checks for bad blocks and looks at the wrong bad block > marker location. > Yifeng Zhao proposes to add a bad block detecting strategy by doing > a read with rk_nandc_hw_syndrome_ecc_read_page() first, > if failure then assume it's still raw unwritten NAND and test bytes > are at the position MTD normaly would check for right from the > factory. When this function is used on a FTL controlled NAND it > creates an awful lot of errors in the kernel log, because it uses the > BB marker for dirty tag tricks for there data storage. > So what is good for a raw empty NAND without FTL > does not work for the majority of Rockchip devices I think. > > Please advise for other options. That's sad that Rockchip BSP does not follow standard rules, but IMHO this kind of research logic is way too much error prone and this is not somehting we want to play with: if you start a mainline kernel with a wrong logic, then later you fix the logic in mainline, you must support the broken logic forever. This means you cannot boot a vendor kernel and a mainline kernel on the same platform anymore. This is sad but we cannot fight against it. Unless, as I am pointing it out above, you can simply reconstruct the page to offer a linear data+OOB layout. > static uint8_t rk_nand_read_byte(struct nand_chip *nand) > { > uint8_t ret; > > rk_nand_read_buf(nand, &ret, 1); > > return ret; > } > > static int rk_nand_block_bad(struct nand_chip *nand, loff_t ofs) > { > struct mtd_info *mtd = nand_to_mtd(nand); > int page, res = 0; > u16 bad = 0xff; > u8 *buf = nand_get_data_buf(nand); > int chipnr = (int)(ofs >> nand->chip_shift); > > page = (int)(ofs >> nand->page_shift) & nand->pagemask; > rk_nand_select_chip(nand, chipnr); > if (rk_nand_hw_syndrome_ecc_read_page(nand, buf, false, page) > == -1) { /* first page of a block*/ > nand_read_page_op(nand, page, nand->badblockpos, > NULL, 0); bad = rk_nand_read_byte(nand); > if (bad != 0xFF) > res = 1; > /* second page of a block*/ > nand_read_page_op(nand, page + 1, nand->badblockpos, > NULL, 0); bad = rk_nand_read_byte(nand); > if (bad != 0xFF) > res = 1; > /* last page of a block */ > page += ((mtd->erasesize - mtd->writesize) >> > nand->chip_shift); page--; > nand_read_page_op(nand, page, nand->badblockpos, > NULL, 0); bad = rk_nand_read_byte(nand); > if (bad != 0xFF) > res = 1; > } > rk_nand_select_chip(nand, -1); > return res; > } > > This also requires a patch for nand_bbt.c > As I try to get to get some shape in the rest of this driver, > I have left it out for version 1 and as I wait for our respons first. > > drivers/mtd/nand/nand_bbt.c > @@ -487,8 +487,10 @@ static int create_bbt(struct mtd_info *mtd, > uint8_t *buf, > int ret; > > BUG_ON(bd->options & NAND_BBT_NO_OOB); > > ret = scan_block_fast(mtd, bd, from, buf, numpages); > if (this->block_bad) > ret = this->block_bad(mtd, from); > else > ret = scan_block_fast(mtd, bd, from, buf, > numpages); > > ///////////////////////////// > > Data structures/Partitions > > The majority of Rockchip devices use a closed source FTL driver, > so when we want to read or write we must deal with it. > > Example MTD string: > mtdparts=rk29xxnand: > 0x00002000@0x00002000(misc), > 0x00008000@0x00004000(kernel), > 0x00008000@0x0000C000(boot), > 0x00008000@0x00014000(recovery), > 0x000C0000@0x0001C000(backup), > 0x00040000@0x000DC000(cache), > 0x00300000@0x0011C000(userdata), > 0x00002000@0x0041C000(kpanic), > 0x00200000@0x0041E000(system), > -@0x0063E000(user)" > > When Rockchip mentions a string like this it has nothing to do > with the real position on NAND. FTL write where wants, > so reading there is not useful. > All sizes have to be multiplied by 512 and casted to (u64)! > All partitions need to contain at least 2 erase blocks. > One for normal use and one spare. I don't think trying to mimic Rockchip FTL is wise. We will not use the FTL in a mainline kernel but very likely: UBI/UBIFS. > -------------------- > FlashSavePhyInfo > RawIdbData > -------------------- > ... > FTL data > ... > -------------------- > Map blocks: > + L2pMapInfo > + VendorBlkInfo > > Sys info: > + sys_save_data > > Vendor partition: > + sys_ext_data 0 > + ect_tbl_info 64 > + vendor 256 + ? > + BootConfig 512 + 0 > + DrmKeyInfo 512 + 1 > + Vendor0Info 512 + 2 > + Vendor1Info 512 + 3 > + sys 512 + ? > + public key 520 > -------------------- > Bad Block Map Tbl(not compatible with MTD) > -------------------- > reserved: last NAND block - n > -------------------- > > From the above diagram only RawIdbData has to be located in the first > erase block. > Boot ROM searches for the tag ID_IDRW = 0xFCDC8C3B. > Only the first 4 sections (4*1024) of a page are used. > When writing multiple pages extra spaces in the data > for between the sections are required. > Older cpu's (RK3066) might need extra RC4 coding. > > FTL uses that RawIdbData area unfortunately also to save struct > FlashSavePhyInfo. > For a bare basic application only RawIdbData is needed. > > For users that might consider MTD as a option to do something on a > Rockchip NAND > see the source code below to get an indication of what is needed for a > useful > setup to just read and write a bootloader alone. Writing of any user > partition > is not even included. > Having a basic MTD driver is just not enough. > Please advise if such a overhead can interface with MTD? > Have fun! > > ///////////////////////////// > [...] > >> +#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 > > > > > I would prefer prefixing everything RK_NANDC_ or RK_ > > Will change define list above and below to RK_NANDC_ > It takes more space though. Already difficult to stay below 80 > char/line. Do your best, if it hurts readability you may break the 80 chars rule. Or try to write it down differently. [...] > >> +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; > > > > > I have not read yet the entire driver but I believe the above 4 > > parameters should probably be moved in rk_nand_chip, right? Anything > > that is NAND chip related should not be in the controller > > structure. It depends if you can change ECC requirements on the fly > > or not. > > Short answer: > The reason that it is the most convenience place to have them for now. > With one pointer from nand_get_controller_data() I have all data > available. > > struct rk_nand_controller *ctrl = nand_get_controller_data(nand); > > The ECC is now sort of fixed to 24 and 40 for legacy reasons. > The older rk3066 bootrom apparently only works for ecc 24. > See info based on older work by Paweł Jarosz for Uboot. > > I'm not too familiar with all inner working of MTD, so please advise. > Can the users get access to struct rk_nand_chip? > Would you like to give users control over what ecc to use? It is already the case: DT properties in the NAND node (see the bindings). But this is a static information, you cannot change it at run time of course. > What program can we use for that? Can't use dd then any more. You should not use dd at all. dd is for block devices or char devices, not MTD devices. There is an mtd-utils package out there that covers pretty much all the actions you want to. > How do we regain ecc control if we really have to for example rk3066? > Or remove that bootrom check and always set ECC with every > rk_nandc_hw_syndrome_ecc_read_page and > rk_nandc_hw_syndrome_ecc_write_page with whatever passed along? I don't understand your question. [...] > >> +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; > > Should I add this comment below? > > /* HW ECC always request ECC bytes for 1024 bytes blocks */ > > >> + nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * 14, > >> 8); > > > > > What do 14 and 8 mean? > > fls - find last (most-significant) bit set > > int fls(unsigned int x) > { > int r = 32; > > if (!x) > return 0; > if (!(x & 0xffff0000u)) { > x <<= 16; > r -= 16; > } > if (!(x & 0xff000000u)) { > x <<= 8; > r -= 8; > } > if (!(x & 0xf0000000u)) { > x <<= 4; > r -= 4; > } > if (!(x & 0xc0000000u)) { > x <<= 2; > r -= 2; > } > if (!(x & 0x80000000u)) { > x <<= 1; > r -= 1; > } > return r; > } > > nand->ecc.bytes = DIV_ROUND_UP(ecc->strength * fls(8 * 1024), > 8); > > formule is used to translate strength in bit/1024 BCH/ECC > to MTD ECC bytes for 1024 bytes blocks in nand->ecc.bytes > > 14 is replacement for fls(8 * 1024) This is absolutely forbidden :) You should never hide your first intention, 14 here is a very bad placeholder because we cannot know what it means. I suppose your intention was to optimize things. fls() already exist in the kernel, 8 and 1024 are constants so GCC (or whatever compiler you use) will optimize things as much as it can. Plus, it is run only once in a lifetime so there is not so much gain anyway. > 8 bits in a byte I think there is a macro for that (otherwise it is not important) > > > > >> + /* 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); > > > > I'm pretty sure most of these writel could be turned into > > writel_relaxed. > > writel() -- write to the little-endian hardware register with > compiler memory barrier, > writel_relaxed -- as above, without barrier, > __raw_writel() -- as above (writel_relaxed()) without endianess > conversion (CPU ordering will be used). > Architecture-dependent. > I don't know. In most of the cases you don't need an explicit barrier (unless, for instance, you are waiting for something to happen with a short timeout). Please try with writel_relaxed in most of the cases. > > > > > Also I am not a big fan of these casts, maybe you should change > > dma_data and dma_oob types (be careful: you enabled COMPILE_TEST in > > Kconfig, you must support 32-bit and 64-bit pointers, please try to > > compile this driver with different toolchains). > > Driver is used for both ARM as arm64. > These registers are 32 bit. Please advise what happens > when writel gets dma_addr_t and dma_data as 64 bit. > Don't have the hardware to find out. You don't need hardware, just a 32-bit and a 64-bit toolchain. We just want to avoid casts and build warnings. > > > > >> + > >> + 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); > > Same here. > > >> + > >> + 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); > >> + } > > > > Space > > > >> + 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) > > > > > Unaligned parameters > > To restyle I use: > astyle -T8 --max-code-length=80 --style=linux rockchip_nandc.c > > Please advise for a better solution. I don't know astyle, but this would certainly trigger a checkpatch.pl warning. Just check them and correct them by hand. > > > > >> +{ > >> +#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 > > > > > Please try to remove these #ifdefs, I don't know why would you need > > the former block? > > This driver is used both for ARM as arm64. > Original from Rockchip: arm64 doesn't have dma_map_single() as I > remember. Don't know what to use instead. > Please advise. I am not aware of such a limitation. Please check again. > > > > >> +} > >> + > >> +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 > > > > Same > > > >> +} > >> + > >> +static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip > >> *nand, > >> + uint8_t *buf, > >> + int oob_required, int page) > > > > > Unaligned parameters (please check all of them). > > Again blame astyle ... > After correcting it manually a few time I just left it as it is. > > > > >> +{ > >> + 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) && > > > > > Camel case is usually not welcome > > Any suggestions for a beter replace for NANDC_IDBResBlkNum are > welcome. SOMETHING_LikeThis -> SOMETHING_LIKE_THIS > > > > >> + ctrl->bootromblocks) > > > > This would probably deserve a helper > > > >> + 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); > > > > > One init_completion is needed (in the probe, probably) then please > > use reinit_completion(). > > Must study this later. > > > > >> + 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); > > > > Yous should check the return status of almost all the functions > > here. > > Please advise what ERROR code should be returned here > that is compatible with MTD. As a general rule, please check the drivers which have been updated recently with git log --one-line -- drivers/mtd/nand/raw/ -> marvell, atmel, fsmc, sunxi, stm, cadence, etc In this case, you can return the error code of the failing function with a simple: ret = func(); if (ret) return ret; I expect the rk_nandc_wiat_for_xfer_done() helper to return -ETIMEDOUT in case of timeout. > > > > >> + 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; > > > > > Please use u8, u16 and u32 types. > > Should this be changed for the entire source? Yes, checkpatch.pl will warn you about this too. uint<x>_t types are deprecated. > > > > >> + > >> + 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; > > > > > Why not using ret = $(real error) instead of using max_bitflips > > here? > > > > Then: > > > > if (ret) { > > > dev_err(); > > Do you really want to litter the kernel log with each time you hit a > bad block, > in case you use this function in a search bad block loop? > Don't thinks so ... > Please advise. Actually I think that I was simplifying your code but you are right that this dev_err has to be removed. > > > return ret; > > } > > > > return max_bitflips; > > rk_nandc_hw_syndrome_ecc_read_page() is used in a bad block search > strategy. I think max_bitflips = -1 was chosen as a saver value to > return. Please advise what number range MTD interprets as > max_bitflips or as fault. Also consider some uncontrolled status > return value, it would be better if we > are in control of what goes return. max_bitflip is a local variable, I'm just saying that using max_bitflip to save an error code is bad. You can check what other drivers do. > > > > >> + } 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); > > [...] > >> + > >> + 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; > > > > > You might want to write an helper to calculate bank_base, to avoid > > repeating these lines. > > Even with a separate define or helper function I still have to supply > my reg, selected_bank and offset. It doesn't make things cleaner > pumping date > to and from a helper or neither doesn't it shorten my source with a > define. Tend to keep it as it is for now. If you agree of course. #define RK_REG(ctrl, off) \ (ctrl)->regs + \ NANDC_REG_BANK0 + \ (ctrl)->selected_bank * BANK_SIZE + \ (off) #define RK_REG_CMD(ctrl) RK_REG(ctrl, 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); > > > > > device managed allocations (devm_...) would be nice > > devm_kzalloc() needs an extra struct device. > Does MTD safe-gard the correct order to detach from struct > rk_nand_controller > without rk_nandc_free_buffer()? &pdev->dev is fine, you can save it your own nand controller structure. I think accessing mtd->dev is not allowed before the registration of the MTD device though. > > > > >> + 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 || > > Added version 7 for RK3228A/RK3228B. Can someone with insider info > confirm if this works or not. > > >> + 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: > > > > I would either refuse ECC_HW or put it besides HW_SYNDROME. > > Is there a fundamental difference in handling ECC_HW and HW_SYNDROME > from the MTD point of view? Other then a indication how it's done on > the driver side? I don't think so.. > Will drop it. > > > > >> + case NAND_ECC_NONE: > >> + case NAND_ECC_SOFT: > > > > > Have you tested with SW BCH? > > Short answer: No > Just copied it from the original. > Please advise a tool to do a test between the individual ecc read > options. Or do I have to write the tool my self with mtd-utils? All the tools are already available. nand_test, flash_speed, nandbiterrs -i, etc > > > > >> + 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); > > > > Why 1 here? > > TODO for version 2. > A little misunderstanding on how for_each_child_of_node works. > All chips should be scanned. > ///// > Derive chipnr > Example from sunxi_nand.c > > if (!of_get_property(np, "reg", &nsels)) > return -EINVAL; > > nsels /= sizeof(u32); > if (!nsels) { > dev_err(dev, "invalid reg property size\n"); > return -EINVAL; > } > ///// > From rk_nandc_chips_init() > > for_each_child_of_node(np, nand_np) { > ret = rk_nandc_chip_init(dev, ctrl, nand_np, i); > > Why does sunxi_nand.c need this extra for_each_child_of_node? for_each_child_of_node will loop over all the NAND chips that are connected to the NAND controller. You may have -depending on the NAND controller- more than one CS per chip. [...] > >> + 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; > > See comments above about Rk3288. Keeping track node alias for nandc0. > To remove or not? You may keep a static variable to point to an array or have a static counter that you increment, but definitely not static structures that would be shared. If there are two distinct NAND controllers, you must have distinct path/structures. ->probe() will be run once for each NAND controller. Thanks, Miquèl
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@rock-chips.com + * + * Update/restyle for linux-next. + * Add exec_op function. + * Combine driver for nandc version 6 and 9. + * 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 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");