mtd: spinand: winbond: Enable high-speed modes on w35n0xjw

w35n0xjw chips can run at up to 166MHz in octal mode, but this is only
possible after programming various VCR registers.

Implement the new ->configure_chip() hook for this purpose.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
Miquel Raynal
2025-06-18 14:14:24 +02:00
parent f1a91175fa
commit 535f30d997
3 changed files with 94 additions and 4 deletions

View File

@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#include <linux/units.h>
#include <linux/delay.h>
#define SPINAND_MFR_WINBOND 0xEF
@@ -21,14 +22,26 @@
#define W25N0XJW_SR4 0xD0
#define W25N0XJW_SR4_HS BIT(2)
#define W35N01JW_VCR_IO_MODE 0x00
#define W35N01JW_VCR_IO_MODE_SINGLE_SDR 0xFF
#define W35N01JW_VCR_IO_MODE_OCTAL_SDR 0xDF
#define W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS 0xE7
#define W35N01JW_VCR_IO_MODE_OCTAL_DDR 0xC7
#define W35N01JW_VCR_DUMMY_CLOCK_REG 0x01
/*
* "X2" in the core is equivalent to "dual output" in the datasheets,
* "X4" in the core is equivalent to "quad output" in the datasheets.
*/
static SPINAND_OP_VARIANTS(read_cache_octal_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 3, NULL, 0, 120 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 2, NULL, 0, 105 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 20, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 16, NULL, 0, 162 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 12, NULL, 0, 124 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 8, NULL, 0, 86 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 1, NULL, 0, 133 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
@@ -269,6 +282,79 @@ static int w25n0xjw_hs_cfg(struct spinand_device *spinand)
return 0;
}
static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(0x81, 1),
SPI_MEM_OP_ADDR(3, reg, 1),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, spinand->scratchbuf, 1));
int ret;
*spinand->scratchbuf = val;
ret = spinand_write_enable_op(spinand);
if (ret)
return ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
if (ret)
return ret;
/*
* Write VCR operation doesn't set the busy bit in SR, which means we
* cannot perform a status poll. Minimum time of 50ns is needed to
* complete the write.
*/
ndelay(50);
return 0;
}
static int w35n0xjw_vcr_cfg(struct spinand_device *spinand)
{
const struct spi_mem_op *op;
unsigned int dummy_cycles;
bool dtr, single;
u8 io_mode;
int ret;
op = spinand->op_templates.read_cache;
single = (op->cmd.buswidth == 1 && op->addr.buswidth == 1 && op->data.buswidth == 1);
dtr = (op->cmd.dtr || op->addr.dtr || op->data.dtr);
if (single && !dtr)
io_mode = W35N01JW_VCR_IO_MODE_SINGLE_SDR;
else if (!single && !dtr)
io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
else if (!single && dtr)
io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
else
return -EINVAL;
ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_IO_MODE, io_mode);
if (ret)
return ret;
dummy_cycles = ((op->dummy.nbytes * 8) / op->dummy.buswidth) / (op->dummy.dtr ? 2 : 1);
switch (dummy_cycles) {
case 8:
case 12:
case 16:
case 20:
case 24:
case 28:
break;
default:
return -EINVAL;
}
ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_DUMMY_CLOCK_REG, dummy_cycles);
if (ret)
return ret;
return 0;
}
static const struct spinand_info winbond_spinand_table[] = {
/* 512M-bit densities */
SPINAND_INFO("W25N512GW", /* 1.8V */
@@ -326,7 +412,8 @@ static const struct spinand_info winbond_spinand_table[] = {
&write_cache_octal_variants,
&update_cache_octal_variants),
0,
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
SPINAND_INFO("W35N02JW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22),
NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1),
@@ -335,7 +422,8 @@ static const struct spinand_info winbond_spinand_table[] = {
&write_cache_octal_variants,
&update_cache_octal_variants),
0,
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
SPINAND_INFO("W35N04JW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
@@ -344,7 +432,8 @@ static const struct spinand_info winbond_spinand_table[] = {
&write_cache_octal_variants,
&update_cache_octal_variants),
0,
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)),
SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)),
/* 2G-bit densities */
SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),