aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-04-14 12:26:42 -0600
committerTom Rini <trini@konsulko.com>2024-04-14 12:26:42 -0600
commitc0268f3d572967740ed97b7e6a802989cc3bb5f4 (patch)
tree87eaffae6289e1ad234702b8ed8fda2d04e5e539
parent266603d8c39cf4d194e2cfe8d86d870590e150e0 (diff)
parent248fc16055858c2028a381bb59e12354c4ae19ea (diff)
downloadu-boot-WIP/14Apr2024.zip
u-boot-WIP/14Apr2024.tar.gz
u-boot-WIP/14Apr2024.tar.bz2
Merge tag 'u-boot-nand-20240414' of https://source.denx.de/u-boot/custodians/u-boot-nand-flashWIP/14Apr2024
The first patch is by Weizhao Ouyang and avoids sf probe crashes. The second patch is by Arseniy Krasnov and adds basic support for Amlogic Meson NAND controller on AXG. The following four patches are by Alexander Dahl and apply some fixes to drivers/mtd/nand/raw/ and port some changes applied in Linux. The following patch is by Bruce Suen and adds support for XTX SPINAND. Finally, the last patch is again by Arseniy Krasnov and adds access to OTP region, supporting info, dump, write and lock operations.
-rw-r--r--cmd/Kconfig7
-rw-r--r--cmd/mtd.c234
-rw-r--r--cmd/sf.c5
-rw-r--r--drivers/mtd/nand/raw/Kconfig115
-rw-r--r--drivers/mtd/nand/raw/Makefile1
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c4
-rw-r--r--drivers/mtd/nand/raw/meson_nand.c1248
-rw-r--r--drivers/mtd/nand/raw/nand_base.c6
-rw-r--r--drivers/mtd/nand/spi/Makefile2
-rw-r--r--drivers/mtd/nand/spi/core.c1
-rw-r--r--drivers/mtd/nand/spi/xtx.c266
-rw-r--r--include/linux/mtd/rawnand.h7
-rw-r--r--include/linux/mtd/spinand.h1
13 files changed, 1835 insertions, 62 deletions
diff --git a/cmd/Kconfig b/cmd/Kconfig
index f9f2716..8eeb99e 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1361,6 +1361,13 @@ config CMD_MTD
help
MTD commands support.
+config CMD_MTD_OTP
+ bool "mtd otp"
+ depends on CMD_MTD
+ select HEXDUMP
+ help
+ MTD commands for OTP access.
+
config CMD_MUX
bool "mux"
depends on MULTIPLEXER
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 9083a68..9189f45 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -11,6 +11,9 @@
#include <command.h>
#include <common.h>
#include <console.h>
+#if CONFIG_IS_ENABLED(CMD_MTD_OTP)
+#include <hexdump.h>
+#endif
#include <malloc.h>
#include <mapmem.h>
#include <mtd.h>
@@ -202,6 +205,221 @@ static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op)
return true;
}
+#if CONFIG_IS_ENABLED(CMD_MTD_OTP)
+static int do_mtd_otp_read(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct mtd_info *mtd;
+ size_t retlen;
+ off_t from;
+ size_t len;
+ bool user;
+ int ret;
+ u8 *buf;
+
+ if (argc != 5)
+ return CMD_RET_USAGE;
+
+ if (!strcmp(argv[2], "u"))
+ user = true;
+ else if (!strcmp(argv[2], "f"))
+ user = false;
+ else
+ return CMD_RET_USAGE;
+
+ mtd = get_mtd_by_name(argv[1]);
+ if (IS_ERR_OR_NULL(mtd))
+ return CMD_RET_FAILURE;
+
+ from = simple_strtoul(argv[3], NULL, 0);
+ len = simple_strtoul(argv[4], NULL, 0);
+
+ ret = CMD_RET_FAILURE;
+
+ buf = malloc(len);
+ if (!buf)
+ goto put_mtd;
+
+ printf("Reading %s OTP from 0x%lx, %zu bytes\n",
+ user ? "user" : "factory", from, len);
+
+ if (user)
+ ret = mtd_read_user_prot_reg(mtd, from, len, &retlen, buf);
+ else
+ ret = mtd_read_fact_prot_reg(mtd, from, len, &retlen, buf);
+ if (ret) {
+ free(buf);
+ pr_err("OTP read failed: %d\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto put_mtd;
+ }
+
+ if (retlen != len)
+ pr_err("OTP read returns %zu, but %zu expected\n",
+ retlen, len);
+
+ print_hex_dump("", 0, 16, 1, buf, retlen, true);
+
+ free(buf);
+
+ ret = CMD_RET_SUCCESS;
+
+put_mtd:
+ put_mtd_device(mtd);
+
+ return ret;
+}
+
+static int do_mtd_otp_lock(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct mtd_info *mtd;
+ off_t from;
+ size_t len;
+ int ret;
+
+ if (argc != 4)
+ return CMD_RET_USAGE;
+
+ mtd = get_mtd_by_name(argv[1]);
+ if (IS_ERR_OR_NULL(mtd))
+ return CMD_RET_FAILURE;
+
+ from = simple_strtoul(argv[2], NULL, 0);
+ len = simple_strtoul(argv[3], NULL, 0);
+
+ ret = mtd_lock_user_prot_reg(mtd, from, len);
+ if (ret) {
+ pr_err("OTP lock failed: %d\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto put_mtd;
+ }
+
+ ret = CMD_RET_SUCCESS;
+
+put_mtd:
+ put_mtd_device(mtd);
+
+ return ret;
+}
+
+static int do_mtd_otp_write(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct mtd_info *mtd;
+ size_t retlen;
+ size_t binlen;
+ u8 *binbuf;
+ off_t from;
+ int ret;
+
+ if (argc != 4)
+ return CMD_RET_USAGE;
+
+ mtd = get_mtd_by_name(argv[1]);
+ if (IS_ERR_OR_NULL(mtd))
+ return CMD_RET_FAILURE;
+
+ from = simple_strtoul(argv[2], NULL, 0);
+ binlen = strlen(argv[3]) / 2;
+
+ ret = CMD_RET_FAILURE;
+ binbuf = malloc(binlen);
+ if (!binbuf)
+ goto put_mtd;
+
+ hex2bin(binbuf, argv[3], binlen);
+
+ printf("Will write:\n");
+
+ print_hex_dump("", 0, 16, 1, binbuf, binlen, true);
+
+ printf("to 0x%lx\n", from);
+
+ printf("Continue (y/n)?\n");
+
+ if (confirm_yesno() != 1) {
+ pr_err("OTP write canceled\n");
+ ret = CMD_RET_SUCCESS;
+ goto put_mtd;
+ }
+
+ ret = mtd_write_user_prot_reg(mtd, from, binlen, &retlen, binbuf);
+ if (ret) {
+ pr_err("OTP write failed: %d\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto put_mtd;
+ }
+
+ if (retlen != binlen)
+ pr_err("OTP write returns %zu, but %zu expected\n",
+ retlen, binlen);
+
+ ret = CMD_RET_SUCCESS;
+
+put_mtd:
+ free(binbuf);
+ put_mtd_device(mtd);
+
+ return ret;
+}
+
+static int do_mtd_otp_info(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct otp_info otp_info;
+ struct mtd_info *mtd;
+ size_t retlen;
+ bool user;
+ int ret;
+
+ if (argc != 3)
+ return CMD_RET_USAGE;
+
+ if (!strcmp(argv[2], "u"))
+ user = true;
+ else if (!strcmp(argv[2], "f"))
+ user = false;
+ else
+ return CMD_RET_USAGE;
+
+ mtd = get_mtd_by_name(argv[1]);
+ if (IS_ERR_OR_NULL(mtd))
+ return CMD_RET_FAILURE;
+
+ if (user)
+ ret = mtd_get_user_prot_info(mtd, sizeof(otp_info), &retlen,
+ &otp_info);
+ else
+ ret = mtd_get_fact_prot_info(mtd, sizeof(otp_info), &retlen,
+ &otp_info);
+ if (ret) {
+ pr_err("OTP info failed: %d\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto put_mtd;
+ }
+
+ if (retlen != sizeof(otp_info)) {
+ pr_err("OTP info returns %zu, but %zu expected\n",
+ retlen, sizeof(otp_info));
+ ret = CMD_RET_FAILURE;
+ goto put_mtd;
+ }
+
+ printf("%s OTP region info:\n", user ? "User" : "Factory");
+ printf("\tstart: %u\n", otp_info.start);
+ printf("\tlength: %u\n", otp_info.length);
+ printf("\tlocked: %u\n", otp_info.locked);
+
+ ret = CMD_RET_SUCCESS;
+
+put_mtd:
+ put_mtd_device(mtd);
+
+ return ret;
+}
+#endif
+
static int do_mtd_list(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@@ -551,6 +769,12 @@ U_BOOT_LONGHELP(mtd,
"\n"
"Specific functions:\n"
"mtd bad <name>\n"
+#if CONFIG_IS_ENABLED(CMD_MTD_OTP)
+ "mtd otpread <name> [u|f] <off> <size>\n"
+ "mtd otpwrite <name> <off> <hex string>\n"
+ "mtd otplock <name> <off> <size>\n"
+ "mtd otpinfo <name> [u|f]\n"
+#endif
"\n"
"With:\n"
"\t<name>: NAND partition/chip name (or corresponding DM device name or OF path)\n"
@@ -561,10 +785,20 @@ U_BOOT_LONGHELP(mtd,
"\t<size>: length of the operation in bytes (default: the entire device)\n"
"\t\t* must be a multiple of a block for erase\n"
"\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n"
+#if CONFIG_IS_ENABLED(CMD_MTD_OTP)
+ "\t<hex string>: hex string without '0x' and spaces. Example: ABCD1234\n"
+ "\t[u|f]: user or factory OTP region\n"
+#endif
"\n"
"The .dontskipff option forces writing empty pages, don't use it if unsure.\n");
U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
+#if CONFIG_IS_ENABLED(CMD_MTD_OTP)
+ U_BOOT_SUBCMD_MKENT(otpread, 5, 1, do_mtd_otp_read),
+ U_BOOT_SUBCMD_MKENT(otpwrite, 4, 1, do_mtd_otp_write),
+ U_BOOT_SUBCMD_MKENT(otplock, 4, 1, do_mtd_otp_lock),
+ U_BOOT_SUBCMD_MKENT(otpinfo, 3, 1, do_mtd_otp_info),
+#endif
U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list),
U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io,
mtd_name_complete),
diff --git a/cmd/sf.c b/cmd/sf.c
index 730996c..e386689 100644
--- a/cmd/sf.c
+++ b/cmd/sf.c
@@ -135,8 +135,9 @@ static int do_spi_flash_probe(int argc, char *const argv[])
}
flash = NULL;
if (use_dt) {
- spi_flash_probe_bus_cs(bus, cs, &new);
- flash = dev_get_uclass_priv(new);
+ ret = spi_flash_probe_bus_cs(bus, cs, &new);
+ if (!ret)
+ flash = dev_get_uclass_priv(new);
} else {
flash = spi_flash_probe(bus, cs, speed, mode);
}
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index bb9994b..9f3f126 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -1,6 +1,6 @@
-
menuconfig MTD_RAW_NAND
bool "Raw NAND Device Support"
+
if MTD_RAW_NAND
config SYS_NAND_SELF_INIT
@@ -49,12 +49,12 @@ config SYS_NAND_NO_SUBPAGE_WRITE
depends on NAND_ARASAN || NAND_DAVINCI || NAND_KIRKWOOD
config DM_NAND_ATMEL
- bool "Support Atmel NAND controller with DM support"
- select SYS_NAND_SELF_INIT
- imply SYS_NAND_USE_FLASH_BBT
- help
- Enable this driver for NAND flash platforms using an Atmel NAND
- controller.
+ bool "Support Atmel NAND controller with DM support"
+ select SYS_NAND_SELF_INIT
+ imply SYS_NAND_USE_FLASH_BBT
+ help
+ Enable this driver for NAND flash platforms using an Atmel NAND
+ controller.
config NAND_ATMEL
bool "Support Atmel NAND controller"
@@ -133,35 +133,35 @@ config NAND_BRCMNAND_6753
Enable support for broadcom nand driver on bcm6753.
config NAND_BRCMNAND_68360
- bool "Support Broadcom NAND controller on bcm68360"
- depends on NAND_BRCMNAND && BCM6856
- help
- Enable support for broadcom nand driver on bcm68360.
+ bool "Support Broadcom NAND controller on bcm68360"
+ depends on NAND_BRCMNAND && BCM6856
+ help
+ Enable support for broadcom nand driver on bcm68360.
config NAND_BRCMNAND_6838
- bool "Support Broadcom NAND controller on bcm6838"
- depends on NAND_BRCMNAND && ARCH_BMIPS && SOC_BMIPS_BCM6838
- help
- Enable support for broadcom nand driver on bcm6838.
+ bool "Support Broadcom NAND controller on bcm6838"
+ depends on NAND_BRCMNAND && ARCH_BMIPS && SOC_BMIPS_BCM6838
+ help
+ Enable support for broadcom nand driver on bcm6838.
config NAND_BRCMNAND_6858
- bool "Support Broadcom NAND controller on bcm6858"
- depends on NAND_BRCMNAND && BCM6858
- help
- Enable support for broadcom nand driver on bcm6858.
+ bool "Support Broadcom NAND controller on bcm6858"
+ depends on NAND_BRCMNAND && BCM6858
+ help
+ Enable support for broadcom nand driver on bcm6858.
config NAND_BRCMNAND_63158
- bool "Support Broadcom NAND controller on bcm63158"
- depends on NAND_BRCMNAND && BCM63158
- help
- Enable support for broadcom nand driver on bcm63158.
+ bool "Support Broadcom NAND controller on bcm63158"
+ depends on NAND_BRCMNAND && BCM63158
+ help
+ Enable support for broadcom nand driver on bcm63158.
config NAND_BRCMNAND_IPROC
- bool "Support Broadcom NAND controller on the iproc family"
- depends on NAND_BRCMNAND
- help
- Enable support for broadcom nand driver on the Broadcom
- iproc family such as Northstar (BCM5301x, BCM4708...)
+ bool "Support Broadcom NAND controller on the iproc family"
+ depends on NAND_BRCMNAND
+ help
+ Enable support for broadcom nand driver on the Broadcom
+ iproc family such as Northstar (BCM5301x, BCM4708...)
config NAND_DAVINCI
bool "Support TI Davinci NAND controller"
@@ -413,10 +413,10 @@ config NAND_VF610_NFC
if NAND_VF610_NFC
config NAND_VF610_NFC_DT
- bool "Support Vybrid's vf610 NAND controller as a DT device"
- depends on OF_CONTROL && DM_MTD
- help
- Enable the driver for Vybrid's vf610 NAND flash on platforms
+ bool "Support Vybrid's vf610 NAND controller as a DT device"
+ depends on OF_CONTROL && DM_MTD
+ help
+ Enable the driver for Vybrid's vf610 NAND flash on platforms
using device tree.
choice
@@ -472,11 +472,11 @@ config NAND_SUNXI
select SPL_NAND_SUPPORT
select SPL_SYS_NAND_SELF_INIT
imply CMD_NAND
- ---help---
- Enable support for NAND. This option enables the standard and
- SPL drivers.
- The SPL driver only supports reading from the NAND using DMA
- transfers.
+ help
+ Enable support for NAND. This option enables the standard and
+ SPL drivers.
+ The SPL driver only supports reading from the NAND using DMA
+ transfers.
if NAND_SUNXI
@@ -504,6 +504,15 @@ config NAND_ARASAN
controller. This uses the hardware ECC for read and
write operations.
+config NAND_MESON
+ bool "Meson NAND support"
+ select SYS_NAND_SELF_INIT
+ depends on DM_MTD && ARCH_MESON
+ imply CMD_NAND
+ help
+ This enables Nand driver support for Meson raw NAND flash
+ controller.
+
config NAND_MXC
bool "MXC NAND support"
depends on CPU_ARM926EJS || CPU_ARM1136 || MX5
@@ -577,16 +586,16 @@ config NAND_OCTEONTX
select SYS_NAND_SELF_INIT
imply CMD_NAND
help
- This enables Nand flash controller hardware found on the OcteonTX
- processors.
+ This enables Nand flash controller hardware found on the OcteonTX
+ processors.
config NAND_OCTEONTX_HW_ECC
bool "Support Hardware ECC for OcteonTX NAND controller"
depends on NAND_OCTEONTX
default y
help
- This enables Hardware BCH engine found on the OcteonTX processors to
- support ECC for NAND flash controller.
+ This enables Hardware BCH engine found on the OcteonTX processors to
+ support ECC for NAND flash controller.
config NAND_STM32_FMC2
bool "Support for NAND controller on STM32MP SoCs"
@@ -751,37 +760,37 @@ config SYS_NAND_BAD_BLOCK_POS
config SYS_NAND_U_BOOT_LOCATIONS
bool "Define U-Boot binaries locations in NAND"
help
- Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig.
- This option should not be enabled when compiling U-Boot for boards
- defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/<board>.h
- file.
+ Enable CONFIG_SYS_NAND_U_BOOT_OFFS though Kconfig.
+ This option should not be enabled when compiling U-Boot for boards
+ defining CONFIG_SYS_NAND_U_BOOT_OFFS in their include/configs/<board>.h
+ file.
config SYS_NAND_U_BOOT_OFFS
hex "Location in NAND to read U-Boot from"
default 0x800000 if NAND_SUNXI
depends on SYS_NAND_U_BOOT_LOCATIONS
help
- Set the offset from the start of the nand where u-boot should be
- loaded from.
+ Set the offset from the start of the nand where u-boot should be
+ loaded from.
config SYS_NAND_U_BOOT_OFFS_REDUND
hex "Location in NAND to read U-Boot from"
default SYS_NAND_U_BOOT_OFFS
depends on SYS_NAND_U_BOOT_LOCATIONS
help
- Set the offset from the start of the nand where the redundant u-boot
- should be loaded from.
+ Set the offset from the start of the nand where the redundant u-boot
+ should be loaded from.
config SPL_NAND_AM33XX_BCH
bool "Enables SPL-NAND driver which supports ELM based"
depends on SPL_NAND_SUPPORT && NAND_OMAP_GPMC && !OMAP34XX
default y
- help
+ help
Hardware ECC correction. This is useful for platforms which have ELM
hardware engine and use NAND boot mode.
Some legacy platforms like OMAP3xx do not have in-built ELM h/w engine,
so those platforms should use CONFIG_SPL_NAND_SIMPLE for enabling
- SPL-NAND driver with software ECC correction support.
+ SPL-NAND driver with software ECC correction support.
config SPL_NAND_DENALI
bool "Support Denali NAND controller for SPL"
@@ -810,6 +819,6 @@ config SYS_NAND_HW_ECC_OOBFIRST
bool "In SPL, read the OOB first and then the data from NAND"
depends on SPL_NAND_SIMPLE
-endif
+endif # if SPL
-endif # if NAND
+endif # if MTD_RAW_NAND
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index ddbba89..46fead6 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_NAND_KMETER1) += kmeter1_nand.o
obj-$(CONFIG_NAND_LPC32XX_MLC) += lpc32xx_nand_mlc.o
obj-$(CONFIG_NAND_LPC32XX_SLC) += lpc32xx_nand_slc.o
obj-$(CONFIG_NAND_VF610_NFC) += vf610_nfc.o
+obj-$(CONFIG_NAND_MESON) += meson_nand.o
obj-$(CONFIG_NAND_MXC) += mxc_nand.o
obj-$(CONFIG_NAND_MXS) += mxs_nand.o
obj-$(CONFIG_NAND_MXS_DT) += mxs_nand_dt.o
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index 0e04414..ee4ec6d 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -1267,7 +1267,7 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
return ret;
/*
- * The write cycle timing is directly matching tWC, but is also
+ * The read cycle timing is directly matching tRC, but is also
* dependent on the setup and hold timings we calculated earlier,
* which gives:
*
@@ -1429,8 +1429,6 @@ static int atmel_nand_setup_data_interface(struct mtd_info *mtd, int csline,
return nc->caps->ops->setup_data_interface(nand, csline, conf);
}
-#define NAND_KEEP_TIMINGS 0x00800000
-
static void atmel_nand_init(struct atmel_nand_controller *nc,
struct atmel_nand *nand)
{
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
new file mode 100644
index 0000000..5d411c4
--- /dev/null
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -0,0 +1,1248 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Amlogic Meson Nand Flash Controller Driver
+ *
+ * Copyright (c) 2018 Amlogic, inc.
+ * Author: Liang Yang <liang.yang@amlogic.com>
+ *
+ * Copyright (c) 2023 SaluteDevices, Inc.
+ * Author: Arseniy Krasnov <avkrasnov@salutedevices.com>
+ */
+
+#include <common.h>
+#include <nand.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/ofnode.h>
+#include <dm/uclass.h>
+#include <linux/bug.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/sizes.h>
+
+#define NFC_CMD_IDLE (0xc << 14)
+#define NFC_CMD_CLE (0x5 << 14)
+#define NFC_CMD_ALE (0x6 << 14)
+#define NFC_CMD_DWR (0x4 << 14)
+#define NFC_CMD_DRD (0x8 << 14)
+#define NFC_CMD_ADL ((0 << 16) | (3 << 20))
+#define NFC_CMD_ADH ((1 << 16) | (3 << 20))
+#define NFC_CMD_AIL ((2 << 16) | (3 << 20))
+#define NFC_CMD_AIH ((3 << 16) | (3 << 20))
+#define NFC_CMD_SEED ((8 << 16) | (3 << 20))
+#define NFC_CMD_M2N ((0 << 17) | (2 << 20))
+#define NFC_CMD_N2M ((1 << 17) | (2 << 20))
+#define NFC_CMD_RB BIT(20)
+#define NFC_CMD_SCRAMBLER_ENABLE BIT(19)
+#define NFC_CMD_SCRAMBLER_DISABLE 0
+#define NFC_CMD_SHORTMODE_DISABLE 0
+#define NFC_CMD_RB_INT BIT(14)
+#define NFC_CMD_RB_INT_NO_PIN ((0xb << 10) | BIT(18) | BIT(16))
+
+#define NFC_CMD_GET_SIZE(x) (((x) >> 22) & GENMASK(4, 0))
+
+#define NFC_REG_CMD 0x00
+#define NFC_REG_CFG 0x04
+#define NFC_REG_DADR 0x08
+#define NFC_REG_IADR 0x0c
+#define NFC_REG_BUF 0x10
+#define NFC_REG_INFO 0x14
+#define NFC_REG_DC 0x18
+#define NFC_REG_ADR 0x1c
+#define NFC_REG_DL 0x20
+#define NFC_REG_DH 0x24
+#define NFC_REG_CADR 0x28
+#define NFC_REG_SADR 0x2c
+#define NFC_REG_PINS 0x30
+#define NFC_REG_VER 0x38
+
+#define CMDRWGEN(cmd_dir, ran, bch, short_mode, page_size, pages) \
+ ( \
+ (cmd_dir) | \
+ (ran) | \
+ ((bch) << 14) | \
+ ((short_mode) << 13) | \
+ (((page_size) & 0x7f) << 6) | \
+ ((pages) & 0x3f) \
+ )
+
+#define GENCMDDADDRL(adl, addr) ((adl) | ((addr) & 0xffff))
+#define GENCMDDADDRH(adh, addr) ((adh) | (((addr) >> 16) & 0xffff))
+#define GENCMDIADDRL(ail, addr) ((ail) | ((addr) & 0xffff))
+#define GENCMDIADDRH(aih, addr) ((aih) | (((addr) >> 16) & 0xffff))
+
+#define DMA_DIR(dir) ((dir) ? NFC_CMD_N2M : NFC_CMD_M2N)
+
+#define ECC_CHECK_RETURN_FF -1
+
+#define NAND_CE0 (0xe << 10)
+#define NAND_CE1 (0xd << 10)
+
+#define DMA_BUSY_TIMEOUT_US 1000000
+#define CMD_DRAIN_TIMEOUT_US 1000
+#define ECC_POLL_TIMEOUT_US 15
+
+#define MAX_CE_NUM 2
+
+/* eMMC clock register, misc control */
+#define CLK_SELECT_NAND BIT(31)
+#define CLK_ALWAYS_ON_NAND BIT(24)
+#define CLK_ENABLE_VALUE 0x245
+
+#define DIRREAD 1
+#define DIRWRITE 0
+
+#define ECC_PARITY_BCH8_512B 14
+#define ECC_COMPLETE BIT(31)
+#define ECC_ERR_CNT(x) (((x) >> 24) & GENMASK(5, 0))
+#define ECC_ZERO_CNT(x) (((x) >> 16) & GENMASK(5, 0))
+#define ECC_UNCORRECTABLE 0x3f
+
+#define PER_INFO_BYTE 8
+
+#define NFC_SEND_CMD(host, cmd) \
+ (writel((cmd), (host)->reg_base + NFC_REG_CMD))
+
+#define NFC_GET_CMD(host) \
+ (readl((host)->reg_base + NFC_REG_CMD))
+
+#define NFC_CMDFIFO_SIZE(host) ((NFC_GET_CMD((host)) >> 22) & GENMASK(4, 0))
+
+#define NFC_CMD_MAKE_IDLE(ce, delay) ((ce) | NFC_CMD_IDLE | ((delay) & 0x3ff))
+#define NFC_CMD_MAKE_DRD(ce, size) ((ce) | NFC_CMD_DRD | (size))
+#define NFC_CMD_MAKE_DWR(ce, data) ((ce) | NFC_CMD_DWR | ((data) & 0xff))
+#define NFC_CMD_MAKE_CLE(ce, cmd_val) ((ce) | NFC_CMD_CLE | ((cmd_val) & 0xff))
+#define NFC_CMD_MAKE_ALE(ce, addr) ((ce) | NFC_CMD_ALE | ((addr) & 0xff))
+
+#define NAND_TWB_TIME_CYCLE 10
+
+#define NFC_DEV_READY_TICK_MAX 5000
+
+/* Both values are recommended by vendor, as the most
+ * tested with almost all SLC NAND flash. Second value
+ * could be calculated dynamically from timing parameters,
+ * but we need both values for initial start of the NAND
+ * controller (e.g. before NAND subsystem processes timings),
+ * so use hardcoded constants.
+ */
+#define NFC_DEFAULT_BUS_CYCLE 6
+#define NFC_DEFAULT_BUS_TIMING 7
+
+#define NFC_SEED_OFFSET 0xc2
+#define NFC_SEED_MASK 0x7fff
+
+#define DMA_ADDR_ALIGN 8
+
+struct meson_nfc_nand_chip {
+ struct list_head node;
+ struct nand_chip nand;
+
+ u32 bch_mode;
+ u8 *data_buf;
+ __le64 *info_buf;
+ u32 nsels;
+ u8 sels[];
+};
+
+struct meson_nfc_param {
+ u32 chip_select;
+ u32 rb_select;
+};
+
+struct meson_nfc {
+ void __iomem *reg_base;
+ void __iomem *reg_clk;
+ struct list_head chips;
+ struct meson_nfc_param param;
+ struct udevice *dev;
+ dma_addr_t daddr;
+ dma_addr_t iaddr;
+ u32 data_bytes;
+ u32 info_bytes;
+ u64 assigned_cs;
+};
+
+struct meson_nand_ecc {
+ u32 bch;
+ u32 strength;
+ u32 size;
+};
+
+enum {
+ NFC_ECC_BCH8_512 = 1,
+ NFC_ECC_BCH8_1K,
+ NFC_ECC_BCH24_1K,
+ NFC_ECC_BCH30_1K,
+ NFC_ECC_BCH40_1K,
+ NFC_ECC_BCH50_1K,
+ NFC_ECC_BCH60_1K,
+};
+
+#define MESON_ECC_DATA(b, s, sz) { .bch = (b), .strength = (s), .size = (sz) }
+
+static struct meson_nand_ecc meson_ecc[] = {
+ MESON_ECC_DATA(NFC_ECC_BCH8_512, 8, 512),
+ MESON_ECC_DATA(NFC_ECC_BCH8_1K, 8, 1024),
+};
+
+static int meson_nand_calc_ecc_bytes(int step_size, int strength)
+{
+ int ecc_bytes;
+
+ if (step_size == 512 && strength == 8)
+ return ECC_PARITY_BCH8_512B;
+
+ ecc_bytes = DIV_ROUND_UP(strength * fls(step_size * 8), 8);
+ ecc_bytes = ALIGN(ecc_bytes, 2);
+
+ return ecc_bytes;
+}
+
+static struct meson_nfc_nand_chip *to_meson_nand(struct nand_chip *nand)
+{
+ return container_of(nand, struct meson_nfc_nand_chip, nand);
+}
+
+static void meson_nfc_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+
+ nfc->param.chip_select = meson_chip->sels[chip] ? NAND_CE1 : NAND_CE0;
+}
+
+static void meson_nfc_cmd_idle(struct meson_nfc *nfc, u32 time)
+{
+ writel(NFC_CMD_MAKE_IDLE(nfc->param.chip_select, time),
+ nfc->reg_base + NFC_REG_CMD);
+}
+
+static void meson_nfc_cmd_seed(const struct meson_nfc *nfc, u32 seed)
+{
+ writel(NFC_CMD_SEED | (NFC_SEED_OFFSET + (seed & NFC_SEED_MASK)),
+ nfc->reg_base + NFC_REG_CMD);
+}
+
+static void meson_nfc_cmd_access(struct nand_chip *nand, bool raw, bool dir,
+ int scrambler)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ const struct meson_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+ const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ u32 bch = meson_chip->bch_mode, cmd;
+ int len = mtd->writesize, pagesize, pages;
+
+ pagesize = nand->ecc.size;
+
+ if (raw) {
+ len = mtd->writesize + mtd->oobsize;
+ cmd = len | scrambler | DMA_DIR(dir);
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+ return;
+ }
+
+ pages = len / nand->ecc.size;
+
+ cmd = CMDRWGEN(DMA_DIR(dir), scrambler, bch,
+ NFC_CMD_SHORTMODE_DISABLE, pagesize, pages);
+
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+}
+
+static void meson_nfc_drain_cmd(struct meson_nfc *nfc)
+{
+ /*
+ * Insert two commands to make sure all valid commands are finished.
+ *
+ * The Nand flash controller is designed as two stages pipleline -
+ * a) fetch and b) execute.
+ * There might be cases when the driver see command queue is empty,
+ * but the Nand flash controller still has two commands buffered,
+ * one is fetched into NFC request queue (ready to run), and another
+ * is actively executing. So pushing 2 "IDLE" commands guarantees that
+ * the pipeline is emptied.
+ */
+ meson_nfc_cmd_idle(nfc, 0);
+ meson_nfc_cmd_idle(nfc, 0);
+}
+
+static int meson_nfc_wait_cmd_finish(const struct meson_nfc *nfc,
+ unsigned int timeout_us)
+{
+ u32 cmd_size = 0;
+
+ /* wait cmd fifo is empty */
+ return readl_relaxed_poll_timeout(nfc->reg_base + NFC_REG_CMD, cmd_size,
+ !NFC_CMD_GET_SIZE(cmd_size),
+ timeout_us);
+}
+
+static int meson_nfc_wait_dma_finish(struct meson_nfc *nfc)
+{
+ meson_nfc_drain_cmd(nfc);
+
+ return meson_nfc_wait_cmd_finish(nfc, DMA_BUSY_TIMEOUT_US);
+}
+
+static u8 *meson_nfc_oob_ptr(struct nand_chip *nand, int i)
+{
+ const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ int len;
+
+ len = nand->ecc.size * (i + 1) + (nand->ecc.bytes + 2) * i;
+
+ return meson_chip->data_buf + len;
+}
+
+static u8 *meson_nfc_data_ptr(struct nand_chip *nand, int i)
+{
+ const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ int len, temp;
+
+ temp = nand->ecc.size + nand->ecc.bytes;
+ len = (temp + 2) * i;
+
+ return meson_chip->data_buf + len;
+}
+
+static void meson_nfc_get_data_oob(struct nand_chip *nand,
+ u8 *buf, u8 *oobbuf)
+{
+ u8 *dsrc, *osrc;
+ int i, oob_len;
+
+ oob_len = nand->ecc.bytes + 2;
+ for (i = 0; i < nand->ecc.steps; i++) {
+ if (buf) {
+ dsrc = meson_nfc_data_ptr(nand, i);
+ memcpy(buf, dsrc, nand->ecc.size);
+ buf += nand->ecc.size;
+ }
+
+ if (oobbuf) {
+ osrc = meson_nfc_oob_ptr(nand, i);
+ memcpy(oobbuf, osrc, oob_len);
+ oobbuf += oob_len;
+ }
+ }
+}
+
+static void meson_nfc_set_data_oob(struct nand_chip *nand,
+ const u8 *buf, u8 *oobbuf)
+{
+ int i, oob_len;
+
+ oob_len = nand->ecc.bytes + 2;
+ for (i = 0; i < nand->ecc.steps; i++) {
+ u8 *osrc;
+
+ if (buf) {
+ u8 *dsrc;
+
+ dsrc = meson_nfc_data_ptr(nand, i);
+ memcpy(dsrc, buf, nand->ecc.size);
+ buf += nand->ecc.size;
+ }
+
+ osrc = meson_nfc_oob_ptr(nand, i);
+ memcpy(osrc, oobbuf, oob_len);
+ oobbuf += oob_len;
+ }
+}
+
+static void meson_nfc_set_user_byte(struct nand_chip *nand, const u8 *oob_buf)
+{
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ int i, count;
+
+ for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) {
+ __le64 *info = &meson_chip->info_buf[i];
+
+ *info |= oob_buf[count];
+ *info |= oob_buf[count + 1] << 8;
+ }
+}
+
+static void meson_nfc_get_user_byte(struct nand_chip *nand, u8 *oob_buf)
+{
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ int i, count;
+
+ for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) {
+ const __le64 *info = &meson_chip->info_buf[i];
+
+ oob_buf[count] = *info;
+ oob_buf[count + 1] = *info >> 8;
+ }
+}
+
+static int meson_nfc_ecc_correct(struct nand_chip *nand, u32 *bitflips,
+ u64 *correct_bitmap)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ int ret = 0, i;
+
+ for (i = 0; i < nand->ecc.steps; i++) {
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ const __le64 *info = &meson_chip->info_buf[i];
+
+ if (ECC_ERR_CNT(*info) != ECC_UNCORRECTABLE) {
+ mtd->ecc_stats.corrected += ECC_ERR_CNT(*info);
+ *bitflips = max_t(u32, *bitflips, ECC_ERR_CNT(*info));
+ *correct_bitmap |= BIT_ULL(i);
+ continue;
+ }
+
+ if ((nand->options & NAND_NEED_SCRAMBLING) &&
+ ECC_ZERO_CNT(*info) < nand->ecc.strength) {
+ mtd->ecc_stats.corrected += ECC_ZERO_CNT(*info);
+ *bitflips = max_t(u32, *bitflips,
+ ECC_ZERO_CNT(*info));
+ ret = ECC_CHECK_RETURN_FF;
+ } else {
+ ret = -EBADMSG;
+ }
+ }
+
+ return ret;
+}
+
+static int meson_nfc_dma_buffer_setup(struct nand_chip *nand, void *databuf,
+ int datalen, void *infobuf, int infolen,
+ enum dma_data_direction dir)
+{
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+ int ret;
+ u32 cmd;
+
+ nfc->daddr = dma_map_single(databuf, datalen, DMA_BIDIRECTIONAL);
+ ret = dma_mapping_error(nfc->dev, nfc->daddr);
+ if (ret)
+ return ret;
+
+ cmd = GENCMDDADDRL(NFC_CMD_ADL, nfc->daddr);
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ cmd = GENCMDDADDRH(NFC_CMD_ADH, nfc->daddr);
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ if (infobuf) {
+ nfc->iaddr = dma_map_single(infobuf, infolen,
+ DMA_BIDIRECTIONAL);
+ ret = dma_mapping_error(nfc->dev, nfc->iaddr);
+ if (ret) {
+ dma_unmap_single(nfc->daddr, datalen, dir);
+ return ret;
+ }
+
+ nfc->info_bytes = infolen;
+ cmd = GENCMDIADDRL(NFC_CMD_AIL, nfc->iaddr);
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ cmd = GENCMDIADDRH(NFC_CMD_AIH, nfc->iaddr);
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+ }
+
+ return 0;
+}
+
+static void meson_nfc_dma_buffer_release(struct nand_chip *nand,
+ int datalen, int infolen,
+ enum dma_data_direction dir)
+{
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+
+ dma_unmap_single(nfc->daddr, datalen, dir);
+
+ if (infolen) {
+ dma_unmap_single(nfc->iaddr, infolen, dir);
+ nfc->info_bytes = 0;
+ }
+}
+
+static void meson_nfc_read_buf(struct mtd_info *mtd, u8 *buf, int size)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ u8 *dma_buf;
+ int ret;
+ u32 cmd;
+
+ if ((uintptr_t)buf % DMA_ADDR_ALIGN) {
+ unsigned long tmp_addr;
+
+ dma_buf = dma_alloc_coherent(size, &tmp_addr);
+ if (!dma_buf)
+ return;
+ } else {
+ dma_buf = buf;
+ }
+
+ ret = meson_nfc_dma_buffer_setup(nand, dma_buf, size, meson_chip->info_buf,
+ PER_INFO_BYTE, DMA_FROM_DEVICE);
+ if (ret) {
+ pr_err("Failed to setup DMA buffer %p/%p\n", dma_buf,
+ meson_chip->info_buf);
+ return;
+ }
+
+ cmd = NFC_CMD_N2M | size;
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ meson_nfc_drain_cmd(nfc);
+ meson_nfc_wait_cmd_finish(nfc, CMD_DRAIN_TIMEOUT_US);
+ meson_nfc_dma_buffer_release(nand, size, PER_INFO_BYTE, DMA_FROM_DEVICE);
+
+ if (buf != dma_buf) {
+ memcpy(buf, dma_buf, size);
+ dma_free_coherent(dma_buf);
+ }
+}
+
+static void meson_nfc_write_buf(struct mtd_info *mtd, const u8 *buf, int size)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+ u8 *dma_buf;
+ int ret;
+ u32 cmd;
+
+ if ((uintptr_t)buf % DMA_ADDR_ALIGN) {
+ unsigned long tmp_addr;
+
+ dma_buf = dma_alloc_coherent(size, &tmp_addr);
+ if (!dma_buf)
+ return;
+
+ memcpy(dma_buf, buf, size);
+ } else {
+ dma_buf = (u8 *)buf;
+ }
+
+ ret = meson_nfc_dma_buffer_setup(nand, (void *)dma_buf, size, NULL,
+ 0, DMA_TO_DEVICE);
+ if (ret) {
+ pr_err("Failed to setup DMA buffer %p\n", dma_buf);
+ return;
+ }
+
+ cmd = NFC_CMD_M2N | size;
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ meson_nfc_drain_cmd(nfc);
+ meson_nfc_wait_cmd_finish(nfc, CMD_DRAIN_TIMEOUT_US);
+ meson_nfc_dma_buffer_release(nand, size, 0, DMA_TO_DEVICE);
+
+ if (buf != dma_buf)
+ dma_free_coherent(dma_buf);
+}
+
+static int meson_nfc_write_page_sub(struct nand_chip *nand,
+ int page, bool raw)
+{
+ const struct mtd_info *mtd = nand_to_mtd(nand);
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+ int data_len, info_len;
+ int ret;
+ u32 cmd;
+
+ data_len = mtd->writesize + mtd->oobsize;
+ info_len = nand->ecc.steps * PER_INFO_BYTE;
+
+ ret = meson_nfc_dma_buffer_setup(nand, meson_chip->data_buf,
+ data_len, meson_chip->info_buf,
+ info_len, DMA_TO_DEVICE);
+ if (ret) {
+ pr_err("Failed to setup DMA buffer %p/%p\n",
+ meson_chip->data_buf, meson_chip->info_buf);
+ return ret;
+ }
+
+ if (nand->options & NAND_NEED_SCRAMBLING) {
+ meson_nfc_cmd_seed(nfc, page);
+ meson_nfc_cmd_access(nand, raw, DIRWRITE,
+ NFC_CMD_SCRAMBLER_ENABLE);
+ } else {
+ meson_nfc_cmd_access(nand, raw, DIRWRITE,
+ NFC_CMD_SCRAMBLER_DISABLE);
+ }
+
+ cmd = nfc->param.chip_select | NFC_CMD_CLE | NAND_CMD_PAGEPROG;
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+
+ meson_nfc_dma_buffer_release(nand, data_len, info_len, DMA_TO_DEVICE);
+
+ return 0;
+}
+
+static int meson_nfc_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf, int oob_required, int page)
+{
+ meson_nfc_set_data_oob(chip, buf, oob_required ? chip->oob_poi : NULL);
+
+ return meson_nfc_write_page_sub(chip, page, true);
+}
+
+static int meson_nfc_write_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
+ const u8 *buf, int oob_required, int page)
+{
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(chip);
+
+ if (buf)
+ memcpy(meson_chip->data_buf, buf, mtd->writesize);
+
+ memset(meson_chip->info_buf, 0, chip->ecc.steps * PER_INFO_BYTE);
+
+ if (oob_required)
+ meson_nfc_set_user_byte(chip, chip->oob_poi);
+
+ return meson_nfc_write_page_sub(chip, page, false);
+}
+
+static void meson_nfc_check_ecc_pages_valid(struct meson_nfc *nfc,
+ struct nand_chip *nand, bool raw)
+{
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ __le64 *info;
+ u32 neccpages;
+ int ret;
+
+ neccpages = raw ? 1 : nand->ecc.steps;
+ info = &meson_chip->info_buf[neccpages - 1];
+ do {
+ udelay(ECC_POLL_TIMEOUT_US);
+ /* info is updated by nfc dma engine*/
+ rmb();
+ invalidate_dcache_range(nfc->iaddr, nfc->iaddr + nfc->info_bytes);
+ ret = *info & ECC_COMPLETE;
+ } while (!ret);
+}
+
+static int meson_nfc_read_page_sub(struct nand_chip *nand,
+ int page, bool raw)
+{
+ struct mtd_info *mtd = nand_to_mtd(nand);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ u32 data_len, info_len;
+ int ret;
+
+ data_len = mtd->writesize + mtd->oobsize;
+ info_len = nand->ecc.steps * PER_INFO_BYTE;
+
+ ret = meson_nfc_dma_buffer_setup(nand, meson_chip->data_buf, data_len,
+ meson_chip->info_buf, info_len,
+ DMA_FROM_DEVICE);
+ if (ret)
+ return ret;
+
+ if (nand->options & NAND_NEED_SCRAMBLING) {
+ meson_nfc_cmd_seed(nfc, page);
+ meson_nfc_cmd_access(nand, raw, DIRREAD,
+ NFC_CMD_SCRAMBLER_ENABLE);
+ } else {
+ meson_nfc_cmd_access(nand, raw, DIRREAD,
+ NFC_CMD_SCRAMBLER_DISABLE);
+ }
+
+ meson_nfc_wait_dma_finish(nfc);
+ meson_nfc_check_ecc_pages_valid(nfc, nand, raw);
+
+ meson_nfc_dma_buffer_release(nand, data_len, info_len,
+ DMA_FROM_DEVICE);
+
+ return 0;
+}
+
+static int meson_nfc_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ u8 *buf, int oob_required, int page)
+{
+ int ret;
+
+ ret = meson_nfc_read_page_sub(chip, page, true);
+ if (ret)
+ return ret;
+
+ meson_nfc_get_data_oob(chip, buf, oob_required ? chip->oob_poi : NULL);
+
+ return 0;
+}
+
+static int meson_nfc_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
+ u8 *buf, int oob_required, int page)
+{
+ const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(chip);
+ u64 correct_bitmap = 0;
+ u32 bitflips = 0;
+ int ret;
+
+ ret = meson_nfc_read_page_sub(chip, page, false);
+ if (ret)
+ return ret;
+
+ if (oob_required)
+ meson_nfc_get_user_byte(chip, chip->oob_poi);
+
+ ret = meson_nfc_ecc_correct(chip, &bitflips, &correct_bitmap);
+
+ if (ret == ECC_CHECK_RETURN_FF) {
+ if (buf)
+ memset(buf, 0xff, mtd->writesize);
+
+ if (oob_required)
+ memset(chip->oob_poi, 0xff, mtd->oobsize);
+ } else if (ret < 0) {
+ struct nand_ecc_ctrl *ecc;
+ int i;
+
+ if ((chip->options & NAND_NEED_SCRAMBLING) || !buf) {
+ mtd->ecc_stats.failed++;
+ return bitflips;
+ }
+
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+
+ ret = meson_nfc_read_page_raw(mtd, chip, buf, 1, page);
+ if (ret)
+ return ret;
+
+ ecc = &chip->ecc;
+
+ for (i = 0; i < chip->ecc.steps ; i++) {
+ u8 *data = buf + i * ecc->size;
+ u8 *oob = chip->oob_poi + i * (ecc->bytes + 2);
+
+ if (correct_bitmap & BIT_ULL(i))
+ continue;
+
+ ret = nand_check_erased_ecc_chunk(data, ecc->size,
+ oob, ecc->bytes + 2,
+ NULL, 0,
+ ecc->strength);
+ if (ret < 0) {
+ mtd->ecc_stats.failed++;
+ } else {
+ mtd->ecc_stats.corrected += ret;
+ bitflips = max_t(u32, bitflips, ret);
+ }
+ }
+ } else if (buf && buf != meson_chip->data_buf) {
+ memcpy(buf, meson_chip->data_buf, mtd->writesize);
+ }
+
+ return bitflips;
+}
+
+static int meson_nfc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ int ret;
+
+ ret = nand_read_page_op(chip, page, 0, NULL, 0);
+ if (ret)
+ return ret;
+
+ return meson_nfc_read_page_raw(mtd, chip, NULL, 1, page);
+}
+
+static int meson_nfc_read_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ int ret;
+
+ ret = nand_read_page_op(chip, page, 0, NULL, 0);
+ if (ret)
+ return ret;
+
+ return meson_nfc_read_page_hwecc(mtd, chip, NULL, 1, page);
+}
+
+static int meson_nfc_write_oob_raw(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ int ret;
+
+ ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+ if (ret)
+ return ret;
+
+ ret = meson_nfc_write_page_raw(mtd, chip, NULL, 1, page);
+ if (ret)
+ return ret;
+
+ return nand_prog_page_end_op(chip);
+}
+
+static int meson_nfc_write_oob(struct mtd_info *mtd, struct nand_chip *chip,
+ int page)
+{
+ int ret;
+
+ ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0);
+ if (ret)
+ return ret;
+
+ ret = meson_nfc_write_page_hwecc(mtd, chip, NULL, 1, page);
+ if (ret)
+ return ret;
+
+ return nand_prog_page_end_op(chip);
+}
+
+static void meson_nfc_nand_cmd_function(struct mtd_info *mtd, unsigned int command,
+ int column, int page_addr)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+
+ chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+
+ if (column != -1 || page_addr != -1) {
+ int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
+
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (chip->options & NAND_BUSWIDTH_16 &&
+ !nand_opcode_8bits(command))
+ column >>= 1;
+
+ chip->cmd_ctrl(mtd, column, ctrl);
+ ctrl &= ~NAND_CTRL_CHANGE;
+ /* Only output a single addr cycle for 8bits
+ * opcodes.
+ */
+ if (!nand_opcode_8bits(command))
+ chip->cmd_ctrl(mtd, column >> 8, ctrl);
+ }
+
+ if (page_addr != -1) {
+ chip->cmd_ctrl(mtd, page_addr, ctrl);
+ chip->cmd_ctrl(mtd, page_addr >> 8, NAND_NCE |
+ NAND_ALE);
+ /* One more address cycle for devices > 128MiB */
+ if (chip->chipsize > SZ_128M)
+ chip->cmd_ctrl(mtd, page_addr >> 16,
+ NAND_NCE | NAND_ALE);
+ }
+
+ switch (command) {
+ case NAND_CMD_READ0:
+ chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
+ NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+ fallthrough;
+ case NAND_CMD_PARAM:
+ nand_wait_ready(mtd);
+ nand_exit_status_op(chip);
+ }
+ }
+}
+
+static void meson_nfc_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+
+ if (cmd == NAND_CMD_NONE)
+ return;
+
+ if (ctrl & NAND_CLE)
+ cmd = NFC_CMD_MAKE_CLE(nfc->param.chip_select, cmd);
+ else
+ cmd = NFC_CMD_MAKE_ALE(nfc->param.chip_select, cmd);
+
+ writel(cmd, nfc->reg_base + NFC_REG_CMD);
+}
+
+static void meson_nfc_wait_cmd_fifo(struct meson_nfc *nfc)
+{
+ while ((NFC_GET_CMD(nfc) >> 22) & GENMASK(4, 0))
+ ;
+}
+
+static u8 meson_nfc_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+
+ writel(NFC_CMD_MAKE_DRD(nfc->param.chip_select, 0), nfc->reg_base + NFC_REG_CMD);
+
+ meson_nfc_cmd_idle(nfc, NAND_TWB_TIME_CYCLE);
+ meson_nfc_cmd_idle(nfc, 0);
+ meson_nfc_cmd_idle(nfc, 0);
+
+ meson_nfc_wait_cmd_fifo(nfc);
+
+ return readl(nfc->reg_base + NFC_REG_BUF);
+}
+
+static void meson_nfc_nand_write_byte(struct mtd_info *mtd, u8 val)
+{
+ struct nand_chip *nand = mtd_to_nand(mtd);
+ struct meson_nfc *nfc = nand_get_controller_data(nand);
+
+ meson_nfc_cmd_idle(nfc, NAND_TWB_TIME_CYCLE);
+
+ writel(NFC_CMD_MAKE_DWR(nfc->param.chip_select, val), nfc->reg_base + NFC_REG_CMD);
+
+ meson_nfc_cmd_idle(nfc, NAND_TWB_TIME_CYCLE);
+ meson_nfc_cmd_idle(nfc, 0);
+ meson_nfc_cmd_idle(nfc, 0);
+
+ meson_nfc_wait_cmd_fifo(nfc);
+}
+
+static int meson_nfc_dev_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ unsigned int time_out_cnt = 0;
+
+ chip->select_chip(mtd, 0);
+
+ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+
+ do {
+ int status;
+
+ status = (int)chip->read_byte(mtd);
+ if (status & NAND_STATUS_READY)
+ break;
+ } while (time_out_cnt++ < NFC_DEV_READY_TICK_MAX);
+
+ return time_out_cnt != NFC_DEV_READY_TICK_MAX;
+}
+
+static int meson_chip_buffer_init(struct nand_chip *nand)
+{
+ const struct mtd_info *mtd = nand_to_mtd(nand);
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+ u32 page_bytes, info_bytes, nsectors;
+ unsigned long tmp_addr;
+
+ nsectors = mtd->writesize / nand->ecc.size;
+
+ page_bytes = mtd->writesize + mtd->oobsize;
+ info_bytes = nsectors * PER_INFO_BYTE;
+
+ meson_chip->data_buf = dma_alloc_coherent(page_bytes, &tmp_addr);
+ if (!meson_chip->data_buf)
+ return -ENOMEM;
+
+ meson_chip->info_buf = dma_alloc_coherent(info_bytes, &tmp_addr);
+ if (!meson_chip->info_buf) {
+ dma_free_coherent(meson_chip->data_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static const int axg_stepinfo_strengths[] = { 8 };
+static const struct nand_ecc_step_info axg_stepinfo_1024 = {
+ .stepsize = 1024,
+ .strengths = axg_stepinfo_strengths,
+ .nstrengths = ARRAY_SIZE(axg_stepinfo_strengths)
+};
+
+static const struct nand_ecc_step_info axg_stepinfo_512 = {
+ .stepsize = 512,
+ .strengths = axg_stepinfo_strengths,
+ .nstrengths = ARRAY_SIZE(axg_stepinfo_strengths)
+};
+
+static const struct nand_ecc_step_info axg_stepinfo[] = { axg_stepinfo_1024, axg_stepinfo_512 };
+
+static const struct nand_ecc_caps meson_axg_ecc_caps = {
+ .stepinfos = axg_stepinfo,
+ .nstepinfos = ARRAY_SIZE(axg_stepinfo),
+ .calc_ecc_bytes = meson_nand_calc_ecc_bytes,
+};
+
+/*
+ * OOB layout:
+ *
+ * For ECC with 512 bytes step size:
+ * 0x00: AA AA BB BB BB BB BB BB BB BB BB BB BB BB BB BB
+ * 0x10: AA AA CC CC CC CC CC CC CC CC CC CC CC CC CC CC
+ * 0x20:
+ * 0x30:
+ *
+ * For ECC with 1024 bytes step size:
+ * 0x00: AA AA BB BB BB BB BB BB BB BB BB BB BB BB BB BB
+ * 0x10: AA AA CC CC CC CC CC CC CC CC CC CC CC CC CC CC
+ * 0x20: AA AA DD DD DD DD DD DD DD DD DD DD DD DD DD DD
+ * 0x30: AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE
+ *
+ * AA - user bytes.
+ * BB, CC, DD, EE - ECC code bytes for each step.
+ */
+static struct nand_ecclayout nand_oob;
+
+static void meson_nfc_init_nand_oob(struct nand_chip *nand)
+{
+ int section_size = 2 + nand->ecc.bytes;
+ int i;
+ int k;
+
+ nand_oob.eccbytes = nand->ecc.steps * nand->ecc.bytes;
+ k = 0;
+
+ for (i = 0; i < nand->ecc.steps; i++) {
+ int j;
+
+ for (j = 0; j < nand->ecc.bytes; j++)
+ nand_oob.eccpos[k++] = (i * section_size) + 2 + j;
+
+ nand_oob.oobfree[i].offset = (i * section_size);
+ nand_oob.oobfree[i].length = 2;
+ }
+
+ nand_oob.oobavail = 2 * nand->ecc.steps;
+ nand->ecc.layout = &nand_oob;
+}
+
+static int meson_nfc_init_ecc(struct nand_chip *nand, ofnode node)
+{
+ const struct mtd_info *mtd = nand_to_mtd(nand);
+ int ret;
+ int i;
+
+ ret = nand_check_ecc_caps(nand, &meson_axg_ecc_caps, mtd->oobsize - 2);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(meson_ecc); i++) {
+ if (meson_ecc[i].strength == nand->ecc.strength &&
+ meson_ecc[i].size == nand->ecc.size) {
+ struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand);
+
+ nand->ecc.steps = mtd->writesize / nand->ecc.size;
+ meson_chip->bch_mode = meson_ecc[i].bch;
+
+ meson_nfc_init_nand_oob(nand);
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int meson_nfc_nand_chip_init(struct udevice *dev, struct meson_nfc *nfc,
+ ofnode node)
+{
+ struct meson_nfc_nand_chip *meson_chip;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ u32 cs[MAX_CE_NUM];
+ u32 nsels;
+ int ret;
+ int i;
+
+ if (!ofnode_get_property(node, "reg", &nsels)) {
+ dev_err(dev, "\"reg\" property is not found\n");
+ return -ENODEV;
+ }
+
+ nsels /= sizeof(u32);
+ if (nsels >= MAX_CE_NUM) {
+ dev_err(dev, "invalid size of CS array, max is %d\n",
+ MAX_CE_NUM);
+ return -EINVAL;
+ }
+
+ ret = ofnode_read_u32_array(node, "reg", cs, nsels);
+ if (ret < 0) {
+ dev_err(dev, "failed to read \"reg\" property\n");
+ return ret;
+ }
+
+ for (i = 0; i < nsels; i++) {
+ if (test_and_set_bit(cs[i], &nfc->assigned_cs)) {
+ dev_err(dev, "CS %d already assigned\n", cs[i]);
+ return -EINVAL;
+ }
+ }
+
+ meson_chip = malloc(sizeof(*meson_chip) + nsels * sizeof(meson_chip->sels[0]));
+ if (!meson_chip) {
+ dev_err(dev, "failed to allocate memory for chip\n");
+ return -ENOMEM;
+ }
+
+ meson_chip->nsels = nsels;
+ nand = &meson_chip->nand;
+
+ nand->flash_node = node;
+ nand_set_controller_data(nand, nfc);
+ /* Set the driver entry points for MTD */
+ nand->cmdfunc = meson_nfc_nand_cmd_function;
+ nand->cmd_ctrl = meson_nfc_cmd_ctrl;
+ nand->select_chip = meson_nfc_nand_select_chip;
+ nand->read_byte = meson_nfc_nand_read_byte;
+ nand->write_byte = meson_nfc_nand_write_byte;
+ nand->dev_ready = meson_nfc_dev_ready;
+
+ /* Buffer read/write routines */
+ nand->read_buf = meson_nfc_read_buf;
+ nand->write_buf = meson_nfc_write_buf;
+ nand->options |= NAND_NO_SUBPAGE_WRITE;
+
+ nand->ecc.mode = NAND_ECC_HW;
+ nand->ecc.hwctl = NULL;
+ nand->ecc.read_page = meson_nfc_read_page_hwecc;
+ nand->ecc.write_page = meson_nfc_write_page_hwecc;
+ nand->ecc.read_page_raw = meson_nfc_read_page_raw;
+ nand->ecc.write_page_raw = meson_nfc_write_page_raw;
+
+ nand->ecc.read_oob = meson_nfc_read_oob;
+ nand->ecc.write_oob = meson_nfc_write_oob;
+ nand->ecc.read_oob_raw = meson_nfc_read_oob_raw;
+ nand->ecc.write_oob_raw = meson_nfc_write_oob_raw;
+
+ nand->ecc.algo = NAND_ECC_BCH;
+
+ mtd = nand_to_mtd(nand);
+
+ ret = nand_scan_ident(mtd, 1, NULL);
+ if (ret) {
+ dev_err(dev, "'nand_scan_ident()' failed: %d\n", ret);
+ goto err_chip_free;
+ }
+
+ ret = meson_nfc_init_ecc(nand, node);
+ if (ret) {
+ dev_err(dev, "failed to init ECC settings: %d\n", ret);
+ goto err_chip_free;
+ }
+
+ ret = meson_chip_buffer_init(nand);
+ if (ret) {
+ dev_err(dev, "failed to init DMA buffers: %d\n", ret);
+ goto err_chip_free;
+ }
+
+ /* 'nand_scan_tail()' needs ECC parameters to be already
+ * set and correct.
+ */
+ ret = nand_scan_tail(mtd);
+ if (ret) {
+ dev_err(dev, "'nand_scan_tail()' failed: %d\n", ret);
+ goto err_chip_buf_free;
+ }
+
+ ret = nand_register(0, mtd);
+ if (ret) {
+ dev_err(dev, "'nand_register()' failed: %d\n", ret);
+ goto err_chip_buf_free;
+ }
+
+ list_add_tail(&meson_chip->node, &nfc->chips);
+
+ return 0;
+
+err_chip_buf_free:
+ dma_free_coherent(meson_chip->info_buf);
+ dma_free_coherent(meson_chip->data_buf);
+
+err_chip_free:
+ free(meson_chip);
+
+ return ret;
+}
+
+static int meson_nfc_nand_chips_init(struct udevice *dev,
+ struct meson_nfc *nfc)
+{
+ ofnode parent = dev_ofnode(dev);
+ ofnode node;
+
+ ofnode_for_each_subnode(node, parent) {
+ int ret = meson_nfc_nand_chip_init(dev, nfc, node);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void meson_nfc_clk_init(struct meson_nfc *nfc)
+{
+ u32 bus_cycle = NFC_DEFAULT_BUS_CYCLE;
+ u32 bus_timing = NFC_DEFAULT_BUS_TIMING;
+ u32 bus_cfg_val;
+
+ writel(CLK_ALWAYS_ON_NAND | CLK_SELECT_NAND | CLK_ENABLE_VALUE, nfc->reg_clk);
+ writel(0, nfc->reg_base + NFC_REG_CFG);
+
+ bus_cfg_val = (((bus_cycle - 1) & 31) | ((bus_timing & 31) << 5));
+ writel(bus_cfg_val, nfc->reg_base + NFC_REG_CFG);
+ writel(BIT(31), nfc->reg_base + NFC_REG_CMD);
+}
+
+static int meson_probe(struct udevice *dev)
+{
+ struct meson_nfc *nfc = dev_get_priv(dev);
+ void *addr;
+ int ret;
+
+ addr = dev_read_addr_ptr(dev);
+ if (!addr) {
+ dev_err(dev, "base register address not found\n");
+ return -EINVAL;
+ }
+
+ nfc->reg_base = addr;
+
+ addr = dev_read_addr_index_ptr(dev, 1);
+ if (!addr) {
+ dev_err(dev, "clk register address not found\n");
+ return -EINVAL;
+ }
+
+ nfc->reg_clk = addr;
+ nfc->dev = dev;
+
+ meson_nfc_clk_init(nfc);
+
+ ret = meson_nfc_nand_chips_init(dev, nfc);
+ if (ret) {
+ dev_err(nfc->dev, "failed to init chips\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id meson_nand_dt_ids[] = {
+ {.compatible = "amlogic,meson-axg-nfc",},
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(meson_nand) = {
+ .name = "meson_nand",
+ .id = UCLASS_MTD,
+ .of_match = meson_nand_dt_ids,
+ .probe = meson_probe,
+ .priv_auto = sizeof(struct meson_nfc),
+};
+
+void board_nand_init(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_get_device_by_driver(UCLASS_MTD,
+ DM_DRIVER_GET(meson_nand), &dev);
+
+ if (ret && ret != -ENODEV)
+ pr_err("Failed to initialize: %d\n", ret);
+}
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index c40a0f2..688d17b 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -4118,7 +4118,7 @@ static int nand_get_bits_per_cell(u8 cellinfo)
*/
void nand_decode_ext_id(struct nand_chip *chip)
{
- struct mtd_info *mtd = &chip->mtd;
+ struct mtd_info *mtd = nand_to_mtd(chip);
int extid;
/* The 3rd id byte holds MLC / multichip data */
chip->bits_per_cell = nand_get_bits_per_cell(chip->id.data[2]);
@@ -4185,7 +4185,7 @@ static int nand_manufacturer_init(struct nand_chip *chip)
*/
static void nand_decode_id(struct nand_chip *chip, struct nand_flash_dev *type)
{
- struct mtd_info *mtd = &chip->mtd;
+ struct mtd_info *mtd = nand_to_mtd(chip);
mtd->erasesize = type->erasesize;
mtd->writesize = type->pagesize;
@@ -4265,7 +4265,7 @@ static const struct nand_manufacturer *nand_get_manufacturer_desc(u8 id)
int nand_detect(struct nand_chip *chip, int *maf_id,
int *dev_id, struct nand_flash_dev *type)
{
- struct mtd_info *mtd = &chip->mtd;
+ struct mtd_info *mtd = nand_to_mtd(chip);
const struct nand_manufacturer *manufacturer_desc;
int busw, ret;
u8 *id_data = chip->id.data;
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index f172f47..65b836b 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
spinand-objs := core.o esmt.o gigadevice.o macronix.o micron.o paragon.o
-spinand-objs += toshiba.o winbond.o
+spinand-objs += toshiba.o winbond.o xtx.o
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8ca3345..62c28aa 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -829,6 +829,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
&toshiba_spinand_manufacturer,
&winbond_spinand_manufacturer,
&esmt_c8_spinand_manufacturer,
+ &xtx_spinand_manufacturer,
};
static int spinand_manufacturer_match(struct spinand_device *spinand,
diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c
new file mode 100644
index 0000000..aee1849
--- /dev/null
+++ b/drivers/mtd/nand/spi/xtx.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author:
+ * Felix Matouschek <felix@matouschek.org>
+ */
+
+#include <linux/bitfield.h>
+#ifndef __UBOOT__
+#include <linux/device.h>
+#include <linux/kernel.h>
+#endif
+#include <linux/mtd/spinand.h>
+
+#define SPINAND_MFR_XTX 0x0B
+
+#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2)
+#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2)
+#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4)
+#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4)
+
+#define XT26XXXD_STATUS_ECC3_ECC2_MASK GENMASK(7, 6)
+#define XT26XXXD_STATUS_ECC_NO_DETECTED (0)
+#define XT26XXXD_STATUS_ECC_1_7_CORRECTED (1)
+#define XT26XXXD_STATUS_ECC_8_CORRECTED (3)
+#define XT26XXXD_STATUS_ECC_UNCOR_ERROR (2)
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD(false, 0, NULL, 0));
+
+static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 48;
+ region->length = 16;
+
+ return 0;
+}
+
+static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 1;
+ region->length = 47;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = {
+ .ecc = xt26g0xa_ooblayout_ecc,
+ .rfree = xt26g0xa_ooblayout_free,
+};
+
+static int xt26g0xa_ecc_get_status(struct spinand_device *spinand,
+ u8 status)
+{
+ status = status & XT26G0XA_STATUS_ECC_MASK;
+
+ switch (status) {
+ case XT26G0XA_STATUS_ECC_NO_DETECTED:
+ return 0;
+ case XT26G0XA_STATUS_ECC_8_CORRECTED:
+ return 8;
+ case XT26G0XA_STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+ default:
+ break;
+ }
+
+ /* At this point values greater than (2 << 4) are invalid */
+ if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR)
+ return -EINVAL;
+
+ /* (1 << 2) through (7 << 2) are 1-7 corrected errors */
+ return status >> 2;
+}
+
+static int xt26xxxd_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = mtd->oobsize / 2;
+ region->length = mtd->oobsize / 2;
+
+ return 0;
+}
+
+static int xt26xxxd_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 2;
+ region->length = mtd->oobsize / 2 - 2;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops xt26xxxd_ooblayout = {
+ .ecc = xt26xxxd_ooblayout_ecc,
+ .rfree = xt26xxxd_ooblayout_free,
+};
+
+static int xt26xxxd_ecc_get_status(struct spinand_device *spinand,
+ u8 status)
+{
+ switch (FIELD_GET(STATUS_ECC_MASK, status)) {
+ case XT26XXXD_STATUS_ECC_NO_DETECTED:
+ return 0;
+ case XT26XXXD_STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+ case XT26XXXD_STATUS_ECC_1_7_CORRECTED:
+ return 4 + FIELD_GET(XT26XXXD_STATUS_ECC3_ECC2_MASK, status);
+ case XT26XXXD_STATUS_ECC_8_CORRECTED:
+ return 8;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct spinand_info xtx_spinand_table[] = {
+ SPINAND_INFO("XT26G01A",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+ xt26g0xa_ecc_get_status)),
+ SPINAND_INFO("XT26G02A",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2),
+ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+ xt26g0xa_ecc_get_status)),
+ SPINAND_INFO("XT26G04A",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3),
+ NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&xt26g0xa_ooblayout,
+ xt26g0xa_ecc_get_status)),
+ SPINAND_INFO("XT26G01D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x31),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26G11D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x34),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26Q01D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26G02D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x32),
+ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26G12D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x35),
+ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26Q02D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x52),
+ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26G04D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x33),
+ NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+ SPINAND_INFO("XT26Q04D",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x53),
+ NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&xt26xxxd_ooblayout,
+ xt26xxxd_ecc_get_status)),
+};
+
+static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer xtx_spinand_manufacturer = {
+ .id = SPINAND_MFR_XTX,
+ .name = "XTX",
+ .chips = xtx_spinand_table,
+ .nchips = ARRAY_SIZE(xtx_spinand_table),
+ .ops = &xtx_spinand_manuf_ops,
+};
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index fb002ae..4abaf47 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -249,6 +249,13 @@ enum nand_ecc_algo {
*/
#define NAND_USE_BOUNCE_BUFFER 0x00100000
+/*
+ * Do not try to tweak the timings at runtime. This is needed when the
+ * controller initializes the timings on itself or when it relies on
+ * configuration done by the bootloader.
+ */
+#define NAND_KEEP_TIMINGS 0x00800000
+
/* Options set by nand scan */
/* bbt has already been read */
#define NAND_BBT_SCANNED 0x40000000
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 6f479fa..13b5a52 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -251,6 +251,7 @@ extern const struct spinand_manufacturer paragon_spinand_manufacturer;
extern const struct spinand_manufacturer toshiba_spinand_manufacturer;
extern const struct spinand_manufacturer winbond_spinand_manufacturer;
extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
+extern const struct spinand_manufacturer xtx_spinand_manufacturer;
/**
* struct spinand_op_variants - SPI NAND operation variants