Skip to content

Commit 76b7dc7

Browse files
committed
mtd: spinand: Add octal DTR support
Create a new bus interface named ODTR for "octal DTR", which matches the following pattern: 8D-8D-8D. Add octal DTR support for all the existing core operations. Add a second set of templates for this bus interface. Give the possibility for drivers to register their read, write and update cache variants as well as their vendor specific operations. Check the SPI controller driver supports all the octal DTR commands that we might need before switching to the ODTR bus interface. Make the switch by calling ->configure_chip() with the ODTR parameter. Fallback in case this step fails. If someone ever attempts to suspend a chip in octal DTR mode, there are changes that it will loose its configuration at resume. Prevent any problem by explicitly switching back to SSDR while suspending. Note: there is a limitation in the current approach, page I/Os are not available as the dirmaps will be created for the ODTR bus interface if that option is supported and not switched back to SSDR during suspend. Switching them is possible but would be costly and would not bring anything as right after resuming we will switch again to ODTR. In case this capability is used for debug, developpers should mind to destroy and recreate suitable direct mappings. Finally, as a side effect, we increase the buffer for reading IDs to 6. No device at this point returns 6 bytes, but we support 5 bytes IDs, which means in octal DTR mode we have no other choice than reading an even number of bytes, hence 6. Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
1 parent f636d92 commit 76b7dc7

2 files changed

Lines changed: 216 additions & 3 deletions

File tree

drivers/mtd/nand/spi/core.c

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ spinand_fill_set_feature_op(struct spinand_device *spinand, u64 reg, const void
5757
{
5858
struct spi_mem_op op = spinand->op_templates->set_feature;
5959

60+
if (op.cmd.dtr && op.cmd.buswidth == 8)
61+
reg |= reg << 8;
62+
6063
op.addr.val = reg;
6164
op.data.buf.out = valptr;
6265

@@ -68,6 +71,9 @@ spinand_fill_get_feature_op(struct spinand_device *spinand, u64 reg, void *valpt
6871
{
6972
struct spi_mem_op op = spinand->op_templates->get_feature;
7073

74+
if (op.cmd.dtr && op.cmd.buswidth == 8)
75+
reg |= reg << 8;
76+
7177
op.addr.val = reg;
7278
op.data.buf.in = valptr;
7379

@@ -1393,6 +1399,11 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
13931399
return spinand->manufacturer->ops->cleanup(spinand);
13941400
}
13951401

1402+
static bool spinand_op_is_odtr(const struct spi_mem_op *op)
1403+
{
1404+
return op->cmd.dtr && op->cmd.buswidth == 8;
1405+
}
1406+
13961407
static void spinand_init_ssdr_templates(struct spinand_device *spinand)
13971408
{
13981409
struct spinand_mem_ops *tmpl = &spinand->ssdr_op_templates;
@@ -1425,13 +1436,60 @@ static int spinand_support_vendor_ops(struct spinand_device *spinand,
14251436
for (i = 0; i < info->vendor_ops->nops; i++) {
14261437
const struct spi_mem_op *op = &info->vendor_ops->ops[i];
14271438

1439+
if ((iface == SSDR && spinand_op_is_odtr(op)) ||
1440+
(iface == ODTR && !spinand_op_is_odtr(op)))
1441+
continue;
1442+
14281443
if (!spi_mem_supports_op(spinand->spimem, op))
14291444
return -EOPNOTSUPP;
14301445
}
14311446

14321447
return 0;
14331448
}
14341449

1450+
static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
1451+
{
1452+
struct spinand_mem_ops *tmpl = &spinand->odtr_op_templates;
1453+
1454+
tmpl->reset = (struct spi_mem_op)SPINAND_RESET_8D_0_0_OP;
1455+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->reset))
1456+
return -EOPNOTSUPP;
1457+
1458+
tmpl->readid = (struct spi_mem_op)SPINAND_READID_8D_8D_8D_OP(0, 0, NULL, 0);
1459+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->readid))
1460+
return -EOPNOTSUPP;
1461+
1462+
tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_8D_0_0_OP;
1463+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_en))
1464+
return -EOPNOTSUPP;
1465+
1466+
tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_8D_0_0_OP;
1467+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_dis))
1468+
return -EOPNOTSUPP;
1469+
1470+
tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_8D_8D_8D_OP(0, NULL);
1471+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->set_feature))
1472+
return -EOPNOTSUPP;
1473+
1474+
tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_8D_8D_8D_OP(0, NULL);
1475+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->get_feature))
1476+
return -EOPNOTSUPP;
1477+
1478+
tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_8D_8D_0_OP(0);
1479+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->blk_erase))
1480+
return -EOPNOTSUPP;
1481+
1482+
tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0);
1483+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read))
1484+
return -EOPNOTSUPP;
1485+
1486+
tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_8D_8D_0_OP(0);
1487+
if (!spi_mem_supports_op(spinand->spimem, &tmpl->prog_exec))
1488+
return -EOPNOTSUPP;
1489+
1490+
return 0;
1491+
}
1492+
14351493
static const struct spi_mem_op *
14361494
spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface,
14371495
const struct spinand_op_variants *variants)
@@ -1447,6 +1505,10 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
14471505
unsigned int nbytes;
14481506
int ret;
14491507

1508+
if ((iface == SSDR && spinand_op_is_odtr(&op)) ||
1509+
(iface == ODTR && !spinand_op_is_odtr(&op)))
1510+
continue;
1511+
14501512
nbytes = nanddev_per_page_oobsize(nand) +
14511513
nanddev_page_size(nand);
14521514

@@ -1523,6 +1585,8 @@ int spinand_match_and_init(struct spinand_device *spinand,
15231585
spinand->read_retries = table[i].read_retries;
15241586
spinand->set_read_retry = table[i].set_read_retry;
15251587

1588+
/* I/O variants selection with single-spi SDR commands */
1589+
15261590
op = spinand_select_op_variant(spinand, SSDR,
15271591
info->op_variants.read_cache);
15281592
if (!op)
@@ -1548,6 +1612,28 @@ int spinand_match_and_init(struct spinand_device *spinand,
15481612
if (ret)
15491613
return ret;
15501614

1615+
/* I/O variants selection with octo-spi DDR commands (optional) */
1616+
1617+
ret = spinand_init_odtr_instruction_set(spinand);
1618+
if (ret)
1619+
return 0;
1620+
1621+
ret = spinand_support_vendor_ops(spinand, info, ODTR);
1622+
if (ret)
1623+
return 0;
1624+
1625+
op = spinand_select_op_variant(spinand, ODTR,
1626+
info->op_variants.read_cache);
1627+
spinand->odtr_op_templates.read_cache = op;
1628+
1629+
op = spinand_select_op_variant(spinand, ODTR,
1630+
info->op_variants.write_cache);
1631+
spinand->odtr_op_templates.write_cache = op;
1632+
1633+
op = spinand_select_op_variant(spinand, ODTR,
1634+
info->op_variants.update_cache);
1635+
spinand->odtr_op_templates.update_cache = op;
1636+
15511637
return 0;
15521638
}
15531639

@@ -1589,9 +1675,34 @@ static int spinand_detect(struct spinand_device *spinand)
15891675

15901676
static int spinand_configure_chip(struct spinand_device *spinand)
15911677
{
1592-
bool quad_enable = false;
1678+
bool odtr = false, quad_enable = false;
15931679
int ret;
15941680

1681+
if (spinand->odtr_op_templates.read_cache &&
1682+
spinand->odtr_op_templates.write_cache &&
1683+
spinand->odtr_op_templates.update_cache)
1684+
odtr = true;
1685+
1686+
if (odtr) {
1687+
if (!spinand->configure_chip)
1688+
goto try_ssdr;
1689+
1690+
/* ODTR bus interface configuration happens here */
1691+
ret = spinand->configure_chip(spinand, ODTR);
1692+
if (ret) {
1693+
spinand->odtr_op_templates.read_cache = NULL;
1694+
spinand->odtr_op_templates.write_cache = NULL;
1695+
spinand->odtr_op_templates.update_cache = NULL;
1696+
goto try_ssdr;
1697+
}
1698+
1699+
spinand->op_templates = &spinand->odtr_op_templates;
1700+
spinand->bus_iface = ODTR;
1701+
1702+
return 0;
1703+
}
1704+
1705+
try_ssdr:
15951706
if (spinand->flags & SPINAND_HAS_QE_BIT) {
15961707
if (spinand->ssdr_op_templates.read_cache->data.buswidth == 4 ||
15971708
spinand->ssdr_op_templates.write_cache->data.buswidth == 4 ||
@@ -1673,6 +1784,32 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
16731784
spinand_ecc_enable(spinand, false);
16741785
}
16751786

1787+
static int spinand_mtd_suspend(struct mtd_info *mtd)
1788+
{
1789+
struct spinand_device *spinand = mtd_to_spinand(mtd);
1790+
int ret;
1791+
1792+
/*
1793+
* Return to SSDR interface in the suspend path to make sure the
1794+
* reset operation is correctly processed upon resume.
1795+
*
1796+
* Note: Once back in SSDR mode, every operation but the page helpers
1797+
* (dirmap based I/O accessors) will work. Page accesses would require
1798+
* destroying and recreating the dirmaps twice to work, which would be
1799+
* impacting for no reason, as this is just a transitional state.
1800+
*/
1801+
if (spinand->bus_iface == ODTR) {
1802+
ret = spinand->configure_chip(spinand, SSDR);
1803+
if (ret)
1804+
return ret;
1805+
1806+
spinand->op_templates = &spinand->ssdr_op_templates;
1807+
spinand->bus_iface = SSDR;
1808+
}
1809+
1810+
return 0;
1811+
}
1812+
16761813
static int spinand_init(struct spinand_device *spinand)
16771814
{
16781815
struct device *dev = &spinand->spimem->spi->dev;
@@ -1742,6 +1879,7 @@ static int spinand_init(struct spinand_device *spinand)
17421879
mtd->_block_isreserved = spinand_mtd_block_isreserved;
17431880
mtd->_erase = spinand_mtd_erase;
17441881
mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
1882+
mtd->_suspend = spinand_mtd_suspend;
17451883
mtd->_resume = spinand_mtd_resume;
17461884

17471885
if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {

include/linux/mtd/spinand.h

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,77 @@
238238
SPI_MEM_OP_NO_DUMMY, \
239239
SPI_MEM_OP_DATA_OUT(len, buf, 8))
240240

241+
/**
242+
* Octal DDR SPI NAND flash operations
243+
*/
244+
245+
#define SPINAND_RESET_8D_0_0_OP \
246+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0xff, 8), \
247+
SPI_MEM_OP_NO_ADDR, \
248+
SPI_MEM_OP_NO_DUMMY, \
249+
SPI_MEM_OP_NO_DATA)
250+
251+
#define SPINAND_READID_8D_8D_8D_OP(naddr, ndummy, buf, len) \
252+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9f, 8), \
253+
SPI_MEM_DTR_OP_ADDR(naddr, 0, 8), \
254+
SPI_MEM_DTR_OP_DUMMY(ndummy, 8), \
255+
SPI_MEM_DTR_OP_DATA_IN(len, buf, 8))
256+
257+
#define SPINAND_WR_EN_8D_0_0_OP \
258+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x06, 8), \
259+
SPI_MEM_OP_NO_ADDR, \
260+
SPI_MEM_OP_NO_DUMMY, \
261+
SPI_MEM_OP_NO_DATA)
262+
263+
#define SPINAND_WR_DIS_8D_0_0_OP \
264+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x04, 8), \
265+
SPI_MEM_OP_NO_ADDR, \
266+
SPI_MEM_OP_NO_DUMMY, \
267+
SPI_MEM_OP_NO_DATA)
268+
269+
#define SPINAND_SET_FEATURE_8D_8D_8D_OP(reg, valptr) \
270+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x1f, 8), \
271+
SPI_MEM_DTR_OP_RPT_ADDR(reg, 8), \
272+
SPI_MEM_OP_NO_DUMMY, \
273+
SPI_MEM_DTR_OP_DATA_OUT(2, valptr, 8))
274+
275+
#define SPINAND_GET_FEATURE_8D_8D_8D_OP(reg, valptr) \
276+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x0f, 8), \
277+
SPI_MEM_DTR_OP_RPT_ADDR(reg, 8), \
278+
SPI_MEM_DTR_OP_DUMMY(14, 8), \
279+
SPI_MEM_DTR_OP_DATA_IN(2, valptr, 8))
280+
281+
#define SPINAND_BLK_ERASE_8D_8D_0_OP(addr) \
282+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0xd8, 8), \
283+
SPI_MEM_DTR_OP_ADDR(2, addr, 8), \
284+
SPI_MEM_OP_NO_DUMMY, \
285+
SPI_MEM_OP_NO_DATA)
286+
287+
#define SPINAND_PAGE_READ_8D_8D_0_OP(addr) \
288+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x13, 8), \
289+
SPI_MEM_DTR_OP_ADDR(2, addr, 8), \
290+
SPI_MEM_OP_NO_DUMMY, \
291+
SPI_MEM_OP_NO_DATA)
292+
293+
#define SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(addr, ndummy, buf, len, freq) \
294+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9d, 8), \
295+
SPI_MEM_DTR_OP_ADDR(2, addr, 8), \
296+
SPI_MEM_DTR_OP_DUMMY(ndummy, 8), \
297+
SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \
298+
SPI_MEM_OP_MAX_FREQ(freq))
299+
300+
#define SPINAND_PROG_EXEC_8D_8D_0_OP(addr) \
301+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x10, 8), \
302+
SPI_MEM_DTR_OP_ADDR(2, addr, 8), \
303+
SPI_MEM_OP_NO_DUMMY, \
304+
SPI_MEM_OP_NO_DATA)
305+
306+
#define SPINAND_PROG_LOAD_8D_8D_8D_OP(reset, addr, buf, len) \
307+
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD((reset ? 0xc2 : 0xc4), 8), \
308+
SPI_MEM_DTR_OP_ADDR(2, addr, 8), \
309+
SPI_MEM_OP_NO_DUMMY, \
310+
SPI_MEM_DTR_OP_DATA_OUT(len, buf, 8))
311+
241312
/* feature register */
242313
#define REG_BLOCK_LOCK 0xa0
243314
#define BL_ALL_UNLOCKED 0x00
@@ -261,7 +332,7 @@
261332
struct spinand_op;
262333
struct spinand_device;
263334

264-
#define SPINAND_MAX_ID_LEN 5
335+
#define SPINAND_MAX_ID_LEN 6
265336
/*
266337
* For erase, write and read operation, we got the following timings :
267338
* tBERS (erase) 1ms to 4ms
@@ -287,7 +358,7 @@ struct spinand_device;
287358

288359
/**
289360
* struct spinand_id - SPI NAND id structure
290-
* @data: buffer containing the id bytes. Currently 5 bytes large, but can
361+
* @data: buffer containing the id bytes. Currently 6 bytes large, but can
291362
* be extended if required
292363
* @len: ID length
293364
*/
@@ -485,9 +556,11 @@ struct spinand_user_otp {
485556
/**
486557
* enum spinand_bus_interface - SPI NAND bus interface types
487558
* @SSDR: Bus configuration supporting all 1S-XX-XX operations, including dual and quad
559+
* @ODTR: Bus configuration supporting only 8D-8D-8D operations
488560
*/
489561
enum spinand_bus_interface {
490562
SSDR,
563+
ODTR,
491564
};
492565

493566
/**
@@ -652,6 +725,7 @@ struct spinand_mem_ops {
652725
* @id: NAND ID as returned by READ_ID
653726
* @flags: NAND flags
654727
* @ssdr_op_templates: Templates for all single SDR SPI mem operations
728+
* @odtr_op_templates: Templates for all octal DTR SPI mem operations
655729
* @op_templates: Templates for all SPI mem operations
656730
* @bus_iface: Current bus interface
657731
* @select_target: select a specific target/die. Usually called before sending
@@ -688,6 +762,7 @@ struct spinand_device {
688762
u32 flags;
689763

690764
struct spinand_mem_ops ssdr_op_templates;
765+
struct spinand_mem_ops odtr_op_templates;
691766
struct spinand_mem_ops *op_templates;
692767
enum spinand_bus_interface bus_iface;
693768

0 commit comments

Comments
 (0)