Merge tag 'mtd/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux

Pull mtd updates from Miquel Raynal:
 "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 a high load of misc fixes and
  hardening changes, and exceptionally no SPI NOR change"

* tag 'mtd/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (30 commits)
  mtd: rawnand: sunxi: fix H6/H616 controller timings
  mtd: rawnand: sunxi: describe tADL and tWHR delays
  mtd: rawnand: sunxi: group controller delay tables
  mtd: maps: remove dead select of MTD_CFI_BE_BYTE_SWAP
  mtd: rawnand: gpmi: add debugfs entry for BCH geometry
  mtd: rawnand: validate ONFI extended parameter page sections
  mtd: rawnand: sunxi: add H616 MBUS DMA support
  mtd: spinand: fmsh: fix FM25G01B/FM25G02B Quad I/O read dummy cycles
  mtd: part: reject MTDPART_OFS_RETAIN in mtd_add_partition()
  mtd: mpc5121_nfc: use platform for irq and ioremap
  mtd: mtdoops: free page bitmap when the backing MTD is removed
  mtd: mtdswap: Avoid freeing registered blktrans device twice
  mtd: afs: validate v2 image info bounds
  mtd: intel-dg: Fix runtime PM error path in probe
  mtd: nand-omap2: Move omap_nand_ids[] to raw nand driver
  mtd: rawnand: add Toshiba TC58NVG1S3H
  mtd: nand: realtek-ecc: add missing MODULE_DEVICE_TABLE()
  mtd: rawnand: qcom: Add MDM9607 compatible
  mtd: rawnand: qcom: Make has_onfi_read_op separate from qpic_version2
  mtd: rawnand: qcom: Make "aon" clock optional
  ...
This commit is contained in:
Linus Torvalds
2026-08-23 09:11:21 -07:00
30 changed files with 609 additions and 108 deletions

View File

@@ -22,17 +22,20 @@ properties:
- qcom,ipq4019-nand
- qcom,ipq6018-nand
- qcom,ipq8074-nand
- qcom,mdm9607-nand
- qcom,sdx55-nand
reg:
maxItems: 1
clocks:
minItems: 1
items:
- description: Core Clock
- description: Always ON Clock
clock-names:
minItems: 1
items:
- const: core
- const: aon
@@ -101,6 +104,27 @@ allOf:
items:
- const: rxtx
# On MDM9607, the OS can only control a single clock.
# The 3 hardware clocks (core, aon, ahb) are invisible to the OS.
- if:
properties:
compatible:
contains:
enum:
- qcom,mdm9607-nand
then:
properties:
clocks:
maxItems: 1
clock-names:
maxItems: 1
else:
properties:
clocks:
minItems: 2
clock-names:
minItems: 2
- if:
properties:
compatible:
@@ -121,6 +145,7 @@ allOf:
- qcom,ipq4019-nand
- qcom,ipq6018-nand
- qcom,ipq8074-nand
- qcom,mdm9607-nand
- qcom,sdx55-nand
then:

View File

@@ -2129,6 +2129,13 @@ static void __maybe_unused gpmc_read_timings_dt(struct device_node *np,
of_property_read_bool(np, "gpmc,time-para-granularity");
}
static int gpmc_child_is_nand(struct device_node *child)
{
/* This has to match drivers/mtd/nand/raw/omap2.c's omap_nand_ids[] */
return of_device_is_compatible(child, "ti,omap2-nand") ||
of_device_is_compatible(child, "ti,am64-nand");
}
/**
* gpmc_probe_generic_child - configures the gpmc for a child device
* @pdev: pointer to gpmc platform device
@@ -2220,7 +2227,7 @@ static int gpmc_probe_generic_child(struct platform_device *pdev,
goto err;
}
if (of_match_node(omap_nand_ids, child)) {
if (gpmc_child_is_nand(child)) {
/* NAND specific setup */
val = 8;
of_property_read_u32(child, "nand-bus-width", &val);

View File

@@ -2565,6 +2565,12 @@ static int cfi_intelext_suspend(struct mtd_info *mtd)
ret = -EAGAIN;
break;
case FL_PM_SUSPENDED:
case FL_SHUTDOWN:
/*
* Already suspended, or put into array mode by the
* reboot notifier ahead of an imminent power-off.
* Either way there is nothing to do.
*/
break;
}
mutex_unlock(&chip->mutex);

View File

@@ -780,7 +780,7 @@ static int intel_dg_mtd_probe(struct auxiliary_device *aux_dev,
dev_name(&aux_dev->dev), invm->regions[i].name);
if (!name) {
ret = -ENOMEM;
goto err;
goto err_norpm;
}
nvm->regions[n].name = name;

View File

@@ -101,7 +101,6 @@ config MTD_PHYSMAP_IXP4XX
depends on MTD_PHYSMAP_OF
depends on ARM
select MTD_COMPLEX_MAPPINGS
select MTD_CFI_BE_BYTE_SWAP if CPU_BIG_ENDIAN
default ARCH_IXP4XX
help
This provides some extra DT physmap parsing for the Intel IXP4xx

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Out-of-line map I/O functions for simple maps when CONFIG_COMPLEX_MAPPINGS
* Out-of-line map I/O functions for simple maps when CONFIG_MTD_COMPLEX_MAPPINGS
* is enabled.
*/

View File

@@ -392,6 +392,9 @@ static void mtdoops_notify_remove(struct mtd_info *mtd)
cxt->mtd = NULL;
flush_work(&cxt->work_erase);
flush_work(&cxt->work_write);
vfree(cxt->oops_page_used);
cxt->oops_page_used = NULL;
cxt->oops_pages = 0;
}

View File

@@ -258,7 +258,8 @@ int mtd_add_partition(struct mtd_info *parent, const char *name,
/* the direct offset is expected */
if (offset == MTDPART_OFS_APPEND ||
offset == MTDPART_OFS_NXTBLK)
offset == MTDPART_OFS_NXTBLK ||
offset == MTDPART_OFS_RETAIN)
return -EINVAL;
if (length == MTDPART_SIZ_FULL)

View File

@@ -1452,6 +1452,7 @@ static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
debugfs_failed:
del_mtd_blktrans_dev(mbd_dev);
mbd_dev = NULL;
cleanup:
mtdswap_cleanup(d);

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 */

View File

@@ -23,7 +23,6 @@
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <asm/mpc5121.h>
@@ -618,14 +617,14 @@ static int mpc5121_nfc_probe(struct platform_device *op)
struct clk *clk;
struct device *dev = &op->dev;
struct mpc5121_nfc_prv *prv;
struct resource res;
struct mtd_info *mtd;
struct nand_chip *chip;
unsigned long regs_paddr, regs_size;
const __be32 *chips_no;
void __iomem *regs;
int resettime = 0;
int retval = 0;
int rev, len;
int irq;
/*
* Check SoC revision. This driver supports only NFC
@@ -637,6 +636,14 @@ static int mpc5121_nfc_probe(struct platform_device *op)
return -ENXIO;
}
regs = devm_platform_ioremap_resource(op, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
irq = platform_get_irq(op, 0);
if (irq < 0)
return irq;
prv = devm_kzalloc(dev, sizeof(*prv), GFP_KERNEL);
if (!prv)
return -ENOMEM;
@@ -660,17 +667,7 @@ static int mpc5121_nfc_probe(struct platform_device *op)
return retval;
}
prv->irq = irq_of_parse_and_map(dn, 0);
if (!prv->irq) {
dev_err(dev, "Error mapping IRQ!\n");
return -EINVAL;
}
retval = of_address_to_resource(dn, 0, &res);
if (retval) {
dev_err(dev, "Error parsing memory region!\n");
return retval;
}
prv->irq = irq;
chips_no = of_get_property(dn, "chips", &len);
if (!chips_no || len != sizeof(*chips_no)) {
@@ -678,19 +675,7 @@ static int mpc5121_nfc_probe(struct platform_device *op)
return -EINVAL;
}
regs_paddr = res.start;
regs_size = resource_size(&res);
if (!devm_request_mem_region(dev, regs_paddr, regs_size, DRV_NAME)) {
dev_err(dev, "Error requesting memory region!\n");
return -EBUSY;
}
prv->regs = devm_ioremap(dev, regs_paddr, regs_size);
if (!prv->regs) {
dev_err(dev, "Error mapping memory region!\n");
return -ENOMEM;
}
prv->regs = regs;
mtd->name = "MPC5121 NAND";
chip->legacy.dev_ready = mpc5121_nfc_dev_ready;

View File

@@ -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) },

View File

@@ -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");

View File

@@ -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)))

View File

@@ -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 = {

View File

@@ -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;
}

View File

@@ -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,
@@ -2411,6 +2421,10 @@ static const struct of_device_id qcom_nandc_of_match[] = {
.compatible = "qcom,ipq8074-nand",
.data = &ipq8074_nandc_props,
},
{
.compatible = "qcom,mdm9607-nand",
.data = &mdm9607_nandc_props,
},
{
.compatible = "qcom,sdx55-nand",
.data = &sdx55_nandc_props,

View File

@@ -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->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 * 32))
min_clk_period = DIV_ROUND_UP(timings->tADL_min, 32);
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 * 32))
min_clk_period = DIV_ROUND_UP(timings->tWHR_min, 32);
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 * 20))
min_clk_period = DIV_ROUND_UP(timings->tRHW_min, 20);
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[] = {

View File

@@ -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

View File

@@ -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,

View File

@@ -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),

View File

@@ -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,
};

View File

@@ -235,6 +235,9 @@ static int afs_parse_v2_partition(struct mtd_info *mtd,
pr_debug("Parsing v2 partition @%08x-%08x\n",
off, off + mtd->erasesize);
if (mtd->erasesize < sizeof(footer))
return -EINVAL;
/* First read the footer */
ptr = off + mtd->erasesize - sizeof(footer);
ret = mtd_read(mtd, ptr, sizeof(footer), &sz, (u_char *)footer);
@@ -245,6 +248,8 @@ static int afs_parse_v2_partition(struct mtd_info *mtd,
}
name = (char *) &footer[0];
version = footer[9];
if (footer[8] > mtd->erasesize - sizeof(footer))
return -EINVAL;
ptr = off + mtd->erasesize - sizeof(footer) - footer[8];
pr_debug("found image \"%s\", version %08x, info @%08x\n",
@@ -278,6 +283,8 @@ static int afs_parse_v2_partition(struct mtd_info *mtd,
entrypoint = imginfo[pad];
attributes = imginfo[pad+1];
region_count = imginfo[pad+2];
if (region_count > (ARRAY_SIZE(imginfo) - pad - 3) / 4)
return -EINVAL;
block_start = imginfo[20];
block_end = imginfo[21];

View File

@@ -192,6 +192,7 @@ static int parse_redboot_partitions(struct mtd_info *master,
for (i = 0; i < numslots; i++) {
struct fis_list *new_fl, **prev;
size_t name_len;
if (buf[i].name[0] == 0xff) {
if (buf[i].name[1] == 0xff) {
@@ -203,8 +204,14 @@ static int parse_redboot_partitions(struct mtd_info *master,
if (!redboot_checksum(&buf[i]))
break;
name_len = strnlen(buf[i].name, sizeof(buf[i].name));
if (name_len == sizeof(buf[i].name)) {
ret = -EINVAL;
goto out;
}
new_fl = kmalloc_obj(struct fis_list);
namelen += strlen(buf[i].name) + 1;
namelen += name_len + 1;
if (!new_fl) {
ret = -ENOMEM;
goto out;

View File

@@ -443,6 +443,7 @@ struct qcom_nand_controller {
* @dev_cmd_reg_start - NAND_DEV_CMD_* registers starting offset
* @supports_bam - whether NAND controller is using BAM
* @nandc_part_of_qpic - whether NAND controller is part of qpic IP
* @has_onfi_read_op - whether ONFI param page read command is supported
* @qpic_version2 - flag to indicate QPIC IP version 2
* @use_codeword_fixup - whether NAND has different layout for boot partitions
*/
@@ -452,6 +453,7 @@ struct qcom_nandc_props {
u32 bam_offset;
bool supports_bam;
bool nandc_part_of_qpic;
bool has_onfi_read_op;
bool qpic_version2;
bool use_codeword_fixup;
};

View File

@@ -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;

View File

@@ -7,7 +7,6 @@
#define _MTD_NAND_OMAP2_H
#include <linux/mtd/partitions.h>
#include <linux/mod_devicetable.h>
#define GPMC_BCH_NUM_REMAINDER 8
@@ -63,10 +62,4 @@ struct gpmc_nand_regs {
void __iomem *gpmc_bch_result6[GPMC_BCH_NUM_REMAINDER];
};
static const struct of_device_id omap_nand_ids[] = {
{ .compatible = "ti,omap2-nand", },
{ .compatible = "ti,am64-nand", },
{},
};
#endif /* _MTD_NAND_OMAP2_H */