diff options
Diffstat (limited to 'drivers/mtd/nand/spi/core.c')
| -rw-r--r-- | drivers/mtd/nand/spi/core.c | 398 |
1 files changed, 346 insertions, 52 deletions
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index d207286572d8..29fb2ac19569 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -20,10 +20,100 @@ #include <linux/spi/spi.h> #include <linux/spi/spi-mem.h> +static struct spi_mem_op +spinand_fill_reset_op(struct spinand_device *spinand) +{ + return spinand->op_templates->reset; +} + +static struct spi_mem_op +spinand_fill_readid_op(struct spinand_device *spinand, + u8 naddr, u8 ndummy, void *buf, unsigned int len) +{ + struct spi_mem_op op = spinand->op_templates->readid; + + op.addr.nbytes = naddr; + op.dummy.nbytes = ndummy; + op.data.buf.in = buf; + op.data.nbytes = len; + + return op; +} + +struct spi_mem_op +spinand_fill_wr_en_op(struct spinand_device *spinand) +{ + return spinand->op_templates->wr_en; +} + +static __maybe_unused struct spi_mem_op +spinand_fill_wr_dis_op(struct spinand_device *spinand) +{ + return spinand->op_templates->wr_dis; +} + +struct spi_mem_op +spinand_fill_set_feature_op(struct spinand_device *spinand, u64 reg, const void *valptr) +{ + struct spi_mem_op op = spinand->op_templates->set_feature; + + if (op.cmd.dtr && op.cmd.buswidth == 8) + reg |= reg << 8; + + op.addr.val = reg; + op.data.buf.out = valptr; + + return op; +} + +struct spi_mem_op +spinand_fill_get_feature_op(struct spinand_device *spinand, u64 reg, void *valptr) +{ + struct spi_mem_op op = spinand->op_templates->get_feature; + + if (op.cmd.dtr && op.cmd.buswidth == 8) + reg |= reg << 8; + + op.addr.val = reg; + op.data.buf.in = valptr; + + return op; +} + +static struct spi_mem_op +spinand_fill_blk_erase_op(struct spinand_device *spinand, u64 addr) +{ + struct spi_mem_op op = spinand->op_templates->blk_erase; + + op.addr.val = addr; + + return op; +} + +static struct spi_mem_op +spinand_fill_page_read_op(struct spinand_device *spinand, u64 addr) +{ + struct spi_mem_op op = spinand->op_templates->page_read; + + op.addr.val = addr; + + return op; +} + +struct spi_mem_op +spinand_fill_prog_exec_op(struct spinand_device *spinand, u64 addr) +{ + struct spi_mem_op op = spinand->op_templates->prog_exec; + + op.addr.val = addr; + + return op; +} + int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) { - struct spi_mem_op op = SPINAND_GET_FEATURE_1S_1S_1S_OP(reg, - spinand->scratchbuf); + struct spi_mem_op op = SPINAND_OP(spinand, get_feature, + reg, spinand->scratchbuf); int ret; ret = spi_mem_exec_op(spinand->spimem, &op); @@ -36,8 +126,8 @@ int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) { - struct spi_mem_op op = SPINAND_SET_FEATURE_1S_1S_1S_OP(reg, - spinand->scratchbuf); + struct spi_mem_op op = SPINAND_OP(spinand, set_feature, + reg, spinand->scratchbuf); *spinand->scratchbuf = val; return spi_mem_exec_op(spinand->spimem, &op); @@ -177,18 +267,9 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand) return 0; } -static int spinand_init_quad_enable(struct spinand_device *spinand) +static int spinand_init_quad_enable(struct spinand_device *spinand, + bool enable) { - bool enable = false; - - if (!(spinand->flags & SPINAND_HAS_QE_BIT)) - return 0; - - if (spinand->op_templates.read_cache->data.buswidth == 4 || - spinand->op_templates.write_cache->data.buswidth == 4 || - spinand->op_templates.update_cache->data.buswidth == 4) - enable = true; - return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, enable ? CFG_QUAD_ENABLE : 0); } @@ -362,7 +443,7 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status) int spinand_write_enable_op(struct spinand_device *spinand) { - struct spi_mem_op op = SPINAND_WR_EN_DIS_1S_0_0_OP(true); + struct spi_mem_op op = SPINAND_OP(spinand, wr_en); return spi_mem_exec_op(spinand->spimem, &op); } @@ -372,7 +453,7 @@ static int spinand_load_page_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, &req->pos); - struct spi_mem_op op = SPINAND_PAGE_READ_1S_1S_0_OP(row); + struct spi_mem_op op = SPINAND_OP(spinand, page_read, row); return spi_mem_exec_op(spinand->spimem, &op); } @@ -527,7 +608,7 @@ static int spinand_program_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, &req->pos); - struct spi_mem_op op = SPINAND_PROG_EXEC_1S_1S_0_OP(row); + struct spi_mem_op op = SPINAND_OP(spinand, prog_exec, row); return spi_mem_exec_op(spinand->spimem, &op); } @@ -537,7 +618,7 @@ static int spinand_erase_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, pos); - struct spi_mem_op op = SPINAND_BLK_ERASE_1S_1S_0_OP(row); + struct spi_mem_op op = SPINAND_OP(spinand, blk_erase, row); return spi_mem_exec_op(spinand->spimem, &op); } @@ -557,8 +638,8 @@ static int spinand_erase_op(struct spinand_device *spinand, 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_1S_1S_1S_OP(REG_STATUS, - spinand->scratchbuf); + struct spi_mem_op op = SPINAND_OP(spinand, get_feature, + REG_STATUS, spinand->scratchbuf); u8 status; int ret; @@ -591,8 +672,8 @@ out: static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr, u8 ndummy, u8 *buf) { - struct spi_mem_op op = SPINAND_READID_1S_1S_1S_OP( - naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN); + struct spi_mem_op op = SPINAND_OP(spinand, readid, + naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN); int ret; ret = spi_mem_exec_op(spinand->spimem, &op); @@ -604,7 +685,7 @@ static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr, static int spinand_reset_op(struct spinand_device *spinand) { - struct spi_mem_op op = SPINAND_RESET_1S_0_0_OP; + struct spi_mem_op op = SPINAND_OP(spinand, reset); int ret; ret = spi_mem_exec_op(spinand->spimem, &op); @@ -859,6 +940,14 @@ static void spinand_cont_read_init(struct spinand_device *spinand) (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || engine_type == NAND_ECC_ENGINE_TYPE_NONE)) { spinand->cont_read_possible = true; + + /* + * Ensure continuous read is disabled on probe. + * Some devices retain this state across soft reset, + * which leaves the OOB area inaccessible and results + * in false positive returns from spinand_isbad(). + */ + spinand_cont_read_enable(spinand, false); } } @@ -1154,7 +1243,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, info.offset = plane << fls(nand->memorg.pagesize); info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); - info.op_tmpl = *spinand->op_templates.update_cache; + info.op_tmpl = *spinand->op_templates->update_cache; desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, spinand->spimem, &info); if (IS_ERR(desc)) @@ -1162,7 +1251,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, spinand->dirmaps[plane].wdesc = desc; - info.op_tmpl = *spinand->op_templates.read_cache; + info.op_tmpl = *spinand->op_templates->read_cache; desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -1177,7 +1266,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, } info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); - info.op_tmpl = *spinand->op_templates.update_cache; + info.op_tmpl = *spinand->op_templates->update_cache; info.op_tmpl.data.ecc = true; desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, spinand->spimem, &info); @@ -1186,7 +1275,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, spinand->dirmaps[plane].wdesc_ecc = desc; - info.op_tmpl = *spinand->op_templates.read_cache; + info.op_tmpl = *spinand->op_templates->read_cache; info.op_tmpl.data.ecc = true; desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) @@ -1227,6 +1316,7 @@ static const struct nand_ops spinand_ops = { static const struct spinand_manufacturer *spinand_manufacturers[] = { &alliancememory_spinand_manufacturer, &ato_spinand_manufacturer, + &dosilicon_spinand_manufacturer, &esmt_8c_spinand_manufacturer, &esmt_c8_spinand_manufacturer, &fmsh_spinand_manufacturer, @@ -1307,12 +1397,6 @@ static int spinand_manufacturer_init(struct spinand_device *spinand) return ret; } - if (spinand->configure_chip) { - ret = spinand->configure_chip(spinand); - if (ret) - return ret; - } - return 0; } @@ -1323,8 +1407,101 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand) return spinand->manufacturer->ops->cleanup(spinand); } +static bool spinand_op_is_odtr(const struct spi_mem_op *op) +{ + return op->cmd.dtr && op->cmd.buswidth == 8; +} + +static void spinand_init_ssdr_templates(struct spinand_device *spinand) +{ + struct spinand_mem_ops *tmpl = &spinand->ssdr_op_templates; + + tmpl->reset = (struct spi_mem_op)SPINAND_RESET_1S_0_0_OP; + tmpl->readid = (struct spi_mem_op)SPINAND_READID_1S_1S_1S_OP(0, 0, NULL, 0); + tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_1S_0_0_OP; + tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_1S_0_0_OP; + tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_1S_1S_1S_OP(0, NULL); + tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_1S_1S_1S_OP(0, NULL); + tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_1S_1S_0_OP(0); + tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_1S_1S_0_OP(0); + tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_1S_1S_0_OP(0); + spinand->op_templates = &spinand->ssdr_op_templates; + spinand->bus_iface = SSDR; +} + +static int spinand_support_vendor_ops(struct spinand_device *spinand, + const struct spinand_info *info, + enum spinand_bus_interface iface) +{ + int i; + + if (!info->vendor_ops) + return 0; + /* + * The vendor ops array is only used in order to verify this chip and all its memory + * operations are supported. If we see patterns emerging, we could ideally name these + * operations and define them at the SPI NAND core level instead. + * For now, this only serves as a sanity check. + */ + for (i = 0; i < info->vendor_ops->nops; i++) { + const struct spi_mem_op *op = &info->vendor_ops->ops[i]; + + if ((iface == SSDR && spinand_op_is_odtr(op)) || + (iface == ODTR && !spinand_op_is_odtr(op))) + continue; + + if (!spi_mem_supports_op(spinand->spimem, op)) + return -EOPNOTSUPP; + } + + return 0; +} + +static int spinand_init_odtr_instruction_set(struct spinand_device *spinand) +{ + struct spinand_mem_ops *tmpl = &spinand->odtr_op_templates; + + tmpl->reset = (struct spi_mem_op)SPINAND_RESET_8D_0_0_OP; + if (!spi_mem_supports_op(spinand->spimem, &tmpl->reset)) + return -EOPNOTSUPP; + + tmpl->readid = (struct spi_mem_op)SPINAND_READID_8D_8D_8D_OP(0, 0, NULL, 0); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->readid)) + return -EOPNOTSUPP; + + tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_8D_0_0_OP; + if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_en)) + return -EOPNOTSUPP; + + tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_8D_0_0_OP; + if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_dis)) + return -EOPNOTSUPP; + + tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_8D_8D_8D_OP(0, NULL); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->set_feature)) + return -EOPNOTSUPP; + + tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_8D_8D_8D_OP(0, NULL); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->get_feature)) + return -EOPNOTSUPP; + + tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_8D_8D_0_OP(0); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->blk_erase)) + return -EOPNOTSUPP; + + tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read)) + return -EOPNOTSUPP; + + tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_8D_8D_0_OP(0); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->prog_exec)) + return -EOPNOTSUPP; + + return 0; +} + static const struct spi_mem_op * -spinand_select_op_variant(struct spinand_device *spinand, +spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface, const struct spinand_op_variants *variants) { struct nand_device *nand = spinand_to_nand(spinand); @@ -1338,6 +1515,10 @@ spinand_select_op_variant(struct spinand_device *spinand, unsigned int nbytes; int ret; + if ((iface == SSDR && spinand_op_is_odtr(&op)) || + (iface == ODTR && !spinand_op_is_odtr(&op))) + continue; + nbytes = nanddev_per_page_oobsize(nand) + nanddev_page_size(nand); @@ -1389,6 +1570,7 @@ int spinand_match_and_init(struct spinand_device *spinand, u8 *id = spinand->id.data; struct nand_device *nand = spinand_to_nand(spinand); unsigned int i; + int ret; for (i = 0; i < table_size; i++) { const struct spinand_info *info = &table[i]; @@ -1413,28 +1595,59 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->read_retries = table[i].read_retries; spinand->set_read_retry = table[i].set_read_retry; - op = spinand_select_op_variant(spinand, + /* I/O variants selection with single-spi SDR commands */ + + op = spinand_select_op_variant(spinand, SSDR, info->op_variants.read_cache); if (!op) - return -ENOTSUPP; + return -EOPNOTSUPP; - spinand->op_templates.read_cache = op; + spinand->ssdr_op_templates.read_cache = op; - op = spinand_select_op_variant(spinand, + op = spinand_select_op_variant(spinand, SSDR, info->op_variants.write_cache); if (!op) - return -ENOTSUPP; + return -EOPNOTSUPP; + + spinand->ssdr_op_templates.write_cache = op; + + op = spinand_select_op_variant(spinand, SSDR, + info->op_variants.update_cache); + if (!op) + return -EOPNOTSUPP; - spinand->op_templates.write_cache = op; + spinand->ssdr_op_templates.update_cache = op; - op = spinand_select_op_variant(spinand, + ret = spinand_support_vendor_ops(spinand, info, SSDR); + if (ret) + return ret; + + /* I/O variants selection with octo-spi DDR commands (optional) */ + + ret = spinand_init_odtr_instruction_set(spinand); + if (ret) + return 0; + + ret = spinand_support_vendor_ops(spinand, info, ODTR); + if (ret) + return 0; + + op = spinand_select_op_variant(spinand, ODTR, + info->op_variants.read_cache); + spinand->odtr_op_templates.read_cache = op; + + op = spinand_select_op_variant(spinand, ODTR, + info->op_variants.write_cache); + spinand->odtr_op_templates.write_cache = op; + + op = spinand_select_op_variant(spinand, ODTR, info->op_variants.update_cache); - spinand->op_templates.update_cache = op; + spinand->odtr_op_templates.update_cache = op; return 0; } - return -ENOTSUPP; + return -EOPNOTSUPP; } static int spinand_detect(struct spinand_device *spinand) @@ -1470,6 +1683,56 @@ static int spinand_detect(struct spinand_device *spinand) return 0; } +static int spinand_configure_chip(struct spinand_device *spinand) +{ + bool odtr = false, quad_enable = false; + int ret; + + if (spinand->odtr_op_templates.read_cache && + spinand->odtr_op_templates.write_cache && + spinand->odtr_op_templates.update_cache) + odtr = true; + + if (odtr) { + if (!spinand->configure_chip) + goto try_ssdr; + + /* ODTR bus interface configuration happens here */ + ret = spinand->configure_chip(spinand, ODTR); + if (ret) { + spinand->odtr_op_templates.read_cache = NULL; + spinand->odtr_op_templates.write_cache = NULL; + spinand->odtr_op_templates.update_cache = NULL; + goto try_ssdr; + } + + spinand->op_templates = &spinand->odtr_op_templates; + spinand->bus_iface = ODTR; + + return 0; + } + +try_ssdr: + if (spinand->flags & SPINAND_HAS_QE_BIT) { + if (spinand->ssdr_op_templates.read_cache->data.buswidth == 4 || + spinand->ssdr_op_templates.write_cache->data.buswidth == 4 || + spinand->ssdr_op_templates.update_cache->data.buswidth == 4) + quad_enable = true; + } + + ret = spinand_init_quad_enable(spinand, quad_enable); + if (ret) + return ret; + + if (spinand->configure_chip) { + ret = spinand->configure_chip(spinand, SSDR); + if (ret) + return ret; + } + + return ret; +} + static int spinand_init_flash(struct spinand_device *spinand) { struct device *dev = &spinand->spimem->spi->dev; @@ -1480,10 +1743,6 @@ static int spinand_init_flash(struct spinand_device *spinand) if (ret) return ret; - ret = spinand_init_quad_enable(spinand); - if (ret) - return ret; - ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); if (ret) return ret; @@ -1496,19 +1755,25 @@ static int spinand_init_flash(struct spinand_device *spinand) return ret; } + ret = spinand_configure_chip(spinand); + if (ret) + goto manuf_cleanup; + /* After power up, all blocks are locked, so unlock them here. */ for (i = 0; i < nand->memorg.ntargets; i++) { ret = spinand_select_target(spinand, i); if (ret) - break; + goto manuf_cleanup; ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); if (ret) - break; + goto manuf_cleanup; } - if (ret) - spinand_manufacturer_cleanup(spinand); + return 0; + +manuf_cleanup: + spinand_manufacturer_cleanup(spinand); return ret; } @@ -1529,6 +1794,32 @@ static void spinand_mtd_resume(struct mtd_info *mtd) spinand_ecc_enable(spinand, false); } +static int spinand_mtd_suspend(struct mtd_info *mtd) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + /* + * Return to SSDR interface in the suspend path to make sure the + * reset operation is correctly processed upon resume. + * + * Note: Once back in SSDR mode, every operation but the page helpers + * (dirmap based I/O accessors) will work. Page accesses would require + * destroying and recreating the dirmaps twice to work, which would be + * impacting for no reason, as this is just a transitional state. + */ + if (spinand->bus_iface == ODTR) { + ret = spinand->configure_chip(spinand, SSDR); + if (ret) + return ret; + + spinand->op_templates = &spinand->ssdr_op_templates; + spinand->bus_iface = SSDR; + } + + return 0; +} + static int spinand_init(struct spinand_device *spinand) { struct device *dev = &spinand->spimem->spi->dev; @@ -1544,6 +1835,8 @@ static int spinand_init(struct spinand_device *spinand) if (!spinand->scratchbuf) return -ENOMEM; + spinand_init_ssdr_templates(spinand); + ret = spinand_detect(spinand); if (ret) goto err_free_bufs; @@ -1596,6 +1889,7 @@ static int spinand_init(struct spinand_device *spinand) mtd->_block_isreserved = spinand_mtd_block_isreserved; mtd->_erase = spinand_mtd_erase; mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks; + mtd->_suspend = spinand_mtd_suspend; mtd->_resume = spinand_mtd_resume; if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) { |
