diff options
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/devices/mchp48l640.c | 9 | ||||
-rw-r--r-- | drivers/mtd/mtdcore.c | 14 | ||||
-rw-r--r-- | drivers/mtd/mtdpart.c | 3 | ||||
-rw-r--r-- | drivers/mtd/mtdpstore.c | 12 | ||||
-rw-r--r-- | drivers/mtd/nand/Makefile | 4 | ||||
-rw-r--r-- | drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 | ||||
-rw-r--r-- | drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 5 | ||||
-rw-r--r-- | drivers/mtd/nand/raw/nand_base.c | 4 | ||||
-rw-r--r-- | drivers/mtd/nand/raw/qcom_nandc.c | 36 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/Makefile | 3 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/core.c | 85 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/esmt.c | 90 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/macronix.c | 79 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/micron.c | 135 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/otp.c | 362 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/core.c | 77 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/macronix.c | 31 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/otp.c | 1 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/swp.c | 1 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/winbond.c | 88 | ||||
-rw-r--r-- | drivers/mtd/ubi/block.c | 2 |
21 files changed, 922 insertions, 121 deletions
diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c index 7584d0ba9396..4af9208f9690 100644 --- a/drivers/mtd/devices/mchp48l640.c +++ b/drivers/mtd/devices/mchp48l640.c @@ -23,6 +23,7 @@ #include <linux/spi/flash.h> #include <linux/spi/spi.h> #include <linux/of.h> +#include <linux/string_choices.h> struct mchp48_caps { unsigned int size; @@ -128,11 +129,11 @@ static int mchp48l640_write_prepare(struct mchp48l640_flash *flash, bool enable) mutex_unlock(&flash->lock); if (ret) - dev_err(&flash->spi->dev, "write %sable failed ret: %d", - (enable ? "en" : "dis"), ret); + dev_err(&flash->spi->dev, "write %s failed ret: %d", + str_enable_disable(enable), ret); - dev_dbg(&flash->spi->dev, "write %sable success ret: %d", - (enable ? "en" : "dis"), ret); + dev_dbg(&flash->spi->dev, "write %s success ret: %d", + str_enable_disable(enable), ret); if (enable) return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, true); diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 724f917f91ba..5ba9a741f5ac 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -741,7 +741,9 @@ int add_mtd_device(struct mtd_info *mtd) mtd->dev.type = &mtd_devtype; mtd->dev.class = &mtd_class; mtd->dev.devt = MTD_DEVT(i); - dev_set_name(&mtd->dev, "mtd%d", i); + error = dev_set_name(&mtd->dev, "mtd%d", i); + if (error) + goto fail_devname; dev_set_drvdata(&mtd->dev, mtd); mtd_check_of_node(mtd); of_node_get(mtd_get_of_node(mtd)); @@ -790,6 +792,7 @@ fail_nvmem_add: device_unregister(&mtd->dev); fail_added: of_node_put(mtd_get_of_node(mtd)); +fail_devname: idr_remove(&mtd_idr, i); fail_locked: mutex_unlock(&mtd_table_mutex); @@ -1053,7 +1056,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types, const struct mtd_partition *parts, int nr_parts) { - int ret; + int ret, err; mtd_set_dev_defaults(mtd); @@ -1105,8 +1108,11 @@ out: nvmem_unregister(mtd->otp_factory_nvmem); } - if (ret && device_is_registered(&mtd->dev)) - del_mtd_device(mtd); + if (ret && device_is_registered(&mtd->dev)) { + err = del_mtd_device(mtd); + if (err) + pr_err("Error when deleting MTD device (%d)\n", err); + } return ret; } diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c index 6811a714349d..994e8c51e674 100644 --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -690,10 +690,9 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types, parser = mtd_part_parser_get(*types); if (!parser && !request_module("%s", *types)) parser = mtd_part_parser_get(*types); - pr_debug("%s: got parser %s\n", master->name, - parser ? parser->name : NULL); if (!parser) continue; + pr_debug("%s: got parser %s\n", master->name, parser->name); ret = mtd_part_do_parse(parser, master, &pparts, data); if (ret <= 0) mtd_part_parser_put(parser); diff --git a/drivers/mtd/mtdpstore.c b/drivers/mtd/mtdpstore.c index 7ac8ac901306..9cf3872e37ae 100644 --- a/drivers/mtd/mtdpstore.c +++ b/drivers/mtd/mtdpstore.c @@ -417,11 +417,14 @@ static void mtdpstore_notify_add(struct mtd_info *mtd) } longcnt = BITS_TO_LONGS(div_u64(mtd->size, info->kmsg_size)); - cxt->rmmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); - cxt->usedmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); + cxt->rmmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL); + cxt->usedmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL); longcnt = BITS_TO_LONGS(div_u64(mtd->size, mtd->erasesize)); - cxt->badmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); + cxt->badmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL); + + if (!cxt->rmmap || !cxt->usedmap || !cxt->badmap) + return; /* just support dmesg right now */ cxt->dev.flags = PSTORE_FLAGS_DMESG; @@ -527,9 +530,6 @@ static void mtdpstore_notify_remove(struct mtd_info *mtd) mtdpstore_flush_removed(cxt); unregister_pstore_device(&cxt->dev); - kfree(cxt->badmap); - kfree(cxt->usedmap); - kfree(cxt->rmmap); cxt->mtd = NULL; cxt->index = -1; } diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index da1586a36574..db516a45f0c5 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -3,7 +3,11 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o +ifeq ($(CONFIG_SPI_QPIC_SNAND),y) +obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o +else obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o +endif obj-y += onenand/ obj-y += raw/ obj-y += spi/ diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c index fea5b6119956..17f6d9723df9 100644 --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -3008,7 +3008,7 @@ static int brcmnand_resume(struct device *dev) brcmnand_save_restore_cs_config(host, 1); /* Reset the chip, required by some chips after power-up */ - nand_reset_op(chip); + nand_reset(chip, 0); } return 0; diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index d76802944453..f4e68008ea03 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -17,6 +17,7 @@ #include <linux/pm_runtime.h> #include <linux/pinctrl/consumer.h> #include <linux/dma/mxs-dma.h> +#include <linux/string_choices.h> #include "gpmi-nand.h" #include "gpmi-regs.h" #include "bch-regs.h" @@ -2319,8 +2320,8 @@ static int gpmi_nand_attach_chip(struct nand_chip *chip) "fsl,no-blockmark-swap")) this->swap_block_mark = false; } - dev_dbg(this->dev, "Blockmark swapping %sabled\n", - this->swap_block_mark ? "en" : "dis"); + dev_dbg(this->dev, "Blockmark swapping %s\n", + str_enabled_disabled(this->swap_block_mark)); ret = gpmi_init_last(this); if (ret) diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 53e16d39af4b..13e4060bd1b6 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -1833,7 +1833,7 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, /* READ_ID data bytes are received twice in NV-DDR mode */ if (len && nand_interface_is_nvddr(conf)) { - ddrbuf = kzalloc(len * 2, GFP_KERNEL); + ddrbuf = kcalloc(2, len, GFP_KERNEL); if (!ddrbuf) return -ENOMEM; @@ -2203,7 +2203,7 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len, * twice. */ if (force_8bit && nand_interface_is_nvddr(conf)) { - ddrbuf = kzalloc(len * 2, GFP_KERNEL); + ddrbuf = kcalloc(2, len, GFP_KERNEL); if (!ddrbuf) return -ENOMEM; diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c index 6720b547892b..5eaa0be367cd 100644 --- a/drivers/mtd/nand/raw/qcom_nandc.c +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -165,9 +165,9 @@ static void nandc_set_read_loc_first(struct nand_chip *chip, { struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); __le32 locreg_val; - u32 val = (((cw_offset) << READ_LOCATION_OFFSET) | - ((read_size) << READ_LOCATION_SIZE) | - ((is_last_read_loc) << READ_LOCATION_LAST)); + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); locreg_val = cpu_to_le32(val); @@ -197,9 +197,9 @@ static void nandc_set_read_loc_last(struct nand_chip *chip, { struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); __le32 locreg_val; - u32 val = (((cw_offset) << READ_LOCATION_OFFSET) | - ((read_size) << READ_LOCATION_SIZE) | - ((is_last_read_loc) << READ_LOCATION_LAST)); + u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) | + FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) | + FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc); locreg_val = cpu_to_le32(val); @@ -271,14 +271,14 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i } if (host->use_ecc) { - cfg0 = cpu_to_le32((host->cfg0 & ~(7U << CW_PER_PAGE)) | - (num_cw - 1) << CW_PER_PAGE); + cfg0 = cpu_to_le32((host->cfg0 & ~CW_PER_PAGE_MASK) | + FIELD_PREP(CW_PER_PAGE_MASK, (num_cw - 1))); cfg1 = cpu_to_le32(host->cfg1); ecc_bch_cfg = cpu_to_le32(host->ecc_bch_cfg); } else { - cfg0 = cpu_to_le32((host->cfg0_raw & ~(7U << CW_PER_PAGE)) | - (num_cw - 1) << CW_PER_PAGE); + cfg0 = cpu_to_le32((host->cfg0_raw & ~CW_PER_PAGE_MASK) | + FIELD_PREP(CW_PER_PAGE_MASK, (num_cw - 1))); cfg1 = cpu_to_le32(host->cfg1_raw); ecc_bch_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE); @@ -882,12 +882,12 @@ static void qcom_nandc_codeword_fixup(struct qcom_nand_host *host, int page) host->bbm_size - host->cw_data; host->cfg0 &= ~(SPARE_SIZE_BYTES_MASK | UD_SIZE_BYTES_MASK); - host->cfg0 |= host->spare_bytes << SPARE_SIZE_BYTES | - host->cw_data << UD_SIZE_BYTES; + host->cfg0 |= FIELD_PREP(SPARE_SIZE_BYTES_MASK, host->spare_bytes) | + FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_data); host->ecc_bch_cfg &= ~ECC_NUM_DATA_BYTES_MASK; - host->ecc_bch_cfg |= host->cw_data << ECC_NUM_DATA_BYTES; - host->ecc_buf_cfg = (host->cw_data - 1) << NUM_STEPS; + host->ecc_bch_cfg |= FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, host->cw_data); + host->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, host->cw_data - 1); } /* implements ecc->read_page() */ @@ -1531,7 +1531,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip) FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, host->ecc_bytes_hw); if (!nandc->props->qpic_version2) - host->ecc_buf_cfg = 0x203 << NUM_STEPS; + host->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203); host->clrflashstatus = FS_READY_BSY_N; host->clrreadstatus = 0xc0; @@ -1817,7 +1817,7 @@ static int qcom_misc_cmd_type_exec(struct nand_chip *chip, const struct nand_sub q_op.cmd_reg |= cpu_to_le32(PAGE_ACC | LAST_PAGE); nandc->regs->addr0 = q_op.addr1_reg; nandc->regs->addr1 = q_op.addr2_reg; - nandc->regs->cfg0 = cpu_to_le32(host->cfg0_raw & ~(7 << CW_PER_PAGE)); + nandc->regs->cfg0 = cpu_to_le32(host->cfg0_raw & ~CW_PER_PAGE_MASK); nandc->regs->cfg1 = cpu_to_le32(host->cfg1_raw); instrs = 3; } else if (q_op.cmd_reg != cpu_to_le32(OP_RESET_DEVICE)) { @@ -1900,8 +1900,8 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_ /* configure CMD1 and VLD for ONFI param probing in QPIC v1 */ if (!nandc->props->qpic_version2) { nandc->regs->vld = cpu_to_le32((nandc->vld & ~READ_START_VLD)); - nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~(0xFF << READ_ADDR)) - | NAND_CMD_PARAM << READ_ADDR); + nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~READ_ADDR_MASK) | + FIELD_PREP(READ_ADDR_MASK, NAND_CMD_PARAM)); } nandc->regs->exec = cpu_to_le32(1); diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 1e61ab21893a..258da42451a4 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o +spinand-objs := core.o otp.o +spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o spinand-objs += 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 da4713692674..d16e42cf8fae 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -534,10 +534,20 @@ static int spinand_erase_op(struct spinand_device *spinand, return spi_mem_exec_op(spinand->spimem, &op); } -static int spinand_wait(struct spinand_device *spinand, - unsigned long initial_delay_us, - unsigned long poll_delay_us, - u8 *s) +/** + * spinand_wait() - Poll memory device status + * @spinand: the spinand device + * @initial_delay_us: delay in us before starting to poll + * @poll_delay_us: time to sleep between reads in us + * @s: the pointer to variable to store the value of REG_STATUS + * + * This function polls a status register (REG_STATUS) and returns when + * the STATUS_READY bit is 0 or when the timeout has expired. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us, + unsigned long poll_delay_us, u8 *s) { struct spi_mem_op op = SPINAND_GET_FEATURE_OP(REG_STATUS, spinand->scratchbuf); @@ -604,8 +614,16 @@ static int spinand_lock_block(struct spinand_device *spinand, u8 lock) return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock); } -static int spinand_read_page(struct spinand_device *spinand, - const struct nand_page_io_req *req) +/** + * spinand_read_page() - Read a page + * @spinand: the spinand device + * @req: the I/O request + * + * Return: 0 or a positive number of bitflips corrected on success. + * A negative error code otherwise. + */ +int spinand_read_page(struct spinand_device *spinand, + const struct nand_page_io_req *req) { struct nand_device *nand = spinand_to_nand(spinand); u8 status; @@ -635,8 +653,16 @@ static int spinand_read_page(struct spinand_device *spinand, return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req); } -static int spinand_write_page(struct spinand_device *spinand, - const struct nand_page_io_req *req) +/** + * spinand_write_page() - Write a page + * @spinand: the spinand device + * @req: the I/O request + * + * Return: 0 or a positive number of bitflips corrected on success. + * A negative error code otherwise. + */ +int spinand_write_page(struct spinand_device *spinand, + const struct nand_page_io_req *req) { struct nand_device *nand = spinand_to_nand(spinand); u8 status; @@ -674,11 +700,15 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, { struct spinand_device *spinand = mtd_to_spinand(mtd); struct nand_device *nand = mtd_to_nanddev(mtd); + struct mtd_ecc_stats old_stats; struct nand_io_iter iter; bool disable_ecc = false; bool ecc_failed = false; + unsigned int retry_mode = 0; int ret; + old_stats = mtd->ecc_stats; + if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout) disable_ecc = true; @@ -690,18 +720,43 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, if (ret) break; +read_retry: ret = spinand_read_page(spinand, &iter.req); if (ret < 0 && ret != -EBADMSG) break; - if (ret == -EBADMSG) + if (ret == -EBADMSG && spinand->set_read_retry) { + if (spinand->read_retries && (++retry_mode <= spinand->read_retries)) { + ret = spinand->set_read_retry(spinand, retry_mode); + if (ret < 0) { + spinand->set_read_retry(spinand, 0); + return ret; + } + + /* Reset ecc_stats; retry */ + mtd->ecc_stats = old_stats; + goto read_retry; + } else { + /* No more retry modes; real failure */ + ecc_failed = true; + } + } else if (ret == -EBADMSG) { ecc_failed = true; - else + } else { *max_bitflips = max_t(unsigned int, *max_bitflips, ret); + } ret = 0; ops->retlen += iter.req.datalen; ops->oobretlen += iter.req.ooblen; + + /* Reset to retry mode 0 */ + if (retry_mode) { + retry_mode = 0; + ret = spinand->set_read_retry(spinand, retry_mode); + if (ret < 0) + return ret; + } } if (ecc_failed && !ret) @@ -1292,6 +1347,10 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->id.len = 1 + table[i].devid.len; spinand->select_target = table[i].select_target; spinand->set_cont_read = table[i].set_cont_read; + spinand->fact_otp = &table[i].fact_otp; + spinand->user_otp = &table[i].user_otp; + spinand->read_retries = table[i].read_retries; + spinand->set_read_retry = table[i].set_read_retry; op = spinand_select_op_variant(spinand, info->op_variants.read_cache); @@ -1478,6 +1537,12 @@ static int spinand_init(struct spinand_device *spinand) mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks; mtd->_resume = spinand_mtd_resume; + if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) { + ret = spinand_set_mtd_otp_ops(spinand); + if (ret) + goto err_cleanup_ecc_engine; + } + if (nand->ecc.engine) { ret = mtd_ooblayout_count_freebytes(mtd); if (ret < 0) diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c index 323a20901fc9..a164d821464d 100644 --- a/drivers/mtd/nand/spi/esmt.c +++ b/drivers/mtd/nand/spi/esmt.c @@ -8,10 +8,15 @@ #include <linux/device.h> #include <linux/kernel.h> #include <linux/mtd/spinand.h> +#include <linux/spi/spi-mem.h> /* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */ #define SPINAND_MFR_ESMT_C8 0xc8 +#define ESMT_F50L1G41LB_CFG_OTP_PROTECT BIT(7) +#define ESMT_F50L1G41LB_CFG_OTP_LOCK \ + (CFG_OTP_ENABLE | ESMT_F50L1G41LB_CFG_OTP_PROTECT) + static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), @@ -102,6 +107,83 @@ static const struct mtd_ooblayout_ops f50l1g41lb_ooblayout = { .free = f50l1g41lb_ooblayout_free, }; +static int f50l1g41lb_otp_info(struct spinand_device *spinand, size_t len, + struct otp_info *buf, size_t *retlen, bool user) +{ + if (len < sizeof(*buf)) + return -EINVAL; + + buf->locked = 0; + buf->start = 0; + buf->length = user ? spinand_user_otp_size(spinand) : + spinand_fact_otp_size(spinand); + + *retlen = sizeof(*buf); + return 0; +} + +static int f50l1g41lb_fact_otp_info(struct spinand_device *spinand, size_t len, + struct otp_info *buf, size_t *retlen) +{ + return f50l1g41lb_otp_info(spinand, len, buf, retlen, false); +} + +static int f50l1g41lb_user_otp_info(struct spinand_device *spinand, size_t len, + struct otp_info *buf, size_t *retlen) +{ + return f50l1g41lb_otp_info(spinand, len, buf, retlen, true); +} + +static int f50l1g41lb_otp_lock(struct spinand_device *spinand, loff_t from, + size_t len) +{ + struct spi_mem_op write_op = SPINAND_WR_EN_DIS_OP(true); + struct spi_mem_op exec_op = SPINAND_PROG_EXEC_OP(0); + u8 status; + int ret; + + ret = spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK, + ESMT_F50L1G41LB_CFG_OTP_LOCK); + if (!ret) + return ret; + + ret = spi_mem_exec_op(spinand->spimem, &write_op); + if (!ret) + goto out; + + ret = spi_mem_exec_op(spinand->spimem, &exec_op); + if (!ret) + goto out; + + ret = spinand_wait(spinand, + SPINAND_WRITE_INITIAL_DELAY_US, + SPINAND_WRITE_POLL_DELAY_US, + &status); + if (!ret && (status & STATUS_PROG_FAILED)) + ret = -EIO; + +out: + if (spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK, 0)) { + dev_warn(&spinand_to_mtd(spinand)->dev, + "Can not disable OTP mode\n"); + ret = -EIO; + } + + return ret; +} + +static const struct spinand_user_otp_ops f50l1g41lb_user_otp_ops = { + .info = f50l1g41lb_user_otp_info, + .lock = f50l1g41lb_otp_lock, + .read = spinand_user_otp_read, + .write = spinand_user_otp_write, +}; + +static const struct spinand_fact_otp_ops f50l1g41lb_fact_otp_ops = { + .info = f50l1g41lb_fact_otp_info, + .read = spinand_fact_otp_read, +}; + static const struct spinand_info esmt_c8_spinand_table[] = { SPINAND_INFO("F50L1G41LB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01, 0x7f, @@ -112,7 +194,9 @@ static const struct spinand_info esmt_c8_spinand_table[] = { &write_cache_variants, &update_cache_variants), 0, - SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL), + SPINAND_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops), + SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)), SPINAND_INFO("F50D1G41LB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11, 0x7f, 0x7f, 0x7f), @@ -122,7 +206,9 @@ static const struct spinand_info esmt_c8_spinand_table[] = { &write_cache_variants, &update_cache_variants), 0, - SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL), + SPINAND_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops), + SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)), SPINAND_INFO("F50D2G41KA", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51, 0x7f, 0x7f, 0x7f), diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 3dc4d63d6832..1ef08ad850a2 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -14,6 +14,8 @@ #define MACRONIX_ECCSR_BF_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr) #define MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(eccsr) FIELD_GET(GENMASK(7, 4), eccsr) #define MACRONIX_CFG_CONT_READ BIT(2) +#define MACRONIX_FEATURE_ADDR_READ_RETRY 0x70 +#define MACRONIX_NUM_READ_RETRY_MODES 5 #define STATUS_ECC_HAS_BITFLIPS_THRESHOLD (3 << 4) @@ -136,6 +138,23 @@ static int macronix_set_cont_read(struct spinand_device *spinand, bool enable) return 0; } +/** + * macronix_set_read_retry - Set the retry mode + * @spinand: SPI NAND device + * @retry_mode: Specify which retry mode to set + * + * Return: 0 on success, a negative error code otherwise. + */ +static int macronix_set_read_retry(struct spinand_device *spinand, + unsigned int retry_mode) +{ + struct spi_mem_op op = SPINAND_SET_FEATURE_OP(MACRONIX_FEATURE_ADDR_READ_RETRY, + spinand->scratchbuf); + + *spinand->scratchbuf = retry_mode; + return spi_mem_exec_op(spinand->spimem, &op); +} + static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO("MX35LF1GE4AB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12), @@ -168,7 +187,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), - SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_CONT_READ(macronix_set_cont_read), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03), NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1), @@ -179,7 +200,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), - SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_CONT_READ(macronix_set_cont_read), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF1G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -188,7 +211,9 @@ static const struct spinand_info macronix_spinand_table[] = { &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF2G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), @@ -198,7 +223,9 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT | SPINAND_HAS_PROG_PLANE_SELECT_BIT, - SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -207,7 +234,9 @@ static const struct spinand_info macronix_spinand_table[] = { &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF4G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), @@ -217,7 +246,9 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT | SPINAND_HAS_PROG_PLANE_SELECT_BIT, - SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35LF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -226,7 +257,9 @@ static const struct spinand_info macronix_spinand_table[] = { &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, - SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX31LF1GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x1e), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -270,7 +303,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT | SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xf5, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -280,7 +315,9 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -291,7 +328,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), - SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_CONT_READ(macronix_set_cont_read), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF2G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), @@ -314,7 +353,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT | SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe4, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -324,7 +365,9 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF2GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -335,7 +378,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), - SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_CONT_READ(macronix_set_cont_read), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF2GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2, 0x01), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -366,7 +411,9 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF1GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -377,7 +424,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status), - SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_CONT_READ(macronix_set_cont_read), + SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES, + macronix_set_read_retry)), SPINAND_INFO("MX35UF1GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92, 0x01), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c index ad0bb9755a09..691f8a2e0791 100644 --- a/drivers/mtd/nand/spi/micron.c +++ b/drivers/mtd/nand/spi/micron.c @@ -9,6 +9,8 @@ #include <linux/device.h> #include <linux/kernel.h> #include <linux/mtd/spinand.h> +#include <linux/spi/spi-mem.h> +#include <linux/string.h> #define SPINAND_MFR_MICRON 0x2c @@ -28,6 +30,10 @@ #define MICRON_SELECT_DIE(x) ((x) << 6) +#define MICRON_MT29F2G01ABAGD_CFG_OTP_STATE BIT(7) +#define MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK \ + (CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE) + static SPINAND_OP_VARIANTS(quadio_read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), @@ -168,6 +174,131 @@ static int micron_8_ecc_get_status(struct spinand_device *spinand, return -EINVAL; } +static int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand) +{ + size_t bufsize = spinand_otp_page_size(spinand); + size_t retlen; + u8 *buf; + int ret; + + buf = kmalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = spinand_upd_cfg(spinand, + MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, + MICRON_MT29F2G01ABAGD_CFG_OTP_STATE); + if (ret) + goto free_buf; + + ret = spinand_user_otp_read(spinand, 0, bufsize, &retlen, buf); + + if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, + 0)) { + dev_warn(&spinand_to_mtd(spinand)->dev, + "Can not disable OTP mode\n"); + ret = -EIO; + } + + if (ret) + goto free_buf; + + /* If all zeros, then the OTP area is locked. */ + if (mem_is_zero(buf, bufsize)) + ret = 1; + +free_buf: + kfree(buf); + return ret; +} + +static int mt29f2g01abagd_otp_info(struct spinand_device *spinand, size_t len, + struct otp_info *buf, size_t *retlen, + bool user) +{ + int locked; + + if (len < sizeof(*buf)) + return -EINVAL; + + locked = mt29f2g01abagd_otp_is_locked(spinand); + if (locked < 0) + return locked; + + buf->locked = locked; + buf->start = 0; + buf->length = user ? spinand_user_otp_size(spinand) : + spinand_fact_otp_size(spinand); + + *retlen = sizeof(*buf); + return 0; +} + +static int mt29f2g01abagd_fact_otp_info(struct spinand_device *spinand, + size_t len, struct otp_info *buf, + size_t *retlen) +{ + return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, false); +} + +static int mt29f2g01abagd_user_otp_info(struct spinand_device *spinand, + size_t len, struct otp_info *buf, + size_t *retlen) +{ + return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, true); +} + +static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from, + size_t len) +{ + struct spi_mem_op write_op = SPINAND_WR_EN_DIS_OP(true); + struct spi_mem_op exec_op = SPINAND_PROG_EXEC_OP(0); + u8 status; + int ret; + + ret = spinand_upd_cfg(spinand, + MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, + MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK); + if (!ret) + return ret; + + ret = spi_mem_exec_op(spinand->spimem, &write_op); + if (!ret) + goto out; + + ret = spi_mem_exec_op(spinand->spimem, &exec_op); + if (!ret) + goto out; + + ret = spinand_wait(spinand, + SPINAND_WRITE_INITIAL_DELAY_US, + SPINAND_WRITE_POLL_DELAY_US, + &status); + if (!ret && (status & STATUS_PROG_FAILED)) + ret = -EIO; + +out: + if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, 0)) { + dev_warn(&spinand_to_mtd(spinand)->dev, + "Can not disable OTP mode\n"); + ret = -EIO; + } + + return ret; +} + +static const struct spinand_user_otp_ops mt29f2g01abagd_user_otp_ops = { + .info = mt29f2g01abagd_user_otp_info, + .lock = mt29f2g01abagd_otp_lock, + .read = spinand_user_otp_read, + .write = spinand_user_otp_write, +}; + +static const struct spinand_fact_otp_ops mt29f2g01abagd_fact_otp_ops = { + .info = mt29f2g01abagd_fact_otp_info, + .read = spinand_fact_otp_read, +}; + static const struct spinand_info micron_spinand_table[] = { /* M79A 2Gb 3.3V */ SPINAND_INFO("MT29F2G01ABAGD", @@ -179,7 +310,9 @@ static const struct spinand_info micron_spinand_table[] = { &x4_update_cache_variants), 0, SPINAND_ECCINFO(µn_8_ooblayout, - micron_8_ecc_get_status)), + micron_8_ecc_get_status), + SPINAND_USER_OTP_INFO(12, 2, &mt29f2g01abagd_user_otp_ops), + SPINAND_FACT_OTP_INFO(2, 0, &mt29f2g01abagd_fact_otp_ops)), /* M79A 2Gb 1.8V */ SPINAND_INFO("MT29F2G01ABBGD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), diff --git a/drivers/mtd/nand/spi/otp.c b/drivers/mtd/nand/spi/otp.c new file mode 100644 index 000000000000..ce41bca86ea9 --- /dev/null +++ b/drivers/mtd/nand/spi/otp.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, SaluteDevices. All Rights Reserved. + * + * Author: Martin Kurbanov <mmkurbanov@salutedevices.com> + */ + +#include <linux/mtd/mtd.h> +#include <linux/mtd/spinand.h> + +/** + * spinand_otp_page_size() - Get SPI-NAND OTP page size + * @spinand: the spinand device + * + * Return: the OTP page size. + */ +size_t spinand_otp_page_size(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + return nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); +} + +static size_t spinand_otp_size(struct spinand_device *spinand, + const struct spinand_otp_layout *layout) +{ + return layout->npages * spinand_otp_page_size(spinand); +} + +/** + * spinand_fact_otp_size() - Get SPI-NAND factory OTP area size + * @spinand: the spinand device + * + * Return: the OTP size. + */ +size_t spinand_fact_otp_size(struct spinand_device *spinand) +{ + return spinand_otp_size(spinand, &spinand->fact_otp->layout); +} + +/** + * spinand_user_otp_size() - Get SPI-NAND user OTP area size + * @spinand: the spinand device + * + * Return: the OTP size. + */ +size_t spinand_user_otp_size(struct spinand_device *spinand) +{ + return spinand_otp_size(spinand, &spinand->user_otp->layout); +} + +static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs, + size_t len, + const struct spinand_otp_layout *layout) +{ + if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout)) + return -EINVAL; + + return 0; +} + +static int spinand_user_otp_check_bounds(struct spinand_device *spinand, + loff_t ofs, size_t len) +{ + return spinand_otp_check_bounds(spinand, ofs, len, + &spinand->user_otp->layout); +} + +static int spinand_otp_rw(struct spinand_device *spinand, loff_t ofs, + size_t len, size_t *retlen, u8 *buf, bool is_write, + const struct spinand_otp_layout *layout) +{ + struct nand_page_io_req req = {}; + unsigned long long page; + size_t copied = 0; + size_t otp_pagesize = spinand_otp_page_size(spinand); + int ret; + + if (!len) + return 0; + + ret = spinand_otp_check_bounds(spinand, ofs, len, layout); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, CFG_OTP_ENABLE); + if (ret) + return ret; + + page = ofs; + req.dataoffs = do_div(page, otp_pagesize); + req.pos.page = page + layout->start_page; + req.type = is_write ? NAND_PAGE_WRITE : NAND_PAGE_READ; + req.mode = MTD_OPS_RAW; + req.databuf.in = buf; + + while (copied < len) { + req.datalen = min_t(unsigned int, + otp_pagesize - req.dataoffs, + len - copied); + + if (is_write) + ret = spinand_write_page(spinand, &req); + else + ret = spinand_read_page(spinand, &req); + + if (ret < 0) + break; + + req.databuf.in += req.datalen; + req.pos.page++; + req.dataoffs = 0; + copied += req.datalen; + } + + *retlen = copied; + + if (spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0)) { + dev_warn(&spinand_to_mtd(spinand)->dev, + "Can not disable OTP mode\n"); + ret = -EIO; + } + + return ret; +} + +/** + * spinand_fact_otp_read() - Read from OTP area + * @spinand: the spinand device + * @ofs: the offset to read + * @len: the number of data bytes to read + * @retlen: the pointer to variable to store the number of read bytes + * @buf: the buffer to store the read data + * + * Return: 0 on success, an error code otherwise. + */ +int spinand_fact_otp_read(struct spinand_device *spinand, loff_t ofs, + size_t len, size_t *retlen, u8 *buf) +{ + return spinand_otp_rw(spinand, ofs, len, retlen, buf, false, + &spinand->fact_otp->layout); +} + +/** + * spinand_user_otp_read() - Read from OTP area + * @spinand: the spinand device + * @ofs: the offset to read + * @len: the number of data bytes to read + * @retlen: the pointer to variable to store the number of read bytes + * @buf: the buffer to store the read data + * + * Return: 0 on success, an error code otherwise. + */ +int spinand_user_otp_read(struct spinand_device *spinand, loff_t ofs, + size_t len, size_t *retlen, u8 *buf) +{ + return spinand_otp_rw(spinand, ofs, len, retlen, buf, false, + &spinand->user_otp->layout); +} + +/** + * spinand_user_otp_write() - Write to OTP area + * @spinand: the spinand device + * @ofs: the offset to write to + * @len: the number of bytes to write + * @retlen: the pointer to variable to store the number of written bytes + * @buf: the buffer with data to write + * + * Return: 0 on success, an error code otherwise. + */ +int spinand_user_otp_write(struct spinand_device *spinand, loff_t ofs, + size_t len, size_t *retlen, const u8 *buf) +{ + return spinand_otp_rw(spinand, ofs, len, retlen, (u8 *)buf, true, + &spinand->user_otp->layout); +} + +static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf, + bool is_fact) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + *retlen = 0; + + mutex_lock(&spinand->lock); + + if (is_fact) + ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen); + else + ret = spinand->user_otp->ops->info(spinand, len, buf, retlen); + + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return spinand_mtd_otp_info(mtd, len, retlen, buf, true); +} + +static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return spinand_mtd_otp_info(mtd, len, retlen, buf, false); +} + +static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t *retlen, u8 *buf, bool is_fact) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + *retlen = 0; + + if (!len) + return 0; + + ret = spinand_otp_check_bounds(spinand, ofs, len, + is_fact ? &spinand->fact_otp->layout : + &spinand->user_otp->layout); + if (ret) + return ret; + + mutex_lock(&spinand->lock); + + if (is_fact) + ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen, + buf); + else + ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen, + buf); + + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs, + size_t len, size_t *retlen, u8 *buf) +{ + return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true); +} + +static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs, + size_t len, size_t *retlen, u8 *buf) +{ + return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false); +} + +static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs, + size_t len, size_t *retlen, const u8 *buf) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + const struct spinand_user_otp_ops *ops = spinand->user_otp->ops; + int ret; + + *retlen = 0; + + if (!len) + return 0; + + ret = spinand_user_otp_check_bounds(spinand, ofs, len); + if (ret) + return ret; + + mutex_lock(&spinand->lock); + ret = ops->write(spinand, ofs, len, retlen, buf); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs, + size_t len) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + const struct spinand_user_otp_ops *ops = spinand->user_otp->ops; + int ret; + + if (!len) + return 0; + + ret = spinand_user_otp_check_bounds(spinand, ofs, len); + if (ret) + return ret; + + mutex_lock(&spinand->lock); + ret = ops->erase(spinand, ofs, len); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs, + size_t len) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + const struct spinand_user_otp_ops *ops = spinand->user_otp->ops; + int ret; + + if (!len) + return 0; + + ret = spinand_user_otp_check_bounds(spinand, ofs, len); + if (ret) + return ret; + + mutex_lock(&spinand->lock); + ret = ops->lock(spinand, ofs, len); + mutex_unlock(&spinand->lock); + + return ret; +} + +/** + * spinand_set_mtd_otp_ops() - Setup OTP methods + * @spinand: the spinand device + * + * Setup OTP methods. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_set_mtd_otp_ops(struct spinand_device *spinand) +{ + struct mtd_info *mtd = spinand_to_mtd(spinand); + const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops; + const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops; + + if (!user_ops && !fact_ops) + return -EINVAL; + + if (user_ops) { + if (user_ops->info) + mtd->_get_user_prot_info = spinand_mtd_user_otp_info; + + if (user_ops->read) + mtd->_read_user_prot_reg = spinand_mtd_user_otp_read; + + if (user_ops->write) + mtd->_write_user_prot_reg = spinand_mtd_user_otp_write; + + if (user_ops->lock) + mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock; + + if (user_ops->erase) + mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase; + } + + if (fact_ops) { + if (fact_ops->info) + mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info; + + if (fact_ops->read) + mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read; + } + + return 0; +} diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 19eb98bd6821..ac4b960101cc 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,16 +7,17 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ -#include <linux/err.h> -#include <linux/errno.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> #include <linux/math64.h> #include <linux/module.h> #include <linux/mtd/mtd.h> #include <linux/mtd/spi-nor.h> #include <linux/mutex.h> -#include <linux/of_platform.h> +#include <linux/of.h> #include <linux/regulator/consumer.h> #include <linux/sched/task_stack.h> #include <linux/sizes.h> @@ -639,32 +640,26 @@ static bool spi_nor_use_parallel_locking(struct spi_nor *nor) static int spi_nor_rww_start_rdst(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - int ret = -EAGAIN; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd) - goto busy; + return -EAGAIN; rww->ongoing_io = true; rww->ongoing_rd = true; - ret = 0; -busy: - mutex_unlock(&nor->lock); - return ret; + return 0; } static void spi_nor_rww_end_rdst(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); rww->ongoing_io = false; rww->ongoing_rd = false; - - mutex_unlock(&nor->lock); } static int spi_nor_lock_rdst(struct spi_nor *nor) @@ -1212,26 +1207,21 @@ static void spi_nor_offset_to_banks(u64 bank_size, loff_t start, size_t len, static bool spi_nor_rww_start_io(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - bool start = false; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io) - goto busy; + return false; rww->ongoing_io = true; - start = true; -busy: - mutex_unlock(&nor->lock); - return start; + return true; } static void spi_nor_rww_end_io(struct spi_nor *nor) { - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); nor->rww.ongoing_io = false; - mutex_unlock(&nor->lock); } static int spi_nor_lock_device(struct spi_nor *nor) @@ -1254,32 +1244,27 @@ static void spi_nor_unlock_device(struct spi_nor *nor) static bool spi_nor_rww_start_exclusive(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - bool start = false; mutex_lock(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe) - goto busy; + return false; rww->ongoing_io = true; rww->ongoing_rd = true; rww->ongoing_pe = true; - start = true; -busy: - mutex_unlock(&nor->lock); - return start; + return true; } static void spi_nor_rww_end_exclusive(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); rww->ongoing_io = false; rww->ongoing_rd = false; rww->ongoing_pe = false; - mutex_unlock(&nor->lock); } int spi_nor_prep_and_lock(struct spi_nor *nor) @@ -1316,30 +1301,26 @@ static bool spi_nor_rww_start_pe(struct spi_nor *nor, loff_t start, size_t len) { struct spi_nor_rww *rww = &nor->rww; unsigned int used_banks = 0; - bool started = false; u8 first, last; int bank; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe) - goto busy; + return false; spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last); for (bank = first; bank <= last; bank++) { if (rww->used_banks & BIT(bank)) - goto busy; + return false; used_banks |= BIT(bank); } rww->used_banks |= used_banks; rww->ongoing_pe = true; - started = true; -busy: - mutex_unlock(&nor->lock); - return started; + return true; } static void spi_nor_rww_end_pe(struct spi_nor *nor, loff_t start, size_t len) @@ -1348,15 +1329,13 @@ static void spi_nor_rww_end_pe(struct spi_nor *nor, loff_t start, size_t len) u8 first, last; int bank; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last); for (bank = first; bank <= last; bank++) rww->used_banks &= ~BIT(bank); rww->ongoing_pe = false; - - mutex_unlock(&nor->lock); } static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t len) @@ -1393,19 +1372,18 @@ static bool spi_nor_rww_start_rd(struct spi_nor *nor, loff_t start, size_t len) { struct spi_nor_rww *rww = &nor->rww; unsigned int used_banks = 0; - bool started = false; u8 first, last; int bank; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd) - goto busy; + return false; spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last); for (bank = first; bank <= last; bank++) { if (rww->used_banks & BIT(bank)) - goto busy; + return false; used_banks |= BIT(bank); } @@ -1413,11 +1391,8 @@ static bool spi_nor_rww_start_rd(struct spi_nor *nor, loff_t start, size_t len) rww->used_banks |= used_banks; rww->ongoing_io = true; rww->ongoing_rd = true; - started = true; -busy: - mutex_unlock(&nor->lock); - return started; + return true; } static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len) @@ -1426,7 +1401,7 @@ static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len) u8 first, last; int bank; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last); for (bank = first; bank <= last; bank++) @@ -1434,8 +1409,6 @@ static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len) rww->ongoing_io = false; rww->ongoing_rd = false; - - mutex_unlock(&nor->lock); } static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t len) diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c index 99936fd25d43..55644a3cd88c 100644 --- a/drivers/mtd/spi-nor/macronix.c +++ b/drivers/mtd/spi-nor/macronix.c @@ -45,8 +45,26 @@ mx25l25635_post_bfpt_fixups(struct spi_nor *nor, return 0; } +static int +macronix_qpp4b_post_sfdp_fixups(struct spi_nor *nor) +{ + /* PP_1_1_4_4B is supported but missing in 4BAIT. */ + struct spi_nor_flash_parameter *params = nor->params; + + params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4; + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP_1_1_4], + SPINOR_OP_PP_1_1_4_4B, SNOR_PROTO_1_1_4); + + return 0; +} + static const struct spi_nor_fixups mx25l25635_fixups = { .post_bfpt = mx25l25635_post_bfpt_fixups, + .post_sfdp = macronix_qpp4b_post_sfdp_fixups, +}; + +static const struct spi_nor_fixups macronix_qpp4b_fixups = { + .post_sfdp = macronix_qpp4b_post_sfdp_fixups, }; static const struct flash_info macronix_nor_parts[] = { @@ -102,11 +120,17 @@ static const struct flash_info macronix_nor_parts[] = { .size = SZ_64M, .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixup_flags = SPI_NOR_4B_OPCODES, + .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x20, 0x1b), .name = "mx66l1g45g", .size = SZ_128M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .fixups = ¯onix_qpp4b_fixups, + }, { + /* MX66L2G45G */ + .id = SNOR_ID(0xc2, 0x20, 0x1c), + .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x23, 0x14), .name = "mx25v8035f", @@ -148,18 +172,25 @@ static const struct flash_info macronix_nor_parts[] = { .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixup_flags = SPI_NOR_4B_OPCODES, + .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x25, 0x3a), .name = "mx66u51235f", .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixup_flags = SPI_NOR_4B_OPCODES, + .fixups = ¯onix_qpp4b_fixups, + }, { + /* MX66U1G45G */ + .id = SNOR_ID(0xc2, 0x25, 0x3b), + .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x25, 0x3c), .name = "mx66u2g45g", .size = SZ_256M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, .fixup_flags = SPI_NOR_4B_OPCODES, + .fixups = ¯onix_qpp4b_fixups, }, { .id = SNOR_ID(0xc2, 0x26, 0x18), .name = "mx25l12855e", diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c index 9a729aa3452d..7d0b145d78d8 100644 --- a/drivers/mtd/spi-nor/otp.c +++ b/drivers/mtd/spi-nor/otp.c @@ -6,6 +6,7 @@ */ #include <linux/log2.h> +#include <linux/math64.h> #include <linux/mtd/mtd.h> #include <linux/mtd/spi-nor.h> diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c index e48c3cff247a..9c9328478d8a 100644 --- a/drivers/mtd/spi-nor/swp.c +++ b/drivers/mtd/spi-nor/swp.c @@ -5,6 +5,7 @@ * Copyright (C) 2005, Intec Automation Inc. * Copyright (C) 2014, Freescale Semiconductor, Inc. */ +#include <linux/math64.h> #include <linux/mtd/mtd.h> #include <linux/mtd/spi-nor.h> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index 8d0a00d69e12..63a93c9eb917 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -10,6 +10,7 @@ #define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */ #define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */ +#define WINBOND_NOR_OP_SELDIE 0xc2 /* Select active die */ #define WINBOND_NOR_WREAR_OP(buf) \ SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \ @@ -17,6 +18,12 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_DATA_OUT(1, buf, 0)) +#define WINBOND_NOR_SELDIE_OP(buf) \ + SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_SELDIE, 0), \ + SPI_MEM_OP_NO_ADDR, \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_DATA_OUT(1, buf, 0)) + static int w25q128_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -66,6 +73,79 @@ static const struct spi_nor_fixups w25q256_fixups = { .post_bfpt = w25q256_post_bfpt_fixups, }; +/** + * winbond_nor_select_die() - Set active die. + * @nor: pointer to 'struct spi_nor'. + * @die: die to set active. + * + * Certain Winbond chips feature more than a single die. This is mostly hidden + * to the user, except that some chips may experience time deviation when + * modifying the status bits between dies, which in some corner cases may + * produce problematic races. Being able to explicitly select a die to check its + * state in this case may be useful. + * + * Return: 0 on success, -errno otherwise. + */ +static int winbond_nor_select_die(struct spi_nor *nor, u8 die) +{ + int ret; + + nor->bouncebuf[0] = die; + + if (nor->spimem) { + struct spi_mem_op op = WINBOND_NOR_SELDIE_OP(nor->bouncebuf); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + } else { + ret = spi_nor_controller_ops_write_reg(nor, + WINBOND_NOR_OP_SELDIE, + nor->bouncebuf, 1); + } + + if (ret) + dev_dbg(nor->dev, "error %d selecting die %d\n", ret, die); + + return ret; +} + +static int winbond_nor_multi_die_ready(struct spi_nor *nor) +{ + int ret, i; + + for (i = 0; i < nor->params->n_dice; i++) { + ret = winbond_nor_select_die(nor, i); + if (ret) + return ret; + + ret = spi_nor_sr_ready(nor); + if (ret <= 0) + return ret; + } + + return 1; +} + +static int +winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor) +{ + /* + * SFDP supports dice numbers, but this information is only available in + * optional additional tables which are not provided by these chips. + * Dice number has an impact though, because these devices need extra + * care when reading the busy bit. + */ + nor->params->n_dice = nor->params->size / SZ_64M; + nor->params->ready = winbond_nor_multi_die_ready; + + return 0; +} + +static const struct spi_nor_fixups winbond_nor_multi_die_fixups = { + .post_sfdp = winbond_nor_multi_die_post_sfdp_fixups, +}; + static const struct flash_info winbond_nor_parts[] = { { .id = SNOR_ID(0xef, 0x30, 0x10), @@ -147,6 +227,10 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_64M, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, }, { + /* W25Q01JV */ + .id = SNOR_ID(0xef, 0x40, 0x21), + .fixups = &winbond_nor_multi_die_fixups, + }, { .id = SNOR_ID(0xef, 0x50, 0x12), .name = "w25q20bw", .size = SZ_256K, @@ -222,6 +306,10 @@ static const struct flash_info winbond_nor_parts[] = { .id = SNOR_ID(0xef, 0x70, 0x19), .name = "w25q256jvm", }, { + /* W25Q02JV */ + .id = SNOR_ID(0xef, 0x70, 0x22), + .fixups = &winbond_nor_multi_die_fixups, + }, { .id = SNOR_ID(0xef, 0x71, 0x19), .name = "w25m512jv", .size = SZ_64M, diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c index 2836905f0152..39cc0a6a4d37 100644 --- a/drivers/mtd/ubi/block.c +++ b/drivers/mtd/ubi/block.c @@ -199,7 +199,7 @@ static blk_status_t ubiblock_read(struct request *req) * and ubi_read_sg() will check that limit. */ ubi_sgl_init(&pdu->usgl); - blk_rq_map_sg(req->q, req, pdu->usgl.sg); + blk_rq_map_sg(req, pdu->usgl.sg); while (bytes_left) { /* |