diff options
Diffstat (limited to 'drivers/mtd/nand/spi/winbond.c')
-rw-r--r-- | drivers/mtd/nand/spi/winbond.c | 212 |
1 files changed, 209 insertions, 3 deletions
diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index 4f8b8de3a9..d8eb12c382 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -13,12 +13,26 @@ #include <linux/kernel.h> #endif #include <linux/bitops.h> +#include <linux/delay.h> #include <linux/mtd/spinand.h> #define SPINAND_MFR_WINBOND 0xEF +#define WINBOND_ID_LEN 3 #define WINBOND_CFG_BUF_READ BIT(3) +/* Octal DTR SPI mode (8D-8D-8D) with Data Strobe output*/ +#define WINBOND_VCR_IO_MODE_OCTAL_DTR 0xE7 +#define WINBOND_VCR_IO_MODE_SINGLE_STR 0xFF +#define WINBOND_VCR_IO_MODE_ADDR 0x00 + +/* Use 12 dummy clk cycles for using Octal DTR SPI at max 120MHZ */ +#define WINBOND_VCR_DUMMY_CLK_COUNT 12 +#define WINBOND_VCR_DUMMY_CLK_DEFAULT 0xFF +#define WINBOND_VCR_DUMMY_CLK_ADDR 0x01 + +#define WINBOND_POR_DELAY_US 1000 + static SPINAND_OP_VARIANTS(read_cache_variants_w25m02gv, SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), @@ -36,15 +50,36 @@ static SPINAND_OP_VARIANTS(update_cache_variants_w25m02gv, SPINAND_PROG_LOAD(false, 0, NULL, 0)); static SPINAND_OP_VARIANTS(read_cache_variants_w35n01jw, + SPINAND_PAGE_READ_FROM_CACHE_OCTALIO_DTR_OP(0, 24, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); static SPINAND_OP_VARIANTS(write_cache_variants_w35n01jw, + SPINAND_PROG_LOAD_OCTALIO_DTR(true, 0, NULL, 0), SPINAND_PROG_LOAD(true, 0, NULL, 0)); static SPINAND_OP_VARIANTS(update_cache_variants_w35n01jw, + SPINAND_PROG_LOAD_OCTALIO_DTR(false, 0, NULL, 0), SPINAND_PROG_LOAD(false, 0, NULL, 0)); +static SPINAND_CTRL_OPS_VARIANTS(ctrl_ops_variants_w35n01jw, + SPINAND_CTRL_OPS(SPINAND_8D, + SPINAND_RESET_OP_OCTAL_DTR, + SPINAND_GET_FEATURE_OP_OCTAL_DTR(0, NULL), + SPINAND_SET_FEATURE_OP_OCTAL_DTR(0, NULL), + SPINAND_WR_EN_DIS_OP_OCTAL_DTR(true), + SPINAND_BLK_ERASE_OP_OCTAL_DTR(0), + SPINAND_PAGE_READ_OP_OCTAL_DTR(0), + SPINAND_PROG_EXEC_OP_OCTAL_DTR(0)), + SPINAND_CTRL_OPS(SPINAND_1S, + SPINAND_RESET_OP, + SPINAND_GET_FEATURE_OP(0, NULL), + SPINAND_SET_FEATURE_OP(0, NULL), + SPINAND_WR_EN_DIS_OP(true), + SPINAND_BLK_ERASE_OP(0), + SPINAND_PAGE_READ_OP(0), + SPINAND_PROG_EXEC_OP(0))); + static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { @@ -141,9 +176,9 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants_w35n01jw, &write_cache_variants_w35n01jw, &update_cache_variants_w35n01jw), - 0, - SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL)), - + SPINAND_HAS_OCTAL_DTR_BIT | SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), + SPINAND_INFO_CTRL_OPS_VARIANTS(&ctrl_ops_variants_w35n01jw)), }; /** @@ -189,9 +224,180 @@ static int winbond_spinand_init(struct spinand_device *spinand) return 0; } +/** + * winbond_write_vcr_op() - write values onto the volatile configuration + * registers (VCR) + * @spinand: the spinand device + * @reg: the address of the particular reg in the VCR to be written on + * @val: the value to be written on the reg in the VCR + * + * Volatile configuration registers are a separate set of configuration + * registers, i.e. they differ from the status registers SR-1/2/3. A different + * SPI instruction is required to write to these registers. Any changes + * to the Volatile Configuration Register get transferred directly to + * the Internal Configuration Register and instantly reflect on the + * device operation. + */ +static int winbond_write_vcr_op(struct spinand_device *spinand, u8 reg, u8 val) +{ + int ret; + 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)); + + *spinand->scratchbuf = val; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + /* + * Write VCR operation doesn't set the busy bit in SR, so can't perform + * a status poll. Minimum time of 50ns is needed to complete the write. + * So, give thrice the minimum required delay. + */ + ndelay(150); + return 0; +} + +static int winbond_spinand_octal_dtr_enable(struct spinand_device *spinand) +{ + int ret; + struct spi_mem_op op; + + ret = winbond_write_vcr_op(spinand, WINBOND_VCR_DUMMY_CLK_ADDR, + WINBOND_VCR_DUMMY_CLK_COUNT); + if (ret) + return ret; + + ret = winbond_write_vcr_op(spinand, WINBOND_VCR_IO_MODE_ADDR, + WINBOND_VCR_IO_MODE_OCTAL_DTR); + if (ret) + return ret; + + /* Read flash ID to make sure the switch was successful. */ + op = (struct spi_mem_op) + SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x9f9f, 8, SPI_MEM_OP_DTR), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(16, 8, SPI_MEM_OP_DTR), + SPI_MEM_OP_DATA_IN(SPINAND_MAX_ID_LEN, + spinand->scratchbuf, 8, + SPI_MEM_OP_DTR)); + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + if (memcmp(spinand->scratchbuf, spinand->id.data + 1, WINBOND_ID_LEN)) + return -EINVAL; + + return 0; +} + +static int winbond_spinand_octal_dtr_disable(struct spinand_device *spinand) +{ + int ret; + struct spi_mem_op op = + SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x8181, 8, SPI_MEM_OP_DTR), + SPI_MEM_OP_ADDR(4, WINBOND_VCR_IO_MODE_ADDR, 8, + SPI_MEM_OP_DTR), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(2, spinand->scratchbuf, 8, + SPI_MEM_OP_DTR)); + + *spinand->scratchbuf = WINBOND_VCR_IO_MODE_SINGLE_STR; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + ret = winbond_write_vcr_op(spinand, WINBOND_VCR_DUMMY_CLK_ADDR, + WINBOND_VCR_DUMMY_CLK_DEFAULT); + if (ret) + return ret; + + /* Read flash ID to make sure the switch was successful. */ + op = (struct spi_mem_op) + SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(1, 1), + SPI_MEM_OP_DATA_IN(SPINAND_MAX_ID_LEN, + spinand->scratchbuf, 1)); + + ret = spi_mem_exec_op(spinand->slave, &op); + if (ret) + return ret; + + if (memcmp(spinand->scratchbuf, spinand->id.data + 1, WINBOND_ID_LEN)) + return -EINVAL; + + return 0; +} + +static int winbond_change_spi_protocol(struct spinand_device *spinand, + const enum spinand_protocol protocol) +{ + if (spinand->protocol == protocol) + return 0; + + switch (spinand->protocol) { + case SPINAND_1S: + if (protocol == SPINAND_8D) + return winbond_spinand_octal_dtr_enable(spinand); + break; + + case SPINAND_8D: + if (protocol == SPINAND_1S) + return winbond_spinand_octal_dtr_disable(spinand); + break; + + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static void winbond_spinand_cleanup(struct spinand_device *spinand) +{ + struct spi_mem_op op; + + /* Perform Power-On-Reset if the device is in Octal-DTR mode */ + if (spinand->protocol == SPINAND_8D) { + op = (struct spi_mem_op) + SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x6666, 8, SPI_MEM_OP_DTR), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + spi_mem_exec_op(spinand->slave, &op); + + op = (struct spi_mem_op) + SPI_MEM_OP(SPI_MEM_OP_EXT_CMD(2, 0x9999, 8, SPI_MEM_OP_DTR), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + spi_mem_exec_op(spinand->slave, &op); + + /* PoR can take max 500 us to complete, so sleep for 1000 us*/ + udelay(WINBOND_POR_DELAY_US); + } +} + static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { .detect = winbond_spinand_detect, .init = winbond_spinand_init, + .change_protocol = winbond_change_spi_protocol, + .cleanup = winbond_spinand_cleanup, }; const struct spinand_manufacturer winbond_spinand_manufacturer = { |