Commit 535f30d9 authored by Miquel Raynal's avatar Miquel Raynal
Browse files

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: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
parent f1a91175
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -360,7 +360,7 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status)
		engine_conf->status = status;
}

static int spinand_write_enable_op(struct spinand_device *spinand)
int spinand_write_enable_op(struct spinand_device *spinand)
{
	struct spi_mem_op op = SPINAND_WR_EN_DIS_1S_0_0_OP(true);

+92 −3
Original line number Diff line number Diff line
@@ -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),
+1 −0
Original line number Diff line number Diff line
@@ -741,6 +741,7 @@ int spinand_match_and_init(struct spinand_device *spinand,
int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
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);
int spinand_write_enable_op(struct spinand_device *spinand);
int spinand_select_target(struct spinand_device *spinand, unsigned int target);

int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us,