diff options
Diffstat (limited to 'drivers/mtd/spi-nor/core.c')
-rw-r--r-- | drivers/mtd/spi-nor/core.c | 608 |
1 files changed, 463 insertions, 145 deletions
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 0369d98b2d12..20df44b753da 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -40,6 +40,81 @@ #define SPI_NOR_MAX_ADDR_WIDTH 4 +#define SPI_NOR_SRST_SLEEP_MIN 200 +#define SPI_NOR_SRST_SLEEP_MAX 400 + +/** + * spi_nor_get_cmd_ext() - Get the command opcode extension based on the + * extension type. + * @nor: pointer to a 'struct spi_nor' + * @op: pointer to the 'struct spi_mem_op' whose properties + * need to be initialized. + * + * Right now, only "repeat" and "invert" are supported. + * + * Return: The opcode extension. + */ +static u8 spi_nor_get_cmd_ext(const struct spi_nor *nor, + const struct spi_mem_op *op) +{ + switch (nor->cmd_ext_type) { + case SPI_NOR_EXT_INVERT: + return ~op->cmd.opcode; + + case SPI_NOR_EXT_REPEAT: + return op->cmd.opcode; + + default: + dev_err(nor->dev, "Unknown command extension type\n"); + return 0; + } +} + +/** + * spi_nor_spimem_setup_op() - Set up common properties of a spi-mem op. + * @nor: pointer to a 'struct spi_nor' + * @op: pointer to the 'struct spi_mem_op' whose properties + * need to be initialized. + * @proto: the protocol from which the properties need to be set. + */ +void spi_nor_spimem_setup_op(const struct spi_nor *nor, + struct spi_mem_op *op, + const enum spi_nor_protocol proto) +{ + u8 ext; + + op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(proto); + + if (op->addr.nbytes) + op->addr.buswidth = spi_nor_get_protocol_addr_nbits(proto); + + if (op->dummy.nbytes) + op->dummy.buswidth = spi_nor_get_protocol_addr_nbits(proto); + + if (op->data.nbytes) + op->data.buswidth = spi_nor_get_protocol_data_nbits(proto); + + if (spi_nor_protocol_is_dtr(proto)) { + /* + * SPIMEM supports mixed DTR modes, but right now we can only + * have all phases either DTR or STR. IOW, SPIMEM can have + * something like 4S-4D-4D, but SPI NOR can't. So, set all 4 + * phases to either DTR or STR. + */ + op->cmd.dtr = true; + op->addr.dtr = true; + op->dummy.dtr = true; + op->data.dtr = true; + + /* 2 bytes per clock cycle in DTR mode. */ + op->dummy.nbytes *= 2; + + ext = spi_nor_get_cmd_ext(nor, op); + op->cmd.opcode = (op->cmd.opcode << 8) | ext; + op->cmd.nbytes = 2; + } +} + /** * spi_nor_spimem_bounce() - check if a bounce buffer is needed for the data * transfer @@ -82,6 +157,32 @@ static int spi_nor_spimem_exec_op(struct spi_nor *nor, struct spi_mem_op *op) return spi_mem_exec_op(nor->spimem, op); } +static int spi_nor_controller_ops_read_reg(struct spi_nor *nor, u8 opcode, + u8 *buf, size_t len) +{ + if (spi_nor_protocol_is_dtr(nor->reg_proto)) + return -EOPNOTSUPP; + + return nor->controller_ops->read_reg(nor, opcode, buf, len); +} + +static int spi_nor_controller_ops_write_reg(struct spi_nor *nor, u8 opcode, + const u8 *buf, size_t len) +{ + if (spi_nor_protocol_is_dtr(nor->reg_proto)) + return -EOPNOTSUPP; + + return nor->controller_ops->write_reg(nor, opcode, buf, len); +} + +static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs) +{ + if (spi_nor_protocol_is_dtr(nor->write_proto)) + return -EOPNOTSUPP; + + return nor->controller_ops->erase(nor, offs); +} + /** * spi_nor_spimem_read_data() - read data from flash's memory region via * spi-mem @@ -96,22 +197,20 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from, size_t len, u8 *buf) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, from, 1), - SPI_MEM_OP_DUMMY(nor->read_dummy, 1), - SPI_MEM_OP_DATA_IN(len, buf, 1)); + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, from, 0), + SPI_MEM_OP_DUMMY(nor->read_dummy, 0), + SPI_MEM_OP_DATA_IN(len, buf, 0)); bool usebouncebuf; ssize_t nbytes; int error; - /* get transfer protocols. */ - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); - op.dummy.buswidth = op.addr.buswidth; - op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); + spi_nor_spimem_setup_op(nor, &op, nor->read_proto); /* convert the dummy cycles to the number of bytes */ op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; + if (spi_nor_protocol_is_dtr(nor->read_proto)) + op.dummy.nbytes *= 2; usebouncebuf = spi_nor_spimem_bounce(nor, &op); @@ -162,20 +261,18 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to, size_t len, const u8 *buf) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, to, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, to, 0), SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(len, buf, 1)); + SPI_MEM_OP_DATA_OUT(len, buf, 0)); ssize_t nbytes; int error; - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); - op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); - if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) op.addr.nbytes = 0; + spi_nor_spimem_setup_op(nor, &op, nor->write_proto); + if (spi_nor_spimem_bounce(nor, &op)) memcpy(nor->bouncebuf, buf, op.data.nbytes); @@ -222,15 +319,17 @@ int spi_nor_write_enable(struct spi_nor *nor) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WREN, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WREN, + NULL, 0); } if (ret) @@ -251,15 +350,17 @@ int spi_nor_write_disable(struct spi_nor *nor) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRDI, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRDI, + NULL, 0); } if (ret) @@ -272,25 +373,37 @@ int spi_nor_write_disable(struct spi_nor *nor) * spi_nor_read_sr() - Read the Status Register. * @nor: pointer to 'struct spi_nor'. * @sr: pointer to a DMA-able buffer where the value of the - * Status Register will be written. + * Status Register will be written. Should be at least 2 bytes. * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) +int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) { int ret; if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr, 1)); + SPI_MEM_OP_DATA_IN(1, sr, 0)); + + if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { + op.addr.nbytes = nor->params->rdsr_addr_nbytes; + op.dummy.nbytes = nor->params->rdsr_dummy; + /* + * We don't want to read only one byte in DTR mode. So, + * read 2 and then discard the second byte. + */ + op.data.nbytes = 2; + } + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR, - sr, 1); + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr, + 1); } if (ret) @@ -303,7 +416,8 @@ static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr) * spi_nor_read_fsr() - Read the Flag Status Register. * @nor: pointer to 'struct spi_nor' * @fsr: pointer to a DMA-able buffer where the value of the - * Flag Status Register will be written. + * Flag Status Register will be written. Should be at least 2 + * bytes. * * Return: 0 on success, -errno otherwise. */ @@ -313,15 +427,27 @@ static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDFSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, fsr, 1)); + SPI_MEM_OP_DATA_IN(1, fsr, 0)); + + if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) { + op.addr.nbytes = nor->params->rdsr_addr_nbytes; + op.dummy.nbytes = nor->params->rdsr_dummy; + /* + * We don't want to read only one byte in DTR mode. So, + * read 2 and then discard the second byte. + */ + op.data.nbytes = 2; + } + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDFSR, - fsr, 1); + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDFSR, fsr, + 1); } if (ret) @@ -345,14 +471,17 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, cr, 1)); + SPI_MEM_OP_DATA_IN(1, cr, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDCR, cr, 1); + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr, + 1); } if (ret) @@ -378,17 +507,19 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable) SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, - 1), + 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, - enable ? SPINOR_OP_EN4B : - SPINOR_OP_EX4B, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, + enable ? SPINOR_OP_EN4B : + SPINOR_OP_EX4B, + NULL, 0); } if (ret) @@ -414,15 +545,17 @@ static int spansion_set_4byte_addr_mode(struct spi_nor *nor, bool enable) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_BRWR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_BRWR, - nor->bouncebuf, 1); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_BRWR, + nor->bouncebuf, 1); } if (ret) @@ -446,15 +579,17 @@ int spi_nor_write_ear(struct spi_nor *nor, u8 ear) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREAR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1)); + SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WREAR, - nor->bouncebuf, 1); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WREAR, + nor->bouncebuf, 1); } if (ret) @@ -477,15 +612,17 @@ int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_XRDSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr, 1)); + SPI_MEM_OP_DATA_IN(1, sr, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_XRDSR, - sr, 1); + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_XRDSR, sr, + 1); } if (ret) @@ -522,15 +659,17 @@ static void spi_nor_clear_sr(struct spi_nor *nor) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLSR, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLSR, + NULL, 0); } if (ret) @@ -586,15 +725,17 @@ static void spi_nor_clear_fsr(struct spi_nor *nor) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CLFSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CLFSR, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_CLFSR, + NULL, 0); } if (ret) @@ -720,7 +861,7 @@ int spi_nor_wait_till_ready(struct spi_nor *nor) * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) +int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) { int ret; @@ -730,15 +871,17 @@ static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(len, sr, 1)); + SPI_MEM_OP_DATA_OUT(len, sr, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR, - sr, len); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr, + len); } if (ret) { @@ -906,7 +1049,7 @@ static int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr) * * Return: 0 on success, -errno otherwise. */ -static int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) +int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1) { if (nor->flags & SNOR_F_HAS_16BIT_SR) return spi_nor_write_16bit_sr_and_check(nor, sr1); @@ -932,15 +1075,17 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(1, sr2, 1)); + SPI_MEM_OP_DATA_OUT(1, sr2, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_WRSR2, - sr2, 1); + ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2, + sr2, 1); } if (ret) { @@ -966,15 +1111,17 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_IN(1, sr2, 1)); + SPI_MEM_OP_DATA_IN(1, sr2, 0)); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR2, - sr2, 1); + ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2, + 1); } if (ret) @@ -997,15 +1144,18 @@ static int spi_nor_erase_chip(struct spi_nor *nor) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->write_proto); + ret = spi_mem_exec_op(nor->spimem, &op); } else { - ret = nor->controller_ops->write_reg(nor, SPINOR_OP_CHIP_ERASE, - NULL, 0); + ret = spi_nor_controller_ops_write_reg(nor, + SPINOR_OP_CHIP_ERASE, + NULL, 0); } if (ret) @@ -1139,14 +1289,16 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) if (nor->spimem) { struct spi_mem_op op = - SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, addr, 1), + SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, addr, 0), SPI_MEM_OP_NO_DUMMY, SPI_MEM_OP_NO_DATA); + spi_nor_spimem_setup_op(nor, &op, nor->write_proto); + return spi_mem_exec_op(nor->spimem, &op); } else if (nor->controller_ops->erase) { - return nor->controller_ops->erase(nor, addr); + return spi_nor_controller_ops_erase(nor, addr); } /* @@ -1158,8 +1310,8 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) addr >>= 8; } - return nor->controller_ops->write_reg(nor, nor->erase_opcode, - nor->bouncebuf, nor->addr_width); + return spi_nor_controller_ops_write_reg(nor, nor->erase_opcode, + nor->bouncebuf, nor->addr_width); } /** @@ -1447,7 +1599,7 @@ destroy_erase_cmd_list: /* * Erase an address range on the nor chip. The address range may extend - * one or more erase sectors. Return an error is there is a problem erasing. + * one or more erase sectors. Return an error if there is a problem erasing. */ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) { @@ -2204,7 +2356,7 @@ static int spi_nor_check(struct spi_nor *nor) return 0; } -static void +void spi_nor_set_read_settings(struct spi_nor_read_command *read, u8 num_mode_clocks, u8 num_wait_states, @@ -2253,6 +2405,7 @@ int spi_nor_hwcaps_read2cmd(u32 hwcaps) { SNOR_HWCAPS_READ_1_8_8, SNOR_CMD_READ_1_8_8 }, { SNOR_HWCAPS_READ_8_8_8, SNOR_CMD_READ_8_8_8 }, { SNOR_HWCAPS_READ_1_8_8_DTR, SNOR_CMD_READ_1_8_8_DTR }, + { SNOR_HWCAPS_READ_8_8_8_DTR, SNOR_CMD_READ_8_8_8_DTR }, }; return spi_nor_hwcaps2cmd(hwcaps, hwcaps_read2cmd, @@ -2269,6 +2422,7 @@ static int spi_nor_hwcaps_pp2cmd(u32 hwcaps) { SNOR_HWCAPS_PP_1_1_8, SNOR_CMD_PP_1_1_8 }, { SNOR_HWCAPS_PP_1_8_8, SNOR_CMD_PP_1_8_8 }, { SNOR_HWCAPS_PP_8_8_8, SNOR_CMD_PP_8_8_8 }, + { SNOR_HWCAPS_PP_8_8_8_DTR, SNOR_CMD_PP_8_8_8_DTR }, }; return spi_nor_hwcaps2cmd(hwcaps, hwcaps_pp2cmd, @@ -2281,7 +2435,7 @@ static int spi_nor_hwcaps_pp2cmd(u32 hwcaps) *@nor: pointer to a 'struct spi_nor' *@op: pointer to op template to be checked * - * Returns 0 if operation is supported, -ENOTSUPP otherwise. + * Returns 0 if operation is supported, -EOPNOTSUPP otherwise. */ static int spi_nor_spimem_check_op(struct spi_nor *nor, struct spi_mem_op *op) @@ -2295,12 +2449,12 @@ static int spi_nor_spimem_check_op(struct spi_nor *nor, op->addr.nbytes = 4; if (!spi_mem_supports_op(nor->spimem, op)) { if (nor->mtd.size > SZ_16M) - return -ENOTSUPP; + return -EOPNOTSUPP; /* If flash size <= 16MB, 3 address bytes are sufficient */ op->addr.nbytes = 3; if (!spi_mem_supports_op(nor->spimem, op)) - return -ENOTSUPP; + return -EOPNOTSUPP; } return 0; @@ -2312,22 +2466,22 @@ static int spi_nor_spimem_check_op(struct spi_nor *nor, *@nor: pointer to a 'struct spi_nor' *@read: pointer to op template to be checked * - * Returns 0 if operation is supported, -ENOTSUPP otherwise. + * Returns 0 if operation is supported, -EOPNOTSUPP otherwise. */ static int spi_nor_spimem_check_readop(struct spi_nor *nor, const struct spi_nor_read_command *read) { - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1), - SPI_MEM_OP_ADDR(3, 0, 1), - SPI_MEM_OP_DUMMY(0, 1), - SPI_MEM_OP_DATA_IN(0, NULL, 1)); + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 0), + SPI_MEM_OP_ADDR(3, 0, 0), + SPI_MEM_OP_DUMMY(1, 0), + SPI_MEM_OP_DATA_IN(1, NULL, 0)); - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(read->proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(read->proto); - op.data.buswidth = spi_nor_get_protocol_data_nbits(read->proto); - op.dummy.buswidth = op.addr.buswidth; - op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) * - op.dummy.buswidth / 8; + spi_nor_spimem_setup_op(nor, &op, read->proto); + + /* convert the dummy cycles to the number of bytes */ + op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8; + if (spi_nor_protocol_is_dtr(nor->read_proto)) + op.dummy.nbytes *= 2; return spi_nor_spimem_check_op(nor, &op); } @@ -2338,19 +2492,17 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor, *@nor: pointer to a 'struct spi_nor' *@pp: pointer to op template to be checked * - * Returns 0 if operation is supported, -ENOTSUPP otherwise. + * Returns 0 if operation is supported, -EOPNOTSUPP otherwise. */ static int spi_nor_spimem_check_pp(struct spi_nor *nor, const struct spi_nor_pp_command *pp) { - struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1), - SPI_MEM_OP_ADDR(3, 0, 1), + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 0), + SPI_MEM_OP_ADDR(3, 0, 0), SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(0, NULL, 1)); + SPI_MEM_OP_DATA_OUT(1, NULL, 0)); - op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(pp->proto); - op.addr.buswidth = spi_nor_get_protocol_addr_nbits(pp->proto); - op.data.buswidth = spi_nor_get_protocol_data_nbits(pp->proto); + spi_nor_spimem_setup_op(nor, &op, pp->proto); return spi_nor_spimem_check_op(nor, &op); } @@ -2368,12 +2520,16 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps) struct spi_nor_flash_parameter *params = nor->params; unsigned int cap; - /* DTR modes are not supported yet, mask them all. */ - *hwcaps &= ~SNOR_HWCAPS_DTR; - /* X-X-X modes are not supported yet, mask them all. */ *hwcaps &= ~SNOR_HWCAPS_X_X_X; + /* + * If the reset line is broken, we do not want to enter a stateful + * mode. + */ + if (nor->flags & SNOR_F_BROKEN_RESET) + *hwcaps &= ~(SNOR_HWCAPS_X_X_X | SNOR_HWCAPS_X_X_X_DTR); + for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) { int rdidx, ppidx; @@ -2537,7 +2693,7 @@ spi_nor_select_uniform_erase(struct spi_nor_erase_map *map, } /* - * Otherwise, the current erase size is still a valid canditate. + * Otherwise, the current erase size is still a valid candidate. * Select the biggest valid candidate. */ if (!erase && tested_erase->size) @@ -2628,7 +2784,7 @@ static int spi_nor_default_setup(struct spi_nor *nor, * controller directly implements the spi_nor interface. * Yet another reason to switch to spi-mem. */ - ignored_mask = SNOR_HWCAPS_X_X_X; + ignored_mask = SNOR_HWCAPS_X_X_X | SNOR_HWCAPS_X_X_X_DTR; if (shared_mask & ignored_mask) { dev_dbg(nor->dev, "SPI n-n-n protocols are not supported.\n"); @@ -2701,11 +2857,10 @@ static void spi_nor_sfdp_init_params(struct spi_nor *nor) memcpy(&sfdp_params, nor->params, sizeof(sfdp_params)); - if (spi_nor_parse_sfdp(nor, &sfdp_params)) { + if (spi_nor_parse_sfdp(nor, nor->params)) { + memcpy(nor->params, &sfdp_params, sizeof(*nor->params)); nor->addr_width = 0; nor->flags &= ~SNOR_F_4B_OPCODES; - } else { - memcpy(nor->params, &sfdp_params, sizeof(*nor->params)); } } @@ -2730,6 +2885,7 @@ static void spi_nor_info_init_params(struct spi_nor *nor) nor->flags |= SNOR_F_HAS_16BIT_SR; /* Set SPI NOR sizes. */ + params->writesize = 1; params->size = (u64)info->sector_size * info->n_sectors; params->page_size = info->page_size; @@ -2774,11 +2930,28 @@ static void spi_nor_info_init_params(struct spi_nor *nor) SNOR_PROTO_1_1_8); } + if (info->flags & SPI_NOR_OCTAL_DTR_READ) { + params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR; + spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_8_8_8_DTR], + 0, 20, SPINOR_OP_READ_FAST, + SNOR_PROTO_8_8_8_DTR); + } + /* Page Program settings. */ params->hwcaps.mask |= SNOR_HWCAPS_PP; spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP], SPINOR_OP_PP, SNOR_PROTO_1_1_1); + if (info->flags & SPI_NOR_OCTAL_DTR_PP) { + params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR; + /* + * Since xSPI Page Program opcode is backward compatible with + * Legacy SPI, use Legacy SPI opcode there as well. + */ + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP_8_8_8_DTR], + SPINOR_OP_PP, SNOR_PROTO_8_8_8_DTR); + } + /* * Sector Erase settings. Sort Erase Types in ascending order, with the * smallest erase size starting at BIT(0). @@ -2886,7 +3059,8 @@ static int spi_nor_init_params(struct spi_nor *nor) spi_nor_manufacturer_init_params(nor); - if ((nor->info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)) && + if ((nor->info->flags & (SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_OCTAL_READ | SPI_NOR_OCTAL_DTR_READ)) && !(nor->info->flags & SPI_NOR_SKIP_SFDP)) spi_nor_sfdp_init_params(nor); @@ -2897,6 +3071,38 @@ static int spi_nor_init_params(struct spi_nor *nor) return 0; } +/** spi_nor_octal_dtr_enable() - enable Octal DTR I/O if needed + * @nor: pointer to a 'struct spi_nor' + * @enable: whether to enable or disable Octal DTR + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_octal_dtr_enable(struct spi_nor *nor, bool enable) +{ + int ret; + + if (!nor->params->octal_dtr_enable) + return 0; + + if (!(nor->read_proto == SNOR_PROTO_8_8_8_DTR && + nor->write_proto == SNOR_PROTO_8_8_8_DTR)) + return 0; + + if (!(nor->flags & SNOR_F_IO_MODE_EN_VOLATILE)) + return 0; + + ret = nor->params->octal_dtr_enable(nor, enable); + if (ret) + return ret; + + if (enable) + nor->reg_proto = SNOR_PROTO_8_8_8_DTR; + else + nor->reg_proto = SNOR_PROTO_1_1_1; + + return 0; +} + /** * spi_nor_quad_enable() - enable Quad I/O if needed. * @nor: pointer to a 'struct spi_nor' @@ -2916,39 +3122,65 @@ static int spi_nor_quad_enable(struct spi_nor *nor) } /** - * spi_nor_unlock_all() - Unlocks the entire flash memory array. + * spi_nor_try_unlock_all() - Tries to unlock the entire flash memory array. * @nor: pointer to a 'struct spi_nor'. * * Some SPI NOR flashes are write protected by default after a power-on reset * cycle, in order to avoid inadvertent writes during power-up. Backward * compatibility imposes to unlock the entire flash memory array at power-up * by default. + * + * Unprotecting the entire flash array will fail for boards which are hardware + * write-protected. Thus any errors are ignored. */ -static int spi_nor_unlock_all(struct spi_nor *nor) +static void spi_nor_try_unlock_all(struct spi_nor *nor) { - if (nor->flags & SNOR_F_HAS_LOCK) - return spi_nor_unlock(&nor->mtd, 0, nor->params->size); + int ret; - return 0; + if (!(nor->flags & SNOR_F_HAS_LOCK)) + return; + + dev_dbg(nor->dev, "Unprotecting entire flash array\n"); + + ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size); + if (ret) + dev_dbg(nor->dev, "Failed to unlock the entire flash memory array\n"); } static int spi_nor_init(struct spi_nor *nor) { int err; - err = spi_nor_quad_enable(nor); + err = spi_nor_octal_dtr_enable(nor, true); if (err) { - dev_dbg(nor->dev, "quad mode not supported\n"); + dev_dbg(nor->dev, "octal mode not supported\n"); return err; } - err = spi_nor_unlock_all(nor); + err = spi_nor_quad_enable(nor); if (err) { - dev_dbg(nor->dev, "Failed to unlock the entire flash memory array\n"); + dev_dbg(nor->dev, "quad mode not supported\n"); return err; } - if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES)) { + /* + * Some SPI NOR flashes are write protected by default after a power-on + * reset cycle, in order to avoid inadvertent writes during power-up. + * Backward compatibility imposes to unlock the entire flash memory + * array at power-up by default. Depending on the kernel configuration + * (1) do nothing, (2) always unlock the entire flash array or (3) + * unlock the entire flash array only when the software write + * protection bits are volatile. The latter is indicated by + * SNOR_F_SWP_IS_VOLATILE. + */ + if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) || + (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) && + nor->flags & SNOR_F_SWP_IS_VOLATILE)) + spi_nor_try_unlock_all(nor); + + if (nor->addr_width == 4 && + nor->read_proto != SNOR_PROTO_8_8_8_DTR && + !(nor->flags & SNOR_F_4B_OPCODES)) { /* * If the RESET# pin isn't hooked up properly, or the system * otherwise doesn't perform a reset command in the boot @@ -2964,6 +3196,59 @@ static int spi_nor_init(struct spi_nor *nor) return 0; } +static void spi_nor_soft_reset(struct spi_nor *nor) +{ + struct spi_mem_op op; + int ret; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 0), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DATA); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + if (ret) { + dev_warn(nor->dev, "Software reset failed: %d\n", ret); + return; + } + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 0), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DATA); + + spi_nor_spimem_setup_op(nor, &op, nor->reg_proto); + + ret = spi_mem_exec_op(nor->spimem, &op); + if (ret) { + dev_warn(nor->dev, "Software reset failed: %d\n", ret); + return; + } + + /* + * Software Reset is not instant, and the delay varies from flash to + * flash. Looking at a few flashes, most range somewhere below 100 + * microseconds. So, sleep for a range of 200-400 us. + */ + usleep_range(SPI_NOR_SRST_SLEEP_MIN, SPI_NOR_SRST_SLEEP_MAX); +} + +/* mtd suspend handler */ +static int spi_nor_suspend(struct mtd_info *mtd) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + int ret; + + /* Disable octal DTR mode if we enabled it. */ + ret = spi_nor_octal_dtr_enable(nor, false); + if (ret) + dev_err(nor->dev, "suspend() failed\n"); + + return ret; +} + /* mtd resume handler */ static void spi_nor_resume(struct mtd_info *mtd) { @@ -2983,6 +3268,9 @@ void spi_nor_restore(struct spi_nor *nor) if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES) && nor->flags & SNOR_F_BROKEN_RESET) nor->params->set_4byte_addr_mode(nor, false); + + if (nor->flags & SNOR_F_SOFT_RESET) + spi_nor_soft_reset(nor); } EXPORT_SYMBOL_GPL(spi_nor_restore); @@ -3007,15 +3295,31 @@ static int spi_nor_set_addr_width(struct spi_nor *nor) { if (nor->addr_width) { /* already configured from SFDP */ + } else if (nor->read_proto == SNOR_PROTO_8_8_8_DTR) { + /* + * In 8D-8D-8D mode, one byte takes half a cycle to transfer. So + * in this protocol an odd address width cannot be used because + * then the address phase would only span a cycle and a half. + * Half a cycle would be left over. We would then have to start + * the dummy phase in the middle of a cycle and so too the data + * phase, and we will end the transaction with half a cycle left + * over. + * + * Force all 8D-8D-8D flashes to use an address width of 4 to + * avoid this situation. + */ + nor->addr_width = 4; } else if (nor->info->addr_width) { nor->addr_width = nor->info->addr_width; - } else if (nor->mtd.size > 0x1000000) { - /* enable 4-byte addressing if the device exceeds 16MiB */ - nor->addr_width = 4; } else { nor->addr_width = 3; } + if (nor->addr_width == 3 && nor->mtd.size > 0x1000000) { + /* enable 4-byte addressing if the device exceeds 16MiB */ + nor->addr_width = 4; + } + if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) { dev_dbg(nor->dev, "address width is too large: %u\n", nor->addr_width); @@ -3145,11 +3449,12 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, mtd->name = dev_name(dev); mtd->priv = nor; mtd->type = MTD_NORFLASH; - mtd->writesize = 1; + mtd->writesize = nor->params->writesize; mtd->flags = MTD_CAP_NORFLASH; mtd->size = nor->params->size; mtd->_erase = spi_nor_erase; mtd->_read = spi_nor_read; + mtd->_suspend = spi_nor_suspend; mtd->_resume = spi_nor_resume; if (nor->params->locking_ops) { @@ -3170,6 +3475,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, nor->flags |= SNOR_F_NO_OP_CHIP_ERASE; if (info->flags & USE_CLSR) nor->flags |= SNOR_F_USE_CLSR; + if (info->flags & SPI_NOR_SWP_IS_VOLATILE) + nor->flags |= SNOR_F_SWP_IS_VOLATILE; if (info->flags & SPI_NOR_4BIT_BP) { nor->flags |= SNOR_F_HAS_4BIT_BP; @@ -3200,6 +3507,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, if (info->flags & SPI_NOR_4B_OPCODES) nor->flags |= SNOR_F_4B_OPCODES; + if (info->flags & SPI_NOR_IO_MODE_EN_VOLATILE) + nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE; + ret = spi_nor_set_addr_width(nor); if (ret) return ret; @@ -3235,23 +3545,28 @@ EXPORT_SYMBOL_GPL(spi_nor_scan); static int spi_nor_create_read_dirmap(struct spi_nor *nor) { struct spi_mem_dirmap_info info = { - .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, 0, 1), - SPI_MEM_OP_DUMMY(nor->read_dummy, 1), - SPI_MEM_OP_DATA_IN(0, NULL, 1)), + .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 0), + SPI_MEM_OP_DUMMY(nor->read_dummy, 0), + SPI_MEM_OP_DATA_IN(0, NULL, 0)), .offset = 0, .length = nor->mtd.size, }; struct spi_mem_op *op = &info.op_tmpl; - /* get transfer protocols. */ - op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto); - op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto); - op->dummy.buswidth = op->addr.buswidth; - op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); + spi_nor_spimem_setup_op(nor, op, nor->read_proto); /* convert the dummy cycles to the number of bytes */ op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8; + if (spi_nor_protocol_is_dtr(nor->read_proto)) + op->dummy.nbytes *= 2; + + /* + * Since spi_nor_spimem_setup_op() only sets buswidth when the number + * of data bytes is non-zero, the data buswidth won't be set here. So, + * do it explicitly. + */ + op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto); nor->dirmap.rdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem, &info); @@ -3261,24 +3576,27 @@ static int spi_nor_create_read_dirmap(struct spi_nor *nor) static int spi_nor_create_write_dirmap(struct spi_nor *nor) { struct spi_mem_dirmap_info info = { - .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1), - SPI_MEM_OP_ADDR(nor->addr_width, 0, 1), + .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0), + SPI_MEM_OP_ADDR(nor->addr_width, 0, 0), SPI_MEM_OP_NO_DUMMY, - SPI_MEM_OP_DATA_OUT(0, NULL, 1)), + SPI_MEM_OP_DATA_OUT(0, NULL, 0)), .offset = 0, .length = nor->mtd.size, }; struct spi_mem_op *op = &info.op_tmpl; - /* get transfer protocols. */ - op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto); - op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto); - op->dummy.buswidth = op->addr.buswidth; - op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); - if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second) op->addr.nbytes = 0; + spi_nor_spimem_setup_op(nor, op, nor->write_proto); + + /* + * Since spi_nor_spimem_setup_op() only sets buswidth when the number + * of data bytes is non-zero, the data buswidth won't be set here. So, + * do it explicitly. + */ + op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto); + nor->dirmap.wdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem, &info); return PTR_ERR_OR_ZERO(nor->dirmap.wdesc); |