summaryrefslogtreecommitdiff
path: root/drivers/mtd/nand
diff options
context:
space:
mode:
authorMiquel Raynal <miquel.raynal@bootlin.com>2026-08-23 14:30:31 +0200
committerMiquel Raynal <miquel.raynal@bootlin.com>2026-08-23 14:30:31 +0200
commit9b7e2fe0fe37f79e35467929d578c8c9f0f9cfdd (patch)
tree891095842d3f57bcde733a21c77e7d1269ff4ee8 /drivers/mtd/nand
parent94d32f1ace8ee3ef3537ac6c8e71b76a418a4762 (diff)
parent15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f (diff)
Merge tag 'nand/for-7.3' into mtd/next
* Raw NAND changes - Sunxi: Support added for the H616 compatible - Qcom: Support added for the MDM9607 compatible - Support for the Toshiba TC58NVG1S3H part - GPMI: New debugfs entry to expose the chip geometry - PL353: Timing updates and software ECC support have been fixed * SPI NAND changes - fmsh: Support added for FM25G{01,02}B chips - HeYangTek: Support added for HYF1GQ4UDACAE Aside from these main changes, there is the usual load of misc fixes and hardening changes.
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r--drivers/mtd/nand/ecc-realtek.c1
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c3
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c29
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h6
-rw-r--r--drivers/mtd/nand/raw/nand_ids.c3
-rw-r--r--drivers/mtd/nand/raw/nand_onfi.c27
-rw-r--r--drivers/mtd/nand/raw/nand_toshiba.c4
-rw-r--r--drivers/mtd/nand/raw/omap2.c6
-rw-r--r--drivers/mtd/nand/raw/pl35x-nand-controller.c20
-rw-r--r--drivers/mtd/nand/raw/qcom_nandc.c30
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c239
-rw-r--r--drivers/mtd/nand/spi/Makefile2
-rw-r--r--drivers/mtd/nand/spi/core.c1
-rw-r--r--drivers/mtd/nand/spi/fmsh.c103
-rw-r--r--drivers/mtd/nand/spi/heyangtek.c132
15 files changed, 535 insertions, 71 deletions
diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c
index 7d003fd72027..76d75e2107df 100644
--- a/drivers/mtd/nand/ecc-realtek.c
+++ b/drivers/mtd/nand/ecc-realtek.c
@@ -450,6 +450,7 @@ static const struct of_device_id rtl_ecc_of_ids[] = {
},
{ /* sentinel */ },
};
+MODULE_DEVICE_TABLE(of, rtl_ecc_of_ids);
static struct platform_driver rtl_ecc_driver = {
.driver = {
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index e7fdf532c5fe..8a19408abb63 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -1799,8 +1799,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
* Legacy bindings only allow connecting a single NAND with a unique CS
* line to the controller.
*/
- nand = devm_kzalloc(nc->dev, sizeof(*nand) + sizeof(*nand->cs),
- GFP_KERNEL);
+ nand = devm_kzalloc(nc->dev, struct_size(nand, cs, 1), GFP_KERNEL);
if (!nand)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index c1f766cb225a..527165ccc839 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -7,6 +7,7 @@
*/
#include <linux/cleanup.h>
#include <linux/clk.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched/task_stack.h>
@@ -732,6 +733,31 @@ static int common_nfc_set_geometry(struct gpmi_nand_data *this)
return err;
}
+#ifdef CONFIG_DEBUG_FS
+static void bch_remove_debugfs(void *data)
+{
+ struct gpmi_nand_data *this = data;
+
+ debugfs_remove_recursive(this->dbg_root);
+ this->dbg_root = NULL;
+}
+
+static void bch_create_debugfs(struct gpmi_nand_data *this)
+{
+ struct bch_geometry *bch_geo = &this->bch_geometry;
+
+ this->dbg_root = debugfs_create_dir("gpmi-nand", NULL);
+ this->dbg_bch_geo.data = (void *)bch_geo;
+ this->dbg_bch_geo.size = sizeof(struct bch_geometry);
+ this->raw_mode = true;
+ debugfs_create_blob("bch_geometry", 0444, this->dbg_root, &this->dbg_bch_geo);
+ debugfs_create_bool("raw_mode", 0444, this->dbg_root, &this->raw_mode);
+ devm_add_action_or_reset(this->dev, bch_remove_debugfs, this);
+}
+#else
+static void bch_create_debugfs(struct gpmi_nand_data *this) {}
+#endif /* CONFIG_DEBUG_FS */
+
/* Configures the geometry for BCH. */
static int bch_set_geometry(struct gpmi_nand_data *this)
{
@@ -2282,6 +2308,9 @@ static int gpmi_init_last(struct gpmi_nand_data *this)
if (ret)
return ret;
+ /* save BCH geometry to debugfs if CONFIG_DEBUG_FS is enabled */
+ bch_create_debugfs(this);
+
/* Init the nand_ecc_ctrl{} */
ecc->read_page = gpmi_ecc_read_page;
ecc->write_page = gpmi_ecc_write_page;
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h
index 3e9bc985e44a..80c32efbaef8 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h
@@ -161,6 +161,12 @@ struct gpmi_nand_data {
#define DMA_CHANS 8
struct dma_chan *dma_chans[DMA_CHANS];
struct completion dma_done;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dbg_root;
+ struct debugfs_blob_wrapper dbg_bch_geo;
+ bool raw_mode;
+#endif
};
/* BCH : Status Block Completion Codes */
diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c
index 62a8cf86d9e2..08707ce9d7c0 100644
--- a/drivers/mtd/nand/raw/nand_ids.c
+++ b/drivers/mtd/nand/raw/nand_ids.c
@@ -29,6 +29,9 @@ struct nand_flash_dev nand_flash_ids[] = {
{"TC58NVG0S3E 1G 3.3V 8-bit",
{ .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} },
SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), },
+ {"TC58NVG1S3H 2G 3.3V 8-bit",
+ { .id = {0x98, 0xda, 0x90, 0x15, 0x76} },
+ SZ_2K, SZ_256, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) },
{"TC58NVG2S0F 4G 3.3V 8-bit",
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c
index cd3ad373883e..b5e304c35738 100644
--- a/drivers/mtd/nand/raw/nand_onfi.c
+++ b/drivers/mtd/nand/raw/nand_onfi.c
@@ -35,16 +35,21 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
struct nand_onfi_params *p)
{
struct nand_device *base = &chip->base;
+ struct mtd_info *mtd = nand_to_mtd(chip);
struct nand_ecc_props requirements;
struct onfi_ext_param_page *ep;
struct onfi_ext_section *s;
struct onfi_ext_ecc_info *ecc;
+ size_t remaining, section_len;
uint8_t *cursor;
int ret;
int len;
int i;
len = le16_to_cpu(p->ext_param_page_length) * 16;
+ if (len < sizeof(*ep))
+ return -EINVAL;
+
ep = kmalloc(len, GFP_KERNEL);
if (!ep)
return -ENOMEM;
@@ -77,11 +82,29 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
/* find the ECC section. */
cursor = (uint8_t *)(ep + 1);
+ remaining = len - sizeof(*ep);
for (i = 0; i < ONFI_EXT_SECTION_MAX; i++) {
s = ep->sections + i;
- if (s->type == ONFI_SECTION_TYPE_2)
+ section_len = s->length * 16;
+ if (section_len > remaining) {
+ dev_dbg(&mtd->dev,
+ "ONFI extended parameter section %d exceeds page\n",
+ i);
+ goto ext_out;
+ }
+
+ if (s->type == ONFI_SECTION_TYPE_2) {
+ if (section_len < sizeof(*ecc)) {
+ dev_dbg(&mtd->dev,
+ "ONFI extended parameter ECC section %d is too short\n",
+ i);
+ goto ext_out;
+ }
break;
- cursor += s->length * 16;
+ }
+
+ cursor += section_len;
+ remaining -= section_len;
}
if (i == ONFI_EXT_SECTION_MAX) {
pr_debug("We can not find the ECC section.\n");
diff --git a/drivers/mtd/nand/raw/nand_toshiba.c b/drivers/mtd/nand/raw/nand_toshiba.c
index d3d34d71921f..32062b26a1f6 100644
--- a/drivers/mtd/nand/raw/nand_toshiba.c
+++ b/drivers/mtd/nand/raw/nand_toshiba.c
@@ -287,7 +287,9 @@ static int toshiba_nand_init(struct nand_chip *chip)
if (!strncmp("TC58NVG0S3E", chip->parameters.model,
sizeof("TC58NVG0S3E") - 1))
tc58nvg0s3e_init(chip);
- if ((!strncmp("TH58NVG2S3HBAI4", chip->parameters.model,
+ if ((!strncmp("TC58NVG1S3H", chip->parameters.model,
+ sizeof("TC58NVG1S3H") - 1)) ||
+ (!strncmp("TH58NVG2S3HBAI4", chip->parameters.model,
sizeof("TH58NVG2S3HBAI4") - 1)) ||
(!strncmp("TH58NVG3S0HBAI4", chip->parameters.model,
sizeof("TH58NVG3S0HBAI4") - 1)))
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index 39e297486721..552f36da79fc 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -2318,7 +2318,11 @@ static void omap_nand_remove(struct platform_device *pdev)
nand_cleanup(nand_chip);
}
-/* omap_nand_ids defined in linux/platform_data/mtd-nand-omap2.h */
+static const struct of_device_id omap_nand_ids[] = {
+ { .compatible = "ti,omap2-nand" },
+ { .compatible = "ti,am64-nand" },
+ { }
+};
MODULE_DEVICE_TABLE(of, omap_nand_ids);
static struct platform_driver omap_nand_driver = {
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 06f8f1e14b9c..bd89aaadd1b2 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -862,8 +862,11 @@ static int pl35x_nfc_setup_interface(struct nand_chip *chip, int cs,
PL35X_SMC_NAND_TAR_CYCLES(tmgs.t_ar) |
PL35X_SMC_NAND_TRR_CYCLES(tmgs.t_rr);
- writel(plnand->timings, nfc->conf_regs + PL35X_SMC_CYCLES);
- pl35x_smc_update_regs(nfc);
+ /*
+ * Reset nfc->selected_chip so the next command will cause the timing
+ * registers to be updated in ->*_select_target().
+ */
+ nfc->selected_chip = NULL;
return 0;
}
@@ -914,7 +917,6 @@ static int pl35x_nand_init_hw_ecc_controller(struct pl35x_nandc *nfc,
chip->ecc.steps = mtd->writesize / chip->ecc.size;
chip->ecc.read_page = pl35x_nand_read_page_hwecc;
chip->ecc.write_page = pl35x_nand_write_page_hwecc;
- chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
pl35x_smc_set_ecc_pg_size(nfc, chip, mtd->writesize);
nfc->ecc_buf = devm_kmalloc(nfc->dev, chip->ecc.bytes * chip->ecc.steps,
@@ -973,18 +975,19 @@ static int pl35x_nand_attach_chip(struct nand_chip *chip)
switch (chip->ecc.engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_DIE:
- dev_dbg(nfc->dev, "Using on-die ECC\n");
+ dev_dbg(nfc->dev, "Using on-die hardware ECC\n");
/* Keep these legacy BBT descriptors for ON_DIE situations */
chip->bbt_td = &bbt_main_descr;
chip->bbt_md = &bbt_mirror_descr;
fallthrough;
case NAND_ECC_ENGINE_TYPE_NONE:
+ dev_dbg(nfc->dev, "Using no ECC engine\n");
+ break;
case NAND_ECC_ENGINE_TYPE_SOFT:
- dev_dbg(nfc->dev, "Using software ECC (Hamming 1-bit/512B)\n");
- chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
+ dev_dbg(nfc->dev, "Using software ECC\n");
break;
case NAND_ECC_ENGINE_TYPE_ON_HOST:
- dev_dbg(nfc->dev, "Using hardware ECC\n");
+ dev_dbg(nfc->dev, "Using on-host hardware ECC\n");
ret = pl35x_nand_init_hw_ecc_controller(nfc, chip);
if (ret)
return ret;
@@ -995,6 +998,9 @@ static int pl35x_nand_attach_chip(struct nand_chip *chip)
return -EINVAL;
}
+ chip->ecc.read_page_raw = nand_monolithic_read_page_raw;
+ chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
+
return 0;
}
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index 4b80ce084d9a..d7642db2e2df 100644
--- a/drivers/mtd/nand/raw/qcom_nandc.c
+++ b/drivers/mtd/nand/raw/qcom_nandc.c
@@ -1564,7 +1564,7 @@ static int qcom_op_cmd_mapping(struct nand_chip *chip, u8 opcode,
cmd = OP_FETCH_ID;
break;
case NAND_CMD_PARAM:
- if (nandc->props->qpic_version2)
+ if (nandc->props->has_onfi_read_op)
cmd = OP_PAGE_READ_ONFI_READ;
else
cmd = OP_PAGE_READ;
@@ -1903,7 +1903,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
nandc->regs->ecc_buf_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE);
/* configure CMD1 and VLD for ONFI param probing in QPIC v1 */
- if (!nandc->props->qpic_version2) {
+ if (!nandc->props->has_onfi_read_op) {
nandc->regs->vld = cpu_to_le32((nandc->vld & ~READ_START_VLD));
nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~READ_ADDR_MASK) |
FIELD_PREP(READ_ADDR_MASK, NAND_CMD_PARAM));
@@ -1911,7 +1911,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
nandc->regs->exec = cpu_to_le32(1);
- if (!nandc->props->qpic_version2) {
+ if (!nandc->props->has_onfi_read_op) {
nandc->regs->orig_cmd1 = cpu_to_le32(nandc->cmd1);
nandc->regs->orig_vld = cpu_to_le32(nandc->vld);
}
@@ -1925,7 +1925,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
else
nandc_set_read_loc_first(chip, reg_base, 0, len, 1);
- if (!nandc->props->qpic_version2) {
+ if (!nandc->props->has_onfi_read_op) {
qcom_write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0);
qcom_write_reg_dma(nandc, &nandc->regs->cmd1, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
}
@@ -1939,7 +1939,7 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
nandc->buf_count, 0);
/* restore CMD1 and VLD regs */
- if (!nandc->props->qpic_version2) {
+ if (!nandc->props->has_onfi_read_op) {
qcom_write_reg_dma(nandc, &nandc->regs->orig_cmd1, NAND_DEV_CMD1_RESTORE, 1, 0);
qcom_write_reg_dma(nandc, &nandc->regs->orig_vld, NAND_DEV_CMD_VLD_RESTORE, 1,
NAND_BAM_NEXT_SGL);
@@ -2041,7 +2041,7 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
if (!nandc->props->nandc_part_of_qpic)
nandc_write(nandc, SFLASHC_BURST_CFG, 0);
- if (!nandc->props->qpic_version2)
+ if (!nandc->props->has_onfi_read_op)
nandc_write(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD),
NAND_DEV_CMD_VLD_VAL);
@@ -2063,7 +2063,7 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
}
/* save the original values of these registers */
- if (!nandc->props->qpic_version2) {
+ if (!nandc->props->has_onfi_read_op) {
nandc->cmd1 = nandc_read(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD1));
nandc->vld = NAND_DEV_CMD_VLD_VAL;
}
@@ -2280,7 +2280,7 @@ static int qcom_nandc_probe(struct platform_device *pdev)
if (IS_ERR(nandc->core_clk))
return PTR_ERR(nandc->core_clk);
- nandc->aon_clk = devm_clk_get(dev, "aon");
+ nandc->aon_clk = devm_clk_get_optional(dev, "aon");
if (IS_ERR(nandc->aon_clk))
return PTR_ERR(nandc->aon_clk);
@@ -2381,10 +2381,20 @@ static const struct qcom_nandc_props ipq8074_nandc_props = {
.bam_offset = 0x30000,
};
+static const struct qcom_nandc_props mdm9607_nandc_props = {
+ .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT),
+ .supports_bam = true,
+ .nandc_part_of_qpic = true,
+ .has_onfi_read_op = true,
+ .dev_cmd_reg_start = 0x7000,
+ .bam_offset = 0x30000,
+};
+
static const struct qcom_nandc_props sdx55_nandc_props = {
.ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT),
.supports_bam = true,
.nandc_part_of_qpic = true,
+ .has_onfi_read_op = true,
.qpic_version2 = true,
.dev_cmd_reg_start = 0x7000,
.bam_offset = 0x30000,
@@ -2412,6 +2422,10 @@ static const struct of_device_id qcom_nandc_of_match[] = {
.data = &ipq8074_nandc_props,
},
{
+ .compatible = "qcom,mdm9607-nand",
+ .data = &mdm9607_nandc_props,
+ },
+ {
.compatible = "qcom,sdx55-nand",
.data = &sdx55_nandc_props,
},
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 02647565c8ba..45ccbce91551 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -79,6 +79,12 @@
#define NFC_REG_H6_MDMA_BUF_ADDR 0x0210
#define NFC_REG_H6_MDMA_CNT 0x0214
+#define NFC_H6_MDMA_STA_DESC0_COMPLETE BIT(0)
+
+#define NFC_MDMA_DESC_LAST BIT(2)
+#define NFC_MDMA_DESC_FIRST BIT(3)
+#define NFC_MDMA_DESC_SIZE_MASK GENMASK(15, 0)
+
#define NFC_RAM0_BASE 0x0400
#define NFC_RAM1_BASE 0x0800
@@ -237,6 +243,18 @@ struct sunxi_nand_hw_ecc {
u32 ecc_ctl;
};
+#define SUNXI_NFC_TIMING_STEPS 4
+
+/* Delay arrays contain internal NDFC clock cycles for field values 0 to 3. */
+struct sunxi_nfc_timings {
+ /* Internal clock cycles used by T1-T4, T7 and T11. */
+ u8 setup_cycles;
+ s32 tWB[SUNXI_NFC_TIMING_STEPS];
+ s32 tADL[SUNXI_NFC_TIMING_STEPS];
+ s32 tWHR[SUNXI_NFC_TIMING_STEPS];
+ s32 tRHW[SUNXI_NFC_TIMING_STEPS];
+};
+
/**
* struct sunxi_nand_chip - stores NAND chip device related information
*
@@ -267,12 +285,19 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
return container_of(nand, struct sunxi_nand_chip, nand);
}
+struct sunxi_nfc_mdma_desc {
+ __le32 config;
+ __le32 size;
+ __le32 buf;
+ __le32 next;
+} __packed __aligned(4);
+
/*
* NAND Controller capabilities structure: stores NAND controller capabilities
* for distinction between compatible strings.
*
- * @has_mdma: Use mbus dma mode, otherwise general dma
- * through MBUS on A23/A33 needs extra configuration.
+ * @has_mdma: Use A23/A33-style MBUS DMA registers
+ * @has_mdma_desc: MBUS DMA uses H6-style descriptors
* @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks
* @has_ecc_clk: If the controller needs an ECC clock.
* @has_mbus_clk: If the controller needs a mbus clock.
@@ -301,9 +326,11 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
* bytes to write
* @nuser_data_tab: Size of @user_data_len_tab
* @sram_size: Size of the NAND controller SRAM
+ * @timings: Controller timing characteristics
*/
struct sunxi_nfc_caps {
bool has_mdma;
+ bool has_mdma_desc;
bool has_ecc_block_512;
bool has_ecc_clk;
bool has_mbus_clk;
@@ -327,6 +354,7 @@ struct sunxi_nfc_caps {
unsigned int nuser_data_tab;
unsigned int max_ecc_steps;
int sram_size;
+ const struct sunxi_nfc_timings *timings;
};
/**
@@ -346,6 +374,9 @@ struct sunxi_nfc_caps {
* controller
* @complete: a completion object used to wait for NAND controller events
* @dmac: the DMA channel attached to the NAND controller
+ * @use_mdma: use an internal MBUS DMA backend
+ * @mdma_desc: H6-style MBUS DMA descriptor
+ * @mdma_desc_dma: DMA address of @mdma_desc
* @caps: NAND Controller capabilities
*/
struct sunxi_nfc {
@@ -362,6 +393,9 @@ struct sunxi_nfc {
struct list_head chips;
struct completion complete;
struct dma_chan *dmac;
+ bool use_mdma;
+ struct sunxi_nfc_mdma_desc *mdma_desc;
+ dma_addr_t mdma_desc_dma;
const struct sunxi_nfc_caps *caps;
};
@@ -466,7 +500,10 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
{
struct dma_async_tx_descriptor *dmad;
enum dma_transfer_direction tdir;
+ dma_addr_t buf_dma;
dma_cookie_t dmat;
+ int len = chunksize * nchunks;
+ u32 data_blocks = nchunks;
int ret;
if (ddir == DMA_FROM_DEVICE)
@@ -474,12 +511,21 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
else
tdir = DMA_MEM_TO_DEV;
- sg_init_one(sg, buf, nchunks * chunksize);
+ sg_init_one(sg, buf, len);
ret = dma_map_sg(nfc->dev, sg, 1, ddir);
if (!ret)
return -ENOMEM;
- if (!nfc->caps->has_mdma) {
+ buf_dma = sg_dma_address(sg);
+
+ if (nfc->mdma_desc &&
+ (len > NFC_MDMA_DESC_SIZE_MASK || !IS_ALIGNED(len, 8) ||
+ !IS_ALIGNED(buf_dma, 4))) {
+ ret = -EINVAL;
+ goto err_unmap_buf;
+ }
+
+ if (!nfc->use_mdma) {
dmad = dmaengine_prep_slave_sg(nfc->dmac, sg, 1, tdir, DMA_CTRL_ACK);
if (!dmad) {
ret = -EINVAL;
@@ -489,14 +535,35 @@ static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RAM_METHOD,
nfc->regs + NFC_REG_CTL);
- writel(nchunks, nfc->regs + NFC_REG_SECTOR_NUM);
+
+ /* H6/H616 use one enable bit per ECC data block. */
+ if (nfc->caps->has_mdma_desc)
+ data_blocks = GENMASK(nchunks - 1, 0);
+ writel(data_blocks, nfc->regs + NFC_REG_SECTOR_NUM);
writel(chunksize, nfc->regs + NFC_REG_CNT);
- if (nfc->caps->has_mdma) {
+ if (nfc->use_mdma)
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_DMA_TYPE_NORMAL,
nfc->regs + NFC_REG_CTL);
- writel(chunksize * nchunks, nfc->regs + NFC_REG_MDMA_CNT);
- writel(sg_dma_address(sg), nfc->regs + NFC_REG_MDMA_ADDR);
+
+ if (nfc->mdma_desc) {
+ struct sunxi_nfc_mdma_desc *desc = nfc->mdma_desc;
+
+ desc->config = cpu_to_le32(NFC_MDMA_DESC_FIRST |
+ NFC_MDMA_DESC_LAST);
+ desc->size = cpu_to_le32(len);
+ /* Descriptor words are little-endian DMA memory, not MMIO. */
+ desc->buf = cpu_to_le32(buf_dma);
+ desc->next = cpu_to_le32(nfc->mdma_desc_dma);
+
+ writel(NFC_H6_MDMA_STA_DESC0_COMPLETE,
+ nfc->regs + NFC_REG_H6_MDMA_STA);
+ dma_wmb();
+ writel(nfc->mdma_desc_dma,
+ nfc->regs + NFC_REG_H6_MDMA_DLBA_REG);
+ } else if (nfc->caps->has_mdma) {
+ writel(len, nfc->regs + NFC_REG_MDMA_CNT);
+ writel(buf_dma, nfc->regs + NFC_REG_MDMA_ADDR);
} else {
dmat = dmaengine_submit(dmad);
@@ -525,6 +592,14 @@ static void sunxi_nfc_dma_op_cleanup(struct sunxi_nfc *nfc,
nfc->regs + NFC_REG_CTL);
}
+static void sunxi_nfc_dma_op_abort(struct sunxi_nfc *nfc)
+{
+ if (nfc->use_mdma)
+ sunxi_nfc_rst(nfc);
+ else
+ dmaengine_terminate_all(nfc->dmac);
+}
+
static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs)
{
struct mtd_info *mtd = nand_to_mtd(nand);
@@ -1202,7 +1277,7 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
wait = NFC_CMD_INT_FLAG;
- if (nfc->caps->has_mdma)
+ if (nfc->use_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
@@ -1211,8 +1286,8 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
- if (ret && !nfc->caps->has_mdma)
- dmaengine_terminate_all(nfc->dmac);
+ if (ret)
+ sunxi_nfc_dma_op_abort(nfc);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
@@ -1613,7 +1688,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
wait = NFC_CMD_INT_FLAG;
- if (nfc->caps->has_mdma)
+ if (nfc->use_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
@@ -1623,8 +1698,8 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false, 0);
- if (ret && !nfc->caps->has_mdma)
- dmaengine_terminate_all(nfc->dmac);
+ if (ret)
+ sunxi_nfc_dma_op_abort(nfc);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
@@ -1667,8 +1742,21 @@ static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
return nand_prog_page_end_op(nand);
}
-static const s32 tWB_lut[] = {6, 12, 16, 20};
-static const s32 tRHW_lut[] = {4, 8, 12, 20};
+static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = {
+ .setup_cycles = 1,
+ .tWB = { 6, 12, 16, 20 },
+ .tADL = { 7, 15, 23, 31 },
+ .tWHR = { 7, 15, 23, 31 },
+ .tRHW = { 4, 8, 12, 20 },
+};
+
+static const struct sunxi_nfc_timings sun50i_h616_nfc_timings = {
+ .setup_cycles = 2,
+ .tWB = { 28, 44, 60, 76 },
+ .tADL = { 0, 12, 28, 44 },
+ .tWHR = { 0, 12, 28, 44 },
+ .tRHW = { 8, 24, 40, 56 },
+};
static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration,
u32 clk_period)
@@ -1693,6 +1781,7 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+ const struct sunxi_nfc_timings *nfc_timings = nfc->caps->timings;
const struct nand_sdr_timings *timings;
u32 min_clk_period = 0;
s32 tWB, tADL, tWHR, tRHW, tCAD;
@@ -1703,20 +1792,28 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
return -ENOTSUPP;
/* T1 <=> tCLS */
- if (timings->tCLS_min > min_clk_period)
- min_clk_period = timings->tCLS_min;
+ if (timings->tCLS_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tCLS_min,
+ nfc_timings->setup_cycles);
/* T2 <=> tCLH */
- if (timings->tCLH_min > min_clk_period)
- min_clk_period = timings->tCLH_min;
+ if (timings->tCLH_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tCLH_min,
+ nfc_timings->setup_cycles);
/* T3 <=> tCS */
- if (timings->tCS_min > min_clk_period)
- min_clk_period = timings->tCS_min;
+ if (timings->tCS_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tCS_min,
+ nfc_timings->setup_cycles);
/* T4 <=> tCH */
- if (timings->tCH_min > min_clk_period)
- min_clk_period = timings->tCH_min;
+ if (timings->tCH_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tCH_min,
+ nfc_timings->setup_cycles);
/* T5 <=> tWP */
if (timings->tWP_min > min_clk_period)
@@ -1727,8 +1824,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
min_clk_period = timings->tWH_min;
/* T7 <=> tALS */
- if (timings->tALS_min > min_clk_period)
- min_clk_period = timings->tALS_min;
+ if (timings->tALS_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tALS_min,
+ nfc_timings->setup_cycles);
/* T8 <=> tDS */
if (timings->tDS_min > min_clk_period)
@@ -1743,8 +1842,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
min_clk_period = DIV_ROUND_UP(timings->tRR_min, 3);
/* T11 <=> tALH */
- if (timings->tALH_min > min_clk_period)
- min_clk_period = timings->tALH_min;
+ if (timings->tALH_min >
+ min_clk_period * nfc_timings->setup_cycles)
+ min_clk_period = DIV_ROUND_UP(timings->tALH_min,
+ nfc_timings->setup_cycles);
/* T12 <=> tRP */
if (timings->tRP_min > min_clk_period)
@@ -1763,17 +1864,25 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
min_clk_period = DIV_ROUND_UP(timings->tWC_min, 2);
/* T16 - T19 + tCAD */
- if (timings->tWB_max > (min_clk_period * 20))
- min_clk_period = DIV_ROUND_UP(timings->tWB_max, 20);
-
- if (timings->tADL_min > (min_clk_period * 32))
- min_clk_period = DIV_ROUND_UP(timings->tADL_min, 32);
-
- if (timings->tWHR_min > (min_clk_period * 32))
- min_clk_period = DIV_ROUND_UP(timings->tWHR_min, 32);
-
- if (timings->tRHW_min > (min_clk_period * 20))
- min_clk_period = DIV_ROUND_UP(timings->tRHW_min, 20);
+ if (timings->tWB_max >
+ (min_clk_period * nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]))
+ min_clk_period = DIV_ROUND_UP(timings->tWB_max,
+ nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]);
+
+ if (timings->tADL_min >
+ (min_clk_period * nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1]))
+ min_clk_period = DIV_ROUND_UP(timings->tADL_min,
+ nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1]);
+
+ if (timings->tWHR_min >
+ (min_clk_period * nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1]))
+ min_clk_period = DIV_ROUND_UP(timings->tWHR_min,
+ nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1]);
+
+ if (timings->tRHW_min >
+ (min_clk_period * nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]))
+ min_clk_period = DIV_ROUND_UP(timings->tRHW_min,
+ nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]);
/*
* In non-EDO, tREA should be less than tRP to guarantee that the
@@ -1789,26 +1898,28 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
if (timings->tREA_max > min_clk_period && !timings->tRLOH_min)
min_clk_period = timings->tREA_max;
- tWB = sunxi_nand_lookup_timing(tWB_lut, timings->tWB_max,
+ tWB = sunxi_nand_lookup_timing(nfc_timings->tWB, timings->tWB_max,
min_clk_period);
if (tWB < 0) {
dev_err(nfc->dev, "unsupported tWB\n");
return tWB;
}
- tADL = DIV_ROUND_UP(timings->tADL_min, min_clk_period) >> 3;
- if (tADL > 3) {
+ tADL = sunxi_nand_lookup_timing(nfc_timings->tADL,
+ timings->tADL_min, min_clk_period);
+ if (tADL < 0) {
dev_err(nfc->dev, "unsupported tADL\n");
- return -EINVAL;
+ return tADL;
}
- tWHR = DIV_ROUND_UP(timings->tWHR_min, min_clk_period) >> 3;
- if (tWHR > 3) {
+ tWHR = sunxi_nand_lookup_timing(nfc_timings->tWHR,
+ timings->tWHR_min, min_clk_period);
+ if (tWHR < 0) {
dev_err(nfc->dev, "unsupported tWHR\n");
- return -EINVAL;
+ return tWHR;
}
- tRHW = sunxi_nand_lookup_timing(tRHW_lut, timings->tRHW_min,
+ tRHW = sunxi_nand_lookup_timing(nfc_timings->tRHW, timings->tRHW_min,
min_clk_period);
if (tRHW < 0) {
dev_err(nfc->dev, "unsupported tRHW\n");
@@ -2073,11 +2184,13 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->write_oob = sunxi_nfc_hw_ecc_write_oob;
mtd_set_ooblayout(mtd, &sunxi_nand_ooblayout_ops);
- if (nfc->dmac || nfc->caps->has_mdma) {
+ if (nfc->dmac || nfc->use_mdma) {
ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma;
ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma;
nand->options |= NAND_USES_DMA;
+ if (nfc->mdma_desc)
+ nand->buf_align = 4;
} else {
ecc->read_page = sunxi_nfc_hw_ecc_read_page;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage;
@@ -2426,8 +2539,32 @@ static int sunxi_nfc_dma_init(struct sunxi_nfc *nfc, struct resource *r)
{
int ret;
- if (nfc->caps->has_mdma)
+ if (nfc->caps->has_mdma_desc) {
+ ret = dma_set_mask_and_coherent(nfc->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_warn(nfc->dev,
+ "failed to set MBUS DMA mask, using PIO: %d\n",
+ ret);
+ return 0;
+ }
+
+ nfc->mdma_desc =
+ dmam_alloc_coherent(nfc->dev, sizeof(*nfc->mdma_desc),
+ &nfc->mdma_desc_dma, GFP_KERNEL);
+ if (!nfc->mdma_desc) {
+ dev_warn(nfc->dev,
+ "failed to allocate MBUS DMA descriptor, using PIO\n");
+ return 0;
+ }
+
+ nfc->use_mdma = true;
return 0;
+ }
+
+ if (nfc->caps->has_mdma) {
+ nfc->use_mdma = true;
+ return 0;
+ }
nfc->dmac = dma_request_chan(nfc->dev, "rxtx");
if (IS_ERR(nfc->dmac)) {
@@ -2595,6 +2732,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
.nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
.max_ecc_steps = 16,
.sram_size = 1024,
+ .timings = &sun4i_a10_nfc_timings,
};
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
@@ -2617,9 +2755,11 @@ static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
.nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
.max_ecc_steps = 16,
.sram_size = 1024,
+ .timings = &sun4i_a10_nfc_timings,
};
static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
+ .has_mdma_desc = true,
.has_ecc_clk = true,
.has_mbus_clk = true,
.reg_io_data = NFC_REG_A23_IO_DATA,
@@ -2641,6 +2781,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
.nuser_data_tab = ARRAY_SIZE(sunxi_user_data_len_h6),
.max_ecc_steps = 32,
.sram_size = 8192,
+ .timings = &sun50i_h616_nfc_timings,
};
static const struct of_device_id sunxi_nfc_ids[] = {
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index a47bd22cd309..b5ccb44860df 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 f86786344d52..35365b67dd8e 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1359,6 +1359,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
&fmsh_spinand_manufacturer,
&foresee_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
+ &heyangtek_spinand_manufacturer,
&macronix_spinand_manufacturer,
&micron_spinand_manufacturer,
&paragon_spinand_manufacturer,
diff --git a/drivers/mtd/nand/spi/fmsh.c b/drivers/mtd/nand/spi/fmsh.c
index f417955f7d1c..6352e9c9e502 100644
--- a/drivers/mtd/nand/spi/fmsh.c
+++ b/drivers/mtd/nand/spi/fmsh.c
@@ -9,6 +9,16 @@
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
+#define FM25G01B_STATUS_ECC_MASK (7 << 4)
+ #define FM25G01B_STATUS_ECC_NO_BITFLIPS (0 << 4)
+ #define FM25G01B_STATUS_ECC_1_3_BITFLIPS (1 << 4)
+ #define FM25G01B_STATUS_ECC_4_BITFLIPS (2 << 4)
+ #define FM25G01B_STATUS_ECC_5_BITFLIPS (3 << 4)
+ #define FM25G01B_STATUS_ECC_6_BITFLIPS (4 << 4)
+ #define FM25G01B_STATUS_ECC_7_BITFLIPS (5 << 4)
+ #define FM25G01B_STATUS_ECC_8_BITFLIPS (6 << 4)
+ #define FM25G01B_STATUS_ECC_UNCOR_ERROR (7 << 4)
+
#define FM25S01BI3_STATUS_ECC_MASK (7 << 4)
#define FM25S01BI3_STATUS_ECC_NO_BITFLIPS (0 << 4)
#define FM25S01BI3_STATUS_ECC_1_3_BITFLIPS (1 << 4)
@@ -34,6 +44,74 @@ 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));
+static SPINAND_OP_VARIANTS(fm25g_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 int fm25g01b_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 64;
+ region->length = 64;
+
+ return 0;
+}
+
+static int fm25g01b_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ /* reserve 2 bytes for the BBM */
+ region->offset = 2;
+ region->length = 62;
+
+ return 0;
+}
+
+static int fm25g01b_ecc_get_status(struct spinand_device *spinand,
+ u8 status)
+{
+ switch (status & FM25G01B_STATUS_ECC_MASK) {
+ case FM25G01B_STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case FM25G01B_STATUS_ECC_1_3_BITFLIPS:
+ return 3;
+
+ case FM25G01B_STATUS_ECC_4_BITFLIPS:
+ return 4;
+
+ case FM25G01B_STATUS_ECC_5_BITFLIPS:
+ return 5;
+
+ case FM25G01B_STATUS_ECC_6_BITFLIPS:
+ return 6;
+
+ case FM25G01B_STATUS_ECC_7_BITFLIPS:
+ return 7;
+
+ case FM25G01B_STATUS_ECC_8_BITFLIPS:
+ return 8;
+
+ case FM25G01B_STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
@@ -102,6 +180,11 @@ static int fm25s01bi3_ooblayout_free(struct mtd_info *mtd, int section,
return 0;
}
+static const struct mtd_ooblayout_ops fm25g01b_ooblayout = {
+ .ecc = fm25g01b_ooblayout_ecc,
+ .free = fm25g01b_ooblayout_free,
+};
+
static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
.ecc = fm25s01a_ooblayout_ecc,
.free = fm25s01a_ooblayout_free,
@@ -113,6 +196,26 @@ static const struct mtd_ooblayout_ops fm25s01bi3_ooblayout = {
};
static const struct spinand_info fmsh_spinand_table[] = {
+ SPINAND_INFO("FM25G01B",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd1),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1),
+ NAND_ECCREQ(8, 528),
+ SPINAND_INFO_OP_VARIANTS(&fm25g_read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&fm25g01b_ooblayout,
+ fm25g01b_ecc_get_status)),
+ SPINAND_INFO("FM25G02B",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd2),
+ NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1),
+ NAND_ECCREQ(8, 528),
+ SPINAND_INFO_OP_VARIANTS(&fm25g_read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&fm25g01b_ooblayout,
+ fm25g01b_ecc_get_status)),
SPINAND_INFO("FM25S01A",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
diff --git a/drivers/mtd/nand/spi/heyangtek.c b/drivers/mtd/nand/spi/heyangtek.c
new file mode 100644
index 000000000000..7fc50fd3de08
--- /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,
+};