DISCLAIMER: Use at your own risk.
Status: For testing only!
Version: V2
Title: Enable RK3066 NANDC for MK808.
The majority of Rockchip devices use a closed source FTL driver
to reduce wear leveling. This patch serie proposes
an experimental raw NAND controller driver for basic tasks
in order to get the bindings and the nodes accepted for in the dts files.
What does it do:
On module load this driver will reserve its resources.
After initialization the MTD framework will then try to detect
the type and number of NAND chips. When all conditions are met,
it registers it self as MTD device.
This driver is then ready to receive user commands
such as to read and write NAND pages.
Test examples:
# dd if=/dev/mtd0 of=dd.bin bs=8192 count=4
# nanddump -a -l 32768 -f nanddump.bin /dev/mtd0
Not tested:
NANDC version 9.
NAND raw write.
RK3066 still has no support for Uboot.
Any write command would interfere with data structures made by the boot loader.
Etc.
Problems:
No bad block support. Most devices use a FTL bad block map with tags
that must be located on specific page locations which is outside
the scope of the raw MTD framework.
hynix_nand_init() add extra option NAND_BBM_LASTPAGE for H27UCG8T2ATR-BC.
No partition support. A FTL driver will store at random locations and
a linear user specific layout does not fit within
the generic character of this basic driver.
Driver assumes that IO pins are correctly set by the boot loader.
Fixed timing setting.
RK3228A/RK3228B compatibility version 701 unknown
RV1108 nand version unknown
Etc.
Todo:
MLC ?
ARM vs arm64 testing
move BCH strengths and array size to struct rk_nandc_data
Changes V2:
Include suggestions by Miquel Raynal
Include suggestions by Rob Herring
Kconfig:
add supported Socs
rk3*.dtsi
change compatible
remove #address-cells and #size-cells;
swap clock
swap clock-names
rockchip,nand-controller.yaml
add "rockchip,idb-res-blk-num" property
add hash
change compatible
change doc name
swap clock-names
rockchip-nand-controller.c
add fls(8 * 1024) original intention
add more struct rk_nandc_data types
add rk_nandc_hw_ecc_setup_helper()
add "rockchip,idb-res-blk-num" property
add struct rk_nandc_cap
add struct rk_nandc_reg_offset
change copyright text
change dev_err to dev_dbg in rk_nandc_probe()
change driver name
change of_rk_nandc_match compatible and data
change rk_nandc_hw_syndrome_ecc_read_page() error message
check return status rk_nandc_hw_syndrome_ecc_read_page()
check return status rk_nandc_hw_syndrome_ecc_write_page()
fix ./scripts/checkpatch.pl --strict issues
fix arm64 compile (untested)
fix rk_nandc_chip_init nand_scan
move empty line above MODULE_LICENSE()
move NAND_ECC_HW below NAND_ECC_HW_SYNDROME
move vars from controller to chip structure
relocate init_completion()
remove nand_to_mtd in rk_nandc_attach_chip()
remove page pointer write tag for bootrom
rename all defines to RK_NANDC_*
rename rk_nand_ctrl
replace bank_base calculations by defines
replace g_nandc_info[id]
replace kmalloc by devm_kzalloc
replace uint8_t by u8
replace uint32_t bu u32
replace writel by writel_relaxed
replace RK_NANDC_NUM_BANKS by ctrl->cap->max_cs
Chris Zhong (1):
ARM: dts: rockchip: add nandc node for rk3066a/rk3188
Dingqiang Lin (2):
arm64: dts: rockchip: add nandc node for px30
arm64: dts: rockchip: add nandc node for rk3308
Jianqun Xu (1):
ARM: dts: rockchip: add nandc nodes for rk3288
Johan Jonker (2):
dt-bindings: mtd: add rockchip nand controller bindings
ARM: dts: rockchip: rk3066a-mk808: enable nandc node
Jon Lin (1):
ARM: dts: rockchip: add nandc node for rv1108
Wenping Zhang (1):
ARM: dts: rockchip: add nandc node for rk322x
Yifeng Zhao (1):
mtd: rawnand: rockchip: Add NAND controller driver
Zhaoyifeng (1):
arm64: dts: rockchip: add nandc node for rk3368
.../bindings/mtd/rockchip,nand-controller.yaml | 92 ++
arch/arm/boot/dts/rk3066a-mk808.dts | 11 +
arch/arm/boot/dts/rk322x.dtsi | 9 +
arch/arm/boot/dts/rk3288.dtsi | 20 +
arch/arm/boot/dts/rk3xxx.dtsi | 9 +
arch/arm/boot/dts/rv1108.dtsi | 9 +
arch/arm64/boot/dts/rockchip/px30.dtsi | 12 +
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 9 +
arch/arm64/boot/dts/rockchip/rk3368.dtsi | 9 +
drivers/mtd/nand/raw/Kconfig | 9 +
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/rockchip-nand-controller.c | 1307 ++++++++++++++++++++
12 files changed, 1497 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/rockchip,nand-controller.yaml
create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c
--
2.11.0
From: Yifeng Zhao <[email protected]>
Add basic Rockchip NAND controller driver.
Compatible with hardware version 6 and 9.
V6:16, 24, 40, 60 per 1024B BCH/ECC.
V9:16, 40, 60, 70 per 1024B BCH/ECC.
8 bit asynchronous flash interface support.
Supports up to 2 identical nandc nodes.
Max 4 nand chips per controller.
Able to select a different hardware ecc setup
for the loader blocks.
No bad block support.
Signed-off-by: Yifeng Zhao <[email protected]>
Signed-off-by: Johan Jonker <[email protected]>
---
drivers/mtd/nand/raw/Kconfig | 9 +
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/rockchip-nand-controller.c | 1307 +++++++++++++++++++++++
3 files changed, 1317 insertions(+)
create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 74fb91ade..acee3d6ee 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,15 @@ config MTD_NAND_CADENCE
Enable the driver for NAND flash on platforms using a Cadence NAND
controller.
+config MTD_NAND_ROCKCHIP
+ tristate "Rockchip raw NAND controller driver"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Enables support for the Rockchip raw NAND controller driver.
+ This controller is found on:
+ px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108 SoCs.
+
comment "Misc"
config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158..a54aa85f4 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o
obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o
obj-$(CONFIG_MTD_NAND_CADENCE) += cadence-nand-controller.o
+obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o
nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
new file mode 100644
index 000000000..76ed1c08d
--- /dev/null
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -0,0 +1,1307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on:
+ * https://github.com/rockchip-linux/kernel
+ *
+ * Copyright (c) 2016-2019 Yifeng Zhao [email protected]
+ *
+ * Support NAND controller versions 6 and 9 found on SoCs:
+ * px30, rk3066, rk3188, rk3228, rk3288, rk3308, rk3368, rv1108
+ *
+ * Copyright (c) 2020 Johan Jonker [email protected]
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <linux/mtd/rawnand.h>
+
+#define RK_NANDC_ID_V600 0x56363030
+#define RK_NANDC_ID_V622 0x56363232
+#define RK_NANDC_ID_V701 0x701
+#define RK_NANDC_ID_V800 0x56383030
+#define RK_NANDC_ID_V801 0x801
+#define RK_NANDC_ID_V900 0x56393030
+
+#define RK_NANDC_IDB_Res_Blk_Num 16
+#define RK_NANDC_IDB_Ecc_Bits 24
+
+#define RK_NANDC_V6_ECC_16 0x00000000
+#define RK_NANDC_V6_ECC_24 0x00000010
+#define RK_NANDC_V6_ECC_40 0x00040000
+#define RK_NANDC_V6_ECC_60 0x00040010
+
+#define RK_NANDC_V9_ECC_16 0x02000001
+#define RK_NANDC_V9_ECC_40 0x04000001
+#define RK_NANDC_V9_ECC_60 0x06000001
+#define RK_NANDC_V9_ECC_70 0x00000001
+
+#define RK_NANDC_NUM_BANKS 8
+#define RK_NANDC_DEF_TIMEOUT 10000
+
+#define RK_NANDC_REG_DATA 0x00
+#define RK_NANDC_REG_ADDR 0x04
+#define RK_NANDC_REG_CMD 0x08
+
+/* register offset nandc version 6 */
+#define RK_NANDC_REG_V6_FMCTL 0x00
+#define RK_NANDC_REG_V6_FMWAIT 0x04
+#define RK_NANDC_REG_V6_FLCTL 0x08
+#define RK_NANDC_REG_V6_BCHCTL 0x0c
+#define RK_NANDC_REG_V6_DMA_CFG 0x10
+#define RK_NANDC_REG_V6_DMA_BUF0 0x14
+#define RK_NANDC_REG_V6_DMA_BUF1 0x18
+#define RK_NANDC_REG_V6_DMA_ST 0x1C
+#define RK_NANDC_REG_V6_BCHST 0x20
+#define RK_NANDC_REG_V6_RANDMZ 0x150
+#define RK_NANDC_REG_V6_VER 0x160
+#define RK_NANDC_REG_V6_INTEN 0x16C
+#define RK_NANDC_REG_V6_INTCLR 0x170
+#define RK_NANDC_REG_V6_INTST 0x174
+#define RK_NANDC_REG_V6_SPARE0 0x200
+#define RK_NANDC_REG_V6_SPARE1 0x230
+
+/* register offset nandc version 9 */
+#define RK_NANDC_REG_V9_FMCTL 0x00
+#define RK_NANDC_REG_V9_FMWAIT 0x04
+#define RK_NANDC_REG_V9_FLCTL 0x10
+#define RK_NANDC_REG_V9_BCHCTL 0x20
+#define RK_NANDC_REG_V9_DMA_CFG 0x30
+#define RK_NANDC_REG_V9_DMA_BUF0 0x34
+#define RK_NANDC_REG_V9_DMA_BUF1 0x38
+#define RK_NANDC_REG_V9_DMA_ST 0x40
+#define RK_NANDC_REG_V9_VER 0x80
+#define RK_NANDC_REG_V9_INTEN 0x120
+#define RK_NANDC_REG_V9_INTCLR 0x124
+#define RK_NANDC_REG_V9_INTST 0x128
+#define RK_NANDC_REG_V9_BCHST 0x150
+#define RK_NANDC_REG_V9_SPARE0 0x200
+#define RK_NANDC_REG_V9_SPARE1 0x204
+#define RK_NANDC_REG_V9_RANDMZ 0x208
+
+/* register offset nandc common */
+#define RK_NANDC_REG_BANK0 0x800
+#define RK_NANDC_REG_SRAM0 0x1000
+
+/* calculate bank_base */
+#define RK_NANDC_BANK_SIZE 0x100
+
+#define RK_REG(ctrl, off) \
+ ((ctrl)->regs + \
+ RK_NANDC_REG_BANK0 + \
+ (ctrl)->selected_bank * RK_NANDC_BANK_SIZE + \
+ (off))
+
+#define RK_REG_DATA(ctrl) RK_REG(ctrl, RK_NANDC_REG_DATA)
+#define RK_REG_ADDR(ctrl) RK_REG(ctrl, RK_NANDC_REG_ADDR)
+#define RK_REG_CMD(ctrl) RK_REG(ctrl, RK_NANDC_REG_CMD)
+
+/* FMCTL */
+#define RK_NANDC_V6_FM_WP BIT(8)
+#define RK_NANDC_V6_FM_CE_SEL_M 0xFF
+#define RK_NANDC_V6_FM_CE_SEL(x) (1 << (x))
+#define RK_NANDC_V6_FM_FREADY BIT(9)
+
+#define RK_NANDC_V9_FM_WP BIT(8)
+#define RK_NANDC_V9_FM_CE_SEL_M 0xFF
+#define RK_NANDC_V9_FM_CE_SEL(x) (1 << (x))
+#define RK_NANDC_V9_RDY BIT(9)
+
+/* FLCTL */
+#define RK_NANDC_V6_FL_RST BIT(0)
+#define RK_NANDC_V6_FL_DIR(x) ((x) ? BIT(1) : 0)
+#define RK_NANDC_V6_FL_XFER_START BIT(2)
+#define RK_NANDC_V6_FL_XFER_EN BIT(3)
+#define RK_NANDC_V6_FL_ST_BUF_S 0x4
+#define RK_NANDC_V6_FL_XFER_COUNT BIT(5)
+#define RK_NANDC_V6_FL_ACORRECT BIT(10)
+#define RK_NANDC_V6_FL_XFER_READY BIT(20)
+#define RK_NANDC_V6_FL_PAGE_NUM(x) ((x) << 22)
+#define RK_NANDC_V6_FL_ASYNC_TOG_MIX BIT(29)
+
+#define RK_NANDC_V9_FL_RST BIT(0)
+#define RK_NANDC_V9_FL_DIR(x) ((x) ? BIT(1) : 0)
+#define RK_NANDC_V9_FL_XFER_START BIT(2)
+#define RK_NANDC_V9_FL_XFER_EN BIT(3)
+#define RK_NANDC_V9_FL_ST_BUF_S 0x4
+#define RK_NANDC_V9_FL_XFER_COUNT BIT(5)
+#define RK_NANDC_V9_FL_ACORRECT BIT(10)
+#define RK_NANDC_V9_FL_XFER_READY BIT(20)
+#define RK_NANDC_V9_FL_PAGE_NUM(x) ((x) << 22)
+#define RK_NANDC_V9_FL_ASYNC_TOG_MIX BIT(29)
+
+/* BCHCTL */
+#define RK_NANDC_V6_BCH_REGION_S 0x5
+#define RK_NANDC_V6_BCH_REGION_M 0x7
+
+#define RK_NANDC_V9_BCH_MODE_S 25
+#define RK_NANDC_V9_BCH_MODE_M 0x7
+
+/* BCHST */
+#define RK_NANDC_V6_BCH0_ST_ERR BIT(2)
+#define RK_NANDC_V6_BCH1_ST_ERR BIT(15)
+#define RK_NANDC_V6_ECC_ERR_CNT0(x) (((((x) & (0x1F << 3)) >> 3) \
+ | (((x) & (1 << 27)) >> 22)) & 0x3F)
+#define RK_NANDC_V6_ECC_ERR_CNT1(x) (((((x) & (0x1F << 16)) >> 16) \
+ | (((x) & (1 << 29)) >> 24)) & 0x3F)
+
+#define RK_NANDC_V9_BCH0_ST_ERR BIT(2)
+#define RK_NANDC_V9_BCH1_ST_ERR BIT(18)
+#define RK_NANDC_V9_ECC_ERR_CNT0(x) (((x) & (0x7F << 3)) >> 3)
+#define RK_NANDC_V9_ECC_ERR_CNT1(x) (((x) & (0x7F << 19)) >> 19)
+
+/* DMA_CFG */
+#define RK_NANDC_V6_DMA_CFG_WR_ST BIT(0)
+#define RK_NANDC_V6_DMA_CFG_WR(x) (!(x) ? BIT(1) : 0)
+#define RK_NANDC_V6_DMA_CFG_BUS_MODE BIT(2)
+
+#define RK_NANDC_V6_DMA_CFG_HSIZE_8 0
+#define RK_NANDC_V6_DMA_CFG_HSIZE_16 BIT(3)
+#define RK_NANDC_V6_DMA_CFG_HSIZE_32 (2 << 3)
+
+#define RK_NANDC_V6_DMA_CFG_BURST_1 0
+#define RK_NANDC_V6_DMA_CFG_BURST_4 (3 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_8 (5 << 6)
+#define RK_NANDC_V6_DMA_CFG_BURST_16 (7 << 6)
+
+#define RK_NANDC_V6_DMA_CFG_INCR_NUM(x) ((x) << 9)
+
+#define RK_NANDC_V9_DMA_CFG_WR_ST BIT(0)
+#define RK_NANDC_V9_DMA_CFG_WR(x) (!(x) ? BIT(1) : 0)
+#define RK_NANDC_V9_DMA_CFG_BUS_MODE BIT(2)
+
+#define RK_NANDC_V9_DMA_CFG_HSIZE_8 0
+#define RK_NANDC_V9_DMA_CFG_HSIZE_16 BIT(3)
+#define RK_NANDC_V9_DMA_CFG_HSIZE_32 (2 << 3)
+
+#define RK_NANDC_V9_DMA_CFG_BURST_1 0
+#define RK_NANDC_V9_DMA_CFG_BURST_4 (3 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_8 (5 << 6)
+#define RK_NANDC_V9_DMA_CFG_BURST_16 (7 << 6)
+
+#define RK_NANDC_V9_DMA_CFG_INCR_NUM(x) ((x) << 9)
+
+/* INTEN */
+#define RK_NANDC_V6_INT_DMA BIT(0)
+
+#define RK_NANDC_V9_INT_DMA BIT(0)
+
+/*
+ * The bit positions for
+ * RK_NANDC_REG_V6_FMCTL and RK_NANDC_REG_V9_FMCTL,
+ * RK_NANDC_REG_V6_FLCTL and RK_NANDC_REG_V9_FLCTL,
+ * RK_NANDC_REG_V6_DMA_CFG and RK_NANDC_REG_V9_DMA_CFG
+ * are identical.
+ */
+
+enum rk_nandc_version {
+ VERSION_6 = 6,
+ VERSION_9 = 9,
+};
+
+struct rk_nandc_reg_offset {
+ u32 fmctl;
+ u32 fmwait;
+ u32 flctl;
+ u32 bchctl;
+ u32 dma_cfg;
+ u32 dma_buf0;
+ u32 dma_buf1;
+ u32 dma_st;
+ u32 bchst;
+ u32 randmz;
+ u32 ver;
+ u32 inten;
+ u32 intclr;
+ u32 intst;
+ u32 spare0;
+ u32 spare1;
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V6_reg_offset = {
+ .fmctl = RK_NANDC_REG_V6_FMCTL,
+ .fmwait = RK_NANDC_REG_V6_FMWAIT,
+ .flctl = RK_NANDC_REG_V6_FLCTL,
+ .bchctl = RK_NANDC_REG_V6_BCHCTL,
+ .dma_cfg = RK_NANDC_REG_V6_DMA_CFG,
+ .dma_buf0 = RK_NANDC_REG_V6_DMA_BUF0,
+ .dma_buf1 = RK_NANDC_REG_V6_DMA_BUF1,
+ .dma_st = RK_NANDC_REG_V6_DMA_ST,
+ .bchst = RK_NANDC_REG_V6_BCHST,
+ .randmz = RK_NANDC_REG_V6_RANDMZ,
+ .ver = RK_NANDC_REG_V6_VER,
+ .inten = RK_NANDC_REG_V6_INTEN,
+ .intclr = RK_NANDC_REG_V6_INTCLR,
+ .intst = RK_NANDC_REG_V6_INTST,
+ .spare0 = RK_NANDC_REG_V6_SPARE0,
+ .spare1 = RK_NANDC_REG_V6_SPARE1,
+};
+
+static const struct rk_nandc_reg_offset rk_nandc_V9_reg_offset = {
+ .fmctl = RK_NANDC_REG_V9_FMCTL,
+ .fmwait = RK_NANDC_REG_V9_FMWAIT,
+ .flctl = RK_NANDC_REG_V9_FLCTL,
+ .bchctl = RK_NANDC_REG_V9_BCHCTL,
+ .dma_cfg = RK_NANDC_REG_V9_DMA_CFG,
+ .dma_buf0 = RK_NANDC_REG_V9_DMA_BUF0,
+ .dma_buf1 = RK_NANDC_REG_V9_DMA_BUF1,
+ .dma_st = RK_NANDC_REG_V9_DMA_ST,
+ .bchst = RK_NANDC_REG_V9_BCHST,
+ .randmz = RK_NANDC_REG_V9_RANDMZ,
+ .ver = RK_NANDC_REG_V9_VER,
+ .inten = RK_NANDC_REG_V9_INTEN,
+ .intclr = RK_NANDC_REG_V9_INTCLR,
+ .intst = RK_NANDC_REG_V9_INTST,
+ .spare0 = RK_NANDC_REG_V9_SPARE0,
+ .spare1 = RK_NANDC_REG_V9_SPARE1,
+};
+
+struct rk_nandc_cap {
+ u8 max_cs;
+};
+
+static const struct rk_nandc_cap rk_nandc_V600_cap = {
+ .max_cs = 8,
+};
+
+static const struct rk_nandc_cap rk_nandc_V622_cap = {
+ .max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V701_cap = {
+ .max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V801_cap = {
+ .max_cs = 4,
+};
+
+static const struct rk_nandc_cap rk_nandc_V900_cap = {
+ .max_cs = 4,
+};
+
+struct rk_nandc_data {
+ enum rk_nandc_version version;
+ struct rk_nandc_reg_offset *ofs;
+ struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_ctrl {
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+ struct clk *hclk;
+ struct clk *clk;
+ struct list_head chips;
+ struct completion complete;
+ struct nand_controller controller;
+ int cs[RK_NANDC_NUM_BANKS];
+ u32 *oob_buf;
+ u32 *page_buf;
+ int selected_bank;
+ unsigned long assigned_cs;
+ enum rk_nandc_version version;
+ struct rk_nandc_reg_offset *ofs;
+ struct rk_nandc_cap *cap;
+};
+
+struct rk_nand_chip {
+ struct nand_chip nand;
+ struct list_head node;
+ bool bootromblocks;
+ int ecc_mode;
+ int max_ecc_strength;
+ int idbresblknum;
+};
+
+static struct rk_nand_chip *to_rk_nand(struct nand_chip *nand)
+{
+ return container_of(nand, struct rk_nand_chip, nand);
+}
+
+static void rk_nandc_init(struct rk_nand_ctrl *ctrl)
+{
+ writel_relaxed(0, ctrl->regs + ctrl->ofs->randmz);
+ writel_relaxed(0, ctrl->regs + ctrl->ofs->dma_cfg);
+ writel_relaxed(RK_NANDC_V6_FM_WP, ctrl->regs + ctrl->ofs->fmctl);
+ writel_relaxed(RK_NANDC_V6_FL_RST, ctrl->regs + ctrl->ofs->flctl);
+ writel_relaxed(0x1081, ctrl->regs + ctrl->ofs->fmwait);
+}
+
+static irqreturn_t rk_nandc_interrupt(int irq, void *dev_id)
+{
+ struct rk_nand_ctrl *ctrl = dev_id;
+
+ u32 st = readl(ctrl->regs + ctrl->ofs->intst);
+ u32 ien = readl(ctrl->regs + ctrl->ofs->inten);
+
+ if (!(ien & st))
+ return IRQ_NONE;
+
+ if ((ien & st) == ien)
+ complete(&ctrl->complete);
+
+ writel_relaxed(st, ctrl->regs + ctrl->ofs->intclr);
+ writel_relaxed(~st & ien, ctrl->regs + ctrl->ofs->inten);
+
+ return IRQ_HANDLED;
+}
+
+static void rk_nandc_select_chip(struct nand_chip *nand, int chipnr)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ u32 reg;
+ int banknr;
+
+ reg = readl(ctrl->regs + ctrl->ofs->fmctl);
+ reg &= ~RK_NANDC_V6_FM_CE_SEL_M;
+
+ if (chipnr == -1) {
+ banknr = -1;
+ } else {
+ banknr = ctrl->cs[chipnr];
+
+ reg |= RK_NANDC_V6_FM_CE_SEL(banknr);
+ }
+
+ ctrl->selected_bank = banknr;
+
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->fmctl);
+}
+
+static int rk_nandc_hw_ecc_setup(struct nand_chip *nand,
+ u32 strength)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ u32 reg;
+
+ nand->ecc.strength = strength;
+ /* HW ECC always request ECC bytes for 1024 bytes blocks */
+ nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * fls(8 * 1024), 8);
+ /* HW ECC only works with an even number of ECC bytes */
+ nand->ecc.bytes = ALIGN(nand->ecc.bytes, 2);
+
+ if (ctrl->version == VERSION_9) {
+ switch (nand->ecc.strength) {
+ case 70:
+ reg = RK_NANDC_V9_ECC_70;
+ break;
+ case 60:
+ reg = RK_NANDC_V9_ECC_60;
+ break;
+ case 40:
+ reg = RK_NANDC_V9_ECC_40;
+ break;
+ case 16:
+ reg = RK_NANDC_V9_ECC_16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ switch (nand->ecc.strength) {
+ case 60:
+ reg = RK_NANDC_V6_ECC_60;
+ break;
+ case 40:
+ reg = RK_NANDC_V6_ECC_40;
+ break;
+ case 24:
+ reg = RK_NANDC_V6_ECC_24;
+ break;
+ case 16:
+ reg = RK_NANDC_V6_ECC_16;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+
+ return 0;
+}
+
+static void rk_nandc_xfer_start(struct rk_nand_ctrl *ctrl,
+ u8 dir, u8 n_KB,
+ dma_addr_t dma_data,
+ dma_addr_t dma_oob)
+{
+ u32 reg;
+
+ if (ctrl->version == VERSION_6) {
+ reg = readl(ctrl->regs + ctrl->ofs->bchctl);
+ reg = (reg & (~(RK_NANDC_V6_BCH_REGION_M <<
+ RK_NANDC_V6_BCH_REGION_S))) |
+ (ctrl->selected_bank << RK_NANDC_V6_BCH_REGION_S);
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->bchctl);
+ }
+
+ reg = RK_NANDC_V6_DMA_CFG_WR_ST |
+ RK_NANDC_V6_DMA_CFG_WR(dir) |
+ RK_NANDC_V6_DMA_CFG_BUS_MODE |
+ RK_NANDC_V6_DMA_CFG_HSIZE_32 |
+ RK_NANDC_V6_DMA_CFG_BURST_16 |
+ RK_NANDC_V6_DMA_CFG_INCR_NUM(16);
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->dma_cfg);
+ writel_relaxed((u32)dma_data, ctrl->regs + ctrl->ofs->dma_buf0);
+ writel_relaxed((u32)dma_oob, ctrl->regs + ctrl->ofs->dma_buf1);
+
+ reg = RK_NANDC_V6_FL_DIR(dir) |
+ RK_NANDC_V6_FL_XFER_EN |
+ RK_NANDC_V6_FL_XFER_COUNT |
+ RK_NANDC_V6_FL_ACORRECT |
+ RK_NANDC_V6_FL_PAGE_NUM(n_KB) |
+ RK_NANDC_V6_FL_ASYNC_TOG_MIX;
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+ reg |= RK_NANDC_V6_FL_XFER_START;
+ writel_relaxed(reg, ctrl->regs + ctrl->ofs->flctl);
+}
+
+static int rk_nandc_wait_for_xfer_done(struct rk_nand_ctrl *ctrl)
+{
+ u32 reg;
+ int ret;
+
+ void __iomem *ptr = ctrl->regs + ctrl->ofs->flctl;
+
+ ret = readl_poll_timeout_atomic(ptr, reg,
+ reg & RK_NANDC_V6_FL_XFER_READY,
+ 1, RK_NANDC_DEF_TIMEOUT);
+
+ return ret;
+}
+
+static void rk_nandc_hw_ecc_setup_helper(struct nand_chip *nand,
+ int page, bool entry)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+
+ int pages_per_blk = mtd->erasesize / mtd->writesize;
+
+ /* only bank 0 used for boot rom blocks */
+ if ((page < pages_per_blk * rk_nand->idbresblknum) &&
+ rk_nand->bootromblocks &&
+ !ctrl->selected_bank &&
+ entry)
+ rk_nandc_hw_ecc_setup(nand, RK_NANDC_IDB_Ecc_Bits);
+ else
+ rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+}
+
+static int rk_nandc_hw_syndrome_ecc_read_page(struct nand_chip *nand,
+ u8 *buf,
+ int oob_required,
+ int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ int max_bitflips = 0;
+ dma_addr_t dma_data, dma_oob;
+ int ret, i;
+ int bch_st;
+ int dma_oob_size = ecc->steps * 128;
+
+ rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+ rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+ nand_read_page_op(nand, page, 0, NULL, 0);
+
+ dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+ DMA_FROM_DEVICE);
+ dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+ DMA_FROM_DEVICE);
+
+ reinit_completion(&ctrl->complete);
+ writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+ rk_nandc_xfer_start(ctrl, 0, ecc->steps, dma_data, dma_oob);
+
+ ret = wait_for_completion_timeout(&ctrl->complete, msecs_to_jiffies(5));
+
+ if (!ret) {
+ dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+ ret = -ETIMEDOUT;
+ goto unmap_dma;
+ }
+
+ ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+ if (ret) {
+ dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+ ret = -ETIMEDOUT;
+ }
+
+unmap_dma:
+ dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_FROM_DEVICE);
+ dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_FROM_DEVICE);
+
+ if (ret)
+ goto ecc_setup;
+
+ memcpy(buf, ctrl->page_buf, mtd->writesize);
+
+ if (oob_required) {
+ u8 *oob;
+ u32 tmp;
+
+ for (i = 0; i < ecc->steps; i++) {
+ oob = nand->oob_poi +
+ i * (ecc->bytes + nand->ecc.prepad);
+ if (ctrl->version == VERSION_9) {
+ tmp = ctrl->oob_buf[i];
+ } else {
+ u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+ ? 64 : 128;
+ tmp = ctrl->oob_buf[i * oob_step / 4];
+ }
+ *oob++ = (u8)tmp;
+ *oob++ = (u8)(tmp >> 8);
+ *oob++ = (u8)(tmp >> 16);
+ *oob++ = (u8)(tmp >> 24);
+ }
+ }
+
+ if (ctrl->version == VERSION_9) {
+ for (i = 0; i < ecc->steps / 2; i++) {
+ bch_st = readl(ctrl->regs +
+ ctrl->ofs->bchst +
+ i * 4);
+ if (bch_st & RK_NANDC_V9_BCH0_ST_ERR ||
+ bch_st & RK_NANDC_V9_BCH1_ST_ERR) {
+ mtd->ecc_stats.failed++;
+ max_bitflips = -1;
+ } else {
+ ret = RK_NANDC_V9_ECC_ERR_CNT0(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+
+ ret = RK_NANDC_V9_ECC_ERR_CNT1(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+ }
+ }
+ } else {
+ for (i = 0; i < ecc->steps / 2; i++) {
+ bch_st = readl(ctrl->regs +
+ ctrl->ofs->bchst +
+ i * 4);
+ if (bch_st & RK_NANDC_V6_BCH0_ST_ERR ||
+ bch_st & RK_NANDC_V6_BCH1_ST_ERR) {
+ mtd->ecc_stats.failed++;
+ max_bitflips = -1;
+ } else {
+ ret = RK_NANDC_V6_ECC_ERR_CNT0(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+
+ ret = RK_NANDC_V6_ECC_ERR_CNT1(bch_st);
+ mtd->ecc_stats.corrected += ret;
+ max_bitflips = max_t(unsigned int,
+ max_bitflips, ret);
+ }
+ }
+ }
+
+ ret = max_bitflips;
+ if (max_bitflips == -1) {
+ dev_err(ctrl->dev, "BCH status error\n");
+ ret = -ENODATA;
+ }
+
+ecc_setup:
+ rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+ return ret;
+}
+
+static int rk_nandc_hw_syndrome_ecc_write_page(struct nand_chip *nand,
+ const u8 *buf,
+ int oob_required,
+ int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ struct nand_ecc_ctrl *ecc = &nand->ecc;
+ dma_addr_t dma_data, dma_oob;
+ int i, ret;
+ int dma_oob_size = ecc->steps * 128;
+
+ rk_nandc_select_chip(nand, ctrl->selected_bank);
+
+ rk_nandc_hw_ecc_setup_helper(nand, page, true);
+
+ nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+ for (i = 0; i < ecc->steps; i++) {
+ u32 tmp;
+
+ if (oob_required) {
+ u8 *oob;
+
+ oob = nand->oob_poi +
+ i * (ecc->bytes + nand->ecc.prepad);
+ tmp = oob[0] |
+ (oob[1] << 8) |
+ (oob[2] << 16) |
+ (oob[3] << 24);
+ } else {
+ tmp = 0xFFFFFFFF;
+ }
+ if (ctrl->version == VERSION_9) {
+ ctrl->oob_buf[i] = tmp;
+ } else {
+ u8 oob_step = (to_rk_nand(nand)->ecc_mode <= 24)
+ ? 64 : 128;
+ ctrl->oob_buf[i * oob_step / 4] = tmp;
+ }
+ }
+
+ memcpy(ctrl->page_buf, buf, mtd->writesize);
+
+ dma_data = dma_map_single(ctrl->dev, ctrl->page_buf, mtd->writesize,
+ DMA_TO_DEVICE);
+ dma_oob = dma_map_single(ctrl->dev, ctrl->oob_buf, dma_oob_size,
+ DMA_TO_DEVICE);
+
+ reinit_completion(&ctrl->complete);
+ writel_relaxed(RK_NANDC_V6_INT_DMA, ctrl->regs + ctrl->ofs->inten);
+ rk_nandc_xfer_start(ctrl, 1, ecc->steps, dma_data, dma_oob);
+
+ ret = wait_for_completion_timeout(&ctrl->complete,
+ msecs_to_jiffies(10));
+
+ if (!ret) {
+ dev_err(ctrl->dev, "wait_for_completion_timeout\n");
+ ret = -ETIMEDOUT;
+ goto unmap_dma;
+ }
+
+ ret = rk_nandc_wait_for_xfer_done(ctrl);
+
+ if (ret) {
+ dev_err(ctrl->dev, "rk_nandc_wait_for_xfer_done\n");
+ ret = -ETIMEDOUT;
+ }
+
+unmap_dma:
+ dma_unmap_single(ctrl->dev, dma_data, mtd->writesize, DMA_TO_DEVICE);
+ dma_unmap_single(ctrl->dev, dma_oob, dma_oob_size, DMA_TO_DEVICE);
+
+ rk_nandc_hw_ecc_setup_helper(nand, page, false);
+
+ if (!ret)
+ ret = nand_prog_page_end_op(nand);
+
+ return ret;
+}
+
+static int rk_nandc_hw_ecc_read_oob(struct nand_chip *nand, int page)
+{
+ u8 *buf = nand_get_data_buf(nand);
+
+ return nand->ecc.read_page(nand, buf, true, page);
+}
+
+static int rk_nandc_hw_ecc_write_oob(struct nand_chip *nand, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ int ret;
+ u8 *buf = nand_get_data_buf(nand);
+
+ memset(buf, 0xFF, mtd->writesize);
+ ret = nand->ecc.write_page(nand, buf, true, page);
+ if (ret)
+ return ret;
+
+ return nand_prog_page_end_op(nand);
+}
+
+static void rk_nandc_read_buf(struct nand_chip *nand, u8 *buf, int len)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ int offs = 0;
+ void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+ for (offs = 0; offs < len; offs++)
+ buf[offs] = readb(bank_base);
+}
+
+static void rk_nandc_write_buf(struct nand_chip *nand, const u8 *buf, int len)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ int offs = 0;
+ void __iomem *bank_base = RK_REG_DATA(ctrl);
+
+ for (offs = 0; offs < len; offs++)
+ writeb(buf[offs], bank_base);
+}
+
+static void rk_nandc_write_cmd(struct nand_chip *nand, u8 cmd)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+ void __iomem *bank_base = RK_REG_CMD(ctrl);
+
+ writeb(cmd, bank_base);
+}
+
+static void rk_nandc_write_addr(struct nand_chip *nand, u8 addr)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+ void __iomem *bank_base = RK_REG_ADDR(ctrl);
+
+ writeb(addr, bank_base);
+}
+
+static int rk_nandc_dev_ready(struct nand_chip *nand)
+{
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+ if (readl(ctrl->regs + ctrl->ofs->fmctl) & RK_NANDC_V6_FM_FREADY)
+ return 1;
+
+ return 0;
+}
+
+static int rk_nandc_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+
+ if (section >= nand->ecc.steps)
+ return -ERANGE;
+
+ oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section +
+ nand->ecc.prepad;
+ oobregion->length = nand->ecc.steps * nand->ecc.bytes;
+
+ return 0;
+}
+
+static int rk_nandc_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+
+ if (section >= nand->ecc.steps)
+ return -ERANGE;
+
+ oobregion->offset = (nand->ecc.bytes + nand->ecc.prepad) * section;
+ oobregion->length = nand->ecc.steps * nand->ecc.prepad;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops rk_nandc_oob_ops = {
+ .ecc = rk_nandc_ooblayout_ecc,
+ .free = rk_nandc_ooblayout_free,
+};
+
+static int rk_nandc_buffer_init(struct nand_chip *nand)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+
+ ctrl->page_buf = devm_kzalloc(ctrl->dev,
+ mtd->writesize,
+ GFP_KERNEL | GFP_DMA);
+ if (!ctrl->page_buf)
+ return -ENOMEM;
+
+ ctrl->oob_buf = devm_kzalloc(ctrl->dev,
+ nand->ecc.steps * 128,
+ GFP_KERNEL | GFP_DMA);
+ if (!ctrl->oob_buf) {
+ devm_kfree(ctrl->dev, ctrl->page_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int rk_nandc_hw_ecc_ctrl_init(struct nand_chip *nand)
+{
+ u8 strengths_v6[] = {60, 40, 24, 16};
+ u8 strengths_v9[] = {70, 60, 40, 16};
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct rk_nand_ctrl *ctrl = nand_get_controller_data(nand);
+ struct rk_nand_chip *rk_nand = to_rk_nand(nand);
+ int max_strength;
+ u32 i, ver;
+
+ if (nand->options & NAND_IS_BOOT_MEDIUM)
+ rk_nand->bootromblocks = true;
+ else
+ rk_nand->bootromblocks = false;
+
+ nand->ecc.prepad = 4;
+ nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+ max_strength = ((mtd->oobsize / nand->ecc.steps) - 4) * 8 / 14;
+ if (ctrl->version == VERSION_9) {
+ rk_nand->max_ecc_strength = 70;
+ ver = readl(ctrl->regs + RK_NANDC_REG_V9_VER);
+ if (ver != RK_NANDC_ID_V900)
+ dev_err(mtd->dev.parent,
+ "unsupported nandc version %x\n", ver);
+
+ if (max_strength > rk_nand->max_ecc_strength)
+ max_strength = rk_nand->max_ecc_strength;
+
+ for (i = 0; i < ARRAY_SIZE(strengths_v9); i++) {
+ if (max_strength >= strengths_v9[i])
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(strengths_v9)) {
+ dev_err(mtd->dev.parent,
+ "unsupported strength\n");
+ return -ENOTSUPP;
+ }
+
+ rk_nand->ecc_mode = strengths_v9[i];
+ } else {
+ rk_nand->max_ecc_strength = 60;
+
+ ver = readl(ctrl->regs + RK_NANDC_REG_V6_VER);
+ if (ver == RK_NANDC_ID_V801)
+ rk_nand->max_ecc_strength = 16;
+ else if (ver == RK_NANDC_ID_V600 ||
+ ver == RK_NANDC_ID_V622 ||
+ ver == RK_NANDC_ID_V701 ||
+ ver == RK_NANDC_ID_V800)
+ rk_nand->max_ecc_strength = 60;
+ else
+ dev_err(mtd->dev.parent,
+ "unsupported nandc version %x\n", ver);
+
+ if (max_strength > rk_nand->max_ecc_strength)
+ max_strength = rk_nand->max_ecc_strength;
+
+ for (i = 0; i < ARRAY_SIZE(strengths_v6); i++) {
+ if (max_strength >= strengths_v6[i])
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(strengths_v6)) {
+ dev_err(mtd->dev.parent,
+ "unsupported strength\n");
+ return -ENOTSUPP;
+ }
+
+ rk_nand->ecc_mode = strengths_v6[i];
+ }
+ rk_nandc_hw_ecc_setup(nand, rk_nand->ecc_mode);
+
+ mtd_set_ooblayout(mtd, &rk_nandc_oob_ops);
+
+ if (mtd->oobsize < ((nand->ecc.bytes + nand->ecc.prepad) *
+ nand->ecc.steps)) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk_nandc_attach_chip(struct nand_chip *nand)
+{
+ int ret;
+
+ switch (nand->ecc.mode) {
+ case NAND_ECC_HW_SYNDROME:
+ case NAND_ECC_HW:
+ ret = rk_nandc_hw_ecc_ctrl_init(nand);
+ if (ret)
+ return ret;
+ ret = rk_nandc_buffer_init(nand);
+ if (ret)
+ return -ENOMEM;
+ nand->ecc.read_page = rk_nandc_hw_syndrome_ecc_read_page;
+ nand->ecc.write_page = rk_nandc_hw_syndrome_ecc_write_page;
+ nand->ecc.read_oob = rk_nandc_hw_ecc_read_oob;
+ nand->ecc.write_oob = rk_nandc_hw_ecc_write_oob;
+ break;
+ case NAND_ECC_NONE:
+ case NAND_ECC_SOFT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rk_nandc_exec_op(struct nand_chip *nand,
+ const struct nand_operation *op,
+ bool check_only)
+{
+ int i;
+ unsigned int op_id;
+ const struct nand_op_instr *instr = NULL;
+
+ rk_nandc_select_chip(nand, op->cs);
+
+ if (check_only)
+ return 0;
+
+ for (op_id = 0; op_id < op->ninstrs; op_id++) {
+ instr = &op->instrs[op_id];
+
+ switch (instr->type) {
+ case NAND_OP_CMD_INSTR:
+ rk_nandc_write_cmd(nand, instr->ctx.cmd.opcode);
+ break;
+ case NAND_OP_ADDR_INSTR:
+ for (i = 0; i < instr->ctx.addr.naddrs; i++)
+ rk_nandc_write_addr(nand,
+ instr->ctx.addr.addrs[i]);
+ break;
+ case NAND_OP_DATA_IN_INSTR:
+ rk_nandc_read_buf(nand, instr->ctx.data.buf.in,
+ instr->ctx.data.len);
+ break;
+ case NAND_OP_DATA_OUT_INSTR:
+ rk_nandc_write_buf(nand, instr->ctx.data.buf.out,
+ instr->ctx.data.len);
+ break;
+ case NAND_OP_WAITRDY_INSTR:
+ rk_nandc_dev_ready(nand);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static const struct nand_controller_ops rk_nand_controller_ops = {
+ .attach_chip = rk_nandc_attach_chip,
+ .exec_op = rk_nandc_exec_op,
+};
+
+static int rk_nandc_chip_init(struct rk_nand_ctrl *ctrl,
+ struct device_node *np)
+{
+ struct rk_nand_chip *rk_nand;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ int nsels, ret, i;
+ u32 cs, val;
+
+ nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32));
+ if (nsels <= 0) {
+ dev_err(ctrl->dev, "missing/invalid reg property\n");
+ return -EINVAL;
+ }
+
+ rk_nand = devm_kzalloc(ctrl->dev, sizeof(*rk_nand), GFP_KERNEL);
+ if (!rk_nand)
+ return -ENOMEM;
+
+ rk_nand->idbresblknum = RK_NANDC_IDB_Res_Blk_Num;
+
+ for (i = 0; i < nsels; i++) {
+ ret = of_property_read_u32_index(np, "reg", i, &cs);
+ if (ret) {
+ dev_err(ctrl->dev,
+ "could not retrieve reg property: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (cs >= ctrl->cap->max_cs) {
+ dev_err(ctrl->dev,
+ "invalid reg value: %u (max CS = %d)\n",
+ cs,
+ ctrl->cap->max_cs);
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(cs, &ctrl->assigned_cs)) {
+ dev_err(ctrl->dev,
+ "CS %d already assigned\n",
+ cs);
+ return -EINVAL;
+ }
+
+ ctrl->cs[i] = cs;
+
+ /* only bank 0 used for boot rom blocks */
+ if (!of_property_read_u32_index(np, "rockchip,idb-res-blk-num",
+ i, &val)) {
+ if (!cs && val >= 2) {
+ rk_nand->idbresblknum = val;
+ } else {
+ dev_err(ctrl->dev,
+ "invalid idb-res-blk-num\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ nand = &rk_nand->nand;
+
+ nand_set_flash_node(nand, np);
+ nand_set_controller_data(nand, ctrl);
+
+ nand->controller = &ctrl->controller;
+ nand->controller->ops = &rk_nand_controller_ops;
+
+ nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+ nand->ecc.size = 1024;
+ nand->ecc.strength = 40;
+
+ nand->options = NAND_SKIP_BBTSCAN | NAND_NO_SUBPAGE_WRITE;
+
+ mtd = nand_to_mtd(nand);
+ mtd->dev.parent = ctrl->dev;
+ mtd->name = devm_kasprintf(ctrl->dev, GFP_KERNEL, "%s",
+ dev_name(ctrl->dev));
+
+ ret = nand_scan(nand, nsels);
+ if (ret)
+ return ret;
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ dev_err(ctrl->dev, "mtd device register failed: %d\n", ret);
+ nand_cleanup(nand);
+ return ret;
+ }
+
+ list_add_tail(&rk_nand->node, &ctrl->chips);
+
+ return 0;
+}
+
+static void rk_nandc_cleanup_chips(struct rk_nand_ctrl *ctrl)
+{
+ struct rk_nand_chip *entry, *temp;
+
+ list_for_each_entry_safe(entry, temp, &ctrl->chips, node) {
+ nand_release(&entry->nand);
+ list_del(&entry->node);
+ }
+}
+
+static int rk_nandc_chips_init(struct rk_nand_ctrl *ctrl)
+{
+ struct device_node *np = ctrl->dev->of_node;
+ struct device_node *nand_np;
+ int nchips;
+ int ret;
+
+ nchips = of_get_child_count(np);
+
+ if (nchips > ctrl->cap->max_cs) {
+ dev_err(ctrl->dev, "too many NAND chips: %d (max CS = %d)\n",
+ nchips,
+ ctrl->cap->max_cs);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node(np, nand_np) {
+ ret = rk_nandc_chip_init(ctrl, nand_np);
+ if (ret) {
+ rk_nandc_cleanup_chips(ctrl);
+ of_node_put(nand_np);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rk_nandc_probe(struct platform_device *pdev)
+{
+ struct rk_nand_ctrl *ctrl;
+ const struct rk_nandc_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -ENODEV;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->dev = dev;
+ ctrl->version = data->version;
+ ctrl->ofs = data->ofs;
+ ctrl->cap = data->cap;
+
+ ctrl->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->regs)) {
+ dev_err(dev, "ioremap failed\n");
+ return PTR_ERR(ctrl->regs);
+ }
+
+ ctrl->irq = platform_get_irq(pdev, 0);
+ if (ctrl->irq < 0) {
+ dev_err(dev, "get irq failed\n");
+ return ctrl->irq;
+ }
+
+ ctrl->hclk = devm_clk_get(dev, "hclk_nandc");
+ if (IS_ERR(ctrl->hclk)) {
+ dev_err(dev, "get hclk_nandc failed\n");
+ return PTR_ERR(ctrl->hclk);
+ }
+
+ ret = clk_prepare_enable(ctrl->hclk);
+ if (ret)
+ return ret;
+
+ ctrl->clk = devm_clk_get(dev, "clk_nandc");
+ if (!(IS_ERR(ctrl->clk))) {
+ clk_set_rate(ctrl->clk, 150 * 1000 * 1000);
+
+ ret = clk_prepare_enable(ctrl->clk);
+ if (ret)
+ goto err_disable_hclk;
+ } else {
+ dev_dbg(dev, "get clk_nandc failed\n");
+ }
+
+ writel_relaxed(0, ctrl->regs + ctrl->ofs->inten);
+ ret = devm_request_irq(dev, ctrl->irq, rk_nandc_interrupt,
+ 0, "nandc", ctrl);
+ if (ret)
+ goto err_disable_clk;
+
+ init_completion(&ctrl->complete);
+ nand_controller_init(&ctrl->controller);
+ INIT_LIST_HEAD(&ctrl->chips);
+
+ rk_nandc_init(ctrl);
+
+ ret = rk_nandc_chips_init(ctrl);
+ if (ret) {
+ dev_err(dev, "init nand chips failed\n");
+ goto err_disable_clk;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ return 0;
+
+err_disable_clk:
+ clk_disable_unprepare(ctrl->clk);
+err_disable_hclk:
+ clk_disable_unprepare(ctrl->hclk);
+
+ return ret;
+}
+
+static int rk_nandc_remove(struct platform_device *pdev)
+{
+ struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+ rk_nandc_cleanup_chips(ctrl);
+
+ clk_disable_unprepare(ctrl->clk);
+ clk_disable_unprepare(ctrl->hclk);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static void rk_nandc_shutdown(struct platform_device *pdev)
+{
+ struct rk_nand_ctrl *ctrl = platform_get_drvdata(pdev);
+
+ rk_nandc_cleanup_chips(ctrl);
+
+ clk_disable_unprepare(ctrl->clk);
+ clk_disable_unprepare(ctrl->hclk);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static const struct rk_nandc_data rk_nandc_V600_data = {
+ .version = VERSION_6,
+ .ofs = (void *)&rk_nandc_V6_reg_offset,
+ .cap = (void *)&rk_nandc_V600_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V622_data = {
+ .version = VERSION_6,
+ .ofs = (void *)&rk_nandc_V6_reg_offset,
+ .cap = (void *)&rk_nandc_V622_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V701_data = {
+ .version = VERSION_6,
+ .ofs = (void *)&rk_nandc_V6_reg_offset,
+ .cap = (void *)&rk_nandc_V701_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V801_data = {
+ .version = VERSION_6,
+ .ofs = (void *)&rk_nandc_V6_reg_offset,
+ .cap = (void *)&rk_nandc_V801_cap,
+};
+
+static const struct rk_nandc_data rk_nandc_V900_data = {
+ .version = VERSION_9,
+ .ofs = (void *)&rk_nandc_V9_reg_offset,
+ .cap = (void *)&rk_nandc_V900_cap,
+};
+
+static const struct of_device_id of_rk_nandc_match[] = {
+ {
+ .compatible = "rockchip,px30-nand-controller",
+ .data = &rk_nandc_V900_data,
+ },
+ {
+ .compatible = "rockchip,rk3066-nand-controller",
+ .data = &rk_nandc_V600_data,
+ },
+ {
+ .compatible = "rockchip,rk3228-nand-controller",
+ .data = &rk_nandc_V701_data,
+ },
+ {
+ .compatible = "rockchip,rk3288-nand-controller",
+ .data = &rk_nandc_V622_data,
+ },
+ {
+ .compatible = "rockchip,rk3308-nand-controller",
+ .data = &rk_nandc_V801_data,
+ },
+ {
+ .compatible = "rockchip,rk3368-nand-controller",
+ .data = &rk_nandc_V622_data,
+ },
+ {
+ .compatible = "rockchip,rv1108-nand-controller",
+ .data = &rk_nandc_V622_data,
+ },
+ { /* sentinel */ },
+};
+
+static struct platform_driver rk_nandc_driver = {
+ .probe = rk_nandc_probe,
+ .remove = rk_nandc_remove,
+ .shutdown = rk_nandc_shutdown,
+ .driver = {
+ .name = "rockchip-nand-controller",
+ .of_match_table = of_rk_nandc_match,
+ },
+};
+module_platform_driver(rk_nandc_driver);
+
+MODULE_LICENSE("GPL v2");
--
2.11.0
From: Dingqiang Lin <[email protected]>
Add nandc node for px30.
Signed-off-by: Dingqiang Lin <[email protected]>
Signed-off-by: Johan Jonker <[email protected]>
---
arch/arm64/boot/dts/rockchip/px30.dtsi | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/px30.dtsi b/arch/arm64/boot/dts/rockchip/px30.dtsi
index 8812b70f3..5560e5b35 100644
--- a/arch/arm64/boot/dts/rockchip/px30.dtsi
+++ b/arch/arm64/boot/dts/rockchip/px30.dtsi
@@ -865,6 +865,18 @@
status = "disabled";
};
+ nandc: nand-controller@ff3b0000 {
+ compatible = "rockchip,px30-nand-controller";
+ reg = <0x0 0xff3b0000 0x0 0x4000>;
+ interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru HCLK_NANDC>, <&cru SCLK_NANDC>;
+ clock-names = "hclk_nandc", "clk_nandc";
+ assigned-clocks = <&cru SCLK_NANDC>;
+ assigned-clock-parents = <&cru SCLK_NANDC_DIV50>;
+ power-domains = <&power PX30_PD_MMC_NAND>;
+ status = "disabled";
+ };
+
vopb: vop@ff460000 {
compatible = "rockchip,px30-vop-big";
reg = <0x0 0xff460000 0x0 0xefc>;
--
2.11.0
From: Dingqiang Lin <[email protected]>
Add nandc node for rk3308.
Signed-off-by: Dingqiang Lin <[email protected]>
Signed-off-by: Johan Jonker <[email protected]>
---
arch/arm64/boot/dts/rockchip/rk3308.dtsi | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
index 8bdc66c62..a6c98edfb 100644
--- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi
@@ -627,6 +627,15 @@
status = "disabled";
};
+ nandc: nand-controller@ff4b0000 {
+ compatible = "rockchip,rk3308-nand-controller";
+ reg = <0x0 0xff4b0000 0x0 0x4000>;
+ interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru HCLK_NANDC>, <&cru SCLK_NANDC>;
+ clock-names = "hclk_nandc", "clk_nandc";
+ status = "disabled";
+ };
+
cru: clock-controller@ff500000 {
compatible = "rockchip,rk3308-cru";
reg = <0x0 0xff500000 0x0 0x1000>;
--
2.11.0
Hi Johan,
Johan Jonker <[email protected]> wrote on Fri, 24 Jan 2020 17:29:51
+0100:
> DISCLAIMER: Use at your own risk.
> Status: For testing only!
>
> Version: V2
>
> Title: Enable RK3066 NANDC for MK808.
>
> The majority of Rockchip devices use a closed source FTL driver
> to reduce wear leveling. This patch serie proposes
> an experimental raw NAND controller driver for basic tasks
> in order to get the bindings and the nodes accepted for in the dts files.
>
> What does it do:
>
> On module load this driver will reserve its resources.
> After initialization the MTD framework will then try to detect
> the type and number of NAND chips. When all conditions are met,
> it registers it self as MTD device.
> This driver is then ready to receive user commands
> such as to read and write NAND pages.
>
> Test examples:
>
> # dd if=/dev/mtd0 of=dd.bin bs=8192 count=4
>
> # nanddump -a -l 32768 -f nanddump.bin /dev/mtd0
>
> Not tested:
>
> NANDC version 9.
> NAND raw write.
nandbiterrs -i /dev/mtd<x> to validate it works!
> RK3066 still has no support for Uboot.
> Any write command would interfere with data structures made by the boot loader.
>
> Etc.
>
> Problems:
>
> No bad block support. Most devices use a FTL bad block map with tags
> that must be located on specific page locations which is outside
> the scope of the raw MTD framework.
I don't understand this story of bad block map. Are you comparing with
a vendor kernel?
If vendors invent new ways to handle MTD blocks it's sad but they will
never be compatible with mainline. It's a fact. However for an upstream
version, I don't get if there is any real issue? The location of the
BBM is not related to your controller driver but depends on the NAND
chip and as you say below we know provide three possible positions in
a block.
What you refer as the FTL is the equivalent of UBI in Linux, which
indeed offers to the user a linear logical view of all the valid blocks
while physically the data is spread across all the available
eraseblocks.
>
> hynix_nand_init() add extra option NAND_BBM_LASTPAGE for H27UCG8T2ATR-BC.
>
> No partition support. A FTL driver will store at random locations and
> a linear user specific layout does not fit within
> the generic character of this basic driver.
>
> Driver assumes that IO pins are correctly set by the boot loader.
Which pins are you talking about? Are you missing a pinctrl driver?
>
> Fixed timing setting.
>
> RK3228A/RK3228B compatibility version 701 unknown
> RV1108 nand version unknown
>
> Etc.
>
> Todo:
>
> MLC ?
This is not related to your NAND controller driver neither.
Cheers,
Miquèl
Hi Miquel,
See below.
Btw, what is your impression of the other V2 'improvements'?
Thanks
[..]
>> Not tested:
>>
>> NANDC version 9.
>> NAND raw write.
>
> nandbiterrs -i /dev/mtd<x> to validate it works!
>
>> RK3066 still has no support for Uboot.
>> Any write command would interfere with data structures made by the
boot loader.
>>
>> Etc.
>>
>> Problems:
>>
>> No bad block support. Most devices use a FTL bad block map with tags
>> that must be located on specific page locations which is outside
>> the scope of the raw MTD framework.
>
> I don't understand this story of bad block map. Are you comparing with
> a vendor kernel?
Two separate things:
-Rockchip FTL has it's own FTL storage format.
The original factory BBM (marker) information is lost, so it stores
a BB (bad block) map/table at the (last erase block - n) and then downwards.
See source below. Not usable for raw MTD. It's the situation most user will
find on there NAND.
- For raw MTD to use it's own BB map in this stage the proposed solution
for raw MTD just didn't gave me something/results that I understand,
so I leave it out for now. For an empty NAND without Rockchip FTL this could
work as long as it stays out of our bootrom blocks.
It's fine that MTD is 'rigid', but not all Socs fit, so find solutions
that are maybe second best. MTD has most components already in place,
see what we can make out of it.
>
> If vendors invent new ways to handle MTD blocks it's sad but they will
> never be compatible with mainline. It's a fact. However for an upstream
> version, I don't get if there is any real issue? The location of the
> BBM is not related to your controller driver but depends on the NAND
> chip and as you say below we know provide three possible positions in
> a block.
>
Handling of MTD blocks remains the same AFAICT.
It transfers data and oob byte to MTD data structures.
Just find a way to tell MTD not mess with bootrom blocks.
Programming/reading in Uboot and Linux is something that Open Source
has to find a solution for.
> What you refer as the FTL is the equivalent of UBI in Linux, which
> indeed offers to the user a linear logical view of all the valid blocks
> while physically the data is spread across all the available
> eraseblocks.
When I refer to FTL it's to the Rockchip FTL.
In this current EXPERIMENTAL driver stage I just warn a potential
unaware user
of the things that are at play on that NAND. UBI might work fine, but
not now.
>
>>
>> hynix_nand_init() add extra option NAND_BBM_LASTPAGE for H27UCG8T2ATR-BC.
>>
Example:
To see what happens when a partition is placed over the bootrom erase
blocks.
From the previous log example it seems that it somehow does do a read
command
at the end of this partition. That what tests are for.
>> No partition support. A FTL driver will store at random locations and
>> a linear user specific layout does not fit within
>> the generic character of this basic driver.
>>
MTD partition support works fine. It's just the current content of my
RK3066 NAND
written with a Rockchip FTL loader that makes write tests not so smart
as I have
to reprogram then with USB. Chicken egg problem.
>> Driver assumes that IO pins are correctly set by the boot loader.
>
> Which pins are you talking about? Are you missing a pinctrl driver?
>
No, NAND share the same data IO pins as emmc.
Set direct in grfReg, not with pinctrl.
Every Rockchip grfReg is different, so leave it out for now.
For my Linux TEST driver not a issue as that is already done
by the loader.
Would you like this grf thing included in a Linux driver for every Soc?
>>
>> Fixed timing setting.
Mentioned for completeness.
>>
>> RK3228A/RK3228B compatibility version 701 unknown
>> RV1108 nand version unknown
Can Shawn Lin or someone else help here?
RV1108 TRM (manual) is still missing?
>>
[..]
Have fun!
/////////////////////
From rk3066a.dtsi:
SPDX-License-Identifier: (GPL-2.0+ OR MIT)
Copyright (c) 2013 MundoReader S.L.
emmc {
emmc_clk: emmc-clk {
rockchip,pins = <3 RK_PD7 2 &pcfg_pull_default>;
};
emmc_cmd: emmc-cmd {
rockchip,pins = <4 RK_PB1 2 &pcfg_pull_default>;
};
emmc_rst: emmc-rst {
rockchip,pins = <4 RK_PB2 2 &pcfg_pull_default>;
};
/*
* The data pins are shared between nandc and emmc and
* not accessible through pinctrl. Also they should've
* been already set correctly by firmware, as
* flash/emmc is the boot-device.
*/
};
/////////////////////
From pinctrl_rk3066.c:
SPDX-License-Identifier: GPL-2.0+
Copyright 2017 Paweł Jarosz
static void pinctrl_rk3066_nand_config(struct rk3066_grf *grf)
{
rk_clrsetreg(&grf->soc_con0,
EMMC_FLASH_SEL_MASK,
0 << EMMC_FLASH_SEL_SHIFT);
rk_clrsetreg(&grf->gpio3d_iomux,
GPIO3D7_MASK,
GPIO3D7_FLASH_DQS << GPIO3D7_SHIFT);
}
/////////////////////
For EMMC:
GRF_GPIO_IOMUX[3].GPIOD_IOMUX = ((0x3<<14)<<16)|(0x2<<14); // dqs
GRF_GPIO_IOMUX[4].GPIOB_IOMUX = ((0xf<<2)<<16)|(0xa<<2); // cmd,rstn
GRF_SOC_CON[0] = ((0x1<<11)<<16)|(0x1<<11); // emmc
data0-7,wp
/////////////////////
GRF_SOC_CON0 RK3066:
Bit Attr Reset Value Description
31:16 RW 0x0 write_enable bit 0~bit 15 write enable
11 RW 0x0 emmc_flash_sel emmc flash select used for iomux
IO_FLASH_DATA[7:0] , IO_FLASH_WP are
selected for emmc instead of flash
/////////////////////////////////////////////////////////////
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019, Johan Jonker <[email protected]>
*
* 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+
*/
struct nand_req {
uint32_t status;
uint32_t page_addr;
uint32_t *p_data;
uint16_t *p_spare;
uint32_t lpn;
uint32_t res2;
uint32_t res3;
uint32_t res4;
uint32_t res5;
};
struct tagBbtInfo {
uint16_t id;
uint16_t page;
uint16_t region;
uint16_t counter;
uint32_t index;
uint16_t blk_cnt[8];
uint32_t *map[8];
};
struct tag_sys_spare_buf {
uint16_t tag;
uint16_t id;
uint32_t index;
uint16_t region;
uint16_t bb_num;
uint32_t sys_blks_per_plane;
};
struct tagBbtInfo gBbtInfo;
struct nand_req req_sys;
uint32_t FtlBbmIsBadBlock(uint32_t lbn)
{
return (gBbtInfo.map[(uint16_t)(lbn / c_ftl_nand_blks_per_die)]
[lbn % c_ftl_nand_blks_per_die >> 5] >>
(lbn % c_ftl_nand_blks_per_die & 0x1F)) & 1;
}
int FtlLoadBbt(void)
{
struct tag_sys_spare_buf *p_spare;
uint32_t counter;
int i;
uint32_t index;
uint16_t page1;
uint32_t page2;
uint8_t *p_buf;
uint32_t *p_map1;
uint8_t *p_map2;
uint16_t region;
size_t size;
uint16_t sys_blks_per_plane;
uint32_t tmp;
FTL_DBG();
req_sys.p_data = p_sys_data_buf;
p_spare = (struct tag_sys_spare_buf *)p_sys_spare_buf;
req_sys.p_spare = p_sys_spare_buf;
FtlBbtMemInit();
for (i = (uint16_t)(c_ftl_nand_blks_per_die - 1);
c_ftl_nand_blks_per_die - 47 <= i;
i = (uint16_t)(i - 1)) {
req_sys.page_addr = i << 10;
FlashReadPages(&req_sys, 1u, 1u);
if (req_sys.status == NAND_STS_ERROR) {
++req_sys.page_addr;
FlashReadPages(&req_sys, 1u, 1u);
}
if (req_sys.status != NAND_STS_ERROR &&
p_spare->tag == ID_BBTB) {
index = p_spare->index;
gBbtInfo.id = i;
gBbtInfo.index = index;
gBbtInfo.region = p_spare->region;
break;
}
}
if (gBbtInfo.id == 0xFFFF)
return -1;
if (gBbtInfo.region != 0xFFFF) {
req_sys.page_addr = gBbtInfo.region << 10;
FlashReadPages(&req_sys, 1u, 1u);
if (req_sys.status != NAND_STS_ERROR &&
p_spare->tag == ID_BBTB &&
p_spare->index > gBbtInfo.index) {
gBbtInfo.index = p_spare->index;
region = p_spare->region;
gBbtInfo.id = gBbtInfo.region;
gBbtInfo.region = region;
}
}
page1 = FtlGetLastWrittenPage(gBbtInfo.id, 1u);
page2 = (uint16_t)page1;
gBbtInfo.page = page1 + 1;
while ((page2 & 0x80000000) == 0) {
req_sys.page_addr = page2 | (gBbtInfo.id << 10);
req_sys.p_data = p_sys_data_buf;
FlashReadPages(&req_sys, 1u, 1u);
if (req_sys.status != NAND_STS_ERROR &&
p_spare->tag == ID_BBTB) {
goto label_1;
}
page2 = (uint16_t)(page2 - 1);
}
FTL_ERR();
label_1:
sys_blks_per_plane = p_spare->sys_blks_per_plane;
gBbtInfo.counter = p_spare->bb_num;
if (sys_blks_per_plane != 0xFFFF &&
sys_blks_per_plane != c_ftl_nand_sys_blks_per_plane) {
tmp = (uint32_t)c_ftl_nand_blk_per_plane >> 2;
if (c_ftl_nand_sys_blks_per_plane < tmp &&
sys_blks_per_plane < tmp) {
FtlSysBlkNumInit(sys_blks_per_plane);
}
}
p_map1 = (uint32_t *)&gBbtInfo.blk_cnt[6];
counter = 0;
while (counter < c_ftl_nand_die_num) {
p_map2 = (uint8_t *)p_map1[1];
++p_map1;
size = 4 * c_ftl_nand_bbm_buf_size;
p_buf = (uint8_t *)req_sys.p_data + counter++ * size;
ftl_memcpy(p_map2, p_buf, size);
}
return 0;
}
void FtlBbmTblFlush(void)
{
struct tag_sys_spare_buf *p_spare;
uint32_t bb_num;
uint16_t counter1;
uint32_t counter2;
uint32_t die_counter;
uint16_t lbn1;
uint32_t lbn2;
uint32_t *p_map;
uint32_t sector;
uint8_t *tmp_p_map;
FTL_DBG();
die_counter = 0;
if (!g_flash_read_only_en) {
p_map = (uint32_t *)&gBbtInfo.blk_cnt[6];
req_sys.p_spare = p_sys_spare_buf;
req_sys.p_data = p_sys_data_buf;
ftl_memset(
p_sys_data_buf,
g_flash_read_only_en,
c_ftl_nand_byte_per_page);
while ((uint32_t)die_counter < c_ftl_nand_die_num) {
tmp_p_map = (uint8_t *)p_map[1];
++p_map;
sector = die_counter++
* c_ftl_nand_bbm_buf_size;
ftl_memcpy(
&req_sys.p_data[sector],
tmp_p_map,
4 * c_ftl_nand_bbm_buf_size);
}
p_spare = (struct tag_sys_spare_buf *)req_sys.p_spare;
counter1 = 0;
counter2 = 0;
ftl_memset(req_sys.p_spare, 0xFFu, 16u);
p_spare->tag = ID_BBTB;
p_spare->index = gBbtInfo.index;
p_spare->id = gBbtInfo.id;
p_spare->region = gBbtInfo.region;
p_spare->bb_num = gBbtInfo.counter;
p_spare->sys_blks_per_plane = c_ftl_nand_sys_blks_per_plane;
do {
while (1) {
req_sys.p_data = p_sys_data_buf;
bb_num = p_spare->bb_num;
req_sys.p_spare = p_sys_spare_buf;
req_sys.status = NAND_STS_OK;
req_sys.page_addr = gBbtInfo.page
| (gBbtInfo.id << 10);
FTL_INFO(
"FtlBbmTblFlush id=%x,page=%x,"
"previd=%x cnt=%d\n",
gBbtInfo.id,
gBbtInfo.page,
gBbtInfo.region,
bb_num);
if (gBbtInfo.page >=
c_ftl_nand_page_per_slc_blk - 1) {
lbn1 = gBbtInfo.id;
gBbtInfo.page = 0;
p_spare->index = ++gBbtInfo.index;
p_spare->region = lbn1;
lbn2 = gBbtInfo.region;
gBbtInfo.region = lbn1;
gBbtInfo.id = lbn2;
req_sys.page_addr = lbn2 << 10;
req_erase->page_addr = lbn2 << 10;
FlashEraseBlocks(
req_erase,
1u,
1u);
}
FlashProgPages(&req_sys, 1u, 1u, 1u);
++gBbtInfo.page;
if (req_sys.status == NAND_STS_ERROR)
break;
if (++counter2 != 1 &&
req_sys.status != NAND_STS_REFRESH) {
return;
}
}
++counter1;
FTL_INFO(
"FtlBbmTblFlush error:%x\n",
req_sys.page_addr);
} while (counter1 <= 3u);
FTL_INFO(
"FtlBbmTblFlush error = %x error count = %d\n",
req_sys.page_addr,
counter1);
g_flash_read_only_en = 1;
}
}