summaryrefslogtreecommitdiff
path: root/drivers/mtd/nand/spi/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand/spi/core.c')
-rw-r--r--drivers/mtd/nand/spi/core.c398
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)) {