From: Aleksei Sviridkin <f@lex.la>
To: Miquel Raynal <miquel.raynal@bootlin.com>,
Vignesh Raghavendra <vigneshr@ti.com>
Cc: Richard Weinberger <richard@nod.at>,
Maxim Anisimov <maxim.anisimov.ua@gmail.com>,
linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org,
Aleksei Sviridkin <f@lex.la>
Subject: [PATCH] mtd: spinand: add support for HeYangTek HYF1GQ4UDACAE
Date: Tue, 16 Jun 2026 16:28:44 +0300 [thread overview]
Message-ID: <20260616132844.43994-1-f@lex.la> (raw)
The HeYangTek HYF1GQ4UDACAE is a 1 Gbit (128 MiB) SLC SPI-NAND with
2048 + 64 byte pages and on-die 4-bit / 512-byte ECC; its JEDEC
manufacturer ID is 0xc9. The die is GD5F1GQ4-compatible, so the OOB
layout is taken from the in-tree gd5fxgq4xa. The die exposes only a
coarse 2-bit ECC status with no fine-grained bitflip-count register, so
the status is decoded into a representative number of corrected bitflips.
It is found, among others, on some Keenetic KN-3411 (Buddy 6) units.
Datasheet:
https://www.heyangtek.cn/previewfile.jsp?file=ABUIABA9GAAgwsvRnwYo-eDpsgc
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
This revives Maxim Anisimov's 2024 submission [1], which stalled on the
review feedback. Changes since then:
- The OOB layout no longer leaves undefined bytes: it is taken verbatim
from the in-tree gd5fxgq4xa, which the die is compatible with (BBM in
byte 0, ECC parity in bytes 8..15 of each 16-byte section, the rest
exposed as free).
- The ECC status decoding is documented and uses '/' instead of a
shift. The die exposes only the coarse 2-bit status with no register
for the exact bitflip count, so the below-threshold code reports
strength/2 and the refresh-recommended code reports the full strength.
- The datasheet link is added to the commit message.
Tested on a Keenetic KN-3411 (Buddy 6): the chip is detected and a UBIFS
rootfs mounts and runs on it. nandbiterrs is not applicable here -- this
is an on-die-ECC part with NOP=1, so the in-place raw page reprogram that
nandbiterrs relies on corrupts the page independently of this driver.
[1] https://lore.kernel.org/linux-mtd/20240624061246.5292-1-maxim.anisimov.ua@gmail.com/
drivers/mtd/nand/spi/Makefile | 2 +-
drivers/mtd/nand/spi/core.c | 1 +
drivers/mtd/nand/spi/heyangtek.c | 132 +++++++++++++++++++++++++++++++
include/linux/mtd/spinand.h | 1 +
4 files changed, 135 insertions(+), 1 deletion(-)
create mode 100644 drivers/mtd/nand/spi/heyangtek.c
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index a47bd22cd..b5ccb4486 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 otp.o
-spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o
+spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o heyangtek.o
spinand-objs += macronix.o micron.o paragon.o skyhigh.o 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 0b076790b..0cdd4a62e 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1336,6 +1336,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
&fmsh_spinand_manufacturer,
&foresee_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
+ &heyangtek_spinand_manufacturer,
¯onix_spinand_manufacturer,
µn_spinand_manufacturer,
¶gon_spinand_manufacturer,
diff --git a/drivers/mtd/nand/spi/heyangtek.c b/drivers/mtd/nand/spi/heyangtek.c
new file mode 100644
index 000000000..7fc50fd3d
--- /dev/null
+++ b/drivers/mtd/nand/spi/heyangtek.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Authors:
+ * Andrey Zolotarev <andrey.zolotarev@keenetic.com> - the main driver logic
+ * Aleksei Sviridkin <f@lex.la> - adaptation to the mainline Linux kernel
+ *
+ * Based on:
+ * https://github.com/keenetic/kernel-49/commit/bacade569fb12bc0ad31ba09bca9b890118fbca7
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+
+#define SPINAND_MFR_HEYANGTEK 0xc9
+
+#define HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS (3 << 4)
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+/*
+ * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken
+ * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each
+ * 16-byte section, the bad block marker sits in byte 0 and the remaining
+ * bytes are exposed as free.
+ */
+static int hyf1gq4_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = (16 * section) + 8;
+ region->length = 8;
+
+ return 0;
+}
+
+static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ if (section) {
+ region->offset = 16 * section;
+ region->length = 8;
+ } else {
+ /* section 0 has one byte reserved for the bad block marker */
+ region->offset = 1;
+ region->length = 7;
+ }
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops hyf1gq4_ooblayout = {
+ .ecc = hyf1gq4_ooblayout_ecc,
+ .free = hyf1gq4_ooblayout_free,
+};
+
+static int hyf1gq4_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ case STATUS_ECC_HAS_BITFLIPS:
+ /*
+ * The die exposes only a coarse 2-bit ECC status and has no
+ * register for the exact bitflip count. This code means
+ * "corrected, below the refresh threshold", so report half of
+ * the ECC strength as a representative value.
+ */
+ return nanddev_get_ecc_conf(nand)->strength / 2;
+
+ case HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS:
+ /*
+ * "Corrected, refresh recommended": report the full ECC
+ * strength so the upper layers relocate the data.
+ */
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct spinand_info heyangtek_spinand_table[] = {
+ SPINAND_INFO("HYF1GQ4UDACAE",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x21),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(4, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&hyf1gq4_ooblayout,
+ hyf1gq4_ecc_get_status)),
+};
+
+static const struct spinand_manufacturer_ops heyangtek_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer heyangtek_spinand_manufacturer = {
+ .id = SPINAND_MFR_HEYANGTEK,
+ .name = "HeYangTek",
+ .chips = heyangtek_spinand_table,
+ .nchips = ARRAY_SIZE(heyangtek_spinand_table),
+ .ops = &heyangtek_spinand_manuf_ops,
+};
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 782984ba3..48357e6ff 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -437,6 +437,7 @@ extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
extern const struct spinand_manufacturer fmsh_spinand_manufacturer;
extern const struct spinand_manufacturer foresee_spinand_manufacturer;
extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
+extern const struct spinand_manufacturer heyangtek_spinand_manufacturer;
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
extern const struct spinand_manufacturer micron_spinand_manufacturer;
extern const struct spinand_manufacturer paragon_spinand_manufacturer;
--
2.39.5
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
WARNING: multiple messages have this Message-ID (diff)
From: Aleksei Sviridkin <f@lex.la>
To: Miquel Raynal <miquel.raynal@bootlin.com>,
Vignesh Raghavendra <vigneshr@ti.com>
Cc: Richard Weinberger <richard@nod.at>,
Maxim Anisimov <maxim.anisimov.ua@gmail.com>,
linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org,
Aleksei Sviridkin <f@lex.la>
Subject: [PATCH] mtd: spinand: add support for HeYangTek HYF1GQ4UDACAE
Date: Tue, 16 Jun 2026 16:28:44 +0300 [thread overview]
Message-ID: <20260616132844.43994-1-f@lex.la> (raw)
The HeYangTek HYF1GQ4UDACAE is a 1 Gbit (128 MiB) SLC SPI-NAND with
2048 + 64 byte pages and on-die 4-bit / 512-byte ECC; its JEDEC
manufacturer ID is 0xc9. The die is GD5F1GQ4-compatible, so the OOB
layout is taken from the in-tree gd5fxgq4xa. The die exposes only a
coarse 2-bit ECC status with no fine-grained bitflip-count register, so
the status is decoded into a representative number of corrected bitflips.
It is found, among others, on some Keenetic KN-3411 (Buddy 6) units.
Datasheet:
https://www.heyangtek.cn/previewfile.jsp?file=ABUIABA9GAAgwsvRnwYo-eDpsgc
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
This revives Maxim Anisimov's 2024 submission [1], which stalled on the
review feedback. Changes since then:
- The OOB layout no longer leaves undefined bytes: it is taken verbatim
from the in-tree gd5fxgq4xa, which the die is compatible with (BBM in
byte 0, ECC parity in bytes 8..15 of each 16-byte section, the rest
exposed as free).
- The ECC status decoding is documented and uses '/' instead of a
shift. The die exposes only the coarse 2-bit status with no register
for the exact bitflip count, so the below-threshold code reports
strength/2 and the refresh-recommended code reports the full strength.
- The datasheet link is added to the commit message.
Tested on a Keenetic KN-3411 (Buddy 6): the chip is detected and a UBIFS
rootfs mounts and runs on it. nandbiterrs is not applicable here -- this
is an on-die-ECC part with NOP=1, so the in-place raw page reprogram that
nandbiterrs relies on corrupts the page independently of this driver.
[1] https://lore.kernel.org/linux-mtd/20240624061246.5292-1-maxim.anisimov.ua@gmail.com/
drivers/mtd/nand/spi/Makefile | 2 +-
drivers/mtd/nand/spi/core.c | 1 +
drivers/mtd/nand/spi/heyangtek.c | 132 +++++++++++++++++++++++++++++++
include/linux/mtd/spinand.h | 1 +
4 files changed, 135 insertions(+), 1 deletion(-)
create mode 100644 drivers/mtd/nand/spi/heyangtek.c
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index a47bd22cd..b5ccb4486 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 otp.o
-spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o
+spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o heyangtek.o
spinand-objs += macronix.o micron.o paragon.o skyhigh.o 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 0b076790b..0cdd4a62e 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1336,6 +1336,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
&fmsh_spinand_manufacturer,
&foresee_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
+ &heyangtek_spinand_manufacturer,
¯onix_spinand_manufacturer,
µn_spinand_manufacturer,
¶gon_spinand_manufacturer,
diff --git a/drivers/mtd/nand/spi/heyangtek.c b/drivers/mtd/nand/spi/heyangtek.c
new file mode 100644
index 000000000..7fc50fd3d
--- /dev/null
+++ b/drivers/mtd/nand/spi/heyangtek.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Authors:
+ * Andrey Zolotarev <andrey.zolotarev@keenetic.com> - the main driver logic
+ * Aleksei Sviridkin <f@lex.la> - adaptation to the mainline Linux kernel
+ *
+ * Based on:
+ * https://github.com/keenetic/kernel-49/commit/bacade569fb12bc0ad31ba09bca9b890118fbca7
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+
+#define SPINAND_MFR_HEYANGTEK 0xc9
+
+#define HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS (3 << 4)
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+/*
+ * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken
+ * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each
+ * 16-byte section, the bad block marker sits in byte 0 and the remaining
+ * bytes are exposed as free.
+ */
+static int hyf1gq4_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = (16 * section) + 8;
+ region->length = 8;
+
+ return 0;
+}
+
+static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ if (section) {
+ region->offset = 16 * section;
+ region->length = 8;
+ } else {
+ /* section 0 has one byte reserved for the bad block marker */
+ region->offset = 1;
+ region->length = 7;
+ }
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops hyf1gq4_ooblayout = {
+ .ecc = hyf1gq4_ooblayout_ecc,
+ .free = hyf1gq4_ooblayout_free,
+};
+
+static int hyf1gq4_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ case STATUS_ECC_HAS_BITFLIPS:
+ /*
+ * The die exposes only a coarse 2-bit ECC status and has no
+ * register for the exact bitflip count. This code means
+ * "corrected, below the refresh threshold", so report half of
+ * the ECC strength as a representative value.
+ */
+ return nanddev_get_ecc_conf(nand)->strength / 2;
+
+ case HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS:
+ /*
+ * "Corrected, refresh recommended": report the full ECC
+ * strength so the upper layers relocate the data.
+ */
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct spinand_info heyangtek_spinand_table[] = {
+ SPINAND_INFO("HYF1GQ4UDACAE",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x21),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(4, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&hyf1gq4_ooblayout,
+ hyf1gq4_ecc_get_status)),
+};
+
+static const struct spinand_manufacturer_ops heyangtek_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer heyangtek_spinand_manufacturer = {
+ .id = SPINAND_MFR_HEYANGTEK,
+ .name = "HeYangTek",
+ .chips = heyangtek_spinand_table,
+ .nchips = ARRAY_SIZE(heyangtek_spinand_table),
+ .ops = &heyangtek_spinand_manuf_ops,
+};
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 782984ba3..48357e6ff 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -437,6 +437,7 @@ extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
extern const struct spinand_manufacturer fmsh_spinand_manufacturer;
extern const struct spinand_manufacturer foresee_spinand_manufacturer;
extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
+extern const struct spinand_manufacturer heyangtek_spinand_manufacturer;
extern const struct spinand_manufacturer macronix_spinand_manufacturer;
extern const struct spinand_manufacturer micron_spinand_manufacturer;
extern const struct spinand_manufacturer paragon_spinand_manufacturer;
--
2.39.5
next reply other threads:[~2026-06-16 13:29 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-16 13:28 Aleksei Sviridkin [this message]
2026-06-16 13:28 ` [PATCH] mtd: spinand: add support for HeYangTek HYF1GQ4UDACAE Aleksei Sviridkin
2026-06-29 14:48 ` Miquel Raynal
2026-06-29 14:48 ` Miquel Raynal
-- strict thread matches above, loose matches on Subject: below --
2024-06-24 6:12 [PATCH] mtd: spinand: Add " Maxim Anisimov
2024-06-24 6:12 ` Maxim Anisimov
2024-07-01 7:59 ` Miquel Raynal
2024-07-01 7:59 ` Miquel Raynal
2024-07-22 6:54 ` Maxim Anisimov
2024-07-22 6:54 ` Maxim Anisimov
2024-08-23 15:47 ` Miquel Raynal
2024-08-23 15:47 ` Miquel Raynal
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260616132844.43994-1-f@lex.la \
--to=f@lex.la \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mtd@lists.infradead.org \
--cc=maxim.anisimov.ua@gmail.com \
--cc=miquel.raynal@bootlin.com \
--cc=richard@nod.at \
--cc=vigneshr@ti.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.