[PATCH 22/24] mtd: rawnand: sunxi: add support for H6/H616 nand controller
Richard GENOUD
richard.genoud at bootlin.com
Mon Oct 20 16:05:21 CEST 2025
Hi,
Le 16/10/2025 à 16:27, Richard Genoud a écrit :
> Introduce H6/H616 NAND controller support for U-Boot
>
> The H616 NAND controller has the same base as A10/A23, with some
> differences:
> - MDMA is based on chained buffers
> - its ECC supports up to 80bit per 1024bytes
> - some registers layouts are a bit different, mainly due do the stronger
> ECC.
> - it uses USER_DATA_LEN registers along USER_DATA registers.
> - it needs a specific clock for ECC and MBUS.
>
> Introduce the basic support, with ECC and scrambling, but without
> DMA/MDMA.
>
> Tested on Whatsminer H616 board (with and without scrambling, ECC)
>
> Signed-off-by: Richard Genoud <richard.genoud at bootlin.com>
> ---
> drivers/mtd/nand/raw/Kconfig | 3 +-
> drivers/mtd/nand/raw/sunxi_nand.c | 112 ++++++++++++++++++++++++++++--
> drivers/mtd/nand/raw/sunxi_nand.h | 32 ++++++++-
> 3 files changed, 139 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
> index 754b99bf3eb6..e4c4d9bcbf63 100644
> --- a/drivers/mtd/nand/raw/Kconfig
> +++ b/drivers/mtd/nand/raw/Kconfig
> @@ -467,7 +467,8 @@ config NAND_SANDBOX
> config NAND_SUNXI
> bool "Support for NAND on Allwinner SoCs"
> default ARCH_SUNXI
> - depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I
> + depends on MACH_SUN4I || MACH_SUN5I || MACH_SUN7I || MACH_SUN8I \
> + || MACH_SUN50I_H616
Hum, it seems I forgot to add MACH_SUN50I_H6 here.
> select SYS_NAND_SELF_INIT
> select SYS_NAND_U_BOOT_LOCATIONS
> select SPL_NAND_SUPPORT
> diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
> index 58c895095ce9..c9133cdc8844 100644
> --- a/drivers/mtd/nand/raw/sunxi_nand.c
> +++ b/drivers/mtd/nand/raw/sunxi_nand.c
> @@ -170,8 +170,14 @@ static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_hw_control *ctrl)
>
> static void sunxi_nfc_set_clk_rate(unsigned long hz)
> {
> +#if defined(CONFIG_MACH_SUN50I_H616) || defined(CONFIG_MACH_SUN50I_H6)
> + void * const ccm = (void *)SUNXI_CCM_BASE;
> + void * const nand0_clk_cfg = ccm + CCU_NAND0_CLK_CFG;
> +#else
> struct sunxi_ccm_reg *const ccm =
> (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
> + u32 *nand0_clk_cfg = &ccm->nand0_clk_cfg;
> +#endif
> int div_m, div_n;
>
> div_m = (clock_get_pll6() + hz - 1) / hz;
> @@ -186,8 +192,16 @@ static void sunxi_nfc_set_clk_rate(unsigned long hz)
> /* config mod clock */
> writel(CCM_NAND_CTRL_ENABLE | CCM_NAND_CTRL_PLL6 |
> CCM_NAND_CTRL_N(div_n) | CCM_NAND_CTRL_M(div_m),
> - &ccm->nand0_clk_cfg);
> + nand0_clk_cfg);
>
> +#if defined(CONFIG_MACH_SUN50I_H616) || defined(CONFIG_MACH_SUN50I_H6)
> + setbits_le32(ccm + CCU_H6_NAND_GATE_RESET,
> + (1 << GATE_SHIFT) | (1 << RESET_SHIFT));
> + setbits_le32(ccm + CCU_H6_MBUS_GATE, (1 << MBUS_GATE_OFFSET_NAND));
> + writel(CCM_NAND_CTRL_ENABLE | CCM_NAND_CTRL_PLL6 |
> + CCM_NAND_CTRL_N(div_n) | CCM_NAND_CTRL_M(div_m),
> + ccm + CCU_NAND1_CLK_CFG);
> +#else
> /* gate on nand clock */
> setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_NAND0));
> #ifdef CONFIG_MACH_SUN9I
> @@ -195,6 +209,7 @@ static void sunxi_nfc_set_clk_rate(unsigned long hz)
> #else
> setbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_DMA));
> #endif
> +#endif
> }
>
> static int sunxi_nfc_wait_int(struct sunxi_nfc *nfc, u32 flags,
> @@ -689,6 +704,53 @@ static inline void sunxi_nfc_user_data_to_buf(u32 user_data, u8 *buf)
> buf[3] = user_data >> 24;
> }
>
> +/*
> + * On H6/H616 the user_data length has to be set in specific registers
> + * before writing.
> + */
> +static void sunxi_nfc_reset_user_data_len(struct sunxi_nfc *nfc)
> +{
> + int loop_step = NFC_REG_USER_DATA_LEN_CAPACITY;
> +
> + /* not all SoCs have this register */
> + if (!nfc->caps->reg_user_data_len)
> + return;
> +
> + for (int i = 0; i < nfc->caps->max_ecc_steps; i += loop_step)
> + writel(0, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, i));
> +}
> +
> +static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
> + int len, int step)
> +{
> + bool found = false;
> + u32 val;
> + int i;
> +
> + /* not all SoCs have this register */
> + if (!nfc->caps->reg_user_data_len)
> + return;
> +
> + for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
> + if (len == nfc->caps->user_data_len_tab[i]) {
> + found = true;
> + break;
> + }
> + }
> +
> + if (!found) {
> + dev_warn(nfc->dev,
> + "Unsupported length for user data reg: %d\n", len);
> + return;
> + }
> +
> + val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
> +
> + val &= ~NFC_USER_DATA_LEN_MSK(step);
> + val |= field_prep(NFC_USER_DATA_LEN_MSK(step), i);
> + writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
> +}
> +
> static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
> u8 *data, int data_off,
> u8 *oob, int oob_off,
> @@ -716,6 +778,9 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct mtd_info *mtd,
> if (ret)
> return ret;
>
> + sunxi_nfc_reset_user_data_len(nfc);
> + sunxi_nfc_set_user_data_len(nfc, 4, 0);
> +
> sunxi_nfc_randomizer_enable(mtd);
> writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
> nfc->regs + NFC_REG_CMD);
> @@ -856,6 +921,9 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct mtd_info *mtd,
> if (ret)
> return ret;
>
> + sunxi_nfc_reset_user_data_len(nfc);
> + sunxi_nfc_set_user_data_len(nfc, 4, 0);
> +
> sunxi_nfc_randomizer_enable(mtd);
> writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
> NFC_ACCESS_DIR | NFC_ECC_OP,
> @@ -1276,7 +1344,6 @@ static int sunxi_nand_chip_init_timings(struct sunxi_nfc *nfc,
> static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
> struct nand_ecc_ctrl *ecc)
> {
> - static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
> struct nand_chip *nand = mtd_to_nand(mtd);
> struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
> struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
> @@ -1303,12 +1370,12 @@ static int sunxi_nand_hw_common_ecc_ctrl_init(struct mtd_info *mtd,
>
> /* Add ECC info retrieval from DT */
> for (i = 0; i < nfc->caps->nstrengths; i++) {
> - if (ecc->strength <= strengths[i]) {
> + if (ecc->strength <= nfc->caps->ecc_strengths[i]) {
> /*
> * Update ecc->strength value with the actual strength
> * that will be used by the ECC engine.
> */
> - ecc->strength = strengths[i];
> + ecc->strength = nfc->caps->ecc_strengths[i];
> break;
> }
> }
> @@ -1722,9 +1789,22 @@ static int sunxi_nand_probe(struct udevice *dev)
> return 0;
> }
>
> +static const u8 sunxi_ecc_strengths_a10[] = {
> + 16, 24, 28, 32, 40, 48, 56, 60, 64
> +};
> +
> +static const u8 sunxi_ecc_strengths_h6[] = {
> + 16, 24, 28, 32, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80
> +};
> +
> +static const u8 sunxi_user_data_len_h6[] = {
> + 0, 4, 8, 12, 16, 20, 24, 28, 32
> +};
> +
> static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
> .has_ecc_block_512 = true,
> - .nstrengths = 9,
> + .nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
> + .ecc_strengths = sunxi_ecc_strengths_a10,
> .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
> .reg_user_data = NFC_REG_A10_USER_DATA,
> .reg_pat_found = NFC_REG_ECC_ST,
> @@ -1733,6 +1813,24 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
> .pat_found_mask = GENMASK(31, 16),
> .ecc_mode_mask = GENMASK(15, 12),
> .random_en_mask = BIT(9),
> + .max_ecc_steps = 16,
> +};
> +
> +static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
> + .nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_h6),
> + .ecc_strengths = sunxi_ecc_strengths_h6,
> + .reg_ecc_err_cnt = NFC_REG_H6_ECC_ERR_CNT,
> + .reg_user_data = NFC_REG_H6_USER_DATA,
> + .reg_user_data_len = NFC_REG_H6_USER_DATA_LEN,
> + .reg_pat_found = NFC_REG_H6_PAT_FOUND,
> + .reg_spare_area = NFC_REG_H6_SPARE_AREA,
> + .reg_pat_id = NFC_REG_H6_PAT_ID,
> + .pat_found_mask = GENMASK(31, 0),
> + .ecc_mode_mask = GENMASK(15, 8),
> + .random_en_mask = BIT(5),
> + .user_data_len_tab = sunxi_user_data_len_h6,
> + .nuser_data_tab = ARRAY_SIZE(sunxi_user_data_len_h6),
> + .max_ecc_steps = 32,
> };
>
> static const struct udevice_id sunxi_nand_ids[] = {
> @@ -1740,6 +1838,10 @@ static const struct udevice_id sunxi_nand_ids[] = {
> .compatible = "allwinner,sun4i-a10-nand",
> .data = (unsigned long)&sunxi_nfc_a10_caps,
> },
> + {
> + .compatible = "allwinner,sun50i-h616-nand-controller",
> + .data = (unsigned long)&sunxi_nfc_h616_caps,
> + },
> { }
> };
>
> diff --git a/drivers/mtd/nand/raw/sunxi_nand.h b/drivers/mtd/nand/raw/sunxi_nand.h
> index 52200468d343..966b743e2613 100644
> --- a/drivers/mtd/nand/raw/sunxi_nand.h
> +++ b/drivers/mtd/nand/raw/sunxi_nand.h
> @@ -44,15 +44,26 @@
> #define NFC_REG_IO_DATA 0x0030
> #define NFC_REG_ECC_CTL 0x0034
> #define NFC_REG_ECC_ST 0x0038
> -#define NFC_REG_DEBUG 0x003C
> +#define NFC_REG_H6_PAT_FOUND 0x003C
> #define NFC_REG_A10_ECC_ERR_CNT 0x0040
> +#define NFC_REG_H6_ECC_ERR_CNT 0x0050
> #define NFC_REG_ECC_ERR_CNT(nfc, x) (((nfc)->caps->reg_ecc_err_cnt + (x)) & ~0x3)
> #define NFC_REG_A10_USER_DATA 0x0050
> +#define NFC_REG_H6_USER_DATA 0x0080
> +#define NFC_REG_H6_USER_DATA_LEN 0x0070
> #define NFC_REG_USER_DATA(nfc, x) ((nfc)->caps->reg_user_data + ((x) * 4))
> +
> +/* A USER_DATA_LEN register can hold the length of 8 USER_DATA registers */
> +#define NFC_REG_USER_DATA_LEN_CAPACITY 8
> +#define NFC_REG_USER_DATA_LEN(nfc, step) \
> + ((nfc)->caps->reg_user_data_len + \
> + ((step) / NFC_REG_USER_DATA_LEN_CAPACITY) * 4)
> #define NFC_REG_SPARE_AREA(nfc) ((nfc)->caps->reg_spare_area)
> #define NFC_REG_A10_SPARE_AREA 0x00A0
> -#define NFC_REG_PAT_ID(nfc) ((nfc)->caps->reg_pat_id)
> +#define NFC_REG_H6_SPARE_AREA 0x0114
> +#define NFC_REG_PAT_ID(nfc) ((nfc)->caps->reg_pat_id)
> #define NFC_REG_A10_PAT_ID 0x00A4
> +#define NFC_REG_H6_PAT_ID 0x0118
> #define NFC_RAM0_BASE 0x0400
> #define NFC_RAM1_BASE 0x0800
>
> @@ -162,6 +173,9 @@
>
> #define NFC_ECC_ERR_CNT(b, x) (((x) >> ((b) * 8)) & 0xff)
>
> +#define NFC_USER_DATA_LEN_MSK(step) \
> + (0xf << (((step) % NFC_REG_USER_DATA_LEN_CAPACITY) * 4))
> +
> #define NFC_DEFAULT_TIMEOUT_MS 1000
>
> #define NFC_SRAM_SIZE 1024
> @@ -174,8 +188,10 @@
> *
> * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chuncks
> * @nstrengths: Number of element of ECC strengths array
> + * @ecc_strengths: available ECC strengths array
> * @reg_ecc_err_cnt: ECC error counter register
> * @reg_user_data: User data register
> + * @reg_user_data_len: User data length register
> * @reg_spare_area: Spare Area Register
> * @reg_pat_id: Pattern ID Register
> * @reg_pat_found: Data Pattern Status Register
> @@ -183,12 +199,21 @@
> * @pat_found_mask: ECC_PAT_FOUND mask in NFC_REG_PAT_FOUND register
> * @ecc_mode_mask: ECC_MODE mask in NFC_ECC_CTL register
> * @random_en_mask: RANDOM_EN mask in NFC_ECC_CTL register
> + * @user_data_len_tab: Table of lenghts supported by USER_DATA_LEN register
> + * The table index is the value to set in NFC_USER_DATA_LEN
> + * registers, and the corresponding value is the number of
> + * bytes to write
> + * @nuser_data_tab: Size of @user_data_len_tab
> + * @max_ecc_steps: Maximum supported steps for ECC, this is also the
> + * number of user data registers
> */
> struct sunxi_nfc_caps {
> bool has_ecc_block_512;
> unsigned int nstrengths;
> + const u8 *ecc_strengths;
> unsigned int reg_ecc_err_cnt;
> unsigned int reg_user_data;
> + unsigned int reg_user_data_len;
> unsigned int reg_spare_area;
> unsigned int reg_pat_id;
> unsigned int reg_pat_found;
> @@ -196,6 +221,9 @@ struct sunxi_nfc_caps {
> unsigned int ecc_err_mask;
> unsigned int ecc_mode_mask;
> unsigned int random_en_mask;
> + const u8 *user_data_len_tab;
> + unsigned int nuser_data_tab;
> + unsigned int max_ecc_steps;
> };
>
> #endif
--
Richard Genoud, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
More information about the U-Boot
mailing list